MMotor OS
Userspace

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:

KeyWhat 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, xselect a pane, go round them, zoom one, kill one
C-arrows, M-arrowsmove the border beside the pane, by one cell or five
( ) $ sprevious, 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
rredraw the console from scratch
ddetach
C-asend 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

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.