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
13 changes: 13 additions & 0 deletions include/SDL3/SDL_video.h
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,19 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetVideoDriver(int index);
*/
extern SDL_DECLSPEC const char * SDLCALL SDL_GetCurrentVideoDriver(void);

/**
* Set the preferred system theme.
* This is a hint to the system to use the preferred theme for the window
*
* \param theme the preferred system theme, light, dark, or unknown for auto.
* \threadsafety This function should only be called on the main thread.
*
* \since This function is available since SDL 3.6.0.
*
* \sa SDL_GetSystemTheme
*/
extern SDL_DECLSPEC void SDLCALL SDL_SetPreferredSystemTheme(SDL_SystemTheme theme);

/**
* Get the current system theme.
*
Expand Down
63 changes: 20 additions & 43 deletions src/core/windows/SDL_windows.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,6 @@ typedef enum {
UXTHEME_APPMODE_MAX
} UxthemePreferredAppMode;

typedef enum {
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;
Expand All @@ -80,7 +68,6 @@ typedef void (WINAPI *AllowDarkModeForWindow_t)(HWND, bool);
typedef void (WINAPI *AllowDarkModeForApp_t)(bool);
typedef void (WINAPI *RefreshImmersiveColorPolicyState_t)(void);
typedef UxthemePreferredAppMode (WINAPI *SetPreferredAppMode_t)(UxthemePreferredAppMode);
typedef BOOL (WINAPI *SetWindowCompositionAttribute_t)(HWND, const WINDOWCOMPOSITIONATTRIBDATA *);
typedef void (NTAPI *RtlGetVersion_t)(NT_OSVERSIONINFOW *);

// Fake window to help with DirectInput events.
Expand Down Expand Up @@ -438,12 +425,25 @@ bool WIN_WindowRectValid(const RECT *rect)
return (rect->right > 0);
}

void WIN_UpdateDarkModeForHWND(HWND hwnd)
UxthemePreferredAppMode WIN_PreferredUXTheme(const SDL_SystemTheme theme)
{
switch (theme) {
case SDL_SYSTEM_THEME_DARK:
return UXTHEME_APPMODE_FORCE_DARK;
case SDL_SYSTEM_THEME_LIGHT:
return UXTHEME_APPMODE_FORCE_LIGHT;
case SDL_SYSTEM_THEME_UNKNOWN:
default:
return UXTHEME_APPMODE_ALLOW_DARK;
}
}

BOOL WIN_UpdatePreferredTheme(SDL_SystemTheme preferredTheme)
{
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
HMODULE ntdll = LoadLibrary(TEXT("ntdll.dll"));
if (!ntdll) {
return;
return false;
}
// There is no function to get Windows build number, so let's get it here via RtlGetVersion
RtlGetVersion_t RtlGetVersionFunc = (RtlGetVersion_t)GetProcAddress(ntdll, "RtlGetVersion");
Expand All @@ -457,52 +457,29 @@ void WIN_UpdateDarkModeForHWND(HWND hwnd)
os_info.dwBuildNumber &= ~0xF0000000;
if (os_info.dwBuildNumber < 17763) {
// Too old to support dark mode
return;
return false;
}
HMODULE uxtheme = LoadLibrary(TEXT("uxtheme.dll"));
if (!uxtheme) {
return;
return false;
}
RefreshImmersiveColorPolicyState_t RefreshImmersiveColorPolicyStateFunc = (RefreshImmersiveColorPolicyState_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(104));
ShouldAppsUseDarkMode_t ShouldAppsUseDarkModeFunc = (ShouldAppsUseDarkMode_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(132));
AllowDarkModeForWindow_t AllowDarkModeForWindowFunc = (AllowDarkModeForWindow_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(133));
if (os_info.dwBuildNumber < 18362) {
AllowDarkModeForApp_t AllowDarkModeForAppFunc = (AllowDarkModeForApp_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(135));
if (AllowDarkModeForAppFunc) {
AllowDarkModeForAppFunc(true);
AllowDarkModeForAppFunc(preferredTheme != SDL_SYSTEM_THEME_LIGHT);
}
} else {
SetPreferredAppMode_t SetPreferredAppModeFunc = (SetPreferredAppMode_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(135));
if (SetPreferredAppModeFunc) {
SetPreferredAppModeFunc(UXTHEME_APPMODE_ALLOW_DARK);
SetPreferredAppModeFunc(WIN_PreferredUXTheme(preferredTheme));
}
}
if (RefreshImmersiveColorPolicyStateFunc) {
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;
}
FreeLibrary(uxtheme);
if (os_info.dwBuildNumber < 18362) {
SetProp(hwnd, TEXT("UseImmersiveDarkModeColors"), SDL_reinterpret_cast(HANDLE, SDL_static_cast(INT_PTR, value)));
} else {
HMODULE user32 = GetModuleHandle(TEXT("user32.dll"));
if (user32) {
SetWindowCompositionAttribute_t SetWindowCompositionAttributeFunc = (SetWindowCompositionAttribute_t)GetProcAddress(user32, "SetWindowCompositionAttribute");
if (SetWindowCompositionAttributeFunc) {
WINDOWCOMPOSITIONATTRIBDATA data = { WCA_USEDARKMODECOLORS, &value, sizeof(value) };
SetWindowCompositionAttributeFunc(hwnd, &data);
}
}
}
return true;
#endif
}

Expand Down
2 changes: 1 addition & 1 deletion src/core/windows/SDL_windows.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ extern void WIN_RectToRECT(const SDL_Rect *sdlrect, RECT *winrect);
// Returns false if a window client rect is not valid
extern bool WIN_WindowRectValid(const RECT *rect);

extern void WIN_UpdateDarkModeForHWND(HWND hwnd);
extern BOOL WIN_UpdatePreferredTheme(SDL_SystemTheme preferredTheme);

extern HICON WIN_CreateIconFromSurface(SDL_Surface *surface);

Expand Down
3 changes: 2 additions & 1 deletion src/dynapi/SDL_dynapi.sym
Original file line number Diff line number Diff line change
Expand Up @@ -1240,7 +1240,7 @@ SDL3_0.0.0 {
SDL_GetDefaultTextureScaleMode;
SDL_CreateGPURenderState;
SDL_SetGPURenderStateFragmentUniforms;
SDL_SetGPURenderState;
SDL_SetRenderGPUState;
SDL_DestroyGPURenderState;
SDL_SetWindowProgressState;
SDL_SetWindowProgressValue;
Expand Down Expand Up @@ -1268,6 +1268,7 @@ SDL3_0.0.0 {
SDL_GetPenDeviceType;
SDL_CreateAnimatedCursor;
SDL_RotateSurface;
SDL_SetPreferredTheme;
# extra symbols go here (don't modify this line)
local: *;
};
1 change: 1 addition & 0 deletions src/dynapi/SDL_dynapi_overrides.h
Original file line number Diff line number Diff line change
Expand Up @@ -1294,3 +1294,4 @@
#define SDL_GetPenDeviceType SDL_GetPenDeviceType_REAL
#define SDL_CreateAnimatedCursor SDL_CreateAnimatedCursor_REAL
#define SDL_RotateSurface SDL_RotateSurface_REAL
#define SDL_SetPreferredSystemTheme SDL_SetPreferredSystemTheme_REAL
1 change: 1 addition & 0 deletions src/dynapi/SDL_dynapi_procs.h
Original file line number Diff line number Diff line change
Expand Up @@ -1302,3 +1302,4 @@ SDL_DYNAPI_PROC(int,SDL_GetSystemPageSize,(void),(),return)
SDL_DYNAPI_PROC(SDL_PenDeviceType,SDL_GetPenDeviceType,(SDL_PenID a),(a),return)
SDL_DYNAPI_PROC(SDL_Cursor*,SDL_CreateAnimatedCursor,(SDL_CursorFrameInfo *a,int b,int c,int d),(a,b,c,d),return)
SDL_DYNAPI_PROC(SDL_Surface*,SDL_RotateSurface,(SDL_Surface *a,float b),(a,b),return)
SDL_DYNAPI_PROC(void,SDL_SetPreferredSystemTheme,(SDL_SystemTheme theme),(theme),return)
21 changes: 19 additions & 2 deletions src/tray/windows/SDL_tray.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

#include "../SDL_tray_utils.h"
#include "../../core/windows/SDL_windows.h"
#include "../../video/windows/SDL_windowsvideo.h"

#include <windowsx.h>
#include <shellapi.h>
Expand Down Expand Up @@ -100,6 +101,20 @@ static SDL_TrayEntry *find_entry_with_id(SDL_Tray *tray, UINT_PTR id)
return find_entry_in_menu(tray->menu, id);
}

static void TrayUpdateDarkMode(HWND hwnd, BOOL value)
{
HMODULE user32 = GetModuleHandle(TEXT("user32.dll"));
if (!user32) {
return;
}

WINDOWCOMPOSITIONATTRIBDATA data = {
WCA_USEDARKMODECOLORS,
&value, sizeof(value)
};
SDL_GetVideoDevice()->internal->SetWindowCompositionAttribute(hwnd, &data);
}

LRESULT CALLBACK TrayWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
SDL_Tray *tray = (SDL_Tray *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
SDL_TrayEntry *entry = NULL;
Expand Down Expand Up @@ -142,7 +157,9 @@ LRESULT CALLBACK TrayWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lPar

case WM_SETTINGCHANGE:
if (wParam == 0 && lParam != 0 && SDL_wcscmp((wchar_t *)lParam, L"ImmersiveColorSet") == 0) {
WIN_UpdateDarkModeForHWND(hwnd);
if (SDL_GetVideoDevice()->preferred_theme == SDL_SYSTEM_THEME_UNKNOWN) {
TrayUpdateDarkMode(hwnd, SDL_GetSystemTheme() == SDL_SYSTEM_THEME_DARK);
}
}
break;

Expand Down Expand Up @@ -288,7 +305,7 @@ SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
tray->hwnd = CreateWindowEx(0, TEXT("SDL_TRAY"), NULL, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, NULL, NULL);

WIN_UpdateDarkModeForHWND(tray->hwnd);
TrayUpdateDarkMode(tray->hwnd, SDL_GetSystemTheme() == SDL_SYSTEM_THEME_DARK);

SDL_zero(tray->nid);
tray->nid.cbSize = sizeof(NOTIFYICONDATAW);
Expand Down
7 changes: 7 additions & 0 deletions src/video/SDL_sysvideo.h
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,12 @@ struct SDL_VideoDevice
// Display the system-level window menu
void (*ShowWindowSystemMenu)(SDL_Window *window, int x, int y);

/*
* Set the preferred theme (light, dark, etc) for the application
* This is a hint, and may be ignored by the platform.
*/
void (*SetPreferredTheme)(SDL_VideoDevice *_this, SDL_SystemTheme theme);

/* * * */
// Data common to all drivers
SDL_ThreadID thread;
Expand All @@ -421,6 +427,7 @@ struct SDL_VideoDevice
bool setting_display_mode;
Uint32 device_caps;
SDL_SystemTheme system_theme;
SDL_SystemTheme preferred_theme;
bool screen_keyboard_shown;

/* * * */
Expand Down
10 changes: 10 additions & 0 deletions src/video/SDL_video.c
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,16 @@ bool SDL_OnVideoThread(void)
return (_this && SDL_GetCurrentThreadID() == _this->thread);
}

void SDL_SetPreferredSystemTheme(SDL_SystemTheme theme)
{
if (_this && theme != _this->preferred_theme) {
_this->preferred_theme = theme;
if (_this->SetPreferredTheme) {
_this->SetPreferredTheme(_this, theme);
}
}
}

void SDL_SetSystemTheme(SDL_SystemTheme theme)
{
if (_this && theme != _this->system_theme) {
Expand Down
6 changes: 4 additions & 2 deletions src/video/windows/SDL_windowsevents.c
Original file line number Diff line number Diff line change
Expand Up @@ -2416,8 +2416,10 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara

case WM_SETTINGCHANGE:
if (wParam == 0 && lParam != 0 && SDL_wcscmp((wchar_t *)lParam, L"ImmersiveColorSet") == 0) {
SDL_SetSystemTheme(WIN_GetSystemTheme());
WIN_UpdateDarkModeForHWND(hwnd);
if (SDL_GetVideoDevice()->preferred_theme == SDL_SYSTEM_THEME_UNKNOWN) {
WIN_SetDarkModeColorsForHWND(SDL_GetVideoDevice(), data->window, WIN_GetSystemTheme() == SDL_SYSTEM_THEME_DARK ? TRUE : FALSE);
SDL_SetSystemTheme(WIN_GetSystemTheme());
}
}
if (wParam == SPI_SETMOUSE || wParam == SPI_SETMOUSESPEED) {
WIN_UpdateMouseSystemScale();
Expand Down
27 changes: 27 additions & 0 deletions src/video/windows/SDL_windowsvideo.c
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,10 @@ static SDL_VideoDevice *WIN_CreateDevice(void)
}
device->internal = data;
device->system_theme = WIN_GetSystemTheme();
device->preferred_theme = SDL_SYSTEM_THEME_UNKNOWN;
device->SetPreferredTheme = WIN_SetPreferredTheme;

WIN_SetPreferredTheme(device, device->system_theme);

#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
data->userDLL = SDL_LoadObject("USER32.DLL");
Expand All @@ -260,6 +264,7 @@ static SDL_VideoDevice *WIN_CreateDevice(void)
data->DisplayConfigGetDeviceInfo = (LONG (WINAPI *)(DISPLAYCONFIG_DEVICE_INFO_HEADER*))SDL_LoadFunction(data->userDLL, "DisplayConfigGetDeviceInfo");
data->GetPointerType = (BOOL (WINAPI *)(UINT32, POINTER_INPUT_TYPE *))SDL_LoadFunction(data->userDLL, "GetPointerType");
data->GetPointerPenInfo = (BOOL (WINAPI *)(UINT32, POINTER_PEN_INFO *))SDL_LoadFunction(data->userDLL, "GetPointerPenInfo");
data->SetWindowCompositionAttribute = (BOOL (WINAPI *)(HWND, const WINDOWCOMPOSITIONATTRIBDATA *))SDL_LoadFunction(data->userDLL, "SetWindowCompositionAttribute");
/* *INDENT-ON* */ // clang-format on
} else {
SDL_ClearError();
Expand Down Expand Up @@ -877,4 +882,26 @@ bool WIN_IsPerMonitorV2DPIAware(SDL_VideoDevice *_this)
return false;
}

void WIN_SetPreferredTheme(SDL_VideoDevice *_this, SDL_SystemTheme preferred)
{
SDL_Window *window;
SDL_SystemTheme theme;

if (!WIN_UpdatePreferredTheme(preferred)) {
return;
}

if (preferred == SDL_SYSTEM_THEME_UNKNOWN) {
theme = WIN_GetSystemTheme();
} else {
theme = preferred;
}

for (window = _this->windows; window; window = window->next) {
WIN_SetDarkModeColorsForHWND(_this, window, preferred == SDL_SYSTEM_THEME_DARK);
}

SDL_SetSystemTheme(theme);
}

#endif // SDL_VIDEO_DRIVER_WINDOWS
17 changes: 17 additions & 0 deletions src/video/windows/SDL_windowsvideo.h
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,18 @@ typedef struct
BOOL transition_on_maxed;
} DWM_BLURBEHIND;

typedef enum {
WCA_UNDEFINED = 0,
WCA_USEDARKMODECOLORS = 26,
WCA_LAST = 27
} WINDOWCOMPOSITIONATTRIB;

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

// Private display data

struct SDL_VideoData
Expand Down Expand Up @@ -572,6 +584,9 @@ struct SDL_VideoData
UINT *dpiX,
UINT *dpiY );
HRESULT (WINAPI *SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS dpiAwareness);

BOOL (WINAPI *SetWindowCompositionAttribute)(HWND, const WINDOWCOMPOSITIONATTRIBDATA *);

/* *INDENT-ON* */ // clang-format on
#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)

Expand Down Expand Up @@ -661,4 +676,6 @@ extern bool D3D_LoadDLL(void **pD3DDLL, IDirect3D9 **pDirect3D9Interface);
extern SDL_SystemTheme WIN_GetSystemTheme(void);
extern bool WIN_IsPerMonitorV2DPIAware(SDL_VideoDevice *_this);

extern void WIN_SetPreferredTheme(SDL_VideoDevice *_this, SDL_SystemTheme theme);

#endif // SDL_windowsvideo_h_
16 changes: 14 additions & 2 deletions src/video/windows/SDL_windowswindow.c
Original file line number Diff line number Diff line change
Expand Up @@ -722,8 +722,6 @@ bool WIN_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Properties
return WIN_SetError("Couldn't create window");
}

WIN_UpdateDarkModeForHWND(hwnd);

WIN_PumpEvents(_this);

if (!SetupWindowData(_this, window, hwnd, parent)) {
Expand All @@ -734,6 +732,8 @@ bool WIN_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Properties
return false;
}

WIN_SetDarkModeColorsForHWND(_this, window, _this->system_theme == SDL_SYSTEM_THEME_DARK ? TRUE : FALSE);

#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
// Ensure that the IME isn't active on the new window until explicitly requested.
WIN_StopTextInput(_this, window);
Expand Down Expand Up @@ -826,6 +826,18 @@ bool WIN_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Properties
return true;
}

void WIN_SetDarkModeColorsForHWND(SDL_VideoDevice *_this, SDL_Window *window, BOOL value)
{
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
HWND hwnd = window->internal->hwnd;
WINDOWCOMPOSITIONATTRIBDATA data = {
WCA_USEDARKMODECOLORS,
&value, sizeof(value)
};
_this->internal->SetWindowCompositionAttribute(hwnd, &data);
#endif
}

void WIN_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window)
{
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
Expand Down
1 change: 1 addition & 0 deletions src/video/windows/SDL_windowswindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ struct SDL_WindowData
};

extern bool WIN_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props);
extern void WIN_SetDarkModeColorsForHWND(SDL_VideoDevice *_this, SDL_Window *window, BOOL value);
extern void WIN_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window);
extern bool WIN_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon);
extern bool WIN_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window);
Expand Down