summaryrefslogtreecommitdiff
path: root/src/win32.c
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-09-27 07:07:52 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-09-27 07:07:52 +0300
commit7f21330692efacf11d2701f62632f0b6c9766621 (patch)
tree2fe9650bfbba5133b8ca71b9edba3047261147d7 /src/win32.c
parentff459a750f2c810142636bf292cdd36cb6a911aa (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.c164
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
34void setDPIAware_Win32(void) { 36/* Windows 10 Dark Mode Support
37
38Apparently Microsoft never documented the Win32 functions that control dark mode for
39apps and windows. Here we manually query certain entrypoints from uxtheme.dll and
40user32.dll and use them to enable dark mode for the application, and switch the title
41bar colors to dark or light depending on the Prefs UI color theme.
42
43Perhaps these Win32 APIs will be documented properly in some future version of Windows,
44but for now this is what we have to do to avoid having a white title bar in dark mode.
45
46Calling random functions from system DLLs is a great way to introduce crashes in the
47future! Be on the lookout for launch problems down the road.
48
49Adapted from https://github.com/ysc3839/win32-darkmode. */
50
51enum 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
82struct WINDOWCOMPOSITIONATTRIBDATA {
83 enum WINDOWCOMPOSITIONATTRIB Attrib;
84 PVOID pvData;
85 SIZE_T cbData;
86};
87
88enum PreferredAppMode { Default, AllowDark, ForceDark, ForceLight };
89
90typedef void (WINAPI *RtlGetNtVersionNumbersFunc)(LPDWORD major, LPDWORD minor, LPDWORD build);
91typedef bool (WINAPI *AllowDarkModeForAppFunc)(BOOL allow);
92typedef enum PreferredAppMode (WINAPI *SetPreferredAppModeFunc)(enum PreferredAppMode appMode);
93typedef BOOL (WINAPI *SetWindowCompositionAttributeFunc)(HWND hWnd, struct WINDOWCOMPOSITIONATTRIBDATA *);
94typedef BOOL (WINAPI *AllowDarkModeForWindowFunc)(HWND hWnd, BOOL allow);
95
96static AllowDarkModeForWindowFunc AllowDarkModeForWindow_;
97static SetWindowCompositionAttributeFunc SetWindowCompositionAttribute_;
98
99static DWORD ntBuildNumber_;
100static BOOL isDark_;
101
102static 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
121static 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
158void init_Win32(void) {
35 SetProcessDPIAware(); 159 SetProcessDPIAware();
160 enableDarkMode_Win32();
36} 161}
37 162
38float desktopDPI_Win32(void) { 163float desktopDPI_Win32(void) {
@@ -51,15 +176,42 @@ float desktopDPI_Win32(void) {
51 return ratio; 176 return ratio;
52} 177}
53 178
179static 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
54void useExecutableIconResource_SDLWindow(SDL_Window *win) { 188void 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
197void enableDarkMode_SDLWindow(SDL_Window *win) {
198 if (AllowDarkModeForWindow_) {
199 HWND hwnd = windowHandle_(win);
200 AllowDarkModeForWindow_(hwnd, TRUE);
201 refreshTitleBarThemeColor_(hwnd);
202 }
203}
204
205void 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}