term-wm is the Spatial Terminal Desktop Environment for Remote Workspaces: floating, z-ordered windows, automatic zero-prefix input passthrough, and persistent multi-viewer workspaces, running headless inside any standard terminal over plain SSH.
The Graphical Desktop for SSH.
Designed for Linux, macOS, and Windows, term-wm brings the spatial organization of a traditional graphical desktop environment (like GNOME or KDE) directly to the command line: mathematically precise tiling, overlapping floating windows with mouse support, and complete desktop chrome (panels, command palette, tasks, and overlays) without requiring a display server.
See the changelog for history (starting with v0.9.0-alpha).
Traditional terminal multiplexers treat the character grid as a rigid, planar matrix governed by memorized prefix chords. term-wm operates one level up: it is a desktop compositor for the ANSI/VT character-cell grid, pairing the deployment simplicity of a headless TUI with the spatial sophistication of a modern graphical desktop, over the same SSH connection you already use.
| Capability | term-wm | tmux / GNU screen | Zellij | WezTerm |
|---|---|---|---|---|
| Runs headless over plain SSH | Yes (no display server) | Yes | Yes | Local GUI app; remote muxing needs extra client/server setup |
| Window model | Hybrid BSP/N-ary tiling plus free-floating layer with z-order drop shadows and depth shading | Rigid 2D panes/windows | Tiling panes with basic grid-bound floating | Native GUI tabs/splits |
| Input routing | Automatic Direct Input Mode via PTY state tracking (no prefix chords to memorize) | Manual prefix chords (Ctrl+B) |
Modal keybindings (explicit mode switching) | Standard local GUI keyboard capture |
| Session persistence | Embedded gateway daemon auto-spawns on first launch; sessions survive disconnects and restarts with zero setup | Persistent but manually managed sessions | Persistent, with built-in layout resurrection | Requires matching client/server daemon configuration |
| Multi-viewer collaboration | Multiple viewers attach to one workspace channel over SSH; attributed events (per-viewer connection IDs) let a host evict one viewer without killing running PTYs | Shared sockets with permissive permissions or third-party wrappers | Shared sessions/web client needing tunneling and tokens | Not designed for multi-user terminal sharing |
| Mobile & narrow viewports | Automatic Monocle mode; touch Floating Action Button with content dodging | Fixed grid output | Keyboard-centric hints consume scarce space | Requires a full desktop environment |
- True Spatial Compositing Over SSH: Mouse-driven window dragging, edge snapping with ghost preview outlines, and z-ordered drop shadows with depth shading, rendered entirely in the character grid of any standard terminal emulator.
- Zero-Setup Session Persistence: A single self-contained binary embeds both the window manager and a background session gateway. On first launch a detached daemon is auto-spawned, so windows, layouts, workspaces, and running PTY processes survive terminal restarts and network drops.
- Your Project Is the Workspace: Launch
term-wmfrom a project folder and it takes that folder's name for the menu, floating action button, and an automatically created matching workspace. Tasks you start keep running on the background gateway daemon after you close the app; return later (even over SSH), pick that workspace from the Command Palette, and everything is where you left it. - Windows & Tasks Across Workspaces: The Command Palette lists every workspace with live counts of open windows and still-running tasks, so you always know where work is active before you switch. Stopping the gateway warns you first, with totals for every session it would take down.
- Autonomous Direct Input Mode:
PtyStateTrackercontinuously monitors the PTY byte stream (built on the forkedterm-wm-vt100parser). The moment a child app requests the alternate screen, mouse tracking, or custom scroll margins,term-wmsteps aside into zero-delay, unbuffered passthrough; keyboard and mouse are yielded independently, so an app likenanokeeps native text selection. - Unified Window Topology: Mathematically precise BSP/N-ary tiling, free-floating stacks, Maximized mode, and mobile-friendly Monocle mode in one layout engine.
- Multiplayer SSH With Attribution: Every input and layout event carries a unique viewer connection ID through the
muxioRPC pipeline. Attach multiple viewers to the same workspace channel and use Detach Viewer to remove one participant without terminating its processes or disturbing the rest. - Context-Aware Task Integration:
.term-wm/tasks.jsonfiles are discovered automatically and surface as searchable entries in thenucleo-powered Command Palette, executing in dedicated PTY windows that stay open with explicit exit markers so build failures are never lost.
Workspaces, persistent sessions, directory-based workspace naming, cross-workspace counts, and project tasks ship enabled by default (cargo install term-wm); custom builds using --no-default-features exclude them.
Build and run from source (Rust 1.85+, edition 2024; no extra toolchain needed):
git clone https://github.com/jzombie/term-wm
cd term-wm
cargo run --releaseThis opens a new default workspace with two terminal windows by default. On first launch a detached background session daemon is auto-spawned, so workspaces and their sessions persist across terminal restarts and SSH disconnects; inspect or stop it with --list-channels / --stop-daemon, and pick a different workspace with -w. Pass programs as arguments to open them in new windows:
cargo run --release -- vim
cargo run --release -- -n 4 # open 4 windows
cargo run --release -- -n 3 -- ls -la # 3 windows; the first runs `ls -la`
cargo run --release -- -r "vim -l" -r "htop" # 2 windows, one command each
cargo run --release -- -n 4 -r "vim -l" -r "htop" -- git log --oneline # 4 windows: 3 commands + 1 default shellOptions (term-wm -h):
-n, --count <N>: number of windows to open (default 2; min 1); only takes effect on new sessions--scrollback <N>: scrollback buffer size per terminal window (default 2000); only takes effect on new sessions-r, --run <CMD>: command to run in a window; repeatable, one window per--run. A trailing-- CMD...runs one command in a window after the--runwindows. Remaining windows launch default shells. Only takes effect on new sessions.-w, --workspace <NAME>: workspace to open. When omitted, the launch folder's name becomes the workspace (and the menu/FAB label); each workspace maps to its own daemon channel<workspace>/mainwith its own PTY session and window-manager instance--no-wm: run without the window manager (headless session client mode)--stop-daemon: stop the running background session daemon--list-channels: list channels and their sessions/clients, then exit-f, --force: force--stop-daemoneven when sessions/participants are active--no-session-persistence: disable session-persistence behavior at runtime (workspaces, gateway, daemon modes); only effective when thesession-persistencefeature is compiled in (it is by default)-h, --help,-V, --version
New terminal windows launch the shell from $SHELL (Unix) or %COMSPEC% (Windows).
| Action | Key |
|---|---|
| Open Command Palette (Super Key) | Ctrl+A |
Send Ctrl+A to the focused app |
Ctrl+A (When Command Palette is open) |
| Cycle focus between windows | Tab / Shift+Tab (When Command Palette is open) |
term-wm automatically enters Direct Input Mode (unfiltered, zero-delay key/mouse passthrough) whenever a child app requests the alternate screen buffer, mouse tracking, or custom scroll margins.
Direct Input Mode is split into two independent dimensions: keyboard (alternate screen / custom margins → raw key passthrough) and mouse capture (the app explicitly requested mouse tracking). Keyboard and mouse are granted independently: an app on the alternate screen without mouse tracking (e.g. pico/nano) keeps native text selection and wheel scrolling.
This mode is application-specific and different windows running different applications can be in different modes at once.
In Direct Input Mode, the following keybindings are not-effective, and are contingent upon the app running inside the window to handle them.
| Action | Keybinding / Input |
|---|---|
| Scrollback Navigation | PageUp / PageDown / Home / End |
| Scroll One Line | Shift + Up / Shift + Down |
| Select & Copy Text | Mouse Click & Drag (release to copy) |
| Paste | Mouse Right-Click |
Note on Clipboard Sync: Clipboard behavior depends on your host OS and terminal emulator. Standard keyboard shortcuts (e.g.,
Cmd+C/Cmd+Von macOS,Ctrl+Shift+C/Ctrl+Shift+Von Linux/Windows) may work depending on your terminal's pass-through rules, but are not guaranteed.
Clipboard split-brain:
term-wmkeeps an internal clipboard alongside your OS clipboard. In most setups they stay in sync, but where the OS clipboard is unreachable (e.g. inside a terminal that doesn't support OSC 52, or over SSH), the two can diverge. Paste is one unified action: it reads the OS clipboard when available and otherwise falls back to the internal copy, so you never have to pick between them. It is bound to mouse right-click, and if a Direct Input Mode app is consuming right-click, Paste is also available from the Command Palette.
Clipboard enablement in Direct Input Mode: While a window is in Direct Input Mode,
term-wm's mouse-managed clipboard integration (click-and-drag selection copy and right-click paste) is overridden: mouse events are forwarded to the running application unfiltered, and clipboard handling within that application is the application's responsibility. Application-initiated copy continues to work, as OSC 52 copy sequences emitted by the running application are still intercepted and relayed to the system clipboard.
term-wm is designed to be highly resilient, running anywhere a standard terminal environment is available, but relies on modern terminal standards for its optimal presentation.
- Colors: Truecolor (24-bit) support is highly recommended. The application will gracefully degrade its color palette in 256-color or 16-color environments, but UI themes and drop shadows are designed against 24-bit depth.
- Unicode & Fonts: Requires a UTF-8 compatible environment and a font capable of rendering standard Unicode box-drawing characters to properly construct window borders and layout splits.
- Linux Virtual Terminals (TTY):
term-wmis fully usable in raw Linux VTs (e.g., accessed viaCtrl+Alt+F1). While the core window management and multiplexing logic remains 100% functional, visual presentation will look significantly different due to the kernel framebuffer's strict font and color limitations. - Non-Standard OS Installs: Minimal or headless OS installations must ensure a valid
terminfodatabase is present and that theLANGenvironment variable is correctly set to a UTF-8 locale to prevent layout corruption.
See docs/compatibility.md for full compatibility details.
term-wm is engineered with a strict modular architecture across a multi-crate Cargo workspace, separating core domain logic from presentation, with the draw pipeline built on Ratatui. Layout calculation, rendering, and PTY I/O are decoupled so the UI thread never blocks on I/O.
The full developer tour (the crate responsibility map, window lifecycle, tiling core, async threading model, draw pipeline, testability, and code coverage) lives in docs/DEVELOPMENT.md.
term-wm is a self-contained binary that embeds both the window manager and a background session daemon (gateway). On first launch a detached gateway is auto-spawned and the TUI runs as an inner session-backed process, giving you persistent sessions without any external daemon setup.
- Workspaces: A workspace is a named channel namespace on top of the session daemon. Each workspace (e.g.
default,dev) maps to a daemon channel<workspace>/mainwith its own PTY session and window-manager instance. Start in a workspace with-w/--workspace <NAME>, or omit it and the launch folder names it for you. - Switching workspaces: From the Command Palette, use New Workspace to create one, Switch to Workspace:
<name>to switch without restarting the process (the viewer's IPC is rebound to the target channel, and the previously shown workspace keeps running in the background), and Detach Viewer to disconnect the current viewer from its session without terminating the PTY process. Workspace entries appear only when session persistence is active, and each entry shows live counts of open windows and running tasks. - Persistence gateway: The daemon endpoint is
term-wm/<user>/gateway. It deliberately does not depend on the runtime environment:--env/TERM_WM_ENVscope project-task visibility only, so changing a profile can never fork daemon lifecycles. Local development isolation is enforced at the toolchain boundary: the committed.cargo/config.tomlinjectsTERM_WM_NAMESPACE=term-wm-dev, so every cargo-driven execution (cargo run,cargo test) usesterm-wm-dev/<user>/gatewaywhile the OS-level<user>segment stays derived at runtime (multi-tenant safe on shared machines). Binaries executed directly bind the sharedterm-wm/<user>/gateway.--gateway <name>overrides the endpoint wholesale per invocation (multi-segment paths round-trip byte-exact), and auto-spawned daemons are pinned to the launcher's resolved endpoint via a hidden--gateway <name>argument so client and daemon can never disagree. Bothterm-wm --helpandterm-session --helpprint aPersistence gateway:footer showing the resolved endpoint. - Runtime disable: Pass
--no-session-persistence(or setTERM_WM_NO_SESSION_PERSISTENCE) to disable workspace/session-persistence behavior at runtime, even when the feature is compiled in. - Managing the daemon:
--list-channelsshows every workspace channel, its session, and its attached clients;--stop-daemonshuts the background gateway down (a confirmation dialog in the Command Palette warns that every workspace session will be terminated, with totals; the CLI refuses while sessions are live unless-f/--forceis given);--no-wmruns a headless session client without the window manager.
Three palette actions terminate different things. Picking the right one matters:
| Action | What ends | What survives |
|---|---|---|
| Detach Viewer | Only your viewing connection | Everything: the workspace keeps running headless on the daemon with all windows and tasks alive; other viewers are unaffected; reattach anytime |
| Exit UI (asks first) | This workspace: its window-manager process exits, taking its windows and running tasks with it | Your other workspaces and the gateway daemon |
| Stop Gateway Daemon (asks first, with totals) | Every workspace session for every user, then the daemon itself | Nothing session-related; a fresh daemon auto-spawns on your next launch |
In builds without session persistence there is nothing to detach from or stop: Exit UI simply quits the app and its processes.
| Variable | Purpose | Default |
|---|---|---|
TERM_WM_ENV |
Runtime environment (dev/prod/test, case-insensitive); scopes project-task visibility only. Gateway endpoints do not depend on it. |
dev in debug builds, prod in release |
TERM_WM_NAMESPACE |
Namespace-root override of the gateway endpoint, preserving the <user> segment (<ns>/<user>/gateway). Set for cargo-driven executions by the committed .cargo/config.toml. |
unset (term-wm) |
TERM_SESSION_CHANNEL |
Session channel override (read by term-session). |
default/main |
TERM_WM_NO_SESSION_PERSISTENCE |
Disables session-persistence behavior at runtime (same as --no-session-persistence). |
unset (persistence enabled) |
TERM_WM_TRACE_ESC |
Dumps raw PTY→emulator bytes to a file (debugging aid). | off |
Traditional terminal multiplexers often collide with the keybindings of the applications running inside them. term-wm is deliberately minimally invasive: its keybindings primarily listen for the Ctrl+A Super Key plus a small set of scrollback navigation keys, and pass everything else straight through to the running application.
- The Super Key: The default modifier is
Ctrl+A(configurable viaKeyBindings). - Scrollback Keys: Outside of Direct Input Mode, the WM also intercepts
PageUp/PageDown/Home/End(no modifier) for scrollback when a window has scrollback available; arrow keys and other navigation fall through to the child application. - Command Palette: Press
Ctrl+Ato open the central Command Palette overlay. This fuzzy-searchable menu (powered bynucleowith exponential decay scoring for recency) is the primary method for executing actions, opening windows, altering layouts, and managing workspaces (New Workspace, Switch to Workspace:<name>, Detach Viewer, Stop Gateway Daemon). - Window Navigation: While the palette is open, press
TaborShift+Tabto instantly cycle focus between active windows. PressEnterto activate the selected command. - Key Passthrough: Pressing
Ctrl+Awhile the palette is already open immediately sends theCtrl+Akeystroke to the focused child application (SendSuperKeyToFocusedWindow).
term-wm features zero-configuration input routing. Direct Input Mode is automatic. Driven by the DirectInputTracker, term-wm continuously monitors the PTY state. When a child application (such as vim, emacs, or tmux) requests the alternate screen buffer, enables mouse tracking, or defines custom scroll margins, the window manager automatically steps out of the way.
The routing decision is a structured DirectInputMode snapshot with independent keyboard and mouse dimensions:
- Keyboard direct (alternate screen / custom margins): all keystrokes pass through to the application unfiltered: zero-delay, unbuffered pass-through. Native scrollback navigation is suspended.
- Mouse capture (app requested mouse tracking): mouse events are encoded and forwarded to the application. Native text selection is suspended only while the app holds the mouse. An app on the alternate screen that did not request mouse tracking (e.g.
pico/nano) keeps native click-and-drag text selection and wheel scrolling.
A brief notification toast appears on transitions and shows the window's combined access, coalescing rapid sub-mode shifts into one message (e.g. Direct Input Mode (keyboard and mouse) enabled for vim, Direct Input Mode (keyboard) enabled for nano). The Ctrl+A Super Key remains active to summon the Command Palette at any time.
To force native text selection inside an app that captured the mouse, hold Shift (or Option on macOS) while clicking and dragging. This is best-effort: it applies to SGR mouse streams that reach term-wm. When running nested inside a host terminal emulator, the host intercepts Shift+mouse first and performs its own selection.
Floating windows support mouse-driven snapping with a live ghost preview. While dragging a window by its title bar, hovering over a snap target shows a dashed outline and a label describing the pending action.
- Snap targets: screen edges (
snap to edge), screen corners (snap to corner), and the top edge (maximize). - Auto-snap countdown: if the pointer leaves the screen area while a snap target is active, the window snaps automatically after a short countdown (default 2 seconds, configurable via
drag_snap_timeout). Releasing the button over the target also snaps immediately. - Micro-positioning: to place a window at a precise position, float it first, move it where you want, then tile it.
Because the system is built as a collection of decoupled crates, its core layout engine and UI components can be embedded into other Ratatui applications, including declarative component trees via the view! macro. Note that the developer-facing library API is currently unsolidified and subject to rapid breaking changes; stabilizing it is a primary focus of future architectural iterations.
For project origins, the crate responsibility map, embedding guidance, the view! macro reference, and component design standards, see docs/DEVELOPMENT.md and AGENTS.md.
term-wm is primarily distributed under the terms of both the MIT license and the Apache License (Version 2.0).
See LICENSE-APACHE and LICENSE-MIT for details.

