summaryrefslogtreecommitdiff
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
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.
-rw-r--r--src/app.c3
-rw-r--r--src/main.c5
-rw-r--r--src/ui/root.c3
-rw-r--r--src/ui/window.c1
-rw-r--r--src/ui/window.h2
-rw-r--r--src/win32.c164
-rw-r--r--src/win32.h6
7 files changed, 171 insertions, 13 deletions
diff --git a/src/app.c b/src/app.c
index 7aa0153e..813fa396 100644
--- a/src/app.c
+++ b/src/app.c
@@ -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;
diff --git a/src/main.c b/src/main.c
index c8a9c335..61320e79 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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 *);
193void drawWhileResizing_MainWindow (iMainWindow *, int w, int h); /* workaround for SDL bug */ 193void drawWhileResizing_MainWindow (iMainWindow *, int w, int h); /* workaround for SDL bug */
194 194
195int snap_MainWindow (const iMainWindow *); 195int snap_MainWindow (const iMainWindow *);
196iBool isFullscreen_Window (const iMainWindow *); 196iBool isFullscreen_MainWindow (const iMainWindow *);
197 197
198#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) 198#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME)
199SDL_HitTestResult hitTest_MainWindow(const iMainWindow *d, iInt2 pos); 199SDL_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
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}
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
29iDeclareType(Window) 29iDeclareType(Window)
30 30
31void setDPIAware_Win32(void); 31void init_Win32(void);
32float desktopDPI_Win32(void); 32float desktopDPI_Win32(void);
33void useExecutableIconResource_SDLWindow(SDL_Window *win); 33void useExecutableIconResource_SDLWindow(SDL_Window *);
34void enableDarkMode_SDLWindow(SDL_Window *);
35void handleCommand_Win32(const char *cmd);
34 36
35#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) 37#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME)
36iInt2 cursor_Win32(void); 38iInt2 cursor_Win32(void);