diff --git a/src/common/Themes/Themes.vcxproj b/src/common/Themes/Themes.vcxproj index bace40814b3d..369e7feba194 100644 --- a/src/common/Themes/Themes.vcxproj +++ b/src/common/Themes/Themes.vcxproj @@ -30,6 +30,7 @@ + diff --git a/src/common/Themes/dark_menu.h b/src/common/Themes/dark_menu.h new file mode 100644 index 000000000000..355fc839979b --- /dev/null +++ b/src/common/Themes/dark_menu.h @@ -0,0 +1,97 @@ +// Theme-aware rendering for classic Win32 popup menus (HMENU / TrackPopupMenu). +// +// Lets a Win32 tray/popup menu follow the OS light/dark theme. It puts the +// process into the matching app theme via uxtheme's preferred-app-mode entry +// points -- the same mechanism the OS uses to render dark menus, as already +// used by ZoomIt and File Explorer -- and then the system draws the real +// themed menu. Native keyboard, accessibility, checkmarks, separators and DPI +// are all preserved; only the colors change. +// +// Theme detection reads the documented AppsUseLightTheme value, fresh on each +// call, so a live light<->dark switch is reflected without restarting. +// +// Drop-in for any PowerToys system-tray utility: +// +// theme::dark_menu::SetAppMode(theme::dark_menu::IsSystemDarkMode()); +// TrackPopupMenu(menu, ...); +#pragma once + +#include + +namespace theme::dark_menu +{ + namespace details + { + // uxtheme preferred-app-mode values (order matters). + enum class PreferredAppMode + { + Default = 0, + AllowDark, + ForceDark, + ForceLight, + Max + }; + + using SetPreferredAppModeFn = PreferredAppMode(WINAPI*)(PreferredAppMode); + using FlushMenuThemesFn = void(WINAPI*)(); + + struct Ordinals + { + SetPreferredAppModeFn setPreferredAppMode = nullptr; + FlushMenuThemesFn flushMenuThemes = nullptr; + }; + + // Resolved once per process: an inline function's local static is a single + // shared instance across all translation units that include this header. + inline const Ordinals& GetOrdinals() noexcept + { + static const Ordinals ordinals = []() noexcept { + Ordinals result{}; + HMODULE uxtheme = GetModuleHandleW(L"uxtheme.dll"); + if (uxtheme == nullptr) + { + uxtheme = LoadLibraryExW(L"uxtheme.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + } + if (uxtheme != nullptr) + { + // Ordinal 135 = SetPreferredAppMode, ordinal 136 = FlushMenuThemes. + result.setPreferredAppMode = reinterpret_cast( + GetProcAddress(uxtheme, MAKEINTRESOURCEA(135))); + result.flushMenuThemes = reinterpret_cast( + GetProcAddress(uxtheme, MAKEINTRESOURCEA(136))); + } + return result; + }(); + return ordinals; + } + } + + // True if the user's app theme is dark, via the documented AppsUseLightTheme value. + inline bool IsSystemDarkMode() noexcept + { + DWORD value = 1; // default to light if the value is missing + DWORD size = sizeof(value); + RegGetValueW(HKEY_CURRENT_USER, + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", + L"AppsUseLightTheme", RRF_RT_REG_DWORD, nullptr, &value, &size); + return value == 0; + } + + // Make this process's classic popup menus render dark or light. Cheap and + // idempotent -- call right before TrackPopupMenu so the menu always matches + // the current theme, including after a live theme switch. No-op if those + // entry points are unavailable. + inline void SetAppMode(bool dark) noexcept + { + const details::Ordinals& ordinals = details::GetOrdinals(); + if (ordinals.setPreferredAppMode != nullptr) + { + ordinals.setPreferredAppMode(dark ? details::PreferredAppMode::ForceDark + : details::PreferredAppMode::ForceLight); + } + if (ordinals.flushMenuThemes != nullptr) + { + ordinals.flushMenuThemes(); + } + } +} diff --git a/src/modules/ZoomIt/ZoomIt/Zoomit.cpp b/src/modules/ZoomIt/ZoomIt/Zoomit.cpp index 5c6065e9a5bb..23e76b13fc51 100644 --- a/src/modules/ZoomIt/ZoomIt/Zoomit.cpp +++ b/src/modules/ZoomIt/ZoomIt/Zoomit.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #endif // __ZOOMIT_POWERTOYS__ @@ -10163,8 +10164,23 @@ LRESULT APIENTRY MainWndProc( InsertMenu( hPopupMenu, 0, MF_BYPOSITION|MF_SEPARATOR, 0, NULL ); InsertMenu( hPopupMenu, 0, MF_BYPOSITION, IDC_OPTIONS, L"&Options" ); } - // Apply dark mode theme to the menu + // Make the popup theme-aware (dark/light). +#ifdef __ZOOMIT_POWERTOYS__ + // Shared common helper: the OS renders the dark/light menu (native + // chrome, keyboard, accessibility). Reusable by other Win32 modules + // such as the runner tray menu. + // + // Detect the theme fresh at open time (respecting ZoomIt's theme + // override) so a live light<->dark switch is reflected without a + // restart. IsDarkModeEnabled() uses uxtheme's cached state, which a + // runtime theme switch may not refresh, whereas the AppsUseLightTheme + // registry value (read by IsSystemDarkMode) is always current. + const bool useDarkMenu = ( g_ThemeOverride == 1 ) || + ( g_ThemeOverride != 0 && theme::dark_menu::IsSystemDarkMode() ); + theme::dark_menu::SetAppMode( useDarkMenu ); +#else ApplyDarkModeToMenu( hPopupMenu ); +#endif TrackPopupMenu( hPopupMenu, 0, pt.x , pt.y, 0, hWnd, NULL ); DestroyMenu( hPopupMenu ); break;