diff --git a/input-keys.c b/input-keys.c index f3281b0e..f10adb91 100644 --- a/input-keys.c +++ b/input-keys.c @@ -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,126 +413,18 @@ input_key_write(const char *from, struct bufferevent *bev, const char *data, bufferevent_write(bev, data, size); } -/* Translate a key code into an output key sequence. */ -int -input_key(struct screen *s, struct bufferevent *bev, key_code key) +/* + * 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) { - struct input_key_entry *ike = NULL; - key_code justkey, newkey, outkey, modifiers; - struct utf8_data ud; - char tmp[64], modifier; + char tmp[64], modifier; + struct utf8_data ud; + wchar_t wc; - /* Mouse keys need a pane. */ - if (KEYC_IS_MOUSE(key)) - return (0); - - /* Literal keys go as themselves (can't be more than eight bits). */ - if (key & KEYC_LITERAL) { - ud.data[0] = (u_char)key; - input_key_write(__func__, bev, &ud.data[0], 1); - return (0); - } - - /* Is this backspace? */ - if ((key & KEYC_MASK_KEY) == KEYC_BSPACE) { - newkey = options_get_number(global_options, "backspace"); - if (newkey >= 0x7f) - newkey = '\177'; - key = newkey|(key & (KEYC_MASK_MODIFIERS|KEYC_MASK_FLAGS)); - } - - /* - * 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. - */ - 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); - } - - /* - * Look up in the tree. If not in application keypad or cursor mode, - * remove the 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)) - ike = input_key_get(key & ~KEYC_META); - if (ike == NULL && (key & KEYC_CURSOR)) - 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); - if ((key == KEYC_PASTE_START || key == KEYC_PASTE_END) && - (~s->mode & MODE_BRACKETPASTE)) - return (0); - if ((key & KEYC_META) && (~key & KEYC_IMPLIED_META)) - input_key_write(__func__, bev, "\033", 1); - input_key_write(__func__, bev, ike->data, strlen(ike->data)); - 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) { + switch (key & KEYC_MASK_MODIFIERS) { case KEYC_SHIFT: modifier = '2'; break; @@ -569,17 +447,240 @@ input_key(struct screen *s, struct bufferevent *bev, key_code key) modifier = '8'; break; default: - goto missing; + return (-1); } - xsnprintf(tmp, sizeof tmp, "\033[%llu;%cu", outkey, modifier); + + 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 |SHIFT, + * but only |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 == '^' || + (onlykey >= '2' && onlykey <= '8') || + (onlykey >= '@' && onlykey <= '~'))) + return (input_key_vt10x(bev, key)); + + /* + * A regular 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) + return (input_key_vt10x(bev, key)); -missing: - log_debug("key 0x%llx missing", 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 newkey; + struct utf8_data ud; + + /* Mouse keys need a pane. */ + if (KEYC_IS_MOUSE(key)) + return (0); + + /* Literal keys go as themselves (can't be more than eight bits). */ + if (key & KEYC_LITERAL) { + ud.data[0] = (u_char)key; + input_key_write(__func__, bev, &ud.data[0], 1); + return (0); + } + + /* Is this backspace? */ + if ((key & KEYC_MASK_KEY) == KEYC_BSPACE) { + newkey = options_get_number(global_options, "backspace"); + if (newkey >= 0x7f) + newkey = '\177'; + 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; + } + } + + /* + * 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. + */ + 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 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 (ike == NULL) + ike = input_key_get(key); + if (ike == NULL && (key & KEYC_META) && (~key & KEYC_IMPLIED_META)) + ike = input_key_get(key & ~KEYC_META); + if (ike == NULL && (key & KEYC_CURSOR)) + ike = input_key_get(key & ~KEYC_CURSOR); + if (ike == NULL && (key & KEYC_KEYPAD)) + ike = input_key_get(key & ~KEYC_KEYPAD); + if (ike != NULL) { + 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); + if ((key & KEYC_META) && (~key & KEYC_IMPLIED_META)) + input_key_write(__func__, bev, "\033", 1); + input_key_write(__func__, bev, ike->data, strlen(ike->data)); + return (0); + } + + /* + * 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: + /* The standard mode. */ + return (input_key_vt10x(bev, key)); + } +} + /* Get mouse event string. */ int input_key_get_mouse(struct screen *s, struct mouse_event *m, u_int x, u_int y,