use std::process::Command;
use crate::error::{BosunError, Result};
use crate::tmux::client::sync_tmux;
#[must_use = "dropping the guard immediately would unbind before the attach completes"]
pub struct AttachGuard {
socket: Option<String>,
done: bool,
}
impl AttachGuard {
pub fn release(mut self) -> Result<()> {
self.done = true;
unbind_detach_key(self.socket.as_deref())
}
}
impl Drop for AttachGuard {
fn drop(&mut self) {
if self.done {
return;
}
let _ = unbind_detach_key(self.socket.as_deref());
}
}
pub fn attach_with_ctrl_q_detach(socket: Option<&str>, name: &str) -> Result<()> {
let guard = install_detach_key(socket)?;
let result = run_attach(socket, name);
drop(guard);
result
}
pub fn install_detach_key_for_test(socket: Option<&str>) -> Result<AttachGuard> {
install_detach_key(socket)
}
fn install_detach_key(socket: Option<&str>) -> Result<AttachGuard> {
let out = sync_tmux(socket, ["bind-key", "-T", "root", "C-q", "detach-client"])
.output()
.map_err(BosunError::Io)?;
if !out.status.success() {
let stderr = String::from_utf8_lossy(&out.stderr);
return Err(BosunError::Tmux(format!(
"bind-key failed: {}",
stderr.trim()
)));
}
Ok(AttachGuard {
socket: socket.map(|s| s.to_string()),
done: false,
})
}
fn run_attach(socket: Option<&str>, name: &str) -> Result<()> {
let status = sync_tmux(socket, ["attach-session", "-t", name])
.status()
.map_err(BosunError::Io)?;
if !status.success() {
return Err(BosunError::Tmux(format!(
"attach-session -t {} failed: {}",
name, status
)));
}
Ok(())
}
fn unbind_detach_key(socket: Option<&str>) -> Result<()> {
let out = sync_tmux(socket, ["unbind-key", "-T", "root", "C-q"])
.output()
.map_err(BosunError::Io)?;
if !out.status.success() {
let stderr = String::from_utf8_lossy(&out.stderr);
tracing::warn!("unbind-key C-q: {}", stderr.trim());
}
Ok(())
}
pub fn emergency_unbind(socket: Option<&str>) {
let _ = Command::new("tmux")
.args(match socket {
Some(s) => vec!["-L", s, "unbind-key", "-T", "root", "C-q"],
None => vec!["unbind-key", "-T", "root", "C-q"],
})
.output();
}