Background sessions

Named, managed tmux sessions: create-or-reuse by name, drive by sid

fastmux’s handle API assumes you can hold a Pane between calls. A background terminal often outlives that: it is created in one tool call and driven from another, shared with a user who attaches by name, and referred to by nothing but that name. This module provides that functionality.

start_session creates-or-reuses a named session, and every verb takes a sid — a session name, a %pane_id, a Session or Pane handle, or None for the current pane.

Managed sessions

A managed session differs from a bare new_session in the ways background work needs: it is found again by name instead of erroring as a duplicate; remain is set (with the dead-pane banner cleared) so a finished command’s output and exit status stay inspectable; and the created pane’s id is recorded in the @fastmux_pane_id session option, so later splits don’t change which pane the sid addresses. cmd is started with respawn-pane only after those options are in place, since a fast-failing command could otherwise die before they land.


source

start_session

def start_session(
    sid:NoneType=None, # Session name; default: a generated `fastmux-`-prefixed name
    cmd:NoneType=None, # Command to run (str or argv list); default: the user's shell
    cwd:NoneType=None, # Working directory
    env:NoneType=None, # Extra environment vars as a dict
    width:NoneType=None, # Terminal width in columns
    height:NoneType=None, # Terminal height in rows
):

The session named sid, created detached (managed, with remain set) if it doesn’t exist

s = start_session('fastmux-test-bg', width=60, height=8)
test_eq(s.name, 'fastmux-test-bg')
test_eq(start_session('fastmux-test-bg').id, s.id)
s

Addressing by sid

pane turns any sid form into the Pane it addresses, and is the bridge to the full handle API: everything this module doesn’t wrap (wait, search, splitting, transcript slicing) is a method on its result. For a session name, the managed pane recorded at creation wins over the currently-active one:


source

pane

def pane(
    sid:NoneType=None
):

The pane sid addresses: a session name (its managed pane), a %pane_id, a Session or Pane handle, or None for the current pane

p = pane('fastmux-test-bg')
test_eq(p.id, pane(s).id)
test_eq(p.id, pane(p.id).id)

Splitting the managed session doesn’t move the sid’s target:

q = p.bsplit(size=3)
test_eq(pane('fastmux-test-bg').id, p.id)
q.kill()

Driving by sid

The verbs mirror Pane’s, with a sid in front: each sends (or just waits), polls until the pane differs from its last-seen state, and returns a Capture. Output that arrived between calls satisfies the next poll immediately.


source

display

def display(
    sid:NoneType=None, lines:int=80
):

Capture the last lines of sid’s pane


source

poll

def poll(
    sid:NoneType=None, wait_ms:int=0, interval_ms:int=50, lines:int=80
):

Wait for sid’s pane to differ from its last-seen state, then capture the last lines


source

interrupt

def interrupt(
    sid:NoneType=None, wait_ms:int=0, interval_ms:int=50, lines:int=80
):

Send Ctrl-C to sid’s pane, then poll


source

send_keys

def send_keys(
    sid:NoneType=None, *keys, wait_ms:int=0, interval_ms:int=50, lines:int=80
):

Send tmux key names to sid’s pane, then poll


source

send

def send(
    sid:NoneType=None, chars:str='', wait_ms:int=0, interval_ms:int=50, lines:int=80
):

Paste chars into sid’s pane literally, then poll

c = send('fastmux-test-bg', 'echo $((6*7)) apples\n', wait_ms=2000)
assert '42 apples' in c.text
c

Lifecycle

close kills the whole session owning the sid’s pane — including when the sid is a %pane_id — and managed_sessions lists every session start_session created, whatever it was named:


source

managed_sessions

def managed_sessions():

Sessions created by start_session


source

close

def close(
    sid:NoneType=None
):

Kill the session owning sid’s pane

test_eq('fastmux-test-bg' in managed_sessions().attrgot('name'), True)
close('fastmux-test-bg')
with expect_fail(TmuxError): pane('fastmux-test-bg')