Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/common/Themes/Themes.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="dark_menu.h" />
<ClInclude Include="icon_helpers.h" />
<ClInclude Include="theme_listener.h" />
<ClInclude Include="theme_helpers.h" />
Expand Down
97 changes: 97 additions & 0 deletions src/common/Themes/dark_menu.h
Original file line number Diff line number Diff line change
@@ -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 <windows.h>

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<SetPreferredAppModeFn>(
GetProcAddress(uxtheme, MAKEINTRESOURCEA(135)));
result.flushMenuThemes = reinterpret_cast<FlushMenuThemesFn>(
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();
}
}
}
18 changes: 17 additions & 1 deletion src/modules/ZoomIt/ZoomIt/Zoomit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include <common/utils/logger_helper.h>
#include <common/utils/winapi_error.h>
#include <common/utils/gpo.h>
#include <common/Themes/dark_menu.h>
#include <array>
#include <vector>
#endif // __ZOOMIT_POWERTOYS__
Expand Down Expand Up @@ -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;
Expand Down
Loading