(quickstart)= # Quickstart libtmux allows for developers and system administrators to control live tmux sessions using python code. In this example, we will launch a tmux session and control the windows from inside a live tmux session. (requirements)= ## Requirements - [tmux] 3.2a or newer - [pip] - for this handbook's examples [tmux]: https://tmux.github.io/ (installation)= ## Installation Next, ensure `libtmux` is installed: ```console $ pip install --user libtmux ``` (developmental-releases)= ### Developmental releases New versions of libtmux are published to [PyPI] as alpha, beta, or release candidates. In their versions you will see notifications like `a1`, `b1`, and `rc1`, respectively. `1.10.0b4` would mean the 4th beta release of `1.10.0` before general availability. - [pip]\: ```console $ pip install --user --upgrade --pre libtmux ``` - [pipx]\: ```console $ pipx install \ --suffix=@next \ --pip-args '\--pre' \ --force \ 'libtmux' ``` Usage: `libtmux@next [command]` - [uv tool install][uv-tools]\: ```console $ uv tool install --prerelease=allow libtmux ``` - [uv]\: ```console $ uv add libtmux --prerelease allow ``` - [uvx]\: ```console $ uvx --from 'libtmux' --prerelease allow python ``` via trunk (can break easily): - [pip]\: ```console $ pip install --user -e git+https://github.com/tmux-python/libtmux.git#egg=libtmux ``` - [pipx]\: ```console $ pipx install \ --suffix=@master \ --force \ 'libtmux @ git+https://github.com/tmux-python/libtmux.git@master' ``` - [uv]\: ```console $ uv tool install libtmux --from git+https://github.com/tmux-python/libtmux.git ``` [pip]: https://pip.pypa.io/en/stable/ [pipx]: https://pypa.github.io/pipx/docs/ [PyPI]: https://pypi.org/project/libtmux/ [uv]: https://docs.astral.sh/uv/ [uv-tools]: https://docs.astral.sh/uv/concepts/tools/ [uvx]: https://docs.astral.sh/uv/guides/tools/ [ptpython]: https://github.com/prompt-toolkit/ptpython ## Start a tmux session Now, let's open a tmux session. ```console $ tmux new-session -n bar -s foo ``` This tutorial will be using the session and window name in the example. Window name `-n`: `bar` Session name `-s`: `foo` ## Control tmux via python :::{seealso} {ref}`api` ::: ```console $ python ``` For commandline completion, you can also use [ptpython]. ```console $ pip install --user ptpython ``` ```console $ ptpython ``` ```{module} libtmux :no-index: ``` First, we can grab a {class}`~libtmux.Server`. ```python >>> import libtmux >>> server = libtmux.Server() >>> server Server(socket_path=/tmp/tmux-.../default) ``` :::{tip} You can also use [tmuxp]'s [`tmuxp shell`] to drop straight into your current tmux server / session / window pane. [tmuxp]: https://tmuxp.git-pull.com/ [`tmuxp shell`]: https://tmuxp.git-pull.com/cli/shell.html ::: :::{note} You can specify a `socket_name`, `socket_path` and `config_file` in your server object. `libtmux.Server(socket_name='mysocket')` is equivalent to `$ tmux -L mysocket`. ::: `server` is now a living object bound to the tmux server's {class}`~libtmux.Session`, {class}`~libtmux.Window`, and {class}`~libtmux.Pane` objects. ## Raw, contextual commands New session: ```python >>> server.cmd('new-session', '-d', '-P', '-F#{session_id}').stdout[0] '$2' ``` ```python >>> session.cmd('new-window', '-P').stdout[0] 'libtmux...:2.0' ``` From raw command output, to a rich {class}`~libtmux.Window` object (in practice and as shown later, you'd use {meth}`Session.new_window() `): ```python >>> Window.from_window_id(window_id=session.cmd('new-window', '-P', '-F#{window_id}').stdout[0], server=session.server) Window(@2 2:..., Session($1 libtmux_...)) ``` Create a pane from a window: ```python >>> window.cmd('split-window', '-P', '-F#{pane_id}').stdout[0] '%2' ``` Raw output directly to a {class}`~libtmux.Pane` (in practice, you'd use {meth}`Window.split() `): ```python >>> Pane.from_pane_id(pane_id=window.cmd('split-window', '-P', '-F#{pane_id}').stdout[0], server=window.server) Pane(%... Window(@1 1:..., Session($1 libtmux_...))) ``` ## Find your {class}`~libtmux.Session` If you have multiple tmux sessions open, all methods in {class}`~libtmux.Server` are available. We can list sessions with {attr}`Server.sessions `: ```python >>> server.sessions [Session($1 ...), Session($0 ...)] ``` This returns a list of {class}`~libtmux.Session` objects you can grab. We can find our current session with: ```python >>> server.sessions[0] Session($1 ...) ``` However, this isn't guaranteed, libtmux works against current tmux information, the session's name could be changed, or another tmux session may be created, so {attr}`Server.sessions ` and {attr}`Server.windows ` exist as a lookup. ## Get session by ID tmux sessions use the `$[0-9]` convention as a way to identify sessions. `$1` is whatever ID {attr}`Server.sessions ` returned above. ```python >>> server.sessions.filter(session_id='$1')[0] Session($1 ...) ``` You may call {meth}`server.get_by_id() ` to use the session object. ## Get session by name / other properties ```python >>> server.sessions[0].rename_session('foo') Session($1 foo) >>> server.sessions.filter(session_name="foo")[0] Session($1 foo) >>> server.sessions.get(session_name="foo") Session($1 foo) ``` With {meth}`filter() `, pass in attributes and return a list of matches. In this case, a {class}`~libtmux.Server` holds a collection of child {class}`~libtmux.Session` objects. {class}`~libtmux.Session` and {class}`~libtmux.Window` both utilize {meth}`filter() ` to sift through windows and panes, respectively. So you may now use: ```python >>> server.sessions[0].rename_session('foo') Session($1 foo) >>> session = server.sessions.get(session_name="foo") >>> session Session($1 foo) ``` to give us a `session` object to play with. ## Playing with our tmux session We now have access to `session` from above with all of the methods available in {class}`~libtmux.Session`. Let's make a {meth}`Session.new_window() `, in the background: ```python >>> session.new_window(attach=False, window_name="ha in the bg") Window(@2 ...:ha in the bg, Session($1 ...)) ``` So a few things: 1. `attach=False` meant to create a new window, but not to switch to it. It is the same as `$ tmux new-window -d`. 2. `window_name` may be specified. 3. Returns the {class}`~libtmux.Window` object created. :::{note} Use the API reference {ref}`api` for more commands. ::: Let's delete that window ({meth}`Session.kill_window() `). Method 1: Use passthrough to tmux's `target` system. ```python >>> session.kill_window(window.window_id) ``` The window in the bg disappeared. This was the equivalent of `$ tmux kill-window -t'ha in'` Internally, tmux uses `target`. Its specific behavior depends on what the target is, view the tmux manpage for more information: ``` This section contains a list of the commands supported by tmux. Most commands accept the optional -t argument with one of target-client, target-session, target-window, or target-pane. ``` In this case, you can also go back in time and recreate the window again. The CLI should have history, so navigate up with the arrow key. ```python >>> session.new_window(attach=False, window_name="ha in the bg") Window(@2 ...:ha in the bg, Session($1 ...)) ``` Try to kill the window by the matching id `@[0-9999]`. ```python >>> session.new_window(attach=False, window_name="ha in the bg") Window(@2 ...:ha in the bg, Session($1 ...)) >>> session.kill_window('ha in the bg') ``` In addition, you could also call {meth}`Window.kill() ` on the {class}`~libtmux.Window` object: ```python >>> window = session.new_window(attach=False, window_name="check this out") >>> window Window(@2 2:check this out, Session($1 ...)) ``` And kill: ```python >>> window.kill() ``` Use {attr}`Session.windows ` and {meth}`Session.windows.filter() ` to list and sort through active {class}`~libtmux.Window` objects. ## Manipulating windows Now that we know how to create windows, let's use one. Let's use {attr}`Session.active_window ` to grab our current window. ```python >>> window = session.active_window ``` `window` now has access to all of the objects inside of {class}`~libtmux.Window`. Let's create a pane, {meth}`Window.split() `: ```python >>> window.split(attach=False) Pane(%2 Window(@1 ...:..., Session($1 ...))) ``` Powered up. Let's have a break down: 1. {attr}`session.active_window ` gave us the {class}`~libtmux.Window` of the current attached window. 2. `attach=False` assures the cursor didn't switch to the newly created pane. 3. Returned the created {class}`~libtmux.Pane`. Also, since you are aware of this power, let's commemorate the experience: ```python >>> window.rename_window('libtmuxower') Window(@1 ...:..., Session($1 ...)) ``` You should have noticed {meth}`Window.rename_window() ` renamed the window. ## Moving cursor across windows and panes You have two ways you can move your cursor to new sessions, windows and panes. For one, arguments such as `attach=False` can be omittted. ```python >>> pane = window.split() ``` This gives you the {class}`~libtmux.Pane` along with moving the cursor to a new window. You can also use the `.select_*` available on the object; in this case the pane has {meth}`Pane.select() `. ```python >>> pane = window.split(attach=False) ``` ```python >>> pane.select() Pane(%1 Window(@1 ...:..., Session($1 ...))) ``` ```{eval-rst} .. todo:: create a ``kill_pane()`` method. ``` ```{eval-rst} .. todo:: have a ``.kill()`` and ``.select()`` proxy for Server, Session, Window and Pane objects. ``` ## Sending commands to tmux panes remotely As long as you have the object, or are iterating through a list of them, you can use {meth}`Pane.send_keys() `. ```python >>> window = session.new_window(attach=False, window_name="test") >>> pane = window.split(attach=False) >>> pane.send_keys('echo hey', enter=False) ``` See the other window, notice that {meth}`Pane.send_keys() ` has "`echo hey`" written, _still in the prompt_. `enter=False` can be used to send keys without pressing return. In this case, you may leave it to the user to press return himself, or complete a command using {meth}`Pane.enter() `: ```python >>> pane.enter() Pane(%1 ...) ``` ### Avoid cluttering shell history `suppress_history=True` can send commands to pane windows and sessions **without** them being visible in the history. ```python >>> pane.send_keys('echo Howdy', enter=True, suppress_history=True) ``` In this case, {meth}`Pane.send_keys() ` has " `echo Howdy`" written, automatically sent, the leading space character prevents adding it to the user's shell history. Omitting `enter=false` means the default behavior (sending the command) is done, without needing to use {meth}`pane.enter() ` after. ## Working with options libtmux provides a unified API for managing tmux options across {class}`~libtmux.Server`, {class}`~libtmux.Session`, {class}`~libtmux.Window`, and {class}`~libtmux.Pane` objects. ### Getting options ```python >>> server.show_option('buffer-limit') 50 >>> window.show_options() # doctest: +ELLIPSIS {...} ``` ### Setting options ```python >>> window.set_option('automatic-rename', False) # doctest: +ELLIPSIS Window(@... ...) >>> window.show_option('automatic-rename') False >>> window.unset_option('automatic-rename') # doctest: +ELLIPSIS Window(@... ...) ``` :::{seealso} See {ref}`options-and-hooks` for more details on options and hooks. ::: ## Final notes These objects use tmux's internal IDs to make servers, sessions, windows, and panes accessible at the object level. You don't have to see the tmux session to be able to orchestrate it. After all, {ref}`workspace-setup` uses these same internals to build sessions in the background. :) :::{seealso} If you want to dig deeper, check out {ref}`API`, the code for and our [test suite] (see {ref}`development`.) ::: [test suite]: https://github.com/tmux-python/libtmux/tree/master/tests