From 03f8690f9cbbd1814e91e80bcfb365542531240e Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 18 Feb 2026 09:10:31 +0000 Subject: [PATCH 01/35] Pass which clipboard is set through to the terminal, from Axel Lindskog in GitHub issue 4858. --- input.c | 43 +++++++++++++++++++++++-------------------- screen-write.c | 4 ++-- tmux.h | 3 ++- tty-keys.c | 12 ++++++++++-- tty.c | 4 ++-- 5 files changed, 39 insertions(+), 27 deletions(-) diff --git a/input.c b/input.c index ee92a7cf..53bcb8bd 100644 --- a/input.c +++ b/input.c @@ -1139,11 +1139,9 @@ input_get(struct input_ctx *ictx, u_int validx, int minval, int defval) static void input_send_reply(struct input_ctx *ictx, const char *reply) { - struct bufferevent *bev = ictx->event; - - if (bev != NULL) { + if (ictx->event != NULL) { log_debug("%s: %s", __func__, reply); - bufferevent_write(bev, reply, strlen(reply)); + bufferevent_write(ictx->event, reply, strlen(reply)); } } @@ -3052,8 +3050,9 @@ input_osc_133(struct input_ctx *ictx, const char *p) /* Handle OSC 52 reply. */ static void -input_osc_52_reply(struct input_ctx *ictx) +input_osc_52_reply(struct input_ctx *ictx, char clip) { + struct bufferevent *ev = ictx->event; struct paste_buffer *pb; int state; const char *buf; @@ -3067,9 +3066,9 @@ input_osc_52_reply(struct input_ctx *ictx) return; buf = paste_buffer_data(pb, &len); if (ictx->input_end == INPUT_END_BEL) - input_reply_clipboard(ictx->event, buf, len, "\007"); + input_reply_clipboard(ev, buf, len, "\007", clip); else - input_reply_clipboard(ictx->event, buf, len, "\033\\"); + input_reply_clipboard(ev, buf, len, "\033\\", clip); return; } input_add_request(ictx, INPUT_REQUEST_CLIPBOARD, ictx->input_end); @@ -3082,7 +3081,7 @@ input_osc_52_reply(struct input_ctx *ictx) */ static int input_osc_52_parse(struct input_ctx *ictx, const char *p, u_char **out, - int *outlen, char *flags) + int *outlen, char *clip) { char *end; size_t len; @@ -3100,13 +3099,13 @@ input_osc_52_parse(struct input_ctx *ictx, const char *p, u_char **out, log_debug("%s: %s", __func__, end); for (i = 0; p + i != end; i++) { - if (strchr(allow, p[i]) != NULL && strchr(flags, p[i]) == NULL) - flags[j++] = p[i]; + if (strchr(allow, p[i]) != NULL && strchr(clip, p[i]) == NULL) + clip[j++] = p[i]; } - log_debug("%s: %.*s %s", __func__, (int)(end - p - 1), p, flags); + log_debug("%s: %.*s %s", __func__, (int)(end - p - 1), p, clip); if (strcmp(end, "?") == 0) { - input_osc_52_reply(ictx); + input_osc_52_reply(ictx, *clip); return (0); } @@ -3132,9 +3131,9 @@ input_osc_52(struct input_ctx *ictx, const char *p) struct screen_write_ctx ctx; u_char *out; int outlen; - char flags[sizeof "cpqs01234567"] = ""; + char clip[sizeof "cpqs01234567"] = ""; - if (!input_osc_52_parse(ictx, p, &out, &outlen, flags)) + if (!input_osc_52_parse(ictx, p, &out, &outlen, clip)) return; if (wp == NULL) { @@ -3143,12 +3142,12 @@ input_osc_52(struct input_ctx *ictx, const char *p) free(out); return; } - tty_set_selection(&ictx->c->tty, flags, out, outlen); + tty_set_selection(&ictx->c->tty, clip, out, outlen); paste_add(NULL, out, outlen); } else { /* Normal window. */ screen_write_start_pane(&ctx, wp, NULL); - screen_write_setselection(&ctx, flags, out, outlen); + screen_write_setselection(&ctx, clip, out, outlen); screen_write_stop(&ctx); notify_pane("pane-set-clipboard", wp); paste_add(NULL, out, outlen); @@ -3196,7 +3195,7 @@ input_osc_104(struct input_ctx *ictx, const char *p) /* Send a clipboard reply. */ void input_reply_clipboard(struct bufferevent *bev, const char *buf, size_t len, - const char *end) + const char *end, char clip) { char *out = NULL; int outlen = 0; @@ -3212,7 +3211,10 @@ input_reply_clipboard(struct bufferevent *bev, const char *buf, size_t len, } } - bufferevent_write(bev, "\033]52;;", 6); + bufferevent_write(bev, "\033]52;", 5); + if (clip != 0) + bufferevent_write(bev, &clip, 1); + bufferevent_write(bev, ";", 1); if (outlen != 0) bufferevent_write(bev, out, outlen); bufferevent_write(bev, end, strlen(end)); @@ -3354,6 +3356,7 @@ static void input_request_clipboard_reply(struct input_request *ir, void *data) { struct input_ctx *ictx = ir->ictx; + struct bufferevent *ev = ictx->event; struct input_request_clipboard_data *cd = data; int state; char *copy; @@ -3368,9 +3371,9 @@ input_request_clipboard_reply(struct input_request *ir, void *data) } if (ir->idx == INPUT_END_BEL) - input_reply_clipboard(ictx->event, cd->buf, cd->len, "\007"); + input_reply_clipboard(ev, cd->buf, cd->len, "\007", cd->clip); else - input_reply_clipboard(ictx->event, cd->buf, cd->len, "\033\\"); + input_reply_clipboard(ev, cd->buf, cd->len, "\033\\", cd->clip); } /* Handle a reply to a request. */ diff --git a/screen-write.c b/screen-write.c index 0eee94bb..61b81ad3 100644 --- a/screen-write.c +++ b/screen-write.c @@ -2275,14 +2275,14 @@ screen_write_overwrite(struct screen_write_ctx *ctx, struct grid_cell *gc, /* Set external clipboard. */ void -screen_write_setselection(struct screen_write_ctx *ctx, const char *flags, +screen_write_setselection(struct screen_write_ctx *ctx, const char *clip, u_char *str, u_int len) { struct tty_ctx ttyctx; screen_write_initctx(ctx, &ttyctx, 0); ttyctx.ptr = str; - ttyctx.ptr2 = (void *)flags; + ttyctx.ptr2 = (void *)clip; ttyctx.num = len; tty_write(tty_cmd_setselection, &ttyctx); diff --git a/tmux.h b/tmux.h index 4c40cf83..2c62bd52 100644 --- a/tmux.h +++ b/tmux.h @@ -1114,6 +1114,7 @@ struct input_request_palette_data { struct input_request_clipboard_data { char *buf; size_t len; + char clip; }; /* Request sent to client on behalf of pane. */ @@ -3021,7 +3022,7 @@ void input_parse_buffer(struct window_pane *, u_char *, size_t); void input_parse_screen(struct input_ctx *, struct screen *, screen_write_init_ctx_cb, void *, u_char *, size_t); void input_reply_clipboard(struct bufferevent *, const char *, size_t, - const char *); + const char *, char); void input_set_buffer_size(size_t); void input_request_reply(struct client *, enum input_request_type, void *); void input_cancel_requests(struct client *); diff --git a/tty-keys.c b/tty-keys.c index 361de3df..7f9e8352 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -1310,7 +1310,7 @@ tty_keys_clipboard(struct tty *tty, const char *buf, size_t len, size_t *size) { struct client *c = tty->client; size_t end, terminator = 0, needed; - char *copy, *out; + char *copy, *out, clip = 0; int outlen; struct input_request_clipboard_data cd; @@ -1360,7 +1360,14 @@ tty_keys_clipboard(struct tty *tty, const char *buf, size_t len, size_t *size) /* Adjust end so that it points to the start of the terminator. */ end -= terminator - 1; - /* Get the second argument. */ + /* + * Save which clipboard was used from the second argument. If more than + * one is specified (should not happen), ignore the argument. + */ + if (end >= 2 && buf[0] != ';' && buf[1] == ';') + clip = buf[0]; + + /* Skip the second argument. */ while (end != 0 && *buf != ';') { buf++; end--; @@ -1393,6 +1400,7 @@ tty_keys_clipboard(struct tty *tty, const char *buf, size_t len, size_t *size) /* Set reply if any. */ cd.buf = out; cd.len = outlen; + cd.clip = clip; input_request_reply(c, INPUT_REQUEST_CLIPBOARD, &cd); /* Create a buffer if requested. */ diff --git a/tty.c b/tty.c index 29369598..d05d2dc2 100644 --- a/tty.c +++ b/tty.c @@ -1993,7 +1993,7 @@ tty_cmd_setselection(struct tty *tty, const struct tty_ctx *ctx) } void -tty_set_selection(struct tty *tty, const char *flags, const char *buf, +tty_set_selection(struct tty *tty, const char *clip, const char *buf, size_t len) { char *encoded; @@ -2009,7 +2009,7 @@ tty_set_selection(struct tty *tty, const char *flags, const char *buf, b64_ntop(buf, len, encoded, size); tty->flags |= TTY_NOBLOCK; - tty_putcode_ss(tty, TTYC_MS, flags, encoded); + tty_putcode_ss(tty, TTYC_MS, clip, encoded); free(encoded); } From 0310404155701d9e03b7db166e8f17f180cc09d3 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 20 Feb 2026 08:41:23 +0000 Subject: [PATCH 02/35] Reuse the same extended slot when clearing non-RGB cells as well. From Michael K Darling in GitHub issue 4865. --- grid.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/grid.c b/grid.c index 28a15d77..df5951f6 100644 --- a/grid.c +++ b/grid.c @@ -209,13 +209,15 @@ grid_clear_cell(struct grid *gd, u_int px, u_int py, u_int bg) int had_extd = (gce->flags & GRID_FLAG_EXTENDED); memcpy(gce, &grid_cleared_entry, sizeof *gce); - if (bg != 8) { + if (had_extd && old_offset < gl->extdsize) { + gce->flags |= GRID_FLAG_EXTENDED; + gce->offset = old_offset; + gee = grid_extended_cell(gl, gce, &grid_cleared_cell); + if (bg != 8) + gee->bg = bg; + } else if (bg != 8) { if (bg & COLOUR_FLAG_RGB) { - if (had_extd && old_offset < gl->extdsize) { - gce->flags |= GRID_FLAG_EXTENDED; - gce->offset = old_offset; - } else - grid_get_extended_cell(gl, gce, gce->flags); + grid_get_extended_cell(gl, gce, gce->flags); gee = grid_extended_cell(gl, gce, &grid_cleared_cell); gee->bg = bg; } else { @@ -493,7 +495,7 @@ static void grid_expand_line(struct grid *gd, u_int py, u_int sx, u_int bg) { struct grid_line *gl; - u_int xx; + u_int xx, old_cellsize; gl = &gd->linedata[py]; if (sx <= gl->cellsize) @@ -506,8 +508,10 @@ grid_expand_line(struct grid *gd, u_int py, u_int sx, u_int bg) else if (gd->sx > sx) sx = gd->sx; - gl->celldata = xreallocarray(gl->celldata, sx, sizeof *gl->celldata); - for (xx = gl->cellsize; xx < sx; xx++) + old_cellsize = gl->cellsize; + gl->celldata = xrecallocarray(gl->celldata, old_cellsize, sx, + sizeof *gl->celldata); + for (xx = old_cellsize; xx < sx; xx++) grid_clear_cell(gd, xx, py, bg); gl->cellsize = sx; } From 8356578a54dd798b40c9165ce30999ae69b5ce34 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Fri, 20 Feb 2026 08:43:35 +0000 Subject: [PATCH 03/35] Update tmux bash completion file link in README, from Hyun Seungmin. --- .github/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/README.md b/.github/README.md index b3e9e747..38a296fc 100644 --- a/.github/README.md +++ b/.github/README.md @@ -79,7 +79,7 @@ A small example configuration is in `example_tmux.conf`. And a bash(1) completion file at: -https://github.com/scop/bash-completion/blob/main/completions/tmux +https://github.com/scop/bash-completion/blob/main/completions-core/tmux.bash For debugging, run tmux with `-v` or `-vv` to generate server and client log files in the current directory. From 23ad10c849bf6a89cfaf0c8f44a3297a3293eaff Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 23 Feb 2026 08:29:30 +0000 Subject: [PATCH 04/35] Set cell to default when off screen to avoid crash when logging it. --- tty-draw.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tty-draw.c b/tty-draw.c index eaf56d2b..fa9cbcd5 100644 --- a/tty-draw.c +++ b/tty-draw.c @@ -223,6 +223,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, */ empty = 0; next_state = TTY_DRAW_LINE_DONE; + gcp = &grid_default_cell; } else { /* Get the current cell. */ grid_view_get_cell(gd, px + i, py, &gc); From 3094ca1da5cdc5bc0e4de0452404b2dc210b51bc Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 23 Feb 2026 08:45:27 +0000 Subject: [PATCH 05/35] Fix memory leaks, reported by Huihui Huang in GitHub issue 4872. --- cmd-display-menu.c | 44 ++++++++++++++++++++++++-------------------- environ.c | 3 +++ 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/cmd-display-menu.c b/cmd-display-menu.c index 894a2f68..fc9a99b6 100644 --- a/cmd-display-menu.c +++ b/cmd-display-menu.c @@ -394,7 +394,7 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item) const char *style = args_get(args, 's'); const char *border_style = args_get(args, 'S'); char *cwd = NULL, *cause = NULL, **argv = NULL; - char *title; + char *title = NULL; int modify = popup_present(tc); int flags = -1, argc = 0; enum box_lines lines = BOX_LINES_DEFAULT; @@ -418,8 +418,7 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item) &cause); if (cause != NULL) { cmdq_error(item, "height %s", cause); - free(cause); - return (CMD_RETURN_ERROR); + goto fail; } } @@ -429,8 +428,7 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item) &cause); if (cause != NULL) { cmdq_error(item, "width %s", cause); - free(cause); - return (CMD_RETURN_ERROR); + goto fail; } } @@ -439,7 +437,7 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item) if (h > tty->sy) h = tty->sy; if (!cmd_display_menu_get_pos(tc, item, args, &px, &py, w, h)) - return (CMD_RETURN_NORMAL); + goto out; value = args_get(args, 'd'); if (value != NULL) @@ -479,8 +477,7 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item) &cause); if (cause != NULL) { cmdq_error(item, "popup-border-lines %s", cause); - free(cause); - return (CMD_RETURN_ERROR); + goto fail; } } @@ -508,22 +505,29 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item) if (modify) { popup_modify(tc, title, style, border_style, lines, flags); - free(title); - return (CMD_RETURN_NORMAL); + goto out; } if (popup_display(flags, lines, item, px, py, w, h, env, shellcmd, argc, - argv, cwd, title, tc, s, style, border_style, NULL, NULL) != 0) { - cmd_free_argv(argc, argv); - if (env != NULL) - environ_free(env); - free(cwd); - free(title); - return (CMD_RETURN_NORMAL); - } - if (env != NULL) - environ_free(env); + argv, cwd, title, tc, s, style, border_style, NULL, NULL) != 0) + goto out; + environ_free(env); free(cwd); free(title); cmd_free_argv(argc, argv); return (CMD_RETURN_WAIT); + +out: + cmd_free_argv(argc, argv); + environ_free(env); + free(cwd); + free(title); + return (CMD_RETURN_NORMAL); + +fail: + free(cause); + cmd_free_argv(argc, argv); + environ_free(env); + free(cwd); + free(title); + return (CMD_RETURN_ERROR); } diff --git a/environ.c b/environ.c index 0b4b3c31..1e167b8a 100644 --- a/environ.c +++ b/environ.c @@ -57,6 +57,9 @@ environ_free(struct environ *env) { struct environ_entry *envent, *envent1; + if (env == NULL) + return; + RB_FOREACH_SAFE(envent, environ, env, envent1) { RB_REMOVE(environ, env, envent); free(envent->name); From 50a3b4c77799a3d3f409212443819f0edb23b59d Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 23 Feb 2026 08:46:57 +0000 Subject: [PATCH 06/35] Free format on -a, reported by Huihui Huang. --- cmd-display-message.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd-display-message.c b/cmd-display-message.c index 9ba6d13e..3138d922 100644 --- a/cmd-display-message.c +++ b/cmd-display-message.c @@ -131,6 +131,7 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'a')) { format_each(ft, cmd_display_message_each, item); + format_free(ft); return (CMD_RETURN_NORMAL); } @@ -155,6 +156,5 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item) free(msg); format_free(ft); - return (CMD_RETURN_NORMAL); } From 8c7278b53ae71a9cbd2acfc9e98d573c61a00620 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 23 Feb 2026 08:50:00 +0000 Subject: [PATCH 07/35] Memory leaks in cmd_display_menu, from Huihui Huang. --- cmd-display-menu.c | 34 ++++++++++++++++++---------------- menu.c | 3 +++ 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/cmd-display-menu.c b/cmd-display-menu.c index fc9a99b6..9d02dca3 100644 --- a/cmd-display-menu.c +++ b/cmd-display-menu.c @@ -295,7 +295,7 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item) const char *border_style = args_get(args, 'S'); const char *selected_style = args_get(args, 'H'); enum box_lines lines = BOX_LINES_DEFAULT; - char *title, *cause; + char *title, *cause = NULL; int flags = 0, starting_choice = 0; u_int px, py, i, count = args_count(args); struct options *o = target->s->curw->window->options; @@ -313,8 +313,7 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item) &cause); if (cause != NULL) { cmdq_error(item, "starting choice %s", cause); - free(cause); - return (CMD_RETURN_ERROR); + goto fail; } } } @@ -335,8 +334,7 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item) if (count - i < 2) { cmdq_error(item, "not enough arguments"); - menu_free(menu); - return (CMD_RETURN_ERROR); + goto fail; } key = args_string(args, i++); @@ -348,17 +346,13 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item) } if (menu == NULL) { cmdq_error(item, "invalid menu arguments"); - return (CMD_RETURN_ERROR); - } - if (menu->count == 0) { - menu_free(menu); - return (CMD_RETURN_NORMAL); + goto fail; } + if (menu->count == 0) + goto out; if (!cmd_display_menu_get_pos(tc, item, args, &px, &py, menu->width + 4, - menu->count + 2)) { - menu_free(menu); - return (CMD_RETURN_NORMAL); - } + menu->count + 2)) + goto out; value = args_get(args, 'b'); if (value != NULL) { @@ -367,8 +361,7 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item) &cause); if (lines == -1) { cmdq_error(item, "menu-border-lines %s", cause); - free(cause); - return (CMD_RETURN_ERROR); + goto fail; } } @@ -380,6 +373,15 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item) style, selected_style, border_style, target, NULL, NULL) != 0) return (CMD_RETURN_NORMAL); return (CMD_RETURN_WAIT); + +out: + menu_free(menu); + return (CMD_RETURN_NORMAL); + +fail: + free(cause); + menu_free(menu); + return (CMD_RETURN_ERROR); } static enum cmd_retval diff --git a/menu.c b/menu.c index c458e68b..e4f79dec 100644 --- a/menu.c +++ b/menu.c @@ -161,6 +161,9 @@ menu_free(struct menu *menu) { u_int i; + if (menu == NULL) + return; + for (i = 0; i < menu->count; i++) { free((void *)menu->items[i].name); free((void *)menu->items[i].command); From a76e6eca6bc03858dd51f24cbfe6f672d3a980ef Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 23 Feb 2026 08:54:56 +0000 Subject: [PATCH 08/35] Another memory leak from Huihui Huang. --- cmd-server-access.c | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd-server-access.c b/cmd-server-access.c index b2b718b8..43cf4d5f 100644 --- a/cmd-server-access.c +++ b/cmd-server-access.c @@ -90,6 +90,7 @@ cmd_server_access_exec(struct cmd *self, struct cmdq_item *item) pw = getpwnam(name); if (pw == NULL) { cmdq_error(item, "unknown user: %s", name); + free(name); return (CMD_RETURN_ERROR); } free(name); From 0dc1b5adfbf60025e940ced3983d097150ce0d83 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 23 Feb 2026 08:58:40 +0000 Subject: [PATCH 09/35] Do not leak list on failure, reported by Huihui Huang. --- status.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/status.c b/status.c index edbb04ad..87d5e017 100644 --- a/status.c +++ b/status.c @@ -1890,7 +1890,7 @@ status_prompt_complete_window_menu(struct client *c, struct session *s, struct winlink *wl; char **list = NULL, *tmp; u_int lines = status_line_size(c), height; - u_int py, size = 0; + u_int py, size = 0, i; if (c->tty.sy - lines < 3) return (NULL); @@ -1969,6 +1969,9 @@ status_prompt_complete_window_menu(struct client *c, struct session *s, BOX_LINES_DEFAULT, NULL, NULL, NULL, NULL, status_prompt_menu_callback, spm) != 0) { menu_free(menu); + for (i = 0; i < size; i++) + free(list[i]); + free(list); free(spm); return (NULL); } From 0cc4f0fd76183324ba33d2bff1405210c1b92ac3 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 23 Feb 2026 09:08:07 +0000 Subject: [PATCH 10/35] Use buffer size for b64_pton, from someone in GitHub issue 4882. --- tty-keys.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tty-keys.c b/tty-keys.c index 7f9e8352..8fc51174 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -1389,7 +1389,7 @@ tty_keys_clipboard(struct tty *tty, const char *buf, size_t len, size_t *size) return (0); } out = xmalloc(needed); - if ((outlen = b64_pton(copy, out, len)) == -1) { + if ((outlen = b64_pton(copy, out, needed)) == -1) { free(out); free(copy); return (0); From f1f9c63cdb4d675d752e2eb3cb520a8fe43f3632 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 23 Feb 2026 09:12:57 +0000 Subject: [PATCH 11/35] Free history entries properly, from Huihui Huang in GitHub issue 4870. --- cmd-show-prompt-history.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd-show-prompt-history.c b/cmd-show-prompt-history.c index 1b95bcaf..5b88499c 100644 --- a/cmd-show-prompt-history.c +++ b/cmd-show-prompt-history.c @@ -60,6 +60,9 @@ cmd_show_prompt_history_exec(struct cmd *self, struct cmdq_item *item) if (cmd_get_entry(self) == &cmd_clear_prompt_history_entry) { if (typestr == NULL) { for (tidx = 0; tidx < PROMPT_NTYPES; tidx++) { + for (hidx = 0; hidx < status_prompt_hsize[tidx]; + hidx++) + free(status_prompt_hlist[tidx][hidx]); free(status_prompt_hlist[tidx]); status_prompt_hlist[tidx] = NULL; status_prompt_hsize[tidx] = 0; @@ -70,6 +73,8 @@ cmd_show_prompt_history_exec(struct cmd *self, struct cmdq_item *item) cmdq_error(item, "invalid type: %s", typestr); return (CMD_RETURN_ERROR); } + for (hidx = 0; hidx < status_prompt_hsize[type]; hidx++) + free(status_prompt_hlist[type][hidx]); free(status_prompt_hlist[type]); status_prompt_hlist[type] = NULL; status_prompt_hsize[type] = 0; From 00946007d8e870c08cc7cdb10c469968febf90f1 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 24 Feb 2026 07:50:27 +0000 Subject: [PATCH 12/35] Respond to DECRQM 2026, from David Turnbull in GitHub issue 4887. --- input.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/input.c b/input.c index 53bcb8bd..a90663ee 100644 --- a/input.c +++ b/input.c @@ -1621,10 +1621,6 @@ input_csi_dispatch(struct input_ctx *ictx) } input_reply(ictx, 1, "\033[?12;%d$y", n); break; - case 2004: /* bracketed paste */ - n = (s->mode & MODE_BRACKETPASTE) ? 1 : 2; - input_reply(ictx, 1, "\033[?2004;%d$y", n); - break; case 1004: /* focus reporting */ n = (s->mode & MODE_FOCUSON) ? 1 : 2; input_reply(ictx, 1, "\033[?1004;%d$y", n); @@ -1633,6 +1629,14 @@ input_csi_dispatch(struct input_ctx *ictx) n = (s->mode & MODE_MOUSE_SGR) ? 1 : 2; input_reply(ictx, 1, "\033[?1006;%d$y", n); break; + case 2004: /* bracketed paste */ + n = (s->mode & MODE_BRACKETPASTE) ? 1 : 2; + input_reply(ictx, 1, "\033[?2004;%d$y", n); + break; + case 2026: /* synchronized output */ + n = (s->mode & MODE_SYNC) ? 1 : 2; + input_reply(ictx, 1, "\033[?2026;%d$y", n); + break; case 2031: input_reply(ictx, 1, "\033[?2031;2$y"); break; From 9316476a733cea89f78b52efe3f761b8cf482bf1 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 24 Feb 2026 08:00:43 +0000 Subject: [PATCH 13/35] Add commands to turn exit on scroll on, off, toggle. From xcdnlgd at hotmail dot com in GitHub issue 4884. --- window-copy.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/window-copy.c b/window-copy.c index c4f10b37..3d4bc837 100644 --- a/window-copy.c +++ b/window-copy.c @@ -2127,6 +2127,36 @@ window_copy_cmd_rectangle_toggle(struct window_copy_cmd_state *cs) return (WINDOW_COPY_CMD_NOTHING); } +static enum window_copy_cmd_action +window_copy_cmd_scroll_exit_on(struct window_copy_cmd_state *cs) +{ + struct window_copy_mode_data *data = cs->wme->data; + + data->scroll_exit = 1; + + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_scroll_exit_off(struct window_copy_cmd_state *cs) +{ + struct window_copy_mode_data *data = cs->wme->data; + + data->scroll_exit = 0; + + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_scroll_exit_toggle(struct window_copy_cmd_state *cs) +{ + struct window_copy_mode_data *data = cs->wme->data; + + data->scroll_exit = !data->scroll_exit; + + return (WINDOW_COPY_CMD_NOTHING); +} + static enum window_copy_cmd_action window_copy_cmd_scroll_down(struct window_copy_cmd_state *cs) { @@ -3073,6 +3103,21 @@ static const struct { .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_scroll_down_and_cancel }, + { .command = "scroll-exit-on", + .args = { "", 0, 0, NULL }, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_scroll_exit_on + }, + { .command = "scroll-exit-off", + .args = { "", 0, 0, NULL }, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_scroll_exit_off + }, + { .command = "scroll-exit-toggle", + .args = { "", 0, 0, NULL }, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_scroll_exit_toggle + }, { .command = "scroll-middle", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, From 09bd686327c7765e8364253004b951a04d011beb Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 24 Feb 2026 08:20:52 +0000 Subject: [PATCH 14/35] Add sorting (-O flag) and a custom format (-F) to list-keys, from Dane Jensen in GitHub issue 4845. --- Makefile | 1 + cmd-list-commands.c | 107 ++++++++++ cmd-list-keys.c | 465 +++++++++++++++----------------------------- key-bindings.c | 13 ++ sort.c | 101 +++++++++- tmux.1 | 58 +++++- tmux.h | 7 + 7 files changed, 439 insertions(+), 313 deletions(-) create mode 100644 cmd-list-commands.c diff --git a/Makefile b/Makefile index 8a3aaa4b..fa87707b 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,7 @@ SRCS= alerts.c \ cmd-kill-window.c \ cmd-list-buffers.c \ cmd-list-clients.c \ + cmd-list-commands.c \ cmd-list-keys.c \ cmd-list-panes.c \ cmd-list-sessions.c \ diff --git a/cmd-list-commands.c b/cmd-list-commands.c new file mode 100644 index 00000000..0b0f912d --- /dev/null +++ b/cmd-list-commands.c @@ -0,0 +1,107 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include + +#include "tmux.h" + +/* + * List all commands. + */ + +#define LIST_COMMANDS_TEMPLATE \ + "#{command_list_name}" \ + "#{?command_list_alias, (#{command_list_alias}),} " \ + "#{command_list_usage}" + +static enum cmd_retval cmd_list_commands(struct cmd *, struct cmdq_item *); + +const struct cmd_entry cmd_list_commands_entry = { + .name = "list-commands", + .alias = "lscm", + + .args = { "F:", 0, 1, NULL }, + .usage = "[-F format] [command]", + + .flags = CMD_STARTSERVER|CMD_AFTERHOOK, + .exec = cmd_list_commands +}; + +static void +cmd_list_single_command(const struct cmd_entry *entry, struct format_tree *ft, + const char *template, struct cmdq_item *item) +{ + const char *s; + char *line; + + format_add(ft, "command_list_name", "%s", entry->name); + if (entry->alias != NULL) + s = entry->alias; + else + s = ""; + format_add(ft, "command_list_alias", "%s", s); + if (entry->usage != NULL) + s = entry->usage; + else + s = ""; + format_add(ft, "command_list_usage", "%s", s); + + line = format_expand(ft, template); + if (*line != '\0') + cmdq_print(item, "%s", line); + free(line); +} + +static enum cmd_retval +cmd_list_commands(struct cmd *self, struct cmdq_item *item) +{ + struct args *args = cmd_get_args(self); + const struct cmd_entry **entryp; + const struct cmd_entry *entry; + struct format_tree *ft; + const char *template, *command; + char *cause; + + if ((template = args_get(args, 'F')) == NULL) + template = LIST_COMMANDS_TEMPLATE; + + ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); + format_defaults(ft, NULL, NULL, NULL, NULL); + + command = args_string(args, 0); + if (command == NULL) { + for (entryp = cmd_table; *entryp != NULL; entryp++) + cmd_list_single_command(*entryp, ft, template, item); + } else { + entry = cmd_find(command, &cause); + if (entry != NULL) + cmd_list_single_command(entry, ft, template, item); + else { + cmdq_error(item, "%s", cause); + free(cause); + format_free(ft); + return (CMD_RETURN_ERROR); + } + } + + format_free(ft); + return (CMD_RETURN_NORMAL); +} diff --git a/cmd-list-keys.c b/cmd-list-keys.c index f25b0636..93d4df7c 100644 --- a/cmd-list-keys.c +++ b/cmd-list-keys.c @@ -27,122 +27,140 @@ * List key bindings. */ -static enum cmd_retval cmd_list_keys_exec(struct cmd *, struct cmdq_item *); +#define LIST_KEYS_TEMPLATE \ + "#{?notes_only," \ + "#{key_prefix} " \ + "#{p|#{key_string_width}:key_string} " \ + "#{?key_note,#{key_note},#{key_command}}" \ + "," \ + "bind-key #{?key_has_repeat,#{?key_repeat,-r, },} " \ + "-T #{p|#{key_table_width}:key_table} " \ + "#{p|#{key_string_width}:key_string} " \ + "#{key_command}}" -static enum cmd_retval cmd_list_keys_commands(struct cmd *, - struct cmdq_item *); +static enum cmd_retval cmd_list_keys_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_list_keys_entry = { .name = "list-keys", .alias = "lsk", - .args = { "1aNP:T:", 0, 1, NULL }, - .usage = "[-1aN] [-P prefix-string] [-T key-table] [key]", + .args = { "1aF:NO:P:rT:", 0, 1, NULL }, + .usage = "[-1aNr] [-F format] [-O order] [-P prefix-string]" + "[-T key-table] [key]", .flags = CMD_STARTSERVER|CMD_AFTERHOOK, .exec = cmd_list_keys_exec }; -const struct cmd_entry cmd_list_commands_entry = { - .name = "list-commands", - .alias = "lscm", +static char * +cmd_list_keys_get_prefix(struct args *args) +{ + key_code prefix; - .args = { "F:", 0, 1, NULL }, - .usage = "[-F format] [command]", + if (args_has(args, 'P')) + return (xstrdup(args_get(args, 'P'))); - .flags = CMD_STARTSERVER|CMD_AFTERHOOK, - .exec = cmd_list_keys_exec -}; + prefix = options_get_number(global_s_options, "prefix"); + if (prefix == KEYC_NONE) + return (xstrdup("")); + return (xstrdup(key_string_lookup_key(prefix, 0))); +} static u_int -cmd_list_keys_get_width(const char *tablename, key_code only) +cmd_list_keys_get_width(struct key_binding **l, u_int n) { - struct key_table *table; - struct key_binding *bd; - u_int width, keywidth = 0; + u_int i, width, keywidth = 0; - table = key_bindings_get_table(tablename, 0); - if (table == NULL) - return (0); - bd = key_bindings_first(table); - while (bd != NULL) { - if ((only != KEYC_UNKNOWN && bd->key != only) || - KEYC_IS_MOUSE(bd->key) || - bd->note == NULL || - *bd->note == '\0') { - bd = key_bindings_next(table, bd); - continue; - } - width = utf8_cstrwidth(key_string_lookup_key(bd->key, 0)); + for (i = 0; i < n; i++) { + width = utf8_cstrwidth(key_string_lookup_key(l[i]->key, 0)); if (width > keywidth) keywidth = width; - - bd = key_bindings_next(table, bd); } return (keywidth); } -static int -cmd_list_keys_print_notes(struct cmdq_item *item, struct args *args, - const char *tablename, u_int keywidth, key_code only, const char *prefix) +static u_int +cmd_list_keys_get_table_width(struct key_binding **l, u_int n) { - struct client *tc = cmdq_get_target_client(item); - struct key_table *table; - struct key_binding *bd; - const char *key; - char *tmp, *note; - int found = 0; + u_int i, width, tablewidth = 0; - table = key_bindings_get_table(tablename, 0); - if (table == NULL) - return (0); - bd = key_bindings_first(table); - while (bd != NULL) { - if ((only != KEYC_UNKNOWN && bd->key != only) || - KEYC_IS_MOUSE(bd->key) || - ((bd->note == NULL || *bd->note == '\0') && - !args_has(args, 'a'))) { - bd = key_bindings_next(table, bd); - continue; - } - found = 1; - key = key_string_lookup_key(bd->key, 0); - - if (bd->note == NULL || *bd->note == '\0') - note = cmd_list_print(bd->cmdlist, - CMD_LIST_PRINT_ESCAPED|CMD_LIST_PRINT_NO_GROUPS); - else - note = xstrdup(bd->note); - tmp = utf8_padcstr(key, keywidth + 1); - if (args_has(args, '1') && tc != NULL) { - status_message_set(tc, -1, 1, 0, 0, "%s%s%s", prefix, - tmp, note); - } else - cmdq_print(item, "%s%s%s", prefix, tmp, note); - free(tmp); - free(note); - - if (args_has(args, '1')) - break; - bd = key_bindings_next(table, bd); + for (i = 0; i < n; i++) { + width = utf8_cstrwidth(l[i]->tablename); + if (width > tablewidth) + tablewidth = width; } - return (found); + return (tablewidth); } -static char * -cmd_list_keys_get_prefix(struct args *args, key_code *prefix) +static struct key_binding ** +cmd_get_root_and_prefix(u_int *n, struct sort_criteria *sort_crit) { - char *s; + const char *tables[] = { "prefix", "root" }; + struct key_table *t; + struct key_binding **lt; + u_int i, ltsz, len = 0, offset = 0; + static struct key_binding **l = NULL; + static u_int lsz = 0; - *prefix = options_get_number(global_s_options, "prefix"); - if (!args_has(args, 'P')) { - if (*prefix != KEYC_NONE) - xasprintf(&s, "%s ", key_string_lookup_key(*prefix, 0)); - else - s = xstrdup(""); - } else - s = xstrdup(args_get(args, 'P')); - return (s); + for (i = 0; i < nitems(tables); i++) { + t = key_bindings_get_table(tables[i], 0); + lt = sort_get_key_bindings_table(t, <sz, sort_crit); + len += ltsz; + if (lsz <= len) { + lsz = len + 100; + l = xreallocarray(l, lsz, sizeof *l); + } + memcpy(l + offset, lt, ltsz * sizeof *l); + offset += ltsz; + } + + *n = len; + return (l); +} + +static void +cmd_filter_key_list(int filter_notes, int filter_key, key_code only, + struct key_binding **l, u_int *n) +{ + key_code key; + u_int i, j = 0; + + for (i = 0; i < *n; i++) { + key = l[i]->key & (KEYC_MASK_KEY|KEYC_MASK_MODIFIERS); + if (filter_key && only != key) + continue; + if (filter_notes && l[i]->note == NULL) + continue; + l[j++] = l[i]; + } + *n = j; +} + +static void +cmd_format_add_key_binding(struct format_tree *ft, + const struct key_binding *bd, const char *prefix) +{ + const char *s; + + if (bd->flags & KEY_BINDING_REPEAT) + format_add(ft, "key_repeat", "1"); + else + format_add(ft, "key_repeat", "0"); + + if (bd->note != NULL) + format_add(ft, "key_note", "%s", bd->note); + else + format_add(ft, "key_note", "%s", ""); + + format_add(ft, "key_prefix", "%s", prefix); + format_add(ft, "key_table", "%s", bd->tablename); + + s = key_string_lookup_key(bd->key, 0); + format_add(ft, "key_string", "%s", s); + + s = cmd_list_print(bd->cmdlist, + CMD_LIST_PRINT_ESCAPED|CMD_LIST_PRINT_NO_GROUPS); + format_add(ft, "key_command", "%s", s); } static enum cmd_retval @@ -150,16 +168,16 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = cmd_get_args(self); struct client *tc = cmdq_get_target_client(item); - struct key_table *table; - struct key_binding *bd; - const char *tablename, *r, *keystr; - char *key, *cp, *tmp, *start, *empty; - key_code prefix, only = KEYC_UNKNOWN; - int repeat, width, tablewidth, keywidth, found = 0; - size_t tmpsize, tmpused, cplen; - - if (cmd_get_entry(self) == &cmd_list_commands_entry) - return (cmd_list_keys_commands(self, item)); + struct format_tree *ft; + struct key_table *table = NULL; + struct key_binding **l; + key_code only = KEYC_UNKNOWN; + const char *template, *tablename, *keystr; + char *line; + char *prefix = NULL; + u_int i, n; + int single, notes_only, filter_notes, filter_key; + struct sort_criteria sort_crit; if ((keystr = args_string(args, 0)) != NULL) { only = key_string_lookup_string(keystr); @@ -170,219 +188,60 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) only &= (KEYC_MASK_KEY|KEYC_MASK_MODIFIERS); } - tablename = args_get(args, 'T'); - if (tablename != NULL && key_bindings_get_table(tablename, 0) == NULL) { - cmdq_error(item, "table %s doesn't exist", tablename); - return (CMD_RETURN_ERROR); - } + sort_crit.order = sort_order_from_string(args_get(args, 'O')); + sort_crit.reversed = args_has(args, 'r'); - if (args_has(args, 'N')) { - if (tablename == NULL) { - start = cmd_list_keys_get_prefix(args, &prefix); - keywidth = cmd_list_keys_get_width("root", only); - if (prefix != KEYC_NONE) { - width = cmd_list_keys_get_width("prefix", only); - if (width == 0) - prefix = KEYC_NONE; - else if (width > keywidth) - keywidth = width; - } - empty = utf8_padcstr("", utf8_cstrwidth(start)); + prefix = cmd_list_keys_get_prefix(args); + single = args_has(args, '1'); + notes_only = args_has(args, 'N'); - found = cmd_list_keys_print_notes(item, args, "root", - keywidth, only, empty); - if (prefix != KEYC_NONE) { - if (cmd_list_keys_print_notes(item, args, - "prefix", keywidth, only, start)) - found = 1; - } - free(empty); - } else { - if (args_has(args, 'P')) - start = xstrdup(args_get(args, 'P')); - else - start = xstrdup(""); - keywidth = cmd_list_keys_get_width(tablename, only); - found = cmd_list_keys_print_notes(item, args, tablename, - keywidth, only, start); - - } - free(start); - goto out; - } - - repeat = 0; - tablewidth = keywidth = 0; - table = key_bindings_first_table(); - while (table != NULL) { - if (tablename != NULL && strcmp(table->name, tablename) != 0) { - table = key_bindings_next_table(table); - continue; - } - bd = key_bindings_first(table); - while (bd != NULL) { - if (only != KEYC_UNKNOWN && bd->key != only) { - bd = key_bindings_next(table, bd); - continue; - } - key = args_escape(key_string_lookup_key(bd->key, 0)); - - if (bd->flags & KEY_BINDING_REPEAT) - repeat = 1; - - width = utf8_cstrwidth(table->name); - if (width > tablewidth) - tablewidth = width; - width = utf8_cstrwidth(key); - if (width > keywidth) - keywidth = width; - - free(key); - bd = key_bindings_next(table, bd); - } - table = key_bindings_next_table(table); - } - - tmpsize = 256; - tmp = xmalloc(tmpsize); - - table = key_bindings_first_table(); - while (table != NULL) { - if (tablename != NULL && strcmp(table->name, tablename) != 0) { - table = key_bindings_next_table(table); - continue; - } - bd = key_bindings_first(table); - while (bd != NULL) { - if (only != KEYC_UNKNOWN && bd->key != only) { - bd = key_bindings_next(table, bd); - continue; - } - found = 1; - key = args_escape(key_string_lookup_key(bd->key, 0)); - - if (!repeat) - r = ""; - else if (bd->flags & KEY_BINDING_REPEAT) - r = "-r "; - else - r = " "; - tmpused = xsnprintf(tmp, tmpsize, "%s-T ", r); - - cp = utf8_padcstr(table->name, tablewidth); - cplen = strlen(cp) + 1; - while (tmpused + cplen + 1 >= tmpsize) { - tmpsize *= 2; - tmp = xrealloc(tmp, tmpsize); - } - strlcat(tmp, cp, tmpsize); - tmpused = strlcat(tmp, " ", tmpsize); - free(cp); - - cp = utf8_padcstr(key, keywidth); - cplen = strlen(cp) + 1; - while (tmpused + cplen + 1 >= tmpsize) { - tmpsize *= 2; - tmp = xrealloc(tmp, tmpsize); - } - strlcat(tmp, cp, tmpsize); - tmpused = strlcat(tmp, " ", tmpsize); - free(cp); - - cp = cmd_list_print(bd->cmdlist, - CMD_LIST_PRINT_ESCAPED|CMD_LIST_PRINT_NO_GROUPS); - cplen = strlen(cp); - while (tmpused + cplen + 1 >= tmpsize) { - tmpsize *= 2; - tmp = xrealloc(tmp, tmpsize); - } - strlcat(tmp, cp, tmpsize); - free(cp); - - if (args_has(args, '1') && tc != NULL) { - status_message_set(tc, -1, 1, 0, 0, - "bind-key %s", tmp); - } else - cmdq_print(item, "bind-key %s", tmp); - free(key); - - if (args_has(args, '1')) - break; - bd = key_bindings_next(table, bd); - } - table = key_bindings_next_table(table); - } - - free(tmp); - -out: - if (only != KEYC_UNKNOWN && !found) { - cmdq_error(item, "unknown key: %s", args_string(args, 0)); - return (CMD_RETURN_ERROR); - } - return (CMD_RETURN_NORMAL); -} - -static void -cmd_list_single_command(const struct cmd_entry *entry, struct format_tree *ft, - const char *template, struct cmdq_item *item) -{ - const char *s; - char *line; - - format_add(ft, "command_list_name", "%s", entry->name); - if (entry->alias != NULL) - s = entry->alias; - else - s = ""; - format_add(ft, "command_list_alias", "%s", s); - if (entry->usage != NULL) - s = entry->usage; - else - s = ""; - format_add(ft, "command_list_usage", "%s", s); - - line = format_expand(ft, template); - if (*line != '\0') - cmdq_print(item, "%s", line); - free(line); -} - -static enum cmd_retval -cmd_list_keys_commands(struct cmd *self, struct cmdq_item *item) -{ - struct args *args = cmd_get_args(self); - const struct cmd_entry **entryp; - const struct cmd_entry *entry; - struct format_tree *ft; - const char *template, *command; - char *cause; - - if ((template = args_get(args, 'F')) == NULL) { - template = "#{command_list_name}" - "#{?command_list_alias, (#{command_list_alias}),} " - "#{command_list_usage}"; - } - - ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); - format_defaults(ft, NULL, NULL, NULL, NULL); - - command = args_string(args, 0); - if (command == NULL) { - for (entryp = cmd_table; *entryp != NULL; entryp++) - cmd_list_single_command(*entryp, ft, template, item); - } else { - entry = cmd_find(command, &cause); - if (entry != NULL) - cmd_list_single_command(entry, ft, template, item); - else { - cmdq_error(item, "%s", cause); - free(cause); - format_free(ft); + if ((tablename = args_get(args, 'T')) != NULL) { + table = key_bindings_get_table(tablename, 0); + if (table == NULL) { + cmdq_error(item, "table %s doesn't exist", tablename); return (CMD_RETURN_ERROR); } } + if ((template = args_get(args, 'F')) == NULL) + template = LIST_KEYS_TEMPLATE; + + if (table) + l = sort_get_key_bindings_table(table, &n, &sort_crit); + else if (notes_only) + l = cmd_get_root_and_prefix(&n, &sort_crit); + else + l = sort_get_key_bindings(&n, &sort_crit); + + filter_notes = notes_only && !args_has(args, 'a'); + filter_key = only != KEYC_UNKNOWN; + if (filter_notes || filter_key) + cmd_filter_key_list(filter_notes, filter_key, only, l, &n); + if (single) + n = 1; + + ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); + format_defaults(ft, NULL, NULL, NULL, NULL); + format_add(ft, "notes_only", "%d", notes_only); + format_add(ft, "key_has_repeat", "%d", key_bindings_has_repeat(l, n)); + format_add(ft, "key_string_width", "%u", cmd_list_keys_get_width(l, n)); + format_add(ft, "key_table_width", "%u", + cmd_list_keys_get_table_width(l, n)); + for (i = 0; i < n; i++) { + cmd_format_add_key_binding(ft, l[i], prefix); + + line = format_expand(ft, template); + if ((single && tc != NULL) || n == 1) + status_message_set(tc, -1, 1, 0, 0, "%s", line); + else if (*line != '\0') + cmdq_print(item, "%s", line); + free(line); + + if (single) + break; + } format_free(ft); + free(prefix); + return (CMD_RETURN_NORMAL); } diff --git a/key-bindings.c b/key-bindings.c index d30c3cdb..a395775f 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -215,6 +215,7 @@ key_bindings_add(const char *name, key_code key, const char *note, int repeat, bd = xcalloc(1, sizeof *bd); bd->key = (key & ~KEYC_MASK_FLAGS); + bd->tablename = table->name; if (note != NULL) bd->note = xstrdup(note); RB_INSERT(key_bindings, &table->key_bindings, bd); @@ -702,3 +703,15 @@ key_bindings_dispatch(struct key_binding *bd, struct cmdq_item *item, new_item = cmdq_append(c, new_item); return (new_item); } + +int +key_bindings_has_repeat(struct key_binding **l, u_int n) +{ + u_int i; + + for (i = 0; i < n; i++) { + if (l[i]->flags & KEY_BINDING_REPEAT) + return (1); + } + return (0); +} diff --git a/sort.c b/sort.c index fb3f89ee..1b18d370 100644 --- a/sort.c +++ b/sort.c @@ -72,6 +72,7 @@ sort_buffer_cmp(const void *a0, const void *b0) break; case SORT_ACTIVITY: case SORT_INDEX: + case SORT_MODIFIER: case SORT_ORDER: case SORT_END: break; @@ -117,6 +118,7 @@ sort_client_cmp(const void *a0, const void *b0) result = 1; break; case SORT_INDEX: + case SORT_MODIFIER: case SORT_ORDER: case SORT_END: break; @@ -167,6 +169,7 @@ sort_session_cmp(const void *a0, const void *b0) case SORT_NAME: result = strcmp(sa->name, sb->name); break; + case SORT_MODIFIER: case SORT_ORDER: case SORT_SIZE: case SORT_END: @@ -208,6 +211,7 @@ sort_pane_cmp(const void *a0, const void *b0) case SORT_NAME: result = strcmp(a->screen->title, b->screen->title); break; + case SORT_MODIFIER: case SORT_ORDER: case SORT_END: break; @@ -263,6 +267,7 @@ sort_winlink_cmp(const void *a0, const void *b0) case SORT_SIZE: result = wa->sx * wa->sy - wb->sx * wb->sy; break; + case SORT_MODIFIER: case SORT_ORDER: case SORT_END: break; @@ -276,6 +281,41 @@ sort_winlink_cmp(const void *a0, const void *b0) return (result); } +static int +sort_key_binding_cmp(const void *a0, const void *b0) +{ + struct sort_criteria *sort_crit = sort_criteria; + const struct key_binding *a = *(struct key_binding **)a0; + const struct key_binding *b = *(struct key_binding **)b0; + int result = 0; + + switch (sort_crit->order) { + case SORT_INDEX: + result = a->key - b->key; + break; + case SORT_MODIFIER: + result = (a->key & KEYC_MASK_MODIFIERS) - + (b->key & KEYC_MASK_MODIFIERS); + break; + case SORT_NAME: + result = strcasecmp(a->tablename, b->tablename) == 0; + break; + case SORT_ACTIVITY: + case SORT_CREATION: + case SORT_ORDER: + case SORT_SIZE: + case SORT_END: + break; + } + + if (result == 0) + result = strcasecmp(a->tablename, b->tablename) == 0; + + if (sort_crit->reversed) + result = -result; + return (result); +} + void sort_next_order(struct sort_criteria *sort_crit) { @@ -306,8 +346,11 @@ sort_order_from_string(const char* order) return (SORT_ACTIVITY); if (strcasecmp(order, "creation") == 0) return (SORT_CREATION); - if (strcasecmp(order, "index") == 0) + if (strcasecmp(order, "index") == 0 || + strcasecmp(order, "key") == 0) return (SORT_INDEX); + if (strcasecmp(order, "modifier") == 0) + return (SORT_MODIFIER); if (strcasecmp(order, "name") == 0 || strcasecmp(order, "title") == 0) return (SORT_NAME); @@ -328,6 +371,8 @@ sort_order_to_string(enum sort_order order) return "creation"; if (order == SORT_INDEX) return "index"; + if (order == SORT_MODIFIER) + return "modifier"; if (order == SORT_NAME) return "name"; if (order == SORT_ORDER) @@ -548,3 +593,57 @@ sort_get_winlinks_session(struct session *s, u_int *n, return (l); } + +struct key_binding ** +sort_get_key_bindings(u_int *n, struct sort_criteria *sort_crit) +{ + struct key_table *table; + struct key_binding *bd; + u_int i = 0; + static struct key_binding **l = NULL; + static u_int lsz = 0; + + table = key_bindings_first_table(); + while (table != NULL) { + bd = key_bindings_first(table); + while (bd != NULL) { + if (lsz <= i) { + lsz += 100; + l = xreallocarray(l, lsz, sizeof *l); + } + l[i++] = bd; + bd = key_bindings_next(table, bd); + } + table = key_bindings_next_table(table); + } + + sort_qsort(l, i, sizeof *l, sort_key_binding_cmp, sort_crit); + *n = i; + + return (l); +} + +struct key_binding ** +sort_get_key_bindings_table(struct key_table *table, u_int *n, + struct sort_criteria *sort_crit) +{ + struct key_binding *bd; + u_int i = 0; + static struct key_binding **l = NULL; + static u_int lsz = 0; + + bd = key_bindings_first(table); + while (bd != NULL) { + if (lsz <= i) { + lsz += 100; + l = xreallocarray(l, lsz, sizeof *l); + } + l[i++] = bd; + bd = key_bindings_next(table, bd); + } + + sort_qsort(l, i, sizeof *l, sort_key_binding_cmp, sort_crit); + *n = i; + + return (l); +} diff --git a/tmux.1 b/tmux.1 index 2229fc95..4c19dfbd 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2245,6 +2245,18 @@ Same as .Ic scroll-down but also exit copy mode if the cursor reaches the bottom. .It Xo +.It Xo +.Ic scroll-exit-on +.Xc +Turn on exiting copy mode when scrolling to the end of the buffer. +.It Xo +.Ic scroll-exit-off +.Xc +Turn off exiting copy mode when scrolling to the end of the buffer. +.It Xo +.Ic scroll-exit-toggle +.Xc +Toggle exiting copy mode when scrolling to the end of the buffer. .Ic scroll-middle (vi: z) .Xc @@ -3825,24 +3837,45 @@ To view the default bindings and possible commands, see the command. .Tg lsk .It Xo Ic list-keys -.Op Fl 1aN +.Op Fl 1aNr +.Op Fl F Ar format +.Op Fl O Ar sort-order .Op Fl P Ar prefix-string .Op Fl T Ar key-table .Op Ar key .Xc .D1 Pq alias: Ic lsk List key bindings. -There are two forms: the default lists keys as +.Fl F +specifies the format of each line. +See the +.Sx FORMATS +section. +.Fl T +specifies a +.Ar key-table +to list from. +.Fl 1 +lists only the first matching key. +.Fl O +specifies the sort order: one of +.Ql key , +.Ql modifier , +.Ql name +(table name). +.Fl r +reverses the sort order. +.Pp +If no +.Ar format +is given, there are two forms: the default lists keys as .Ic bind-key commands; .Fl N lists only keys with attached notes and shows only the key and note for each key. .Pp -With the default form, all key tables are listed by default. -.Fl T -lists only keys in -.Ar key-table . +With the default form, all key tables are listed unless specified otherwise. .Pp With the .Fl N @@ -3855,9 +3888,7 @@ key tables are listed by default; also lists only keys in .Ar key-table . .Fl P -specifies a prefix to print before each key and -.Fl 1 -lists only the first matching key. +specifies a prefix to print before each key. .Fl a lists the command for keys that do not have a note rather than skipping them. .Tg send @@ -6234,6 +6265,15 @@ The following variables are available, where appropriate: .It Li "host" Ta "#H" Ta "Hostname of local host" .It Li "host_short" Ta "#h" Ta "Hostname of local host (no domain name)" .It Li "insert_flag" Ta "" Ta "Pane insert flag" +.It Li "key_string" Ta "" Ta "String representation of the key binding" +.It Li "key_repeat" Ta "" Ta "1 if key binding is repeatable" +.It Li "key_note" Ta "" Ta "Note of the key binding" +.It Li "key_prefix" Ta "" Ta "Global key prefix" +.It Li "key_table" Ta "" Ta "Table name of the key binding" +.It Li "key_command" Ta "" Ta "Command of the key binding" +.It Li "key_has_repeat" Ta "" Ta "1 if list contain a repeatable key" +.It Li "key_string_width" Ta "" Ta "Maximum key_string width in list" +.It Li "key_table_width" Ta "" Ta "Maximum key_table width in list" .It Li "keypad_cursor_flag" Ta "" Ta "Pane keypad cursor flag" .It Li "keypad_flag" Ta "" Ta "Pane keypad flag" .It Li "last_window_index" Ta "" Ta "Index of last window in session" diff --git a/tmux.h b/tmux.h index 2c62bd52..82ce5f50 100644 --- a/tmux.h +++ b/tmux.h @@ -2111,6 +2111,7 @@ struct key_binding { key_code key; struct cmd_list *cmdlist; const char *note; + const char *tablename; int flags; #define KEY_BINDING_REPEAT 0x1 @@ -2249,6 +2250,7 @@ enum sort_order { SORT_ACTIVITY, SORT_CREATION, SORT_INDEX, + SORT_MODIFIER, SORT_NAME, SORT_ORDER, SORT_SIZE, @@ -2347,6 +2349,10 @@ struct window_pane **sort_get_panes_window(struct window *, u_int *, struct winlink **sort_get_winlinks(u_int *, struct sort_criteria *); struct winlink **sort_get_winlinks_session(struct session *, u_int *, struct sort_criteria *); +struct key_binding **sort_get_key_bindings(u_int *, + struct sort_criteria *); +struct key_binding **sort_get_key_bindings_table(struct key_table *, + u_int *, struct sort_criteria *); /* format.c */ #define FORMAT_STATUS 0x1 @@ -2849,6 +2855,7 @@ void key_bindings_reset(const char *, key_code); void key_bindings_remove_table(const char *); void key_bindings_reset_table(const char *); void key_bindings_init(void); +int key_bindings_has_repeat(struct key_binding **, u_int); struct cmdq_item *key_bindings_dispatch(struct key_binding *, struct cmdq_item *, struct client *, struct key_event *, struct cmd_find_state *); From 08779aa2a3ddb67f10357962c3bb8fde8820cef5 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 24 Feb 2026 08:27:15 +0000 Subject: [PATCH 15/35] Fix breakage in fuzzer, from David Korczynski, GitHub issue 4871. --- fuzz/input-fuzzer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fuzz/input-fuzzer.c b/fuzz/input-fuzzer.c index c7c5c99b..0e441547 100644 --- a/fuzz/input-fuzzer.c +++ b/fuzz/input-fuzzer.c @@ -44,7 +44,7 @@ LLVMFuzzerTestOneInput(const u_char *data, size_t size) w = window_create(PANE_WIDTH, PANE_HEIGHT, 0, 0); wp = window_add_pane(w, NULL, 0, 0); bufferevent_pair_new(libevent, BEV_OPT_CLOSE_ON_FREE, vpty); - wp->ictx = input_init(wp, vpty[0], NULL); + wp->ictx = input_init(wp, vpty[0], NULL, NULL); window_add_ref(w, __func__); wp->fd = open("/dev/null", O_WRONLY); From bd6e201926f6a5bf1249c806acf0ab5f002e72d4 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 24 Feb 2026 18:06:41 +0000 Subject: [PATCH 16/35] Do not expand #() in E: and T:, from Mason Davis. --- format.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/format.c b/format.c index 96b6585b..48629d38 100644 --- a/format.c +++ b/format.c @@ -5221,11 +5221,13 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, done: /* Expand again if required. */ if (modifiers & FORMAT_EXPAND) { - new = format_expand1(es, value); + format_copy_state(&next, es, FORMAT_EXPAND_NOJOBS); + new = format_expand1(&next, value); free(value); value = new; } else if (modifiers & FORMAT_EXPANDTIME) { - format_copy_state(&next, es, FORMAT_EXPAND_TIME); + format_copy_state(&next, es, FORMAT_EXPAND_TIME| + FORMAT_EXPAND_NOJOBS); new = format_expand1(&next, value); free(value); value = new; From 5b3c642195a1fc5f555c490b6b78dae84697cc51 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 25 Feb 2026 07:53:41 +0000 Subject: [PATCH 17/35] Pass paste buffer through vis(3) when pasting to prevent buffers containing for example the bracket end sequence causing issues. -S flag disables. Reported by Mason Davis. --- cmd-paste-buffer.c | 33 ++++++++++++++++++++++++++------- tmux.1 | 7 ++++++- tmux.h | 6 +++--- utf8.c | 10 +++++----- 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/cmd-paste-buffer.c b/cmd-paste-buffer.c index bef39593..fdf2bce7 100644 --- a/cmd-paste-buffer.c +++ b/cmd-paste-buffer.c @@ -34,8 +34,8 @@ const struct cmd_entry cmd_paste_buffer_entry = { .name = "paste-buffer", .alias = "pasteb", - .args = { "db:prs:t:", 0, 0, NULL }, - .usage = "[-dpr] [-s separator] " CMD_BUFFER_USAGE " " + .args = { "db:prSs:t:", 0, 0, NULL }, + .usage = "[-dprS] [-s separator] " CMD_BUFFER_USAGE " " CMD_TARGET_PANE_USAGE, .target = { 't', CMD_FIND_PANE, 0 }, @@ -44,6 +44,17 @@ const struct cmd_entry cmd_paste_buffer_entry = { .exec = cmd_paste_buffer_exec }; +static void +cmd_paste_buffer_paste(struct window_pane *wp, const char *buf, size_t len) +{ + char *cp; + size_t n; + + n = utf8_stravisx(&cp, buf, len, VIS_SAFE); + bufferevent_write(wp->event, cp, n); + free(cp); +} + static enum cmd_retval cmd_paste_buffer_exec(struct cmd *self, struct cmdq_item *item) { @@ -52,7 +63,7 @@ cmd_paste_buffer_exec(struct cmd *self, struct cmdq_item *item) struct window_pane *wp = target->wp; struct paste_buffer *pb; const char *sepstr, *bufname, *bufdata, *bufend, *line; - size_t seplen, bufsize; + size_t seplen, bufsize, len; int bracket = args_has(args, 'p'); if (window_pane_exited(wp)) { @@ -94,14 +105,22 @@ cmd_paste_buffer_exec(struct cmd *self, struct cmdq_item *item) line = memchr(bufdata, '\n', bufend - bufdata); if (line == NULL) break; - - bufferevent_write(wp->event, bufdata, line - bufdata); + len = line - bufdata; + if (args_has(args, 'S')) + bufferevent_write(wp->event, bufdata, len); + else + cmd_paste_buffer_paste(wp, bufdata, len); bufferevent_write(wp->event, sepstr, seplen); bufdata = line + 1; } - if (bufdata != bufend) - bufferevent_write(wp->event, bufdata, bufend - bufdata); + if (bufdata != bufend) { + len = bufend - bufdata; + if (args_has(args, 'S')) + bufferevent_write(wp->event, bufdata, len); + else + cmd_paste_buffer_paste(wp, bufdata, len); + } if (bracket && (wp->screen->mode & MODE_BRACKETPASTE)) bufferevent_write(wp->event, "\033[201~", 6); diff --git a/tmux.1 b/tmux.1 index 4c19dfbd..f89e6ee5 100644 --- a/tmux.1 +++ b/tmux.1 @@ -7449,7 +7449,7 @@ is the contents are read from stdin. .Tg pasteb .It Xo Ic paste-buffer -.Op Fl dpr +.Op Fl dprS .Op Fl b Ar buffer-name .Op Fl s Ar separator .Op Fl t Ar target-pane @@ -7457,9 +7457,14 @@ the contents are read from stdin. .D1 Pq alias: Ic pasteb Insert the contents of a paste buffer into the specified pane. If not specified, paste into the current one. +By default, control characters are sanitized with +.Xr vis 3 ; +.Fl S +disables this. With .Fl d , also delete the paste buffer. +.Pp When output, any linefeed (LF) characters in the paste buffer are replaced with a separator, by default carriage return (CR). A custom separator may be specified using the diff --git a/tmux.h b/tmux.h index 82ce5f50..349347c9 100644 --- a/tmux.h +++ b/tmux.h @@ -3573,9 +3573,9 @@ void utf8_copy(struct utf8_data *, const struct utf8_data *); enum utf8_state utf8_open(struct utf8_data *, u_char); enum utf8_state utf8_append(struct utf8_data *, u_char); int utf8_isvalid(const char *); -int utf8_strvis(char *, const char *, size_t, int); -int utf8_stravis(char **, const char *, int); -int utf8_stravisx(char **, const char *, size_t, int); +size_t utf8_strvis(char *, const char *, size_t, int); +size_t utf8_stravis(char **, const char *, int); +size_t utf8_stravisx(char **, const char *, size_t, int); char *utf8_sanitize(const char *); size_t utf8_strlen(const struct utf8_data *); u_int utf8_strwidth(const struct utf8_data *, ssize_t); diff --git a/utf8.c b/utf8.c index e877f2d0..38f270a8 100644 --- a/utf8.c +++ b/utf8.c @@ -639,7 +639,7 @@ utf8_append(struct utf8_data *ud, u_char ch) * bytes available for each character from src (for \abc or UTF-8) plus space * for \0. */ -int +size_t utf8_strvis(char *dst, const char *src, size_t len, int flag) { struct utf8_data ud; @@ -677,11 +677,11 @@ utf8_strvis(char *dst, const char *src, size_t len, int flag) } /* Same as utf8_strvis but allocate the buffer. */ -int +size_t utf8_stravis(char **dst, const char *src, int flag) { char *buf; - int len; + size_t len; buf = xreallocarray(NULL, 4, strlen(src) + 1); len = utf8_strvis(buf, src, strlen(src), flag); @@ -691,11 +691,11 @@ utf8_stravis(char **dst, const char *src, int flag) } /* Same as utf8_strvis but allocate the buffer. */ -int +size_t utf8_stravisx(char **dst, const char *src, size_t srclen, int flag) { char *buf; - int len; + size_t len; buf = xreallocarray(NULL, 4, srclen + 1); len = utf8_strvis(buf, src, srclen, flag); From 5ff385f8a9a43647956f467bafbac09120ee60a2 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 25 Feb 2026 07:59:45 +0000 Subject: [PATCH 18/35] Tweak previous to reset cursor again as well. --- window-copy.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/window-copy.c b/window-copy.c index 3d4bc837..902d8314 100644 --- a/window-copy.c +++ b/window-copy.c @@ -2732,16 +2732,20 @@ window_copy_cmd_refresh_from_pane(struct window_copy_cmd_state *cs) struct window_mode_entry *wme = cs->wme; struct window_pane *wp = wme->swp; struct window_copy_mode_data *data = wme->data; + u_int oy_from_top; if (data->viewmode) return (WINDOW_COPY_CMD_NOTHING); + oy_from_top = screen_hsize(data->backing) - data->oy; screen_free(data->backing); free(data->backing); data->backing = window_copy_clone_screen(&wp->base, &data->screen, NULL, NULL, wme->swp != wme->wp); - if (data->oy > screen_hsize(data->backing)) { + if (oy_from_top <= screen_hsize(data->backing)) + data->oy = screen_hsize(data->backing) - oy_from_top; + else { data->cy = 0; data->oy = screen_hsize(data->backing); } From 478eaea98213c13146c233560756608d281e15fa Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Thu, 26 Feb 2026 10:55:27 +0000 Subject: [PATCH 19/35] Update base64 compat. --- compat.h | 2 +- compat/base64.c | 34 ++++++++++++++-------------------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/compat.h b/compat.h index 76ef70df..55fbad48 100644 --- a/compat.h +++ b/compat.h @@ -388,7 +388,7 @@ int clock_gettime(int, struct timespec *); /* base64.c */ #undef b64_ntop #undef b64_pton -int b64_ntop(const char *, size_t, char *, size_t); +int b64_ntop(const u_char *, size_t, char *, size_t); int b64_pton(const char *, u_char *, size_t); #endif diff --git a/compat/base64.c b/compat/base64.c index e90696df..d6dc6b96 100644 --- a/compat/base64.c +++ b/compat/base64.c @@ -1,4 +1,4 @@ -/* $OpenBSD: base64.c,v 1.8 2015/01/16 16:48:51 deraadt Exp $ */ +/* $OpenBSD: base64.c,v 1.15 2021/10/25 14:41:09 jca Exp $ */ /* * Copyright (c) 1996 by Internet Software Consortium. @@ -46,15 +46,15 @@ #include #include #include -#include #include #include -#include #include #include +#include "compat.h" + static const char Base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static const char Pad64 = '='; @@ -107,9 +107,9 @@ static const char Pad64 = '='; end of the data is performed using the '=' character. Since all base64 input is an integral number of octets, only the - ------------------------------------------------- + ------------------------------------------------- following cases can arise: - + (1) the final quantum of encoding input is an integral multiple of 24 bits; here, the final unit of encoded output will be an integral multiple of 4 characters @@ -123,15 +123,12 @@ static const char Pad64 = '='; */ int -b64_ntop(src, srclength, target, targsize) - u_char const *src; - size_t srclength; - char *target; - size_t targsize; +b64_ntop(unsigned char const *src, size_t srclength, char *target, + size_t targsize) { size_t datalength = 0; - u_char input[3]; - u_char output[4]; + unsigned char input[3]; + unsigned char output[4]; int i; while (2 < srclength) { @@ -152,14 +149,14 @@ b64_ntop(src, srclength, target, targsize) target[datalength++] = Base64[output[2]]; target[datalength++] = Base64[output[3]]; } - + /* Now we worry about padding. */ if (0 != srclength) { /* Get what's left. */ input[0] = input[1] = input[2] = '\0'; for (i = 0; i < srclength; i++) input[i] = *src++; - + output[0] = input[0] >> 2; output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4); output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6); @@ -187,13 +184,10 @@ b64_ntop(src, srclength, target, targsize) */ int -b64_pton(src, target, targsize) - char const *src; - u_char *target; - size_t targsize; +b64_pton(char const *src, unsigned char *target, size_t targsize) { int tarindex, state, ch; - u_char nextbyte; + unsigned char nextbyte; char *pos; state = 0; @@ -207,7 +201,7 @@ b64_pton(src, target, targsize) break; pos = strchr(Base64, ch); - if (pos == 0) /* A non-base64 character. */ + if (pos == 0) /* A non-base64 character. */ return (-1); switch (state) { From dbb23d2182d4b18bab485b634e04eb9a5cf7a0c6 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 26 Feb 2026 11:01:48 +0000 Subject: [PATCH 20/35] Do not free buffer after adding to paste (since it now owns it). --- input.c | 1 - 1 file changed, 1 deletion(-) diff --git a/input.c b/input.c index a90663ee..daab123b 100644 --- a/input.c +++ b/input.c @@ -3156,7 +3156,6 @@ input_osc_52(struct input_ctx *ictx, const char *p) notify_pane("pane-set-clipboard", wp); paste_add(NULL, out, outlen); } - free(out); } /* Handle the OSC 104 sequence for unsetting (multiple) palette entries. */ From 35be70f1f05db097dc88d89fbafe82e4873e4201 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Thu, 26 Feb 2026 11:32:15 +0000 Subject: [PATCH 21/35] Add DECRPM test from David Turnbull. --- regress/decrqm-sync.sh | 82 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 regress/decrqm-sync.sh diff --git a/regress/decrqm-sync.sh b/regress/decrqm-sync.sh new file mode 100644 index 00000000..9620faf5 --- /dev/null +++ b/regress/decrqm-sync.sh @@ -0,0 +1,82 @@ +#!/bin/sh + +# Test DECRPM response for mode 2026 (synchronized output). +# +# DECRQM (ESC[?2026$p) should elicit DECRPM (ESC[?2026;Ps$y) where +# Ps=1 when MODE_SYNC is active, Ps=2 when reset. + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -Ltest" +$TMUX kill-server 2>/dev/null +sleep 1 + +TMP=$(mktemp) +TMP2=$(mktemp) +trap "rm -f $TMP $TMP2; $TMUX kill-server 2>/dev/null" 0 1 15 + +$TMUX -f/dev/null new -d -x80 -y24 || exit 1 +sleep 1 + +# Keep the session alive regardless of pane exits. +$TMUX set -g remain-on-exit on + +exit_status=0 + +# query_decrpm [setup_seq] +# Spawn a pane that optionally sends setup_seq, then sends DECRQM for +# mode 2026 and captures the response into outfile in cat -v form. +query_decrpm () { + _outfile=$1 + _setup=$2 + + $TMUX respawnw -k -t:0 -- sh -c " + exec 2>/dev/null + stty raw -echo + ${_setup:+printf '$_setup'; sleep 0.2} + printf '\033[?2026\$p' + dd bs=1 count=11 2>/dev/null | cat -v > $_outfile + sleep 0.2 + " || exit 1 + sleep 2 +} + +# ------------------------------------------------------------------ +# Test 1: mode 2026 should be reset by default (Ps=2) +# ------------------------------------------------------------------ +query_decrpm "$TMP" + +actual=$(cat "$TMP") +expected='^[[?2026;2$y' + +if [ "$actual" = "$expected" ]; then + if [ -n "$VERBOSE" ]; then + echo "[PASS] DECRQM 2026 (default/reset) -> $actual" + fi +else + echo "[FAIL] DECRQM 2026 (default/reset): expected '$expected', got '$actual'" + exit_status=1 +fi + +# ------------------------------------------------------------------ +# Test 2: set mode 2026 (SM ?2026), then query (expect Ps=1) +# ------------------------------------------------------------------ +query_decrpm "$TMP2" '\033[?2026h' + +actual=$(cat "$TMP2") +expected='^[[?2026;1$y' + +if [ "$actual" = "$expected" ]; then + if [ -n "$VERBOSE" ]; then + echo "[PASS] DECRQM 2026 (set) -> $actual" + fi +else + echo "[FAIL] DECRQM 2026 (set): expected '$expected', got '$actual'" + exit_status=1 +fi + +$TMUX kill-server 2>/dev/null + +exit $exit_status \ No newline at end of file From 50e51b4513eda93a0bc56c212ebbe93b1283856c Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 27 Feb 2026 08:23:02 +0000 Subject: [PATCH 22/35] Fix memory leak, from Chris Lewis, reported by Huihui Huang. --- file.c | 1 + 1 file changed, 1 insertion(+) diff --git a/file.c b/file.c index 407a7dc7..030082a5 100644 --- a/file.c +++ b/file.c @@ -58,6 +58,7 @@ file_get_path(struct client *c, const char *file) if (*path == '/') return (path); xasprintf(&full_path, "%s/%s", server_client_get_cwd(c, NULL), path); + free(path); return (full_path); } From 5413953d300b13ed032b618222d95162f2d965e9 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 27 Feb 2026 08:25:12 +0000 Subject: [PATCH 23/35] Validate -O flags, from Dane Jensen in GitHub issue 4889. --- cmd-choose-tree.c | 7 +++++++ cmd-list-buffers.c | 4 ++++ cmd-list-clients.c | 4 ++++ cmd-list-keys.c | 4 ++++ cmd-list-panes.c | 7 +++++++ cmd-list-sessions.c | 4 ++++ cmd-list-windows.c | 4 ++++ cmd-switch-client.c | 4 ++++ 8 files changed, 38 insertions(+) diff --git a/cmd-choose-tree.c b/cmd-choose-tree.c index a1167af8..186672f4 100644 --- a/cmd-choose-tree.c +++ b/cmd-choose-tree.c @@ -98,6 +98,13 @@ cmd_choose_tree_exec(struct cmd *self, struct cmdq_item *item) struct cmd_find_state *target = cmdq_get_target(item); struct window_pane *wp = target->wp; const struct window_mode *mode; + enum sort_order order; + + order = sort_order_from_string(args_get(args, 'O')); + if (order == SORT_END && args_has(args, 'O')) { + cmdq_error(item, "invalid sort order"); + return (CMD_RETURN_ERROR); + } if (cmd_get_entry(self) == &cmd_choose_buffer_entry) { if (paste_is_empty()) diff --git a/cmd-list-buffers.c b/cmd-list-buffers.c index 6e37a980..347c6934 100644 --- a/cmd-list-buffers.c +++ b/cmd-list-buffers.c @@ -60,6 +60,10 @@ cmd_list_buffers_exec(struct cmd *self, struct cmdq_item *item) filter = args_get(args, 'f'); sort_crit.order = sort_order_from_string(args_get(args, 'O')); + if (sort_crit.order == SORT_END && args_has(args, 'O')) { + cmdq_error(item, "invalid sort order"); + return (CMD_RETURN_ERROR); + } sort_crit.reversed = args_has(args, 'r'); l = sort_get_buffers(&n, &sort_crit); diff --git a/cmd-list-clients.c b/cmd-list-clients.c index 68cf043c..ae235d83 100644 --- a/cmd-list-clients.c +++ b/cmd-list-clients.c @@ -74,6 +74,10 @@ cmd_list_clients_exec(struct cmd *self, struct cmdq_item *item) filter = args_get(args, 'f'); sort_crit.order = sort_order_from_string(args_get(args, 'O')); + if (sort_crit.order == SORT_END && args_has(args, 'O')) { + cmdq_error(item, "invalid sort order"); + return (CMD_RETURN_ERROR); + } sort_crit.reversed = args_has(args, 'r'); l = sort_get_clients(&n, &sort_crit); diff --git a/cmd-list-keys.c b/cmd-list-keys.c index 93d4df7c..6c0fec67 100644 --- a/cmd-list-keys.c +++ b/cmd-list-keys.c @@ -189,6 +189,10 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) } sort_crit.order = sort_order_from_string(args_get(args, 'O')); + if (sort_crit.order == SORT_END && args_has(args, 'O')) { + cmdq_error(item, "invalid sort order"); + return (CMD_RETURN_ERROR); + } sort_crit.reversed = args_has(args, 'r'); prefix = cmd_list_keys_get_prefix(args); diff --git a/cmd-list-panes.c b/cmd-list-panes.c index fe7e1a4f..6a670b29 100644 --- a/cmd-list-panes.c +++ b/cmd-list-panes.c @@ -55,6 +55,13 @@ cmd_list_panes_exec(struct cmd *self, struct cmdq_item *item) struct cmd_find_state *target = cmdq_get_target(item); struct session *s = target->s; struct winlink *wl = target->wl; + enum sort_order order; + + order = sort_order_from_string(args_get(args, 'O')); + if (order == SORT_END && args_has(args, 'O')) { + cmdq_error(item, "invalid sort order"); + return (CMD_RETURN_ERROR); + } if (args_has(args, 'a')) cmd_list_panes_server(self, item); diff --git a/cmd-list-sessions.c b/cmd-list-sessions.c index 89fefb5c..79a1ab62 100644 --- a/cmd-list-sessions.c +++ b/cmd-list-sessions.c @@ -66,6 +66,10 @@ cmd_list_sessions_exec(struct cmd *self, struct cmdq_item *item) filter = args_get(args, 'f'); sort_crit.order = sort_order_from_string(args_get(args, 'O')); + if (sort_crit.order == SORT_END && args_has(args, 'O')) { + cmdq_error(item, "invalid sort order"); + return (CMD_RETURN_ERROR); + } sort_crit.reversed = args_has(args, 'r'); l = sort_get_sessions(&n, &sort_crit); diff --git a/cmd-list-windows.c b/cmd-list-windows.c index a7438de0..92797639 100644 --- a/cmd-list-windows.c +++ b/cmd-list-windows.c @@ -73,6 +73,10 @@ cmd_list_windows_exec(struct cmd *self, struct cmdq_item *item) filter = args_get(args, 'f'); sort_crit.order = sort_order_from_string(args_get(args, 'O')); + if (sort_crit.order == SORT_END && args_has(args, 'O')) { + cmdq_error(item, "invalid sort order"); + return (CMD_RETURN_ERROR); + } sort_crit.reversed = args_has(args, 'r'); if (args_has(args, 'a')) { diff --git a/cmd-switch-client.c b/cmd-switch-client.c index 9317d4f3..b0a0d928 100644 --- a/cmd-switch-client.c +++ b/cmd-switch-client.c @@ -97,6 +97,10 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item) } sort_crit.order = sort_order_from_string(args_get(args, 'O')); + if (sort_crit.order == SORT_END && args_has(args, 'O')) { + cmdq_error(item, "invalid sort order"); + return (CMD_RETURN_ERROR); + } sort_crit.reversed = args_has(args, 'r'); if (args_has(args, 'n')) { From 0c678b1e1a4fe166eb1d59efa79a79625111626f Mon Sep 17 00:00:00 2001 From: Thomas Adam Date: Sun, 1 Mar 2026 17:01:08 +0000 Subject: [PATCH 24/35] mailcap: update entry for Thomas Adam --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index c32c8db6..ae9cea50 100644 --- a/.mailmap +++ b/.mailmap @@ -29,6 +29,7 @@ Ted Unangst tedu Theo de Raadt Theo Deraadt Theo de Raadt deraadt Thomas Adam Thomas +Thomas Adam Thomas Adam Thomas Adam Thomas Adam Thomas Adam n6tadam Tim van der Molen tim From 9e804202b6d66c6d9f0d824c03a6728a8101b748 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 2 Mar 2026 08:38:23 +0000 Subject: [PATCH 25/35] Fix memory leak, from Emmanuel Ugwu in GitHub issue 4900. --- menu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/menu.c b/menu.c index e4f79dec..0df6fb47 100644 --- a/menu.c +++ b/menu.c @@ -90,6 +90,7 @@ menu_add_item(struct menu *menu, const struct menu_item *item, else s = format_single(qitem, item->name, c, NULL, NULL, NULL); if (*s == '\0') { /* no item if empty after format expanded */ + free(s); menu->count--; return; } From e80e0c761a82fe36a1452ad40ed54a0c09887412 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 2 Mar 2026 08:39:44 +0000 Subject: [PATCH 26/35] Fix incorrect placement of It Xo, from Dane Jensen. --- tmux.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tmux.1 b/tmux.1 index f89e6ee5..f5be9a8e 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2245,7 +2245,6 @@ Same as .Ic scroll-down but also exit copy mode if the cursor reaches the bottom. .It Xo -.It Xo .Ic scroll-exit-on .Xc Turn on exiting copy mode when scrolling to the end of the buffer. @@ -2257,6 +2256,7 @@ Turn off exiting copy mode when scrolling to the end of the buffer. .Ic scroll-exit-toggle .Xc Toggle exiting copy mode when scrolling to the end of the buffer. +.It Xo .Ic scroll-middle (vi: z) .Xc From e603549563b585966431c9dac324639113e3a30a Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 2 Mar 2026 08:41:43 +0000 Subject: [PATCH 27/35] Do not leak active/all strings in format_loop_windows; from Huihui Huang in GitHub issue 4898. --- format.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/format.c b/format.c index 48629d38..b1f64034 100644 --- a/format.c +++ b/format.c @@ -4429,6 +4429,9 @@ format_loop_sessions(struct format_expand_state *es, const char *fmt) free(expanded); } + free(active); + free(all); + return (value); } From f33b14bda64a4bd1fc97fd719430970f2ed145ca Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 2 Mar 2026 08:48:57 +0000 Subject: [PATCH 28/35] Revert r1.343 for the moment since it breaks behaviour (#() in status-left) that we need to keep. --- format.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/format.c b/format.c index b1f64034..dd4ce542 100644 --- a/format.c +++ b/format.c @@ -5224,13 +5224,11 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, done: /* Expand again if required. */ if (modifiers & FORMAT_EXPAND) { - format_copy_state(&next, es, FORMAT_EXPAND_NOJOBS); - new = format_expand1(&next, value); + new = format_expand1(es, value); free(value); value = new; } else if (modifiers & FORMAT_EXPANDTIME) { - format_copy_state(&next, es, FORMAT_EXPAND_TIME| - FORMAT_EXPAND_NOJOBS); + format_copy_state(&next, es, FORMAT_EXPAND_TIME); new = format_expand1(&next, value); free(value); value = new; From d781d2eaa16e4b7d1df8344f4a478452232ed96c Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 2 Mar 2026 09:32:45 +0000 Subject: [PATCH 29/35] Add regression test from Ilya Grigoriev, GitHub issue 4818. --- regress/session-group-resize.sh | 70 +++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100755 regress/session-group-resize.sh diff --git a/regress/session-group-resize.sh b/regress/session-group-resize.sh new file mode 100755 index 00000000..84eb31a4 --- /dev/null +++ b/regress/session-group-resize.sh @@ -0,0 +1,70 @@ +#!/bin/sh + +# Test that window-size=latest resizes windows correctly when switching +# windows in session groups. When a client switches to a window, it should +# resize immediately to match that client's size. +# +# Tests both switch-client and select-window, which use different code paths. + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -Ltest" +$TMUX kill-server 2>/dev/null + +TMP1=$(mktemp) +TMP2=$(mktemp) +TMP3=$(mktemp) +trap "rm -f $TMP1 $TMP2 $TMP3" 0 1 15 + +# Create a session with two windows, staying on window 0. +$TMUX -f/dev/null new -d -s test -x 20 -y 6 || exit 1 +$TMUX neww -t test || exit 1 +$TMUX selectw -t test:0 || exit 1 + +# Attach a small 20x6 client in control-mode and have it select window 1. This makes +# the small client the "latest" for window 1. The sleep keeps stdin open so the +# control client stays attached. +(echo "refresh-client -C 20,6"; echo "selectw -t :1"; sleep 5) | + $TMUX -f/dev/null -C attach -t test >$TMP1 2>&1 & + +# Wait for small client to be on window 1. +n=0 +while [ $n -lt 20 ]; do + $TMUX lsc -F '#{client_name} #{window_index}' 2>/dev/null | grep -q " 1$" && break + sleep 0.1 + n=$((n + 1)) +done + +# Create a grouped session with a larger 30x10 client, also in control mode. It +# starts on window 0 (inherited), then switches to window 1 with +# `switch-client`. +(echo "refresh-client -C 30,10"; echo "switch-client -t :=1"; sleep 5) | + $TMUX -f/dev/null -C new -t test -x 30 -y 10 >$TMP2 2>&1 & + +# Wait briefly for the switch-client command to execute, then check. +# The resize should happen immediately (within 0.2s). +sleep 0.2 +OUT1=$($TMUX display -t test:1 -p '#{window_width}x#{window_height}' 2>/dev/null) + +# Also test selectw (select-window) which uses a different code path. +# Create a third grouped session with a 25x8 client, switch to window 1 +# using selectw instead of switch-client. +(echo "refresh-client -C 25,8"; echo "selectw -t :1"; sleep 5) | + $TMUX -f/dev/null -C new -t test -x 25 -y 8 >$TMP3 2>&1 & + +sleep 0.2 +OUT2=$($TMUX display -t test:1 -p '#{window_width}x#{window_height}' 2>/dev/null) + +# Clean up - kill server (terminates clients). Don't wait for background +# sleeps; they'll be orphaned but harmless. +$TMUX kill-server 2>/dev/null + +# Window 1 should have resized to 30x10 (the second client's size). +[ "$OUT1" = "30x10" ] || { echo "switch-client resize failed: $OUT1"; exit 1; } + +# Window 1 should have resized to 25x8 (the third client's size). +[ "$OUT2" = "25x8" ] || { echo "selectw resize failed: $OUT2"; exit 1; } + +exit 0 From 77ce0a837d4439cec281747bb34cddccfac0b554 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 3 Mar 2026 12:24:18 +0000 Subject: [PATCH 30/35] Check window is not NULL, from Chema Gonzalez in GitHub issue 4908. --- server-fn.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/server-fn.c b/server-fn.c index fc20fce6..c36dc74f 100644 --- a/server-fn.c +++ b/server-fn.c @@ -97,7 +97,9 @@ server_redraw_window(struct window *w) struct client *c; TAILQ_FOREACH(c, &clients, entry) { - if (c->session != NULL && c->session->curw->window == w) + if (c->session != NULL && + c->session->curw != NULL && + c->session->curw->window == w) server_redraw_client(c); } } @@ -108,7 +110,9 @@ server_redraw_window_borders(struct window *w) struct client *c; TAILQ_FOREACH(c, &clients, entry) { - if (c->session != NULL && c->session->curw->window == w) + if (c->session != NULL && + c->session->curw != NULL && + c->session->curw->window == w) c->flags |= CLIENT_REDRAWBORDERS; } } From 49bb43047d56beb0b9c2db256f6f19abd57c3d20 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 3 Mar 2026 12:26:14 +0000 Subject: [PATCH 31/35] Allow copy mode to work for readonly clients, except for copy commands, from Dane Jensen. --- cmd-copy-mode.c | 2 +- cmd-send-keys.c | 8 +++- key-bindings.c | 1 + window-copy.c | 101 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 2 deletions(-) diff --git a/cmd-copy-mode.c b/cmd-copy-mode.c index b2a2231e..fff0d9dd 100644 --- a/cmd-copy-mode.c +++ b/cmd-copy-mode.c @@ -36,7 +36,7 @@ const struct cmd_entry cmd_copy_mode_entry = { .source = { 's', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 }, - .flags = CMD_AFTERHOOK, + .flags = CMD_AFTERHOOK|CMD_READONLY, .exec = cmd_copy_mode_exec }; diff --git a/cmd-send-keys.c b/cmd-send-keys.c index aa7b22fd..6c2b0998 100644 --- a/cmd-send-keys.c +++ b/cmd-send-keys.c @@ -39,7 +39,8 @@ const struct cmd_entry cmd_send_keys_entry = { .target = { 't', CMD_FIND_PANE, 0 }, - .flags = CMD_AFTERHOOK|CMD_CLIENT_CFLAG|CMD_CLIENT_CANFAIL, + .flags = CMD_AFTERHOOK|CMD_CLIENT_CFLAG|CMD_CLIENT_CANFAIL| + CMD_READONLY, .exec = cmd_send_keys_exec }; @@ -167,6 +168,11 @@ cmd_send_keys_exec(struct cmd *self, struct cmdq_item *item) u_int count = args_count(args); char *cause = NULL; + if (tc->flags & CLIENT_READONLY && !args_has(args, 'X')) { + cmdq_error(item, "client is read-only"); + return (CMD_RETURN_ERROR); + } + if (args_has(args, 'N')) { np = args_strtonum_and_expand(args, 'N', 1, UINT_MAX, item, &cause); diff --git a/key-bindings.c b/key-bindings.c index a395775f..a6c726f6 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -688,6 +688,7 @@ key_bindings_dispatch(struct key_binding *bd, struct cmdq_item *item, readonly = 1; else readonly = cmd_list_all_have(bd->cmdlist, CMD_READONLY); + if (!readonly) new_item = cmdq_get_callback(key_bindings_read_only, NULL); else { diff --git a/window-copy.c b/window-copy.c index 902d8314..bf265b5b 100644 --- a/window-copy.c +++ b/window-copy.c @@ -2759,351 +2759,424 @@ static const struct { u_int minargs; u_int maxargs; struct args_parse args; + +#define WINDOW_COPY_CMD_FLAG_READONLY 0x1 + int flags; + enum window_copy_cmd_clear clear; enum window_copy_cmd_action (*f)(struct window_copy_cmd_state *); } window_copy_cmd_table[] = { { .command = "append-selection", .args = { "", 0, 0, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_append_selection }, { .command = "append-selection-and-cancel", .args = { "", 0, 0, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_append_selection_and_cancel }, { .command = "back-to-indentation", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_back_to_indentation }, { .command = "begin-selection", .args = { "", 0, 0, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_begin_selection }, { .command = "bottom-line", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_bottom_line }, { .command = "cancel", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_cancel }, { .command = "clear-selection", .args = { "", 0, 0, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_clear_selection }, { .command = "copy-end-of-line", .args = { "CP", 0, 1, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_copy_end_of_line }, { .command = "copy-end-of-line-and-cancel", .args = { "CP", 0, 1, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_copy_end_of_line_and_cancel }, { .command = "copy-pipe-end-of-line", .args = { "CP", 0, 2, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_copy_pipe_end_of_line }, { .command = "copy-pipe-end-of-line-and-cancel", .args = { "CP", 0, 2, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_copy_pipe_end_of_line_and_cancel }, { .command = "copy-line", .args = { "CP", 0, 1, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_copy_line }, { .command = "copy-line-and-cancel", .args = { "CP", 0, 1, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_copy_line_and_cancel }, { .command = "copy-pipe-line", .args = { "CP", 0, 2, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_copy_pipe_line }, { .command = "copy-pipe-line-and-cancel", .args = { "CP", 0, 2, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_copy_pipe_line_and_cancel }, { .command = "copy-pipe-no-clear", .args = { "CP", 0, 2, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_NEVER, .f = window_copy_cmd_copy_pipe_no_clear }, { .command = "copy-pipe", .args = { "CP", 0, 2, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_copy_pipe }, { .command = "copy-pipe-and-cancel", .args = { "CP", 0, 2, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_copy_pipe_and_cancel }, { .command = "copy-selection-no-clear", .args = { "CP", 0, 1, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_NEVER, .f = window_copy_cmd_copy_selection_no_clear }, { .command = "copy-selection", .args = { "CP", 0, 1, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_copy_selection }, { .command = "copy-selection-and-cancel", .args = { "CP", 0, 1, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_copy_selection_and_cancel }, { .command = "cursor-down", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_cursor_down }, { .command = "cursor-down-and-cancel", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_cursor_down_and_cancel }, { .command = "cursor-left", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_cursor_left }, { .command = "cursor-right", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_cursor_right }, { .command = "cursor-up", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_cursor_up }, { .command = "cursor-centre-vertical", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_centre_vertical, }, { .command = "cursor-centre-horizontal", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_centre_horizontal, }, { .command = "end-of-line", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_end_of_line }, { .command = "goto-line", .args = { "", 1, 1, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_goto_line }, { .command = "halfpage-down", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_halfpage_down }, { .command = "halfpage-down-and-cancel", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_halfpage_down_and_cancel }, { .command = "halfpage-up", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_halfpage_up }, { .command = "history-bottom", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_history_bottom }, { .command = "history-top", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_history_top }, { .command = "jump-again", .args = { "", 0, 0, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_jump_again }, { .command = "jump-backward", .args = { "", 1, 1, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_jump_backward }, { .command = "jump-forward", .args = { "", 1, 1, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_jump_forward }, { .command = "jump-reverse", .args = { "", 0, 0, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_jump_reverse }, { .command = "jump-to-backward", .args = { "", 1, 1, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_jump_to_backward }, { .command = "jump-to-forward", .args = { "", 1, 1, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_jump_to_forward }, { .command = "jump-to-mark", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_jump_to_mark }, { .command = "next-prompt", .args = { "o", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_next_prompt }, { .command = "previous-prompt", .args = { "o", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_previous_prompt }, { .command = "middle-line", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_middle_line }, { .command = "next-matching-bracket", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_next_matching_bracket }, { .command = "next-paragraph", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_next_paragraph }, { .command = "next-space", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_next_space }, { .command = "next-space-end", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_next_space_end }, { .command = "next-word", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_next_word }, { .command = "next-word-end", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_next_word_end }, { .command = "other-end", .args = { "", 0, 0, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_other_end }, { .command = "page-down", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_page_down }, { .command = "page-down-and-cancel", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_page_down_and_cancel }, { .command = "page-up", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_page_up }, { .command = "pipe-no-clear", .args = { "", 0, 1, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_NEVER, .f = window_copy_cmd_pipe_no_clear }, { .command = "pipe", .args = { "", 0, 1, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_pipe }, { .command = "pipe-and-cancel", .args = { "", 0, 1, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_pipe_and_cancel }, { .command = "previous-matching-bracket", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_previous_matching_bracket }, { .command = "previous-paragraph", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_previous_paragraph }, { .command = "previous-space", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_previous_space }, { .command = "previous-word", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_previous_word }, { .command = "rectangle-on", .args = { "", 0, 0, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_rectangle_on }, { .command = "rectangle-off", .args = { "", 0, 0, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_rectangle_off }, { .command = "rectangle-toggle", .args = { "", 0, 0, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_rectangle_toggle }, { .command = "refresh-from-pane", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_refresh_from_pane }, { .command = "scroll-bottom", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_scroll_bottom }, { .command = "scroll-down", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_scroll_down }, { .command = "scroll-down-and-cancel", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_scroll_down_and_cancel }, @@ -3124,101 +3197,121 @@ static const struct { }, { .command = "scroll-middle", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_scroll_middle }, { .command = "scroll-to-mouse", .args = { "e", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_scroll_to_mouse }, { .command = "scroll-top", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_scroll_top }, { .command = "scroll-up", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_scroll_up }, { .command = "search-again", .args = { "", 0, 0, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_search_again }, { .command = "search-backward", .args = { "", 0, 1, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_search_backward }, { .command = "search-backward-text", .args = { "", 0, 1, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_search_backward_text }, { .command = "search-backward-incremental", .args = { "", 1, 1, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_search_backward_incremental }, { .command = "search-forward", .args = { "", 0, 1, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_search_forward }, { .command = "search-forward-text", .args = { "", 0, 1, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_search_forward_text }, { .command = "search-forward-incremental", .args = { "", 1, 1, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_search_forward_incremental }, { .command = "search-reverse", .args = { "", 0, 0, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_search_reverse }, { .command = "select-line", .args = { "", 0, 0, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_select_line }, { .command = "select-word", .args = { "", 0, 0, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_select_word }, { .command = "selection-mode", .args = { "", 0, 1, NULL }, + .flags = 0, .clear = 0, .f = window_copy_cmd_selection_mode }, { .command = "set-mark", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_set_mark }, { .command = "start-of-line", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_start_of_line }, { .command = "stop-selection", .args = { "", 0, 0, NULL }, + .flags = 0, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_stop_selection }, { .command = "toggle-position", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_NEVER, .f = window_copy_cmd_toggle_position }, { .command = "top-line", .args = { "", 0, 0, NULL }, + .flags = WINDOW_COPY_CMD_FLAG_READONLY, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_top_line } @@ -3258,6 +3351,14 @@ window_copy_command(struct window_mode_entry *wme, struct client *c, action = WINDOW_COPY_CMD_NOTHING; for (i = 0; i < nitems(window_copy_cmd_table); i++) { if (strcmp(window_copy_cmd_table[i].command, command) == 0) { + if (c->flags & CLIENT_READONLY && + (~window_copy_cmd_table[i].flags & + WINDOW_COPY_CMD_FLAG_READONLY)) { + status_message_set(c, -1, 1, 0, 0, + "client is read-only"); + return; + } + cs.wargs = args_parse(&window_copy_cmd_table[i].args, args_values(args), count, &error); From bcbad050d4b89be969c96039b5ff7d8c226547c7 Mon Sep 17 00:00:00 2001 From: tb Date: Wed, 4 Mar 2026 07:17:01 +0000 Subject: [PATCH 32/35] tmux: tc can be NULL, so check before deref to avoid crashing the server ok nicm --- cmd-send-keys.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd-send-keys.c b/cmd-send-keys.c index 6c2b0998..86e7daed 100644 --- a/cmd-send-keys.c +++ b/cmd-send-keys.c @@ -168,7 +168,7 @@ cmd_send_keys_exec(struct cmd *self, struct cmdq_item *item) u_int count = args_count(args); char *cause = NULL; - if (tc->flags & CLIENT_READONLY && !args_has(args, 'X')) { + if (tc != NULL && tc->flags & CLIENT_READONLY && !args_has(args, 'X')) { cmdq_error(item, "client is read-only"); return (CMD_RETURN_ERROR); } From d2fa20a39049b99b9d4038164aecaffbc13b4611 Mon Sep 17 00:00:00 2001 From: tb Date: Wed, 4 Mar 2026 07:19:32 +0000 Subject: [PATCH 33/35] tmux: use VIS_NOSLASH to avoid annoying double escaping on paste from nicm --- cmd-paste-buffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd-paste-buffer.c b/cmd-paste-buffer.c index fdf2bce7..03fcb0d4 100644 --- a/cmd-paste-buffer.c +++ b/cmd-paste-buffer.c @@ -50,7 +50,7 @@ cmd_paste_buffer_paste(struct window_pane *wp, const char *buf, size_t len) char *cp; size_t n; - n = utf8_stravisx(&cp, buf, len, VIS_SAFE); + n = utf8_stravisx(&cp, buf, len, VIS_SAFE|VIS_NOSLASH); bufferevent_write(wp->event, cp, n); free(cp); } From 55722a7ed7eaff7bdf39dd27df0c5026d6ab51ee Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 4 Mar 2026 08:15:24 +0000 Subject: [PATCH 34/35] Another memory leak, from Huihui Huang. --- cmd-confirm-before.c | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd-confirm-before.c b/cmd-confirm-before.c index 1f592ae0..72e9d87d 100644 --- a/cmd-confirm-before.c +++ b/cmd-confirm-before.c @@ -92,6 +92,7 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item) cdata->confirm_key = confirm_key[0]; else { cmdq_error(item, "invalid confirm key"); + cmd_list_free(cdata->cmdlist); free(cdata); return (CMD_RETURN_ERROR); } From 6d37cc94a70d01464b71b449a679f3a9a9284bf0 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 4 Mar 2026 08:16:47 +0000 Subject: [PATCH 35/35] Make -c work with new-session -A, from Jody Frankowski in GitHub issue 4906. --- cmd-new-session.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd-new-session.c b/cmd-new-session.c index 06082653..a3e74888 100644 --- a/cmd-new-session.c +++ b/cmd-new-session.c @@ -117,8 +117,9 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) as = target->s; if (as != NULL) { retval = cmd_attach_session(item, as->name, - args_has(args, 'D'), args_has(args, 'X'), 0, NULL, - args_has(args, 'E'), args_get(args, 'f')); + args_has(args, 'D'), args_has(args, 'X'), 0, + args_get(args, 'c'), args_has(args, 'E'), + args_get(args, 'f')); free(newname); return (retval); }