Allow building the tray subsystem without the video subsystem

Fixes https://github.com/libsdl-org/SDL/issues/13235
This commit is contained in:
Sam Lantinga
2025-08-27 07:34:18 -07:00
parent 943d0f67ba
commit da6e9bbf7c
18 changed files with 219 additions and 263 deletions

View File

@@ -24,6 +24,8 @@
#include "SDL_windows.h"
#include "../../video/SDL_surface_c.h"
#include <objbase.h> // for CoInitialize/CoUninitialize (Win32 only)
#ifdef HAVE_ROAPI_H
#include <roapi.h> // For RoInitialize/RoUninitialize (Win32 only)
@@ -53,6 +55,44 @@ typedef enum RO_INIT_TYPE
#define WC_ERR_INVALID_CHARS 0x00000080
#endif
// Dark mode support
typedef enum {
UXTHEME_APPMODE_DEFAULT,
UXTHEME_APPMODE_ALLOW_DARK,
UXTHEME_APPMODE_FORCE_DARK,
UXTHEME_APPMODE_FORCE_LIGHT,
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;
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 *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.
HWND SDL_HelperWindow = NULL;
static const TCHAR *SDL_HelperWindowClassName = TEXT("SDLHelperWindowInputCatcher");
@@ -408,6 +448,148 @@ bool WIN_WindowRectValid(const RECT *rect)
return (rect->right > 0);
}
void WIN_UpdateDarkModeForHWND(HWND hwnd)
{
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
HMODULE ntdll = LoadLibrary(TEXT("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)GetProcAddress(ntdll, "RtlGetVersion");
NT_OSVERSIONINFOW os_info;
os_info.dwOSVersionInfoSize = sizeof(NT_OSVERSIONINFOW);
os_info.dwBuildNumber = 0;
if (RtlGetVersionFunc) {
RtlGetVersionFunc(&os_info);
}
FreeLibrary(ntdll);
os_info.dwBuildNumber &= ~0xF0000000;
if (os_info.dwBuildNumber < 17763) {
// Too old to support dark mode
return;
}
HMODULE uxtheme = LoadLibrary(TEXT("uxtheme.dll"));
if (!uxtheme) {
return;
}
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);
}
} else {
SetPreferredAppMode_t SetPreferredAppModeFunc = (SetPreferredAppMode_t)GetProcAddress(uxtheme, MAKEINTRESOURCEA(135));
if (SetPreferredAppModeFunc) {
SetPreferredAppModeFunc(UXTHEME_APPMODE_ALLOW_DARK);
}
}
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);
}
}
}
#endif
}
HICON WIN_CreateIconFromSurface(SDL_Surface *surface)
{
#if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES))
SDL_Surface *s = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888);
if (!s) {
return NULL;
}
/* The dimensions will be needed after s is freed */
const int width = s->w;
const int height = s->h;
BITMAPINFO bmpInfo;
SDL_zero(bmpInfo);
bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpInfo.bmiHeader.biWidth = width;
bmpInfo.bmiHeader.biHeight = -height; /* Top-down bitmap */
bmpInfo.bmiHeader.biPlanes = 1;
bmpInfo.bmiHeader.biBitCount = 32;
bmpInfo.bmiHeader.biCompression = BI_RGB;
HDC hdc = GetDC(NULL);
void *pBits = NULL;
HBITMAP hBitmap = CreateDIBSection(hdc, &bmpInfo, DIB_RGB_COLORS, &pBits, NULL, 0);
if (!hBitmap) {
ReleaseDC(NULL, hdc);
SDL_DestroySurface(s);
return NULL;
}
SDL_memcpy(pBits, s->pixels, width * height * 4);
SDL_DestroySurface(s);
HBITMAP hMask = CreateBitmap(width, height, 1, 1, NULL);
if (!hMask) {
DeleteObject(hBitmap);
ReleaseDC(NULL, hdc);
return NULL;
}
HDC hdcMem = CreateCompatibleDC(hdc);
HGDIOBJ oldBitmap = SelectObject(hdcMem, hMask);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
BYTE* pixel = (BYTE*)pBits + (y * width + x) * 4;
BYTE alpha = pixel[3];
COLORREF maskColor = (alpha == 0) ? RGB(0, 0, 0) : RGB(255, 255, 255);
SetPixel(hdcMem, x, y, maskColor);
}
}
ICONINFO iconInfo;
iconInfo.fIcon = TRUE;
iconInfo.xHotspot = 0;
iconInfo.yHotspot = 0;
iconInfo.hbmMask = hMask;
iconInfo.hbmColor = hBitmap;
HICON hIcon = CreateIconIndirect(&iconInfo);
SelectObject(hdcMem, oldBitmap);
DeleteDC(hdcMem);
DeleteObject(hBitmap);
DeleteObject(hMask);
ReleaseDC(NULL, hdc);
return hIcon;
#else
return NULL;
#endif
}
// Some GUIDs we need to know without linking to libraries that aren't available before Vista.
/* *INDENT-OFF* */ // clang-format off
static const GUID SDL_KSDATAFORMAT_SUBTYPE_PCM = { 0x00000001, 0x0000, 0x0010,{ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };

View File

@@ -157,7 +157,11 @@ extern void WIN_RECTToRect(const RECT *winrect, SDL_Rect *sdlrect);
extern void WIN_RectToRECT(const SDL_Rect *sdlrect, RECT *winrect);
// Returns false if a window client rect is not valid
bool WIN_WindowRectValid(const RECT *rect);
extern bool WIN_WindowRectValid(const RECT *rect);
extern void WIN_UpdateDarkModeForHWND(HWND hwnd);
extern HICON WIN_CreateIconFromSurface(SDL_Surface *surface);
extern SDL_AudioFormat SDL_WaveFormatExToSDLFormat(WAVEFORMATEX *waveformat);