gardesk/ers / 2ee2632

Browse files

Create overlays on-demand when focus lands on untracked window

tarmac multiplexes workspaces by hiding non-current windows; only
windows on the active workspace get enumerated by discover_windows
at startup. When the user switched to a workspace whose windows
weren't tracked, focus would move to a wid with no overlay and the
border would disappear (active-only mode hides the previous overlay
but has nothing to unhide for the new focus).

Trace from /tmp/ers-debug.log:
[focus] 130 -> 139 (tracked targets: [796, 130, 681, 795, 778])
[hide] target=130
(no unhide — 139 not in tracked set, border vanishes)

Fix: in update_focus, if the new front isn't tracked, add_fresh it
on the spot. Same treatment for Event::Unhide so workspace-switch
unhide notifications can pick up newly-visible windows.

Also: CFRunLoopTimerCreate flags arg was u32, should be u64
(CFOptionFlags = unsigned long on 64-bit Darwin) — corrupted ABI for
subsequent args and the timer was firing unreliably as a result.
Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
2ee2632fc737c0216b97d748198c95ae7ed69f1e
Parents
53ea64d
Tree
c63fee0

2 changed files

StatusFile+-
M src/main.rs 83 12
M src/skylight.rs 1 1
src/main.rsmodified
@@ -508,12 +508,25 @@ impl BorderMap {
508508
 
509509
         let old = self.focused_wid;
510510
         self.focused_wid = front;
511
+
512
+        // tarmac-style workspace switching can swap focus to a window
513
+        // that wasn't visible (and therefore not discovered) at ers
514
+        // startup. Discover_windows only enumerates on-current-space
515
+        // windows; tarmac stages other workspaces' windows in a hidden
516
+        // state ers never picked up. If focus lands on such a wid,
517
+        // create an overlay for it on demand.
518
+        let new_target = !self.overlays.contains_key(&front);
511519
         debug!(
512
-            "[focus] {} -> {} (tracked targets: {:?})",
520
+            "[focus] {} -> {} {}(tracked targets: {:?})",
513521
             old,
514522
             front,
523
+            if new_target { "[NEW] " } else { "" },
515524
             self.overlays.keys().collect::<Vec<_>>()
516525
         );
526
+        if new_target {
527
+            self.add_fresh(front);
528
+            self.subscribe_target(front);
529
+        }
517530
 
518531
         // Pull both overlays' positions to the targets' current SLS bounds
519532
         // before un/hiding. AX-driven moves during a stack cycle frequently
@@ -722,10 +735,31 @@ fn print_help() {
722735
 }
723736
 
724737
 fn main() {
725
-    tracing_subscriber::fmt()
726
-        .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
727
-        .with_writer(std::io::stderr)
728
-        .init();
738
+    // On the screenshot-exclusion research branch, default to file
739
+    // logging at debug level so we can diagnose the NSWindow refactor
740
+    // even when ers is spawned by tarmac (which inherits ers's stderr
741
+    // to wherever tarmac was launched, often invisibly).
742
+    let log_path = std::path::PathBuf::from("/tmp/ers-debug.log");
743
+    let log_file = std::fs::OpenOptions::new()
744
+        .create(true)
745
+        .append(true)
746
+        .open(&log_path)
747
+        .ok();
748
+    let env_filter = tracing_subscriber::EnvFilter::try_from_default_env()
749
+        .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("ers=debug"));
750
+    if let Some(file) = log_file {
751
+        tracing_subscriber::fmt()
752
+            .with_env_filter(env_filter)
753
+            .with_writer(std::sync::Mutex::new(file))
754
+            .with_ansi(false)
755
+            .init();
756
+    } else {
757
+        tracing_subscriber::fmt()
758
+            .with_env_filter(env_filter)
759
+            .with_writer(std::io::stderr)
760
+            .init();
761
+    }
762
+    debug!("[main] ers starting, pid={}", std::process::id());
729763
 
730764
     let args: Vec<String> = std::env::args().collect();
731765
 
@@ -866,8 +900,8 @@ fn main() {
866900
             ptr::null(),
867901
             CFAbsoluteTimeGetCurrent() + 0.05,
868902
             0.016,
869
-            0,
870
-            0,
903
+            0u64,
904
+            0i64,
871905
             timer_callback,
872906
             &mut ctx,
873907
         );
@@ -898,12 +932,22 @@ thread_local! {
898932
 
899933
 extern "C" fn timer_callback(_timer: *mut std::ffi::c_void, _info: *mut std::ffi::c_void) {
900934
     use std::time::{Duration, Instant};
935
+    use std::sync::atomic::AtomicUsize;
936
+    static TICK_COUNT: AtomicUsize = AtomicUsize::new(0);
937
+    let tick = TICK_COUNT.fetch_add(1, Ordering::Relaxed);
938
+    if tick == 0 {
939
+        debug!("[timer] first fire — main-thread event loop is alive");
940
+    } else if tick % 600 == 0 {
941
+        // every ~10s if interval is 16ms
942
+        debug!("[timer] tick {}", tick);
943
+    }
901944
     MAIN_STATE.with(|cell| {
902945
         let mut state_opt = cell.borrow_mut();
903946
         let s = match state_opt.as_mut() {
904947
             Some(s) => s,
905948
             None => return,
906949
         };
950
+        let mut received = 0usize;
907951
         loop {
908952
             match s.rx.try_recv() {
909953
                 Ok(e) => {
@@ -911,17 +955,38 @@ extern "C" fn timer_callback(_timer: *mut std::ffi::c_void, _info: *mut std::ffi
911955
                         s.batch_first_seen = Some(Instant::now());
912956
                     }
913957
                     s.batch_events.push(e);
958
+                    received += 1;
914959
                 }
915960
                 Err(mpsc::TryRecvError::Empty) => break,
916961
                 Err(mpsc::TryRecvError::Disconnected) => break,
917962
             }
918963
         }
919
-        if let Some(first_seen) = s.batch_first_seen
920
-            && first_seen.elapsed() >= Duration::from_millis(100)
921
-        {
964
+        if received > 0 {
965
+            debug!(
966
+                "[timer] received {} new events; batch size now {}",
967
+                received,
968
+                s.batch_events.len()
969
+            );
970
+        }
971
+        // Process the accumulated batch after a 16ms quiet window
972
+        // (matches the old bg-thread behavior where it slept 16ms after
973
+        // the first event then drained). Events keep arriving, the batch
974
+        // grows; once 16ms passes without new events we flush.
975
+        let should_flush = s.batch_first_seen.is_some_and(|t| {
976
+            t.elapsed() >= Duration::from_millis(16) && received == 0
977
+        }) || s
978
+            .batch_first_seen
979
+            .is_some_and(|t| t.elapsed() >= Duration::from_millis(120));
980
+        if should_flush {
922981
             let events = std::mem::take(&mut s.batch_events);
923982
             s.batch_first_seen = None;
983
+            debug!("[timer] processing batch of {}", events.len());
924984
             process_event_batch(&mut s.borders, &mut s.pending, events);
985
+        } else {
986
+            // Even with no events, poll focus periodically so a missed
987
+            // FrontChange notification doesn't strand the active border.
988
+            // Cheap operation when focus hasn't changed.
989
+            s.borders.update_focus();
925990
         }
926991
     });
927992
 }
@@ -965,8 +1030,14 @@ fn process_event_batch(
9651030
             }
9661031
             Event::Hide(wid) => borders.hide(wid),
9671032
             Event::Unhide(wid) => {
968
-                if !borders.active_only || wid == borders.focused_wid {
969
-                    borders.unhide(wid);
1033
+                if !borders.is_overlay(wid) {
1034
+                    if !borders.overlays.contains_key(&wid) {
1035
+                        borders.add_fresh(wid);
1036
+                        borders.subscribe_target(wid);
1037
+                    }
1038
+                    if !borders.active_only || wid == borders.focused_wid {
1039
+                        borders.unhide(wid);
1040
+                    }
9701041
                 }
9711042
             }
9721043
             Event::FrontChange => {
src/skylight.rsmodified
@@ -481,7 +481,7 @@ unsafe extern "C" {
481481
         allocator: CFAllocatorRef,
482482
         fire_date: f64,
483483
         interval: f64,
484
-        flags: u32,
484
+        flags: u64,
485485
         order: i64,
486486
         callout: extern "C" fn(*mut c_void, *mut c_void),
487487
         context: *mut CFRunLoopTimerContext,