gardesk/gar / 15a7748

Browse files

kill spawned children on exit to prevent orphaned processes

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
15a77487ba5a40f8b0f94790e6bf3b2171b02175
Parents
255874d
Tree
ea22096

2 changed files

StatusFile+-
M gar/src/config/lua.rs 47 12
M gar/src/x11/events.rs 45 33
gar/src/config/lua.rsmodified
@@ -68,6 +68,8 @@ pub struct LuaState {
6868
     pub callbacks: Vec<mlua::RegistryKey>,
6969
     pub rules: Vec<WindowRule>,
7070
     pub exec_once_cmds: HashSet<String>,
71
+    /// PIDs of processes spawned via gar.exec()/gar.exec_once()
72
+    pub spawned_pids: Vec<u32>,
7173
 }
7274
 
7375
 impl Default for LuaState {
@@ -78,6 +80,24 @@ impl Default for LuaState {
7880
             callbacks: Vec::new(),
7981
             rules: Vec::new(),
8082
             exec_once_cmds: HashSet::new(),
83
+            spawned_pids: Vec::new(),
84
+        }
85
+    }
86
+}
87
+
88
+impl LuaState {
89
+    /// Kill all processes spawned via gar.exec()/gar.exec_once()
90
+    /// Called on gar shutdown to clean up child processes
91
+    pub fn kill_spawned_children(&self) {
92
+        tracing::info!("Killing {} spawned child processes", self.spawned_pids.len());
93
+        for &pid in &self.spawned_pids {
94
+            // Check if process still exists before trying to kill
95
+            // SAFETY: Sending signal 0 just checks if process exists
96
+            let exists = unsafe { libc::kill(pid as i32, 0) == 0 };
97
+            if exists {
98
+                tracing::debug!("Sending SIGTERM to PID {}", pid);
99
+                unsafe { libc::kill(pid as i32, libc::SIGTERM); }
100
+            }
81101
         }
82102
     }
83103
 }
@@ -619,33 +639,48 @@ impl LuaConfig {
619639
     }
620640
 
621641
     fn register_exec(&self, gar: &Table) -> LuaResult<()> {
622
-        let exec_fn = self.lua.create_function(|_, cmd: String| {
642
+        // gar.exec(cmd) - spawn a command, track PID for cleanup on exit
643
+        let state = Arc::clone(&self.state);
644
+        let exec_fn = self.lua.create_function(move |_, cmd: String| {
623645
             tracing::debug!("exec: {}", cmd);
624
-            std::process::Command::new("sh")
646
+            if let Ok(child) = std::process::Command::new("sh")
625647
                 .arg("-c")
626648
                 .arg(&cmd)
627649
                 .spawn()
628
-                .ok();
650
+            {
651
+                let pid = child.id();
652
+                tracing::debug!("exec: spawned PID {}", pid);
653
+                if let Ok(mut state) = state.lock() {
654
+                    state.spawned_pids.push(pid);
655
+                }
656
+            }
629657
             Ok(())
630658
         })?;
631659
         gar.set("exec", exec_fn)?;
632660
 
633661
         // gar.exec_once(cmd) - only run if not already run this session
634
-        let state = Arc::clone(&self.state);
662
+        let state_once = Arc::clone(&self.state);
635663
         let exec_once_fn = self.lua.create_function(move |_, cmd: String| {
636
-            let mut state = state.lock().unwrap();
637
-            if state.exec_once_cmds.contains(&cmd) {
638
-                tracing::debug!("exec_once: skipping already-run command: {}", cmd);
639
-                return Ok(());
664
+            {
665
+                let state = state_once.lock().unwrap();
666
+                if state.exec_once_cmds.contains(&cmd) {
667
+                    tracing::debug!("exec_once: skipping already-run command: {}", cmd);
668
+                    return Ok(());
669
+                }
640670
             }
641671
             tracing::info!("exec_once: {}", cmd);
642
-            state.exec_once_cmds.insert(cmd.clone());
643
-            drop(state); // Release lock before spawning
644
-            std::process::Command::new("sh")
672
+            if let Ok(child) = std::process::Command::new("sh")
645673
                 .arg("-c")
646674
                 .arg(&cmd)
647675
                 .spawn()
648
-                .ok();
676
+            {
677
+                let pid = child.id();
678
+                tracing::debug!("exec_once: spawned PID {}", pid);
679
+                if let Ok(mut state) = state_once.lock() {
680
+                    state.exec_once_cmds.insert(cmd);
681
+                    state.spawned_pids.push(pid);
682
+                }
683
+            }
649684
             Ok(())
650685
         })?;
651686
         gar.set("exec_once", exec_once_fn)
gar/src/x11/events.rsmodified
@@ -1943,43 +1943,50 @@ impl WindowManager {
19431943
             // Check if memory was used (preferred matched target) or default algorithm was used
19441944
             let used_memory = preferred == Some(target);
19451945
 
1946
+            tracing::info!("NAV: {:?} from {} to {}, preferred={:?}, used_memory={}",
1947
+                direction, focused, target, preferred, used_memory);
1948
+
19461949
             // Store the directional focus memory for next time
19471950
             self.directional_focus_memory.insert((focused, direction), target);
1951
+            tracing::info!("NAV: stored ({}, {:?}) -> {}", focused, direction, target);
1952
+
1953
+            // Always store reverse direction if windows are aligned (same row/column)
1954
+            // This ensures "go back" always returns to the window we came from
1955
+            if let (Some((_, from_rect)), Some((_, to_rect))) = (
1956
+                geometries.iter().find(|(w, _)| *w == focused),
1957
+                geometries.iter().find(|(w, _)| *w == target),
1958
+            ) {
1959
+                let overlaps = match direction {
1960
+                    // For Left/Right: store reverse if windows share vertical space (same row)
1961
+                    Direction::Left | Direction::Right => {
1962
+                        let overlap_start = from_rect.y.max(to_rect.y);
1963
+                        let overlap_end = (from_rect.y + from_rect.height as i16)
1964
+                            .min(to_rect.y + to_rect.height as i16);
1965
+                        tracing::info!("NAV: L/R overlap check: from_y={},{} to_y={},{} overlap=[{},{}]",
1966
+                            from_rect.y, from_rect.height, to_rect.y, to_rect.height, overlap_start, overlap_end);
1967
+                        overlap_start < overlap_end
1968
+                    }
1969
+                    // For Up/Down: store reverse if windows share horizontal space (same column)
1970
+                    Direction::Up | Direction::Down => {
1971
+                        let overlap_start = from_rect.x.max(to_rect.x);
1972
+                        let overlap_end = (from_rect.x + from_rect.width as i16)
1973
+                            .min(to_rect.x + to_rect.width as i16);
1974
+                        tracing::info!("NAV: U/D overlap check: from_x={},{} to_x={},{} overlap=[{},{}]",
1975
+                            from_rect.x, from_rect.width, to_rect.x, to_rect.width, overlap_start, overlap_end);
1976
+                        overlap_start < overlap_end
1977
+                    }
1978
+                };
19481979
 
1949
-            // Store reverse direction only if:
1950
-            // 1. Default algorithm was used (not memory-assisted jump that skipped windows)
1951
-            // 2. Windows are aligned (same row/column)
1952
-            if !used_memory {
1953
-                if let (Some((_, from_rect)), Some((_, to_rect))) = (
1954
-                    geometries.iter().find(|(w, _)| *w == focused),
1955
-                    geometries.iter().find(|(w, _)| *w == target),
1956
-                ) {
1957
-                    let dominated = match direction {
1958
-                        // For Left/Right: store reverse if windows share vertical space (same row)
1959
-                        Direction::Left | Direction::Right => {
1960
-                            let overlap_start = from_rect.y.max(to_rect.y);
1961
-                            let overlap_end = (from_rect.y + from_rect.height as i16)
1962
-                                .min(to_rect.y + to_rect.height as i16);
1963
-                            overlap_start < overlap_end
1964
-                        }
1965
-                        // For Up/Down: store reverse if windows share horizontal space (same column)
1966
-                        Direction::Up | Direction::Down => {
1967
-                            let overlap_start = from_rect.x.max(to_rect.x);
1968
-                            let overlap_end = (from_rect.x + from_rect.width as i16)
1969
-                                .min(to_rect.x + to_rect.width as i16);
1970
-                            overlap_start < overlap_end
1971
-                        }
1980
+                tracing::info!("NAV: overlaps={}", overlaps);
1981
+                if overlaps {
1982
+                    let opposite = match direction {
1983
+                        Direction::Left => Direction::Right,
1984
+                        Direction::Right => Direction::Left,
1985
+                        Direction::Up => Direction::Down,
1986
+                        Direction::Down => Direction::Up,
19721987
                     };
1973
-
1974
-                    if dominated {
1975
-                        let opposite = match direction {
1976
-                            Direction::Left => Direction::Right,
1977
-                            Direction::Right => Direction::Left,
1978
-                            Direction::Up => Direction::Down,
1979
-                            Direction::Down => Direction::Up,
1980
-                        };
1981
-                        self.directional_focus_memory.insert((target, opposite), focused);
1982
-                    }
1988
+                    self.directional_focus_memory.insert((target, opposite), focused);
1989
+                    tracing::info!("NAV: stored reverse ({}, {:?}) -> {}", target, opposite, focused);
19831990
                 }
19841991
             }
19851992
 
@@ -2542,6 +2549,11 @@ impl WindowManager {
25422549
         let _ = self.conn.sync();
25432550
         tracing::info!("Windows unmapped and synced");
25442551
 
2552
+        // Kill all processes spawned via gar.exec()/gar.exec_once()
2553
+        if let Ok(state) = self.lua_state.lock() {
2554
+            state.kill_spawned_children();
2555
+        }
2556
+
25452557
         // Stop garbar if it was spawned
25462558
         if let Some(ref mut child) = self.garbar_process {
25472559
             stop_garbar(child);