tmux client library for Erlang
Control tmux, connect to and create servers, navigate sessions, windows, and panes, send keys, capture pane contents.
Guides
Installation, queries and owned operations.
Topics
Object identity, command execution and compatibility.
Examples
Complete programs from the tested source checkout.
API reference
Native records, modules, functions and types.
Install
{deps, [ {libtmux_erlang, {git_subdir, "https://github.com/libtmux/libtmux-erlang.git", {ref, "master"}, "apps/libtmux_erlang"}}]}.At a glance
Server → Session → Window → Pane. Each level of the tmux hierarchy has its own entry in the Erlang API, with the tmux object it wraps.
| Type | Wraps |
|---|---|
tmux_server:t/0 | A tmux server |
#tmux_model:session{} | A session, ID $n |
#tmux_model:window{} | A physical tmux window; session membership uses window links, ID @n |
#tmux_model:pane{} | A pane in a window, ID %n |
What it does
-module(readme_demo).-moduledoc "Create a workspace, query its graph and preserve captured values.".-export([run/1]).-import_record(tmux_model, [pane]).
-spec run(binary()) -> map().run(Executable) -> {ok, Server, Ownership} = tmux_server:start_owned(#{executable => Executable}), try {ok, Session} = tmux_session:create(Server, #{name => <<"workspace">>, window_name => <<"editor">>, width => 120, height => 30, launch => #{argv => [<<"/bin/cat">>]}}), {ok, Worker} = tmux_window:create(Session, #{name => <<"workers">>, launch => #{argv => [<<"/bin/cat">>]}}), {ok, Right} = tmux_pane:split(Worker, #{direction => horizontal, launch => #{argv => [<<"/bin/cat">>]}}), {ok, _} = tmux_window:select_layout(Worker, even_horizontal), {ok, _} = tmux_pane:resize(Right, #{width => 40}), {ok, Snapshot} = tmux_server:snapshot(Server), Windows = tmux_select:windows(Snapshot), Workers = #{name => <<"workers">>, panes => #{count => 2, every => #{current_command => <<"cat">>}}}, {ok, Captured} = tmux_select:one(Workers, Windows), {ok, Children} = tmux_snapshot:related(panes, Captured, Snapshot), Ids = [Id || #pane{id = Id} <:- Children], 2 = length(Ids), Criteria = #{width => #{gte => 20}, window => #{is => #{name => <<"workers">>}}}, Query = tmux_query:where(Criteria, tmux_query:panes(Server)), {ok, Fetched} = tmux_query:fetch(Query), 2 = tmux_select:size(Fetched), {ok, _} = tmux_window:rename(Worker, <<"finished">>), <<"workers">> = tmux_model:get(name, Captured), {ok, Fresh} = tmux_query:fetch(tmux_query:where( #{name => <<"finished">>}, tmux_query:windows(Server))), {ok, Renamed} = tmux_select:one(Fresh), <<"finished">> = tmux_model:get(name, Renamed), 1 = length(tmux_snapshot:sessions(Snapshot)), 2 = length(tmux_snapshot:windows(Snapshot)), 3 = length(tmux_snapshot:panes(Snapshot)), #{sessions => 1, windows => 2, panes => 3, matched_panes => 2, captured_name => <<"workers">>, immutable => true} after ok = tmux_server:stop_owned(Ownership) end.-module(readme_demo).-moduledoc "Create a workspace, query its graph and preserve captured values.".-export([run/1]).-import_record(tmux_model, [pane]).
-spec run(binary()) -> map().run(Executable) -> {ok, Server, Ownership} = tmux_server:start_owned(#{executable => Executable}), try {ok, Session} = tmux_session:create(Server, #{name => <<"workspace">>, window_name => <<"editor">>, width => 120, height => 30, launch => #{argv => [<<"/bin/cat">>]}}), {ok, Worker} = tmux_window:create(Session, #{name => <<"workers">>, launch => #{argv => [<<"/bin/cat">>]}}), {ok, Right} = tmux_pane:split(Worker, #{direction => horizontal, launch => #{argv => [<<"/bin/cat">>]}}), {ok, _} = tmux_window:select_layout(Worker, even_horizontal), {ok, _} = tmux_pane:resize(Right, #{width => 40}), {ok, Snapshot} = tmux_server:snapshot(Server), Windows = tmux_select:windows(Snapshot), Workers = #{name => <<"workers">>, panes => #{count => 2, every => #{current_command => <<"cat">>}}}, {ok, Captured} = tmux_select:one(Workers, Windows), {ok, Children} = tmux_snapshot:related(panes, Captured, Snapshot), Ids = [Id || #pane{id = Id} <:- Children], 2 = length(Ids), Criteria = #{width => #{gte => 20}, window => #{is => #{name => <<"workers">>}}}, Query = tmux_query:where(Criteria, tmux_query:panes(Server)), {ok, Fetched} = tmux_query:fetch(Query), 2 = tmux_select:size(Fetched), {ok, _} = tmux_window:rename(Worker, <<"finished">>), <<"workers">> = tmux_model:get(name, Captured), {ok, Fresh} = tmux_query:fetch(tmux_query:where( #{name => <<"finished">>}, tmux_query:windows(Server))), {ok, Renamed} = tmux_select:one(Fresh), <<"finished">> = tmux_model:get(name, Renamed), 1 = length(tmux_snapshot:sessions(Snapshot)), 2 = length(tmux_snapshot:windows(Snapshot)), 3 = length(tmux_snapshot:panes(Snapshot)), #{sessions => 1, windows => 2, panes => 3, matched_panes => 2, captured_name => <<"workers">>, immutable => true} after ok = tmux_server:stop_owned(Ownership) end.-module(readme_demo).-moduledoc "Create a workspace, query its graph and preserve captured values.".-export([run/1]).-import_record(tmux_model, [pane]).
-spec run(binary()) -> map().run(Executable) -> {ok, Server, Ownership} = tmux_server:start_owned(#{executable => Executable}), try {ok, Session} = tmux_session:create(Server, #{name => <<"workspace">>, window_name => <<"editor">>, width => 120, height => 30, launch => #{argv => [<<"/bin/cat">>]}}), try {ok, Worker} = tmux_window:create(Session, #{name => <<"workers">>, launch => #{argv => [<<"/bin/cat">>]}}), {ok, Right} = tmux_pane:split(Worker, #{direction => horizontal, launch => #{argv => [<<"/bin/cat">>]}}), {ok, _} = tmux_window:select_layout(Worker, even_horizontal), {ok, _} = tmux_pane:resize(Right, #{width => 40}), {ok, Snapshot} = tmux_server:snapshot(Server), Windows = tmux_select:windows(Snapshot), Workers = #{name => <<"workers">>, panes => #{count => 2, every => #{current_command => <<"cat">>}}}, {ok, Captured} = tmux_select:one(Workers, Windows), {ok, Children} = tmux_snapshot:related(panes, Captured, Snapshot), Ids = [Id || #pane{id = Id} <:- Children], 2 = length(Ids), Criteria = #{width => #{gte => 20}, window => #{is => #{name => <<"workers">>}}}, Query = tmux_query:where(Criteria, tmux_query:panes(Server)), {ok, Fetched} = tmux_query:fetch(Query), 2 = tmux_select:size(Fetched), {ok, _} = tmux_window:rename(Worker, <<"finished">>), <<"workers">> = tmux_model:get(name, Captured), {ok, Fresh} = tmux_query:fetch(tmux_query:where( #{name => <<"finished">>}, tmux_query:windows(Server))), {ok, Renamed} = tmux_select:one(Fresh), <<"finished">> = tmux_model:get(name, Renamed), 1 = length(tmux_snapshot:sessions(Snapshot)), 2 = length(tmux_snapshot:windows(Snapshot)), 3 = length(tmux_snapshot:panes(Snapshot)), #{sessions => 1, windows => 2, panes => 3, matched_panes => 2, captured_name => <<"workers">>, immutable => true} after {ok, _} = tmux_session:kill(Session) end after ok = tmux_server:stop_owned(Ownership) end.-module(readme_demo).-moduledoc "Create a workspace, query its graph and preserve captured values.".-export([run/1]).-import_record(tmux_model, [pane]).
-spec run(binary()) -> map().run(Executable) -> {ok, Server, Ownership} = tmux_server:start_owned(#{executable => Executable}), try {ok, Session} = tmux_session:create(Server, #{name => <<"workspace">>, window_name => <<"editor">>, width => 120, height => 30, launch => #{argv => [<<"/bin/cat">>]}}), try {ok, Worker} = tmux_window:create(Session, #{name => <<"workers">>, launch => #{argv => [<<"/bin/cat">>]}}), {ok, Right} = tmux_pane:split(Worker, #{direction => horizontal, launch => #{argv => [<<"/bin/cat">>]}}), {ok, _} = tmux_window:select_layout(Worker, even_horizontal), {ok, _} = tmux_pane:resize(Right, #{width => 40}), {ok, Snapshot} = tmux_server:snapshot(Server), Windows = tmux_select:windows(Snapshot), Workers = #{name => <<"workers">>, panes => #{count => 2, every => #{current_command => <<"cat">>}}}, {ok, Captured} = tmux_select:one(Workers, Windows), {ok, Children} = tmux_snapshot:related(panes, Captured, Snapshot), Ids = [Id || #pane{id = Id} <:- Children], 2 = length(Ids), Criteria = #{width => #{gte => 20}, window => #{is => #{name => <<"workers">>}}}, Query = tmux_query:where(Criteria, tmux_query:panes(Server)), {ok, Fetched} = tmux_query:fetch(Query), 2 = tmux_select:size(Fetched), {ok, _} = tmux_window:rename(Worker, <<"finished">>), <<"workers">> = tmux_model:get(name, Captured), {ok, Fresh} = tmux_query:fetch(tmux_query:where( #{name => <<"finished">>}, tmux_query:windows(Server))), {ok, Renamed} = tmux_select:one(Fresh), <<"finished">> = tmux_model:get(name, Renamed), 1 = length(tmux_snapshot:sessions(Snapshot)), 2 = length(tmux_snapshot:windows(Snapshot)), 3 = length(tmux_snapshot:panes(Snapshot)), #{sessions => 1, windows => 2, panes => 3, matched_panes => 2, captured_name => <<"workers">>, immutable => true} after {ok, _} = tmux_session:kill(Session) end after ok = tmux_server:stop_owned(Ownership) end.Run this example
Linux, tmux 3.3a–3.8, Erlang/OTP 29.1.2, Rebar3 3.28.0, Git, make and a C11 compiler. Save the displayed files in an empty project directory.
Save the program above as src/readme_demo.erl in a new directory, along with the files below.
Every version retires its owned server in try/after. Cleanup also removes the created session explicitly. The program stops its private tmux server and removes any temporary files it created when it finishes. Unmatched tagged results raise with a stack trace. The program propagates failures through try/after cleanup.
rebar.config
{minimum_otp_vsn, "29.1.2"}.{erl_opts, [debug_info, warnings_as_errors]}.{deps, [ {libtmux_erlang, {git_subdir, "https://github.com/libtmux/libtmux-erlang.git", {ref, "9a1ca749f7bf6f082c910c99f961bb586781ab0b"}, "apps/libtmux_erlang"}}]}.src/tmux_example.app.src
{application, tmux_example, [ {vsn, "0.1.0"}, {applications, [kernel, stdlib, libtmux_erlang]}, {modules, []}]}.Setup and run
rebar3 compileenv \ -u TMUX \ -u TMUX_PANE \ erl \ -noshell \ -pa _build/default/lib/tmux_example/ebin \ -pa _build/default/lib/libtmux_erlang/apps/libtmux_erlang/ebin \ -eval ' {ok, _} = application:ensure_all_started(libtmux_erlang), Tmux = unicode:characters_to_binary(os:find_executable("tmux")), Stats = readme_demo:run(Tmux), io:format("~B session, ~B windows, ~B panes; ~B matches~n", [ maps:get(sessions, Stats), maps:get(windows, Stats), maps:get(panes, Stats), maps:get(matched_panes, Stats)]), halt().'Expected output
1 session, 2 windows, 3 panes; 2 matchesTyped API
- Typed objects instead of parsed format strings:
#tmux_model:pane{} - Addressing by tmux ID:
tmux_model:pane_id/0 - Filtering and queries over tmux objects:
tmux_filter - Typed errors instead of exit codes and stderr:
tmux_operation:failure/0
What you get over the tmux CLI
- Connect to or start a tmux server:
tmux_server:open/1 - Traversal from a server down to its panes:
tmux_snapshot:panes/1 - Send keys to a pane:
tmux_pane:send_keys/2 - Capture pane contents:
tmux_pane:capture/2 - Arguments passed as a list, with no shell quoting:
tmux_command - A typed error when the running tmux lacks a feature:
tmux_capability - Async operations:
tmux_operation
Testing
Run tests against an isolated tmux server: tmux_server:start_owned/1.