From 588013bb44231d3873dc58483c2cc75d93df5b91 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 3 Feb 2026 08:53:58 +0000 Subject: [PATCH 01/15] Refresh copy mode when style changes, from Josh Cooper in GitHub issue 4830. --- server-client.c | 18 +++++++++++++++--- tmux.h | 1 + window-copy.c | 13 +++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/server-client.c b/server-client.c index 050ca10a..c2044398 100644 --- a/server-client.c +++ b/server-client.c @@ -2709,14 +2709,26 @@ server_client_handle_key(struct client *c, struct key_event *event) void server_client_loop(void) { - struct client *c; - struct window *w; - struct window_pane *wp; + struct client *c; + struct window *w; + struct window_pane *wp; + struct window_mode_entry *wme; /* Check for window resize. This is done before redrawing. */ RB_FOREACH(w, windows, &windows) server_client_check_window_resize(w); + /* Notify modes that pane styles may have changed. */ + RB_FOREACH(w, windows, &windows) { + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp->flags & PANE_STYLECHANGED) { + wme = TAILQ_FIRST(&wp->modes); + if (wme != NULL && wme->mode->style_changed != NULL) + wme->mode->style_changed(wme); + } + } + } + /* Check clients. */ TAILQ_FOREACH(c, &clients, entry) { server_client_check_exit(c); diff --git a/tmux.h b/tmux.h index be4632c0..34958d4b 100644 --- a/tmux.h +++ b/tmux.h @@ -1069,6 +1069,7 @@ struct window_mode { void (*free)(struct window_mode_entry *); void (*resize)(struct window_mode_entry *, u_int, u_int); void (*update)(struct window_mode_entry *); + void (*style_changed)(struct window_mode_entry *); void (*key)(struct window_mode_entry *, struct client *, struct session *, struct winlink *, key_code, struct mouse_event *); diff --git a/window-copy.c b/window-copy.c index 04cfe85d..3a147c85 100644 --- a/window-copy.c +++ b/window-copy.c @@ -51,6 +51,7 @@ static void window_copy_redraw_selection(struct window_mode_entry *, u_int); static void window_copy_redraw_lines(struct window_mode_entry *, u_int, u_int); static void window_copy_redraw_screen(struct window_mode_entry *); +static void window_copy_style_changed(struct window_mode_entry *); static void window_copy_write_line(struct window_mode_entry *, struct screen_write_ctx *, u_int); static void window_copy_write_lines(struct window_mode_entry *, @@ -158,6 +159,7 @@ const struct window_mode window_copy_mode = { .init = window_copy_init, .free = window_copy_free, .resize = window_copy_resize, + .style_changed = window_copy_style_changed, .key_table = window_copy_key_table, .command = window_copy_command, .formats = window_copy_formats, @@ -170,6 +172,7 @@ const struct window_mode window_view_mode = { .init = window_copy_view_init, .free = window_copy_free, .resize = window_copy_resize, + .style_changed = window_copy_style_changed, .key_table = window_copy_key_table, .command = window_copy_command, .formats = window_copy_formats, @@ -4595,6 +4598,16 @@ window_copy_redraw_screen(struct window_mode_entry *wme) window_copy_redraw_lines(wme, 0, screen_size_y(&data->screen)); } +static void +window_copy_style_changed(struct window_mode_entry *wme) +{ + struct window_copy_mode_data *data = wme->data; + + if (data->screen.sel != NULL) + window_copy_set_selection(wme, 0, 1); + window_copy_redraw_screen(wme); +} + static void window_copy_synchronize_cursor_end(struct window_mode_entry *wme, int begin, int no_reset) From 62944da74b7add5c7ac73527d8eb5d9d51ebbd60 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 3 Feb 2026 09:07:44 +0000 Subject: [PATCH 02/15] Make OSC 52 work in popups, from gogongxt at 163 dot com in GitHub issue 4797. --- input.c | 89 +++++++++++++++++++++++++++++++++------------------ popup.c | 2 +- tmux.h | 2 +- window-copy.c | 2 +- window.c | 2 +- 5 files changed, 62 insertions(+), 35 deletions(-) diff --git a/input.c b/input.c index 45b70db6..c8ae3544 100644 --- a/input.c +++ b/input.c @@ -101,6 +101,7 @@ struct input_ctx { struct bufferevent *event; struct screen_write_ctx ctx; struct colour_palette *palette; + struct client *c; struct input_cell cell; struct input_cell old_cell; @@ -854,7 +855,7 @@ input_restore_state(struct input_ctx *ictx) /* Initialise input parser. */ struct input_ctx * input_init(struct window_pane *wp, struct bufferevent *bev, - struct colour_palette *palette) + struct colour_palette *palette, struct client *c) { struct input_ctx *ictx; @@ -862,6 +863,7 @@ input_init(struct window_pane *wp, struct bufferevent *bev, ictx->wp = wp; ictx->event = bev; ictx->palette = palette; + ictx->c = c; ictx->input_space = INPUT_BUF_START; ictx->input_buf = xmalloc(INPUT_BUF_START); @@ -3073,31 +3075,28 @@ input_osc_52_reply(struct input_ctx *ictx) input_add_request(ictx, INPUT_REQUEST_CLIPBOARD, ictx->input_end); } -/* Handle the OSC 52 sequence for setting the clipboard. */ -static void -input_osc_52(struct input_ctx *ictx, const char *p) +/* + * Parse and decode OSC 52 clipboard data. Returns 0 on failure or if handled + * as a query. On success, returns 1 and sets *out, *outlen, and *flags (caller + * must free *out). + */ +static int +input_osc_52_parse(struct input_ctx *ictx, const char *p, u_char **out, + int *outlen, char *flags) { - struct window_pane *wp = ictx->wp; - size_t len; - char *end; - u_char *out; - int outlen, state; - struct screen_write_ctx ctx; - const char* allow = "cpqs01234567"; - char flags[sizeof "cpqs01234567"] = ""; - u_int i, j = 0; + char *end; + size_t len; + const char *allow = "cpqs01234567"; + u_int i, j = 0; - if (wp == NULL) - return; - state = options_get_number(global_options, "set-clipboard"); - if (state != 2) - return; + if (options_get_number(global_options, "set-clipboard") != 2) + return (0); if ((end = strchr(p, ';')) == NULL) - return; + return (0); end++; if (*end == '\0') - return; + return (0); log_debug("%s: %s", __func__, end); for (i = 0; p + i != end; i++) { @@ -3108,25 +3107,53 @@ input_osc_52(struct input_ctx *ictx, const char *p) if (strcmp(end, "?") == 0) { input_osc_52_reply(ictx); - return; + return (0); } len = (strlen(end) / 4) * 3; if (len == 0) - return; + return (0); - out = xmalloc(len); - if ((outlen = b64_pton(end, out, len)) == -1) { - free(out); - return; + *out = xmalloc(len); + if ((*outlen = b64_pton(end, *out, len)) == -1) { + free(*out); + *out = NULL; + return (0); } - screen_write_start_pane(&ctx, wp, NULL); - screen_write_setselection(&ctx, flags, out, outlen); - screen_write_stop(&ctx); - notify_pane("pane-set-clipboard", wp); + return (1); +} - paste_add(NULL, out, outlen); +/* Handle the OSC 52 sequence for setting the clipboard. */ +static void +input_osc_52(struct input_ctx *ictx, const char *p) +{ + struct window_pane *wp = ictx->wp; + struct screen_write_ctx ctx; + u_char *out; + int outlen; + char flags[sizeof "cpqs01234567"] = ""; + + if (!input_osc_52_parse(ictx, p, &out, &outlen, flags)) + return; + + if (wp == NULL) { + /* Popup window. */ + if (ictx->c == NULL) { + free(out); + return; + } + tty_set_selection(&ictx->c->tty, flags, 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_stop(&ctx); + notify_pane("pane-set-clipboard", wp); + paste_add(NULL, out, outlen); + } + free(out); } /* Handle the OSC 104 sequence for unsetting (multiple) palette entries. */ diff --git a/popup.c b/popup.c index 70f4def4..04584b80 100644 --- a/popup.c +++ b/popup.c @@ -851,7 +851,7 @@ popup_display(int flags, enum box_lines lines, struct cmdq_item *item, u_int px, pd->job = job_run(shellcmd, argc, argv, env, s, cwd, popup_job_update_cb, popup_job_complete_cb, NULL, pd, JOB_NOWAIT|JOB_PTY|JOB_KEEPWRITE|JOB_DEFAULTSHELL, jx, jy); - pd->ictx = input_init(NULL, job_get_event(pd->job), &pd->palette); + pd->ictx = input_init(NULL, job_get_event(pd->job), &pd->palette, c); server_client_set_overlay(c, 0, popup_check_cb, popup_mode_cb, popup_draw_cb, popup_key_cb, popup_free_cb, popup_resize_cb, pd); diff --git a/tmux.h b/tmux.h index 34958d4b..0f84398b 100644 --- a/tmux.h +++ b/tmux.h @@ -3011,7 +3011,7 @@ void recalculate_sizes_now(int); /* input.c */ #define INPUT_BUF_DEFAULT_SIZE 1048576 struct input_ctx *input_init(struct window_pane *, struct bufferevent *, - struct colour_palette *); + struct colour_palette *, struct client *); void input_free(struct input_ctx *); void input_reset(struct input_ctx *, int); struct evbuffer *input_pending(struct input_ctx *); diff --git a/window-copy.c b/window-copy.c index 3a147c85..c372c4b1 100644 --- a/window-copy.c +++ b/window-copy.c @@ -494,7 +494,7 @@ window_copy_view_init(struct window_mode_entry *wme, data->backing = xmalloc(sizeof *data->backing); screen_init(data->backing, sx, screen_size_y(base), UINT_MAX); - data->ictx = input_init(NULL, NULL, NULL); + data->ictx = input_init(NULL, NULL, NULL, NULL); data->mx = data->cx; data->my = screen_hsize(data->backing) + data->cy - data->oy; data->showmark = 0; diff --git a/window.c b/window.c index 9581c64f..31a8a8ee 100644 --- a/window.c +++ b/window.c @@ -1057,7 +1057,7 @@ window_pane_set_event(struct window_pane *wp) NULL, window_pane_error_callback, wp); if (wp->event == NULL) fatalx("out of memory"); - wp->ictx = input_init(wp, wp->event, &wp->palette); + wp->ictx = input_init(wp, wp->event, &wp->palette, NULL); bufferevent_enable(wp->event, EV_READ|EV_WRITE); } From 5865001e4a1e524c492d614a5dad6e9701a91926 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 6 Feb 2026 10:23:26 +0000 Subject: [PATCH 03/15] Also check PANE_STATUSREADY for pane_dead format to match pane_dead_status. GitHub issue 4841 from Joshua Pollack. --- format.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/format.c b/format.c index 5804869d..96b6585b 100644 --- a/format.c +++ b/format.c @@ -1998,8 +1998,10 @@ format_cb_pane_bottom(struct format_tree *ft) static void * format_cb_pane_dead(struct format_tree *ft) { - if (ft->wp != NULL) { - if (ft->wp->fd == -1) + struct window_pane *wp = ft->wp; + + if (wp != NULL) { + if (wp->fd == -1 && (wp->flags & PANE_STATUSREADY)) return (xstrdup("1")); return (xstrdup("0")); } From 7b1c503086d8e2911f62a4e4475c715c0bb6a0d7 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 6 Feb 2026 10:28:42 +0000 Subject: [PATCH 04/15] Clear search counts when clearing marks in case of repeated search, reported by Daniel Pereira in GitHub issue 4817. --- window-copy.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/window-copy.c b/window-copy.c index c372c4b1..04f62643 100644 --- a/window-copy.c +++ b/window-copy.c @@ -4271,6 +4271,9 @@ window_copy_clear_marks(struct window_mode_entry *wme) { struct window_copy_mode_data *data = wme->data; + data->searchcount = -1; + data->searchmore = 0; + free(data->searchmark); data->searchmark = NULL; } From f016e0815397e4b88252c9ed7475df9acac97413 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 10 Feb 2026 08:27:17 +0000 Subject: [PATCH 05/15] Fix wrong TAILQ member in input_cancel_requests from Conor Taylor in GitHub issue 4848. --- input.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/input.c b/input.c index c8ae3544..ee92a7cf 100644 --- a/input.c +++ b/input.c @@ -3424,7 +3424,7 @@ input_cancel_requests(struct client *c) { struct input_request *ir, *ir1; - TAILQ_FOREACH_SAFE(ir, &c->input_requests, entry, ir1) + TAILQ_FOREACH_SAFE(ir, &c->input_requests, centry, ir1) input_free_request(ir); } From 19b9a34c4863e668c1221ab6bb6d36967dda0b1e Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 10 Feb 2026 08:28:53 +0000 Subject: [PATCH 06/15] Only loop over clients if table actually found, from Conor Taylor in GitHub issue 4848. --- key-bindings.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/key-bindings.c b/key-bindings.c index 5e6449b7..d30c3cdb 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -297,12 +297,12 @@ key_bindings_remove_table(const char *name) table = key_bindings_get_table(name, 0); if (table != NULL) { RB_REMOVE(key_tables, &key_tables, table); + TAILQ_FOREACH(c, &clients, entry) { + if (c->keytable == table) + server_client_set_key_table(c, NULL); + } key_bindings_unref_table(table); } - TAILQ_FOREACH(c, &clients, entry) { - if (c->keytable == table) - server_client_set_key_table(c, NULL); - } } void From b7939eb26693a462672d96ff2ce0659866d569ed Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 10 Feb 2026 08:30:21 +0000 Subject: [PATCH 07/15] Don't call event_add again if the event is already pending, from Conor Taylor in GitHub issue 4848. --- tty.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tty.c b/tty.c index 6e413572..5325f38c 100644 --- a/tty.c +++ b/tty.c @@ -628,7 +628,8 @@ tty_add(struct tty *tty, const char *buf, size_t len) if (tty_log_fd != -1) write(tty_log_fd, buf, len); - if (tty->flags & TTY_STARTED) + if ((tty->flags & TTY_STARTED) && + !event_pending(&tty->event_out, EV_WRITE, NULL)) event_add(&tty->event_out, NULL); } From d8794e2b30f92db606a9f6e80316696d90240793 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 10 Feb 2026 08:31:45 +0000 Subject: [PATCH 08/15] Check log level before log_debug in tty_draw_line, from Conor Taylor in GitHub issue 4848. --- tty-draw.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tty-draw.c b/tty-draw.c index c506853d..eaf56d2b 100644 --- a/tty-draw.c +++ b/tty-draw.c @@ -262,10 +262,12 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, else next_state = TTY_DRAW_LINE_NEW1; } - log_debug("%s: cell %u empty %u, bg %u; state: current %s, " - "next %s", __func__, px + i, empty, gcp->bg, - tty_draw_line_states[current_state], - tty_draw_line_states[next_state]); + if (log_get_level() != 0) { + log_debug("%s: cell %u empty %u, bg %u; state: " + "current %s, next %s", __func__, px + i, empty, + gcp->bg, tty_draw_line_states[current_state], + tty_draw_line_states[next_state]); + } /* If the state has changed, flush any collected data. */ if (next_state != current_state) { From aa03706ed01abaa6c793ad90c73b48783a074bb1 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 10 Feb 2026 08:34:43 +0000 Subject: [PATCH 09/15] Remove redundant call to tty_attributes, from Conor Taylor in GitHub issue 4848. --- tty.c | 1 - 1 file changed, 1 deletion(-) diff --git a/tty.c b/tty.c index 5325f38c..29369598 100644 --- a/tty.c +++ b/tty.c @@ -2064,7 +2064,6 @@ tty_cell(struct tty *tty, const struct grid_cell *gc, /* If it is a single character, write with putc to handle ACS. */ if (gcp->data.size == 1) { - tty_attributes(tty, gcp, defaults, palette, hl); if (*gcp->data.data < 0x20 || *gcp->data.data == 0x7f) return; tty_putc(tty, *gcp->data.data); From 1cf17b06aee30ab64cc2e90617099d63a30f88eb Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 10 Feb 2026 08:40:03 +0000 Subject: [PATCH 10/15] Batch printable output in control mode, from Conor Taylor in GitHub issue 4848. --- control.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/control.c b/control.c index 8b44a274..c02b2041 100644 --- a/control.c +++ b/control.c @@ -611,7 +611,7 @@ control_append_data(struct client *c, struct control_pane *cp, uint64_t age, struct evbuffer *message, struct window_pane *wp, size_t size) { u_char *new_data; - size_t new_size; + size_t new_size, start; u_int i; if (message == NULL) { @@ -630,10 +630,16 @@ control_append_data(struct client *c, struct control_pane *cp, uint64_t age, if (new_size < size) fatalx("not enough data: %zu < %zu", new_size, size); for (i = 0; i < size; i++) { - if (new_data[i] < ' ' || new_data[i] == '\\') + if (new_data[i] < ' ' || new_data[i] == '\\') { evbuffer_add_printf(message, "\\%03o", new_data[i]); - else - evbuffer_add_printf(message, "%c", new_data[i]); + } else { + start = i; + while (i + 1 < size && + new_data[i + 1] >= ' ' && + new_data[i + 1] != '\\') + i++; + evbuffer_add(message, new_data + start, i - start + 1); + } } window_pane_update_used_data(wp, &cp->offset, size); return (message); From 5a33616e650458ab392ab32c7e38fb3bf49d933f Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 10 Feb 2026 09:00:30 +0000 Subject: [PATCH 11/15] Check for no window when updating clients, GitHub issue 4851. --- server-client.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server-client.c b/server-client.c index c2044398..db5c02ff 100644 --- a/server-client.c +++ b/server-client.c @@ -2723,7 +2723,8 @@ server_client_loop(void) TAILQ_FOREACH(wp, &w->panes, entry) { if (wp->flags & PANE_STYLECHANGED) { wme = TAILQ_FIRST(&wp->modes); - if (wme != NULL && wme->mode->style_changed != NULL) + if (wme != NULL && + wme->mode->style_changed != NULL) wme->mode->style_changed(wme); } } @@ -2732,7 +2733,7 @@ server_client_loop(void) /* Check clients. */ TAILQ_FOREACH(c, &clients, entry) { server_client_check_exit(c); - if (c->session != NULL) { + if (c->session != NULL && c->session->curw != NULL) { server_client_check_modes(c); server_client_check_redraw(c); server_client_reset_state(c); From 25f6d8b1e9b23ee367af94b3a548b4f3da8aa04b Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 10 Feb 2026 09:55:53 +0000 Subject: [PATCH 12/15] Implement some obvious missing sort orders, from Dane Jensen in GitHub issue 4813. --- sort.c | 40 +++++++++++++++++++++++++++------------- tmux.1 | 10 +++++++++- tmux.h | 1 + window.c | 3 +++ 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/sort.c b/sort.c index a1d85891..fb3f89ee 100644 --- a/sort.c +++ b/sort.c @@ -197,23 +197,25 @@ sort_pane_cmp(const void *a0, const void *b0) case SORT_CREATION: result = a->id - b->id; break; - case SORT_INDEX: - case SORT_NAME: - case SORT_ORDER: case SORT_SIZE: - case SORT_END: + result = a->sx * a->sy - b->sx * b->sy; break; - } - if (result == 0) { - /* - * Panes don't have names, so use number order for any other - * sort field. - */ + case SORT_INDEX: window_pane_index(a, &ai); window_pane_index(b, &bi); result = ai - bi; + break; + case SORT_NAME: + result = strcmp(a->screen->title, b->screen->title); + break; + case SORT_ORDER: + case SORT_END: + break; } + if (result == 0) + result = strcmp(a->screen->title, b->screen->title); + if (sort_crit->reversed) result = -result; return (result); @@ -235,6 +237,16 @@ sort_winlink_cmp(const void *a0, const void *b0) case SORT_INDEX: result = wla->idx - wlb->idx; break; + case SORT_CREATION: + if (timercmp(&wa->creation_time, &wb->creation_time, >)) { + result = -1; + break; + } + if (timercmp(&wa->creation_time, &wb->creation_time, <)) { + result = 1; + break; + } + break; case SORT_ACTIVITY: if (timercmp(&wa->activity_time, &wb->activity_time, >)) { result = -1; @@ -248,9 +260,10 @@ sort_winlink_cmp(const void *a0, const void *b0) case SORT_NAME: result = strcmp(wa->name, wb->name); break; - case SORT_CREATION: - case SORT_ORDER: case SORT_SIZE: + result = wa->sx * wa->sy - wb->sx * wb->sy; + break; + case SORT_ORDER: case SORT_END: break; } @@ -295,7 +308,8 @@ sort_order_from_string(const char* order) return (SORT_CREATION); if (strcasecmp(order, "index") == 0) return (SORT_INDEX); - if (strcasecmp(order, "name") == 0) + if (strcasecmp(order, "name") == 0 || + strcasecmp(order, "title") == 0) return (SORT_NAME); if (strcasecmp(order, "order") == 0) return (SORT_ORDER); diff --git a/tmux.1 b/tmux.1 index 48751888..2229fc95 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3090,7 +3090,11 @@ See the section. .Fl O specifies the sort order: one of -.Ql name , +.Ql name +(title), +.Ql index , +.Ql size +(area), .Ql creation (time), or .Ql activity @@ -3123,6 +3127,10 @@ section. specifies the sort order: one of .Ql index , .Ql name , +.Ql size +(area), +.Ql creation +(time), or .Ql activity (time). diff --git a/tmux.h b/tmux.h index 0f84398b..fdbf55c1 100644 --- a/tmux.h +++ b/tmux.h @@ -1254,6 +1254,7 @@ struct window { struct event offset_timer; struct timeval activity_time; + struct timeval creation_time; struct window_pane *active; struct window_panes last_panes; diff --git a/window.c b/window.c index 31a8a8ee..60f2204a 100644 --- a/window.c +++ b/window.c @@ -330,6 +330,9 @@ window_create(u_int sx, u_int sy, u_int xpixel, u_int ypixel) RB_INSERT(windows, &windows, w); window_set_fill_character(w); + + if (gettimeofday(&w->creation_time, NULL) != 0) + fatal("gettimeofday failed"); window_update_activity(w); log_debug("%s: @%u create %ux%u (%ux%u)", __func__, w->id, sx, sy, From 7d41761e8441d38eea9d752d4c12ccdfeeb9991b Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 10 Feb 2026 10:02:11 +0000 Subject: [PATCH 13/15] Fix clients_calculate_size for manual type when window is NULL. From Elias Oenal in GitHub issue 4849. --- resize.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resize.c b/resize.c index 9a61c300..6cbc938d 100644 --- a/resize.c +++ b/resize.c @@ -250,7 +250,7 @@ skip: /* Return whether a suitable size was found. */ if (type == WINDOW_SIZE_MANUAL) { log_debug("%s: type is manual", __func__); - return (1); + return (w != NULL); } if (type == WINDOW_SIZE_LARGEST) { log_debug("%s: type is largest", __func__); From 5b455abecc334b614990052506373d01f59cec04 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 11 Feb 2026 08:23:21 +0000 Subject: [PATCH 14/15] Do not write before buffer when parsing empty clipboard or palette replies, or try to allocate zero bytes with an empty clipboard sequence. Reported by DongHan Kim. --- tty-keys.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tty-keys.c b/tty-keys.c index e3e20eec..361de3df 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -1377,6 +1377,10 @@ tty_keys_clipboard(struct tty *tty, const char *buf, size_t len, size_t *size) /* Convert from base64. */ needed = (end / 4) * 3; + if (needed == 0) { + free(copy); + return (0); + } out = xmalloc(needed); if ((outlen = b64_pton(copy, out, len)) == -1) { free(out); @@ -1612,8 +1616,10 @@ tty_keys_extended_device_attributes(struct tty *tty, const char *buf, } if (i == (sizeof tmp) - 1) return (-1); - tmp[i - 1] = '\0'; *size = 5 + i; + if (i == 0) + return (0); + tmp[i - 1] = '\0'; /* Add terminal features. */ if (strncmp(tmp, "iTerm2 ", 7) == 0) @@ -1686,11 +1692,13 @@ tty_keys_colours(struct tty *tty, const char *buf, size_t len, size_t *size, } if (i == (sizeof tmp) - 1) return (-1); + *size = 6 + i; + if (i == 0) + return (0); if (tmp[i - 1] == '\033') tmp[i - 1] = '\0'; else tmp[i] = '\0'; - *size = 6 + i; /* Work out the colour. */ n = colour_parseX11(tmp); @@ -1755,11 +1763,13 @@ tty_keys_palette(struct tty *tty, const char *buf, size_t len, size_t *size) } if (i == (sizeof tmp) - 1) return (-1); + *size = 5 + i; + if (i == 0) + return (0); if (tmp[i - 1] == '\033') tmp[i - 1] = '\0'; else tmp[i] = '\0'; - *size = 5 + i; /* Parse index. */ idx = strtol(tmp, &endptr, 10); From 7e50eb0e835cc052a5fdda1765b101a80c07a7d9 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 11 Feb 2026 08:30:37 +0000 Subject: [PATCH 15/15] Make paste_get_top return a copy of the buffer name which is more sensible and avoids a double free pointed out by DongHan Kim. --- cmd-set-buffer.c | 40 +++++++++++++++++++++------------------- paste.c | 4 ++-- tmux.h | 2 +- window-copy.c | 5 +++-- 4 files changed, 27 insertions(+), 24 deletions(-) diff --git a/cmd-set-buffer.c b/cmd-set-buffer.c index 0b0ec3a2..5b66dbee 100644 --- a/cmd-set-buffer.c +++ b/cmd-set-buffer.c @@ -58,29 +58,31 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item) struct args *args = cmd_get_args(self); struct client *tc = cmdq_get_target_client(item); struct paste_buffer *pb; - char *bufdata, *cause; - const char *bufname, *olddata; - size_t bufsize, newsize; + char *bufname, *bufdata = NULL, *cause = NULL; + const char *olddata; + size_t bufsize = 0, newsize; - bufname = args_get(args, 'b'); - if (bufname == NULL) + if (args_get(args, 'b') == NULL) pb = NULL; - else + else { + bufname = xstrdup(args_get(args, 'b')); pb = paste_get_name(bufname); + } if (cmd_get_entry(self) == &cmd_delete_buffer_entry) { if (pb == NULL) { if (bufname != NULL) { cmdq_error(item, "unknown buffer: %s", bufname); - return (CMD_RETURN_ERROR); + goto fail; } pb = paste_get_top(&bufname); } if (pb == NULL) { cmdq_error(item, "no buffer"); - return (CMD_RETURN_ERROR); + goto fail; } paste_free(pb); + free(bufname); return (CMD_RETURN_NORMAL); } @@ -88,32 +90,28 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item) if (pb == NULL) { if (bufname != NULL) { cmdq_error(item, "unknown buffer: %s", bufname); - return (CMD_RETURN_ERROR); + goto fail; } pb = paste_get_top(&bufname); } if (pb == NULL) { cmdq_error(item, "no buffer"); - return (CMD_RETURN_ERROR); + goto fail; } if (paste_rename(bufname, args_get(args, 'n'), &cause) != 0) { cmdq_error(item, "%s", cause); - free(cause); - return (CMD_RETURN_ERROR); + goto fail; } return (CMD_RETURN_NORMAL); } if (args_count(args) != 1) { cmdq_error(item, "no data specified"); - return (CMD_RETURN_ERROR); + goto fail; } if ((newsize = strlen(args_string(args, 0))) == 0) return (CMD_RETURN_NORMAL); - bufsize = 0; - bufdata = NULL; - if (args_has(args, 'a') && pb != NULL) { olddata = paste_buffer_data(pb, &bufsize); bufdata = xmalloc(bufsize); @@ -126,12 +124,16 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item) if (paste_set(bufdata, bufsize, bufname, &cause) != 0) { cmdq_error(item, "%s", cause); - free(bufdata); - free(cause); - return (CMD_RETURN_ERROR); + goto fail; } if (args_has(args, 'w') && tc != NULL) tty_set_selection(&tc->tty, "", bufdata, bufsize); return (CMD_RETURN_NORMAL); + +fail: + free(bufdata); + free(bufname); + free(cause); + return (CMD_RETURN_ERROR); } diff --git a/paste.c b/paste.c index a35bc4b3..509e2aa3 100644 --- a/paste.c +++ b/paste.c @@ -107,7 +107,7 @@ paste_is_empty(void) /* Get the most recent automatic buffer. */ struct paste_buffer * -paste_get_top(const char **name) +paste_get_top(char **name) { struct paste_buffer *pb; @@ -117,7 +117,7 @@ paste_get_top(const char **name) if (pb == NULL) return (NULL); if (name != NULL) - *name = pb->name; + *name = xstrdup(pb->name); return (pb); } diff --git a/tmux.h b/tmux.h index fdbf55c1..4c40cf83 100644 --- a/tmux.h +++ b/tmux.h @@ -2320,7 +2320,7 @@ time_t paste_buffer_created(struct paste_buffer *); const char *paste_buffer_data(struct paste_buffer *, size_t *); struct paste_buffer *paste_walk(struct paste_buffer *); int paste_is_empty(void); -struct paste_buffer *paste_get_top(const char **); +struct paste_buffer *paste_get_top(char **); struct paste_buffer *paste_get_name(const char *); void paste_free(struct paste_buffer *); void paste_add(const char *, char *, size_t); diff --git a/window-copy.c b/window-copy.c index 04f62643..f3a8507d 100644 --- a/window-copy.c +++ b/window-copy.c @@ -5052,9 +5052,9 @@ static void window_copy_append_selection(struct window_mode_entry *wme) { struct window_pane *wp = wme->wp; - char *buf; + char *buf, *bufname = NULL; struct paste_buffer *pb; - const char *bufdata, *bufname = NULL; + const char *bufdata; size_t len, bufsize; struct screen_write_ctx ctx; @@ -5079,6 +5079,7 @@ window_copy_append_selection(struct window_mode_entry *wme) } if (paste_set(buf, len, bufname, NULL) != 0) free(buf); + free(bufname); } static void