aboutsummaryrefslogtreecommitdiffstats
path: root/include/slosh.h
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-20 23:23:07 +0200
committeruser <user@clank>2026-08-20 23:23:07 +0200
commit965966f9fb3808fbc69e6cb55960ad7a3c72c5f4 (patch)
tree388d17a48155b9fae25c65fdc6c8765b6bb3ec01 /include/slosh.h
parentslosh: floating panes and the drop grammar (diff)
downloadslosh-965966f9fb3808fbc69e6cb55960ad7a3c72c5f4.tar.gz
slosh-965966f9fb3808fbc69e6cb55960ad7a3c72c5f4.tar.xz
slosh-965966f9fb3808fbc69e6cb55960ad7a3c72c5f4.zip
slosh: OSC 8 hyperlinks pass through the compositor
lib-vt tracks a hyperlink per cell and slosh dropped every one of them: the client's terminal can only offer a link it was sent, so `ls --hyperlink`, gcc diagnostics and friends silently lost theirs inside the session. The link rides beside the cell -- cell_t is the shader ABI and a link is not a colour -- as a plane of ids into an interned URI table, append-only so an id in prev_link still means the same URI next frame (which is what lets the diff compare ids instead of strings), and capped so a hostile pane cannot grow it forever. URIs with control bytes are refused outright: they are emitted verbatim inside an OSC, and a control byte in one is a way to write escape sequences into the client's terminal. The diff wraps emitted runs in OSC 8, closes what it opens before the frame ends, and treats a link-only change as a change. Chrome painted over a link takes the link with it, so a frame, modal or float across a URL is never clickable with the pane's link; links survive scrollback with the text that carries them. Plain-text URLs stay the outer terminal's own matcher, with the catch documented in panes.md: under mouse capture Ghostty only offers links with shift added to the gesture (shift+cmd hover/click), shift being its capture escape. Wrapped URLs can never be regex-matched through a multiplexer -- rows repaint as hard lines -- which is exactly what OSC 8 links, passing through whole, are for. `snapshot format:"bytes"` returns the emitter's own output for a frame (a second call is the delta): the model and the bytes are two different things, and this codebase already paid once for asserting on one while the other was wrong.
Diffstat (limited to 'include/slosh.h')
-rw-r--r--include/slosh.h16
1 files changed, 16 insertions, 0 deletions
diff --git a/include/slosh.h b/include/slosh.h
index 834879f..45392af 100644
--- a/include/slosh.h
+++ b/include/slosh.h
@@ -47,6 +47,16 @@ typedef struct {
uint16_t cols, rows;
cell_t *cur; /* what we want on screen */
cell_t *prev; /* what the terminal last received */
+ /* OSC 8 hyperlinks, carried beside the cells rather than in them: cell_t
+ * is the shader ABI and a link is not a colour. 0 is no link; anything
+ * else is 1+index into `links`. The table is append-only for the screen's
+ * lifetime, so an id in `prev_link` still means the same URI next frame —
+ * which is what lets the diff compare ids instead of strings — and it is
+ * capped, because a hostile pane must not be able to grow it forever: a
+ * link past the cap is dropped, which costs a click and nothing else. */
+ uint16_t *cur_link, *prev_link;
+ char **links;
+ size_t nlinks;
bool force_full;
hitlist_t hits;
@@ -77,6 +87,12 @@ uint16_t screen_cells(const char *txt);
* of characters when any of them is wide. */
uint16_t screen_text(screen_t *s, uint16_t x, uint16_t y, const char *txt,
color_t fg, color_t bg, uint16_t attrs);
+/* Intern a hyperlink URI into this screen's table: an id for screen_set_link,
+ * or 0 for none — an empty URI, one with control bytes in it, or a full
+ * table all answer 0, because a link that cannot be emitted safely is a link
+ * the frame does not carry. */
+uint16_t screen_link_id(screen_t *s, const char *uri, size_t len);
+void screen_set_link(screen_t *s, uint16_t x, uint16_t y, uint16_t id);
/* Diff cur against prev into s->out (the minimal byte stream for a terminal). */
void screen_render(screen_t *s);
/* screen_render, then write it to fd. */