diff options
| author | user <user@clank> | 2026-08-20 23:23:07 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-20 23:23:07 +0200 |
| commit | 965966f9fb3808fbc69e6cb55960ad7a3c72c5f4 (patch) | |
| tree | 388d17a48155b9fae25c65fdc6c8765b6bb3ec01 /include/slosh.h | |
| parent | slosh: floating panes and the drop grammar (diff) | |
| download | slosh-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.h | 16 |
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. */ |
