|
| 1 | +--- |
| 2 | +orphan: true |
| 3 | +--- |
| 4 | + |
| 5 | +(control-mode)= |
| 6 | + |
| 7 | +# Control Mode Engine (experimental) |
| 8 | + |
| 9 | +:::{warning} |
| 10 | +This is an **experimental API**. Names and behavior may change between releases. |
| 11 | +Use with caution and pin your libtmux version if you depend on it. |
| 12 | +::: |
| 13 | + |
| 14 | +libtmux can drive tmux through a persistent Control Mode client. This keeps a |
| 15 | +single connection open, pipelines commands, and surfaces tmux notifications in a |
| 16 | +typed stream. |
| 17 | + |
| 18 | +## Why use Control Mode? |
| 19 | + |
| 20 | +- Lower overhead than spawning a tmux process per command |
| 21 | +- Access to live notifications: layout changes, window/link events, client |
| 22 | + detach/attach, paste buffer updates, and more |
| 23 | +- Structured command results with timing/flag metadata |
| 24 | + |
| 25 | +## Using ControlModeEngine |
| 26 | + |
| 27 | +```python |
| 28 | +from __future__ import annotations |
| 29 | + |
| 30 | +from libtmux._internal.engines.control_mode import ControlModeEngine |
| 31 | +from libtmux.server import Server |
| 32 | + |
| 33 | +engine = ControlModeEngine(command_timeout=5) |
| 34 | +server = Server(engine=engine) |
| 35 | + |
| 36 | +session = server.new_session(session_name="ctrl-demo") |
| 37 | +print(session.name) |
| 38 | + |
| 39 | +# Consume notifications (non-blocking example) |
| 40 | +for notif in engine.iter_notifications(timeout=0.1): |
| 41 | + print(notif.kind, notif.data) |
| 42 | +``` |
| 43 | + |
| 44 | +:::{note} |
| 45 | +Control mode creates a bootstrap tmux session named ``libtmux_control_mode``. |
| 46 | +If your code enumerates sessions, filter it out. |
| 47 | +::: |
| 48 | + |
| 49 | +## Parsing notifications directly |
| 50 | + |
| 51 | +The protocol parser can be used without tmux to understand the wire format. |
| 52 | + |
| 53 | +```python |
| 54 | +>>> from libtmux._internal.engines.control_protocol import ControlProtocol |
| 55 | +>>> proto = ControlProtocol() |
| 56 | +>>> proto.feed_line("%layout-change @1 abcd efgh 0") |
| 57 | +>>> notif = proto.get_notification() |
| 58 | +>>> notif.kind.name |
| 59 | +'WINDOW_LAYOUT_CHANGED' |
| 60 | +>>> notif.data['window_layout'] |
| 61 | +'abcd' |
| 62 | +``` |
| 63 | + |
| 64 | +## Fallback engine |
| 65 | + |
| 66 | +If control mode is unavailable, ``SubprocessEngine`` matches the same |
| 67 | +``Engine`` interface but runs one tmux process per command: |
| 68 | + |
| 69 | +```python |
| 70 | +from libtmux._internal.engines.subprocess_engine import SubprocessEngine |
| 71 | +from libtmux.server import Server |
| 72 | + |
| 73 | +server = Server(engine=SubprocessEngine()) |
| 74 | +print(server.list_sessions()) # legacy behavior |
| 75 | +``` |
| 76 | + |
| 77 | +## Key behaviors |
| 78 | + |
| 79 | +- Commands still return ``tmux_cmd`` objects for compatibility, but extra |
| 80 | + metadata (``exit_status``, ``cmd_id``, ``tmux_time``) is attached. |
| 81 | +- Notifications are queued; drops are counted when consumers fall behind. |
| 82 | +- Timeouts raise ``ControlModeTimeout`` and restart the control client. |
0 commit comments