use std::process::Command;
use serde::Deserialize;
use serde::de::DeserializeOwned;
#[derive(Deserialize, Default, Clone)]
#[serde(default)]
pub(crate) struct Session {
pub(crate) name: String,
pub(crate) id: String,
pub(crate) windows: i64,
pub(crate) created: i64,
pub(crate) attached: bool,
pub(crate) group: String,
pub(crate) activity: i64,
}
#[derive(Deserialize, Default, Clone)]
#[serde(default)]
pub(crate) struct Window {
pub(crate) session: String,
pub(crate) index: i64,
pub(crate) name: String,
pub(crate) id: String,
pub(crate) active: bool,
pub(crate) panes: i64,
pub(crate) width: i64,
pub(crate) height: i64,
pub(crate) layout: String,
pub(crate) zoomed: bool,
pub(crate) bell: bool,
pub(crate) activity: bool,
pub(crate) silence: bool,
}
#[derive(Deserialize, Default, Clone)]
#[serde(default)]
pub(crate) struct Pane {
pub(crate) session: String,
pub(crate) window: i64,
pub(crate) index: i64,
pub(crate) id: String,
pub(crate) active: bool,
pub(crate) dead: bool,
pub(crate) pid: i64,
pub(crate) tty: String,
pub(crate) command: String,
pub(crate) path: String,
pub(crate) title: String,
pub(crate) width: i64,
pub(crate) height: i64,
pub(crate) history_size: i64,
pub(crate) history_limit: i64,
pub(crate) in_mode: bool,
pub(crate) mode: String,
pub(crate) marked: bool,
}
#[derive(Deserialize, Default, Clone)]
#[serde(default)]
pub(crate) struct Client {
pub(crate) name: String,
pub(crate) tty: String,
pub(crate) session: String,
pub(crate) width: i64,
pub(crate) height: i64,
pub(crate) termname: String,
pub(crate) pid: i64,
}
#[derive(Default)]
pub(crate) struct Snapshot {
pub(crate) sessions: Vec<Session>,
pub(crate) windows: Vec<Window>,
pub(crate) panes: Vec<Pane>,
pub(crate) clients: Vec<Client>,
pub(crate) error: Option<String>,
}
pub(crate) fn ztmux_cmd(socket: &str, args: &[&str]) -> Command {
let exe = std::env::current_exe().unwrap_or_else(|_| std::path::PathBuf::from("ztmux"));
let mut c = Command::new(exe);
if !socket.is_empty() {
c.arg("-S").arg(socket);
}
c.args(args);
c
}
pub(crate) fn capture_pane(socket: &str, target: &str, history: bool) -> Option<String> {
let mut args: Vec<&str> = vec!["capture-pane", "-p", "-t", target];
if history {
args.push("-S");
args.push("-");
}
let out = ztmux_cmd(socket, &args).output().ok()?;
if !out.status.success() {
return None;
}
Some(String::from_utf8_lossy(&out.stdout).into_owned())
}
pub(crate) fn query_lines(socket: &str, args: &[&str]) -> Vec<String> {
let Ok(out) = ztmux_cmd(socket, args).output() else {
return Vec::new();
};
if !out.status.success() {
return Vec::new();
}
String::from_utf8_lossy(&out.stdout)
.lines()
.map(str::to_string)
.collect()
}
pub(crate) fn run_json<T: DeserializeOwned>(socket: &str, args: &[&str]) -> Result<Vec<T>, String> {
let out = ztmux_cmd(socket, args)
.output()
.map_err(|e| format!("spawn: {e}"))?;
if !out.status.success() {
return Err(String::from_utf8_lossy(&out.stderr).trim().to_string());
}
serde_json::from_slice(&out.stdout).map_err(|e| format!("parse {args:?}: {e}"))
}
pub(crate) fn poll(socket: &str) -> Snapshot {
let mut snap = Snapshot::default();
match run_json::<Session>(socket, &["list-sessions", "-o", "json"]) {
Ok(v) => snap.sessions = v,
Err(e) => {
snap.error = Some(e);
return snap;
}
}
snap.windows =
run_json::<Window>(socket, &["list-windows", "-a", "-o", "json"]).unwrap_or_default();
snap.panes = run_json::<Pane>(socket, &["list-panes", "-a", "-o", "json"]).unwrap_or_default();
snap.clients = run_json::<Client>(socket, &["list-clients", "-o", "json"]).unwrap_or_default();
snap
}