aboutsummaryrefslogtreecommitdiffstats
path: root/src/server.c
diff options
context:
space:
mode:
authorsl0p <sl0p@sl0p.foo>2026-08-14 01:01:04 +0200
committersl0p <sl0p@sl0p.foo>2026-08-14 01:01:04 +0200
commitb6c175e58ee21e2532d2826411a5351d9a676c09 (patch)
tree08562ab30350e6353d8f78eb19132d4e8e8a2b09 /src/server.c
parentsl0ptty: the split guide arms on dwell, not on contact (diff)
downloadslosh-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.c12
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();