aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authoruser <user@clank>2026-09-07 15:54:34 +0200
committeruser <user@clank>2026-09-07 15:54:34 +0200
commit2a440fbc01c4e4afd58e15ace5eddd4a171d15ef (patch)
tree06cef911eca321ea6b00ed8aabc91b5242077e10 /tests
parentMerge branch 'graphics-fixes' (PR #2 from iksteen): five fixes to the kitty p... (diff)
downloadslosh-2a440fbc01c4e4afd58e15ace5eddd4a171d15ef.tar.gz
slosh-2a440fbc01c4e4afd58e15ace5eddd4a171d15ef.tar.xz
slosh-2a440fbc01c4e4afd58e15ace5eddd4a171d15ef.zip
graphics: a retransmit invalidates every placement of the image
Replacing an image's pixels deletes all of its placements on the client -- kitty drops them when the data is replaced, which is the very fact the place-once logic leans on. But the invalidation rode a local in one gfx_place() call, so only the placement said in the same breath as the transmit was owed again. The image's other placements -- a second one elsewhere, the other pieces of one a float has cut -- were judged unchanged against a client that no longer holds them, and stayed unsaid: a picture the client deleted and we believe it still has, gone until something moves it. Masked before place-once by the every-frame re-emission, so this is the regression that fix bought. The transmit now un-marks every placement of its image, and each is said again as its gfx_place() comes around. The live check drives one image placed twice, then replaces its pixels and places both again: both must reach the client after the second transmit, where the old code re-sent one.
Diffstat (limited to 'tests')
-rw-r--r--tests/live_graphics.py47
1 files changed, 45 insertions, 2 deletions
diff --git a/tests/live_graphics.py b/tests/live_graphics.py
index 934cd46..a2ffc54 100644
--- a/tests/live_graphics.py
+++ b/tests/live_graphics.py
@@ -75,13 +75,36 @@ time.sleep(30)
"""
+# One image, two placements, then the pixels replaced -- which deletes both
+# placements on the client, so the program says them again, same geometry.
+# Both must reach the client again: "unchanged" is measured against what the
+# client is holding, and after a retransmit it is holding neither.
+REPLACED = r"""
+import base64, sys, time
+px1 = base64.b64encode(bytes([255, 0, 0] * 8)).decode() # 4x2 red
+px2 = base64.b64encode(bytes([0, 255, 0] * 8)).decode() # 4x2 green
+def both(px):
+ sys.stdout.write("\x1b[1;1H")
+ sys.stdout.write("\x1b_Ga=t,f=24,q=2,s=4,v=2,i=7;%s\x1b\\" % px)
+ sys.stdout.write("\x1b_Ga=p,i=7,p=1,q=2,c=6,r=2\x1b\\")
+ sys.stdout.write("\x1b[1;20H")
+ sys.stdout.write("\x1b_Ga=p,i=7,p=2,q=2,c=6,r=2\x1b\\")
+ sys.stdout.flush()
+both(px1)
+time.sleep(2)
+both(px2)
+time.sleep(30)
+"""
+
+
def spawn(cols=80, rows=24, child=None, session=None):
pid, fd = pty.fork()
if pid == 0:
os.environ["TERM"] = "xterm-ghostty"
os.environ["SLOSH_CONFIG"] = "/nonexistent/slosh.kdl"
- os.execv(BIN, [BIN, "-s", session or SESSION, "--", "python3", "-c",
- child or CHILD])
+ os.execv(
+ BIN, [BIN, "-s", session or SESSION, "--", "python3", "-c", child or CHILD]
+ )
os._exit(127)
fcntl.ioctl(fd, termios.TIOCSWINSZ, struct.pack("HHHH", rows, cols, 0, 0))
return pid, fd
@@ -235,6 +258,26 @@ def main():
)
os.kill(pid3, 9)
+ # The other edge of "place once": a retransmit invalidates every placement
+ # of the image (kitty deletes them all when the data is replaced), not
+ # just the one that rode in the same call as the transmit. Un-invalidated,
+ # the second placement is judged unchanged and never said again -- a
+ # picture the client deleted and we believe it still has.
+ pid4, fd4 = spawn(child=REPLACED, session=SESSION + "-replaced")
+ replaced = drain(fd4, idle=3.5, limit=20.0)
+ transmits = [m.start() for m in re.finditer(rb"\x1b_Ga=t,", replaced)]
+ replaces = (
+ [c for c, _ in gfx_commands(replaced[transmits[1] :]) if c.startswith(b"a=p")]
+ if len(transmits) >= 2
+ else []
+ )
+ ok &= check(
+ "a retransmit re-places every placement of the image",
+ len(transmits) >= 2 and len(replaces) == 2,
+ f"{len(transmits)} transmits, then {len(replaces)} placements: {replaces[:4]}",
+ )
+ os.kill(pid4, 9)
+
os.write(fd2, b"\x01q")
drain(fd2, idle=0.2, limit=2.0)
for child in (pid, pid2):