Permalink
Browse files

I think we want to track.. windows and pids

  • Loading branch information...
FauxFaux committed Oct 8, 2018
1 parent f174355 commit 1e338fb40fb381d9e4406148b006dee84af612ec
Showing with 116 additions and 59 deletions.
  1. +109 −54 src/main.rs
  2. +7 −5 src/x.rs
@@ -8,6 +8,8 @@ extern crate regex;
extern crate toml;
extern crate xcb;
use std::collections::HashMap;
use std::collections::HashSet;
use std::env;
use std::io;
use std::io::Write;
@@ -23,13 +25,27 @@ use failure::ResultExt;
use nix::sys::termios;
use config::Config;
use x::XWindow;
// TODO: don't want this to be PartialEq/Eq
#[derive(Clone, Debug, PartialEq, Eq)]
struct Proc {
window: x::XWindow,
struct WindowInfo {
pids: HashSet<u32>,
supports_delete: bool,
class: String,
pid: Option<u32>,
supported_protocols: Vec<xcb::Atom>,
title: String,
}
#[derive(Clone, Debug)]
struct PidInfo {
windows: Vec<XWindow>,
exe: String,
}
#[derive(Clone, Debug, Default)]
struct Hatred {
blacklisted_pids: HashMap<u32, String>,
blacklisted_windows: HashMap<XWindow, String>,
}
fn main() -> Result<(), Error> {
@@ -47,6 +63,8 @@ fn main() -> Result<(), Error> {
term::StdOutTermios::with_setup(|attr| attr.local_flags ^= termios::LocalFlags::ICANON)?;
let stdin = term::async_stdin();
// let mut seen_windows = HashMap::new();
// let mut seen_pids = HashMap::new();
let mut last_procs = Vec::new();
'app: loop {
@@ -59,7 +77,10 @@ fn main() -> Result<(), Error> {
if procs != last_procs {
println!();
println!("Waiting for: {}", compressed_list(&procs));
println!(
"Waiting for: {}",
compressed_list(&procs.iter().map(|(_, v)| v.clone()).collect::<Vec<_>>())
);
print!("Action? [d]elete, [t]erm, [k]ill, [q]uit)? ");
io::stdout().flush()?;
last_procs = procs.clone();
@@ -68,23 +89,23 @@ fn main() -> Result<(), Error> {
match stdin.recv_timeout(Duration::from_millis(50)) {
Ok(b'd') => {
println!("Asking everyone to quit.");
for proc in &procs {
conn.delete_window(proc.window)?;
for (window, _info) in &procs {
conn.delete_window(window)?;
}
}
Ok(b't') => {
println!("Telling everyone to quit.");
for proc in &procs {
if let Some(pid) = proc.pid {
let _ = kill(pid, Some(nix::sys::signal::SIGTERM))?;
for (_window, info) in &procs {
for pid in &info.pids {
let _ = kill(*pid, Some(nix::sys::signal::SIGTERM))?;
}
}
}
Ok(b'k') => {
println!("Quitting everyone.");
for proc in &procs {
if let Some(pid) = proc.pid {
let _ = kill(pid, Some(nix::sys::signal::SIGKILL))?;
for (_window, info) in &procs {
for pid in &info.pids {
let _ = kill(*pid, Some(nix::sys::signal::SIGKILL))?;
}
}
}
@@ -104,11 +125,11 @@ fn main() -> Result<(), Error> {
Ok(())
}
fn find_procs(config: &Config, conn: &mut x::XServer) -> Result<Vec<Proc>, Error> {
fn find_procs(config: &Config, conn: &mut x::XServer) -> Result<Vec<(XWindow, WindowInfo)>, Error> {
let mut procs = Vec::with_capacity(16);
conn.for_windows(|conn, window_id| {
match gather_window_details(&config, conn, window_id) {
match gather_window_details(&config, conn, window_id, &mut Hatred::default()) {
Ok(Some(proc)) => procs.push(proc),
Ok(None) => (),
Err(e) => eprintln!(
@@ -119,12 +140,12 @@ fn find_procs(config: &Config, conn: &mut x::XServer) -> Result<Vec<Proc>, Error
Ok(())
})?;
procs.sort_by_key(|proc| (proc.class.to_string(), proc.pid));
procs.sort_by_key(|(_window, info)| info.class.to_string());
Ok(procs)
}
fn compressed_list(procs: &[Proc]) -> String {
fn compressed_list(procs: &[WindowInfo]) -> String {
let mut buf = String::with_capacity(procs.len() * 32);
let mut last = procs[0].class.to_string();
buf.push_str(&last);
@@ -138,10 +159,13 @@ fn compressed_list(procs: &[Proc]) -> String {
buf.push_str(" (");
last = proc.class.to_string();
}
if let Some(pid) = proc.pid {
buf.push_str(&format!("{}, ", pid));
} else {
buf.push_str("?, ");
match proc.pids.len() {
0 => buf.push_str("?, "),
_ => {
for pid in &proc.pids {
buf.push_str(&format!("{}, ", pid));
}
}
}
}
buf.pop(); // comma space
@@ -154,46 +178,73 @@ fn gather_window_details(
config: &Config,
conn: &x::XServer,
window: x::XWindow,
) -> Result<Option<Proc>, Error> {
let class = conn
.read_class(window)
.with_context(|_| format_err!("finding class of {:?}", window))?;
hatred: &mut Hatred,
) -> Result<Option<(XWindow, WindowInfo)>, Error> {
let class = match conn.read_class(window) {
Ok(class) => class,
Err(e) => {
hatred
.blacklisted_windows
.insert(window, format!("read class failed: {:?}", e));
return Ok(None);
}
};
for ignore in &config.ignore {
if ignore.is_match(&class) {
return Ok(None);
}
}
let pids = conn
.pids(window)
.with_context(|_| format_err!("finding pid of {:?} ({:?})", class, window))?;
let pid = match pids.len() {
1 => {
let pid = pids[0];
match kill(pid, None) {
Ok(true) => (),
Ok(false) => {
eprintln!("{:?} (pid {}), wasn't even alive to start with", class, pid);
return Ok(None);
}
Err(other) => eprintln!("{:?} (pid {}): kill test failed: {:?}", class, pid, other),
}
let mut pids = match conn.pids(window) {
Ok(pids) => pids,
Err(e) => {
// TODO: complain somewhere?
Vec::new()
}
}
.into_iter()
.collect::<HashSet<_>>();
// Note: `pids` will almost always have a length of 1, sometimes zero.
// We're processing it as a list for code simplicity
Some(pid)
// if it's already blacklisted, we don't care
pids.retain(|pid| !hatred.blacklisted_pids.contains_key(pid));
pids.retain(|&pid| match kill(pid, None) {
Ok(true) => true,
Ok(false) => {
hatred
.blacklisted_pids
.insert(pid, "reported nonexistent pid".to_string());
false
}
Err(e) => {
hatred
.blacklisted_pids
.insert(pid, format!("reported erroring pid: {:?}", e));
false
}
});
let supports_delete = match conn.supports_delete(window) {
Ok(val) => val,
Err(_) => {
// TODO: don't totally ignore
false
}
_ => None,
};
Ok(Some(Proc {
Ok(Some((
window,
pid,
supported_protocols: conn
.supported_protocols(window)
.with_context(|_| format_err!("finding protocols of {:?} (pid {:?})", class, pid))?,
class,
}))
WindowInfo {
pids,
class,
supports_delete,
title: String::new(), // TODO
},
)))
}
fn kill(pid: u32, signal: Option<nix::sys::signal::Signal>) -> Result<bool, Error> {
@@ -211,12 +262,16 @@ fn kill(pid: u32, signal: Option<nix::sys::signal::Signal>) -> Result<bool, Erro
#[cfg(test)]
mod tests {
fn proc(class: &str, pid: u32) -> ::Proc {
::Proc {
use std::collections::HashSet;
fn proc(class: &str, pid: u32) -> ::WindowInfo {
let mut pids = HashSet::new();
pids.insert(pid);
::WindowInfo {
class: class.to_string(),
pid: Some(pid),
supported_protocols: Vec::new(),
window: ::x::XWindow(0),
pids,
supports_delete: true,
title: String::new(),
}
}
@@ -10,7 +10,7 @@ pub struct XServer {
atoms: ExtraAtoms,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub struct XWindow(pub xcb::Window);
#[derive(Copy, Clone, Debug)]
@@ -66,7 +66,7 @@ impl XServer {
Ok(String::from_utf8_lossy(class).to_string())
}
pub fn delete_window(&mut self, window: XWindow) -> Result<(), Error> {
pub fn delete_window(&mut self, window: &XWindow) -> Result<(), Error> {
let event = xcb::xproto::ClientMessageEvent::new(
32,
window.0,
@@ -91,11 +91,13 @@ impl XServer {
}
pub fn pids(&self, window: XWindow) -> Result<Vec<u32>, Error> {
Ok(self.get_property::<u32>(window, self.atoms.net_wm_pid, xp::ATOM_CARDINAL, 2)?)
Ok(self.get_property::<u32>(window, self.atoms.net_wm_pid, xp::ATOM_CARDINAL, 64)?)
}
pub fn supported_protocols(&self, window: XWindow) -> Result<Vec<xcb::Atom>, Error> {
Ok(self.get_property::<u32>(window, self.atoms.wm_protocols, xp::ATOM_ATOM, 1_024)?)
pub fn supports_delete(&self, window: XWindow) -> Result<bool, Error> {
let supported =
self.get_property::<u32>(window, self.atoms.wm_protocols, xp::ATOM_ATOM, 1_024)?;
Ok(supported.contains(&self.atoms.wm_delete_window))
}
fn get_property<T: Clone>(

0 comments on commit 1e338fb

Please sign in to comment.