rmux-server 0.1.0

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
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
use rmux_proto::request::Request;
use rmux_proto::{
    ControlModeResponse, ErrorResponse, HandshakeResponse, Response, RmuxError,
    SUPPORTED_CAPABILITIES,
};
#[cfg(test)]
use tokio::sync::broadcast;
#[cfg(test)]
use tracing::warn;

use crate::hook_runtime::{capture_inline_hooks, PendingInlineHook};
use crate::outer_terminal::OuterTerminalContext;
use crate::pane_io::HandleOutcome;

use super::{client_environment_snapshot, effective_client_terminal_context, RequestHandler};

impl RequestHandler {
    #[cfg(test)]
    pub(crate) async fn handle(&self, request: Request) -> Response {
        let mut lifecycle_events = self.subscribe_lifecycle_events();
        let outcome = self.dispatch(std::process::id(), request).await;

        loop {
            match lifecycle_events.try_recv() {
                Ok(event) => self.dispatch_lifecycle_hook(event).await,
                Err(
                    broadcast::error::TryRecvError::Empty | broadcast::error::TryRecvError::Closed,
                ) => {
                    break;
                }
                Err(broadcast::error::TryRecvError::Lagged(skipped)) => {
                    warn!(
                        skipped,
                        "test lifecycle hook receiver lagged; dropping events"
                    );
                }
            }
        }

        outcome.response
    }

    #[cfg(test)]
    pub(crate) async fn dispatch(&self, requester_pid: u32, request: Request) -> HandleOutcome {
        self.dispatch_for_connection(requester_pid, u64::from(requester_pid), request)
            .await
    }

    pub(crate) async fn dispatch_for_connection(
        &self,
        requester_pid: u32,
        connection_id: u64,
        request: Request,
    ) -> HandleOutcome {
        let request_for_hooks = request.clone();
        let (outcome, inline_hooks) = self
            .dispatch_captured(requester_pid, connection_id, request)
            .await;
        self.run_inline_hooks(requester_pid, inline_hooks, None)
            .await;
        self.run_request_hooks(requester_pid, &request_for_hooks, &outcome.response, None)
            .await;
        outcome
    }

    #[async_recursion::async_recursion]
    pub(crate) async fn dispatch_captured(
        &self,
        requester_pid: u32,
        connection_id: u64,
        request: Request,
    ) -> (HandleOutcome, Vec<PendingInlineHook>) {
        capture_inline_hooks(Box::pin(self.dispatch_request(
            requester_pid,
            connection_id,
            request,
        )))
        .await
    }

    #[async_recursion::async_recursion]
    async fn dispatch_request(
        &self,
        requester_pid: u32,
        connection_id: u64,
        request: Request,
    ) -> HandleOutcome {
        if let Request::Handshake(request) = request {
            let response = if let Err(error) = request.validate_against(SUPPORTED_CAPABILITIES) {
                Response::Error(ErrorResponse { error })
            } else {
                Response::Handshake(HandshakeResponse::current())
            };
            return HandleOutcome::response(response);
        }

        if let Some(error) = self.take_startup_config_error().await {
            return HandleOutcome::response(Response::Error(ErrorResponse { error }));
        }

        let command_name = request.command_name().to_owned();
        #[allow(unreachable_patterns)]
        match request {
            Request::NewSession(request) => {
                HandleOutcome::response(self.handle_new_session(requester_pid, request).await)
            }
            Request::HasSession(request) => {
                HandleOutcome::response(self.handle_has_session(request).await)
            }
            Request::KillSession(request) => {
                HandleOutcome::response(self.handle_kill_session(request).await)
            }
            Request::RenameSession(request) => {
                HandleOutcome::response(self.handle_rename_session(request).await)
            }
            Request::NewWindow(request) => {
                HandleOutcome::response(self.handle_new_window(request).await)
            }
            Request::KillWindow(request) => {
                HandleOutcome::response(self.handle_kill_window(request).await)
            }
            Request::SelectWindow(request) => {
                HandleOutcome::response(self.handle_select_window(request).await)
            }
            Request::RenameWindow(request) => {
                HandleOutcome::response(self.handle_rename_window(request).await)
            }
            Request::NextWindow(request) => {
                HandleOutcome::response(self.handle_next_window(request).await)
            }
            Request::PreviousWindow(request) => {
                HandleOutcome::response(self.handle_previous_window(request).await)
            }
            Request::LastWindow(request) => {
                HandleOutcome::response(self.handle_last_window(request).await)
            }
            Request::ListWindows(request) => {
                HandleOutcome::response(self.handle_list_windows(request).await)
            }
            Request::LinkWindow(request) => {
                HandleOutcome::response(self.handle_link_window(request).await)
            }
            Request::MoveWindow(request) => {
                HandleOutcome::response(self.handle_move_window(request).await)
            }
            Request::SwapWindow(request) => {
                HandleOutcome::response(self.handle_swap_window(request).await)
            }
            Request::RotateWindow(request) => {
                HandleOutcome::response(self.handle_rotate_window(request).await)
            }
            Request::ResizeWindow(request) => {
                HandleOutcome::response(self.handle_resize_window(request).await)
            }
            Request::RespawnWindow(request) => {
                HandleOutcome::response(self.handle_respawn_window(request).await)
            }
            Request::SplitWindow(request) => {
                HandleOutcome::response(self.handle_split_window(request).await)
            }
            Request::SplitWindowExt(request) => {
                HandleOutcome::response(self.handle_split_window_ext(request).await)
            }
            Request::SwapPane(request) => {
                HandleOutcome::response(self.handle_swap_pane(request).await)
            }
            Request::LastPane(request) => {
                HandleOutcome::response(self.handle_last_pane(request).await)
            }
            Request::JoinPane(request) => {
                HandleOutcome::response(self.handle_join_pane(request).await)
            }
            Request::MovePane(request) => {
                HandleOutcome::response(self.handle_move_pane(request).await)
            }
            Request::BreakPane(request) => {
                HandleOutcome::response(self.handle_break_pane(request).await)
            }
            Request::PipePane(request) => {
                HandleOutcome::response(self.handle_pipe_pane(requester_pid, request).await)
            }
            Request::RespawnPane(request) => {
                HandleOutcome::response(self.handle_respawn_pane(request).await)
            }
            Request::KillPane(request) => {
                HandleOutcome::response(self.handle_kill_pane(request).await)
            }
            Request::SelectLayout(request) => {
                HandleOutcome::response(self.handle_select_layout(request).await)
            }
            Request::SelectCustomLayout(request) => {
                HandleOutcome::response(self.handle_select_custom_layout(request).await)
            }
            Request::SelectOldLayout(request) => {
                HandleOutcome::response(self.handle_select_old_layout(request).await)
            }
            Request::SpreadLayout(request) => {
                HandleOutcome::response(self.handle_spread_layout(request).await)
            }
            Request::KillServer(_request) => {
                HandleOutcome::response(self.handle_kill_server().await)
            }
            Request::LockServer(_request) => {
                HandleOutcome::response(self.handle_lock_server().await)
            }
            Request::LockSession(request) => {
                HandleOutcome::response(self.handle_lock_session(request).await)
            }
            Request::LockClient(request) => {
                HandleOutcome::response(self.handle_lock_client(requester_pid, request).await)
            }
            Request::ServerAccess(request) => {
                HandleOutcome::response(self.handle_server_access(request).await)
            }
            Request::NextLayout(request) => {
                HandleOutcome::response(self.handle_next_layout(request).await)
            }
            Request::PreviousLayout(request) => {
                HandleOutcome::response(self.handle_previous_layout(request).await)
            }
            Request::ResizePane(request) => {
                HandleOutcome::response(self.handle_resize_pane(request).await)
            }
            Request::DisplayPanes(request) => {
                HandleOutcome::response(self.handle_display_panes(request).await)
            }
            Request::ListPanes(request) => {
                HandleOutcome::response(self.handle_list_panes(request).await)
            }
            Request::SelectPane(request) => {
                HandleOutcome::response(self.handle_select_pane(request).await)
            }
            Request::SelectPaneAdjacent(request) => {
                HandleOutcome::response(self.handle_select_pane_adjacent(request).await)
            }
            Request::SelectPaneMark(request) => {
                HandleOutcome::response(self.handle_select_pane_mark(request).await)
            }
            Request::CopyMode(request) => {
                HandleOutcome::response(self.handle_copy_mode(requester_pid, request).await)
            }
            Request::ClockMode(request) => {
                HandleOutcome::response(self.handle_clock_mode(requester_pid, request).await)
            }
            Request::SendKeys(request) => {
                HandleOutcome::response(self.handle_send_keys(request).await)
            }
            Request::SendKeysExt(request) => HandleOutcome::response(
                Box::pin(self.handle_send_keys_ext(requester_pid, request)).await,
            ),
            Request::BindKey(request) => {
                HandleOutcome::response(self.handle_bind_key(request).await)
            }
            Request::UnbindKey(request) => {
                HandleOutcome::response(self.handle_unbind_key(request).await)
            }
            Request::ListKeys(request) => {
                HandleOutcome::response(self.handle_list_keys(request).await)
            }
            Request::SendPrefix(request) => {
                HandleOutcome::response(self.handle_send_prefix(requester_pid, request).await)
            }
            Request::AttachSession(request) => {
                self.handle_attach_session(requester_pid, request).await
            }
            Request::SwitchClient(request) => {
                HandleOutcome::response(self.handle_switch_client(requester_pid, request).await)
            }
            Request::SwitchClientExt(request) => {
                HandleOutcome::response(self.handle_switch_client_ext(requester_pid, request).await)
            }
            Request::DetachClient(_request) => {
                HandleOutcome::response(self.handle_detach_client(requester_pid).await)
            }
            Request::SetOption(request) => {
                HandleOutcome::response(self.handle_set_option(request).await)
            }
            Request::SetOptionByName(request) => {
                HandleOutcome::response(self.handle_set_option_by_name(request).await)
            }
            Request::SetEnvironment(request) => {
                HandleOutcome::response(self.handle_set_environment(request).await)
            }
            Request::SetHook(request) => {
                HandleOutcome::response(self.handle_set_hook(request).await)
            }
            Request::SetHookMutation(request) => {
                HandleOutcome::response(self.handle_set_hook_mutation(request).await)
            }
            Request::ShowOptions(request) => {
                HandleOutcome::response(self.handle_show_options(request).await)
            }
            Request::ShowEnvironment(request) => {
                HandleOutcome::response(self.handle_show_environment(request).await)
            }
            Request::ShowHooks(request) => {
                HandleOutcome::response(self.handle_show_hooks(request).await)
            }
            Request::ListSessions(request) => {
                HandleOutcome::response(self.handle_list_sessions(request).await)
            }
            Request::SetBuffer(request) => {
                HandleOutcome::response(self.handle_set_buffer(requester_pid, request).await)
            }
            Request::ShowBuffer(request) => {
                HandleOutcome::response(self.handle_show_buffer(request).await)
            }
            Request::PasteBuffer(request) => {
                HandleOutcome::response(self.handle_paste_buffer(request).await)
            }
            Request::ListBuffers(request) => {
                HandleOutcome::response(self.handle_list_buffers(request).await)
            }
            Request::DeleteBuffer(request) => {
                HandleOutcome::response(self.handle_delete_buffer(request).await)
            }
            Request::LoadBuffer(request) => {
                HandleOutcome::response(self.handle_load_buffer(requester_pid, request).await)
            }
            Request::SaveBuffer(request) => {
                HandleOutcome::response(self.handle_save_buffer(request).await)
            }
            Request::CapturePane(request) => {
                HandleOutcome::response(self.handle_capture_pane(request).await)
            }
            Request::PaneSnapshot(request) => {
                HandleOutcome::response(self.handle_pane_snapshot(request).await)
            }
            Request::SubscribePaneOutput(request) => HandleOutcome::response(
                self.handle_subscribe_pane_output(connection_id, request)
                    .await,
            ),
            Request::UnsubscribePaneOutput(request) => HandleOutcome::response(
                self.handle_unsubscribe_pane_output(connection_id, request)
                    .await,
            ),
            Request::PaneOutputCursor(request) => HandleOutcome::response(
                self.handle_pane_output_cursor(connection_id, request).await,
            ),
            Request::SdkWaitForOutput(request) => HandleOutcome::response(
                self.handle_sdk_wait_for_output(connection_id, request)
                    .await,
            ),
            Request::CancelSdkWait(request) => {
                HandleOutcome::response(self.handle_cancel_sdk_wait(request).await)
            }
            Request::ClearHistory(request) => {
                HandleOutcome::response(self.handle_clear_history(request).await)
            }
            Request::DisplayMessage(request) => {
                HandleOutcome::response(self.handle_display_message(requester_pid, request).await)
            }
            Request::ResolveTarget(request) => {
                HandleOutcome::response(self.handle_resolve_target(request).await)
            }
            Request::ShowMessages(request) => {
                HandleOutcome::response(self.handle_show_messages(requester_pid, request).await)
            }
            Request::NewSessionExt(request) => {
                HandleOutcome::response(self.handle_new_session_ext(requester_pid, request).await)
            }
            Request::AttachSessionExt(request) => {
                self.handle_attach_session_ext(requester_pid, request).await
            }
            Request::AttachSessionExt2(request) => {
                self.handle_attach_session_ext2(requester_pid, request)
                    .await
            }
            Request::RefreshClient(request) => {
                HandleOutcome::response(self.handle_refresh_client(requester_pid, request).await)
            }
            Request::ListClients(request) => {
                HandleOutcome::response(self.handle_list_clients(requester_pid, request).await)
            }
            Request::SuspendClient(request) => {
                HandleOutcome::response(self.handle_suspend_client(requester_pid, request).await)
            }
            Request::DetachClientExt(request) => {
                HandleOutcome::response(self.handle_detach_client_ext(requester_pid, request).await)
            }
            Request::SwitchClientExt2(request) => HandleOutcome::response(
                self.handle_switch_client_ext2(requester_pid, request).await,
            ),
            Request::SwitchClientExt3(request) => HandleOutcome::response(
                self.handle_switch_client_ext3(requester_pid, request).await,
            ),
            Request::RunShell(request) => {
                HandleOutcome::response(self.handle_run_shell(request).await)
            }
            Request::IfShell(request) => {
                HandleOutcome::response(self.handle_if_shell(requester_pid, request).await)
            }
            Request::WaitFor(request) => {
                HandleOutcome::response(self.handle_wait_for(true, request).await)
            }
            Request::SourceFile(request) => {
                HandleOutcome::response(self.handle_source_file(requester_pid, request).await)
            }
            Request::UnlinkWindow(request) => {
                HandleOutcome::response(self.handle_unlink_window(request).await)
            }
            Request::ControlMode(request) => {
                let client_environment = client_environment_snapshot(requester_pid);
                let client_terminal = effective_client_terminal_context(
                    client_environment.as_ref(),
                    &request.client_terminal,
                );
                let terminal_context =
                    OuterTerminalContext::from_environment(client_environment.as_ref())
                        .with_client_terminal(&client_terminal);
                HandleOutcome::control(
                    Response::ControlMode(ControlModeResponse { mode: request.mode }),
                    crate::control::ControlModeUpgrade {
                        mode: request.mode,
                        terminal_context,
                    },
                )
            }
            _ => HandleOutcome::response(Response::Error(ErrorResponse {
                error: RmuxError::Server(format!(
                    "{command_name} is only available through queued command dispatch"
                )),
            })),
        }
    }
}