theming: reload ghostty over D-Bus, apply hypr keywords one by one
hyprctl --batch reported one combined result, so a rejected key took the rest with it and errors were swallowed; each keyword now reports to the journal. Ghostty's reload_config keybind never arrived via sendshortcut — use its org.gtk.Actions reload-config action instead, which needs no window. Add a gsettings theme toggle to nudge running GTK apps. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
c85bb4545f
commit
43f6a886ac
1 changed files with 75 additions and 37 deletions
|
|
@ -6,10 +6,10 @@
|
|||
# `theme-apply` drops that wallpaper's rendered config into place and nudges
|
||||
# every app that can reload without restarting.
|
||||
#
|
||||
# What follows a switch immediately: quickshell, hyprland, GTK apps, vesktop,
|
||||
# folder icon tint. What follows on next launch: btop, zen, spotify-player.
|
||||
# Ghostty is reloaded in place when a window exists to send the keybind to,
|
||||
# otherwise its (windowless) daemon is restarted.
|
||||
# What follows a switch immediately: quickshell, hyprland, vesktop, folder
|
||||
# icons, and ghostty (reloaded over its D-Bus action). GTK apps only re-read
|
||||
# their stylesheet at startup, so nemo etc. change on next launch; so do btop,
|
||||
# zen and spotify-player.
|
||||
#
|
||||
# stylix stays the source of truth for build-time defaults and for every
|
||||
# target not listed here — walls/wallpaper.png remains its image.
|
||||
|
|
@ -270,21 +270,26 @@ let
|
|||
theme[process_end]="${base08}"
|
||||
'');
|
||||
|
||||
# One `hyprctl --batch` argument string. Mirrors the colour keys set
|
||||
# statically in settings/hyprland.nix, which stay the build-time fallback
|
||||
# for the window or two before theme-apply first runs.
|
||||
hyprBatch = s: pkgs.writeText "hypr.batch" (lib.concatStringsSep " ; " [
|
||||
"keyword general:col.active_border rgb(${s.base04})"
|
||||
"keyword general:col.inactive_border rgb(${s.base03})"
|
||||
"keyword decoration:shadow:color rgba(${s.base00}99)"
|
||||
"keyword group:col.border_active rgb(${s.base04})"
|
||||
"keyword group:col.border_inactive rgb(${s.base03})"
|
||||
"keyword group:col.border_locked_active rgb(${s.base05})"
|
||||
"keyword group:groupbar:text_color rgb(${s.base05})"
|
||||
"keyword group:groupbar:col.active rgb(${s.base04})"
|
||||
"keyword group:groupbar:col.inactive rgb(${s.base03})"
|
||||
"keyword misc:background_color rgb(${s.base00})"
|
||||
]);
|
||||
# One `<key> <value>` per line, applied by theme-apply with a separate
|
||||
# `hyprctl keyword` each. NOT `hyprctl --batch`: a batch reports one
|
||||
# combined result, so a single rejected key silently takes the rest of the
|
||||
# colours with it. Per-key, a bad one names itself in the journal and the
|
||||
# other nine still land.
|
||||
#
|
||||
# Mirrors the colour keys set statically in settings/hyprland.nix, which
|
||||
# stay the build-time fallback until theme-apply first runs.
|
||||
hyprKeywords = s: pkgs.writeText "hypr.keywords" ''
|
||||
general:col.active_border rgb(${s.base04})
|
||||
general:col.inactive_border rgb(${s.base03})
|
||||
decoration:shadow:color rgba(${s.base00}99)
|
||||
group:col.border_active rgb(${s.base04})
|
||||
group:col.border_inactive rgb(${s.base03})
|
||||
group:col.border_locked_active rgb(${s.base05})
|
||||
group:groupbar:text_color rgb(${s.base05})
|
||||
group:groupbar:col.active rgb(${s.base04})
|
||||
group:groupbar:col.inactive rgb(${s.base03})
|
||||
misc:background_color rgb(${s.base00})
|
||||
'';
|
||||
|
||||
# A featherweight icon theme that inherits Papirus-Dark and overrides only
|
||||
# the folder faces, rather than a recoloured copy of the whole 200MB tree
|
||||
|
|
@ -338,7 +343,7 @@ let
|
|||
cp ${gtk4Css s} $out/gtk4.css
|
||||
cp ${ghosttyConf s} $out/ghostty.conf
|
||||
cp ${btopTheme s} $out/btop.theme
|
||||
cp ${hyprBatch s} $out/hypr.batch
|
||||
cp ${hyprKeywords s} $out/hypr.keywords
|
||||
cp ${pkgs.writeText "vesktop.css" (stylixize s (builtins.readFile "${inputs.self}/templates/vesktop-quickCss.css"))} $out/vesktop.css
|
||||
cp ${pkgs.writeText "zen.css" (stylixize s (builtins.readFile "${inputs.self}/templates/zen-userChrome.css"))} $out/zen.css
|
||||
ln -s ${iconTheme s} $out/icons
|
||||
|
|
@ -355,7 +360,7 @@ let
|
|||
check gtk3.css '@define-color window_bg_color'
|
||||
check gtk4.css 'adw-gtk3/gtk-4.0/gtk.css'
|
||||
check ghostty.conf '^background = #'
|
||||
check hypr.batch 'col.active_border rgb('
|
||||
check hypr.keywords 'col.active_border rgb('
|
||||
check vesktop.css '^ --bg-4: #'
|
||||
'';
|
||||
|
||||
|
|
@ -398,26 +403,59 @@ let
|
|||
cp -r "$theme"/icons/. "$icons"/
|
||||
chmod -R u+w "$icons"
|
||||
|
||||
# Hyprland takes its colours live.
|
||||
${pkgs.hyprland}/bin/hyprctl --batch "$(cat "$theme/hypr.batch")" >/dev/null 2>&1 || true
|
||||
# Hyprland takes its colours live, one keyword at a time. hyprctl answers
|
||||
# "ok" and exits 0 even for a key it did not like, so the reply is what
|
||||
# gets checked — anything else is named in quickshell's journal
|
||||
# (journalctl --user -u quickshell) instead of vanishing.
|
||||
while read -r key value; do
|
||||
[ -n "$key" ] || continue
|
||||
reply=$(${pkgs.hyprland}/bin/hyprctl keyword "$key" "$value" 2>&1)
|
||||
[ "$reply" = "ok" ] \
|
||||
|| echo "theme-apply: hyprctl keyword $key $value -> $reply" >&2
|
||||
done < "$theme/hypr.keywords"
|
||||
|
||||
# Ghostty holds its config in one process (gtk-single-instance), so a new
|
||||
# window would inherit the old theme. Poke the reload_config keybind at
|
||||
# any live window — the reload is process-wide, so one is enough.
|
||||
# window inherits whatever the daemon parsed at startup — reopening a
|
||||
# terminal is NOT enough to pick up a new theme.
|
||||
#
|
||||
# The match has to cover the --class overrides from settings/hyprland.nix
|
||||
# (dev.fred.scratchpad, dev.fred.spotify) as well as the default id: those
|
||||
# windows are the same process, and mistaking them for "no ghostty open"
|
||||
# would restart the daemon out from under a running shell.
|
||||
cls=$(${pkgs.hyprland}/bin/hyprctl -j clients 2>/dev/null \
|
||||
| ${pkgs.jq}/bin/jq -r 'map(select(.class | test("ghostty|^dev\\.fred\\.")))[0].class // empty')
|
||||
if [ -n "$cls" ]; then
|
||||
${pkgs.hyprland}/bin/hyprctl dispatch sendshortcut \
|
||||
"CTRL SHIFT ALT, F12, class:^(''${cls//./\\.})$" >/dev/null 2>&1 || true
|
||||
# It is a GTK application, so it publishes its actions on the session bus;
|
||||
# `reload-config` is the same action the reload_config keybind fires. Going
|
||||
# through D-Bus works with no window open, needs no window focused, and
|
||||
# doesn't depend on synthesising a keypress into the right surface.
|
||||
if ! ${pkgs.glib.bin}/bin/gdbus call --session \
|
||||
--dest com.mitchellh.ghostty \
|
||||
--object-path /com/mitchellh/ghostty \
|
||||
--method org.gtk.Actions.Activate \
|
||||
reload-config '[]' '{}' >/dev/null 2>&1; then
|
||||
# No instance on the bus, or the action is gone. Restarting the daemon
|
||||
# fixes it but takes every open terminal with it, so that is only on the
|
||||
# table when there is nothing to lose. The class test has to cover the
|
||||
# --class overrides from settings/hyprland.nix (dev.fred.scratchpad,
|
||||
# dev.fred.spotify) — those windows are the same process.
|
||||
if ${pkgs.hyprland}/bin/hyprctl -j clients 2>/dev/null \
|
||||
| ${pkgs.jq}/bin/jq -e 'any(.[].class; test("ghostty|^dev\\.fred\\."))' >/dev/null 2>&1; then
|
||||
echo "theme-apply: ghostty reload-config failed; open terminals keep the old theme" >&2
|
||||
else
|
||||
# No windows to lose: the daemon is the only thing holding stale config.
|
||||
${pkgs.systemd}/bin/systemctl --user restart ghostty-daemon.service 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# GTK reads ~/.config/gtk-{3,4}.0/gtk.css once at startup and does not
|
||||
# watch it, so running apps only re-read on a theme-name change. Toggling
|
||||
# the gsettings key away and back is the one nudge that reaches them, and
|
||||
# only lands if something is bridging gsettings to GTK (the portal). Nemo
|
||||
# picks the new colours up on next launch regardless.
|
||||
gsettings=${pkgs.glib.bin}/bin/gsettings
|
||||
iface=org.gnome.desktop.interface
|
||||
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
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue