diff options
author | Nicholas Marriott <nicm@cvs.openbsd.org> | 2024-08-21 04:17:10 +0000 |
---|---|---|
committer | Nicholas Marriott <nicm@cvs.openbsd.org> | 2024-08-21 04:17:10 +0000 |
commit | 68678c1d7b3238e8b1cebb2e8d3123afcc8966f1 (patch) | |
tree | 1af3ae7780f15e3d527b256886aa92d8b2053934 /usr.bin/tmux/input-keys.c | |
parent | 684cbf0b8edc0de26da51c1f789cc31508c4715b (diff) |
Revamp extended keys support to more closely match xterm and support
mode 2 as well as mode 1. From Stanislav Kljuhhin (GitHub issue 4038).
This changes tmux to always request mode 2 from parent terminal, change
to an unambiguous internal representation of keys, and adds an option
(extended-keys-format) to control the format similar to the xterm(1)
formatOtherKeys resource.
Diffstat (limited to 'usr.bin/tmux/input-keys.c')
-rw-r--r-- | usr.bin/tmux/input-keys.c | 319 |
1 files changed, 209 insertions, 110 deletions
diff --git a/usr.bin/tmux/input-keys.c b/usr.bin/tmux/input-keys.c index 3dbf465eca9..7a83b165e48 100644 --- a/usr.bin/tmux/input-keys.c +++ b/usr.bin/tmux/input-keys.c @@ -1,4 +1,4 @@ -/* $OpenBSD: input-keys.c,v 1.94 2023/01/12 18:49:11 nicm Exp $ */ +/* $OpenBSD: input-keys.c,v 1.95 2024/08/21 04:17:09 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> @@ -308,20 +308,6 @@ static struct input_key_entry input_key_defaults[] = { { .key = KEYC_DC|KEYC_BUILD_MODIFIERS, .data = "\033[3;_~" }, - - /* Tab and modifiers. */ - { .key = '\011'|KEYC_CTRL, - .data = "\011" - }, - { .key = '\011'|KEYC_CTRL|KEYC_EXTENDED, - .data = "\033[9;5u" - }, - { .key = '\011'|KEYC_CTRL|KEYC_SHIFT, - .data = "\033[Z" - }, - { .key = '\011'|KEYC_CTRL|KEYC_SHIFT|KEYC_EXTENDED, - .data = "\033[1;5Z" - } }; static const key_code input_key_modifiers[] = { 0, @@ -427,14 +413,163 @@ input_key_write(const char *from, struct bufferevent *bev, const char *data, bufferevent_write(bev, data, size); } +/* + * Encode and write an extended key escape sequence in one of the two + * possible formats, depending on the configured output mode. + */ +static int +input_key_extended(struct bufferevent *bev, key_code key) +{ + char tmp[64], modifier; + struct utf8_data ud; + wchar_t wc; + + switch (key & KEYC_MASK_MODIFIERS) { + case KEYC_SHIFT: + modifier = '2'; + break; + case KEYC_META: + modifier = '3'; + break; + case KEYC_SHIFT|KEYC_META: + modifier = '4'; + break; + case KEYC_CTRL: + modifier = '5'; + break; + case KEYC_SHIFT|KEYC_CTRL: + modifier = '6'; + break; + case KEYC_META|KEYC_CTRL: + modifier = '7'; + break; + case KEYC_SHIFT|KEYC_META|KEYC_CTRL: + modifier = '8'; + break; + default: + return (-1); + } + + if (KEYC_IS_UNICODE(key)) { + utf8_to_data(key & KEYC_MASK_KEY, &ud); + if (utf8_towc(&ud, &wc) == UTF8_DONE) + key = wc; + else + return (-1); + } else + key &= KEYC_MASK_KEY; + + if (options_get_number(global_options, "extended-keys-format") == 1) + xsnprintf(tmp, sizeof tmp, "\033[27;%c;%llu~", modifier, key); + else + xsnprintf(tmp, sizeof tmp, "\033[%llu;%cu", key, modifier); + + input_key_write(__func__, bev, tmp, strlen(tmp)); + return (0); +} + +/* + * Outputs the key in the "standard" mode. This is by far the most + * complicated output mode, with a lot of remapping in order to + * emulate quirks of terminals that today can be only found in museums. + */ +static int +input_key_vt10x(struct bufferevent *bev, key_code key) +{ + struct utf8_data ud; + key_code onlykey; + char *p; + static const char *standard_map[2] = { + "1!9(0)=+;:'\",<.>/-8? 2", + "119900=+;;'',,..\x1f\x1f\x7f\x7f\0\0", + }; + + log_debug("%s: key in %llx", __func__, key); + + if (key & KEYC_META) + input_key_write(__func__, bev, "\033", 1); + + /* + * There's no way to report modifiers for unicode keys in standard mode + * so lose the modifiers. + */ + if (KEYC_IS_UNICODE(key)) { + utf8_to_data(key, &ud); + input_key_write(__func__, bev, ud.data, ud.size); + return (0); + } + + onlykey = key & KEYC_MASK_KEY; + + /* Prevent TAB and RET from being swallowed by C0 remapping logic. */ + if (onlykey == '\r' || onlykey == '\t') + key &= ~KEYC_CTRL; + + /* + * Convert keys with Ctrl modifier into corresponding C0 control codes, + * with the exception of *some* keys, which are remapped into printable + * ASCII characters. + * + * There is no special handling for Shift modifier, which is pretty + * much redundant anyway, as no terminal will send <base key>|SHIFT, + * but only <shifted key>|SHIFT. + */ + if (key & KEYC_CTRL) { + p = strchr(standard_map[0], onlykey); + if (p != NULL) + key = standard_map[1][p - standard_map[0]]; + else if (onlykey >= '3' && onlykey <= '7') + key = onlykey - '\030'; + else if (onlykey >= '@' && onlykey <= '~') + key = onlykey & 0x1f; + else + return (-1); + } + + log_debug("%s: key out %llx", __func__, key); + + ud.data[0] = key & 0x7f; + input_key_write(__func__, bev, &ud.data[0], 1); + return (0); +} + +/* Pick keys that are reported as vt10x keys in modifyOtherKeys=1 mode. */ +static int +input_key_mode1(struct bufferevent *bev, key_code key) +{ + key_code onlykey; + + log_debug("%s: key in %llx", __func__, key); + + /* + * As per + * https://invisible-island.net/xterm/modified-keys-us-pc105.html. + */ + onlykey = key & KEYC_MASK_KEY; + if ((key & (KEYC_META | KEYC_CTRL)) == KEYC_CTRL && + (onlykey == '/' || onlykey == '@' || onlykey == '^' || + (onlykey >= '2' && onlykey <= '8') || + (onlykey >= '@' && onlykey <= '~'))) + return (input_key_vt10x(bev, key)); + + /* + * A regular or shifted Unicode key + Meta. In the absence of a + * standard to back this, we mimic what iTerm 2 does. + */ + if ((key & (KEYC_CTRL | KEYC_META)) == KEYC_META && + KEYC_IS_UNICODE(key)) + return (input_key_vt10x(bev, key)); + + return (-1); +} + /* Translate a key code into an output key sequence. */ int input_key(struct screen *s, struct bufferevent *bev, key_code key) { struct input_key_entry *ike = NULL; - key_code justkey, newkey, outkey, modifiers; + key_code newkey; struct utf8_data ud; - char tmp[64], modifier; /* Mouse keys need a pane. */ if (KEYC_IS_MOUSE(key)) @@ -455,36 +590,45 @@ input_key(struct screen *s, struct bufferevent *bev, key_code key) key = newkey|(key & (KEYC_MASK_MODIFIERS|KEYC_MASK_FLAGS)); } + /* Is this backtab? */ + if ((key & KEYC_MASK_KEY) == KEYC_BTAB) { + if (s->mode & EXTENDED_KEY_MODES) { + /* When in xterm extended mode, remap into S-Tab. */ + key = '\011' | (key & ~KEYC_MASK_KEY) | KEYC_SHIFT; + } else { + /* Otherwise clear modifiers. */ + key &= ~KEYC_MASK_MODIFIERS; + } + } + /* - * If this is a normal 7-bit key, just send it, with a leading escape - * if necessary. If it is a UTF-8 key, split it and send it. + * A trivial case, that is a 7-bit key, excluding C0 control characters + * that can't be entered from the keyboard, and no modifiers; or a UTF-8 + * key and no modifiers. */ - justkey = (key & ~(KEYC_META|KEYC_IMPLIED_META)); - if (justkey <= 0x7f) { - if (key & KEYC_META) - input_key_write(__func__, bev, "\033", 1); - ud.data[0] = justkey; - input_key_write(__func__, bev, &ud.data[0], 1); - return (0); - } - if (KEYC_IS_UNICODE(justkey)) { - if (key & KEYC_META) - input_key_write(__func__, bev, "\033", 1); - utf8_to_data(justkey, &ud); - input_key_write(__func__, bev, ud.data, ud.size); - return (0); + if (!(key & ~KEYC_MASK_KEY)) { + if (key == C0_BS || key == C0_HT || + key == C0_CR || key == C0_ESC || + (key >= 0x20 && key <= 0x7f)) { + ud.data[0] = key; + input_key_write(__func__, bev, &ud.data[0], 1); + return (0); + } + if (KEYC_IS_UNICODE(key)) { + utf8_to_data(key, &ud); + input_key_write(__func__, bev, ud.data, ud.size); + return (0); + } } /* - * Look up in the tree. If not in application keypad or cursor mode, - * remove the flags from the key. + * Look up the standard VT10x keys in the tree. If not in application + * keypad or cursor mode, remove the respective flags from the key. */ if (~s->mode & MODE_KKEYPAD) key &= ~KEYC_KEYPAD; if (~s->mode & MODE_KCURSOR) key &= ~KEYC_CURSOR; - if (s->mode & MODE_KEXTENDED) - ike = input_key_get(key|KEYC_EXTENDED); if (ike == NULL) ike = input_key_get(key); if (ike == NULL && (key & KEYC_META) && (~key & KEYC_IMPLIED_META)) @@ -493,10 +637,9 @@ input_key(struct screen *s, struct bufferevent *bev, key_code key) ike = input_key_get(key & ~KEYC_CURSOR); if (ike == NULL && (key & KEYC_KEYPAD)) ike = input_key_get(key & ~KEYC_KEYPAD); - if (ike == NULL && (key & KEYC_EXTENDED)) - ike = input_key_get(key & ~KEYC_EXTENDED); if (ike != NULL) { - log_debug("found key 0x%llx: \"%s\"", key, ike->data); + log_debug("%s: found key 0x%llx: \"%s\"", __func__, key, + ike->data); if ((key == KEYC_PASTE_START || key == KEYC_PASTE_END) && (~s->mode & MODE_BRACKETPASTE)) return (0); @@ -506,78 +649,34 @@ input_key(struct screen *s, struct bufferevent *bev, key_code key) return (0); } - /* No builtin key sequence; construct an extended key sequence. */ - if (~s->mode & MODE_KEXTENDED) { - if ((key & KEYC_MASK_MODIFIERS) != KEYC_CTRL) - goto missing; - justkey = (key & KEYC_MASK_KEY); - switch (justkey) { - case ' ': - case '2': - key = 0|(key & ~KEYC_MASK_KEY); - break; - case '|': - key = 28|(key & ~KEYC_MASK_KEY); - break; - case '6': - key = 30|(key & ~KEYC_MASK_KEY); - break; - case '-': - case '/': - key = 31|(key & ~KEYC_MASK_KEY); - break; - case '?': - key = 127|(key & ~KEYC_MASK_KEY); - break; - default: - if (justkey >= 'A' && justkey <= '_') - key = (justkey - 'A')|(key & ~KEYC_MASK_KEY); - else if (justkey >= 'a' && justkey <= '~') - key = (justkey - 96)|(key & ~KEYC_MASK_KEY); - else - return (0); - break; - } - return (input_key(s, bev, key & ~KEYC_CTRL)); - } - outkey = (key & KEYC_MASK_KEY); - modifiers = (key & KEYC_MASK_MODIFIERS); - if (outkey < 32 && outkey != 9 && outkey != 13 && outkey != 27) { - outkey = 64 + outkey; - modifiers |= KEYC_CTRL; - } - switch (modifiers) { - case KEYC_SHIFT: - modifier = '2'; - break; - case KEYC_META: - modifier = '3'; - break; - case KEYC_SHIFT|KEYC_META: - modifier = '4'; - break; - case KEYC_CTRL: - modifier = '5'; - break; - case KEYC_SHIFT|KEYC_CTRL: - modifier = '6'; - break; - case KEYC_META|KEYC_CTRL: - modifier = '7'; - break; - case KEYC_SHIFT|KEYC_META|KEYC_CTRL: - modifier = '8'; - break; + /* + * No builtin key sequence; construct an extended key sequence + * depending on the client mode. + * + * If something invalid reaches here, an invalid output may be + * produced. For example Ctrl-Shift-2 is invalid (as there's + * no way to enter it). The correct form is Ctrl-Shift-@, at + * least in US English keyboard layout. + */ + switch (s->mode & EXTENDED_KEY_MODES) { + case MODE_KEYS_EXTENDED_2: + /* + * The simplest mode to handle - *all* modified keys are + * reported in the extended form. + */ + return (input_key_extended(bev, key)); + case MODE_KEYS_EXTENDED: + /* + * Some keys are still reported in standard mode, to maintain + * compatibility with applications unaware of extended keys. + */ + if (input_key_mode1(bev, key) == -1) + return (input_key_extended(bev, key)); + return (0); default: - goto missing; + /* The standard mode. */ + return (input_key_vt10x(bev, key)); } - xsnprintf(tmp, sizeof tmp, "\033[%llu;%cu", outkey, modifier); - input_key_write(__func__, bev, tmp, strlen(tmp)); - return (0); - -missing: - log_debug("key 0x%llx missing", key); - return (-1); } /* Get mouse event string. */ |