From: nicm Date: Wed, 21 Aug 2024 04:17:09 +0000 (+0000) Subject: Revamp extended keys support to more closely match xterm and support X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=719f5715a68555953a1997f39792665cc714f31c;p=openbsd 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. --- diff --git a/usr.bin/tmux/format.c b/usr.bin/tmux/format.c index 2377697bcba..aa17e924ffe 100644 --- a/usr.bin/tmux/format.c +++ b/usr.bin/tmux/format.c @@ -1,4 +1,4 @@ -/* $OpenBSD: format.c,v 1.318 2023/09/08 06:52:31 nicm Exp $ */ +/* $OpenBSD: format.c,v 1.319 2024/08/21 04:17:09 nicm Exp $ */ /* * Copyright (c) 2011 Nicholas Marriott @@ -1962,6 +1962,23 @@ format_cb_pane_unseen_changes(struct format_tree *ft) return (NULL); } +/* Callback for pane_key_mode. */ +static void * +format_cb_pane_key_mode(struct format_tree *ft) +{ + if (ft->wp != NULL && ft->wp->screen != NULL) { + switch (ft->wp->screen->mode & EXTENDED_KEY_MODES) { + case MODE_KEYS_EXTENDED: + return (xstrdup("Ext 1")); + case MODE_KEYS_EXTENDED_2: + return (xstrdup("Ext 2")); + default: + return (xstrdup("VT10x")); + } + } + return (NULL); +} + /* Callback for pane_last. */ static void * format_cb_pane_last(struct format_tree *ft) @@ -2997,6 +3014,9 @@ static const struct format_table_entry format_table[] = { { "pane_input_off", FORMAT_TABLE_STRING, format_cb_pane_input_off }, + { "pane_key_mode", FORMAT_TABLE_STRING, + format_cb_pane_key_mode + }, { "pane_last", FORMAT_TABLE_STRING, format_cb_pane_last }, 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 @@ -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 |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 >= '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. */ diff --git a/usr.bin/tmux/input.c b/usr.bin/tmux/input.c index 00268e8e933..eb23e89c7f7 100644 --- a/usr.bin/tmux/input.c +++ b/usr.bin/tmux/input.c @@ -1,4 +1,4 @@ -/* $OpenBSD: input.c,v 1.226 2024/08/19 08:31:36 nicm Exp $ */ +/* $OpenBSD: input.c,v 1.227 2024/08/21 04:17:09 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -1408,17 +1408,29 @@ input_csi_dispatch(struct input_ctx *ictx) case INPUT_CSI_MODSET: n = input_get(ictx, 0, 0, 0); m = input_get(ictx, 1, 0, 0); + /* + * Set the extended key reporting mode as per the client request, + * unless "extended-keys always" forces us into mode 1. + */ if (options_get_number(global_options, "extended-keys") == 2) break; - if (n == 0 || (n == 4 && m == 0)) - screen_write_mode_clear(sctx, MODE_KEXTENDED); - else if (n == 4 && (m == 1 || m == 2)) - screen_write_mode_set(sctx, MODE_KEXTENDED); + screen_write_mode_clear(sctx, + MODE_KEYS_EXTENDED|MODE_KEYS_EXTENDED_2); + if (n == 4 && m == 1) + screen_write_mode_set(sctx, MODE_KEYS_EXTENDED); + if (n == 4 && m == 2) + screen_write_mode_set(sctx, MODE_KEYS_EXTENDED_2); break; case INPUT_CSI_MODOFF: n = input_get(ictx, 0, 0, 0); - if (n == 4) - screen_write_mode_clear(sctx, MODE_KEXTENDED); + /* + * Clear the extended key reporting mode as per the client request, + * unless "extended-keys always" forces us into mode 1. + */ + if (n == 4) { + screen_write_mode_clear(sctx, + MODE_KEYS_EXTENDED|MODE_KEYS_EXTENDED_2); + } break; case INPUT_CSI_WINOPS: input_csi_dispatch_winops(ictx); diff --git a/usr.bin/tmux/key-string.c b/usr.bin/tmux/key-string.c index 9b22d2ea101..d9ba95afae6 100644 --- a/usr.bin/tmux/key-string.c +++ b/usr.bin/tmux/key-string.c @@ -1,4 +1,4 @@ -/* $OpenBSD: key-string.c,v 1.71 2023/01/16 11:26:14 nicm Exp $ */ +/* $OpenBSD: key-string.c,v 1.72 2024/08/21 04:17:09 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -56,12 +56,47 @@ static const struct { { "PPage", KEYC_PPAGE|KEYC_IMPLIED_META }, { "PageUp", KEYC_PPAGE|KEYC_IMPLIED_META }, { "PgUp", KEYC_PPAGE|KEYC_IMPLIED_META }, - { "Tab", '\011' }, { "BTab", KEYC_BTAB }, { "Space", ' ' }, { "BSpace", KEYC_BSPACE }, - { "Enter", '\r' }, - { "Escape", '\033' }, + + /* + * C0 control characters, with the exception of Tab, Enter, + * and Esc, should never appear as keys. We still render them, + * so to be able to spot them in logs in case of an abnormality. + */ + { "[NUL]", C0_NUL }, + { "[SOH]", C0_SOH }, + { "[STX]", C0_STX }, + { "[ETX]", C0_ETX }, + { "[EOT]", C0_EOT }, + { "[ENQ]", C0_ENQ }, + { "[ASC]", C0_ASC }, + { "[BEL]", C0_BEL }, + { "[BS]", C0_BS }, + { "Tab", C0_HT }, + { "[LF]", C0_LF }, + { "[VT]", C0_VT }, + { "[FF]", C0_FF }, + { "Enter", C0_CR }, + { "[SO]", C0_SO }, + { "[SI]", C0_SI }, + { "[DLE]", C0_DLE }, + { "[DC1]", C0_DC1 }, + { "[DC2]", C0_DC2 }, + { "[DC3]", C0_DC3 }, + { "[DC4]", C0_DC4 }, + { "[NAK]", C0_NAK }, + { "[SYN]", C0_SYN }, + { "[ETB]", C0_ETB }, + { "[CAN]", C0_CAN }, + { "[EM]", C0_EM }, + { "[SUB]", C0_SUB }, + { "Escape", C0_ESC }, + { "[FS]", C0_FS }, + { "[GS]", C0_GS }, + { "[RS]", C0_RS }, + { "[US]", C0_US }, /* Arrow keys. */ { "Up", KEYC_UP|KEYC_CURSOR|KEYC_IMPLIED_META }, @@ -206,7 +241,6 @@ key_string_get_modifiers(const char **string) key_code key_string_lookup_string(const char *string) { - static const char *other = "!#()+,-.0123456789:;<=>'\r\t\177`/"; key_code key, modifiers; u_int u, i; struct utf8_data ud, *udp; @@ -281,26 +315,6 @@ key_string_lookup_string(const char *string) key &= ~KEYC_IMPLIED_META; } - /* Convert the standard control keys. */ - if (key <= 127 && - (modifiers & KEYC_CTRL) && - strchr(other, key) == NULL && - key != 9 && - key != 13 && - key != 27) { - if (key >= 97 && key <= 122) - key -= 96; - else if (key >= 64 && key <= 95) - key -= 64; - else if (key == 32) - key = 0; - else if (key == 63) - key = 127; - else - return (KEYC_UNKNOWN); - modifiers &= ~KEYC_CTRL; - } - return (key|modifiers); } @@ -324,10 +338,6 @@ key_string_lookup_key(key_code key, int with_flags) goto out; } - /* Display C-@ as C-Space. */ - if ((key & (KEYC_MASK_KEY|KEYC_MASK_MODIFIERS)) == 0) - key = ' '|KEYC_CTRL; - /* Fill in the modifiers. */ if (key & KEYC_CTRL) strlcat(out, "C-", sizeof out); @@ -427,13 +437,8 @@ key_string_lookup_key(key_code key, int with_flags) goto out; } - /* Check for standard or control key. */ - if (key <= 32) { - if (key == 0 || key > 26) - xsnprintf(tmp, sizeof tmp, "C-%c", (int)(64 + key)); - else - xsnprintf(tmp, sizeof tmp, "C-%c", (int)(96 + key)); - } else if (key >= 32 && key <= 126) { + /* Printable ASCII keys. */ + if (key > 32 && key <= 126) { tmp[0] = key; tmp[1] = '\0'; } else if (key == 127) @@ -460,8 +465,6 @@ out: strlcat(out, "I", sizeof out); if (saved & KEYC_BUILD_MODIFIERS) strlcat(out, "B", sizeof out); - if (saved & KEYC_EXTENDED) - strlcat(out, "E", sizeof out); if (saved & KEYC_SENT) strlcat(out, "S", sizeof out); strlcat(out, "]", sizeof out); diff --git a/usr.bin/tmux/menu.c b/usr.bin/tmux/menu.c index 79ad009ffdc..10ad6646937 100644 --- a/usr.bin/tmux/menu.c +++ b/usr.bin/tmux/menu.c @@ -1,4 +1,4 @@ -/* $OpenBSD: menu.c,v 1.52 2023/08/15 07:01:47 nicm Exp $ */ +/* $OpenBSD: menu.c,v 1.53 2024/08/21 04:17:09 nicm Exp $ */ /* * Copyright (c) 2019 Nicholas Marriott @@ -335,7 +335,7 @@ menu_key_cb(struct client *c, void *data, struct key_event *event) c->flags |= CLIENT_REDRAWOVERLAY; return (0); case KEYC_PPAGE: - case '\002': /* C-b */ + case 'b'|KEYC_CTRL: if (md->choice < 6) md->choice = 0; else { @@ -394,13 +394,13 @@ menu_key_cb(struct client *c, void *data, struct key_event *event) } c->flags |= CLIENT_REDRAWOVERLAY; break; - case '\006': /* C-f */ + case 'f'|KEYC_CTRL: break; case '\r': goto chosen; case '\033': /* Escape */ - case '\003': /* C-c */ - case '\007': /* C-g */ + case 'c'|KEYC_CTRL: + case 'g'|KEYC_CTRL: case 'q': return (1); } diff --git a/usr.bin/tmux/mode-tree.c b/usr.bin/tmux/mode-tree.c index 8c62602428b..41c220100ca 100644 --- a/usr.bin/tmux/mode-tree.c +++ b/usr.bin/tmux/mode-tree.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mode-tree.c,v 1.68 2024/08/04 08:53:43 nicm Exp $ */ +/* $OpenBSD: mode-tree.c,v 1.69 2024/08/21 04:17:09 nicm Exp $ */ /* * Copyright (c) 2017 Nicholas Marriott @@ -1088,22 +1088,22 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, switch (*key) { case 'q': case '\033': /* Escape */ - case '\007': /* C-g */ + case 'g'|KEYC_CTRL: return (1); case KEYC_UP: case 'k': case KEYC_WHEELUP_PANE: - case '\020': /* C-p */ + case 'p'|KEYC_CTRL: mode_tree_up(mtd, 1); break; case KEYC_DOWN: case 'j': case KEYC_WHEELDOWN_PANE: - case '\016': /* C-n */ + case 'n'|KEYC_CTRL: mode_tree_down(mtd, 1); break; case KEYC_PPAGE: - case '\002': /* C-b */ + case 'b'|KEYC_CTRL: for (i = 0; i < mtd->height; i++) { if (mtd->current == 0) break; @@ -1111,7 +1111,7 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, } break; case KEYC_NPAGE: - case '\006': /* C-f */ + case 'f'|KEYC_CTRL: for (i = 0; i < mtd->height; i++) { if (mtd->current == mtd->line_size - 1) break; @@ -1155,7 +1155,7 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, for (i = 0; i < mtd->line_size; i++) mtd->line_list[i].item->tagged = 0; break; - case '\024': /* C-t */ + case 't'|KEYC_CTRL: for (i = 0; i < mtd->line_size; i++) { if ((mtd->line_list[i].item->parent == NULL && !mtd->line_list[i].item->no_tag) || @@ -1211,7 +1211,7 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, break; case '?': case '/': - case '\023': /* C-s */ + case 's'|KEYC_CTRL: mtd->references++; status_prompt_set(c, NULL, "(search) ", "", mode_tree_search_callback, mode_tree_search_free, mtd, diff --git a/usr.bin/tmux/options-table.c b/usr.bin/tmux/options-table.c index f1e85609206..dd1782bc280 100644 --- a/usr.bin/tmux/options-table.c +++ b/usr.bin/tmux/options-table.c @@ -1,4 +1,4 @@ -/* $OpenBSD: options-table.c,v 1.175 2024/08/04 09:35:30 nicm Exp $ */ +/* $OpenBSD: options-table.c,v 1.176 2024/08/21 04:17:09 nicm Exp $ */ /* * Copyright (c) 2011 Nicholas Marriott @@ -94,6 +94,9 @@ static const char *options_table_detach_on_destroy_list[] = { 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 +}; static const char *options_table_allow_passthrough_list[] = { "off", "on", "all", NULL }; @@ -310,11 +313,19 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SERVER, .choices = options_table_extended_keys_list, - .default_num = 0, + .default_num = 1, .text = "Whether to request extended key sequences from terminals " "that support it." }, + { .name = "extended-keys-format", + .type = OPTIONS_TABLE_CHOICE, + .scope = OPTIONS_TABLE_SERVER, + .choices = options_table_extended_keys_format_list, + .default_num = 1, + .text = "The format of emitted extended key sequences." + }, + { .name = "focus-events", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SERVER, @@ -614,7 +625,7 @@ const struct options_table_entry options_table[] = { { .name = "prefix", .type = OPTIONS_TABLE_KEY, .scope = OPTIONS_TABLE_SESSION, - .default_num = '\002', + .default_num = 'b'|KEYC_CTRL, .text = "The prefix key." }, diff --git a/usr.bin/tmux/popup.c b/usr.bin/tmux/popup.c index 8f543f83f20..81d749d8009 100644 --- a/usr.bin/tmux/popup.c +++ b/usr.bin/tmux/popup.c @@ -1,4 +1,4 @@ -/* $OpenBSD: popup.c,v 1.53 2024/03/21 11:30:42 nicm Exp $ */ +/* $OpenBSD: popup.c,v 1.54 2024/08/21 04:17:09 nicm Exp $ */ /* * Copyright (c) 2020 Nicholas Marriott @@ -543,7 +543,7 @@ popup_key_cb(struct client *c, void *data, struct key_event *event) } if ((((pd->flags & (POPUP_CLOSEEXIT|POPUP_CLOSEEXITZERO)) == 0) || pd->job == NULL) && - (event->key == '\033' || event->key == '\003')) + (event->key == '\033' || event->key == ('c'|KEYC_CTRL))) return (1); if (pd->job != NULL) { if (KEYC_IS_MOUSE(event->key)) { diff --git a/usr.bin/tmux/screen-write.c b/usr.bin/tmux/screen-write.c index 350ff672b9f..17dfb1d33cb 100644 --- a/usr.bin/tmux/screen-write.c +++ b/usr.bin/tmux/screen-write.c @@ -1,4 +1,4 @@ -/* $OpenBSD: screen-write.c,v 1.225 2024/03/21 12:10:57 nicm Exp $ */ +/* $OpenBSD: screen-write.c,v 1.226 2024/08/21 04:17:09 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -326,8 +326,9 @@ screen_write_reset(struct screen_write_ctx *ctx) screen_write_scrollregion(ctx, 0, screen_size_y(s) - 1); s->mode = MODE_CURSOR|MODE_WRAP; + if (options_get_number(global_options, "extended-keys") == 2) - s->mode |= MODE_KEXTENDED; + s->mode = (s->mode & ~EXTENDED_KEY_MODES)|MODE_KEYS_EXTENDED; screen_write_clearscreen(ctx, 8); screen_write_set_cursor(ctx, 0, 0); diff --git a/usr.bin/tmux/screen.c b/usr.bin/tmux/screen.c index ff73a8c4e72..182d6a89428 100644 --- a/usr.bin/tmux/screen.c +++ b/usr.bin/tmux/screen.c @@ -1,4 +1,4 @@ -/* $OpenBSD: screen.c,v 1.85 2024/03/21 11:26:28 nicm Exp $ */ +/* $OpenBSD: screen.c,v 1.86 2024/08/21 04:17:09 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -107,8 +107,9 @@ screen_reinit(struct screen *s) s->rlower = screen_size_y(s) - 1; s->mode = MODE_CURSOR|MODE_WRAP|(s->mode & MODE_CRLF); + if (options_get_number(global_options, "extended-keys") == 2) - s->mode |= MODE_KEXTENDED; + s->mode = (s->mode & ~EXTENDED_KEY_MODES)|MODE_KEYS_EXTENDED; if (s->saved_grid != NULL) screen_alternate_off(s, NULL, 0); @@ -714,8 +715,10 @@ screen_mode_to_string(int mode) strlcat(tmp, "ORIGIN,", sizeof tmp); if (mode & MODE_CRLF) strlcat(tmp, "CRLF,", sizeof tmp); - if (mode & MODE_KEXTENDED) - strlcat(tmp, "KEXTENDED,", sizeof tmp); + if (mode & MODE_KEYS_EXTENDED) + strlcat(tmp, "KEYS_EXTENDED,", sizeof tmp); + if (mode & MODE_KEYS_EXTENDED_2) + strlcat(tmp, "KEYS_EXTENDED_2,", sizeof tmp); tmp[strlen(tmp) - 1] = '\0'; return (tmp); } diff --git a/usr.bin/tmux/status.c b/usr.bin/tmux/status.c index 5c75db37aeb..f85937fd961 100644 --- a/usr.bin/tmux/status.c +++ b/usr.bin/tmux/status.c @@ -1,4 +1,4 @@ -/* $OpenBSD: status.c,v 1.242 2024/05/15 08:39:30 nicm Exp $ */ +/* $OpenBSD: status.c,v 1.243 2024/08/21 04:17:09 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -839,19 +839,19 @@ status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) { if (c->prompt_mode == PROMPT_ENTRY) { switch (key) { - case '\001': /* C-a */ - case '\003': /* C-c */ - case '\005': /* C-e */ - case '\007': /* C-g */ - case '\010': /* C-h */ + case 'a'|KEYC_CTRL: + case 'c'|KEYC_CTRL: + case 'e'|KEYC_CTRL: + case 'g'|KEYC_CTRL: + case 'h'|KEYC_CTRL: case '\011': /* Tab */ - case '\013': /* C-k */ - case '\016': /* C-n */ - case '\020': /* C-p */ - case '\024': /* C-t */ - case '\025': /* C-u */ - case '\027': /* C-w */ - case '\031': /* C-y */ + case 'k'|KEYC_CTRL: + case 'n'|KEYC_CTRL: + case 'p'|KEYC_CTRL: + case 't'|KEYC_CTRL: + case 'u'|KEYC_CTRL: + case 'w'|KEYC_CTRL: + case 'y'|KEYC_CTRL: case '\n': case '\r': case KEYC_LEFT|KEYC_CTRL: @@ -890,7 +890,7 @@ status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) case 'S': c->prompt_mode = PROMPT_ENTRY; c->flags |= CLIENT_REDRAWSTATUS; - *new_key = '\025'; /* C-u */ + *new_key = 'u'|KEYC_CTRL; return (1); case 'i': case '\033': /* Escape */ @@ -911,7 +911,7 @@ status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) return (1); case 'C': case 'D': - *new_key = '\013'; /* C-k */ + *new_key = 'k'|KEYC_CTRL; return (1); case KEYC_BSPACE: case 'X': @@ -924,7 +924,7 @@ status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) *new_key = 'B'|KEYC_VI; return (1); case 'd': - *new_key = '\025'; /* C-u */ + *new_key = 'u'|KEYC_CTRL; return (1); case 'e': *new_key = 'e'|KEYC_VI; @@ -939,10 +939,10 @@ status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) *new_key = 'W'|KEYC_VI; return (1); case 'p': - *new_key = '\031'; /* C-y */ + *new_key = 'y'|KEYC_CTRL; return (1); case 'q': - *new_key = '\003'; /* C-c */ + *new_key = 'c'|KEYC_CTRL; return (1); case 's': case KEYC_DC: @@ -966,8 +966,8 @@ status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) case 'k': *new_key = KEYC_UP; return (1); - case '\010' /* C-h */: - case '\003' /* C-c */: + case 'h'|KEYC_CTRL: + case 'c'|KEYC_CTRL: case '\n': case '\r': return (1); @@ -1263,28 +1263,28 @@ status_prompt_key(struct client *c, key_code key) process_key: switch (key) { case KEYC_LEFT: - case '\002': /* C-b */ + case 'b'|KEYC_CTRL: if (c->prompt_index > 0) { c->prompt_index--; break; } break; case KEYC_RIGHT: - case '\006': /* C-f */ + case 'f'|KEYC_CTRL: if (c->prompt_index < size) { c->prompt_index++; break; } break; case KEYC_HOME: - case '\001': /* C-a */ + case 'a'|KEYC_CTRL: if (c->prompt_index != 0) { c->prompt_index = 0; break; } break; case KEYC_END: - case '\005': /* C-e */ + case 'e'|KEYC_CTRL: if (c->prompt_index != size) { c->prompt_index = size; break; @@ -1295,7 +1295,7 @@ process_key: goto changed; break; case KEYC_BSPACE: - case '\010': /* C-h */ + case 'h'|KEYC_CTRL: if (c->prompt_index != 0) { if (c->prompt_index == size) c->prompt_buffer[--c->prompt_index].size = 0; @@ -1310,7 +1310,7 @@ process_key: } break; case KEYC_DC: - case '\004': /* C-d */ + case 'd'|KEYC_CTRL: if (c->prompt_index != size) { memmove(c->prompt_buffer + c->prompt_index, c->prompt_buffer + c->prompt_index + 1, @@ -1319,17 +1319,17 @@ process_key: goto changed; } break; - case '\025': /* C-u */ + case 'u'|KEYC_CTRL: c->prompt_buffer[0].size = 0; c->prompt_index = 0; goto changed; - case '\013': /* C-k */ + case 'k'|KEYC_CTRL: if (c->prompt_index < size) { c->prompt_buffer[c->prompt_index].size = 0; goto changed; } break; - case '\027': /* C-w */ + case 'w'|KEYC_CTRL: separators = options_get_string(oo, "word-separators"); idx = c->prompt_index; @@ -1397,7 +1397,7 @@ process_key: status_prompt_backward_word(c, separators); goto changed; case KEYC_UP: - case '\020': /* C-p */ + case 'p'|KEYC_CTRL: histstr = status_prompt_up_history(c->prompt_hindex, c->prompt_type); if (histstr == NULL) @@ -1407,7 +1407,7 @@ process_key: c->prompt_index = utf8_strlen(c->prompt_buffer); goto changed; case KEYC_DOWN: - case '\016': /* C-n */ + case 'n'|KEYC_CTRL: histstr = status_prompt_down_history(c->prompt_hindex, c->prompt_type); if (histstr == NULL) @@ -1416,11 +1416,11 @@ process_key: c->prompt_buffer = utf8_fromcstr(histstr); c->prompt_index = utf8_strlen(c->prompt_buffer); goto changed; - case '\031': /* C-y */ + case 'y'|KEYC_CTRL: if (status_prompt_paste(c)) goto changed; break; - case '\024': /* C-t */ + case 't'|KEYC_CTRL: idx = c->prompt_index; if (idx < size) idx++; @@ -1443,12 +1443,12 @@ process_key: free(s); break; case '\033': /* Escape */ - case '\003': /* C-c */ - case '\007': /* C-g */ + case 'c'|KEYC_CTRL: + case 'g'|KEYC_CTRL: if (c->prompt_inputcb(c, c->prompt_data, NULL, 1) == 0) status_prompt_clear(c); break; - case '\022': /* C-r */ + case 'r'|KEYC_CTRL: if (~c->prompt_flags & PROMPT_INCREMENTAL) break; if (c->prompt_buffer[0].size == 0) { @@ -1459,7 +1459,7 @@ process_key: } else prefix = '-'; goto changed; - case '\023': /* C-s */ + case 's'|KEYC_CTRL: if (~c->prompt_flags & PROMPT_INCREMENTAL) break; if (c->prompt_buffer[0].size == 0) { diff --git a/usr.bin/tmux/tmux.1 b/usr.bin/tmux/tmux.1 index 11ac32c62db..983383c4f74 100644 --- a/usr.bin/tmux/tmux.1 +++ b/usr.bin/tmux/tmux.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: tmux.1,v 1.947 2024/08/04 09:01:18 nicm Exp $ +.\" $OpenBSD: tmux.1,v 1.948 2024/08/21 04:17:09 nicm Exp $ .\" .\" Copyright (c) 2007 Nicholas Marriott .\" @@ -14,7 +14,7 @@ .\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING .\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: August 4 2024 $ +.Dd $Mdocdate: August 21 2024 $ .Dt TMUX 1 .Os .Sh NAME @@ -3733,6 +3733,10 @@ Note that aliases are expanded when a command is parsed rather than when it is executed, so binding an alias with .Ic bind-key will bind the expanded form. +.It Ic copy-command Ar shell-command +Give the command to pipe to if the +.Ic copy-pipe +copy mode command is used without arguments. .It Ic default-terminal Ar terminal Set the default terminal for new windows created in this session - the default value of the @@ -3746,10 +3750,6 @@ be set to .Ql screen , .Ql tmux or a derivative of them. -.It Ic copy-command Ar shell-command -Give the command to pipe to if the -.Ic copy-pipe -copy mode command is used without arguments. .It Ic escape-time Ar time Set the time in milliseconds for which .Nm @@ -3771,22 +3771,53 @@ If enabled, the server will exit when there are no attached clients. .It Xo Ic extended-keys .Op Ic on | off | always .Xc -When -.Ic on -or -.Ic always , -the escape sequence to enable extended keys is sent to the terminal, if -.Nm -knows that it is supported. -.Nm -always recognises extended keys itself. -If this option is +Controls how modified keys (keys pressed together with Control, Meta, or Shift) +are reported. +This is the equivalent of the +.Ic modifyOtherKeys +.Xr xterm 1 +resource. +.Pp +When set to .Ic on , -.Nm -will only forward extended keys to applications when they request them; if +the program inside the pane can request one of two modes: mode 1 which changes +the sequence for only keys which lack an existing well-known representation; or +mode 2 which changes the sequence for all keys. +When set to .Ic always , +mode 1 output is forced and the program cannot change it. +When set to +.Ic off , +this feature is disabled and only standard keys are reported. +.Pp .Nm -will always forward the keys. +will always request extended keys itself if the terminal supports them. +See also the +.Ic extkeys +feature for the +.Ic terminal-features +option, the +.Ic extended-keys-format +option and the +.Ic pane_key_mode +variable. +.It Xo Ic extended-keys-format +.Op Ic csi-u | xterm +.Xc +Selects one of the two possible formats for reporting modified keys to +applications. +This is the equivalent of the +.Ic formatOtherKeys +.Xr xterm 1 +resource. +For example, C-S-a will be reported as +.Ql ^[[27;6;65~ +when set to +.Ic xterm , +and as +.Ql ^[[65;6u +when set to +.Ic csi-u . .It Xo Ic focus-events .Op Ic on | off .Xc @@ -5512,6 +5543,7 @@ The following variables are available, where appropriate: .It Li "pane_in_mode" Ta "" Ta "1 if pane is in a mode" .It Li "pane_index" Ta "#P" Ta "Index of pane" .It Li "pane_input_off" Ta "" Ta "1 if input to pane is disabled" +.It Li "pane_key_mode" Ta "" Ta "Extended key reporting mode in this pane" .It Li "pane_last" Ta "" Ta "1 if last pane" .It Li "pane_left" Ta "" Ta "Left of pane" .It Li "pane_marked" Ta "" Ta "1 if this is the marked pane" diff --git a/usr.bin/tmux/tmux.h b/usr.bin/tmux/tmux.h index a1ffc50b32f..7c6f7c7c84b 100644 --- a/usr.bin/tmux/tmux.h +++ b/usr.bin/tmux/tmux.h @@ -1,4 +1,4 @@ -/* $OpenBSD: tmux.h,v 1.1220 2024/08/04 08:53:43 nicm Exp $ */ +/* $OpenBSD: tmux.h,v 1.1221 2024/08/21 04:17:09 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -138,8 +138,7 @@ struct winlink; #define KEYC_IMPLIED_META 0x08000000000000ULL #define KEYC_BUILD_MODIFIERS 0x10000000000000ULL #define KEYC_VI 0x20000000000000ULL -#define KEYC_EXTENDED 0x40000000000000ULL -#define KEYC_SENT 0x80000000000000ULL +#define KEYC_SENT 0x40000000000000ULL /* Masks for key bits. */ #define KEYC_MASK_MODIFIERS 0x00f00000000000ULL @@ -187,6 +186,42 @@ struct winlink; */ typedef unsigned long long key_code; +/* C0 control characters */ +enum { + C0_NUL, + C0_SOH, + C0_STX, + C0_ETX, + C0_EOT, + C0_ENQ, + C0_ASC, + C0_BEL, + C0_BS, + C0_HT, + C0_LF, + C0_VT, + C0_FF, + C0_CR, + C0_SO, + C0_SI, + C0_DLE, + C0_DC1, + C0_DC2, + C0_DC3, + C0_DC4, + C0_NAK, + C0_SYN, + C0_ETB, + C0_CAN, + C0_EM, + C0_SUB, + C0_ESC, + C0_FS, + C0_GS, + C0_RS, + C0_US +}; + /* Special key codes. */ enum { /* Focus events. */ @@ -582,14 +617,16 @@ enum tty_code_code { #define MODE_MOUSE_ALL 0x1000 #define MODE_ORIGIN 0x2000 #define MODE_CRLF 0x4000 -#define MODE_KEXTENDED 0x8000 +#define MODE_KEYS_EXTENDED 0x8000 #define MODE_CURSOR_VERY_VISIBLE 0x10000 #define MODE_CURSOR_BLINKING_SET 0x20000 +#define MODE_KEYS_EXTENDED_2 0x40000 #define ALL_MODES 0xffffff #define ALL_MOUSE_MODES (MODE_MOUSE_STANDARD|MODE_MOUSE_BUTTON|MODE_MOUSE_ALL) #define MOTION_MOUSE_MODES (MODE_MOUSE_BUTTON|MODE_MOUSE_ALL) #define CURSOR_MODES (MODE_CURSOR|MODE_CURSOR_BLINKING|MODE_CURSOR_VERY_VISIBLE) +#define EXTENDED_KEY_MODES (MODE_KEYS_EXTENDED|MODE_KEYS_EXTENDED_2) /* Mouse protocol constants. */ #define MOUSE_PARAM_MAX 0xff diff --git a/usr.bin/tmux/tty-features.c b/usr.bin/tmux/tty-features.c index 6e75ebcf2c9..3d5529fbfdd 100644 --- a/usr.bin/tmux/tty-features.c +++ b/usr.bin/tmux/tty-features.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tty-features.c,v 1.30 2023/11/14 15:38:33 nicm Exp $ */ +/* $OpenBSD: tty-features.c,v 1.31 2024/08/21 04:17:09 nicm Exp $ */ /* * Copyright (c) 2020 Nicholas Marriott @@ -227,7 +227,7 @@ static const struct tty_feature tty_feature_sync = { /* Terminal supports extended keys. */ static const char *const tty_feature_extkeys_capabilities[] = { - "Eneks=\\E[>4;1m", + "Eneks=\\E[>4;2m", "Dseks=\\E[>4m", NULL }; diff --git a/usr.bin/tmux/tty-keys.c b/usr.bin/tmux/tty-keys.c index 67b7f5844e1..f859747e3c0 100644 --- a/usr.bin/tmux/tty-keys.c +++ b/usr.bin/tmux/tty-keys.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tty-keys.c,v 1.176 2024/08/19 08:29:16 nicm Exp $ */ +/* $OpenBSD: tty-keys.c,v 1.177 2024/08/21 04:17:09 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -664,7 +664,7 @@ tty_keys_next(struct tty *tty) size_t len, size; cc_t bspace; int delay, expired = 0, n; - key_code key; + key_code key, onlykey; struct mouse_event m = { 0 }; struct key_event *event; @@ -801,6 +801,26 @@ first_key: key = (u_char)buf[0]; size = 1; } + + /* C-Space is special. */ + if ((key & KEYC_MASK_KEY) == C0_NUL) + key = ' ' | KEYC_CTRL | (key & KEYC_META); + + /* + * Fix up all C0 control codes that don't have a dedicated key into + * corresponding Ctrl keys. Convert characters in the A-Z range into + * lowercase, so ^A becomes a|CTRL. + */ + onlykey = key & KEYC_MASK_KEY; + if (onlykey < 0x20 && onlykey != C0_BS && + onlykey != C0_HT && onlykey != C0_CR && + onlykey != C0_ESC) { + onlykey |= 0x40; + if (onlykey >= 'A' && onlykey <= 'Z') + onlykey |= 0x20; + key = onlykey | KEYC_CTRL | (key & KEYC_META); + } + goto complete_key; partial_key: @@ -910,7 +930,6 @@ tty_keys_extended_key(struct tty *tty, const char *buf, size_t len, char tmp[64]; cc_t bspace; key_code nkey; - key_code onlykey; struct utf8_data ud; utf8_char uc; @@ -974,7 +993,13 @@ tty_keys_extended_key(struct tty *tty, const char *buf, size_t len, /* Update the modifiers. */ if (modifiers > 0) { modifiers--; - if (modifiers & 1) + /* + * The Shift modifier may not be reported in some input modes, + * which is unfortunate, as in general case determining if a + * character is shifted or not requires knowing the input + * keyboard layout. So we only fix up the trivial case. + */ + if (modifiers & 1 || (nkey >= 'A' && nkey <= 'Z')) nkey |= KEYC_SHIFT; if (modifiers & 2) nkey |= (KEYC_META|KEYC_IMPLIED_META); /* Alt */ @@ -984,34 +1009,15 @@ tty_keys_extended_key(struct tty *tty, const char *buf, size_t len, nkey |= (KEYC_META|KEYC_IMPLIED_META); /* Meta */ } - /* - * Don't allow both KEYC_CTRL and as an implied modifier. Also convert - * C-X into C-x and so on. - */ - if (nkey & KEYC_CTRL) { - onlykey = (nkey & KEYC_MASK_KEY); - if (onlykey < 32 && - onlykey != 9 && - onlykey != 13 && - onlykey != 27) - /* nothing */; - else if (onlykey >= 97 && onlykey <= 122) - onlykey -= 96; - else if (onlykey >= 64 && onlykey <= 95) - onlykey -= 64; - else if (onlykey == 32) - onlykey = 0; - else if (onlykey == 63) - onlykey = 127; - else - onlykey |= KEYC_CTRL; - nkey = onlykey|((nkey & KEYC_MASK_MODIFIERS) & ~KEYC_CTRL); - } + /* Convert S-Tab into Backtab. */ + if ((nkey & KEYC_MASK_KEY) == '\011' && (nkey & KEYC_SHIFT)) + nkey = KEYC_BTAB | (nkey & ~KEYC_MASK_KEY & ~KEYC_SHIFT); if (log_get_level() != 0) { log_debug("%s: extended key %.*s is %llx (%s)", c->name, (int)*size, buf, nkey, key_string_lookup_key(nkey, 1)); } + *key = nkey; return (0); }