diff --git a/patches/kiconthemes/dark-mode-detection.patch b/patches/kiconthemes/dark-mode-detection.patch index 16427d30..c3a767d6 100755 --- a/patches/kiconthemes/dark-mode-detection.patch +++ b/patches/kiconthemes/dark-mode-detection.patch @@ -1,244 +1,245 @@ #! /bin/sh patch -p1 -l -f $* < $0 exit $? From 528c3f0429b52a8c77f229493c5a013ca202e6da Mon Sep 17 00:00:00 2001 From: Sune Vuorela <sune@vuorela.dk> Date: Mon, 23 Oct 2023 09:48:51 +0200 Subject: [PATCH] Use dark icons if configured and available --- src/CMakeLists.txt | 5 ++ src/DarkModeSetup.cpp | 130 ++++++++++++++++++++++++++++++++++++++++++ src/DarkModeSetup.h | 20 +++++++ src/kicontheme.cpp | 28 ++++++++- 4 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 src/DarkModeSetup.cpp create mode 100644 src/DarkModeSetup.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ce4ca1f1..40b37873 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -29,6 +29,11 @@ target_sources(KF5IconThemes PRIVATE hicolor.qrc ) +if (WIN32) + target_sources(KF5IconThemes PRIVATE + DarkModeSetup.cpp) +endif() + # TODO what's that PRIVATE about above? ki18n_wrap_ui(KF5IconThemes kicondialog.ui) diff --git a/src/DarkModeSetup.cpp b/src/DarkModeSetup.cpp new file mode 100644 index 00000000..1508aa5b --- /dev/null +++ b/src/DarkModeSetup.cpp -@@ -0,0 +1,130 @@ +@@ -0,0 +1,131 @@ +/* + SPDX-FileCopyrightText: 2019 Richard Yu + SPDX-FileCopyrightText: 2023 g10 Code GmbH + SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk> + SPDX-License-Identifier: MIT +*/ + +#define WIN32_LEAN_AND_MEAN +#include "DarkModeSetup.h" +#include <QByteArray> +#include <QList> +#include <QtGlobal> +#include <windows.h> +#include <uxtheme.h> +struct DarkModeSetupHelper { + // Code originates from https://github.com/ysc3839/win32-darkmode/ + enum PreferredAppMode { Default, AllowDark, ForceDark, ForceLight, Max }; + // needed to get version number; some functions depend on the build version + using fnRtlGetNtVersionNumbers = void(WINAPI *)(LPDWORD major, LPDWORD minor, LPDWORD build); + // check if apps should use darkmode + using fnShouldAppsUseDarkMode = bool(WINAPI *)(); // ordinal 132 + // tell back if we can use darkmode; version dependent + using fnAllowDarkModeForApp = bool(WINAPI *)(bool allow); // ordinal 135, in 1809 + using fnSetPreferredAppMode = PreferredAppMode(WINAPI *)(PreferredAppMode appMode); // ordinal 135, in 1903 + + // Tells windows that it should reread things + using fnRefreshImmersiveColorPolicyState = void(WINAPI *)(); // ordinal 104 + + fnRefreshImmersiveColorPolicyState _RefreshImmersiveColorPolicyState = nullptr; + fnAllowDarkModeForApp _AllowDarkModeForApp = nullptr; + fnShouldAppsUseDarkMode _ShouldAppsUseDarkMode = nullptr; + fnSetPreferredAppMode _SetPreferredAppMode = nullptr; + + bool darkModeSupported = false; + bool darkModeEnabled = false; + + bool IsHighContrast() { + HIGHCONTRASTW highContrast = {sizeof(highContrast)}; + if (SystemParametersInfoW(SPI_GETHIGHCONTRAST, sizeof(highContrast), &highContrast, FALSE)) { + return highContrast.dwFlags & HCF_HIGHCONTRASTON; + } + return false; + } + + void AllowDarkModeForApp(bool allow) { + if (_AllowDarkModeForApp) { + _AllowDarkModeForApp(allow); + } else if (_SetPreferredAppMode) { + _SetPreferredAppMode(allow ? AllowDark : Default); + } + } + + DarkModeSetupHelper() { + auto RtlGetNtVersionNumbers = reinterpret_cast<fnRtlGetNtVersionNumbers>(GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlGetNtVersionNumbers")); + if (RtlGetNtVersionNumbers) { + DWORD buildNumber = 0; + DWORD major, minor; + RtlGetNtVersionNumbers(&major, &minor, &buildNumber); + buildNumber &= ~0xF0000000; + + if (major == 10 && minor == 0) { + HMODULE hUxtheme = LoadLibraryExW(L"uxtheme.dll", nullptr, + LOAD_LIBRARY_SEARCH_SYSTEM32); + if (hUxtheme) { + _RefreshImmersiveColorPolicyState = reinterpret_cast<fnRefreshImmersiveColorPolicyState>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(104))); + + _ShouldAppsUseDarkMode = reinterpret_cast<fnShouldAppsUseDarkMode>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(132))); + + auto ord135 = GetProcAddress(hUxtheme, MAKEINTRESOURCEA(135)); + if (buildNumber < 18362) { + _AllowDarkModeForApp = reinterpret_cast<fnAllowDarkModeForApp>(ord135); + } else { + _SetPreferredAppMode = reinterpret_cast<fnSetPreferredAppMode>(ord135); + } + + if (_ShouldAppsUseDarkMode && + _RefreshImmersiveColorPolicyState && + (_AllowDarkModeForApp || _SetPreferredAppMode)) { + darkModeSupported = true; + + AllowDarkModeForApp(true); + _RefreshImmersiveColorPolicyState(); + -+ darkModeEnabled = _ShouldAppsUseDarkMode() && !IsHighContrast(); ++ // Windows 10 only has one bright high contrast mode and three dark high contrast themes. So we check for the white one. ++ darkModeEnabled = _ShouldAppsUseDarkMode() || (IsHighContrast() && GetSysColor(COLOR_WINDOW) != 0xFFFFFF); + } + } + } + } + } +}; + +const DarkModeSetupHelper &getHelper() { + static DarkModeSetupHelper h; + return h; +} + +bool DarkModeSetup::isDarkModeActive() { return getHelper().darkModeEnabled; } + +void DarkModeSetup::tellQt(int mode) { + int qtm = qtMode(); + if (qtm > 0) { + qWarning("Qt darkmode already enabled"); + return; + } + QByteArray arr = qgetenv("QT_QPA_PLATFORM"); + if (!arr.isEmpty()) { + arr.append(','); + } + arr.append("windows:darkmode=" + QByteArray::number(mode)); + qputenv("QT_QPA_PLATFORM", arr); +} + +int DarkModeSetup::qtMode() { + const auto envvar = qgetenv("QT_QPA_PLATFORM"); + if (envvar.isEmpty()) { + return 0; + } + const auto list = envvar.split(','); + for (const auto &element : list) { + if (element.startsWith("windows:darkmode=")) { + auto data = element.right(element.length() - 17); + bool success; + int number = data.toInt(&success); + if (success && number >= 0 && number <= 2) { + return number; + } + } + } + return 0; +} diff --git a/src/DarkModeSetup.h b/src/DarkModeSetup.h new file mode 100644 index 00000000..87ecfd76 --- /dev/null +++ b/src/DarkModeSetup.h @@ -0,0 +1,20 @@ +/* + SPDX-FileCopyrightText: 2019 Richard Yu + SPDX-FileCopyrightText: 2023 g10 Code GmbH + SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk> + SPDX-License-Identifier: MIT +*/ + + +class DarkModeSetup { +public: + // Queries thru dark magic if dark mode is active or not using weird windows non-api's + // This is valid before QApplication has been created + bool isDarkModeActive(); + // Tell Qt about our capabilities + // See https://doc.qt.io/qt-6/qguiapplication.html#platform-specific-arguments for details + // must be called before creating QApplication + void tellQt(int mode = 2); + // gets previously mode told qt + int qtMode(); +}; diff --git a/src/kicontheme.cpp b/src/kicontheme.cpp index 4eee2ff6..a5617218 100644 --- a/src/kicontheme.cpp +++ b/src/kicontheme.cpp @@ -34,14 +34,40 @@ #include <array> #include <cmath> +#ifdef Q_OS_WIN +#include "DarkModeSetup.h" +#endif + Q_GLOBAL_STATIC(QString, _themeOverride) +#ifdef Q_OS_WIN +bool useDarkMode() { + DarkModeSetup s; + if (!s.isDarkModeActive()) { + return false; + } + return s.qtMode() == 2 || s.qtMode() == 1; +} +#else +inline bool useDarkMode() { + return false; +} +#endif + + + // Support for icon themes in RCC files. // The intended use case is standalone apps on Windows / MacOS / etc. // For this reason we use AppDataLocation: BINDIR/data on Windows, Resources on OS X void initRCCIconTheme() { - const QString iconThemeRcc = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("icontheme.rcc")); + QString iconThemeRcc; + if (useDarkMode()) { + iconThemeRcc = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("icontheme-dark.rcc")); + } + if (iconThemeRcc.isEmpty()) { + iconThemeRcc = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("icontheme.rcc")); + } if (!iconThemeRcc.isEmpty()) { const QString iconThemeName = QStringLiteral("kf5_rcc_theme"); const QString iconSubdir = QStringLiteral("/icons/") + iconThemeName; -- GitLab