From 36bc3515573cd1d607199fdc2c926b8f6c3c045d Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 22 May 2026 09:21:32 +0000 Subject: [PATCH 1/7] Use a union for the data passed around in tty_ctx instead of void *. --- screen-write.c | 36 ++++++++++++++++++------------------ tmux.h | 17 ++++++++++++++--- tty.c | 33 +++++++++++++++++---------------- 3 files changed, 49 insertions(+), 37 deletions(-) diff --git a/screen-write.c b/screen-write.c index c5ae0d36..41135963 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1121,7 +1121,7 @@ screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) grid_view_insert_cells(s->grid, s->cx, s->cy, nx, bg); screen_write_collect_flush(ctx, 0, __func__); - ttyctx.num = nx; + ttyctx.n = nx; tty_write(tty_cmd_insertcharacter, &ttyctx); } @@ -1149,7 +1149,7 @@ screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) grid_view_delete_cells(s->grid, s->cx, s->cy, nx, bg); screen_write_collect_flush(ctx, 0, __func__); - ttyctx.num = nx; + ttyctx.n = nx; tty_write(tty_cmd_deletecharacter, &ttyctx); } @@ -1177,7 +1177,7 @@ screen_write_clearcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) grid_view_clear(s->grid, s->cx, s->cy, nx, 1, bg); screen_write_collect_flush(ctx, 0, __func__); - ttyctx.num = nx; + ttyctx.n = nx; tty_write(tty_cmd_clearcharacter, &ttyctx); } @@ -1204,7 +1204,7 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) grid_view_insert_lines(gd, s->cy, ny, bg); screen_write_collect_flush(ctx, 0, __func__); - ttyctx.num = ny; + ttyctx.n = ny; tty_write(tty_cmd_insertline, &ttyctx); return; } @@ -1224,7 +1224,7 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) screen_write_collect_flush(ctx, 0, __func__); - ttyctx.num = ny; + ttyctx.n = ny; tty_write(tty_cmd_insertline, &ttyctx); } @@ -1252,7 +1252,7 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) grid_view_delete_lines(gd, s->cy, ny, bg); screen_write_collect_flush(ctx, 0, __func__); - ttyctx.num = ny; + ttyctx.n = ny; tty_write(tty_cmd_deleteline, &ttyctx); return; } @@ -1271,7 +1271,7 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) grid_view_delete_lines_region(gd, s->rlower, s->cy, ny, bg); screen_write_collect_flush(ctx, 0, __func__); - ttyctx.num = ny; + ttyctx.n = ny; tty_write(tty_cmd_deleteline, &ttyctx); } @@ -1492,7 +1492,7 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg) grid_view_scroll_region_down(gd, s->rupper, s->rlower, bg); screen_write_collect_flush(ctx, 0, __func__); - ttyctx.num = lines; + ttyctx.n = lines; tty_write(tty_cmd_scrolldown, &ttyctx); } @@ -1757,7 +1757,7 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, ctx->scrolled = s->rlower - s->rupper + 1; screen_write_initctx(ctx, &ttyctx, 1); - ttyctx.num = ctx->scrolled; + ttyctx.n = ctx->scrolled; ttyctx.bg = ctx->bg; tty_write(tty_cmd_scrollup, &ttyctx); @@ -1785,15 +1785,15 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, if (ci->type == CLEAR) { screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = ci->bg; - ttyctx.num = ci->used; + ttyctx.n = ci->used; tty_write(tty_cmd_clearcharacter, &ttyctx); } else { screen_write_initctx(ctx, &ttyctx, 0); ttyctx.cell = &ci->gc; if (ci->wrapped) ttyctx.flags |= TTY_CTX_WRAPPED; - ttyctx.ptr = cl->data + ci->x; - ttyctx.num = ci->used; + ttyctx.data.data = cl->data + ci->x; + ttyctx.data.size = ci->used; tty_write(tty_cmd_cells, &ttyctx); } items++; @@ -2081,7 +2081,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) /* Create space for character in insert mode. */ if (s->mode & MODE_INSERT) { screen_write_collect_flush(ctx, 0, __func__); - ttyctx.num = width; + ttyctx.n = width; tty_write(tty_cmd_insertcharacter, &ttyctx); } @@ -2293,9 +2293,9 @@ screen_write_setselection(struct screen_write_ctx *ctx, const char *clip, struct tty_ctx ttyctx; screen_write_initctx(ctx, &ttyctx, 0); - ttyctx.ptr = str; - ttyctx.ptr2 = (void *)clip; - ttyctx.num = len; + ttyctx.sel.clip = clip; + ttyctx.sel.data = str; + ttyctx.sel.size = len; tty_write(tty_cmd_setselection, &ttyctx); } @@ -2310,8 +2310,8 @@ screen_write_rawstring(struct screen_write_ctx *ctx, u_char *str, u_int len, screen_write_initctx(ctx, &ttyctx, 0); if (allow_invisible_panes) ttyctx.flags |= TTY_CTX_INVISIBLE_PANES; - ttyctx.ptr = str; - ttyctx.num = len; + ttyctx.data.data = str; + ttyctx.data.size = len; tty_write(tty_cmd_rawstring, &ttyctx); } diff --git a/tmux.h b/tmux.h index bba8beaa..d21a9e93 100644 --- a/tmux.h +++ b/tmux.h @@ -1704,9 +1704,20 @@ struct tty_ctx { #define TTY_CTX_CELL_DRAW_LINE 0x20 #define TTY_CTX_CELL_INVALIDATE 0x40 - u_int num; - void *ptr; - void *ptr2; + union { + u_int n; + + struct { + const char *data; + size_t size; + } data; + + struct { + const char *clip; + const char *data; + size_t size; + } sel; + }; /* * Cursor and region position before the screen was updated - this is diff --git a/tty.c b/tty.c index 883bea60..4966f432 100644 --- a/tty.c +++ b/tty.c @@ -1555,7 +1555,7 @@ tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx) tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy); - tty_emulate_repeat(tty, TTYC_ICH, TTYC_ICH1, ctx->num); + tty_emulate_repeat(tty, TTYC_ICH, TTYC_ICH1, ctx->n); } void @@ -1578,7 +1578,7 @@ tty_cmd_deletecharacter(struct tty *tty, const struct tty_ctx *ctx) tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy); - tty_emulate_repeat(tty, TTYC_DCH, TTYC_DCH1, ctx->num); + tty_emulate_repeat(tty, TTYC_DCH, TTYC_DCH1, ctx->n); } void @@ -1587,7 +1587,7 @@ tty_cmd_clearcharacter(struct tty *tty, const struct tty_ctx *ctx) tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, ctx->s->hyperlinks); - tty_clear_pane_line(tty, ctx, ctx->ocy, ctx->ocx, ctx->num, ctx->bg); + tty_clear_pane_line(tty, ctx, ctx->ocy, ctx->ocx, ctx->n, ctx->bg); } void @@ -1614,7 +1614,7 @@ tty_cmd_insertline(struct tty *tty, const struct tty_ctx *ctx) tty_margin_off(tty); tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy); - tty_emulate_repeat(tty, TTYC_IL, TTYC_IL1, ctx->num); + tty_emulate_repeat(tty, TTYC_IL, TTYC_IL1, ctx->n); tty->cx = tty->cy = UINT_MAX; } @@ -1642,7 +1642,7 @@ tty_cmd_deleteline(struct tty *tty, const struct tty_ctx *ctx) tty_margin_off(tty); tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy); - tty_emulate_repeat(tty, TTYC_DL, TTYC_DL1, ctx->num); + tty_emulate_repeat(tty, TTYC_DL, TTYC_DL1, ctx->n); tty->cx = tty->cy = UINT_MAX; } @@ -1775,19 +1775,19 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); - if (ctx->num == 1 || !tty_term_has(tty->term, TTYC_INDN)) { + if (ctx->n == 1 || !tty_term_has(tty->term, TTYC_INDN)) { if (!tty_use_margin(tty)) tty_cursor(tty, 0, tty->rlower); else tty_cursor(tty, tty->rright, tty->rlower); - for (i = 0; i < ctx->num; i++) + for (i = 0; i < ctx->n; i++) tty_putc(tty, '\n'); } else { if (tty->cy == UINT_MAX) tty_cursor(tty, 0, 0); else tty_cursor(tty, 0, tty->cy); - tty_putcode_i(tty, TTYC_INDN, ctx->num); + tty_putcode_i(tty, TTYC_INDN, ctx->n); } } @@ -1818,9 +1818,9 @@ tty_cmd_scrolldown(struct tty *tty, const struct tty_ctx *ctx) tty_cursor_pane(tty, ctx, ctx->ocx, ctx->orupper); if (tty_term_has(tty->term, TTYC_RIN)) - tty_putcode_i(tty, TTYC_RIN, ctx->num); + tty_putcode_i(tty, TTYC_RIN, ctx->n); else { - for (i = 0; i < ctx->num; i++) + for (i = 0; i < ctx->n; i++) tty_putcode(tty, TTYC_RI); } } @@ -1970,14 +1970,15 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) struct visible_ranges *r; struct visible_range *rr; u_int i, px, py, cx; - char *cp = ctx->ptr; + const char *cp = ctx->data.data; + size_t n = ctx->data.size; - if (!tty_is_visible(tty, ctx, ctx->ocx, ctx->ocy, ctx->num, 1)) + if (!tty_is_visible(tty, ctx, ctx->ocx, ctx->ocy, n, 1)) return; if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) && (ctx->xoff + ctx->ocx < ctx->wox || - ctx->xoff + ctx->ocx + ctx->num > ctx->wox + ctx->wsx)) { + ctx->xoff + ctx->ocx + n > ctx->wox + ctx->wsx)) { if ((~ctx->flags & TTY_CTX_WRAPPED) || !tty_full_width(tty, ctx) || (tty->term->flags & TERM_NOAM) || @@ -2000,7 +2001,7 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) px = ctx->xoff + ctx->ocx - ctx->wox; py = ctx->yoff + ctx->ocy - ctx->woy; - r = tty_check_overlay_range(tty, px, py, ctx->num); + r = tty_check_overlay_range(tty, px, py, n); for (i = 0; i < r->used; i++) { rr = &r->ranges[i]; if (rr->nx != 0) { @@ -2014,7 +2015,7 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_setselection(struct tty *tty, const struct tty_ctx *ctx) { - tty_set_selection(tty, ctx->ptr2, ctx->ptr, ctx->num); + tty_set_selection(tty, ctx->sel.clip, ctx->sel.data, ctx->sel.size); } void @@ -2043,7 +2044,7 @@ void tty_cmd_rawstring(struct tty *tty, const struct tty_ctx *ctx) { tty->flags |= TTY_NOBLOCK; - tty_add(tty, ctx->ptr, ctx->num); + tty_add(tty, ctx->data.data, ctx->data.size); tty_invalidate(tty); } From 285a3b752230f4612c53f58aa42556e6c3d5a32e Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Fri, 22 May 2026 12:44:08 +0100 Subject: [PATCH 2/7] Fix up SIXEL with recent changes. --- screen-write.c | 2 +- tmux.h | 4 ++++ tty.c | 14 ++++++++------ 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/screen-write.c b/screen-write.c index 97a11998..97714a5a 100644 --- a/screen-write.c +++ b/screen-write.c @@ -2463,7 +2463,7 @@ screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si, screen_write_collect_flush(ctx, 0, __func__); screen_write_initctx(ctx, &ttyctx, 0); - ttyctx.ptr = image_store(s, si); + ttyctx.image = image_store(s, si); tty_write(tty_cmd_sixelimage, &ttyctx); diff --git a/tmux.h b/tmux.h index 50092e90..869f84eb 100644 --- a/tmux.h +++ b/tmux.h @@ -1754,6 +1754,10 @@ struct tty_ctx { const char *data; size_t size; } sel; + +#ifdef ENABLE_SIXEL + struct image *image; +#endif }; /* diff --git a/tty.c b/tty.c index a11c5af3..ea1705d3 100644 --- a/tty.c +++ b/tty.c @@ -1479,9 +1479,11 @@ tty_set_client_cb(struct tty_ctx *ttyctx, struct client *c) if (wp->layout_cell == NULL) return (0); - /* Set the properties relevant to the current client. */ - ttyctx->bigger = tty_window_offset(&c->tty, &ttyctx->wox, &ttyctx->woy, - &ttyctx->wsx, &ttyctx->wsy); + if (tty_window_offset(&c->tty, &ttyctx->wox, &ttyctx->woy, &ttyctx->wsx, + &ttyctx->wsy)) + ttyctx->flags |= TTY_CTX_WINDOW_BIGGER; + else + ttyctx->flags &= ~TTY_CTX_WINDOW_BIGGER; ttyctx->yoff = ttyctx->ryoff = wp->yoff; if (status_at_line(c) == 0) @@ -1510,10 +1512,10 @@ tty_draw_images(struct client *c, struct window_pane *wp, struct screen *s) ttyctx.sx = wp->sx; ttyctx.sy = wp->sy; - ttyctx.ptr = im; + ttyctx.image = im; ttyctx.arg = wp; ttyctx.set_client_cb = tty_set_client_cb; - ttyctx.allow_invisible_panes = 1; + ttyctx.flags |= TTY_CTX_INVISIBLE_PANES; tty_write_one(tty_cmd_sixelimage, c, &ttyctx); } } @@ -2122,7 +2124,7 @@ tty_cmd_rawstring(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_sixelimage(struct tty *tty, const struct tty_ctx *ctx) { - struct image *im = ctx->ptr; + struct image *im = ctx->image; struct sixel_image *si = im->data; struct sixel_image *new; char *data; From d45a9cad8c5d77ab9fe384f70bc4a98a5c7d2420 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 22 May 2026 11:55:43 +0000 Subject: [PATCH 3/7] Work out if a pane is obscured by another one when drawing. --- screen-write.c | 89 +++++++++++++++++++++++++++++++++++--------------- tmux.h | 3 ++ 2 files changed, 66 insertions(+), 26 deletions(-) diff --git a/screen-write.c b/screen-write.c index 41135963..c9a3fad7 100644 --- a/screen-write.c +++ b/screen-write.c @@ -172,10 +172,43 @@ screen_write_set_client_cb(struct tty_ctx *ttyctx, struct client *c) return (1); } +/* Return 1 if there is a floating window pane overlapping this pane. */ +static int +screen_write_pane_is_obscured(struct screen_write_ctx *ctx) +{ + struct window_pane *wp = ctx->wp; + + if (ctx->wp == NULL) + return (0); + + if (ctx->flags & SCREEN_WRITE_CHECKED_IF_OBSCURED) { + if (ctx->flags & SCREEN_WRITE_OBSCURED) + return (1); + return (0); + } + ctx->flags |= SCREEN_WRITE_CHECKED_IF_OBSCURED; + + while ((wp = TAILQ_PREV(wp, window_panes, zentry)) != NULL) { + if ((wp->flags & PANE_FLOATING) && + ((wp->yoff >= ctx->wp->yoff && + wp->yoff <= ctx->wp->yoff + (int)ctx->wp->sy) || + (wp->yoff + (int)wp->sy >= ctx->wp->yoff && + wp->yoff + wp->sy <= ctx->wp->yoff + ctx->wp->sy)) && + ((wp->xoff >= ctx->wp->xoff && + wp->xoff <= ctx->wp->xoff + (int)ctx->wp->sx) || + (wp->xoff + (int)wp->sx >= ctx->wp->xoff && + wp->xoff + wp->sx <= ctx->wp->xoff + ctx->wp->sx))) { + ctx->flags |= SCREEN_WRITE_OBSCURED; + return (1); + } + } + return (0); +} + /* Set up context for TTY command. */ static void screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, - int sync) + int is_sync, int check_obscured) { struct screen *s = ctx->s; @@ -190,6 +223,9 @@ screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, ttyctx->orlower = s->rlower; ttyctx->orupper = s->rupper; + if (check_obscured && screen_write_pane_is_obscured(ctx)) + ttyctx->flags |= TTY_CTX_PANE_OBSCURED; + memcpy(&ttyctx->defaults, &grid_default_cell, sizeof ttyctx->defaults); if (ctx->init_ctx_cb != NULL) { ctx->init_ctx_cb(ctx, ttyctx); @@ -221,7 +257,7 @@ screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, else { if (ctx->wp == NULL) ttyctx->flags |= TTY_CTX_OVERLAY_SYNC; - if (sync) + if (is_sync) ttyctx->flags |= TTY_CTX_SYNC; } tty_write(tty_cmd_syncstart, ttyctx); @@ -588,7 +624,7 @@ screen_write_fast_copy(struct screen_write_ctx *ctx, struct screen *src, break; s->cx = cx; if (wp != NULL) - screen_write_initctx(ctx, &ttyctx, 0); + screen_write_initctx(ctx, &ttyctx, 0, 0); for (xx = px; xx < px + nx; xx++) { gl = grid_get_line(gd, yy); sgl = grid_get_line(ctx->s->grid, s->cy); @@ -1091,7 +1127,7 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx) s->rupper = 0; s->rlower = screen_size_y(s) - 1; - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 1); screen_write_collect_clear(ctx, 0, screen_size_y(s) - 1); tty_write(tty_cmd_alignmenttest, &ttyctx); @@ -1115,7 +1151,7 @@ screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) if (s->cx > screen_size_x(s) - 1) return; - screen_write_initctx(ctx, &ttyctx, 0); + screen_write_initctx(ctx, &ttyctx, 0, 1); ttyctx.bg = bg; grid_view_insert_cells(s->grid, s->cx, s->cy, nx, bg); @@ -1143,7 +1179,7 @@ screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) if (s->cx > screen_size_x(s) - 1) return; - screen_write_initctx(ctx, &ttyctx, 0); + screen_write_initctx(ctx, &ttyctx, 0, 1); ttyctx.bg = bg; grid_view_delete_cells(s->grid, s->cx, s->cy, nx, bg); @@ -1171,7 +1207,7 @@ screen_write_clearcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) if (s->cx > screen_size_x(s) - 1) return; - screen_write_initctx(ctx, &ttyctx, 0); + screen_write_initctx(ctx, &ttyctx, 0, 0); ttyctx.bg = bg; grid_view_clear(s->grid, s->cx, s->cy, nx, 1, bg); @@ -1198,7 +1234,7 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) if (ny == 0) return; - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 1); ttyctx.bg = bg; grid_view_insert_lines(gd, s->cy, ny, bg); @@ -1214,7 +1250,7 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) if (ny == 0) return; - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 1); ttyctx.bg = bg; if (s->cy < s->rupper || s->cy > s->rlower) @@ -1246,7 +1282,7 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) if (ny == 0) return; - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 1); ttyctx.bg = bg; grid_view_delete_lines(gd, s->cy, ny, bg); @@ -1262,7 +1298,7 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) if (ny == 0) return; - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 1); ttyctx.bg = bg; if (s->cy < s->rupper || s->cy > s->rlower) @@ -1382,10 +1418,11 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg) struct tty_ctx ttyctx; if (s->cy == s->rupper) { + grid_view_scroll_region_down(s->grid, s->rupper, s->rlower, bg); screen_write_collect_flush(ctx, 0, __func__); - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 0); ttyctx.bg = bg; tty_write(tty_cmd_reverseindex, &ttyctx); @@ -1480,7 +1517,7 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg) struct tty_ctx ttyctx; u_int i; - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 1); ttyctx.bg = bg; if (lines == 0) @@ -1512,7 +1549,7 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg) struct tty_ctx ttyctx; u_int sx = screen_size_x(s), sy = screen_size_y(s); - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 1); ttyctx.bg = bg; /* Scroll into history if it is enabled and clearing entire screen. */ @@ -1541,7 +1578,7 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg) struct tty_ctx ttyctx; u_int sx = screen_size_x(s); - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 1); ttyctx.bg = bg; if (s->cy > 0) @@ -1564,7 +1601,7 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg) struct tty_ctx ttyctx; u_int sx = screen_size_x(s), sy = screen_size_y(s); - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 1); ttyctx.bg = bg; /* Scroll into history if it is enabled. */ @@ -1594,7 +1631,7 @@ screen_write_fullredraw(struct screen_write_ctx *ctx) screen_write_collect_flush(ctx, 0, __func__); - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 0); if (ttyctx.redraw_cb != NULL) ttyctx.redraw_cb(&ttyctx); } @@ -1756,7 +1793,7 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, if (ctx->scrolled > s->rlower - s->rupper + 1) ctx->scrolled = s->rlower - s->rupper + 1; - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 1); ttyctx.n = ctx->scrolled; ttyctx.bg = ctx->bg; tty_write(tty_cmd_scrollup, &ttyctx); @@ -1783,12 +1820,12 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, } screen_write_set_cursor(ctx, ci->x, y); if (ci->type == CLEAR) { - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 0); ttyctx.bg = ci->bg; ttyctx.n = ci->used; tty_write(tty_cmd_clearcharacter, &ttyctx); } else { - screen_write_initctx(ctx, &ttyctx, 0); + screen_write_initctx(ctx, &ttyctx, 0, 0); ttyctx.cell = &ci->gc; if (ci->wrapped) ttyctx.flags |= TTY_CTX_WRAPPED; @@ -2006,7 +2043,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) /* Sanity check cursor position. */ if (s->cx > sx - width || s->cy > sy - 1) return; - screen_write_initctx(ctx, &ttyctx, 0); + screen_write_initctx(ctx, &ttyctx, 0, 0); /* Handle overwriting of UTF-8 characters. */ gl = grid_get_line(s->grid, s->grid->hsize + s->cy); @@ -2203,7 +2240,7 @@ screen_write_combine(struct screen_write_ctx *ctx, const struct grid_cell *gc) * and what it is going to do now. */ screen_write_set_cursor(ctx, cx - n, cy); - screen_write_initctx(ctx, &ttyctx, 0); + screen_write_initctx(ctx, &ttyctx, 0, 0); ttyctx.cell = &last; if (force_wide) ttyctx.flags |= TTY_CTX_CELL_INVALIDATE; @@ -2292,7 +2329,7 @@ screen_write_setselection(struct screen_write_ctx *ctx, const char *clip, { struct tty_ctx ttyctx; - screen_write_initctx(ctx, &ttyctx, 0); + screen_write_initctx(ctx, &ttyctx, 0, 0); ttyctx.sel.clip = clip; ttyctx.sel.data = str; ttyctx.sel.size = len; @@ -2307,7 +2344,7 @@ screen_write_rawstring(struct screen_write_ctx *ctx, u_char *str, u_int len, { struct tty_ctx ttyctx; - screen_write_initctx(ctx, &ttyctx, 0); + screen_write_initctx(ctx, &ttyctx, 0, 0); if (allow_invisible_panes) ttyctx.flags |= TTY_CTX_INVISIBLE_PANES; ttyctx.data.data = str; @@ -2335,7 +2372,7 @@ screen_write_alternateon(struct screen_write_ctx *ctx, struct grid_cell *gc, server_redraw_window_borders(wp->window); } - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 0); if (ttyctx.redraw_cb != NULL) ttyctx.redraw_cb(&ttyctx); } @@ -2359,7 +2396,7 @@ screen_write_alternateoff(struct screen_write_ctx *ctx, struct grid_cell *gc, server_redraw_window_borders(wp->window); } - screen_write_initctx(ctx, &ttyctx, 1); + screen_write_initctx(ctx, &ttyctx, 1, 0); if (ttyctx.redraw_cb != NULL) ttyctx.redraw_cb(&ttyctx); } diff --git a/tmux.h b/tmux.h index d21a9e93..c20286c2 100644 --- a/tmux.h +++ b/tmux.h @@ -1021,6 +1021,8 @@ struct screen_write_ctx { int flags; #define SCREEN_WRITE_SYNC 0x1 +#define SCREEN_WRITE_OBSCURED 0x2 +#define SCREEN_WRITE_CHECKED_IF_OBSCURED 0x4 screen_write_init_ctx_cb init_ctx_cb; void *arg; @@ -1703,6 +1705,7 @@ struct tty_ctx { #define TTY_CTX_OVERLAY_SYNC 0x10 #define TTY_CTX_CELL_DRAW_LINE 0x20 #define TTY_CTX_CELL_INVALIDATE 0x40 +#define TTY_CTX_PANE_OBSCURED 0x80 union { u_int n; From 55fbacb46922d50cf4fe5545d7f5df6a36f8919c Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Fri, 22 May 2026 13:07:54 +0100 Subject: [PATCH 4/7] Fix merge error. --- screen-write.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/screen-write.c b/screen-write.c index 88413ca0..3708c5cd 100644 --- a/screen-write.c +++ b/screen-write.c @@ -2144,8 +2144,8 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, ttyctx.cell = &ci->gc; if (ci->wrapped) ttyctx.flags |= TTY_CTX_WRAPPED; - ttyctx.ptr = cl->data + wr_start; - ttyctx.n = wr_length; + ttyctx.data.data = cl->data + wr_start; + ttyctx.data.size = wr_length; tty_write(tty_cmd_cells, &ttyctx); } items++; From 143a1770552c89034f94c0f15985b5d48c299456 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 22 May 2026 15:22:43 +0000 Subject: [PATCH 5/7] Tighten up read-only checks on attach-session, detach-client and switch-client so that a user should be able to only detach their own client. Reported by John Walker. --- cmd-attach-session.c | 11 ++++++++++- cmd-detach-client.c | 8 ++++++++ cmd-switch-client.c | 10 ++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/cmd-attach-session.c b/cmd-attach-session.c index 4e2d15da..32b1e0cb 100644 --- a/cmd-attach-session.c +++ b/cmd-attach-session.c @@ -61,6 +61,7 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag, struct window_pane *wp; char *cwd, *cause; enum msgtype msgtype; + uid_t uid; if (RB_EMPTY(&sessions)) { cmdq_error(item, "no sessions"); @@ -106,8 +107,16 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag, } if (fflag) server_client_set_flags(c, fflag); - if (rflag) + if (rflag) { + if (c->flags & CLIENT_READONLY) { + uid = proc_get_peer_uid(c->peer); + if (uid != getuid()) { + cmdq_error(item, "client is read-only"); + return (CMD_RETURN_ERROR); + } + } c->flags |= (CLIENT_READONLY|CLIENT_IGNORESIZE); + } c->last_session = c->session; if (c->session != NULL) { diff --git a/cmd-detach-client.c b/cmd-detach-client.c index 661293ae..b5a4cf1c 100644 --- a/cmd-detach-client.c +++ b/cmd-detach-client.c @@ -59,6 +59,7 @@ cmd_detach_client_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = cmd_get_args(self); struct cmd_find_state *source = cmdq_get_source(item); + struct client *c = cmdq_get_client(item); struct client *tc = cmdq_get_target_client(item), *loop; struct session *s; enum msgtype msgtype; @@ -69,6 +70,13 @@ cmd_detach_client_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_NORMAL); } + if (c->flags & CLIENT_READONLY) { + if (args_has(args, 's') || args_has(args, 'a') || c != tc) { + cmdq_error(item, "client is read-only"); + return (CMD_RETURN_ERROR); + } + } + if (args_has(args, 'P')) msgtype = MSG_DETACHKILL; else diff --git a/cmd-switch-client.c b/cmd-switch-client.c index b0a0d928..9865ac52 100644 --- a/cmd-switch-client.c +++ b/cmd-switch-client.c @@ -20,6 +20,7 @@ #include #include +#include #include "tmux.h" @@ -53,6 +54,7 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item) const char *tflag = args_get(args, 't'); enum cmd_find_type type; int flags; + struct client *c = cmdq_get_client(item); struct client *tc = cmdq_get_target_client(item); struct session *s; struct winlink *wl; @@ -61,6 +63,7 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item) const char *tablename; struct key_table *table; struct sort_criteria sort_crit; + uid_t uid; if (tflag != NULL && (tflag[strcspn(tflag, ":.%")] != '\0' || strcmp(tflag, "=") == 0)) { @@ -77,6 +80,13 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item) wp = target.wp; if (args_has(args, 'r')) { + if (tc->flags & CLIENT_READONLY) { + uid = proc_get_peer_uid(c->peer); + if (uid != getuid()) { + cmdq_error(item, "client is read-only"); + return (CMD_RETURN_ERROR); + } + } if (tc->flags & CLIENT_READONLY) tc->flags &= ~(CLIENT_READONLY|CLIENT_IGNORESIZE); else From 29a1a1f8b0c9ef3923c26e3ae9dd8ec8668350d1 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Fri, 22 May 2026 18:03:01 +0100 Subject: [PATCH 6/7] Check overlay/BCE first to avoid walking panes unless needed. --- tty.c | 63 ++++++++++++++++++++++++++++++----------------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/tty.c b/tty.c index df14f670..7c064b0d 100644 --- a/tty.c +++ b/tty.c @@ -1004,10 +1004,7 @@ tty_window_offset1(struct tty *tty, u_int *ox, u_int *oy, u_int *sx, u_int *sy) else if (cy > w->sy - *sy) *oy = w->sy - *sy; else - /* cy-sy/2 was causing panned panes to scroll - * when the cursor was half way down the pane. - */ - *oy = cy - *sy + 1; /* cy - *sy / 2; */ + *oy = cy - *sy + 1; } c->pan_window = NULL; @@ -1096,17 +1093,14 @@ tty_redraw_region(struct tty *tty, const struct tty_ctx *ctx) * If region is large, schedule a redraw. In most cases this is likely * to be followed by some more scrolling. */ - log_debug("%s: %s orlower=%u orupper=%u sy=%u large=%d", __func__, - c->name, ctx->orlower, ctx->orupper, ctx->sy, - tty_large_region(tty, ctx)); if (tty_large_region(tty, ctx) && ~ctx->flags & TTY_CTX_PANE_OBSCURED) { log_debug("%s: %s large region redraw", __func__, c->name); ctx->redraw_cb(ctx); return; } - log_debug("%s: %s small redraw, drawing rows %u-%u", __func__, - c->name, ctx->orupper, ctx->orlower); + log_debug("%s: %s small region redraw (%u-%u)", __func__, c->name, + ctx->orupper, ctx->orlower); for (i = ctx->orupper; i <= ctx->orlower; i++) tty_draw_pane(tty, ctx, i); } @@ -1233,7 +1227,7 @@ tty_clear_pane_line(struct tty *tty, const struct tty_ctx *ctx, u_int py, if (tty_clamp_line(tty, ctx, px, py, nx, &l, &x, &rx, &ry)) { r = tty_check_overlay_range(tty, x, ry, rx); - for (i=0; i < r->used; i++) { + for (i = 0; i < r->used; i++) { ri = &r->ranges[i]; if (ri->nx == 0) continue; @@ -1316,7 +1310,7 @@ tty_clear_area(struct tty *tty, const struct tty_ctx *ctx, u_int py, struct window_pane *wpl, *wp = ctx->arg; struct visible_ranges *r; struct visible_range *ri; - u_int i, yy, overlap = 0, oy = 0; + u_int i, yy, region = 1, oy = 0; char tmp[64]; log_debug("%s: %s, %u,%u at %u,%u", __func__, c->name, nx, ny, px, py); @@ -1325,23 +1319,30 @@ tty_clear_area(struct tty *tty, const struct tty_ctx *ctx, u_int py, if (nx == 0 || ny == 0) return; - /* Verify there's nothing overlapping in z-index before using BCE. */ - TAILQ_FOREACH(wpl, &w->z_index, zentry) { - if (wpl == wp || ~wpl->flags & PANE_FLOATING) - continue; - if ((int)wpl->xoff - 1 > (int)(px + nx) || - wpl->xoff + (int)wpl->sx + 1 < (int)px) - continue; - if ((int)wpl->yoff - 1 > (int)(py + ny) || - wpl->yoff + (int)wpl->sy + 1 < (int)py) - continue; - overlap++; - if (overlap > 0) break; + /* + * If there is an overlay or BCE is not available, cannot clear as a + * region. + */ + if (c->overlay_check != NULL || tty_fake_bce(tty, defaults, bg)) + region = 0; + else { + /* Any overlapping pane also means no region. */ + TAILQ_FOREACH(wpl, &w->z_index, zentry) { + if (wpl == wp || ~wpl->flags & PANE_FLOATING) + continue; + if ((int)wpl->xoff - 1 > (int)(px + nx) || + wpl->xoff + (int)wpl->sx + 1 < (int)px) + continue; + if ((int)wpl->yoff - 1 > (int)(py + ny) || + wpl->yoff + (int)wpl->sy + 1 < (int)py) + continue; + region = 0; + break; + } } - /* If genuine BCE is available, can try escape sequences. */ - if (!overlap && c->overlay_check == NULL && - !tty_fake_bce(tty, defaults, bg)) { + /* Clear as a region if possible. */ + if (region) { /* Use ED if clearing off the bottom of the terminal. */ if (px == 0 && px + nx >= tty->sx && @@ -1392,15 +1393,15 @@ tty_clear_area(struct tty *tty, const struct tty_ctx *ctx, u_int py, } } - if (c->session->statusat == 0) - oy = c->session->statuslines; - /* Couldn't use an escape sequence, loop over the lines. */ + if (c->session->statusat == 0) + oy = c->session->statuslines; for (yy = py; yy < py + ny; yy++) { r = tty_check_overlay_range(tty, px, yy - oy, nx); - for (i=0; i < r->used; i++) { + for (i = 0; i < r->used; i++) { ri = &r->ranges[i]; - if (ri->nx == 0) continue; + if (ri->nx == 0) + continue; tty_clear_line(tty, defaults, yy, ri->px, ri->nx, bg); } } From b8e0004ff23784c2988e0bb080ff7c592ec8fb84 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Fri, 22 May 2026 18:33:04 +0100 Subject: [PATCH 7/7] Fix missing arguments. --- screen-write.c | 2 +- tmux.h | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/screen-write.c b/screen-write.c index 6c24c27b..f166e5d8 100644 --- a/screen-write.c +++ b/screen-write.c @@ -2498,7 +2498,7 @@ screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si, } screen_write_collect_flush(ctx, 0, __func__); - screen_write_initctx(ctx, &ttyctx, 0); + screen_write_initctx(ctx, &ttyctx, 0, 0); ttyctx.image = image_store(s, si); tty_write(tty_cmd_sixelimage, &ttyctx); diff --git a/tmux.h b/tmux.h index 8e999942..db772fea 100644 --- a/tmux.h +++ b/tmux.h @@ -2739,11 +2739,9 @@ void tty_cmd_scrolldown(struct tty *, const struct tty_ctx *); void tty_cmd_reverseindex(struct tty *, const struct tty_ctx *); void tty_cmd_setselection(struct tty *, const struct tty_ctx *); void tty_cmd_rawstring(struct tty *, const struct tty_ctx *); - #ifdef ENABLE_SIXEL void tty_cmd_sixelimage(struct tty *, const struct tty_ctx *); #endif - void tty_cmd_syncstart(struct tty *, const struct tty_ctx *); void tty_default_colours(struct grid_cell *, struct window_pane *);