<feed xmlns='http://www.w3.org/2005/Atom'>
<title>slosh.git/src/config.c, branch v0.1.3</title>
<subtitle>an opinionated terminal multiplexer in C with programmable pane chrome</subtitle>
<id>https://git.sl0p.foo/slosh.git/atom/src/config.c?h=v0.1.3</id>
<link rel='self' href='https://git.sl0p.foo/slosh.git/atom/src/config.c?h=v0.1.3'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/'/>
<updated>2026-09-02T13:09:36Z</updated>
<entry>
<title>attach tag: just the count, in theme colours</title>
<updated>2026-09-02T13:09:36Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-02T13:09:36Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=cafa48e0a7e1eb48a783bfde6862c5fd82c7aede'/>
<id>urn:sha1:cafa48e0a7e1eb48a783bfde6862c5fd82c7aede</id>
<content type='text'>
The +x+y offset goes -- 'N clients' says the thing that matters. The
inverse-video chip goes with it: the tag now draws in attach_fg /
attach_bg from the theme, defaulting to gray ink on the terminal's
own background. Standing information, not an announcement.
</content>
</entry>
<entry>
<title>multi-attach: say who else is watching, and where you are</title>
<updated>2026-09-02T13:03:53Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-02T13:03:53Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=1a3ba183ae8f2613cae847c8f6460782512acc40'/>
<id>urn:sha1:1a3ba183ae8f2613cae847c8f6460782512acc40</id>
<content type='text'>
A small tag in each client's own top-right corner when several
terminals share the session: the viewer count, and on a view that is
a crop being panned by somebody else's cursor, the crop's offset into
the shared screen ("2 clients +12+4") -- which is exactly the moment
a screen moving on its own needs explaining. Stamped per client after
projection, because every terminal has its own count of others and
its own offset; the shared status bar is composed once and can be
cropped out of a panned view anyway. attach_indicator false hides
it.
</content>
</entry>
<entry>
<title>multi-attach: the config knobs</title>
<updated>2026-09-02T12:57:13Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-02T12:57:13Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=d698946464f25a0eb43730ffad15656f3247f56b'/>
<id>urn:sha1:d698946464f25a0eb43730ffad15656f3247f56b</id>
<content type='text'>
The patch made shared attach unconditional; these make it a policy.
multi_attach false restores the classic displacing attach
(EXIT_REPLACED, which old clients already understand). size_follows
picks whose size the shared screen takes: active (the patch's rule),
smallest (the tmux rule: filler, never a crop), or largest. The size
fold lives in apply_canvas, so any attach, resize or departure can
move the canvas under the folding policies -- including a non-active
client leaving, which under smallest may have been the very client
holding the floor down. A config reload re-applies both knobs to the
running session.
</content>
</entry>
<entry>
<title>shaders: remove the .so plugin loader</title>
<updated>2026-09-02T12:43:09Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-02T12:43:09Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=18d1aef0f9bf4620135fd9f77f26683e6f2f8c00'/>
<id>urn:sha1:18d1aef0f9bf4620135fd9f77f26683e6f2f8c00</id>
<content type='text'>
The static musl release builds cannot dlopen -- musl compiles it out
-- so loadable shader plugins were a feature most shipped binaries
could not use. Nothing we ship used it either: every shader in
contrib/shaders is an expression. Gone: the dlopen/glob loader in
shader.c, shader_dir in the config, the plugin ABI table and entry
macro, the Windows dl*/glob shims that existed only for it, the
contrib/shader-plugin skeleton, its tests, and every doc mention.
shader_abi.h stays as the shader model (cell_t, shade_ctx_t,
shader_t) with a note about what it used to be.
</content>
</entry>
<entry>
<title>path: a directory ends at either separator on Windows</title>
<updated>2026-08-28T13:09:18Z</updated>
<author>
<name>Peter Geissler</name>
<email>peter@haxx.in</email>
</author>
<published>2026-08-28T13:04:28Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=6f8bdf838b82c0c3326993a741985840dc130af6'/>
<id>urn:sha1:6f8bdf838b82c0c3326993a741985840dc130af6</id>
<content type='text'>
Two places split a path by hand, both with strrchr(path, '/'), both
falling back to "." when there was none: include_path() in config.c and
watch_config() in server.c. On Windows "none" is the ordinary case.
`SLOSH_CONFIG=C:\Users\you\slosh\config.kdl` is how a person writes it,
and a path that has been near a native API comes back all backslashes
whatever it started as.

So an include resolved against "." -- `cannot open ./themes/t.kdl` --
and, worse and quietly, the config watcher watched "." too. Not the
config's directory: the current directory of whatever started the
session. Hot reload did not fail, it watched somewhere else and waited.
Saving your config did nothing at all, with no error to go looking for.

The default path escaped this by accident. slosh builds it as
`C:\Users\you/.config/slosh/config.kdl` -- mixed, because a literal
joined to $HOME always is -- and that one embedded '/' is what
strrchr found. It worked for the path slosh writes and not for the one
a user does.

Both callers now use path_dir(), which learns to stop at a backslash
too, and server.c takes path_base() for the other half rather than
keeping its own copy of the arithmetic. Windows-only: a backslash is a
legal character in a POSIX file name, and splitting on it there would
invent a directory out of a file called `a\b`.

path_dir() also keeps the separator when what is left would be a bare
drive, because `C:\config.kdl` lives in `C:\` and `C:` names the
current directory on drive C, which is a different place.

Verified on the Windows box with a fully backslashed SLOSH_CONFIG:
--check passes, the include loads, and editing the config reloads the
running session. Watching a second directory is still unreliable; that
is the watcher's own bug and the next commit.
</content>
</entry>
<entry>
<title>path: an absolute path does not always start with a slash</title>
<updated>2026-08-28T13:02:49Z</updated>
<author>
<name>Peter Geissler</name>
<email>peter@haxx.in</email>
</author>
<published>2026-08-28T13:02:49Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=22e70baa7172e8487913cceae3d3ec89bbe40627'/>
<id>urn:sha1:22e70baa7172e8487913cceae3d3ec89bbe40627</id>
<content type='text'>
`include "C:/themes/t.kdl"` on Windows did not fail to find the file. It
found a different one, or rather looked for it: absoluteness was tested
as `path[0] == '/'`, a drive letter is not that, so the path was read as
relative and joined to the including file's directory. The result was

  cannot open C:/Users/you/cfgA/C:/Users/you/cfgB/t.kdl

which at least says what happened. A layout's `cwd="C:/srv/api"` took
the same turn through path_resolve() and said nothing at all -- the pane
started in the wrong directory, which is the failure this file's own
header comment is about.

Windows spells absolute three ways and all three now count: rooted on
the current drive (`\dir`), rooted on a named one (`C:\dir`, `C:/dir`),
and a UNC share. A drive letter has to be followed by a separator to
qualify, because `C:file` is relative to the current directory *of that
drive* -- a real thing, and not one we can resolve for anyone.

Windows-only, deliberately. `C:` is an ordinary file name on POSIX and a
path beginning with one is relative there, so reading it as a drive
would break the platform that works in order to fix the one that does
not.
</content>
</entry>
<entry>
<title>ctrl_d_exits: Ctrl-D at a prompt, where the tty cannot do it</title>
<updated>2026-08-28T12:41:42Z</updated>
<author>
<name>Peter Geissler</name>
<email>peter@haxx.in</email>
</author>
<published>2026-08-28T12:41:42Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=2fbd36c1cef6ea6c9fcf9c36afb5209089288257'/>
<id>urn:sha1:2fbd36c1cef6ea6c9fcf9c36afb5209089288257</id>
<content type='text'>
Windows has no VEOF. The console driver does not know the character, so
^D arrives at cmd.exe and PowerShell as an ordinary keystroke and they
ignore it -- it even echoes, which is how you find out: the line reads
`echo hello^D`. The one key that closes a pane on every other platform
does nothing at all, and closing a pane becomes a word you have to type.

That you can type `exit` instead is the tell: the intent was never
ambiguous, only unspoken. So slosh speaks it. What goes to the pane is
the word, not a signal and not a close from outside -- the shell runs
its own cleanup, returns its own status, and keep_dead decides what is
left behind, all three of which reaching past it would skip. It is also
what the user would have typed, so there is nothing new to explain.

The knob is what makes it honest, because a wrong guess costs a pane.
It fires only with the shell itself in the foreground (a program that
is running may want the EOF for itself -- `cat` reading to end is the
whole job), only on the primary screen (in an editor or a pager ^D is
half a page down, and the alternate screen is the pane stating that
rather than us matching process names), and only on an empty line
(POSIX sends EOF there; what it does not do is discard what you typed).

The line is an estimate. The shell's editor is on the far side of a
pty, so pane_send_key counts what it forwarded since the last Enter.
Editing it does not model -- a recalled history entry, ^U, completion
-- leaves the count too high, which forgets to exit. That is the
direction to be wrong in; the other one throws away a half-typed
command.

Default true on Windows, false on POSIX, where the line discipline
already does this and does it properly: ^D there is also EOF for `cat`
and every REPL, none of which is ours to intercept. A knob rather than
an #ifdef so the guards are the same code everywhere and can be tested
where the test suite runs -- test_ctrl_d.py turns it on and drives all
four decisions, plus the case that does exit.

Telling trapped from forwarded needs a program that reports what it
received: on POSIX a forwarded ^D also ends an idle shell, and looking
for the word `exit` cannot separate them either, because bash prints
`exit` itself on EOF.

Verified on the Windows box as well, where it is the default: ^D at an
idle cmd.exe prompt takes the pane (and with it the session, having no
panes left), `more` in the foreground keeps its own EOF, and a
half-typed line survives with the line still on screen.
</content>
</entry>
<entry>
<title>selection: drag past an edge scrolls the viewport under it</title>
<updated>2026-08-27T13:09:02Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-27T13:09:02Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=2ee881452aa4b19701a071f0333a15265e3b314e'/>
<id>urn:sha1:2ee881452aa4b19701a071f0333a15265e3b314e</id>
<content type='text'>
Text taller than the viewport was not selectable in one gesture: the
drag stopped dead at the pane's border. Two faults, one visible and one
waiting. The visible one: DRAG_SELECT motion subtracted the content
origin in uint16_t, so a pointer above or left of the pane underflowed,
the grid lookup failed, and the selection froze. The waiting one: the
anchor was a pair of *viewport* coordinates re-resolved on every extend,
so the moment anything scrolled mid-drag the anchor would have drifted
with the viewport, re-anchoring to whatever slid under the press.

The anchor is now a tracked grid ref -- the thing lib-vt documents as
being for 'long-lived anchors such as selections' -- so it follows its
cell across scrolling, pruning and reflow, and only the near end of the
selection moves. If a program flooding output prunes the anchored row
out of scrollback entirely, the selection keeps what it had; there is no
cell left to anchor a new answer to.

Motion clamps into the content rect (a pointer past an edge is pointing
at the nearest cell), and holding past the top or bottom edge scrolls on
a clock rather than on motion, because a resting pointer sends no events
and 'keep going' is exactly what resting it there means. app_tick() does
the step, app_next_deadline_ms() wakes the poll loop for it -- the same
mechanism toasts and hover guides already use -- and both front ends
call the tick, so the behaviour exists under the server and the headless
harness alike. Rows per step is how far past the edge the pointer sits:
one row past creeps, further hurries, so the hand already dragging is
also the throttle. The metronome is select_scroll_ms, a knob so the test
can configure the clock down instead of sleeping.

The test drags to one row above the pane and simply holds: the viewport
walks to the top of scrollback (clamping makes the endpoint exact), the
copy starts at a line that was never on screen with the press, and still
ends at the pressed row -- that last check is the tracked anchor's.
</content>
</entry>
<entry>
<title>compact: shared borders instead of gaps (D23)</title>
<updated>2026-08-25T12:23:33Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-25T12:23:33Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=b744a90859bf248bfbea97f990668301b5fa9537'/>
<id>urn:sha1:b744a90859bf248bfbea97f990668301b5fa9537</id>
<content type='text'>
One knob, `compact true`: panes pack flush against 1-cell divider lines
that meet in real junctions, one frame rings the tab, and a pane's title
rides the line above it -- a boundary costs one cell where the default
look spends four. The lines are strokes: each write says which edges of
its cell it needs, unions with what is there, and never clobbers text,
so ├ ┬ ┼ fall out of paint order and the focus ring recolours without
inventing arms. The divider *is* the gap (eff_gap, one answer for the
layout, split_fits and the resize clamp), so gap hits, hover hints and
corner drags work unchanged. Interior edges give up click-to-split --
one cell cannot hold two verbs -- and a pane's status row overlays its
last content row. Floats, zoom and the D6 flatten keep classic frames
through place_hidden: zero new tree cases.

Underneath it, mode or no mode: a border drag now moves the boundary on
that side, converted on the first motion so the press that never moves
keeps its old meaning -- the handle's click still splits, the rim's is
still deliberately nothing (drag.rim). Which is also the fix for gap 0,
where flush panes leave no gap cell to grab and the resize gesture had
silently vanished with it.
</content>
</entry>
<entry>
<title>slosh: bell_mark defaults to "[!]"</title>
<updated>2026-08-20T22:27:58Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-20T22:27:58Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=2e74c6dceef18f75b849c729bd768330f73396fb'/>
<id>urn:sha1:2e74c6dceef18f75b849c729bd768330f73396fb</id>
<content type='text'>
Plain ASCII that says what it means, over a dot that had to be noticed.
The mark was always a short string rather than a glyph (the emoji-width
argument in config.kdl stands); the default just stops being shy.
</content>
</entry>
</feed>
