diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 130f458b55..068c343369 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -147,7 +147,6 @@ betadele betsegaw BGR bgra -BGSOUNDS bhid Bicubic bigbar @@ -245,7 +244,6 @@ CLASSNOTAVAILABLE clickable clickonce CLIENTEDGE -CLIENTPULL clientside CLIPCHILDREN Clipperton @@ -452,19 +450,14 @@ dimm directshow dirs DISABLEASACTIONKEY -dispid -DISPIDAMBIENTDLCONTROL DISPLAYCHANGE DISPLAYCONFIG displayname divyan -DLACTIVEXCTLS -DLCONTROL Dlg DLGFRAME DLGMODALFRAME dlib -DLIMAGES dllexport dllhost dllimport @@ -476,7 +469,6 @@ doctype DONOTROUND DONTVALIDATEPATH dotnet -DOWNLOADONLY DPICHANGED DPIs DPolicy @@ -536,6 +528,7 @@ Emoji emptyrecyclebin ENABLEDPOPUP endforeach +endian endif endl endpointvolume @@ -547,7 +540,6 @@ enum EOAC eol epicgames -epo Eqn ERASEBKGND EREOF @@ -595,7 +587,6 @@ fallthrough fancyzones FANCYZONESDRAWLAYOUTTEST FANCYZONESEDITOR -FANCYZONESWINDOWSTYLES Farbraum Faroe FARPROC @@ -634,11 +625,9 @@ FOFX FOLDERID folderpath FORCEMINIMIZE -FORCEOFFLINE foreach formatetc FRAMECHANGED -FRAMEDOWNLOAD franky frankychen Froml @@ -681,7 +670,6 @@ globals GNumber google GPTR -gsuberland gtm gui guiddef @@ -718,7 +706,6 @@ helptext Heure HEVC hfile -HFONT hglobal hhk HHmmss @@ -778,8 +765,8 @@ hstring hsv htcfreek HTHUMBNAIL -Htmdid HTTRANSPARENT +HValue Hvci hwb HWINEVENTHOOK @@ -813,10 +800,8 @@ ICompositor ICONERROR IContext ICONWARNING -ICore ICreate IData -IDCANCEL IDD IDelayed IDesktop @@ -827,7 +812,6 @@ IDispatcher IDisposable idl IDLIST -IDOK IDOn IDR IDrive @@ -895,7 +879,6 @@ INFOEXAMPLE Infotip ingbuffer inheritdoc -INITDIALOG initguid Inkscape Inlines @@ -958,7 +941,6 @@ IPublic IQuery IRead IReference -IReflect IRegistered IRegistration IRegistry @@ -1006,9 +988,7 @@ IWbem IWeb IWIC iwindow -IWindows IWork -IXaml IXml IYUV IZone @@ -1066,7 +1046,6 @@ Kyrgyzstan Kyzylorda LAlt Lambson -lamotile langword Lastdevice Latn @@ -1153,6 +1132,7 @@ LPSAFEARRAY LPSTR lpsz lpt +LPTHREAD LPTOP lptpm LPTSTR @@ -1225,7 +1205,6 @@ MENUBREAK MENUITEMINFO MENUITEMINFOW messageboxes -METACHARSET Metadatas metafile mfapi @@ -1252,7 +1231,6 @@ miniz minlevel MINORVERSION Miracast -mirophone Mishkeegogamang mjpg mkd @@ -1296,7 +1274,6 @@ msdata MSDN msedge MSGFLT -mshtmdid MSIFASTINSTALL MSIHANDLE msiquery @@ -1362,10 +1339,8 @@ netcore netcoreapp netcpl netframework -NETFX netsetup netsh -netstandard Neue newcolor newdev @@ -1443,7 +1418,6 @@ NUMLOCK NUMPAD Nunavut Nusa -Nvidia nwc NWSE Objbase @@ -1483,6 +1457,7 @@ ostream ostringstream OSVERSIONINFOEX OSVERSIONINFOEXW +OSVERSIONINFOW osvi otating OUTOFCONTEXT @@ -1566,7 +1541,6 @@ Pohnpei popup POPUPWINDOW posix -Postion powercfg powerlauncher powerpreview @@ -1596,7 +1570,6 @@ Prefixer Preinstalled preload PREMULTIPLIED -preperty prevhost previewer PREVIEWHANDLERFRAMEINFO @@ -1623,6 +1596,7 @@ PROPBAG PROPERTYKEY propkey propvarutil +PRTL prvpane psapi PSECURITY @@ -1824,7 +1798,6 @@ SENDCHANGE sendvirtualinput serializationexception serializer -serizalization serverside SETCONTEXT setcursor @@ -1839,8 +1812,6 @@ SETTEXT SETTINGCHANGE settingsheader settingshotkeycontrol -settingsv -Setttings SETWORKAREA sfgao SFGAOF @@ -1920,7 +1891,6 @@ somil Soref SOURCECLIENTAREAONLY SOURCEHEADER -sourceid sourcesdirectory spam spdisp @@ -2076,7 +2046,6 @@ Tenggara testcase testhost testprocess -testtrocess testzones TEXCOORD textblock @@ -2100,7 +2069,6 @@ TMPVAR TNP todo toggleswitch -Toolchain toolkitcontrols toolkitconverters Toolset @@ -2227,7 +2195,6 @@ vcruntime vcvars VDesktop vdi -VDId vec VERBSONLY VERBW @@ -2385,6 +2352,7 @@ wpf wpr wprintf wprp +wql WQL wregex WResize @@ -2410,8 +2378,6 @@ Wwan Wwanpp xamarin XAttribute -xbf -XBind Xbox XBUTTON XBUTTONDBLCLK @@ -2422,7 +2388,6 @@ XDocument XElement XFile XIncrement -XInstance XLoc XNamespace XOffset diff --git a/installer/MSIX/PackagingLayout.xml b/installer/MSIX/PackagingLayout.xml index 419b388f3c..31d1d1128b 100644 --- a/installer/MSIX/PackagingLayout.xml +++ b/installer/MSIX/PackagingLayout.xml @@ -25,6 +25,7 @@ + @@ -33,6 +34,7 @@ + diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index 0a863871fe..8450cd3427 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -40,13 +40,13 @@ --> - + - + - + - + @@ -872,7 +872,7 @@ - + @@ -892,7 +892,7 @@ - + @@ -1731,7 +1731,7 @@ - + diff --git a/src/common/ManagedCommon/ManagedCommon.csproj b/src/common/ManagedCommon/ManagedCommon.csproj index 6ddd715395..30dd826a0f 100644 --- a/src/common/ManagedCommon/ManagedCommon.csproj +++ b/src/common/ManagedCommon/ManagedCommon.csproj @@ -17,6 +17,8 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/src/common/ManagedCommon/Theme.cs b/src/common/ManagedCommon/Theme.cs index 4e0cda2f3b..3726050b5b 100644 --- a/src/common/ManagedCommon/Theme.cs +++ b/src/common/ManagedCommon/Theme.cs @@ -14,4 +14,10 @@ namespace ManagedCommon HighContrastBlack, HighContrastWhite, } + + public enum AppTheme + { + Dark = 0, + Light = 1, + } } diff --git a/src/common/ManagedCommon/ThemeHelpers.cs b/src/common/ManagedCommon/ThemeHelpers.cs new file mode 100644 index 0000000000..2bc6f50602 --- /dev/null +++ b/src/common/ManagedCommon/ThemeHelpers.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.Win32; + +namespace ManagedCommon +{ + // Based on https://stackoverflow.com/a/62811758/5001796 + public static class ThemeHelpers + { + [DllImport("dwmapi.dll")] + private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize); + + internal const string HKeyRoot = "HKEY_CURRENT_USER"; + internal const string HkeyWindowsTheme = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Themes"; + internal const string HkeyWindowsPersonalizeTheme = $@"{HkeyWindowsTheme}\Personalize"; + internal const string HValueAppTheme = "AppsUseLightTheme"; + internal const int DWMWAImmersiveDarkMode = 20; + + // based on https://stackoverflow.com/questions/51334674/how-to-detect-windows-10-light-dark-mode-in-win32-application + public static AppTheme GetAppTheme() + { + int value = (int)Registry.GetValue($"{HKeyRoot}\\{HkeyWindowsPersonalizeTheme}", HValueAppTheme, 1); + return (AppTheme)value; + } + + public static void SetImmersiveDarkMode(IntPtr window, bool enabled) + { + int useImmersiveDarkMode = enabled ? 1 : 0; + _ = DwmSetWindowAttribute(window, DWMWAImmersiveDarkMode, ref useImmersiveDarkMode, sizeof(int)); + } + } +} diff --git a/src/common/ManagedCommon/ThemeListener.cs b/src/common/ManagedCommon/ThemeListener.cs new file mode 100644 index 0000000000..4e74754581 --- /dev/null +++ b/src/common/ManagedCommon/ThemeListener.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Management; +using System.Security.Principal; + +namespace ManagedCommon +{ + /// + /// The Delegate for a ThemeChanged Event. + /// + /// Sender ThemeListener + public delegate void ThemeChangedEvent(ThemeListener sender); + + public class ThemeListener : IDisposable + { + /// + /// Gets the App Theme. + /// + public AppTheme AppTheme { get; private set; } + + /// + /// An event that fires if the Theme changes. + /// + public event ThemeChangedEvent ThemeChanged; + + private readonly ManagementEventWatcher watcher; + + public ThemeListener() + { + var currentUser = WindowsIdentity.GetCurrent(); + var query = new WqlEventQuery( + $"SELECT * FROM RegistryValueChangeEvent WHERE Hive='HKEY_USERS' AND " + + $"KeyPath='{currentUser.User.Value}\\\\{ThemeHelpers.HkeyWindowsPersonalizeTheme.Replace("\\", "\\\\")}' AND ValueName='{ThemeHelpers.HValueAppTheme}'"); + watcher = new ManagementEventWatcher(query); + watcher.EventArrived += Watcher_EventArrived; + watcher.Start(); + + AppTheme = ThemeHelpers.GetAppTheme(); + } + + private void Watcher_EventArrived(object sender, EventArrivedEventArgs e) + { + var appTheme = ThemeHelpers.GetAppTheme(); + + if (appTheme != AppTheme) + { + AppTheme = appTheme; + + ThemeChanged?.Invoke(this); + } + } + + public void Dispose() + { + watcher.Dispose(); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/common/Themes/Themes.vcxproj b/src/common/Themes/Themes.vcxproj index b9335299b1..30a529c62e 100644 --- a/src/common/Themes/Themes.vcxproj +++ b/src/common/Themes/Themes.vcxproj @@ -31,10 +31,14 @@ + + + + diff --git a/src/common/Themes/theme_helpers.cpp b/src/common/Themes/theme_helpers.cpp new file mode 100644 index 0000000000..280eda9ded --- /dev/null +++ b/src/common/Themes/theme_helpers.cpp @@ -0,0 +1,43 @@ +// Port Based on https://stackoverflow.com/a/62811758/5001796 +#include "theme_helpers.h" +#include "dwmapi.h" +#include +#include + +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#define HKEY_WINDOWS_THEME L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize" + +// based on https://stackoverflow.com/questions/51334674/how-to-detect-windows-10-light-dark-mode-in-win32-application +AppTheme ThemeHelpers::GetAppTheme() +{ + // The value is expected to be a REG_DWORD, which is a signed 32-bit little-endian + auto buffer = std::vector(4); + auto cbData = static_cast(buffer.size() * sizeof(char)); + auto res = RegGetValueW( + HKEY_CURRENT_USER, + HKEY_WINDOWS_THEME, + L"AppsUseLightTheme", + RRF_RT_REG_DWORD, // expected value type + nullptr, + buffer.data(), + &cbData); + + if (res != ERROR_SUCCESS) + { + return AppTheme::Light; + } + + // convert bytes written to our buffer to an int, assuming little-endian + auto i = int(buffer[3] << 24 | + buffer[2] << 16 | + buffer[1] << 8 | + buffer[0]); + + return AppTheme(i); +} + +void ThemeHelpers::SetImmersiveDarkMode(HWND window, bool enabled) +{ + int useImmersiveDarkMode = enabled ? 1 : 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, &useImmersiveDarkMode, sizeof(useImmersiveDarkMode)); +} diff --git a/src/common/Themes/theme_helpers.h b/src/common/Themes/theme_helpers.h new file mode 100644 index 0000000000..026a971988 --- /dev/null +++ b/src/common/Themes/theme_helpers.h @@ -0,0 +1,14 @@ +#pragma once +#include + +enum class AppTheme +{ + Dark = 0, + Light = 1 +}; + +struct ThemeHelpers +{ + static AppTheme GetAppTheme(); + static void ThemeHelpers::SetImmersiveDarkMode(HWND window, bool enabled); +}; \ No newline at end of file diff --git a/src/common/Themes/theme_listener.cpp b/src/common/Themes/theme_listener.cpp new file mode 100644 index 0000000000..4728b049e0 --- /dev/null +++ b/src/common/Themes/theme_listener.cpp @@ -0,0 +1,57 @@ +#include "theme_listener.h" + +#define HKEY_WINDOWS_THEME L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize" + +DWORD WINAPI _checkTheme(LPVOID lpParam) +{ + auto listener = (ThemeListener*)lpParam; + listener->CheckTheme(); + return 0; +} + +void ThemeListener::AddChangedHandler(THEME_HANDLE handle) +{ + handles.push_back(handle); +} + +void ThemeListener::DelChangedHandler(THEME_HANDLE handle) +{ + auto it = std::find(handles.begin(), handles.end(), handle); + handles.erase(it); +} + +void ThemeListener::CheckTheme() +{ + HANDLE hEvent; + HKEY hKey; + + // Open the Key to listen + RegOpenKeyEx(HKEY_CURRENT_USER, HKEY_WINDOWS_THEME, 0, KEY_NOTIFY, &hKey); + + while (true) + { + // Create an event. + hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (hEvent != 0) + { + // Watch the registry key for a change of value. + RegNotifyChangeKeyValue(hKey, + TRUE, + REG_NOTIFY_CHANGE_LAST_SET, + hEvent, + TRUE); + + WaitForSingleObject(hEvent, INFINITE); + + auto _theme = ThemeHelpers::GetAppTheme(); + if (AppTheme != _theme) + { + AppTheme = _theme; + for (int i = 0; i < handles.size(); i++) + { + handles[i](); + } + } + } + } +} \ No newline at end of file diff --git a/src/common/Themes/theme_listener.h b/src/common/Themes/theme_listener.h new file mode 100644 index 0000000000..d9c01925cf --- /dev/null +++ b/src/common/Themes/theme_listener.h @@ -0,0 +1,34 @@ +#include "theme_helpers.h" +#include "dwmapi.h" +#include +#include +#include + +typedef void (*THEME_HANDLE)(); +DWORD WINAPI _checkTheme(LPVOID lpParam); + +#pragma once +class ThemeListener +{ +public: + ThemeListener() + { + AppTheme = ThemeHelpers::GetAppTheme(); + dwThreadHandle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)_checkTheme, this, 0, &dwThreadId); + } + ~ThemeListener() + { + CloseHandle(dwThreadHandle); + dwThreadId = 0; + } + + AppTheme AppTheme; + void ThemeListener::AddChangedHandler(THEME_HANDLE handle); + void ThemeListener::DelChangedHandler(THEME_HANDLE handle); + void CheckTheme(); + +private: + HANDLE dwThreadHandle; + DWORD dwThreadId; + std::vector handles; +}; \ No newline at end of file diff --git a/src/common/Themes/windows_colors.cpp b/src/common/Themes/windows_colors.cpp index 962c2acf4c..a30329c181 100644 --- a/src/common/Themes/windows_colors.cpp +++ b/src/common/Themes/windows_colors.cpp @@ -1,4 +1,5 @@ #include "windows_colors.h" +#include "theme_helpers.h" DWORD WindowsColors::rgb_color(DWORD abgr_color) { @@ -65,7 +66,7 @@ WindowsColors::Color WindowsColors::get_background_color() bool WindowsColors::is_dark_mode() { - return rgb_color(get_background_color()) == 0; + return ThemeHelpers::GetAppTheme() == AppTheme::Dark; } bool WindowsColors::update() diff --git a/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.vcxproj b/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.vcxproj index 93758a4a27..2edf88a3dd 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.vcxproj +++ b/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.vcxproj @@ -102,7 +102,7 @@ ./;$(SolutionDir)src\modules\;$(SolutionDir)src\modules\KeyboardManager\KeyboardManagerEditorLibrary\;$(SolutionDir)src\common\Display;$(SolutionDir)src\common\inc;$(SolutionDir)src\common\Telemetry;$(SolutionDir)src;%(AdditionalIncludeDirectories) - Display.lib;shcore.lib;Dbghelp.lib;%(AdditionalDependencies) + Display.lib;shcore.lib;Dbghelp.lib;dwmapi.lib;%(AdditionalDependencies) $(SolutionDir)$(Platform)\$(ConfigurationName);%(AdditionalLibraryDirectories) @@ -113,7 +113,7 @@ true true - Display.lib;shcore.lib;Dbghelp.lib;%(AdditionalDependencies) + Display.lib;shcore.lib;Dbghelp.lib;dwmapi.lib;%(AdditionalDependencies) $(SolutionDir)$(Platform)\$(ConfigurationName);%(AdditionalLibraryDirectories) diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp index d2fa577f4c..2ef8c732f9 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp @@ -22,6 +22,7 @@ #include "UIHelpers.h" #include "ShortcutErrorType.h" #include "EditorConstants.h" +#include using namespace winrt::Windows::Foundation; @@ -42,6 +43,20 @@ std::mutex editKeyboardWindowMutex; // Stores a pointer to the Xaml Bridge object so that it can be accessed from the window procedure static XamlBridge* xamlBridgePtr = nullptr; +// Theming +ThemeListener theme_listener{}; + +void handleTheme() +{ + auto theme = theme_listener.AppTheme; + auto isDark = theme == AppTheme::Dark; + Logger::info(L"Theme is now {}", isDark ? L"Dark" : L"Light"); + if (hwndEditKeyboardNativeWindow != nullptr) + { + ThemeHelpers::SetImmersiveDarkMode(hwndEditKeyboardNativeWindow, isDark); + } +} + static IAsyncOperation OrphanKeysConfirmationDialog( KBMEditor::KeyboardManagerState& state, const std::vector& keys, @@ -130,7 +145,7 @@ inline void CreateEditKeyboardWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMan 48, 48, LR_DEFAULTCOLOR); - + if (RegisterClassEx(&windowClass) == NULL) { MessageBox(NULL, GET_RESOURCE_STRING(IDS_REGISTERCLASSFAILED_ERRORMESSAGE).c_str(), GET_RESOURCE_STRING(IDS_REGISTERCLASSFAILED_ERRORTITLE).c_str(), NULL); @@ -149,7 +164,7 @@ inline void CreateEditKeyboardWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMan DPIAware::ConvertByCursorPosition(windowWidth, windowHeight); DPIAware::GetScreenDPIForCursor(g_currentDPI); - + // Window Creation HWND _hWndEditKeyboardWindow = CreateWindow( szWindowClass, @@ -163,7 +178,7 @@ inline void CreateEditKeyboardWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMan NULL, hInst, NULL); - + if (_hWndEditKeyboardWindow == NULL) { MessageBox(NULL, GET_RESOURCE_STRING(IDS_CREATEWINDOWFAILED_ERRORMESSAGE).c_str(), GET_RESOURCE_STRING(IDS_CREATEWINDOWFAILED_ERRORTITLE).c_str(), NULL); @@ -181,12 +196,15 @@ inline void CreateEditKeyboardWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMan hwndEditKeyboardNativeWindow = _hWndEditKeyboardWindow; hwndLock.unlock(); + handleTheme(); + theme_listener.AddChangedHandler(handleTheme); + // Create the xaml bridge object XamlBridge xamlBridge(_hWndEditKeyboardWindow); // DesktopSource needs to be declared before the RelativePanel xamlContainer object to avoid errors winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource desktopSource; - + // Create the desktop window xaml source object and set its content hWndXamlIslandEditKeyboardWindow = xamlBridge.InitDesktopWindowsXamlSource(desktopSource); @@ -253,10 +271,10 @@ inline void CreateEditKeyboardWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMan SingleKeyRemapControl::keyboardManagerState = &keyboardManagerState; KeyDropDownControl::keyboardManagerState = &keyboardManagerState; KeyDropDownControl::mappingConfiguration = &mappingConfiguration; - + // Clear the single key remap buffer SingleKeyRemapControl::singleKeyRemapBuffer.clear(); - + // Vector to store dynamically allocated control objects to avoid early destruction std::vector>> keyboardRemapControlObjects; @@ -378,6 +396,7 @@ inline void CreateEditKeyboardWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMan xamlBridgePtr = nullptr; hWndXamlIslandEditKeyboardWindow = nullptr; hwndLock.lock(); + theme_listener.DelChangedHandler(handleTheme); hwndEditKeyboardNativeWindow = nullptr; keyboardManagerState.ResetUIState(); keyboardManagerState.ClearRegisteredKeyDelays(); @@ -450,8 +469,7 @@ LRESULT CALLBACK EditKeyboardWindowProc(HWND hWnd, UINT messageCode, WPARAM wPar rect->top, rect->right - rect->left, rect->bottom - rect->top, - SWP_NOZORDER | SWP_NOACTIVATE - ); + SWP_NOZORDER | SWP_NOACTIVATE); Logger::trace(L"WM_DPICHANGED: new dpi {} rect {} {} ", newDPI, rect->right - rect->left, rect->bottom - rect->top); } @@ -487,7 +505,7 @@ bool CheckEditKeyboardWindowActive() { ShowWindow(hwndEditKeyboardNativeWindow, SW_RESTORE); } - + // If there is an already existing window no need to create a new open bring it on foreground. SetForegroundWindow(hwndEditKeyboardNativeWindow); result = true; diff --git a/src/modules/powerrename/PowerRenameUILib/MainWindow.xaml.cpp b/src/modules/powerrename/PowerRenameUILib/MainWindow.xaml.cpp index 09fe0055a2..273638c61e 100644 --- a/src/modules/powerrename/PowerRenameUILib/MainWindow.xaml.cpp +++ b/src/modules/powerrename/PowerRenameUILib/MainWindow.xaml.cpp @@ -21,6 +21,8 @@ #include "microsoft.ui.xaml.window.h" #include #include +#include +#include using namespace winrt; using namespace Windows::UI::Xaml; @@ -35,15 +37,31 @@ HINSTANCE g_hostHInst; extern std::vector g_files; +// Theming +ThemeListener theme_listener{}; +HWND CurrentWindow; + +void handleTheme() { + auto theme = theme_listener.AppTheme; + auto isDark = theme == AppTheme::Dark; + Logger::info(L"Theme is now {}", isDark ? L"Dark" : L"Light"); + ThemeHelpers::SetImmersiveDarkMode(CurrentWindow, isDark); +} + namespace winrt::PowerRenameUI::implementation { MainWindow::MainWindow() : m_instance{ nullptr }, m_allSelected{ true }, m_managerEvents{ this } { - auto windowNative{ this->try_as<::IWindowNative>() }; winrt::check_bool(windowNative); windowNative->get_WindowHandle(&m_window); + CurrentWindow = m_window; + + // Attach theme handling + theme_listener.AddChangedHandler(handleTheme); + handleTheme(); + Microsoft::UI::WindowId windowId = Microsoft::UI::GetWindowIdFromWindow(m_window); @@ -69,7 +87,6 @@ namespace winrt::PowerRenameUI::implementation } } - Microsoft::UI::Windowing::AppWindow appWindow = Microsoft::UI::Windowing::AppWindow::GetFromWindowId(windowId); appWindow.SetIcon(PowerRenameUIIco); diff --git a/src/modules/powerrename/PowerRenameUILib/PowerRenameUI.vcxproj b/src/modules/powerrename/PowerRenameUILib/PowerRenameUI.vcxproj index 97d78f39d4..4bb7d8663a 100644 --- a/src/modules/powerrename/PowerRenameUILib/PowerRenameUI.vcxproj +++ b/src/modules/powerrename/PowerRenameUILib/PowerRenameUI.vcxproj @@ -69,6 +69,9 @@ _DEBUG;%(PreprocessorDefinitions) + + kernel32.lib;user32.lib;dwmapi.lib;%(AdditionalDependencies) + @@ -77,6 +80,7 @@ true true + kernel32.lib;user32.lib;dwmapi.lib;%(AdditionalDependencies) @@ -161,6 +165,9 @@ {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + {98537082-0fdb-40de-abd8-0dc5a4269bab} + {cc6e41ac-8174-4e8a-8d22-85dd7f4851df} diff --git a/src/runner/runner.vcxproj b/src/runner/runner.vcxproj index 3ddc45fae8..6d89ca5d71 100644 --- a/src/runner/runner.vcxproj +++ b/src/runner/runner.vcxproj @@ -39,7 +39,7 @@ AsInvoker $(OutDir)$(TargetName)$(TargetExt) - Shcore.lib;gdiplus.lib;Msi.lib;WindowsApp.lib;taskschd.lib;Rstrtmgr.lib;Shlwapi.lib;%(AdditionalDependencies) + Shcore.lib;gdiplus.lib;Msi.lib;WindowsApp.lib;taskschd.lib;Rstrtmgr.lib;Shlwapi.lib;dwmapi.lib;%(AdditionalDependencies) false diff --git a/src/settings-ui/Settings.UI/App.xaml.cs b/src/settings-ui/Settings.UI/App.xaml.cs index 26a4702a20..310181053b 100644 --- a/src/settings-ui/Settings.UI/App.xaml.cs +++ b/src/settings-ui/Settings.UI/App.xaml.cs @@ -15,7 +15,6 @@ using Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events; using Microsoft.PowerToys.Telemetry; using Microsoft.UI.Xaml; using Windows.UI.Popups; -using Windows.UI.ViewManagement; using WinRT.Interop; namespace Microsoft.PowerToys.Settings.UI @@ -73,7 +72,7 @@ namespace Microsoft.PowerToys.Settings.UI { if (settingsWindow == null) { - settingsWindow = new MainWindow(); + settingsWindow = new MainWindow(IsDarkTheme()); } settingsWindow.Activate(); @@ -88,6 +87,7 @@ namespace Microsoft.PowerToys.Settings.UI protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) { var cmdArgs = Environment.GetCommandLineArgs(); + var isDark = IsDarkTheme(); if (cmdArgs != null && cmdArgs.Length >= RequiredArgumentsQty) { @@ -143,7 +143,7 @@ namespace Microsoft.PowerToys.Settings.UI if (!ShowOobe && !ShowScoobe) { - settingsWindow = new MainWindow(); + settingsWindow = new MainWindow(isDark); settingsWindow.Activate(); settingsWindow.NavigateToSection(StartupPage); } @@ -152,19 +152,19 @@ namespace Microsoft.PowerToys.Settings.UI // Create the Settings window hidden so that it's fully initialized and // it will be ready to receive the notification if the user opens // the Settings from the tray icon. - settingsWindow = new MainWindow(true); + settingsWindow = new MainWindow(isDark, true); if (ShowOobe) { PowerToysTelemetry.Log.WriteEvent(new OobeStartedEvent()); - OobeWindow oobeWindow = new OobeWindow(OOBE.Enums.PowerToysModules.Overview); + OobeWindow oobeWindow = new OobeWindow(OOBE.Enums.PowerToysModules.Overview, isDark); oobeWindow.Activate(); SetOobeWindow(oobeWindow); } else if (ShowScoobe) { PowerToysTelemetry.Log.WriteEvent(new ScoobeStartedEvent()); - OobeWindow scoobeWindow = new OobeWindow(OOBE.Enums.PowerToysModules.WhatsNew); + OobeWindow scoobeWindow = new OobeWindow(OOBE.Enums.PowerToysModules.WhatsNew, isDark); scoobeWindow.Activate(); SetOobeWindow(scoobeWindow); } @@ -174,7 +174,7 @@ namespace Microsoft.PowerToys.Settings.UI { // For debugging purposes // Window is also needed to show MessageDialog - settingsWindow = new MainWindow(); + settingsWindow = new MainWindow(isDark); settingsWindow.Activate(); ShowMessageDialog("The application cannot be run as a standalone process. Please start the application through the runner.", "Forbidden"); } @@ -203,18 +203,50 @@ namespace Microsoft.PowerToys.Settings.UI return ipcmanager; } + public static string SelectedTheme() + { + return SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Theme.ToUpper(CultureInfo.InvariantCulture); + } + public static bool IsDarkTheme() { - var selectedTheme = SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Theme.ToUpper(CultureInfo.InvariantCulture); - var defaultTheme = new UISettings(); - var uiTheme = defaultTheme.GetColorValue(UIColorType.Background).ToString(System.Globalization.CultureInfo.InvariantCulture); - return selectedTheme == "DARK" || (selectedTheme == "SYSTEM" && uiTheme == "#FF000000"); + var selectedTheme = SelectedTheme(); + return selectedTheme == "DARK" || (selectedTheme == "SYSTEM" && ThemeHelpers.GetAppTheme() == AppTheme.Dark); + } + + public static void HandleThemeChange() + { + var isDark = IsDarkTheme(); + if (settingsWindow != null) + { + var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(settingsWindow); + ThemeHelpers.SetImmersiveDarkMode(hWnd, isDark); + } + + if (oobeWindow != null) + { + var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(oobeWindow); + ThemeHelpers.SetImmersiveDarkMode(hWnd, isDark); + } + + var selectedTheme = SelectedTheme(); + if (selectedTheme == "SYSTEM") + { + themeListener = new ThemeListener(); + themeListener.ThemeChanged += (_) => HandleThemeChange(); + } + else if (themeListener != null) + { + themeListener.Dispose(); + themeListener = null; + } } private static ISettingsUtils settingsUtils = new SettingsUtils(); private static MainWindow settingsWindow; private static OobeWindow oobeWindow; + private static ThemeListener themeListener; public static void ClearSettingsWindow() { diff --git a/src/settings-ui/Settings.UI/MainWindow.xaml.cs b/src/settings-ui/Settings.UI/MainWindow.xaml.cs index b7e111c022..8723a01110 100644 --- a/src/settings-ui/Settings.UI/MainWindow.xaml.cs +++ b/src/settings-ui/Settings.UI/MainWindow.xaml.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. using System; -using System.Drawing; +using ManagedCommon; using Microsoft.PowerLauncher.Telemetry; using Microsoft.PowerToys.Settings.UI.Helpers; using Microsoft.PowerToys.Settings.UI.Library.Utilities; @@ -22,7 +22,7 @@ namespace Microsoft.PowerToys.Settings.UI /// public sealed partial class MainWindow : Window { - public MainWindow(bool createHidden = false) + public MainWindow(bool isDark, bool createHidden = false) { var bootTime = new System.Diagnostics.Stopwatch(); bootTime.Start(); @@ -36,6 +36,12 @@ namespace Microsoft.PowerToys.Settings.UI AppWindow appWindow = AppWindow.GetFromWindowId(windowId); appWindow.SetIcon("icon.ico"); + // Passed by parameter, as it needs to be evaluated ASAP, otherwise there is a white flash + if (isDark) + { + ThemeHelpers.SetImmersiveDarkMode(hWnd, isDark); + } + var placement = Utils.DeserializePlacementOrDefault(hWnd); if (createHidden) { @@ -72,7 +78,7 @@ namespace Microsoft.PowerToys.Settings.UI { if (App.GetOobeWindow() == null) { - App.SetOobeWindow(new OobeWindow(Microsoft.PowerToys.Settings.UI.OOBE.Enums.PowerToysModules.Overview)); + App.SetOobeWindow(new OobeWindow(Microsoft.PowerToys.Settings.UI.OOBE.Enums.PowerToysModules.Overview, App.IsDarkTheme())); } App.GetOobeWindow().Activate(); diff --git a/src/settings-ui/Settings.UI/OobeWindow.xaml.cs b/src/settings-ui/Settings.UI/OobeWindow.xaml.cs index 8fa07ea7ea..713e06be3d 100644 --- a/src/settings-ui/Settings.UI/OobeWindow.xaml.cs +++ b/src/settings-ui/Settings.UI/OobeWindow.xaml.cs @@ -4,6 +4,7 @@ using System; using interop; +using ManagedCommon; using Microsoft.PowerToys.Settings.UI.Helpers; using Microsoft.PowerToys.Settings.UI.OOBE.Enums; using Microsoft.PowerToys.Settings.UI.OOBE.Views; @@ -30,7 +31,7 @@ namespace Microsoft.PowerToys.Settings.UI private IntPtr _hWnd; private AppWindow _appWindow; - public OobeWindow(PowerToysModules initialModule) + public OobeWindow(PowerToysModules initialModule, bool isDark) { this.InitializeComponent(); @@ -40,6 +41,12 @@ namespace Microsoft.PowerToys.Settings.UI _appWindow = AppWindow.GetFromWindowId(_windowId); _appWindow.SetIcon("icon.ico"); + // Passed by parameter, as it needs to be evaluated ASAP, otherwise there is a white flash + if (isDark) + { + ThemeHelpers.SetImmersiveDarkMode(_hWnd, isDark); + } + OverlappedPresenter presenter = _appWindow.Presenter as OverlappedPresenter; presenter.IsMinimizable = false; presenter.IsMaximizable = false; diff --git a/src/settings-ui/Settings.UI/Views/GeneralPage.xaml.cs b/src/settings-ui/Settings.UI/Views/GeneralPage.xaml.cs index 3b74e1ac6b..eb0c40ebfd 100644 --- a/src/settings-ui/Settings.UI/Views/GeneralPage.xaml.cs +++ b/src/settings-ui/Settings.UI/Views/GeneralPage.xaml.cs @@ -78,6 +78,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views break; } + App.HandleThemeChange(); return 0; }