Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
feat: restart once=false startup programs on config reload
src/server/config.rs | 2 +-
src/server/run_server.rs | 63 ++++++---------------
src/server/server.rs | 1 +
src/server/window_manager.rs | 132 +++++++++++++++++++++++++++++++++----------
4 files changed, 120 insertions(+), 78 deletions(-)
diff --git a/src/server/config.rs b/src/server/config.rs
index 50d2ecb..058d22f 100644
--- a/src/server/config.rs
+++ b/src/server/config.rs
@@ -163,7 +163,7 @@ pub struct GestureBindConfig {
pub command: Option<String>,
}
-#[derive(Debug, Deserialize, Clone)]
+#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
pub struct StartupConfig {
pub exec: String,
#[serde(default)]
diff --git a/src/server/run_server.rs b/src/server/run_server.rs
index 9328a6f..e2f9cff 100644
--- a/src/server/run_server.rs
+++ b/src/server/run_server.rs
@@ -182,53 +182,17 @@ pub fn run_server() {
std::process::exit(1);
}
- let mut startup_pids = Vec::new();
// Spawn TOML startup programs
- for prog in &server.wm.startup {
- log::info!("spawning TOML startup program: {}", prog.exec);
- let cmd = prog.exec.clone();
+ let current_startup = server.wm.startup.clone();
+ for prog in current_startup {
unsafe {
- match nix::unistd::fork() {
- Ok(nix::unistd::ForkResult::Child) => {
- process::cleanup_child();
- std::env::set_var("WAYLAND_DISPLAY", &socket_str);
-
- if !args.no_xwayland && !server.xwayland.is_null() {
- let xwayland_cast = server.xwayland as *mut server::WlrXwayland;
- if !(*xwayland_cast).display_name.is_null() {
- let display_name = CStr::from_ptr((*xwayland_cast).display_name)
- .to_string_lossy()
- .into_owned();
- std::env::set_var("DISPLAY", display_name);
- }
- }
-
- let cmd_c = CString::new(cmd).unwrap();
- let sh_c = CString::new("/bin/sh").unwrap();
- let c_c = CString::new("-c").unwrap();
- let args = [sh_c.as_c_str(), c_c.as_c_str(), cmd_c.as_c_str()];
-
- let env: Vec<CString> = std::env::vars()
- .map(|(k, v)| CString::new(format!("{}={}", k, v)).unwrap())
- .collect();
- let env_ptrs: Vec<&CStr> = env.iter().map(|s| s.as_c_str()).collect();
-
- let _ = nix::unistd::execve(&sh_c, &args, &env_ptrs);
- std::process::exit(1);
- }
- Ok(nix::unistd::ForkResult::Parent { child }) => {
- startup_pids.push(child);
- }
- Err(e) => {
- log::error!("failed to fork child for startup program: {}", e);
- }
- }
+ server.wm.spawn_startup_program(prog);
}
}
struct ServerGuard {
init_pid: Option<nix::unistd::Pid>,
- startup_pids: Vec<nix::unistd::Pid>,
+ wm: *mut crate::window_manager::WindowManager,
}
impl Drop for ServerGuard {
fn drop(&mut self) {
@@ -239,12 +203,16 @@ pub fn run_server() {
nix::sys::signal::Signal::SIGTERM,
);
}
- for pid in &self.startup_pids {
- log::info!("sending SIGTERM to startup program pid {}", pid);
- let _ = nix::sys::signal::kill(
- *pid,
- nix::sys::signal::Signal::SIGTERM,
- );
+ unsafe {
+ if !self.wm.is_null() {
+ for (_, pid) in &(*self.wm).startup_pids {
+ log::info!("sending SIGTERM to startup program pid {}", pid);
+ let _ = nix::sys::signal::kill(
+ *pid,
+ nix::sys::signal::Signal::SIGTERM,
+ );
+ }
+ }
}
}
}
@@ -294,7 +262,7 @@ pub fn run_server() {
let _guard = ServerGuard {
init_pid: child_pgid,
- startup_pids,
+ wm: &mut server.wm as *mut crate::window_manager::WindowManager,
};
log::info!("running server");
@@ -303,5 +271,6 @@ pub fn run_server() {
}
log::info!("shutting down server");
+ std::mem::drop(_guard);
server.deinit();
}
diff --git a/src/server/server.rs b/src/server/server.rs
index 036381a..07a0bd2 100644
--- a/src/server/server.rs
+++ b/src/server/server.rs
@@ -832,6 +832,7 @@ impl Default for Server {
std::ptr::write(&mut (*server.as_mut_ptr()).wm.gesture_binds, Vec::new());
std::ptr::write(&mut (*server.as_mut_ptr()).wm.ipc_rx, None);
std::ptr::write(&mut (*server.as_mut_ptr()).wm.startup, Vec::new());
+ std::ptr::write(&mut (*server.as_mut_ptr()).wm.startup_pids, Vec::new());
std::ptr::write(&mut (*server.as_mut_ptr()).wm.status_sender, None);
std::ptr::write(&mut (*server.as_mut_ptr()).layer_shell.surfaces, crate::slotmap::SlotMap::new());
std::ptr::write(&mut (*server.as_mut_ptr()).inspector, crate::inspector::Inspector::new());
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index 6cf1129..03c81c3 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -66,6 +66,7 @@ pub struct WindowManager {
pub ipc_rx: Option<std::sync::mpsc::Receiver<crate::ipc_server::IpcRequest>>,
pub ipc_timer: *mut ffi::wl_event_source,
pub startup: Vec<crate::config::StartupConfig>,
+ pub startup_pids: Vec<(crate::config::StartupConfig, nix::unistd::Pid)>,
pub status_sender: Option<crate::status_server::StatusSender>,
pub output_scale: f32,
pub input_rules: Vec<crate::config::InputDeviceConfigRule>,
@@ -127,6 +128,7 @@ impl WindowManager {
self.ipc_rx = None;
self.ipc_timer = std::ptr::null_mut();
self.startup = Vec::new();
+ self.startup_pids = Vec::new();
self.status_sender = None;
self.input_rules = Vec::new();
self.input_config = crate::config::InputConfig::default();
@@ -1248,29 +1250,19 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
}
Action::Reload => {
log::info!("monolithic execute_action: Reload requested");
- if let Some(path) = crate::config::default_config_path() {
- match crate::config::parse_config(&path, self) {
- Ok(()) => {
- self.dirty_windowing();
- let _ = std::process::Command::new("notify-send")
- .arg("cce")
- .arg("Configuration reloaded successfully")
- .spawn();
- }
- Err(e) => {
- log::error!("failed to reload config: {}", e);
- let _ = std::process::Command::new("notify-send")
- .arg("cce")
- .arg(format!("Failed to reload config:\n{}", e))
- .spawn();
- }
+ match self.reload_config() {
+ Ok(()) => {
+ let _ = std::process::Command::new("notify-send")
+ .arg("cce")
+ .arg("Configuration reloaded successfully")
+ .spawn();
+ }
+ Err(e) => {
+ let _ = std::process::Command::new("notify-send")
+ .arg("cce")
+ .arg(format!("Failed to reload config:\n{}", e))
+ .spawn();
}
- } else {
- log::error!("no config file found to reload");
- let _ = std::process::Command::new("notify-send")
- .arg("cce")
- .arg("No config file found to reload")
- .spawn();
}
}
Action::Exit => {
@@ -1676,16 +1668,11 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
"ok\n".to_string()
}
"reload" => {
- if let Some(path) = crate::config::default_config_path() {
- match crate::config::parse_config(&path, self) {
- Ok(()) => {
- self.dirty_windowing();
- "ok\n".to_string()
- }
+ unsafe {
+ match self.reload_config() {
+ Ok(()) => "ok\n".to_string(),
Err(e) => format!("error: failed to reload config: {}\n", e),
}
- } else {
- "error: no config file found\n".to_string()
}
}
"retile" => {
@@ -1895,6 +1882,91 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
curr = next;
}
}
+
+ pub unsafe fn spawn_startup_program(&mut self, prog: crate::config::StartupConfig) {
+ log::info!("spawning TOML startup program: {}", prog.exec);
+ let cmd = prog.exec.clone();
+ match nix::unistd::fork() {
+ Ok(nix::unistd::ForkResult::Child) => {
+ crate::process::cleanup_child();
+
+ if !self.server.is_null() && !(*self.server).xwayland.is_null() {
+ let xwayland_cast = (*self.server).xwayland as *mut crate::server::WlrXwayland;
+ if !(*xwayland_cast).display_name.is_null() {
+ let display_name = std::ffi::CStr::from_ptr((*xwayland_cast).display_name)
+ .to_string_lossy()
+ .into_owned();
+ std::env::set_var("DISPLAY", display_name);
+ }
+ }
+
+ let env: Vec<std::ffi::CString> = std::env::vars()
+ .map(|(k, v)| std::ffi::CString::new(format!("{}={}", k, v)).unwrap())
+ .collect();
+ let env_ptrs: Vec<&std::ffi::CStr> = env.iter().map(|s| s.as_c_str()).collect();
+ let sh_c = std::ffi::CString::new("/bin/sh").unwrap();
+ let c_c = std::ffi::CString::new("-c").unwrap();
+ let cmd_c = std::ffi::CString::new(cmd).unwrap();
+ let args = [sh_c.as_c_str(), c_c.as_c_str(), cmd_c.as_c_str()];
+ let _ = nix::unistd::execve(&sh_c, &args, &env_ptrs);
+ std::process::exit(1);
+ }
+ Ok(nix::unistd::ForkResult::Parent { child }) => {
+ self.startup_pids.push((prog, child));
+ }
+ Err(e) => {
+ log::error!("failed to fork child for startup program: {}", e);
+ }
+ }
+ }
+
+ pub unsafe fn reload_config(&mut self) -> Result<(), String> {
+ if let Some(path) = crate::config::default_config_path() {
+ let old_pids = std::mem::take(&mut self.startup_pids);
+ match crate::config::parse_config(&path, self) {
+ Ok(()) => {
+ self.dirty_windowing();
+
+ // Process old PIDs
+ for (old_prog, old_pid) in old_pids {
+ // If it is still in new startup and once == true, keep it running
+ let still_exists_and_once = self.startup.iter().any(|p| p.exec == old_prog.exec && p.once);
+ if still_exists_and_once {
+ self.startup_pids.push((old_prog, old_pid));
+ } else {
+ log::info!("Terminating old startup program pid {} ({})", old_pid, old_prog.exec);
+ let _ = nix::sys::signal::kill(old_pid, nix::sys::signal::Signal::SIGTERM);
+ }
+ }
+
+ // Spawn new/restarted programs
+ let current_startup = self.startup.clone();
+ for prog in current_startup {
+ if prog.once {
+ // Only spawn if not already running
+ let running = self.startup_pids.iter().any(|(p, _)| p.exec == prog.exec);
+ if !running {
+ self.spawn_startup_program(prog);
+ }
+ } else {
+ // once == false: spawn a new instance
+ self.spawn_startup_program(prog);
+ }
+ }
+
+ Ok(())
+ }
+ Err(e) => {
+ self.startup_pids = old_pids;
+ log::error!("failed to reload config: {}", e);
+ Err(e)
+ }
+ }
+ } else {
+ log::error!("no config file found to reload");
+ Err("No config file found".to_string())
+ }
+ }
}
unsafe extern "C" fn handle_ipc_timer(data: *mut std::ffi::c_void) -> std::os::raw::c_int {