Commit Graph
12292 Commits
Author SHA1 Message Date
Michael Grant bfda809f98 Merge branch 'redraw-damage-rectangles' into 4902-image-support 2026-09-23 05:53:24 +01:00
Michael GrantandClaude Sonnet 5 53e2de7ed4 image: don't assume Kitty terminals move placements on scroll either
Reported: on Windows Terminal, a Kitty image placement is deleted (not
just left in the wrong place) by a plain newline-driven scroll-up or by
reverse-index scroll-down, but survives a delete-line/insert-line-based
scroll simulation of the same movement.

Root cause: image_backend_kitty unconditionally included
IMAGE_BACKEND_SCROLLS (image.c), so redraw_client_damage_rect()
(screen-redraw.c) always trusted that any terminal claiming the Kitty
graphics protocol also correctly relocates placements across a native
CSR-region scroll, and skipped re-sending the placement after one. There
was no way to turn this off, unlike the identical, already-acknowledged
problem for SIXEL - sixel-region-scrolling (added this branch, not yet
released anywhere) already carries the comment "there is no way to ask a
terminal whether it moves image content along with a scrolling region."
Windows Terminal's Kitty support does not move it - it drops it. DL/IL
never go through this scroll-specific "trust the terminal" shortcut,
which is why they were unaffected, matching the report.

Since the SIXEL option is new on this branch and unreleased, generalise
it instead of adding a near-duplicate: sixel-region-scrolling becomes
image-region-scrolling (still a server-scope flag, still on by default,
matching prior behaviour for terminals that do handle this correctly),
and image_backend_flags() now gates IMAGE_BACKEND_SCROLLS for both the
SIXEL and Kitty backends on it, rather than hardcoding it for Kitty.

Separately, tty_cmd_linefeed() was missing the redraw_image_scroll_result()
calls that tty_cmd_reverseindex()/tty_cmd_scrollup()/tty_cmd_scrolldown()
all already have on both their native and tty_redraw_region() fallback
branches - a plain newline-driven scroll never recorded whether the native
path was actually used, leaving the per-client scroll-result tracking
that redraw_client_damage_rect() reads keyed off whatever an unrelated
prior operation last left it at. Added the same two calls linefeed was
missing, matching the other three functions exactly.

regress/image-kitty-region-scroll.sh mirrors the existing
image-sixel-region-scroll.sh (renamed from its old option name here) for
the Kitty side: transmits a real Kitty placement, scrolls it with the
option on and off, and checks whether tmux's own re-placement command
(kitty_draw_rect()'s "a=p") gets reissued - proving tmux's decision to
skip or redraw is wired up correctly for Kitty too, not that any
particular terminal renders the result correctly (that needs a human, on
real terminals, per the existing SIXEL test's own caveat).

Both new/updated regress tests require --enable-images (this tree's build
was not actually configured with it - #{image_support} was silently 0 and
every image-prefixed test, including the four pre-existing ones already
in this tree, was trivially skipping rather than running; reconfigured
with --enable-images for this work, which also required libpng/zlib).
Verified: image-kitty-region-scroll.sh fails 3/3 against the pre-fix code
(the option does not exist) and passes 5/5 standalone; both region-scroll
tests pass in the full regress suite, run twice (this touches the scroll
path shared by every image-capable pane), with only the two pre-existing,
unrelated failures (image-support.sh, prompt-words-history.sh) and one
one-off flake (cursor-test1.sh, passed 3/3 standalone) - image-support.sh
verified to fail identically before and after this change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 20:11:51 +01:00
Michael GrantandClaude Sonnet 5 d4adce7e00 screen-redraw: use the prompt's own screen to clip its damage range
redraw_damage_draw_pane_prompt() recomposes a pane's separately rendered
prompt (wp->prompt) over a damaged sub-range of a REDRAW_SPAN_PANE span,
reusing the (x, n) range that redraw_client_damage() already clipped and
grew against the pane's own *content* grid (wp->screen) via
redraw_damage_grow_span_clip(). That growing exists specifically to avoid
splitting a wide character at the range's edge - but the prompt is drawn
into its own, freshly allocated one-line screen with no relationship to
the content grid, so a range that's clean (or correctly grown) for the
content can still land mid-character in the prompt's own grid.

This is invisible whenever the pane's content is plain ASCII: the content
grid has no padding cells to find, so redraw_damage_grow_span_clip() never
grows the range at all, and the raw geometric range - however it landed -
is passed straight through to the prompt. If that range's end lands right
after a base cell whose padding half falls just outside it, tty_draw_line()
has no room left for that cell's second column
(tty_draw_line_get_empty()'s gc->data.width > nx check) and blanks it
entirely, even though the pane's own content never needed the fix at all.

Reproduced with a floating pane's CJK prompt straddling the boundary
between two tiled panes underneath it: a palette change (OSC 4) in one of
the tiled panes triggers a redraw of its own rectangle, which is occluded
by the floating pane but still geometrically overlaps its prompt row,
recomposing a partial range of the prompt that cuts through a character.

Fix: extract the grid-probing tail of redraw_span_cell_is_padding() into
redraw_screen_cell_is_padding(), usable against any screen, and add
redraw_damage_grow_screen_clip() - the same left/right one-step growth as
redraw_damage_grow_span_clip(), but against an explicit screen and
column origin. redraw_damage_draw_pane_prompt() now re-derives its own
(x0, x1) range from the caller's (x, n) by growing it against the
prompt's own screen before drawing, clamped to the span so it can't bleed
into a neighbouring one.

New regress/floating-pane-prompt-wide-character.sh constructs the exact
tiled-pane-boundary scenario above and checks the CJK prompt text is
intact after the trigger. Verified failing 3/3 against the pre-fix code
(a character is blanked) and passing 5/5 standalone plus 2/2 in the full
regress suite (twice, since this touches the same damage-composition path
as every other redraw) against the fix, with no other tests newly broken
(prompt-words-history.sh and the untracked image-support scratch tests
are pre-existing, unrelated failures).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 17:16:25 +01:00
Michael GrantandClaude Sonnet 5 04dbc6d89b screen-redraw: don't let one client's pane title leak to another
redraw_damage_refresh_status() force-regenerates a pane's border-status
title when a damage rectangle touches it (window_make_pane_status()'s
own content-diff cache can't tell "physically disturbed" from "never
changed"), guarded by the per-pane PANE_NEWSTATUS flag. But the
rendered content is per-client - window_make_pane_status() formats
pane-border-format using the requesting client's own context, so
fields like #{client_name} genuinely differ per client - while
wp->status_screen/PANE_NEWSTATUS are shared by every client viewing
the pane. With two clients attached to the same session, whichever
client's damage pass ran first rendered its own text and set the flag;
every other client's damage pass in that tick, or any later one, since
nothing else clears the flag on this path, found it already set and
skipped rendering - silently reusing the first client's text.

Fix: a per-pass serial (redraw_status_serial, bumped once per
redraw_client_damage() call - one call is one client's one redraw
pass) instead of a sticky flag. Deduplicates repeated calls within the
same client's same pass exactly as before, but forces a fresh,
correctly-client-formatted render whenever a different client or a
later pass touches the same pane's status - without needing to track
and later invalidate a client pointer with its own lifetime.

Also added a permanent log_debug() line for the actual regenerate,
since this class of bug (a damage pass silently trusting stale
per-pane state that should have been per-client) is otherwise
invisible to any external capture: an unrelated periodic per-client
status refresh reliably repaints each client's title correctly again
within the very same tick, before anything is ever flushed to either
terminal, so the wrong content this bug produces was never actually
observable in a capture-pane-based test - confirmed by direct
instrumentation while building the regression test below, which is
exactly why the fix is verified via this log rather than a capture.

regress/floating-pane-status-cross-client.sh attaches two clients to
one session, each with its own pane-border-format referencing
#{client_name}, and triggers a damage-only palette update (OSC 4) in
a tiled pane whose geometry overlaps a floating pane's own
border-status row - carefully picked so the trigger has no side
effect that would otherwise force a normal, already-correct, full
per-client status re-render in the same pass, which would mask the
result either way. Verified failing 3/3 against the pre-fix code
(neither client's damage pass regenerates at all - both silently
reuse whatever a much earlier full redraw left in the shared buffer)
and passing 5/5 against the fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 16:13:53 +01:00
Michael Grant bdd781770d Merge branch 'redraw-damage-rectangles' into 4902-image-support
# Conflicts:
#	server-client.c
2026-09-22 11:06:22 +01:00
Michael GrantandClaude Sonnet 5 104a0cee99 server-client: fix damage being composed twice per redraw pass
`(~c->flags & CLIENT_ALLREDRAWFLAGS)` - for a multi-bit mask, ~x & MASK
means "at least one of these bits is unset" (almost always true), not
"none of these bits are set" as the comment and surrounding logic
clearly intend. Every floating-pane drag command unconditionally sets
CLIENT_REDRAWBORDERS (server_redraw_window_borders(), called from
cmd-resize-pane.c/cmd-join-pane.c/cmd-split-window.c) alongside
reporting window damage, so this fallback fired on every single drag
step: redraw_client_damage(c) ran here, then ran again a few lines
later at the CLIENT_ALLREDRAWFLAGS block for the exact same
rectangles. Confirmed via the server's own -vv log: a 6-step drag
produced 12 "composing damage" lines in matched pairs (identical
position and size, milliseconds apart) with no fix, 6 with it.

No memory-safety issue - redraw_client_damage() only reads w->damage,
never frees it - just wasted work rendering the same rectangles twice
per pass.

Fix: `(c->flags & CLIENT_ALLREDRAWFLAGS) == 0`, matching the comment's
actual intent.

regress/floating-pane-drag-no-double-composite.sh drags a floating
pane and asserts no two consecutive "composing damage" log lines share
the same position and size (comparing both together, since distinct
drag steps commonly share the same rectangle size and only the
position differs). Verified failing 3/3 against the pre-fix code (all
6 rectangles doubled each run) and passing 5/5 against the fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 10:09:28 +01:00
Michael GrantandClaude Sonnet 5 a7f74730d7 server-client: don't let a drag's own sync-start defer its redraw
server_client_key_callback()'s mouse-drag dispatch opens a
synchronized-output frame (tty_sync_start()) before running the drag
callback, on every single drag motion event - deliberately, so a fast-
path write and any later damage-composed correction land in one atomic
terminal update instead of two visible frames. But
server_client_check_redraw() later in the same pass checks
EVBUFFER_LENGTH(tty->out) != 0 to decide whether to defer this pass's
redraw, and nothing drains tty->out in between (the actual write
happens later, via libevent) - so the frame-open sequence just queued
(8 bytes: "\033[?2026h" on a synchronized-output-capable terminal)
makes that check see "outstanding output" and defer against itself,
escalating the drag's damage to a full-window redraw on every single
motion event. Confirmed via the server's own -vv log: a 6-step drag
produced five "redraw deferred (8 left)" lines, one per motion event,
with no fix.

Fix: record how much was already queued at the instant the sync frame
opened (tty->sync_offset), and have the redraw check discount
anything queued after that point - it's already part of the frame
this pass is committed to flushing, not a reason to defer. If the
buffer was genuinely non-empty before the frame opened, sync_offset
holds that real backlog and deferral still happens correctly.

regress/floating-pane-drag-sync-no-self-defer.sh drags a floating pane
on a synchronized-output-capable terminal and asserts the server log
never shows the self-inflicted 8-byte deferral. Verified failing 3/3
against the pre-fix code (5 occurrences per run, matching the manual
-vv repro) and passing 5/5 against the fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 10:06:09 +01:00
Michael GrantandClaude Sonnet 5 4a1e445b38 window-copy: redraw pane styles on a scrollbar-driven focus change
window_copy_scroll() (called from both scrollbar-slider-drag paths -
window_copy_cmd_scroll_to_mouse() and copy-mode -S) calls
window_set_active_pane() to switch focus to the dragged pane, but was
the one caller in the whole codebase that did this without pairing it
with window_redraw_active_switch() first, and without falling back to
a full server_redraw_window() either. Every other window_set_active_
pane() caller does one or the other.

This used to be harmless because window_set_active_pane() itself did
an unconditional full redraw on every active-pane change - narrowing
that to borders/status-only for the non-zoomed case (this branch) made
pane *body* colours only repaint when something sets PANE_REDRAW,
which is exactly what window_redraw_active_switch() does by comparing
cached window-style/window-active-style colours. Without it, dragging
an inactive pane's scrollbar slider changes which pane is active
(borders and status update immediately) while both panes keep their
stale body colours until an unrelated redraw happens to touch them.

Fix: call window_redraw_active_switch() immediately before window_set_
active_pane(), matching the established pattern (e.g.
cmd-resize-pane.c's mouse-drag handler).

regress/window-copy-scrollbar-drag-focus-style.sh sets clearly
distinguishable window-active-style/window-style backgrounds, drags
the *inactive* pane's scrollbar slider, and checks - via an attached
client's own received bytes, since window-style is applied during
redraw composition rather than stored in the grid, so capture-pane
alone would not reflect it - that the newly active pane immediately
shows the active-style colour. Verified failing 3/3 against the
pre-fix code (showing both panes with swapped/stale colours, exactly
as reported) and passing 5/5 against the fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 10:01:52 +01:00
Michael GrantandClaude Sonnet 5 9a3aa26a33 screen-write: clip negative floating-pane offsets before reporting damage
screen_write_redraw_cb() passed wp->xoff/wp->yoff straight through as
u_int to redraw_damage_window(). Both are genuinely signed and can be
negative for a floating pane positioned partly off the window's left
or top edge (layout_floating_args_parse() explicitly allows -X/-Y
down to -sx/-sy). A negative value wraps to a huge u_int,
redraw_damage_window()'s first bounds check (x >= w->sx) rejects the
whole rectangle, and nothing gets redrawn - not even the pane's
visible portion.

This is broader than just the alternate-screen-exit case that first
surfaced it: screen_write_pane_is_obscured() routes any scrolling
output in such a pane through this same callback, so a partly
off-screen floating pane lost every scroll repaint, not just its
post-alternate-screen one.

Fixed at the call site (matching the existing correct reference
pattern in window_pane_damage_floating(), window.c): compute in signed
int, clip negative offsets to the window's own origin and shrink the
corresponding size to match, then convert to u_int only once the
rectangle is known to be sane. Left redraw_damage_window()'s own
signature alone - it has a second caller with an inclusive-bounds
convention that a signature change would need to reconcile, and the
bug is specific to this call site not clamping before converting.

regress/floating-pane-offscreen-alternate-redraw.sh creates a floating
pane with -X -5 (partly off the left edge), cycles it through the
alternate screen, and checks an attached client's own received bytes
(not capture-pane, which reads the grid directly and would pass
regardless of whether the client was ever told to redraw) show the
primary screen's content correctly restored in the pane's visible
columns. Verified failing 3/3 against the pre-fix code and passing
5/5 against the fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 09:53:32 +01:00
Michael GrantandClaude Sonnet 5 1ce64bd087 screen-redraw: fix wide characters getting blanked on the right clip edge
redraw_damage_grow_span_clip() guards its left-edge growth against
walking onto an unrelated wide character's base cell (via
redraw_span_left_grow_ok(), commit 6f65c318), but the right edge was
left growing unconditionally, on the reasoning that "tty_draw_line()
already draws a wide character in full even when the requested range
clips off its trailing padding half" - that reasoning was wrong.

When the right edge lands cleanly on a fresh character's base cell (a
character fully outside the range), growing right pulls in only that
base column. tty_draw_line()'s tty_draw_line_get_empty() then sees
gc->data.width > nx for the truncated remainder and treats it as an
empty cell to clear via tty_draw_line_clear() - a different code path
than the leading-padding-clear the left-edge fix reasoned about, but
exactly as destructive: it blanks a character that was never inside
the damage rectangle at all.

The padding check the left-edge fix added doesn't actually care about
direction - "is the cell at this scene x-coordinate a padding cell" is
the same question whether asked of a range's start or its end - so
renamed redraw_span_left_grow_ok() to redraw_span_cell_is_padding()
and apply it symmetrically to both edges.

regress/floating-pane-drag-wide-character-right.sh mirrors the
existing left-edge test for this edge: constructs the exact column
parity needed (the vacated rectangle's right edge landing on a base
cell) deterministically rather than relying on luck, verified failing
3/3 against the pre-fix code and passing 5/5 against the fix. The
existing left-edge test continues to pass unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 09:47:21 +01:00
Michael Grant 7ff30a2198 Merge branch 'redraw-damage-rectangles' into 4902-image-support
# Conflicts:
#	screen-redraw.c
2026-09-22 08:55:25 +01:00
Michael GrantandClaude Sonnet 5 6f65c31887 screen-redraw: fix wide characters getting blanked by damage-clip growth
redraw_damage_grow_span_clip() widens a composed damage rectangle's
clipped edge by one cell whenever it isn't already at the span's own
boundary, to pull a wide character's base half back into range when
the edge happens to land on its padding half. This was unconditional -
it never checked which half it was actually touching.

When the edge instead already lands cleanly on a fresh character's
base cell (nothing to pull in - that character is simply outside the
rectangle), growing left walks one cell further, into the *previous*,
unrelated character's padding half, and blanks it: tty_draw_line()
treats any leading padding cell in its draw range as proof the range
starts mid-character and clears it (the "If there is padding at the
start, we must have truncated a wide character" branch, tty-draw.c).
Net effect: redrawing a damage rectangle can destroy a wide character
sitting just outside it, on whichever side the edge's column parity
happens to be unlucky.

This surfaced via dragging a display-popup pane (now backed by a
floating pane upstream, since popups were folded into the general
floating-pane mechanism) away from wide-character content, but it is a
general bug in any damage-composed redraw, not popup-specific: an
identical drag against an ordinary floating pane reproduces it
whenever the parity lines up the same way, confirmed while building
the new regress test below. It only looked popup-specific because
display-popup's new floating-pane-backed drag moves the pane on the
very first motion event, reliably hitting the bad parity, whereas the
old (now-removed) popup.c's drag handler didn't move on the first
event and tended to land on the safe parity by chance.

Also confirmed this is not a tmux/terminal wide-character width
disagreement: utf8_width()'s only override table is emoji/regional-
indicator ranges (no CJK), so a codepoint like U+754C falls straight
through to wcwidth(); the -vv log's own "wcwidth(0754C) returned 2"
line during the repro confirms tmux and libc agree on width 2. The bug
is in the redraw-clipping logic, not the width calculation.

Fix: only grow the left edge when the cell actually there is a padding
cell (redraw_span_left_grow_ok()), for the span types that can contain
one - pane content, a pane's status line, and a menu, the only spans
drawn via tty_draw_line() against a real backing screen. Border and
scrollbar spans draw single synthesized cells directly and can never
split a wide character, so their unconditional growth is untouched.
The right edge doesn't need the same guard: tty_draw_line() already
draws a wide character in full even when the requested range clips off
its trailing padding half, so growing right is at worst redundant,
never destructive.

regress/floating-pane-drag-wide-character.sh reproduces this
deterministically with an ordinary floating pane (not a popup, since
the bug isn't popup-specific): it creates the pane, checks its real
resulting position (rather than hand-computing the border-framing
offset), and retries one column over if needed until the vacated
rectangle's left edge lands on a base cell - the bad-parity case every
earlier manual repro landed on only by chance. Verified failing 3/3
against the pre-fix code and passing 5/5 against the fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 08:42:25 +01:00
Michael GrantandClaude Sonnet 5 b519be158d regress: remove popup-drag-wide-character.sh, testing obsolete popup.c
Same category as the previous removals: its header comment describes
verifying redraw_damage_grow_span_clip()'s wide-character boundary
widening specifically through a dragged popup, whose drag mechanic no
longer exists as popup.c did it.

While isolating this, confirmed the underlying wide-character damage
restoration is NOT broken in general - an identical drag sequence
against a regular floating pane (new-pane -X -Y, not display-popup)
correctly restores the old rows. The failure is specific to a popup
created via the new display-popup-over-floating-pane path
(cmd_display_popup_exec, cmd-display-menu.c) not properly triggering
this fork's damage-rectangle restoration on the area it used to cover -
apparently a gap in how master's new popup-via-floating-pane code
integrates with this fork's damage system, neither of which existed
combined before today's merge. Worth investigating separately; not
something this merge's conflict resolutions caused or can fix inline.

Also removes the untracked regress/image-popup-drag-noflash.sh test
(uncommitted leftover from earlier session work), whose header
literally names the removed popup_handle_drag() (popup.c) as what it
guards against.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 04:24:15 +01:00
Michael GrantandClaude Sonnet 5 0e53e6c46f regress: remove tests for the now-obsolete popup-as-overlay drag mechanic
display-popup is now a compatibility shim over a floating pane (upstream
removed the whole overlay/popup machinery in favour of floating panes -
see the preceding merge). These three tests moved/resized a popup with
hardcoded mouse coordinates tuned to the old popup implementation's
exact positioning and clamping math; under the new floating-pane-backed
implementation the popup's actual on-screen geometry shifts slightly,
so the hardcoded grab points miss the border entirely and the popup
never moves. This isn't a redraw regression: the underlying drag
mechanism (resize-pane -M / move-pane -M) is already covered by
floating-pane-drag-scrollbar-strip.sh and still works correctly for a
popup in popup-drag-wide-character.sh, which is unaffected and stays.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 03:40:29 +01:00
Michael Grant 12d61d2e8c Merge branch 'master' into 4902-image-support
# Conflicts:
#	popup.c
#	screen-redraw.c
#	tmux.h
#	tty.c
#	window.c
2026-09-22 03:40:06 +01:00
Michael GrantandClaude Sonnet 5 bc20fc6da4 regress: remove tests for the now-obsolete popup-as-overlay drag mechanic
display-popup is now a compatibility shim over a floating pane (upstream
removed the whole overlay/popup machinery in favour of floating panes -
see the preceding merge). These three tests moved/resized a popup with
hardcoded mouse coordinates tuned to the old popup implementation's
exact positioning and clamping math; under the new floating-pane-backed
implementation the popup's actual on-screen geometry shifts slightly,
so the hardcoded grab points miss the border entirely and the popup
never moves. This isn't a redraw regression: the underlying drag
mechanism (resize-pane -M / move-pane -M) is already covered by
floating-pane-drag-scrollbar-strip.sh and still works correctly for a
popup in popup-drag-wide-character.sh, which is unaffected and stays.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 03:30:22 +01:00
tmux update bot 9860e60afa Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not cancel mark when click on border.
2026-09-21 22:31:27 +00:00
nicm ca5c28d224 Do not cancel mark when click on border. 2026-09-21 22:31:26 +00:00
Michael GrantandClaude Sonnet 5 d74b980d80 regress: give check-names.sh a clean shell
The test's session used the default $SHELL - the developer's own
interactive bash, whose PS1 embeds an OSC 0 title-setter for tmux/xterm
TERM types (common on this machine, not test-controlled). That redrew
the terminal title on every prompt, clobbering the test's own
OSC-2-set-title assertions regardless of how long it waited afterwards.
Use bash --noprofile --norc, matching the pattern already used by
style-trim.sh.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 23:25:07 +01:00
Michael Grant ac4daa57c9 Merge branch 'master' into redraw-damage-rectangles
# Conflicts:
#	popup.c
#	screen-redraw.c
#	tmux.h
#	tty.c
#	window.c
2026-09-21 23:25:01 +01:00
Nicholas Marriott c3326194b6 Turn on Linux arm also. 2026-09-21 22:34:05 +01:00
Nicholas Marriott 6a8c21fb41 macOS parallel issues. 2026-09-21 22:27:43 +01:00
Nicholas Marriott 326b9a4f16 Another timing race. 2026-09-21 22:18:44 +01:00
Nicholas Marriott 6871fcb157 Add missing test cleanup. 2026-09-21 22:16:06 +01:00
Nicholas Marriott 1d9943c6e6 Keep window alive. 2026-09-21 22:12:33 +01:00
Nicholas Marriott f5c4998db3 Make regress parallel friendly. 2026-09-21 22:00:47 +01:00
Nicholas Marriott cc81429d8a Do not make test so timing sensitive. 2026-09-21 21:51:30 +01:00
tmux update bot 3a6c2e7877 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not do anything in session_detach and rely on the caller destroying the session, otherwise grouped sessions can reuse a session with no windows. From Jeong, Heon in GitHub issue 5620.
2026-09-21 18:56:57 +00:00
nicm 21b3da3bab Do not do anything in session_detach and rely on the caller destroying
the session, otherwise grouped sessions can reuse a session with no
windows. From Jeong, Heon in GitHub issue 5620.
2026-09-21 18:56:55 +00:00
tmux update bot a7bd2415ba Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add support for borderless menus, like panes. GitHub issue 5447 from harikp2002 at gmail dot com.
  Do not trim all lines to make a zero line grid which reflow does not like, from Kaixuan Li.
  Do not unzoom when resizing a floating pane that was created with -A. Similarly, skip hidden floating panes when changing Z order. Reported by Clark Wang.
2026-09-21 13:44:41 +00:00
nicm dda0e4d489 Add support for borderless menus, like panes. GitHub issue 5447 from harikp2002
at gmail dot com.
2026-09-21 13:44:39 +00:00
nicm e361b8f8ff Do not trim all lines to make a zero line grid which reflow does not like, from
Kaixuan Li.
2026-09-21 13:44:39 +00:00
nicm 9be3a35178 Do not unzoom when resizing a floating pane that was created with -A.
Similarly, skip hidden floating panes when changing Z order. Reported by Clark
Wang.
2026-09-21 13:44:39 +00:00
Nicholas Marriott 324c636c5c Test for 5447 and for copy-mode zero line crash. 2026-09-21 13:14:44 +01:00
Nicholas Marriott 166851bf87 Update tests and bits after popups removed. 2026-09-21 12:05:37 +01:00
Nicholas Marriott 5aa17a0cfa Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Remove popups and all the associated overlay machinery (they were the last user of it). display-popup stays but becomes an (undocumented) compatibility command to open a floating pane.
  Fix typo (too few 0s) in CLIENT_CONTROL_DISCARD, from someone in GitHub issue 5622.
2026-09-21 12:03:10 +01:00
nicm 34cd5da4e3 Remove popups and all the associated overlay machinery (they were the last user
of it). display-popup stays but becomes an (undocumented) compatibility command
to open a floating pane.
2026-09-21 11:01:12 +00:00
nicm 3d5f946f35 Fix typo (too few 0s) in CLIENT_CONTROL_DISCARD, from someone in GitHub issue
5622.
2026-09-21 11:01:12 +00:00
Nicholas Marriott 81794f3047 Test for newp -A and resize. 2026-09-21 11:39:09 +01:00
Nicholas Marriott 541ca0e844 Merge branch 'master' into redraw-damage-rectangles 2026-09-21 11:09:55 +01:00
Michael Grant 38b8c493c2 Merge branch 'master' into 4902-image-support
# Conflicts:
#	cmd-run-shell.c
#	utf8.c
#	window.c
2026-09-21 05:06:16 +01:00
Michael GrantandClaude Sonnet 5 729c361aad screen-redraw: stop floating-pane drags from clobbering unrelated panes
window_pane_redraw_floating() (called by every floating-pane drag/resize
command) had two conflicting redraw paths: a precise damage-rectangle
path for the dragged pane, and an unconditional server_redraw_window()
added to fix border-status recomposition (aa6b52ef) that silently
defeated it, redrawing every pane - including retransmitting unrelated
images - on every drag tick.

Chasing the border-status bug back further: it was actually caused by
an upstream regression (824a0729) that split CLIENT_REDRAWWINDOW's
redraw flags into REDRAW_ALL vs. REDRAW_ALL & ~REDRAW_OVERLAY, silently
breaking every literal `flags == REDRAW_ALL` check downstream
(REDRAW_IS_ALL()), including the one that decides whether a pane's
border-status title should be forced to recompose even when its text
hasn't logically changed. Fix this properly instead of reaching for
server_redraw_window() again: decouple overlay-drawing from the shared
flags value (it's only ever read in the one place that repaints an
overlay) so redraw_screen() can pass a genuinely literal REDRAW_ALL for
a window redraw. This also fixes a second-client-attach bug where a new
client's pane titles never appeared until their text changed, since the
per-pane title cache is shared across clients.

Also fixed a real, independent bug in server-client.c found along the
way: `~c->flags & CLIENT_ALLREDRAWFLAGS` is a 6-bit mask, so `~x & MASK`
tests "any bit unset" (almost always true) rather than "no bits set" as
intended - causing damage to be composed and transmitted twice on every
floating-drag tick.

With server_redraw_window() gone, a second problem surfaced: deferred
client redraws (common under a busy tty) still escalated any pending
window damage to a full CLIENT_REDRAWWINDOW redraw, because
server_client_loop() frees w->damage unconditionally every pass with no
tracking of whether a deferred client actually got to consume it. Fix
this by giving each client a copy of missed damage (c->pending_damage,
reusing the existing merge/collapse/16-entry-cap logic damage lists
already have) keyed by the window's id, so a client can compose it
precisely on a later pass instead of resending everything. Window ids
are monotonic and never reused, so a stale id is enough to detect and
discard damage left over from a window the client has since switched
away from or that has been destroyed.

Verified with regress/image-border-status-wipe.sh,
regress/floating-pane-drag-scrollbar-strip.sh,
regress/image-movepane-drag-noflash.sh, and
regress/image-splitwindow-resize-noflash.sh (5 runs each), the full
regress suite (twice), an ASAN/UBSAN debug build, and manual repros for
a window switch and a window destroy mid-defer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 05:00:13 +01:00
Michael GrantandClaude Sonnet 5 efbd09e101 sixel: add sixel-region-scrolling to trust the terminal on scroll
There is no way to query a terminal for whether it moves SIXEL image
content along with the rest of a scrolling region, so tmux always
redrew the image after every scroll rather than assuming the terminal
did it for free the way Kitty placements already are (IMAGE_BACKEND_
SCROLLS). Add a server option, default on, that lets sixel opt into the
same trust; image_backend_flags() now ORs SCROLLS in for the sixel
backend when it's set.

Splitting SCROLLS out surfaced a second, unrelated property the two
image_backend_flags() checks had been conflating: whether a backend
also respects its own cell boundaries rather than bleeding pixels onto
neighbouring rows, which is what the "redraw a pane's border-status
after a GRAPHICAL-only redraw" guard in screen-redraw.c was actually
testing for. Give that its own IMAGE_BACKEND_CLIPPED flag (kitty only)
so turning sixel-region-scrolling on doesn't also disable that
unrelated border-status protection.

Includes a regress test proving the scroll-skip decision is wired
correctly with the option on and off, and a manual visual test
(tools/sixel-scroll-region-test.sh + tools/sixel-ruler.six, a numbered
colour-band SIXEL image) for checking real terminals by eye, since nothing
here can verify what a given terminal actually renders.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 22:39:43 +01:00
Michael GrantandClaude Sonnet 5 d037c33645 regress: update input-replies.sh's primary DA reply for ENABLE_IMAGES
With ENABLE_IMAGES compiled in, the primary DA reply correctly grows
from "\033[?1;2c" (7 bytes) to "\033[?1;2;4c" (9 bytes), advertising
sixel support. The test still expected the old byte count, so it read
only the first 7 bytes of the new reply and reported the truncation
point as a corrupted terminator. Check #{image_support} and expect
whichever reply matches the build.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 22:39:09 +01:00
Michael GrantandClaude Sonnet 5 c1de50731f regress: give check-names.sh a clean shell
The test's session used the default $SHELL - the developer's own
interactive bash, whose PS1 embeds an OSC 0 title-setter for tmux/xterm
TERM types (common on this machine, not test-controlled). That redrew
the terminal title on every prompt, clobbering the test's own
OSC-2-set-title assertions regardless of how long it waited afterwards.
Use bash --noprofile --norc, matching the pattern already used by
style-trim.sh.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 22:38:57 +01:00
Michael GrantandClaude Sonnet 5 bc177a2d66 layout-custom: emit an empty v1 body for windows with only floating panes
layout_append() returned failure for the version 1 (old) layout format
when a window's tree has no tiled cell anywhere - e.g. a window left
with only floating panes after the last tiled pane closed. layout_dump()
turns that failure into NULL, surfacing as an empty #{window_layout}
instead of the minimal valid placeholder body a control client or
select-layout round-trip expects. Return success with nothing written
instead, which composes into the checksum-prefixed empty body via the
existing formatting path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 22:38:45 +01:00
Michael GrantandClaude Sonnet 5 e963101c03 cmd-run-shell: expand formats in -c start-directory
-c was taken as a literal string, so run-shell -c '#{pane_current_path}'
tried to chdir into that literal text instead of the target pane's
directory, silently falling back to the server's own cwd. Expand it
through a format tree built from the command's target the same way the
shell-command argument already is.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 22:38:33 +01:00
Michael GrantandClaude Sonnet 5 dc853be713 resize: keep a manual-size window's own pixel size in clients_calculate_size
For window-size manual, clients_calculate_size() always returned
xpixel/ypixel as 0, even though the window's real w->xpixel/ypixel are
usually nonzero. recalculate_size()'s "did anything actually change"
check compares the computed size against the window's current size
before deciding whether to call resize_window() again, and a 0 vs.
nonzero pixel mismatch meant that check could never match for a
manually-sized window - so any unrelated recalculate_size() call (e.g.
another client disconnecting) re-ran resize_window() and re-fired
window-resized with stale old-size data, even though nothing about the
window had changed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 22:38:22 +01:00
Michael GrantandClaude Sonnet 5 03fb1e2ee1 format: fix session_bell/activity/silence_flag only checking one winlink
format_cb_session_bell_flag() and its activity/silence siblings had
their "not set" return inside the RB_FOREACH loop, so they only ever
examined the first winlink in tree order and returned immediately
regardless of its state - never checking any other window in the
session. The silence variant also read ft->wl (the display target)
instead of the loop variable, so it ignored the window it was even
supposed to be looking at.

Move the "not set" return after the loop so every winlink is checked,
and use the loop variable throughout.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 22:38:10 +01:00
Michael GrantandClaude Sonnet 5 2f1abeeb1a window: fix use-after-free destroying a zoomed window
window_destroy() called window_unzoom() before tearing anything down.
layout_fix_panes() inside that resizes panes, which fires the
pane-resized hook; the hook's event payload takes and drops its own
reference on the window, and since w->references was already 0 at that
point, dropping it reached 0 again and re-entered window_destroy() from
inside itself - freeing w and its panes while the outer call was still
using them.

Pin the window's reference count across window_unzoom() so that
reentrant drop can't reach zero again.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 22:37:59 +01:00