This commit is contained in:
Michael Grant
2026-09-17 06:00:13 +01:00
parent b63b17ff18
commit e1612fcab3
5 changed files with 181 additions and 1 deletions

12
CHANGES
View File

@@ -114,6 +114,18 @@ CHANGES FROM 3.7c TO 3.8
* Change show-options and show-hooks to use formats and add a -F flag to each. * 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 * Allow individual terminal features to be disabled by adding @ to their names
in terminal-features, and add a utf8 feature for terminals which support in terminal-features, and add a utf8 feature for terminals which support
UTF-8 output. UTF-8 output.

View File

@@ -1617,6 +1617,43 @@ format_cb_client_height(struct format_tree *ft)
return (NULL); 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. */ /* Callback for client_key_table. */
static void * static void *
format_cb_client_key_table(struct format_tree *ft) 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, { "client_height", FORMAT_TABLE_STRING,
format_cb_client_height format_cb_client_height
}, },
{ "client_key_mode", FORMAT_TABLE_STRING,
format_cb_client_key_mode
},
{ "client_key_table", FORMAT_TABLE_STRING, { "client_key_table", FORMAT_TABLE_STRING,
format_cb_client_key_table format_cb_client_key_table
}, },

View File

@@ -51,6 +51,33 @@ wait_for_output()
[ -s "$1" ] [ -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. # A server preferring Kitty must negotiate each client independently.
M="$TEST_TMUX -LtestKmc$$ -f$KCONF" M="$TEST_TMUX -LtestKmc$$ -f$KCONF"
KA="$TEST_TMUX -LtestKka$$ -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 "$KA" 'Kitty 1' || exit 1
wait_for_mode "$UA" 'Ext 2' || 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}' | uc=$($M list-clients -F '#{client_name} #{client_session}' |
awk '$2 == "csiu" { print $1 }') awk '$2 == "csiu" { print $1 }')
[ -n "$uc" ] || exit 1 [ -n "$uc" ] || exit 1
@@ -71,6 +102,14 @@ sleep 0.2
$UA send-keys 'Escape [97;5u' $UA send-keys 'Escape [97;5u'
wait "$pid" wait "$pid"
[ "$(tr -d '[:space:]' <"$TMP")" = 'C-a' ] || exit 1 [ "$(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 $KA kill-server 2>/dev/null
$UA kill-server 2>/dev/null $UA kill-server 2>/dev/null
$M 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 "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 $RU new-session -d -x80 -y24 "$R attach-session" || exit 1
wait_for_mode "$RU" 'Ext 2' || exit 1 wait_for_mode "$RU" 'Ext 2' || exit 1
wait_for_client_mode "$R" '' 'Ext' || exit 1
$RU send-keys C-a $RU send-keys C-a
wait_for_output "$OUT" || exit 1 wait_for_output "$OUT" || exit 1
[ "$(tr -d ' \n' <"$OUT")" = '1b5b39373b3575' ] || 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 $LU new-session -d -x80 -y24 "$LQ attach-session" || exit 1
wait_for_output "$OUT" || exit 1 wait_for_output "$OUT" || exit 1
[ "$(tr -d ' \n' <"$OUT")" = '1b5b3f3075' ] || exit 1 [ "$(tr -d ' \n' <"$OUT")" = '1b5b3f3075' ] || exit 1
wait_for_client_mode "$LQ" '' 'VT10x' || exit 1
exit 0 exit 0

81
regress/kitty-keys-terminal.sh Executable file
View File

@@ -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

8
tmux.1
View File

@@ -4935,6 +4935,9 @@ When set to
.Ic always , .Ic always ,
modes 1 and 2 can still be requested by applications, but mode 1 will be forced modes 1 and 2 can still be requested by applications, but mode 1 will be forced
instead of the standard mode. 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 When set to
.Ic off , .Ic off ,
this feature is disabled and only standard keys are reported. this feature is disabled and only standard keys are reported.
@@ -4959,7 +4962,9 @@ option, the
.Ic extended\-keys\-format .Ic extended\-keys\-format
option and the option and the
.Ic pane_key_mode .Ic pane_key_mode
variable. and
.Ic client_key_mode
variables.
.It Xo Ic extended\-keys\-format .It Xo Ic extended\-keys\-format
.Op Ic csi\-u | xterm | kitty .Op Ic csi\-u | xterm | kitty
.Xc .Xc
@@ -7315,6 +7320,7 @@ The following variables are available, where appropriate:
.It Li "client_discarded" Ta "" Ta "Bytes discarded when client behind" .It Li "client_discarded" Ta "" Ta "Bytes discarded when client behind"
.It Li "client_flags" Ta "" Ta "List of client flags" .It Li "client_flags" Ta "" Ta "List of client flags"
.It Li "client_height" Ta "" Ta "Height of client" .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_key_table" Ta "" Ta "Current key table"
.It Li "client_last_session" Ta "" Ta "Name of the client's last session" .It Li "client_last_session" Ta "" Ta "Name of the client's last session"
.It Li "client_name" Ta "" Ta "Name of client" .It Li "client_name" Ta "" Ta "Name of client"