use swayipc::{Connection, WindowChange, EventType}; use std::{env,fs::File, io::{Read, self}, num, fmt::Display}; use regex::Regex; struct WindowHider { term: Vec, regex: Regex, } enum Error { IoError(io::Error), ParseIntError(num::ParseIntError), RegexParseError(i32), SwayIpcError(swayipc::Error) } impl From for Error { fn from(value: io::Error) -> Self { Self::IoError(value) } } impl From for Error { fn from(value: num::ParseIntError) -> Self { Self::ParseIntError(value) } } impl From for Error { fn from(value: swayipc::Error) -> Self { Self::SwayIpcError(value) } } impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Error::IoError(err) => err.fmt(f), Error::ParseIntError(err) => err.fmt(f), Error::SwayIpcError(err) => err.fmt(f), Error::RegexParseError(pid) => write!(f, "Failed to regex match for pid {pid}"), } } } impl WindowHider { fn get_parent_term_pid(&self, pid: i32, is_root: bool) -> Result, Error> { let mut buf = String::new(); File::open(format!("/proc/{pid}/stat"))?.read_to_string(&mut buf)?; let Some((_, [name, ppid])) = self.regex.captures(&buf).map(|caps| caps.extract()) else { return Err(Error::RegexParseError(pid)); }; let ppid = ppid.parse::()?; match name { "init" => Ok(None), _ if self.term.contains(&name.to_string()) && !is_root => Ok(Some(pid)), _ => self.get_parent_term_pid(ppid, false) } } fn hide(&self, conn: &mut Connection, pid: i32) -> Result<(), Error> { let ppid = self.get_parent_term_pid(pid, true)?; if let Some(ppid) = ppid { println!("found parent pid of {pid} as {ppid}"); conn.run_command(format!("[pid={ppid}] mark --add {pid}, move scratchpad"))?; } Ok(()) } fn show(&self, conn: &mut Connection, pid: i32) -> Result<(), Error> { conn.run_command(format!("[con_mark={pid}] move workspace current"))?; conn.run_command(format!("[con_mark={pid}] floating toggle"))?; Ok(()) } } fn main() -> Result<(), swayipc::Error> { let mut conn = Connection::new().expect("couldn't connect to sway's socket"); let evs = Connection::new()?.subscribe([EventType::Window])?; // one can't use the same // connection for events and for // running commands. :( let mut args: Vec = env::args().collect(); let nterms = args.len() - 1; if nterms < 1 { println!("usage: swhd "); return Ok(()); } let wh = WindowHider { term: args.drain(1..nterms).collect(), regex: Regex::new(r"\d+ \((.*)\) \w (\d+) \d+").unwrap() }; for item in evs { if let Ok(item) = item { match item { swayipc::Event::Window(win) => { if win.change == WindowChange::New { if let Some(pid) = win.container.pid { match wh.hide(&mut conn, pid) { Ok(_) => {}, Err(err) => eprintln!("failed to hide terminal {pid}: {err}") }; } } else if win.change == WindowChange::Close { if let Some(pid) = win.container.pid { match wh.show(&mut conn, pid) { Ok(_) => {}, Err(err) => eprintln!("failed to show terminal {pid}: {err}") }; } } } _ => {} } } } Ok(()) }