kitty: capture theme choices however the kitten was launched

Saving a theme stopped sticking: the choice was captured only by the fish
`kitten` wrapper, so `kitty +kitten themes`, a keybinding or any non-fish
shell wrote <appearance>-theme.auto.conf directly and the next regeneration
overwrote it from theme-modes. The dark slot had already lost a pick that
way -- dark-theme.auto.conf held #0d0f31 while canonical dark said #2b2c2e.

Move the capture into apply-theme-mode.sh, which every path runs. It records
a cksum of each generated file after the repair step; on the next run a file
whose checksum differs was written by something else, so it becomes canonical
for that appearance. With no state recorded yet nothing is adopted, so a
first run cannot mistake a pinned mode's duplicated files for real choices.

The fish wrapper keeps only its useful part: re-apply the mode and reload so
the change shows at once.

theme-modes/dark.conf is the recovered #0d0f31 (1984). Covered by a new case
in tests/test_theme_mode.sh that writes the theme file with no shell involved.
This commit is contained in:
Anton Volnuhin 2026-08-06 04:42:59 +03:00
parent 489b401f2f
commit 9e2667f8fe
5 changed files with 124 additions and 75 deletions

View File

@ -65,6 +65,7 @@ Library
# every machine flag the others' mode. run_after_kitty-theme-mode.sh
# regenerates them after apply.
.config/kitty/theme-mode
.config/kitty/.theme-generated
.config/kitty/dark-theme.auto.conf
.config/kitty/light-theme.auto.conf
.config/kitty/no-preference-theme.auto.conf

View File

@ -47,18 +47,46 @@ esac
mkdir -p "$canonical_dir"
# Adopt the current automatic themes as canonical when they have not been
# captured yet. Only safe while following the OS: under a pinned mode the
# generated files all hold the pinned theme and would overwrite the others.
if [ "$mode" = auto ]; then
for appearance in dark light no-preference; do
canonical="$canonical_dir/$appearance.conf"
generated="$config_dir/$appearance-theme.auto.conf"
if [ ! -f "$canonical" ] && [ -f "$generated" ]; then
cp "$generated" "$canonical"
fi
done
fi
# Checksums of the files as this script last wrote them. Anything that differs
# now was rewritten by something else -- in practice the themes kitten saving a
# theme for that appearance -- so it is a new choice and becomes canonical.
# Recording it here rather than in the fish `kitten` wrapper means the choice
# survives however the kitten was launched: `kitty +kitten themes`, a keybinding
# or another shell all bypass that wrapper.
state_file="$config_dir/.theme-generated"
# cksum is POSIX, unlike shasum/sha256sum which differ across platforms.
sum_of() {
[ -f "$1" ] || return 1
cksum < "$1"
}
recorded_sum() {
[ -f "$state_file" ] || return 1
awk -v want="$1" '$1 == want { $1 = ""; sub(/^ /, ""); print; found = 1 }
END { exit !found }' "$state_file"
}
for appearance in dark light no-preference; do
canonical="$canonical_dir/$appearance.conf"
generated="$config_dir/$appearance-theme.auto.conf"
[ -f "$generated" ] || continue
# First adoption of an existing setup: take whatever is there, but only
# while following the OS. Under a pinned mode every generated file holds
# the pinned theme, so adopting them would overwrite the other appearances.
if [ ! -f "$canonical" ]; then
[ "$mode" = auto ] && cp "$generated" "$canonical"
continue
fi
# With no state yet we cannot tell a fresh choice from a file we wrote, so
# leave canonical alone; the checksums recorded below make the next run
# able to tell.
now=$(sum_of "$generated") || continue
was=$(recorded_sum "$appearance") || continue
[ "$now" = "$was" ] || cp "$generated" "$canonical"
done
for appearance in dark light no-preference; do
if [ "$mode" = auto ]; then
@ -85,3 +113,16 @@ for appearance in dark light no-preference; do
done
"$script_dir/repair-auto-themes.sh" "$config_dir"
# Record what we ended up with, after the repair step, so the next run can tell
# an externally-saved theme from one of ours.
tmp_state=$(mktemp "$state_file.tmp.XXXXXX")
trap 'rm -f "$tmp_state"' EXIT HUP INT TERM
for appearance in dark light no-preference; do
generated="$config_dir/$appearance-theme.auto.conf"
[ -f "$generated" ] || continue
printf '%s %s\n' "$appearance" "$(sum_of "$generated")" >> "$tmp_state"
done
chmod 600 "$tmp_state"
mv -f "$tmp_state" "$state_file"
trap - EXIT HUP INT TERM

View File

@ -117,6 +117,37 @@ expect "new light theme is used once selected" \
expect "dark theme survived the whole exchange" \
"$config_dir/theme-modes/dark.conf" "$DARK"
# --- a theme saved without going through the fish shim is still kept -----
# `kitty +kitten themes`, a keybinding, or any non-fish shell writes the
# automatic theme file directly and never runs the `kitten` function. The
# capture therefore lives in apply-theme-mode.sh, keyed on checksums, or the
# next regeneration silently reverts the choice.
echo dark >"$config_dir/theme-mode"
"$root/apply-theme-mode.sh" "$config_dir"
BYPASS_DARK='#0d0f31'
printf 'background %s\n' "$BYPASS_DARK" >"$config_dir/dark-theme.auto.conf"
"$root/apply-theme-mode.sh" "$config_dir"
expect "theme saved outside the shim becomes canonical" \
"$config_dir/theme-modes/dark.conf" "$BYPASS_DARK"
expect "and stays applied" "$config_dir/dark-theme.auto.conf" "$BYPASS_DARK"
# the same for a light theme while the pin is on dark
BYPASS_LIGHT='#fafafa'
printf 'background %s\n' "$BYPASS_LIGHT" >"$config_dir/light-theme.auto.conf"
"$root/apply-theme-mode.sh" "$config_dir"
expect "light theme saved outside the shim becomes canonical" \
"$config_dir/theme-modes/light.conf" "$BYPASS_LIGHT"
expect "but the dark pin still wins on screen" \
"$config_dir/light-theme.auto.conf" "$BYPASS_DARK"
# and a further run must not mistake our own output for a new choice
"$root/apply-theme-mode.sh" "$config_dir"
expect "adoption is idempotent (dark)" \
"$config_dir/theme-modes/dark.conf" "$BYPASS_DARK"
expect "adoption is idempotent (light)" \
"$config_dir/theme-modes/light.conf" "$BYPASS_LIGHT"
# --- a bare `theme` records auto when it lands on the macOS appearance ----
echo auto >"$config_dir/theme-mode"
"$root/apply-theme-mode.sh" "$config_dir"

View File

@ -1,29 +1,37 @@
background #1c2836
foreground #ffffff
cursor #bbbbbb
selection_background #b4d5ff
color0 #000000
color8 #545454
color1 #f9555f
color9 #fa8b8e
color2 #20af89
color10 #34bb99
color3 #fdf029
color11 #ffff55
color4 #589cf5
color12 #589cf5
color5 #934d95
color13 #e75598
color6 #1e9ee6
color14 #3978bb
color7 #bbbbbb
color15 #ffffff
selection_foreground #1c2836
# 1984 theme by Bertie Blackman, https://github.com/covertbert/iterm2-1984/
foreground #ffffff
background #0d0f31
selection_foreground #000000
selection_background #00d5eb
color0 #000000
color1 #ff16b0
color2 #b3f361
color3 #ffea16
color4 #46bdff
color5 #f806fa
color6 #59e1e3
color7 #feffff
color8 #000000
color9 #ff16b0
color10 #b3f361
color11 #ffea16
color12 #46bdff
color13 #f806fa
color14 #6be4e6
color15 #feffff
# URL styles
url_color #f806fa
url_style single
# Cursor styles
cursor #59e1e3
# START_AUTOGENERATED_TAB_STYLE
# Feel free to update these colors manually and remove these comments.
active_tab_foreground #444444
active_tab_background #b4d5ff
active_tab_foreground #eeeeee
active_tab_background #00d5eb
inactive_tab_foreground #ffffff
inactive_tab_background #16202b
inactive_tab_background #0a0c27
# END_AUTOGENERATED_TAB_STYLE

View File

@ -4,49 +4,17 @@ function kitten --description 'Run kitten and restore Kitty theme overrides'
set config_dir "$KITTY_CONFIG_DIRECTORY"
end
set -l is_themes false
if test (count $argv) -gt 0; and test "$argv[1]" = themes
set is_themes true
end
# The automatic theme files are generated from $config_dir/theme-modes, so record
# their state first: whichever one the kitten rewrites is the theme just
# chosen for that appearance, and has to be captured before the mode is
# re-applied over the top of it.
set -l appearances dark light no-preference
set -l before
if test $is_themes = true
for appearance in $appearances
set -l file "$config_dir/$appearance-theme.auto.conf"
if test -r "$file"
set -a before (command shasum <"$file" | string split -f1 ' ')
else
set -a before missing
end
end
end
command kitten $argv
set -l kitten_status $status
# Re-apply the pinned mode over whatever the themes kitten just wrote, and
# reload so the change shows immediately. apply-theme-mode.sh does the
# capturing itself, by checksum, so a theme saved through some other route
# -- `kitty +kitten themes`, a keybinding, another shell -- is picked up on
# its next run rather than being lost.
if test $kitten_status -eq 0
and test $is_themes = true
for index in (seq (count $appearances))
set -l appearance $appearances[$index]
set -l file "$config_dir/$appearance-theme.auto.conf"
test -r "$file"
or continue
set -l now (command shasum <"$file" | string split -f1 ' ')
test "$now" = "$before[$index]"
and continue
command mkdir -p "$config_dir/theme-modes"
or return $status
command cp "$file" "$config_dir/theme-modes/$appearance.conf"
or return $status
end
and test (count $argv) -gt 0
and test "$argv[1]" = themes
"$HOME/.config/kitty/apply-theme-mode.sh" "$config_dir"
or return $status