Skip to main content

rmux_proto/
request.rs

1//! Detached request contracts.
2
3use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5
6use crate::{
7    ControlModeRequest, HandshakeRequest, PaneTarget, PaneTargetRef, SdkWaitId, SdkWaitOwnerId,
8    SessionName, Target, WindowTarget,
9};
10
11#[path = "request/compat.rs"]
12mod compat;
13
14#[path = "request/display.rs"]
15mod display;
16pub use display::{DisplayMessageExtRequest, DisplayMessageRequest};
17
18#[path = "request/show.rs"]
19mod show;
20pub use show::ShowHooksRequest;
21pub use show::{ShowEnvironmentRequest, ShowOptionsRequest};
22
23#[path = "request/layout.rs"]
24mod layout;
25pub use layout::{
26    NextLayoutRequest, PreviousLayoutRequest, SelectCustomLayoutRequest, SelectLayoutRequest,
27    SelectLayoutTarget, SelectOldLayoutRequest, SpreadLayoutRequest,
28};
29
30#[path = "request/pane.rs"]
31mod pane;
32pub use pane::{
33    BreakPaneRequest, DisplayPanesRequest, JoinPaneRequest, KillPaneRequest, LastPaneRequest,
34    MovePaneRequest, PaneBroadcastInputRequest, PaneInputRequest, PaneKillRequest,
35    PaneOutputCursorRequest, PaneOutputSubscriptionStart, PaneResizeRequest, PaneRespawnRequest,
36    PaneSelectRequest, PaneSnapshotRefRequest, PaneSnapshotRequest, PaneSplitSize, PipePaneRequest,
37    ResizePaneRequest, ResizePaneTargetActionRequest, RespawnPaneRequest,
38    SelectPaneAdjacentRequest, SelectPaneDirection, SelectPaneMarkRequest, SelectPaneRequest,
39    SendKeysExt2Request, SendKeysExtRequest, SendKeysRequest, SplitWindowExtRequest,
40    SplitWindowRequest, SplitWindowTarget, SplitWindowTargetActionRequest,
41    SubscribePaneOutputRefRequest, SubscribePaneOutputRequest, SwapPaneDirection, SwapPaneRequest,
42    UnsubscribePaneOutputRequest,
43};
44
45#[path = "request/window.rs"]
46mod window;
47pub use window::{
48    KillWindowRequest, LastWindowRequest, LinkWindowRequest, ListWindowsRequest, MoveWindowRequest,
49    MoveWindowTarget, NewWindowRequest, NextWindowRequest, PreviousWindowRequest,
50    RenameWindowRequest, ResizeWindowAdjustment, ResizeWindowRequest, RespawnWindowRequest,
51    RotateWindowDirection, RotateWindowRequest, SelectWindowRequest, SwapWindowRequest,
52};
53
54#[path = "request/target.rs"]
55mod target;
56pub use target::{ResolveTargetRequest, ResolveTargetType};
57
58#[path = "request/session.rs"]
59mod session;
60pub use session::{
61    CreateSessionLeaseRequest, HasSessionRequest, KillSessionRequest, ListSessionsRequest,
62    NewSessionExtRequest, NewSessionRequest, ReleaseSessionLeaseRequest, RenameSessionRequest,
63    RenewSessionLeaseRequest,
64};
65
66#[path = "request/server.rs"]
67mod server;
68pub use server::{
69    DaemonStatusRequest, KillServerRequest, LockClientRequest, LockServerRequest,
70    LockSessionRequest, ServerAccessRequest, ShutdownIfIdleRequest,
71};
72
73#[path = "request/client.rs"]
74mod client;
75pub use client::{
76    AttachSessionExt2Request, AttachSessionExt3Request, AttachSessionExtRequest,
77    AttachSessionRequest, DetachClientExtRequest, DetachClientRequest, ListClientsRequest,
78    RefreshClientRequest, SuspendClientRequest, SwitchClientExt2Request, SwitchClientExt3Request,
79    SwitchClientExtRequest, SwitchClientRequest,
80};
81
82#[path = "request/keys.rs"]
83mod keys;
84pub use keys::{
85    BindKeyRequest, ClockModeRequest, CopyModeRequest, ListKeysRequest, SendPrefixRequest,
86    UnbindKeyRequest,
87};
88
89#[path = "request/options.rs"]
90mod options;
91pub use options::{
92    SetEnvironmentMode, SetEnvironmentRequest, SetHookMutationRequest, SetHookRequest,
93    SetOptionByNameRequest, SetOptionRequest,
94};
95
96#[path = "request/buffer.rs"]
97mod buffer;
98pub use buffer::{
99    CapturePaneRequest, CapturePaneTargetActionRequest, ClearHistoryRequest, DeleteBufferRequest,
100    ListBuffersRequest, LoadBufferRequest, PasteBufferRequest, SaveBufferRequest, SetBufferRequest,
101    ShowBufferRequest,
102};
103
104#[path = "request/web.rs"]
105mod web;
106pub use web::{
107    CreateWebShareRequest, ListWebSharesRequest, LookupWebShareRequest, StopAllWebSharesRequest,
108    StopWebShareRequest, WebShareConfigRequest, WebShareRequest, WebShareScope, WebShareUrlOptions,
109    WebTerminalPalette, WebTerminalTheme,
110};
111
112/// All detached public command and internal RPC requests supported by the wire
113/// protocol.
114#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
115pub enum Request {
116    /// `new-session`
117    NewSession(NewSessionRequest),
118    /// `has-session`
119    HasSession(HasSessionRequest),
120    /// `kill-session`
121    KillSession(KillSessionRequest),
122    /// `new-window`
123    NewWindow(Box<NewWindowRequest>),
124    /// `kill-window`
125    KillWindow(KillWindowRequest),
126    /// `select-window`
127    SelectWindow(SelectWindowRequest),
128    /// `rename-window`
129    RenameWindow(RenameWindowRequest),
130    /// `next-window`
131    NextWindow(NextWindowRequest),
132    /// `previous-window`
133    PreviousWindow(PreviousWindowRequest),
134    /// `last-window`
135    LastWindow(LastWindowRequest),
136    /// `list-windows`
137    ListWindows(ListWindowsRequest),
138    /// `move-window`
139    MoveWindow(MoveWindowRequest),
140    /// `swap-window`
141    SwapWindow(SwapWindowRequest),
142    /// `rotate-window`
143    RotateWindow(RotateWindowRequest),
144    /// `split-window`
145    SplitWindow(SplitWindowRequest),
146    /// `swap-pane`
147    SwapPane(SwapPaneRequest),
148    /// `last-pane`
149    LastPane(LastPaneRequest),
150    /// `join-pane`
151    JoinPane(JoinPaneRequest),
152    /// `break-pane`
153    BreakPane(Box<BreakPaneRequest>),
154    /// `kill-pane`
155    KillPane(KillPaneRequest),
156    /// `select-layout`
157    SelectLayout(SelectLayoutRequest),
158    /// `resize-pane`
159    ResizePane(ResizePaneRequest),
160    /// `display-panes`
161    DisplayPanes(DisplayPanesRequest),
162    /// `select-pane`
163    SelectPane(Box<SelectPaneRequest>),
164    /// `select-pane -U/-D/-L/-R`
165    SelectPaneAdjacent(SelectPaneAdjacentRequest),
166    /// `send-keys`
167    SendKeys(SendKeysRequest),
168    /// `attach-session`
169    AttachSession(AttachSessionRequest),
170    /// `switch-client`
171    SwitchClient(SwitchClientRequest),
172    /// `detach-client`
173    DetachClient(DetachClientRequest),
174    /// `set-option`
175    SetOption(SetOptionRequest),
176    /// `set-environment`
177    SetEnvironment(Box<SetEnvironmentRequest>),
178    /// `set-hook`
179    SetHook(SetHookRequest),
180    /// `next-layout`
181    NextLayout(NextLayoutRequest),
182    /// `previous-layout`
183    PreviousLayout(PreviousLayoutRequest),
184    /// `show-options`
185    ShowOptions(ShowOptionsRequest),
186    /// `show-environment`
187    ShowEnvironment(ShowEnvironmentRequest),
188    /// `set-buffer`
189    SetBuffer(SetBufferRequest),
190    /// `show-buffer`
191    ShowBuffer(ShowBufferRequest),
192    /// `paste-buffer`
193    PasteBuffer(Box<PasteBufferRequest>),
194    /// `list-buffers`
195    ListBuffers(ListBuffersRequest),
196    /// `delete-buffer`
197    DeleteBuffer(DeleteBufferRequest),
198    /// `load-buffer`
199    LoadBuffer(LoadBufferRequest),
200    /// `save-buffer`
201    SaveBuffer(SaveBufferRequest),
202    /// `capture-pane`
203    CapturePane(Box<CapturePaneRequest>),
204    /// `display-message`
205    DisplayMessage(DisplayMessageRequest),
206    /// `run-shell`
207    RunShell(Box<RunShellRequest>),
208    /// `if-shell`
209    IfShell(Box<IfShellRequest>),
210    /// `wait-for`
211    WaitFor(WaitForRequest),
212    /// `rename-session`
213    RenameSession(RenameSessionRequest),
214    /// `list-sessions`
215    ListSessions(ListSessionsRequest),
216    /// `list-panes`
217    ListPanes(ListPanesRequest),
218    /// `source-file`
219    SourceFile(Box<SourceFileRequest>),
220    /// `set-option` using an open string-based option name.
221    SetOptionByName(Box<SetOptionByNameRequest>),
222    /// Extended `set-hook` mutation semantics.
223    SetHookMutation(SetHookMutationRequest),
224    /// `show-hooks`
225    ShowHooks(ShowHooksRequest),
226    /// Extended `send-keys` semantics including key-table dispatch and format expansion.
227    SendKeysExt(SendKeysExtRequest),
228    /// Extended `switch-client` semantics including `-T key-table`.
229    SwitchClientExt(SwitchClientExtRequest),
230    /// `bind-key`
231    BindKey(Box<BindKeyRequest>),
232    /// `unbind-key`
233    UnbindKey(UnbindKeyRequest),
234    /// `list-keys`
235    ListKeys(Box<ListKeysRequest>),
236    /// `send-prefix`
237    SendPrefix(SendPrefixRequest),
238    /// `clear-history`
239    ClearHistory(ClearHistoryRequest),
240    /// `copy-mode`
241    CopyMode(CopyModeRequest),
242    /// Internal detached upgrade into tmux-compatible control mode.
243    ControlMode(ControlModeRequest),
244    /// `clock-mode`
245    ClockMode(ClockModeRequest),
246    /// `show-messages`
247    ShowMessages(ShowMessagesRequest),
248    /// Extended `new-session` semantics including grouped sessions and attach-if-exists.
249    NewSessionExt(Box<NewSessionExtRequest>),
250    /// Extended `attach-session` semantics including client flags and detach-others.
251    AttachSessionExt(AttachSessionExtRequest),
252    /// Extended `switch-client` semantics including `-l`, `-n`, `-p`, and readonly toggles.
253    SwitchClientExt2(Box<SwitchClientExt2Request>),
254    /// `select-layout` with a tmux custom layout string.
255    SelectCustomLayout(SelectCustomLayoutRequest),
256    /// `select-layout -o`
257    SelectOldLayout(SelectOldLayoutRequest),
258    /// `select-layout -E`
259    SpreadLayout(SpreadLayoutRequest),
260    /// `kill-server`
261    KillServer(KillServerRequest),
262    /// `lock-server`
263    LockServer(LockServerRequest),
264    /// `lock-session`
265    LockSession(LockSessionRequest),
266    /// `lock-client`
267    LockClient(LockClientRequest),
268    /// `server-access`
269    ServerAccess(ServerAccessRequest),
270    /// `refresh-client`
271    RefreshClient(Box<RefreshClientRequest>),
272    /// `list-clients`
273    ListClients(Box<ListClientsRequest>),
274    /// `suspend-client`
275    SuspendClient(SuspendClientRequest),
276    /// Extended `detach-client` semantics including `-a`, `-s`, `-P`, and `-E`.
277    DetachClientExt(DetachClientExtRequest),
278    /// Further-extended `attach-session` semantics including `-c working-directory`.
279    AttachSessionExt2(Box<AttachSessionExt2Request>),
280    /// Further-extended `switch-client` semantics including `-c target-client` and `-Z`.
281    SwitchClientExt3(Box<SwitchClientExt3Request>),
282    /// `resize-window`
283    ResizeWindow(ResizeWindowRequest),
284    /// `respawn-window`
285    RespawnWindow(Box<RespawnWindowRequest>),
286    /// `move-pane`
287    MovePane(MovePaneRequest),
288    /// `pipe-pane`
289    PipePane(PipePaneRequest),
290    /// `respawn-pane`
291    RespawnPane(Box<RespawnPaneRequest>),
292    /// `link-window`
293    LinkWindow(LinkWindowRequest),
294    /// `unlink-window`
295    UnlinkWindow(UnlinkWindowRequest),
296    /// `select-pane -m` / `select-pane -M`
297    SelectPaneMark(SelectPaneMarkRequest),
298    /// Internal detached target resolution for tmux-style raw target text.
299    ResolveTarget(ResolveTargetRequest),
300    /// Extended `split-window` semantics including an explicit shell command.
301    SplitWindowExt(Box<SplitWindowExtRequest>),
302    /// Internal SDK/daemon version and capability negotiation.
303    Handshake(HandshakeRequest),
304    /// Internal daemon-backed structured pane snapshot endpoint.
305    PaneSnapshot(PaneSnapshotRequest),
306    /// Internal daemon-backed pane output subscription endpoint.
307    SubscribePaneOutput(SubscribePaneOutputRequest),
308    /// Internal daemon-backed pane output unsubscription endpoint.
309    UnsubscribePaneOutput(UnsubscribePaneOutputRequest),
310    /// Internal daemon-backed pane output cursor polling endpoint.
311    PaneOutputCursor(PaneOutputCursorRequest),
312    /// Internal daemon-backed SDK byte wait endpoint.
313    SdkWaitForOutput(SdkWaitForOutputRequest),
314    /// Internal daemon-backed SDK wait cancellation endpoint.
315    CancelSdkWait(CancelSdkWaitRequest),
316    /// SDK pane input endpoint with stable pane-id targeting.
317    PaneInput(PaneInputRequest),
318    /// SDK pane resize endpoint with stable pane-id targeting.
319    PaneResize(PaneResizeRequest),
320    /// SDK pane kill endpoint with stable pane-id targeting.
321    PaneKill(PaneKillRequest),
322    /// SDK pane respawn endpoint with stable pane-id targeting.
323    PaneRespawn(Box<PaneRespawnRequest>),
324    /// SDK pane snapshot endpoint with stable pane-id targeting.
325    PaneSnapshotRef(PaneSnapshotRefRequest),
326    /// SDK pane select/title endpoint with stable pane-id targeting.
327    PaneSelect(PaneSelectRequest),
328    /// SDK pane input broadcast endpoint with stable pane-id targeting.
329    PaneBroadcastInput(PaneBroadcastInputRequest),
330    /// SDK app-owned session lease create endpoint.
331    CreateSessionLease(CreateSessionLeaseRequest),
332    /// SDK app-owned session lease renewal endpoint.
333    RenewSessionLease(RenewSessionLeaseRequest),
334    /// SDK app-owned session lease release endpoint.
335    ReleaseSessionLease(ReleaseSessionLeaseRequest),
336    /// Internal daemon-backed pane output subscription endpoint with stable pane-id targeting.
337    SubscribePaneOutputRef(SubscribePaneOutputRefRequest),
338    /// Internal daemon-backed SDK byte wait endpoint with stable pane-id targeting.
339    SdkWaitForOutputRef(SdkWaitForOutputRefRequest),
340    /// Internal daemon version and activity status endpoint.
341    DaemonStatus(DaemonStatusRequest),
342    /// Internal idle-only shutdown endpoint used by seamless upgrades.
343    ShutdownIfIdle(ShutdownIfIdleRequest),
344    /// Browser-visible pane sharing command family.
345    WebShare(Box<WebShareRequest>),
346    /// `display-message` extension with target-client context.
347    DisplayMessageExt(Box<DisplayMessageExtRequest>),
348    /// `send-keys` extension with target-client context.
349    SendKeysExt2(Box<SendKeysExt2Request>),
350    /// Attach-session extension with attach-stream client capabilities.
351    AttachSessionExt3(Box<AttachSessionExt3Request>),
352    /// `split-window` with raw target text resolved server-side.
353    SplitWindowTargetAction(Box<SplitWindowTargetActionRequest>),
354    /// `resize-pane` with raw target text resolved server-side.
355    ResizePaneTargetAction(ResizePaneTargetActionRequest),
356    /// `capture-pane` with raw target text resolved server-side.
357    CapturePaneTargetAction(Box<CapturePaneTargetActionRequest>),
358}
359
360impl Request {
361    /// Returns the stable routing name for the request variant.
362    #[must_use]
363    pub const fn command_name(&self) -> &'static str {
364        match self {
365            Self::NewSession(_) => "new-session",
366            Self::HasSession(_) => "has-session",
367            Self::KillSession(_) => "kill-session",
368            Self::NewWindow(_) => "new-window",
369            Self::KillWindow(_) => "kill-window",
370            Self::SelectWindow(_) => "select-window",
371            Self::RenameWindow(_) => "rename-window",
372            Self::NextWindow(_) => "next-window",
373            Self::PreviousWindow(_) => "previous-window",
374            Self::LastWindow(_) => "last-window",
375            Self::ListWindows(_) => "list-windows",
376            Self::LinkWindow(_) => "link-window",
377            Self::MoveWindow(_) => "move-window",
378            Self::SwapWindow(_) => "swap-window",
379            Self::RotateWindow(_) => "rotate-window",
380            Self::SplitWindow(_) | Self::SplitWindowExt(_) | Self::SplitWindowTargetAction(_) => {
381                "split-window"
382            }
383            Self::SwapPane(_) => "swap-pane",
384            Self::LastPane(_) => "last-pane",
385            Self::JoinPane(_) => "join-pane",
386            Self::BreakPane(_) => "break-pane",
387            Self::KillPane(_) => "kill-pane",
388            Self::SelectLayout(_) => "select-layout",
389            Self::ResizePane(_) => "resize-pane",
390            Self::DisplayPanes(_) => "display-panes",
391            Self::SelectPane(_) | Self::SelectPaneAdjacent(_) | Self::SelectPaneMark(_) => {
392                "select-pane"
393            }
394            Self::SendKeys(_) | Self::SendKeysExt2(_) => "send-keys",
395            Self::AttachSession(_) => "attach-session",
396            Self::SwitchClient(_) => "switch-client",
397            Self::DetachClient(_) => "detach-client",
398            Self::SetOption(_) => "set-option",
399            Self::SetEnvironment(_) => "set-environment",
400            Self::SetHook(_) => "set-hook",
401            Self::NextLayout(_) => "next-layout",
402            Self::PreviousLayout(_) => "previous-layout",
403            Self::ShowOptions(_) => "show-options",
404            Self::ShowEnvironment(_) => "show-environment",
405            Self::SetBuffer(_) => "set-buffer",
406            Self::ShowBuffer(_) => "show-buffer",
407            Self::PasteBuffer(_) => "paste-buffer",
408            Self::ListBuffers(_) => "list-buffers",
409            Self::DeleteBuffer(_) => "delete-buffer",
410            Self::LoadBuffer(_) => "load-buffer",
411            Self::SaveBuffer(_) => "save-buffer",
412            Self::CapturePane(_) | Self::CapturePaneTargetAction(_) => "capture-pane",
413            Self::PaneSnapshot(_) => "pane-snapshot",
414            Self::SubscribePaneOutput(_) | Self::SubscribePaneOutputRef(_) => {
415                "subscribe-pane-output"
416            }
417            Self::UnsubscribePaneOutput(_) => "unsubscribe-pane-output",
418            Self::PaneOutputCursor(_) => "pane-output-cursor",
419            Self::SdkWaitForOutput(_) | Self::SdkWaitForOutputRef(_) => "sdk-wait-output",
420            Self::CancelSdkWait(_) => "cancel-sdk-wait",
421            Self::PaneInput(_) => "send-keys",
422            Self::PaneBroadcastInput(_) => "send-keys",
423            Self::CreateSessionLease(_) => "create-session-lease",
424            Self::RenewSessionLease(_) => "renew-session-lease",
425            Self::ReleaseSessionLease(_) => "release-session-lease",
426            Self::PaneResize(_) | Self::ResizePaneTargetAction(_) => "resize-pane",
427            Self::PaneKill(_) => "kill-pane",
428            Self::PaneRespawn(_) => "respawn-pane",
429            Self::PaneSnapshotRef(_) => "pane-snapshot",
430            Self::PaneSelect(_) => "select-pane",
431            Self::DisplayMessage(_) | Self::DisplayMessageExt(_) => "display-message",
432            Self::ResolveTarget(_) => "resolve-target",
433            Self::RunShell(_) => "run-shell",
434            Self::IfShell(_) => "if-shell",
435            Self::WaitFor(_) => "wait-for",
436            Self::RenameSession(_) => "rename-session",
437            Self::ListSessions(_) => "list-sessions",
438            Self::ListPanes(_) => "list-panes",
439            Self::SourceFile(_) => "source-file",
440            Self::UnlinkWindow(_) => "unlink-window",
441            Self::SetOptionByName(_) => "set-option",
442            Self::SetHookMutation(_) => "set-hook",
443            Self::ShowHooks(_) => "show-hooks",
444            Self::SendKeysExt(_) => "send-keys",
445            Self::SwitchClientExt(_) => "switch-client",
446            Self::BindKey(_) => "bind-key",
447            Self::UnbindKey(_) => "unbind-key",
448            Self::ListKeys(_) => "list-keys",
449            Self::SendPrefix(_) => "send-prefix",
450            Self::ClearHistory(_) => "clear-history",
451            Self::CopyMode(_) => "copy-mode",
452            Self::ControlMode(_) => "control-mode",
453            Self::ClockMode(_) => "clock-mode",
454            Self::ShowMessages(_) => "show-messages",
455            Self::NewSessionExt(_) => "new-session",
456            Self::AttachSessionExt(_) => "attach-session",
457            Self::SwitchClientExt2(_) => "switch-client",
458            Self::SelectCustomLayout(_) => "select-layout",
459            Self::SelectOldLayout(_) => "select-layout",
460            Self::SpreadLayout(_) => "select-layout",
461            Self::ResizeWindow(_) => "resize-window",
462            Self::RespawnWindow(_) => "respawn-window",
463            Self::MovePane(_) => "move-pane",
464            Self::PipePane(_) => "pipe-pane",
465            Self::RespawnPane(_) => "respawn-pane",
466            Self::KillServer(_) => "kill-server",
467            Self::LockServer(_) => "lock-server",
468            Self::LockSession(_) => "lock-session",
469            Self::LockClient(_) => "lock-client",
470            Self::ServerAccess(_) => "server-access",
471            Self::RefreshClient(_) => "refresh-client",
472            Self::ListClients(_) => "list-clients",
473            Self::SuspendClient(_) => "suspend-client",
474            Self::DetachClientExt(_) => "detach-client",
475            Self::AttachSessionExt2(_) | Self::AttachSessionExt3(_) => "attach-session",
476            Self::SwitchClientExt3(_) => "switch-client",
477            Self::Handshake(_) => "handshake",
478            Self::DaemonStatus(_) => "daemon-status",
479            Self::ShutdownIfIdle(_) => "shutdown-if-idle",
480            Self::WebShare(_) => "web-share",
481        }
482    }
483}
484
485/// Request payload for `show-messages`.
486#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
487pub struct ShowMessagesRequest {
488    /// Whether to print the server job summary.
489    pub jobs: bool,
490    /// Whether to print terminal information.
491    pub terminals: bool,
492    /// The optional target client filter used by terminal and job summaries.
493    pub target_client: Option<String>,
494}
495
496/// Request payload for `run-shell`.
497#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
498pub struct RunShellRequest {
499    /// The server-local shell command passed to `sh -c`.
500    pub command: String,
501    /// Whether the command should run fire-and-forget without output capture.
502    pub background: bool,
503    /// Whether the command should be executed as tmux commands instead of `sh -c`.
504    #[serde(default)]
505    pub as_commands: bool,
506    /// Whether stderr should be captured alongside stdout.
507    #[serde(default)]
508    pub show_stderr: bool,
509    /// Optional delay, in seconds, before the command runs.
510    #[serde(default)]
511    pub delay_seconds: Option<RunShellDelaySeconds>,
512    /// Optional explicit working directory.
513    #[serde(default)]
514    pub start_directory: Option<PathBuf>,
515    /// Optional explicit target pane used for format and session context.
516    #[serde(default)]
517    pub target: Option<PaneTarget>,
518    /// Internal source-file recursion depth inherited by queued run-shell.
519    #[serde(default)]
520    pub source_depth: Option<usize>,
521}
522
523/// Losslessly serializable `run-shell -d` seconds value with stable `Eq`.
524#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
525pub struct RunShellDelaySeconds(pub f64);
526
527impl RunShellDelaySeconds {
528    /// Returns the raw seconds value.
529    #[must_use]
530    pub const fn as_secs_f64(self) -> f64 {
531        self.0
532    }
533}
534
535impl PartialEq for RunShellDelaySeconds {
536    fn eq(&self, other: &Self) -> bool {
537        self.0.to_bits() == other.0.to_bits()
538    }
539}
540
541impl Eq for RunShellDelaySeconds {}
542
543/// Request payload for `if-shell`.
544#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
545pub struct IfShellRequest {
546    /// The condition string expanded through the shared formatter before evaluation.
547    pub condition: String,
548    /// Whether to evaluate the expanded condition with format truthiness instead of `sh -c`.
549    pub format_mode: bool,
550    /// The nested RMUX command string dispatched when the condition is true.
551    pub then_command: String,
552    /// The optional nested RMUX command string dispatched when the condition is false.
553    pub else_command: Option<String>,
554    /// Optional exact target used as shared-format context.
555    pub target: Option<Target>,
556    /// The caller working directory used to resolve nested relative file paths.
557    pub caller_cwd: Option<PathBuf>,
558    /// Whether the condition should be evaluated asynchronously.
559    #[serde(default)]
560    pub background: bool,
561}
562
563/// Request payload for `source-file`.
564#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
565pub struct SourceFileRequest {
566    /// The path arguments to expand, glob, parse, and optionally execute.
567    pub paths: Vec<String>,
568    /// Whether missing files and glob misses should be suppressed.
569    pub quiet: bool,
570    /// Whether to parse only without executing parsed commands.
571    pub parse_only: bool,
572    /// Whether parsed commands should be printed to stdout.
573    pub verbose: bool,
574    /// Whether each path argument should be format-expanded before globbing.
575    pub expand_paths: bool,
576    /// Optional pane target used as the `-F` format context.
577    pub target: Option<PaneTarget>,
578    /// The caller working directory used to resolve relative paths.
579    pub caller_cwd: Option<PathBuf>,
580    /// Content read from client stdin for `source-file -`.
581    pub stdin: Option<String>,
582}
583
584/// Request payload for `unlink-window`.
585#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
586pub struct UnlinkWindowRequest {
587    /// The window slot to remove.
588    pub target: WindowTarget,
589    /// Whether removing the final link is allowed (`-k`).
590    #[serde(default)]
591    pub kill_if_last: bool,
592}
593
594/// The supported `wait-for` operation modes.
595#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
596pub enum WaitForMode {
597    /// Wait for the next signal on the named channel.
598    Wait,
599    /// Signal all current plain waiters on the named channel.
600    Signal,
601    /// Acquire the named server-local lock, waiting in FIFO order when held.
602    Lock,
603    /// Release the named server-local lock.
604    Unlock,
605}
606
607/// Request payload for `wait-for`.
608#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
609pub struct WaitForRequest {
610    /// The server-local wait channel name.
611    pub channel: String,
612    /// The selected wait operation.
613    pub mode: WaitForMode,
614}
615
616/// Request payload for a daemon-backed SDK byte wait.
617///
618/// This is intentionally distinct from tmux-compatible [`WaitForRequest`].
619/// SDK waits are pane-output waits with typed IDs used only for cancellation
620/// and teardown bookkeeping; they never signal or lock tmux `wait-for`
621/// channels.
622#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
623pub struct SdkWaitForOutputRequest {
624    /// Opaque SDK transport owner for this wait.
625    pub owner_id: SdkWaitOwnerId,
626    /// Wait ID allocated by the SDK under `owner_id`.
627    pub wait_id: SdkWaitId,
628    /// Pane whose raw output stream is observed.
629    pub target: PaneTarget,
630    /// Raw byte sequence to search for in pane output.
631    pub bytes: Vec<u8>,
632    /// Cursor position used when arming the wait.
633    pub start: PaneOutputSubscriptionStart,
634}
635
636/// Request payload for a daemon-backed SDK byte wait by slot or stable id.
637#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
638pub struct SdkWaitForOutputRefRequest {
639    /// Opaque SDK transport owner for this wait.
640    pub owner_id: SdkWaitOwnerId,
641    /// Wait ID allocated by the SDK under `owner_id`.
642    pub wait_id: SdkWaitId,
643    /// Pane whose raw output stream is observed.
644    pub target: PaneTargetRef,
645    /// Raw byte sequence to search for in pane output.
646    pub bytes: Vec<u8>,
647    /// Cursor position used when arming the wait.
648    pub start: PaneOutputSubscriptionStart,
649}
650
651/// Request payload for best-effort cancellation of a daemon-backed SDK wait.
652#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
653pub struct CancelSdkWaitRequest {
654    /// Opaque SDK transport owner for the wait being cancelled.
655    pub owner_id: SdkWaitOwnerId,
656    /// Wait ID allocated by the SDK under `owner_id`.
657    pub wait_id: SdkWaitId,
658}
659
660/// Request payload for `list-panes`.
661#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
662pub struct ListPanesRequest {
663    /// The exact target session name.
664    pub target: SessionName,
665    /// Optional exact target window index.
666    #[serde(default)]
667    pub target_window_index: Option<u32>,
668    /// An optional server-side format template.
669    pub format: Option<String>,
670}
671
672#[cfg(test)]
673#[path = "request/compat_tests.rs"]
674mod compat_tests;
675
676#[cfg(test)]
677mod tests {
678    use super::*;
679    use crate::{
680        OptionScopeSelector, PaneTarget, ScopeSelector, SelectPaneDirection, SetOptionMode,
681        SplitDirection, WebShareScope,
682    };
683    use serde::Serialize;
684    use std::mem::size_of;
685
686    fn alpha() -> SessionName {
687        SessionName::new("alpha").expect("valid session")
688    }
689
690    fn pane() -> PaneTarget {
691        PaneTarget::new(alpha(), 0)
692    }
693
694    #[test]
695    fn request_command_names_cover_extended_aliases_and_internal_tags() {
696        assert_eq!(
697            Request::NewSessionExt(Box::new(NewSessionExtRequest {
698                session_name: None,
699                working_directory: None,
700                detached: true,
701                size: None,
702                environment: None,
703                group_target: None,
704                attach_if_exists: false,
705                detach_other_clients: false,
706                kill_other_clients: false,
707                flags: None,
708                window_name: None,
709                print_session_info: false,
710                print_format: None,
711                command: None,
712                process_command: None,
713                client_environment: None,
714                skip_environment_update: false,
715            }))
716            .command_name(),
717            "new-session"
718        );
719        assert_eq!(
720            Request::SplitWindowExt(Box::new(SplitWindowExtRequest {
721                target: SplitWindowTarget::Pane(pane()),
722                direction: SplitDirection::Vertical,
723                before: false,
724                environment: None,
725                command: None,
726                process_command: None,
727                start_directory: None,
728                keep_alive_on_exit: None,
729                detached: false,
730                size: None,
731                preserve_zoom: false,
732                full_size: false,
733                stdin_payload: None,
734            }))
735            .command_name(),
736            "split-window"
737        );
738        assert_eq!(
739            Request::SelectPaneAdjacent(SelectPaneAdjacentRequest {
740                target: pane(),
741                direction: SelectPaneDirection::Right,
742                preserve_zoom: false,
743            })
744            .command_name(),
745            "select-pane"
746        );
747        assert_eq!(
748            Request::SelectPaneMark(SelectPaneMarkRequest {
749                target: pane(),
750                clear: false,
751                title: None,
752            })
753            .command_name(),
754            "select-pane"
755        );
756        assert_eq!(
757            Request::SetOptionByName(Box::new(SetOptionByNameRequest {
758                scope: OptionScopeSelector::ServerGlobal,
759                name: "status".to_owned(),
760                value: Some("on".to_owned()),
761                mode: SetOptionMode::Replace,
762                only_if_unset: false,
763                unset: false,
764                unset_pane_overrides: false,
765                format: false,
766                format_target: None,
767            }))
768            .command_name(),
769            "set-option"
770        );
771        assert_eq!(
772            Request::SetHookMutation(SetHookMutationRequest {
773                scope: ScopeSelector::Global,
774                hook: crate::HookName::AfterNewSession,
775                command: None,
776                lifecycle: crate::HookLifecycle::Persistent,
777                append: false,
778                unset: true,
779                run_immediately: false,
780                index: None,
781            })
782            .command_name(),
783            "set-hook"
784        );
785        assert_eq!(
786            Request::AttachSessionExt2(Box::new(AttachSessionExt2Request {
787                target: Some(alpha()),
788                target_spec: None,
789                detach_other_clients: false,
790                kill_other_clients: false,
791                read_only: false,
792                skip_environment_update: false,
793                flags: None,
794                working_directory: None,
795                client_terminal: crate::ClientTerminalContext::default(),
796                client_size: None,
797            }))
798            .command_name(),
799            "attach-session"
800        );
801        assert_eq!(
802            Request::AttachSessionExt3(Box::new(AttachSessionExt3Request::from_ext2(
803                AttachSessionExt2Request {
804                    target: Some(alpha()),
805                    target_spec: None,
806                    detach_other_clients: false,
807                    kill_other_clients: false,
808                    read_only: false,
809                    skip_environment_update: false,
810                    flags: None,
811                    working_directory: None,
812                    client_terminal: crate::ClientTerminalContext::default(),
813                    client_size: None,
814                },
815                vec![crate::CAPABILITY_ATTACH_RENDER.to_owned()],
816            )))
817            .command_name(),
818            "attach-session"
819        );
820        assert_eq!(
821            Request::SwitchClientExt3(Box::new(SwitchClientExt3Request {
822                target_client: None,
823                target: Some("alpha:0.0".to_owned()),
824                key_table: None,
825                last_session: false,
826                next_session: false,
827                previous_session: false,
828                toggle_read_only: false,
829                sort_order: None,
830                skip_environment_update: false,
831                zoom: false,
832            }))
833            .command_name(),
834            "switch-client"
835        );
836        assert_eq!(
837            Request::ResolveTarget(ResolveTargetRequest {
838                target: Some("alpha:0.0".to_owned()),
839                target_type: ResolveTargetType::Pane,
840                window_index: false,
841                prefer_unattached: false,
842            })
843            .command_name(),
844            "resolve-target"
845        );
846        assert_eq!(
847            Request::Handshake(HandshakeRequest::current()).command_name(),
848            "handshake"
849        );
850        assert_eq!(
851            Request::SdkWaitForOutput(SdkWaitForOutputRequest {
852                owner_id: SdkWaitOwnerId::new(1),
853                wait_id: SdkWaitId::new(1),
854                target: pane(),
855                bytes: b"ready".to_vec(),
856                start: PaneOutputSubscriptionStart::Now,
857            })
858            .command_name(),
859            "sdk-wait-output"
860        );
861        assert_eq!(
862            Request::CancelSdkWait(CancelSdkWaitRequest {
863                owner_id: SdkWaitOwnerId::new(1),
864                wait_id: SdkWaitId::new(1),
865            })
866            .command_name(),
867            "cancel-sdk-wait"
868        );
869        assert_eq!(
870            Request::WaitFor(WaitForRequest {
871                channel: "ready".to_owned(),
872                mode: WaitForMode::Wait,
873            })
874            .command_name(),
875            "wait-for"
876        );
877    }
878
879    #[test]
880    fn pr6g_request_boxing_keeps_request_size_bounded() {
881        let request_size = size_of::<Request>();
882        let box_size = size_of::<Box<NewSessionExtRequest>>();
883
884        eprintln!("PR6G size baseline: Request={request_size} Box<T>={box_size}");
885        eprintln!(
886            "PR6G candidate sizes: NewSessionExt={} NewWindow={} SetOptionByName={} \
887             AttachSessionExt3={} SwitchClientExt3={} SendKeysExt2={} SourceFile={} WebShare={}",
888            size_of::<NewSessionExtRequest>(),
889            size_of::<NewWindowRequest>(),
890            size_of::<SetOptionByNameRequest>(),
891            size_of::<AttachSessionExt3Request>(),
892            size_of::<SwitchClientExt3Request>(),
893            size_of::<SendKeysExt2Request>(),
894            size_of::<SourceFileRequest>(),
895            size_of::<WebShareRequest>(),
896        );
897        let mut remaining = [
898            ("AttachSessionExt2", size_of::<AttachSessionExt2Request>()),
899            ("BindKey", size_of::<BindKeyRequest>()),
900            ("BreakPane", size_of::<BreakPaneRequest>()),
901            ("CapturePane", size_of::<CapturePaneRequest>()),
902            ("ClearHistory", size_of::<ClearHistoryRequest>()),
903            ("ClockMode", size_of::<ClockModeRequest>()),
904            ("ControlMode", size_of::<ControlModeRequest>()),
905            ("CopyMode", size_of::<CopyModeRequest>()),
906            ("CreateSessionLease", size_of::<CreateSessionLeaseRequest>()),
907            ("DeleteBuffer", size_of::<DeleteBufferRequest>()),
908            ("DetachClientExt", size_of::<DetachClientExtRequest>()),
909            ("DisplayPanes", size_of::<DisplayPanesRequest>()),
910            ("DisplayMessage", size_of::<DisplayMessageRequest>()),
911            ("DisplayMessageExt", size_of::<DisplayMessageExtRequest>()),
912            ("Handshake", size_of::<HandshakeRequest>()),
913            ("IfShell", size_of::<IfShellRequest>()),
914            ("JoinPane", size_of::<JoinPaneRequest>()),
915            ("KillPane", size_of::<KillPaneRequest>()),
916            ("KillWindow", size_of::<KillWindowRequest>()),
917            ("LinkWindow", size_of::<LinkWindowRequest>()),
918            ("ListClients", size_of::<ListClientsRequest>()),
919            ("ListKeys", size_of::<ListKeysRequest>()),
920            ("ListPanes", size_of::<ListPanesRequest>()),
921            ("ListSessions", size_of::<ListSessionsRequest>()),
922            ("ListWindows", size_of::<ListWindowsRequest>()),
923            ("LoadBuffer", size_of::<LoadBufferRequest>()),
924            ("MovePane", size_of::<MovePaneRequest>()),
925            ("MoveWindow", size_of::<MoveWindowRequest>()),
926            ("NewSession", size_of::<NewSessionRequest>()),
927            ("PasteBuffer", size_of::<PasteBufferRequest>()),
928            ("PaneInput", size_of::<PaneInputRequest>()),
929            ("PaneKill", size_of::<PaneKillRequest>()),
930            ("PaneResize", size_of::<PaneResizeRequest>()),
931            ("PaneRespawn", size_of::<PaneRespawnRequest>()),
932            ("PaneSelect", size_of::<PaneSelectRequest>()),
933            ("PaneSnapshot", size_of::<PaneSnapshotRequest>()),
934            ("PaneSnapshotRef", size_of::<PaneSnapshotRefRequest>()),
935            ("PipePane", size_of::<PipePaneRequest>()),
936            ("RefreshClient", size_of::<RefreshClientRequest>()),
937            ("RespawnPane", size_of::<RespawnPaneRequest>()),
938            ("RespawnWindow", size_of::<RespawnWindowRequest>()),
939            ("ResizePane", size_of::<ResizePaneRequest>()),
940            ("ResizeWindow", size_of::<ResizeWindowRequest>()),
941            ("RotateWindow", size_of::<RotateWindowRequest>()),
942            ("RunShell", size_of::<RunShellRequest>()),
943            ("SaveBuffer", size_of::<SaveBufferRequest>()),
944            ("SelectCustomLayout", size_of::<SelectCustomLayoutRequest>()),
945            ("SelectLayout", size_of::<SelectLayoutRequest>()),
946            ("SelectPane", size_of::<SelectPaneRequest>()),
947            ("SelectPaneAdjacent", size_of::<SelectPaneAdjacentRequest>()),
948            ("SelectPaneMark", size_of::<SelectPaneMarkRequest>()),
949            ("SetEnvironment", size_of::<SetEnvironmentRequest>()),
950            ("SetBuffer", size_of::<SetBufferRequest>()),
951            ("SetHook", size_of::<SetHookRequest>()),
952            ("SetHookMutation", size_of::<SetHookMutationRequest>()),
953            ("SetOption", size_of::<SetOptionRequest>()),
954            ("ShowBuffer", size_of::<ShowBufferRequest>()),
955            ("ShowEnvironment", size_of::<ShowEnvironmentRequest>()),
956            ("ShowHooks", size_of::<ShowHooksRequest>()),
957            ("ShowMessages", size_of::<ShowMessagesRequest>()),
958            ("ShowOptions", size_of::<ShowOptionsRequest>()),
959            ("SplitWindow", size_of::<SplitWindowRequest>()),
960            ("SplitWindowExt", size_of::<SplitWindowExtRequest>()),
961            (
962                "SubscribePaneOutput",
963                size_of::<SubscribePaneOutputRequest>(),
964            ),
965            (
966                "SubscribePaneOutputRef",
967                size_of::<SubscribePaneOutputRefRequest>(),
968            ),
969            ("SwitchClientExt2", size_of::<SwitchClientExt2Request>()),
970        ];
971        remaining.sort_by_key(|(_, size)| std::cmp::Reverse(*size));
972        eprintln!("PR6G largest remaining candidates: {remaining:?}");
973
974        assert!(
975            request_size <= 96,
976            "Request grew past the PR6G boxed payload budget: {request_size}"
977        );
978        assert_eq!(box_size, size_of::<usize>());
979    }
980
981    #[test]
982    fn pr6g_boxed_payloads_are_bincode_transparent_for_request_candidates() {
983        assert_box_serializes_like_value(NewSessionExtRequest {
984            session_name: Some(alpha()),
985            working_directory: Some("/tmp".to_owned()),
986            detached: true,
987            size: Some(crate::TerminalSize {
988                cols: 120,
989                rows: 40,
990            }),
991            environment: Some(vec!["A=B".to_owned()]),
992            group_target: None,
993            attach_if_exists: false,
994            detach_other_clients: false,
995            kill_other_clients: false,
996            flags: Some(vec!["read-only".to_owned()]),
997            window_name: Some("main".to_owned()),
998            print_session_info: true,
999            print_format: Some("#{session_name}".to_owned()),
1000            command: Some(vec!["sh".to_owned(), "-lc".to_owned(), "true".to_owned()]),
1001            process_command: None,
1002            client_environment: Some(vec!["PATH=/bin".to_owned()]),
1003            skip_environment_update: false,
1004        });
1005        assert_box_serializes_like_value(SetOptionByNameRequest {
1006            scope: OptionScopeSelector::ServerGlobal,
1007            name: "@plugin".to_owned(),
1008            value: Some("enabled".to_owned()),
1009            mode: SetOptionMode::Replace,
1010            only_if_unset: false,
1011            unset: false,
1012            unset_pane_overrides: false,
1013            format: true,
1014            format_target: Some(crate::Target::Session(alpha())),
1015        });
1016        assert_box_serializes_like_value(AttachSessionExt3Request::from_ext2(
1017            AttachSessionExt2Request {
1018                target: Some(alpha()),
1019                target_spec: Some("alpha:0.0".to_owned()),
1020                detach_other_clients: false,
1021                kill_other_clients: false,
1022                read_only: true,
1023                skip_environment_update: false,
1024                flags: Some(vec!["active-pane".to_owned()]),
1025                working_directory: Some("/tmp".to_owned()),
1026                client_terminal: crate::ClientTerminalContext::default(),
1027                client_size: Some(crate::TerminalSize { cols: 80, rows: 24 }),
1028            },
1029            vec![crate::CAPABILITY_ATTACH_RENDER.to_owned()],
1030        ));
1031        assert_box_serializes_like_value(WebShareRequest::Create(CreateWebShareRequest {
1032            scope: WebShareScope::Session(alpha()),
1033            public_base_url: Some("https://example.invalid".to_owned()),
1034            tunnel_provider: None,
1035            frontend_url: Some("https://share.example.invalid".to_owned()),
1036            ttl_seconds: Some(60),
1037            expires_at_unix: None,
1038            max_spectators: Some(8),
1039            max_operators: Some(1),
1040            url_options: WebShareUrlOptions::default(),
1041            require_pin: true,
1042            operator_pin: Some("123456".to_owned()),
1043            spectator_pin: Some("654321".to_owned()),
1044            terminal_palette: None,
1045            operator: true,
1046            spectator: true,
1047            controls: true,
1048            kill_session_on_expire: false,
1049        }));
1050    }
1051
1052    fn assert_box_serializes_like_value<T>(value: T)
1053    where
1054        T: Clone + Serialize,
1055    {
1056        let boxed = Box::new(value.clone());
1057        assert_eq!(
1058            bincode::serialize(&value).expect("value encodes"),
1059            bincode::serialize(&boxed).expect("boxed value encodes"),
1060            "Box<T> must be transparent before PR6G boxes enum variants"
1061        );
1062    }
1063}