Skip to content

Commit fe452f0

Browse files
committed
feat: Windows深色模式
1 parent ea5f5a3 commit fe452f0

File tree

4 files changed

+170
-0
lines changed

4 files changed

+170
-0
lines changed

cmake/compile_definitions/windows.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ set(PLATFORM_TARGET_FILES
6767
"${CMAKE_SOURCE_DIR}/src/platform/windows/ftime_compat.cpp"
6868
"${CMAKE_SOURCE_DIR}/src/platform/windows/misc.h"
6969
"${CMAKE_SOURCE_DIR}/src/platform/windows/misc.cpp"
70+
"${CMAKE_SOURCE_DIR}/src/platform/windows/win_dark_mode.h"
71+
"${CMAKE_SOURCE_DIR}/src/platform/windows/win_dark_mode.cpp"
7072
"${CMAKE_SOURCE_DIR}/src/platform/windows/input.cpp"
7173
"${CMAKE_SOURCE_DIR}/src/platform/windows/dsu_server.h"
7274
"${CMAKE_SOURCE_DIR}/src/platform/windows/dsu_server.cpp"

src/main.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
#include "version.h"
2424
#include "video.h"
2525

26+
#ifdef _WIN32
27+
#include "platform/windows/win_dark_mode.h"
28+
#endif
29+
2630
extern "C" {
2731
#include "rswrapper.h"
2832
}
@@ -122,6 +126,10 @@ main(int argc, char *argv[]) {
122126
// by placing a user-writable directory in the system-wide PATH variable.
123127
SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32);
124128

129+
// Enable dark mode for the entire process before creating any windows
130+
// This must be called early, before any windows or system tray icons are created
131+
win_dark_mode::enable_process_dark_mode();
132+
125133
// Set locale to UTF-8 instead of C locale
126134
setlocale(LC_ALL, ".UTF-8");
127135
#endif
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/**
2+
* @file src/platform/windows/win_dark_mode.cpp
3+
* @brief Implementation of Windows dark mode support
4+
*/
5+
6+
#ifdef _WIN32
7+
8+
#include "win_dark_mode.h"
9+
10+
#include <windows.h>
11+
#include <dwmapi.h>
12+
13+
#pragma comment(lib, "dwmapi.lib")
14+
15+
namespace win_dark_mode {
16+
17+
// Undocumented PreferredAppMode enum from uxtheme.dll
18+
enum class PreferredAppMode {
19+
Default = 0, // Use system default (usually light)
20+
AllowDark = 1, // Allow dark mode (follow system setting)
21+
ForceDark = 2, // Force dark mode regardless of system setting
22+
ForceLight = 3, // Force light mode regardless of system setting
23+
Max = 4,
24+
};
25+
26+
// Function pointer types for undocumented uxtheme.dll APIs
27+
using SetPreferredAppModeFn = PreferredAppMode(WINAPI *)(PreferredAppMode);
28+
using AllowDarkModeForAppFn = BOOL(WINAPI *)(BOOL);
29+
30+
// Global function pointers (initialized once)
31+
static SetPreferredAppModeFn g_SetPreferredAppMode = nullptr;
32+
static AllowDarkModeForAppFn g_AllowDarkModeForApp = nullptr;
33+
34+
/**
35+
* @brief Initialize the dark mode API function pointers
36+
*
37+
* This function loads uxtheme.dll and retrieves the undocumented function pointers.
38+
* It only runs once and caches the results.
39+
*/
40+
static void
41+
init_dark_mode_apis() {
42+
static bool initialized = false;
43+
if (initialized) {
44+
return;
45+
}
46+
initialized = true;
47+
48+
// Load uxtheme.dll
49+
HMODULE hUxTheme = LoadLibraryW(L"uxtheme.dll");
50+
if (!hUxTheme) {
51+
return;
52+
}
53+
54+
// Try to get SetPreferredAppMode (Windows 10 1903+)
55+
// Both SetPreferredAppMode and AllowDarkModeForApp use ordinal 135,
56+
// but they have different signatures depending on Windows version:
57+
// - Win10 1903+: SetPreferredAppMode(PreferredAppMode) -> PreferredAppMode
58+
// - Win10 1809-1903: AllowDarkModeForApp(BOOL) -> BOOL
59+
// We try SetPreferredAppMode first with the newer signature
60+
g_SetPreferredAppMode =
61+
reinterpret_cast<SetPreferredAppModeFn>(
62+
GetProcAddress(hUxTheme, MAKEINTRESOURCEA(135)));
63+
64+
// If SetPreferredAppMode signature doesn't match (older Windows),
65+
// try AllowDarkModeForApp with BOOL signature using the same ordinal
66+
if (!g_SetPreferredAppMode) {
67+
g_AllowDarkModeForApp =
68+
reinterpret_cast<AllowDarkModeForAppFn>(
69+
GetProcAddress(hUxTheme, MAKEINTRESOURCEA(135)));
70+
}
71+
72+
// Note: We intentionally don't call FreeLibrary(hUxTheme) because we need
73+
// the function pointers to remain valid for the lifetime of the process
74+
}
75+
76+
void
77+
enable_process_dark_mode() {
78+
// Initialize the API function pointers
79+
init_dark_mode_apis();
80+
81+
// Call the appropriate function based on what's available
82+
if (g_SetPreferredAppMode) {
83+
// Windows 10 1903+ supports SetPreferredAppMode
84+
// Use AllowDark to follow the system's dark/light mode preference
85+
g_SetPreferredAppMode(PreferredAppMode::AllowDark);
86+
}
87+
else if (g_AllowDarkModeForApp) {
88+
// Windows 10 1809-1903 supports AllowDarkModeForApp
89+
// TRUE means allow dark mode (follows system setting)
90+
g_AllowDarkModeForApp(TRUE);
91+
}
92+
// If neither API is available, dark mode is not supported on this Windows version
93+
// (Windows 10 < 1809 or earlier). We silently do nothing in this case.
94+
}
95+
96+
void
97+
apply_window_dark_title_bar(HWND hwnd, bool enable) {
98+
if (!hwnd) {
99+
return;
100+
}
101+
102+
// DWMWA_USE_IMMERSIVE_DARK_MODE is documented for Windows 11
103+
// but also works on Windows 10 20H1+
104+
constexpr DWORD DWMWA_USE_IMMERSIVE_DARK_MODE = 20;
105+
106+
BOOL useDark = enable ? TRUE : FALSE;
107+
DwmSetWindowAttribute(
108+
hwnd,
109+
DWMWA_USE_IMMERSIVE_DARK_MODE,
110+
&useDark,
111+
sizeof(useDark));
112+
}
113+
114+
} // namespace win_dark_mode
115+
116+
#endif // _WIN32
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* @file src/platform/windows/win_dark_mode.h
3+
* @brief Windows dark mode support for the entire process
4+
*
5+
* This module provides process-wide dark mode support for Windows 10 1809+ and Windows 11.
6+
* It handles the undocumented Windows APIs for enabling dark mode for menus, dialogs, and windows.
7+
*
8+
* Note: This should be called early in program initialization, before creating any windows or menus.
9+
*/
10+
11+
#pragma once
12+
13+
#ifdef _WIN32
14+
15+
namespace win_dark_mode {
16+
17+
/**
18+
* @brief Enable dark mode support for the entire process
19+
*
20+
* This function enables dark mode support by calling SetPreferredAppMode (or AllowDarkModeForApp
21+
* on older Windows versions). It should be called once during application initialization,
22+
* before creating any windows or system tray icons.
23+
*
24+
* The function will:
25+
* - Load the necessary APIs from uxtheme.dll
26+
* - Set the process to allow dark mode (follows system setting)
27+
* - This affects all menus, dialogs, and windows created after this call
28+
*/
29+
void enable_process_dark_mode();
30+
31+
/**
32+
* @brief Apply dark mode to a specific window's title bar
33+
*
34+
* This function applies the immersive dark mode to a window's title bar using
35+
* DwmSetWindowAttribute with DWMWA_USE_IMMERSIVE_DARK_MODE.
36+
*
37+
* @param hwnd Window handle to apply dark mode to
38+
* @param enable Whether to enable (true) or disable (false) dark mode for the title bar
39+
*/
40+
void apply_window_dark_title_bar(HWND hwnd, bool enable);
41+
42+
} // namespace win_dark_mode
43+
44+
#endif // _WIN32

0 commit comments

Comments
 (0)