graphics: respect Windows dark mode (#765)

Only affects the caption bar.

Should work for:

- [x] standalone configurator
- [x] dx9 games, including multi-window games like gitadora
- [x] Unity games
This commit is contained in:
bicarus
2026-06-17 11:51:31 -07:00
committed by GitHub
parent 301e15c44d
commit 50fc80a1c3
5 changed files with 61 additions and 0 deletions
+3
View File
@@ -12,6 +12,7 @@
#include "overlay/overlay.h"
#include "util/logging.h"
#include "util/precise_timer.h"
#include "util/utils.h"
#include "cfg/configurator.h"
#include "icon.h"
@@ -214,6 +215,8 @@ cfg::ConfiguratorWindow::ConfiguratorWindow() {
if (this->hWnd) {
overlay::USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT = true;
// force dark title bar
set_window_dark_titlebar(this->hWnd, true);
}
}
@@ -27,6 +27,7 @@
#include "hooks/graphics/graphics.h"
#include "launcher/launcher.h"
#include "misc/eamuse.h"
#include "util/utils.h"
// --------------------------------------------------------------------------
// overlay render bridge
@@ -123,6 +124,10 @@ void try_create_overlay(IDXGISwapChain *swapchain) {
return;
}
// theme the native title bar; first present is the only reliable point for
// windows whose swapchain bypasses our factory hooks (e.g. UnityPlayer.dll)
set_window_dark_titlebar(desc.OutputWindow);
ID3D11Device *device = nullptr;
if (FAILED(swapchain->GetDevice(IID_PPV_ARGS(&device))) || !device) {
return;
+6
View File
@@ -585,6 +585,9 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
hWndParent, hMenu, hInstance, lpParam);
GRAPHICS_WINDOWS.push_back(result);
// theme the native title bar (dark/light)
set_window_dark_titlebar(result);
if (is_tdj_sub_window) {
// TDJ windowed mode: remember the subscreen window handle for later
TDJ_SUBSCREEN_WINDOW = result;
@@ -706,6 +709,9 @@ static HWND WINAPI CreateWindowExW_hook(DWORD dwExStyle, LPCWSTR lpClassName, LP
hWndParent, hMenu, hInstance, lpParam);
GRAPHICS_WINDOWS.push_back(result);
// theme the native title bar (dark/light)
set_window_dark_titlebar(result);
log_misc(
"graphics",
"CreateWindowExW returned {}, {}",
+43
View File
@@ -4,6 +4,15 @@
#include "utils.h"
#if !SPICE_XP
#include <dwmapi.h>
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
#endif
// older numbering used by Win10 1809..1909
#define DWMWA_USE_IMMERSIVE_DARK_MODE_OLD 19
#endif
const char *inet_ntop(short af, const void *src, char *dst, DWORD size) {
// prepare storage
@@ -124,3 +133,37 @@ void generate_ea_card(char card[17]) {
// terminate and flush
card[16] = 0;
}
void set_window_dark_titlebar(HWND hWnd, bool force_dark) {
#if !SPICE_XP
if (hWnd == nullptr) {
return;
}
// AppsUseLightTheme == 0 means the dark app theme is selected
DWORD light = 1;
if (!force_dark) {
DWORD size = sizeof(light);
HKEY key;
if (RegOpenKeyExW(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
0, KEY_QUERY_VALUE, &key) == ERROR_SUCCESS) {
RegQueryValueExW(key, L"AppsUseLightTheme", nullptr, nullptr,
reinterpret_cast<LPBYTE>(&light), &size);
RegCloseKey(key);
}
}
BOOL dark = (force_dark || light == 0) ? TRUE : FALSE;
// fall back to the older attribute numbering on Win10 1809..1909
if (FAILED(DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE,
&dark, sizeof(dark)))) {
DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE_OLD,
&dark, sizeof(dark));
}
#else
(void) hWnd;
(void) force_dark;
#endif
}
+4
View File
@@ -28,6 +28,10 @@ static const char HEX_LOOKUP_UPPERCASE[16] = {
const char *inet_ntop(short af, const void *src, char *dst, DWORD size);
// make a window's native title bar follow the Windows dark/light app theme.
// pass force_dark=true to always use the dark caption regardless of the system theme.
void set_window_dark_titlebar(HWND hWnd, bool force_dark = false);
static inline bool string_begins_with(const std::string &s, const std::string &prefix) {
return s.compare(0, prefix.size(), prefix) == 0;
}