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    /// Whether session creation should skip updating from the invoking client environment.
72    #[serde(default)]
73    pub skip_environment_update: bool,
74}
75
76impl<'de> Deserialize<'de> for NewSessionExtRequest {
77    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
78    where
79        D: Deserializer<'de>,
80    {
81        deserializer.deserialize_struct(
82            "NewSessionExtRequest",
83            &[
84                "session_name",
85                "working_directory",
86                "detached",
87                "size",
88                "environment",
89                "group_target",
90                "attach_if_exists",
91                "detach_other_clients",
92                "kill_other_clients",
93                "flags",
94                "window_name",
95                "print_session_info",
96                "print_format",
97                "command",
98                "process_command",
99                "client_environment",
100                "skip_environment_update",
101            ],
102            NewSessionExtRequestVisitor,
103        )
104    }
105}
106
107struct NewSessionExtRequestVisitor;
108
109impl<'de> Visitor<'de> for NewSessionExtRequestVisitor {
110    type Value = NewSessionExtRequest;
111
112    fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        formatter.write_str("a new-session extended request")
114    }
115
116    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
117    where
118        A: SeqAccess<'de>,
119    {
120        let session_name = required_next(&mut seq, 0, &self)?;
121        let working_directory = required_next(&mut seq, 1, &self)?;
122        let detached = required_next(&mut seq, 2, &self)?;
123        let size = required_next(&mut seq, 3, &self)?;
124        let environment = required_next(&mut seq, 4, &self)?;
125        let group_target = required_next(&mut seq, 5, &self)?;
126        let attach_if_exists = required_next(&mut seq, 6, &self)?;
127        let detach_other_clients = required_next(&mut seq, 7, &self)?;
128        let kill_other_clients = required_next(&mut seq, 8, &self)?;
129        let flags = required_next(&mut seq, 9, &self)?;
130        let window_name = required_next(&mut seq, 10, &self)?;
131        let print_session_info = required_next(&mut seq, 11, &self)?;
132        let print_format = required_next(&mut seq, 12, &self)?;
133        let command = required_next(&mut seq, 13, &self)?;
134        let process_command = compat_next_element(&mut seq)?;
135        let client_environment = compat_next_element(&mut seq)?;
136        let skip_environment_update: bool = compat_next_element(&mut seq)?;
137
138        Ok(NewSessionExtRequest {
139            session_name,
140            working_directory,
141            detached,
142            size,
143            environment,
144            group_target,
145            attach_if_exists,
146            detach_other_clients,
147            kill_other_clients,
148            flags,
149            window_name,
150            print_session_info,
151            print_format,
152            command,
153            process_command,
154            client_environment,
155            skip_environment_update,
156        })
157    }
158
159    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
160    where
161        A: MapAccess<'de>,
162    {
163        let mut session_name = None;
164        let mut working_directory = None;
165        let mut detached = None;
166        let mut size = None;
167        let mut environment = None;
168        let mut group_target = None;
169        let mut attach_if_exists = None;
170        let mut detach_other_clients = None;
171        let mut kill_other_clients = None;
172        let mut flags = None;
173        let mut window_name = None;
174        let mut print_session_info = None;
175        let mut print_format = None;
176        let mut command = None;
177        let mut process_command = None;
178        let mut client_environment = None;
179        let mut skip_environment_update = None;
180
181        while let Some(key) = map.next_key::<String>()? {
182            match key.as_str() {
183                "session_name" => session_name = Some(map.next_value()?),
184                "working_directory" => working_directory = Some(map.next_value()?),
185                "detached" => detached = Some(map.next_value()?),
186                "size" => size = Some(map.next_value()?),
187                "environment" => environment = Some(map.next_value()?),
188                "group_target" => group_target = Some(map.next_value()?),
189                "attach_if_exists" => attach_if_exists = Some(map.next_value()?),
190                "detach_other_clients" => detach_other_clients = Some(map.next_value()?),
191                "kill_other_clients" => kill_other_clients = Some(map.next_value()?),
192                "flags" => flags = Some(map.next_value()?),
193                "window_name" => window_name = Some(map.next_value()?),
194                "print_session_info" => print_session_info = Some(map.next_value()?),
195                "print_format" => print_format = Some(map.next_value()?),
196                "command" => command = Some(map.next_value()?),
197                "process_command" => process_command = Some(map.next_value()?),
198                "client_environment" => client_environment = Some(map.next_value()?),
199                "skip_environment_update" => skip_environment_update = Some(map.next_value()?),
200                _ => {
201                    let _: de::IgnoredAny = map.next_value()?;
202                }
203            }
204        }
205
206        Ok(NewSessionExtRequest {
207            session_name: session_name.ok_or_else(|| de::Error::missing_field("session_name"))?,
208            working_directory: working_directory
209                .ok_or_else(|| de::Error::missing_field("working_directory"))?,
210            detached: detached.ok_or_else(|| de::Error::missing_field("detached"))?,
211            size: size.ok_or_else(|| de::Error::missing_field("size"))?,
212            environment: environment.ok_or_else(|| de::Error::missing_field("environment"))?,
213            group_target: group_target.ok_or_else(|| de::Error::missing_field("group_target"))?,
214            attach_if_exists: attach_if_exists
215                .ok_or_else(|| de::Error::missing_field("attach_if_exists"))?,
216            detach_other_clients: detach_other_clients
217                .ok_or_else(|| de::Error::missing_field("detach_other_clients"))?,
218            kill_other_clients: kill_other_clients
219                .ok_or_else(|| de::Error::missing_field("kill_other_clients"))?,
220            flags: flags.ok_or_else(|| de::Error::missing_field("flags"))?,
221            window_name: window_name.ok_or_else(|| de::Error::missing_field("window_name"))?,
222            print_session_info: print_session_info
223                .ok_or_else(|| de::Error::missing_field("print_session_info"))?,
224            print_format: print_format.ok_or_else(|| de::Error::missing_field("print_format"))?,
225            command: command.ok_or_else(|| de::Error::missing_field("command"))?,
226            process_command: process_command.unwrap_or_default(),
227            client_environment: client_environment.unwrap_or_default(),
228            skip_environment_update: skip_environment_update.unwrap_or_default(),
229        })
230    }
231}
232
233/// Request payload for `has-session`.
234#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
235pub struct HasSessionRequest {
236    /// The exact target session name.
237    pub target: SessionName,
238}
239
240/// Request payload for `kill-session`.
241#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
242pub struct KillSessionRequest {
243    /// The exact target session name.
244    pub target: SessionName,
245    /// Whether every other session should be destroyed instead of the target session.
246    #[serde(default)]
247    pub kill_all_except_target: bool,
248    /// Whether the target session's window alert flags should be cleared instead of destroying it.
249    #[serde(default)]
250    pub clear_alerts: bool,
251}
252
253/// Request payload for creating an app-owner lease for one session.
254#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
255pub struct CreateSessionLeaseRequest {
256    /// Session kept alive only while the owner renews this lease.
257    pub session_name: SessionName,
258    /// Requested lease time-to-live in milliseconds.
259    pub ttl_millis: u64,
260}
261
262/// Request payload for renewing an app-owner session lease.
263#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
264pub struct RenewSessionLeaseRequest {
265    /// Leased session name.
266    pub session_name: SessionName,
267    /// Server-issued lease token.
268    pub token: u64,
269    /// Requested renewed time-to-live in milliseconds.
270    pub ttl_millis: u64,
271}
272
273/// Request payload for releasing an app-owner session lease.
274#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
275pub struct ReleaseSessionLeaseRequest {
276    /// Leased session name.
277    pub session_name: SessionName,
278    /// Server-issued lease token.
279    pub token: u64,
280}
281
282/// Request payload for `rename-session`.
283#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
284pub struct RenameSessionRequest {
285    /// The exact existing session name.
286    pub target: SessionName,
287    /// The validated destination session name.
288    pub new_name: SessionName,
289}
290
291/// Request payload for `list-sessions`.
292#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
293pub struct ListSessionsRequest {
294    /// An optional server-side format template.
295    pub format: Option<String>,
296    /// An optional server-side filter expression.
297    #[serde(default)]
298    pub filter: Option<String>,
299    /// The optional tmux sort order name.
300    #[serde(default)]
301    pub sort_order: Option<String>,
302    /// Whether the selected sort order should be reversed.
303    #[serde(default)]
304    pub reversed: bool,
305}