gardesk/ers / 1319fb0

Browse files

Fix close-persistence and new-window race

Two bugs from user testing:

1. Spawning a new terminal made the border disappear until the next
focus move. Cause: focus moves to the new wid before its SLS state
passes the add_fresh filter (SLSWindowIsOrderedIn race). The
focus-changed branch attempted add_fresh once and gave up; the
focus-unchanged branch returned early, never retrying.

Fix: in update_focus, when front == focused_wid AND the focused
wid still has no overlay, retry add_fresh on every poll. The
filter passes once the new window settles (typically 1-2 ticks).

2. Closing the only window on a workspace left a phantom border.
Cause: sync_overlay returned early on SLSGetWindowBounds failure
(which is what happens for a destroyed wid) without removing the
overlay. The Drop never fired, so NSWindow.orderOut never ran.

Fix: on SLSGetWindowBounds failure, treat the window as gone and
call remove() to drop the overlay.

Also: timer's idle branch now runs reconcile_tracked once per second
as a safety net for any other class of missed Close/Destroy event.
Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
1319fb0360ef7bd78ea9b104a1e38fdd30d97cc8
Parents
2ee2632
Tree
4acaa8a

1 changed file

StatusFile+-
M src/main.rs 46 4
src/main.rsmodified
@@ -385,8 +385,17 @@ impl BorderMap {
385385
     }
386386
 
387387
     fn remove(&mut self, target_wid: u32) {
388
-        // OverlayWindow's Drop closes the NSWindow.
389
-        self.overlays.remove(&target_wid);
388
+        if let Some(overlay) = self.overlays.remove(&target_wid) {
389
+            debug!(
390
+                "[remove] target={} overlay_wid={} dropping NSWindow",
391
+                target_wid,
392
+                overlay.wid()
393
+            );
394
+            // OverlayWindow's Drop runs orderOut + close.
395
+            drop(overlay);
396
+        } else {
397
+            debug!("[remove] target={} not tracked", target_wid);
398
+        }
390399
     }
391400
 
392401
     /// Reconcile a tracked overlay against its target window.
@@ -398,7 +407,13 @@ impl BorderMap {
398407
         let mut bounds = CGRect::default();
399408
         unsafe {
400409
             if SLSGetWindowBounds(self.main_cid, target_wid, &mut bounds) != kCGErrorSuccess {
401
-                return false;
410
+                // Window is gone (destroyed). Reap the overlay.
411
+                debug!(
412
+                    "[sync_overlay] target={} SLSGetWindowBounds failed — reaping overlay",
413
+                    target_wid
414
+                );
415
+                self.remove(target_wid);
416
+                return true;
402417
             }
403418
 
404419
             if !is_suitable_window(self.main_cid, target_wid) {
@@ -502,7 +517,24 @@ impl BorderMap {
502517
     /// Detect focused window and update border colors if focus changed.
503518
     fn update_focus(&mut self) {
504519
         let front = get_front_window(self.own_pid);
505
-        if front == 0 || front == self.focused_wid {
520
+        if front == 0 {
521
+            return;
522
+        }
523
+        if front == self.focused_wid {
524
+            // Same focus as last poll. But a freshly-spawned window may
525
+            // have been focused before its SLS state was complete enough
526
+            // to pass the add_fresh filter — retry on every poll until
527
+            // it sticks.
528
+            if !self.overlays.contains_key(&front) {
529
+                debug!("[focus-retry] front={} still untracked, retrying add_fresh", front);
530
+                self.add_fresh(front);
531
+                if self.overlays.contains_key(&front) {
532
+                    self.subscribe_target(front);
533
+                    if self.active_only {
534
+                        self.unhide(front);
535
+                    }
536
+                }
537
+            }
506538
             return;
507539
         }
508540
 
@@ -987,6 +1019,15 @@ extern "C" fn timer_callback(_timer: *mut std::ffi::c_void, _info: *mut std::ffi
9871019
             // FrontChange notification doesn't strand the active border.
9881020
             // Cheap operation when focus hasn't changed.
9891021
             s.borders.update_focus();
1022
+            // Once per second, reconcile tracked overlays against
1023
+            // current SLS state. Catches missed Close/Destroy events
1024
+            // that would otherwise leave a dead border on screen.
1025
+            if tick % 60 == 0 && tick > 0 {
1026
+                let removed = s.borders.reconcile_tracked();
1027
+                if removed {
1028
+                    debug!("[timer] periodic reconcile removed stale overlays");
1029
+                }
1030
+            }
9901031
         }
9911032
     });
9921033
 }
@@ -1018,6 +1059,7 @@ fn process_event_batch(
10181059
             }
10191060
             Event::Close(wid) | Event::Destroy(wid) => {
10201061
                 if !borders.is_overlay(wid) {
1062
+                    debug!("[event] Close/Destroy target_wid={}", wid);
10211063
                     destroyed.insert(wid);
10221064
                     pending.remove(&wid);
10231065
                 }