leftwm_core/utils/
child_process.rs1use std::cmp::Reverse;
4use std::collections::BinaryHeap;
5use std::collections::HashMap;
6use std::ffi::OsStr;
7use std::fs;
8use std::iter::{Extend, FromIterator};
9use std::path::{Path, PathBuf};
10use std::process::{Child, Command, Stdio};
11use std::sync::{atomic::AtomicBool, Arc};
12
13use xdg::BaseDirectories;
14
15use crate::errors::Result;
16
17pub type ChildID = u32;
18
19#[derive(Default)]
20pub struct Nanny {}
21
22impl Nanny {
23 fn get_config_dir() -> Result<PathBuf> {
30 BaseDirectories::with_prefix("leftwm")?
31 .create_config_directory("")
32 .map_err(Into::into)
33 }
34
35 fn run_script(path: &Path) -> Result<Child> {
37 Command::new(path)
38 .stdin(Stdio::null())
39 .stdout(Stdio::null())
40 .stderr(Stdio::null())
41 .spawn()
42 .map_err(Into::into)
43 }
44
45 pub fn run_global_up_script() -> Result<Child> {
52 let mut path = Self::get_config_dir()?;
53 let mut scripts = Self::get_files_in_path_with_ext(&path, "up")?;
54
55 while let Some(Reverse(script)) = scripts.pop() {
56 if let Err(e) = Self::run_script(&script) {
57 tracing::error!("Unable to run script {script:?}, error: {e}");
58 }
59 }
60
61 path.push("up");
62 Self::run_script(&path)
63 }
64
65 fn get_files_in_path_with_ext(
71 path: impl AsRef<Path>,
72 ext: impl AsRef<OsStr>,
73 ) -> Result<BinaryHeap<Reverse<PathBuf>>> {
74 let dir = fs::read_dir(&path)?;
75
76 let mut files = BinaryHeap::new();
77
78 for entry in dir.flatten() {
79 let file = entry.path();
80
81 if let Some(extension) = file.extension() {
82 if extension == ext.as_ref() {
83 files.push(Reverse(file));
84 }
85 }
86 }
87
88 Ok(files)
89 }
90
91 pub fn boot_current_theme() -> Result<Child> {
98 let mut path = Self::get_config_dir()?;
99 path.push("themes");
100 path.push("current");
101 path.push("up");
102 Self::run_script(&path)
103 }
104}
105
106#[derive(Debug, Default)]
108pub struct Children {
109 inner: HashMap<ChildID, Child>,
110}
111
112impl Children {
113 #[must_use]
114 pub fn new() -> Self {
115 Self::default()
116 }
117 #[must_use]
118 pub fn len(&self) -> usize {
119 self.inner.len()
120 }
121 #[must_use]
122 pub fn is_empty(&self) -> bool {
123 self.inner.is_empty()
124 }
125 pub fn insert(&mut self, child: Child) -> bool {
131 self.inner.insert(child.id(), child).is_none()
132 }
133
134 pub fn merge(&mut self, reaper: Self) {
136 self.inner.extend(reaper.inner);
137 }
138
139 pub fn remove_finished_children(&mut self) {
141 self.inner
142 .retain(|_, child| child.try_wait().map_or(true, |ret| ret.is_none()));
143 }
144}
145
146impl FromIterator<Child> for Children {
147 fn from_iter<T: IntoIterator<Item = Child>>(iter: T) -> Self {
148 Self {
149 inner: iter.into_iter().map(|child| (child.id(), child)).collect(),
150 }
151 }
152}
153
154impl Extend<Child> for Children {
155 fn extend<T: IntoIterator<Item = Child>>(&mut self, iter: T) {
156 self.inner
157 .extend(iter.into_iter().map(|child| (child.id(), child)));
158 }
159}
160
161pub fn register_child_hook(flag: Arc<AtomicBool>) {
164 _ = signal_hook::flag::register(signal_hook::consts::signal::SIGCHLD, flag)
165 .map_err(|err| tracing::error!("Cannot register SIGCHLD signal handler: {:?}", err));
166}
167
168pub fn exec_shell(command: &str, children: &mut Children) -> Option<ChildID> {
171 exec_shell_with_args(command, Vec::new(), children)
172}
173
174pub fn exec_shell_with_args(
177 command: &str,
178 args: Vec<String>,
179 children: &mut Children,
180) -> Option<ChildID> {
181 let child = Command::new(command)
182 .args(args)
183 .stdin(Stdio::null())
184 .stdout(Stdio::null())
185 .stderr(Stdio::null())
186 .spawn()
187 .ok()?;
188 let pid = child.id();
189 children.insert(child);
190 Some(pid)
191}