# Control mode vs one-shot

Source: https://libtmux.org/en/tmux/concepts/transports/

> Choose subprocesses, command batches, or persistent connections to send commands and receive events.

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.

<a id="where-each-port-draws-the-line"></a>

## Available transports

### Python
Each library call starts a tmux subprocess. [`ControlMode`](<https://libtmux.org/en/py/latest/reference/libtmux-_internal-control_mode-controlmode/>) in
`libtmux._internal` is an internal test helper, not a public command transport.

### TypeScript
Commands use a subprocess by default. [`pipeline()`](<https://libtmux.org/en/ts/latest/reference/server-server-pipeline/>) and [`batch()`](<https://libtmux.org/en/ts/latest/reference/server-server-batch/>) group
operations. [`connect()`](<https://libtmux.org/en/ts/latest/reference/server-server-connect/>) and [`watch()`](<https://libtmux.org/en/ts/latest/reference/server-server-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`](<https://libtmux.org/en/go/latest/reference/tmux-plan-run/>)
groups commands into fewer invocations. Use [`Session.OpenControl`](<https://libtmux.org/en/go/latest/reference/tmux-session-opencontrol/>) for a
persistent command connection and [`Session.OpenNotifications`](<https://libtmux.org/en/go/latest/reference/tmux-session-opennotifications/>) for a notification
stream.

### Rust
Commands use subprocesses by default. [`CommandChain`](<https://libtmux.org/en/rs/latest/reference/command-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`](<https://libtmux.org/en/csharp/latest/reference/libtmux-server-chain/>)
groups commands, and [`EnterControlModeAsync`](<https://libtmux.org/en/csharp/latest/reference/libtmux-server-entercontrolmodeasync/>) opens a persistent command
connection.

### C++
The default transport uses bounded subprocesses. [`Chain`](<https://libtmux.org/en/cxx/latest/reference/libtmux-chain/>) groups commands
into an invocation. [`Server::control()`](<https://libtmux.org/en/cxx/latest/reference/libtmux-server-control/>) opens a persistent [`Connection`](<https://libtmux.org/en/cxx/latest/reference/libtmux-connection/>).

### Java
Ordinary calls run through tmux subprocesses. [`Batch`](<https://libtmux.org/en/java/latest/reference/io-github-libtmux-batch-batch-batch/>) groups commands,
and [`ControlClient`](<https://libtmux.org/en/java/latest/reference/io-github-libtmux-control-controlclient-controlclient/>) provides a persistent connection for commands and events.

### Swift
Ordinary calls use subprocesses. [`Server.connected(attachingTo:_:)`](<https://libtmux.org/en/swift/latest/reference/server-connected(attachingto-_-)/>) provides
a persistent connection, and [`ControlConnection.watch(_:)`](<https://libtmux.org/en/swift/latest/reference/controlsession-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()`](<https://libtmux.org/en/ts/latest/reference/server-server-connect/>) adds an event observer while commands such as
[`session.newWindow(...)`](<https://libtmux.org/en/ts/latest/reference/session-session-newwindow/>) and [`pane.sendKeys(...)`](<https://libtmux.org/en/ts/latest/reference/pane-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`](<https://libtmux.org/en/go/latest/reference/tmux-session-opencontrol/>) opens a persistent command connection. [`Session.OpenNotifications`](<https://libtmux.org/en/go/latest/reference/tmux-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`](<https://libtmux.org/en/csharp/latest/reference/libtmux-server-entercontrolmodeasync/>) for commands on a persistent connection.
[`ControlClient.send`](<https://libtmux.org/en/java/latest/reference/io-github-libtmux-control-controlclient-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`](<https://libtmux.org/en/py/latest/reference/libtmux-_internal-control_mode-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()`](<https://libtmux.org/en/ts/latest/reference/server-server-batch/>) resolves planned mutations from one final snapshot.
A [`Plan`](<https://libtmux.org/en/go/latest/reference/tmux-plan/>) groups operations without attaching a control client.
A `Chain` groups operations without attaching a control client.

<a id="choosing-a-lane"></a>

## 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.

```python
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")
```

```typescript
import { Server } from "libtmux";

// Each awaited command uses a tmux subprocess.
const server = new Server();
const session = await server.newSession({ name: "work" });
const editor = await session.newWindow({ name: "editor" });
await editor.panes.at(0)?.sendKeys("echo hello");
```

```rust
use libtmux::Server;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // One-shot: every call underneath this handle spawns a `tmux` process.
    let server = Server::new()?;
    let session = server.new_session("work").await?;
    let window = session.active_window().await?.expect("a session has a window");
    let pane = window.active_pane().await?.expect("a window has a pane");
    pane.send_line("echo hello").await?;
    Ok(())
}
```

```go
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

// One-shot: every call underneath this handle spawns a `tmux` process.
server, err := tmux.NewServer(tmux.ServerOptions{})
if err != nil {
	return err
}
session, err := server.NewSession(ctx, tmux.NewSessionRequest{Name: "work"})
if err != nil {
	return err
}
window, err := session.ResolveActiveWindow(ctx)
if err != nil {
	return err
}
pane, err := window.ResolveActivePane(ctx)
if err != nil {
	return err
}
command := "echo hello"
return pane.SendKeys(ctx, tmux.SendKeysRequest{Command: &command, Literal: true})
```

```java
ServerConfig config = ServerConfig.builder()
        .endpoint(ServerEndpoint.defaultSocket())
        .build();

// One-shot: every call underneath this handle spawns a `tmux` process.
try (Server server = Server.open(config)) {
    Session session = server.newSession("work");
    Pane pane = session.windows().get(0).panes().get(0);
    pane.sendLine("echo hello");
}
```

```csharp
using LibTmux;

// One-shot: every call underneath this handle spawns a `tmux` process.
Server server = await Server.ConnectAsync();
Session session = await server.CreateSessionAsync(new NewSessionRequest(name: "work"));
Window window = (await session.GetWindowsAsync())[0];
Pane pane = (await window.GetPanesAsync())[0];

await pane.SendTextAsync("echo hello");
```

```cpp
#include <libtmux/libtmux.hpp>

// One-shot: every call answers with a value; no tmux failure is thrown.
const auto server = libtmux::Server::at_default();
if (!server.has_value()) {
  return 1;
}

const auto session = server->new_session("work");
if (!session.has_value()) {
  return 1;
}

const auto pane = session->active_pane();
if (pane.has_value()) {
  (void)pane->send_text("echo hello");
  (void)pane->send_key("Enter");
}
```

```swift
import LibTmux

// One-shot: every call underneath this handle spawns a `tmux` process.
let server = try Server(socketName: "default")
let session = try await server.newSession(named: "work", windowName: "editor")
let window = try await server.newWindow(in: session, named: "logs").window
let pane = try await server.splitWindow(window, direction: .right)
try await server.run("echo hello", in: pane)
```

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

```console
$ tmux -C attach-session -t work
```
