Locating yourself¶
Most libtmux code starts from a handle you already hold — you make a
Server, you find a Session, you walk down.
Sometimes you hold nothing, because your code is running inside a pane: a
script you launched in a split, a tmux hook, a test harness, an agent. Before it
can do anything useful it has to answer one question — where am I?
You don’t have to search the server for yourself. tmux already told you. It writes two variables into every pane it spawns, and each level of the hierarchy reads them back:
the tmux server you are running on |
|
the session that holds you |
|
the window that contains you |
|
the pane you are running in |
If all you need is a handle on yourself, the first section is the whole story. The rest is for the rarer cases: outside tmux, background panes, and windows that live in more than one session.
To follow along live, start tmux, then run python inside a pane — that is
the situation this page is about:
$ tmux
$ python
Ask where you are¶
Inside a pane each call takes no arguments. It reads os.environ, which is
where tmux put the answer:
>>> socket_path = server.cmd(
... "display-message", "-p", "-t", session.session_id, "#{socket_path}"
... ).stdout[0]
>>> monkeypatch.setenv("TMUX", f"{socket_path},1,{session.session_id}")
>>> monkeypatch.setenv("TMUX_PANE", pane.pane_id)
>>> Pane.from_env().pane_id == pane.pane_id
True
>>> Window.from_env().window_id == window.window_id
True
>>> Session.from_env().session_id == session.session_id
True
That is the whole call — in a real pane tmux has already set those two variables for you, and there is nothing to arrange. These docs are not running in a pane, so the example sets them first.
Once you hold any of the four you are back on the hierarchy, and everything in Traversal applies.
Each call also accepts an environment mapping in place of os.environ.
The examples below pass one explicitly, and it is the seam your own tests can use
— see Testing code that locates itself.
>>> socket_path = server.cmd(
... "display-message", "-p", "-t", session.session_id, "#{socket_path}"
... ).stdout[0]
>>> env = {
... "TMUX": f"{socket_path},1,{session.session_id}",
... "TMUX_PANE": pane.pane_id,
... }
>>> Session.from_env(env).session_id == session.session_id
True
When you are not in tmux¶
There is no pane to return, and answering with somebody else’s would be worse
than not answering, so all four raise NotInsideTmux. Catch it
when your program is meant to run inside a pane and out:
>>> from libtmux import exc
>>> try:
... here = Pane.from_env({})
... except exc.NotInsideTmux:
... here = None
>>> here is None
True
The window that contains you, not the one in front¶
A background pane is still somewhere. Window.from_env() returns the window that contains you, which is not
the window someone happens to be looking at:
>>> socket_path = server.cmd(
... "display-message", "-p", "-t", session.session_id, "#{socket_path}"
... ).stdout[0]
>>> worker_window = session.new_window(window_name="worker", attach=False)
>>> worker = worker_window.active_pane
>>> env = {
... "TMUX": f"{socket_path},1,{session.session_id}",
... "TMUX_PANE": worker.pane_id,
... }
>>> session.active_window.window_id == worker_window.window_id
False
>>> Window.from_env(env).window_id == worker_window.window_id
True
session.active_window answers a different
question — what is focused.
The server answers, not the environment¶
TMUX looks like it settles the session question on its own. Its three fields are
socket_path,server_pid,session_id, and that last one is a session id.
Don’t reach for it. tmux writes these variables into a pane’s environment once,
when it spawns the pane, and never revises them — a running process’s environment
is not something tmux can rewrite. Move the pane’s window to another session and
the pane really is somewhere else, while TMUX still names the session it was
born in.
So from_env anchors on TMUX_PANE, the one id tmux keeps answering for live,
and asks the server where that pane is now:
>>> socket_path = server.cmd(
... "display-message", "-p", "-t", session.session_id, "#{socket_path}"
... ).stdout[0]
>>> worker_window = session.new_window(window_name="worker", attach=False)
>>> worker = worker_window.active_pane
>>> env = {
... "TMUX": f"{socket_path},1,{session.session_id}", # names *this* session
... "TMUX_PANE": worker.pane_id,
... }
>>> elsewhere = server.new_session(session_name="elsewhere")
>>> _ = worker_window.move_window(session=elsewhere.session_id, no_select=True)
>>> Session.from_env(env).session_name # where the pane is, not where TMUX says
'elsewhere'
The same staleness is why pane.session resolves
through pane.window instead of reading the
session_id it is already carrying: a Pane you fetched earlier
remembers the session it was in then. The extra round-trip is what keeps the
answer current. If you read it in a loop, bind it to a variable once.
When a window belongs to two sessions¶
link-window puts a single window in several sessions at once, and then the pane
genuinely belongs to all of them. Asked “which session am I in?”, there is more
than one true answer.
libtmux does not invent a tie-break. tmux already has to settle this every time
you type a command with a -t target, so libtmux asks it, and hands you back the
session tmux itself would act on. Below, the pane’s window is linked into a
second session — holders shows it really is in both — and libtmux answers with
the session tmux’s own display-message -t names:
>>> socket_path = server.cmd(
... "display-message", "-p", "-t", session.session_id, "#{socket_path}"
... ).stdout[0]
>>> home = server.new_session(session_name="aaa-home")
>>> shared = home.new_window(window_name="shared", attach=False)
>>> worker = shared.active_pane
>>> env = {
... "TMUX": f"{socket_path},1,{home.session_id}",
... "TMUX_PANE": worker.pane_id,
... }
>>> guest = server.new_session(session_name="zzz-guest")
>>> _ = server.cmd(
... "link-window", "-s", shared.window_id, "-t", f"{guest.session_id}:"
... )
>>> holders = {p.session_name for p in server.panes.filter(pane_id=worker.pane_id)}
>>> holders == {"aaa-home", "zzz-guest"}
True
>>> tmux_says = server.cmd(
... "display-message", "-p", "-t", worker.pane_id, "#{session_name}"
... ).stdout[0]
>>> Session.from_env(env).session_name == tmux_says
True
So the rule, in full: TMUX_PANE says which pane you are, and tmux says which
session that pane is in — including when the answer is contested. The session id
in TMUX is never read, not even as a tie-break. It records where the process was
spawned, which is a different fact from where it is, and one that goes stale.
There is no Client.from_env()¶
A Client is an attached terminal, and a pane is not owned by
one. No client may be attached at all — a detached session, a CI job, a
send-keys script all run with a perfectly good TMUX_PANE and nobody watching
— or several may be, each with its own view. tmux exports no client id into a
pane, so there is nothing to read back. See Clients for the
view-versus-identity model this follows from.
Testing code that locates itself¶
Every from_env takes an optional env mapping. Passing one is how you test a
function that locates itself without running your test suite inside a pane:
>>> def announce(env=None):
... """Report the session this code is running in."""
... return Session.from_env(env).session_name
>>> socket_path = server.cmd(
... "display-message", "-p", "-t", session.session_id, "#{socket_path}"
... ).stdout[0]
>>> env = {
... "TMUX": f"{socket_path},1,{session.session_id}",
... "TMUX_PANE": pane.pane_id,
... }
>>> announce(env) == session.session_name
True
In production announce() takes no argument and reads the real environment.