tmuxtmuxConcepts

Choose documentation 1

latest

tmux manual version

Latest (3.7c) 3.7c 3.2a
English

Prerelease This site documents an alpha of libtmux. Its structure, URLs and APIs are subject to change.

Edit this page on GitHub

Control mode vs one-shot

libtmux sends commands to tmux through subprocesses or persistent control-mode connections. tmux also accepts several commands in one invocation:

  1. One-shot subprocess. Each command spawns a fresh tmux process, which sends the request to the server, prints the result, and exits.
  2. A persistent control-mode client. tmux -C attach-session starts one long-lived tmux process that stays attached and speaks a line-oriented protocol over its stdout: commands go in, replies and asynchronous notifications (%window-add, %output, ...) come out, without starting a process per call.
  3. One invocation, several commands. tmux accepts more than one command per invocation (;-joined, or one -F-tagged list-* per line). Grouping operations this way reduces process starts without opening a control-mode connection.

Available transports

Python

Each library call starts a tmux subprocess. ControlMode in libtmux._internal is an internal test helper, not a public command transport.

TypeScript

Commands use a subprocess by default. pipeline() and batch() group operations. connect() and watch() keep a connection for notifications; ordinary commands still use separate processes.

Go

The process transport starts a tmux subprocess for each call. Plan.Run groups commands into fewer invocations. Use Session.OpenControl for a persistent command connection and Session.OpenNotifications for a notification stream.

Rust

Commands use subprocesses by default. CommandChain and the planning API group commands; the control-mode feature enables persistent connections.

C#

The default one-shot mode runs commands through subprocesses. Server.Chain groups commands, and EnterControlModeAsync opens a persistent command connection.

C++

The default transport uses bounded subprocesses. Chain groups commands into an invocation. Server::control() opens a persistent Connection.

Java

Ordinary calls run through tmux subprocesses. Batch groups commands, and ControlClient provides a persistent connection for commands and events.

Swift

Ordinary calls use subprocesses. Server.connected(attachingTo:_:) provides a persistent connection, and ControlConnection.watch(_:) receives events.

Choose based on whether you need command results, notifications, or a batch of changes.

Notifications and commands are separable

TypeScript's connect() adds an event observer while commands such as session.newWindow(...) and pane.sendKeys(...) continue to run as separate tmux processes. A dedicated process provides a completion boundary for output from alias-expanded or waiting commands.

Session.OpenControl opens a persistent command connection. Session.OpenNotifications opens a notification stream. Close each handle when finished. Starting an observer does not change the transport used by an existing server handle. Use EnterControlModeAsync for commands on a persistent connection. ControlClient.send sends commands through the persistent connection. Enable the control-mode feature to send commands through a persistent connection.

A control client is a real client

A persistent control connection attaches a tmux client. It appears in list-clients, increments session_attached, and affects destroy-unattached, client hooks, and idle-client accounting. Each connection counts separately. Python's internal ControlMode test helper uses this behavior for commands that require an attached client, such as display-popup and detach-client.

Why fold several commands into one invocation

Creating an object can require a second command to read its resulting state. Batching can reduce repeated reads and process starts.

batch() resolves planned mutations from one final snapshot. A Plan groups operations without attaching a control client. A Chain groups operations without attaching a control client.

What this costs in practice

A persistent connection avoids starting a client for each command. A chain groups a known sequence into one invocation. For occasional commands, use the default subprocess transport; measure your workload before changing transports for performance. Use a notification stream when your program needs tmux events.

Sending a command

The example uses the subprocess API. See the reference for batching and control-mode setup.

import libtmux
# One-shot: every call underneath this handle spawns a `tmux` process.
server = libtmux.Server()
session = server.new_session(session_name="work")
session.active_window.active_pane.send_keys("echo hello")

To inspect tmux's control protocol, attach a control client:

Terminal window
$ tmux -C attach-session -t work
Esc

Type to search.