From 083936e99b515a3f2854767b9e654450db22f74c Mon Sep 17 00:00:00 2001 From: Claude <242468646+Claude@users.noreply.github.com> Date: Sun, 8 Mar 2026 18:31:58 +0100 Subject: [PATCH] Add Windows dark mode detection and automatic GTK theme switching (#111) --- src/gui/gsc_init.cpp | 26 ++++++++++++++ src/gui/gsc_main_window_iconview.cpp | 8 ++++- src/hz/win32_tools.h | 51 ++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) diff --git a/src/gui/gsc_init.cpp b/src/gui/gsc_init.cpp index 0dff60c..de288ef 100644 --- a/src/gui/gsc_init.cpp +++ b/src/gui/gsc_init.cpp @@ -505,6 +505,32 @@ bool app_init_and_loop(int& argc, char**& argv) } */ + // Detect Windows dark mode and set GTK theme preference accordingly + if constexpr(BuildEnv::is_kernel_family_windows()) { + Glib::RefPtr gtk_settings = Gtk::Settings::get_default(); + if (gtk_settings) { + bool use_dark_theme = false; + #ifdef _WIN32 + // Check Windows registry for dark mode preference + // HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize + // AppsUseLightTheme = 0 means dark mode, 1 means light mode + DWORD apps_use_light_theme = 1; // Default to light mode + if (hz::win32_get_registry_value_dword(HKEY_CURRENT_USER, + R"(Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)", + "AppsUseLightTheme", apps_use_light_theme)) { + use_dark_theme = (apps_use_light_theme == 0); + debug_out_dump("app", "Windows theme detected: " << (use_dark_theme ? "dark" : "light") << "\n"); + } else { + debug_out_dump("app", "Could not read Windows theme preference, defaulting to light mode.\n"); + } + #endif + // Apply the dark theme preference to GTK + gtk_settings->property_gtk_application_prefer_dark_theme().set_value(use_dark_theme); + debug_out_dump("app", "GTK dark theme preference set to: " << (use_dark_theme ? "dark" : "light") << "\n"); + } + } + + // The application is dpi-aware in Windows. // However, Gtk3 does not support fractional scaling, so at 250% scaling in system settings, the UI will use 200%. // diff --git a/src/gui/gsc_main_window_iconview.cpp b/src/gui/gsc_main_window_iconview.cpp index dcb1057..98e47cc 100644 --- a/src/gui/gsc_main_window_iconview.cpp +++ b/src/gui/gsc_main_window_iconview.cpp @@ -120,7 +120,7 @@ bool GscMainWindowIconView::on_draw(const Cairo::RefPtr& cr) return true; } if (empty_view_message_ != Message::None && this->num_icons_ == 0) { // no icons - Glib::RefPtr layout = this->create_pango_layout(""); + const Glib::RefPtr layout = this->create_pango_layout(""); layout->set_alignment(Pango::ALIGN_CENTER); layout->set_markup(get_message_string(empty_view_message_)); @@ -131,6 +131,12 @@ bool GscMainWindowIconView::on_draw(const Cairo::RefPtr& cr) const int pos_y = (get_allocation().get_height() - layout_h) / 2; cr->move_to(pos_x, pos_y); + // Use the foreground color from the widget's style context so + // the text is visible in both light and dark themes. + const auto style_context = get_style_context(); + const Gdk::RGBA fg_color = style_context->get_color(style_context->get_state()); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), fg_color.get_alpha()); + layout->show_in_cairo_context(cr); return true; diff --git a/src/hz/win32_tools.h b/src/hz/win32_tools.h index 73beeae..b33d9db 100644 --- a/src/hz/win32_tools.h +++ b/src/hz/win32_tools.h @@ -79,6 +79,14 @@ inline bool win32_set_registry_value_string(HKEY base, const std::string& keydir, const std::string& key, const std::string& value); +/// Get registry value as a DWORD. +/// Base may be e.g. HKEY_CURRENT_USER. +/// Note that this works only with REG_DWORD types. +/// False is returned for all other types. +inline bool win32_get_registry_value_dword(HKEY base, + const std::string& keydir, const std::string& key, DWORD& put_here); + + /// Redirect stdout and stderr to console window (if open). Requires winxp (at compile-time). /// \param create_if_none if true, create a new console if none was found and attach to it. /// \return false if failed or unsupported. @@ -340,6 +348,49 @@ inline bool win32_set_registry_value_string(HKEY base, +// Get registry value as a DWORD. +// Note that this works only with REG_DWORD types. +inline bool win32_get_registry_value_dword(HKEY base, + const std::string& keydir, const std::string& key, DWORD& put_here) +{ + std::wstring wkeydir = win32_utf8_to_utf16(keydir); + if (wkeydir.empty()) + return false; + + HKEY reg_key = nullptr; + bool open_status = (RegOpenKeyExW(base, wkeydir.c_str(), 0, KEY_QUERY_VALUE, ®_key) == ERROR_SUCCESS); + + if (!open_status) + return false; + + bool ok = false; + std::wstring wkey = win32_utf8_to_utf16(key, &ok); + if (!ok) { // conversion error. Note that an empty string is not an error. + if (reg_key) + RegCloseKey(reg_key); + return false; + } + + DWORD type = 0; + DWORD value = 0; + DWORD nbytes = sizeof(DWORD); + bool status = (RegQueryValueExW(reg_key, wkey.c_str(), nullptr, &type, + reinterpret_cast(&value), &nbytes) == ERROR_SUCCESS); + + if (status && type == REG_DWORD) { + put_here = value; + } else { + status = false; + } + + if (reg_key) + RegCloseKey(reg_key); + + return status; +} + + + // Redirect stdout and stderr to console window (if open). inline bool win32_redirect_stdio_to_console(bool create_if_none) {