From 5460855ea410caa8fcfc9f9411319f73a381923c Mon Sep 17 00:00:00 2001 From: deepend Date: Mon, 16 Feb 2026 20:26:45 -0700 Subject: [PATCH] =?UTF-8?q?Implemented=20a=20Windows-specific=20theme=20de?= =?UTF-8?q?tection=20path=20with=20High=20Contrast=20safeguards,=20includi?= =?UTF-8?q?ng=20helpers=20to=20detect=20HC=20mode,=20read=20Windows=20syst?= =?UTF-8?q?em=20theme=20preference=20from=20the=20registry,=20and=20gate?= =?UTF-8?q?=20native=20titlebar=20dark=20mode=20updates=20when=20HC=20is?= =?UTF-8?q?=20active.=20Also=20added=20DWM=20header=20usage=20for=20native?= =?UTF-8?q?=20titlebar=20APIs.=20Added=20a=20unified=20theme=20application?= =?UTF-8?q?=20pipeline=20via=20fe=5Fapply=5Ftheme=5Ffor=5Fmode()=20and=20f?= =?UTF-8?q?e=5Fapply=5Ftheme=5Fto=5Ftoplevel(),=20and=20routed=20auto-mode?= =?UTF-8?q?=20refresh=20logic=20through=20it=20so=20palette=20+=20GTK=20in?= =?UTF-8?q?put=20styling=20are=20applied=20consistently=20from=20one=20pla?= =?UTF-8?q?ce.=20Switched=20startup=20and=20settings-apply=20flows=20to=20?= =?UTF-8?q?use=20the=20unified=20theming=20function,=20replacing=20direct?= =?UTF-8?q?=20palette-only=20calls=20so=20system-mode=20detection=20?= =?UTF-8?q?=E2=86=92=20theme=20apply=20is=20consistent=20across=20initiali?= =?UTF-8?q?zation=20and=20preferences=20changes.=20Applied=20native=20titl?= =?UTF-8?q?ebar=20updates=20when=20top-level=20windows=20are=20shown=20and?= =?UTF-8?q?=20during=20setup=20reapply=20across=20open=20sessions,=20and?= =?UTF-8?q?=20wired=20Windows=20builds=20to=20link=20dwmapi=20in=20both=20?= =?UTF-8?q?Meson=20and=20MSVC=20project=20files.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/fe-gtk/fe-gtk.c | 144 ++++++++++++++++++++++++++++++++--------- src/fe-gtk/fe-gtk.h | 2 + src/fe-gtk/maingui.c | 4 ++ src/fe-gtk/meson.build | 1 + src/fe-gtk/setup.c | 4 +- 5 files changed, 122 insertions(+), 33 deletions(-) diff --git a/src/fe-gtk/fe-gtk.c b/src/fe-gtk/fe-gtk.c index 5345ab5f..9a1365c7 100644 --- a/src/fe-gtk/fe-gtk.c +++ b/src/fe-gtk/fe-gtk.c @@ -28,6 +28,7 @@ #ifdef WIN32 #include +#include #else #include #endif @@ -285,6 +286,8 @@ const char cursor_color_rc[] = "}" "widget \"*.zoitechat-inputbox\" style : application \"xc-ib-st\""; +InputStyle *create_input_style (InputStyle *style); + static const char adwaita_workaround_rc[] = "style \"zoitechat-input-workaround\"" "{" @@ -301,6 +304,85 @@ static const char adwaita_workaround_rc[] = "}" "widget \"*.zoitechat-inputbox\" style \"zoitechat-input-workaround\""; +#ifdef G_OS_WIN32 +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +static gboolean +fe_win32_high_contrast_is_enabled (void) +{ + HIGHCONTRASTW hc; + + ZeroMemory (&hc, sizeof (hc)); + hc.cbSize = sizeof (hc); + if (!SystemParametersInfoW (SPI_GETHIGHCONTRAST, sizeof (hc), &hc, 0)) + return FALSE; + + return (hc.dwFlags & HCF_HIGHCONTRASTON) != 0; +} + +static gboolean +fe_win32_try_get_system_dark (gboolean *prefer_dark) +{ + DWORD value = 1; + DWORD value_size = sizeof (value); + LSTATUS status; + + status = RegGetValueW (HKEY_CURRENT_USER, + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", + L"AppsUseLightTheme", + RRF_RT_REG_DWORD, + NULL, + &value, + &value_size); + if (status != ERROR_SUCCESS) + status = RegGetValueW (HKEY_CURRENT_USER, + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", + L"SystemUsesLightTheme", + RRF_RT_REG_DWORD, + NULL, + &value, + &value_size); + + if (status != ERROR_SUCCESS) + return FALSE; + + *prefer_dark = (value == 0); + return TRUE; +} + +static void +fe_win32_apply_native_titlebar (GtkWidget *window, gboolean dark_mode) +{ + HWND hwnd; + BOOL use_dark; + + if (!window || !gtk_widget_get_realized (window)) + return; + + hwnd = gdk_win32_window_get_handle (gtk_widget_get_window (window)); + if (!hwnd) + return; + + if (fe_win32_high_contrast_is_enabled ()) + return; + + use_dark = dark_mode ? TRUE : FALSE; + DwmSetWindowAttribute (hwnd, + DWMWA_USE_IMMERSIVE_DARK_MODE, + &use_dark, + sizeof (use_dark)); +} +#else +static void +fe_win32_apply_native_titlebar (GtkWidget *window, gboolean dark_mode) +{ + (void) window; + (void) dark_mode; +} +#endif + static gboolean fe_system_prefers_dark (void) { @@ -309,42 +391,16 @@ fe_system_prefers_dark (void) char *theme_name = NULL; #ifdef G_OS_WIN32 gboolean have_win_pref = FALSE; + + if (fe_win32_high_contrast_is_enabled ()) + return FALSE; #endif if (!settings) return FALSE; #ifdef G_OS_WIN32 - { - DWORD value = 1; - DWORD value_size = sizeof (value); - LSTATUS status; - - status = RegGetValueW (HKEY_CURRENT_USER, - L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", - L"AppsUseLightTheme", - RRF_RT_REG_DWORD, - NULL, - &value, - &value_size); - if (status != ERROR_SUCCESS) - status = RegGetValueW (HKEY_CURRENT_USER, - L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", - L"SystemUsesLightTheme", - RRF_RT_REG_DWORD, - NULL, - &value, - &value_size); - - if (status == ERROR_SUCCESS) - { - prefer_dark = (value == 0); - have_win_pref = TRUE; - } - } -#endif - -#ifdef G_OS_WIN32 + have_win_pref = fe_win32_try_get_system_dark (&prefer_dark); if (!have_win_pref) #endif if (g_object_class_find_property (G_OBJECT_GET_CLASS (settings), @@ -388,7 +444,7 @@ fe_auto_dark_mode_changed (GtkSettings *settings, GParamSpec *pspec, gpointer da return; auto_dark_mode_enabled = enabled; - palette_apply_dark_mode (enabled); + fe_apply_theme_for_mode (ZOITECHAT_DARK_MODE_AUTO, NULL); setup_apply_real (0, TRUE, FALSE, FALSE); } @@ -404,6 +460,30 @@ fe_refresh_auto_dark_mode (void) fe_auto_dark_mode_changed (NULL, NULL, NULL); } +gboolean +fe_apply_theme_for_mode (unsigned int mode, gboolean *palette_changed) +{ + gboolean enabled = fe_dark_mode_is_enabled_for (mode); + gboolean changed = palette_apply_dark_mode (enabled); + + if (palette_changed) + *palette_changed = changed; + + if (input_style) + create_input_style (input_style); + + return enabled; +} + +void +fe_apply_theme_to_toplevel (GtkWidget *window) +{ + if (!window) + return; + + fe_win32_apply_native_titlebar (window, fe_dark_mode_is_enabled ()); +} + gboolean fe_dark_mode_is_enabled_for (unsigned int mode) { @@ -625,7 +705,7 @@ fe_init (void) GtkSettings *settings; palette_load (); - palette_apply_dark_mode (fe_dark_mode_is_enabled ()); + fe_apply_theme_for_mode (prefs.hex_gui_dark_mode, NULL); key_init (); pixmaps_init (); diff --git a/src/fe-gtk/fe-gtk.h b/src/fe-gtk/fe-gtk.h index 03e55508..626bfb2a 100644 --- a/src/fe-gtk/fe-gtk.h +++ b/src/fe-gtk/fe-gtk.h @@ -211,6 +211,8 @@ gboolean fe_dark_mode_is_enabled (void); gboolean fe_dark_mode_is_enabled_for (unsigned int mode); void fe_set_auto_dark_mode_state (gboolean enabled); void fe_refresh_auto_dark_mode (void); +gboolean fe_apply_theme_for_mode (unsigned int mode, gboolean *palette_changed); +void fe_apply_theme_to_toplevel (GtkWidget *window); #define SPELL_ENTRY_GET_TEXT(e) ((char *)(gtk_entry_get_text (GTK_ENTRY(e)))) #define SPELL_ENTRY_SET_TEXT(e,txt) gtk_entry_set_text(GTK_ENTRY(e),txt) diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index 5a59b876..ec5e6b6c 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -3944,6 +3944,7 @@ mg_create_topwindow (session *sess) mg_place_userlist_and_chanview (sess->gui); gtk_widget_show (win); + fe_apply_theme_to_toplevel (win); #ifdef G_OS_WIN32 parent_win = gtk_widget_get_window (win); @@ -4118,6 +4119,7 @@ mg_create_tabwindow (session *sess) mg_place_userlist_and_chanview (sess->gui); gtk_widget_show (win); + fe_apply_theme_to_toplevel (win); #ifdef G_OS_WIN32 parent_win = gtk_widget_get_window (win); @@ -4141,6 +4143,8 @@ mg_apply_setup (void) ((xtext_buffer *)sess->res->buffer)->needs_recalc = TRUE; if (!sess->gui->is_tab || !done_main) mg_place_userlist_and_chanview (sess->gui); + if (sess->gui->window) + fe_apply_theme_to_toplevel (sess->gui->window); if (sess->gui->is_tab) done_main = TRUE; list = list->next; diff --git a/src/fe-gtk/meson.build b/src/fe-gtk/meson.build index a845ab05..b66dfd73 100644 --- a/src/fe-gtk/meson.build +++ b/src/fe-gtk/meson.build @@ -77,6 +77,7 @@ zoitechat_gtk_ldflags = [] if host_machine.system() == 'windows' zoitechat_gtk_sources += 'notifications/notification-windows.c' + zoitechat_gtk_deps += cc.find_library('dwmapi', required: true) # TODO: mingw doesn't have these headers or libs # add_languages('cpp') diff --git a/src/fe-gtk/setup.c b/src/fe-gtk/setup.c index 0402e27e..84eff508 100644 --- a/src/fe-gtk/setup.c +++ b/src/fe-gtk/setup.c @@ -3059,7 +3059,9 @@ setup_apply (struct zoitechatprefs *pr) * the preference flips but the palette stays the same (aka: "nothing happens"). */ { - gboolean pal_changed = palette_apply_dark_mode (fe_dark_mode_is_enabled_for (prefs.hex_gui_dark_mode)); + gboolean pal_changed = FALSE; + + fe_apply_theme_for_mode (prefs.hex_gui_dark_mode, &pal_changed); if (prefs.hex_gui_dark_mode != old_dark_mode || pal_changed) color_change = TRUE; }