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.
40 #include <Carbon/Carbon.h>
41 #include <CoreFoundation/CoreFoundation.h>
42 #include <CoreServices/CoreServices.h>
46 #include <QDBusInterface>
47 #endif // QBT_USES_DBUS
50 #include <QCoreApplication>
53 #include "base/global.h"
54 #include "base/types.h"
56 void Utils::OS::shutdownComputer([[maybe_unused
]] const ShutdownDialogAction
&action
)
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
))
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
)
78 if (action
== ShutdownDialogAction::Suspend
)
80 ::SetSuspendState(FALSE
, FALSE
, FALSE
);
82 else if (action
== ShutdownDialogAction::Hibernate
)
84 ::SetSuspendState(TRUE
, FALSE
, FALSE
);
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
;
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
);
114 error
= ::AECreateAppleEvent(kCoreEventClass
, EventToSend
, &targetDesc
, kAutoGenerateReturnID
115 , kAnyTransactionID
, &appleEventToSend
);
117 AEDisposeDesc(&targetDesc
);
121 error
= ::AESend(&appleEventToSend
, &eventReply
, kAENoReply
, kAENormalPriority
, kAEDefaultTimeout
124 ::AEDisposeDesc(&appleEventToSend
);
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);
142 login1Iface
.call(u
"Hibernate"_s
, false);
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
);
153 upowerIface
.call(u
"Hibernate"_s
);
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);
163 halIface
.call(u
"Hibernate"_s
);
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);
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
);
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
);
195 const CFStringRef torrentExtension
= CFSTR("torrent");
196 const CFStringRef magnetUrlScheme
= CFSTR("magnet");
199 bool Utils::OS::isTorrentFileAssocSet()
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
);
218 void Utils::OS::setTorrentFileAssoc()
220 if (isTorrentFileAssocSet())
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()
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
);
247 void Utils::OS::setMagnetLinkAssoc()
249 if (isMagnetLinkAssocSet())
252 const CFStringRef myBundleId
= ::CFBundleGetIdentifier(::CFBundleGetMainBundle());
253 if (myBundleId
!= NULL
)
254 ::LSSetDefaultHandlerForURLScheme(magnetUrlScheme
, myBundleId
);
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
));
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:"));
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
)
286 ::CFDictionarySetValue(properties
, kLSQuarantineTypeKey
, kLSQuarantineTypeOtherDownload
);
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
297 ::CFRelease(fileURL
);
298 ::CFRelease(fileString
);
299 ::CFRelease(properties
);
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
)
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();
317 const BOOL writeResult
= ::WriteFile(handle
, zoneID
.constData(), zoneID
.size(), &written
, nullptr);
318 ::CloseHandle(handle
);
320 return writeResult
&& (written
== zoneID
.size());
323 #endif // Q_OS_MACOS || Q_OS_WIN