diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-09-27 07:07:52 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-09-27 07:07:52 +0300 |
commit | 7f21330692efacf11d2701f62632f0b6c9766621 (patch) | |
tree | 2fe9650bfbba5133b8ca71b9edba3047261147d7 /src/win32.c | |
parent | ff459a750f2c810142636bf292cdd36cb6a911aa (diff) |
Windows: Enable dark mode; use dark title bar for dark themes
This is quite a hack, but Win32 apps don't seem to have documented
access to dark mode.
Diffstat (limited to 'src/win32.c')
-rw-r--r-- | src/win32.c | 164 |
1 files changed, 158 insertions, 6 deletions
diff --git a/src/win32.c b/src/win32.c index 63e7885a..05ed2c4b 100644 --- a/src/win32.c +++ b/src/win32.c | |||
@@ -22,6 +22,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
22 | 22 | ||
23 | #include "win32.h" | 23 | #include "win32.h" |
24 | #include "ui/window.h" | 24 | #include "ui/window.h" |
25 | #include "ui/command.h" | ||
26 | #include "prefs.h" | ||
25 | #include "app.h" | 27 | #include "app.h" |
26 | #include <SDL_syswm.h> | 28 | #include <SDL_syswm.h> |
27 | 29 | ||
@@ -31,8 +33,131 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
31 | #include <dwmapi.h> | 33 | #include <dwmapi.h> |
32 | #include <d2d1.h> | 34 | #include <d2d1.h> |
33 | 35 | ||
34 | void setDPIAware_Win32(void) { | 36 | /* Windows 10 Dark Mode Support |
37 | |||
38 | Apparently Microsoft never documented the Win32 functions that control dark mode for | ||
39 | apps and windows. Here we manually query certain entrypoints from uxtheme.dll and | ||
40 | user32.dll and use them to enable dark mode for the application, and switch the title | ||
41 | bar colors to dark or light depending on the Prefs UI color theme. | ||
42 | |||
43 | Perhaps these Win32 APIs will be documented properly in some future version of Windows, | ||
44 | but for now this is what we have to do to avoid having a white title bar in dark mode. | ||
45 | |||
46 | Calling random functions from system DLLs is a great way to introduce crashes in the | ||
47 | future! Be on the lookout for launch problems down the road. | ||
48 | |||
49 | Adapted from https://github.com/ysc3839/win32-darkmode. */ | ||
50 | |||
51 | enum WINDOWCOMPOSITIONATTRIB { | ||
52 | WCA_UNDEFINED = 0, | ||
53 | WCA_NCRENDERING_ENABLED = 1, | ||
54 | WCA_NCRENDERING_POLICY = 2, | ||
55 | WCA_TRANSITIONS_FORCEDISABLED = 3, | ||
56 | WCA_ALLOW_NCPAINT = 4, | ||
57 | WCA_CAPTION_BUTTON_BOUNDS = 5, | ||
58 | WCA_NONCLIENT_RTL_LAYOUT = 6, | ||
59 | WCA_FORCE_ICONIC_REPRESENTATION = 7, | ||
60 | WCA_EXTENDED_FRAME_BOUNDS = 8, | ||
61 | WCA_HAS_ICONIC_BITMAP = 9, | ||
62 | WCA_THEME_ATTRIBUTES = 10, | ||
63 | WCA_NCRENDERING_EXILED = 11, | ||
64 | WCA_NCADORNMENTINFO = 12, | ||
65 | WCA_EXCLUDED_FROM_LIVEPREVIEW = 13, | ||
66 | WCA_VIDEO_OVERLAY_ACTIVE = 14, | ||
67 | WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15, | ||
68 | WCA_DISALLOW_PEEK = 16, | ||
69 | WCA_CLOAK = 17, | ||
70 | WCA_CLOAKED = 18, | ||
71 | WCA_ACCENT_POLICY = 19, | ||
72 | WCA_FREEZE_REPRESENTATION = 20, | ||
73 | WCA_EVER_UNCLOAKED = 21, | ||
74 | WCA_VISUAL_OWNER = 22, | ||
75 | WCA_HOLOGRAPHIC = 23, | ||
76 | WCA_EXCLUDED_FROM_DDA = 24, | ||
77 | WCA_PASSIVEUPDATEMODE = 25, | ||
78 | WCA_USEDARKMODECOLORS = 26, | ||
79 | WCA_LAST = 27 | ||
80 | }; | ||
81 | |||
82 | struct WINDOWCOMPOSITIONATTRIBDATA { | ||
83 | enum WINDOWCOMPOSITIONATTRIB Attrib; | ||
84 | PVOID pvData; | ||
85 | SIZE_T cbData; | ||
86 | }; | ||
87 | |||
88 | enum PreferredAppMode { Default, AllowDark, ForceDark, ForceLight }; | ||
89 | |||
90 | typedef void (WINAPI *RtlGetNtVersionNumbersFunc)(LPDWORD major, LPDWORD minor, LPDWORD build); | ||
91 | typedef bool (WINAPI *AllowDarkModeForAppFunc)(BOOL allow); | ||
92 | typedef enum PreferredAppMode (WINAPI *SetPreferredAppModeFunc)(enum PreferredAppMode appMode); | ||
93 | typedef BOOL (WINAPI *SetWindowCompositionAttributeFunc)(HWND hWnd, struct WINDOWCOMPOSITIONATTRIBDATA *); | ||
94 | typedef BOOL (WINAPI *AllowDarkModeForWindowFunc)(HWND hWnd, BOOL allow); | ||
95 | |||
96 | static AllowDarkModeForWindowFunc AllowDarkModeForWindow_; | ||
97 | static SetWindowCompositionAttributeFunc SetWindowCompositionAttribute_; | ||
98 | |||
99 | static DWORD ntBuildNumber_; | ||
100 | static BOOL isDark_; | ||
101 | |||
102 | static iBool refreshTitleBarThemeColor_(HWND hwnd) { | ||
103 | BOOL dark = isDark_ColorTheme(prefs_App()->theme); | ||
104 | if (dark == isDark_) { | ||
105 | return FALSE; | ||
106 | } | ||
107 | if (ntBuildNumber_ < 18362) { | ||
108 | INT_PTR pDark = dark; | ||
109 | SetPropW(hwnd, L"UseImmersiveDarkModeColors", (HANDLE) pDark); | ||
110 | } | ||
111 | else if (SetWindowCompositionAttribute_) { | ||
112 | struct WINDOWCOMPOSITIONATTRIBDATA data = { | ||
113 | WCA_USEDARKMODECOLORS, &dark, sizeof(dark) | ||
114 | }; | ||
115 | SetWindowCompositionAttribute_(hwnd, &data); | ||
116 | } | ||
117 | isDark_ = dark; | ||
118 | return TRUE; | ||
119 | } | ||
120 | |||
121 | static void enableDarkMode_Win32(void) { | ||
122 | RtlGetNtVersionNumbersFunc RtlGetNtVersionNumbers_ = | ||
123 | (RtlGetNtVersionNumbersFunc) | ||
124 | GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlGetNtVersionNumbers"); | ||
125 | if (!RtlGetNtVersionNumbers_) { | ||
126 | return; | ||
127 | } | ||
128 | DWORD major, minor; | ||
129 | RtlGetNtVersionNumbers_(&major, &minor, &ntBuildNumber_); | ||
130 | ntBuildNumber_ &= ~0xf0000000; | ||
131 | //printf("%u.%u %u\n", major, minor, ntBuildNumber_); | ||
132 | /* Windows 11 is apparently still NT version 10. */ | ||
133 | if (!(major == 10 && minor == 0 && ntBuildNumber_ >= 17763)) { | ||
134 | return; | ||
135 | } | ||
136 | HMODULE hUxtheme = LoadLibraryExW(L"uxtheme.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); | ||
137 | if (hUxtheme) { | ||
138 | AllowDarkModeForWindow_ = (AllowDarkModeForWindowFunc) | ||
139 | GetProcAddress(hUxtheme, MAKEINTRESOURCEA(133)); | ||
140 | AllowDarkModeForAppFunc AllowDarkModeForApp_ = NULL; | ||
141 | SetPreferredAppModeFunc SetPreferredAppMode_ = NULL; | ||
142 | FARPROC ord135 = GetProcAddress(hUxtheme, MAKEINTRESOURCEA(135)); | ||
143 | if (ord135) { | ||
144 | if (ntBuildNumber_ < 18362) { | ||
145 | AllowDarkModeForApp_ = (AllowDarkModeForAppFunc) ord135; | ||
146 | AllowDarkModeForApp_(TRUE); | ||
147 | } | ||
148 | else { | ||
149 | SetPreferredAppMode_ = (SetPreferredAppModeFunc) ord135; | ||
150 | SetPreferredAppMode_(AllowDark); | ||
151 | } | ||
152 | } | ||
153 | SetWindowCompositionAttribute_ = (SetWindowCompositionAttributeFunc) | ||
154 | GetProcAddress(GetModuleHandleW(L"user32.dll"), "SetWindowCompositionAttribute"); | ||
155 | } | ||
156 | } | ||
157 | |||
158 | void init_Win32(void) { | ||
35 | SetProcessDPIAware(); | 159 | SetProcessDPIAware(); |
160 | enableDarkMode_Win32(); | ||
36 | } | 161 | } |
37 | 162 | ||
38 | float desktopDPI_Win32(void) { | 163 | float desktopDPI_Win32(void) { |
@@ -51,15 +176,42 @@ float desktopDPI_Win32(void) { | |||
51 | return ratio; | 176 | return ratio; |
52 | } | 177 | } |
53 | 178 | ||
179 | static HWND windowHandle_(SDL_Window *win) { | ||
180 | SDL_SysWMinfo wmInfo; | ||
181 | SDL_VERSION(&wmInfo.version); | ||
182 | if (SDL_GetWindowWMInfo(win, &wmInfo)) { | ||
183 | return wmInfo.info.win.window; | ||
184 | } | ||
185 | return NULL; | ||
186 | } | ||
187 | |||
54 | void useExecutableIconResource_SDLWindow(SDL_Window *win) { | 188 | void useExecutableIconResource_SDLWindow(SDL_Window *win) { |
55 | HINSTANCE handle = GetModuleHandle(NULL); | 189 | HINSTANCE handle = GetModuleHandle(NULL); |
56 | HICON icon = LoadIcon(handle, "IDI_ICON1"); | 190 | HICON icon = LoadIcon(handle, "IDI_ICON1"); |
57 | if (icon) { | 191 | if (icon) { |
58 | SDL_SysWMinfo wmInfo; | 192 | HWND hwnd = windowHandle_(win); |
59 | SDL_VERSION(&wmInfo.version); | 193 | SetClassLongPtr(hwnd, -14 /*GCL_HICON*/, (LONG_PTR) icon); |
60 | if (SDL_GetWindowWMInfo(win, &wmInfo)) { | 194 | } |
61 | HWND hwnd = wmInfo.info.win.window; | 195 | } |
62 | SetClassLongPtr(hwnd, -14 /*GCL_HICON*/, (LONG_PTR) icon); | 196 | |
197 | void enableDarkMode_SDLWindow(SDL_Window *win) { | ||
198 | if (AllowDarkModeForWindow_) { | ||
199 | HWND hwnd = windowHandle_(win); | ||
200 | AllowDarkModeForWindow_(hwnd, TRUE); | ||
201 | refreshTitleBarThemeColor_(hwnd); | ||
202 | } | ||
203 | } | ||
204 | |||
205 | void handleCommand_Win32(const char *cmd) { | ||
206 | if (equal_Command(cmd, "theme.changed")) { | ||
207 | iMainWindow *mw = get_MainWindow(); | ||
208 | SDL_Window *win = mw->base.win; | ||
209 | if (refreshTitleBarThemeColor_(windowHandle_(win)) && | ||
210 | !isFullscreen_MainWindow(mw) && | ||
211 | !argLabel_Command(cmd, "auto")) { | ||
212 | /* This will ensure that the non-client area is repainted. */ | ||
213 | SDL_MinimizeWindow(win); | ||
214 | SDL_RestoreWindow(win); | ||
63 | } | 215 | } |
64 | } | 216 | } |
65 | } | 217 | } |