Enable customizing the save statistics time interval
[qBittorrent.git] / src / base / utils / os.cpp
blob8bfd7d07e6641fd468f9463cb304c8b1038e36cf
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2023 Mike Tzou (Chocobo1)
4 * Copyright (C) 2014 sledgehammer999 <hammered999@gmail.com>
5 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 * In addition, as a special exception, the copyright holders give permission to
22 * link this program with the OpenSSL project's "OpenSSL" library (or with
23 * modified versions of it that use the same license as the "OpenSSL" library),
24 * and distribute the linked executables. You must obey the GNU General Public
25 * License in all respects for all of the code used other than "OpenSSL". If you
26 * modify file(s), you may extend this exception to your version of the file(s),
27 * but you are not obligated to do so. If you do not wish to do so, delete this
28 * exception statement from your version.
31 #include "os.h"
33 #ifdef Q_OS_WIN
34 #include <windows.h>
35 #include <powrprof.h>
36 #include <shlobj.h>
37 #endif // Q_OS_WIN
39 #ifdef Q_OS_MACOS
40 #include <Carbon/Carbon.h>
41 #include <CoreFoundation/CoreFoundation.h>
42 #include <CoreServices/CoreServices.h>
43 #endif // Q_OS_MACOS
45 #ifdef QBT_USES_DBUS
46 #include <QDBusInterface>
47 #endif // QBT_USES_DBUS
49 #ifdef Q_OS_WIN
50 #include <QCoreApplication>
51 #endif // Q_OS_WIN
53 #include "base/global.h"
54 #include "base/types.h"
56 void Utils::OS::shutdownComputer([[maybe_unused]] const ShutdownDialogAction &action)
58 #if defined(Q_OS_WIN)
59 HANDLE hToken; // handle to process token
60 TOKEN_PRIVILEGES tkp; // pointer to token structure
61 if (!::OpenProcessToken(::GetCurrentProcess(), (TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY), &hToken))
62 return;
63 // Get the LUID for shutdown privilege.
64 ::LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid);
66 tkp.PrivilegeCount = 1; // one privilege to set
67 tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
69 // Get shutdown privilege for this process.
71 ::AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, 0);
73 // Cannot test the return value of AdjustTokenPrivileges.
75 if (::GetLastError() != ERROR_SUCCESS)
76 return;
78 if (action == ShutdownDialogAction::Suspend)
80 ::SetSuspendState(FALSE, FALSE, FALSE);
82 else if (action == ShutdownDialogAction::Hibernate)
84 ::SetSuspendState(TRUE, FALSE, FALSE);
86 else
88 std::wstring msg = QCoreApplication::translate("misc"
89 , "qBittorrent will shutdown the computer now because all downloads are complete.").toStdWString();
90 ::InitiateSystemShutdownW(nullptr, msg.data(), 10, TRUE, FALSE);
93 // Disable shutdown privilege.
94 tkp.Privileges[0].Attributes = 0;
95 ::AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, 0);
97 #elif defined(Q_OS_MACOS)
98 AEEventID EventToSend;
99 if (action != ShutdownDialogAction::Shutdown)
100 EventToSend = kAESleep;
101 else
102 EventToSend = kAEShutDown;
103 AEAddressDesc targetDesc;
104 const ProcessSerialNumber kPSNOfSystemProcess = {0, kSystemProcess};
105 AppleEvent eventReply = {typeNull, NULL};
106 AppleEvent appleEventToSend = {typeNull, NULL};
108 OSStatus error = ::AECreateDesc(typeProcessSerialNumber, &kPSNOfSystemProcess
109 , sizeof(kPSNOfSystemProcess), &targetDesc);
111 if (error != noErr)
112 return;
114 error = ::AECreateAppleEvent(kCoreEventClass, EventToSend, &targetDesc, kAutoGenerateReturnID
115 , kAnyTransactionID, &appleEventToSend);
117 AEDisposeDesc(&targetDesc);
118 if (error != noErr)
119 return;
121 error = ::AESend(&appleEventToSend, &eventReply, kAENoReply, kAENormalPriority, kAEDefaultTimeout
122 , NULL, NULL);
124 ::AEDisposeDesc(&appleEventToSend);
125 if (error != noErr)
126 return;
128 ::AEDisposeDesc(&eventReply);
130 #elif defined(QBT_USES_DBUS)
131 // Use dbus to power off / suspend the system
132 if (action != ShutdownDialogAction::Shutdown)
134 // Some recent systems use systemd's logind
135 QDBusInterface login1Iface(u"org.freedesktop.login1"_s, u"/org/freedesktop/login1"_s,
136 u"org.freedesktop.login1.Manager"_s, QDBusConnection::systemBus());
137 if (login1Iface.isValid())
139 if (action == ShutdownDialogAction::Suspend)
140 login1Iface.call(u"Suspend"_s, false);
141 else
142 login1Iface.call(u"Hibernate"_s, false);
143 return;
145 // Else, other recent systems use UPower
146 QDBusInterface upowerIface(u"org.freedesktop.UPower"_s, u"/org/freedesktop/UPower"_s,
147 u"org.freedesktop.UPower"_s, QDBusConnection::systemBus());
148 if (upowerIface.isValid())
150 if (action == ShutdownDialogAction::Suspend)
151 upowerIface.call(u"Suspend"_s);
152 else
153 upowerIface.call(u"Hibernate"_s);
154 return;
156 // HAL (older systems)
157 QDBusInterface halIface(u"org.freedesktop.Hal"_s, u"/org/freedesktop/Hal/devices/computer"_s,
158 u"org.freedesktop.Hal.Device.SystemPowerManagement"_s,
159 QDBusConnection::systemBus());
160 if (action == ShutdownDialogAction::Suspend)
161 halIface.call(u"Suspend"_s, 5);
162 else
163 halIface.call(u"Hibernate"_s);
165 else
167 // Some recent systems use systemd's logind
168 QDBusInterface login1Iface(u"org.freedesktop.login1"_s, u"/org/freedesktop/login1"_s,
169 u"org.freedesktop.login1.Manager"_s, QDBusConnection::systemBus());
170 if (login1Iface.isValid())
172 login1Iface.call(u"PowerOff"_s, false);
173 return;
175 // Else, other recent systems use ConsoleKit
176 QDBusInterface consolekitIface(u"org.freedesktop.ConsoleKit"_s, u"/org/freedesktop/ConsoleKit/Manager"_s,
177 u"org.freedesktop.ConsoleKit.Manager"_s, QDBusConnection::systemBus());
178 if (consolekitIface.isValid())
180 consolekitIface.call(u"Stop"_s);
181 return;
183 // HAL (older systems)
184 QDBusInterface halIface(u"org.freedesktop.Hal"_s, u"/org/freedesktop/Hal/devices/computer"_s,
185 u"org.freedesktop.Hal.Device.SystemPowerManagement"_s,
186 QDBusConnection::systemBus());
187 halIface.call(u"Shutdown"_s);
189 #endif
192 #ifdef Q_OS_MACOS
193 namespace
195 const CFStringRef torrentExtension = CFSTR("torrent");
196 const CFStringRef magnetUrlScheme = CFSTR("magnet");
199 bool Utils::OS::isTorrentFileAssocSet()
201 bool isSet = false;
202 const CFStringRef torrentId = ::UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, torrentExtension, NULL);
203 if (torrentId != NULL)
205 const CFStringRef defaultHandlerId = ::LSCopyDefaultRoleHandlerForContentType(torrentId, kLSRolesViewer);
206 if (defaultHandlerId != NULL)
208 const CFStringRef myBundleId = ::CFBundleGetIdentifier(::CFBundleGetMainBundle());
209 if (myBundleId != NULL)
210 isSet = ::CFStringCompare(myBundleId, defaultHandlerId, 0) == kCFCompareEqualTo;
211 ::CFRelease(defaultHandlerId);
213 ::CFRelease(torrentId);
215 return isSet;
218 void Utils::OS::setTorrentFileAssoc()
220 if (isTorrentFileAssocSet())
221 return;
223 const CFStringRef torrentId = ::UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, torrentExtension, NULL);
224 if (torrentId != NULL)
226 const CFStringRef myBundleId = ::CFBundleGetIdentifier(::CFBundleGetMainBundle());
227 if (myBundleId != NULL)
228 ::LSSetDefaultRoleHandlerForContentType(torrentId, kLSRolesViewer, myBundleId);
229 ::CFRelease(torrentId);
233 bool Utils::OS::isMagnetLinkAssocSet()
235 bool isSet = false;
236 const CFStringRef defaultHandlerId = ::LSCopyDefaultHandlerForURLScheme(magnetUrlScheme);
237 if (defaultHandlerId != NULL)
239 const CFStringRef myBundleId = ::CFBundleGetIdentifier(::CFBundleGetMainBundle());
240 if (myBundleId != NULL)
241 isSet = ::CFStringCompare(myBundleId, defaultHandlerId, 0) == kCFCompareEqualTo;
242 ::CFRelease(defaultHandlerId);
244 return isSet;
247 void Utils::OS::setMagnetLinkAssoc()
249 if (isMagnetLinkAssocSet())
250 return;
252 const CFStringRef myBundleId = ::CFBundleGetIdentifier(::CFBundleGetMainBundle());
253 if (myBundleId != NULL)
254 ::LSSetDefaultHandlerForURLScheme(magnetUrlScheme, myBundleId);
256 #endif // Q_OS_MACOS
258 #ifdef Q_OS_WIN
259 Path Utils::OS::windowsSystemPath()
261 static const Path path = []() -> Path
263 WCHAR systemPath[MAX_PATH] = {0};
264 ::GetSystemDirectoryW(systemPath, sizeof(systemPath) / sizeof(WCHAR));
265 return Path(QString::fromWCharArray(systemPath));
266 }();
267 return path;
269 #endif // Q_OS_WIN
271 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
272 bool Utils::OS::applyMarkOfTheWeb(const Path &file, const QString &url)
274 Q_ASSERT(url.isEmpty() || url.startsWith(u"http:") || url.startsWith(u"https:"));
276 #ifdef Q_OS_MACOS
277 // References:
278 // https://searchfox.org/mozilla-central/rev/ffdc4971dc18e1141cb2a90c2b0b776365650270/xpcom/io/CocoaFileUtils.mm#230
279 // https://github.com/transmission/transmission/blob/f62f7427edb1fd5c430e0ef6956bbaa4f03ae597/macosx/Torrent.mm#L1945-L1955
281 CFMutableDictionaryRef properties = ::CFDictionaryCreateMutable(kCFAllocatorDefault, 0
282 , &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
283 if (properties == NULL)
284 return false;
286 ::CFDictionarySetValue(properties, kLSQuarantineTypeKey, kLSQuarantineTypeOtherDownload);
287 if (!url.isEmpty())
288 ::CFDictionarySetValue(properties, kLSQuarantineDataURLKey, url.toCFString());
290 const CFStringRef fileString = file.toString().toCFString();
291 const CFURLRef fileURL = ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault
292 , fileString, kCFURLPOSIXPathStyle, false);
294 const Boolean success = ::CFURLSetResourcePropertyForKey(fileURL, kCFURLQuarantinePropertiesKey
295 , properties, NULL);
297 ::CFRelease(fileURL);
298 ::CFRelease(fileString);
299 ::CFRelease(properties);
301 return success;
302 #elif defined(Q_OS_WIN)
303 const QString zoneIDStream = file.toString() + u":Zone.Identifier";
304 HANDLE handle = ::CreateFileW(zoneIDStream.toStdWString().c_str(), GENERIC_WRITE
305 , (FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE)
306 , nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
307 if (handle == INVALID_HANDLE_VALUE)
308 return false;
310 // 5.6.1 Zone.Identifier Stream Name
311 // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/6e3f7352-d11c-4d76-8c39-2516a9df36e8
312 const QString hostURL = !url.isEmpty() ? url : u"about:internet"_s;
313 const QByteArray zoneID = QByteArrayLiteral("[ZoneTransfer]\r\nZoneId=3\r\n")
314 + u"HostUrl=%1\r\n"_s.arg(hostURL).toUtf8();
316 DWORD written = 0;
317 const BOOL writeResult = ::WriteFile(handle, zoneID.constData(), zoneID.size(), &written, nullptr);
318 ::CloseHandle(handle);
320 return writeResult && (written == zoneID.size());
321 #endif
323 #endif // Q_OS_MACOS || Q_OS_WIN