1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
|
(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]
- [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 notification 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 'libtmux' --pip-args '\--pre' --force
// 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 'libtmux @ git+https://github.com/tmux-python/libtmux.git@master' --force
```
- [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/
[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
```
First, we can grab a {class}`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 Sessions,
Windows and Panes.
## 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}`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}`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}`Session`
If you have multiple tmux sessions open, all methods in {class}`Server` are available.
We can list sessions with {meth}`Server.sessions`:
```python
>>> server.sessions
[Session($1 ...), Session($0 ...)]
```
This returns a list of {class}`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 {meth}`Server.sessions` and {meth}`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 the ID `sessions()` returned above.
```python
>>> server.sessions.filter(session_id='$1')[0]
Session($1 ...)
```
You may `session = server.get_by_id('$<yourId>')` 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 `filter`, pass in attributes and return a list of matches. In
this case, a {class}`Server` holds a collection of child {class}`Session`.
{class}`Session` and {class}`Window` both utilize `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}`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}`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 `.kill_window` direction from the {class}`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 {meth}`Session.windows` and {meth}`Session.windows.filter()` to list and sort
through active {class}`Window`'s.
## Manipulating windows
Now that we know how to create windows, let's use one. Let's use {meth}`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}`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. `window = session.active_window()` gave us the {class}`Window` of the current attached to window.
2. `attach=False` assures the cursor didn't switch to the newly created pane.
3. Returned the created {class}`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}`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 `.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 `pane.enter()` after.
## Final notes
These objects created use tmux's internal usage of ID's 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, {class}`WorkspaceBuilder` uses these same internals to build your
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`.)
:::
[workspacebuilder.py]: https://github.com/tmux-python/libtmux/blob/master/libtmux/workspacebuilder.py
[test suite]: https://github.com/tmux-python/libtmux/tree/master/tests
[ptpython]: https://github.com/prompt-toolkit/ptpython
|