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 | |
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.
-rw-r--r-- | src/app.c | 3 | ||||
-rw-r--r-- | src/main.c | 5 | ||||
-rw-r--r-- | src/ui/root.c | 3 | ||||
-rw-r--r-- | src/ui/window.c | 1 | ||||
-rw-r--r-- | src/ui/window.h | 2 | ||||
-rw-r--r-- | src/win32.c | 164 | ||||
-rw-r--r-- | src/win32.h | 6 |
7 files changed, 171 insertions, 13 deletions
@@ -1310,6 +1310,9 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1310 | #if defined (iPlatformAppleDesktop) | 1310 | #if defined (iPlatformAppleDesktop) |
1311 | handleCommand_MacOS(command_UserEvent(&ev)); | 1311 | handleCommand_MacOS(command_UserEvent(&ev)); |
1312 | #endif | 1312 | #endif |
1313 | #if defined (iPlatformMsys) | ||
1314 | handleCommand_Win32(command_UserEvent(&ev)); | ||
1315 | #endif | ||
1313 | if (isMetricsChange_UserEvent(&ev)) { | 1316 | if (isMetricsChange_UserEvent(&ev)) { |
1314 | iConstForEach(PtrArray, iter, listWindows_App_(d)) { | 1317 | iConstForEach(PtrArray, iter, listWindows_App_(d)) { |
1315 | iWindow *window = iter.ptr; | 1318 | iWindow *window = iter.ptr; |
@@ -49,9 +49,8 @@ int main(int argc, char **argv) { | |||
49 | registerURLHandler_MacOS(); | 49 | registerURLHandler_MacOS(); |
50 | #endif | 50 | #endif |
51 | #if defined (iPlatformMsys) | 51 | #if defined (iPlatformMsys) |
52 | /* MSYS runtime takes care of WinMain. */ | 52 | init_Win32(); /* DPI awareness, dark mode */ |
53 | setDPIAware_Win32(); | 53 | SDL_SetMainReady(); /* MSYS runtime takes care of WinMain. */ |
54 | SDL_SetMainReady(); | ||
55 | #endif | 54 | #endif |
56 | /* Initialize libraries. */ | 55 | /* Initialize libraries. */ |
57 | #if defined (LAGRANGE_ENABLE_MPG123) | 56 | #if defined (LAGRANGE_ENABLE_MPG123) |
diff --git a/src/ui/root.c b/src/ui/root.c index e0a12665..f15b59c4 100644 --- a/src/ui/root.c +++ b/src/ui/root.c | |||
@@ -380,7 +380,8 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) { | |||
380 | argLabel_Command(cmd, "height")); | 380 | argLabel_Command(cmd, "height")); |
381 | SDL_SetWindowPosition(window->base.win, coord.x, coord.y); | 381 | SDL_SetWindowPosition(window->base.win, coord.x, coord.y); |
382 | SDL_SetWindowSize(window->base.win, size.x, size.y); | 382 | SDL_SetWindowSize(window->base.win, size.x, size.y); |
383 | window->place.snap = snap; | 383 | //window->place.snap = snap; |
384 | setSnap_MainWindow(get_MainWindow(), snap); | ||
384 | return iTrue; | 385 | return iTrue; |
385 | } | 386 | } |
386 | } | 387 | } |
diff --git a/src/ui/window.c b/src/ui/window.c index 164c2478..bd1254d7 100644 --- a/src/ui/window.c +++ b/src/ui/window.c | |||
@@ -571,6 +571,7 @@ void init_MainWindow(iMainWindow *d, iRect rect) { | |||
571 | #if defined(iPlatformMsys) | 571 | #if defined(iPlatformMsys) |
572 | SDL_SetWindowMinimumSize(d->base.win, minSize.x * d->base.displayScale, minSize.y * d->base.displayScale); | 572 | SDL_SetWindowMinimumSize(d->base.win, minSize.x * d->base.displayScale, minSize.y * d->base.displayScale); |
573 | useExecutableIconResource_SDLWindow(d->base.win); | 573 | useExecutableIconResource_SDLWindow(d->base.win); |
574 | enableDarkMode_SDLWindow(d->base.win); | ||
574 | #endif | 575 | #endif |
575 | #if defined (iPlatformLinux) | 576 | #if defined (iPlatformLinux) |
576 | SDL_SetWindowMinimumSize(d->base.win, minSize.x * d->base.pixelRatio, minSize.y * d->base.pixelRatio); | 577 | SDL_SetWindowMinimumSize(d->base.win, minSize.x * d->base.pixelRatio, minSize.y * d->base.pixelRatio); |
diff --git a/src/ui/window.h b/src/ui/window.h index f1827931..81fc2c06 100644 --- a/src/ui/window.h +++ b/src/ui/window.h | |||
@@ -193,7 +193,7 @@ void draw_MainWindow (iMainWindow *); | |||
193 | void drawWhileResizing_MainWindow (iMainWindow *, int w, int h); /* workaround for SDL bug */ | 193 | void drawWhileResizing_MainWindow (iMainWindow *, int w, int h); /* workaround for SDL bug */ |
194 | 194 | ||
195 | int snap_MainWindow (const iMainWindow *); | 195 | int snap_MainWindow (const iMainWindow *); |
196 | iBool isFullscreen_Window (const iMainWindow *); | 196 | iBool isFullscreen_MainWindow (const iMainWindow *); |
197 | 197 | ||
198 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) | 198 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) |
199 | SDL_HitTestResult hitTest_MainWindow(const iMainWindow *d, iInt2 pos); | 199 | SDL_HitTestResult hitTest_MainWindow(const iMainWindow *d, iInt2 pos); |
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 | } |
diff --git a/src/win32.h b/src/win32.h index d53a2fe8..d3fe3faf 100644 --- a/src/win32.h +++ b/src/win32.h | |||
@@ -28,9 +28,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
28 | 28 | ||
29 | iDeclareType(Window) | 29 | iDeclareType(Window) |
30 | 30 | ||
31 | void setDPIAware_Win32(void); | 31 | void init_Win32(void); |
32 | float desktopDPI_Win32(void); | 32 | float desktopDPI_Win32(void); |
33 | void useExecutableIconResource_SDLWindow(SDL_Window *win); | 33 | void useExecutableIconResource_SDLWindow(SDL_Window *); |
34 | void enableDarkMode_SDLWindow(SDL_Window *); | ||
35 | void handleCommand_Win32(const char *cmd); | ||
34 | 36 | ||
35 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) | 37 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) |
36 | iInt2 cursor_Win32(void); | 38 | iInt2 cursor_Win32(void); |