Skip to content

turin-dev/tmux-windows

Repository files navigation

tmuxw

A native Windows terminal multiplexer — a tmux clone built on the Windows ConPTY (Pseudo Console) API, with no WSL/Cygwin/MSYS2 dependency.

Status

Early development. Milestones (see the full plan for detail):

  • Phase 0 — scaffolding + ConPTY passthrough (spawn a shell, stream I/O)
  • Phase 1 — terminal emulation via libvterm (per-pane screen grid + renderer)
  • Phase 2 — multiple panes + binary layout tree + prefix key + compositor
  • Phase 3 — client/server split over Named Pipes (detach / attach)
  • Phase 4 — windows (tabs) + status bar (window list + clock)
  • Phase 5 — scrollback + vi-style copy mode + clipboard
  • Phase 6 — command system + key bindings + config file
  • Phase 7 — layout presets · pane resize · zoom · swap/rotate · mouse

Install

Prebuilt Windows x64 downloads are attached to each Release:

  • Setup wizardtmuxw-<version>-setup.exe. A GUI installer with a license page, install-location picker, and an option to add tmuxw to your PATH. It installs both command names, tmux and tmuxw, and includes a clean uninstaller.
  • Portabletmuxw-<version>-win-x64.zip (or the bare .exe). No install; unzip and run. Statically linked, so no VC++ redistributable is required.
  • wingetwinget install Turin.tmuxw (both tmux and tmuxw commands).

Requirements

  • Windows 10 1809+ (build 17763) or Windows 11 — for the ConPTY API
  • To build from source: Visual Studio 2022/2026 with the C++ toolset (MSVC, CMake, Ninja)

Build

This repo vendors libvterm as a git submodule, so clone recursively:

git clone --recursive <repo-url>
REM or, in an existing clone:
git submodule update --init --recursive

Then build with the bundled VS toolchain:

scripts\build.bat            REM Debug build -> build\tmuxw.exe
scripts\build.bat Release

Or manually from a Developer prompt:

cmake -S . -B build -G Ninja
cmake --build build

Run

build\tmuxw.exe              REM auto-detected shell (pwsh > powershell > cmd)
build\tmuxw.exe cmd.exe      REM a specific command line

Running tmuxw starts a background server (if one isn't already running) that owns the session's panes, and attaches this terminal as a thin client. The server survives detach: Ctrl-B d leaves the client and keeps the shells running; run tmuxw again to reattach. Each pane runs a shell inside a pseudo console; output is parsed by libvterm into a cell grid, composited by the server, and streamed to the client. Exit the last pane to end the session.

Once installed (or with build\ on your PATH), the familiar tmux name works too — tmux and tmuxw are the same binary:

tmux                          REM attach to (or start) the default session
tmux new                      REM start a session and attach (alias: new-session)
tmux new -s work               REM start/attach a named session
tmux new -d -s work            REM start a session without attaching (background it)
tmux new -s work -c C:\proj     REM start a session with a given starting directory
tmux new -s work -x 200 -y 50  REM start a session at a given initial size
tmux -f other.conf new -s work REM start a session, loading an alternate config file
tmux -L work new -s build      REM start a session in an independent namespace
tmux attach -t work            REM attach to an existing session (alias: a)
tmux attach -d -t work         REM attach, kicking any client already attached to it
tmux has-session -t work       REM exit 0 if that session's server is running (alias: has)
tmux ls                        REM list running sessions (alias: list-sessions)
tmux kill-session -t work      REM stop one session's server
tmux kill-session -t work -a    REM stop every OTHER session's server
tmux kill-server               REM stop every tmuxw session
tmux -V                        REM print the version (alias: --version)
tmuxw --standalone             REM run in one process, no server (for debugging)

-L <name> / -S <path> put every session created or targeted by that invocation in an independent namespace, so -L work and the default namespace never collide even if they use the same session names. Windows named pipes don't have a real notion of a "socket path" the way -S implies on Unix, so both flags do the same thing here: fold a token into the pipe name.

Each named session is an independent background server, so sessions detach and reattach separately and tmux ls enumerates them. A session's server keeps running independently of the terminal that started it — including across an SSH disconnect, which on Windows normally tears down everything in the login session's process tree; the server escapes that so reconnecting finds your panes and shells exactly as you left them.

Scripting: run a command without attaching

Any subcommand that isn't one of the ones above (send-keys, new-window, split-window, rename-session, kill-window, set, ...) is forwarded to a session's server as a single in-session command line — whether or not anything is attached to it, so it works against a session started with new -d:

tmux send-keys -t work "make test" Enter   REM type a command into the session
tmux new-window -t work                    REM open a window in it
tmux rename-session -t work built          REM rename it
tmux list-windows -t work                  REM list windows (alias: lsw)
tmux list-panes -t work                    REM list panes in the current window (alias: lsp)

A -t <session> pair immediately after the subcommand name picks the session (default session otherwise); it's only recognized in that position, since several in-session commands (select-window -t, select-pane -t, ...) use -t for their own window/pane-local targeting.

Key bindings (prefix = Ctrl-B)

Key Action
Ctrl-B % split active pane left | right
Ctrl-B " split active pane top / bottom
Ctrl-B o select next pane
Ctrl-B ←↑↓→ select pane by direction
Ctrl-B Ctrl-←↑↓→ resize active pane (1 cell)
Ctrl-B Alt-←↑↓→ resize active pane (5 cells)
Ctrl-B q show pane numbers (digit selects)
Ctrl-B w choose window from a list (j/k, Enter)
Ctrl-B t big-clock mode (any key exits)
Ctrl-B z zoom / unzoom active pane
Ctrl-B Space cycle layout preset
Ctrl-B { / } swap pane with prev / next
Ctrl-B Ctrl-O rotate panes
Ctrl-B ; select last (previously active) pane
Ctrl-B l select last window
Ctrl-B ! break active pane into a new window
Ctrl-B ] paste the clipboard into the active pane
Ctrl-B x kill active pane
Ctrl-B c create a new window (tab)
Ctrl-B n / p next / previous window
Ctrl-B 09 select window by number
Ctrl-B & kill the current window
Ctrl-B [ enter copy mode (scrollback)
Ctrl-B : command prompt
Ctrl-B d detach (server keeps running)
Ctrl-B Ctrl-B send a literal Ctrl-B

Bindings are configurable — see the config file below.

A status bar along the bottom row shows the session name, the window list (the current window marked *), and a clock.

Copy mode (Ctrl-B [)

Scroll back through a pane's history and copy text to the Windows clipboard:

Key Action
↑↓←→ / h j k l move the cursor
w / b / e next / previous / end of word
f / F <c> jump to next / previous char c
t / T <c> jump just before next / previous c
0 / ^ / $ line start / first word / line end
PageUp / PageDown scroll a full page
Ctrl-U / Ctrl-D scroll a half page
H / M / L top / middle / bottom of the viewport
g / G jump to top / bottom of history
/ / ? search forward / backward
n / N repeat search, same / opposite way
Space / v start / clear a characterwise selection
V linewise selection
Ctrl-V rectangle (block) selection
Enter or y copy the selection and exit
q / Esc cancel

Configuration

On startup the server reads %USERPROFILE%\.tmuxw.conf (if present) — a list of commands, one per line (# starts a comment). The same commands work at the command prompt (Ctrl-B :). See tmuxw.conf.example.

set prefix C-a              # change the prefix key
set status on              # show/hide the status bar
bind | split-window -h     # bind a key to a command
unbind '"'

Commands: new-window, split-window [-h|-v], select-pane [-U|-D|-L|-R|-t N], resize-pane [-U|-D|-L|-R [n]] [-Z], select-layout <name>, next-layout, previous-layout, rotate-window [-U], swap-pane [-U|-D], break-pane, join-pane -s N [-h|-v], paste-buffer [-b name], last-pane, last-window, kill-pane [-a], next-window, previous-window, select-window -t N, swap-window [-s a] -t b, move-window -t N, kill-window, link-window [-s src] [-t dst], unlink-window [-t idx], rename-window <name>, rename-session <name>, copy-mode, detach-client, send-prefix, send-keys <keys...>, display-panes, choose-window, display-message <text>, run-shell <cmd>, if-shell <cond> <then> [else], clock-mode, command-prompt, set <option> <value>, bind <key> <command>, unbind <key>, source-file <path>, list-windows (alias lsw), list-panes (alias lsp), list-keys (alias lsk), show-options (aliases show-window-options, show), list-buffers (alias lsb), show-buffer [-b name], set-buffer [-b name] <text>, delete-buffer [-b name], load-buffer [-b name] <path>, save-buffer [-b name] <path>, capture-pane (alias capturep), clear-history (alias clearhist), confirm-before -p <message> <command> (alias confirm), respawn-pane, respawn-window, pipe-pane [-o] [<shell-command>], list-commands (alias lscm), list-clients (alias lsc), set-environment [-u] <name> [value], show-environment, unset-environment <name>, choose-buffer, choose-client, set-hook [-u] <event> <command>, show-hooks, wait-for [-S|-L|-U] <channel>, switch-client -t <session>, suspend-client, refresh-client, resize-window [-x width] [-y height], lock-session / lock-client / lock-server / lock.

set-environment overrides apply to panes created from then on (new-window, split-window), not retroactively to already-running ones, matching tmux.

Hooks fired automatically by this build: window-linked (new-window), pane-died, client-attached, client-detached. set-hook/show-hooks accept and store any event name (matching tmux), but only those four ever fire on their own.

wait-for (no flag) blocks the caller until wait-for -S <channel> is run elsewhere against the same session — the server itself never blocks (that would freeze the session for anyone attached to it), so the CLI polls underneath.

switch-client -t <session> reconnects an attached client to a different session's server in place, without detaching first. lock-* locks the Windows workstation (LockWorkStation) — broader than tmux's per-terminal lock, but the same practical intent. Global -2 / -8 / -u / -q are accepted (for script/shebang compatibility) and are no-ops: color depth and encoding are whatever the terminal's own VT support is, since tmuxw passes escape sequences straight through rather than reinterpreting them.

link-window inserts a second reference to the same window at another index, within one session: both indices show the exact same live window, so a rename (or anything else) via either index is visible via the other, and unlink-window on one index leaves the other fully intact.

Paste buffers are a small internal stack (newest on top), separate from but kept in sync with the Windows clipboard: copy-mode yanks and capture-pane push a new buffer; paste-buffer prefers the top of that stack and falls back to the clipboard so a plain OS-level copy still pastes as before.

Multiple commands can be chained on one line (in a binding, config, or prompt) with a standalone ;, e.g. bind S "split-window -v ; select-pane -D".

bind -r <key> <command> makes a binding repeatable: for a short window after it runs you can press the key again without the prefix (handy for bind -r H resize-pane -L).

Layout presets for select-layout: even-horizontal, even-vertical, main-horizontal, main-vertical, tiled (also cycled with Ctrl-B Space).

Options for set: prefix <key>, status on|off, mouse on|off, base-index <n> (first window number), pane-base-index <n>, status-left <fmt>, status-right <fmt>. With mouse on, click a pane to select it, drag a divider to resize, and use the scroll wheel to enter copy mode and scroll back.

Status formats expand #S (session), #W/#I (current window name/index), #H (host), ## (literal #), and %H %M %S %Y %m %d (time). Defaults: status-left "[#S] ", status-right "%H:%M".

Headless self-tests

Useful without an interactive terminal (also run in CI):

build\tmuxw.exe --selftest [cmd]          REM ConPTY output straight to stdout
build\tmuxw.exe --selftest-render [cmd]   REM ConPTY -> libvterm -> print grid
build\tmuxw.exe --selftest-split          REM two panes + layout + compositor
build\tmuxw.exe --selftest-ipc            REM full server/client round trip
build\tmuxw.exe --selftest-cmdipc         REM one-off commands against a detached session
build\tmuxw.exe --selftest-attachd        REM attach -d kicks an already-attached client
build\tmuxw.exe --selftest-switch         REM switch-client sends MSG_SWITCH to the right target
build\tmuxw.exe --selftest-link           REM link-window shares one window across two indices
build\tmuxw.exe --selftest-confirm        REM confirm-before y/n gate + capture-pane/clear-history/previous-layout
build\tmuxw.exe --selftest-windows        REM window (tab) switching + status bar
build\tmuxw.exe --selftest-copymode       REM enter/exit copy mode via the session
build\tmuxw.exe --selftest-cmd            REM command system + bindings + config
build\tmuxw.exe --selftest-mouse          REM mouse reporting + SGR parse + actions
ctest --test-dir build                    REM emu, layout, ipc, status, copymode, cmd

License

tmuxw is released under the MIT License. Bundled third-party components and their licenses are listed in THIRD-PARTY-NOTICES.md.

About

Native Windows terminal multiplexer (tmux clone) built on ConPTY — no WSL required.

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors