@@ -7,9 +7,9 @@ use events::Event; |
| 7 | 7 | use skylight::*; |
| 8 | 8 | use std::collections::HashMap; |
| 9 | 9 | use std::ptr; |
| 10 | +use std::sync::Arc; |
| 10 | 11 | use std::sync::atomic::{AtomicBool, Ordering}; |
| 11 | 12 | use std::sync::mpsc; |
| 12 | | -use std::sync::Arc; |
| 13 | 13 | use tracing::debug; |
| 14 | 14 | |
| 15 | 15 | /// Per-overlay state: the connection it was created on + its wid. |
@@ -18,6 +18,25 @@ struct Overlay { |
| 18 | 18 | wid: u32, |
| 19 | 19 | } |
| 20 | 20 | |
| 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 | + |
| 21 | 40 | /// Tracks overlays for target windows. |
| 22 | 41 | struct BorderMap { |
| 23 | 42 | overlays: HashMap<u32, Overlay>, |
@@ -41,13 +60,17 @@ impl BorderMap { |
| 41 | 60 | radius: 10.0, |
| 42 | 61 | focused_wid: 0, |
| 43 | 62 | 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 |
| 45 | 64 | active_only: false, |
| 46 | 65 | } |
| 47 | 66 | } |
| 48 | 67 | |
| 49 | 68 | 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 | + } |
| 51 | 74 | } |
| 52 | 75 | |
| 53 | 76 | fn is_overlay(&self, wid: u32) -> bool { |
@@ -56,9 +79,17 @@ impl BorderMap { |
| 56 | 79 | |
| 57 | 80 | /// Add border (batch mode, uses main cid). |
| 58 | 81 | 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 | + } |
| 60 | 85 | 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 | + ) { |
| 62 | 93 | self.overlays.insert(target_wid, Overlay { cid, wid }); |
| 63 | 94 | } |
| 64 | 95 | } |
@@ -66,23 +97,31 @@ impl BorderMap { |
| 66 | 97 | /// Add border (event mode). Uses main_cid — fresh connections create |
| 67 | 98 | /// invisible windows on Tahoe. |
| 68 | 99 | 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 | + } |
| 70 | 103 | |
| 71 | 104 | // Filter: must be visible, owned by another process, not tiny |
| 72 | 105 | let bounds = unsafe { |
| 73 | 106 | let mut shown = false; |
| 74 | 107 | SLSWindowIsOrderedIn(self.main_cid, target_wid, &mut shown); |
| 75 | | - if !shown { return; } |
| 108 | + if !shown { |
| 109 | + return; |
| 110 | + } |
| 76 | 111 | |
| 77 | 112 | let mut wid_cid: CGSConnectionID = 0; |
| 78 | 113 | SLSGetWindowOwner(self.main_cid, target_wid, &mut wid_cid); |
| 79 | 114 | let mut pid: i32 = 0; |
| 80 | 115 | SLSConnectionGetPID(wid_cid, &mut pid); |
| 81 | | - if pid == self.own_pid { return; } |
| 116 | + if pid == self.own_pid { |
| 117 | + return; |
| 118 | + } |
| 82 | 119 | |
| 83 | 120 | let mut bounds = CGRect::default(); |
| 84 | 121 | 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 | + } |
| 86 | 125 | bounds |
| 87 | 126 | }; |
| 88 | 127 | |
@@ -90,7 +129,7 @@ impl BorderMap { |
| 90 | 129 | // keep the smaller one (content) and skip the larger one (container) |
| 91 | 130 | let cx = bounds.origin.x + bounds.size.width / 2.0; |
| 92 | 131 | 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); |
| 94 | 133 | for &existing_wid in self.overlays.keys() { |
| 95 | 134 | unsafe { |
| 96 | 135 | let mut eb = CGRect::default(); |
@@ -100,14 +139,22 @@ impl BorderMap { |
| 100 | 139 | let ecx = eb.origin.x + eb.size.width / 2.0; |
| 101 | 140 | let ecy = eb.origin.y + eb.size.height / 2.0; |
| 102 | 141 | 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 |
| 105 | 146 | } |
| 106 | 147 | } |
| 107 | 148 | } |
| 108 | 149 | |
| 109 | 150 | 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 | + ) { |
| 111 | 158 | self.overlays.insert(target_wid, Overlay { cid, wid }); |
| 112 | 159 | } |
| 113 | 160 | } |
@@ -123,7 +170,10 @@ impl BorderMap { |
| 123 | 170 | if let Some(overlay) = self.overlays.remove(&target_wid) { |
| 124 | 171 | unsafe { |
| 125 | 172 | // 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 | + }; |
| 127 | 177 | SLSMoveWindow(overlay.cid, overlay.wid, &offscreen); |
| 128 | 178 | SLSSetWindowAlpha(overlay.cid, overlay.wid, 0.0); |
| 129 | 179 | SLSOrderWindow(overlay.cid, overlay.wid, 0, 0); |
@@ -155,7 +205,9 @@ impl BorderMap { |
| 155 | 205 | |
| 156 | 206 | /// Recreate overlay at new size. |
| 157 | 207 | 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 | + } |
| 159 | 211 | self.remove(target_wid); |
| 160 | 212 | self.add_fresh(target_wid); |
| 161 | 213 | if self.active_only && target_wid != self.focused_wid { |
@@ -166,7 +218,9 @@ impl BorderMap { |
| 166 | 218 | |
| 167 | 219 | fn hide(&self, target_wid: u32) { |
| 168 | 220 | 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 | + } |
| 170 | 224 | } |
| 171 | 225 | } |
| 172 | 226 | |
@@ -187,7 +241,9 @@ impl BorderMap { |
| 187 | 241 | |
| 188 | 242 | fn subscribe_all(&self) { |
| 189 | 243 | 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 | + } |
| 191 | 247 | unsafe { |
| 192 | 248 | SLSRequestNotificationsForWindows( |
| 193 | 249 | self.main_cid, |
@@ -210,7 +266,9 @@ impl BorderMap { |
| 210 | 266 | let oh = bounds.size.height + 2.0 * bw; |
| 211 | 267 | |
| 212 | 268 | let ctx = SLWindowContextCreate(overlay.cid, overlay.wid, ptr::null()); |
| 213 | | - if ctx.is_null() { return; } |
| 269 | + if ctx.is_null() { |
| 270 | + return; |
| 271 | + } |
| 214 | 272 | |
| 215 | 273 | let color = self.color_for(target_wid); |
| 216 | 274 | draw_border(ctx, ow, oh, bw, self.radius, color); |
@@ -223,7 +281,9 @@ impl BorderMap { |
| 223 | 281 | /// Detect focused window and update border colors if focus changed. |
| 224 | 282 | fn update_focus(&mut self) { |
| 225 | 283 | 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 | + } |
| 227 | 287 | |
| 228 | 288 | let old = self.focused_wid; |
| 229 | 289 | self.focused_wid = front; |
@@ -258,7 +318,9 @@ impl BorderMap { |
| 258 | 318 | |
| 259 | 319 | /// In active-only mode, ensure only the focused overlay is visible. |
| 260 | 320 | fn enforce_active_only(&self) { |
| 261 | | - if !self.active_only { return; } |
| 321 | + if !self.active_only { |
| 322 | + return; |
| 323 | + } |
| 262 | 324 | for (&target_wid, o) in &self.overlays { |
| 263 | 325 | if target_wid == self.focused_wid { |
| 264 | 326 | unsafe { |
@@ -266,7 +328,9 @@ impl BorderMap { |
| 266 | 328 | SLSOrderWindow(o.cid, o.wid, 1, target_wid); |
| 267 | 329 | } |
| 268 | 330 | } else { |
| 269 | | - unsafe { SLSOrderWindow(o.cid, o.wid, 0, 0); } |
| 331 | + unsafe { |
| 332 | + SLSOrderWindow(o.cid, o.wid, 0, 0); |
| 333 | + } |
| 270 | 334 | } |
| 271 | 335 | } |
| 272 | 336 | } |
@@ -285,22 +349,42 @@ fn get_front_window(own_pid: i32) -> u32 { |
| 285 | 349 | SLSGetConnectionIDForPSN(SLSMainConnectionID(), &mut psn, &mut front_cid); |
| 286 | 350 | let mut front_pid: i32 = 0; |
| 287 | 351 | 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 | + } |
| 289 | 355 | |
| 290 | 356 | // Step 2: find the topmost layer-0 window belonging to that process |
| 291 | 357 | let list = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID); |
| 292 | | - if list.is_null() { return 0; } |
| 358 | + if list.is_null() { |
| 359 | + return 0; |
| 360 | + } |
| 293 | 361 | |
| 294 | 362 | 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 | + ); |
| 298 | 378 | |
| 299 | 379 | let mut front_wid: u32 = 0; |
| 380 | + let mut front_bounds = CGRect::default(); |
| 381 | + let mut have_front_bounds = false; |
| 300 | 382 | let mut fallback_wid: u32 = 0; |
| 301 | 383 | for i in 0..count { |
| 302 | 384 | let dict = CFArrayGetValueAtIndex(list, i); |
| 303 | | - if dict.is_null() { continue; } |
| 385 | + if dict.is_null() { |
| 386 | + continue; |
| 387 | + } |
| 304 | 388 | |
| 305 | 389 | let mut v: CFTypeRef = ptr::null(); |
| 306 | 390 | |
@@ -308,29 +392,58 @@ fn get_front_window(own_pid: i32) -> u32 { |
| 308 | 392 | if CFDictionaryGetValueIfPresent(dict, layer_key as CFTypeRef, &mut v) { |
| 309 | 393 | CFNumberGetValue(v, kCFNumberSInt32Type, &mut layer as *mut _ as *mut _); |
| 310 | 394 | } |
| 311 | | - if layer != 0 { continue; } |
| 395 | + if layer != 0 { |
| 396 | + continue; |
| 397 | + } |
| 312 | 398 | |
| 313 | 399 | let mut pid: i32 = 0; |
| 314 | 400 | if CFDictionaryGetValueIfPresent(dict, pid_key as CFTypeRef, &mut v) { |
| 315 | 401 | CFNumberGetValue(v, kCFNumberSInt32Type, &mut pid as *mut _ as *mut _); |
| 316 | 402 | } |
| 317 | | - if pid == own_pid { continue; } |
| 403 | + if pid == own_pid { |
| 404 | + continue; |
| 405 | + } |
| 318 | 406 | |
| 319 | 407 | let mut wid: u32 = 0; |
| 320 | 408 | if CFDictionaryGetValueIfPresent(dict, wid_key as CFTypeRef, &mut v) { |
| 321 | 409 | CFNumberGetValue(v, kCFNumberSInt32Type, &mut wid as *mut _ as *mut _); |
| 322 | 410 | } |
| 323 | | - if wid == 0 { continue; } |
| 411 | + if wid == 0 { |
| 412 | + continue; |
| 413 | + } |
| 324 | 414 | |
| 325 | 415 | // Track first non-self window as fallback (z-order based) |
| 326 | 416 | if fallback_wid == 0 { |
| 327 | 417 | fallback_wid = wid; |
| 328 | 418 | } |
| 329 | 419 | |
| 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. |
| 331 | 424 | 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 | + } |
| 334 | 447 | } |
| 335 | 448 | } |
| 336 | 449 | |
@@ -351,13 +464,17 @@ fn get_front_window(own_pid: i32) -> u32 { |
| 351 | 464 | /// Parse hex color string (#RRGGBB or #RRGGBBAA) to (r, g, b, a) floats. |
| 352 | 465 | fn parse_color(s: &str) -> Option<(f64, f64, f64, f64)> { |
| 353 | 466 | 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 | + } |
| 355 | 470 | let r = u8::from_str_radix(&hex[0..2], 16).ok()? as f64 / 255.0; |
| 356 | 471 | let g = u8::from_str_radix(&hex[2..4], 16).ok()? as f64 / 255.0; |
| 357 | 472 | let b = u8::from_str_radix(&hex[4..6], 16).ok()? as f64 / 255.0; |
| 358 | 473 | let a = if hex.len() == 8 { |
| 359 | 474 | u8::from_str_radix(&hex[6..8], 16).ok()? as f64 / 255.0 |
| 360 | | - } else { 1.0 }; |
| 475 | + } else { |
| 476 | + 1.0 |
| 477 | + }; |
| 361 | 478 | Some((r, g, b, a)) |
| 362 | 479 | } |
| 363 | 480 | |
@@ -454,7 +571,9 @@ fn main() { |
| 454 | 571 | |
| 455 | 572 | if borders.active_only { |
| 456 | 573 | 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() |
| 458 | 577 | .filter(|&&wid| wid != focused) |
| 459 | 578 | .copied() |
| 460 | 579 | .collect(); |
@@ -554,9 +673,11 @@ fn main() { |
| 554 | 673 | |
| 555 | 674 | // Promote pending creates that have waited ≥100ms (tarmac positioning time) |
| 556 | 675 | let now = Instant::now(); |
| 557 | | - let ready: Vec<u32> = pending.iter() |
| 676 | + let ready: Vec<u32> = pending |
| 677 | + .iter() |
| 558 | 678 | .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) |
| 560 | 681 | }) |
| 561 | 682 | .map(|(wid, _)| *wid) |
| 562 | 683 | .collect(); |
@@ -573,7 +694,7 @@ fn main() { |
| 573 | 694 | // If two new windows overlap closely, skip the larger one (container) |
| 574 | 695 | let mut skip: std::collections::HashSet<u32> = HashSet::new(); |
| 575 | 696 | 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() { |
| 577 | 698 | let (wid_a, a) = &bounds_map[i]; |
| 578 | 699 | let (wid_b, b) = &bounds_map[j]; |
| 579 | 700 | // Check if centers are close (within 30px) |
@@ -583,8 +704,8 @@ fn main() { |
| 583 | 704 | let cy_b = b.origin.y + b.size.height / 2.0; |
| 584 | 705 | if (cx_a - cx_b).abs() < 30.0 && (cy_a - cy_b).abs() < 30.0 { |
| 585 | 706 | // 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); |
| 588 | 709 | if area_a > area_b { |
| 589 | 710 | skip.insert(*wid_a); |
| 590 | 711 | } else { |
@@ -652,9 +773,19 @@ fn main() { |
| 652 | 773 | fn setup_event_port(cid: CGSConnectionID) { |
| 653 | 774 | unsafe { |
| 654 | 775 | 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 | + } |
| 658 | 789 | _CFMachPortSetOptions(cf_port, 0x40); |
| 659 | 790 | let source = CFMachPortCreateRunLoopSource(ptr::null(), cf_port, 0); |
| 660 | 791 | if !source.is_null() { |
@@ -665,7 +796,12 @@ fn setup_event_port(cid: CGSConnectionID) { |
| 665 | 796 | } |
| 666 | 797 | } |
| 667 | 798 | |
| 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 | +) { |
| 669 | 805 | unsafe { |
| 670 | 806 | let cid = SLSMainConnectionID(); |
| 671 | 807 | let mut ev = SLEventCreateNextEvent(cid); |
@@ -679,36 +815,58 @@ unsafe extern "C" fn drain_events(_: CFMachPortRef, _: *mut std::ffi::c_void, _: |
| 679 | 815 | fn discover_windows(_cid: CGSConnectionID, own_pid: i32) -> Vec<u32> { |
| 680 | 816 | unsafe { |
| 681 | 817 | let list = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID); |
| 682 | | - if list.is_null() { return vec![]; } |
| 818 | + if list.is_null() { |
| 819 | + return vec![]; |
| 820 | + } |
| 683 | 821 | |
| 684 | 822 | 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 | + ); |
| 688 | 838 | |
| 689 | 839 | let mut wids = Vec::new(); |
| 690 | 840 | for i in 0..count { |
| 691 | 841 | let dict = CFArrayGetValueAtIndex(list, i); |
| 692 | | - if dict.is_null() { continue; } |
| 842 | + if dict.is_null() { |
| 843 | + continue; |
| 844 | + } |
| 693 | 845 | |
| 694 | 846 | let mut v: CFTypeRef = ptr::null(); |
| 695 | 847 | let mut wid: u32 = 0; |
| 696 | 848 | if CFDictionaryGetValueIfPresent(dict, wid_key as CFTypeRef, &mut v) { |
| 697 | 849 | CFNumberGetValue(v, kCFNumberSInt32Type, &mut wid as *mut _ as *mut _); |
| 698 | 850 | } |
| 699 | | - if wid == 0 { continue; } |
| 851 | + if wid == 0 { |
| 852 | + continue; |
| 853 | + } |
| 700 | 854 | |
| 701 | 855 | let mut pid: i32 = 0; |
| 702 | 856 | if CFDictionaryGetValueIfPresent(dict, pid_key as CFTypeRef, &mut v) { |
| 703 | 857 | CFNumberGetValue(v, kCFNumberSInt32Type, &mut pid as *mut _ as *mut _); |
| 704 | 858 | } |
| 705 | | - if pid == own_pid { continue; } |
| 859 | + if pid == own_pid { |
| 860 | + continue; |
| 861 | + } |
| 706 | 862 | |
| 707 | 863 | let mut layer: i32 = -1; |
| 708 | 864 | if CFDictionaryGetValueIfPresent(dict, layer_key as CFTypeRef, &mut v) { |
| 709 | 865 | CFNumberGetValue(v, kCFNumberSInt32Type, &mut layer as *mut _ as *mut _); |
| 710 | 866 | } |
| 711 | | - if layer != 0 { continue; } |
| 867 | + if layer != 0 { |
| 868 | + continue; |
| 869 | + } |
| 712 | 870 | |
| 713 | 871 | wids.push(wid); |
| 714 | 872 | } |
@@ -766,7 +924,10 @@ fn create_overlay( |
| 766 | 924 | return None; |
| 767 | 925 | } |
| 768 | 926 | 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 | + ); |
| 770 | 931 | return None; |
| 771 | 932 | } |
| 772 | 933 | |
@@ -792,8 +953,10 @@ fn create_overlay( |
| 792 | 953 | return None; |
| 793 | 954 | } |
| 794 | 955 | |
| 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 | + ); |
| 797 | 960 | |
| 798 | 961 | SLSSetWindowResolution(cid, wid, 2.0); |
| 799 | 962 | SLSSetWindowOpacity(cid, wid, false); |
@@ -820,15 +983,30 @@ fn list_windows() { |
| 820 | 983 | let cid = unsafe { SLSMainConnectionID() }; |
| 821 | 984 | unsafe { |
| 822 | 985 | let list = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID); |
| 823 | | - if list.is_null() { return; } |
| 986 | + if list.is_null() { |
| 987 | + return; |
| 988 | + } |
| 824 | 989 | 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 | + ); |
| 829 | 1005 | for i in 0..count { |
| 830 | 1006 | let dict = CFArrayGetValueAtIndex(list, i); |
| 831 | | - if dict.is_null() { continue; } |
| 1007 | + if dict.is_null() { |
| 1008 | + continue; |
| 1009 | + } |
| 832 | 1010 | |
| 833 | 1011 | let mut v: CFTypeRef = ptr::null(); |
| 834 | 1012 | let mut wid: u32 = 0; |
@@ -839,12 +1017,16 @@ fn list_windows() { |
| 839 | 1017 | if CFDictionaryGetValueIfPresent(dict, layer_key as CFTypeRef, &mut v) { |
| 840 | 1018 | CFNumberGetValue(v, kCFNumberSInt32Type, &mut layer as *mut _ as *mut _); |
| 841 | 1019 | } |
| 842 | | - if layer != 0 || wid == 0 { continue; } |
| 1020 | + if layer != 0 || wid == 0 { |
| 1021 | + continue; |
| 1022 | + } |
| 843 | 1023 | |
| 844 | 1024 | let mut bounds = CGRect::default(); |
| 845 | 1025 | 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 | + ); |
| 848 | 1030 | } |
| 849 | 1031 | CFRelease(wid_key as CFTypeRef); |
| 850 | 1032 | CFRelease(layer_key as CFTypeRef); |
@@ -852,3 +1034,28 @@ fn list_windows() { |
| 852 | 1034 | } |
| 853 | 1035 | } |
| 854 | 1036 | |
| 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 | +} |