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 }),
}
}
}
}
}