rmux-server 0.10.0

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
use rmux_core::LifecycleEvent;
use rmux_proto::request::DetachClientExtRequest;
use rmux_proto::{DetachClientResponse, ErrorResponse, Response, RmuxError, SessionId};

use crate::pane_io::AttachControl;

use super::super::{
    attach_support::AttachedClientControlOutcome,
    control_support::{ControlClientIdentity, ManagedClient},
    RequestHandler,
};

impl RequestHandler {
    async fn detach_attach_client_with_mode(
        &self,
        attach_pid: u32,
        expected_attach_id: u64,
        kill_on_detach: bool,
        exec_command: Option<String>,
        command_name: &str,
    ) -> Result<AttachedClientControlOutcome, RmuxError> {
        let (session_name, session_id) = {
            let active_attach = self.active_attach.lock().await;
            let active = active_attach
                .by_pid
                .get(&attach_pid)
                .filter(|active| {
                    active.id == expected_attach_id
                        && !active.closing.load(std::sync::atomic::Ordering::SeqCst)
                })
                .ok_or_else(|| crate::handler_support::attached_client_required(command_name))?;
            (active.session_name.clone(), active.session_id)
        };
        let control = if let Some(command) = exec_command {
            let command = self
                .attach_shell_command_for_session(&session_name, command)
                .await?;
            AttachControl::DetachExecShellCommand(command)
        } else if kill_on_detach {
            AttachControl::DetachKill
        } else {
            AttachControl::Detach
        };
        let outcome = self
            .send_attach_control_for_client_current_session_identity(
                attach_pid,
                expected_attach_id,
                session_id,
                control,
                command_name,
            )
            .await?;
        self.reconcile_attached_session_size_and_emit(&outcome.session_name)
            .await?;
        Ok(outcome)
    }

    async fn detach_attach_client_with_mode_for_current_session_identity(
        &self,
        attach_pid: u32,
        expected_attach_id: u64,
        expected_session_name: &rmux_proto::SessionName,
        expected_session_id: SessionId,
        kill_on_detach: bool,
        exec_command: Option<String>,
    ) -> Result<AttachedClientControlOutcome, RmuxError> {
        let control = if let Some(command) = exec_command {
            let command = self
                .attach_shell_command_for_session(expected_session_name, command)
                .await?;
            AttachControl::DetachExecShellCommand(command)
        } else if kill_on_detach {
            AttachControl::DetachKill
        } else {
            AttachControl::Detach
        };
        let outcome = self
            .send_attach_control_for_client_current_session_identity(
                attach_pid,
                expected_attach_id,
                expected_session_id,
                control,
                "detach-client",
            )
            .await?;
        self.reconcile_attached_session_size_and_emit(&outcome.session_name)
            .await?;
        Ok(outcome)
    }

    pub(in crate::handler) async fn detach_other_attach_clients_for_session(
        &self,
        session_name: &rmux_proto::SessionName,
        requester_pid: u32,
        kill_clients: bool,
    ) {
        let session_id = {
            let state = self.state.lock().await;
            state
                .sessions
                .session(session_name)
                .map(rmux_core::Session::id)
        };
        let Some(session_id) = session_id else {
            return;
        };
        let _ = self
            .detach_other_attach_clients_for_session_identity(
                session_name,
                session_id,
                requester_pid,
                kill_clients,
            )
            .await;
    }

    pub(in crate::handler) async fn detach_other_attach_clients_for_session_identity(
        &self,
        expected_session_name: &rmux_proto::SessionName,
        session_id: SessionId,
        requester_pid: u32,
        kill_clients: bool,
    ) -> Result<(), RmuxError> {
        let (clients, active_window_id) = {
            let state = self.state.lock().await;
            let Some((_session_name, session)) = state
                .sessions
                .iter()
                .find(|(_session_name, session)| session.id() == session_id)
            else {
                return Err(crate::pane_terminals::session_not_found(
                    expected_session_name,
                ));
            };
            let active_window_id = session.window().id();
            let active_attach = self.active_attach.lock().await;
            let clients = active_attach
                .by_pid
                .iter()
                .filter(|(pid, active)| **pid != requester_pid && active.session_id == session_id)
                .map(|(&pid, active)| (pid, active.id))
                .collect::<Vec<_>>();
            (clients, active_window_id)
        };

        for (attach_pid, attach_id) in clients {
            let control = if kill_clients {
                AttachControl::DetachKill
            } else {
                AttachControl::Detach
            };
            if let Ok(outcome) = self
                .send_attach_control_for_client_current_session_identity(
                    attach_pid,
                    attach_id,
                    session_id,
                    control,
                    "attach-session",
                )
                .await
            {
                let event = LifecycleEvent::ClientDetached {
                    session_name: outcome.session_name.clone(),
                    client_name: Some(outcome.client_name),
                };
                self.emit_for_session_identity(event, &outcome.session_name, session_id)
                    .await;
            }
        }
        self.reconcile_attached_window_identity_size_and_emit(session_id, active_window_id)
            .await?;
        Ok(())
    }

    pub(in crate::handler) async fn handle_detach_client(&self, requester_pid: u32) -> Response {
        self.handle_detach_client_ext(
            requester_pid,
            DetachClientExtRequest {
                target_client: None,
                all_other_clients: false,
                target_session: None,
                kill_on_detach: false,
                exec_command: None,
            },
        )
        .await
    }

    pub(in crate::handler) async fn handle_detach_client_for_identity(
        &self,
        identity: super::super::attach_support::ActiveAttachIdentity,
    ) -> Response {
        match self
            .detach_attach_client_with_mode(
                identity.attach_pid(),
                identity.attach_id(),
                false,
                None,
                "detach-client",
            )
            .await
        {
            Ok(outcome) => {
                self.emit(LifecycleEvent::ClientDetached {
                    session_name: outcome.session_name,
                    client_name: Some(outcome.client_name),
                })
                .await;
                Response::DetachClient(DetachClientResponse)
            }
            Err(error) => Response::Error(ErrorResponse { error }),
        }
    }

    pub(in crate::handler) async fn handle_detach_client_ext(
        &self,
        requester_pid: u32,
        request: DetachClientExtRequest,
    ) -> Response {
        if request.target_session.is_some() && request.target_client.is_some() {
            return Response::Error(ErrorResponse {
                error: RmuxError::Server("detach-client accepts -t or -s, not both".to_owned()),
            });
        }

        if let Some(session_name) = request.target_session.as_ref() {
            let (session_id, attach_clients, control_clients) = {
                let state = self.state.lock().await;
                let Some(session_id) = state
                    .sessions
                    .session(session_name)
                    .map(rmux_core::Session::id)
                else {
                    return Response::DetachClient(DetachClientResponse);
                };
                let active_attach = self.active_attach.lock().await;
                let clients = active_attach
                    .by_pid
                    .iter()
                    .filter(|(_, active)| active.session_id == session_id)
                    .map(|(&pid, active)| (pid, active.id))
                    .collect::<Vec<_>>();
                let active_control = self.active_control.lock().await;
                let control_clients = active_control
                    .by_pid
                    .iter()
                    .filter_map(|(&pid, active)| {
                        (active.session_id == Some(session_id))
                            .then_some(ControlClientIdentity::new(pid, active.id))
                    })
                    .collect::<Vec<_>>();
                (session_id, clients, control_clients)
            };
            for (attach_pid, attach_id) in attach_clients {
                if let Ok(outcome) = self
                    .detach_attach_client_with_mode_for_current_session_identity(
                        attach_pid,
                        attach_id,
                        session_name,
                        session_id,
                        request.kill_on_detach,
                        request.exec_command.clone(),
                    )
                    .await
                {
                    self.emit_for_session_identity(
                        LifecycleEvent::ClientDetached {
                            session_name: outcome.session_name.clone(),
                            client_name: Some(outcome.client_name),
                        },
                        &outcome.session_name,
                        session_id,
                    )
                    .await;
                }
            }
            let outcome = self
                .detach_control_clients_for_session_identity(session_id, control_clients, None)
                .await;
            for event in outcome.lifecycle_events {
                self.emit_prepared(event).await;
            }
            return Response::DetachClient(DetachClientResponse);
        }

        if request.all_other_clients {
            let keep_pid = match self
                .resolve_target_attach_client_pid(
                    requester_pid,
                    request.target_client.as_deref(),
                    "detach-client",
                )
                .await
            {
                Ok(pid) => pid,
                Err(error) => return Response::Error(ErrorResponse { error }),
            };
            let attach_clients = {
                let active_attach = self.active_attach.lock().await;
                active_attach
                    .by_pid
                    .iter()
                    .filter(|(pid, active)| {
                        **pid != keep_pid
                            && !active.closing.load(std::sync::atomic::Ordering::SeqCst)
                    })
                    .map(|(&pid, active)| (pid, active.id))
                    .collect::<Vec<_>>()
            };
            for (attach_pid, attach_id) in attach_clients {
                if let Ok(outcome) = self
                    .detach_attach_client_with_mode(
                        attach_pid,
                        attach_id,
                        request.kill_on_detach,
                        request.exec_command.clone(),
                        "detach-client",
                    )
                    .await
                {
                    self.emit(LifecycleEvent::ClientDetached {
                        session_name: outcome.session_name,
                        client_name: Some(outcome.client_name),
                    })
                    .await;
                }
            }
            return Response::DetachClient(DetachClientResponse);
        }

        let client = match self
            .resolve_target_managed_client(
                requester_pid,
                request.target_client.as_deref(),
                "detach-client",
            )
            .await
        {
            Ok(client) => client,
            Err(error) => return Response::Error(ErrorResponse { error }),
        };

        match client {
            ManagedClient::Attach {
                pid: attach_pid,
                attach_id,
            } => match self
                .detach_attach_client_with_mode(
                    attach_pid,
                    attach_id,
                    request.kill_on_detach,
                    request.exec_command,
                    "detach-client",
                )
                .await
            {
                Ok(outcome) => {
                    self.emit(LifecycleEvent::ClientDetached {
                        session_name: outcome.session_name,
                        client_name: Some(outcome.client_name),
                    })
                    .await;
                    Response::DetachClient(DetachClientResponse)
                }
                Err(error) => Response::Error(ErrorResponse { error }),
            },
            ManagedClient::Control(identity) => {
                let control_pid = identity.requester_pid();
                match self
                    .exit_control_client_for_identity(control_pid, identity.control_id(), None)
                    .await
                {
                    Ok(outcome) => {
                        if let Some(event) = outcome.lifecycle_event {
                            self.emit_prepared(event).await;
                        }
                        Response::DetachClient(DetachClientResponse)
                    }
                    Err(error) => Response::Error(ErrorResponse { error }),
                }
            }
        }
    }
}