gardesk/ers / 8871c72

Browse files

prefer outer front surface for active border

Authored by espadonne
SHA
8871c7215d0c24d0ea66ae3b92cb8692561f6540
Parents
61dc57a
Tree
01269b6

1 changed file

StatusFile+-
M src/main.rs 272 65
src/main.rsmodified
@@ -7,9 +7,9 @@ use events::Event;
77
 use skylight::*;
88
 use std::collections::HashMap;
99
 use std::ptr;
10
+use std::sync::Arc;
1011
 use std::sync::atomic::{AtomicBool, Ordering};
1112
 use std::sync::mpsc;
12
-use std::sync::Arc;
1313
 use tracing::debug;
1414
 
1515
 /// Per-overlay state: the connection it was created on + its wid.
@@ -18,6 +18,25 @@ struct Overlay {
1818
     wid: u32,
1919
 }
2020
 
21
+fn window_area(bounds: CGRect) -> f64 {
22
+    bounds.size.width * bounds.size.height
23
+}
24
+
25
+fn intersection_area(a: CGRect, b: CGRect) -> f64 {
26
+    let left = a.origin.x.max(b.origin.x);
27
+    let top = a.origin.y.max(b.origin.y);
28
+    let right = (a.origin.x + a.size.width).min(b.origin.x + b.size.width);
29
+    let bottom = (a.origin.y + a.size.height).min(b.origin.y + b.size.height);
30
+    let width = (right - left).max(0.0);
31
+    let height = (bottom - top).max(0.0);
32
+    width * height
33
+}
34
+
35
+fn is_same_window_surface(a: CGRect, b: CGRect) -> bool {
36
+    let smaller = window_area(a).min(window_area(b));
37
+    smaller > 0.0 && intersection_area(a, b) / smaller >= 0.9
38
+}
39
+
2140
 /// Tracks overlays for target windows.
2241
 struct BorderMap {
2342
     overlays: HashMap<u32, Overlay>,
@@ -41,13 +60,17 @@ impl BorderMap {
4160
             radius: 10.0,
4261
             focused_wid: 0,
4362
             active_color: (0.32, 0.58, 0.89, 1.0),   // #5294e2
44
-            inactive_color: (0.35, 0.35, 0.35, 0.8),  // dim gray
63
+            inactive_color: (0.35, 0.35, 0.35, 0.8), // dim gray
4564
             active_only: false,
4665
         }
4766
     }
4867
 
4968
     fn color_for(&self, target_wid: u32) -> (f64, f64, f64, f64) {
50
-        if target_wid == self.focused_wid { self.active_color } else { self.inactive_color }
69
+        if target_wid == self.focused_wid {
70
+            self.active_color
71
+        } else {
72
+            self.inactive_color
73
+        }
5174
     }
5275
 
5376
     fn is_overlay(&self, wid: u32) -> bool {
@@ -56,9 +79,17 @@ impl BorderMap {
5679
 
5780
     /// Add border (batch mode, uses main cid).
5881
     fn add_batch(&mut self, target_wid: u32) {
59
-        if self.overlays.contains_key(&target_wid) { return; }
82
+        if self.overlays.contains_key(&target_wid) {
83
+            return;
84
+        }
6085
         let color = self.color_for(target_wid);
61
-        if let Some((cid, wid)) = create_overlay(self.main_cid, target_wid, self.border_width, self.radius, color) {
86
+        if let Some((cid, wid)) = create_overlay(
87
+            self.main_cid,
88
+            target_wid,
89
+            self.border_width,
90
+            self.radius,
91
+            color,
92
+        ) {
6293
             self.overlays.insert(target_wid, Overlay { cid, wid });
6394
         }
6495
     }
@@ -66,23 +97,31 @@ impl BorderMap {
6697
     /// Add border (event mode). Uses main_cid — fresh connections create
6798
     /// invisible windows on Tahoe.
6899
     fn add_fresh(&mut self, target_wid: u32) {
69
-        if self.overlays.contains_key(&target_wid) { return; }
100
+        if self.overlays.contains_key(&target_wid) {
101
+            return;
102
+        }
70103
 
71104
         // Filter: must be visible, owned by another process, not tiny
72105
         let bounds = unsafe {
73106
             let mut shown = false;
74107
             SLSWindowIsOrderedIn(self.main_cid, target_wid, &mut shown);
75
-            if !shown { return; }
108
+            if !shown {
109
+                return;
110
+            }
76111
 
77112
             let mut wid_cid: CGSConnectionID = 0;
78113
             SLSGetWindowOwner(self.main_cid, target_wid, &mut wid_cid);
79114
             let mut pid: i32 = 0;
80115
             SLSConnectionGetPID(wid_cid, &mut pid);
81
-            if pid == self.own_pid { return; }
116
+            if pid == self.own_pid {
117
+                return;
118
+            }
82119
 
83120
             let mut bounds = CGRect::default();
84121
             SLSGetWindowBounds(self.main_cid, target_wid, &mut bounds);
85
-            if bounds.size.width < 50.0 || bounds.size.height < 50.0 { return; }
122
+            if bounds.size.width < 50.0 || bounds.size.height < 50.0 {
123
+                return;
124
+            }
86125
             bounds
87126
         };
88127
 
@@ -90,7 +129,7 @@ impl BorderMap {
90129
         // keep the smaller one (content) and skip the larger one (container)
91130
         let cx = bounds.origin.x + bounds.size.width / 2.0;
92131
         let cy = bounds.origin.y + bounds.size.height / 2.0;
93
-        let area = bounds.size.width * bounds.size.height;
132
+        let area = window_area(bounds);
94133
         for &existing_wid in self.overlays.keys() {
95134
             unsafe {
96135
                 let mut eb = CGRect::default();
@@ -100,14 +139,22 @@ impl BorderMap {
100139
                 let ecx = eb.origin.x + eb.size.width / 2.0;
101140
                 let ecy = eb.origin.y + eb.size.height / 2.0;
102141
                 if (cx - ecx).abs() < 30.0 && (cy - ecy).abs() < 30.0 {
103
-                    let earea = eb.size.width * eb.size.height;
104
-                    if area >= earea { return; } // new window is container, skip
142
+                    let earea = window_area(eb);
143
+                    if area >= earea {
144
+                        return;
145
+                    } // new window is container, skip
105146
                 }
106147
             }
107148
         }
108149
 
109150
         let color = self.color_for(target_wid);
110
-        if let Some((cid, wid)) = create_overlay(self.main_cid, target_wid, self.border_width, self.radius, color) {
151
+        if let Some((cid, wid)) = create_overlay(
152
+            self.main_cid,
153
+            target_wid,
154
+            self.border_width,
155
+            self.radius,
156
+            color,
157
+        ) {
111158
             self.overlays.insert(target_wid, Overlay { cid, wid });
112159
         }
113160
     }
@@ -123,7 +170,10 @@ impl BorderMap {
123170
         if let Some(overlay) = self.overlays.remove(&target_wid) {
124171
             unsafe {
125172
                 // Move off-screen first (most reliable hide on Tahoe)
126
-                let offscreen = CGPoint { x: -99999.0, y: -99999.0 };
173
+                let offscreen = CGPoint {
174
+                    x: -99999.0,
175
+                    y: -99999.0,
176
+                };
127177
                 SLSMoveWindow(overlay.cid, overlay.wid, &offscreen);
128178
                 SLSSetWindowAlpha(overlay.cid, overlay.wid, 0.0);
129179
                 SLSOrderWindow(overlay.cid, overlay.wid, 0, 0);
@@ -155,7 +205,9 @@ impl BorderMap {
155205
 
156206
     /// Recreate overlay at new size.
157207
     fn recreate(&mut self, target_wid: u32) {
158
-        if !self.overlays.contains_key(&target_wid) { return; }
208
+        if !self.overlays.contains_key(&target_wid) {
209
+            return;
210
+        }
159211
         self.remove(target_wid);
160212
         self.add_fresh(target_wid);
161213
         if self.active_only && target_wid != self.focused_wid {
@@ -166,7 +218,9 @@ impl BorderMap {
166218
 
167219
     fn hide(&self, target_wid: u32) {
168220
         if let Some(o) = self.overlays.get(&target_wid) {
169
-            unsafe { SLSOrderWindow(o.cid, o.wid, 0, 0); }
221
+            unsafe {
222
+                SLSOrderWindow(o.cid, o.wid, 0, 0);
223
+            }
170224
         }
171225
     }
172226
 
@@ -187,7 +241,9 @@ impl BorderMap {
187241
 
188242
     fn subscribe_all(&self) {
189243
         let target_wids: Vec<u32> = self.overlays.keys().copied().collect();
190
-        if target_wids.is_empty() { return; }
244
+        if target_wids.is_empty() {
245
+            return;
246
+        }
191247
         unsafe {
192248
             SLSRequestNotificationsForWindows(
193249
                 self.main_cid,
@@ -210,7 +266,9 @@ impl BorderMap {
210266
                 let oh = bounds.size.height + 2.0 * bw;
211267
 
212268
                 let ctx = SLWindowContextCreate(overlay.cid, overlay.wid, ptr::null());
213
-                if ctx.is_null() { return; }
269
+                if ctx.is_null() {
270
+                    return;
271
+                }
214272
 
215273
                 let color = self.color_for(target_wid);
216274
                 draw_border(ctx, ow, oh, bw, self.radius, color);
@@ -223,7 +281,9 @@ impl BorderMap {
223281
     /// Detect focused window and update border colors if focus changed.
224282
     fn update_focus(&mut self) {
225283
         let front = get_front_window(self.own_pid);
226
-        if front == 0 || front == self.focused_wid { return; }
284
+        if front == 0 || front == self.focused_wid {
285
+            return;
286
+        }
227287
 
228288
         let old = self.focused_wid;
229289
         self.focused_wid = front;
@@ -258,7 +318,9 @@ impl BorderMap {
258318
 
259319
     /// In active-only mode, ensure only the focused overlay is visible.
260320
     fn enforce_active_only(&self) {
261
-        if !self.active_only { return; }
321
+        if !self.active_only {
322
+            return;
323
+        }
262324
         for (&target_wid, o) in &self.overlays {
263325
             if target_wid == self.focused_wid {
264326
                 unsafe {
@@ -266,7 +328,9 @@ impl BorderMap {
266328
                     SLSOrderWindow(o.cid, o.wid, 1, target_wid);
267329
                 }
268330
             } else {
269
-                unsafe { SLSOrderWindow(o.cid, o.wid, 0, 0); }
331
+                unsafe {
332
+                    SLSOrderWindow(o.cid, o.wid, 0, 0);
333
+                }
270334
             }
271335
         }
272336
     }
@@ -285,22 +349,42 @@ fn get_front_window(own_pid: i32) -> u32 {
285349
         SLSGetConnectionIDForPSN(SLSMainConnectionID(), &mut psn, &mut front_cid);
286350
         let mut front_pid: i32 = 0;
287351
         SLSConnectionGetPID(front_cid, &mut front_pid);
288
-        if front_pid == 0 || front_pid == own_pid { return 0; }
352
+        if front_pid == 0 || front_pid == own_pid {
353
+            return 0;
354
+        }
289355
 
290356
         // Step 2: find the topmost layer-0 window belonging to that process
291357
         let list = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
292
-        if list.is_null() { return 0; }
358
+        if list.is_null() {
359
+            return 0;
360
+        }
293361
 
294362
         let count = CFArrayGetCount(list);
295
-        let wid_key = CFStringCreateWithCString(ptr::null(), b"kCGWindowNumber\0".as_ptr(), kCFStringEncodingUTF8);
296
-        let pid_key = CFStringCreateWithCString(ptr::null(), b"kCGWindowOwnerPID\0".as_ptr(), kCFStringEncodingUTF8);
297
-        let layer_key = CFStringCreateWithCString(ptr::null(), b"kCGWindowLayer\0".as_ptr(), kCFStringEncodingUTF8);
363
+        let wid_key = CFStringCreateWithCString(
364
+            ptr::null(),
365
+            b"kCGWindowNumber\0".as_ptr(),
366
+            kCFStringEncodingUTF8,
367
+        );
368
+        let pid_key = CFStringCreateWithCString(
369
+            ptr::null(),
370
+            b"kCGWindowOwnerPID\0".as_ptr(),
371
+            kCFStringEncodingUTF8,
372
+        );
373
+        let layer_key = CFStringCreateWithCString(
374
+            ptr::null(),
375
+            b"kCGWindowLayer\0".as_ptr(),
376
+            kCFStringEncodingUTF8,
377
+        );
298378
 
299379
         let mut front_wid: u32 = 0;
380
+        let mut front_bounds = CGRect::default();
381
+        let mut have_front_bounds = false;
300382
         let mut fallback_wid: u32 = 0;
301383
         for i in 0..count {
302384
             let dict = CFArrayGetValueAtIndex(list, i);
303
-            if dict.is_null() { continue; }
385
+            if dict.is_null() {
386
+                continue;
387
+            }
304388
 
305389
             let mut v: CFTypeRef = ptr::null();
306390
 
@@ -308,29 +392,58 @@ fn get_front_window(own_pid: i32) -> u32 {
308392
             if CFDictionaryGetValueIfPresent(dict, layer_key as CFTypeRef, &mut v) {
309393
                 CFNumberGetValue(v, kCFNumberSInt32Type, &mut layer as *mut _ as *mut _);
310394
             }
311
-            if layer != 0 { continue; }
395
+            if layer != 0 {
396
+                continue;
397
+            }
312398
 
313399
             let mut pid: i32 = 0;
314400
             if CFDictionaryGetValueIfPresent(dict, pid_key as CFTypeRef, &mut v) {
315401
                 CFNumberGetValue(v, kCFNumberSInt32Type, &mut pid as *mut _ as *mut _);
316402
             }
317
-            if pid == own_pid { continue; }
403
+            if pid == own_pid {
404
+                continue;
405
+            }
318406
 
319407
             let mut wid: u32 = 0;
320408
             if CFDictionaryGetValueIfPresent(dict, wid_key as CFTypeRef, &mut v) {
321409
                 CFNumberGetValue(v, kCFNumberSInt32Type, &mut wid as *mut _ as *mut _);
322410
             }
323
-            if wid == 0 { continue; }
411
+            if wid == 0 {
412
+                continue;
413
+            }
324414
 
325415
             // Track first non-self window as fallback (z-order based)
326416
             if fallback_wid == 0 {
327417
                 fallback_wid = wid;
328418
             }
329419
 
330
-            // Prefer a window from the front process
420
+            // Prefer a window from the front process. If another layer-0 surface
421
+            // from that app nearly fully contains the current one, treat the
422
+            // larger surface as the real window. Firefox can surface a tab-strip
423
+            // child ahead of the outer window after a tile.
331424
             if pid == front_pid {
332
-                front_wid = wid;
333
-                break;
425
+                let mut bounds = CGRect::default();
426
+                if SLSGetWindowBounds(SLSMainConnectionID(), wid, &mut bounds) != kCGErrorSuccess {
427
+                    if front_wid == 0 {
428
+                        front_wid = wid;
429
+                    }
430
+                    continue;
431
+                }
432
+
433
+                if front_wid == 0 {
434
+                    front_wid = wid;
435
+                    front_bounds = bounds;
436
+                    have_front_bounds = true;
437
+                    continue;
438
+                }
439
+
440
+                if have_front_bounds
441
+                    && is_same_window_surface(front_bounds, bounds)
442
+                    && window_area(bounds) > window_area(front_bounds)
443
+                {
444
+                    front_wid = wid;
445
+                    front_bounds = bounds;
446
+                }
334447
             }
335448
         }
336449
 
@@ -351,13 +464,17 @@ fn get_front_window(own_pid: i32) -> u32 {
351464
 /// Parse hex color string (#RRGGBB or #RRGGBBAA) to (r, g, b, a) floats.
352465
 fn parse_color(s: &str) -> Option<(f64, f64, f64, f64)> {
353466
     let hex = s.strip_prefix('#').unwrap_or(s);
354
-    if hex.len() != 6 && hex.len() != 8 { return None; }
467
+    if hex.len() != 6 && hex.len() != 8 {
468
+        return None;
469
+    }
355470
     let r = u8::from_str_radix(&hex[0..2], 16).ok()? as f64 / 255.0;
356471
     let g = u8::from_str_radix(&hex[2..4], 16).ok()? as f64 / 255.0;
357472
     let b = u8::from_str_radix(&hex[4..6], 16).ok()? as f64 / 255.0;
358473
     let a = if hex.len() == 8 {
359474
         u8::from_str_radix(&hex[6..8], 16).ok()? as f64 / 255.0
360
-    } else { 1.0 };
475
+    } else {
476
+        1.0
477
+    };
361478
     Some((r, g, b, a))
362479
 }
363480
 
@@ -454,7 +571,9 @@ fn main() {
454571
 
455572
     if borders.active_only {
456573
         let focused = borders.focused_wid;
457
-        let to_hide: Vec<u32> = borders.overlays.keys()
574
+        let to_hide: Vec<u32> = borders
575
+            .overlays
576
+            .keys()
458577
             .filter(|&&wid| wid != focused)
459578
             .copied()
460579
             .collect();
@@ -554,9 +673,11 @@ fn main() {
554673
 
555674
             // Promote pending creates that have waited ≥100ms (tarmac positioning time)
556675
             let now = Instant::now();
557
-            let ready: Vec<u32> = pending.iter()
676
+            let ready: Vec<u32> = pending
677
+                .iter()
558678
                 .filter(|(wid, seen_at)| {
559
-                    !destroyed.contains(wid) && now.duration_since(**seen_at) >= Duration::from_millis(100)
679
+                    !destroyed.contains(wid)
680
+                        && now.duration_since(**seen_at) >= Duration::from_millis(100)
560681
                 })
561682
                 .map(|(wid, _)| *wid)
562683
                 .collect();
@@ -573,7 +694,7 @@ fn main() {
573694
             // If two new windows overlap closely, skip the larger one (container)
574695
             let mut skip: std::collections::HashSet<u32> = HashSet::new();
575696
             for i in 0..bounds_map.len() {
576
-                for j in (i+1)..bounds_map.len() {
697
+                for j in (i + 1)..bounds_map.len() {
577698
                     let (wid_a, a) = &bounds_map[i];
578699
                     let (wid_b, b) = &bounds_map[j];
579700
                     // Check if centers are close (within 30px)
@@ -583,8 +704,8 @@ fn main() {
583704
                     let cy_b = b.origin.y + b.size.height / 2.0;
584705
                     if (cx_a - cx_b).abs() < 30.0 && (cy_a - cy_b).abs() < 30.0 {
585706
                         // Skip the larger one
586
-                        let area_a = a.size.width * a.size.height;
587
-                        let area_b = b.size.width * b.size.height;
707
+                        let area_a = window_area(*a);
708
+                        let area_b = window_area(*b);
588709
                         if area_a > area_b {
589710
                             skip.insert(*wid_a);
590711
                         } else {
@@ -652,9 +773,19 @@ fn main() {
652773
 fn setup_event_port(cid: CGSConnectionID) {
653774
     unsafe {
654775
         let mut port: u32 = 0;
655
-        if SLSGetEventPort(cid, &mut port) != kCGErrorSuccess { return; }
656
-        let cf_port = CFMachPortCreateWithPort(ptr::null(), port, drain_events as *const _, ptr::null(), false);
657
-        if cf_port.is_null() { return; }
776
+        if SLSGetEventPort(cid, &mut port) != kCGErrorSuccess {
777
+            return;
778
+        }
779
+        let cf_port = CFMachPortCreateWithPort(
780
+            ptr::null(),
781
+            port,
782
+            drain_events as *const _,
783
+            ptr::null(),
784
+            false,
785
+        );
786
+        if cf_port.is_null() {
787
+            return;
788
+        }
658789
         _CFMachPortSetOptions(cf_port, 0x40);
659790
         let source = CFMachPortCreateRunLoopSource(ptr::null(), cf_port, 0);
660791
         if !source.is_null() {
@@ -665,7 +796,12 @@ fn setup_event_port(cid: CGSConnectionID) {
665796
     }
666797
 }
667798
 
668
-unsafe extern "C" fn drain_events(_: CFMachPortRef, _: *mut std::ffi::c_void, _: i64, _: *mut std::ffi::c_void) {
799
+unsafe extern "C" fn drain_events(
800
+    _: CFMachPortRef,
801
+    _: *mut std::ffi::c_void,
802
+    _: i64,
803
+    _: *mut std::ffi::c_void,
804
+) {
669805
     unsafe {
670806
         let cid = SLSMainConnectionID();
671807
         let mut ev = SLEventCreateNextEvent(cid);
@@ -679,36 +815,58 @@ unsafe extern "C" fn drain_events(_: CFMachPortRef, _: *mut std::ffi::c_void, _:
679815
 fn discover_windows(_cid: CGSConnectionID, own_pid: i32) -> Vec<u32> {
680816
     unsafe {
681817
         let list = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
682
-        if list.is_null() { return vec![]; }
818
+        if list.is_null() {
819
+            return vec![];
820
+        }
683821
 
684822
         let count = CFArrayGetCount(list);
685
-        let wid_key = CFStringCreateWithCString(ptr::null(), b"kCGWindowNumber\0".as_ptr(), kCFStringEncodingUTF8);
686
-        let pid_key = CFStringCreateWithCString(ptr::null(), b"kCGWindowOwnerPID\0".as_ptr(), kCFStringEncodingUTF8);
687
-        let layer_key = CFStringCreateWithCString(ptr::null(), b"kCGWindowLayer\0".as_ptr(), kCFStringEncodingUTF8);
823
+        let wid_key = CFStringCreateWithCString(
824
+            ptr::null(),
825
+            b"kCGWindowNumber\0".as_ptr(),
826
+            kCFStringEncodingUTF8,
827
+        );
828
+        let pid_key = CFStringCreateWithCString(
829
+            ptr::null(),
830
+            b"kCGWindowOwnerPID\0".as_ptr(),
831
+            kCFStringEncodingUTF8,
832
+        );
833
+        let layer_key = CFStringCreateWithCString(
834
+            ptr::null(),
835
+            b"kCGWindowLayer\0".as_ptr(),
836
+            kCFStringEncodingUTF8,
837
+        );
688838
 
689839
         let mut wids = Vec::new();
690840
         for i in 0..count {
691841
             let dict = CFArrayGetValueAtIndex(list, i);
692
-            if dict.is_null() { continue; }
842
+            if dict.is_null() {
843
+                continue;
844
+            }
693845
 
694846
             let mut v: CFTypeRef = ptr::null();
695847
             let mut wid: u32 = 0;
696848
             if CFDictionaryGetValueIfPresent(dict, wid_key as CFTypeRef, &mut v) {
697849
                 CFNumberGetValue(v, kCFNumberSInt32Type, &mut wid as *mut _ as *mut _);
698850
             }
699
-            if wid == 0 { continue; }
851
+            if wid == 0 {
852
+                continue;
853
+            }
700854
 
701855
             let mut pid: i32 = 0;
702856
             if CFDictionaryGetValueIfPresent(dict, pid_key as CFTypeRef, &mut v) {
703857
                 CFNumberGetValue(v, kCFNumberSInt32Type, &mut pid as *mut _ as *mut _);
704858
             }
705
-            if pid == own_pid { continue; }
859
+            if pid == own_pid {
860
+                continue;
861
+            }
706862
 
707863
             let mut layer: i32 = -1;
708864
             if CFDictionaryGetValueIfPresent(dict, layer_key as CFTypeRef, &mut v) {
709865
                 CFNumberGetValue(v, kCFNumberSInt32Type, &mut layer as *mut _ as *mut _);
710866
             }
711
-            if layer != 0 { continue; }
867
+            if layer != 0 {
868
+                continue;
869
+            }
712870
 
713871
             wids.push(wid);
714872
         }
@@ -766,7 +924,10 @@ fn create_overlay(
766924
             return None;
767925
         }
768926
         if bounds.size.width < 10.0 || bounds.size.height < 10.0 {
769
-            debug!("[create_overlay] wid={target_wid} too small: {}x{}", bounds.size.width, bounds.size.height);
927
+            debug!(
928
+                "[create_overlay] wid={target_wid} too small: {}x{}",
929
+                bounds.size.width, bounds.size.height
930
+            );
770931
             return None;
771932
         }
772933
 
@@ -792,8 +953,10 @@ fn create_overlay(
792953
             return None;
793954
         }
794955
 
795
-        debug!("[create_overlay] created overlay wid={wid} for target={target_wid} color=({:.2},{:.2},{:.2},{:.2})",
796
-            color.0, color.1, color.2, color.3);
956
+        debug!(
957
+            "[create_overlay] created overlay wid={wid} for target={target_wid} color=({:.2},{:.2},{:.2},{:.2})",
958
+            color.0, color.1, color.2, color.3
959
+        );
797960
 
798961
         SLSSetWindowResolution(cid, wid, 2.0);
799962
         SLSSetWindowOpacity(cid, wid, false);
@@ -820,15 +983,30 @@ fn list_windows() {
820983
     let cid = unsafe { SLSMainConnectionID() };
821984
     unsafe {
822985
         let list = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
823
-        if list.is_null() { return; }
986
+        if list.is_null() {
987
+            return;
988
+        }
824989
         let count = CFArrayGetCount(list);
825
-        let wid_key = CFStringCreateWithCString(ptr::null(), b"kCGWindowNumber\0".as_ptr(), kCFStringEncodingUTF8);
826
-        let layer_key = CFStringCreateWithCString(ptr::null(), b"kCGWindowLayer\0".as_ptr(), kCFStringEncodingUTF8);
827
-
828
-        eprintln!("{:>6}  {:>8}  {:>8}  {:>6}  {:>6}", "wid", "x", "y", "w", "h");
990
+        let wid_key = CFStringCreateWithCString(
991
+            ptr::null(),
992
+            b"kCGWindowNumber\0".as_ptr(),
993
+            kCFStringEncodingUTF8,
994
+        );
995
+        let layer_key = CFStringCreateWithCString(
996
+            ptr::null(),
997
+            b"kCGWindowLayer\0".as_ptr(),
998
+            kCFStringEncodingUTF8,
999
+        );
1000
+
1001
+        eprintln!(
1002
+            "{:>6}  {:>8}  {:>8}  {:>6}  {:>6}",
1003
+            "wid", "x", "y", "w", "h"
1004
+        );
8291005
         for i in 0..count {
8301006
             let dict = CFArrayGetValueAtIndex(list, i);
831
-            if dict.is_null() { continue; }
1007
+            if dict.is_null() {
1008
+                continue;
1009
+            }
8321010
 
8331011
             let mut v: CFTypeRef = ptr::null();
8341012
             let mut wid: u32 = 0;
@@ -839,12 +1017,16 @@ fn list_windows() {
8391017
             if CFDictionaryGetValueIfPresent(dict, layer_key as CFTypeRef, &mut v) {
8401018
                 CFNumberGetValue(v, kCFNumberSInt32Type, &mut layer as *mut _ as *mut _);
8411019
             }
842
-            if layer != 0 || wid == 0 { continue; }
1020
+            if layer != 0 || wid == 0 {
1021
+                continue;
1022
+            }
8431023
 
8441024
             let mut bounds = CGRect::default();
8451025
             SLSGetWindowBounds(cid, wid, &mut bounds);
846
-            eprintln!("{wid:>6}  {:>8.0}  {:>8.0}  {:>6.0}  {:>6.0}",
847
-                bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height);
1026
+            eprintln!(
1027
+                "{wid:>6}  {:>8.0}  {:>8.0}  {:>6.0}  {:>6.0}",
1028
+                bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height
1029
+            );
8481030
         }
8491031
         CFRelease(wid_key as CFTypeRef);
8501032
         CFRelease(layer_key as CFTypeRef);
@@ -852,3 +1034,28 @@ fn list_windows() {
8521034
     }
8531035
 }
8541036
 
1037
+#[cfg(test)]
1038
+mod tests {
1039
+    use super::{CGRect, intersection_area, is_same_window_surface};
1040
+
1041
+    #[test]
1042
+    fn same_surface_detects_contained_strip() {
1043
+        let outer = CGRect::new(100.0, 100.0, 1200.0, 900.0);
1044
+        let strip = CGRect::new(114.0, 105.0, 1160.0, 140.0);
1045
+        assert!(is_same_window_surface(outer, strip));
1046
+    }
1047
+
1048
+    #[test]
1049
+    fn different_windows_are_not_treated_as_one_surface() {
1050
+        let a = CGRect::new(100.0, 100.0, 1200.0, 900.0);
1051
+        let b = CGRect::new(300.0, 300.0, 1160.0, 140.0);
1052
+        assert!(!is_same_window_surface(a, b));
1053
+    }
1054
+
1055
+    #[test]
1056
+    fn intersection_area_is_zero_without_overlap() {
1057
+        let a = CGRect::new(100.0, 100.0, 200.0, 200.0);
1058
+        let b = CGRect::new(400.0, 400.0, 200.0, 200.0);
1059
+        assert_eq!(intersection_area(a, b), 0.0);
1060
+    }
1061
+}