rmux: the multiplexer
rmux is a terminal multiplexer for Motor OS: one console, many programs, each believing it owns a terminal. A server owns the programs and does the rendering; a thin client attaches to it, relays keystrokes, and paints what it is sent. Detaching kills the client and leaves everything else running.
Why not port tmux? tmux is built on one pty per pane, and Motor OS has no pty, no tty
layer, no termios, no ioctl, and no signals. It has one console, a polled UART owned by
sys-tty, and a multiplexer is what makes that one console usable, so it had to be
written. rmux has zero dependencies, only Rust std, builds and runs on Linux
as well, and is tested there against real tmux: a corpus of key scripts is driven
through both, and the two must paint the same picture. Its differences from tmux are
listed and each is itself tested.
Using it
rmux attach to the most recent session, or start one
rmux new [-s name] start a session and attach
rmux attach [-t name] attach to a named session
rmux ls list sessions
rmux kill-session -t name kill it and everything in it
The prefix is C-a. After it:
| Key | What it does |
|---|---|
| c n p 0-9 , & | new, next, previous, select, rename, kill a window |
| | - | split the pane side by side, or one above the other |
| arrows, o, z, x | select a pane, go round them, zoom one, kill one |
| C-arrows, M-arrows | move the border beside the pane, by one cell or five |
| ( ) $ s | previous, next, rename session, and a list to pick from |
| [ ] | copy mode, and paste the top buffer |
| : | type a command, in the same words rmux.toml binds |
| r | redraw the console from scratch |
| d | detach |
| C-a | send one literal C-a to the pane |
Without the prefix, S-Left and S-Right change window and
M-arrows change pane. Copy mode is vi by default: hjklwb0$,
g/G, C-u/C-d/C-f/C-b,
/ and ? with n/N, Space to start a
selection, Enter to take it, q to leave. mode-keys = "emacs"
swaps in tmux's other table.
Overrides go in /user/cfg/rmux.toml on Motor OS and
$HOME/.config/rmux.toml on Linux: the key = value subset of TOML
plus three binding tables. A missing file is fine; the compiled-in defaults are those of
the .tmux.conf at the head of the project, and every option in it works with
no configuration file present.
Under QEMU, start the VM with run-qemu-echr.sh so that C-a
reaches rmux instead of QEMU's monitor.
What works
- Panes: splits over a binary tree, box-drawing borders, geometric selection, zoom, kill, and resizing.
- Windows: new, next, previous, select by number, rename, kill, and
renumber-windows. A window's name follows an explicit terminal title, which rush sets to the foreground command's name, until a rename takes over. - Sessions: real and multiple, named or auto-numbered, with detach and
re-attach and a list to pick from;
aggressive-resizesizes a window to the smallest client watching it. - A terminal emulator per pane: cursor motion, erases, insert and delete, scroll regions, SGR including the 256-color palette and true color, the alternate screen, autowrap, bracketed paste, titles, and the terminal-size protocol answered with the pane's own geometry (see Terminals). This is what lets red and rush size themselves to a pane.
- Scrollback and copy mode: history compacted to text plus style runs, vi and emacs motions, search, selection, and server-wide paste buffers.
- The status line: session name, window list, the copy-mode indicator, and the prompts, in near-black on amber, with the current window in a deeper amber. It doubles as the message line for errors.
- Rendering by frame diff: a keystroke echoed in a pane costs one byte, and moving between panes repaints no pane content. The console is a polled UART, so bytes are not free.
- Resizing: the console changing size is noticed within a second and the layout follows.
Non-goals
The mouse; the #{...} format language; control mode; hooks;
if-shell; layouts by name; pane marking; session groups;
run-shell; the host clipboard (OSC 52); wide characters, which are stored but
treated as one column; and terminfo, since rmux emits a fixed, conservative ANSI
vocabulary and assumes the same of the programs in its panes.
Deliberate differences from tmux
prefix & and prefix x kill outright where tmux asks first. A mode owns every key, the prefix included, so q comes before the prefix in copy mode. Copy mode's word is a run of non-blanks, its search is case-sensitive and not incremental, and its indicator borrows the status row. The active pane's border is not highlighted; the cursor says which pane is in front. And rmux has no paste heuristic: a bound key is rmux's however fast it arrived.
How it works on Motor OS
A pane's child is spawned with the terminal launch hint, so is_terminal()
answers true for it, exactly as it does for children of sys-tty and russhd. What a pty
would otherwise provide is provided elsewhere: the pane sets $COLUMNS and
$LINES, answers the size protocol with its own geometry, writes byte
0x03 for an interrupt, and calls Child::kill to kill. The one thing
a pty buys that has no equivalent, SIGWINCH, is replaced by a subscription:
a program that enables mode 2048 gets its size written into its stdin at once and on
every later resize.
The client and server are unrelated processes and meet over loopback TCP: the server
binds 127.0.0.1:0 and writes the port to a file under the scratch
directory. The server is spawned detached, which needs CAP_SPAWN_DETACHED;
that is why rmux is on rush's detached-spawn list, and why an rmux server survives the
SSH session that started it.