From cb80f01460ef21f4c525712e75e7d79b858df957 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Thu, 17 Sep 2026 06:00:13 +0100 Subject: [PATCH] Cleanup. --- CHANGES | 12 +++++ format.c | 40 +++++++++++++++++ regress/kitty-keys-clients.sh | 41 +++++++++++++++++ regress/kitty-keys-terminal.sh | 81 ++++++++++++++++++++++++++++++++++ tmux.1 | 8 +++- 5 files changed, 181 insertions(+), 1 deletion(-) create mode 100755 regress/kitty-keys-terminal.sh diff --git a/CHANGES b/CHANGES index 62a7f8fc5..2aad808bf 100644 --- a/CHANGES +++ b/CHANGES @@ -111,6 +111,18 @@ CHANGES FROM 3.7c TO 3.8 * Change show-options and show-hooks to use formats and add a -F flag to each. +* Add support for the Kitty keyboard protocol, selected with a new kitty + value for the extended-keys-format option. This can report the Super and + Hyper modifiers (key name prefixes s- and H-) as well as Control, Meta + and Shift. The protocol is negotiated separately with each client, so + terminals with different capabilities may be attached at the same time; + clients without it use standard extended keys, or VT10x keys if those + are also unavailable, and their input is translated for applications + which have asked for Kitty encoding. + +* Add a client_key_mode format giving the extended key reporting mode in + use for a client, alongside the existing pane_key_mode for a pane. + * Allow individual terminal features to be disabled by adding @ to their names in terminal-features, and add a utf8 feature for terminals which support UTF-8 output. diff --git a/format.c b/format.c index c36e58f51..a65d1b962 100644 --- a/format.c +++ b/format.c @@ -1617,6 +1617,43 @@ format_cb_client_height(struct format_tree *ft) return (NULL); } +/* Callback for client_key_mode. */ +static void * +format_cb_client_key_mode(struct format_tree *ft) +{ + struct client *c = ft->c; + const char *mode; + char *s; + int kitty, eks; + + if (c == NULL || (~c->tty.flags & TTY_STARTED)) + return (NULL); + + if (c->tty.flags & TTY_KKBPUSHED) { + xasprintf(&s, "Kitty %u", c->tty.kitty_keys); + return (s); + } + + /* This mirrors the choice made in tty_update_features(). */ + kitty = ((c->tty.flags & (TTY_HAVEKKB|TTY_KKBSUPPORT)) == + (TTY_HAVEKKB|TTY_KKBSUPPORT)); + eks = (options_get_number(global_options, "extended-keys") != 0); + if (kitty && options_get_number(global_options, + "extended-keys-format") == EXTENDED_KEYS_KITTY) + eks = 0; + if (eks && tty_term_has(c->tty.term, TTYC_ENEKS)) + mode = "Ext"; + else + mode = "VT10x"; + + /* The terminal supports Kitty keys but they are not in use. */ + if (kitty) { + xasprintf(&s, "%s (Kitty)", mode); + return (s); + } + return (xstrdup(mode)); +} + /* Callback for client_key_table. */ static void * format_cb_client_key_table(struct format_tree *ft) @@ -3652,6 +3689,9 @@ static const struct format_table_entry format_table[] = { { "client_height", FORMAT_TABLE_STRING, format_cb_client_height }, + { "client_key_mode", FORMAT_TABLE_STRING, + format_cb_client_key_mode + }, { "client_key_table", FORMAT_TABLE_STRING, format_cb_client_key_table }, diff --git a/regress/kitty-keys-clients.sh b/regress/kitty-keys-clients.sh index c36cfc07c..504cafcd7 100755 --- a/regress/kitty-keys-clients.sh +++ b/regress/kitty-keys-clients.sh @@ -51,6 +51,33 @@ wait_for_output() [ -s "$1" ] } +client_mode() +{ + tmux=$1 + session=$2 + + if [ -z "$session" ]; then + $tmux list-clients -F '#{client_key_mode}' + else + $tmux list-clients -F '#{client_session}:#{client_key_mode}' | + sed -n "s/^$session://p" + fi +} + +wait_for_client_mode() +{ + tmux=$1 + session=$2 + wanted=$3 + i=0 + while [ "$(client_mode "$tmux" "$session")" != "$wanted" ] && + [ "$i" -lt 50 ]; do + sleep 0.1 + i=$((i + 1)) + done + [ "$(client_mode "$tmux" "$session")" = "$wanted" ] +} + # A server preferring Kitty must negotiate each client independently. M="$TEST_TMUX -LtestKmc$$ -f$KCONF" KA="$TEST_TMUX -LtestKka$$ -f$KCONF" @@ -62,6 +89,10 @@ $UA new-session -d -x80 -y24 "$M attach-session -t csiu" || exit 1 wait_for_mode "$KA" 'Kitty 1' || exit 1 wait_for_mode "$UA" 'Ext 2' || exit 1 +# Each client must report the protocol its own terminal negotiated. +wait_for_client_mode "$M" kitty 'Kitty 1' || exit 1 +wait_for_client_mode "$M" csiu 'Ext' || exit 1 + uc=$($M list-clients -F '#{client_name} #{client_session}' | awk '$2 == "csiu" { print $1 }') [ -n "$uc" ] || exit 1 @@ -71,6 +102,14 @@ sleep 0.2 $UA send-keys 'Escape [97;5u' wait "$pid" [ "$(tr -d '[:space:]' <"$TMP")" = 'C-a' ] || exit 1 + +# A Kitty-capable terminal must still be reported when it is not in use. +$M set-option -g extended-keys off +wait_for_client_mode "$M" kitty 'VT10x (Kitty)' || exit 1 +wait_for_client_mode "$M" csiu 'VT10x' || exit 1 +$M set-option -g extended-keys on +wait_for_client_mode "$M" kitty 'Kitty 1' || exit 1 + $KA kill-server 2>/dev/null $UA kill-server 2>/dev/null $M kill-server 2>/dev/null @@ -83,6 +122,7 @@ $R new-session -d -x80 -y24 \ "stty raw -echo; printf '\033[>1u'; dd bs=1 count=7 2>/dev/null | od -An -v -t x1 >'$OUT'; sleep 5" || exit 1 $RU new-session -d -x80 -y24 "$R attach-session" || exit 1 wait_for_mode "$RU" 'Ext 2' || exit 1 +wait_for_client_mode "$R" '' 'Ext' || exit 1 $RU send-keys C-a wait_for_output "$OUT" || exit 1 [ "$(tr -d ' \n' <"$OUT")" = '1b5b39373b3575' ] || exit 1 @@ -99,5 +139,6 @@ $LQ new-session -d -x80 -y24 \ $LU new-session -d -x80 -y24 "$LQ attach-session" || exit 1 wait_for_output "$OUT" || exit 1 [ "$(tr -d ' \n' <"$OUT")" = '1b5b3f3075' ] || exit 1 +wait_for_client_mode "$LQ" '' 'VT10x' || exit 1 exit 0 diff --git a/regress/kitty-keys-terminal.sh b/regress/kitty-keys-terminal.sh new file mode 100755 index 000000000..7371dbfa9 --- /dev/null +++ b/regress/kitty-keys-terminal.sh @@ -0,0 +1,81 @@ +#!/bin/sh + +# Test the Kitty keyboard protocol against a real terminal rather than against +# another copy of tmux. This drives kitty through its remote control interface +# so that kitty itself encodes the key presses. It is skipped unless kitty, a +# display and a kitty new enough to have send-key are all available. + +PATH=/bin:/usr/bin + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) + +command -v kitty >/dev/null 2>&1 || exit 0 +[ -n "$DISPLAY" ] || [ -n "$WAYLAND_DISPLAY" ] || exit 0 +kitty @ --help 2>/dev/null | grep -q 'send-key' || exit 0 + +DIR=$(mktemp -d) +CONF=$DIR/conf +SOCKET=$DIR/kitty +OUT=$DIR/out +TMUX="$TEST_TMUX -LtestKrt$$ -f$CONF" + +printf '%s\n' 'set -g extended-keys on' \ + 'set -g extended-keys-format kitty' >"$CONF" + +cleanup() +{ + $TMUX kill-server 2>/dev/null + rm -rf "$DIR" +} +trap cleanup 0 1 15 + +kitty -o allow_remote_control=yes --listen-on "unix:$SOCKET" \ + -o confirm_os_window_close=0 -e \ + $TMUX new-session -s r "sleep 120" >"$DIR/err" 2>&1 & + +# The terminfo entry for kitty may be missing, so treat a server which never +# appears as a reason to skip rather than as a failure. +i=0 +while ! $TMUX has-session -t r 2>/dev/null && [ "$i" -lt 150 ]; do + sleep 0.1 + i=$((i + 1)) +done +$TMUX has-session -t r 2>/dev/null || exit 0 + +# The client must negotiate the protocol with the real terminal. +i=0 +while [ "$($TMUX list-clients -F '#{client_key_mode}')" != 'Kitty 1' ] && + [ "$i" -lt 100 ]; do + sleep 0.1 + i=$((i + 1)) +done +[ "$($TMUX list-clients -F '#{client_key_mode}')" = 'Kitty 1' ] || exit 1 + +# kitty encodes the key, tmux parses it and encodes it again for the pane. +try_key() +{ + key=$1 + expected=$2 + + : >"$OUT" + $TMUX respawn-pane -k -t r: \ + "stty raw -echo; printf '\033[>1u'; dd bs=1 count=7 2>/dev/null | od -An -v -t x1 >'$OUT'; sleep 20" || return 1 + sleep 1 + kitty @ --to "unix:$SOCKET" send-key "$key" 2>/dev/null || return 1 + i=0 + while [ ! -s "$OUT" ] && [ "$i" -lt 50 ]; do + sleep 0.1 + i=$((i + 1)) + done + [ "$(tr -d ' \n' <"$OUT")" = "$expected" ] +} + +try_key ctrl+a '1b5b39373b3575' || exit 1 +try_key alt+a '1b5b39373b3375' || exit 1 +try_key ctrl+shift+a '1b5b39373b3675' || exit 1 + +# Super cannot be represented by the standard extended keys at all, so this +# only succeeds if the whole chain used the Kitty protocol. +try_key super+a '1b5b39373b3975' || exit 1 + +exit 0 diff --git a/tmux.1 b/tmux.1 index 3c798c49e..93ba2d797 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4929,6 +4929,9 @@ When set to .Ic always , modes 1 and 2 can still be requested by applications, but mode 1 will be forced instead of the standard mode. +This applies only to applications inside panes and does not change what +.Nm +requests from the terminal. When set to .Ic off , this feature is disabled and only standard keys are reported. @@ -4953,7 +4956,9 @@ option, the .Ic extended\-keys\-format option and the .Ic pane_key_mode -variable. +and +.Ic client_key_mode +variables. .It Xo Ic extended\-keys\-format .Op Ic csi\-u | xterm | kitty .Xc @@ -7342,6 +7347,7 @@ The following variables are available, where appropriate: .It Li "client_discarded" Ta "" Ta "Bytes discarded when client behind" .It Li "client_flags" Ta "" Ta "List of client flags" .It Li "client_height" Ta "" Ta "Height of client" +.It Li "client_key_mode" Ta "" Ta "Extended key reporting mode for this client" .It Li "client_key_table" Ta "" Ta "Current key table" .It Li "client_last_session" Ta "" Ta "Name of the client's last session" .It Li "client_name" Ta "" Ta "Name of client"