diff options
| author | sl0p <sl0p@sl0p.foo> | 2026-08-14 01:01:04 +0200 |
|---|---|---|
| committer | sl0p <sl0p@sl0p.foo> | 2026-08-14 01:01:04 +0200 |
| commit | b6c175e58ee21e2532d2826411a5351d9a676c09 (patch) | |
| tree | 08562ab30350e6353d8f78eb19132d4e8e8a2b09 /src/server.c | |
| parent | sl0ptty: the split guide arms on dwell, not on contact (diff) | |
| download | slosh-b6c175e58ee21e2532d2826411a5351d9a676c09.tar.gz slosh-b6c175e58ee21e2532d2826411a5351d9a676c09.tar.xz slosh-b6c175e58ee21e2532d2826411a5351d9a676c09.zip | |
sl0ptty: kitty graphics passthrough
A program in a pane transmits an image and it appears. libghostty-vt parses
the protocol and tracks images and placements; what a multiplexer has to add
is re-emitting them to the client, which is exactly what tmux and zellij do
not do — and the reason tools fall back to sixel under them.
Per frame: walk the visible tab's panes, transmit each image once (a=t),
place every visible placement (a=p,C=1) after the cell diff so a repainted
cell cannot land on top of one, and delete placements that vanished. Image ids
are remapped into a high range, so two panes both using image 7 stay two
images and neither collides with what the client's terminal already had. A
reattaching client is told everything again, because it has seen nothing.
The cropping rule is the part worth remembering: a placement is N pixels drawn
across M cells, so the placement itself tells you the pixels per cell — the
client's font metrics never come into it. Clipping at a pane edge, or at the
top of the viewport when a tall image is partly scrolled off, moves the source
rectangle; asking for fewer columns or rows alone scales the image into them
instead of cropping it. The first version did exactly that, and dropped
partially-visible placements entirely rather than cropping them.
Tests cover the multiplexer-specific case first: two panes, both using image
id 7, must get different client ids. Then placement position in screen cells,
following the layout, cropping at edges and when taller than the pane, nothing
from collapsed panes, and removal when a pane closes or the image scrolls
away. A live check confirms the wire: transmitted once, re-placed per frame,
not re-transmitted.
416 checks green (389 headless + 27 live).
Diffstat (limited to 'src/server.c')
| -rw-r--r-- | src/server.c | 12 |
1 files changed, 12 insertions, 0 deletions
diff --git a/src/server.c b/src/server.c index 1579281..17ce05a 100644 --- a/src/server.c +++ b/src/server.c @@ -119,6 +119,16 @@ static char *b64(const char *in, size_t len, size_t *out_len) { return out; } +/* Images go out after the cell diff, so a repainted cell cannot land on top + * of a placement we just made. */ +static void push_graphics(server_t *s) { + conn_t *c = display_conn(s); + if (!c) return; + size_t len = 0; + const char *bytes = app_graphics(s->app, &len); + if (len) msg_send(c->fd, MSG_OUTPUT, bytes, len); +} + /* The clipboard lives on the client's machine, not ours, so a copy travels as * OSC 52 for the outer terminal to honour. */ static void push_clipboard(server_t *s) { @@ -280,6 +290,7 @@ int server_run(const char *name, const char *const argv[], uint16_t cols, } } s.screen.force_full = true; /* a fresh client knows nothing */ + app_graphics_reset(s.app); /* including any image we had sent */ pending_paint = true; next_frame = now_ms(); break; @@ -337,6 +348,7 @@ int server_run(const char *name, const char *const argv[], uint16_t cols, if (pending_paint && now_ms() >= next_frame) { app_compose(s.app, &s.screen); push_frame(&s); + push_graphics(&s); push_clipboard(&s); pending_paint = false; next_frame = now_ms(); |
