Skip to main content

rmux_proto/request/
session.rs

1use serde::de::{self, MapAccess, SeqAccess, Visitor};
2use serde::{Deserialize, Deserializer, Serialize};
3
4use crate::{ProcessCommand, SessionName, TerminalSize};
5
6use super::compat::{compat_next_element, required_next};
7
8/// Request payload for `new-session`.
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10pub struct NewSessionRequest {
11    /// The exact session name to create.
12    pub session_name: SessionName,
13    /// Whether the session should remain detached after creation.
14    pub detached: bool,
15    /// The initial pane geometry, when explicitly requested.
16    pub size: Option<TerminalSize>,
17    /// Optional per-spawn environment overrides in `NAME=VALUE` form.
18    #[serde(default)]
19    pub environment: Option<Vec<String>>,
20}
21
22/// Extended request payload for `new-session`.
23#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
24pub struct NewSessionExtRequest {
25    /// The optional exact session name to create.
26    pub session_name: Option<SessionName>,
27    /// Optional tmux format-expanded start directory for the new session.
28    #[serde(default)]
29    pub working_directory: Option<String>,
30    /// Whether the session should remain detached after creation.
31    pub detached: bool,
32    /// The initial pane geometry, when explicitly requested.
33    pub size: Option<TerminalSize>,
34    /// Optional per-spawn environment overrides in `NAME=VALUE` form.
35    #[serde(default)]
36    pub environment: Option<Vec<String>>,
37    /// The optional target session or group name for grouped-session creation.
38    #[serde(default)]
39    pub group_target: Option<SessionName>,
40    /// Whether an existing target session should be attached instead of erroring.
41    #[serde(default)]
42    pub attach_if_exists: bool,
43    /// Whether other attached clients should be detached before attaching.
44    #[serde(default)]
45    pub detach_other_clients: bool,
46    /// Whether other attached clients should be detached and terminated.
47    #[serde(default)]
48    pub kill_other_clients: bool,
49    /// Optional tmux client-flag names such as `read-only` or `active-pane`.
50    #[serde(default)]
51    pub flags: Option<Vec<String>>,
52    /// The optional initial active-window name for standalone session creation.
53    #[serde(default)]
54    pub window_name: Option<String>,
55    /// Whether the created session should print formatted session information.
56    #[serde(default)]
57    pub print_session_info: bool,
58    /// The optional format template used when printing session information.
59    #[serde(default)]
60    pub print_format: Option<String>,
61    /// Legacy optional shell command argv. A single argument is executed via
62    /// `$SHELL -c`.
63    #[serde(default)]
64    pub command: Option<Vec<String>>,
65    /// Explicit process launch mode for the initial pane.
66    #[serde(default)]
67    pub process_command: Option<ProcessCommand>,
68    /// Full invoking client environment in `NAME=VALUE` form.
69    #[serde(default)]
70    pub client_environment: Option<Vec<String>>,
71}
72
73impl<'de> Deserialize<'de> for NewSessionExtRequest {
74    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
75    where
76        D: Deserializer<'de>,
77    {
78        deserializer.deserialize_struct(
79            "NewSessionExtRequest",
80            &[
81                "session_name",
82                "working_directory",
83                "detached",
84                "size",
85                "environment",
86                "group_target",
87                "attach_if_exists",
88                "detach_other_clients",
89                "kill_other_clients",
90                "flags",
91                "window_name",
92                "print_session_info",
93                "print_format",
94                "command",
95                "process_command",
96                "client_environment",
97            ],
98            NewSessionExtRequestVisitor,
99        )
100    }
101}
102
103struct NewSessionExtRequestVisitor;
104
105impl<'de> Visitor<'de> for NewSessionExtRequestVisitor {
106    type Value = NewSessionExtRequest;
107
108    fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109        formatter.write_str("a new-session extended request")
110    }
111
112    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
113    where
114        A: SeqAccess<'de>,
115    {
116        let session_name = required_next(&mut seq, 0, &self)?;
117        let working_directory = required_next(&mut seq, 1, &self)?;
118        let detached = required_next(&mut seq, 2, &self)?;
119        let size = required_next(&mut seq, 3, &self)?;
120        let environment = required_next(&mut seq, 4, &self)?;
121        let group_target = required_next(&mut seq, 5, &self)?;
122        let attach_if_exists = required_next(&mut seq, 6, &self)?;
123        let detach_other_clients = required_next(&mut seq, 7, &self)?;
124        let kill_other_clients = required_next(&mut seq, 8, &self)?;
125        let flags = required_next(&mut seq, 9, &self)?;
126        let window_name = required_next(&mut seq, 10, &self)?;
127        let print_session_info = required_next(&mut seq, 11, &self)?;
128        let print_format = required_next(&mut seq, 12, &self)?;
129        let command = required_next(&mut seq, 13, &self)?;
130        let process_command = compat_next_element(&mut seq)?;
131        let client_environment = compat_next_element(&mut seq)?;
132
133        Ok(NewSessionExtRequest {
134            session_name,
135            working_directory,
136            detached,
137            size,
138            environment,
139            group_target,
140            attach_if_exists,
141            detach_other_clients,
142            kill_other_clients,
143            flags,
144            window_name,
145            print_session_info,
146            print_format,
147            command,
148            process_command,
149            client_environment,
150        })
151    }
152
153    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
154    where
155        A: MapAccess<'de>,
156    {
157        let mut session_name = None;
158        let mut working_directory = None;
159        let mut detached = None;
160        let mut size = None;
161        let mut environment = None;
162        let mut group_target = None;
163        let mut attach_if_exists = None;
164        let mut detach_other_clients = None;
165        let mut kill_other_clients = None;
166        let mut flags = None;
167        let mut window_name = None;
168        let mut print_session_info = None;
169        let mut print_format = None;
170        let mut command = None;
171        let mut process_command = None;
172        let mut client_environment = None;
173
174        while let Some(key) = map.next_key::<String>()? {
175            match key.as_str() {
176                "session_name" => session_name = Some(map.next_value()?),
177                "working_directory" => working_directory = Some(map.next_value()?),
178                "detached" => detached = Some(map.next_value()?),
179                "size" => size = Some(map.next_value()?),
180                "environment" => environment = Some(map.next_value()?),
181                "group_target" => group_target = Some(map.next_value()?),
182                "attach_if_exists" => attach_if_exists = Some(map.next_value()?),
183                "detach_other_clients" => detach_other_clients = Some(map.next_value()?),
184                "kill_other_clients" => kill_other_clients = Some(map.next_value()?),
185                "flags" => flags = Some(map.next_value()?),
186                "window_name" => window_name = Some(map.next_value()?),
187                "print_session_info" => print_session_info = Some(map.next_value()?),
188                "print_format" => print_format = Some(map.next_value()?),
189                "command" => command = Some(map.next_value()?),
190                "process_command" => process_command = Some(map.next_value()?),
191                "client_environment" => client_environment = Some(map.next_value()?),
192                _ => {
193                    let _: de::IgnoredAny = map.next_value()?;
194                }
195            }
196        }
197
198        Ok(NewSessionExtRequest {
199            session_name: session_name.ok_or_else(|| de::Error::missing_field("session_name"))?,
200            working_directory: working_directory
201                .ok_or_else(|| de::Error::missing_field("working_directory"))?,
202            detached: detached.ok_or_else(|| de::Error::missing_field("detached"))?,
203            size: size.ok_or_else(|| de::Error::missing_field("size"))?,
204            environment: environment.ok_or_else(|| de::Error::missing_field("environment"))?,
205            group_target: group_target.ok_or_else(|| de::Error::missing_field("group_target"))?,
206            attach_if_exists: attach_if_exists
207                .ok_or_else(|| de::Error::missing_field("attach_if_exists"))?,
208            detach_other_clients: detach_other_clients
209                .ok_or_else(|| de::Error::missing_field("detach_other_clients"))?,
210            kill_other_clients: kill_other_clients
211                .ok_or_else(|| de::Error::missing_field("kill_other_clients"))?,
212            flags: flags.ok_or_else(|| de::Error::missing_field("flags"))?,
213            window_name: window_name.ok_or_else(|| de::Error::missing_field("window_name"))?,
214            print_session_info: print_session_info
215                .ok_or_else(|| de::Error::missing_field("print_session_info"))?,
216            print_format: print_format.ok_or_else(|| de::Error::missing_field("print_format"))?,
217            command: command.ok_or_else(|| de::Error::missing_field("command"))?,
218            process_command: process_command.unwrap_or_default(),
219            client_environment: client_environment.unwrap_or_default(),
220        })
221    }
222}
223
224/// Request payload for `has-session`.
225#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
226pub struct HasSessionRequest {
227    /// The exact target session name.
228    pub target: SessionName,
229}
230
231/// Request payload for `kill-session`.
232#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
233pub struct KillSessionRequest {
234    /// The exact target session name.
235    pub target: SessionName,
236    /// Whether every other session should be destroyed instead of the target session.
237    #[serde(default)]
238    pub kill_all_except_target: bool,
239    /// Whether the target session's window alert flags should be cleared instead of destroying it.
240    #[serde(default)]
241    pub clear_alerts: bool,
242}
243
244/// Request payload for creating an app-owner lease for one session.
245#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
246pub struct CreateSessionLeaseRequest {
247    /// Session kept alive only while the owner renews this lease.
248    pub session_name: SessionName,
249    /// Requested lease time-to-live in milliseconds.
250    pub ttl_millis: u64,
251}
252
253/// Request payload for renewing an app-owner session lease.
254#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
255pub struct RenewSessionLeaseRequest {
256    /// Leased session name.
257    pub session_name: SessionName,
258    /// Server-issued lease token.
259    pub token: u64,
260    /// Requested renewed time-to-live in milliseconds.
261    pub ttl_millis: u64,
262}
263
264/// Request payload for releasing an app-owner session lease.
265#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
266pub struct ReleaseSessionLeaseRequest {
267    /// Leased session name.
268    pub session_name: SessionName,
269    /// Server-issued lease token.
270    pub token: u64,
271}
272
273/// Request payload for `rename-session`.
274#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
275pub struct RenameSessionRequest {
276    /// The exact existing session name.
277    pub target: SessionName,
278    /// The validated destination session name.
279    pub new_name: SessionName,
280}
281
282/// Request payload for `list-sessions`.
283#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
284pub struct ListSessionsRequest {
285    /// An optional server-side format template.
286    pub format: Option<String>,
287    /// An optional server-side filter expression.
288    #[serde(default)]
289    pub filter: Option<String>,
290    /// The optional tmux sort order name.
291    #[serde(default)]
292    pub sort_order: Option<String>,
293    /// Whether the selected sort order should be reversed.
294    #[serde(default)]
295    pub reversed: bool,
296}