From c7e61a01e53905b09ee423c93f29e13267d86f99 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 21 Aug 2024 04:17:09 +0000 Subject: [PATCH 1/5] 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. --- format.c | 20 +++++++++++++ key-string.c | 77 ++++++++++++++++++++++++++------------------------ menu.c | 8 +++--- mode-tree.c | 14 ++++----- popup.c | 2 +- screen-write.c | 3 +- screen.c | 9 ++++-- status.c | 72 +++++++++++++++++++++++----------------------- tmux.1 | 66 ++++++++++++++++++++++++++++++++----------- tmux.h | 43 ++++++++++++++++++++++++++-- tty-features.c | 2 +- tty-keys.c | 58 ++++++++++++++++++++----------------- 12 files changed, 238 insertions(+), 136 deletions(-) diff --git a/format.c b/format.c index 7979e970..91d78f89 100644 --- a/format.c +++ b/format.c @@ -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/key-string.c b/key-string.c index 699d460f..04f0e5bf 100644 --- a/key-string.c +++ b/key-string.c @@ -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/menu.c b/menu.c index 8d7b40a2..c1f3b211 100644 --- a/menu.c +++ b/menu.c @@ -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/mode-tree.c b/mode-tree.c index 50e65499..efa966dc 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -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/popup.c b/popup.c index 804dd6ef..6d126cf4 100644 --- a/popup.c +++ b/popup.c @@ -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/screen-write.c b/screen-write.c index 554232eb..fe58ee48 100644 --- a/screen-write.c +++ b/screen-write.c @@ -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/screen.c b/screen.c index de072d1a..d51af6ac 100644 --- a/screen.c +++ b/screen.c @@ -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/status.c b/status.c index 4afac626..d0b4e3b5 100644 --- a/status.c +++ b/status.c @@ -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/tmux.1 b/tmux.1 index 3d1cb1ed..9ab65993 100644 --- a/tmux.1 +++ b/tmux.1 @@ -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/tmux.h b/tmux.h index beced79d..a60f89e9 100644 --- a/tmux.h +++ b/tmux.h @@ -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/tty-features.c b/tty-features.c index 42f83f41..110bc1f8 100644 --- a/tty-features.c +++ b/tty-features.c @@ -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/tty-keys.c b/tty-keys.c index a04d951d..9555dc31 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -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); } From 4fa90c9acf76c429ab3f2e0a4763c8dee71afb4e Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 21 Aug 2024 04:37:42 +0000 Subject: [PATCH 2/5] Set the default for extended-keys back to off because it appears emacs turns the keys on but does not correctly handle them except in xterm (!). Also fix so that off takes effect as expected. --- input.c | 26 +++++++++++++++++++------- options-table.c | 13 ++++++++++++- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/input.c b/input.c index 70caff47..3bcd7c67 100644 --- a/input.c +++ b/input.c @@ -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); - if (options_get_number(global_options, "extended-keys") == 2) + /* + * 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") != 1) 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/options-table.c b/options-table.c index 929ca5e5..738001d5 100644 --- a/options-table.c +++ b/options-table.c @@ -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 }; @@ -315,6 +318,14 @@ const struct options_table_entry options_table[] = { "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." }, From ceda0a68ae00489af2a303e2c7256c3c5033c712 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 21 Aug 2024 04:55:57 +0000 Subject: [PATCH 3/5] C-Space and Meta keys should not be translated in mode 1 extended keys. --- input-keys.c | 373 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 237 insertions(+), 136 deletions(-) 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, From 06292baadcc8747b9aee0bf1ea3960df84119b01 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 21 Aug 2024 05:03:13 +0000 Subject: [PATCH 4/5] Add mirrored versions of the main-horizontal and main-vertical layouts where the main pane is bottom or right instead of top or left, from Sherwyn Sen. --- key-bindings.c | 2 + layout-set.c | 200 +++++++++++++++++++++++++++++++++++++++++++++++++ status.c | 5 +- tmux.1 | 38 +++++++--- 4 files changed, 231 insertions(+), 14 deletions(-) diff --git a/key-bindings.c b/key-bindings.c index eaf05f02..86da8018 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -413,6 +413,8 @@ key_bindings_init(void) "bind -N 'Set the main-horizontal layout' M-3 { select-layout main-horizontal }", "bind -N 'Set the main-vertical layout' M-4 { select-layout main-vertical }", "bind -N 'Select the tiled layout' M-5 { select-layout tiled }", + "bind -N 'Set the main-horizontal-mirrored layout' M-6 { select-layout main-horizontal-mirrored }", + "bind -N 'Set the main-vertical-mirrored layout' M-7 { select-layout main-vertical-mirrored }", "bind -N 'Select the next window with an alert' M-n { next-window -a }", "bind -N 'Rotate through the panes in reverse' M-o { rotate-window -D }", "bind -N 'Select the previous window with an alert' M-p { previous-window -a }", diff --git a/layout-set.c b/layout-set.c index c702817d..cc2e74bf 100644 --- a/layout-set.c +++ b/layout-set.c @@ -31,7 +31,9 @@ static void layout_set_even_h(struct window *); static void layout_set_even_v(struct window *); static void layout_set_main_h(struct window *); +static void layout_set_main_h_mirrored(struct window *); static void layout_set_main_v(struct window *); +static void layout_set_main_v_mirrored(struct window *); static void layout_set_tiled(struct window *); static const struct { @@ -41,7 +43,9 @@ static const struct { { "even-horizontal", layout_set_even_h }, { "even-vertical", layout_set_even_v }, { "main-horizontal", layout_set_main_h }, + { "main-horizontal-mirrored", layout_set_main_h_mirrored }, { "main-vertical", layout_set_main_v }, + { "main-vertical-mirrored", layout_set_main_v_mirrored }, { "tiled", layout_set_tiled }, }; @@ -279,6 +283,104 @@ layout_set_main_h(struct window *w) server_redraw_window(w); } +static void +layout_set_main_h_mirrored(struct window *w) +{ + struct window_pane *wp; + struct layout_cell *lc, *lcmain, *lcother, *lcchild; + u_int n, mainh, otherh, sx, sy; + char *cause; + const char *s; + + layout_print_cell(w->layout_root, __func__, 1); + + /* Get number of panes. */ + n = window_count_panes(w); + if (n <= 1) + return; + n--; /* take off main pane */ + + /* Find available height - take off one line for the border. */ + sy = w->sy - 1; + + /* Get the main pane height. */ + s = options_get_string(w->options, "main-pane-height"); + mainh = args_string_percentage(s, 0, sy, sy, &cause); + if (cause != NULL) { + mainh = 24; + free(cause); + } + + /* Work out the other pane height. */ + if (mainh + PANE_MINIMUM >= sy) { + if (sy <= PANE_MINIMUM + PANE_MINIMUM) + mainh = PANE_MINIMUM; + else + mainh = sy - PANE_MINIMUM; + otherh = PANE_MINIMUM; + } else { + s = options_get_string(w->options, "other-pane-height"); + otherh = args_string_percentage(s, 0, sy, sy, &cause); + if (cause != NULL || otherh == 0) { + otherh = sy - mainh; + free(cause); + } else if (otherh > sy || sy - otherh < mainh) + otherh = sy - mainh; + else + mainh = sy - otherh; + } + + /* Work out what width is needed. */ + sx = (n * (PANE_MINIMUM + 1)) - 1; + if (sx < w->sx) + sx = w->sx; + + /* Free old tree and create a new root. */ + layout_free(w); + lc = w->layout_root = layout_create_cell(NULL); + layout_set_size(lc, sx, mainh + otherh + 1, 0, 0); + layout_make_node(lc, LAYOUT_TOPBOTTOM); + + /* Create the other pane. */ + lcother = layout_create_cell(lc); + layout_set_size(lcother, sx, otherh, 0, 0); + if (n == 1) { + wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry); + layout_make_leaf(lcother, wp); + TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); + } else { + layout_make_node(lcother, LAYOUT_LEFTRIGHT); + TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); + + /* Add the remaining panes as children. */ + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp == TAILQ_FIRST(&w->panes)) + continue; + lcchild = layout_create_cell(lcother); + layout_set_size(lcchild, PANE_MINIMUM, otherh, 0, 0); + layout_make_leaf(lcchild, wp); + TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry); + } + layout_spread_cell(w, lcother); + } + + /* Create the main pane. */ + lcmain = layout_create_cell(lc); + layout_set_size(lcmain, sx, mainh, 0, 0); + layout_make_leaf(lcmain, TAILQ_FIRST(&w->panes)); + TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry); + + /* Fix cell offsets. */ + layout_fix_offsets(w); + layout_fix_panes(w, NULL); + + layout_print_cell(w->layout_root, __func__, 1); + + window_resize(w, lc->sx, lc->sy, -1, -1); + notify_window("window-layout-changed", w); + server_redraw_window(w); +} + static void layout_set_main_v(struct window *w) { @@ -377,6 +479,104 @@ layout_set_main_v(struct window *w) server_redraw_window(w); } +static void +layout_set_main_v_mirrored(struct window *w) +{ + struct window_pane *wp; + struct layout_cell *lc, *lcmain, *lcother, *lcchild; + u_int n, mainw, otherw, sx, sy; + char *cause; + const char *s; + + layout_print_cell(w->layout_root, __func__, 1); + + /* Get number of panes. */ + n = window_count_panes(w); + if (n <= 1) + return; + n--; /* take off main pane */ + + /* Find available width - take off one line for the border. */ + sx = w->sx - 1; + + /* Get the main pane width. */ + s = options_get_string(w->options, "main-pane-width"); + mainw = args_string_percentage(s, 0, sx, sx, &cause); + if (cause != NULL) { + mainw = 80; + free(cause); + } + + /* Work out the other pane width. */ + if (mainw + PANE_MINIMUM >= sx) { + if (sx <= PANE_MINIMUM + PANE_MINIMUM) + mainw = PANE_MINIMUM; + else + mainw = sx - PANE_MINIMUM; + otherw = PANE_MINIMUM; + } else { + s = options_get_string(w->options, "other-pane-width"); + otherw = args_string_percentage(s, 0, sx, sx, &cause); + if (cause != NULL || otherw == 0) { + otherw = sx - mainw; + free(cause); + } else if (otherw > sx || sx - otherw < mainw) + otherw = sx - mainw; + else + mainw = sx - otherw; + } + + /* Work out what height is needed. */ + sy = (n * (PANE_MINIMUM + 1)) - 1; + if (sy < w->sy) + sy = w->sy; + + /* Free old tree and create a new root. */ + layout_free(w); + lc = w->layout_root = layout_create_cell(NULL); + layout_set_size(lc, mainw + otherw + 1, sy, 0, 0); + layout_make_node(lc, LAYOUT_LEFTRIGHT); + + /* Create the other pane. */ + lcother = layout_create_cell(lc); + layout_set_size(lcother, otherw, sy, 0, 0); + if (n == 1) { + wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry); + layout_make_leaf(lcother, wp); + TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); + } else { + layout_make_node(lcother, LAYOUT_TOPBOTTOM); + TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); + + /* Add the remaining panes as children. */ + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp == TAILQ_FIRST(&w->panes)) + continue; + lcchild = layout_create_cell(lcother); + layout_set_size(lcchild, otherw, PANE_MINIMUM, 0, 0); + layout_make_leaf(lcchild, wp); + TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry); + } + layout_spread_cell(w, lcother); + } + + /* Create the main pane. */ + lcmain = layout_create_cell(lc); + layout_set_size(lcmain, mainw, sy, 0, 0); + layout_make_leaf(lcmain, TAILQ_FIRST(&w->panes)); + TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry); + + /* Fix cell offsets. */ + layout_fix_offsets(w); + layout_fix_panes(w, NULL); + + layout_print_cell(w->layout_root, __func__, 1); + + window_resize(w, lc->sx, lc->sy, -1, -1); + notify_window("window-layout-changed", w); + server_redraw_window(w); +} + void layout_set_tiled(struct window *w) { diff --git a/status.c b/status.c index d0b4e3b5..9096dbec 100644 --- a/status.c +++ b/status.c @@ -1626,8 +1626,9 @@ status_prompt_complete_list(u_int *size, const char *s, int at_start) struct options_entry *o; struct options_array_item *a; const char *layouts[] = { - "even-horizontal", "even-vertical", "main-horizontal", - "main-vertical", "tiled", NULL + "even-horizontal", "even-vertical", + "main-horizontal", "main-horizontal-mirrored", + "main-vertical", "main-vertical-mirrored", "tiled", NULL }; *size = 0; diff --git a/tmux.1 b/tmux.1 index 9ab65993..920f8e9f 100644 --- a/tmux.1 +++ b/tmux.1 @@ -370,8 +370,10 @@ Enter copy mode and scroll one page up. Change to the pane above, below, to the left, or to the right of the current pane. .It M-1 to M-5 -Arrange panes in one of the five preset layouts: even-horizontal, -even-vertical, main-horizontal, main-vertical, or tiled. +Arrange panes in one of the seven preset layouts: +even-horizontal, even-vertical, +main-horizontal, main-horizontal-mirrored, +main-vertical, main-vertical, or tiled. .It Space Arrange the current window in the next preset layout. .It M-n @@ -2162,14 +2164,20 @@ are spread from left to right in the leftover space at the bottom. Use the .Em main-pane-height window option to specify the height of the top pane. -.It Ic main-vertical -Similar to +.It Ic main-horizontal-mirrored +The same as .Ic main-horizontal -but the large pane is placed on the left and the others spread from top to -bottom along the right. -See the +but mirrored so the main pane is at the bottom of the window. +.It Ic main-vertical +A large (main) pane is shown on the left of the window and the remaining panes +are spread from top to bottom in the leftover space on the right. +Use the .Em main-pane-width -window option. +window option to specify the width of the left pane. +.It Ic main-vertical-mirrored +The same as +.Ic main-vertical +but mirrored so the main pane is on the right of the window. .It Ic tiled Panes are spread out as evenly as possible over the window in both rows and columns. @@ -4483,9 +4491,11 @@ Set the character used to fill areas of the terminal unused by a window. .It Ic main-pane-height Ar height .It Ic main-pane-width Ar width Set the width or height of the main (left or top) pane in the -.Ic main-horizontal +.Ic main-horizontal, +.Ic main-horizontal-mirrored, +.Ic main-vertical, or -.Ic main-vertical +.Ic main-vertical-mirrored layouts. If suffixed by .Ql % , @@ -4559,7 +4569,9 @@ An interval of zero disables the monitoring. .It Ic other-pane-height Ar height Set the height of the other panes (not the main pane) in the .Ic main-horizontal -layout. +and +.Ic main-horizontal-mirrored +layouts. If this option is set to 0 (the default), it will have no effect. If both the .Ic main-pane-height @@ -4576,7 +4588,9 @@ Like .Ic other-pane-height , but set the width of other panes in the .Ic main-vertical -layout. +and +.Ic main-vertical-mirrored +layouts. .Pp .It Ic pane-active-border-style Ar style Set the pane border style for the currently active pane. From a6645c4de4f243bf457225b0373258dd1e6c0178 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 21 Aug 2024 05:06:45 +0000 Subject: [PATCH 5/5] Mention that load- and save-buffer can use stdin, from Ramon Fischer. --- tmux.1 | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tmux.1 b/tmux.1 index 920f8e9f..11ea5504 100644 --- a/tmux.1 +++ b/tmux.1 @@ -6616,6 +6616,11 @@ is given, the buffer is also sent to the clipboard for using the .Xr xterm 1 escape sequence, if possible. +If +.Ar path +is +.Ql - , +the contents are read from stdin. .Tg pasteb .It Xo Ic paste-buffer .Op Fl dpr @@ -6653,6 +6658,11 @@ Save the contents of the specified paste buffer to The .Fl a option appends to rather than overwriting the file. +If +.Ar path +is +.Ql - , +the contents are read from stdin. .It Xo Ic set-buffer .Op Fl aw .Op Fl b Ar buffer-name