From 127ae123feb96281e760bc73209ff31f334bc534 Mon Sep 17 00:00:00 2001 From: Santiago Date: Thu, 10 Jul 2025 20:16:16 +0200 Subject: [PATCH] Add option to set OS dark theme --- docs/window.md | 5 +++++ include/GLFW/glfw3.h | 11 +++++++++++ src/internal.h | 2 ++ src/win32_init.c | 10 ++++++++++ src/win32_platform.h | 5 ++++- src/win32_window.c | 30 ++++++++++++++++++++++++++++++ src/window.c | 11 +++++++++++ 7 files changed, 73 insertions(+), 1 deletion(-) diff --git a/docs/window.md b/docs/window.md index 371baa56..5a8bd953 100644 --- a/docs/window.md +++ b/docs/window.md @@ -262,6 +262,11 @@ This is the new name, introduced in GLFW 3.4. The older `GLFW_COCOA_RETINA_FRAMEBUFFER` name is also available for compatibility. Both names modify the same hint value. +@anchor GLFW_THEME +__GLFW_THEME__ specifies whether the window should use light theme or not. +Possible values are `GLFW_THEME_AUTO`, `GLFW_THEME_DARK`, and `GLFW_THEME_LIGHT`. This hint only has effect +on windows operating system. + @anchor GLFW_MOUSE_PASSTHROUGH_hint __GLFW_MOUSE_PASSTHROUGH__ specifies whether the window is transparent to mouse input, letting any mouse events pass through to whatever window is behind it. diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index 79b06288..b75f5f32 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -1108,6 +1108,11 @@ extern "C" { * [GLFW_SCALE_FRAMEBUFFER](@ref GLFW_SCALE_FRAMEBUFFER_hint) window hint for * compatibility with earlier versions. */ +#define GLFW_THEME 0x0002200E +/*! @brief windows specific + * + * Allows specifying whether light or dark theme should be used. + */ #define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001 /*! @brief macOS specific * [window hint](@ref GLFW_COCOA_FRAME_NAME_hint). @@ -1182,6 +1187,10 @@ extern "C" { #define GLFW_ANY_POSITION 0x80000000 +#define GLFW_THEME_AUTO 0x00000000 +#define GLFW_THEME_DARK 0x00000001 +#define GLFW_THEME_LIGHT 0x00000002 + /*! @defgroup shapes Standard cursor shapes * @brief Standard system cursor shapes. * @@ -3480,6 +3489,8 @@ GLFWAPI void glfwGetWindowPos(GLFWwindow* window, int* xpos, int* ypos); */ GLFWAPI void glfwSetWindowPos(GLFWwindow* window, int xpos, int ypos); +GLFWAPI int glfwIsWindowLightTheme(GLFWwindow* window); + /*! @brief Retrieves the size of the content area of the specified window. * * This function retrieves the size, in screen coordinates, of the content area diff --git a/src/internal.h b/src/internal.h index 4f097aa8..1a2cf4f1 100644 --- a/src/internal.h +++ b/src/internal.h @@ -415,6 +415,7 @@ struct _GLFWwndconfig GLFWbool mousePassthrough; GLFWbool scaleToMonitor; GLFWbool scaleFramebuffer; + int theme; struct { char frameName[256]; } ns; @@ -539,6 +540,7 @@ struct _GLFWwindow GLFWbool focusOnShow; GLFWbool mousePassthrough; GLFWbool shouldClose; + GLFWbool isLightTheme; void* userPointer; GLFWbool doublebuffer; GLFWvidmode videoMode; diff --git a/src/win32_init.c b/src/win32_init.c index 77ab56ba..66fdc2e8 100644 --- a/src/win32_init.c +++ b/src/win32_init.c @@ -149,6 +149,8 @@ static GLFWbool loadLibraries(void) _glfwPlatformGetModuleSymbol(_glfw.win32.dwmapi.instance, "DwmEnableBlurBehindWindow"); _glfw.win32.dwmapi.GetColorizationColor = (PFN_DwmGetColorizationColor) _glfwPlatformGetModuleSymbol(_glfw.win32.dwmapi.instance, "DwmGetColorizationColor"); + _glfw.win32.dwmapi.DwmSetWindowAttribute = (PFN_DwmSetWindowAttribute) + _glfwPlatformGetModuleSymbol(_glfw.win32.dwmapi.instance, "DwmSetWindowAttribute"); } _glfw.win32.shcore.instance = _glfwPlatformLoadModule("shcore.dll"); @@ -567,6 +569,14 @@ void _glfwUpdateKeyNamesWin32(void) } } +void _glfwSetWindowTheme(BOOL dark, HWND hwnd) +{ +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + _glfw.win32.dwmapi.DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &dark, sizeof(dark)); +} + // Replacement for IsWindowsVersionOrGreater, as we cannot rely on the // application having a correct embedded manifest // diff --git a/src/win32_platform.h b/src/win32_platform.h index bcbc604b..b103d113 100644 --- a/src/win32_platform.h +++ b/src/win32_platform.h @@ -284,8 +284,9 @@ typedef int (WINAPI * PFN_GetSystemMetricsForDpi)(int,UINT); // dwmapi.dll function pointer typedefs typedef HRESULT (WINAPI * PFN_DwmIsCompositionEnabled)(BOOL*); typedef HRESULT (WINAPI * PFN_DwmFlush)(VOID); -typedef HRESULT(WINAPI * PFN_DwmEnableBlurBehindWindow)(HWND,const DWM_BLURBEHIND*); +typedef HRESULT (WINAPI * PFN_DwmEnableBlurBehindWindow)(HWND,const DWM_BLURBEHIND*); typedef HRESULT (WINAPI * PFN_DwmGetColorizationColor)(DWORD*,BOOL*); +typedef HRESULT (WINAPI * PFN_DwmSetWindowAttribute)(HWND, DWORD, LPCVOID, DWORD); #define DwmIsCompositionEnabled _glfw.win32.dwmapi.IsCompositionEnabled #define DwmFlush _glfw.win32.dwmapi.Flush #define DwmEnableBlurBehindWindow _glfw.win32.dwmapi.EnableBlurBehindWindow @@ -475,6 +476,7 @@ typedef struct _GLFWlibraryWin32 PFN_DwmFlush Flush; PFN_DwmEnableBlurBehindWindow EnableBlurBehindWindow; PFN_DwmGetColorizationColor GetColorizationColor; + PFN_DwmSetWindowAttribute DwmSetWindowAttribute; } dwmapi; struct { @@ -521,6 +523,7 @@ BOOL _glfwIsWindowsVersionOrGreaterWin32(WORD major, WORD minor, WORD sp); BOOL _glfwIsWindows10BuildOrGreaterWin32(WORD build); void _glfwInputErrorWin32(int error, const char* description); void _glfwUpdateKeyNamesWin32(void); +void _glfwSetWindowTheme(BOOL dark, HWND hwnd); void _glfwPollMonitorsWin32(void); void _glfwSetVideoModeWin32(_GLFWmonitor* monitor, const GLFWvidmode* desired); diff --git a/src/win32_window.c b/src/win32_window.c index d014944b..5662c88f 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -1396,6 +1396,36 @@ static int createNativeWindow(_GLFWwindow* window, _glfw.win32.instance, (LPVOID) wndconfig); + BOOL should_use_light_mode; + if (wndconfig->theme == GLFW_THEME_AUTO) + { + BOOL success = FALSE; + HKEY hRootKey = HKEY_CURRENT_USER; + const wchar_t* lpSubKey = L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; + const wchar_t* lpValueName = L"AppsUseLightTheme"; + DWORD result; + { + HKEY hKey = 0; + if (RegOpenKeyExW(hRootKey, lpSubKey, 0, KEY_READ, &hKey) == ERROR_SUCCESS) + { + DWORD dwBufferSize = sizeof(DWORD); + DWORD dwData = 0; + if (RegQueryValueExW(hKey, lpValueName, 0, NULL, (LPBYTE)(&dwData), &dwBufferSize) == ERROR_SUCCESS) + { + result = dwData; + success = TRUE; + } + RegCloseKey(hKey); + } + } + should_use_light_mode = success && result != 0; + } + else + should_use_light_mode = wndconfig->theme == GLFW_THEME_LIGHT; + + _glfwSetWindowTheme(!should_use_light_mode, window->win32.handle); + window->isLightTheme = should_use_light_mode ? GLFW_TRUE : GLFW_FALSE; + _glfw_free(wideTitle); if (!window->win32.handle) diff --git a/src/window.c b/src/window.c index e03121a4..ba4ea191 100644 --- a/src/window.c +++ b/src/window.c @@ -388,6 +388,8 @@ GLFWAPI void glfwWindowHint(int hint, int value) case GLFW_SCALE_FRAMEBUFFER: case GLFW_COCOA_RETINA_FRAMEBUFFER: _glfw.hints.window.scaleFramebuffer = value ? GLFW_TRUE : GLFW_FALSE; + case GLFW_THEME: + _glfw.hints.window.theme = value; return; case GLFW_CENTER_CURSOR: _glfw.hints.window.centerCursor = value ? GLFW_TRUE : GLFW_FALSE; @@ -607,6 +609,15 @@ GLFWAPI void glfwSetWindowPos(GLFWwindow* handle, int xpos, int ypos) _glfw.platform.setWindowPos(window, xpos, ypos); } +GLFWAPI int glfwIsWindowLightTheme(GLFWwindow* handle) +{ + _GLFWwindow* window = (_GLFWwindow*)handle; + assert(window != NULL); + + _GLFW_REQUIRE_INIT_OR_RETURN(0); + return window->isLightTheme; +} + GLFWAPI void glfwGetWindowSize(GLFWwindow* handle, int* width, int* height) { if (width)