diff --git a/input-keys.c b/input-keys.c index 765e948d0..fbb7e1528 100644 --- a/input-keys.c +++ b/input-keys.c @@ -419,9 +419,8 @@ input_key_client_supports_extended(struct client *c, key_code key) return (1); if (options_get_number(global_options, "extended-keys") == 0) return (0); - if (options_get_number(global_options, "extended-keys-format") == - EXTENDED_KEYS_KITTY) - return ((c->tty.flags & TTY_KKBPUSHED) != 0); + if (c->tty.flags & TTY_KKBPUSHED) + return (1); return (tty_term_has(c->tty.term, TTYC_ENEKS)); } diff --git a/input.c b/input.c index 97427c2be..4e3652c9d 100644 --- a/input.c +++ b/input.c @@ -162,6 +162,7 @@ static void input_set_state(struct input_ctx *, const struct input_transition *); static void input_reset_cell(struct input_ctx *); static void input_report_current_theme(struct input_ctx *); +static int input_pane_supports_extended(struct window_pane *); static void input_osc_4(struct input_ctx *, const char *); static void input_osc_8(struct input_ctx *, const char *); static void input_osc_9(struct input_ctx *, const char *); @@ -1179,6 +1180,29 @@ input_send_reply(struct input_ctx *ictx, const char *reply) } } +/* Return whether attached tty clients can provide extended keys to a pane. */ +static int +input_pane_supports_extended(struct window_pane *wp) +{ + struct client *c; + + if (wp == NULL) + return (1); + TAILQ_FOREACH(c, &clients, entry) { + if ((c->flags & (CLIENT_TERMINAL|CLIENT_ATTACHED)) != + (CLIENT_TERMINAL|CLIENT_ATTACHED)) + continue; + if (c->flags & + (CLIENT_UNATTACHEDFLAGS|CLIENT_CONTROL|CLIENT_READONLY)) + continue; + if (c->session == NULL || !session_has(c->session, wp->window)) + continue; + if (!input_key_client_supports_extended(c, 0)) + return (0); + } + return (1); +} + /* Reply to terminal query. */ static void printflike(3, 4) input_reply(struct input_ctx *ictx, int add, const char *fmt, ...) @@ -1576,7 +1600,10 @@ input_csi_dispatch(struct input_ctx *ictx) options_get_number(global_options, "extended-keys-format") != EXTENDED_KEYS_KITTY) break; - input_reply(ictx, 1, "\033[?%uu", s->kitty_keys.flags); + n = s->kitty_keys.flags; + if (!input_pane_supports_extended(ictx->wp)) + n = 0; + input_reply(ictx, 1, "\033[?%uu", n); break; case INPUT_CSI_KITTY_SET: if (options_get_number(global_options, "extended-keys") == 0 || diff --git a/regress/kitty-keys-clients.sh b/regress/kitty-keys-clients.sh new file mode 100755 index 000000000..c36cfc07c --- /dev/null +++ b/regress/kitty-keys-clients.sh @@ -0,0 +1,103 @@ +#!/bin/sh + +# Test per-client keyboard protocol negotiation and pane-side fallback. + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +KCONF=$(mktemp) +UCONF=$(mktemp) +LCONF=$(mktemp) +OUT=$(mktemp) +TMP=$(mktemp) +SOCKETS="testKmc$$ testKka$$ testKua$$ testKrt$$ testKru$$ testKlq$$ testKlu$$" + +printf '%s\n' 'set -g extended-keys on' \ + 'set -g extended-keys-format kitty' >"$KCONF" +printf '%s\n' 'set -g extended-keys on' \ + 'set -g extended-keys-format csi-u' >"$UCONF" +printf '%s\n' 'set -g extended-keys off' >"$LCONF" + +cleanup() +{ + rm -f "$KCONF" "$UCONF" "$LCONF" "$OUT" "$TMP" + for socket in $SOCKETS; do + $TEST_TMUX -L"$socket" kill-server 2>/dev/null + done +} +trap cleanup 0 1 15 + +wait_for_mode() +{ + tmux=$1 + wanted=$2 + i=0 + while [ "$($tmux display-message -pt: '#{pane_key_mode}')" != "$wanted" ] && + [ "$i" -lt 50 ]; do + sleep 0.1 + i=$((i + 1)) + done + [ "$($tmux display-message -pt: '#{pane_key_mode}')" = "$wanted" ] +} + +wait_for_output() +{ + i=0 + while [ ! -s "$1" ] && [ "$i" -lt 50 ]; do + sleep 0.1 + i=$((i + 1)) + done + [ -s "$1" ] +} + +# A server preferring Kitty must negotiate each client independently. +M="$TEST_TMUX -LtestKmc$$ -f$KCONF" +KA="$TEST_TMUX -LtestKka$$ -f$KCONF" +UA="$TEST_TMUX -LtestKua$$ -f$UCONF" +$M new-session -d -x80 -y24 -s kitty || exit 1 +$M new-session -d -x80 -y24 -s csiu || exit 1 +$KA new-session -d -x80 -y24 "$M attach-session -t kitty" || exit 1 +$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 + +uc=$($M list-clients -F '#{client_name} #{client_session}' | + awk '$2 == "csiu" { print $1 }') +[ -n "$uc" ] || exit 1 +$M command-prompt -t"$uc" -k 'display-message -pl "%%"' >"$TMP" & +pid=$! +sleep 0.2 +$UA send-keys 'Escape [97;5u' +wait "$pid" +[ "$(tr -d '[:space:]' <"$TMP")" = 'C-a' ] || exit 1 +$KA kill-server 2>/dev/null +$UA kill-server 2>/dev/null +$M kill-server 2>/dev/null + +# Standard extended input must be translated for a Kitty-requesting pane. +: >"$OUT" +R="$TEST_TMUX -LtestKrt$$ -f$KCONF" +RU="$TEST_TMUX -LtestKru$$ -f$UCONF" +$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 +$RU send-keys C-a +wait_for_output "$OUT" || exit 1 +[ "$(tr -d ' \n' <"$OUT")" = '1b5b39373b3575' ] || exit 1 +$RU kill-server 2>/dev/null +$R kill-server 2>/dev/null + +# Do not report Kitty active when an attached client only supports VT10x. +: >"$OUT" +printf '%s\n' "set -as terminal-overrides ',*:Eneks@:Dseks@'" >>"$KCONF" +LQ="$TEST_TMUX -LtestKlq$$ -f$KCONF" +LU="$TEST_TMUX -LtestKlu$$ -f$LCONF" +$LQ new-session -d -x80 -y24 \ + "stty raw -echo; sleep 2; printf '\033[>1u\033[?u'; dd bs=1 count=5 2>/dev/null | od -An -v -t x1 >'$OUT'; sleep 5" || exit 1 +$LU new-session -d -x80 -y24 "$LQ attach-session" || exit 1 +wait_for_output "$OUT" || exit 1 +[ "$(tr -d ' \n' <"$OUT")" = '1b5b3f3075' ] || exit 1 + +exit 0 diff --git a/tmux.1 b/tmux.1 index b04c652d4..614d2cebd 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4935,6 +4935,13 @@ this feature is disabled and only standard keys are reported. .Pp .Nm will always request extended keys itself if the terminal supports them. +When the +.Ic kitty +format is selected, it is preferred separately for each client; clients which +do not support it use standard extended keys if available, or standard VT10x +keys otherwise. +Input from standard extended-key clients is translated to Kitty encoding for +applications which have requested it. See also the .Ic extkeys feature for the diff --git a/tty-keys.c b/tty-keys.c index 57a9cf941..6ecbd003c 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -1112,8 +1112,7 @@ tty_keys_extended_key(struct tty *tty, const char *buf, size_t len, utf8_char uc; *size = 0; - if (options_get_number(global_options, "extended-keys-format") == - EXTENDED_KEYS_KITTY) + if (tty->flags & TTY_KKBPUSHED) return (-1); /* First two bytes are always \033[. */ diff --git a/tty.c b/tty.c index 635582524..85303b8c9 100644 --- a/tty.c +++ b/tty.c @@ -562,16 +562,21 @@ void tty_update_features(struct tty *tty) { struct client *c = tty->client; + int extended_keys, format; if (tty_apply_features(tty->term)) tty_term_apply_overrides(tty->term); if (tty_use_margin(tty)) tty_putcode(tty, TTYC_ENMG); - if (options_get_number(global_options, "extended-keys-format") == - EXTENDED_KEYS_KITTY) + extended_keys = options_get_number(global_options, "extended-keys"); + format = options_get_number(global_options, "extended-keys-format"); + if (extended_keys == 0 || + (format == EXTENDED_KEYS_KITTY && + (tty->flags & (TTY_HAVEKKB|TTY_KKBSUPPORT)) == + (TTY_HAVEKKB|TTY_KKBSUPPORT))) tty_puts(tty, tty_term_string(tty->term, TTYC_DSEKS)); - else if (options_get_number(global_options, "extended-keys")) + else tty_puts(tty, tty_term_string(tty->term, TTYC_ENEKS)); tty_update_kitty(tty, NULL); if (options_get_number(global_options, "focus-events"))