aboutsummaryrefslogtreecommitdiff
path: root/src/main.rs
blob: f3199e56159eb9880b9fb0785f6f94bfd2571ddf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
use swayipc::{Connection, WindowChange, EventType};
use std::{env,fs::File, io::{Read, self}, num, fmt::Display};
use regex::Regex;

struct WindowHider {
    term: Vec<String>,
    regex: Regex,
}

enum Error {
    IoError(io::Error),
    ParseIntError(num::ParseIntError),
    RegexParseError(i32),
    SwayIpcError(swayipc::Error)
}

impl From<io::Error> for Error {
    fn from(value: io::Error) -> Self {
        Self::IoError(value)
    }
}

impl From<num::ParseIntError> for Error {
    fn from(value: num::ParseIntError) -> Self {
        Self::ParseIntError(value)
    }
}

impl From<swayipc::Error> 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<Option<i32>, 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::<i32>()?;

        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 {
            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<String> = env::args().collect();
    let nterms = args.len() - 1;

    if nterms < 1 {
        eprintln!("usage: swhd <list of terminals>");
        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(())
}