Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Win32 darkmode and fix title bar's context menu #11543

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
97 changes: 86 additions & 11 deletions src/video/windows/SDL_windowswindow.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,41 @@ typedef HRESULT (WINAPI *DwmSetWindowAttribute_t)(HWND hwnd, DWORD dwAttribute,
typedef HRESULT (WINAPI *DwmGetWindowAttribute_t)(HWND hwnd, DWORD dwAttribute, PVOID pvAttribute, DWORD cbAttribute);

// Dark mode support
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
#endif
typedef enum {
UXTHEME_APPMODE_DEFAULT,
UXTHEME_APPMODE_ALLOW_DARK,
UXTHEME_APPMODE_FORCE_DARK,
UXTHEME_APPMODE_FORCE_LIGHT,
UXTHEME_APPMODE_MAX
} WinPreferredAppMode;
typedef enum {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add blank lines between declarations.

WCA_UNDEFINED = 0,
WCA_USEDARKMODECOLORS = 26,
WCA_LAST = 27
} WINDOWCOMPOSITIONATTRIB;
typedef struct {
WINDOWCOMPOSITIONATTRIB Attrib;
PVOID pvData;
SIZE_T cbData;
} WINDOWCOMPOSITIONATTRIBDATA;
typedef struct {
ULONG dwOSVersionInfoSize;
ULONG dwMajorVersion;
ULONG dwMinorVersion;
ULONG dwBuildNumber;
ULONG dwPlatformId;
WCHAR szCSDVersion[128];
} NT_OSVERSIONINFOW;
typedef bool (WINAPI *ShouldAppsUseDarkMode_t)(void);
typedef void (WINAPI *AllowDarkModeForWindow_t)(HWND, bool);
typedef void (WINAPI *AllowDarkModeForApp_t)(bool);
typedef void (WINAPI *FlushMenuThemes_t)(void);
typedef void (WINAPI *RefreshImmersiveColorPolicyState_t)(void);
typedef bool (WINAPI *ShouldSystemUseDarkMode_t)(void);
typedef WinPreferredAppMode (WINAPI *SetPreferredAppMode_t)(WinPreferredAppMode);
typedef bool (WINAPI *IsDarkModeAllowedForApp_t)(void);
typedef BOOL (WINAPI *SetWindowCompositionAttribute_t)(HWND, const WINDOWCOMPOSITIONATTRIBDATA*);
typedef void (NTAPI *RtlGetVersion_t)(NT_OSVERSIONINFOW*);

// Corner rounding support (Win 11+)
#ifndef DWMWA_WINDOW_CORNER_PREFERENCE
Expand Down Expand Up @@ -2226,15 +2258,58 @@ bool WIN_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool foc

void WIN_UpdateDarkModeForHWND(HWND hwnd)
{
SDL_SharedObject *handle = SDL_LoadObject("dwmapi.dll");
if (handle) {
DwmSetWindowAttribute_t DwmSetWindowAttributeFunc = (DwmSetWindowAttribute_t)SDL_LoadFunction(handle, "DwmSetWindowAttribute");
if (DwmSetWindowAttributeFunc) {
// FIXME: Do we need to traverse children?
BOOL value = (SDL_GetSystemTheme() == SDL_SYSTEM_THEME_DARK) ? TRUE : FALSE;
DwmSetWindowAttributeFunc(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value));
SDL_SharedObject *ntdll = SDL_LoadObject("ntdll.dll");
if (!ntdll)
return;
// There is no function to get Windows build number, so let's get it here via RtlGetVersion
RtlGetVersion_t RtlGetVersionFunc = (RtlGetVersion_t)SDL_LoadFunction(ntdll, "RtlGetVersion");
NT_OSVERSIONINFOW os_info;
os_info.dwOSVersionInfoSize = sizeof(NT_OSVERSIONINFOW);
os_info.dwBuildNumber = 0;
if (RtlGetVersionFunc)
RtlGetVersionFunc(&os_info);
SDL_UnloadObject(ntdll);
os_info.dwBuildNumber &= ~0xF0000000;
if (os_info.dwBuildNumber < 17763)
return; // Too old to support dark mode
SDL_SharedObject *uxtheme = SDL_LoadObject("uxtheme.dll");
if (!uxtheme)
return;
RefreshImmersiveColorPolicyState_t RefreshImmersiveColorPolicyStateFunc = (RefreshImmersiveColorPolicyState_t)SDL_LoadFunction(uxtheme, MAKEINTRESOURCEA(104));
ShouldAppsUseDarkMode_t ShouldAppsUseDarkModeFunc = (ShouldAppsUseDarkMode_t)SDL_LoadFunction(uxtheme, MAKEINTRESOURCEA(132));
AllowDarkModeForWindow_t AllowDarkModeForWindowFunc = (AllowDarkModeForWindow_t)SDL_LoadFunction(uxtheme, MAKEINTRESOURCEA(133));
if (os_info.dwBuildNumber < 18362) {
AllowDarkModeForApp_t AllowDarkModeForAppFunc = (AllowDarkModeForApp_t)SDL_LoadFunction(uxtheme, MAKEINTRESOURCEA(135));
if (AllowDarkModeForAppFunc)
AllowDarkModeForAppFunc(true);
} else {
SetPreferredAppMode_t SetPreferredAppModeFunc = (SetPreferredAppMode_t)SDL_LoadFunction(uxtheme, MAKEINTRESOURCEA(135));
if (SetPreferredAppModeFunc)
SetPreferredAppModeFunc(UXTHEME_APPMODE_ALLOW_DARK);
}
if (RefreshImmersiveColorPolicyStateFunc)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SDL style is:

if (true) {
    statement;
} else {
    statement;
}

RefreshImmersiveColorPolicyStateFunc();
if (AllowDarkModeForWindowFunc)
AllowDarkModeForWindowFunc(hwnd, true);
BOOL value;
// Check dark mode using ShouldAppsUseDarkMode, but use SDL_GetSystemTheme as a fallback
if (ShouldAppsUseDarkModeFunc)
value = ShouldAppsUseDarkModeFunc() ? TRUE : FALSE;
else
value = (SDL_GetSystemTheme() == SDL_SYSTEM_THEME_DARK) ? TRUE : FALSE;
SDL_UnloadObject(uxtheme);
if (os_info.dwBuildNumber < 18362)
SetProp(hwnd, TEXT("UseImmersiveDarkModeColors"), SDL_reinterpret_cast(HANDLE, SDL_static_cast(INT_PTR, value)));
else {
SDL_SharedObject *user32 = SDL_LoadObject("user32.dll");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be replaced with GetModuleHandle() and GetProcAddress() if you want to avoid a separate load.

if (user32) {
SetWindowCompositionAttribute_t SetWindowCompositionAttributeFunc = (SetWindowCompositionAttribute_t)SDL_LoadFunction(user32, "SetWindowCompositionAttribute");
if (SetWindowCompositionAttributeFunc) {
WINDOWCOMPOSITIONATTRIBDATA data = { WCA_USEDARKMODECOLORS, &value, sizeof(value) };
SetWindowCompositionAttributeFunc(hwnd, &data);
}
SDL_UnloadObject(user32);
}
SDL_UnloadObject(handle);
}
}

Expand Down
Loading