1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
|
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>slosh — a terminal multiplexer</title>
<meta name="description" content="slosh: panes, tabs, sessions you can detach from. One static binary, written in C on libghostty-vt.">
<link rel="stylesheet" href="site.css">
</head>
<body>
<nav class="nav" aria-label="Site">
<a href="./" aria-current="page">slosh</a>
<a href="demo/">demo</a>
<a href="docs/">docs</a>
<span class="nav-fill"></span>
<a href="https://slosh.foo/src">src</a>
</nav>
<main>
<header class="hero">
<h1>slosh</h1>
<p class="tagline">A terminal multiplexer — panes, tabs, sessions you can
detach from — written in C on top of
<a href="https://github.com/ghostty-org/ghostty">libghostty-vt</a>.</p>
<p class="hero-links">
<a class="btn" href="#install">install</a>
<a class="btn ghost" href="demo/">try it in the browser</a>
</p>
</header>
<!-- ================================================================ -->
<section id="video">
<h2>watch it</h2>
<video controls preload="metadata" src="slosh-preview.mp4"></video>
</section>
<!-- ================================================================ -->
<section id="install">
<h2>install</h2>
<div class="cmd" data-copy="curl -fsSL https://slosh.foo/install.sh | sudo sh">
<pre><code><span class="prompt">$</span> curl -fsSL https://slosh.foo/install.sh | sudo sh</code></pre>
<button class="copy" type="button" aria-label="Copy command">copy</button>
</div>
<p class="aside">Piping a URL into a root shell should make you flinch —
<a href="install.sh">read it first</a>, it is short. Or skip it entirely:
building from source needs <a href="https://ziglang.org">zig</a> 0.16 and
nothing else. No cmake, no autotools, no libraries. The result is one static
binary.</p>
<div class="cmd">
<pre><code><span class="prompt">$</span> git clone https://slosh.foo/src slosh && cd slosh
<span class="prompt">$</span> make vendor <span class="c"># the vendored terminal core, once</span>
<span class="prompt">$</span> make <span class="c"># about a second</span>
<span class="prompt">$</span> sudo install build/slosh /usr/local/bin/</code></pre>
</div>
<p class="aside">On Windows it is the same one binary: <code>slosh.exe</code>,
Windows 10/11, x86-64 and ARM64, no runtime dependency beyond the OS. Panes
are ConPTYs and sessions are AF_UNIX sockets, so everything below works there
too. It is cross-compiled from a mac or a Linux box
(<code>make -f Makefile.windows</code>), then copied across —
<a href="docs/windows.html">the details</a>.</p>
<p>Then:</p>
<div class="cmd">
<pre><code><span class="prompt">$</span> slosh <span class="c"># attach to session "main", creating it if needed</span>
<span class="prompt">$</span> slosh -s work <span class="c"># a named session</span>
<span class="prompt">$</span> slosh ls <span class="c"># what is running</span></code></pre>
</div>
<p>Sessions survive detaching, and your ssh connection dying. Then
<kbd>C-a</kbd> <kbd>\</kbd> to split, <kbd>C-a</kbd> <kbd>?</kbd> for the
keys, <kbd>C-a</kbd> <kbd>p</kbd> for every command by name.</p>
</section>
<!-- ================================================================ -->
<section id="different">
<h2>what is different about it</h2>
<dl class="points">
<dt>The mouse works properly.</dt>
<dd>Click the middle of a border to split toward it, drag a gap to move a
boundary, drag a pane by its title to swap it. Everything drawn registers
what it is as it is drawn, so a click and a preview cannot disagree.</dd>
<dt>The layout is recomputed, never stored.</dt>
<dd>Small terminal? The tab becomes a list of headers, and comes back to
exactly the arrangement you had. Nothing drifts, because there is nothing
stored to drift.</dd>
<dt>Dead panes keep their output.</dt>
<dd>A pane told to run something keeps what it printed when that thing
exits, with <code>[re-run]</code> and <code>[close]</code> in its
frame.</dd>
<dt>Configuration is one KDL file.</dt>
<dd>It reloads on save, can be built from <code>include</code>d pieces, and
refuses a broken file instead of half-applying it.</dd>
<dt>Colour passes over cells.</dt>
<dd>Dim, tint, rulers, spotlights, and a border that can flash when a pane
rings — with strengths written as expressions.</dd>
<dt>Scriptable to the same depth it is usable.</dt>
<dd>One JSON object per line over the session's socket, and a pane can draw
its own status bar and buttons with an escape sequence.</dd>
</dl>
<p class="aside">The multiplexers we have are crusty, and the things that
make them awkward follow from how the programs are built. So: start over, and
stand on the shoulders of the person who already solved the genuinely hard
part — the terminal core from <a href="https://ghostty.org">ghostty</a> does
the VT parsing, scrollback, selection, images and key encoding. What sits on
top is about 15k lines of C, and recomputes rather than remembers.
The decisions behind it: <a href="docs/design.html">how it
works</a>.</p>
</section>
<!-- ================================================================ -->
<section id="try">
<h2>try it without installing anything</h2>
<p>The <a href="demo/">demo page</a> boots a riscv64 Linux —
Fabrice Bellard's TinyEMU compiled to WebAssembly — with slosh running as the
thing you land in. Nothing is installed, nothing is uploaded, and reloading
the page throws the machine away. It streams about 11 MiB once, then
never touches the network again.</p>
<p class="aside">It is an emulator interpreting every instruction, so the
machine is honest but not fast. The real thing redraws in microseconds.</p>
</section>
<!-- ================================================================ -->
<section id="docs">
<h2>docs</h2>
<ul class="doclist">
<li><a href="docs/keys.html">keys</a><span> — the leader, the defaults, rebinding, the palette</span></li>
<li><a href="docs/panes.html">panes and tabs</a><span> — splitting, moving, zooming, the mouse</span></li>
<li><a href="docs/config.html">configuration</a><span> — one file, live reload, themes, include</span></li>
<li><a href="docs/shaders.html">shaders</a><span> — colour passes over a pane's contents</span></li>
<li><a href="docs/chrome.html">chrome</a><span> — the same passes over a pane's frame</span></li>
<li><a href="docs/layouts.html">layouts</a><span> — a session as a file you check in</span></li>
<li><a href="docs/scripting.html">scripting</a><span> — the control socket, and what a pane can draw</span></li>
<li><a href="docs/design.html">how it works</a><span> — the two decisions that shape the rest</span></li>
<li><a href="docs/windows.html">windows</a><span> — the native port, and what is different there</span></li>
</ul>
</section>
</main>
<footer class="foot">
<span>MIT</span>
<span>C on <a href="https://github.com/ghostty-org/ghostty">libghostty-vt</a></span>
<span>one static binary</span>
</footer>
<script>
// The one script on the page: the copy buttons. No framework, no tracker,
// nothing fetched from anywhere.
for (const box of document.querySelectorAll(".cmd[data-copy]")) {
const btn = box.querySelector(".copy");
btn.addEventListener("click", async () => {
try {
await navigator.clipboard.writeText(box.dataset.copy);
btn.textContent = "copied";
setTimeout(() => (btn.textContent = "copy"), 1200);
} catch {
btn.textContent = "select it";
}
});
}
</script>
</body>
</html>
|