Stop script-vars and close windows on eww kill

This commit is contained in:
elkowar 2020-10-23 20:28:18 +02:00
parent e427101c19
commit 3849835ff9
8 changed files with 129 additions and 69 deletions

25
Cargo.lock generated
View file

@ -247,6 +247,16 @@ dependencies = [
"syn 1.0.44", "syn 1.0.44",
] ]
[[package]]
name = "ctrlc"
version = "3.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b57a92e9749e10f25a171adcebfafe72991d45e7ec2dcb853e8f83d9dafaeb08"
dependencies = [
"nix 0.18.0",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "debug_stub_derive" name = "debug_stub_derive"
version = "0.3.0" version = "0.3.0"
@ -300,6 +310,7 @@ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",
"crossbeam-channel", "crossbeam-channel",
"ctrlc",
"debug_stub_derive", "debug_stub_derive",
"derive_more", "derive_more",
"extend", "extend",
@ -317,7 +328,7 @@ dependencies = [
"libc", "libc",
"log 0.4.11", "log 0.4.11",
"maplit", "maplit",
"nix", "nix 0.19.0",
"notify", "notify",
"num", "num",
"popol", "popol",
@ -1049,6 +1060,18 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "nix"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83450fe6a6142ddd95fb064b746083fc4ef1705fe81f64a64e1d4b39f54a1055"
dependencies = [
"bitflags",
"cc",
"cfg-if",
"libc",
]
[[package]] [[package]]
name = "nix" name = "nix"
version = "0.19.0" version = "0.19.0"

View file

@ -45,6 +45,7 @@ popol = "0.3"
nix = "0.19" nix = "0.19"
smart-default = "0.6" smart-default = "0.6"
filedescriptor = "0.7" filedescriptor = "0.7"
ctrlc = { version = "3.1", features = [ "termination" ] }
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.6.1" pretty_assertions = "0.6.1"

View file

@ -61,6 +61,9 @@ impl App {
EwwCommand::ReloadCss(css) => self.load_css(&css), EwwCommand::ReloadCss(css) => self.load_css(&css),
EwwCommand::KillServer => { EwwCommand::KillServer => {
log::info!("Received kill command, stopping server!"); log::info!("Received kill command, stopping server!");
self.script_var_handler.stop();
self.windows.values().for_each(|w| w.gtk_window.close());
script_var_process::on_application_death();
std::process::exit(0); std::process::exit(0);
} }
EwwCommand::OpenWindow { window_name, pos, size } => self.open_window(&window_name, pos, size), EwwCommand::OpenWindow { window_name, pos, size } => self.open_window(&window_name, pos, size),

View file

@ -1,3 +1,5 @@
use std::process::Command;
use anyhow::*; use anyhow::*;
use crate::ensure_xml_tag_is; use crate::ensure_xml_tag_is;
@ -11,6 +13,12 @@ pub struct PollScriptVar {
pub interval: std::time::Duration, pub interval: std::time::Duration,
} }
impl PollScriptVar {
pub fn run_once(&self) -> Result<PrimitiveValue> {
run_command(&self.command)
}
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct TailScriptVar { pub struct TailScriptVar {
pub name: VarName, pub name: VarName,
@ -33,7 +41,7 @@ impl ScriptVar {
pub fn initial_value(&self) -> Result<PrimitiveValue> { pub fn initial_value(&self) -> Result<PrimitiveValue> {
match self { match self {
ScriptVar::Poll(x) => Ok(crate::run_command(&x.command)?), ScriptVar::Poll(x) => Ok(run_command(&x.command)?),
ScriptVar::Tail(_) => Ok(PrimitiveValue::from_string(String::new())), ScriptVar::Tail(_) => Ok(PrimitiveValue::from_string(String::new())),
} }
} }
@ -51,3 +59,10 @@ impl ScriptVar {
} }
} }
} }
/// Run a command and get the output
fn run_command(cmd: &str) -> Result<PrimitiveValue> {
let output = String::from_utf8(Command::new("/bin/sh").arg("-c").arg(cmd).output()?.stdout)?;
let output = output.trim_matches('\n');
Ok(PrimitiveValue::from(output))
}

View file

@ -4,7 +4,7 @@ use crate::{
value::{AttrName, AttrValueElement, VarName}, value::{AttrName, AttrValueElement, VarName},
}; };
use anyhow::*; use anyhow::*;
use std::{collections::HashMap, process::Command, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use crate::value::{AttrValue, PrimitiveValue}; use crate::value::{AttrValue, PrimitiveValue};
@ -171,10 +171,3 @@ impl EwwState {
} }
} }
} }
/// Run a command and get the output
pub fn run_command(cmd: &str) -> Result<PrimitiveValue> {
let output = String::from_utf8(Command::new("/bin/sh").arg("-c").arg(cmd).output()?.stdout)?;
let output = output.trim_matches('\n');
Ok(PrimitiveValue::from(output))
}

View file

@ -70,6 +70,7 @@ lazy_static::lazy_static! {
fn main() { fn main() {
pretty_env_logger::init(); pretty_env_logger::init();
if let Err(e) = try_main() { if let Err(e) = try_main() {
eprintln!("{:?}", e); eprintln!("{:?}", e);
} }
@ -181,6 +182,13 @@ fn try_main() -> Result<()> {
do_detach()?; do_detach()?;
} }
ctrlc::set_handler(|| {
println!("Shutting down eww daemon...");
script_var_handler::script_var_process::on_application_death();
std::process::exit(0);
})
.context("Error setting signal hook")?;
initialize_server(action)?; initialize_server(action)?;
} }
} }
@ -344,13 +352,12 @@ fn do_detach() -> Result<()> {
let fd = file.as_raw_fd(); let fd = file.as_raw_fd();
if nix::unistd::isatty(1)? { if nix::unistd::isatty(1)? {
nix::unistd::dup2(std::io::stdout().as_raw_fd(), fd)?; nix::unistd::dup2(fd, std::io::stdout().as_raw_fd())?;
} }
if nix::unistd::isatty(2)? { if nix::unistd::isatty(2)? {
nix::unistd::dup2(std::io::stderr().as_raw_fd(), fd)?; nix::unistd::dup2(fd, std::io::stderr().as_raw_fd())?;
} }
nix::unistd::setsid().context("Failed to run setsid")?;
Ok(()) Ok(())
} }

View file

@ -1,12 +1,14 @@
use std::{collections::HashMap, ffi::CString, io::BufReader, time::Duration}; use std::{collections::HashMap, time::Duration};
use crate::{app, config, eww_state, util, value::PrimitiveValue}; use crate::{app, config, util, value::PrimitiveValue};
use anyhow::*; use anyhow::*;
use app::EwwCommand; use app::EwwCommand;
use glib; use glib;
use itertools::Itertools; use itertools::Itertools;
use scheduled_executor; use scheduled_executor;
use std::{io::BufRead, os::unix::io::AsRawFd}; use std::io::BufRead;
use self::script_var_process::ScriptVarProcess;
/// Handler that manages running and updating [ScriptVar]s /// Handler that manages running and updating [ScriptVar]s
pub struct ScriptVarHandler { pub struct ScriptVarHandler {
@ -68,8 +70,7 @@ impl ScriptVarHandler {
var.interval, var.interval,
glib::clone!(@strong var, @strong evt_send => move |_| { glib::clone!(@strong var, @strong evt_send => move |_| {
let result: Result<_> = try { let result: Result<_> = try {
let output = eww_state::run_command(&var.command)?; evt_send.send(app::EwwCommand::UpdateVar(var.name.clone(), var.run_once()?))?;
evt_send.send(app::EwwCommand::UpdateVar(var.name.clone(), output))?;
}; };
util::print_result_err("while running script-var command", &result); util::print_result_err("while running script-var command", &result);
}), }),
@ -85,25 +86,18 @@ impl ScriptVarHandler {
log::info!("initializing handler for tail script vars"); log::info!("initializing handler for tail script vars");
let mut sources = popol::Sources::with_capacity(tail_script_vars.len()); let mut sources = popol::Sources::with_capacity(tail_script_vars.len());
// TODO clean up this unnecessary vec, it really should not be needed. let mut script_var_processes: HashMap<_, ScriptVarProcess> = HashMap::new();
// should be possibel to just keep a BufReader in TailVarProcess directly
let mut command_children = Vec::new();
let mut command_out_handles: HashMap<_, BufReader<filedescriptor::FileDescriptor>> = HashMap::new();
for var in tail_script_vars { for var in tail_script_vars {
match TailVarProcess::run(&var.command) { match ScriptVarProcess::run(&var.command) {
Ok(process) => { Ok(process) => {
command_out_handles.insert(var.name.clone(), BufReader::new(process.out_fd.try_clone()?)); sources.register(var.name.clone(), process.stdout_reader.get_ref(), popol::interest::READ);
command_children.push(process); script_var_processes.insert(var.name.clone(), process);
} }
Err(err) => eprintln!("Failed to launch script-var command for tail: {:?}", err), Err(err) => eprintln!("Failed to launch script-var command for tail: {:?}", err),
} }
} }
for (var_name, handle) in command_out_handles.iter() {
sources.register(var_name.clone(), handle.get_ref(), popol::interest::READ);
}
let mut events = popol::Events::with_capacity(tail_script_vars.len()); let mut events = popol::Events::with_capacity(tail_script_vars.len());
let evt_send = self.evt_send.clone(); let evt_send = self.evt_send.clone();
// TODO this is rather ugly // TODO this is rather ugly
@ -113,26 +107,24 @@ impl ScriptVarHandler {
sources.wait(&mut events)?; sources.wait(&mut events)?;
for (var_name, event) in events.iter() { for (var_name, event) in events.iter() {
if event.readable { if event.readable {
let handle = command_out_handles let handle = script_var_processes
.get_mut(var_name) .get_mut(var_name)
.with_context(|| format!("No command output handle found for variable '{}'", var_name))?; .with_context(|| format!("No command output handle found for variable '{}'", var_name))?;
let mut buffer = String::new(); let mut buffer = String::new();
handle.read_line(&mut buffer)?; handle.stdout_reader.read_line(&mut buffer)?;
evt_send.send(EwwCommand::UpdateVar( evt_send.send(EwwCommand::UpdateVar(
var_name.clone(), var_name.to_owned(),
PrimitiveValue::from_string(buffer.trim_matches('\n').to_owned()), PrimitiveValue::from_string(buffer.trim_matches('\n').to_owned()),
))?; ))?;
} else if event.hangup { } else if event.hangup {
command_out_handles.remove(var_name); script_var_processes.remove(var_name);
sources.unregister(var_name); sources.unregister(var_name);
} }
} }
}; };
util::print_result_err("in script-var tail handler thread", &result); util::print_result_err("in script-var tail handler thread", &result);
} }
script_var_processes.values().for_each(|process| process.kill());
// stop child processes after exit
command_children.drain(..).for_each(|process| process.kill());
}); });
self.tail_handler_thread = Some(thread_handle); self.tail_handler_thread = Some(thread_handle);
Ok(()) Ok(())
@ -145,42 +137,68 @@ impl Drop for ScriptVarHandler {
} }
} }
#[derive(Debug)] pub mod script_var_process {
struct TailVarProcess { use anyhow::*;
pid: nix::unistd::Pid, use nix::{
out_fd: filedescriptor::FileDescriptor, sys::{signal, wait},
unistd::Pid,
};
use std::{io::BufReader, process::Stdio, sync::Mutex};
use crate::util;
lazy_static::lazy_static! {
static ref SCRIPT_VAR_CHILDREN: Mutex<Vec<u32>> = Mutex::new(Vec::new());
} }
impl TailVarProcess { fn terminate_pid(pid: u32) {
pub fn run(command: &str) -> Result<Self> { println!("Killing pid: {}", pid);
use nix::unistd::*; let result = signal::kill(Pid::from_raw(pid as i32), signal::SIGTERM);
util::print_result_err("While killing tail-var child processes", &result);
let pipe = filedescriptor::Pipe::new()?; let wait_result = wait::waitpid(Pid::from_raw(pid as i32), None);
util::print_result_err("While killing tail-var child processes", &wait_result);
match unsafe { fork()? } {
ForkResult::Child => {
std::mem::drop(pipe.read);
dup2(pipe.write.as_raw_fd(), std::io::stdout().as_raw_fd())?;
setpgid(Pid::from_raw(0), Pid::from_raw(0))?;
execv(
CString::new("/bin/sh")?.as_ref(),
&[CString::new("/bin/sh")?, CString::new("-c")?, CString::new(command)?],
)?;
unreachable!("Child fork called exec, thus the process was replaced by the command the user provided");
} }
ForkResult::Parent { child, .. } => {
std::mem::drop(pipe.write); /// This function should be called in the signal handler, killing all child processes.
setpgid(child, child)?; pub fn on_application_death() {
Ok(TailVarProcess { SCRIPT_VAR_CHILDREN
pid: child, .lock()
out_fd: pipe.read, .unwrap()
.drain(..)
.for_each(|pid| terminate_pid(pid));
}
pub struct ScriptVarProcess {
child: std::process::Child,
pub stdout_reader: BufReader<std::process::ChildStdout>,
}
impl ScriptVarProcess {
pub(super) fn run(command: &str) -> Result<Self> {
println!("Running {}", command);
let mut child = std::process::Command::new("/bin/sh")
.arg("-c")
.arg(command)
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.stdin(Stdio::null())
.spawn()?;
SCRIPT_VAR_CHILDREN.lock().unwrap().push(child.id());
Ok(ScriptVarProcess {
stdout_reader: BufReader::new(child.stdout.take().unwrap()),
child,
}) })
} }
pub(super) fn kill(&self) {
SCRIPT_VAR_CHILDREN.lock().unwrap().retain(|item| *item != self.child.id());
terminate_pid(self.child.id());
} }
} }
pub fn kill(self) { impl Drop for ScriptVarProcess {
let result = nix::sys::signal::kill(self.pid, Some(nix::sys::signal::SIGTERM)); fn drop(&mut self) {
util::print_result_err("Killing tail-var process", &result); self.kill();
}
} }
} }

View file

@ -16,9 +16,9 @@ const CMD_STRING_PLACEHODLER: &str = "{}";
/// Run a command that was provided as an attribute. This command may use a /// Run a command that was provided as an attribute. This command may use a
/// placeholder ('{}') which will be replaced by the value provided as [`arg`] /// placeholder ('{}') which will be replaced by the value provided as [`arg`]
pub fn run_command<T: std::fmt::Display>(cmd: &str, arg: T) { pub(self) fn run_command<T: std::fmt::Display>(cmd: &str, arg: T) {
let cmd = cmd.replace(CMD_STRING_PLACEHODLER, &format!("{}", arg)); let cmd = cmd.replace(CMD_STRING_PLACEHODLER, &format!("{}", arg));
if let Err(e) = Command::new("/bin/sh").arg("-c").arg(cmd).output() { if let Err(e) = Command::new("/bin/sh").arg("-c").arg(cmd).spawn() {
eprintln!("{}", e); eprintln!("{}", e);
} }
} }