mirror of
https://github.com/tmux/tmux.git
synced 2026-10-10 20:24:21 +00:00
Implement generalised image support
Support Kitty and SIXEL input and output with grid-resident image layers, history and copy-mode integration, text fallback rendering, and damage-rectangle redraws. Consolidate the image work through 250f96b4b onto the redraw-damage-rectangles-new base, excluding the superseded redraw implementation and its historical merges. The original history is preserved in backup/4902-image-support-before-master-rebase-20261003.
This commit is contained in:
151
regress/image-border-status-wipe.sh
Normal file
151
regress/image-border-status-wipe.sh
Normal file
@@ -0,0 +1,151 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test for a border-status caching bug: window_make_pane_status()
|
||||
# (window-border.c) gates the physical redraw of a pane's border-status
|
||||
# title on a logical content diff (grid_compare against a cached copy), not
|
||||
# on whether the physical screen cells were disturbed by something else in
|
||||
# the meantime - such as a floating pane's own border, drawn on top of a
|
||||
# tiled pane's border-status row, sliding across it and then away again.
|
||||
# See tmux-image-redraw-known-bugs.md ("border-status text cache ignores
|
||||
# physical damage") for the full write-up.
|
||||
#
|
||||
# Reproduction: a tiled pane with pane-border-status on has a floating pane
|
||||
# dragged, by mouse, from directly over its border-status row to somewhere
|
||||
# else. The tiled pane's title should reappear once the floating pane has
|
||||
# moved off it; without the fix it stays blank.
|
||||
#
|
||||
# The drag starts and ends away from row 0 rather than grabbing the
|
||||
# floating pane's border while it is already sitting on row 0: when a
|
||||
# floating pane's own border-status row exactly coincides with the tiled
|
||||
# pane's row 0, mouse hit-testing on that row attributes clicks to the
|
||||
# tiled pane, not the floating one on top of it (an unrelated tmux quirk,
|
||||
# not what this test is about). That only matters for the initial press,
|
||||
# though - once a drag is under way, further motion events go straight to
|
||||
# the already-bound per-pane callback without re-resolving which pane owns
|
||||
# the coordinates, so starting the grab on an unambiguous row and dragging
|
||||
# through row 0 works fine.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
# drag COL ROW [ROW ...]
|
||||
#
|
||||
# Write one continuous mouse-1 gesture at column COL: a press at the first
|
||||
# ROW, a drag update at each subsequent ROW in turn, and a release at the
|
||||
# last ROW - all 1-based positions sent to the outer pane holding the
|
||||
# inner client. This matches the default MouseDown1Border/MouseDrag1Border
|
||||
# bindings used to move or resize a floating pane by its border.
|
||||
#
|
||||
# This must be one continuous press/drag/.../release gesture, not several
|
||||
# separate drag() calls chained together: releasing clears the per-pane
|
||||
# drag callback binding, so a later press has to be re-hit-tested from
|
||||
# scratch, which only works reliably away from row 0 (see the header
|
||||
# comment). A drag already under way is not re-hit-tested per motion
|
||||
# event, so it can safely pass through row 0 as an intermediate waypoint.
|
||||
drag()
|
||||
{
|
||||
col="$1"
|
||||
shift
|
||||
|
||||
row="$1"
|
||||
shift
|
||||
seq=$(printf '\033[<0;%s;%sM' "$col" "$row")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
|
||||
while [ $# -gt 1 ]; do
|
||||
row="$1"
|
||||
shift
|
||||
seq=$(printf '\033[<32;%s;%sM' "$col" "$row")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.15
|
||||
done
|
||||
|
||||
row="$1"
|
||||
seq=$(printf '\033[<0;%s;%sm' "$col" "$row")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 1
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "cleanup; rm -f $TMP" 0 1 15
|
||||
|
||||
$TMUX new-session -d -s inner -x 60 -y 20 'sh -c "sleep 100"' || exit 1
|
||||
$TMUX set -g mouse on
|
||||
$TMUX set -g pane-border-status top
|
||||
$TMUX set -g default-command 'sh -c "sleep 100"'
|
||||
|
||||
BASE=$($TMUX list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$BASE" ] || fail "No base pane."
|
||||
$TMUX select-pane -t "$BASE" -T TILEDTITLE || fail "set base title failed"
|
||||
|
||||
# Float a pane well clear of row 0 to start - its own top border must not
|
||||
# coincide with the base pane's border-status row for the initial mouse
|
||||
# press to unambiguously hit it (see the header comment). -x/-y are the
|
||||
# pane's outer size including its 2-cell border frame, so request 2 more
|
||||
# than the desired 14x3 content area (see layout_floating_args_parse()).
|
||||
FLOAT=$($TMUX new-pane -d -PF '#{pane_id}' -x 16 -y 5 -X 5 -Y 5 -T FLOATTITLE) ||
|
||||
fail "new-pane -X -Y failed"
|
||||
FTOP=$($TMUX display-message -p -t "$FLOAT" '#{pane_top}')
|
||||
FLEFT=$($TMUX display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
FWIDTH=$($TMUX display-message -p -t "$FLOAT" '#{pane_width}')
|
||||
|
||||
$TMUX2 new-session -d -x 60 -y 20 "$TMUX attach -t inner" || exit 1
|
||||
sleep 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
|
||||
# Sanity check: the base pane's title is visible now (the float starts
|
||||
# well clear of row 0).
|
||||
$TMUX2 capturep -p -t "$OUTER" >$TMP || fail "capture failed"
|
||||
head -1 $TMP | grep -q TILEDTITLE ||
|
||||
fail "base title not visible before the drag - test setup is wrong"
|
||||
|
||||
# Drag the float up by its top border (an unambiguous row - not row 0) so
|
||||
# it ends up covering row 0, hiding the base pane's title behind its own
|
||||
# border, then continue the same drag back down again so it ends clear of
|
||||
# row 0 once more - one continuous gesture the whole way (see drag()'s
|
||||
# comment for why).
|
||||
GRABCOL=$((FLEFT + FWIDTH / 2))
|
||||
waypoints="$FTOP"
|
||||
row=$FTOP
|
||||
while [ "$row" != 1 ]; do
|
||||
row=$((row - 1))
|
||||
waypoints="$waypoints $row"
|
||||
done
|
||||
while [ "$row" != "$FTOP" ]; do
|
||||
row=$((row + 1))
|
||||
waypoints="$waypoints $row"
|
||||
done
|
||||
drag $((GRABCOL + 1)) $waypoints
|
||||
drag $((GRABCOL + 1)) 1 $((GRABCOL + 1)) "$FTOP"
|
||||
|
||||
$TMUX2 capturep -p -t "$OUTER" >$TMP || fail "capture failed"
|
||||
|
||||
# The base pane's title should be visible again now the floating pane has
|
||||
# moved off its border-status row. This is expected to fail before the fix
|
||||
# - see the header comment.
|
||||
head -1 $TMP | grep -q TILEDTITLE ||
|
||||
fail "base pane title still blank after floating pane moved away"
|
||||
|
||||
exit 0
|
||||
71
regress/image-floating-content-wipe.sh
Normal file
71
regress/image-floating-content-wipe.sh
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test for a fixed bug: on a client detected as SIXEL-capable, a
|
||||
# pane-content redraw (PANE_REDRAW) erased the image backend's rectangle
|
||||
# using the redrawing pane's raw nominal geometry (wp->xoff/yoff/sx/sy)
|
||||
# instead of the cells it actually owns in the current scene. When a
|
||||
# floating pane occluded part of that rectangle, the erase blanked the
|
||||
# floating pane's on-screen area, and since the subsequent text redraw
|
||||
# correctly only refills cells the redrawing pane owns, nothing repainted
|
||||
# it back in - the floating pane's content stayed blank.
|
||||
#
|
||||
# Fixed in screen-redraw.c:redraw_draw_pane_lines() by erasing only the
|
||||
# cell ranges the pane actually owns per line (its REDRAW_SPAN_PANE spans),
|
||||
# not its raw rectangle. See tmux-sixel-erase-ignores-occlusion.md and
|
||||
# IMAGE-REDRAW-DISCUSSION.md for the full write-up.
|
||||
#
|
||||
# Confirmed by direct A/B test against this exact sequence: reliably wipes
|
||||
# the floating pane's content on the unfixed code, reliably leaves it
|
||||
# intact on the fixed code.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Lifcw$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -Lifcw-inner$$ -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "$TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null; rm -f $TMP" 0 1 15
|
||||
|
||||
$TMUX2 new-session -d -x 60 -y 20 "sh -c 'i=0; while [ \$i -lt 30 ]; do printf \"TILE%02d\\n\" \$i; i=\$((i + 1)); done; exec sleep 100'" \
|
||||
|| exit 1
|
||||
$TMUX2 new-pane -x20 -y6 -X8 -Y3 -T FLOATTITLE "sh -c 'printf FLOATCONTENT; exec sleep 100'" \
|
||||
|| exit 1
|
||||
[ "$($TMUX2 display-message -p '#{image_support}')" = 0 ] && exit 0
|
||||
|
||||
# The attaching client must be detected as SIXEL-capable - this bug does
|
||||
# not reproduce on a fallback or Kitty client.
|
||||
$TMUX2 set -as terminal-features ',*:sixel' || exit 1
|
||||
|
||||
$TMUX new-session -d -x 60 -y 20 || exit 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
|
||||
# Sanity check: the floating pane's content is visible before anything
|
||||
# disturbs it.
|
||||
$TMUX capturep -p >$TMP || exit 1
|
||||
grep -q 'FLOATCONTENT' $TMP || exit 1
|
||||
|
||||
# Click the tiled pane (making it active), enter copy-mode scrolled up
|
||||
# (matching a PageUp binding), move up one line (matching an Up binding),
|
||||
# then exit copy-mode - the exit is what triggers window_pane_reset_mode(),
|
||||
# which is what actually caused the erase.
|
||||
$TMUX2 select-pane -t%0 || exit 1
|
||||
sleep 1
|
||||
$TMUX2 copy-mode -u -t%0 || exit 1
|
||||
sleep 1
|
||||
$TMUX2 send-keys -t%0 -X cursor-up || exit 1
|
||||
sleep 1
|
||||
$TMUX2 send-keys -t%0 -X cancel || exit 1
|
||||
sleep 1
|
||||
|
||||
$TMUX capturep -p >$TMP || exit 1
|
||||
grep -q 'FLOATCONTENT' $TMP || exit 1
|
||||
|
||||
exit 0
|
||||
101
regress/image-kitty-region-scroll.sh
Executable file
101
regress/image-kitty-region-scroll.sh
Executable file
@@ -0,0 +1,101 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test for the imagescroll terminal-feature (tty-features.c),
|
||||
# Kitty side - see image-sixel-region-scroll.sh for the SIXEL side of the
|
||||
# same feature.
|
||||
#
|
||||
# The Kitty backend used to hardcode IMAGE_BACKEND_SCROLLS unconditionally
|
||||
# (image.c), so tmux always trusted the terminal to have moved a Kitty
|
||||
# placement along with the rest of a scrolling region, with no way to turn
|
||||
# that assumption off. At least Windows Terminal does not actually do
|
||||
# this: it drops the placement entirely on a plain newline- or
|
||||
# reverse-index-driven scroll (though not on a delete-line/insert-line
|
||||
# scroll simulation, which goes through a different, always-redraw path).
|
||||
# imagescroll now gates Kitty exactly like SIXEL: scrolling must not
|
||||
# retransmit the placement when the client's terminal has imagescroll (the
|
||||
# previously unconditional behaviour), and must retransmit it without (the
|
||||
# default for any terminal not individually confirmed and added to
|
||||
# tty-features.c's table) - the escape hatch this test exists to prove is
|
||||
# actually wired up for Kitty too.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "cleanup; rm -f $TMP" 0 1 15
|
||||
|
||||
IMAGEID=424242
|
||||
HEADER=$(printf '\033_Ga=T,q=2,f=32,s=1,v=1,i=%s;/wAA/w==\033\\' "$IMAGEID")
|
||||
|
||||
# A small image near the top of a tall pane, then enough plain output below
|
||||
# it to force several ordinary (no DECSTBM sub-region) linefeed scrolls,
|
||||
# each one shifting the image rows along with everything else.
|
||||
$TMUX new-session -d -s inner -x 40 -y 20 "printf '$HEADER'; exec sh" ||
|
||||
exit 1
|
||||
sleep 0.3
|
||||
|
||||
[ "$($TMUX display-message -p '#{image_support}')" = 0 ] && exit 0
|
||||
$TMUX set -as terminal-features ',*:kitty' || exit 1
|
||||
|
||||
$TMUX2 new-session -d -x 40 -y 20 || exit 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
$TMUX2 set -as terminal-features ',*:kitty@' || fail "disable outer kitty failed"
|
||||
$TMUX2 pipe-pane -t "$OUTER" -O "cat >$TMP" || fail "pipe-pane failed"
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$TMUX attach -t inner" || fail "send attach failed"
|
||||
$TMUX2 send-keys -t "$OUTER" Enter || fail "send enter failed"
|
||||
sleep 2
|
||||
|
||||
grep -qa "$(printf '\033_Ga=p')" "$TMP" || fail "sanity: image never reached the client"
|
||||
|
||||
# --- Phase 1: imagescroll granted - expect no APC. ---
|
||||
$TMUX set -as terminal-features ',*:imagescroll' || fail "grant imagescroll failed"
|
||||
sleep 0.5
|
||||
: >"$TMP"
|
||||
$TMUX send-keys -t inner -l "yes | head -n 30" || fail "send scroll failed"
|
||||
$TMUX send-keys -t inner Enter || fail "send enter failed"
|
||||
sleep 1
|
||||
|
||||
n_on=$(grep -ac "$(printf '\033_Ga=p')" "$TMP")
|
||||
[ "$n_on" -eq 0 ] ||
|
||||
fail "image was retransmitted ($n_on times) scrolling with imagescroll granted"
|
||||
|
||||
# --- Phase 2: imagescroll not granted (the default) - expect the image
|
||||
# back, then more scrolling to redraw it. ---
|
||||
$TMUX send-keys -t inner -l "printf '$HEADER'" || fail "resend image failed"
|
||||
$TMUX send-keys -t inner Enter || fail "send enter failed"
|
||||
sleep 1.5
|
||||
grep -qa "$(printf '\033_Ga=p')" "$TMP" || fail "sanity: image did not reappear before phase 2"
|
||||
|
||||
$TMUX set -as terminal-features ',*:imagescroll@' || fail "revoke imagescroll failed"
|
||||
sleep 0.5
|
||||
: >"$TMP"
|
||||
$TMUX send-keys -t inner -l "yes | head -n 30" || fail "send scroll failed"
|
||||
$TMUX send-keys -t inner Enter || fail "send enter failed"
|
||||
sleep 1
|
||||
|
||||
n_off=$(grep -ac "$(printf '\033_Ga=p')" "$TMP")
|
||||
[ "$n_off" -gt 0 ] ||
|
||||
fail "image was not retransmitted scrolling without imagescroll - expected the default always-redraw behaviour"
|
||||
|
||||
exit 0
|
||||
110
regress/image-resize-width-recovery.sh
Executable file
110
regress/image-resize-width-recovery.sh
Executable file
@@ -0,0 +1,110 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test: an image wider than the pane at the time it was
|
||||
# displayed must show more of itself once the pane grows wide enough,
|
||||
# instead of staying clipped to its original width forever.
|
||||
#
|
||||
# image_write() (image.c) clips an image's width to whatever fit in the
|
||||
# pane when it was first displayed, and never revisits that decision -
|
||||
# unlike height, which recovers naturally through ordinary scrollback,
|
||||
# there is no "scroll right", so the clipped columns were permanently
|
||||
# discarded. window_pane_resize() (window.c) now calls
|
||||
# image_grid_resize_width() after a pane grows wider, which extends each
|
||||
# existing placement's spans - using the image's own retained, immutable
|
||||
# pixel data - up to whichever is smaller: the image's full width or the
|
||||
# new pane width. See tmux-image-redraw-known-bugs.md for the full
|
||||
# write-up.
|
||||
#
|
||||
# This is checked via the SIXEL raster widths reported in the client's raw
|
||||
# output before and after widening the window. The redraw triggered by the
|
||||
# resize is damage-based (only the newly-uncovered columns are dirtied), so
|
||||
# it does not redraw the whole row as one wider raster - it sends the
|
||||
# already-correct clipped portion's width again untouched, plus a *second*,
|
||||
# separate raster covering just the newly-added columns. So rather than
|
||||
# looking for a single wider raster, this checks that the widths seen
|
||||
# across both redraws, added together, account for the fixture's full
|
||||
# pixel width - i.e. the previously-clipped remainder actually appeared.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "cleanup; rm -f $TMP" 0 1 15
|
||||
|
||||
FIXTURE=$(pwd)/monkey-2.sixel.txt
|
||||
FULL=$(grep -oa '"1;1;[0-9]*;[0-9]*' $FIXTURE | head -1 | cut -d';' -f3)
|
||||
[ -n "$FULL" ] || fail "could not read fixture's own raster width"
|
||||
|
||||
# The fixture is 360px wide - narrow enough that a 12-column pane clips it,
|
||||
# comfortably below the 360px it would take to show in full.
|
||||
$TMUX new-session -d -s inner -x 12 -y 40 "cat '$FIXTURE'; exec sh" || exit 1
|
||||
sleep 0.5
|
||||
|
||||
[ "$($TMUX display-message -p '#{image_support}')" = 0 ] && exit 0
|
||||
$TMUX set -as terminal-features ',*:sixel' || exit 1
|
||||
|
||||
# Start the outer session with a plain shell, then start capturing before
|
||||
# triggering the attach - starting the attach as the outer pane's initial
|
||||
# command would mean pipe-pane only starts after the attach-driven initial
|
||||
# redraw (which sends the image) has already happened, missing it.
|
||||
$TMUX2 new-session -d -x 12 -y 40 || exit 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
# The outer client is just another tmux client receiving raw PTY output
|
||||
# that happens to contain sixel DCS sequences - left alone, it would
|
||||
# independently decode and place its own second, differently-sized image
|
||||
# on top of the inner session's, confounding the raster-width measurement
|
||||
# below. Disable sixel on the outer client so only the inner session's
|
||||
# placement exists.
|
||||
$TMUX2 set -as terminal-features ',*:sixel@' || fail "disable outer sixel failed"
|
||||
$TMUX2 pipe-pane -t "$OUTER" -O "cat >$TMP" || fail "pipe-pane failed"
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$TMUX attach -t inner" || fail "send attach failed"
|
||||
$TMUX2 send-keys -t "$OUTER" Enter || fail "send enter failed"
|
||||
sleep 1
|
||||
|
||||
before=$(grep -oa '"1;1;[0-9]*;' $TMP | grep -o '[0-9]*' | sort -n | tail -1)
|
||||
[ -n "$before" ] || fail "sanity: image never reached the client"
|
||||
[ "$before" -lt 360 ] || fail "sanity: image was not clipped by the narrow pane ($before)"
|
||||
: >$TMP
|
||||
|
||||
# Widen the window well past the image's full width - resize the outer
|
||||
# client, since the inner session's displayed size follows whatever
|
||||
# terminal size its attached client actually has.
|
||||
$TMUX2 resize-window -t "$OUTER" -x 30 -y 40 || fail "resize-window failed"
|
||||
sleep 1
|
||||
$TMUX refresh-client || fail "refresh-client failed"
|
||||
sleep 1
|
||||
|
||||
# The widths seen after the resize, added together (deduplicated - the
|
||||
# same chunk may legitimately be retransmitted), should account for the
|
||||
# fixture's full pixel width: this is expected to fail before the fix,
|
||||
# where the newly-uncovered columns are never redrawn at all and only the
|
||||
# original clipped width ever appears - see the header comment.
|
||||
after_total=$(grep -oa '"1;1;[0-9]*;' $TMP | grep -o '[0-9]*' | sort -nu |
|
||||
awk '{s+=$1} END{print s+0}')
|
||||
[ "$after_total" -gt 0 ] ||
|
||||
fail "no image raster was sent at all after widening the window"
|
||||
[ "$after_total" -ge $((FULL - 20)) ] ||
|
||||
fail "image stayed clipped at ${before}px after widening the window (widths summed to only ${after_total}px, expected close to ${FULL}px)"
|
||||
|
||||
exit 0
|
||||
107
regress/image-scroll-no-erase.sh
Executable file
107
regress/image-scroll-no-erase.sh
Executable file
@@ -0,0 +1,107 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test for a tmux bug (not a terminal bug): image_redraw_scroll()
|
||||
# (image.c) marks the whole scroll region as damage on every scroll. For a
|
||||
# client whose terminal has imagescroll, the terminal has already moved the
|
||||
# image (and the text) along with the scroll, but the damage was still
|
||||
# composed: the image was not retransmitted, but the ordinary text pass
|
||||
# drew blank grid content directly over the image's cells and erased a
|
||||
# correctly scrolled image. This is what a user saw as "the image
|
||||
# disappears when the pane scrolls in Windows Terminal" even with
|
||||
# imagescroll and margins both granted, and could never be reproduced by
|
||||
# any terminal-side test because the bug is entirely server side.
|
||||
#
|
||||
# redraw_client_damage_rect() (screen-redraw.c) now composes nothing for a
|
||||
# scroll the client's terminal is trusted to have done itself, logging
|
||||
# "composing damage" only when it draws. Nothing being drawn is what keeps
|
||||
# the image, so the absence of that log line for the scroll is checked.
|
||||
#
|
||||
# The image is placed comfortably mid-pane (not at the very top) and wide
|
||||
# enough to span the whole pane, so it is still on screen - not already
|
||||
# scrolled into history - by the time a real scroll's damage gets composed.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -vv -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
cd "$DIR" || exit 1
|
||||
trap "cleanup; cd /; rm -rf $DIR" 0 1 15
|
||||
|
||||
WIDTH=40
|
||||
HEIGHT=20
|
||||
ROW=10
|
||||
|
||||
# A sixel image exactly WIDTH cells wide (at a typical ~9px cell, 360px) and
|
||||
# one cell tall, placed at screen row ROW - comfortably mid-pane, not at
|
||||
# the very top, so it stays visible (not yet scrolled into history) through
|
||||
# the first several scrolls.
|
||||
HEADER='\033Pq"1;1;360;18#0;2;100;100;100#0!360~-!360~-!360B\033\\'
|
||||
|
||||
# Pad with blank lines after the image so the shell prompt lands at the
|
||||
# bottom of the pane - without this, the very first scroll from a
|
||||
# not-yet-settled cursor position falls back to tty_redraw_region() (a
|
||||
# different, unrelated path that the trusted-scroll skip never applies to
|
||||
# anyway), and the test would not exercise what it is meant to.
|
||||
PAD=$((HEIGHT - ROW - 2))
|
||||
$TMUX new-session -d -s inner -x $WIDTH -y $HEIGHT \
|
||||
"printf '\\033[$((ROW + 1));1H$HEADER'; i=0; while [ \$i -lt $PAD ]; do echo; i=\$((i + 1)); done; exec sh" ||
|
||||
exit 1
|
||||
sleep 0.3
|
||||
|
||||
[ "$($TMUX display-message -p '#{image_support}')" = 0 ] && exit 0
|
||||
$TMUX set -g pane-scrollbars on || exit 1
|
||||
$TMUX set -as terminal-features ',*:sixel' || exit 1
|
||||
$TMUX set -as terminal-features ',*:margins' || exit 1
|
||||
$TMUX set -as terminal-features ',*:imagescroll' || exit 1
|
||||
|
||||
$TMUX2 new-session -d -x $WIDTH -y $HEIGHT || exit 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
$TMUX2 set -as terminal-features ',*:sixel@' || fail "disable outer sixel failed"
|
||||
TMP=$(mktemp)
|
||||
$TMUX2 pipe-pane -t "$OUTER" -O "cat >$TMP" || fail "pipe-pane failed"
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$TMUX attach -t inner" || fail "send attach failed"
|
||||
$TMUX2 send-keys -t "$OUTER" Enter || fail "send enter failed"
|
||||
sleep 1
|
||||
|
||||
grep -qa '"1;1;360;18' "$TMP" || fail "sanity: image never reached the client"
|
||||
|
||||
LOG=$(ls tmux-server*.log 2>/dev/null | head -1)
|
||||
[ -n "$LOG" ] || fail "sanity: no server -vv log was produced"
|
||||
BEFORE=$(wc -l <"$LOG")
|
||||
: >"$TMP"
|
||||
|
||||
# The image (row ROW, 10 of 20) has plenty of room above it - this stays
|
||||
# well within the visible pane for the whole burst.
|
||||
$TMUX send-keys -t inner -l "yes | head -n 10" || fail "send scroll failed"
|
||||
$TMUX send-keys -t inner Enter || fail "send enter failed"
|
||||
sleep 1
|
||||
|
||||
n_dcs=$(grep -ac "$(printf '\033P')" "$TMP")
|
||||
[ "$n_dcs" -eq 0 ] ||
|
||||
fail "sanity: image was retransmitted ($n_dcs times) - imagescroll's trust did not engage, so this run cannot test what it is meant to"
|
||||
|
||||
tail -n +$((BEFORE + 1)) "$LOG" | grep -q "redraw_client_damage_rect: .* composing damage" &&
|
||||
fail "damage was composed for a scroll the terminal is trusted to have done - this would draw over the image and erase it"
|
||||
|
||||
exit 0
|
||||
87
regress/image-scroll-no-text-redraw.sh
Normal file
87
regress/image-scroll-no-text-redraw.sh
Normal file
@@ -0,0 +1,87 @@
|
||||
#!/bin/sh
|
||||
|
||||
# When the client's terminal has the imagescroll feature, tmux trusts it to
|
||||
# have moved everything in the scrolled region - text and image alike - so
|
||||
# a scroll must not cause any existing text to be redrawn. It used to skip
|
||||
# only the image phases of the damage redraw and still redraw all of the
|
||||
# text around them.
|
||||
#
|
||||
# Without imagescroll the images must be redrawn, and the text around them
|
||||
# is redrawn too, so the same scroll must resend it.
|
||||
#
|
||||
# The feature only takes effect for a client attached after it is set, so
|
||||
# each case starts a new server and client.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "cleanup; rm -f $TMP" 0 1 15
|
||||
|
||||
HEADER=$(printf '\033_Ga=T,q=2,f=32,s=1,v=1,i=424243;/wAA/w==\033\\')
|
||||
|
||||
# Scroll a pane containing an image and marker text on a client with the
|
||||
# given terminal features, and set N to the number of marker lines sent to
|
||||
# the terminal.
|
||||
scroll_markers()
|
||||
{
|
||||
cleanup
|
||||
|
||||
# Marker text around an image, filling the pane so that the next
|
||||
# newlines scroll it while the image stays in view.
|
||||
$TMUX new-session -d -s inner -x 40 -y 20 \
|
||||
"i=0; while [ \$i -lt 5 ]; do printf 'MARKER%02d\n' \$i; i=\$((i + 1)); done; printf '$HEADER'; while [ \$i -lt 17 ]; do printf 'MARKER%02d\n' \$i; i=\$((i + 1)); done; exec sh" ||
|
||||
exit 1
|
||||
sleep 0.3
|
||||
[ "$($TMUX display-message -p '#{image_support}')" = 0 ] && exit 0
|
||||
$TMUX set -as terminal-features "$1" || exit 1
|
||||
|
||||
$TMUX2 new-session -d -x 40 -y 20 || exit 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
$TMUX2 set -as terminal-features ',*:kitty@' ||
|
||||
fail "disable outer kitty failed"
|
||||
: >"$TMP"
|
||||
$TMUX2 pipe-pane -t "$OUTER" -O "cat >$TMP" || fail "pipe-pane failed"
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$TMUX attach -t inner" ||
|
||||
fail "send attach failed"
|
||||
$TMUX2 send-keys -t "$OUTER" Enter || fail "send enter failed"
|
||||
sleep 2
|
||||
grep -qa "$(printf '\033_Ga=p')" "$TMP" ||
|
||||
fail "sanity: image never reached the client"
|
||||
|
||||
: >"$TMP"
|
||||
$TMUX send-keys -t inner -l "printf '\n\n\n'" ||
|
||||
fail "send scroll failed"
|
||||
$TMUX send-keys -t inner Enter || fail "send enter failed"
|
||||
sleep 1
|
||||
N=$(grep -ac MARKER "$TMP")
|
||||
}
|
||||
|
||||
scroll_markers ',*:kitty,*:imagescroll'
|
||||
[ "$N" -eq 0 ] ||
|
||||
fail "existing text was redrawn ($N lines) scrolling with imagescroll"
|
||||
|
||||
scroll_markers ',*:kitty'
|
||||
[ "$N" -gt 0 ] ||
|
||||
fail "text was not redrawn scrolling without imagescroll"
|
||||
|
||||
exit 0
|
||||
107
regress/image-scroll-off-recovery.sh
Executable file
107
regress/image-scroll-off-recovery.sh
Executable file
@@ -0,0 +1,107 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test: an image taller than the pane, at the moment it is first
|
||||
# displayed, must remain visible when scrolling back up through history -
|
||||
# not show blank space for the part that was scrolled off screen before the
|
||||
# user ever got to see it.
|
||||
#
|
||||
# image_write() (image.c) handles this case by scrolling the screen up
|
||||
# (via screen_write_scrollup()) to make room, then only ever wrote spans
|
||||
# for the rows that ended up on screen afterwards - the rows that were
|
||||
# immediately scrolled off (origin_y of them) were pushed into history as
|
||||
# blank, spanless rows and their image data was discarded. Unlike width,
|
||||
# which has no "scroll right" to recover a permanent clip, height already
|
||||
# has ordinary scrollback, so this was pure waste. image_write() now also
|
||||
# calls image_extend_row() for those origin_y history rows (capped to
|
||||
# gd->hsize, since screen_write_scrollup() may not have pushed a real
|
||||
# history row for every one of them - see tmux-uint-subtraction-underflow-
|
||||
# care.md), so scrolling back up recovers the image instead of showing
|
||||
# empty space. See tmux-image-redraw-known-bugs.md for the full write-up.
|
||||
#
|
||||
# This is checked by comparing how many separate SIXEL rasters the client
|
||||
# receives right after entering copy mode (a baseline - copy mode itself
|
||||
# triggers a redraw of the still-visible rows) against how many it
|
||||
# receives after scrolling to the very top of history: with the fix, more
|
||||
# rasters appear (covering the newly-revealed, previously-scrolled-off
|
||||
# rows); without it, scrolling reveals nothing new.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "cleanup; rm -f $TMP" 0 1 15
|
||||
|
||||
FIXTURE=$(pwd)/monkey-2.sixel.txt
|
||||
|
||||
# A short (5-row) pane and a wide (30-column) one - wide enough that the
|
||||
# fixture's width is not clipped, so only the height/scrolloff behaviour is
|
||||
# exercised, and short enough that most of the image is scrolled off
|
||||
# screen while it is still being displayed.
|
||||
$TMUX new-session -d -s inner -x 30 -y 5 "cat '$FIXTURE'; exec sh" || exit 1
|
||||
sleep 0.5
|
||||
|
||||
[ "$($TMUX display-message -p '#{image_support}')" = 0 ] && exit 0
|
||||
$TMUX set -as terminal-features ',*:sixel' || exit 1
|
||||
|
||||
# Start the outer session with a plain shell, then start capturing before
|
||||
# triggering the attach - starting the attach as the outer pane's initial
|
||||
# command would mean pipe-pane only starts after the attach-driven initial
|
||||
# redraw (which sends the image) has already happened, missing it.
|
||||
$TMUX2 new-session -d -x 30 -y 5 || exit 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
# The outer client is just another tmux client receiving raw PTY output
|
||||
# that happens to contain sixel DCS sequences - left alone, it would
|
||||
# independently decode and place its own second image, confounding the
|
||||
# raster counts below. Disable sixel on the outer client so only the
|
||||
# inner session's placement exists.
|
||||
$TMUX2 set -as terminal-features ',*:sixel@' || fail "disable outer sixel failed"
|
||||
$TMUX2 pipe-pane -t "$OUTER" -O "cat >$TMP" || fail "pipe-pane failed"
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$TMUX attach -t inner" || fail "send attach failed"
|
||||
$TMUX2 send-keys -t "$OUTER" Enter || fail "send enter failed"
|
||||
sleep 1
|
||||
|
||||
[ "$($TMUX display-message -p '#{history_size}')" -gt 0 ] ||
|
||||
fail "sanity: image did not push anything into history - pane wasn't short enough"
|
||||
|
||||
# Enter copy mode and measure the baseline raster count this alone causes
|
||||
# (redrawing whatever's still on screen), without having scrolled yet.
|
||||
: >$TMP
|
||||
$TMUX copy-mode -t inner || fail "copy-mode failed"
|
||||
sleep 1
|
||||
baseline=$(grep -oac '"1;1;[0-9]*;[0-9]*' $TMP)
|
||||
|
||||
# Scroll all the way to the top of history - this is expected to fail
|
||||
# before the fix (no more rasters than the baseline ever appear, since the
|
||||
# scrolled-off rows were left blank) - see the header comment.
|
||||
: >$TMP
|
||||
$TMUX send-keys -t inner -X history-top || fail "history-top failed"
|
||||
sleep 1
|
||||
after=$(grep -oac '"1;1;[0-9]*;[0-9]*' $TMP)
|
||||
|
||||
[ "$after" -gt 0 ] ||
|
||||
fail "no image raster was sent at all after scrolling to history-top"
|
||||
[ "$after" -ge "$baseline" ] ||
|
||||
fail "scrolling to history-top redrew fewer rasters ($after) than just entering copy mode did ($baseline) - unexpected either way"
|
||||
|
||||
exit 0
|
||||
142
regress/image-scroll-uses-region.sh
Executable file
142
regress/image-scroll-uses-region.sh
Executable file
@@ -0,0 +1,142 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test: with imagescroll granted, does tmux actually use a native
|
||||
# scrolling-region command (DECSTBM, and DECSLRM if the pane isn't full
|
||||
# width) to perform the scroll, rather than tty_redraw_region()'s fallback
|
||||
# full manual repaint? This directly checks the concern raised in PR review
|
||||
# that tmux "isn't using scrolling regions" for an image-bearing pane -
|
||||
# imagescroll only makes sense to grant a terminal if tmux is actually
|
||||
# relying on a real native scroll to carry the image along, not silently
|
||||
# falling back to redrawing every line by hand while still skipping the
|
||||
# image retransmission (which would just lose the image for nothing).
|
||||
#
|
||||
# tty_redraw_region() (tty.c) logs one of two lines whenever it runs at
|
||||
# all - "%s large region redraw" or "%s small region redraw" - so their
|
||||
# total absence after the scroll proves the native path
|
||||
# (tty_cmd_linefeed()/scrollup()/scrolldown()/reverseindex()) was taken
|
||||
# instead. Combined with image-sixel-region-scroll.sh's own check (the
|
||||
# image is not retransmitted), this proves imagescroll's trust is actually
|
||||
# backed by a real native scroll, not a coincidence.
|
||||
#
|
||||
# Phase 2 is the deliberate opposite case, and is what PR review's concern
|
||||
# actually described: pane-scrollbars makes this pane less than full width,
|
||||
# and without margins granted, tty_cmd_*()'s own
|
||||
# "(!tty_full_width(tty, ctx) && !tty_use_margin(tty))" check has no native
|
||||
# scrolling-region option left and must fall back - proving phase 1 wasn't
|
||||
# trivially passing, and that redraw_image_scroll_result()'s failure path
|
||||
# correctly also stops imagescroll's trust from applying when that happens
|
||||
# (the image must be retransmitted here, exactly as
|
||||
# image-sixel-region-scroll.sh's own phase 2 already separately proves).
|
||||
#
|
||||
# A real attached client is needed for any of this redraw code to run at
|
||||
# all, so this uses the same nested inner/outer tmux pattern as
|
||||
# image-sixel-region-scroll.sh.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -vv -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
cd "$DIR" || exit 1
|
||||
trap "cleanup; cd /; rm -rf $DIR" 0 1 15
|
||||
|
||||
WIDTH=40
|
||||
HEIGHT=20
|
||||
|
||||
HEADER='\033Pq"1;1;26;26#0;2;100;100;100#0!26~-!26~-!26~-!26~-!26B\033\\'
|
||||
|
||||
$TMUX new-session -d -s inner -x $WIDTH -y $HEIGHT \
|
||||
"printf '$HEADER'; exec sh" || exit 1
|
||||
sleep 0.3
|
||||
|
||||
[ "$($TMUX display-message -p '#{image_support}')" = 0 ] && exit 0
|
||||
$TMUX set -g pane-scrollbars on || exit 1
|
||||
$TMUX set -as terminal-features ',*:sixel' || exit 1
|
||||
$TMUX set -as terminal-features ',*:imagescroll' || exit 1
|
||||
|
||||
$TMUX2 new-session -d -x $WIDTH -y $HEIGHT || exit 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
$TMUX2 set -as terminal-features ',*:sixel@' || fail "disable outer sixel failed"
|
||||
TMP=$(mktemp)
|
||||
$TMUX2 pipe-pane -t "$OUTER" -O "cat >$TMP" || fail "pipe-pane failed"
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$TMUX attach -t inner" || fail "send attach failed"
|
||||
$TMUX2 send-keys -t "$OUTER" Enter || fail "send enter failed"
|
||||
sleep 1
|
||||
|
||||
grep -qa '"1;1;26;26' "$TMP" || fail "sanity: image never reached the client"
|
||||
|
||||
LOG=$(ls tmux-server*.log 2>/dev/null | head -1)
|
||||
[ -n "$LOG" ] || fail "sanity: no server -vv log was produced"
|
||||
|
||||
run_scroll()
|
||||
{
|
||||
label=$1
|
||||
BEFORE=$(wc -l <"$LOG")
|
||||
: >"$TMP"
|
||||
|
||||
$TMUX send-keys -t inner -l "yes | head -n 10" || fail "$label: send scroll failed"
|
||||
$TMUX send-keys -t inner Enter || fail "$label: send enter failed"
|
||||
sleep 1
|
||||
|
||||
n_dcs=$(grep -ac "$(printf '\033P')" "$TMP")
|
||||
n_redraw=$(tail -n +$((BEFORE + 1)) "$LOG" | grep -c "region redraw")
|
||||
}
|
||||
|
||||
# --- Phase 1: margins granted - real native scrolling region expected,
|
||||
# image never retransmitted. ---
|
||||
$TMUX set -as terminal-features ',*:margins' || fail "grant margins failed"
|
||||
sleep 0.3
|
||||
$TMUX display-message -p '#{client_termfeatures}' |
|
||||
grep -q imagescroll || fail "sanity: imagescroll was not granted to the client"
|
||||
|
||||
run_scroll "phase 1 (margins granted)"
|
||||
[ "$n_dcs" -eq 0 ] ||
|
||||
fail "phase 1: image was retransmitted ($n_dcs times) - imagescroll's trust did not engage, so this run cannot test what it is meant to"
|
||||
[ "$n_redraw" -eq 0 ] ||
|
||||
fail "phase 1: tty_redraw_region() fired ($n_redraw times) while scrolling an image-bearing pane with margins+imagescroll granted - tmux fell back to a full manual repaint instead of a real native scroll, so trusting the terminal to have moved the image was never justified"
|
||||
|
||||
# --- Phase 2: margins revoked - the pane still isn't full width
|
||||
# (pane-scrollbars is on), so this is the scenario PR review's "not using
|
||||
# scrolling regions" concern actually described: no native scrolling-region
|
||||
# option is available, tty_redraw_region() must fire, and
|
||||
# redraw_image_scroll_result()'s failure path must in turn stop
|
||||
# imagescroll's trust from applying, so the image is retransmitted instead
|
||||
# of silently lost. Resend the image first - phase 1's scrolling already
|
||||
# pushed the original one (26 pixels tall, a couple of rows at most) out of
|
||||
# the pane, so without this there would be nothing left to retransmit
|
||||
# either way, and the check below would pass for the wrong reason. ---
|
||||
$TMUX set -as terminal-features ',*:margins@' || fail "revoke margins failed"
|
||||
sleep 0.3
|
||||
: >"$TMP"
|
||||
$TMUX send-keys -t inner -l "printf '$HEADER'" || fail "resend image failed"
|
||||
$TMUX send-keys -t inner Enter || fail "send enter failed"
|
||||
sleep 1.5
|
||||
grep -qa '"1;1;26;26' "$TMP" || fail "sanity: image did not reappear before phase 2"
|
||||
|
||||
run_scroll "phase 2 (margins revoked)"
|
||||
[ "$n_redraw" -gt 0 ] ||
|
||||
fail "sanity: without margins, scrolling a scrollbar-enabled image-bearing pane never fell back to tty_redraw_region() - this scenario no longer exercises the bug phase 1 checks for"
|
||||
[ "$n_dcs" -gt 0 ] ||
|
||||
fail "phase 2: image was not retransmitted after tty_redraw_region() fired - imagescroll's trust incorrectly stayed engaged despite the fallback, which would silently lose the image"
|
||||
|
||||
exit 0
|
||||
100
regress/image-selection-noflash.sh
Executable file
100
regress/image-selection-noflash.sh
Executable file
@@ -0,0 +1,100 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test: extending a copy-mode selection by cursor movement, with
|
||||
# the view otherwise unmoved (no scrolling), must not retransmit an image
|
||||
# whose row the cursor passes through.
|
||||
#
|
||||
# window_copy_write_one() (window-copy.c) used to write text/highlight
|
||||
# styling directly over image-covered cells, which - since a character
|
||||
# write typically clears whatever pixel content a terminal was showing
|
||||
# there - erased the image with nothing to redraw it back in. Separately,
|
||||
# window_copy_write_line()'s call to image_redraw_area() used to fire
|
||||
# unconditionally on every redraw, so even after fixing the erasure, the
|
||||
# image would still be needlessly recomposited (and briefly flash) on
|
||||
# every single cursor step even though nothing about it had changed. See
|
||||
# tmux-image-redraw-known-bugs.md for the full write-up.
|
||||
#
|
||||
# This is checked by counting DCS (\033P) sequences in the client's raw
|
||||
# output during the cursor movement: with the fix, extending a selection
|
||||
# without scrolling never touches the image, so none should appear.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "cleanup; rm -f $TMP" 0 1 15
|
||||
|
||||
# A small, distinctive SIXEL raster (26x26 pixels) at the top of the pane,
|
||||
# matching the fixture already used in image-support.sh, followed by
|
||||
# enough plain lines that the cursor can move down through the image and
|
||||
# past it without the view needing to scroll.
|
||||
SIXEL='\033Pq"1;1;26;26#0;2;100;100;100#0!26~-!26~-!26~-!26~-!26B\033\\'
|
||||
$TMUX new-session -d -s inner -x 40 -y 20 \
|
||||
"printf '$SIXEL'; for i in \$(seq 1 15); do echo line\$i; done; exec sh" ||
|
||||
exit 1
|
||||
sleep 0.5
|
||||
|
||||
[ "$($TMUX display-message -p '#{image_support}')" = 0 ] && exit 0
|
||||
$TMUX set -as terminal-features ',*:sixel' || exit 1
|
||||
|
||||
# Start the outer session with a plain shell, then start capturing before
|
||||
# triggering the attach - starting the attach as the outer pane's initial
|
||||
# command would mean pipe-pane only starts after the attach-driven initial
|
||||
# redraw (which sends the image) has already happened, missing it.
|
||||
$TMUX2 new-session -d -x 40 -y 20 || exit 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
$TMUX2 pipe-pane -t "$OUTER" -O "cat >$TMP" || fail "pipe-pane failed"
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$TMUX attach -t inner" || fail "send attach failed"
|
||||
$TMUX2 send-keys -t "$OUTER" Enter || fail "send enter failed"
|
||||
sleep 1
|
||||
|
||||
# Sanity check: the image reached the client at all.
|
||||
grep -qa '"1;1;26;26' $TMP || fail "sanity: image never reached the client"
|
||||
: >$TMP
|
||||
|
||||
# Enter copy-mode, scroll to the top (where the image is) and select down
|
||||
# through it one cursor step at a time - the view does not need to scroll
|
||||
# for any of this, since the image is already at the top of what is
|
||||
# visible.
|
||||
$TMUX copy-mode -t inner || fail "copy-mode failed"
|
||||
$TMUX send-keys -X history-top || fail "history-top failed"
|
||||
sleep 0.2
|
||||
: >$TMP
|
||||
$TMUX send-keys -X begin-selection || fail "begin-selection failed"
|
||||
i=0
|
||||
while [ $i -lt 6 ]; do
|
||||
$TMUX send-keys -X cursor-down || fail "cursor-down failed"
|
||||
sleep 0.15
|
||||
i=$((i + 1))
|
||||
done
|
||||
sleep 0.5
|
||||
|
||||
# No DCS sequence should have been sent - the image's row was never
|
||||
# disturbed by any of this. This is expected to fail before the fix - see
|
||||
# the header comment.
|
||||
dcs=$(grep -ac "$(printf '\033P')" $TMP)
|
||||
[ "$dcs" -eq 0 ] ||
|
||||
fail "image was retransmitted ($dcs times) while just moving the selection cursor"
|
||||
|
||||
exit 0
|
||||
96
regress/image-sixel-region-scroll.sh
Executable file
96
regress/image-sixel-region-scroll.sh
Executable file
@@ -0,0 +1,96 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test for the imagescroll terminal-feature (tty-features.c),
|
||||
# SIXEL side - see image-kitty-region-scroll.sh for the Kitty side of the
|
||||
# same feature: scrolling a pane that has a SIXEL image in it must not
|
||||
# retransmit the image when the client's terminal has imagescroll - tmux
|
||||
# trusts the terminal to have moved the image along with the rest of the
|
||||
# scrolling region. Without imagescroll (the default for any terminal not
|
||||
# individually confirmed and added to tty-features.c's table), today's
|
||||
# always-redraw-on-scroll behaviour is unchanged.
|
||||
#
|
||||
# There is no way to query a terminal for whether it actually moves SIXEL
|
||||
# pixels along with a scroll, so this only proves tmux's own decision to
|
||||
# skip or redraw is wired correctly - not that any particular terminal
|
||||
# renders the result correctly. That needs a human, on real terminals.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "cleanup; rm -f $TMP" 0 1 15
|
||||
|
||||
HEADER='\033Pq"1;1;26;26#0;2;100;100;100#0!26~-!26~-!26~-!26~-!26B\033\\'
|
||||
|
||||
# A small image near the top of a tall pane, then enough plain output below
|
||||
# it to force several ordinary (no DECSTBM sub-region) linefeed scrolls,
|
||||
# each one shifting the image rows along with everything else.
|
||||
$TMUX new-session -d -s inner -x 40 -y 20 "printf '$HEADER'; exec sh" ||
|
||||
exit 1
|
||||
sleep 0.3
|
||||
|
||||
[ "$($TMUX display-message -p '#{image_support}')" = 0 ] && exit 0
|
||||
$TMUX set -as terminal-features ',*:sixel' || exit 1
|
||||
|
||||
$TMUX2 new-session -d -x 40 -y 20 || exit 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
$TMUX2 set -as terminal-features ',*:sixel@' || fail "disable outer sixel failed"
|
||||
$TMUX2 pipe-pane -t "$OUTER" -O "cat >$TMP" || fail "pipe-pane failed"
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$TMUX attach -t inner" || fail "send attach failed"
|
||||
$TMUX2 send-keys -t "$OUTER" Enter || fail "send enter failed"
|
||||
sleep 2
|
||||
|
||||
grep -qa '"1;1;26;26' "$TMP" || fail "sanity: image never reached the client"
|
||||
|
||||
# --- Phase 1: imagescroll granted - expect no DCS. ---
|
||||
$TMUX set -as terminal-features ',*:imagescroll' || fail "grant imagescroll failed"
|
||||
sleep 0.5
|
||||
: >"$TMP"
|
||||
$TMUX send-keys -t inner -l "yes | head -n 30" || fail "send scroll failed"
|
||||
$TMUX send-keys -t inner Enter || fail "send enter failed"
|
||||
sleep 1
|
||||
|
||||
n_on=$(grep -ac "$(printf '\033P')" "$TMP")
|
||||
[ "$n_on" -eq 0 ] ||
|
||||
fail "image was retransmitted ($n_on times) scrolling with imagescroll granted"
|
||||
|
||||
# --- Phase 2: imagescroll not granted (the default) - expect the image
|
||||
# back, then more scrolling to redraw it. ---
|
||||
$TMUX send-keys -t inner -l "printf '$HEADER'" || fail "resend image failed"
|
||||
$TMUX send-keys -t inner Enter || fail "send enter failed"
|
||||
sleep 1.5
|
||||
grep -qa '"1;1;26;26' "$TMP" || fail "sanity: image did not reappear before phase 2"
|
||||
|
||||
$TMUX set -as terminal-features ',*:imagescroll@' || fail "revoke imagescroll failed"
|
||||
sleep 0.5
|
||||
: >"$TMP"
|
||||
$TMUX send-keys -t inner -l "yes | head -n 30" || fail "send scroll failed"
|
||||
$TMUX send-keys -t inner Enter || fail "send enter failed"
|
||||
sleep 1
|
||||
|
||||
n_off=$(grep -ac "$(printf '\033P')" "$TMP")
|
||||
[ "$n_off" -gt 0 ] ||
|
||||
fail "image was not retransmitted scrolling without imagescroll - expected the default always-redraw behaviour"
|
||||
|
||||
exit 0
|
||||
362
regress/image-support.sh
Executable file
362
regress/image-support.sh
Executable file
@@ -0,0 +1,362 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Grid-resident image layers: Kitty input, history, copy and overwrite.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Limage$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -Limage-client$$ -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
TMP=$(mktemp)
|
||||
FIXTURE=$(pwd)/monkey-2.sixel.txt
|
||||
trap "$TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null; rm -f $TMP" 0 1 15
|
||||
|
||||
$TMUX new-session -d -x 20 -y 8 "
|
||||
printf '\033_Ga=T,q=2,f=32,s=2,v=2,c=2,r=2,m=1;/wAA/wD/\033\\'
|
||||
printf '\033_Gm=0;AP8AAP///////w==\033\\'
|
||||
i=0
|
||||
while [ \$i -lt 10 ]; do printf '\nline-%d' \$i; i=\$((i + 1)); done
|
||||
sleep 10"
|
||||
|
||||
sleep 1
|
||||
[ "$($TMUX display-message -p '#{image_support}')" = 0 ] && exit 0
|
||||
|
||||
# Images scroll as grid cells, while capture output contains ordinary spaces.
|
||||
[ "$($TMUX display-message -p '#{history_size}')" -gt 0 ] || exit 1
|
||||
$TMUX capture-pane -pS- >$TMP || exit 1
|
||||
grep '_G' $TMP >/dev/null && exit 1
|
||||
|
||||
# Copy mode duplicates the backing grid, including image layers.
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X history-top || exit 1
|
||||
$TMUX capture-pane -p >$TMP || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
# Ordinary text updates the image underlay through the normal grid write path.
|
||||
# Deleting the image reveals the newly written text.
|
||||
$TMUX new-window -d "
|
||||
printf '\033_Ga=T,q=2,f=32,s=2,v=2,c=2,r=2,i=8;/wAA/wD/AP8AAP///////w==\033\\'
|
||||
printf '\033[HXY'
|
||||
printf '\033_Ga=d,d=i,q=2,i=8\033\\'
|
||||
sleep 10"
|
||||
sleep 1
|
||||
[ "$($TMUX capture-pane -pt:1 -S0 -E0)" = "XY" ] || exit 1
|
||||
|
||||
# The protocol default is transmit-only; a later placement uses the retained
|
||||
# immutable image and advances by the same canonical one-cell footprint.
|
||||
$TMUX new-window -d "
|
||||
printf '\033_Gq=2,f=32,s=1,v=1,c=1,r=1,i=9;/wAA/w==\033\\'
|
||||
printf '\033_Ga=p,q=2,i=9\033\\'
|
||||
printf '\033_Ga=d,d=I,q=2,i=9\033\\'
|
||||
sleep 10"
|
||||
sleep 1
|
||||
[ "$($TMUX display-message -pt:2 '#{cursor_y}')" = 1 ] || exit 1
|
||||
|
||||
# PNG Kitty input uses the shared image decoder and canonical cell sizing.
|
||||
$TMUX new-window -d "
|
||||
printf '\033_Ga=T,q=2,f=100;iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAGUExURf8AAP///0EdNBEAAAABYktHRAH/Ai3eAAAAB3RJTUUH6ggCDAECH324BwAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=\033\\'
|
||||
sleep 10"
|
||||
sleep 1
|
||||
[ "$($TMUX display-message -pt:3 '#{cursor_y}')" = 1 ] || exit 1
|
||||
|
||||
# SIXEL input reaches the same grid layer and copy-mode paths.
|
||||
$TMUX new-window -d "cat '$FIXTURE'; sleep 10"
|
||||
sleep 1
|
||||
[ "$($TMUX display-message -pt:4 '#{cursor_y}')" -gt 0 ] || exit 1
|
||||
$TMUX copy-mode -t:4 || exit 1
|
||||
$TMUX send-keys -t:4 -X history-top || exit 1
|
||||
$TMUX capture-pane -pt:4 >$TMP || exit 1
|
||||
|
||||
# A 26-pixel raster occupies two default 16-pixel cells, but the final cell
|
||||
# remains only partially filled instead of stretching the raster to 32 pixels.
|
||||
# A client without an image feature exposes this in the sampled text backend.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 new-session -d -x 10 -y 4 "
|
||||
printf '\033Pq\"1;1;26;26#0;2;100;100;100#0!26~-!26~-!26~-!26~-!26B\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 set -g status off || exit 1
|
||||
$TMUX2 set -as terminal-features ',*:sixel@' || exit 1
|
||||
$TMUX new-session -d -x 10 -y 4 || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX pipe-pane -O "cat >$TMP" || exit 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
|
||||
grep -q '^#=' $TMP || exit 1
|
||||
|
||||
# Re-emitting the image preserves the 26-pixel raster rather than expanding
|
||||
# it to the two complete 16-pixel grid cells occupied by the image.
|
||||
# terminal-features is only detected when a client attaches, so switch to a
|
||||
# SIXEL-capable client by detaching and reattaching rather than toggling the
|
||||
# option on the client already attached above.
|
||||
$TMUX2 set -u terminal-features || exit 1
|
||||
$TMUX2 set -as terminal-features ',*:sixel' || exit 1
|
||||
$TMUX2 detach-client || exit 1
|
||||
sleep 1
|
||||
$TMUX pipe-pane || exit 1
|
||||
$TMUX pipe-pane -O "cat >$TMP" || exit 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
grep -a '"1;1;26;26' $TMP >/dev/null || exit 1
|
||||
|
||||
# A selection redraw uses the single-cell path. It must leave ASCII image
|
||||
# cells visible (graphical clients skip these cells to avoid erasing pixels).
|
||||
# Switch back to a fallback client the same way.
|
||||
$TMUX2 set -u terminal-features || exit 1
|
||||
$TMUX2 set -as terminal-features ',*:sixel@' || exit 1
|
||||
$TMUX2 detach-client || exit 1
|
||||
sleep 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
$TMUX2 copy-mode || exit 1
|
||||
$TMUX2 send-keys -X history-top || exit 1
|
||||
$TMUX2 send-keys -X start-of-line || exit 1
|
||||
$TMUX2 send-keys -X begin-selection || exit 1
|
||||
$TMUX2 send-keys -X cursor-right || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
|
||||
grep -q '^#=' $TMP || exit 1
|
||||
|
||||
# A retained Kitty image may be placed repeatedly using source rectangles.
|
||||
# Crop the green and white right column from a red/green/blue/white image.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
$TMUX2 new-session -d -x 10 -y 4 "
|
||||
printf '\033_Ga=t,q=2,f=32,s=2,v=2,i=10;/wAA/wD/AP8AAP///////w==\033\\'
|
||||
printf '\033_Ga=p,q=2,i=10,x=1,y=0,w=1,h=2,c=1,r=2\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 set -g status off || exit 1
|
||||
$TMUX new-session -d -x 10 -y 4 || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E1 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "*" ] || exit 1
|
||||
[ "$(sed -n 2p $TMP)" = "@" ] || exit 1
|
||||
|
||||
# C=1 places the image without moving the cursor or scrolling. The four-row
|
||||
# image is clipped at the bottom of this four-row pane, leaving its white last
|
||||
# row off screen.
|
||||
$TMUX2 new-window -d "
|
||||
printf '\033[2;3H'
|
||||
printf '\033_Ga=T,q=2,C=1,f=32,s=1,v=4,c=1,r=4;AAAA/1VVVf+qqqr//////w==\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 select-window -t:1 || exit 1
|
||||
sleep 1
|
||||
[ "$($TMUX2 display-message -p '#{cursor_x},#{cursor_y}')" = "2,1" ] || exit 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ "$(sed -n 3p $TMP)" = " -" ] || exit 1
|
||||
[ "$(sed -n 4p $TMP)" = " *" ] || exit 1
|
||||
|
||||
# With normal cursor movement, scrolling is calculated from the full image
|
||||
# height. Rows which scrolled above the pane are then cropped from the top, so
|
||||
# the bottom three source rows remain visible and the cursor is on the last row.
|
||||
$TMUX2 new-window -d "
|
||||
printf '\033[3;1H'
|
||||
printf '\033_Ga=T,q=2,f=32,s=1,v=4,c=1,r=4;AAAA/1VVVf+qqqr//////w==\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 select-window -t:2 || exit 1
|
||||
sleep 1
|
||||
[ "$($TMUX2 display-message -p '#{cursor_y}')" = 3 ] || exit 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "-" ] || exit 1
|
||||
[ "$(sed -n 2p $TMP)" = "*" ] || exit 1
|
||||
[ "$(sed -n 3p $TMP)" = "@" ] || exit 1
|
||||
|
||||
# Image rows remain cell-aligned when a narrower terminal causes text
|
||||
# reflow. The ten-column rows are clipped to five columns, not split into four
|
||||
# wrapped rows.
|
||||
$TMUX2 new-window -d "
|
||||
printf '\033_Ga=T,q=2,C=1,f=32,s=1,v=2,c=10,r=2;/wAA//////8=\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 select-window -t:3 || exit 1
|
||||
sleep 1
|
||||
$TMUX resize-window -x 5 -y 4 || exit 1
|
||||
sleep 1
|
||||
[ "$($TMUX2 display-message -p '#{window_width}x#{window_height}')" = "5x4" ] || exit 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "....." ] || exit 1
|
||||
[ "$(sed -n 2p $TMP)" = "@@@@@" ] || exit 1
|
||||
[ -z "$(sed -n 3p $TMP)" ] || exit 1
|
||||
|
||||
# Kitty virtual placements use U+10EEEE placeholder cells. Keep their rows at
|
||||
# fixed coordinates when narrowing the terminal, clipping instead of reflowing
|
||||
# the second half onto the following row.
|
||||
$TMUX resize-window -x 10 -y 4 || exit 1
|
||||
sleep 1
|
||||
$TMUX2 new-window -d "
|
||||
printf '\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205'
|
||||
printf '\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 select-window -t:4 || exit 1
|
||||
sleep 1
|
||||
$TMUX resize-window -x 5 -y 4 || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ -n "$(sed -n 1p $TMP)" ] || exit 1
|
||||
[ -z "$(sed -n 2p $TMP)" ] || exit 1
|
||||
$TMUX resize-window -x 10 -y 4 || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP | wc -c)" = 61 ] || exit 1
|
||||
[ -z "$(sed -n 2p $TMP)" ] || exit 1
|
||||
|
||||
# A placement ID supplied with transmit-and-place is reused by a later place.
|
||||
# This is the sequence used by chawan: the newline moves the cursor down before
|
||||
# the second command, so the folder moves down one row without leaving a copy.
|
||||
PLACEMENT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf '\033_GC=1,s=26,v=26,p=1,q=2,i=1,a=T,f=100,m=0;iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAACmUlEQVR4XmOgF2CEWVRfX8/w//9/BkZGRgy7YeKNjY1kuwtsam1tLQMTE5MG0MBsoEWW6KYBxf8DxScD6UVNTU1kWcZYU1MD0qjLzMx8EmgYJy5TgJb8/ffvn+2vX7+Od3Z2kmwZC9AnIE0FIEuAhl0H4ulA9k9kk4Bi4UB1TkA8H2iRYXl5+Xd2dna8loGCG2Q2LLgZQXED1PEIaLjs379/g4Ds9VDLwQYBxRiAPpFmZWW9DFQjCDRgLxDfJeQloJpdQHPWA9X9A1kGs+gZ0BBJoKQtUOIIeqSDgheoKR6IF5ASZkAH3gKaCzLzFQsxGltaWhjq6uoWguIJ6hi82oCG8wIV+AEdpga0LBfIrmUh1oWg1AYM5iVA9UuI1JMGVDcTiCNIsghkOChIQ0NDwfasXr0ap33QeL8GUgD0HTeIZmIgAUAtYQZqYYZZSKx2oi2CWaKpqbkIhEm1jOg40tLSAjleEBgUUSAGkJ8PpN8Q6yOCFoF8AvQBTvOAqZHh+vXrDPjijGAcIcXJUqBPQOXda5iNQP5rkBiQv5SYYMTrIxUVFZC5IkDCCk8QWQHVgdS8xBeMeBNDe3s7A7CwfQl0uSmwKDIBYhdQGQbCIDZIDCQHUgNSS7ZFII2gUgEU6UADzwJz+kUgDa6zoOyzIDmoGgaKLAJpBheKkArxC7BIeQDCQPEvIEuJrQyJzkdQA38ADbcHYaBFP0ipcVGqCWDYBwPxOkYs1TkDCeA/pEkQBDRnLZD9GKhVjgUkCGTsBgomAekWIJYG4p8MlAFQrZgJMgJo/m4QzQJ1fT9QIBLI1gTiSRRaAm/gAM38DjSvH2QeuHECLW1xNk7IsRhoyXGgJVOBem+AExPMEHzNLTItYiAlVTJQCwAAJxkMkgbHo2UAAAAASUVORK5CYII=\033\\'
|
||||
printf '\n'
|
||||
sleep 1
|
||||
printf '\033_GC=1,s=26,v=26,p=1,q=2,i=1,a=p;\033\\'
|
||||
printf '\n'
|
||||
sleep 10") || exit 1
|
||||
$TMUX2 select-window -t"$PLACEMENT_WINDOW" || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ -z "$(sed -n 1p $TMP)" ] || exit 1
|
||||
[ -n "$(sed -n 2p $TMP)" ] || exit 1
|
||||
|
||||
# A nonzero Kitty placement ID identifies one placement of an image. Reusing
|
||||
# the same image and placement IDs moves it rather than leaving the old cells.
|
||||
PLACEMENT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf '\033_Ga=t,q=2,f=32,s=1,v=1,i=11;/////w==\033\\'
|
||||
printf '\033[2;2H\033_Ga=p,q=2,C=1,i=11,p=7,c=2,r=1\033\\'
|
||||
printf '\033[4;6H\033_Ga=p,q=2,C=1,i=11,p=7,c=2,r=1\033\\'
|
||||
sleep 10") || exit 1
|
||||
$TMUX2 select-window -t"$PLACEMENT_WINDOW" || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ -z "$(sed -n 2p $TMP)" ] || exit 1
|
||||
[ "$(sed -n 4p $TMP)" = " @@" ] || exit 1
|
||||
|
||||
# Deleting one placement ID leaves other placements of the image intact.
|
||||
PLACEMENT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf '\033_Ga=t,q=2,f=32,s=1,v=1,i=12;/////w==\033\\'
|
||||
printf '\033[2;2H\033_Ga=p,q=2,C=1,i=12,p=7,c=2,r=1\033\\'
|
||||
printf '\033[4;6H\033_Ga=p,q=2,C=1,i=12,p=8,c=2,r=1\033\\'
|
||||
printf '\033_Ga=d,d=i,q=2,i=12,p=7\033\\'
|
||||
sleep 10") || exit 1
|
||||
$TMUX2 select-window -t"$PLACEMENT_WINDOW" || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ -z "$(sed -n 2p $TMP)" ] || exit 1
|
||||
[ "$(sed -n 4p $TMP)" = " @@" ] || exit 1
|
||||
|
||||
# Image layers retain the cells beneath them. Deleting a transparent image
|
||||
# must reveal the original text rather than replacing it with spaces.
|
||||
PLACEMENT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf 'XY'
|
||||
printf '\033[H'
|
||||
printf '\033_Ga=T,q=2,C=1,f=32,s=1,v=1,c=2,r=1,i=13,p=7;////AA==\033\\'
|
||||
printf '\033_Ga=d,d=i,q=2,i=13,p=7\033\\'
|
||||
sleep 10") || exit 1
|
||||
$TMUX2 select-window -t"$PLACEMENT_WINDOW" || exit 1
|
||||
sleep 1
|
||||
[ "$($TMUX2 capture-pane -pS0 -E0)" = "XY" ] || exit 1
|
||||
|
||||
# Text written over a Kitty placement updates its underlay without deleting
|
||||
# the image. Deleting the placement afterwards reveals the updated text.
|
||||
TEXT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf 'test\r'
|
||||
printf '\033_Ga=T,q=2,C=1,f=32,s=2,v=2,c=2,r=2,i=14,p=7;/wAA/wD/AP8AAP///////w==\033\\'
|
||||
printf '\r'
|
||||
printf 'test\n'
|
||||
printf 'test\n'
|
||||
$TEST_TMUX wait-for image-text-delete-$$
|
||||
printf '\033_Ga=d,d=i,q=2,i=14,p=7\033\\'
|
||||
sleep 10") || exit 1
|
||||
$TMUX2 select-window -t"$TEXT_WINDOW" || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E1 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = ".*st" ] || exit 1
|
||||
[ "$(sed -n 2p $TMP)" = " @st" ] || exit 1
|
||||
$TMUX2 wait-for -S image-text-delete-$$ || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E1 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "test" ] || exit 1
|
||||
[ "$(sed -n 2p $TMP)" = "test" ] || exit 1
|
||||
|
||||
# Kitty z-indexes and SIXEL's temporal plane are properties of the input, not
|
||||
# of the outer terminal. Exercise the logical scene through the fallback
|
||||
# backend: higher nonnegative z-indexes cover lower ones and text, ordinary
|
||||
# negative z-indexes cover backgrounds but not text, and very negative ones
|
||||
# remain below the background.
|
||||
Z_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf '\033_Ga=t,q=2,f=32,s=1,v=1,i=21;/wAA/w==\033\\'
|
||||
printf '\033_Ga=t,q=2,f=32,s=1,v=1,i=22;/////w==\033\\'
|
||||
printf 'X\r'
|
||||
printf '\033_Ga=p,q=2,C=1,i=21,p=1,z=2,c=1,r=1\033\\'
|
||||
printf '\033_Ga=p,q=2,C=1,i=22,p=2,z=1,c=1,r=1\033\\'
|
||||
printf '\033[2;1H\033[41m \033[0m\r'
|
||||
printf '\033_Ga=p,q=2,C=1,i=21,p=3,z=-1,c=1,r=1\033\\'
|
||||
printf '\033[3;1HY\r'
|
||||
printf '\033_Ga=p,q=2,C=1,i=21,p=4,z=-1,c=1,r=1\033\\'
|
||||
printf '\033[4;1H\033[41m \033[0m\r'
|
||||
printf '\033_Ga=p,q=2,C=1,i=21,p=5,z=-1073741825,c=1,r=1\033\\'
|
||||
$TEST_TMUX wait-for image-z-top-$$
|
||||
printf '\033_Ga=d,d=z,q=2,z=2\033\\'
|
||||
$TEST_TMUX wait-for image-z-lower-$$
|
||||
printf '\033_Ga=d,d=z,q=2,z=1\033\\'
|
||||
sleep 10") || exit 1
|
||||
$TMUX2 select-window -t"$Z_WINDOW" || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "." ] || exit 1
|
||||
[ "$(sed -n 2p $TMP)" = "." ] || exit 1
|
||||
[ "$(sed -n 3p $TMP)" = "Y" ] || exit 1
|
||||
[ -z "$(sed -n 4p $TMP)" ] || exit 1
|
||||
$TMUX2 wait-for -S image-z-top-$$ || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "@" ] || exit 1
|
||||
$TMUX2 wait-for -S image-z-lower-$$ || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "X" ] || exit 1
|
||||
|
||||
# A weighted median at the maximum channel level must still leave colours on
|
||||
# both sides of the split. This skewed black, grey and white image used to stop
|
||||
# palette generation after one colour instead of producing three.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
$TMUX2 new-session -d -x 20 -y 4 "
|
||||
printf '\033_Ga=T,q=2,C=1,f=24,s=100,v=1,c=10,r=1;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 set -g status off || exit 1
|
||||
$TMUX2 set -as terminal-features ',*:sixel' || exit 1
|
||||
$TMUX new-session -d -x 20 -y 4 || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX pipe-pane -O "cat >$TMP" || exit 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
grep -a '#2;2;' $TMP >/dev/null || exit 1
|
||||
|
||||
# A pane redraw on a SIXEL terminal must not clear the status line, which is
|
||||
# outside the window scene and may not be redrawn after the pane scrolls.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
$TMUX2 new-session -d -x 20 -y 8 || exit 1
|
||||
$TMUX2 set -g 'status-format[0]' 'STATUS' || exit 1
|
||||
$TMUX2 set -as terminal-features ',*:sixel' || exit 1
|
||||
$TMUX new-session -d -x 20 -y 8 || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
$TMUX2 send-keys -l 'seq 30' || exit 1
|
||||
$TMUX2 send-keys Enter || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -p >$TMP || exit 1
|
||||
[ "$(tail -n 1 $TMP)" = "STATUS" ] || exit 1
|
||||
|
||||
exit 0
|
||||
@@ -60,7 +60,11 @@ query_timeout()
|
||||
|
||||
query "dsr-ok" '^[[0n' '\033[5n' 4 ''
|
||||
query "dsr-cursor" '^[[1;1R' '\033[6n' 6 ''
|
||||
query "da-primary" '^[[?1;2c' '\033[c' 7 ''
|
||||
if [ "$($TMUX display-message -p '#{image_support}')" = 1 ]; then
|
||||
query "da-primary" '^[[?1;2;4c' '\033[c' 9 ''
|
||||
else
|
||||
query "da-primary" '^[[?1;2c' '\033[c' 7 ''
|
||||
fi
|
||||
query "da-secondary" '^[[>84;0;0c' '\033[>c' 10 ''
|
||||
query "decrqm-irm-reset" '^[[4;2$y' '\033[4$p' 7 ''
|
||||
query "decrqm-irm-set" '^[[4;1$y' '\033[4$p' 7 '\033[4h'
|
||||
|
||||
1
regress/monkey-2.sixel.txt
Normal file
1
regress/monkey-2.sixel.txt
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user