Core Library0.xHome

Choose documentation 1

latest

Current version

latest
English

Early release libtmux is released, and its APIs may still change between releases. This site's structure and URLs may change too.

Edit this page on GitHub

tmux client library for Lua

Control tmux, connect to and create servers, navigate sessions, windows, and panes, send keys, watch for changes via control mode, capture pane contents.

Install

luarocks --local install libtmux

At a glance

Server → Session → Window → Pane. Each level of the tmux hierarchy has its own entry in the Lua API, with the tmux object it wraps.

TypeWraps
libtmux.Server A tmux server
libtmux.Session A session, ID $n
libtmux.Window A window in a session, ID @n
libtmux.Pane A pane in a window, ID %n

What it does

Start with Lua

Example details
local adapter = require("libtmux.runtime.luv")
local uv = require("luv")
local function must(value, err)
if err ~= nil then error(err, 0) end
return value
end
local binary
for directory in (os.getenv("PATH") or ""):gmatch("[^:]+") do
local candidate = uv.fs_realpath(directory .. "/tmux")
if candidate and uv.fs_access(candidate, "X") then
binary = candidate
break
end
end
assert(binary, "tmux must be installed on PATH")
local directory = assert(uv.fs_mkdtemp("/tmp/ltx-lua-home-XXXXXX"))
local socket = directory .. "/tmux.sock"
local function tmux(arguments)
local args = { "-S", socket }
for _, argument in ipairs(arguments) do
args[#args + 1] = argument
end
local timer = assert(uv.new_timer())
local process, status
process = assert(uv.spawn(binary, {
args = args,
stdio = { nil, 1, 2 },
}, function(code)
status = code
process:close()
timer:stop()
timer:close()
end))
timer:start(10000, 0, function()
process:kill("sigkill")
end)
uv.run()
assert(status == 0, "tmux command failed or timed out")
end
-- This Lua API connects to an existing daemon. Start our own first.
tmux({
"-f", "/dev/null", "new-session", "-d",
"-s", "work", "-n", "main", "/bin/sh",
})
must(adapter.run(function(runtime)
local server = must(runtime:connect({
binary = binary,
socket_path = socket,
}):await())
local created = must(server:new_session({
name = "quickstart",
window_name = "main",
argv = { "/bin/sh" },
}):await())
local logs = must(created.session:new_window({
name = "logs",
argv = { "/bin/cat" },
}):await())
must(logs.pane:split({
direction = "right",
percent = 40,
argv = { "/bin/cat" },
}):await())
local marker = "libtmux-ready"
local quoted = "'" .. binary:gsub("'", "'\\''") .. "'"
local command = ("printf '\\nlibtmux ready\\n'; "
.. "%s wait-for -S %s"):format(quoted, marker)
must(created.pane:send_text(command):await())
must(created.pane:send_keys({ "Enter" }):await())
must(server:command(
{ "wait-for", marker },
{ timeout = 10000 }
):await())
local capture = must(created.pane:capture():await())
assert(must(capture:text()):find(
"\nlibtmux ready\n", 1, true
), "pane output was not captured")
print("libtmux ready")
must(server:close():await())
return true
end))
local adapter = require("libtmux.runtime.luv")
local uv = require("luv")
local function must(value, err)
if err ~= nil then error(err, 0) end
return value
end
local binary
for directory in (os.getenv("PATH") or ""):gmatch("[^:]+") do
local candidate = uv.fs_realpath(directory .. "/tmux")
if candidate and uv.fs_access(candidate, "X") then
binary = candidate
break
end
end
assert(binary, "tmux must be installed on PATH")
local directory = assert(uv.fs_mkdtemp("/tmp/ltx-lua-home-XXXXXX"))
local socket = directory .. "/tmux.sock"
local function tmux(arguments)
local args = { "-S", socket }
for _, argument in ipairs(arguments) do
args[#args + 1] = argument
end
local timer = assert(uv.new_timer())
local process, status
process = assert(uv.spawn(binary, {
args = args,
stdio = { nil, 1, 2 },
}, function(code)
status = code
process:close()
timer:stop()
timer:close()
end))
timer:start(10000, 0, function()
process:kill("sigkill")
end)
uv.run()
assert(status == 0, "tmux command failed or timed out")
end
local ok, failure = xpcall(function()
-- This Lua API connects to an existing daemon. Start our own first.
tmux({
"-f", "/dev/null", "new-session", "-d",
"-s", "work", "-n", "main", "/bin/sh",
})
must(adapter.run(function(runtime)
local server = must(runtime:connect({
binary = binary,
socket_path = socket,
}):await())
local created = must(server:new_session({
name = "quickstart",
window_name = "main",
argv = { "/bin/sh" },
}):await())
local logs = must(created.session:new_window({
name = "logs",
argv = { "/bin/cat" },
}):await())
must(logs.pane:split({
direction = "right",
percent = 40,
argv = { "/bin/cat" },
}):await())
local marker = "libtmux-ready"
local quoted = "'" .. binary:gsub("'", "'\\''") .. "'"
local command = ("printf '\\nlibtmux ready\\n'; "
.. "%s wait-for -S %s"):format(quoted, marker)
must(created.pane:send_text(command):await())
must(created.pane:send_keys({ "Enter" }):await())
must(server:command(
{ "wait-for", marker },
{ timeout = 10000 }
):await())
local capture = must(created.pane:capture():await())
assert(must(capture:text()):find(
"\nlibtmux ready\n", 1, true
), "pane output was not captured")
print("libtmux ready")
must(server:close():await())
return true
end))
end, debug.traceback)
if not ok then error(failure, 0) end
local adapter = require("libtmux.runtime.luv")
local uv = require("luv")
local function must(value, err)
if err ~= nil then error(err, 0) end
return value
end
local binary
for directory in (os.getenv("PATH") or ""):gmatch("[^:]+") do
local candidate = uv.fs_realpath(directory .. "/tmux")
if candidate and uv.fs_access(candidate, "X") then
binary = candidate
break
end
end
assert(binary, "tmux must be installed on PATH")
local directory = assert(uv.fs_mkdtemp("/tmp/ltx-lua-home-XXXXXX"))
local socket = directory .. "/tmux.sock"
local function tmux(arguments)
local args = { "-S", socket }
for _, argument in ipairs(arguments) do
args[#args + 1] = argument
end
local timer = assert(uv.new_timer())
local process, status
process = assert(uv.spawn(binary, {
args = args,
stdio = { nil, 1, 2 },
}, function(code)
status = code
process:close()
timer:stop()
timer:close()
end))
timer:start(10000, 0, function()
process:kill("sigkill")
end)
uv.run()
assert(status == 0, "tmux command failed or timed out")
end
local ok, failure = pcall(function()
-- This Lua API connects to an existing daemon. Start our own first.
tmux({
"-f", "/dev/null", "new-session", "-d",
"-s", "work", "-n", "main", "/bin/sh",
})
must(adapter.run(function(runtime)
local server = must(runtime:connect({
binary = binary,
socket_path = socket,
}):await())
local created = must(server:new_session({
name = "quickstart",
window_name = "main",
argv = { "/bin/sh" },
}):await())
local logs = must(created.session:new_window({
name = "logs",
argv = { "/bin/cat" },
}):await())
must(logs.pane:split({
direction = "right",
percent = 40,
argv = { "/bin/cat" },
}):await())
local marker = "libtmux-ready"
local quoted = "'" .. binary:gsub("'", "'\\''") .. "'"
local command = ("printf '\\nlibtmux ready\\n'; "
.. "%s wait-for -S %s"):format(quoted, marker)
must(created.pane:send_text(command):await())
must(created.pane:send_keys({ "Enter" }):await())
must(server:command(
{ "wait-for", marker },
{ timeout = 10000 }
):await())
local capture = must(created.pane:capture():await())
assert(must(capture:text()):find(
"\nlibtmux ready\n", 1, true
), "pane output was not captured")
print("libtmux ready")
must(server:close():await())
return true
end))
end)
local cleaned, cleanup_error = pcall(function()
if uv.fs_stat(socket) then tmux({ "kill-server" }) end
local removed, _, code = uv.fs_unlink(socket)
assert(removed or code == "ENOENT", "could not remove owned socket")
assert(uv.fs_rmdir(directory))
end)
if not cleaned then io.stderr:write(tostring(cleanup_error), "\n") end
if not ok then error(failure, 0) end
assert(cleaned, "cleanup failed; retained " .. directory)
local adapter = require("libtmux.runtime.luv")
local uv = require("luv")
local function must(value, err)
if err ~= nil then error(err, 0) end
return value
end
local binary
for directory in (os.getenv("PATH") or ""):gmatch("[^:]+") do
local candidate = uv.fs_realpath(directory .. "/tmux")
if candidate and uv.fs_access(candidate, "X") then
binary = candidate
break
end
end
assert(binary, "tmux must be installed on PATH")
local directory = assert(uv.fs_mkdtemp("/tmp/ltx-lua-home-XXXXXX"))
local socket = directory .. "/tmux.sock"
local function tmux(arguments)
local args = { "-S", socket }
for _, argument in ipairs(arguments) do
args[#args + 1] = argument
end
local timer = assert(uv.new_timer())
local process, status
process = assert(uv.spawn(binary, {
args = args,
stdio = { nil, 1, 2 },
}, function(code)
status = code
process:close()
timer:stop()
timer:close()
end))
timer:start(10000, 0, function()
process:kill("sigkill")
end)
uv.run()
assert(status == 0, "tmux command failed or timed out")
end
local ok, failure = xpcall(function()
-- This Lua API connects to an existing daemon. Start our own first.
tmux({
"-f", "/dev/null", "new-session", "-d",
"-s", "work", "-n", "main", "/bin/sh",
})
must(adapter.run(function(runtime)
local server = must(runtime:connect({
binary = binary,
socket_path = socket,
}):await())
local created = must(server:new_session({
name = "quickstart",
window_name = "main",
argv = { "/bin/sh" },
}):await())
local logs = must(created.session:new_window({
name = "logs",
argv = { "/bin/cat" },
}):await())
must(logs.pane:split({
direction = "right",
percent = 40,
argv = { "/bin/cat" },
}):await())
local marker = "libtmux-ready"
local quoted = "'" .. binary:gsub("'", "'\\''") .. "'"
local command = ("printf '\\nlibtmux ready\\n'; "
.. "%s wait-for -S %s"):format(quoted, marker)
must(created.pane:send_text(command):await())
must(created.pane:send_keys({ "Enter" }):await())
must(server:command(
{ "wait-for", marker },
{ timeout = 10000 }
):await())
local capture = must(created.pane:capture():await())
assert(must(capture:text()):find(
"\nlibtmux ready\n", 1, true
), "pane output was not captured")
print("libtmux ready")
must(server:close():await())
return true
end))
end, debug.traceback)
local cleaned, cleanup_error = pcall(function()
if uv.fs_stat(socket) then tmux({ "kill-server" }) end
local removed, _, code = uv.fs_unlink(socket)
assert(removed or code == "ENOENT", "could not remove owned socket")
assert(uv.fs_rmdir(directory))
end)
if not cleaned then io.stderr:write(tostring(cleanup_error), "\n") end
if not ok then error(failure, 0) end
assert(cleaned, "cleanup failed; retained " .. directory)
Run this example

POSIX shell, Git, tmux 3.3a or newer, Lua 5.5.1, LuaRocks 3.13.0, CMake and a C compiler; setup installs luv 1.52.1-0.

Save the program above as quickstart.lua in a new directory, along with the files below.

Cleanup is omitted; the private tmux server and its temporary files may remain. Select Cleanup to remove them when the program finishes. The program stops its private tmux server and removes any temporary files it created when it finishes. Error handling adds diagnostics; every version checks required results and deadlines.

Setup and run

git init -q source
git -C source fetch -q --depth=1 \
https://github.com/libtmux/libtmux-lua.git \
0395620d362ef75c74cd488f9577afda68922586
git -C source checkout -q --detach FETCH_HEAD
luarocks --tree .rocks install luv 1.52.1-0
(cd source && luarocks --tree ../.rocks make \
rockspecs/libtmux-scm-1.rockspec)
unset TMUX TMUX_PANE
export ENV=/dev/null BASH_ENV=/dev/null SHELL=/bin/sh
eval "$(luarocks --tree .rocks path)"
lua quickstart.lua

Expected output

libtmux ready

Library revision: 0395620d362e.

Typed API

What you get over the tmux CLI

Esc

Type to search.

Reference previewOpen reference