From 1210d32b93aad3bc21481633d5bfe9386612ce2d Mon Sep 17 00:00:00 2001 From: Ken Sanislo Date: Mon, 11 May 2026 00:28:26 -0700 Subject: [PATCH] PerKeyEditor: rebuild tool icons on GTK theme change Tool icon pixbufs are baked at construction time using the button's current style context foreground color. Without this, switching the desktop theme (e.g. light <-> dark) at runtime leaves the editor's icons stuck in the previous color while the rest of the UI updates. Subscribe to Gtk.Settings notify::gtk-theme-name and notify::gtk-application-prefer-dark-theme; on either, replace each themed button's child Gtk.Image with a freshly-recolored one. Disconnect both signals from PerKeyEditor.shutdown so the editor doesn't leak handlers across openings. --- lib/solaar/ui/perkey/editor.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/lib/solaar/ui/perkey/editor.py b/lib/solaar/ui/perkey/editor.py index 71a2e7c1..892446fc 100644 --- a/lib/solaar/ui/perkey/editor.py +++ b/lib/solaar/ui/perkey/editor.py @@ -130,6 +130,9 @@ class PerKeyEditor(Gtk.Box): # toolbar row toolbar = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) self._tool_buttons: dict[str, Gtk.RadioButton] = {} + # Track which buttons display a themed icon, so we can re-render them + # when the active GTK theme switches (light <-> dark, theme name). + self._themed_icon_buttons: dict[Gtk.RadioButton, str] = {} self._gradient_swatch: GradientSwatch | None = None first: Gtk.RadioButton | None = None supported = layout.supported_tools if layout else ("single", "rect", "bucket", "gradient") @@ -150,6 +153,7 @@ class PerKeyEditor(Gtk.Box): btn.add(image) btn.set_tooltip_text(tip or label) btn.get_accessible().set_name(label) + self._themed_icon_buttons[btn] = icon_name else: btn.set_label(label) btn.set_tooltip_text(tip) @@ -159,6 +163,14 @@ class PerKeyEditor(Gtk.Box): toolbar.pack_start(btn, False, False, 0) self._tool_buttons[name] = btn + # Re-render themed icons when the GTK theme changes at runtime. + self._theme_signal_handlers: list[tuple[object, int]] = [] + if self._themed_icon_buttons: + settings = Gtk.Settings.get_default() + for prop in ("notify::gtk-theme-name", "notify::gtk-application-prefer-dark-theme"): + hid = settings.connect(prop, self._on_gtk_theme_changed) + self._theme_signal_handlers.append((settings, hid)) + initial_active, initial_previous = 0xFF0000, 0xFF0000 try: persisted = sink.palette_state() @@ -209,6 +221,24 @@ class PerKeyEditor(Gtk.Box): except Exception as e: logger.debug("perkey sink unsubscribe failed: %s", e) self._unsubscribe = None + for obj, hid in self._theme_signal_handlers: + try: + obj.disconnect(hid) + except Exception as e: + logger.debug("theme signal disconnect failed: %s", e) + self._theme_signal_handlers = [] + + def _on_gtk_theme_changed(self, _settings, _pspec) -> None: + """Rebuild themed tool icons so they match the new theme's foreground.""" + for btn, icon_name in self._themed_icon_buttons.items(): + old = btn.get_child() + new_image = _tool_icon_image(icon_name, btn) + if new_image is None: + continue + if old is not None: + btn.remove(old) + btn.add(new_image) + new_image.show() def canvas_size(self) -> tuple[int, int]: """Return the canvas's pixel size_request — what the dialog should