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

[SDL3] Win32 darkmode doesn't apply for title bar's context menu #11535

Closed
Pixelsuft opened this issue Nov 25, 2024 · 5 comments
Closed

[SDL3] Win32 darkmode doesn't apply for title bar's context menu #11535

Pixelsuft opened this issue Nov 25, 2024 · 5 comments
Milestone

Comments

@Pixelsuft
Copy link
Contributor

Just a small visual bug:
Screenshot

@slouken slouken added this to the 3.2.0 milestone Nov 25, 2024
@AntTheAlchemist
Copy link
Contributor

I noticed the same with all other Windows 10 apps as well. Probably not an SDL thing.

@Pixelsuft
Copy link
Contributor Author

Nope:
image

BTW, here is C++ code I used for my SDL2 game:

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <app.hpp>

#include "SDL_syswm.h"
#ifdef _MSC_VER
#pragma comment(lib, "ntdll.lib")
#endif

#ifndef NTAPI
#define NTAPI __stdcall
#endif
#ifndef NTSYSAPI
#define NTSYSAPI declspec(dllimport)
#endif

#define LOAD_FUNC_ORD(func_name, func_ord) do { \
	win_dark.func_name = reinterpret_cast<func_name##_t>(GetProcAddress(win_dark.uxtheme_handle, MAKEINTRESOURCEA(func_ord))); \
	if (win_dark.func_name == NULL) { \
		app->show_error("failed to load uxtheme.dll func " #func_name); \
		FreeLibrary(win_dark.uxtheme_handle); \
		return true; \
	} \
} while (0)

typedef LONG WIN_NTDLL_NTSTATUS;

typedef struct {
	ULONG dwOSVersionInfoSize;
	ULONG dwMajorVersion;
	ULONG dwMinorVersion;
	ULONG dwBuildNumber;
	ULONG dwPlatformId;
	WCHAR szCSDVersion[128];
	USHORT wServicePackMajor;
	USHORT wServicePackMinor;
	USHORT wSuiteMask;
	UCHAR wProductType;
	UCHAR wReserved;
} WIN_NTDLL_OSVERSIONINFOEXW;

typedef enum {
	WIN_APPMODE_DEFAULT,
	WIN_APPMODE_ALLOW_DARK,
	WIN_APPMODE_FORCE_DARK,
	WIN_APPMODE_FORCE_LIGHT,
	WIN_APPMODE_MAX
} WinPreferredAppMode;

enum WINDOWCOMPOSITIONATTRIB {
	WCA_UNDEFINED = 0,
	WCA_NCRENDERING_ENABLED = 1,
	WCA_NCRENDERING_POLICY = 2,
	WCA_TRANSITIONS_FORCEDISABLED = 3,
	WCA_ALLOW_NCPAINT = 4,
	WCA_CAPTION_BUTTON_BOUNDS = 5,
	WCA_NONCLIENT_RTL_LAYOUT = 6,
	WCA_FORCE_ICONIC_REPRESENTATION = 7,
	WCA_EXTENDED_FRAME_BOUNDS = 8,
	WCA_HAS_ICONIC_BITMAP = 9,
	WCA_THEME_ATTRIBUTES = 10,
	WCA_NCRENDERING_EXILED = 11,
	WCA_NCADORNMENTINFO = 12,
	WCA_EXCLUDED_FROM_LIVEPREVIEW = 13,
	WCA_VIDEO_OVERLAY_ACTIVE = 14,
	WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15,
	WCA_DISALLOW_PEEK = 16,
	WCA_CLOAK = 17,
	WCA_CLOAKED = 18,
	WCA_ACCENT_POLICY = 19,
	WCA_FREEZE_REPRESENTATION = 20,
	WCA_EVER_UNCLOAKED = 21,
	WCA_VISUAL_OWNER = 22,
	WCA_HOLOGRAPHIC = 23,
	WCA_EXCLUDED_FROM_DDA = 24,
	WCA_PASSIVEUPDATEMODE = 25,
	WCA_USEDARKMODECOLORS = 26,
	WCA_LAST = 27
};

typedef struct {
	WINDOWCOMPOSITIONATTRIB Attrib;
	PVOID pvData;
	SIZE_T cbData;
} WINDOWCOMPOSITIONATTRIBDATA;

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 *IsDarkModeAllowedForWindow_t)(HWND);
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 struct {
	HMODULE uxtheme_handle;
	HMODULE user32_handle;
	ShouldAppsUseDarkMode_t ShouldAppsUseDarkMode;
	AllowDarkModeForWindow_t AllowDarkModeForWindow;
	AllowDarkModeForApp_t AllowDarkModeForApp;
	FlushMenuThemes_t FlushMenuThemes;
	RefreshImmersiveColorPolicyState_t RefreshImmersiveColorPolicyState;
	IsDarkModeAllowedForWindow_t IsDarkModeAllowedForWindow;
	ShouldSystemUseDarkMode_t ShouldSystemUseDarkMode;
	SetPreferredAppMode_t SetPreferredAppMode;
	IsDarkModeAllowedForApp_t IsDarkModeAllowedForApp;
	SetWindowCompositionAttribute_t SetWindowCompositionAttribute;
	DWORD build_num;
} win_dark_type;

static win_dark_type win_dark;

// Ugly import from ntdll
extern "C" NTSYSAPI WIN_NTDLL_NTSTATUS NTAPI RtlGetVersion(WIN_NTDLL_OSVERSIONINFOEXW* ver_info);

bool app_fix_win32_theme(App* app) {
	win_dark.uxtheme_handle = NULL;
	win_dark.user32_handle = NULL;
	WIN_NTDLL_OSVERSIONINFOEXW os_ver;
	os_ver.dwOSVersionInfoSize = sizeof(WIN_NTDLL_OSVERSIONINFOEXW);
	RtlGetVersion(&os_ver);
	win_dark.build_num = os_ver.dwBuildNumber;
	win_dark.build_num &= ~0xF0000000;
	if (win_dark.build_num < 17763)
		return false; // windows is too old
	win_dark.uxtheme_handle = LoadLibraryExW(L"uxtheme.dll", NULL, LOAD_IGNORE_CODE_AUTHZ_LEVEL | LOAD_LIBRARY_SEARCH_SYSTEM32);
	if (win_dark.uxtheme_handle == NULL) {
		app->show_error("failed to load uxtheme.dll");
		return true;
	};
	win_dark.user32_handle = LoadLibraryExW(L"user32.dll", NULL, LOAD_IGNORE_CODE_AUTHZ_LEVEL | LOAD_LIBRARY_SEARCH_SYSTEM32);
	if (win_dark.user32_handle == NULL) {
		FreeLibrary(win_dark.uxtheme_handle);
		app->show_error("failed to load user32.dll");
		return true;
	};
	win_dark.SetWindowCompositionAttribute = (SetWindowCompositionAttribute_t)GetProcAddress(win_dark.user32_handle, "SetWindowCompositionAttribute");
	LOAD_FUNC_ORD(ShouldAppsUseDarkMode, 132);
	LOAD_FUNC_ORD(AllowDarkModeForWindow, 133);
	if (win_dark.build_num < 18362) {
		win_dark.SetPreferredAppMode = NULL;
		LOAD_FUNC_ORD(AllowDarkModeForApp, 135);
	}
	else {
		win_dark.AllowDarkModeForApp = NULL;
		LOAD_FUNC_ORD(SetPreferredAppMode, 135);
	}
	LOAD_FUNC_ORD(FlushMenuThemes, 136);
	LOAD_FUNC_ORD(RefreshImmersiveColorPolicyState, 104);
	LOAD_FUNC_ORD(IsDarkModeAllowedForWindow, 137);
	if (win_dark.build_num >= 18290)
		LOAD_FUNC_ORD(ShouldSystemUseDarkMode, 138);
	else
		win_dark.ShouldSystemUseDarkMode = NULL;
	if (win_dark.build_num >= 18334)
		LOAD_FUNC_ORD(IsDarkModeAllowedForApp, 139);
	else
		win_dark.IsDarkModeAllowedForApp = NULL;
	// Get HWND
	SDL_SysWMinfo wm_info;
	SDL_VERSION(&wm_info.version);
	HWND hwnd = NULL;
	if (SDL_GetWindowWMInfo(app->w, &wm_info) == SDL_TRUE)
		hwnd = wm_info.info.win.window;
	else {
		FreeLibrary(win_dark.user32_handle);
		FreeLibrary(win_dark.uxtheme_handle);
		app->show_error("Failed to get window HWND");
		return true;
	}
	// Let's begin our magic
	if (win_dark.AllowDarkModeForApp != NULL)
		win_dark.AllowDarkModeForApp(true);
	if (win_dark.SetPreferredAppMode != NULL)
		win_dark.SetPreferredAppMode(WIN_APPMODE_ALLOW_DARK);
	win_dark.RefreshImmersiveColorPolicyState();
	win_dark.AllowDarkModeForWindow(hwnd, true);
	bool enable_dark = win_dark.ShouldAppsUseDarkMode();
	BOOL win_dark = enable_dark ? TRUE : FALSE;
	if (win_dark.build_num < 18362)
		SetPropW(hwnd, L"UseImmersiveDarkModeColors", reinterpret_cast<HANDLE>(static_cast<INT_PTR>(win_dark)));
	else if (win_dark.SetWindowCompositionAttribute != NULL) {
		WINDOWCOMPOSITIONATTRIBDATA data = { WCA_USEDARKMODECOLORS, &win_dark, sizeof(win_dark) };
		win_dark.SetWindowCompositionAttribute(hwnd, &data);
	}
	FreeLibrary(win_dark.user32_handle);
	FreeLibrary(win_dark.uxtheme_handle);
	return false;
}
#endif

@AntTheAlchemist
Copy link
Contributor

Microsoft's very own Settings app doesn't do it:

image

Neither does Visual Studio 2022

image

@slouken slouken modified the milestones: 3.2.0, 3.x Nov 26, 2024
@slouken
Copy link
Collaborator

slouken commented Nov 26, 2024

Feel free to submit a PR to fix this. I'm bumping it out of the 3.2.0 milestone so we can focus on shipping blockers.

@Squall-Leonhart
Copy link

the title bar context menu is one of many that is not dark mode implemented in the shell, and must be handled explicitly by the application developer.

@slouken slouken closed this as completed Dec 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants