Add Kitty keyboard protocol support

This commit is contained in:
Michael Grant
2026-09-14 14:14:28 +01:00
parent e880cf63e0
commit 75cb580e36
18 changed files with 1258 additions and 26 deletions

View File

@@ -171,6 +171,7 @@ dist_tmux_SOURCES = \
grid.c \
hooks.c \
hyperlinks.c \
input-kitty.c \
input-keys.c \
input.c \
job.c \
@@ -212,6 +213,7 @@ dist_tmux_SOURCES = \
tty-acs.c \
tty-draw.c \
tty-features.c \
tty-kitty.c \
tty-keys.c \
tty-term.c \
tty.c \

View File

@@ -2487,7 +2487,17 @@ format_cb_pane_unseen_changes(struct format_tree *ft)
static void *
format_cb_pane_key_mode(struct format_tree *ft)
{
char *s;
if (ft->wp != NULL && ft->wp->screen != NULL) {
if (options_get_number(global_options, "extended-keys-format") ==
EXTENDED_KEYS_KITTY) {
if (ft->wp->screen->kitty_keys.flags == 0)
return (xstrdup("VT10x"));
xasprintf(&s, "Kitty %u",
ft->wp->screen->kitty_keys.flags);
return (s);
}
switch (ft->wp->screen->mode & EXTENDED_KEY_MODES) {
case MODE_KEYS_EXTENDED:
return (xstrdup("Ext 1"));

View File

@@ -395,7 +395,8 @@ input_key_build(void)
/* Translate a key code into an output key sequence for a pane. */
int
input_key_pane(struct window_pane *wp, key_code key, struct mouse_event *m)
input_key_pane(struct window_pane *wp, key_code key, struct mouse_event *m,
int extended_encoding)
{
if (log_get_level() != 0) {
log_debug("writing key 0x%llx (%s) to %%%u", key,
@@ -407,7 +408,21 @@ input_key_pane(struct window_pane *wp, key_code key, struct mouse_event *m)
input_key_mouse(wp, m);
return (0);
}
return (input_key(wp->screen, wp->event, key));
return (input_key(wp->screen, wp->event, key, extended_encoding));
}
/* Return whether a key may use the configured extended encoding. */
int
input_key_client_supports_extended(struct client *c, key_code key)
{
if (c == NULL || (key & KEYC_SENT))
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);
return (tty_term_has(c->tty.term, TTYC_ENEKS));
}
static void
@@ -464,7 +479,8 @@ input_key_extended(struct bufferevent *bev, key_code key)
} else
key &= KEYC_MASK_KEY;
if (options_get_number(global_options, "extended-keys-format") == 1)
if (options_get_number(global_options, "extended-keys-format") ==
EXTENDED_KEYS_XTERM)
xsnprintf(tmp, sizeof tmp, "\033[27;%c;%llu~", modifier, key);
else
xsnprintf(tmp, sizeof tmp, "\033[%llu;%cu", key, modifier);
@@ -571,7 +587,8 @@ input_key_mode1(struct bufferevent *bev, key_code key)
/* Translate a key code into an output key sequence. */
int
input_key(struct screen *s, struct bufferevent *bev, key_code key)
input_key(struct screen *s, struct bufferevent *bev, key_code key,
int extended_encoding)
{
struct input_key_entry *ike = NULL;
key_code newkey;
@@ -588,6 +605,11 @@ input_key(struct screen *s, struct bufferevent *bev, key_code key)
return (0);
}
if (extended_encoding &&
options_get_number(global_options, "extended-keys-format") ==
EXTENDED_KEYS_KITTY && input_key_kitty(s, bev, key) == 0)
return (0);
/* Is this backspace? */
if ((key & KEYC_MASK_KEY) == KEYC_BSPACE) {
newkey = options_get_number(global_options, "backspace");
@@ -677,6 +699,11 @@ input_key(struct screen *s, struct bufferevent *bev, key_code key)
log_debug("%s: ignoring key 0x%llx", __func__, key);
return (0);
}
if (key & (KEYC_SUPER|KEYC_HYPER))
return (input_key_vt10x(bev, key));
if (options_get_number(global_options, "extended-keys-format") ==
EXTENDED_KEYS_KITTY)
return (input_key_vt10x(bev, key));
/*
* No builtin key sequence; construct an extended key sequence

327
input-kitty.c Normal file
View File

@@ -0,0 +1,327 @@
/* $OpenBSD$ */
/*
* Copyright (c) 2026 Michael Grant <mgrant@grant.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <string.h>
#include <wchar.h>
#include "tmux.h"
/* Track pane keyboard state and encode keys for applications. */
struct input_kitty_key {
key_code key;
u_int number;
char final;
};
static const struct input_kitty_key input_kitty_keys[] = {
{ C0_ESC, 27, 'u' },
{ C0_CR, 13, 'u' },
{ C0_HT, 9, 'u' },
{ KEYC_BSPACE, 127, 'u' },
{ KEYC_IC, 2, '~' },
{ KEYC_DC, 3, '~' },
{ KEYC_LEFT, 1, 'D' },
{ KEYC_RIGHT, 1, 'C' },
{ KEYC_UP, 1, 'A' },
{ KEYC_DOWN, 1, 'B' },
{ KEYC_PPAGE, 5, '~' },
{ KEYC_NPAGE, 6, '~' },
{ KEYC_HOME, 1, 'H' },
{ KEYC_END, 1, 'F' },
{ KEYC_F1, 1, 'P' },
{ KEYC_F2, 1, 'Q' },
{ KEYC_F3, 13, '~' },
{ KEYC_F4, 1, 'S' },
{ KEYC_F5, 15, '~' },
{ KEYC_F6, 17, '~' },
{ KEYC_F7, 18, '~' },
{ KEYC_F8, 19, '~' },
{ KEYC_F9, 20, '~' },
{ KEYC_F10, 21, '~' },
{ KEYC_F11, 23, '~' },
{ KEYC_F12, 24, '~' },
{ KEYC_CAPSLOCK, 57358, 'u' },
{ KEYC_SCROLLLOCK, 57359, 'u' },
{ KEYC_NUMLOCK, 57360, 'u' },
{ KEYC_PRINTSCREEN, 57361, 'u' },
{ KEYC_PAUSE, 57362, 'u' },
{ KEYC_MENU, 57363, 'u' },
{ KEYC_F13, 57376, 'u' },
{ KEYC_F14, 57377, 'u' },
{ KEYC_F15, 57378, 'u' },
{ KEYC_F16, 57379, 'u' },
{ KEYC_F17, 57380, 'u' },
{ KEYC_F18, 57381, 'u' },
{ KEYC_F19, 57382, 'u' },
{ KEYC_F20, 57383, 'u' },
{ KEYC_F21, 57384, 'u' },
{ KEYC_F22, 57385, 'u' },
{ KEYC_F23, 57386, 'u' },
{ KEYC_F24, 57387, 'u' },
{ KEYC_F25, 57388, 'u' },
{ KEYC_F26, 57389, 'u' },
{ KEYC_F27, 57390, 'u' },
{ KEYC_F28, 57391, 'u' },
{ KEYC_F29, 57392, 'u' },
{ KEYC_F30, 57393, 'u' },
{ KEYC_F31, 57394, 'u' },
{ KEYC_F32, 57395, 'u' },
{ KEYC_F33, 57396, 'u' },
{ KEYC_F34, 57397, 'u' },
{ KEYC_F35, 57398, 'u' },
{ KEYC_KP_ZERO|KEYC_KEYPAD, 57399, 'u' },
{ KEYC_KP_ONE|KEYC_KEYPAD, 57400, 'u' },
{ KEYC_KP_TWO|KEYC_KEYPAD, 57401, 'u' },
{ KEYC_KP_THREE|KEYC_KEYPAD, 57402, 'u' },
{ KEYC_KP_FOUR|KEYC_KEYPAD, 57403, 'u' },
{ KEYC_KP_FIVE|KEYC_KEYPAD, 57404, 'u' },
{ KEYC_KP_SIX|KEYC_KEYPAD, 57405, 'u' },
{ KEYC_KP_SEVEN|KEYC_KEYPAD, 57406, 'u' },
{ KEYC_KP_EIGHT|KEYC_KEYPAD, 57407, 'u' },
{ KEYC_KP_NINE|KEYC_KEYPAD, 57408, 'u' },
{ KEYC_KP_PERIOD|KEYC_KEYPAD, 57409, 'u' },
{ KEYC_KP_SLASH|KEYC_KEYPAD, 57410, 'u' },
{ KEYC_KP_STAR|KEYC_KEYPAD, 57411, 'u' },
{ KEYC_KP_MINUS|KEYC_KEYPAD, 57412, 'u' },
{ KEYC_KP_PLUS|KEYC_KEYPAD, 57413, 'u' },
{ KEYC_KP_ENTER|KEYC_KEYPAD, 57414, 'u' },
{ KEYC_KP_EQUAL|KEYC_KEYPAD, 57415, 'u' },
{ KEYC_KP_SEPARATOR|KEYC_KEYPAD, 57416, 'u' },
{ KEYC_KP_LEFT|KEYC_KEYPAD, 57417, 'u' },
{ KEYC_KP_RIGHT|KEYC_KEYPAD, 57418, 'u' },
{ KEYC_KP_UP|KEYC_KEYPAD, 57419, 'u' },
{ KEYC_KP_DOWN|KEYC_KEYPAD, 57420, 'u' },
{ KEYC_KP_PPAGE|KEYC_KEYPAD, 57421, 'u' },
{ KEYC_KP_NPAGE|KEYC_KEYPAD, 57422, 'u' },
{ KEYC_KP_HOME|KEYC_KEYPAD, 57423, 'u' },
{ KEYC_KP_END|KEYC_KEYPAD, 57424, 'u' },
{ KEYC_KP_IC|KEYC_KEYPAD, 57425, 'u' },
{ KEYC_KP_DC|KEYC_KEYPAD, 57426, 'u' },
{ KEYC_KP_BEGIN|KEYC_KEYPAD, 57427, '~' },
{ KEYC_MEDIA_PLAY, 57428, 'u' },
{ KEYC_MEDIA_PAUSE, 57429, 'u' },
{ KEYC_MEDIA_PLAYPAUSE, 57430, 'u' },
{ KEYC_MEDIA_REVERSE, 57431, 'u' },
{ KEYC_MEDIA_STOP, 57432, 'u' },
{ KEYC_MEDIA_FASTFORWARD, 57433, 'u' },
{ KEYC_MEDIA_REWIND, 57434, 'u' },
{ KEYC_MEDIA_NEXT, 57435, 'u' },
{ KEYC_MEDIA_PREVIOUS, 57436, 'u' },
{ KEYC_MEDIA_RECORD, 57437, 'u' },
{ KEYC_VOLUME_DOWN, 57438, 'u' },
{ KEYC_VOLUME_UP, 57439, 'u' },
{ KEYC_VOLUME_MUTE, 57440, 'u' },
{ KEYC_LEFT_SHIFT, 57441, 'u' },
{ KEYC_LEFT_CTRL, 57442, 'u' },
{ KEYC_LEFT_ALT, 57443, 'u' },
{ KEYC_LEFT_SUPER, 57444, 'u' },
{ KEYC_LEFT_HYPER, 57445, 'u' },
{ KEYC_LEFT_META, 57446, 'u' },
{ KEYC_RIGHT_SHIFT, 57447, 'u' },
{ KEYC_RIGHT_CTRL, 57448, 'u' },
{ KEYC_RIGHT_ALT, 57449, 'u' },
{ KEYC_RIGHT_SUPER, 57450, 'u' },
{ KEYC_RIGHT_HYPER, 57451, 'u' },
{ KEYC_RIGHT_META, 57452, 'u' },
{ KEYC_ISO_LEVEL3_SHIFT, 57453, 'u' },
{ KEYC_ISO_LEVEL5_SHIFT, 57454, 'u' }
};
static u_int
input_kitty_default_flags(void)
{
if (options_get_number(global_options, "extended-keys") == 2 &&
options_get_number(global_options, "extended-keys-format") ==
EXTENDED_KEYS_KITTY)
return (KITTY_KEY_DISAMBIGUATE);
return (0);
}
void
input_kitty_reset(struct screen *s)
{
memset(&s->kitty_keys, 0, sizeof s->kitty_keys);
s->kitty_keys.flags = input_kitty_default_flags();
}
void
input_kitty_alternate_on(struct screen *s)
{
s->saved_kitty_keys = s->kitty_keys;
input_kitty_reset(s);
}
void
input_kitty_alternate_off(struct screen *s)
{
s->kitty_keys = s->saved_kitty_keys;
memset(&s->saved_kitty_keys, 0, sizeof s->saved_kitty_keys);
}
void
input_kitty_set(struct screen *s, u_int flags, int mode)
{
flags &= KITTY_KEY_SUPPORTED;
if (mode == 2)
s->kitty_keys.flags |= flags;
else if (mode == 3)
s->kitty_keys.flags &= ~flags;
else
s->kitty_keys.flags = flags;
s->kitty_keys.flags |= input_kitty_default_flags();
}
void
input_kitty_push(struct screen *s, u_int flags)
{
s->kitty_keys.saved_flags = s->kitty_keys.flags;
s->kitty_keys.have_saved = 1;
s->kitty_keys.flags = (flags & KITTY_KEY_SUPPORTED)|
input_kitty_default_flags();
}
void
input_kitty_pop(struct screen *s, u_int count)
{
if (count == 0)
return;
if (s->kitty_keys.have_saved && count == 1)
s->kitty_keys.flags = s->kitty_keys.saved_flags;
else
s->kitty_keys.flags = input_kitty_default_flags();
s->kitty_keys.have_saved = 0;
s->kitty_keys.flags |= input_kitty_default_flags();
}
static u_int
input_kitty_modifiers(key_code key)
{
u_int modifiers = 1;
if (key & KEYC_SHIFT)
modifiers += 1;
if (key & KEYC_META)
modifiers += 2;
if (key & KEYC_CTRL)
modifiers += 4;
if (key & KEYC_SUPER)
modifiers += 8;
if (key & KEYC_HYPER)
modifiers += 16;
return (modifiers);
}
static const struct input_kitty_key *
input_kitty_lookup(key_code key)
{
key_code lookup;
u_int i;
lookup = key & (KEYC_MASK_KEY|KEYC_KEYPAD);
for (i = 0; i < nitems(input_kitty_keys); i++) {
if (input_kitty_keys[i].key == lookup)
return (&input_kitty_keys[i]);
}
return (NULL);
}
int
input_key_kitty(struct screen *s, struct bufferevent *bev, key_code key)
{
const struct input_kitty_key *ikk;
struct utf8_data ud;
wchar_t wc;
key_code modifiers, onlykey;
u_int flags, number, modifier;
char tmp[64];
size_t size;
flags = s->kitty_keys.flags & KITTY_KEY_SUPPORTED;
if (flags == 0)
return (-1);
if ((key & KEYC_MASK_KEY) == KEYC_BTAB)
key = C0_HT|(key & ~KEYC_MASK_KEY)|KEYC_SHIFT;
modifiers = key & (KEYC_SHIFT|KEYC_META|KEYC_CTRL|KEYC_SUPER|KEYC_HYPER);
ikk = input_kitty_lookup(key);
if (ikk != NULL) {
if ((flags & KITTY_KEY_REPORT_ALL) == 0) {
if (ikk->key == C0_CR || ikk->key == C0_HT ||
ikk->key == KEYC_BSPACE)
return (-1);
if (ikk->final != 'u' &&
(modifiers & (KEYC_SUPER|KEYC_HYPER)) == 0)
return (-1);
}
number = ikk->number;
modifier = input_kitty_modifiers(key);
if (ikk->final == 'u') {
if (modifier == 1)
size = xsnprintf(tmp, sizeof tmp, "\033[%uu", number);
else
size = xsnprintf(tmp, sizeof tmp, "\033[%u;%uu",
number, modifier);
} else if (ikk->final == '~') {
if (modifier == 1)
size = xsnprintf(tmp, sizeof tmp, "\033[%u~", number);
else
size = xsnprintf(tmp, sizeof tmp, "\033[%u;%u~",
number, modifier);
} else {
if (modifier == 1)
size = xsnprintf(tmp, sizeof tmp, "\033[%c",
ikk->final);
else
size = xsnprintf(tmp, sizeof tmp, "\033[1;%u%c",
modifier, ikk->final);
}
goto write;
}
if ((flags & KITTY_KEY_REPORT_ALL) == 0 &&
(modifiers & (KEYC_META|KEYC_CTRL|KEYC_SUPER|KEYC_HYPER)) == 0)
return (-1);
onlykey = key & KEYC_MASK_KEY;
if (KEYC_IS_UNICODE(key)) {
utf8_to_data(onlykey, &ud);
if (utf8_towc(&ud, &wc) != UTF8_DONE)
return (-1);
number = wc;
} else if (onlykey <= 0x7f)
number = onlykey;
else
return (-1);
if ((key & KEYC_SHIFT) && number >= 'A' && number <= 'Z')
number += 'a' - 'A';
modifier = input_kitty_modifiers(key);
if (modifier == 1)
size = xsnprintf(tmp, sizeof tmp, "\033[%uu", number);
else
size = xsnprintf(tmp, sizeof tmp, "\033[%u;%uu", number,
modifier);
write:
log_debug("%s: %.*s", __func__, (int)size, tmp);
bufferevent_write(bev, tmp, size);
return (0);
}

51
input.c
View File

@@ -279,6 +279,10 @@ enum input_csi_type {
INPUT_CSI_HPA,
INPUT_CSI_ICH,
INPUT_CSI_IL,
INPUT_CSI_KITTY_POP,
INPUT_CSI_KITTY_PUSH,
INPUT_CSI_KITTY_QUERY,
INPUT_CSI_KITTY_SET,
INPUT_CSI_MODOFF,
INPUT_CSI_MODSET,
INPUT_CSI_QUERY,
@@ -344,7 +348,11 @@ static const struct input_table_entry input_csi_table[] = {
{ 'r', "", INPUT_CSI_DECSTBM },
{ 's', "", INPUT_CSI_SCP },
{ 't', "", INPUT_CSI_WINOPS },
{ 'u', "", INPUT_CSI_RCP }
{ 'u', "", INPUT_CSI_RCP },
{ 'u', "<", INPUT_CSI_KITTY_POP },
{ 'u', "=", INPUT_CSI_KITTY_SET },
{ 'u', ">", INPUT_CSI_KITTY_PUSH },
{ 'u', "?", INPUT_CSI_KITTY_QUERY }
};
/* Input transition. */
@@ -1525,6 +1533,9 @@ input_csi_dispatch(struct input_ctx *ictx)
screen_write_cursormove(sctx, m - 1, n - 1, 1);
break;
case INPUT_CSI_MODSET:
if (options_get_number(global_options, "extended-keys-format") ==
EXTENDED_KEYS_KITTY)
break;
n = input_get(ictx, 0, 0, 0);
if (n != 4)
break;
@@ -1544,6 +1555,9 @@ input_csi_dispatch(struct input_ctx *ictx)
screen_write_mode_set(sctx, MODE_KEYS_EXTENDED);
break;
case INPUT_CSI_MODOFF:
if (options_get_number(global_options, "extended-keys-format") ==
EXTENDED_KEYS_KITTY)
break;
n = input_get(ictx, 0, 0, 0);
if (n != 4)
break;
@@ -1557,6 +1571,41 @@ input_csi_dispatch(struct input_ctx *ictx)
if (options_get_number(global_options, "extended-keys") == 2)
screen_write_mode_set(sctx, MODE_KEYS_EXTENDED);
break;
case INPUT_CSI_KITTY_QUERY:
if (options_get_number(global_options, "extended-keys") == 0 ||
options_get_number(global_options, "extended-keys-format") !=
EXTENDED_KEYS_KITTY)
break;
input_reply(ictx, 1, "\033[?%uu", s->kitty_keys.flags);
break;
case INPUT_CSI_KITTY_SET:
if (options_get_number(global_options, "extended-keys") == 0 ||
options_get_number(global_options, "extended-keys-format") !=
EXTENDED_KEYS_KITTY)
break;
n = input_get(ictx, 0, 0, 0);
m = input_get(ictx, 1, 1, 1);
if (n >= 0 && m >= 1 && m <= 3)
input_kitty_set(s, n, m);
break;
case INPUT_CSI_KITTY_PUSH:
if (options_get_number(global_options, "extended-keys") == 0 ||
options_get_number(global_options, "extended-keys-format") !=
EXTENDED_KEYS_KITTY)
break;
n = input_get(ictx, 0, 0, 0);
if (n >= 0)
input_kitty_push(s, n);
break;
case INPUT_CSI_KITTY_POP:
if (options_get_number(global_options, "extended-keys") == 0 ||
options_get_number(global_options, "extended-keys-format") !=
EXTENDED_KEYS_KITTY)
break;
n = input_get(ictx, 0, 1, 1);
if (n >= 0)
input_kitty_pop(s, n);
break;
case INPUT_CSI_WINOPS:
input_csi_dispatch_winops(ictx);
break;

View File

@@ -45,6 +45,29 @@ static const struct {
{ "F10", KEYC_F10|KEYC_IMPLIED_META },
{ "F11", KEYC_F11|KEYC_IMPLIED_META },
{ "F12", KEYC_F12|KEYC_IMPLIED_META },
{ "F13", KEYC_F13|KEYC_IMPLIED_META },
{ "F14", KEYC_F14|KEYC_IMPLIED_META },
{ "F15", KEYC_F15|KEYC_IMPLIED_META },
{ "F16", KEYC_F16|KEYC_IMPLIED_META },
{ "F17", KEYC_F17|KEYC_IMPLIED_META },
{ "F18", KEYC_F18|KEYC_IMPLIED_META },
{ "F19", KEYC_F19|KEYC_IMPLIED_META },
{ "F20", KEYC_F20|KEYC_IMPLIED_META },
{ "F21", KEYC_F21|KEYC_IMPLIED_META },
{ "F22", KEYC_F22|KEYC_IMPLIED_META },
{ "F23", KEYC_F23|KEYC_IMPLIED_META },
{ "F24", KEYC_F24|KEYC_IMPLIED_META },
{ "F25", KEYC_F25|KEYC_IMPLIED_META },
{ "F26", KEYC_F26|KEYC_IMPLIED_META },
{ "F27", KEYC_F27|KEYC_IMPLIED_META },
{ "F28", KEYC_F28|KEYC_IMPLIED_META },
{ "F29", KEYC_F29|KEYC_IMPLIED_META },
{ "F30", KEYC_F30|KEYC_IMPLIED_META },
{ "F31", KEYC_F31|KEYC_IMPLIED_META },
{ "F32", KEYC_F32|KEYC_IMPLIED_META },
{ "F33", KEYC_F33|KEYC_IMPLIED_META },
{ "F34", KEYC_F34|KEYC_IMPLIED_META },
{ "F35", KEYC_F35|KEYC_IMPLIED_META },
{ "IC", KEYC_IC|KEYC_IMPLIED_META },
{ "Insert", KEYC_IC|KEYC_IMPLIED_META },
{ "DC", KEYC_DC|KEYC_IMPLIED_META },
@@ -122,6 +145,54 @@ static const struct {
{ "KPEnter", KEYC_KP_ENTER|KEYC_KEYPAD },
{ "KP0", KEYC_KP_ZERO|KEYC_KEYPAD },
{ "KP.", KEYC_KP_PERIOD|KEYC_KEYPAD },
{ "KP=", KEYC_KP_EQUAL|KEYC_KEYPAD },
{ "KP,", KEYC_KP_SEPARATOR|KEYC_KEYPAD },
{ "KPLeft", KEYC_KP_LEFT|KEYC_KEYPAD },
{ "KPRight", KEYC_KP_RIGHT|KEYC_KEYPAD },
{ "KPUp", KEYC_KP_UP|KEYC_KEYPAD },
{ "KPDown", KEYC_KP_DOWN|KEYC_KEYPAD },
{ "KPPageUp", KEYC_KP_PPAGE|KEYC_KEYPAD },
{ "KPPageDown", KEYC_KP_NPAGE|KEYC_KEYPAD },
{ "KPHome", KEYC_KP_HOME|KEYC_KEYPAD },
{ "KPEnd", KEYC_KP_END|KEYC_KEYPAD },
{ "KPInsert", KEYC_KP_IC|KEYC_KEYPAD },
{ "KPDelete", KEYC_KP_DC|KEYC_KEYPAD },
{ "KPBegin", KEYC_KP_BEGIN|KEYC_KEYPAD },
/* Other function keys. */
{ "CapsLock", KEYC_CAPSLOCK },
{ "ScrollLock", KEYC_SCROLLLOCK },
{ "NumLock", KEYC_NUMLOCK },
{ "PrintScreen", KEYC_PRINTSCREEN },
{ "Pause", KEYC_PAUSE },
{ "Menu", KEYC_MENU },
{ "MediaPlay", KEYC_MEDIA_PLAY },
{ "MediaPause", KEYC_MEDIA_PAUSE },
{ "MediaPlayPause", KEYC_MEDIA_PLAYPAUSE },
{ "MediaReverse", KEYC_MEDIA_REVERSE },
{ "MediaStop", KEYC_MEDIA_STOP },
{ "MediaFastForward", KEYC_MEDIA_FASTFORWARD },
{ "MediaRewind", KEYC_MEDIA_REWIND },
{ "MediaNext", KEYC_MEDIA_NEXT },
{ "MediaPrevious", KEYC_MEDIA_PREVIOUS },
{ "MediaRecord", KEYC_MEDIA_RECORD },
{ "VolumeDown", KEYC_VOLUME_DOWN },
{ "VolumeUp", KEYC_VOLUME_UP },
{ "VolumeMute", KEYC_VOLUME_MUTE },
{ "LeftShift", KEYC_LEFT_SHIFT },
{ "LeftControl", KEYC_LEFT_CTRL },
{ "LeftAlt", KEYC_LEFT_ALT },
{ "LeftSuper", KEYC_LEFT_SUPER },
{ "LeftHyper", KEYC_LEFT_HYPER },
{ "LeftMeta", KEYC_LEFT_META },
{ "RightShift", KEYC_RIGHT_SHIFT },
{ "RightControl", KEYC_RIGHT_CTRL },
{ "RightAlt", KEYC_RIGHT_ALT },
{ "RightSuper", KEYC_RIGHT_SUPER },
{ "RightHyper", KEYC_RIGHT_HYPER },
{ "RightMeta", KEYC_RIGHT_META },
{ "ISOLevel3Shift", KEYC_ISO_LEVEL3_SHIFT },
{ "ISOLevel5Shift", KEYC_ISO_LEVEL5_SHIFT },
/* Mouse keys. */
KEYC_MOUSE_STRING(MOUSEDOWN1, MouseDown1),
@@ -226,9 +297,14 @@ key_string_get_modifiers(const char **string)
modifiers |= KEYC_META;
break;
case 'S':
case 's':
modifiers |= KEYC_SHIFT;
break;
case 's':
modifiers |= KEYC_SUPER;
break;
case 'H':
modifiers |= KEYC_HYPER;
break;
default:
*string = NULL;
return (0);
@@ -349,6 +425,10 @@ key_string_lookup_key(key_code key, int with_flags)
strlcat(out, "M-", sizeof out);
if (key & KEYC_SHIFT)
strlcat(out, "S-", sizeof out);
if (key & KEYC_SUPER)
strlcat(out, "s-", sizeof out);
if (key & KEYC_HYPER)
strlcat(out, "H-", sizeof out);
key &= KEYC_MASK_KEY;
/* Handle no key. */

View File

@@ -104,7 +104,7 @@ static const char *options_table_extended_keys_list[] = {
"off", "on", "always", NULL
};
static const char *options_table_extended_keys_format_list[] = {
"csi-u", "xterm", NULL
"csi-u", "xterm", "kitty", NULL
};
static const char *options_table_allow_passthrough_list[] = {
"off", "on", "all", NULL

View File

@@ -449,7 +449,8 @@ popup_key_cb(struct client *c, void *data, struct key_event *event)
bufferevent_write(job_get_event(pd->job), buf, len);
return (0);
}
input_key(&pd->s, job_get_event(pd->job), event->key);
input_key(&pd->s, job_get_event(pd->job), event->key,
input_key_client_supports_extended(pd->c, event->key));
}
return (0);

107
regress/kitty-keys.sh Normal file
View File

@@ -0,0 +1,107 @@
#!/bin/sh
# Test pane-side Kitty keyboard protocol state and key encoding. This uses
# synthetic protocol sequences and does not require a particular terminal.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
CONF=$(mktemp)
OUT=$(mktemp)
TMUX="$TEST_TMUX -LtestK$$ -f$CONF"
trap 'rm -f "$CONF" "$OUT"; $TMUX kill-server 2>/dev/null' 0 1 15
printf '%s\n' 'set -g extended-keys on' \
'set -g extended-keys-format kitty' >"$CONF"
wait_for_output()
{
i=0
while [ ! -s "$OUT" ] && [ "$i" -lt 50 ]; do
sleep 0.1
i=$((i + 1))
done
[ -s "$OUT" ]
}
wait_for_mode()
{
wanted=$1
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" ]
}
check_output()
{
expected=$1
actual=$(tr -d ' \n' <"$OUT")
if [ "$actual" != "$expected" ]; then
echo "expected $expected, got $actual"
exit 1
fi
}
$TMUX 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
wait_for_mode 'Kitty 1'
$TMUX send-keys -t: C-S-a
wait_for_output
check_output 1b5b39373b3675
: >"$OUT"
$TMUX respawn-pane -k -t: \
"stty raw -echo; printf '\033[>1u'; dd bs=1 count=7 2>/dev/null | od -An -v -t x1 >'$OUT'; sleep 5"
wait_for_mode 'Kitty 1'
$TMUX send-keys -t: s-a
wait_for_output
check_output 1b5b39373b3975
: >"$OUT"
$TMUX respawn-pane -k -t: \
"stty raw -echo; printf '\033[>8u'; dd bs=1 count=5 2>/dev/null | od -An -v -t x1 >'$OUT'; sleep 5"
wait_for_mode 'Kitty 8'
$TMUX send-keys -t: a
wait_for_output
check_output 1b5b393775
: >"$OUT"
$TMUX respawn-pane -k -t: \
"stty raw -echo; printf '\033[>1u\033[?u'; dd bs=1 count=5 2>/dev/null | od -An -v -t x1 >'$OUT'; sleep 5"
wait_for_output
check_output 1b5b3f3175
: >"$OUT"
$TMUX respawn-pane -k -t: \
"stty raw -echo; printf '\033[>4;2m\033[?u'; dd bs=1 count=5 2>/dev/null | od -An -v -t x1 >'$OUT'; sleep 5"
wait_for_output
check_output 1b5b3f3075
: >"$OUT"
$TMUX respawn-pane -k -t: \
"stty raw -echo; printf '\033[>1u\033[>8u\033[<u\033[?u\033[>8u\033[<2u\033[?u'; dd bs=1 count=10 2>/dev/null | od -An -v -t x1 >'$OUT'; sleep 5"
wait_for_output
check_output 1b5b3f31751b5b3f3075
: >"$OUT"
$TMUX set-option -g extended-keys always
$TMUX respawn-pane -k -t: \
"stty raw -echo; printf '\033[>0u\033[?u'; dd bs=1 count=5 2>/dev/null | od -An -v -t x1 >'$OUT'; sleep 5"
wait_for_output
check_output 1b5b3f3175
$TMUX set-option -g extended-keys on
: >"$OUT"
$TMUX respawn-pane -k -t: \
"stty raw -echo; printf '\033[>1u\033[?u\033[?1049h\033[?u\033[>8u\033[?u\033[?1049l\033[?u'; dd bs=1 count=20 2>/dev/null | od -An -v -t x1 >'$OUT'; sleep 5"
wait_for_output
check_output 1b5b3f31751b5b3f30751b5b3f38751b5b3f3175
$TMUX kill-server 2>/dev/null
exit 0

View File

@@ -437,8 +437,11 @@ screen_write_reset(struct screen_write_ctx *ctx)
s->mode = MODE_CURSOR|MODE_WRAP;
if (options_get_number(global_options, "extended-keys") == 2)
if (options_get_number(global_options, "extended-keys") == 2 &&
options_get_number(global_options, "extended-keys-format") !=
EXTENDED_KEYS_KITTY)
s->mode = (s->mode & ~EXTENDED_KEY_MODES)|MODE_KEYS_EXTENDED;
input_kitty_reset(s);
screen_write_clearscreen(ctx, 8);
screen_write_set_cursor(ctx, 0, 0);

View File

@@ -88,6 +88,8 @@ screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit)
s->default_cstyle = SCREEN_CURSOR_DEFAULT;
s->mode = MODE_CURSOR;
s->default_mode = 0;
memset(&s->kitty_keys, 0, sizeof s->kitty_keys);
memset(&s->saved_kitty_keys, 0, sizeof s->saved_kitty_keys);
s->ccolour = -1;
s->default_ccolour = -1;
s->tabs = NULL;
@@ -116,11 +118,13 @@ screen_reinit(struct screen *s, int check)
s->mode = MODE_CURSOR|MODE_WRAP|(s->mode & MODE_CRLF);
if (options_get_number(global_options, "extended-keys") == 2)
if (options_get_number(global_options, "extended-keys") == 2 &&
options_get_number(global_options, "extended-keys-format") !=
EXTENDED_KEYS_KITTY)
s->mode = (s->mode & ~EXTENDED_KEY_MODES)|MODE_KEYS_EXTENDED;
if (SCREEN_IS_ALTERNATE(s))
screen_alternate_off(s, NULL, 0);
input_kitty_reset(s);
s->saved_cx = UINT_MAX;
s->saved_cy = UINT_MAX;
@@ -698,6 +702,7 @@ screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor)
if (SCREEN_IS_ALTERNATE(s))
return 0;
input_kitty_alternate_on(s);
sx = screen_size_x(s);
sy = screen_size_y(s);
@@ -758,6 +763,7 @@ screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor)
s->cy = screen_size_y(s) - 1;
return 0;
}
input_kitty_alternate_off(s);
/* Restore the saved grid. */
grid_duplicate_lines(s->grid, screen_hsize(s), s->saved_grid, 0,

View File

@@ -2194,6 +2194,7 @@ server_client_reset_state(struct client *c)
log_debug("%s: client %s mode %s", __func__, c->name,
screen_mode_to_string(mode));
}
tty_update_kitty(tty, s);
/* Reset region and margin. */
tty_region_off(tty);

37
tmux.1
View File

@@ -4907,12 +4907,18 @@ If enabled, the server will exit when there are no attached clients.
.It Xo Ic extended\-keys
.Op Ic on | off | always
.Xc
Controls how modified keys (keys pressed together with Control, Meta, or Shift)
are reported.
This is the equivalent of the
Controls how modified keys are reported.
For the
.Ic csi\-u
and
.Ic xterm
formats, this controls the
.Ic modifyOtherKeys
.Xr xterm 1
resource.
For the
.Ic kitty
format, this controls the progressive keyboard protocol enhancements.
.Pp
When set to
.Ic on ,
@@ -4939,11 +4945,24 @@ option and the
.Ic pane_key_mode
variable.
.It Xo Ic extended\-keys\-format
.Op Ic csi\-u | xterm
.Op Ic csi\-u | xterm | kitty
.Xc
Selects one of the two possible formats for reporting modified keys to
applications.
This is the equivalent of the
Selects the format for reporting modified keys to applications.
The
.Ic kitty
format uses the Kitty keyboard protocol and supports the Super and Hyper
modifiers.
Their key name prefixes are
.Ql s-
and
.Ql H- .
The disambiguate and report-all enhancements are supported; event types,
alternate keys and associated text are not requested.
For the
.Ic csi\-u
and
.Ic xterm
formats, this is the equivalent of the
.Ic formatOtherKeys
.Xr xterm 1
resource.
@@ -4955,6 +4974,10 @@ and as
.Ql \[ha][[65;6u
when set to
.Ic csi\-u .
With
.Ic kitty ,
it will be reported as
.Ql \[ha][[97;6u .
.It Xo Ic focus\-events
.Op Ic on | off
.Xc

117
tmux.h
View File

@@ -142,6 +142,8 @@ struct winlink;
#define VISUAL_BOTH 2
/* Key modifier bits. */
#define KEYC_SUPER 0x00010000000000ULL
#define KEYC_HYPER 0x00020000000000ULL
#define KEYC_META 0x00100000000000ULL
#define KEYC_CTRL 0x00200000000000ULL
#define KEYC_SHIFT 0x00400000000000ULL
@@ -374,6 +376,29 @@ enum {
KEYC_F10,
KEYC_F11,
KEYC_F12,
KEYC_F13,
KEYC_F14,
KEYC_F15,
KEYC_F16,
KEYC_F17,
KEYC_F18,
KEYC_F19,
KEYC_F20,
KEYC_F21,
KEYC_F22,
KEYC_F23,
KEYC_F24,
KEYC_F25,
KEYC_F26,
KEYC_F27,
KEYC_F28,
KEYC_F29,
KEYC_F30,
KEYC_F31,
KEYC_F32,
KEYC_F33,
KEYC_F34,
KEYC_F35,
KEYC_IC,
KEYC_DC,
KEYC_HOME,
@@ -405,6 +430,54 @@ enum {
KEYC_KP_ENTER,
KEYC_KP_ZERO,
KEYC_KP_PERIOD,
KEYC_KP_EQUAL,
KEYC_KP_SEPARATOR,
KEYC_KP_LEFT,
KEYC_KP_RIGHT,
KEYC_KP_UP,
KEYC_KP_DOWN,
KEYC_KP_PPAGE,
KEYC_KP_NPAGE,
KEYC_KP_HOME,
KEYC_KP_END,
KEYC_KP_IC,
KEYC_KP_DC,
KEYC_KP_BEGIN,
/* Other function keys. */
KEYC_CAPSLOCK,
KEYC_SCROLLLOCK,
KEYC_NUMLOCK,
KEYC_PRINTSCREEN,
KEYC_PAUSE,
KEYC_MENU,
KEYC_MEDIA_PLAY,
KEYC_MEDIA_PAUSE,
KEYC_MEDIA_PLAYPAUSE,
KEYC_MEDIA_REVERSE,
KEYC_MEDIA_STOP,
KEYC_MEDIA_FASTFORWARD,
KEYC_MEDIA_REWIND,
KEYC_MEDIA_NEXT,
KEYC_MEDIA_PREVIOUS,
KEYC_MEDIA_RECORD,
KEYC_VOLUME_DOWN,
KEYC_VOLUME_UP,
KEYC_VOLUME_MUTE,
KEYC_LEFT_SHIFT,
KEYC_LEFT_CTRL,
KEYC_LEFT_ALT,
KEYC_LEFT_SUPER,
KEYC_LEFT_HYPER,
KEYC_LEFT_META,
KEYC_RIGHT_SHIFT,
KEYC_RIGHT_CTRL,
KEYC_RIGHT_ALT,
KEYC_RIGHT_SUPER,
KEYC_RIGHT_HYPER,
KEYC_RIGHT_META,
KEYC_ISO_LEVEL3_SHIFT,
KEYC_ISO_LEVEL5_SHIFT,
/* Theme reporting. */
KEYC_REPORT_DARK_THEME,
@@ -702,6 +775,16 @@ enum tty_code_code {
#define CURSOR_MODES (MODE_CURSOR|MODE_CURSOR_BLINKING|MODE_CURSOR_VERY_VISIBLE)
#define EXTENDED_KEY_MODES (MODE_KEYS_EXTENDED|MODE_KEYS_EXTENDED_2)
/* Extended key output formats. */
#define EXTENDED_KEYS_CSI_U 0
#define EXTENDED_KEYS_XTERM 1
#define EXTENDED_KEYS_KITTY 2
/* Supported Kitty keyboard protocol flags. */
#define KITTY_KEY_DISAMBIGUATE 0x1
#define KITTY_KEY_REPORT_ALL 0x8
#define KITTY_KEY_SUPPORTED (KITTY_KEY_DISAMBIGUATE|KITTY_KEY_REPORT_ALL)
/* Mouse protocol constants. */
#define MOUSE_PARAM_MAX 0xff
#define MOUSE_PARAM_UTF8_MAX 0x7ff
@@ -1068,6 +1151,11 @@ struct progress_bar {
/* Virtual screen. */
struct screen_sel;
struct screen_titles;
struct kitty_key_state {
u_int flags;
u_int saved_flags;
int have_saved;
};
struct screen {
char *title;
char *path;
@@ -1089,6 +1177,8 @@ struct screen {
int mode;
int default_mode;
struct kitty_key_state kitty_keys;
struct kitty_key_state saved_kitty_keys;
u_int saved_cx;
u_int saved_cy;
@@ -1819,9 +1909,13 @@ struct tty {
#define TTY_WAITBG 0x4000
#define TTY_BRACKETPASTE 0x8000
#define TTY_HAVESYNC 0x10000
#define TTY_HAVEKKB 0x20000
#define TTY_KKBPUSHED 0x40000
#define TTY_KKBSUPPORT 0x80000
#define TTY_ALL_REQUEST_FLAGS \
(TTY_HAVEDA|TTY_HAVEDA2|TTY_HAVEXDA|TTY_HAVESYNC)
(TTY_HAVEDA|TTY_HAVEDA2|TTY_HAVEXDA|TTY_HAVESYNC|TTY_HAVEKKB)
int flags;
u_int kitty_keys;
struct tty_term *term;
@@ -2974,6 +3068,7 @@ int tty_open(struct tty *, char **);
void tty_close(struct tty *);
void tty_free(struct tty *);
void tty_update_features(struct tty *);
void tty_update_kitty(struct tty *, struct screen *);
void tty_set_selection(struct tty *, const char *, const char *, size_t);
void tty_write(void (*)(struct tty *, const struct tty_ctx *),
struct tty_ctx *);
@@ -3450,10 +3545,26 @@ void input_cancel_requests(struct client *);
/* input-key.c */
void input_key_build(void);
int input_key_pane(struct window_pane *, key_code, struct mouse_event *);
int input_key(struct screen *, struct bufferevent *, key_code);
int input_key_pane(struct window_pane *, key_code, struct mouse_event *,
int);
int input_key(struct screen *, struct bufferevent *, key_code, int);
int input_key_get_mouse(struct screen *, struct mouse_event *, u_int,
u_int, const char **, size_t *);
int input_key_client_supports_extended(struct client *, key_code);
/* input-kitty.c */
void input_kitty_reset(struct screen *);
void input_kitty_alternate_on(struct screen *);
void input_kitty_alternate_off(struct screen *);
void input_kitty_set(struct screen *, u_int, int);
void input_kitty_push(struct screen *, u_int);
void input_kitty_pop(struct screen *, u_int);
int input_key_kitty(struct screen *, struct bufferevent *, key_code);
/* tty-kitty.c */
int tty_keys_kitty(struct tty *, const char *, size_t, size_t *,
key_code *);
int tty_keys_kitty_query(struct tty *, const char *, size_t, size_t *);
/* colour.c */
int colour_find_rgb(u_char, u_char, u_char);

View File

@@ -772,6 +772,17 @@ tty_keys_next(struct tty *tty)
goto partial_key;
}
/* Is this a Kitty keyboard protocol response? */
switch (tty_keys_kitty_query(tty, buf, len, &size)) {
case 0: /* yes */
key = KEYC_UNKNOWN;
goto complete_key;
case -1: /* no, or not valid */
break;
case 1: /* partial */
goto partial_key;
}
/* Is this a synchronized update mode response? */
switch (tty_keys_sync(tty, buf, len, &size)) {
case 0: /* yes */
@@ -858,6 +869,18 @@ tty_keys_next(struct tty *tty)
goto partial_key;
}
/* Is this an extended key press? */
switch (tty_keys_kitty(tty, buf, len, &size, &key)) {
case 0: /* yes */
goto complete_key;
case -1: /* no, or not valid */
break;
case -2: /* yes, but not representable by tmux */
goto discard_key;
case 1: /* partial */
goto partial_key;
}
/* Is this an extended key press? */
switch (tty_keys_extended_key(tty, buf, len, &size, &key)) {
case 0: /* yes */
@@ -1089,6 +1112,9 @@ 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)
return (-1);
/* First two bytes are always \033[. */
if (buf[0] != '\033')
@@ -1527,6 +1553,9 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len,
}
log_debug("%s: received primary DA %.*s", c->name, (int)*size, buf);
/* A DA response without a preceding keyboard response means no support. */
if (~tty->flags & TTY_HAVEKKB)
tty->flags |= TTY_HAVEKKB;
tty_update_features(tty);
tty->flags |= TTY_HAVEDA;

440
tty-kitty.c Normal file
View File

@@ -0,0 +1,440 @@
/* $OpenBSD$ */
/*
* Copyright (c) 2026 Michael Grant <mgrant@grant.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <ctype.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include "tmux.h"
/* Detect support in the outside terminal and decode its key sequences. */
struct tty_kitty_key {
u_int number;
key_code key;
};
static const struct tty_kitty_key tty_kitty_keys[] = {
{ 57358, KEYC_CAPSLOCK },
{ 57359, KEYC_SCROLLLOCK },
{ 57360, KEYC_NUMLOCK },
{ 57361, KEYC_PRINTSCREEN },
{ 57362, KEYC_PAUSE },
{ 57363, KEYC_MENU },
{ 57376, KEYC_F13 },
{ 57377, KEYC_F14 },
{ 57378, KEYC_F15 },
{ 57379, KEYC_F16 },
{ 57380, KEYC_F17 },
{ 57381, KEYC_F18 },
{ 57382, KEYC_F19 },
{ 57383, KEYC_F20 },
{ 57384, KEYC_F21 },
{ 57385, KEYC_F22 },
{ 57386, KEYC_F23 },
{ 57387, KEYC_F24 },
{ 57388, KEYC_F25 },
{ 57389, KEYC_F26 },
{ 57390, KEYC_F27 },
{ 57391, KEYC_F28 },
{ 57392, KEYC_F29 },
{ 57393, KEYC_F30 },
{ 57394, KEYC_F31 },
{ 57395, KEYC_F32 },
{ 57396, KEYC_F33 },
{ 57397, KEYC_F34 },
{ 57398, KEYC_F35 },
{ 57399, KEYC_KP_ZERO|KEYC_KEYPAD },
{ 57400, KEYC_KP_ONE|KEYC_KEYPAD },
{ 57401, KEYC_KP_TWO|KEYC_KEYPAD },
{ 57402, KEYC_KP_THREE|KEYC_KEYPAD },
{ 57403, KEYC_KP_FOUR|KEYC_KEYPAD },
{ 57404, KEYC_KP_FIVE|KEYC_KEYPAD },
{ 57405, KEYC_KP_SIX|KEYC_KEYPAD },
{ 57406, KEYC_KP_SEVEN|KEYC_KEYPAD },
{ 57407, KEYC_KP_EIGHT|KEYC_KEYPAD },
{ 57408, KEYC_KP_NINE|KEYC_KEYPAD },
{ 57409, KEYC_KP_PERIOD|KEYC_KEYPAD },
{ 57410, KEYC_KP_SLASH|KEYC_KEYPAD },
{ 57411, KEYC_KP_STAR|KEYC_KEYPAD },
{ 57412, KEYC_KP_MINUS|KEYC_KEYPAD },
{ 57413, KEYC_KP_PLUS|KEYC_KEYPAD },
{ 57414, KEYC_KP_ENTER|KEYC_KEYPAD },
{ 57415, KEYC_KP_EQUAL|KEYC_KEYPAD },
{ 57416, KEYC_KP_SEPARATOR|KEYC_KEYPAD },
{ 57417, KEYC_KP_LEFT|KEYC_KEYPAD },
{ 57418, KEYC_KP_RIGHT|KEYC_KEYPAD },
{ 57419, KEYC_KP_UP|KEYC_KEYPAD },
{ 57420, KEYC_KP_DOWN|KEYC_KEYPAD },
{ 57421, KEYC_KP_PPAGE|KEYC_KEYPAD },
{ 57422, KEYC_KP_NPAGE|KEYC_KEYPAD },
{ 57423, KEYC_KP_HOME|KEYC_KEYPAD },
{ 57424, KEYC_KP_END|KEYC_KEYPAD },
{ 57425, KEYC_KP_IC|KEYC_KEYPAD },
{ 57426, KEYC_KP_DC|KEYC_KEYPAD },
{ 57427, KEYC_KP_BEGIN|KEYC_KEYPAD },
{ 57428, KEYC_MEDIA_PLAY },
{ 57429, KEYC_MEDIA_PAUSE },
{ 57430, KEYC_MEDIA_PLAYPAUSE },
{ 57431, KEYC_MEDIA_REVERSE },
{ 57432, KEYC_MEDIA_STOP },
{ 57433, KEYC_MEDIA_FASTFORWARD },
{ 57434, KEYC_MEDIA_REWIND },
{ 57435, KEYC_MEDIA_NEXT },
{ 57436, KEYC_MEDIA_PREVIOUS },
{ 57437, KEYC_MEDIA_RECORD },
{ 57438, KEYC_VOLUME_DOWN },
{ 57439, KEYC_VOLUME_UP },
{ 57440, KEYC_VOLUME_MUTE },
{ 57441, KEYC_LEFT_SHIFT },
{ 57442, KEYC_LEFT_CTRL },
{ 57443, KEYC_LEFT_ALT },
{ 57444, KEYC_LEFT_SUPER },
{ 57445, KEYC_LEFT_HYPER },
{ 57446, KEYC_LEFT_META },
{ 57447, KEYC_RIGHT_SHIFT },
{ 57448, KEYC_RIGHT_CTRL },
{ 57449, KEYC_RIGHT_ALT },
{ 57450, KEYC_RIGHT_SUPER },
{ 57451, KEYC_RIGHT_HYPER },
{ 57452, KEYC_RIGHT_META },
{ 57453, KEYC_ISO_LEVEL3_SHIFT },
{ 57454, KEYC_ISO_LEVEL5_SHIFT }
};
static int
tty_kitty_parse_number(const char *s, u_int *value, int empty)
{
char *end;
unsigned long number;
if (*s == '\0') {
if (!empty)
return (-1);
return (0);
}
if (!isdigit((u_char)*s))
return (-1);
number = strtoul(s, &end, 10);
if (*end != '\0' || number > UINT_MAX)
return (-1);
*value = number;
return (0);
}
static int
tty_kitty_parse_list(char *s, u_int *first, u_int *second, int empty,
u_int maximum)
{
char *copy, *item;
u_int n = 0, value;
int result = 0;
copy = s;
while ((item = strsep(&copy, ":")) != NULL) {
if (maximum != 0 && n == maximum)
return (-1);
value = 0;
if (tty_kitty_parse_number(item, &value, empty) != 0) {
result = -1;
break;
}
if (n == 0 && *item != '\0')
*first = value;
else if (n == 1 && *item != '\0')
*second = value;
n++;
}
return (result);
}
static int
tty_kitty_find_end(const char *buf, size_t len, size_t *end)
{
size_t i;
for (i = 2; i < len; i++) {
if (buf[i] == 'u' || buf[i] == '~' || buf[i] == 'A' ||
buf[i] == 'B' || buf[i] == 'C' || buf[i] == 'D' ||
buf[i] == 'E' || buf[i] == 'F' || buf[i] == 'H' ||
buf[i] == 'P' || buf[i] == 'Q' || buf[i] == 'S') {
*end = i;
return (0);
}
if (!isdigit((u_char)buf[i]) && buf[i] != ';' && buf[i] != ':')
return (-1);
}
return (1);
}
static key_code
tty_kitty_function(u_int number, char final)
{
u_int i;
if (final == 'u') {
switch (number) {
case 9:
return (C0_HT);
case 13:
return (C0_CR);
case 27:
return (C0_ESC);
case 127:
return (KEYC_BSPACE);
}
for (i = 0; i < nitems(tty_kitty_keys); i++) {
if (tty_kitty_keys[i].number == number)
return (tty_kitty_keys[i].key);
}
return (KEYC_NONE);
}
if (final == '~') {
switch (number) {
case 2: return (KEYC_IC);
case 3: return (KEYC_DC);
case 5: return (KEYC_PPAGE);
case 6: return (KEYC_NPAGE);
case 7: return (KEYC_HOME);
case 8: return (KEYC_END);
case 11: return (KEYC_F1);
case 12: return (KEYC_F2);
case 13: return (KEYC_F3);
case 14: return (KEYC_F4);
case 15: return (KEYC_F5);
case 17: return (KEYC_F6);
case 18: return (KEYC_F7);
case 19: return (KEYC_F8);
case 20: return (KEYC_F9);
case 21: return (KEYC_F10);
case 23: return (KEYC_F11);
case 24: return (KEYC_F12);
case 29: return (KEYC_MENU);
case 57427: return (KEYC_KP_BEGIN|KEYC_KEYPAD);
}
return (KEYC_UNKNOWN);
}
if (number != 1)
return (KEYC_UNKNOWN);
switch (final) {
case 'A': return (KEYC_UP|KEYC_CURSOR);
case 'B': return (KEYC_DOWN|KEYC_CURSOR);
case 'C': return (KEYC_RIGHT|KEYC_CURSOR);
case 'D': return (KEYC_LEFT|KEYC_CURSOR);
case 'E': return (KEYC_KP_BEGIN|KEYC_KEYPAD);
case 'F': return (KEYC_END);
case 'H': return (KEYC_HOME);
case 'P': return (KEYC_F1);
case 'Q': return (KEYC_F2);
case 'S': return (KEYC_F4);
}
return (KEYC_UNKNOWN);
}
int
tty_keys_kitty_query(struct tty *tty, const char *buf, size_t len,
size_t *size)
{
struct client *c = tty->client;
size_t i;
u_int flags = 0;
*size = 0;
if (tty->flags & TTY_HAVEKKB)
return (-1);
if (options_get_number(global_options, "extended-keys") == 0 ||
options_get_number(global_options, "extended-keys-format") !=
EXTENDED_KEYS_KITTY)
return (-1);
if (buf[0] != '\033')
return (-1);
if (len == 1)
return (1);
if (buf[1] != '[')
return (-1);
if (len == 2)
return (1);
if (buf[2] != '?')
return (-1);
if (len == 3)
return (1);
for (i = 3; i < len && isdigit((u_char)buf[i]); i++) {
if (flags > (UINT_MAX - (buf[i] - '0')) / 10)
return (-1);
flags = (flags * 10) + (buf[i] - '0');
}
if (i == len)
return (1);
if (i == 3 || buf[i] != 'u')
return (-1);
*size = i + 1;
tty->kitty_keys = flags;
tty->flags |= (TTY_HAVEKKB|TTY_KKBSUPPORT);
log_debug("%s: received Kitty keyboard flags %u", c->name, flags);
tty_update_features(tty);
return (0);
}
void
tty_update_kitty(struct tty *tty, struct screen *s)
{
u_int flags;
char buf[32];
if (options_get_number(global_options, "extended-keys") == 0 ||
options_get_number(global_options, "extended-keys-format") !=
EXTENDED_KEYS_KITTY) {
if (tty->flags & TTY_KKBPUSHED) {
tty_puts(tty, "\033[<u");
tty->flags &= ~TTY_KKBPUSHED;
}
return;
}
if ((tty->flags & (TTY_HAVEKKB|TTY_KKBSUPPORT)) !=
(TTY_HAVEKKB|TTY_KKBSUPPORT))
return;
flags = KITTY_KEY_DISAMBIGUATE;
if (s != NULL && (s->kitty_keys.flags & KITTY_KEY_SUPPORTED) != 0)
flags = s->kitty_keys.flags & KITTY_KEY_SUPPORTED;
if ((tty->flags & TTY_KKBPUSHED) == 0) {
xsnprintf(buf, sizeof buf, "\033[>%uu", flags);
tty_puts(tty, buf);
tty->flags |= TTY_KKBPUSHED;
} else if (tty->kitty_keys != flags) {
xsnprintf(buf, sizeof buf, "\033[=%uu", flags);
tty_puts(tty, buf);
}
tty->kitty_keys = flags;
}
int
tty_keys_kitty(struct tty *tty, const char *buf, size_t len, size_t *size,
key_code *key)
{
struct client *c = tty->client;
struct utf8_data ud;
utf8_char uc;
size_t end;
char tmp[128], *copy, *field, final;
u_int number = 0, modifiers = 1, event = 1, value;
u_int fields = 0;
key_code nkey, onlykey;
int result;
*size = 0;
if ((tty->flags & TTY_KKBPUSHED) == 0 ||
options_get_number(global_options, "extended-keys-format") !=
EXTENDED_KEYS_KITTY)
return (-1);
if (len == 0 || buf[0] != '\033')
return (-1);
if (len == 1)
return (1);
if (buf[1] != '[')
return (-1);
if (len == 2)
return (1);
if (buf[2] == '?')
return (-1);
result = tty_kitty_find_end(buf, len, &end);
if (result != 0)
return (result);
if (end - 2 >= sizeof tmp)
return (-1);
memcpy(tmp, buf + 2, end - 2);
tmp[end - 2] = '\0';
final = buf[end];
*size = end + 1;
copy = tmp;
while ((field = strsep(&copy, ";")) != NULL) {
if (fields == 3)
return (-1);
value = 0;
if (fields == 0) {
if (*field == '\0') {
if (final == 'u')
return (-1);
number = 1;
} else if (tty_kitty_parse_list(field, &number, &value,
1, 3) != 0)
return (-1);
} else if (fields == 1) {
if (tty_kitty_parse_list(field, &modifiers, &event, 1,
2) != 0)
return (-1);
} else if (tty_kitty_parse_list(field, &value, &value, 1, 0) != 0)
return (-1);
fields++;
}
if (fields == 0 || number == 0 || modifiers == 0)
return (-1);
if (event == 0 || event == 3 || event > 3)
return (-2);
modifiers--;
if (modifiers & 32)
return (-2);
nkey = tty_kitty_function(number, final);
if (nkey == KEYC_NONE) {
if (final != 'u')
return (-1);
if (number >= 57344 && number <= 63743)
return (-2);
if (number <= 0x7f)
nkey = number;
else if (utf8_fromwc(number, &ud) != UTF8_DONE ||
utf8_from_data(&ud, &uc) != UTF8_DONE)
return (-2);
else
nkey = uc;
} else if (nkey == KEYC_UNKNOWN)
return (-1);
if (modifiers & 1)
nkey |= KEYC_SHIFT;
if (modifiers & 2)
nkey |= KEYC_META|KEYC_IMPLIED_META;
if (modifiers & 4)
nkey |= KEYC_CTRL;
if (modifiers & 8)
nkey |= KEYC_SUPER;
if (modifiers & 16)
nkey |= KEYC_HYPER;
/* Shift-Tab has a dedicated tmux key code. */
onlykey = nkey & KEYC_MASK_KEY;
if (onlykey == C0_HT && (nkey & KEYC_SHIFT))
nkey = KEYC_BTAB|(nkey & ~KEYC_MASK_KEY & ~KEYC_SHIFT);
if (log_get_level() != 0) {
log_debug("%s: Kitty key %.*s is %llx (%s)", c->name,
(int)*size, buf, nkey, key_string_lookup_key(nkey, 1));
}
*key = nkey;
return (0);
}

15
tty.c
View File

@@ -409,6 +409,11 @@ tty_send_requests(struct tty *tty)
return;
if (tty->term->flags & TERM_VT100LIKE) {
if (~tty->flags & TTY_HAVEKKB &&
options_get_number(global_options, "extended-keys") != 0 &&
options_get_number(global_options, "extended-keys-format") ==
EXTENDED_KEYS_KITTY)
tty_puts(tty, "\033[?u");
if (~tty->flags & TTY_HAVEDA)
tty_puts(tty, "\033[c");
if (~tty->flags & TTY_HAVEDA2)
@@ -507,6 +512,10 @@ tty_stop_tty(struct tty *tty)
tty_raw(tty, "\033[?7727l");
tty_raw(tty, tty_term_string(tty->term, TTYC_DSFCS));
tty_raw(tty, tty_term_string(tty->term, TTYC_DSEKS));
if (tty->flags & TTY_KKBPUSHED) {
tty_raw(tty, "\033[<u");
tty->flags &= ~TTY_KKBPUSHED;
}
if (tty_use_margin(tty))
tty_raw(tty, tty_term_string(tty->term, TTYC_DSMG));
@@ -559,8 +568,12 @@ tty_update_features(struct tty *tty)
if (tty_use_margin(tty))
tty_putcode(tty, TTYC_ENMG);
if (options_get_number(global_options, "extended-keys"))
if (options_get_number(global_options, "extended-keys-format") ==
EXTENDED_KEYS_KITTY)
tty_puts(tty, tty_term_string(tty->term, TTYC_DSEKS));
else if (options_get_number(global_options, "extended-keys"))
tty_puts(tty, tty_term_string(tty->term, TTYC_ENEKS));
tty_update_kitty(tty, NULL);
if (options_get_number(global_options, "focus-events"))
tty_puts(tty, tty_term_string(tty->term, TTYC_ENFCS));
if (tty->term->flags & TERM_VT100LIKE)

View File

@@ -1980,7 +1980,8 @@ window_pane_copy_paste(struct window_pane *wp, char *buf, size_t len)
}
static void
window_pane_copy_key(struct window_pane *wp, key_code key)
window_pane_copy_key(struct window_pane *wp, key_code key,
int extended_encoding)
{
struct window_pane *loop;
@@ -1991,7 +1992,7 @@ window_pane_copy_key(struct window_pane *wp, key_code key)
(~loop->flags & PANE_INPUTOFF) &&
window_pane_is_visible(loop) &&
options_get_number(loop->options, "synchronize-panes"))
input_key_pane(loop, key, NULL);
input_key_pane(loop, key, NULL, extended_encoding);
}
}
@@ -2019,6 +2020,7 @@ window_pane_key(struct window_pane *wp, struct client *c, struct session *s,
struct winlink *wl, key_code key, struct mouse_event *m)
{
struct window_mode_entry *wme;
int extended_encoding;
if (KEYC_IS_MOUSE(key) && m == NULL)
return (-1);
@@ -2041,13 +2043,14 @@ window_pane_key(struct window_pane *wp, struct client *c, struct session *s,
if (wp->fd == -1 || wp->flags & PANE_INPUTOFF)
return (0);
if (input_key_pane(wp, key, m) != 0)
extended_encoding = input_key_client_supports_extended(c, key);
if (input_key_pane(wp, key, m, extended_encoding) != 0)
return (-1);
if (KEYC_IS_MOUSE(key))
return (0);
if (options_get_number(wp->options, "synchronize-panes"))
window_pane_copy_key(wp, key);
window_pane_copy_key(wp, key, extended_encoding);
return (0);
}