diff options
| author | user <user@clank> | 2026-09-07 15:54:34 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-09-07 15:54:34 +0200 |
| commit | 2a440fbc01c4e4afd58e15ace5eddd4a171d15ef (patch) | |
| tree | 06cef911eca321ea6b00ed8aabc91b5242077e10 | |
| parent | Merge branch 'graphics-fixes' (PR #2 from iksteen): five fixes to the kitty p... (diff) | |
| download | slosh-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.
| -rw-r--r-- | src/graphics.c | 11 | ||||
| -rw-r--r-- | tests/live_graphics.py | 47 |
2 files changed, 55 insertions, 3 deletions
diff --git a/src/graphics.c b/src/graphics.c index 77b3b7f..86a5b56 100644 --- a/src/graphics.c +++ b/src/graphics.c @@ -220,6 +220,14 @@ void gfx_place(graphics_t *g, const gfx_req_t *req) { transmit(g, img, req->px_w, req->px_h, req->format, req->compression, req->data, req->data_len); transmitted = img->sent; + /* Replacing an image's data deletes every placement of it on the client, + * not just the one this call is about to make: kitty drops them all. The + * other placements of this image -- the other pieces of a cut one, a + * second placement in another spot -- are invalidated here, or a `same` + * verdict below leaves them un-said while the client no longer has them. */ + if (transmitted) + for (size_t i = 0; i < g->nplaces; i++) + if (g->places[i].out_id == img->out_id) g->places[i].shown = false; } uint32_t pid = req->place_id ? req->place_id : 1; @@ -253,7 +261,8 @@ void gfx_place(graphics_t *g, const gfx_req_t *req) { bool same = slot->shown && !transmitted && slot->col == req->col && slot->row == req->row && slot->cols == req->cols && slot->rows == req->rows && slot->x_off == req->x_off && - slot->y_off == req->y_off && slot->scale_cols == req->scale_cols && + slot->y_off == req->y_off && + slot->scale_cols == req->scale_cols && slot->scale_rows == req->scale_rows && slot->sx == req->sx && slot->sy == req->sy && slot->sw == req->sw && slot->sh == req->sh; slot->col = req->col; 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): |
