diff --git a/src/app/application.cpp b/src/app/application.cpp index 1b98f7b80..a715b60fc 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -85,6 +85,7 @@ #include "base/torrentfileswatcher.h" #include "base/utils/fs.h" #include "base/utils/misc.h" +#include "base/utils/os.h" #include "base/utils/string.h" #include "base/version.h" #include "applicationinstancemanager.h" @@ -99,10 +100,6 @@ #include "gui/uithememanager.h" #include "gui/utils.h" #include "gui/windowstate.h" - -#ifdef Q_OS_WIN -#include "base/utils/os.h" -#endif // Q_OS_WIN #endif // DISABLE_GUI #ifndef DISABLE_WEBUI @@ -1207,12 +1204,12 @@ void Application::setProcessMemoryPriority(const MemoryPriority priority) void Application::applyMemoryPriority() const { using SETPROCESSINFORMATION = BOOL (WINAPI *)(HANDLE, PROCESS_INFORMATION_CLASS, LPVOID, DWORD); - const auto setProcessInformation = Utils::Misc::loadWinAPI(u"Kernel32.dll"_s, "SetProcessInformation"); + const auto setProcessInformation = Utils::OS::loadWinAPI(u"Kernel32.dll"_s, "SetProcessInformation"); if (!setProcessInformation) // only available on Windows >= 8 return; using SETTHREADINFORMATION = BOOL (WINAPI *)(HANDLE, THREAD_INFORMATION_CLASS, LPVOID, DWORD); - const auto setThreadInformation = Utils::Misc::loadWinAPI(u"Kernel32.dll"_s, "SetThreadInformation"); + const auto setThreadInformation = Utils::OS::loadWinAPI(u"Kernel32.dll"_s, "SetThreadInformation"); if (!setThreadInformation) // only available on Windows >= 8 return; @@ -1352,7 +1349,7 @@ void Application::cleanup() if (m_shutdownAct != ShutdownDialogAction::Exit) { qDebug() << "Sending computer shutdown/suspend/hibernate signal..."; - Utils::Misc::shutdownComputer(m_shutdownAct); + Utils::OS::shutdownComputer(m_shutdownAct); } } diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index de42feaa8..58c7a92d2 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -73,7 +73,7 @@ #include "sessionimpl.h" #if defined(Q_OS_MACOS) || defined(Q_OS_WIN) -#include "base/utils/misc.h" +#include "base/utils/os.h" #endif // Q_OS_MACOS || Q_OS_WIN using namespace BitTorrent; @@ -2248,7 +2248,7 @@ void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p) if (isDownloading()) { const Path fullpath = actualStorageLocation() / actualPath; - Utils::Misc::applyMarkOfTheWeb(fullpath); + Utils::OS::applyMarkOfTheWeb(fullpath); } #endif // Q_OS_MACOS || Q_OS_WIN diff --git a/src/base/net/downloadhandlerimpl.cpp b/src/base/net/downloadhandlerimpl.cpp index d0a3e7731..32294bb5f 100644 --- a/src/base/net/downloadhandlerimpl.cpp +++ b/src/base/net/downloadhandlerimpl.cpp @@ -42,6 +42,10 @@ #include "base/utils/gzip.h" #endif +#if defined(Q_OS_MACOS) || defined(Q_OS_WIN) +#include "base/utils/os.h" +#endif // Q_OS_MACOS || Q_OS_WIN + const int MAX_REDIRECTIONS = 20; // the common value for web browsers namespace @@ -151,7 +155,7 @@ void Net::DownloadHandlerImpl::processFinishedDownload() m_result.filePath = result.value(); #if defined(Q_OS_MACOS) || defined(Q_OS_WIN) - Utils::Misc::applyMarkOfTheWeb(m_result.filePath, m_result.url); + Utils::OS::applyMarkOfTheWeb(m_result.filePath, m_result.url); #endif // Q_OS_MACOS || Q_OS_WIN } else @@ -167,7 +171,7 @@ void Net::DownloadHandlerImpl::processFinishedDownload() m_result.filePath = destinationPath; #if defined(Q_OS_MACOS) || defined(Q_OS_WIN) - Utils::Misc::applyMarkOfTheWeb(m_result.filePath, m_result.url); + Utils::OS::applyMarkOfTheWeb(m_result.filePath, m_result.url); #endif // Q_OS_MACOS || Q_OS_WIN } else diff --git a/src/base/utils/misc.cpp b/src/base/utils/misc.cpp index 292bd2417..68139d8f2 100644 --- a/src/base/utils/misc.cpp +++ b/src/base/utils/misc.cpp @@ -30,23 +30,6 @@ #include -#ifdef Q_OS_WIN -#include - -#include -#include -#include -#else -#include -#include -#endif - -#ifdef Q_OS_MACOS -#include -#include -#include -#endif - #include #include #include @@ -60,16 +43,11 @@ #include #include #include +#include #include -#include -#ifdef QBT_USES_DBUS -#include -#endif - -#include "base/types.h" +#include "base/path.h" #include "base/unicodestrings.h" -#include "base/utils/fs.h" #include "base/utils/string.h" namespace @@ -113,145 +91,6 @@ namespace } } -void Utils::Misc::shutdownComputer([[maybe_unused]] const ShutdownDialogAction &action) -{ -#if defined(Q_OS_WIN) - HANDLE hToken; // handle to process token - TOKEN_PRIVILEGES tkp; // pointer to token structure - if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) - return; - // Get the LUID for shutdown privilege. - LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, - &tkp.Privileges[0].Luid); - - tkp.PrivilegeCount = 1; // one privilege to set - tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - - // Get shutdown privilege for this process. - - AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, - (PTOKEN_PRIVILEGES) NULL, 0); - - // Cannot test the return value of AdjustTokenPrivileges. - - if (GetLastError() != ERROR_SUCCESS) - return; - - if (action == ShutdownDialogAction::Suspend) - { - ::SetSuspendState(FALSE, FALSE, FALSE); - } - else if (action == ShutdownDialogAction::Hibernate) - { - ::SetSuspendState(TRUE, FALSE, FALSE); - } - else - { - const QString msg = QCoreApplication::translate("misc", "qBittorrent will shutdown the computer now because all downloads are complete."); - auto msgWchar = std::make_unique(msg.length() + 1); - msg.toWCharArray(msgWchar.get()); - ::InitiateSystemShutdownW(nullptr, msgWchar.get(), 10, TRUE, FALSE); - } - - // Disable shutdown privilege. - tkp.Privileges[0].Attributes = 0; - AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES) NULL, 0); - -#elif defined(Q_OS_MACOS) - AEEventID EventToSend; - if (action != ShutdownDialogAction::Shutdown) - EventToSend = kAESleep; - else - EventToSend = kAEShutDown; - AEAddressDesc targetDesc; - const ProcessSerialNumber kPSNOfSystemProcess = {0, kSystemProcess}; - AppleEvent eventReply = {typeNull, NULL}; - AppleEvent appleEventToSend = {typeNull, NULL}; - - OSStatus error = AECreateDesc(typeProcessSerialNumber, &kPSNOfSystemProcess, - sizeof(kPSNOfSystemProcess), &targetDesc); - - if (error != noErr) - return; - - error = AECreateAppleEvent(kCoreEventClass, EventToSend, &targetDesc, - kAutoGenerateReturnID, kAnyTransactionID, &appleEventToSend); - - AEDisposeDesc(&targetDesc); - if (error != noErr) - return; - - error = AESend(&appleEventToSend, &eventReply, kAENoReply, - kAENormalPriority, kAEDefaultTimeout, NULL, NULL); - - AEDisposeDesc(&appleEventToSend); - if (error != noErr) - return; - - AEDisposeDesc(&eventReply); - -#elif defined(QBT_USES_DBUS) - // Use dbus to power off / suspend the system - if (action != ShutdownDialogAction::Shutdown) - { - // Some recent systems use systemd's logind - QDBusInterface login1Iface(u"org.freedesktop.login1"_s, u"/org/freedesktop/login1"_s, - u"org.freedesktop.login1.Manager"_s, QDBusConnection::systemBus()); - if (login1Iface.isValid()) - { - if (action == ShutdownDialogAction::Suspend) - login1Iface.call(u"Suspend"_s, false); - else - login1Iface.call(u"Hibernate"_s, false); - return; - } - // Else, other recent systems use UPower - QDBusInterface upowerIface(u"org.freedesktop.UPower"_s, u"/org/freedesktop/UPower"_s, - u"org.freedesktop.UPower"_s, QDBusConnection::systemBus()); - if (upowerIface.isValid()) - { - if (action == ShutdownDialogAction::Suspend) - upowerIface.call(u"Suspend"_s); - else - upowerIface.call(u"Hibernate"_s); - return; - } - // HAL (older systems) - QDBusInterface halIface(u"org.freedesktop.Hal"_s, u"/org/freedesktop/Hal/devices/computer"_s, - u"org.freedesktop.Hal.Device.SystemPowerManagement"_s, - QDBusConnection::systemBus()); - if (action == ShutdownDialogAction::Suspend) - halIface.call(u"Suspend"_s, 5); - else - halIface.call(u"Hibernate"_s); - } - else - { - // Some recent systems use systemd's logind - QDBusInterface login1Iface(u"org.freedesktop.login1"_s, u"/org/freedesktop/login1"_s, - u"org.freedesktop.login1.Manager"_s, QDBusConnection::systemBus()); - if (login1Iface.isValid()) - { - login1Iface.call(u"PowerOff"_s, false); - return; - } - // Else, other recent systems use ConsoleKit - QDBusInterface consolekitIface(u"org.freedesktop.ConsoleKit"_s, u"/org/freedesktop/ConsoleKit/Manager"_s, - u"org.freedesktop.ConsoleKit.Manager"_s, QDBusConnection::systemBus()); - if (consolekitIface.isValid()) - { - consolekitIface.call(u"Stop"_s); - return; - } - // HAL (older systems) - QDBusInterface halIface(u"org.freedesktop.Hal"_s, u"/org/freedesktop/Hal/devices/computer"_s, - u"org.freedesktop.Hal.Device.SystemPowerManagement"_s, - QDBusConnection::systemBus()); - halIface.call(u"Shutdown"_s); - } -#endif -} - QString Utils::Misc::unitString(const SizeUnit unit, const bool isSpeed) { const auto &unitString = units[static_cast(unit)]; @@ -413,21 +252,6 @@ QString Utils::Misc::userFriendlyDuration(const qlonglong seconds, const qlonglo return QCoreApplication::translate("misc", "%1y %2d", "e.g: 2 years 10 days").arg(QString::number(years), QString::number(days)); } -QString Utils::Misc::getUserIDString() -{ - QString uid = u"0"_s; -#ifdef Q_OS_WIN - const int UNLEN = 256; - WCHAR buffer[UNLEN + 1] = {0}; - DWORD buffer_len = sizeof(buffer) / sizeof(*buffer); - if (::GetUserNameW(buffer, &buffer_len)) - uid = QString::fromWCharArray(buffer); -#else - uid = QString::number(getuid()); -#endif - return uid; -} - QString Utils::Misc::languageToLocalizedString(const QString &localeStr) { if (localeStr.startsWith(u"eo", Qt::CaseInsensitive)) @@ -620,70 +444,3 @@ QString Utils::Misc::zlibVersionString() static const auto version {QString::fromLatin1(zlibVersion())}; return version; } - -#ifdef Q_OS_WIN -Path Utils::Misc::windowsSystemPath() -{ - static const Path path = []() -> Path - { - WCHAR systemPath[MAX_PATH] = {0}; - GetSystemDirectoryW(systemPath, sizeof(systemPath) / sizeof(WCHAR)); - return Path(QString::fromWCharArray(systemPath)); - }(); - return path; -} -#endif // Q_OS_WIN - -#if defined(Q_OS_MACOS) || defined(Q_OS_WIN) -bool Utils::Misc::applyMarkOfTheWeb(const Path &file, const QString &url) -{ - Q_ASSERT(url.isEmpty() || url.startsWith(u"http:") || url.startsWith(u"https:")); - -#ifdef Q_OS_MACOS - // References: - // https://searchfox.org/mozilla-central/rev/ffdc4971dc18e1141cb2a90c2b0b776365650270/xpcom/io/CocoaFileUtils.mm#230 - // https://github.com/transmission/transmission/blob/f62f7427edb1fd5c430e0ef6956bbaa4f03ae597/macosx/Torrent.mm#L1945-L1955 - - CFMutableDictionaryRef properties = ::CFDictionaryCreateMutable(kCFAllocatorDefault, 0 - , &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - if (properties == NULL) - return false; - - ::CFDictionarySetValue(properties, kLSQuarantineTypeKey, kLSQuarantineTypeOtherDownload); - if (!url.isEmpty()) - ::CFDictionarySetValue(properties, kLSQuarantineDataURLKey, url.toCFString()); - - const CFStringRef fileString = file.toString().toCFString(); - const CFURLRef fileURL = ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault - , fileString, kCFURLPOSIXPathStyle, false); - - const Boolean success = ::CFURLSetResourcePropertyForKey(fileURL, kCFURLQuarantinePropertiesKey - , properties, NULL); - - ::CFRelease(fileURL); - ::CFRelease(fileString); - ::CFRelease(properties); - - return success; -#elif defined(Q_OS_WIN) - const QString zoneIDStream = file.toString() + u":Zone.Identifier"; - HANDLE handle = ::CreateFileW(zoneIDStream.toStdWString().c_str(), GENERIC_WRITE - , (FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE) - , nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - if (handle == INVALID_HANDLE_VALUE) - return false; - - // 5.6.1 Zone.Identifier Stream Name - // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/6e3f7352-d11c-4d76-8c39-2516a9df36e8 - const QString hostURL = !url.isEmpty() ? url : u"about:internet"_s; - const QByteArray zoneID = QByteArrayLiteral("[ZoneTransfer]\r\nZoneId=3\r\n") - + u"HostUrl=%1\r\n"_s.arg(hostURL).toUtf8(); - - DWORD written = 0; - const BOOL writeResult = ::WriteFile(handle, zoneID.constData(), zoneID.size(), &written, nullptr); - ::CloseHandle(handle); - - return writeResult && (written == zoneID.size()); -#endif -} -#endif // Q_OS_MACOS || Q_OS_WIN diff --git a/src/base/utils/misc.h b/src/base/utils/misc.h index 863260ec1..6ce938067 100644 --- a/src/base/utils/misc.h +++ b/src/base/utils/misc.h @@ -28,20 +28,13 @@ #pragma once -#include +#include -#ifdef Q_OS_WIN -#include -#endif +#include "base/pathfwd.h" -#include - -#include "base/path.h" - -enum class ShutdownDialogAction; +class QString; /* Miscellaneous functions that can be useful */ - namespace Utils::Misc { // use binary prefix standards from IEC 60027-2 @@ -68,8 +61,6 @@ namespace Utils::Misc QString parseHtmlLinks(const QString &rawText); - void shutdownComputer(const ShutdownDialogAction &action); - QString osName(); QString boostVersionString(); QString libtorrentVersionString(); @@ -90,22 +81,6 @@ namespace Utils::Misc // Take a number of seconds and return a user-friendly // time duration like "1d 2h 10m". QString userFriendlyDuration(qlonglong seconds, qlonglong maxCap = -1, TimeResolution resolution = TimeResolution::Minutes); - QString getUserIDString(); QString languageToLocalizedString(const QString &localeStr); - -#ifdef Q_OS_WIN - Path windowsSystemPath(); - - template - T loadWinAPI(const QString &source, const char *funcName) - { - const std::wstring path = (windowsSystemPath() / Path(source)).toString().toStdWString(); - return reinterpret_cast(::GetProcAddress(::LoadLibraryW(path.c_str()), funcName)); - } -#endif // Q_OS_WIN - -#if defined(Q_OS_MACOS) || defined(Q_OS_WIN) - bool applyMarkOfTheWeb(const Path &file, const QString &url = {}); -#endif // Q_OS_MACOS || Q_OS_WIN } diff --git a/src/base/utils/os.cpp b/src/base/utils/os.cpp index b1d2c22a1..0430652be 100644 --- a/src/base/utils/os.cpp +++ b/src/base/utils/os.cpp @@ -30,14 +30,168 @@ #include "os.h" +#ifdef Q_OS_WIN +#include + +#include +#include +#include +#endif // Q_OS_WIN + #ifdef Q_OS_MACOS +#include +#include #include #endif // Q_OS_MACOS -#include +#ifdef QBT_USES_DBUS +#include +#endif // QBT_USES_DBUS + +#ifdef Q_OS_WIN +#include +#endif // Q_OS_WIN #include "base/global.h" -#include "base/path.h" +#include "base/types.h" + +void Utils::OS::shutdownComputer([[maybe_unused]] const ShutdownDialogAction &action) +{ +#if defined(Q_OS_WIN) + HANDLE hToken; // handle to process token + TOKEN_PRIVILEGES tkp; // pointer to token structure + if (!::OpenProcessToken(::GetCurrentProcess(), (TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY), &hToken)) + return; + // Get the LUID for shutdown privilege. + ::LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid); + + tkp.PrivilegeCount = 1; // one privilege to set + tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + // Get shutdown privilege for this process. + + ::AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, 0); + + // Cannot test the return value of AdjustTokenPrivileges. + + if (::GetLastError() != ERROR_SUCCESS) + return; + + if (action == ShutdownDialogAction::Suspend) + { + ::SetSuspendState(FALSE, FALSE, FALSE); + } + else if (action == ShutdownDialogAction::Hibernate) + { + ::SetSuspendState(TRUE, FALSE, FALSE); + } + else + { + const QString msg = QCoreApplication::translate("misc" + , "qBittorrent will shutdown the computer now because all downloads are complete."); + auto msgWchar = std::make_unique(msg.length() + 1); + msg.toWCharArray(msgWchar.get()); + ::InitiateSystemShutdownW(nullptr, msgWchar.get(), 10, TRUE, FALSE); + } + + // Disable shutdown privilege. + tkp.Privileges[0].Attributes = 0; + ::AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, 0); + +#elif defined(Q_OS_MACOS) + AEEventID EventToSend; + if (action != ShutdownDialogAction::Shutdown) + EventToSend = kAESleep; + else + EventToSend = kAEShutDown; + AEAddressDesc targetDesc; + const ProcessSerialNumber kPSNOfSystemProcess = {0, kSystemProcess}; + AppleEvent eventReply = {typeNull, NULL}; + AppleEvent appleEventToSend = {typeNull, NULL}; + + OSStatus error = ::AECreateDesc(typeProcessSerialNumber, &kPSNOfSystemProcess + , sizeof(kPSNOfSystemProcess), &targetDesc); + + if (error != noErr) + return; + + error = ::AECreateAppleEvent(kCoreEventClass, EventToSend, &targetDesc, kAutoGenerateReturnID + , kAnyTransactionID, &appleEventToSend); + + AEDisposeDesc(&targetDesc); + if (error != noErr) + return; + + error = ::AESend(&appleEventToSend, &eventReply, kAENoReply, kAENormalPriority, kAEDefaultTimeout + , NULL, NULL); + + ::AEDisposeDesc(&appleEventToSend); + if (error != noErr) + return; + + ::AEDisposeDesc(&eventReply); + +#elif defined(QBT_USES_DBUS) + // Use dbus to power off / suspend the system + if (action != ShutdownDialogAction::Shutdown) + { + // Some recent systems use systemd's logind + QDBusInterface login1Iface(u"org.freedesktop.login1"_s, u"/org/freedesktop/login1"_s, + u"org.freedesktop.login1.Manager"_s, QDBusConnection::systemBus()); + if (login1Iface.isValid()) + { + if (action == ShutdownDialogAction::Suspend) + login1Iface.call(u"Suspend"_s, false); + else + login1Iface.call(u"Hibernate"_s, false); + return; + } + // Else, other recent systems use UPower + QDBusInterface upowerIface(u"org.freedesktop.UPower"_s, u"/org/freedesktop/UPower"_s, + u"org.freedesktop.UPower"_s, QDBusConnection::systemBus()); + if (upowerIface.isValid()) + { + if (action == ShutdownDialogAction::Suspend) + upowerIface.call(u"Suspend"_s); + else + upowerIface.call(u"Hibernate"_s); + return; + } + // HAL (older systems) + QDBusInterface halIface(u"org.freedesktop.Hal"_s, u"/org/freedesktop/Hal/devices/computer"_s, + u"org.freedesktop.Hal.Device.SystemPowerManagement"_s, + QDBusConnection::systemBus()); + if (action == ShutdownDialogAction::Suspend) + halIface.call(u"Suspend"_s, 5); + else + halIface.call(u"Hibernate"_s); + } + else + { + // Some recent systems use systemd's logind + QDBusInterface login1Iface(u"org.freedesktop.login1"_s, u"/org/freedesktop/login1"_s, + u"org.freedesktop.login1.Manager"_s, QDBusConnection::systemBus()); + if (login1Iface.isValid()) + { + login1Iface.call(u"PowerOff"_s, false); + return; + } + // Else, other recent systems use ConsoleKit + QDBusInterface consolekitIface(u"org.freedesktop.ConsoleKit"_s, u"/org/freedesktop/ConsoleKit/Manager"_s, + u"org.freedesktop.ConsoleKit.Manager"_s, QDBusConnection::systemBus()); + if (consolekitIface.isValid()) + { + consolekitIface.call(u"Stop"_s); + return; + } + // HAL (older systems) + QDBusInterface halIface(u"org.freedesktop.Hal"_s, u"/org/freedesktop/Hal/devices/computer"_s, + u"org.freedesktop.Hal.Device.SystemPowerManagement"_s, + QDBusConnection::systemBus()); + halIface.call(u"Shutdown"_s); + } +#endif +} #ifdef Q_OS_MACOS namespace @@ -104,3 +258,70 @@ void Utils::OS::setMagnetLinkAssoc() ::LSSetDefaultHandlerForURLScheme(magnetUrlScheme, myBundleId); } #endif // Q_OS_MACOS + +#ifdef Q_OS_WIN +Path Utils::OS::windowsSystemPath() +{ + static const Path path = []() -> Path + { + WCHAR systemPath[MAX_PATH] = {0}; + ::GetSystemDirectoryW(systemPath, sizeof(systemPath) / sizeof(WCHAR)); + return Path(QString::fromWCharArray(systemPath)); + }(); + return path; +} +#endif // Q_OS_WIN + +#if defined(Q_OS_MACOS) || defined(Q_OS_WIN) +bool Utils::OS::applyMarkOfTheWeb(const Path &file, const QString &url) +{ + Q_ASSERT(url.isEmpty() || url.startsWith(u"http:") || url.startsWith(u"https:")); + +#ifdef Q_OS_MACOS + // References: + // https://searchfox.org/mozilla-central/rev/ffdc4971dc18e1141cb2a90c2b0b776365650270/xpcom/io/CocoaFileUtils.mm#230 + // https://github.com/transmission/transmission/blob/f62f7427edb1fd5c430e0ef6956bbaa4f03ae597/macosx/Torrent.mm#L1945-L1955 + + CFMutableDictionaryRef properties = ::CFDictionaryCreateMutable(kCFAllocatorDefault, 0 + , &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + if (properties == NULL) + return false; + + ::CFDictionarySetValue(properties, kLSQuarantineTypeKey, kLSQuarantineTypeOtherDownload); + if (!url.isEmpty()) + ::CFDictionarySetValue(properties, kLSQuarantineDataURLKey, url.toCFString()); + + const CFStringRef fileString = file.toString().toCFString(); + const CFURLRef fileURL = ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault + , fileString, kCFURLPOSIXPathStyle, false); + + const Boolean success = ::CFURLSetResourcePropertyForKey(fileURL, kCFURLQuarantinePropertiesKey + , properties, NULL); + + ::CFRelease(fileURL); + ::CFRelease(fileString); + ::CFRelease(properties); + + return success; +#elif defined(Q_OS_WIN) + const QString zoneIDStream = file.toString() + u":Zone.Identifier"; + HANDLE handle = ::CreateFileW(zoneIDStream.toStdWString().c_str(), GENERIC_WRITE + , (FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE) + , nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (handle == INVALID_HANDLE_VALUE) + return false; + + // 5.6.1 Zone.Identifier Stream Name + // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/6e3f7352-d11c-4d76-8c39-2516a9df36e8 + const QString hostURL = !url.isEmpty() ? url : u"about:internet"_s; + const QByteArray zoneID = QByteArrayLiteral("[ZoneTransfer]\r\nZoneId=3\r\n") + + u"HostUrl=%1\r\n"_s.arg(hostURL).toUtf8(); + + DWORD written = 0; + const BOOL writeResult = ::WriteFile(handle, zoneID.constData(), zoneID.size(), &written, nullptr); + ::CloseHandle(handle); + + return writeResult && (written == zoneID.size()); +#endif +} +#endif // Q_OS_MACOS || Q_OS_WIN diff --git a/src/base/utils/os.h b/src/base/utils/os.h index 43b632365..9dc4df021 100644 --- a/src/base/utils/os.h +++ b/src/base/utils/os.h @@ -32,12 +32,41 @@ #include +#ifdef Q_OS_WIN +#include + +#include + +#include +#endif + +#include "base/path.h" + +enum class ShutdownDialogAction; + namespace Utils::OS { + void shutdownComputer(const ShutdownDialogAction &action); + #ifdef Q_OS_MACOS bool isTorrentFileAssocSet(); void setTorrentFileAssoc(); bool isMagnetLinkAssocSet(); void setMagnetLinkAssoc(); #endif // Q_OS_MACOS + +#ifdef Q_OS_WIN + Path windowsSystemPath(); + + template + T loadWinAPI(const QString &source, const char *funcName) + { + const std::wstring path = (windowsSystemPath() / Path(source)).toString().toStdWString(); + return reinterpret_cast(::GetProcAddress(::LoadLibraryW(path.c_str()), funcName)); + } +#endif // Q_OS_WIN + +#if defined(Q_OS_MACOS) || defined(Q_OS_WIN) + bool applyMarkOfTheWeb(const Path &file, const QString &url = {}); +#endif // Q_OS_MACOS || Q_OS_WIN } diff --git a/src/base/utils/random.cpp b/src/base/utils/random.cpp index e9ae647f4..99343f0e1 100644 --- a/src/base/utils/random.cpp +++ b/src/base/utils/random.cpp @@ -44,7 +44,10 @@ #include #include "base/global.h" -#include "base/utils/misc.h" + +#ifdef Q_OS_WIN +#include "base/utils/os.h" +#endif namespace { @@ -56,7 +59,7 @@ namespace using result_type = uint32_t; RandomLayer() - : m_rtlGenRandom {Utils::Misc::loadWinAPI(u"Advapi32.dll"_s, "SystemFunction036")} + : m_rtlGenRandom {Utils::OS::loadWinAPI(u"Advapi32.dll"_s, "SystemFunction036")} { if (!m_rtlGenRandom) qFatal("Failed to load RtlGenRandom()"); diff --git a/src/gui/macosdockbadge/badgeview.mm b/src/gui/macosdockbadge/badgeview.mm index a40a04d48..593f51833 100644 --- a/src/gui/macosdockbadge/badgeview.mm +++ b/src/gui/macosdockbadge/badgeview.mm @@ -28,6 +28,9 @@ */ #import "badgeview.h" + +#include + #include "base/utils/misc.h" static const CGFloat kBetweenPadding = 2.0;