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>
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>
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>
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>
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>
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>
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>
* 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.
* 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.
* 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.
* refs/remotes/tmux-openbsd/master:
build tmux with debug symbols, ok claudio nicm
If no floating panes, reset default starting position, and reset if any part of the pane goes outside the window.
Empty string for invalid old-style formats causes old iTerm2 versions to crash, so emit "0000," instead.
Fix session_*_flag format variables which loop over the windows (they should only be false if all windows do not have the flag, not the first one). GitHub issue 5599.
Do not loop forever if someone tries to give WCHAR_MAX a width, GitHub issue 5602.
Expand -c for run-shell like the other -c flags, reported by Saúl Nogueras.
Reset layout manually instead of calling window_unzoom which can go down the notification path and end up double freeing the pane (this was previously removed in 2015 but added back to fix a problem with late destroy - this is a better fix). GitHub issue 5591 from Romain Francoise.
notification path and end up double freeing the pane (this was previously
removed in 2015 but added back to fix a problem with late destroy - this is a
better fix). GitHub issue 5591 from Romain Francoise.
Call window_redraw_active_switch() before selecting a pane so differing window-style and window-active-style colours are updated immediately.\n\nAdd a regression test for selecting a pane through attach-session.
* refs/remotes/tmux-openbsd/master:
Rather than allowing floating panes to remain outside the window and invisible after resize, move them and resize them so they are fully inside the window. GitHub issue 5582 from Noam Stolero.
* refs/remotes/tmux-openbsd/master:
Add remain-on-exit failed-key and a -D flag to new-pane to have a modal pane wait for Escape/C-c. Both to allow better compatibility with popups.