@@ -7,7 +7,9 @@ use events::Event; |
| 7 | 7 | use skylight::*; |
| 8 | 8 | use std::collections::HashMap; |
| 9 | 9 | use std::ptr; |
| 10 | +use std::sync::atomic::{AtomicBool, Ordering}; |
| 10 | 11 | use std::sync::mpsc; |
| 12 | +use std::sync::Arc; |
| 11 | 13 | |
| 12 | 14 | /// Per-overlay state: the connection it was created on + its wid. |
| 13 | 15 | struct Overlay { |
@@ -88,6 +90,13 @@ impl BorderMap { |
| 88 | 90 | } |
| 89 | 91 | } |
| 90 | 92 | |
| 93 | + fn remove_all(&mut self) { |
| 94 | + let wids: Vec<u32> = self.overlays.keys().copied().collect(); |
| 95 | + for wid in wids { |
| 96 | + self.remove(wid); |
| 97 | + } |
| 98 | + } |
| 99 | + |
| 91 | 100 | fn remove(&mut self, target_wid: u32) { |
| 92 | 101 | if let Some(overlay) = self.overlays.remove(&target_wid) { |
| 93 | 102 | unsafe { |
@@ -401,17 +410,32 @@ fn main() { |
| 401 | 410 | |
| 402 | 411 | eprintln!("{} overlays tracked", borders.overlays.len()); |
| 403 | 412 | |
| 413 | + // SIGINT flag — background thread checks this to clean up |
| 414 | + let running = Arc::new(AtomicBool::new(true)); |
| 415 | + unsafe { |
| 416 | + libc::signal(libc::SIGINT, { |
| 417 | + unsafe extern "C" fn handler(_: libc::c_int) { |
| 418 | + // Stop CFRunLoop on main thread — this returns control to main() |
| 419 | + CFRunLoopStop(CFRunLoopGetMain()); |
| 420 | + } |
| 421 | + handler as libc::sighandler_t |
| 422 | + }); |
| 423 | + } |
| 424 | + |
| 404 | 425 | // Process events on background thread with coalescing |
| 405 | | - std::thread::spawn(move || { |
| 426 | + let running_bg = Arc::clone(&running); |
| 427 | + let handle = std::thread::spawn(move || { |
| 406 | 428 | use std::collections::HashSet; |
| 429 | + use std::time::Duration; |
| 407 | 430 | |
| 408 | 431 | // Persist across batches: windows we know about but haven't bordered yet |
| 409 | 432 | let mut pending: HashSet<u32> = HashSet::new(); |
| 410 | 433 | |
| 411 | | - loop { |
| 412 | | - let first = match rx.recv() { |
| 434 | + while running_bg.load(Ordering::Relaxed) { |
| 435 | + let first = match rx.recv_timeout(Duration::from_millis(100)) { |
| 413 | 436 | Ok(e) => e, |
| 414 | | - Err(_) => break, |
| 437 | + Err(mpsc::RecvTimeoutError::Timeout) => continue, |
| 438 | + Err(mpsc::RecvTimeoutError::Disconnected) => break, |
| 415 | 439 | }; |
| 416 | 440 | |
| 417 | 441 | std::thread::sleep(std::time::Duration::from_millis(16)); |
@@ -548,9 +572,16 @@ fn main() { |
| 548 | 572 | // After all processing, enforce active-only visibility |
| 549 | 573 | borders.enforce_active_only(); |
| 550 | 574 | } |
| 575 | + |
| 576 | + // Clean up all overlays before exiting |
| 577 | + borders.remove_all(); |
| 551 | 578 | }); |
| 552 | 579 | |
| 553 | 580 | unsafe { CFRunLoopRun() }; |
| 581 | + |
| 582 | + // SIGINT received — signal background thread to stop and wait |
| 583 | + running.store(false, Ordering::Relaxed); |
| 584 | + let _ = handle.join(); |
| 554 | 585 | } |
| 555 | 586 | |
| 556 | 587 | fn setup_event_port(cid: CGSConnectionID) { |