theming: live-retheme GTK3 apps via a named theme per wallpaper
GTK3 parses ~/.config/gtk-3.0/gtk.css once per process behind a static guard and never re-reads it, and it outranks the theme at PRIORITY_USER — so colours there pin an app to whatever was live when it started. Install a Fred-<wallpaper> theme instead and switch gtk-theme over gsettings, which xdg-desktop-portal-gtk forwards to running apps. One name per wallpaper because gtk_css_provider_get_named memoises providers in a static hash table keyed by name. GTK4/libadwaita ignores gtk-theme-name, so it keeps the user CSS and still needs a restart. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
f1a0a70244
commit
e9f27b8255
1 changed files with 80 additions and 23 deletions
|
|
@ -6,10 +6,10 @@
|
||||||
# `theme-apply` drops that wallpaper's rendered config into place and nudges
|
# `theme-apply` drops that wallpaper's rendered config into place and nudges
|
||||||
# every app that can reload without restarting.
|
# every app that can reload without restarting.
|
||||||
#
|
#
|
||||||
# What follows a switch immediately: quickshell, hyprland, vesktop, folder
|
# What follows a switch immediately: quickshell, hyprland, ghostty (D-Bus
|
||||||
# icons, and ghostty (reloaded over its D-Bus action). GTK apps only re-read
|
# reload-config), GTK3 apps such as nemo (each wallpaper installs its own
|
||||||
# their stylesheet at startup, so nemo etc. change on next launch; so do btop,
|
# named GTK theme, switched over gsettings) and the folder icon tint. On next
|
||||||
# zen and spotify-player.
|
# launch only: GTK4/libadwaita apps, btop, zen, spotify-player.
|
||||||
#
|
#
|
||||||
# stylix stays the source of truth for build-time defaults and for every
|
# stylix stays the source of truth for build-time defaults and for every
|
||||||
# target not listed here — walls/wallpaper.png remains its image.
|
# target not listed here — walls/wallpaper.png remains its image.
|
||||||
|
|
@ -337,11 +337,60 @@ let
|
||||||
done
|
done
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
# A real, installed GTK3 theme per wallpaper — the only way a RUNNING GTK3
|
||||||
|
# app ever changes colour.
|
||||||
|
#
|
||||||
|
# ~/.config/gtk-3.0/gtk.css cannot do it. gtksettings.c loads that file
|
||||||
|
# behind `if (G_UNLIKELY (!css_provider))` on a static, so it is parsed once
|
||||||
|
# per process and never re-read; and it sits at PRIORITY_USER, above the
|
||||||
|
# theme, so leaving a stale copy there would also outrank whatever the theme
|
||||||
|
# supplies. The named-theme provider, by contrast, is reloaded whenever
|
||||||
|
# gtk-theme-name changes — that signal reaches apps here through
|
||||||
|
# xdg-desktop-portal-gtk.
|
||||||
|
#
|
||||||
|
# Hence one theme NAME per wallpaper rather than a single "Fred" that gets
|
||||||
|
# rewritten: gtk_css_provider_get_named memoises providers in a static hash
|
||||||
|
# table keyed by name, so reusing the name would hand back the CSS parsed
|
||||||
|
# the first time. Distinct names also make switching back to an earlier
|
||||||
|
# wallpaper a cache hit with the right colours.
|
||||||
|
gtkThemeName = f: "Fred-${wallSlug f}";
|
||||||
|
gtkTheme = f: s: pkgs.runCommand "gtk-theme-${wallSlug f}" { } ''
|
||||||
|
mkdir -p $out/gtk-3.0
|
||||||
|
# GTK resolves the theme's relative url(assets/...) against the theme dir,
|
||||||
|
# not the imported file, so the artwork has to be reachable from here.
|
||||||
|
ln -s ${pkgs.adw-gtk3}/share/themes/adw-gtk3/gtk-3.0/assets $out/gtk-3.0/assets
|
||||||
|
|
||||||
|
# Both variants: gtk-application-prefer-dark-theme is on, so GTK asks for
|
||||||
|
# gtk-dark.css, and _gtk_css_provider_load_named falls back to gtk.css
|
||||||
|
# only if the variant is missing entirely.
|
||||||
|
for v in gtk.css gtk-dark.css; do
|
||||||
|
{
|
||||||
|
echo "@import url(\"file://${pkgs.adw-gtk3}/share/themes/adw-gtk3/gtk-3.0/$v\");"
|
||||||
|
cat ${gtkCss s}
|
||||||
|
} > $out/gtk-3.0/$v
|
||||||
|
done
|
||||||
|
|
||||||
|
cat > $out/index.theme <<'EOF'
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=X-GNOME-Metatheme
|
||||||
|
Name=${gtkThemeName f}
|
||||||
|
Comment=adw-gtk3 tinted to ${wallStem f}
|
||||||
|
Encoding=UTF-8
|
||||||
|
|
||||||
|
[X-GNOME-Metatheme]
|
||||||
|
GtkTheme=${gtkThemeName f}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
grep -q '@import url' $out/gtk-3.0/gtk-dark.css
|
||||||
|
grep -q '@define-color window_bg_color' $out/gtk-3.0/gtk-dark.css
|
||||||
|
'';
|
||||||
|
|
||||||
# ── One theme directory per wallpaper ──────────────────────────────────
|
# ── One theme directory per wallpaper ──────────────────────────────────
|
||||||
themeFor = f: let s = schemeOf f; in pkgs.runCommand "theme-${wallSlug f}" { } ''
|
themeFor = f: let s = schemeOf f; in pkgs.runCommand "theme-${wallSlug f}" { } ''
|
||||||
mkdir -p $out
|
mkdir -p $out
|
||||||
cp ${wallPalette f}/palette.json $out/palette.json
|
cp ${wallPalette f}/palette.json $out/palette.json
|
||||||
cp ${gtkCss s} $out/gtk3.css
|
cp ${gtkCss s} $out/gtk3.css
|
||||||
|
echo -n "${gtkThemeName f}" > $out/gtk-theme-name
|
||||||
cp ${gtk4Css s} $out/gtk4.css
|
cp ${gtk4Css s} $out/gtk4.css
|
||||||
cp ${ghosttyConf s} $out/ghostty.conf
|
cp ${ghosttyConf s} $out/ghostty.conf
|
||||||
cp ${btopTheme s} $out/btop.theme
|
cp ${btopTheme s} $out/btop.theme
|
||||||
|
|
@ -403,7 +452,14 @@ let
|
||||||
# Ghostty and btop are pointed straight at the state symlink instead
|
# Ghostty and btop are pointed straight at the state symlink instead
|
||||||
# (see the home-manager block below) — one less write to go wrong, and no
|
# (see the home-manager block below) — one less write to go wrong, and no
|
||||||
# way for a stale copy to survive a switch.
|
# way for a stale copy to survive a switch.
|
||||||
install -Dm644 "$theme/gtk3.css" "$HOME/.config/gtk-3.0/gtk.css"
|
# NOTE: no ~/.config/gtk-3.0/gtk.css. GTK3's colours come from the
|
||||||
|
# installed Fred-<wallpaper> theme instead (see gtkTheme above); a copy
|
||||||
|
# here would sit at PRIORITY_USER, outranking the theme, and GTK3 parses
|
||||||
|
# it once per process — so it would pin every GTK3 app to whichever
|
||||||
|
# wallpaper was live when the app started.
|
||||||
|
#
|
||||||
|
# GTK4 still needs it: libadwaita ignores gtk-theme-name, so user CSS is
|
||||||
|
# the only way in, and those apps do change only on restart.
|
||||||
install -Dm644 "$theme/gtk4.css" "$HOME/.config/gtk-4.0/gtk.css"
|
install -Dm644 "$theme/gtk4.css" "$HOME/.config/gtk-4.0/gtk.css"
|
||||||
# Vesktop gets the same CSS twice, on purpose.
|
# Vesktop gets the same CSS twice, on purpose.
|
||||||
#
|
#
|
||||||
|
|
@ -463,22 +519,13 @@ let
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# GTK reads ~/.config/gtk-{3,4}.0/gtk.css once at startup and does not
|
# Point GTK3 at this wallpaper's theme. Every wallpaper has its own theme
|
||||||
# watch it, so running apps only re-read on a theme-name change. Toggling
|
# name, so this is always a real change: gsettings emits, the portal
|
||||||
# the gsettings key away and back is the one nudge that reaches them, and
|
# forwards it, and running GTK3 apps reload the named theme — no restart,
|
||||||
# only lands if something is bridging gsettings to GTK (the portal). Nemo
|
# no away-and-back dance.
|
||||||
# picks the new colours up on next launch regardless.
|
gtktheme=$(cat "$theme/gtk-theme-name")
|
||||||
gsettings=${pkgs.glib.bin}/bin/gsettings
|
${pkgs.glib.bin}/bin/gsettings set org.gnome.desktop.interface gtk-theme "$gtktheme" \
|
||||||
iface=org.gnome.desktop.interface
|
|| echo "gsettings set gtk-theme $gtktheme failed"
|
||||||
if cur=$($gsettings get $iface gtk-theme 2>/dev/null); then
|
|
||||||
cur=''${cur//\'/}
|
|
||||||
# The detour has to be a DIFFERENT name or gsettings writes the same
|
|
||||||
# value, emits no change signal, and nothing reloads.
|
|
||||||
away=Adwaita
|
|
||||||
[ "$cur" = "$away" ] && away=Adwaita-dark
|
|
||||||
$gsettings set $iface gtk-theme "$away" 2>/dev/null || true
|
|
||||||
$gsettings set $iface gtk-theme "$cur" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
'';
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
|
@ -527,8 +574,18 @@ in
|
||||||
# every ANSI-coloured program in a ghostty window follows a switch live.
|
# every ANSI-coloured program in a ghostty window follows a switch live.
|
||||||
stylix.targets.fish.enable = false;
|
stylix.targets.fish.enable = false;
|
||||||
|
|
||||||
# Everything stylix's gtk target did apart from writing gtk.css.
|
# Every wallpaper's GTK3 theme, installed up front. theme-apply only
|
||||||
gtk.theme = { package = pkgs.adw-gtk3; name = "adw-gtk3"; };
|
# flips gtk-theme between them, so they all have to exist on disk —
|
||||||
|
# GTK silently falls back to Adwaita for a name it cannot find.
|
||||||
|
home.file = lib.listToAttrs (map (f: lib.nameValuePair
|
||||||
|
".local/share/themes/${gtkThemeName f}"
|
||||||
|
{ source = gtkTheme f (schemeOf f); })
|
||||||
|
wallFiles);
|
||||||
|
|
||||||
|
# Everything stylix's gtk target did apart from writing gtk.css. The
|
||||||
|
# name here is only the pre-first-switch fallback and what lands in
|
||||||
|
# settings.ini; the live value comes from gsettings via the portal.
|
||||||
|
gtk.theme = { package = pkgs.adw-gtk3; name = gtkThemeName (lib.head wallFiles); };
|
||||||
# Explicitly null, not merely unset: home-manager defaults gtk4.theme to
|
# Explicitly null, not merely unset: home-manager defaults gtk4.theme to
|
||||||
# gtk.theme and then writes gtk-4.0/gtk.css to @import the theme, which
|
# gtk.theme and then writes gtk-4.0/gtk.css to @import the theme, which
|
||||||
# is a file theme-apply owns. gtk4Css reproduces that import itself.
|
# is a file theme-apply owns. gtk4Css reproduces that import itself.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue