diff --git a/private_dot_config/kitty/theme-modes/private_light.conf b/private_dot_config/kitty/theme-modes/private_light.conf index 27835ec..1a5db3f 100644 --- a/private_dot_config/kitty/theme-modes/private_light.conf +++ b/private_dot_config/kitty/theme-modes/private_light.conf @@ -1,80 +1,59 @@ # vim:ft=kitty -## name: Catppuccin-Latte -## author: Pocco81 (https://github.com/Pocco81) -## license: MIT -## upstream: https://github.com/catppuccin/kitty/blob/main/themes/latte.conf -## blurb: Soothing pastel theme for the high-spirited! - +## name: Kaolin Breeze +## author: Ogden Webb +## license: GPLv3 +## blurb: Light Kaolin theme with soft colors # The basic colors -foreground #4C4F69 -background #EFF1F5 -selection_foreground #EFF1F5 -selection_background #DC8A78 +foreground #383e3f +background #EBE8E4 +selection_foreground #EBE8E4 +selection_background #383e3f # Cursor colors -cursor #DC8A78 -cursor_text_color #EFF1F5 +cursor #383e3f +cursor_text_color #EBE8E4 -# URL underline color when hovering with mouse -url_color #DC8A78 - -# Kitty window border colors -active_border_color #7287FD -inactive_border_color #9CA0B0 -bell_border_color #DF8E1D - -# OS Window titlebar colors -wayland_titlebar_color system -macos_titlebar_color system +# kitty window border colors +active_border_color #48a9a9 +inactive_border_color #7D8468 # Tab bar colors -active_tab_foreground #EFF1F5 -active_tab_background #8839EF -inactive_tab_foreground #4C4F69 -inactive_tab_background #9CA0B0 -tab_bar_background #BCC0CC - -# Colors for marks (marked text in the terminal) -mark1_foreground #EFF1F5 -mark1_background #7287fD -mark2_foreground #EFF1F5 -mark2_background #8839EF -mark3_foreground #EFF1F5 -mark3_background #209FB5 - -# The 16 terminal colors +active_tab_background #383e3f +active_tab_foreground #C9C2BD +inactive_tab_background #7D8468 +inactive_tab_foreground #EBE8E4 # black -color0 #5C5F77 -color8 #6C6F85 +color0 #383e3f +color8 #7D8468 # red -color1 #D20F39 -color9 #D20F39 +color1 #cd5c60 +color9 #ef6787 # green -color2 #40A02B -color10 #40A02B +color2 #39855f +color10 #3e594e # yellow -color3 #DF8E1D -color11 #DF8E1D +color3 #b87e3c +color11 #d1832e # blue -color4 #1E66F5 -color12 #1E66F5 +color4 #2683b5 +color12 #4F9CB8 # magenta -color5 #EA76CB -color13 #EA76CB +color5 #845A84 +color13 #605DB3 # cyan -color6 #179299 -color14 #179299 +color6 #48a9a9 +color14 #008b8b # white -color7 #ACB0BE -color15 #BCC0CC +color7 #C9C2BD +color15 #60696b diff --git a/private_dot_config/private_fish/functions/private___tide_parse_osc_background.fish b/private_dot_config/private_fish/functions/private___tide_parse_osc_background.fish new file mode 100644 index 0000000..eb5f620 --- /dev/null +++ b/private_dot_config/private_fish/functions/private___tide_parse_osc_background.fish @@ -0,0 +1,35 @@ +function __tide_parse_osc_background --argument-names reply \ + --description 'Extract RRGGBB from a terminal OSC 11 background report' + + # The reply is OSC 11 ; rgb:R/G/B ST, with one to four hex digits per + # channel. Scan for it rather than anchoring: Fish keeps kitty's colour + # scheme notifications subscribed, so an unrelated report can share the + # buffer with this one. + set -l parts ( + string match -gr \ + ']11;rgb:([0-9A-Fa-f]{1,4})/([0-9A-Fa-f]{1,4})/([0-9A-Fa-f]{1,4})' \ + -- "$reply" + ) + test (count $parts) -eq 3 + or return 1 + + set -l hex + for part in $parts + # Each channel is scaled to the width the terminal chose to report it + # in, so f, ff and ffff all mean full intensity. + set -l max + switch (string length -- $part) + case 1 + set max 15 + case 2 + set max 255 + case 3 + set max 4095 + case '*' + set max 65535 + end + set -a hex (printf '%02X' (math -s0 "round(255 * 0x$part / $max)")) + end + + string join '' $hex +end diff --git a/private_dot_config/private_fish/functions/private___tide_terminal_background_color.fish b/private_dot_config/private_fish/functions/private___tide_terminal_background_color.fish index 25c315b..77bc10c 100644 --- a/private_dot_config/private_fish/functions/private___tide_terminal_background_color.fish +++ b/private_dot_config/private_fish/functions/private___tide_terminal_background_color.fish @@ -1,18 +1,50 @@ +function __tide_background_agrees_with --argument-names hex appearance \ + --description 'True when a background is as light or dark as the appearance claims' + + set -l luminance (__tide_relative_luminance $hex) + test -n "$luminance" + or return 1 + + set -l scaled (math -s0 "round($luminance * 1000)") + if test "$appearance" = light + test $scaled -gt 500 + else + test $scaled -lt 500 + end +end + function __tide_terminal_background_color --argument-names appearance \ - --description 'Print the background color kitty is showing for an appearance' + --description 'Print the background color the terminal is showing' contains -- $appearance dark light or return 1 + # Ask the terminal first. It is the only source that is right over ssh, + # where the config files below belong to the *remote* machine and describe + # the mode pinned there, not the window this shell is displayed in. A remote + # box pinned to dark offers its dark theme as its "light" one, the check + # rejects it, and the caller then falls back to plain white -- lighter than + # the real background rather than darker. + if functions -q __tide_terminal_osc_background + set -l queried (__tide_terminal_osc_background) + if string match -qr '^[0-9A-Fa-f]{6}$' -- "$queried" + set queried (string upper -- $queried) + if __tide_background_agrees_with $queried $appearance + echo $queried + return 0 + end + end + end + functions -q __kitty_config_dir or return 1 set -l config_dir (__kitty_config_dir) - # kitty gives the *-theme.auto.conf files priority over includes and even - # over --override, so whatever is in them is what the window is actually - # showing -- whether it got there from `kitten themes` or from - # apply-theme-mode.sh pinning a mode. + # Locally these describe this very window: kitty gives the + # *-theme.auto.conf files priority over includes and even over --override, + # so whatever is in them is what it is actually showing -- whether that came + # from `kitten themes` or from apply-theme-mode.sh pinning a mode. for file in $config_dir/$appearance-theme.auto.conf $config_dir/no-preference-theme.auto.conf test -r $file or continue @@ -22,21 +54,11 @@ function __tide_terminal_background_color --argument-names appearance \ or continue set hex (string upper -- $hex) - set -l luminance (__tide_relative_luminance $hex) - test -n "$luminance" - or continue - # A background that disagrees with the appearance Fish reported means # we read the wrong file. A pill derived from the wrong background is # worse than the static fallback, so skip it. - set -l scaled (math -s0 "round($luminance * 1000)") - if test "$appearance" = light - test $scaled -gt 500 - or continue - else - test $scaled -lt 500 - or continue - end + __tide_background_agrees_with $hex $appearance + or continue echo $hex return 0 diff --git a/private_dot_config/private_fish/functions/private___tide_terminal_osc_background.fish b/private_dot_config/private_fish/functions/private___tide_terminal_osc_background.fish new file mode 100644 index 0000000..ac297d3 --- /dev/null +++ b/private_dot_config/private_fish/functions/private___tide_terminal_osc_background.fish @@ -0,0 +1,48 @@ +function __tide_terminal_osc_background \ + --description 'Ask the terminal which background it is showing (OSC 11)' + + # Only an interactive shell has anyone to ask. Do not test stdin or stdout + # here: the caller always reads this function through a command + # substitution, so its stdout is a pipe every single time. /dev/tty is the + # terminal however the two are redirected, and the stty below fails + # harmlessly if there is no controlling terminal to open. + status is-interactive + or return 1 + + # tmux and screen both need the query wrapped, and each wants it wrapped + # differently. Leave those to the caller's config-file path instead of + # guessing wrong and eating the user's keystrokes waiting for a reply. + set -q TMUX + and return 1 + string match -q 'screen*' -- "$TERM" + and return 1 + + set -l saved (stty -g /dev/null) + test -n "$saved" + or return 1 + + # min 0 / time 2 makes every read give up after two tenths of a second, so a + # terminal that ignores OSC 11 costs that much once per appearance change + # and can never hang the shell. Restore the settings the moment the read + # loop ends, before anything that could fail. + stty raw -echo min 0 time 2 /dev/null + printf '\e]11;?\e\\' >/dev/tty 2>/dev/null + + set -l reply + for attempt in 1 2 3 4 5 + set -l chunk (dd bs=64 count=1 /dev/null | string collect -N) + test -n "$chunk" + or break + set reply "$reply$chunk" + + # Stop as soon as a whole report has arrived; a channel that is still + # being written would parse as a narrower one and give a wrong colour. + string match -q '*]11;rgb:*' -- "$reply" + and string match -qr '\a|\e\\\\' -- "$reply" + and break + end + + stty $saved /dev/null + + __tide_parse_osc_background "$reply" +end diff --git a/private_dot_config/private_fish/private_tests/private_fixtures/kitty-remote-dark/private_dark-theme.auto.conf b/private_dot_config/private_fish/private_tests/private_fixtures/kitty-remote-dark/private_dark-theme.auto.conf new file mode 100644 index 0000000..fd54daf --- /dev/null +++ b/private_dot_config/private_fish/private_tests/private_fixtures/kitty-remote-dark/private_dark-theme.auto.conf @@ -0,0 +1,5 @@ +# vim:ft=kitty +# A machine pinned to dark puts its dark theme in every slot, so even the +# "light" one describes a dark window -- the ssh case this fixture stands in for. +background #212121 +foreground #BDB9AE diff --git a/private_dot_config/private_fish/private_tests/private_fixtures/kitty-remote-dark/private_light-theme.auto.conf b/private_dot_config/private_fish/private_tests/private_fixtures/kitty-remote-dark/private_light-theme.auto.conf new file mode 100644 index 0000000..fd54daf --- /dev/null +++ b/private_dot_config/private_fish/private_tests/private_fixtures/kitty-remote-dark/private_light-theme.auto.conf @@ -0,0 +1,5 @@ +# vim:ft=kitty +# A machine pinned to dark puts its dark theme in every slot, so even the +# "light" one describes a dark window -- the ssh case this fixture stands in for. +background #212121 +foreground #BDB9AE diff --git a/private_dot_config/private_fish/private_tests/private_fixtures/kitty-remote-dark/private_no-preference-theme.auto.conf b/private_dot_config/private_fish/private_tests/private_fixtures/kitty-remote-dark/private_no-preference-theme.auto.conf new file mode 100644 index 0000000..fd54daf --- /dev/null +++ b/private_dot_config/private_fish/private_tests/private_fixtures/kitty-remote-dark/private_no-preference-theme.auto.conf @@ -0,0 +1,5 @@ +# vim:ft=kitty +# A machine pinned to dark puts its dark theme in every slot, so even the +# "light" one describes a dark window -- the ssh case this fixture stands in for. +background #212121 +foreground #BDB9AE diff --git a/private_dot_config/private_fish/private_tests/private_test_tide_terminal_appearance.fish b/private_dot_config/private_fish/private_tests/private_test_tide_terminal_appearance.fish index 8e2e6c2..2ea15a3 100644 --- a/private_dot_config/private_fish/private_tests/private_test_tide_terminal_appearance.fish +++ b/private_dot_config/private_fish/private_tests/private_test_tide_terminal_appearance.fish @@ -37,9 +37,10 @@ set -l luminance_helper $project_root/functions/__tide_relative_luminance.fish set -l scale_helper $project_root/functions/__tide_scale_luminance.fish set -l ceiling_helper $project_root/functions/__tide_contrast_ceiling.fish set -l background_probe $project_root/functions/__tide_terminal_background_color.fish +set -l osc_parser $project_root/functions/__tide_parse_osc_background.fish for file in $light_mapper $appearance_applicator $luminance_helper $scale_helper \ - $ceiling_helper $background_probe + $ceiling_helper $background_probe $osc_parser test -f $file or __test_fail "missing "(path basename $file) end @@ -49,6 +50,7 @@ source $luminance_helper source $scale_helper source $ceiling_helper source $background_probe +source $osc_parser source $appearance_applicator # The applicator exports its overrides, so a shell that already applied a @@ -112,7 +114,67 @@ set -l empty_config (mktemp -d) KITTY_CONFIG_DIRECTORY=$empty_config __tide_terminal_background_color light __test_assert_equal 1 $status "the probe reports failure when no theme is readable" -rm -rf $fake_config $empty_config +# --- OSC 11 replies ------------------------------------------------------ +__test_assert_equal EBE8E4 \ + (__tide_parse_osc_background \e']11;rgb:ebeb/e8e8/e4e4'\e\\) \ + "a four-digit-per-channel report is scaled to eight bits" +__test_assert_equal FFFFFF (__tide_parse_osc_background \e']11;rgb:ff/ff/ff'\a) \ + "a two-digit report with a BEL terminator is read" +__test_assert_equal FFFFFF (__tide_parse_osc_background \e']11;rgb:f/f/f'\a) \ + "channels are scaled to the width they are reported in" +__test_assert_equal 000000 (__tide_parse_osc_background \e']11;rgb:0000/0000/0000'\e\\) \ + "black is not mistaken for a failed parse" +# Fish keeps kitty's colour-scheme notifications on, so another report can be +# sitting in the buffer either side of this one. +__test_assert_equal EBE8E4 \ + (__tide_parse_osc_background \e'[?997;1n'\e']11;rgb:ebeb/e8e8/e4e4'\e\\) \ + "the report is found next to unrelated terminal output" + +__tide_parse_osc_background 'not a reply at all' +__test_assert_equal 1 $status "a reply without a colour is a failure, not a colour" +__tide_parse_osc_background '' +__test_assert_equal 1 $status "an empty reply is a failure" + +# --- the terminal outranks the config files ------------------------------ +# This is the ssh case: the remote box is pinned to dark, so *its* +# light-theme.auto.conf holds a dark theme, while the window on this end is +# light. Reading files there yields no usable answer and the caller falls back +# to white, which lands lighter than the real background instead of darker. +set -l remote_config (mktemp -d) +printf 'background #212121\n' >$remote_config/light-theme.auto.conf +printf 'background #212121\n' >$remote_config/dark-theme.auto.conf +printf 'background #212121\n' >$remote_config/no-preference-theme.auto.conf + +KITTY_CONFIG_DIRECTORY=$remote_config __tide_terminal_background_color light +__test_assert_equal 1 $status \ + "without the terminal, a dark-pinned remote gives the probe nothing to use" + +function __tide_terminal_osc_background + echo ebe8e4 +end +__test_assert_equal EBE8E4 \ + (KITTY_CONFIG_DIRECTORY=$remote_config __tide_terminal_background_color light) \ + "the terminal's own answer wins over a remote machine's config files" + +# A terminal that cannot be asked has to leave the config files in charge. +function __tide_terminal_osc_background + return 1 +end +__test_assert_equal EBE8E4 \ + (KITTY_CONFIG_DIRECTORY=$fake_config __tide_terminal_background_color light) \ + "the config files still answer when the terminal will not" + +# And an answer contradicting the appearance is refused just like a file's. +function __tide_terminal_osc_background + echo 212121 +end +__test_assert_equal EBE8E4 \ + (KITTY_CONFIG_DIRECTORY=$fake_config __tide_terminal_background_color light) \ + "a terminal answer that contradicts the appearance is skipped" + +functions --erase __tide_terminal_osc_background + +rm -rf $fake_config $empty_config $remote_config set -l empty_palette_result ( fish --no-config -c ' diff --git a/private_dot_config/private_fish/private_tests/private_verify_tide_terminal_events.exp b/private_dot_config/private_fish/private_tests/private_verify_tide_terminal_events.exp index 853469a..4613cd4 100644 --- a/private_dot_config/private_fish/private_tests/private_verify_tide_terminal_events.exp +++ b/private_dot_config/private_fish/private_tests/private_verify_tide_terminal_events.exp @@ -6,16 +6,35 @@ log_user 0 # The segment background is derived from the theme kitty is showing, so the # emulated terminal responses below and the theme files have to agree. These # fixtures are the white/black the responses report. -set kitty_config [file join [file dirname [file normalize [info script]]] fixtures kitty] +set fixtures [file join [file dirname [file normalize [info script]]] fixtures] +set kitty_config [file join $fixtures kitty] + +# What the emulated terminal answers OSC 11 with, and the segment background +# Tide must derive from it. +set light_reply "ffff/ffff/ffff" +set light_segment "F2F2F2" set path_label "direct" set through_tmux 0 -if {[llength $argv] > 0 && [lindex $argv 0] eq "tmux"} { +set mode [expr {[llength $argv] > 0 ? [lindex $argv 0] : "direct"}] + +if {$mode eq "tmux"} { set path_label "tmux" set through_tmux 1 spawn env TERM=xterm-kitty KITTY_CONFIG_DIRECTORY=$kitty_config \ tmux -L tide-appearance-test \ -f "$env(HOME)/.tmux.conf" new-session fish --interactive +} elseif {$mode eq "remote"} { + # The ssh case: the config files belong to a machine pinned to dark, so + # every slot there describes a dark window, while the terminal this shell + # is displayed in is light. Only asking the terminal gets this right -- + # reading the files yields nothing usable and Tide falls back to white, + # putting the segment *above* the background instead of below it. + set path_label "remote" + set kitty_config [file join $fixtures kitty-remote-dark] + set light_reply "ebeb/e8e8/e4e4" + set light_segment "DFDCD8" + spawn env TERM=xterm-kitty KITTY_CONFIG_DIRECTORY=$kitty_config fish --interactive } else { spawn env TERM=xterm-kitty KITTY_CONFIG_DIRECTORY=$kitty_config fish --interactive } @@ -31,7 +50,7 @@ if {$through_tmux} { exp_continue } -exact "\033]11;?\033\\" { - send -- "\033]11;rgb:ffff/ffff/ffff\033\\" + send -- "\033]11;rgb:$light_reply\033\\" exp_continue } -exact "\033\[0c" { @@ -49,7 +68,7 @@ if {$through_tmux} { } else { expect { -exact "\033]11;?\033\\" { - send -- "\033]11;rgb:ffff/ffff/ffff\033\\" + send -- "\033]11;rgb:$light_reply\033\\" exp_continue } -exact "\033\[0c" { @@ -66,7 +85,7 @@ if {$through_tmux} { send -- "printf 'CHECK:%s:%s\\n' \$__tide_terminal_appearance \$tide_pwd_bg_color\r" expect { - -re {CHECK:light:F2F2F2} {} + -re "CHECK:light:$light_segment" {} timeout { puts stderr "The initial light terminal background did not apply the light Tide palette" exit 1