@@ -16,6 +16,13 @@ static SIGNAL_STOP_REQUESTED: AtomicBool = AtomicBool::new(false); |
| 16 | 16 | const MIN_TRACKED_WINDOW_SIZE: f64 = 4.0; |
| 17 | 17 | const GEOMETRY_EPSILON: f64 = 0.5; |
| 18 | 18 | const SCALE_EPSILON: f64 = 0.01; |
| 19 | +const WINDOW_ATTRIBUTE_REAL: u64 = 1 << 1; |
| 20 | +const WINDOW_TAG_DOCUMENT: u64 = 1 << 0; |
| 21 | +const WINDOW_TAG_FLOATING: u64 = 1 << 1; |
| 22 | +const WINDOW_TAG_ATTACHED: u64 = 1 << 7; |
| 23 | +const WINDOW_TAG_IGNORES_CYCLE: u64 = 1 << 18; |
| 24 | +const WINDOW_TAG_MODAL: u64 = 1 << 31; |
| 25 | +const WINDOW_TAG_REAL_SURFACE: u64 = 1 << 58; |
| 19 | 26 | |
| 20 | 27 | /// Per-overlay state: the connection it was created on + its wid. |
| 21 | 28 | struct Overlay { |
@@ -50,6 +57,13 @@ enum SurfacePreference { |
| 50 | 57 | ReplaceExisting, |
| 51 | 58 | } |
| 52 | 59 | |
| 60 | +#[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| 61 | +struct WindowMetadata { |
| 62 | + parent_wid: u32, |
| 63 | + tags: u64, |
| 64 | + attributes: u64, |
| 65 | +} |
| 66 | + |
| 53 | 67 | fn surface_preference(existing: CGRect, candidate: CGRect) -> Option<SurfacePreference> { |
| 54 | 68 | if !is_same_window_surface(existing, candidate) { |
| 55 | 69 | return None; |
@@ -81,6 +95,72 @@ fn size_changed(a: CGRect, b: CGRect) -> bool { |
| 81 | 95 | || (a.size.height - b.size.height).abs() > GEOMETRY_EPSILON |
| 82 | 96 | } |
| 83 | 97 | |
| 98 | +fn is_suitable_window_metadata(metadata: WindowMetadata) -> bool { |
| 99 | + metadata.parent_wid == 0 |
| 100 | + && ((metadata.attributes & WINDOW_ATTRIBUTE_REAL) != 0 |
| 101 | + || (metadata.tags & WINDOW_TAG_REAL_SURFACE) != 0) |
| 102 | + && (metadata.tags & WINDOW_TAG_ATTACHED) == 0 |
| 103 | + && (metadata.tags & WINDOW_TAG_IGNORES_CYCLE) == 0 |
| 104 | + && ((metadata.tags & WINDOW_TAG_DOCUMENT) != 0 |
| 105 | + || ((metadata.tags & WINDOW_TAG_FLOATING) != 0 |
| 106 | + && (metadata.tags & WINDOW_TAG_MODAL) != 0)) |
| 107 | +} |
| 108 | + |
| 109 | +fn query_window_metadata(cid: CGSConnectionID, wid: u32) -> Option<WindowMetadata> { |
| 110 | + unsafe { |
| 111 | + let window_ref = cfarray_of_cfnumbers( |
| 112 | + (&wid as *const u32).cast(), |
| 113 | + std::mem::size_of::<u32>(), |
| 114 | + 1, |
| 115 | + kCFNumberSInt32Type, |
| 116 | + ); |
| 117 | + if window_ref.is_null() { |
| 118 | + return None; |
| 119 | + } |
| 120 | + |
| 121 | + let query = SLSWindowQueryWindows(cid, window_ref, 0x0); |
| 122 | + CFRelease(window_ref); |
| 123 | + if query.is_null() { |
| 124 | + return None; |
| 125 | + } |
| 126 | + |
| 127 | + let iterator = SLSWindowQueryResultCopyWindows(query); |
| 128 | + CFRelease(query); |
| 129 | + if iterator.is_null() { |
| 130 | + return None; |
| 131 | + } |
| 132 | + |
| 133 | + let metadata = if SLSWindowIteratorAdvance(iterator) { |
| 134 | + Some(WindowMetadata { |
| 135 | + parent_wid: SLSWindowIteratorGetParentID(iterator), |
| 136 | + tags: SLSWindowIteratorGetTags(iterator), |
| 137 | + attributes: SLSWindowIteratorGetAttributes(iterator), |
| 138 | + }) |
| 139 | + } else { |
| 140 | + None |
| 141 | + }; |
| 142 | + |
| 143 | + CFRelease(iterator); |
| 144 | + metadata |
| 145 | + } |
| 146 | +} |
| 147 | + |
| 148 | +fn is_suitable_window(cid: CGSConnectionID, wid: u32) -> bool { |
| 149 | + match query_window_metadata(cid, wid) { |
| 150 | + Some(metadata) => { |
| 151 | + let suitable = is_suitable_window_metadata(metadata); |
| 152 | + if !suitable { |
| 153 | + debug!( |
| 154 | + "[window_filter] rejecting wid={} parent={} tags={:#x} attributes={:#x}", |
| 155 | + wid, metadata.parent_wid, metadata.tags, metadata.attributes |
| 156 | + ); |
| 157 | + } |
| 158 | + suitable |
| 159 | + } |
| 160 | + None => false, |
| 161 | + } |
| 162 | +} |
| 163 | + |
| 84 | 164 | fn cf_string_from_static(name: &std::ffi::CStr) -> CFStringRef { |
| 85 | 165 | unsafe { CFStringCreateWithCString(ptr::null(), name.as_ptr().cast(), kCFStringEncodingUTF8) } |
| 86 | 166 | } |
@@ -236,6 +316,9 @@ impl BorderMap { |
| 236 | 316 | if pid == self.own_pid { |
| 237 | 317 | return; |
| 238 | 318 | } |
| 319 | + if !is_suitable_window(self.main_cid, target_wid) { |
| 320 | + return; |
| 321 | + } |
| 239 | 322 | |
| 240 | 323 | let mut bounds = CGRect::default(); |
| 241 | 324 | SLSGetWindowBounds(self.main_cid, target_wid, &mut bounds); |
@@ -315,6 +398,11 @@ impl BorderMap { |
| 315 | 398 | return false; |
| 316 | 399 | } |
| 317 | 400 | |
| 401 | + if !is_suitable_window(self.main_cid, target_wid) { |
| 402 | + self.remove(target_wid); |
| 403 | + return true; |
| 404 | + } |
| 405 | + |
| 318 | 406 | if !is_trackable_window(bounds, self.border_width) { |
| 319 | 407 | self.remove(target_wid); |
| 320 | 408 | return true; |
@@ -569,6 +657,10 @@ fn get_front_window(own_pid: i32) -> u32 { |
| 569 | 657 | continue; |
| 570 | 658 | } |
| 571 | 659 | |
| 660 | + if !is_suitable_window(SLSMainConnectionID(), wid) { |
| 661 | + continue; |
| 662 | + } |
| 663 | + |
| 572 | 664 | // Track first non-self window as fallback (z-order based) |
| 573 | 665 | if fallback_wid == 0 { |
| 574 | 666 | fallback_wid = wid; |
@@ -987,7 +1079,7 @@ unsafe extern "C" fn drain_events( |
| 987 | 1079 | } |
| 988 | 1080 | } |
| 989 | 1081 | |
| 990 | | -fn discover_windows(_cid: CGSConnectionID, own_pid: i32) -> Vec<u32> { |
| 1082 | +fn discover_windows(cid: CGSConnectionID, own_pid: i32) -> Vec<u32> { |
| 991 | 1083 | unsafe { |
| 992 | 1084 | let list = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID); |
| 993 | 1085 | if list.is_null() { |
@@ -1023,6 +1115,10 @@ fn discover_windows(_cid: CGSConnectionID, own_pid: i32) -> Vec<u32> { |
| 1023 | 1115 | continue; |
| 1024 | 1116 | } |
| 1025 | 1117 | |
| 1118 | + if !is_suitable_window(cid, wid) { |
| 1119 | + continue; |
| 1120 | + } |
| 1121 | + |
| 1026 | 1122 | let mut layer: i32 = -1; |
| 1027 | 1123 | if CFDictionaryGetValueIfPresent(dict, layer_key as CFTypeRef, &mut v) { |
| 1028 | 1124 | CFNumberGetValue(v, kCFNumberSInt32Type, &mut layer as *mut _ as *mut _); |
@@ -1193,8 +1289,8 @@ fn list_windows() { |
| 1193 | 1289 | #[cfg(test)] |
| 1194 | 1290 | mod tests { |
| 1195 | 1291 | use super::{ |
| 1196 | | - CGRect, SurfacePreference, intersection_area, is_same_window_surface, is_trackable_window, |
| 1197 | | - surface_preference, |
| 1292 | + CGRect, SurfacePreference, WindowMetadata, intersection_area, is_same_window_surface, |
| 1293 | + is_suitable_window_metadata, is_trackable_window, surface_preference, |
| 1198 | 1294 | }; |
| 1199 | 1295 | |
| 1200 | 1296 | #[test] |
@@ -1233,4 +1329,24 @@ mod tests { |
| 1233 | 1329 | let small = CGRect::new(100.0, 100.0, 12.0, 18.0); |
| 1234 | 1330 | assert!(is_trackable_window(small, 4.0)); |
| 1235 | 1331 | } |
| 1332 | + |
| 1333 | + #[test] |
| 1334 | + fn suitable_window_metadata_matches_document_windows() { |
| 1335 | + let metadata = WindowMetadata { |
| 1336 | + parent_wid: 0, |
| 1337 | + tags: super::WINDOW_TAG_DOCUMENT, |
| 1338 | + attributes: super::WINDOW_ATTRIBUTE_REAL, |
| 1339 | + }; |
| 1340 | + assert!(is_suitable_window_metadata(metadata)); |
| 1341 | + } |
| 1342 | + |
| 1343 | + #[test] |
| 1344 | + fn attached_windows_are_not_suitable_targets() { |
| 1345 | + let metadata = WindowMetadata { |
| 1346 | + parent_wid: 7, |
| 1347 | + tags: super::WINDOW_TAG_DOCUMENT | super::WINDOW_TAG_ATTACHED, |
| 1348 | + attributes: super::WINDOW_ATTRIBUTE_REAL, |
| 1349 | + }; |
| 1350 | + assert!(!is_suitable_window_metadata(metadata)); |
| 1351 | + } |
| 1236 | 1352 | } |