# libtmux > Typed tmux control libraries for Python, Ruby, Lua, TypeScript, Rust, Go, Java, Kotlin, Scala, C#, F#, C++, Swift. This site includes shared concepts and language-specific guides, examples and API references. --- # Server, session, window, pane Source: https://libtmux.org/ja/tmux/concepts/server-session-window-pane/ > How tmux servers, sessions, windows, panes and attached clients relate to each other. libtmux models tmux's server, session, window, and pane objects: ``` Server ├── Session │ └── Window │ └── Pane └── Client (attached view) ``` A `Server` contains sessions. Each `Session` contains links to windows, and each `Window` contains panes. Commands run inside a `Pane`, where you send input and capture output. A window can be linked to more than one session. ## Stable identity, not name or index tmux assigns a unique ID to each session, window, and pane at creation. The ID remains stable for that object's lifetime even if its name or index changes: | Object | ID prefix | Example | |--------|-----------|---------| | Session | `$` | `$13` | | Window | `@` | `@3243` | | Pane | `%` | `%5433` | | Server | - | identified by socket name or path instead | Use stable IDs to identify the same tmux object across reads. Names and indexes can change while a program is running. Read the hierarchy: ```python for session in server.sessions: print(session.session_name) for window in session.windows: print(" ", window.window_index, window.window_name) for pane in window.panes: print(" ", pane.pane_id, pane.pane_current_command) ``` ```typescript for (const session of await server.sessions()) { console.log(session.name); for (const window of session.windows) { console.log(" ", window.index, window.name); for (const pane of window.panes) { console.log(" ", pane.id, pane.currentCommand); } } } ``` ```rust // Three tmux commands total, not one per object: walking down would cost a // command per session and per window. for branch in server.hierarchy().await? { let session = &branch.session; println!("{session} {} ({} windows)", session.name().to_string_lossy(), session.window_count()); for built in &branch.windows { let window = &built.window; println!(" {window} {}", window.name().to_string_lossy()); for pane in &built.panes { println!(" {pane}"); } } } ``` ```go // A snapshot reads sessions, windows, and panes in one pass; relations // resolve against it without another tmux command. snapshot, err := server.Snapshot(ctx) if err != nil { return err } for _, session := range snapshot.Sessions() { name, _ := session.Name() fmt.Printf("%s %q\n", session.ID(), name) windows, _ := session.Windows() for _, window := range windows { fmt.Printf(" %s:%d\n", window.ID(), window.Index()) panes, _ := window.Panes() for _, pane := range panes { command, _ := pane.CurrentCommand() fmt.Printf(" %s %q\n", pane.ID(), command) } } } ``` ```java for (Session session : server.sessions()) { System.out.println(session.name()); for (Window window : session.windows()) { System.out.println(" " + window.index() + " " + window.name()); for (Pane pane : window.panes()) { System.out.println(" " + pane.id().value() + " " + pane.currentCommand()); } } } ``` ```csharp foreach (Session session in await server.GetSessionsAsync()) { Console.WriteLine(session.Name); foreach (Window window in await session.GetWindowsAsync()) { Console.WriteLine($" {window.Index} {window.Name}"); foreach (Pane pane in await window.GetPanesAsync()) { Console.WriteLine($" {pane.Index} {pane.Width}x{pane.Height}"); } } } ``` ```cpp const auto sessions = server.sessions(); if (!sessions.has_value()) return 1; for (const libtmux::Session& session : *sessions) { std::printf("%s (%lld windows)\n", std::string{session.name()}.c_str(), session.window_count()); const auto windows = session.windows(); if (!windows.has_value()) continue; for (const libtmux::Window& window : *windows) { std::printf(" %s\n", std::string{window.name()}.c_str()); const auto panes = window.panes(); if (!panes.has_value()) continue; for (const libtmux::Pane& pane : *panes) { std::printf(" %s\n", std::string{pane.id()}.c_str()); } } } ``` ```swift // A snapshot reads sessions, windows, and panes in one pass; the relations // below resolve against it without another tmux command. let snapshot = try await server.snapshot() for session in snapshot.sessions { print(session.name) for window in snapshot.windows(of: session) { print(" ", window.name) for pane in snapshot.panes(of: window) { print(" ", pane.id, pane.currentCommand) } } } ``` ## Client: a view, not a child A `Client` represents a terminal attached to a session. Several clients can view the same server, and each can switch sessions or windows independently. Client fields describe the view at the time of the read. A [control-mode connection](https://libtmux.org/ja/tmux/concepts/transports/) is also a client. It appears in `list-clients`, counts toward `session_attached`, and affects attachment-dependent behavior such as `destroy-unattached`. Closing the last attached client can therefore destroy a session configured with that option. ## What differs between ports Ports differ in how they read state and report failures: - **Refreshing state.** Python objects reflect their last read until you call `.refresh()`. TypeScript, Swift, and Go also provide snapshots whose relationships can be queried without another tmux command. - **Blocking and async calls.** Python and Java use blocking calls. Rust, TypeScript, C#, and Swift provide async APIs. Go uses ordinary calls with contexts for cancellation and deadlines. - **Failure handling.** Python and C# raise exceptions. C++ returns `expected`. Some commands have an expected negative answer, such as `has-session` when a session is absent; check the method's result contract before treating that answer as a failure. See [Control mode vs one-shot](https://libtmux.org/ja/tmux/concepts/transports/) for command costs and connection behavior. ## Snapshots and live commands [`Server.Snapshot(ctx)`]() reads the hierarchy. The resulting relationship methods read that captured data without another tmux command. A boolean return value reports whether a relationship was captured. Use [`Session.SearchWindows`]() or [`Server.SearchPanes`]() for current live state, and check the returned error. Calls that contact tmux accept a context for cancellation and deadlines. Cancelling a mutation does not prove that tmux never received it; check the resulting state before retrying. [Errors and exceptions](https://libtmux.org/en/go/latest/topics/errors-and-exceptions/) covers failure handling. ## tmux command reference Read the tmux command references for [list-sessions](https://libtmux.org/en/tmux/latest/manual/list-sessions/), [list-windows](https://libtmux.org/en/tmux/latest/manual/list-windows/), and [list-panes](https://libtmux.org/en/tmux/latest/manual/list-panes/). The [target syntax](https://libtmux.org/en/tmux/latest/manual/full/#COMMANDS) explains IDs, names, and indexes. --- # Control mode vs one-shot Source: https://libtmux.org/ja/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. ## 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. ```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> { // 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 // 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 ``` --- # Filtering and queries Source: https://libtmux.org/ja/tmux/concepts/queries/ > How you get from every session on the server to the one pane you mean, and what happens when zero or several match. Use a collection filter to find matching sessions, windows, or panes. Use an exactly-one lookup when your next operation requires a single target. - **Filtering returns a collection; exactly-one lookup checks the result count.** A filter returns zero or more matches. An exactly-one operation returns one object or reports a missing or ambiguous match. - **Choose where to filter.** Filter a snapshot in your program when you need several queries over the same data. A tmux format filter can reduce the rows returned by a live read. The cost depends on the data and queries you need. ## Filter collections and select one object [`server.sessions`](), [`session.windows`](), and [`window.panes`]() are [`QueryList`]() collections. Call [`.filter()`]() with field names and optional lookup suffixes: ```python >>> session.windows.filter(window_name__startswith='api') [Window(@... ...:api-server, Session($... ...))] >>> session.windows.filter(window_name__iregex=r'n?vim') ``` Lookups include `exact`, `contains`, `startswith`, `endswith`, [`regex`](), and their case-insensitive `i`-prefixed variants. Multiple keywords and chained [`.filter()`]() calls combine with AND. `.get()` requires exactly one match. Its [`default`]() argument handles an absent result; multiple matches still raise [`MultipleObjectsReturned`](). Server-wide collections ([`server.windows`](), [`server.panes`]()) enumerate window links. A window linked to two sessions appears once per session, so a lookup can be ambiguous even when the window ID is unique. Use [`Window.linked_sessions`]() to find its sessions. For a known ID, use [`Pane.from_pane_id()`]() or [`Window.from_window_id()`]() to resolve the object directly. For servers with hundreds or thousands of panes, [`.filter()`]() still builds every object before you discard the ones that don't match. [`search_sessions`](), `search_windows`, and `search_panes` push a tmux `-f` filter expression down to the server instead, so libtmux builds objects only for the matches: ```python >>> server.search_sessions(filter='#{==:#{session_name},alpha-1}') ``` Python-side lookups work with the library's supported tmux versions. The tmux filter grammar requires tmux 3.2 or newer. An unknown format token expands to an empty value, so a malformed filter can look like a valid filter with no matches. If `search_*()` unexpectedly returns no results, try `#{m:*,#{session_name}}` to check that the session data is available. ## TypeScript criteria The [TypeScript filtering guide](https://libtmux.org/en/ts/latest/concepts/queries/) includes complete programs for matching names, handling result counts, traversing linked windows, refreshing snapshots, validating query documents, and filtering live tmux rows. ## Typed and local filters Typed field operations let the compiler reject incompatible comparisons: [`tmux.PaneFilter`]() describes a typed predicate over captured values. Use [`tmuxq.Matching`]() or compile its [`PaneFilter.Predicate()`]() for local queries. To filter a live tmux listing, pass a [`TmuxFilter`]() expression to [`Server.SearchPanes`](). Typed fields reject invalid comparisons: `fields.pane_active.eq(true)` is valid, while `.gt(...)` on a boolean field fails to compile. Compose expressions with [`.and()`](). The `serde` feature supports versioned query JSON for configuration or MCP. [`Pane_`](), [`Window_`](), and [`Session_`]() expose typed field accessors for stream predicates. A numeric field has no [`startsWith`]() operation. Use [`Selections.exactlyOne`]() to reject absent or ambiguous matches. Compose [`FilterExpr`]() values with `&&`, `||`, and `!`. For example, [`pane::command.starts_with("nv") && pane::active`]() is valid, while `pane::active.starts_with("x")` fails to compile. Examples of typed and local filters: ```rust use libtmux::query::{Filterable as _, QueryIteratorExt as _}; let fields = libtmux::Pane::filter_fields(); // `pane_active` is a flag, so `.eq(true)` compiles; `.gt(..)` would not. let active = fields .pane_current_command .starts_with("sh") .and(fields.pane_active.eq(true)); let panes = server.panes().await?; let matched: Vec<_> = panes.iter().matching(&active).collect(); ``` ```go // Read once, filter in Go: several answers from one read. snapshot, err := server.Snapshot(ctx) if err != nil { return err } predicate, err := tmux.PaneActiveIs(true).Predicate() if err != nil { return err } active := tmuxq.Where(snapshot.Panes(), predicate) fmt.Println("active panes:", len(active)) // Or push the filter down: tmux returns only the matches. filter := tmux.TmuxFilter("#{==:#{pane_active},1}") panes, err := server.SearchPanes(ctx, &filter) if err != nil { return err } fmt.Println("live matches:", len(panes)) ``` ```java List editors = server.windows().stream() .filter(Window_.name().startsWith("edit")) .toList(); // Selections.exactlyOne() is the `.get()`-shaped call. Session build = Selections.exactlyOne( server.sessions().stream().filter(Session_.name().is("build")).toList()); ``` ```csharp IReadOnlyList windows = await session.GetWindowsAsync(ct); IEnumerable building = windows.Where( each => each.Name.StartsWith("build", StringComparison.Ordinal)); // A declarative query is a document, not just a lambda run in place. IReadOnlyList sessions = await server.GetSessionsAsync(ct); IReadOnlyList matched = sessions.Matching( session => session.Name.StartsWith("build", StringComparison.Ordinal)); ``` ```cpp // A filter is a value built from typed fields; `window::active.starts_with(...)` // would not compile: a flag has no string operations. const auto interesting = libtmux::window::name.starts_with("e") || libtmux::window::name == "logs"; auto matched = *windows | libtmux::matching(interesting); // "Exactly one, or say why not" is a question the library answers directly. auto logs = *windows | libtmux::matching(libtmux::window::name == "logs"); if (const auto only = libtmux::exactly_one(logs); only.has_value()) { std::printf("exactly one: %s\n", std::string{only->get().id()}.c_str()); } ``` ```swift // Filter locally with the standard library: let editors = try await server.panes().filter { $0.currentCommand == "nvim" } // Or build a filter that travels: stored, sent, replayed elsewhere: let expression = try FilterExpr.where(\.currentCommand, .isIn(["nvim", "vim"])) let matching = try await server.panes().filter(expression) ``` ## Result counts ### Python Use [`QueryList.filter`]() to keep matching objects and [`QueryList.get`]() when exactly one must match. An empty result raises [`ObjectDoesNotExist`]() unless you supply `default=`. Several matches raise [`MultipleObjectsReturned`](). ### TypeScript Use [`where()`]() or [`filter()`]() to select matches and [`one()`]() to require exactly one. No match raises [`NoMatchError`](); [`oneOrUndefined()`]() accepts that case. Several matches raise [`MultipleMatchesError`](). ### Java Filter with [`Stream.filter`]() and require one match with [`Selections.exactlyOne`](). Empty and multiple results raise [`CardinalityException.NoMatch`]() and [`CardinalityException.MultipleMatches`](), respectively. [Filtering and querying](https://libtmux.org/ja/tmux/guides/querying-and-filtering/) shows exactly-one lookups and their error handling. Do not index the first result until the operation has established that a match exists. ## tmux command reference The tmux [list-panes](https://libtmux.org/en/tmux/latest/manual/list-panes/) and [list-windows](https://libtmux.org/en/tmux/latest/manual/list-windows/) references describe native format filters. See [formats](https://libtmux.org/en/tmux/latest/manual/full/#FORMATS) for expressions and available variables. --- # Workspaces Source: https://libtmux.org/ja/tmux/concepts/workspaces/ > Build pane layouts with the object API or a workspace configuration file. A workspace arranges windows and panes for a task, such as editing code, running a development server, and following logs. Build it with the object API when the layout depends on program logic. Use a declarative builder when you want to store the layout in a configuration file, such as [tmuxp](https://tmuxp.git-pull.com/) YAML or JSON. ## Building one imperatively Create a window, split it into panes, apply a layout, and send each pane its command: ```python def create_dev_workspace(session, name='dev'): window = session.new_window(window_name=name, attach=False) window.resize(height=50, width=160) main_pane = window.active_pane terminal_pane = main_pane.split(size='30%') log_pane = terminal_pane.split(direction=PaneDirection.Right) return {'window': window, 'main': main_pane, 'terminal': terminal_pane, 'logs': log_pane} ``` ```rust let mut window = session.new_window("dev").await?; let main_pane = window.active_pane().await?.expect("a new window has a pane"); let terminal_pane = main_pane .split(SplitOptions::new(SplitDirection::Below).size(PaneSize::Percent(30))) .await?; let logs_pane = terminal_pane .split(SplitOptions::new(SplitDirection::Right)) .await?; window.select_layout(Layout::MainVertical).await?; ``` ```go window, err := session.NewWindow(ctx, tmux.NewWindowRequest{Name: tmux.Ptr("dev")}) if err != nil { return err } // Attach: true makes the split active, so the next split divides it rather // than the pane that was already there. terminal, err := window.SplitPane(ctx, tmux.SplitPaneRequest{ Attach: true, Percentage: tmux.Ptr(30), }) if err != nil { return err } if _, err := window.SplitPane(ctx, tmux.SplitPaneRequest{Direction: tmux.PaneDirectionRight}); err != nil { return err } _ = terminal return window.SelectLayout(ctx, tmux.SelectLayoutRequest{Layout: "main-vertical"}) ``` ```java Window window = session.newWindow(w -> w.named("dev").detached()); Pane terminal = window.split(split -> split.percent(30)); Pane logs = terminal.split(split -> split.toRight()); window.selectLayout(Layout.MAIN_VERTICAL); ``` ```csharp Window window = await session.CreateWindowAsync(new NewWindowRequest(name: "dev")); Pane main = (await window.GetPanesAsync())[0]; Pane terminal = await main.SplitAsync(new SplitPaneRequest(percentage: 30)); Pane logs = await terminal.SplitAsync(new SplitPaneRequest(direction: PaneDirection.Right)); await window.SelectLayoutAsync(new SelectLayoutRequest("main-vertical")); ``` ```cpp const auto window = session.new_window({.name = "dev"}); if (!window.has_value()) return 1; // `focus = true` makes the new pane active, so the next split divides it. const auto terminal = window->split({.percentage = 30, .focus = true}); if (!terminal.has_value()) return 1; const auto logs = window->split({.horizontal = true}); if (!logs.has_value()) return 1; (void)window->select_layout("main-vertical"); ``` ```swift let session = try await server.newSession(named: "work") let window = try await server.newWindow(in: session, named: "dev").window let terminal = try await server.splitWindow(window, size: .percentage(30)) let logs = try await server.split(terminal, direction: .right) try await server.selectLayout(window, "main-vertical") ``` A split creates a pane; direction and size control its placement. Applying a layout rearranges existing panes while their processes continue running. tmux provides `even-horizontal`, `even-vertical`, `main-horizontal`, `main-vertical`, and `tiled` layouts. Choose detached creation when the user's current window should retain focus. Splits and resizes issue tmux commands; [Control mode vs one-shot](https://libtmux.org/ja/tmux/concepts/transports/) covers their transport costs and batching. `attach=False` keeps a newly created window in the background. ## Building one declaratively These packages read or build workspace configurations based on tmuxp: ### Python `tmuxp` loads a workspace configuration and creates its sessions, windows, and panes. See [Workspace Manager](https://libtmux.org/en/py/latest/workspace/) for its configuration and CLI. ### TypeScript `@libtmux/workspace` applies a workspace configuration through [`applyWorkspace`](). Pass the server and a configuration containing `session_name` and `windows`. ### Go The [`workspace`]() package loads tmuxp-shaped workspace configurations. See [Workspace Manager](https://libtmux.org/en/go/latest/workspace/) for the supported fields and CLI. ### Rust The `tmux-workspace` crate loads tmuxp-shaped configurations. See [Workspace Manager](https://libtmux.org/en/rs/latest/workspace/) for the supported fields and CLI. ### Java `libtmux-workspace` supports the tmuxp configuration fields needed to describe a workspace. See [Workspace Manager](https://libtmux.org/en/java/latest/workspace/) for the supported configuration and CLI. ### C# [`LibTmux.Workspace`]() reads tmuxp YAML. See [Workspace Manager](https://libtmux.org/en/csharp/latest/workspace/) for configuration fields and the CLI. ### Swift `TmuxWorkspace` accepts configurations written in Swift, JSON, or YAML. YAML support requires the `YAMLWorkspaces` trait. TypeScript's [`applyWorkspace`]() applies a desired configuration. Applying the same configuration again reuses its existing objects: ```ts await applyWorkspace(server, { session_name: "api", windows: [ { window_name: "editor", panes: ["vim", "git status"] }, { window_name: "server", panes: [{ shell_command: "bun dev", focus: true }] }, ], }); ``` ```rust use tmux_workspace::{Workspace, WorkspaceBuilder}; let workspace = Workspace::from_yaml(yaml_source)?; let session = WorkspaceBuilder::new(&server).build(&workspace).await?; ``` ```go described, err := workspace.Parse(document) if err != nil { return err } session, err := workspace.Build(ctx, server, described) if err != nil { return err } fmt.Println("workspace session:", session.ID()) ``` ```java Workspace workspace = WorkspaceBuilder.parse(yaml); Session session = WorkspaceBuilder.build(server, workspace); ``` ```csharp WorkspaceFile workspace = WorkspaceFile.Parse(yaml); WorkspaceResult result = await new WorkspaceBuilder(server).BuildAsync(workspace, ct); ``` ```swift let workspace = Workspace( sessionName: "work", windows: [WindowPlan(windowName: "editor", panes: [PanePlan(), PanePlan()])] ) let session = try await WorkspaceBuilder.build(workspace, on: server) ``` C++ provides a consumer example in [`examples/workspace/`]() that reads tmuxp configuration. The workspace builder is part of that example, rather than a library package: ```cpp // Not a package: this is the examples/workspace/ consumer, showing the // shape a tmuxp document builds into rather than a library entry point. const workspace::Workspace description{ .session_name = "dev", .windows = {{.name = "editor", .panes = {{}, {}}}}}; const auto built = workspace::build(server, description); ``` ## Cleaning up [`Window`]() and [`Session`]() context managers kill their objects on block exit, including when the block raises: Use a named error result in the enclosing function so deferred cleanup can return its own failure. Give cleanup a fresh, bounded context: An ownership scope kills its session when `await using` exits: ```python with session.new_window(window_name='temp-window') as temp_win: pane = temp_win.active_pane pane.send_keys('echo "temporary workspace"') # window is gone here, even if the block raised ``` ```go // No context manager: defer runs the cleanup at the end of the enclosing // function instead of the end of a block. session, err := server.NewSession(ctx, tmux.NewSessionRequest{Name: "temp-session"}) if err != nil { return err } defer func() { cleanupCtx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() err = errors.Join(err, session.Kill(cleanupCtx)) }() ``` ```csharp await using OwnedSessionScope scope = await server.CreateOwnedSessionAsync( new NewSessionRequest(name: "temp-session")); Window window = (await scope.Value.GetWindowsAsync())[0]; Pane pane = (await window.GetPanesAsync())[0]; await pane.SendTextAsync("echo temporary workspace"); // session is gone here, even if an exception unwound through the block ``` See [Context managers](https://libtmux.org/ja/tmux/topics/context-managers/) for cleanup support in each port. Use explicit kill methods when the handle does not provide scope-based cleanup. --- # Examples Source: https://libtmux.org/ja/tmux/examples/ > Programs for sending input, capturing output, and building workspaces. Use these programs to send input, capture output, and build a workspace. Each example page includes its source and test coverage. Check those details before adapting an excerpt into a standalone program. ## Source and verification Port repositories use the following checks for their source examples. This site reads or copies those examples; a successful site build alone does not execute them: ### Python The test configuration includes [`README.md`]() and [`src/libtmux/`]() in its doctest collection. The `>>>` examples run against isolated tmux sessions. ### TypeScript [`scripts/check-doc-runnable.ts`]() checks that a block tagged with its example source matches that file line for line. The integration suite executes the source program. ### Go `go generate ./tmux` refreshes documented regions from matching source regions under [`examples/`](). CI checks that those generated excerpts are current. ### Rust The library includes its README as a crate doc comment. `cargo test --doc` compiles and runs its executable Rust code blocks. ### Java `./gradlew :docs-tests:test` compiles executable Java blocks in READMEs and guides against the library artifacts, then runs them against tmux through `libtmux-junit5`. ### C# `sync_snippets.py --check` checks excerpts from tested `[Example]` methods. `ReadmeExampleTests` also compiles and executes blocks marked `csharp run`. ### C++ [`tools/docs/check_readme.py`]() checks that README examples match their source regions in [`examples/05-readme.cpp`](). CTest builds and runs that program. ### Swift [`Scripts/check_examples.py`]() checks that README examples match sources under [`Examples/Sources/`](). `swift test --package-path Examples` compiles those examples through the public products. See [Testing with libtmux](https://libtmux.org/ja/tmux/guides/testing-with-libtmux/) for the fixture each of those test suites runs against, and each example page for the exact file a given snippet was quoted from. - [Attach and send keys](https://libtmux.org/ja/tmux/examples/attach-and-send-keys/): Find a session, send a command, and read its output. - [Capture pane output](https://libtmux.org/ja/tmux/examples/capture-pane-output/): Read a pane's screen and wait for output to appear. - [Build a workspace from a file](https://libtmux.org/ja/tmux/examples/workspace-from-file/): Create a session, windows, and panes from configuration. --- # Attach and send keys Source: https://libtmux.org/ja/tmux/examples/attach-and-send-keys/ > Get a session handle, send a command to a pane, and capture output. Get a session handle, send a command to a pane, and capture output. These examples use libtmux from your program; to attach your terminal interactively, see [Attaching to tmux](https://libtmux.org/ja/tmux/guides/attaching-to-tmux/). The examples include setup, error handling, and cleanup. [Source and verification](https://libtmux.org/ja/tmux/examples/attach-and-send-keys/#where-this-comes-from) identifies their files and checks. ```python >>> import libtmux >>> server = libtmux.Server() >>> session = server.new_session(session_name='demo') Session(...) >>> window = session.active_window >>> pane = window.split(shell='sh') >>> pane.capture_pane() ['$'] >>> pane.send_keys('echo "Hello world"', enter=True) >>> pane.capture_pane() ['$ echo "Hello world"', 'Hello world', '$'] ``` ```typescript file="examples/quickstart/quickstart.ts" import { Server, TmuxCommandError, type ServerSnapshot } from "libtmux"; /** * A runnable tour of the API, driven by the tests so it cannot rot. * * Every step here appears in README.md. */ export async function quickstart(server: Server): Promise { // Nothing is read until you ask. `snapshot()` is the only step that talks to // tmux; everything reachable from it resolves locally. const session = await server.newSession({ name: "quickstart" }); const editor = await session.newWindow({ name: "editor" }); await editor.split(); const snapshot = await server.snapshot(); // Declarative filtering, serializable and stable on the wire. const found = snapshot.windows.where({ name: "editor" }).one(); // Relations are plain properties: no await, no tmux command. const paneCount = found.panes.length; if (paneCount !== 2) throw new Error(`expected two panes, saw ${String(paneCount)}`); // A criterion is spelled like the handle accessor it filters. if (snapshot.panes.count({ currentCommand: { contains: "" } }) === 0) { throw new Error("expected panes to report a current command"); } const first = found.panes.at(0); if (first === undefined) throw new Error("expected a pane"); await first.sendKeys("echo hello-from-libtmux", { literal: true }); // Failures carry their parts rather than a formatted sentence. try { await server.setOption("not-a-real-option", "1"); } catch (error) { if (!(error instanceof TmuxCommandError)) throw error; if (error.args[0] !== "set-option") throw error; } return snapshot; } ``` ```rust file="crates/libtmux/examples/scratch.rs" //! Build a throwaway session, use it, and leave nothing behind. //! //! ```console //! $ cargo run --example scratch //! ``` use std::time::Duration; use libtmux::test::unique_name; use libtmux::{NewWindowOptions, PaneWait, Server, SplitDirection, SplitOptions}; #[tokio::main] async fn main() -> Result<(), Box> { // An example must not build sessions on whatever server the reader // happens to be using, so this one gets a socket of its own. More than one // libtmux runs on a developer's machine, so it goes in a directory this // one owns rather than straight into the temporary directory. let root = std::path::Path::new("/tmp/libtmux-rs-dev"); std::fs::create_dir_all(root)?; let socket = root.join(format!("{}.sock", unique_name("libtmux-scratch"))); let server = Server::builder().socket_path(&socket).build()?; // The scope kills the session whether the body succeeds or fails, so a // failure partway through does not leave a session behind. println!("server on {}", socket.display()); let output = server .with_session(unique_name("scratch").as_str(), async |session| { println!(" session {} created", session.id()); let window = session .new_window(NewWindowOptions::new("work").command("sh")) .await?; println!(" window {} running sh", window.id()); window .split(SplitOptions::new(SplitDirection::Below).command("sh")) .await?; println!(" split it: {} panes", window.panes().await?.len()); // A window always has an active pane, but saying so with a panic // would be a worse example than handling it. let Some(pane) = window.active_pane().await? else { return Ok(0); }; println!(" typing into {}", pane.id()); pane.send_line("printf 'hello from tmux\\n'").await?; // tmux runs the shell asynchronously, so wait for the output // rather than sleeping and hoping. The outcome is checked because // a wait that reached its deadline still returns successfully. match pane.wait_for_text("hello", Duration::from_secs(5)).await? { PaneWait::Arrived => println!(" the pane printed it"), other => println!(" gave up: {other:?}"), } // region: capture let lines = pane.capture().await?; for line in lines.iter().filter(|line| !line.as_bytes().is_empty()) { println!(" | {}", line.to_string_lossy()); } // endregion Ok::<_, Box>(lines.len()) }) .await?; println!("captured {output} lines, then the scope killed the session"); // The lenient form is the one that answers this question. The scope killed // the only session, so tmux exited with it, and the loud form reports that // as the failure it is rather than as the empty listing this is asking for. assert!( server.sessions().await.unwrap_or_default().is_empty(), "the scope cleaned up", ); println!( "sessions left behind: {}", server.sessions().await.unwrap_or_default().len() ); server.shutdown().await?; // tmux does not unlink its socket when the server exits, so whatever named // one owns removing it. Leaving it behind is invisible until /tmp fills up. std::fs::remove_file(&socket)?; Ok(()) } ``` ```go file="examples/quickstart/main.go" // Command quickstart demonstrates a complete session, window, and pane lifecycle. package main import ( "bufio" "context" "errors" "fmt" "log" "time" "github.com/libtmux/libtmux-go/tmux" ) func main() { if err := start(); err != nil { log.Fatal(err) } } // start owns cleanup because log.Fatal skips deferred calls in main. func start() error { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() server, err := tmux.NewServer(tmux.ServerOptions{}) if err != nil { return fmt.Errorf("configure tmux server: %w", err) } return run(ctx, server) } // run accepts injected server state so tests can isolate the example. func run(ctx context.Context, server tmux.Server) (err error) { // docs:quickstart given:ctx context.Context; server tmux.Server session, err := server.NewSession(ctx, tmux.NewSessionRequest{ Name: "libtmux-go-quickstart", WindowName: "start", }) if err != nil { return fmt.Errorf("create session: %w", err) } defer func() { cleanupCtx, cleanupCancel := context.WithTimeout(context.WithoutCancel(ctx), time.Second) defer cleanupCancel() err = errors.Join(err, session.Kill(cleanupCtx)) }() window, err := session.NewWindow(ctx, tmux.NewWindowRequest{Name: new("work")}) if err != nil { return fmt.Errorf("create window: %w", err) } pane, err := window.SplitPane(ctx, tmux.SplitPaneRequest{ Direction: tmux.PaneDirectionRight, Command: "sh", }) if err != nil { return fmt.Errorf("split window: %w", err) } output, err := pane.OpenObservation(ctx) if err != nil { return fmt.Errorf("watch pane: %w", err) } defer func() { err = errors.Join(err, output.Close()) }() if _, err := fmt.Fprintln(pane.Writer(ctx), "printf 'libtmux ready\\n'"); err != nil { return fmt.Errorf("send command: %w", err) } // docs:end scanner := bufio.NewScanner(output.Reader(ctx)) for scanner.Scan() { if scanner.Text() == "libtmux ready" { fmt.Println("libtmux ready") return nil } } return fmt.Errorf("read pane: %w", scanner.Err()) } ``` ```java file="examples/src/main/java/io/github/libtmux/examples/BuildAWorkspace.java" package io.github.libtmux.examples; import io.github.libtmux.Layout; import io.github.libtmux.Pane; import io.github.libtmux.Server; import io.github.libtmux.ServerConfig; import io.github.libtmux.ServerEndpoint; import io.github.libtmux.Session; import io.github.libtmux.Window; import io.github.libtmux.exception.ServerUnavailableException; import java.nio.file.Path; import java.util.Optional; /** * Lays out a session the way you would set one up by hand before starting work. * *
{@code
 * java BuildAWorkspace.java /tmp/libtmux-java-dev/demo/s
 * }
*/ public final class BuildAWorkspace { private BuildAWorkspace() {} public static void main(String[] args) { System.out.println(run(Path.of(args.length > 0 ? args[0] : "/tmp/libtmux-java-dev/demo/s"))); } /** Separated from {@code main} so the suite can run exactly what a reader runs. */ public static String run(Path socket) { ServerConfig config = ServerConfig.builder() .endpoint(ServerEndpoint.socketPath(socket)) .configFile(Path.of("/dev/null")) .build(); // Closing a server closes this client. The tmux server, and the session, outlive the program // — which is the whole point of tmux and the reason nothing here kills it. try (Server server = Server.open(config)) { // One read decides and answers. An absent daemon means the name cannot be taken, so // that specific failure is the other way this resolves to "not found". Optional existing; try { existing = server.session("work"); } catch (ServerUnavailableException absent) { existing = Optional.empty(); } Session session = existing.orElseGet(() -> server.newSession("work")); Window editor = session.newWindow(window -> window.named("editor").detached()); Pane shell = editor.split(split -> split.toRight()); shell.sendLine("git status --short"); editor.selectLayout(Layout.MAIN_VERTICAL); return "session " + session.name() + " has " + session.refresh().windows().size() + " windows"; } } } ``` ```csharp file="examples/LibTmux.Examples/Snippets/OneShot.cs" using System.Runtime.Versioning; namespace LibTmux.Examples.Snippets; /// The default mode: one command, one client, one materialized object. [UnsupportedOSPlatform("windows")] public static class OneShot { /// Connects, builds a hierarchy, and types into the pane it made. [Example("Connect, build a session and window, and type into a pane")] public static async Task ConnectAndBuild() { #region ConnectAndBuild // Requires a tmux server already listening on this socket: // ConnectAsync() discovers one, it never starts one. With nothing // running yet, call Server.CreateOwnedAsync() instead. Server server = await Server.ConnectAsync(); Session session = await server.CreateSessionAsync(new NewSessionRequest { Name = "build" }); Window window = await session.CreateWindowAsync(new NewWindowRequest { Name = "tests" }); Pane pane = (await window.GetPanesAsync())[0]; await pane.SendTextAsync("dotnet test"); #endregion } /// Creates a window and prints what tmux answered about it. [Example("One command, one materialized window")] public static async Task CreateWindow(Session session, CancellationToken ct) { #region CreateWindow Window window = await session.CreateWindowAsync(new NewWindowRequest { Name = "build" }, ct); Console.WriteLine($"{window.Id} {window.Index}:{window.Name}"); #endregion } } ``` ```cpp // No tmux failure is thrown. Every call answers with a value that is either // the result or the reason there isn't one. const auto sessions = server.sessions(); if (!sessions.has_value()) { std::fprintf(stderr, "%s\n", sessions.error().diagnostic.c_str()); return 1; } for (const libtmux::Session& session : *sessions) { std::printf("%s has %lld window(s)\n", std::string{session.name()}.c_str(), session.window_count()); } const libtmux::Session& session = sessions->at(0); // Build an arrangement without composing a single tmux argument. const auto editor = session.new_window({.name = "editor"}); if (!editor.has_value()) { std::fprintf(stderr, "%s\n", editor.error().diagnostic.c_str()); return 1; } const auto logs = editor->split({.horizontal = true, .percentage = 30}); if (!logs.has_value()) { std::fprintf(stderr, "%s\n", logs.error().diagnostic.c_str()); return 1; } (void)logs->send_text("journalctl -f"); (void)logs->send_key("Enter"); ``` ```swift file="Examples/Sources/ExampleCode/Changing.swift" // The examples in the README's "Change what is there" section. import LibTmux public func buildASessionByHand(_ server: Server) async throws -> Pane { let session = try await server.newSession(named: "work", windowName: "editor") _ = try await server.setOption("@purpose", to: "development", scope: .session(session)) let logs = try await server.newWindow(in: session, named: "logs").window let pane = try await server.splitWindow(logs, direction: .right) try await server.run("tail -f /tmp/build.log", in: pane) return pane } public func readBackWhatAPanePrinted(_ server: Server, _ pane: Pane) async throws -> [String] { let lines = try await server.capture(pane) print(lines.suffix(5).joined(separator: "\n")) return lines } public func spendOneProcessOnAllOfIt(_ server: Server) async throws { var plan = TmuxCommandList() for name in ["edit", "test", "logs"] { plan = plan.then("new-window", ["-d", "-n", name]) } _ = try await server.run(plan) } ``` ## Finding an existing session instead For a script that runs repeatedly, look up a session before creating it. [Attaching to tmux](https://libtmux.org/ja/tmux/guides/attaching-to-tmux/#finding-a-session-instead-of-always-creating-one) shows that pattern, and [Filtering and querying, in practice](https://libtmux.org/ja/tmux/guides/querying-and-filtering/) covers absent and ambiguous matches. ## Where this comes from ### Python **Source:** [`src/libtmux/server.py`](), [`session.py`](), [`pane.py`]() docstrings **In this page:** hand-quoted, composed from three separate docstrings **Checked by:** `pytest` runs every `>>>` doctest (`testpaths` includes `src/libtmux`) against a real, isolated tmux session on every test run ### TypeScript **Source:** [`examples/quickstart/quickstart.ts`]() **In this page:** read whole from the file **Checked by:** run against real tmux by `bun test examples`; its first half is also mirrored into README.md under a `` marker, checked line-for-line by [`scripts/check-doc-runnable.ts`]() ### Rust **Source:** [`crates/libtmux/examples/scratch.rs`]() **In this page:** read whole from the file **Checked by:** run to completion against a throwaway tmux by [`scripts/run-examples.sh`]() (`just examples`), which CI runs and which also asserts the example leaves no session behind ### Go **Source:** [`examples/quickstart/main.go`]() **In this page:** read whole from the file **Checked by:** the whole file runs against a real tmux server as `TestQuickstart`; the `docs:quickstart` region inside it is additionally mirrored into README.md by `go generate ./tmux`, and CI fails if the two drift ### Java **Source:** [`examples/src/main/java/io/github/libtmux/examples/BuildAWorkspace.java`]() **In this page:** read whole from the file **Checked by:** run against real tmux by the `examples` module's own `ExamplesRunTest` ### C# **Source:** [`examples/LibTmux.Examples/Snippets/OneShot.cs`]() **In this page:** read whole from the file **Checked by:** its `ConnectAndBuild` region is mirrored into README.md and checked by `sync_snippets.py --check`; the mirrored `csharp run` block is additionally compiled and run by `ReadmeExampleTests` ### C++ **Source:** [`examples/05-readme.cpp`](), the [`connect`]() and [`build`]() regions **In this page:** Copied excerpts from the [`connect`]() and [`build`]() regions. **Checked by:** quoted verbatim into README.md, checked for drift by [`tools/docs/check_readme.py`](), and the whole file is built and run by CTest ### Swift **Source:** [`Examples/Sources/ExampleCode/Changing.swift`]() **In this page:** read whole from the file **Checked by:** matched against the README's "Change what is there" section by [`Scripts/check_examples.py`](); compiled and run through the package's public products by `swift test --package-path Examples` ### Source inclusion A `file="..."` fence reads the named source during the site build. Hand-quoted excerpts are copies; their source files and regions are listed above. --- # Capture pane output Source: https://libtmux.org/ja/tmux/examples/capture-pane-output/ > Capture a tmux pane's screen and wait for a complete output line. `capture-pane -p` prints a pane's visible screen. Sending a command and reading its result are separate operations: the pane's shell may still be processing input when the first capture runs. ## Read what's on screen This complete shell program starts a private tmux server, sends a command, and captures until the expected line appears. It removes the server on exit and fails after 100 unsuccessful checks with 50-millisecond pauses. Save it as `capture.sh` and run `sh capture.sh`, or paste the whole block into a POSIX shell. It requires tmux 3.2 or newer and `sleep` with fractional seconds. ```sh title="capture.sh" ( set -eu directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-capture.XXXXXX") socket="$directory/tmux.sock" cleanup() { status=$? trap - EXIT if [ -S "$socket" ] && ! tmux -S "$socket" kill-server; then printf '%s\n' "Cannot stop tmux; kept $directory." >&2 exit 1 fi rm -rf "$directory" || status=$? exit "$status" } trap cleanup EXIT trap 'exit 1' HUP INT TERM tmux -S "$socket" -f /dev/null new-session -d -s capture \ -e ENV=/dev/null 'sh' tmux -S "$socket" send-keys -t capture:0.0 -l \ "printf '\\nlibtmux capture ready\\n'" tmux -S "$socket" send-keys -t capture:0.0 Enter attempt=0 while [ "$attempt" -lt 100 ]; do screen=$(tmux -S "$socket" capture-pane -p -t capture:0.0) if printf '%s\n' "$screen" | grep -Fqx 'libtmux capture ready'; then printf '%s\n' "$screen" exit 0 fi attempt=$((attempt + 1)) sleep 0.05 done printf '%s\n' 'Timed out waiting for captured output.' >&2 exit 1 ) ``` Cleanup also runs if startup fails after creating the server. If tmux cannot be stopped, the script reports the error and keeps its socket directory so you can inspect or stop that server. ## Wait for output or completion The leading newline puts the output on a fresh screen row even if the shell's first prompt arrives late. `grep -Fx` matches the complete line, so the echoed command cannot satisfy the check. The short pause limits polling; the captured output determines when the program finishes. Capture reads screen state, so it can miss output that has scrolled away. [Capturing output](https://libtmux.org/ja/tmux/guides/capturing-output/) covers history and streaming; [Sending keys](https://libtmux.org/ja/tmux/guides/sending-keys/) explains input and completion. ## Use a language library Complete programs with imports, setup, and cleanup: [Python](https://libtmux.org/en/py/latest/examples/capture-pane-output/) · [TypeScript](https://libtmux.org/en/ts/latest/examples/capture-pane-output/) · [Go](https://libtmux.org/en/go/latest/examples/capture-pane-output/) · [Rust](https://libtmux.org/en/rs/latest/examples/capture-pane-output/) · [Java](https://libtmux.org/en/java/latest/examples/capture-pane-output/) · [Kotlin](https://libtmux.org/en/kotlin/latest/examples/capture-pane-output/) · [Scala](https://libtmux.org/en/scala/latest/examples/capture-pane-output/) · [C#](https://libtmux.org/en/csharp/latest/examples/capture-pane-output/) · [F#](https://libtmux.org/en/fsharp/latest/examples/capture-pane-output/) · [C++](https://libtmux.org/en/cxx/latest/examples/capture-pane-output/) · [Swift](https://libtmux.org/en/swift/latest/examples/capture-pane-output/) · [Ruby](https://libtmux.org/en/ruby/latest/examples/capture-pane-output/) · [Lua](https://libtmux.org/en/lua/latest/examples/capture-pane-output/) The [tmux manual source](https://github.com/tmux/tmux/blob/94796f6b1182507efac8a272fc309a79e22e58a5/tmux.1) describes `capture-pane`, `send-keys`, and `kill-server`. --- # Build a workspace from a file Source: https://libtmux.org/ja/tmux/examples/workspace-from-file/ > Create tmux windows and panes from a command file on a private server. A tmux command file can create a session, arrange its windows, and split its panes. This example builds two windows with three panes, prints their names and pane counts, then removes its private server. It requires tmux 3.2 or newer and a POSIX shell. ## Define the layout Save this command file: ```text title="workspace.conf" new-session -d -s dev -n editor -x 100 -y 30 'sh' split-window -h -t '=dev:editor' 'sh' new-window -t '=dev' -n logs 'sh' select-window -t '=dev:editor' select-pane -t '=dev:editor.0' ``` The `editor` window has two panes; `logs` has one. Each pane starts `sh`. Explicit targets keep each command tied to the intended session and window. ## Build and inspect it Save this complete program as `workspace.sh` beside the configuration file: ```sh title="workspace.sh" ( set -eu directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-workspace.XXXXXX") socket="$directory/tmux.sock" cleanup() { status=$? trap - EXIT if [ -S "$socket" ] && ! tmux -S "$socket" kill-server; then printf '%s\n' "Cannot stop tmux; kept $directory." >&2 exit 1 fi rm -rf "$directory" || status=$? exit "$status" } trap cleanup EXIT trap 'exit 1' HUP INT TERM tmux -S "$socket" -f /dev/null start-server \; \ set-option -s exit-empty off tmux -S "$socket" source-file ./workspace.conf tmux -S "$socket" list-windows -t '=dev' \ -F '#{window_name}: #{window_panes} panes' ) ``` Run it from that directory: ```console $ sh workspace.sh ``` The result is: ```text editor: 2 panes logs: 1 panes ``` `source-file` executes the configuration on the private server. Setting `exit-empty` to `off` keeps that server available for cleanup even when a configuration error prevents session creation. Errors remain visible and make the program fail. Cleanup runs after partial creation too; if stopping tmux fails, the script keeps the socket directory and reports its location. ## Use a workspace manager For YAML or JSON configuration, validation, and language APIs, choose a port: [Python CLI](https://libtmux.org/en/py/latest/workspace/examples/) · [TypeScript](https://libtmux.org/en/ts/latest/workspace/internals/examples/) · [Go](https://libtmux.org/en/go/latest/workspace/internals/examples/) · [Rust](https://libtmux.org/en/rs/latest/workspace/internals/examples/) · [Java](https://libtmux.org/en/java/latest/workspace/internals/examples/) · [C#](https://libtmux.org/en/csharp/latest/workspace/internals/examples/) · [C++](https://libtmux.org/en/cxx/latest/workspace/internals/examples/) · [Swift](https://libtmux.org/en/swift/latest/workspace/internals/examples/) The [workspace concept guide](https://libtmux.org/ja/tmux/concepts/workspaces/) explains configuration and ownership. The [capture example](https://libtmux.org/ja/tmux/examples/capture-pane-output/) shows how to wait for pane output after sending a command. The [tmux manual source](https://github.com/tmux/tmux/blob/94796f6b1182507efac8a272fc309a79e22e58a5/tmux.1) describes `source-file`, window targets, and `exit-empty`. --- # Guides Source: https://libtmux.org/ja/tmux/guides/ > Task-oriented walkthroughs that sit between the concepts and each port's own API reference. Use these guides to connect to tmux, send input, capture output, query objects, and test your program. [Concepts](https://libtmux.org/ja/tmux/concepts/) explains the object model and transport choices. [Examples](https://libtmux.org/ja/tmux/examples/) provides complete programs with setup and cleanup. The general guides use tmux shell commands. Select a port from the dropdown for its library APIs, imports and native project setup. - [Getting started](https://libtmux.org/ja/tmux/guides/getting-started/): Install tmux and run a complete example. - [Attaching to tmux](https://libtmux.org/ja/tmux/guides/attaching-to-tmux/): Select a socket and find or create a session. - [Sending keys](https://libtmux.org/ja/tmux/guides/sending-keys/): Send literal text, named keys, and Enter. - [Capturing output](https://libtmux.org/ja/tmux/guides/capturing-output/): Read the screen or scrollback and wait for a result. - [Filtering and querying](https://libtmux.org/ja/tmux/guides/querying-and-filtering/): Find objects and handle missing or ambiguous matches. - [Testing](https://libtmux.org/ja/tmux/guides/testing-with-libtmux/): Use isolated tmux servers and manage test cleanup. --- # Getting started Source: https://libtmux.org/ja/tmux/guides/getting-started/ > Create a tmux session, add a window and split it into panes. Create a session, add a window, and split that window into panes. These are the same tmux objects that the language libraries control. ## Install tmux Install tmux with your platform's package manager. These examples require tmux 3.2a or newer and a POSIX shell. Confirm the installed version: ```console $ tmux -V ``` ## Run the smallest thing that proves it works Save this complete script as `start.sh`, or paste its entire block into a shell. It creates a private server, so it does not need an existing session. Each pane runs `cat` to keep it alive until cleanup. ```sh title="start.sh" #!/bin/sh set -eu directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-guide.XXXXXX") socket="$directory/tmux.sock" cleanup() { status=$? trap - 0 HUP INT TERM if [ -S "$socket" ] && ! tmux -S "$socket" kill-server; then printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 exit 1 fi rm -rf "$directory" || status=$? exit "$status" } trap cleanup 0 trap 'exit 1' HUP INT TERM tmux -S "$socket" -f /dev/null new-session -d -s work -n main 'cat' tmux -S "$socket" new-window -t work: -n editor 'cat' tmux -S "$socket" split-window -h -t work:editor 'cat' tmux -S "$socket" list-panes -a -F '#{session_name}:#{window_name}' ``` Run the saved script: ```console $ sh start.sh ``` The output contains `work:main` once and `work:editor` twice: one session, two windows, and three panes. The script stops its server on exit, including after a failed command. If shutdown fails, it reports and retains the socket path. ## What just happened `-S` selects the server socket. `new-session` starts that server and its first window; `new-window` adds another window; `split-window` adds a pane. `-d` starts the session without taking over the terminal. `-f /dev/null` starts this private server without a user configuration file. [Server, session, window, pane](https://libtmux.org/ja/tmux/concepts/server-session-window-pane/) explains the hierarchy. [Attaching to tmux](https://libtmux.org/ja/tmux/guides/attaching-to-tmux/) shows how to open a session interactively and leave it running after detaching. ## Pick a port Use the port dropdown for language-specific installation and APIs. The complete programs below include imports, project files, setup, error handling and cleanup: [Python](https://libtmux.org/en/py/latest/examples/capture-pane-output/) · [TypeScript](https://libtmux.org/en/ts/latest/examples/capture-pane-output/) · [Go](https://libtmux.org/en/go/latest/examples/capture-pane-output/) · [Rust](https://libtmux.org/en/rs/latest/examples/capture-pane-output/) · [Java](https://libtmux.org/en/java/latest/examples/capture-pane-output/) · [Kotlin](https://libtmux.org/en/kotlin/latest/examples/capture-pane-output/) · [Scala](https://libtmux.org/en/scala/latest/examples/capture-pane-output/) · [C#](https://libtmux.org/en/csharp/latest/examples/capture-pane-output/) · [F#](https://libtmux.org/en/fsharp/latest/examples/capture-pane-output/) · [C++](https://libtmux.org/en/cxx/latest/examples/capture-pane-output/) · [Swift](https://libtmux.org/en/swift/latest/examples/capture-pane-output/) · [Ruby](https://libtmux.org/en/ruby/latest/examples/capture-pane-output/) · [Lua](https://libtmux.org/en/lua/latest/examples/capture-pane-output/) ## Where to go next [Sending keys](https://libtmux.org/ja/tmux/guides/sending-keys/) types input, and [Capturing output](https://libtmux.org/ja/tmux/guides/capturing-output/) reads a result. Use [Querying and filtering](https://libtmux.org/ja/tmux/guides/querying-and-filtering/) to choose a target. ## tmux reference Read the command references for [new-session](https://libtmux.org/en/tmux/latest/manual/new-session/), [list-sessions](https://libtmux.org/en/tmux/latest/manual/list-sessions/), and [kill-server](https://libtmux.org/en/tmux/latest/manual/kill-server/). Select your tmux version on any reference page. The [tmux manual](https://github.com/tmux/tmux/blob/94796f6b1182507efac8a272fc309a79e22e58a5/tmux.1) documents these commands and their flags. --- # Attaching to tmux Source: https://libtmux.org/ja/tmux/guides/attaching-to-tmux/ > Create or attach to a tmux session, select its socket, and keep terminal attachment separate from automation. Attaching opens a tmux session in your terminal. Its shells and programs keep running when you detach. A script can also query or control that session without taking over a terminal. ## Open a session in your terminal Run this from a terminal outside tmux. It creates `work` if needed and attaches to it otherwise. `-L libtmux-demo` keeps this demonstration on its own named server. `-f /dev/null` skips personal tmux configuration for this demonstration. ```console $ tmux -L libtmux-demo -f /dev/null new-session -A -s work ``` Detach with **Ctrl-b**, then **d**. You return to the original shell while the tmux session keeps running. List that server's sessions: ```console $ tmux -L libtmux-demo list-sessions ``` Attach to the existing session again. The leading `=` selects its exact name: ```console $ tmux -L libtmux-demo attach-session -t '=work' ``` `attach-session` expects a session to exist. `new-session -A` is the command to use when either creating or attaching is acceptable. From inside tmux, `attach-session` switches the attached client to the target session. After detaching, remove the demonstration session when you are finished: ```console $ tmux -L libtmux-demo kill-session -t '=work' ``` ## Choose the server socket A socket identifies a tmux server. Use the same selection on every command: - `-L name` selects a named socket in tmux's socket directory. - `-S path` selects an explicit socket path and overrides `-L`. - Without either flag, tmux uses the socket from [`TMUX`]() when applicable, otherwise its default socket. Inside a pane, [`TMUX`]() identifies its server and [`TMUX_PANE`]() identifies the pane. Prefer explicit socket selection in automation that may run both inside and outside tmux. ## Query a session from a shell script This complete program creates an isolated server, looks up `work`, and prints its name. Each command stays in the calling shell; no terminal is attached. It stops its own server on success or failure. Save it as `connect.sh` and run `sh connect.sh`, or paste the whole block into a POSIX shell. It requires tmux 3.2a or newer. ```sh title="connect.sh" ( set -eu directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-attach.XXXXXX") socket="$directory/tmux.sock" cleanup() { status=$? trap - 0 HUP INT TERM if [ -S "$socket" ] && ! tmux -S "$socket" kill-server; then printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 exit 1 fi rm -rf "$directory" || exit 1 exit "$status" } trap cleanup 0 trap 'exit 1' HUP INT TERM unset TMUX TMUX_PANE tmux -S "$socket" -f /dev/null new-session -d -s work /bin/cat tmux -S "$socket" has-session -t '=work' tmux -S "$socket" list-sessions -F '#{session_name}' ) ``` `-d` starts the session without attaching. `/bin/cat` keeps its pane open without loading a shell configuration. The script addresses only its private socket. If shutdown fails, it reports the error and keeps that socket's directory for inspection. ## Find a session before creating one Use `has-session -t '=name'` to check an exact session name. It exits unsuccessfully when tmux cannot find the session or contact the server; keep the diagnostic so you can distinguish those failures. A lookup does not reserve a name. Another client can create or remove a session before your next command. Handle the result of `new-session` even after checking. Use `new-session -A` for the interactive create-or-attach workflow shown above. ## Connect from a language library Use the port menu to open this guide with a complete program, imports, build files, and a private-server launcher. Each program connects to an existing socket and leaves that server running: [Python](https://libtmux.org/en/py/latest/guides/attaching-to-tmux/) · [TypeScript](https://libtmux.org/en/ts/latest/guides/attaching-to-tmux/) · [Go](https://libtmux.org/en/go/latest/guides/attaching-to-tmux/) · [Rust](https://libtmux.org/en/rs/latest/guides/attaching-to-tmux/) · [Java](https://libtmux.org/en/java/latest/guides/attaching-to-tmux/) · [Kotlin](https://libtmux.org/en/kotlin/latest/guides/attaching-to-tmux/) · [Scala](https://libtmux.org/en/scala/latest/guides/attaching-to-tmux/) · [C#](https://libtmux.org/en/csharp/latest/guides/attaching-to-tmux/) · [F#](https://libtmux.org/en/fsharp/latest/guides/attaching-to-tmux/) · [C++](https://libtmux.org/en/cxx/latest/guides/attaching-to-tmux/) · [Swift](https://libtmux.org/en/swift/latest/guides/attaching-to-tmux/) · [Ruby](https://libtmux.org/en/ruby/latest/guides/attaching-to-tmux/) · [Lua](https://libtmux.org/en/lua/latest/guides/attaching-to-tmux/) ## Where to go next - [Sending keys](https://libtmux.org/ja/tmux/guides/sending-keys/) explains input and command completion. - [Capturing output](https://libtmux.org/ja/tmux/guides/capturing-output/) reads a pane's screen and history. - [Socket and servers](https://libtmux.org/ja/tmux/topics/socket-and-servers/) covers server selection. The [attach-session reference](https://libtmux.org/en/tmux/latest/manual/attach-session/) covers attachment flags. See [has-session](https://libtmux.org/en/tmux/latest/manual/has-session/) for existence checks and the [global options](https://libtmux.org/en/tmux/latest/manual/full/#DESCRIPTION) for selecting a server socket. The [tmux manual source](https://github.com/tmux/tmux/blob/94796f6b1182507efac8a272fc309a79e22e58a5/tmux.1) describes `new-session`, `attach-session`, socket selection, and targeting. --- # Sending keys Source: https://libtmux.org/ja/tmux/guides/sending-keys/ > Send literal text or named keys to a tmux pane and distinguish input from completion. `send-keys -l` types literal characters. Without `-l`, tmux recognizes key names such as `Enter`, `C-c`, and [`Up`](). Typing the word `Enter` and pressing Enter are separate operations. ## Literal text, key names, and whether Enter follows This complete script types the word `Enter` into a pane running `cat`, then presses the Enter key. Neither command starts an interactive tmux client. Save it as `send.sh` and run it in a POSIX shell with tmux 3.2a or newer and fractional `sleep` support. ```sh title="send.sh" #!/bin/sh set -eu directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-guide.XXXXXX") socket="$directory/tmux.sock" cleanup() { status=$? trap - 0 HUP INT TERM if [ -S "$socket" ] && ! tmux -S "$socket" kill-server; then printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 exit 1 fi rm -rf "$directory" || status=$? exit "$status" } trap cleanup 0 trap 'exit 1' HUP INT TERM tmux -S "$socket" -f /dev/null new-session -d -s input 'cat' tmux -S "$socket" send-keys -t input:0.0 -l 'Enter' tmux -S "$socket" send-keys -t input:0.0 Enter attempt=0 while [ "$attempt" -lt 100 ]; do screen=$(tmux -S "$socket" capture-pane -p -t input:0.0) if printf '%s\n' "$screen" | grep -Fqx 'Enter'; then printf '%s\n' "$screen" exit 0 fi attempt=$((attempt + 1)) sleep 0.05 done printf '%s\n' 'Timed out waiting for typed input.' >&2 exit 1 ``` Run the saved script: ```console $ sh send.sh ``` The screen contains `Enter`: the terminal echoes the input, and `cat` writes it back after the newline. The polling loop waits for visible text and fails after 100 unsuccessful checks. Cleanup stops only the private server. Literal input disables tmux's key-name lookup. The application still interprets those characters. In a shell pane, that includes shell quoting, expansions and commands; literal mode does not make shell input safe to compose from arbitrary text. ## The race you can't see from the call site Completing `send-keys` means tmux accepted the input. It does not establish that the application read it or finished a command. Terminal echo can appear before the application processes a line. For a shell command, wait for its distinct output or a completion signal before using the result. [Capture pane output](https://libtmux.org/ja/tmux/examples/capture-pane-output/) matches a complete output line so the echoed command cannot satisfy the check. [Capturing output](https://libtmux.org/ja/tmux/guides/capturing-output/) explains screen and history capture. ## Use a language library Each port's complete capture program sends a command, waits for its output and cleans up. Use the port dropdown for its input APIs, or open the program: [Python](https://libtmux.org/en/py/latest/examples/capture-pane-output/) · [TypeScript](https://libtmux.org/en/ts/latest/examples/capture-pane-output/) · [Go](https://libtmux.org/en/go/latest/examples/capture-pane-output/) · [Rust](https://libtmux.org/en/rs/latest/examples/capture-pane-output/) · [Java](https://libtmux.org/en/java/latest/examples/capture-pane-output/) · [Kotlin](https://libtmux.org/en/kotlin/latest/examples/capture-pane-output/) · [Scala](https://libtmux.org/en/scala/latest/examples/capture-pane-output/) · [C#](https://libtmux.org/en/csharp/latest/examples/capture-pane-output/) · [F#](https://libtmux.org/en/fsharp/latest/examples/capture-pane-output/) · [C++](https://libtmux.org/en/cxx/latest/examples/capture-pane-output/) · [Swift](https://libtmux.org/en/swift/latest/examples/capture-pane-output/) · [Ruby](https://libtmux.org/en/ruby/latest/examples/capture-pane-output/) · [Lua](https://libtmux.org/en/lua/latest/examples/capture-pane-output/) ## tmux reference The [send-keys reference](https://libtmux.org/en/tmux/latest/manual/send-keys/) describes literal input, key names, and each supported flag. Select your installed tmux version on that page. The [tmux manual](https://github.com/tmux/tmux/blob/94796f6b1182507efac8a272fc309a79e22e58a5/tmux.1) documents these commands and their flags. --- # Capturing output Source: https://libtmux.org/ja/tmux/guides/capturing-output/ > Capture a tmux pane screen or include its scrollback history. `capture-pane -p` prints a pane's visible screen. Add `-S -` to start at the oldest line still present in its scrollback history. Capture is a snapshot of terminal state; it is not a log of every byte the application wrote. ## Visible pane vs. scrollback This example forces output into scrollback: it prints 40 numbered lines in a pane with 10 rows. A normal capture shows the last screenful. Adding `-S -` also retrieves earlier lines, including `row-1`. Save the script as `history.sh`. It needs tmux 3.2a or newer, a POSIX shell and fractional `sleep` support. ```sh title="history.sh" #!/bin/sh set -eu directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-guide.XXXXXX") socket="$directory/tmux.sock" cleanup() { status=$? trap - 0 HUP INT TERM if [ -S "$socket" ] && ! tmux -S "$socket" kill-server; then printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 exit 1 fi rm -rf "$directory" || status=$? exit "$status" } trap cleanup 0 trap 'exit 1' HUP INT TERM tmux -S "$socket" -f /dev/null new-session -d -s capture -x 80 -y 10 \ 'i=1; while [ "$i" -le 40 ]; do printf "row-%s\n" "$i"; i=$((i + 1)); done; exec cat' tmux -S "$socket" resize-window -t capture:0 -x 80 -y 10 attempt=0 while [ "$attempt" -lt 100 ]; do screen=$(tmux -S "$socket" capture-pane -p -t capture:0.0) if printf '%s\n' "$screen" | grep -Fqx 'row-40'; then printf 'Visible screen:\n%s\n' "$screen" printf '\nScreen and scrollback:\n' tmux -S "$socket" capture-pane -p -S - -t capture:0.0 exit 0 fi attempt=$((attempt + 1)) sleep 0.05 done printf '%s\n' 'Timed out waiting for pane output.' >&2 exit 1 ``` Run the saved script: ```console $ sh history.sh ``` `row-1` appears in the history capture but has already scrolled off the visible screen. `row-40` appears in both. The script cleans up its private server after printing or after any failure. A numeric `-S` chooses a starting row: `0` is the top visible row and negative values reach into history. `-E` selects the final row. `-J` joins wrapped rows; `-e` includes terminal escape sequences for attributes such as color. History is bounded by `history-limit`, so discarded lines cannot be recovered by capture. ## Wait for the expected text Wait for an observable result with a deadline. An immediate capture after [Sending keys](https://libtmux.org/ja/tmux/guides/sending-keys/) can race the application. Match a complete output line, as [Capture pane output](https://libtmux.org/ja/tmux/examples/capture-pane-output/) does, to avoid treating an echoed command as completed work. A screen may change before the next capture. For continuously consumed output, use a pipe or an attached control-mode client's output events; see [Control mode vs one-shot](https://libtmux.org/ja/tmux/concepts/transports/). ## Wait for a completion signal A program that controls its own completion can send `wait-for -S` on a dedicated tmux channel. A matching `wait-for` waits on that server. Use the same socket and a distinct channel for each task, and put a deadline around the wait. [Waiting and retrying](https://libtmux.org/ja/tmux/topics/waiting-and-retry/) covers the channel protocol. ## Use a language library The port dropdown opens that language's capture guide. Complete programs with imports and setup are available here: [Python](https://libtmux.org/en/py/latest/examples/capture-pane-output/) · [TypeScript](https://libtmux.org/en/ts/latest/examples/capture-pane-output/) · [Go](https://libtmux.org/en/go/latest/examples/capture-pane-output/) · [Rust](https://libtmux.org/en/rs/latest/examples/capture-pane-output/) · [Java](https://libtmux.org/en/java/latest/examples/capture-pane-output/) · [Kotlin](https://libtmux.org/en/kotlin/latest/examples/capture-pane-output/) · [Scala](https://libtmux.org/en/scala/latest/examples/capture-pane-output/) · [C#](https://libtmux.org/en/csharp/latest/examples/capture-pane-output/) · [F#](https://libtmux.org/en/fsharp/latest/examples/capture-pane-output/) · [C++](https://libtmux.org/en/cxx/latest/examples/capture-pane-output/) · [Swift](https://libtmux.org/en/swift/latest/examples/capture-pane-output/) · [Ruby](https://libtmux.org/en/ruby/latest/examples/capture-pane-output/) · [Lua](https://libtmux.org/en/lua/latest/examples/capture-pane-output/) ## tmux reference The [capture-pane reference](https://libtmux.org/en/tmux/latest/manual/capture-pane/) documents line ranges, scrollback, and output flags for each supported tmux version. See [wait-for](https://libtmux.org/en/tmux/latest/manual/wait-for/) for completion channels. The [tmux manual](https://github.com/tmux/tmux/blob/94796f6b1182507efac8a272fc309a79e22e58a5/tmux.1) documents these commands and their flags. --- # Querying and filtering Source: https://libtmux.org/ja/tmux/guides/querying-and-filtering/ > Find an exact tmux session and select one pane using formats and filters. Use an exact target when you know its name, or filter a listing when you need to inspect several objects. A session named `work` and one named `worker` should not become interchangeable targets. ## Require exactly one match Prefix a session target with `=` to require an exact name. `has-session` reports whether it exists; it does not return a pane. This script selects panes in `work` and rejects both zero matches and multiple matches before using an ID. Save the complete script as `query.sh`. It requires tmux 3.2a or newer and a POSIX shell. It creates and cleans up its own server. ```sh title="query.sh" #!/bin/sh set -eu directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-guide.XXXXXX") socket="$directory/tmux.sock" cleanup() { status=$? trap - 0 HUP INT TERM if [ -S "$socket" ] && ! tmux -S "$socket" kill-server; then printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 exit 1 fi rm -rf "$directory" || status=$? exit "$status" } trap cleanup 0 trap 'exit 1' HUP INT TERM tmux -S "$socket" -f /dev/null new-session -d -s work 'cat' tmux -S "$socket" new-session -d -s worker 'cat' tmux -S "$socket" has-session -t '=work' panes=$(tmux -S "$socket" list-panes -a \ -f '#{==:#{session_name},work}' -F '#{pane_id}') # Pane IDs contain no whitespace; split the rows to count matches. # shellcheck disable=SC2086 set -- $panes if [ "$#" -ne 1 ]; then printf 'Expected one work pane, found %s.\n' "$#" >&2 exit 1 fi tmux -S "$socket" display-message -p -t "$1" '#{session_name}' ``` Run the saved script: ```console $ sh query.sh ``` The output is `work`. The `worker` session remains outside the result. Targeting the returned pane ID avoids repeating name matching when the next command runs. An object can still disappear between commands; keep errors visible. ## Declarative filters `list-panes -a` searches every session. `-f` evaluates a tmux format as a boolean for each pane; here `#{==:#{session_name},work}` keeps only exact session-name matches. `-F` chooses what each returned row contains. Using only `#{pane_id}` keeps the result easy to pass to another tmux command. ## Case-insensitive matching Choose case handling explicitly when a name may vary in capitalization. tmux's `m` format operator supports an `i` modifier for case-insensitive matching. Keep the ordinary `==` comparison when exact case is part of your contract. [Filtering and queries](https://libtmux.org/ja/tmux/concepts/queries/) explains the query model. ## Push the filter into tmux, or read once and filter locally A tmux-side filter reduces returned rows. Capturing a listing once and filtering it in your program is useful when several decisions should use the same read. Neither approach reserves the objects. Unknown format names expand to empty values; check an unexpectedly empty result before assuming nothing exists. ## Use a language library The port dropdown opens the language's query guide. These complete programs connect to an existing server, find exactly the `work` session and report its absence: [Python](https://libtmux.org/en/py/latest/guides/attaching-to-tmux/) · [TypeScript](https://libtmux.org/en/ts/latest/guides/attaching-to-tmux/) · [Go](https://libtmux.org/en/go/latest/guides/attaching-to-tmux/) · [Rust](https://libtmux.org/en/rs/latest/guides/attaching-to-tmux/) · [Java](https://libtmux.org/en/java/latest/guides/attaching-to-tmux/) · [Kotlin](https://libtmux.org/en/kotlin/latest/guides/attaching-to-tmux/) · [Scala](https://libtmux.org/en/scala/latest/guides/attaching-to-tmux/) · [C#](https://libtmux.org/en/csharp/latest/guides/attaching-to-tmux/) · [F#](https://libtmux.org/en/fsharp/latest/guides/attaching-to-tmux/) · [C++](https://libtmux.org/en/cxx/latest/guides/attaching-to-tmux/) · [Swift](https://libtmux.org/en/swift/latest/guides/attaching-to-tmux/) · [Ruby](https://libtmux.org/en/ruby/latest/guides/attaching-to-tmux/) · [Lua](https://libtmux.org/en/lua/latest/guides/attaching-to-tmux/) ## tmux reference See [has-session](https://libtmux.org/en/tmux/latest/manual/has-session/), [list-panes](https://libtmux.org/en/tmux/latest/manual/list-panes/), and the [target syntax](https://libtmux.org/en/tmux/latest/manual/full/#COMMANDS). The version selector shows the flags supported by your installed tmux release. The [tmux manual](https://github.com/tmux/tmux/blob/94796f6b1182507efac8a272fc309a79e22e58a5/tmux.1) documents these commands and their flags. --- # Testing with tmux Source: https://libtmux.org/ja/tmux/guides/testing-with-libtmux/ > Test on a private tmux server and preserve errors during cleanup. Give each test its own tmux socket. Start it with a known configuration, assert the state your program needs, and stop only the server the test owns. This keeps a test run separate from your interactive sessions. ## Run an isolated test This complete shell test creates a session and a window, checks their names, and prints `tmux fixture passed` on success. It requires tmux 3.2a or newer and a POSIX shell. Save it as `test-tmux.sh`. ```sh title="test-tmux.sh" #!/bin/sh set -eu directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-guide.XXXXXX") socket="$directory/tmux.sock" cleanup() { status=$? trap - 0 HUP INT TERM if [ -S "$socket" ] && ! tmux -S "$socket" kill-server; then printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 exit 1 fi rm -rf "$directory" || status=$? exit "$status" } trap cleanup 0 trap 'exit 1' HUP INT TERM tmux -S "$socket" -f /dev/null new-session -d -s fixture -n main 'cat' tmux -S "$socket" new-window -t fixture: -n worker 'cat' name=$(tmux -S "$socket" display-message -p -t fixture:worker '#{session_name}') if [ "$name" != fixture ]; then printf 'Expected fixture, got %s.\n' "$name" >&2 exit 1 fi windows=$(tmux -S "$socket" list-windows -t '=fixture' -F '#{window_name}') if ! printf '%s\n' "$windows" | grep -Fqx worker; then printf '%s\n' 'The worker window was not created.' >&2 exit 1 fi printf '%s\n' 'tmux fixture passed' ``` Run the saved script: ```console $ sh test-tmux.sh ``` The trap preserves a failed assertion's exit status and also reports cleanup failures. If the server cannot be stopped, its socket directory stays available for inspection. Each invocation gets a fresh directory from `mktemp`. ## Wait for the state you assert A successful `send-keys` call establishes that tmux accepted input, not that the application finished processing it. For output assertions, use a bounded wait such as the [complete capture example](https://libtmux.org/ja/tmux/examples/capture-pane-output/). A fixed pause alone cannot establish that the result arrived. ## Use a language test fixture Language ports have different fixture and lifetime APIs. Select a port from the dropdown for its testing guidance. The complete programs below demonstrate creating a private server, checking a result and cleaning up through that port: [Python](https://libtmux.org/en/py/latest/examples/capture-pane-output/) · [TypeScript](https://libtmux.org/en/ts/latest/examples/capture-pane-output/) · [Go](https://libtmux.org/en/go/latest/examples/capture-pane-output/) · [Rust](https://libtmux.org/en/rs/latest/examples/capture-pane-output/) · [Java](https://libtmux.org/en/java/latest/examples/capture-pane-output/) · [Kotlin](https://libtmux.org/en/kotlin/latest/examples/capture-pane-output/) · [Scala](https://libtmux.org/en/scala/latest/examples/capture-pane-output/) · [C#](https://libtmux.org/en/csharp/latest/examples/capture-pane-output/) · [F#](https://libtmux.org/en/fsharp/latest/examples/capture-pane-output/) · [C++](https://libtmux.org/en/cxx/latest/examples/capture-pane-output/) · [Swift](https://libtmux.org/en/swift/latest/examples/capture-pane-output/) · [Ruby](https://libtmux.org/en/ruby/latest/examples/capture-pane-output/) · [Lua](https://libtmux.org/en/lua/latest/examples/capture-pane-output/) ## Where to go next [Attaching to tmux](https://libtmux.org/ja/tmux/guides/attaching-to-tmux/) connects to a server that should remain running. [Querying and filtering](https://libtmux.org/ja/tmux/guides/querying-and-filtering/) selects an exact target, and [Capturing output](https://libtmux.org/ja/tmux/guides/capturing-output/) reads its screen. ## tmux reference See [new-session](https://libtmux.org/en/tmux/latest/manual/new-session/), [new-window](https://libtmux.org/en/tmux/latest/manual/new-window/), and [kill-server](https://libtmux.org/en/tmux/latest/manual/kill-server/) for the commands used by the fixture. The [global options](https://libtmux.org/en/tmux/latest/manual/full/#DESCRIPTION) describe socket selection and configuration files. The [tmux manual](https://github.com/tmux/tmux/blob/94796f6b1182507efac8a272fc309a79e22e58a5/tmux.1) documents these commands and their flags. --- # Third-party notices Source: https://libtmux.org/ja/third-party-notices/ > Thanks to tmux's creator and contributors, with licences and attribution for the software behind libtmux.org. ## tmux First and foremost, thank you to **[Nicholas Marriott (nicm)](https://github.com/nicm)**, the creator of tmux, and to the many [tmux contributors](https://github.com/tmux/tmux/graphs/contributors). We are grateful for the work that makes tmux possible. libtmux is a separate project. Our libraries and supporting tools control a real tmux server; tmux itself provides the terminal multiplexer. This is the libtmux website. Visit the [official tmux website](https://github.com/tmux/tmux/wiki) for the upstream project and its documentation. See tmux's [COPYING file](https://github.com/tmux/tmux/blob/master/COPYING) and the copyright and permission notices in its source files for its licensing. ### tmux artwork The tmux logomark is by Jason Long. The site uses the unmodified [upstream SVG](https://github.com/tmux/tmux/blob/8f25579c5aef8d93924a20681f394e2a582fd3ad/logo/tmux-logomark.svg) under its [copyright and permission notice](https://libtmux.org/ja/brand/tmux/LICENSE.txt). ## Programming-language artwork The homepage language selector uses local copies of the following artwork to identify the selected language. Each source record includes the download URL, retrieval time, file hash, copyright information and usage terms. These marks identify their respective languages and do not imply endorsement of libtmux. The SVG files are copied unchanged except for Scala, whose empty surrounding canvas is cropped. Its paths, gradients and colors are preserved, and the original SVG is retained beside the cropped copy. Rust's supplied SVG includes its own dark-theme colors. | Language | Credit and terms | Local source record | |---|---|---| | Python | Python Software Foundation. The [PSF logo terms](https://www.python.org/psf/trademarks/) permit the unaltered mark to identify Python. | [Python provenance](https://libtmux.org/ja/brand/languages/py/provenance.json) | | Ruby | Copyright © 2006, Yukihiro Matsumoto. The [Ruby logo](https://www.ruby-lang.org/en/about/logo/) is licensed under [CC BY-SA 2.5](https://creativecommons.org/licenses/by-sa/2.5/). | [Ruby provenance](https://libtmux.org/ja/brand/languages/ruby/provenance.json) | | Lua | Copyright © 1998 Lua.org; graphic design by Alexandre Nakonechnyj. The Devicon copy carries its [MIT notice](https://libtmux.org/ja/brand/languages/lua/LICENSE.txt); [Lua's logo terms](https://www.lua.org/images/) also apply. Visit [Lua.org](https://www.lua.org/). | [Lua provenance](https://libtmux.org/ja/brand/languages/lua/provenance.json) | | TypeScript | Microsoft. The [official branding terms](https://www.typescriptlang.org/branding/) govern the mark; the website repository licenses exclude logo and trademark rights. | [TypeScript provenance](https://libtmux.org/ja/brand/languages/ts/provenance.json) | | Rust | The Rust Foundation. [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) and the [Rust trademark policy](https://rustfoundation.org/policy/rust-trademark-policy/) apply. | [Rust provenance](https://libtmux.org/ja/brand/languages/rs/provenance.json) | | Go | The [Go gopher](https://go.dev/blog/gopher) is by Renee French, licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). | [Go provenance](https://libtmux.org/ja/brand/languages/go/provenance.json) | | C++ | Created by Jeremy Kratz and licensed by the Standard C++ Foundation under its [logo-use terms](https://isocpp.org/home/terms-of-use). | [C++ provenance](https://libtmux.org/ja/brand/languages/cxx/provenance.json) | | Swift | Apple Inc., under the [Swift Logo Guidelines](https://developer.apple.com/swift/downloads/swift-logo.zip). Swift and the Swift logo are trademarks of Apple Inc. | [Swift provenance](https://libtmux.org/ja/brand/languages/swift/provenance.json) | | Java | Devicon collection copyright (c) 2015 konpa, with its [MIT notice](https://libtmux.org/ja/brand/languages/java/LICENSE.txt). This does not establish unrestricted rights to the underlying Java logo; [Oracle's logo terms](https://www.oracle.com/legal/logos/) apply. | [Java provenance](https://libtmux.org/ja/brand/languages/java/provenance.json) | | Kotlin | Kotlin Foundation brand guidelines preserve JetBrains copyrights. The [icon-use terms](https://kotlinfoundation.org/guidelines/) permit identifying Kotlin alongside other programming-language icons. | [Kotlin provenance](https://libtmux.org/ja/brand/languages/kotlin/provenance.json) | | Scala | Copyright EPFL. [Historical permission](https://groups.google.com/g/scala-user/c/bCC-R0FQn1w) covers noncommercial Scala promotion; the [general artwork-license question](https://github.com/scala/scala-lang/issues/1040) remains unresolved. | [Scala provenance](https://libtmux.org/ja/brand/languages/scala/provenance.json) | | C# | Copyright the .NET authors. [Brand-use permission](https://github.com/dotnet/brand/issues/10#issuecomment-669465301) allows the unmodified logo to represent .NET. The repository's CC0 statement covers illustrations; no blanket CC0 claim is made for the logo. | [.NET provenance](https://libtmux.org/ja/brand/languages/csharp/provenance.json) | | F# | The F# Software Foundation. Its [logo terms](https://foundation.fsharp.org/logo) require unchanged shape, colors and proportions, without implying Foundation representation. | [F# provenance](https://libtmux.org/ja/brand/languages/fsharp/provenance.json) | ## Terminal artwork The tmux CLI selector displays the Windows Terminal artwork by Microsoft Corporation, licensed under [CC BY-ND 4.0](https://creativecommons.org/licenses/by-nd/4.0/). The SVG is unmodified. See the [source and provenance](https://libtmux.org/ja/brand/tools/terminal/provenance.json) and [copyright and license](https://libtmux.org/ja/brand/tools/terminal/LICENSE.txt). ## Documentation toolchain libtmux and this site are built with open-source software. The following tools have their own licences and attribution requirements. | Tool | Licence | Role | |---|---|---| | [Astro](https://astro.build/) | MIT | The site shell | | [Tailwind CSS](https://tailwindcss.com/) | MIT | Styling | | [Pagefind](https://pagefind.app/) | MIT | Site-wide search | | [Expressive Code](https://expressive-code.com/) | MIT | Code blocks | | [Sphinx](https://www.sphinx-doc.org/) | BSD-2-Clause | Python and C++ reference | | [Furo](https://github.com/pradyunsg/furo) | MIT | Sphinx theme | | [Breathe](https://github.com/breathe-doc/breathe) | BSD-3-Clause | Doxygen XML into Sphinx | | [Doxygen](https://www.doxygen.nl/) | GPL-2.0-only | Parses C++ headers to XML | | [API Extractor](https://api-extractor.com/) | MIT | TypeScript API model | | [IBM Plex](https://github.com/IBM/plex) | OFL-1.1 | Typeface | ### A note on Doxygen Doxygen is licensed GPL-2.0-only. It runs as a build step that reads libtmux's own headers and emits XML; that XML is rendered by Breathe and Sphinx, and no Doxygen-generated HTML is published. Running a GPL program over your own input does not place its licence on the output, and libtmux does not distribute Doxygen or any modified version of it. ## Reference hosting The [PyPI blocks logo](https://pypi.org/trademarks/) is a trademark of the Python Software Foundation and identifies links to the Python Package Index. Other package-host icons use [Simple Icons](https://simpleicons.org/) (CC0-1.0). Three ports deep-link to the canonical host their ecosystem already uses, rather than duplicating it here: - Rust: [docs.rs](https://docs.rs/libtmux) - Go: [pkg.go.dev](https://pkg.go.dev/github.com/libtmux/libtmux-go/tmux) - Java and Kotlin: [javadoc.io](https://javadoc.io/doc/io.github.libtmux/libtmux) Those sites are operated independently of this project and carry their own terms. ## libtmux itself Each port is MIT licensed. See the `LICENSE` file in that port's repository for the authoritative text. --- # Topics Source: https://libtmux.org/ja/tmux/topics/ > Object traversal, cleanup, pane I/O, configuration, and failure handling. Use these pages for object traversal, cleanup, pane I/O, configuration, and failure handling. [Concepts](https://libtmux.org/ja/tmux/concepts/) introduces the shared object model. The concept guides also cover [control mode vs one-shot](https://libtmux.org/ja/tmux/concepts/transports/), [filtering and queries](https://libtmux.org/ja/tmux/concepts/queries/), and [workspaces](https://libtmux.org/ja/tmux/concepts/workspaces/). Choose a topic for more detailed behavior. Use your port's API reference for signatures and defaults. Each topic explains the behavior behind those calls. - [Architecture](https://libtmux.org/ja/tmux/topics/architecture/): Locate operations and field definitions in the library source. - [Traversal](https://libtmux.org/ja/tmux/topics/traversal/): Navigate related objects, test membership, and compare identity. - [Ownership and cleanup](https://libtmux.org/ja/tmux/topics/context-managers/): Manage cleanup on block exit and identify objects that need a kill. - [Pane interaction](https://libtmux.org/ja/tmux/topics/pane-interaction/): Choose input modes, capture ranges, and completion waits. - [Options and hooks](https://libtmux.org/ja/tmux/topics/options-and-hooks/): Configure tmux and register event commands at a supported scope. - [Format-token fields](https://libtmux.org/ja/tmux/topics/format-tokens/): Read typed state and handle absent fields. - [Waiting and retrying](https://libtmux.org/ja/tmux/topics/waiting-and-retry/): Wait on a condition or a named tmux signal. - [Environment](https://libtmux.org/ja/tmux/topics/environment/): Locate objects from process variables and configure new panes. - [Socket and servers](https://libtmux.org/ja/tmux/topics/socket-and-servers/): Select a server, check liveness, and detect a replacement daemon. - [Errors and exceptions](https://libtmux.org/ja/tmux/topics/errors-and-exceptions/): Handle command failures and decide whether a mutation can be retried. --- # Architecture Source: https://libtmux.org/ja/tmux/topics/architecture/ > Locate operations, distinguish snapshots from live commands, and find their implementation. A server handle selects a tmux server. Object IDs select sessions, windows, and panes within it. For the object hierarchy and stable IDs, start with [Server, session, window, pane](https://libtmux.org/ja/tmux/concepts/server-session-window-pane/). ## Calling operations Session, window, and pane handles carry their ID and server context. Call an operation on the object you want to change. The examples below send input and kill that pane. ```python pane.send_keys("echo hi") pane.kill() ``` ```typescript await pane.sendKeys("echo hi"); await pane.kill(); ``` ```go cmd := "printf 'hello\\n'" if err := pane.SendKeys(ctx, tmux.SendKeysRequest{Command: &cmd, Literal: true}); err != nil { return err } if err := pane.Kill(ctx); err != nil { return err } ``` ```rust pane.send_line("echo hi").await?; pane.kill().await?; // consumes the handle: see Context managers ``` ```java pane.sendLine("echo hi"); pane.kill(); ``` ```csharp await pane.SendTextAsync("echo hi"); await pane.KillAsync(); ``` ```cpp pane->send_text("echo hi"); pane->kill(); ``` Swift's [`Session`](), [`Window`](), and [`Pane`]() are [`Sendable`]() value types holding IDs and state fields. [Format-token fields](https://libtmux.org/ja/tmux/topics/format-tokens/) lists their fields. Perform operations through [`Server`](), passing the target value: ```swift try await server.sendKeys(["echo hi", "Enter"], to: pane) try await server.kill(pane) ``` Keep the [`Server`]() that produced the snapshot. Pass its captured values back to that server for operations. ## Reading tmux fields Object IDs become tmux targets (`-t`). Format variables (`#{...}`) provide the state returned by tmux. ### Python [`libtmux.constants`]() defines the format fields and their scope and tmux-version requirements. [`Obj`]() in [`libtmux.neo`]() exposes the captured values as dataclass fields. A field excluded by those requirements is [`None`](). ### TypeScript [`packages/libtmux/src/_generated/format_fields.ts`]() records each token's scope and first tmux version. [`packages/libtmux/src/_generated/field_aliases.ts`]() provides camelCase aliases for fields on [`Pane`](), [`Session`](), and [`Window`](). ### Go [`tmux/format_generated.go`]() defines format fields, and [`tmux/option_generated.go`]() defines options. The format generator lives in [`tmux/internal/generate/formats/`](). Accessors return a value and a boolean when a field can be unavailable; check the boolean before using the value. ### Rust [`crates/libtmux/src/formats.rs`]() defines the format catalog. Each entry records the tmux name, required context, first supported release, decoder, and handling of empty values. [`crates/libtmux/src/snapshot.rs`]() uses that catalog to decode captured fields. Handle accessors such as [`Pane.current_command`]() return `Option` when a value can be absent. Typed field queries return [`Availability`](), which also distinguishes an unsupported field from an absent value. See [Format-token fields](https://libtmux.org/ja/tmux/topics/format-tokens/) for field availability. ### Java Typed field classes such as [`Pane_`]() and [`Session_`]() support the query layer. Accessors use `Optional` for fields that may be unavailable on the running tmux version. [Filtering and queries](https://libtmux.org/en/java/latest/concepts/queries/) explains how to select and query those fields. ### C# Typed properties read a dictionary captured from tmux. A property throws [`IncompleteSnapshotException`]() when the capture did not request its field. This differs from a captured field whose value is absent. ### Swift Snapshots capture a fixed set of non-optional fields, including indices, dimensions, active state, command, path, and edge flags. [Format-token fields](https://libtmux.org/ja/tmux/topics/format-tokens/) covers other tokens. ### C++ Handles capture a fixed set of non-optional fields. Use [`pane->expand("#{...}")`]() for tokens outside those fields. [Format-token fields](https://libtmux.org/ja/tmux/topics/format-tokens/) covers their interpretation. ## Source layout ### Python [`Server`](), [`Session`](), [`Window`](), [`Pane`](), and [`Client`]() each have their own module: [`src/libtmux/server.py`](), [`src/libtmux/session.py`](), [`src/libtmux/window.py`](), [`src/libtmux/pane.py`](), and [`src/libtmux/client.py`](). [`src/libtmux/common.py`]() holds shared behavior. [`src/libtmux/neo.py`]() defines the dataclass query layer, [`src/libtmux/options.py`]() and [`src/libtmux/hooks.py`]() provide mixins, and [`src/libtmux/exc.py`]() defines the exception hierarchy. ### TypeScript The public classes live in [`packages/libtmux/src/server.ts`](), [`packages/libtmux/src/session.ts`](), [`packages/libtmux/src/window.ts`](), [`packages/libtmux/src/pane.ts`](), and [`packages/libtmux/src/client.ts`](). Their operations are split by concern under [`packages/libtmux/src/_internal/operations/`](), including [`packages/libtmux/src/_internal/operations/pane_io.ts`](), [`packages/libtmux/src/_internal/operations/hooks.ts`](), [`packages/libtmux/src/_internal/operations/options.ts`](), and [`packages/libtmux/src/_internal/operations/topology.ts`](). [`packages/libtmux/src/_generated/`]() contains the generated field catalogs. Workspaces and the MCP server have separate packages in the same repository. ### Go The [`tmux`]() package groups its implementation by concern. [`tmux/model.go`]() defines the core structs. [`tmux/lifecycle_kill.go`](), [`tmux/pane_capture.go`](), [`tmux/pane_geometry.go`](), and [`tmux/hierarchy.go`]() implement lifecycle, capture, geometry, and traversal. [`tmux/plan_server.go`]() combines commands into fewer invocations. [`tmuxq/`]() provides predicate queries over an already-read snapshot; see [Filtering and queries](https://libtmux.org/en/go/latest/concepts/queries/). Workspace support lives in [`workspace/`](). ### Rust [`Server`](), [`Session`](), [`Window`](), and [`Pane`]() are defined in [`crates/libtmux/src/server.rs`](), [`crates/libtmux/src/session.rs`](), [`crates/libtmux/src/window.rs`](), and [`crates/libtmux/src/pane.rs`](). Their additional operations live in [`crates/libtmux/src/server/`](), [`crates/libtmux/src/session/`](), [`crates/libtmux/src/window/`](), and [`crates/libtmux/src/pane/`](). The [options and hooks](https://libtmux.org/ja/tmux/topics/options-and-hooks/) methods are grouped in [`crates/libtmux/src/server/settings.rs`](), [`crates/libtmux/src/session/settings.rs`](), [`crates/libtmux/src/window/settings.rs`](), and [`crates/libtmux/src/pane/settings.rs`](). [`crates/libtmux/src/hooks.rs`]() defines [`IndexedHooks`]() and [`SparseValues`](); [`crates/libtmux/src/options.rs`]() defines option schemas and values. Workspaces and the MCP server are separate crates under [`crates/tmux-workspace/`]() and [`crates/tmux-mcp/`](). ### Java [`libtmux/src/main/java/io/github/libtmux/`]() holds the [`Server`](), [`Session`](), [`Window`](), and [`Pane`]() classes. Their `options()` and `hooks()` accessors return [`Options`]() and [`Hooks`]() views scoped to the object. [`Session_`](), [`Window_`](), and [`Pane_`]() are typed-field classes for the query layer. ### C# [`src/LibTmux/`]() splits each entity into partial-class files by concern. For example, [`src/LibTmux/Pane.cs`](), [`src/LibTmux/Pane.Capture.cs`](), [`src/LibTmux/Pane.Input.cs`](), [`src/LibTmux/Pane.Relations.cs`](), [`src/LibTmux/Pane.Scopes.cs`](), and [`src/LibTmux/Pane.Topology.cs`]() contribute to one [`Pane`]() type. `Options` and `Hooks` views are reached through the object's properties. ### C++ [`include/libtmux/entities.hpp`]() declares [`Session`](), [`Window`](), and [`Pane`](). Their method bodies live in [`src/`](). [`include/libtmux/server.hpp`](), [`include/libtmux/options.hpp`](), and [`include/libtmux/capabilities.hpp`]() define server operations, options, and capability checks. The [`include/libtmux/testing/`]() component is separate from the library; [Context managers](https://libtmux.org/ja/tmux/topics/context-managers/) explains its test-server ownership. ### Swift [`Sources/LibTmux/Server.swift`]() defines [`Server`](). [`Sources/LibTmux/Session.swift`](), [`Sources/LibTmux/Window.swift`](), and [`Sources/LibTmux/Pane.swift`]() define the snapshot value types. [`Sources/LibTmux/Snapshot.swift`]() implements the [relationship queries](https://libtmux.org/ja/tmux/topics/traversal/). Extensions on [`Server`]() group related operations: [`Sources/LibTmux/Options.swift`]() implements options and hooks, [`Sources/LibTmux/PaneInteraction.swift`]() handles input and capture, and [`Sources/LibTmux/Mutations.swift`]() handles changes such as killing a pane. ## Naming conventions Method names follow language conventions: Python, Rust, and C++ use `snake_case`; TypeScript, Java, and Swift use `camelCase`; Go and C# use `PascalCase`. Option and hook names remain tmux's dash-separated strings, such as `automatic-rename`, regardless of the method's spelling. --- # Traversal Source: https://libtmux.org/ja/tmux/topics/traversal/ > Moving up and down the server/session/window/pane tree, and the two questions that come up once you have more than one object. Use relationships to move between sessions, windows, and panes. [Server, session, window, pane](https://libtmux.org/ja/tmux/concepts/server-session-window-pane/) explains the hierarchy and snapshot model. This page covers relationship calls, collection membership, and object identity. ## Down the hierarchy List children through the parent object or a captured snapshot. Whether a read issues another tmux command depends on the API, independently of whether the call is async; see [Server, session, window, pane](https://libtmux.org/ja/tmux/concepts/server-session-window-pane/). ### Python Read [`Server.sessions`](), then [`Session.windows`]() and [`Window.panes`]() to traverse from the server down to its panes. ### TypeScript Await [`Server.sessions`](), then read [`Session.windows`]() and [`Window.panes`]() from the loaded graph. ### Go Call [`Server.Sessions`]() with a context to read the sessions. [`Session.Windows`]() and [`Window.Panes`]() traverse their captured relationships. ### Rust Use [`Server.sessions`](), [`Session.windows`](), and [`Window.panes`]() to read each level of the hierarchy. ### Java Use [`Server.sessions`](), [`Session.windows`](), and [`Window.panes`]() to read each level of the hierarchy. ### C# Use [`Server.GetSessionsAsync`](), [`Session.GetWindowsAsync`](), and [`Window.GetPanesAsync`]() to read each level of the hierarchy. ### C++ Use [`Server::sessions`](), [`Session::windows`](), and [`Window::panes`]() to read each level of the hierarchy. ### Swift Read sessions through [`Server.sessions()`](). Once you have a [`Snapshot`](), use its relationship queries, including [`Snapshot.windows(of:)`](), to find the windows and panes for those captured objects. [`session.windows`]() and [`window.panes`]() read the graph loaded by [`await server.sessions()`]() without additional tmux commands. [`server.attached_sessions()`]() lists only sessions with an attached client. ## All panes in a session Use a session-wide pane collection when the task spans several windows, such as finding a command or capturing output from every pane. A window linked to multiple sessions still refers to the same tmux panes. Check whether the API reads live state or traverses a captured graph before reusing its result. ### Python [`libtmux.Session.panes`]() runs a session-scoped `list-panes -s` read. Use it to list panes across the session's windows without manually listing each window. ### TypeScript [`session.Session.panes`]() returns a [`Selection`]() from the session's captured graph. Reading it does not issue another tmux command; refresh the snapshot when you need newer state. ### Rust [`session.Session.panes`]() performs a live listing and returns a [`Result`]() from the async call. Handle a command failure before using the returned panes. ### Go [`tmux.Session.Panes`]() reads captured relations without another tmux command. The relation must have been included in the read that produced the session; an uncaptured relation does not establish that the session has no panes. ### C# [`LibTmux.Session.Panes`]() reads the session's captured relations. It does not issue a tmux command; an incomplete capture may lack the required relation. ### C++ [`libtmux::Session::panes`]() runs a session-scoped `list-panes` command. Inspect the returned result before traversing the panes. ### Swift [`Snapshot.panes(of:)`]() accepts a session or a window. The session overload traverses the captured graph and deduplicates pane IDs without a tmux read. ### Java Java has no direct session-wide pane member. Traverse [`Session.windows()`]() and then [`Window.panes()`]() in the captured relations. Deduplicate pane IDs when combining results from sessions that may share linked windows. ## Up the hierarchy Parent lookups may read captured data or query tmux again. Check the method's read and failure semantics; [Server, session, window, pane](https://libtmux.org/ja/tmux/concepts/server-session-window-pane/) introduces that distinction: ### Python Read [`Pane.window`]() for a pane’s window and [`Window.session`]() for a window’s session. ### TypeScript [`Pane.window`]() and [`Window.session`]() are getters that follow the relationships in the loaded graph. ### Go [`Pane.Window`]() returns `(Window, bool)` and [`Window.Session`]() returns `(Session, bool)`. Check the boolean before using the parent. ### Rust [`Pane.window`]() and [`Window.session`]() asynchronously return `Result, Error>` and `Result, Error>`. Handle both a command failure and an absent parent. ### Java Call [`Pane.window`]() and [`Window.session`]() to find an object’s parent. ### C# Read the [`Pane.Window`]() and [`Window.Session`]() properties. ### C++ Call [`Pane::window`]() and [`Window::session`]() to find an object’s parent. ### Swift Use [`Pane.windowID`]() to look up the window in a [`Snapshot`](). Find a window’s sessions through the snapshot’s relationships; a window does not store a single session field. Check the boolean from a relationship lookup. It reports whether the relation was captured, not whether the object still exists in tmux. Use a live read when current existence matters. Handle both a command failure and an absent parent in the optional result. [`.Window`](), [`.Session`](), [`.ActiveWindow`](), and `.ActivePane` read captured state synchronously. They throw [`IncompleteSnapshotException`]() when the capture lacks the required context. ## One walk, down and back up List a session's windows, then look up a parent and compare its identity with the starting object: ```python session = server.sessions[0] window = session.windows[0] pane = window.panes[0] pane.window.window_id == window.window_id window.session.session_id == session.session_id ``` ```typescript const session = (await server.sessions())[0]; const window = session.windows[0]; const pane = window.panes[0]; pane.window.id === window.id; window.session.id === session.id; ``` ```go sessions, err := server.Sessions(ctx) if err != nil { return err } for _, session := range sessions { windows, captured := session.Windows() if !captured { return fmt.Errorf("session %s has no captured window relations", session.ID()) } for _, window := range windows { back, captured := window.Session() if !captured { return fmt.Errorf("window %s has no captured session", window.ID()) } fmt.Println(window.ID(), "belongs to session:", back.ID() == session.ID()) } } ``` ```rust let sessions = server.sessions().await?; let session = &sessions[0]; let windows = session.windows().await?; let window = &windows[0]; let back = window.session().await?; // Result, Error> back.is_some_and(|s| s.id() == session.id()) ``` ```java Session session = server.sessions().get(0); Window window = session.windows().get(0); Session back = window.session(); back.equals(session); ``` ```csharp Session session = (await server.GetSessionsAsync())[0]; Window window = (await session.GetWindowsAsync())[0]; Session back = window.Session; // property, read from the captured snapshot back.Equals(session); ``` ```cpp auto sessions = server.sessions(); // expected, CommandFailure> const auto& session = sessions->at(0); auto windows = session.windows(); // expected, CommandFailure> const auto& window = windows->at(0); auto back = window.session(); // expected *back == session; // operator== is defined directly on Session/Window/Pane ``` ```swift let sessions = try await server.sessions() let session = sessions[0] let snapshot = try await server.snapshot() let window = snapshot.windows(of: session)[0] let pane = snapshot.panes(of: window)[0] // An array, not one Session: link-window can put a window in more than one. snapshot.sessions(of: window).contains(session) ``` ## The active child The active window and pane identify where untargeted input goes. Use their accessors or inspect the active flags in a captured snapshot: ### Python Read [`Session.active_window`]() and [`Window.active_pane`]() for the selected objects. ### TypeScript Read the [`Session.activeWindow`]() and [`Window.activePane`]() getters. ### Go [`Session.ActiveWindow`]() returns `(Window, bool)` and [`Window.ActivePane`]() returns `(Pane, bool)`. Check the boolean before use. ### Rust [`Session.active_window`]() and [`Window.active_pane`]() asynchronously return `Result, Error>` and `Result, Error>`. Handle errors and the absence of an active object. ### Java [`Session.activeWindow`]() returns `Optional`, and [`Window.activePane`]() returns `Optional`. ### C# Read the [`Session.ActiveWindow`]() and [`Window.ActivePane`]() properties. ### C++ Call [`Session::active_window`]() and [`Window::active_pane`](). ### Swift Filter the snapshot’s windows or panes by their `isActive` flag. For example, inspect the windows returned by [`Snapshot.windows(of:)`]() for the selected session. Filter snapshot children by `isActive`. [Format-token fields](https://libtmux.org/ja/tmux/topics/format-tokens/) describes the underlying `window_active` and `pane_active` fields. ## Is it in that collection? Checking membership generally goes through whatever your language uses for collection membership, since most of these calls already return an ordinary array, slice, or list: [`QueryList`]() supports [`in`](): use `window in session.windows` or `pane in window.panes`. Use standard collection membership operations with the object identity comparison described below. `Selection` is iterable. Iterate and compare IDs, or spread it into an array for standard array operations. Iterate over the slice and compare each object's stable ID, such as [`Pane.ID()`](). Confirm the objects belong to the same server before comparing IDs. Iterate over the vector and compare each object's `id()`. Confirm the objects belong to the same server before comparing IDs. Use the collection's `contains(where:)` to compare IDs. Confirm the objects belong to the same server before comparing IDs. ## Is this the same object? Compare IDs to determine whether two handles refer to the same tmux object on the same server. Check what handle equality includes before using it as an identity test: ### Python Compare [`Window.window_id`]() or [`Pane.pane_id`]() when checking whether two handles name the same object on the same server. ### TypeScript Compare the handles’ `id` properties when checking identity on the same server. ### Go Compare [`Pane.ID`]() values. [`PaneID`]() is a string type and supports `==`; include the server context when comparing objects from different servers. ### Rust Compare [`Pane.id`]() values when checking identity on the same server. ### Java [`Pane.equals`]() compares the server identity and pane ID. ### C# [`Pane.Equals`]() compares a generation counter and the pane ID. ### C++ [`Session`](), [`Window`](), and [`Pane`]() define `operator==` for equality checks. ### Swift Compare the `id` values when checking identity on the same server. The equality behavior described below also includes captured state. **Swift's equality compares captured state.** Compiler-synthesized equality for [`Session`](), [`Window`](), and [`Pane`]() compares every stored property, including dimensions and current command. Two reads can compare unequal even when they describe the same tmux object. Compare `.id` when checking identity on the same server. --- # Ownership and cleanup Source: https://libtmux.org/ja/tmux/topics/context-managers/ > Scope-based cleanup for tmux objects, and when your program must kill them explicitly. A tmux session, window, or pane normally remains until you kill it. Scope-based cleanup can kill it when your code leaves a block, including after an exception. See [Workspaces](https://libtmux.org/ja/tmux/concepts/workspaces/) for a temporary layout example. Python provides context managers for tmux objects. C# provides ownership scopes for servers, sessions, and windows. Other ports require explicit cleanup or offer guards for test servers: | Port | Server | Session | Window | Pane | |------|:------:|:-------:|:------:|:----:| | Python | yes | yes | yes | yes | | C# | yes | yes | yes | - | | Java | closes conn. | - | - | - | | Rust | test-only | - | - | - | | C++ | test-only | - | - | - | | TypeScript | - | - | - | - | | Go | - | - | - | - | | Swift | - | - | - | - | "test-only" means a guard owns an entire disposable test server. Java's [`AutoCloseable`]() server releases its transport but leaves tmux running. A dash means no built-in cleanup scope is listed for that object; use an explicit kill call with the cleanup mechanism appropriate to your language. ## Nested context managers Python's [`Server`](), [`Session`](), [`Window`](), and [`Pane`]() support context managers. Entry returns the existing object; exit kills it, including when the block raises: ```python with Server() as server: with server.new_session() as session: with session.new_window() as window: with window.split() as pane: pane.send_keys('echo "Hello"') # everything above is killed on the way out, in reverse order ``` Nested scopes exit in reverse order: pane, window, session, then server. ## Owned sessions and windows C#'s [`OwnedSessionScope`]() and [`OwnedWindowScope`]() wrap the created object and implement [`IAsyncDisposable`](). The [`Session`]() and [`Window`]() handles themselves are not disposable: ```csharp await using OwnedSessionScope session = await server.CreateOwnedSessionAsync(); await using OwnedWindowScope window = await session.Value.CreateOwnedWindowAsync(); await window.Value.SendTextAsync("echo hello"); // window, then session, killed on the way out ``` For tests that need an owned pane, [`TmuxTestFactory.CreateHierarchyAsync()`]() returns a [`TemporaryHierarchyScope`]() containing a private server, session, window, and pane. Disposing it kills the server. ## Closing a server connection Java's [`Server`]() implements [`AutoCloseable`](). Exiting `try (Server server = Server.open(config))` releases the owned transport while tmux and its sessions remain running. Kill sessions, windows, panes, or the server explicitly when your program owns their cleanup. ```java ServerConfig config = ServerConfig.builder() .endpoint(ServerEndpoint.socketPath(socket)) .build(); try (Server server = Server.open(config)) { Session session = server.newSession("demo"); Window window = session.newWindow("build"); Pane pane = window.split(); pane.sendLine("echo hello from libtmux"); // session, window, and pane all outlive this block: only the // connection this `server` handle held is released on the way out. } ``` ## Explicit asynchronous cleanup Rust's [`Drop`]() is synchronous and cannot await an async tmux kill. Use explicit shutdown when you need to observe cleanup failures: - **`kill(self)` consumes the handle.** Session, window, and pane kill methods take `self` by value, preventing subsequent use of that handle. - **[`libtmux::test::TestServer`]() provides a test guard.** Call [`TestServer.shutdown`]() to handle cleanup errors. Its [`Drop`]() implementation makes a synchronous cleanup attempt. ```rust use libtmux::test::TestServer; let guard = TestServer::new().await?; let server = guard.server(); let session = server.new_session("work").await?; session.new_window("editor").await?; // Await shutdown to handle cleanup errors. guard.shutdown().await?; ``` ## Owning a test server C++'s [`Session`](), [`Window`](), and [`Pane`]() are non-owning values; destroying a handle does not kill its tmux object. [`libtmux::test::ScopedTmuxServer`](), in the separate `testing` CMake component, owns a private test server and its temporary socket directory: ```cpp auto fixture = libtmux::test::ScopedTmuxServer::start( {.socket_namespace = libtmux::test::SocketNamespace::consumer("my-suite")}); // fixture killed, and its tree removed, when this scope ends: // even if the test that follows fails ``` ## Release connections and kill owned sessions Control connections and notification streams implement `[Symbol.asyncDispose]`. `await using` releases those handles; it leaves the watched session and panes running. Use `finally` to kill a session your program owns: ```typescript const session = await server.newSession({ name: "work" }); try { const window = await session.newWindow({ name: "editor" }); await window.panes.at(0)?.sendKeys("echo hi"); } finally { await session.kill(); } ``` ## Defer cleanup with a fresh context A [`Session`](), [`Window`](), or [`Pane`]() handle does not own its tmux object. Dropping the value leaves tmux running. Register cleanup after successful creation and use a separate, bounded context so cancellation of the work cannot prevent cleanup. Return cleanup failures along with any work failure: ```go func temporarySession(ctx context.Context, server tmux.Server) (err error) { session, err := server.NewSession(ctx, tmux.NewSessionRequest{}) if err != nil { return err } defer func() { cleanup, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() err = errors.Join(err, session.Kill(cleanup)) }() _, err = session.SearchWindows(ctx, nil) return err } ``` This function uses `context`, `errors`, `time`, and the [`tmux`]() package. It owns only the session it creates. Do not kill a shared server as session cleanup. [`ControlClient`](), [`PaneObservation`](), and [`NotificationStream`]() implement [`io.Closer`](). Close those resources separately from killing tmux objects. For tests, [`tmuxtest.NewServer`]() registers isolated server cleanup with the Go test runner. ## Kill objects your program owns Session, window, and pane values are non-owning. Call [`try await server.kill(session)`]() or the corresponding window or pane overload when cleanup is required. Perform cleanup on both success and failure paths; Swift's synchronous `defer` cannot await a tmux command. ## Testing cleanup Use explicit cleanup for objects whose handles have no disposal hook. For an entire disposable test server, prefer your port's test fixture or server guard; see [Testing with libtmux](https://libtmux.org/ja/tmux/guides/testing-with-libtmux/). --- # Pane interaction Source: https://libtmux.org/ja/tmux/topics/pane-interaction/ > Input defaults, screen capture, and waiting for a command to finish. Send input to a pane and capture its screen to interact with a running program. [Attach and send keys](https://libtmux.org/ja/tmux/examples/attach-and-send-keys/) provides examples. [Sending keys](https://libtmux.org/ja/tmux/guides/sending-keys/) and [Capturing output](https://libtmux.org/ja/tmux/guides/capturing-output/) are task guides; this page covers input defaults, capture ranges, and completion handling. ## Typing into a pane Two questions come up every time you send something to a pane: should tmux press Enter afterward, and should tmux interpret what you sent as key names (`Enter`, `C-c`) rather than literal characters? Choose both explicitly when a command depends on them. ### Python **Type without Enter:** [`pane.send_keys(text, enter=False)`]() **Type + Enter (default):** [`pane.send_keys(text)`]() **How "literal" is chosen:** `literal=True` flag on the same method ### TypeScript **Type without Enter:** `pane.sendKeys(text, { enter: false })` **Type + Enter (default):** [`pane.sendKeys(text)`]() **How "literal" is chosen:** `{ literal: true }` option ### Go **Type without Enter:** `pane.SendKeys(ctx, SendKeysRequest{Command: &text, SkipEnter: true})` **Type + Enter (default):** `pane.SendKeys(ctx, SendKeysRequest{Command: &text})` **How "literal" is chosen:** `Literal: true` field ### Rust **Type without Enter:** [`pane.send_keys(keys)`](): **always literal**, key names typed as text **Type + Enter (default):** [`pane.send_line(text)`]() **How "literal" is chosen:** [`send_keys`]() sends literal text; [`send_key_names`]() interprets tmux key names. ### Java **Type without Enter:** [`pane.send(keys)`]() **Type + Enter (default):** [`pane.sendLine(command)`]() **How "literal" is chosen:** Separate text and key-sending methods. ### C# **Type without Enter:** [`SendKeysAsync(new SendKeysRequest(text, enter: false))`]() **Type + Enter (default):** [`SendTextAsync(text)`]() (defaults `enter: true`) **How "literal" is chosen:** [`SendKeysRequest.Literal`]() field; [`SendTextAsync`]() hardcodes it ### C++ **Type without Enter:** [`pane->send_text(text)`]() **Type + Enter:** [`pane->send_text(text)`]() then [`pane->send_key("Enter")`]() separately: no combined convenience exists **How "literal" is chosen:** `send_text` is always literal; `send_key` is always a key name ### Swift **Type without Enter:** [`server.sendKeys([text], to: pane)`]() **Type + Enter (default):** `server.run(text, in: pane)` (sugar for [`sendKeys([text, "Enter"], to: pane)`]()) **How "literal" is chosen:** `literally: true` option on [`sendKeys`]() ### Examples [`send_keys`]() sends literal text. Use [`send_key_names`]() for tmux key names. Passing `"Enter"` to [`send_keys`]() types those characters. [`send_line`]() appends a carriage return and delivers it with the text in one tmux command. `sendLine` appends a carriage return and delivers it with the text in one tmux command. Text and Enter can be separate tmux commands. If the second operation fails, the text may already be in the pane. Check the current state before retrying; repeating the whole request can duplicate input. Send a command line and press Enter: ```python pane.send_keys("echo hi", enter=False) # type without pressing Enter pane.send_keys("echo hi") # default: presses Enter afterward ``` ```typescript await pane.sendKeys("echo hi", { enter: false }); await pane.sendKeys("echo hi"); ``` ```go text := "printf 'hello\\n'" if err := pane.SendKeys(ctx, tmux.SendKeysRequest{ Command: &text, Literal: true, }); err != nil { return fmt.Errorf("send command: %w", err) } ``` ```rust pane.send_keys("echo hi").await?; // Always literal text. pane.send_line("echo hi").await?; // text and Enter in one send-keys -l call ``` ```java pane.send("echo hi"); // no Enter pane.sendLine("echo hi"); // text and \r in one send-keys -l call ``` ```csharp await pane.SendKeysAsync(new SendKeysRequest("echo hi", enter: false)); await pane.SendTextAsync("echo hi"); // defaults enter: true ``` ```cpp pane->send_text("echo hi"); pane->send_key("Enter"); // separate command: no combined convenience exists ``` ```swift try await server.sendKeys(["echo hi"], to: pane) // no Enter try await server.run("echo hi", in: pane) // sugar for sendKeys([text, "Enter"]) ``` ## Reading a pane back Capture reads the pane's visible screen by default. Request scrollback when you need earlier output. A capture is a snapshot of terminal contents, including any input echoed by the application. [`pane.Capture`]() returns `([]string, error)`. Pass a [`context.Context`]() with a deadline and check the error before using the result. Set the start boundary to [`tmux.CaptureBoundary`]() to include scrollback. `End: tmux.CaptureBoundary` includes the bottom of the visible pane. ```python pane.capture_pane() ``` ```typescript await pane.capture(); ``` ```go lines, err := pane.Capture(ctx, tmux.CapturePaneRequest{}) if err != nil { return fmt.Errorf("capture pane: %w", err) } for _, line := range lines { fmt.Println(line) } ``` ```rust pane.capture().await?; ``` ```java pane.capture(); ``` ```csharp await pane.CaptureAsync(); ``` ```cpp pane->capture(); ``` ```swift try await server.capture(pane) ``` ## Waiting for something to finish A send call completes when input reaches tmux. It does not wait for the shell command to finish. Wait for expected output or a completion signal. Poll capture output for a marker or use the [`wait_for`]() signal channel when you control the command. The test-support module provides `libtmux.test.retry_until(condition, ...)` for arbitrary conditions. [`server.waitForOutput(...)`]() waits for a pattern in pane output and returns an [`OutputWait`](). Give the wait a timeout. Use [`server.WaitFor`]() with a [`WaitForRequest`]() when the command can signal tmux's `wait-for` channel. For streaming output, open [`pane.OpenObservation(ctx)`]() before sending input so the observation includes the command's first bytes. Both paths take a context; cancellation bounds how long the caller waits. For a new command whose exit status matters, use [`session.Run`]() and inspect its result. Capturing screen text alone cannot establish the command's exit status. Use [`TmuxWaitChannel`]() when the command can signal a named tmux `wait-for` channel. Use a cancellation token to bound the wait. [Capture pane output](https://libtmux.org/ja/tmux/examples/capture-pane-output/) shows capture and waiting examples. [Waiting and retrying](https://libtmux.org/ja/tmux/topics/waiting-and-retry/) explains completion conditions and timeouts.
tmux manual and source The tmux manual defines [key-name and literal input](https://github.com/tmux/tmux/blob/94796f6b1182507efac8a272fc309a79e22e58a5/tmux.1#L4457) and [screen and history capture](https://github.com/tmux/tmux/blob/94796f6b1182507efac8a272fc309a79e22e58a5/tmux.1#L2798). A send operation delivers input; it does not establish the program's exit status.
--- # Options and hooks Source: https://libtmux.org/ja/tmux/topics/options-and-hooks/ > Read and update tmux options, and register commands for tmux events. Use options to change tmux behavior, such as `automatic-rename` or the status-line format. Use hooks to run commands on events such as `session-renamed` or `after-split-window`. Choose the scope supported by the option or hook. ## Reading and writing options Read, set, or unset an option at its supported scope. Distinguish a local override from the effective value inherited from a parent scope. ### Python **Read all (this scope):** [`pane.show_options()`]() **Read effective/inherited:** [`pane.show_option(name, global_=True)`]() reaches the global fallback explicitly; no separate "resolved" call **Set:** [`pane.set_option(name, value)`]() **Unset:** [`pane.unset_option(name)`]() ### TypeScript **Read all (this scope):** [`pane.showOptions()`]() **Read effective/inherited:** [`pane.showResolvedOptions()`]() **Set:** [`pane.setOption(name, value)`]() **Unset:** [`pane.unsetOption(name)`]() ### Go [`pane.Options(ctx)`]() returns a fresh typed snapshot, including inherited values. Its accessors return an [`OptionValue`](); use [`OptionValue.Get`]() to distinguish a present value from an absent one. Check the read error before inspecting the snapshot. Use the handle that owns the option's scope. For example, `automatic-rename` belongs to a window: use [`Window.SetOption`]() or [`Window.UnsetOption`](), then read [`Window.Options`]() again for an updated snapshot. For a single raw value, [`Pane.RawOption`]() returns the string, its presence, and an error. Do not treat an error as an absent option. ### Rust **Read all (this scope):** [`pane.options()`]() (typed [`BTreeMap`]()), [`pane.option_names()`]() **Read effective/inherited:** [`pane.typed_option(name)`]() decodes one value by its declared kind **Set:** [`pane.set_option(name, value)`](), [`pane.append_option(name, value)`]() **Unset:** [`pane.unset_option(name)`]() ### Java **Read all (this scope):** [`pane.options().all()`]() **Read effective/inherited:** [`pane.options().get(name)`]() reads `show-options -A -v`: inherited, not just local **Set:** [`pane.options().set(name, value)`]() **Unset:** [`pane.options().unset(name)`]() ### C# **Read all (this scope):** [`pane.Options.GetAllAsync()`]() **Read effective/inherited:** [`pane.Options.GetAsync(new GetOptionRequest(name, includeInherited: true))`](): an explicit opt-in flag, mapped straight to tmux's own `-A` **Set:** [`pane.Options.SetAsync(new SetOptionRequest(name, value))`]() **Unset:** [`pane.Options.UnsetAsync(...)`]() ### C++ **Read all (this scope):** [`pane->options()`]() **Read effective/inherited:** Not listed. **Set:** [`pane->set_option(name, value)`]() **Unset:** [`pane->unset_option(name)`]() ### Swift **Read all (this scope):** [`server.options(.pane(pane))`]() **Read effective/inherited:** [`server.option(name, scope: .pane(pane))`]() reads presence from the listing, then the value with `-v` **Set:** [`server.setOption(name, to: value, scope: .pane(pane))`]() **Unset:** [`server.unsetOption(name, scope: .pane(pane))`]() ### Examples After a successful write completes, read the option to obtain its updated value. The following examples set, read, and unset an option: ```python pane.set_option("automatic-rename", "off") pane.show_options() pane.unset_option("automatic-rename") ``` ```typescript await pane.setOption("automatic-rename", "off"); await pane.showOptions(); await pane.unsetOption("automatic-rename"); ``` ```go if err := window.SetOption(ctx, "automatic-rename", "off", tmux.SetOptionOptions{}); err != nil { return fmt.Errorf("set automatic rename: %w", err) } options, err := window.Options(ctx) if err != nil { return fmt.Errorf("read window options: %w", err) } value, present := options.AutomaticRename().Get() fmt.Println("automatic rename:", value, "present:", present) if err := window.UnsetOption(ctx, "automatic-rename", tmux.UnsetOptionOptions{}); err != nil { return fmt.Errorf("unset automatic rename: %w", err) } ``` ```rust pane.set_option("automatic-rename", "off").await?; pane.options().await?; pane.unset_option("automatic-rename").await?; ``` ```java pane.options().set("automatic-rename", "off"); pane.options().all(); pane.options().unset("automatic-rename"); ``` ```csharp await pane.Options.SetAsync(new SetOptionRequest("automatic-rename", "off")); await pane.Options.GetAllAsync(); await pane.Options.UnsetAsync("automatic-rename"); ``` ```cpp pane->set_option("automatic-rename", "off"); pane->options(); pane->unset_option("automatic-rename"); ``` ```swift try await server.setOption("automatic-rename", to: "off", scope: .pane(pane)) try await server.options(.pane(pane)) try await server.unsetOption("automatic-rename", scope: .pane(pane)) ``` ## Hooks ### Python **Set:** [`pane.set_hook(name, command)`]() **Unset:** [`pane.unset_hook(name)`]() **List:** [`pane.show_hook(name)`](), [`pane.show_hooks()`]() (all) **Run now, without the event:** Not listed. ### TypeScript **Set:** [`pane.setHook(name, command, { append })`]() **Unset:** [`pane.unsetHook(name)`]() **List:** [`pane.showHooks()`]() (all; no singular `showHook`) **Run now, without the event:** Not listed. ### Go Use [`session.SetHook`]() to register a command and [`session.Hooks`]() to read the session's typed hook values. [`session.UnsetHook`]() removes a registration. [`Session.SetHooks`]() writes indexed entries when an event needs more than one command. Use [`server.GlobalSessionScope()`]() for hooks shared by sessions. Keep hook registration at a scope supported by the tmux event. ### Rust **Set:** [`pane.set_hook(name, command)`]() **Unset:** [`pane.unset_hook(name)`]() **List:** [`pane.hook(name)`](): one name only; **no listing at pane/window scope**, by design (see below) **Run now, without the event:** Not listed. ### Java **Set:** [`pane.hooks().set(event, command)`](), `.append(event, command)` **Unset:** [`pane.hooks().unset(event)`]() **List:** [`pane.hooks().all()`]() **Run now, without the event:** [`pane.hooks().run(event)`](): tmux's `set-hook -R` ### C# **Set:** [`pane.Hooks.SetAsync(new SetHookRequest(event, command))`]() **Unset:** [`pane.Hooks.UnsetAsync(...)`]() **List:** [`pane.Hooks.GetAllAsync()`]() **Run now, without the event:** [`pane.Hooks.RunAsync(...)`]() ### C++ **Set:** [`session.set_hook(name, command)`](): no [`Window`]()/[`Pane`]() overload exists at all **Unset:** Not listed. **List:** [`session.hooks()`](), [`server.global_hooks()`]() **Run now, without the event:** Not listed. ### Swift **Set:** [`server.setHook(name, to: command, at: index, in: scope)`]() **Unset:** [`server.unsetHook(name, in: scope)`]() **List:** [`server.hooks(scope)`]() **Run now, without the event:** [`server.runHook(name, in: scope)`]() ### Examples tmux stores hook commands in indexed arrays, such as `after-new-window[0]`. Include the array index in the hook name. Include the array index in the hook name or pass `{ append: true }` to append. [`Session.SetHooks`]() accepts indexed hook entries. Pass a context and check errors from both mutations and reads; a successful write does not refresh earlier snapshots. The `at:` parameter selects the array index. Use `.append()` to add a command without choosing the next array index. Set and list a session hook. The next section explains window and pane scope limitations: ```python session.set_hook("session-renamed", "display-message 'renamed'") session.show_hooks() ``` ```typescript await session.setHook("session-renamed", "display-message 'renamed'"); await session.showHooks(); ``` ```go if err := session.SetHook(ctx, "session-renamed", "display-message 'renamed'"); err != nil { return fmt.Errorf("set session hook: %w", err) } value, present, err := session.RawHook(ctx, "session-renamed") if err != nil { return fmt.Errorf("read session hooks: %w", err) } fmt.Println("session-renamed:", value, "present:", present) ``` ```rust session.set_hook("session-renamed", "display-message 'renamed'").await?; session.hooks().await?; ``` ```java session.hooks().set("session-renamed", "display-message 'renamed'"); session.hooks().all(); ``` ```csharp await session.Hooks.SetAsync(new SetHookRequest("session-renamed", "display-message 'renamed'")); await session.Hooks.GetAllAsync(); ``` ```cpp session.set_hook("session-renamed", "display-message 'renamed'"); session.hooks(); // No session unset helper is listed above. ``` ```swift try await server.setHook("session-renamed", to: "display-message 'renamed'", in: .session(session.id.rawValue)) try await server.hooks(.session(session.id.rawValue)) ``` ## Supported hook scopes tmux stores hooks globally or per session. Accepted `set-hook -w` or `-p` flags do not imply a separate window or pane hook table, and `show-hooks` does not provide a corresponding listing. Check the event's supported scope if a hook is accepted but never fires. Check `hooks().all()` when diagnosing a hook that does not fire. tmux can accept a hook at an unsupported scope without an effective registration. [`Pane::set_hook`]() and [`Window::set_hook`]() return [`Error::OptionScopeMismatch`]() for an unsupported scope. [`HookScope`]() restricts registration to `.global` and `.session`. Use [`Session::set_hook`]() or [`Server::global_hooks()`](). Window and pane handles do not provide hook methods. Options have window and pane tables of their own. The hook-scope limitation does not apply to ordinary options. ## tmux version compatibility The compatibility notes list these tmux requirements: | Feature | Minimum tmux | |---------|-------------| | All options/hooks features | 3.2+ | | Window/pane hook *scope flags* (`-w`, `-p`) accepted | 3.2+; see the supported-scope caveat above | | `client-active`, `window-resized` hooks | 3.3+ | | `pane-title-changed` hook | 3.5+ | Check the library's supported tmux versions before using a version-specific option or hook. Use the command references for [show-options](https://libtmux.org/en/tmux/latest/manual/show-options/), [set-option](https://libtmux.org/en/tmux/latest/manual/set-option/), [show-hooks](https://libtmux.org/en/tmux/latest/manual/show-hooks/), and [set-hook](https://libtmux.org/en/tmux/latest/manual/set-hook/) to check the flags and scope rules for your tmux version.
tmux manual and source The tmux manual defines [option scopes and inherited reads](https://github.com/tmux/tmux/blob/94796f6b1182507efac8a272fc309a79e22e58a5/tmux.1#L4725). Unsetting a local value restores inheritance. Hook programs run in tmux when their event occurs; see the [hook implementation](https://github.com/tmux/tmux/blob/94796f6b1182507efac8a272fc309a79e22e58a5/cmd-set-option.c).
--- # Format-token fields Source: https://libtmux.org/ja/tmux/topics/format-tokens/ > The typed fields every object exposes, mirroring tmux's own format tokens, and why a field is sometimes absent. Object fields expose values from tmux's [FORMATS](https://man.openbsd.org/tmux.1#FORMATS), such as `pane_id`, `window_zoomed_flag`, and [`session_name`](). The available fields depend on the accessor, object scope, tmux version, and data requested by the read. A token needs the right **scope** and **tmux version**. For example, a pane token needs a pane context, and a token added after your tmux release may be absent. Check the accessor's result before using a field that can be missing, as described below. ## Handling an absent field ### Python An excluded field has the value [`None`](). ### TypeScript An excluded field has the value [`undefined`](). ### Go Accessors return a value and a boolean when a field can be unavailable. For example, [`Pane.DeadSignal`]() returns `(string, bool)`; check the boolean before using the string. ### Rust Some handle accessors return `Option` for unavailable values. Check the reference for the selected accessor and its return type. ### Java Accessors use `Optional` for fields that may be unavailable. For example, [`Pane.floating`]() returns an empty `Optional` when that field is not populated. ### C# A nullable value represents an absent value. A field that was not captured can instead raise [`IncompleteSnapshotException`](). ### C++ Handles expose fixed, non-optional fields. See below for accessing tokens outside that fixed set. ### Swift Snapshots expose fixed, non-optional fields. See below for accessing tokens outside that fixed set. These examples read optional fields, including `pane_dead_signal` on tmux 3.3 or newer: ```python pane.pane_dead_signal # None below tmux 3.3, or on a pane that isn't dead ``` ```typescript pane.deadSignal; // undefined under the same conditions ``` ```go signal, ok := pane.DeadSignal() // ok is false when the token isn't populated ``` ```java pane.floating(); // Optional: a different field, same idiom: empty // rather than a sentinel when the token isn't populated ``` [`crates/libtmux/src/formats.rs`]() marks this token as optional. Consult the reference for the accessor name. Missing values differ from incomplete captures. [`Pane.Title`]() is nullable because tmux may report no title. [`Pane.Height`](), `.Width`, and `.Index` throw [`IncompleteSnapshotException`]() when the read that produced the handle did not request those fields. A handle resolved by ID alone may therefore lack enough data to answer: ```csharp string? title = pane.Title; // nullable: the ordinary absence case int height = pane.Height; // throws IncompleteSnapshotException instead, // if this Pane wasn't captured with a full listing ``` ## Field availability Field accessors retain the scope and version requirements of tmux tokens. ### TypeScript [`packages/libtmux/src/_generated/format_fields.ts`]() records each token's scope and first tmux version. For example, `pane_zoomed_flag` has pane scope and requires tmux 3.7. [`packages/libtmux/src/_generated/field_aliases.ts`]() supplies the camelCase alias `pane.zoomedFlag`. ### Rust [`crates/libtmux/src/formats.rs`]() records each token's tmux name, required context, first supported release, decoder, and handling of empty values. `pane_dead_signal` requires pane context and tmux 3.3. Its text value preserves arbitrary non-NUL bytes. ### Go [`tmux/internal/generate/formats/`]() generates [`tmux/format_generated.go`](). Some accessors also parse the returned text: [`Pane.DeadTime`]() returns `(time.Time, bool)`, so the caller does not need to parse the timestamp. Version gates describe tmux behavior: `pane_dead_signal` and `pane_dead_time` arrived in tmux 3.3, and a cluster of pane-geometry and floating-pane tokens (`pane_floating_flag`, `pane_pb_progress`, `pane_x`, `pane_y`, `pane_z`, `pane_zoomed_flag`, `bracket_paste_flag`, `synchronized_output_flag`, among others) arrived together in 3.7. ## Fixed fields and additional tokens `Session`, `Window`, and `Pane` expose a fixed set of captured fields: ### Swift Captured pane fields include `index`, `width`, `height`, `isActive`, [`currentCommand`](), [`currentPath`](), and the four edge flags. ### C++ The `kFields` arrays declare which fields to capture. Pane fields include `id`, `command`, `active`, `index`, `title`, `pid`, `tty`, `path`, `width`, `height`, `dead`, `in_mode`, edge flags, and `piping`. Accessors return [`std::string_view`](), [`bool`](), or `long long`. ```swift pane.isActive // Bool, not Bool?: always populated, never gated pane.currentCommand // String, likewise ``` ```cpp pane->active(); // bool, not std::optional pane->command(); // std::string_view, likewise ``` Expand a token outside the fixed fields with [`pane->expand("#{pane_dead_signal}")`](). Use [`FormatSubscription`]() on a control connection to observe other tokens. It delivers [`SubscriptionChange`]() when tmux re-evaluates the token. [Architecture](https://libtmux.org/ja/tmux/topics/architecture/) describes the fixed-field model. ## Fields promoted from the active child Python exposes fields promoted from an active child. For example, [`session.pane_id`]() identifies the active pane of the session's active window: ```python >>> session = server.new_session() >>> session.pane_id == session.active_window.active_pane.pane_id True ``` tmux's format engine includes active-child fields when listing a parent. A `list-sessions -F` row can include `window_id` and `pane_id` for the active window and pane. Check the port reference for typed access to those fields, or use the explicit relationships described in [Traversal](https://libtmux.org/ja/tmux/topics/traversal/). A pane context can include parent window and session fields. A session cannot identify one attached client when several clients may be attached, so client tokens such as `client_name` require a client context. --- # Waiting and retrying Source: https://libtmux.org/ja/tmux/topics/waiting-and-retry/ > Polling a condition instead of guessing a sleep, and tmux's own wait-for signal channel as the alternative to polling. After sending input or starting a process, wait for the state your next step requires. [Pane interaction](https://libtmux.org/ja/tmux/topics/pane-interaction/#waiting-for-something-to-finish) covers waiting for screen text. This page covers arbitrary conditions and tmux's named `wait-for` signal channels. ## Polling a condition Polling checks a condition repeatedly until it succeeds or a deadline expires. Set a deadline and choose an interval that limits unnecessary tmux commands. ### Python **Helper:** `libtmux.test.retry_until(fn, seconds=, interval=)` **Where it lives:** [`src/libtmux/test/`]() in the main package; raises [`WaitTimeout`](). ### TypeScript **Helper:** [`connectedServer.waitFor(matches, options)`]() **Where it lives:** The public control-connection API. Tests a predicate over [`ServerSnapshot`](); see [Control mode vs one-shot](https://libtmux.org/en/ts/latest/concepts/transports/). ### Go **Helper:** [`tmuxtest.WaitFor(ctx, interval, condition)`]() **Where it lives:** [`tmuxtest`](), a separate test-support package from [`tmux`]() ### Rust **Helper:** [`libtmux::test::retry_until(within, condition)`]() **Where it lives:** [`crates/libtmux/src/test.rs`](), enabled with the `test-support` Cargo feature. ### Java **Helper:** Not listed. **Where it lives:** not found in the shipped library; a package-private `Await.until(...)` exists only inside the `integration-tests` module, which downstream code cannot depend on ### C# **Helper:** [`LibTmux.Testing.TmuxWait.UntilAsync(probe, timeout, interval)`]() **Where it lives:** the separate [`LibTmux.Testing`]() package ### C++ **Helper:** Not listed. **Where it lives:** no generic condition-poll helper found in the public library; a [`wait_until`]() exists only in the private `testing` component, for waiting on a spawned child process, not on tmux state ### Swift **Helper:** Not listed. **Where it lives:** a [`waitUntil`]() helper exists only inside the test target's own support code, not shipped ### Examples [`waitFor`]() subscribes before reading a server snapshot so it does not miss a change between those steps. [`tmuxtest.WaitFor`]() probes immediately, then at the requested positive interval. It returns a probe error or context error unchanged. Each probe must read fresh state and honor its context. [`Session.Windows()`]() only reads a stored snapshot; use [`Session.SearchWindows`]() to query tmux on every probe. ```python def is_window_up(pane, name): return any(w.window_name == name for w in pane.window.session.windows) libtmux.test.retry_until(lambda: is_window_up(pane, "build"), seconds=5.0) ``` ```typescript await using live = await server.connect(); await live.waitFor((snapshot) => snapshot.windows.exists({ name: "build" })); ``` ```go waitCtx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() err := tmuxtest.WaitFor(waitCtx, 50*time.Millisecond, func(ctx context.Context) (bool, error) { windows, err := session.SearchWindows(ctx, nil) if err != nil { return false, err } for _, w := range windows { if name, ok := w.Name(); ok && name == "build" { return true, nil } } return false, nil }) if err != nil { return fmt.Errorf("wait for build window: %w", err) } ``` ```rust libtmux::test::retry_until(std::time::Duration::from_secs(5), async || { session.windows().await.map(|ws| ws.iter().any(|w| w.name() == "build")).unwrap_or(false) }) .await?; ``` ```csharp await LibTmux.Testing.TmuxWait.UntilAsync( async ct => (await session.GetWindowsAsync(ct)).Any(w => w.Name == "build"), TimeSpan.FromSeconds(5), TimeSpan.FromMilliseconds(50)); ``` Use a loop with a deadline and interval when a specific wait API does not fit. [Pane interaction](https://libtmux.org/ja/tmux/topics/pane-interaction/#waiting-for-something-to-finish) covers output waits. ## tmux's own wait-for channel Use `tmux wait-for -S ` to signal and `tmux wait-for ` to block until signalled. This avoids repeated screen captures when the command can announce its own completion: ### Python Signal a channel with [`Server.wait_for`]() and `set_flag=True`. Call the same method without that flag to wait for the signal. ### TypeScript The public API does not expose the channel handshake used internally by the test server during startup. ### Go Call [`Server.WaitFor`]() with a [`WaitForRequest`](). Set its mode to [`WaitForModeSignal`]() to signal the channel; the zero-value mode waits. ### Rust [`Server.signal_channel`]() signals a channel. [`Server.wait_for_channel`]() waits with a timeout and returns a [`ChannelWait`]() indicating whether it was signalled or timed out. ### Java Obtain a channel through [`Server.channel`](). Its [`signal()`]() method wakes a waiter; `await(timeout)` returns a [`WakeReason`]() so the caller can distinguish a signal from a timeout. ### C# [`Server.OpenWaitChannel`]() returns a [`TmuxWaitChannel`](). Keep it in an `await using` scope and call [`WaitAsync`]() with a budget. Select the signal mode to signal the channel. ### C++ [`Server.signal`]() signals a channel. [`Server.wait_for`]() waits for the channel with a timeout. ### Swift [`Server.signal`]() signals a channel, and [`Server.wait(for:)`]() waits for it. Both calls are async and throwing. ```python server.new_session(session_name="work") server.wait_for("built", set_flag=True) # signal server.wait_for("built") # block until signalled ``` ```go if err := server.WaitFor(ctx, tmux.WaitForRequest{ Channel: "built", Mode: tmux.WaitForModeSignal, }); err != nil { return err } if err := server.WaitFor(ctx, tmux.WaitForRequest{Channel: "built"}); err != nil { return err } ``` ```rust server.signal_channel("built").await?; let outcome = server.wait_for_channel("built", std::time::Duration::from_secs(5)).await?; ``` ```java Channel built = server.channel("built"); built.signal(); WakeReason reason = built.await(Duration.ofSeconds(5)); ``` ```csharp await using TmuxWaitChannel channel = server.OpenWaitChannel("built"); bool signalled = await channel.WaitAsync(TimeSpan.FromSeconds(5)); ``` ```cpp server.signal("built"); server.wait_for("built", std::chrono::seconds{5}); ``` ```swift try await server.signal("built") try await server.wait(for: "built") ``` tmux remembers a signal sent before a waiter starts. The next wait on that channel returns immediately, so completion is not lost when the command finishes first. A raw `wait-for` client can exit zero when the server dies, as well as when the channel is signalled. Verify server liveness when a lost server must be treated as a failed task. Check [`WakeReason`]() to distinguish server loss from a signal. [`drain()`]() can clear a remembered signal before reusing a channel. [`wait(for:)`]() checks the server PID before and after waiting to distinguish server loss from a signal. Use a channel name specific to the task. A remembered signal can otherwise satisfy an unrelated later wait. The [wait-for reference](https://libtmux.org/en/tmux/latest/manual/wait-for/) describes completion signals and locks for each supported tmux version.
tmux manual and source The tmux manual describes [completion channels](https://github.com/tmux/tmux/blob/94796f6b1182507efac8a272fc309a79e22e58a5/tmux.1#L8715). The [channel implementation](https://github.com/tmux/tmux/blob/94796f6b1182507efac8a272fc309a79e22e58a5/cmd-wait-for.c#L388) retains an early signal until a waiter consumes it. Use a fresh channel name for each operation.
--- # Environment Source: https://libtmux.org/ja/tmux/topics/environment/ > Locate tmux objects from process variables and manage the environment inherited by new panes. tmux exposes two environment APIs. Process variables such as [`TMUX`]() and [`TMUX_PANE`]() let code inside a pane identify its server and pane. The server also stores variables through `set-environment` and `show-environment` for new processes to inherit. Like the tables in [Options and hooks](https://libtmux.org/ja/tmux/topics/options-and-hooks/), this persistent store has explicit scopes. ## Locating yourself from inside a pane Inside a pane, [`TMUX`]() contains `,,`, and [`TMUX_PANE`]() contains the pane ID, such as `%1`. Use these variables to locate the current tmux objects. Select the object your operation needs: ### Python **Server:** [`Server.from_env()`]() **Session:** [`Session.from_env()`]() **Window:** [`Window.from_env()`]() **Pane:** [`Pane.from_env()`]() ### TypeScript **Server:** Not listed. **Session:** [`Session.fromEnv()`]() **Window:** Not listed. **Pane:** Not listed. ### Go **Server:** [`NewServerFromEnv(env)`]() **Session:** [`SessionFromEnv(ctx, env)`]() **Window:** [`WindowFromEnv(ctx, env)`]() **Pane:** [`PaneFromEnv(ctx, env)`]() ### Rust **Server:** [`Server::from_env()`]() **Session:** [`Session::from_env(&server)`]() **Window:** [`Window::from_env(&server)`]() **Pane:** [`Pane::from_env(&server)`]() ### Java **Server:** Not listed. **Session:** Not listed. **Window:** Not listed. **Pane:** See the Java context example below. ### C# **Server:** [`Server.FromEnvironment(env)`]() **Session:** [`Session.FromEnvironmentAsync()`]() **Window:** [`Window.FromEnvironmentAsync()`]() **Pane:** [`Pane.FromEnvironmentAsync()`]() ### C++ **Server:** [`Server::from_env()`]() **Session:** Not listed. **Window:** Not listed. **Pane:** Not listed. ### Swift **Server:** [`TmuxContext.current()`]() **Session:** [`TmuxContext.current()`]() (same call: see below) **Window:** Not listed. **Pane:** Not listed. ### Examples [`Session`](), [`Window`](), and [`Pane::from_env`]() require an existing `&Server`. Use [`Session.fromEnv()`]() to resolve the session of the current process. ```python Pane.from_env().pane_id Window.from_env().window_id Session.from_env().session_id Server.from_env().sessions ``` ```typescript const session = await Session.fromEnv(); ``` ```go pane, err := tmux.PaneFromEnv(ctx, nil) if err != nil { return err } fmt.Println("current pane:", pane.ID()) ``` ```rust let server = libtmux::Server::from_env()?; let session = libtmux::Session::from_env(&server).await?; // Option let window = libtmux::Window::from_env(&server).await?; let pane = libtmux::Pane::from_env(&server).await?; ``` ```csharp Server server = Server.FromEnvironment(null); Session session = await Session.FromEnvironmentAsync(); Window window = await Window.FromEnvironmentAsync(); Pane pane = await Pane.FromEnvironmentAsync(); ``` Environment lookup requires valid tmux targeting variables. If your process can run outside tmux, handle that case before using the result. [`NotInsideTmux`]() reports a missing or invalid tmux environment. Check the returned [`error`](). A [`FromEnvError`]() identifies a missing or malformed variable. Passing `nil` reads the process environment; pass an explicit map to resolve a captured environment. Handle [`TmuxObjectNotFoundException`]() when the environment cannot identify an object. ### Resolve the server socket [`Server::from_env()`]() selects the socket. Use the resulting server to resolve sessions or panes. ### Resolve context identifiers [`TmuxEnvironment`]() parses `TMUX` and `TMUX_PANE` into identifiers: socket path, server PID, [`SessionId`](), and `Optional`. It returns context data rather than a live pane handle: ```java TmuxEnvironment here = TmuxEnvironment.current().orElseThrow(); try (Server server = Server.open(here.config())) { Session mine = server.sessions().stream() .filter(session -> session.id().equals(here.session())) .findFirst() .orElseThrow(); } ``` ### Swift context fields Swift's [`TmuxContext.current()`]() parses the socket path, server PID, and session ID from `TMUX`. It does not read `TMUX_PANE`, so it cannot identify the current pane: ```swift let context = TmuxContext.current()! // socket path, server pid, session id let server = try context.server() ``` Read `TMUX_PANE` separately if you need the pane ID; [`TmuxContext`]() does not provide it. ## tmux's own environment variable store Like [Options and hooks](https://libtmux.org/ja/tmux/topics/options-and-hooks/), tmux's persistent environment store has global and per-session scopes. It is read with `show-environment` and updated with `set-environment`. Newly spawned processes inherit it; existing processes retain their own environments. ### Python **Set:** [`server.set_environment(name, value)`](), [`session.set_environment(...)`]() **Read all:** [`server.show_environment()`](), [`session.show_environment()`]() **Unset:** [`server.unset_environment(name)`](). See below for `.remove_environment()`. ### TypeScript **Set:** [`server.setEnvironment(name, value)`](), [`session.setEnvironment(...)`]() **Read all:** [`server.showEnvironment()`](), [`session.showEnvironment()`]() **Unset:** [`server.unsetEnvironment(name)`](), [`session.unsetEnvironment(name)`]() ### Go **Set:** [`server.SetEnvironment(ctx, name, value, opts)`]() (global, `-g`) **Read all:** [`server.ShowEnvironment(ctx)`]() **Unset:** [`server.UnsetEnvironment(ctx, name)`]() ### Rust **Set:** [`server.set_environment(...)`](), [`session.set_environment(...)`]() **Read all:** [`server.environment_all()`](), [`session.environment_all()`]() **Unset:** [`server.unset_environment(name)`](), [`session.unset_environment(name)`]() ### Java **Set:** Not documented here; see the process-environment note below. **Read all:** Not documented here; see the process-environment note below. **Unset:** Not documented here; see the process-environment note below. ### C# **Set:** [`server.Environment.SetAsync(name, value)`](), [`session.Environment.SetAsync(...)`]() **Read all:** [`server.Environment.GetAllAsync()`]() **Unset:** [`server.Environment.UnsetAsync(name)`](), [`.RemoveAsync(name)`]() ### C++ **Set:** Not documented here; see the process-environment note below. **Read all:** Not documented here; see the process-environment note below. **Unset:** Not documented here; see the process-environment note below. ### Swift **Set:** [`server.setEnvironment(name, to: value, in: scope)`]() **Read all:** [`server.environment(scope)`]() **Unset:** [`server.unsetEnvironment(name, in: scope)`](), [`.removeEnvironment(name, in:)`]() ### Examples ```python server.set_environment("EDITOR", "vim") # global session.set_environment("EDITOR", "hx") # this session only session.show_environment() ``` ```typescript await server.setEnvironment("EDITOR", "vim"); await session.setEnvironment("EDITOR", "hx"); await session.showEnvironment(); ``` ```go if err := session.SetEnvironment(ctx, "EDITOR", "hx", tmux.SetEnvironmentOptions{}); err != nil { return err } values, err := session.ShowEnvironment(ctx) if err != nil { return err } fmt.Println(values) ``` ```rust server.set_environment("EDITOR", "vim").await?; session.set_environment("EDITOR", "hx").await?; session.environment_all().await?; ``` ```csharp await server.Environment.SetAsync("EDITOR", "vim"); await session.Environment.SetAsync("EDITOR", "hx"); await session.Environment.GetAllAsync(); ``` ```swift try await server.setEnvironment("EDITOR", to: "vim", in: .global) try await server.setEnvironment("EDITOR", to: "hx", in: .session(session.id.rawValue)) try await server.environment(.session(session.id.rawValue)) ``` `set-environment` writes a value. Its `-u` flag removes the entry, while `-r` marks the variable for exclusion from new processes, including values tmux inherited at startup. The listing retains an excluded variable as `-NAME`. Use `unset_environment` for `-u`, or `remove_environment` for `-r`. Use [`unsetEnvironment`]() for `-u`, or [`removeEnvironment`]() for `-r`. Use `.UnsetAsync` for `-u`, or [`.RemoveAsync`]() for `-r`. ### Process environment [`SessionSpec.Builder.environment(Map)`](), [`WindowSpec.Builder.environment(Map)`](), and [`SplitSpec.Builder.environment(Map)`]() pass initial variables to `new-session -e`, `new-window -e`, and `split-window -e`. These creation options do not change the stored environment of an existing session. ### Process environment Creation-time environment values affect the new process. They do not update an existing process's environment. To change tmux's persistent table, use `set-environment` on the same socket as the server handle. --- # Socket and servers Source: https://libtmux.org/ja/tmux/topics/socket-and-servers/ > Select a server socket, check liveness, and detect a replacement daemon. A tmux server is selected by its Unix-domain socket. Use different sockets for independent servers, such as a development session and an isolated test server. Choose the default socket, a named socket (`-L`), or an explicit path (`-S`). ## Naming a server ### Python [`Server()`]() selects the default socket. Pass `socket_name="work"` to select a named socket, or `socket_path="/tmp/tmux-1000/work"` for an explicit path. ### TypeScript `new Server()` selects the default socket. Set `socketName` to select a named socket, or `socketPath` to use an explicit path. ### Go [`tmux.NewServer(tmux.ServerOptions{})`]() selects the default socket. Set [`ServerOptions.SocketName`]() for a named socket or [`ServerOptions.SocketPath`]() for an explicit path. ### Rust [`Server::new()`]() selects the default socket. Use [`Server::builder().socket_name("work").build()?`]() for a named socket or [`Server::builder().socket_path(path).build()?`]() for an explicit path. ### Java [`ServerEndpoint.defaultSocket()`]() selects the default socket. [`ServerEndpoint.namedSocket("work")`]() selects a named socket, and [`ServerEndpoint.socketPath(path)`]() selects an explicit path. ### C# `new ServerConnectionOptions()` selects the default socket. Supply `socketName` for a named socket or `socketPath` for an explicit path. ### C++ [`Server::at_default()`]() selects the default socket. Use [`Server::at_socket_name("work")`]() for a named socket or [`Server::at_socket_path(path)`]() for an explicit path. ### Swift Select a named socket with [`Server(socketName: "work")`]() or an explicit path with [`Server(socketPath: path)`](). The initializer requires a socket selection; see the default-socket example below. ```python default_server = libtmux.Server() named = libtmux.Server(socket_name="work") ``` ```typescript const named = new Server({ socketName: "work" }); ``` ```go named, err := tmux.NewServer(tmux.ServerOptions{SocketName: "work"}) if err != nil { return err } ``` ```rust let named = libtmux::Server::builder().socket_name("work").build()?; ``` ```java ServerConfig config = ServerConfig.builder() .endpoint(ServerEndpoint.namedSocket("work")) .build(); Server named = Server.open(config); ``` ```csharp using LibTmux; Server named = await Server.ConnectAsync(new ServerConnectionOptions(socketName: "work")); ``` ```cpp auto named = libtmux::Server::at_socket_name("work"); ``` ```swift let named = try Server(socketName: "work") ``` Choose either a socket name or a socket path. tmux uses `TMUX_TMPDIR` to resolve the directory for default and named sockets. Supplying both selectors raises [`TypeError`](). [`ServerOptions.SocketPath`]() takes precedence over [`ServerOptions.SocketName`]() when both are set. Swift requires an explicit `socketPath` or [`socketName`]() argument. To reach tmux's default socket, use [`Server(socketName: "default")`](). [`Server(socket_name_factory=...)`]() accepts a callable that generates socket names. Use a unique name for each isolated test server. [`ServerConnectionOptions(socketNameFactory: ...)`]() accepts a callable that generates socket names. Use a unique name for each isolated test server. ## Is the server actually there? A server handle does not prove that the target server is running. Use a liveness check when your program needs to distinguish a live server from an unavailable socket: ### Python [`Server.is_alive`]() returns a boolean indicating whether the server responds. ### TypeScript [`Server.isAlive`]() returns a promise of a boolean. [`Server.raiseIfDead`]() throws when the server is unavailable and retains the reason reported by tmux. ### Go [`Server.IsAlive`]() returns `(bool, error)`. The error reports a check that could not be completed; a false result alone means the server is not alive. ### Rust [`Server.is_alive`]() returns a boolean. Use [`Server.check_alive`]() when you also need to distinguish a failed check from a server that is not alive. ### Java [`Server.isAlive`]() returns a boolean. ### C# [`Server.IsAliveAsync`]() returns `Task`. ### C++ [`Server.is_alive`]() takes a timeout and returns a boolean. ### Swift [`Server.isRunning`]() is an async throwing check that returns [`Bool`](). ```python if server.is_alive(): server.sessions ``` ```typescript if (await server.isAlive()) { await server.snapshot(); } ``` ```go alive, err := server.IsAlive(ctx) if err != nil { return err } fmt.Println("server running:", alive) ``` ```rust if server.is_alive().await { server.sessions().await?; } ``` ```java if (server.isAlive()) { server.sessions(); } ``` ```csharp if (await server.IsAliveAsync()) { await server.GetSessionsAsync(); } ``` ```cpp if (server.is_alive(std::chrono::seconds{2})) { server.sessions(); } ``` ```swift if try await server.isRunning() { try await server.sessions() } ``` [`isAlive()`]() returns a boolean. Use [`raiseIfDead()`]() when you need failure details. [`is_alive()`]() returns a boolean. Use [`check_alive()`]() when you need failure details. ## Killing a server, and telling two apart Killing a server ends all its sessions. Use this only for a server your program owns; for narrower cleanup, kill the session or pane you created. Call [`server.kill_server()`](). [`Server.__eq__`]() compares [`socket_name`]() and `socket_path` when deciding whether two handles select the same endpoint. Call [`await server.kill()`](). [`TmuxServerRestartedError`]() reports an operation whose handle encountered a replacement daemon on the same socket. Call [`server.Kill(ctx)`]() and check its error. [`server.Equal(other)`]() compares the captured socket bindings, resolving relative paths and environment-based socket names. Equal endpoints do not prove equal daemon lifetimes. [`ErrDaemonReplaced`]() reports a replacement daemon on the selected socket. Call [`server.kill().await?`]() and handle a cleanup failure before returning. Call [`server.killServer()`]() to stop tmux. [`Server.close()`]() releases the local connection and leaves tmux running; see [Ownership and cleanup](https://libtmux.org/ja/tmux/topics/context-managers/). Call [`await server.KillAsync()`]() and handle a cleanup failure before returning. Call [`server.kill()`]() and inspect its result for a failure. Call [`try await server.killServer()`]() and handle a cleanup failure before returning. A restarted server can reuse a socket path while having different state. Do not treat a matching path as proof that a cached object still exists. --- # Errors and exceptions Source: https://libtmux.org/ja/tmux/topics/errors-and-exceptions/ > Handle command failures, inspect delivery status, and decide when a retry is safe. A command can fail because tmux rejects it, or because the transport stops before returning a reply. Inspect the reported error and delivery state before retrying a mutation: tmux may already have received it. For lookup failures caused by zero or multiple matches, see [Filtering and queries](https://libtmux.org/ja/tmux/concepts/queries/#the-cardinality-contract-side-by-side). ## A failed command, as a value ### Python Command failures raise [`LibTmuxException`](). It can carry a `subcommand`; its string representation includes the subcommand and stderr. ### TypeScript [`TmuxCommandError`]() reports a command tmux rejected. [`TmuxTransportError`]() reports a failure to obtain a reply. Catch these error types separately when the distinction affects recovery. ### Go Operations return an error alongside their result. Inspect typed errors and sentinel values with [`errors.As`]() and [`errors.Is`](); preserve them when wrapping with `%w`. ### Rust Fallible operations return `Result`. Match the non-exhaustive [`Error`]() enum and include a fallback for variants added later. ### Java Command failures raise unchecked exceptions derived from [`LibTmuxException`](), which extends [`RuntimeException`](). ### C# Failures raise subclasses of [`LibTmuxException`](), including [`TmuxCommandException`](), [`TmuxTransportException`](), and [`TmuxObjectNotFoundException`](). ### C++ Fallible operations return `expected`. [`CommandFailure`]() records the failure kind, delivery status, exit code, and diagnostic. ### Swift Fallible operations use typed throws with [`TmuxError`](). Catch that error to inspect the failure. Use [`errors.Is`]() for sentinel errors such as [`tmux.ErrNoServer`]() and [`tmux.ErrNotFound`](). Use [`errors.As`]() to inspect a `*tmux.CommandError` and its completed command result. Wrap errors with `%w` to preserve that information. Distinguish a command rejected by tmux from a transport failure: ```typescript import { TmuxCommandError, TmuxTransportError } from "libtmux"; try { await pane.capture(); } catch (error) { if (error instanceof TmuxCommandError) { error.args; // the argument vector error.exitCode; error.stderr; // tmux's own lines } else if (error instanceof TmuxTransportError) { error.kind; // "cancelled" | "pipe" | "protocol" | "spawn" | "timeout" error.delivery; // see below: this is the question a retry depends on } } ``` ## Is it safe to retry? Retry a mutation automatically only when you know it was not dispatched, or when repeating it is safe for your operation. A timeout, cancellation, or dropped connection can occur after tmux has acted. These APIs expose delivery information: ### TypeScript [`TmuxTransportError.delivery`]() distinguishes `not_started`, [`written`](), `replied`, and `indeterminate`. Only `not_started` establishes that retrying cannot repeat an already-dispatched command. ### C# [`LibTmuxException.Dispatch`]() reports a [`TmuxDispatchState`](): [`NotDispatched`](), [`Dispatched`](), or [`Unknown`](). Treat the default, [`Unknown`](), as potentially dispatched. ### Java `TmuxTimeoutException.outcome()` exposes a [`DispatchOutcome`](): [`NOT_DISPATCHED`](), `COMPLETE`, or `UNKNOWN`. ### C++ [`DeliveryStatus`]() distinguishes [`not_started`](), [`written`](), [`replied`](), and [`indeterminate`](). ### Rust With the `control-mode` feature, [`ControlModeErrorKind.DispatchTimedOut`]() means the command was not dispatched. A plain timeout can occur after tmux received the command, so do not infer that retrying is safe. ### Python Inspect the exit status after a subprocess completes. If the call was interrupted, verify the resulting tmux state before repeating a mutation. ### Go Inspect the exit status after a subprocess completes. If the call was interrupted, verify the resulting tmux state before repeating a mutation. A failed pooled control connection is retired, but its failure does not prove that a mutation was never dispatched. ### Swift A control-session failure does not establish that tmux never received the command. Retry only when non-delivery is known or repeating the operation is safe. ```typescript import { TmuxTransportError } from "libtmux"; try { await session.newWindow({ name: "build" }); } catch (error) { if (error instanceof TmuxTransportError && error.delivery === "not_started") { // safe to retry: nothing reached tmux } } ``` ```csharp try { await session.CreateWindowAsync(new NewWindowRequest(name: "build")); } catch (LibTmuxException error) when (error.Dispatch == TmuxDispatchState.NotDispatched) { // safe to retry } ``` ```cpp auto result = session.new_window({.name = "build"}); if (!result.has_value() && result.error().delivery == libtmux::DeliveryStatus::not_started) { // safe to retry } ``` Treat unknown delivery as potentially executed. A [`not_started`]() result means the request did not reach tmux. A [`written`]() result means the transport accepted the request but no terminal reply arrived. [`NotDispatched`]() identifies a request that did not reach tmux. [`NOT_DISPATCHED`]() identifies a request that did not reach tmux. [`DispatchTimedOut`]() identifies a request that did not reach tmux. For subprocess calls that complete normally, inspect the exit status. If a call is interrupted or times out without a delivery state, check tmux's resulting state before repeating a mutation. --- # コンセプト Source: https://libtmux.org/ja/tmux/concepts/ > tmux のサーバー、セッション、ウィンドウ、ペインの仕組み。 libtmux は tmux のサーバー、セッション、ウィンドウ、ペインを操作するライブラリです。まずオブジェクト階層を確認し、必要に応じて通信方式、クエリ、ワークスペースの説明を参照してください。 各ページでは、共通する概念と実装ごとの違いを説明します。 - **[サーバー、セッション、ウィンドウ、ペイン](https://libtmux.org/ja/tmux/concepts/server-session-window-pane/)**: tmux 自身が定義するオブジェクト階層と、その外側にある唯一の存在 (アタッチ中のクライアント)。 - **[コントロールモードとワンショット](https://libtmux.org/ja/tmux/concepts/transports/)**: プログラム内の呼び出しが実際に tmux サーバーへ届くまでの経路。コマンドごとの サブプロセス、持続的なコントロールモードのクライアント、あるいは複数の コマンドを一回の呼び出しにまとめる方式。 - **[フィルタリングとクエリ](https://libtmux.org/ja/tmux/concepts/queries/)**: 「サーバー上のすべてのセッション」から「目的のペイン一つ」へ絞り込む方法と、 一致が零件または複数件だったときに何が起きるか。 - **[ワークスペース](https://libtmux.org/ja/tmux/concepts/workspaces/)**: 複数ペインのレイアウトを手作業ではなくコードから組み立てる方法と、 多くの実装がオブジェクト API と併せて提供する tmuxp 型の宣言的ビルダー。 各言語の API リファレンスを開くには、ヘッダーの実装へのリンクを使ってください。