From f58b8d0d6abb2477b584547a4e72cc362ecbbcdb Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 8 Dec 2025 08:04:35 +0000 Subject: [PATCH 01/11] Setting working directory after fork means there is a race with pane_current_path (especially on platforms with systemd which have to take time to do some additional faffing around). To avoid this, change it before fork and back in the parent afterwards. GitHub issue 4719. --- spawn.c | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/spawn.c b/spawn.c index d06d3969..3b5fae26 100644 --- a/spawn.c +++ b/spawn.c @@ -213,7 +213,9 @@ spawn_pane(struct spawn_context *sc, char **cause) struct environ *child; struct environ_entry *ee; char **argv, *cp, **argvp, *argv0, *cwd, *new_cwd; - const char *cmd, *tmp; + char path[PATH_MAX]; + const char *cmd, *tmp, *home = find_home(); + const char *actual_cwd = NULL; int argc; u_int idx; struct termios now; @@ -368,6 +370,16 @@ spawn_pane(struct spawn_context *sc, char **cause) goto complete; } + /* Store current working directory and change to new one. */ + if (getcwd(path, sizeof path) != NULL) { + if (chdir(new_wp->cwd) == 0) + actual_cwd = new_wp->cwd; + else if (home != NULL && chdir(home) == 0) + actual_cwd = home; + else if (chdir("/") == 0) + actual_cwd = "/"; + } + /* Fork the new process. */ new_wp->pid = fdforkpty(ptm_fd, &new_wp->fd, new_wp->tty, NULL, &ws); if (new_wp->pid == -1) { @@ -383,22 +395,23 @@ spawn_pane(struct spawn_context *sc, char **cause) return (NULL); } - /* In the parent process, everything is done now. */ - if (new_wp->pid != 0) + /* + * In the parent process, everything is done now. Change the working + * directory back. + */ + if (new_wp->pid != 0) { + if (actual_cwd != NULL && + chdir(path) != 0 && + (home == NULL || chdir(home) != 0)) + chdir("/"); goto complete; + } /* - * Child process. Change to the working directory or home if that - * fails. + * Child process. Set PWD to the working directory. */ - if (chdir(new_wp->cwd) == 0) - environ_set(child, "PWD", 0, "%s", new_wp->cwd); - else if ((tmp = find_home()) != NULL && chdir(tmp) == 0) - environ_set(child, "PWD", 0, "%s", tmp); - else if (chdir("/") == 0) - environ_set(child, "PWD", 0, "/"); - else - fatal("chdir failed"); + if (actual_cwd != NULL) + environ_set(child, "PWD", 0, "%s", actual_cwd); /* * Update terminal escape characters from the session if available and From afa05ae15ed7129690e42a6f207b6488ff86bee9 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 8 Dec 2025 21:32:50 +0000 Subject: [PATCH 02/11] Use correct style for bottom line when pane status line is on, GitHub issue 4732. --- screen-redraw.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index db518708..0dda2fea 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -197,9 +197,11 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, } else { /* sb_pos == PANE_SCROLLBARS_RIGHT */ if ((wp->xoff == 0 || px >= wp->xoff) && (px <= ex || (sb_w != 0 && px < ex + sb_w))) { - if (wp->yoff != 0 && py == wp->yoff - 1) + if (pane_status != PANE_STATUS_BOTTOM && + wp->yoff != 0 && + py == wp->yoff - 1) return (SCREEN_REDRAW_BORDER_TOP); - if (py == ey) + if (pane_status != PANE_STATUS_TOP && py == ey) return (SCREEN_REDRAW_BORDER_BOTTOM); } } @@ -380,7 +382,6 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, /* Check if CELL_SCROLLBAR */ if (window_pane_show_scrollbar(wp, pane_scrollbars)) { - if (pane_status == PANE_STATUS_TOP) line = wp->yoff - 1; else From 52e2a7d990b8721e8ac2deba65f259b454f8d192 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 9 Dec 2025 08:13:59 +0000 Subject: [PATCH 03/11] Fix key code for M-BSpace, GitHub issue 4717. --- tty-keys.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tty-keys.c b/tty-keys.c index 7474621c..c59e6a68 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -909,9 +909,15 @@ first_key: * used. termios should have a better idea. */ bspace = tty->tio.c_cc[VERASE]; - if (bspace != _POSIX_VDISABLE && key == bspace) { - log_debug("%s: key %#llx is backspace", c->name, key); - key = KEYC_BSPACE; + if (bspace != _POSIX_VDISABLE) { + if (key == bspace) { + log_debug("%s: key %#llx is BSpace", c->name, key); + key = KEYC_BSPACE; + } + if (key == (bspace|KEYC_META)) { + log_debug("%s: key %#llx is M-BSpace", c->name, key); + key = KEYC_BSPACE|KEYC_META; + } } /* @@ -1304,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; - int outlen; + int outlen; struct input_request_clipboard_data cd; *size = 0; From 672e89a6407e248bfc0098bbdbea5d6d7dd09401 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 10 Dec 2025 21:24:43 +0000 Subject: [PATCH 04/11] Add a scroll-to-mouse command for copy mode to scroll to the mouse position and bind to the scrollbar, brings the scrollbar keys into line with the other mouse keys. From Michael Grant, GitHub issue 4731. --- key-bindings.c | 6 +++--- tmux.1 | 15 ++++++++++----- window-copy.c | 19 +++++++++++++++++++ 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/key-bindings.c b/key-bindings.c index a5e1f9c8..22e5cf59 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -480,9 +480,9 @@ key_bindings_init(void) "bind -n M-MouseDown3Pane { display-menu -t= -xM -yM -T '#[align=centre]#{pane_index} (#{pane_id})' " DEFAULT_PANE_MENU " }", /* Mouse on scrollbar. */ - "bind -n MouseDown1ScrollbarUp { copy-mode -u }", - "bind -n MouseDown1ScrollbarDown { copy-mode -d }", - "bind -n MouseDrag1ScrollbarSlider { copy-mode -S }", + "bind -n MouseDown1ScrollbarUp { if -Ft= '#{pane_in_mode}' { send -X page-up } {copy-mode -u } }", + "bind -n MouseDown1ScrollbarDown { if -Ft= '#{pane_in_mode}' { send -X page-down } {copy-mode -d } }", + "bind -n MouseDrag1ScrollbarSlider { if -Ft= '#{pane_in_mode}' { send -X scroll-to-mouse } { copy-mode -S } }", /* Copy mode (emacs) keys. */ "bind -Tcopy-mode C-Space { send -X begin-selection }", diff --git a/tmux.1 b/tmux.1 index dac3a648..dc1bfc00 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2211,6 +2211,13 @@ but also exit copy mode if the cursor reaches the bottom. Scroll so that the current line becomes the middle one while keeping the cursor on that line. .It Xo +.Ic scroll-to-mouse +.Xc +Scroll pane in copy-mode when bound to a mouse drag event. +.Fl e +causes copy mode to exit when at the bottom. +.Pp +.It Xo .Ic scroll-top .Xc Scroll down until the current line is at the top while keeping the cursor on @@ -2447,12 +2454,10 @@ cancels copy mode and any other modes. .Fl M begins a mouse drag (only valid if bound to a mouse key binding, see .Sx MOUSE SUPPORT ) . +.Pp .Fl S -scrolls when bound to a mouse drag event; for example, -.Ic copy-mode -Se -is bound to -.Ar MouseDrag1ScrollbarSlider -by default. +enters copy-mode and scrolls when bound to a mouse drag event; See +.Ic scroll-to-mouse . .Pp .Fl s copies from diff --git a/window-copy.c b/window-copy.c index 2d528496..f8700555 100644 --- a/window-copy.c +++ b/window-copy.c @@ -1479,6 +1479,20 @@ window_copy_cmd_scroll_middle(struct window_copy_cmd_state *cs) return (window_copy_cmd_scroll_to(cs, mid_value)); } +/* Scroll the pane to the mouse in the scrollbar. */ +static enum window_copy_cmd_action +window_copy_cmd_scroll_to_mouse(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct window_pane *wp = wme->wp; + struct client *c = cs->c; + struct mouse_event *m = cs->m; + int scroll_exit = args_has(cs->wargs, 'e'); + + window_copy_scroll(wp, c->tty.mouse_slider_mpos, m->y, scroll_exit); + return (WINDOW_COPY_CMD_NOTHING); +} + /* Scroll line containing the cursor to the top. */ static enum window_copy_cmd_action window_copy_cmd_scroll_top(struct window_copy_cmd_state *cs) @@ -3044,6 +3058,11 @@ static const struct { .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_scroll_middle }, + { .command = "scroll-to-mouse", + .args = { "e", 0, 0, NULL }, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_scroll_to_mouse + }, { .command = "scroll-top", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, From 21c27fdcaea6a8c679c557e38ec09b865f01bd62 Mon Sep 17 00:00:00 2001 From: bket Date: Thu, 11 Dec 2025 04:17:17 +0000 Subject: [PATCH 05/11] Simplify argument move using TAILQ_CONCAT() Replace the manual loop moving each argument from cmd->arguments to last->arguments with a single TAILQ_CONCAT() call. This makes the code clearer and more efficient, while preserving identical behavior. OK nicm@ --- cmd-parse.y | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/cmd-parse.y b/cmd-parse.y index 4cd8a420..215943b1 100644 --- a/cmd-parse.y +++ b/cmd-parse.y @@ -758,7 +758,7 @@ static int cmd_parse_expand_alias(struct cmd_parse_command *cmd, struct cmd_parse_input *pi, struct cmd_parse_result *pr) { - struct cmd_parse_argument *arg, *arg1, *first; + struct cmd_parse_argument *first; struct cmd_parse_commands *cmds; struct cmd_parse_command *last; char *alias, *name, *cause; @@ -798,10 +798,7 @@ cmd_parse_expand_alias(struct cmd_parse_command *cmd, TAILQ_REMOVE(&cmd->arguments, first, entry); cmd_parse_free_argument(first); - TAILQ_FOREACH_SAFE(arg, &cmd->arguments, entry, arg1) { - TAILQ_REMOVE(&cmd->arguments, arg, entry); - TAILQ_INSERT_TAIL(&last->arguments, arg, entry); - } + TAILQ_CONCAT(&last->arguments, &cmd->arguments, entry); cmd_parse_log_commands(cmds, __func__); pi->flags |= CMD_PARSE_NOALIAS; From 7abf3e8bdf32b6b98c44d282f50ca2a3fa845098 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 15 Dec 2025 21:21:25 +0000 Subject: [PATCH 06/11] Note that switch-client only changes the key table for one key, pointed out by Jeenu Viswambharan. --- tmux.1 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tmux.1 b/tmux.1 index dc1bfc00..f032a8de 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1661,9 +1661,10 @@ is used, option will not be applied. .Pp .Fl T -sets the client's key table; the next key from the client will be interpreted -from +sets the client's key table; the next key will be looked up using .Ar key-table . +After that key, the client is returned to its default key table (normally +.Em root ) . This may be used to configure multiple prefix keys, or to bind commands to sequences of keys. For example, to make typing From 99ed397e9c136c3d304f185e08d9d4df074aaf6a Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 17 Dec 2025 08:38:12 +0000 Subject: [PATCH 07/11] Make clock mode seconds synchronized to the second, GitHub issue 4760 from Joao Pedro. --- window-clock.c | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/window-clock.c b/window-clock.c index 51620d4a..81d33858 100644 --- a/window-clock.c +++ b/window-clock.c @@ -123,6 +123,26 @@ const char window_clock_table[14][5][5] = { { 1,0,0,0,1 } }, }; +static void +window_clock_start_timer(struct window_mode_entry *wme) +{ + struct window_clock_mode_data *data = wme->data; + struct timeval tv; + struct timespec ts; + long delay; + + clock_gettime(CLOCK_REALTIME, &ts); + delay = 1000000 - (ts.tv_nsec / 1000); + + tv.tv_sec = delay / 1000000; + tv.tv_usec = delay % 1000000; + if (tv.tv_sec < 0 || (tv.tv_sec == 0 && tv.tv_usec <= 0)) { + tv.tv_sec = 1; + tv.tv_usec = 0; + } + evtimer_add(&data->timer, &tv); +} + static void window_clock_timer_callback(__unused int fd, __unused short events, void *arg) { @@ -131,23 +151,20 @@ window_clock_timer_callback(__unused int fd, __unused short events, void *arg) struct window_clock_mode_data *data = wme->data; struct tm now, then; time_t t; - struct timeval tv = { .tv_sec = 1 }; evtimer_del(&data->timer); - evtimer_add(&data->timer, &tv); - - if (TAILQ_FIRST(&wp->modes) != wme) - return; t = time(NULL); gmtime_r(&t, &now); gmtime_r(&data->tim, &then); - if (now.tm_sec == then.tm_sec) - return; - data->tim = t; - window_clock_draw_screen(wme); - wp->flags |= PANE_REDRAW; + if (now.tm_sec != then.tm_sec) { + data->tim = t; + window_clock_draw_screen(wme); + wp->flags |= PANE_REDRAW; + } + + window_clock_start_timer(wme); } static struct screen * @@ -157,13 +174,12 @@ window_clock_init(struct window_mode_entry *wme, struct window_pane *wp = wme->wp; struct window_clock_mode_data *data; struct screen *s; - struct timeval tv = { .tv_sec = 1 }; wme->data = data = xmalloc(sizeof *data); data->tim = time(NULL); evtimer_set(&data->timer, window_clock_timer_callback, wme); - evtimer_add(&data->timer, &tv); + window_clock_start_timer(wme); s = &data->screen; screen_init(s, screen_size_x(&wp->base), screen_size_y(&wp->base), 0); From ce7eb22e3abacc0cb74124468b27a1e7d765ccef Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 17 Dec 2025 10:20:21 +0000 Subject: [PATCH 08/11] Do not have a default prompt cursor colour because some terminals (urxvt, st) do not support the reset sequence. GitHub issue 4759. --- options-table.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/options-table.c b/options-table.c index b328a57f..4385c9fd 100644 --- a/options-table.c +++ b/options-table.c @@ -959,7 +959,7 @@ const struct options_table_entry options_table[] = { { .name = "prompt-cursor-colour", .type = OPTIONS_TABLE_COLOUR, .scope = OPTIONS_TABLE_SESSION, - .default_num = 6, + .default_num = -1, .text = "Colour of the cursor when in the command prompt." }, From 1c7e164c22a3cb21d1e281087cd4aaa489d72b84 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 17 Dec 2025 11:49:29 +0000 Subject: [PATCH 09/11] Add support for applications to use synchronized output mode (DECSET 2026) to prevent screen tearing during rapid updates. When an application sends SM ?2026, tmux buffers output until RM ?2026 is received or a 1-second timeout expires. From Chris Lloyd with the assistance of Claude Code, GitHub issue 4744. --- format.c | 15 +++++++++++++++ input.c | 10 ++++++++++ screen-redraw.c | 3 +++ screen-write.c | 51 ++++++++++++++++++++++++++++++++++++++++++++++++- tmux.1 | 1 + tmux.h | 4 ++++ window.c | 4 ++++ 7 files changed, 87 insertions(+), 1 deletion(-) diff --git a/format.c b/format.c index afcf7535..ec55fc91 100644 --- a/format.c +++ b/format.c @@ -1947,6 +1947,18 @@ format_cb_origin_flag(struct format_tree *ft) return (NULL); } +/* Callback for synchronized_output_flag. */ +static void * +format_cb_synchronized_output_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & MODE_SYNC) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + /* Callback for pane_active. */ static void * format_cb_pane_active(struct format_tree *ft) @@ -3439,6 +3451,9 @@ static const struct format_table_entry format_table[] = { { "start_time", FORMAT_TABLE_TIME, format_cb_start_time }, + { "synchronized_output_flag", FORMAT_TABLE_STRING, + format_cb_synchronized_output_flag + }, { "tree_mode_format", FORMAT_TABLE_STRING, format_cb_tree_mode_format }, diff --git a/input.c b/input.c index ce888887..b3f67b23 100644 --- a/input.c +++ b/input.c @@ -898,6 +898,8 @@ input_free(struct input_ctx *ictx) evbuffer_free(ictx->since_ground); event_del(&ictx->ground_timer); + screen_write_stop_sync(ictx->wp); + free(ictx); } @@ -1897,6 +1899,11 @@ input_csi_dispatch_rm_private(struct input_ctx *ictx) case 2031: screen_write_mode_clear(sctx, MODE_THEME_UPDATES); break; + case 2026: /* synchronized output */ + screen_write_stop_sync(ictx->wp); + if (ictx->wp != NULL) + ictx->wp->flags |= PANE_REDRAW; + break; default: log_debug("%s: unknown '%c'", __func__, ictx->ch); break; @@ -1995,6 +2002,9 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx) case 2031: screen_write_mode_set(sctx, MODE_THEME_UPDATES); break; + case 2026: /* synchronized output */ + screen_write_start_sync(ictx->wp); + break; default: log_debug("%s: unknown '%c'", __func__, ictx->ch); break; diff --git a/screen-redraw.c b/screen-redraw.c index 0dda2fea..84ab17a6 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -898,6 +898,9 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) struct grid_cell defaults; u_int i, j, top, x, y, width; + if (wp->base.mode & MODE_SYNC) + screen_write_stop_sync(wp); + log_debug("%s: %s @%u %%%u", __func__, c->name, w->id, wp->id); if (wp->xoff + wp->sx <= ctx->ox || wp->xoff >= ctx->ox + ctx->sx) diff --git a/screen-write.c b/screen-write.c index 39c15b39..86eb45c2 100644 --- a/screen-write.c +++ b/screen-write.c @@ -894,6 +894,52 @@ screen_write_mode_clear(struct screen_write_ctx *ctx, int mode) log_debug("%s: %s", __func__, screen_mode_to_string(mode)); } +/* Sync timeout callback. */ +static void +screen_write_sync_callback(__unused int fd, __unused short events, void *arg) +{ + struct window_pane *wp = arg; + + log_debug("%s: %%%u sync timer expired", __func__, wp->id); + evtimer_del(&wp->sync_timer); + + if (wp->base.mode & MODE_SYNC) { + wp->base.mode &= ~MODE_SYNC; + wp->flags |= PANE_REDRAW; + } +} + +/* Start sync mode. */ +void +screen_write_start_sync(struct window_pane *wp) +{ + struct timeval tv = { .tv_sec = 1, .tv_usec = 0 }; + + if (wp == NULL) + return; + + wp->base.mode |= MODE_SYNC; + if (!event_initialized(&wp->sync_timer)) + evtimer_set(&wp->sync_timer, screen_write_sync_callback, wp); + evtimer_add(&wp->sync_timer, &tv); + + log_debug("%s: %%%u started sync mode", __func__, wp->id); +} + +/* Stop sync mode. */ +void +screen_write_stop_sync(struct window_pane *wp) +{ + if (wp == NULL) + return; + + if (event_initialized(&wp->sync_timer)) + evtimer_del(&wp->sync_timer); + wp->base.mode &= ~MODE_SYNC; + + log_debug("%s: %%%u stopped sync mode", __func__, wp->id); +} + /* Cursor up by ny. */ void screen_write_cursorup(struct screen_write_ctx *ctx, u_int ny) @@ -1692,6 +1738,9 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, u_int y, cx, cy, last, items = 0; struct tty_ctx ttyctx; + if (s->mode & MODE_SYNC) + return; + if (ctx->scrolled != 0) { log_debug("%s: scrolled %u (region %u-%u)", __func__, ctx->scrolled, s->rupper, s->rlower); @@ -1985,7 +2034,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) } /* Write to the screen. */ - if (!skip) { + if (!skip && !(s->mode & MODE_SYNC)) { if (selected) { screen_select_cell(s, &tmp_gc, gc); ttyctx.cell = &tmp_gc; diff --git a/tmux.1 b/tmux.1 index f032a8de..6e5b70f7 100644 --- a/tmux.1 +++ b/tmux.1 @@ -6262,6 +6262,7 @@ The following variables are available, where appropriate: .It Li "socket_path" Ta "" Ta "Server socket path" .It Li "sixel_support" Ta "" Ta "1 if server has support for SIXEL" .It Li "start_time" Ta "" Ta "Server start time" +.It Li "synchronized_output_flag" Ta "" Ta "1 if pane has synchronized output enabled" .It Li "uid" Ta "" Ta "Server UID" .It Li "user" Ta "" Ta "Server user" .It Li "version" Ta "" Ta "Server version" diff --git a/tmux.h b/tmux.h index 9cd11609..180208e3 100644 --- a/tmux.h +++ b/tmux.h @@ -643,6 +643,7 @@ enum tty_code_code { #define MODE_CURSOR_BLINKING_SET 0x20000 #define MODE_KEYS_EXTENDED_2 0x40000 #define MODE_THEME_UPDATES 0x80000 +#define MODE_SYNC 0x100000 #define ALL_MODES 0xffffff #define ALL_MOUSE_MODES (MODE_MOUSE_STANDARD|MODE_MOUSE_BUTTON|MODE_MOUSE_ALL) @@ -1190,6 +1191,7 @@ struct window_pane { struct window_pane_resizes resize_queue; struct event resize_timer; + struct event sync_timer; struct input_ctx *ictx; @@ -3099,6 +3101,8 @@ void screen_write_preview(struct screen_write_ctx *, struct screen *, u_int, void screen_write_backspace(struct screen_write_ctx *); void screen_write_mode_set(struct screen_write_ctx *, int); void screen_write_mode_clear(struct screen_write_ctx *, int); +void screen_write_start_sync(struct window_pane *); +void screen_write_stop_sync(struct window_pane *); void screen_write_cursorup(struct screen_write_ctx *, u_int); void screen_write_cursordown(struct screen_write_ctx *, u_int); void screen_write_cursorright(struct screen_write_ctx *, u_int); diff --git a/window.c b/window.c index 80bc57c2..24bc5936 100644 --- a/window.c +++ b/window.c @@ -990,6 +990,8 @@ window_pane_destroy(struct window_pane *wp) if (event_initialized(&wp->resize_timer)) event_del(&wp->resize_timer); + if (event_initialized(&wp->sync_timer)) + event_del(&wp->sync_timer); TAILQ_FOREACH_SAFE(r, &wp->resize_queue, entry, r1) { TAILQ_REMOVE(&wp->resize_queue, r, entry); free(r); @@ -1069,6 +1071,8 @@ window_pane_resize(struct window_pane *wp, u_int sx, u_int sy) if (sx == wp->sx && sy == wp->sy) return; + screen_write_stop_sync(wp); + r = xmalloc(sizeof *r); r->sx = sx; r->sy = sy; From fe645c5bcc6d0814ed2a2e2bfb85099bea0b233f Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Thu, 18 Dec 2025 09:05:37 +0000 Subject: [PATCH 10/11] Fix image scrolling at exact screen height, and add more logging for images. --- image.c | 54 +++++++++++++++++++++++++++++++++++++++++--------- screen-write.c | 2 +- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/image.c b/image.c index 6e2b1701..0948925b 100644 --- a/image.c +++ b/image.c @@ -27,11 +27,36 @@ static struct images all_images = TAILQ_HEAD_INITIALIZER(all_images); static u_int all_images_count; #define MAX_IMAGE_COUNT 20 +static void printflike(3, 4) +image_log(struct image *im, const char* from, const char* fmt, ...) +{ + va_list ap; + char s[128]; + + if (log_get_level() == 0) + return; + + if (fmt == NULL) { + log_debug("%s: %p (%ux%u %u,%u)", from, im, im->sx, im->sy, + im->px, im->py); + return; + } + + va_start(ap, fmt); + vsnprintf(s, sizeof s, fmt, ap); + va_end(ap); + + log_debug("%s: %p (%ux%u %u,%u): %s", from, im, im->sx, im->sy, + im->px, im->py, s); +} + static void image_free(struct image *im) { struct screen *s = im->s; + image_log(im, __func__, NULL); + TAILQ_REMOVE(&all_images, im, all_entry); all_images_count--; @@ -47,6 +72,8 @@ image_free_all(struct screen *s) struct image *im, *im1; int redraw = !TAILQ_EMPTY(&s->images); + if (redraw) + log_debug ("%s", __func__); TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) image_free(im); return (redraw); @@ -109,6 +136,7 @@ image_store(struct screen *s, struct sixel_image *si) image_fallback(&im->fallback, im->sx, im->sy); + image_log(im, __func__, NULL); TAILQ_INSERT_TAIL(&s->images, im, entry); TAILQ_INSERT_TAIL(&all_images, im, all_entry); @@ -122,10 +150,12 @@ int image_check_line(struct screen *s, u_int py, u_int ny) { struct image *im, *im1; - int redraw = 0; + int redraw = 0, in; TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) { - if (py + ny > im->py && py < im->py + im->sy) { + in = (py + ny > im->py && py < im->py + im->sy); + image_log(im, __func__, "py=%u, ny=%u, in=%d", py, ny, in); + if (in) { image_free(im); redraw = 1; } @@ -137,15 +167,18 @@ int image_check_area(struct screen *s, u_int px, u_int py, u_int nx, u_int ny) { struct image *im, *im1; - int redraw = 0; + int redraw = 0, in; TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) { - if (py + ny <= im->py || py >= im->py + im->sy) - continue; - if (px + nx <= im->px || px >= im->px + im->sx) - continue; - image_free(im); - redraw = 1; + in = (py < im->py + im->sy && + py + ny > im->py && + px < im->px + im->sx && + px + nx > im->px); + image_log(im, __func__, "py=%u, ny=%u, in=%d", py, ny, in); + if (in) { + image_free(im); + redraw = 1; + } } return (redraw); } @@ -160,17 +193,20 @@ image_scroll_up(struct screen *s, u_int lines) TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) { if (im->py >= lines) { + image_log(im, __func__, "1, lines=%u", lines); im->py -= lines; redraw = 1; continue; } if (im->py + im->sy <= lines) { + image_log(im, __func__, "2, lines=%u", lines); image_free(im); redraw = 1; continue; } sx = im->sx; sy = (im->py + im->sy) - lines; + image_log(im, __func__, "3, lines=%u, sy=%u", lines, sy); new = sixel_scale(im->data, 0, 0, 0, im->sy - sy, sx, sy, 1); sixel_free(im->data); diff --git a/screen-write.c b/screen-write.c index 105a8afb..c95a9786 100644 --- a/screen-write.c +++ b/screen-write.c @@ -2388,7 +2388,7 @@ screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si, } sy = screen_size_y(s) - cy; - if (sy < y) { + if (sy <= y) { lines = y - sy + 1; if (image_scroll_up(s, lines) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; From 54202fcaad727cadadc934a43d7138f0f9517d9f Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Thu, 18 Dec 2025 13:44:17 +0000 Subject: [PATCH 11/11] Check image size against available screen size properly, GitHub issue 4739. --- screen-write.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/screen-write.c b/screen-write.c index c95a9786..d25462df 100644 --- a/screen-write.c +++ b/screen-write.c @@ -2367,8 +2367,11 @@ screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si, u_int x, y, sx, sy, cx = s->cx, cy = s->cy, i, lines; struct sixel_image *new; + if (screen_size_y(s) == 1) + return; + sixel_size_in_cells(si, &x, &y); - if (x > screen_size_x(s) || y > screen_size_y(s)) { + if (x > screen_size_x(s) || y > screen_size_y(s) - 1) { if (x > screen_size_x(s) - cx) sx = screen_size_x(s) - cx; else @@ -2379,12 +2382,10 @@ screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si, sy = y; new = sixel_scale(si, 0, 0, 0, y - sy, sx, sy, 1); sixel_free(si); - si = new; - - /* Bail out if the image cannot be scaled. */ - if (si == NULL) + if (new == NULL) return; - sixel_size_in_cells(si, &x, &y); + sixel_size_in_cells(new, &x, &y); + si = new; } sy = screen_size_y(s) - cy;