@@ -1087,6 +1087,68 @@ unsafe extern "C" fn drain_events( |
| 1087 | 1087 | } |
| 1088 | 1088 | } |
| 1089 | 1089 | |
| 1090 | +/// Look up an overlay window in CGWindowListCopyWindowInfo and dump the |
| 1091 | +/// keys that the screenshot picker / ScreenCaptureKit care about. Lets |
| 1092 | +/// us tell whether SLSSetWindowSharingState(0) propagates through to |
| 1093 | +/// the CG window list (the layer SCWindow filters on) or stops at SLS. |
| 1094 | +fn probe_cg_window_info(target_wid: u32) { |
| 1095 | + unsafe { |
| 1096 | + let list = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID); |
| 1097 | + if list.is_null() { |
| 1098 | + debug!("[probe_cg_window_info] wid={target_wid} list is null"); |
| 1099 | + return; |
| 1100 | + } |
| 1101 | + let count = CFArrayGetCount(list); |
| 1102 | + let wid_key = cf_string_from_static(c"kCGWindowNumber"); |
| 1103 | + let sharing_key = cf_string_from_static(c"kCGWindowSharingState"); |
| 1104 | + let layer_key = cf_string_from_static(c"kCGWindowLayer"); |
| 1105 | + let alpha_key = cf_string_from_static(c"kCGWindowAlpha"); |
| 1106 | + let on_screen_key = cf_string_from_static(c"kCGWindowIsOnscreen"); |
| 1107 | + let mut found = false; |
| 1108 | + |
| 1109 | + for i in 0..count { |
| 1110 | + let dict = CFArrayGetValueAtIndex(list, i); |
| 1111 | + if dict.is_null() { |
| 1112 | + continue; |
| 1113 | + } |
| 1114 | + let mut v: CFTypeRef = ptr::null(); |
| 1115 | + let mut wid: u32 = 0; |
| 1116 | + if CFDictionaryGetValueIfPresent(dict, wid_key as CFTypeRef, &mut v) { |
| 1117 | + CFNumberGetValue(v, kCFNumberSInt32Type, &mut wid as *mut _ as *mut _); |
| 1118 | + } |
| 1119 | + if wid != target_wid { |
| 1120 | + continue; |
| 1121 | + } |
| 1122 | + |
| 1123 | + let mut sharing: i32 = -1; |
| 1124 | + if CFDictionaryGetValueIfPresent(dict, sharing_key as CFTypeRef, &mut v) { |
| 1125 | + CFNumberGetValue(v, kCFNumberSInt32Type, &mut sharing as *mut _ as *mut _); |
| 1126 | + } |
| 1127 | + let mut layer: i32 = i32::MIN; |
| 1128 | + if CFDictionaryGetValueIfPresent(dict, layer_key as CFTypeRef, &mut v) { |
| 1129 | + CFNumberGetValue(v, kCFNumberSInt32Type, &mut layer as *mut _ as *mut _); |
| 1130 | + } |
| 1131 | + let mut alpha: f64 = -1.0; |
| 1132 | + if CFDictionaryGetValueIfPresent(dict, alpha_key as CFTypeRef, &mut v) { |
| 1133 | + CFNumberGetValue(v, 13 /* kCFNumberDoubleType */, &mut alpha as *mut _ as *mut _); |
| 1134 | + } |
| 1135 | + let on_screen_present = |
| 1136 | + CFDictionaryGetValueIfPresent(dict, on_screen_key as CFTypeRef, &mut v); |
| 1137 | + |
| 1138 | + debug!( |
| 1139 | + "[probe_cg_window_info] wid={target_wid} cg_sharing={sharing} layer={layer} alpha={alpha:.3} on_screen_present={on_screen_present}" |
| 1140 | + ); |
| 1141 | + found = true; |
| 1142 | + break; |
| 1143 | + } |
| 1144 | + |
| 1145 | + if !found { |
| 1146 | + debug!("[probe_cg_window_info] wid={target_wid} NOT FOUND in CGWindowList"); |
| 1147 | + } |
| 1148 | + CFRelease(list as CFTypeRef); |
| 1149 | + } |
| 1150 | +} |
| 1151 | + |
| 1090 | 1152 | fn discover_windows(cid: CGSConnectionID, own_pid: i32) -> Vec<u32> { |
| 1091 | 1153 | unsafe { |
| 1092 | 1154 | let list = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID); |
@@ -1213,11 +1275,47 @@ fn create_overlay( |
| 1213 | 1275 | return None; |
| 1214 | 1276 | } |
| 1215 | 1277 | |
| 1278 | + // Empty hit-test shape: an SLS window with an empty opaque_shape |
| 1279 | + // is click-through at the compositor level (no input region). |
| 1280 | + let empty = CGRect::new(0.0, 0.0, 0.0, 0.0); |
| 1281 | + let mut empty_region: CFTypeRef = ptr::null(); |
| 1282 | + if CGSNewRegionWithRect(&empty, &mut empty_region) != kCGErrorSuccess |
| 1283 | + || empty_region.is_null() |
| 1284 | + { |
| 1285 | + debug!("[create_overlay] CGSNewRegionWithRect (empty) failed for wid={target_wid}"); |
| 1286 | + CFRelease(region); |
| 1287 | + return None; |
| 1288 | + } |
| 1289 | + |
| 1290 | + // Create the overlay via SLSNewWindowWithOpaqueShapeAndContext |
| 1291 | + // and bake tag bit 1 (click-through) and tag bit 9 (screenshot |
| 1292 | + // exclusion) into the window at birth. Tahoe classifies windows |
| 1293 | + // for capture/picker based on tags observed at creation time; |
| 1294 | + // post-creation tag mutation lands too late and the picker keeps |
| 1295 | + // including the overlay. Mirrors the JankyBorders unmanaged |
| 1296 | + // create path (.refs/JankyBorders/src/misc/window.h:239). |
| 1297 | + // options 13|(1<<18): documentation-window | ignores-cycle. |
| 1298 | + let mut tags: u64 = (1u64 << 1) | (1u64 << 9); |
| 1216 | 1299 | let mut wid: u32 = 0; |
| 1217 | | - SLSNewWindow(cid, 2, ox as f32, oy as f32, region, &mut wid); |
| 1300 | + SLSNewWindowWithOpaqueShapeAndContext( |
| 1301 | + cid, |
| 1302 | + 2, |
| 1303 | + region, |
| 1304 | + empty_region, |
| 1305 | + 13 | (1 << 18), |
| 1306 | + &mut tags as *mut u64, |
| 1307 | + ox as f32, |
| 1308 | + oy as f32, |
| 1309 | + 64, |
| 1310 | + &mut wid, |
| 1311 | + ptr::null_mut(), |
| 1312 | + ); |
| 1218 | 1313 | CFRelease(region); |
| 1314 | + CFRelease(empty_region); |
| 1219 | 1315 | if wid == 0 { |
| 1220 | | - debug!("[create_overlay] SLSNewWindow returned 0 for target={target_wid} cid={cid}"); |
| 1316 | + debug!( |
| 1317 | + "[create_overlay] SLSNewWindowWithOpaqueShapeAndContext returned 0 for target={target_wid} cid={cid}" |
| 1318 | + ); |
| 1221 | 1319 | return None; |
| 1222 | 1320 | } |
| 1223 | 1321 | |
@@ -1226,6 +1324,27 @@ fn create_overlay( |
| 1226 | 1324 | color.0, color.1, color.2, color.3 |
| 1227 | 1325 | ); |
| 1228 | 1326 | |
| 1327 | + if let Some(metadata) = query_window_metadata(cid, wid) { |
| 1328 | + debug!( |
| 1329 | + "[create_overlay] post-create overlay wid={wid} tags={:#x} attributes={:#x} parent={}", |
| 1330 | + metadata.tags, metadata.attributes, metadata.parent_wid |
| 1331 | + ); |
| 1332 | + } else { |
| 1333 | + debug!("[create_overlay] post-create wid={wid} metadata query failed"); |
| 1334 | + } |
| 1335 | + |
| 1336 | + SLSSetWindowSharingState(cid, wid, 0); |
| 1337 | + let mut sharing_state: u32 = u32::MAX; |
| 1338 | + let rc = SLSGetWindowSharingState(cid, wid, &mut sharing_state); |
| 1339 | + debug!("[create_overlay] sharing_state wid={wid} get_rc={rc} sls_state={sharing_state}"); |
| 1340 | + |
| 1341 | + // Probe what CGWindowListCopyWindowInfo (which the screenshot |
| 1342 | + // picker / SCWindow use) reports for our overlay. If |
| 1343 | + // kCGWindowSharingState comes back != 0 here, then SLS-side |
| 1344 | + // sharing state is not propagated to the CG window list and |
| 1345 | + // we'll need a different exclusion mechanism. |
| 1346 | + probe_cg_window_info(wid); |
| 1347 | + |
| 1229 | 1348 | SLSSetWindowResolution(cid, wid, scale); |
| 1230 | 1349 | SLSSetWindowOpacity(cid, wid, false); |
| 1231 | 1350 | SLSSetWindowLevel(cid, wid, 0); |
@@ -1243,24 +1362,6 @@ fn create_overlay( |
| 1243 | 1362 | SLSFlushWindowContentRegion(cid, wid, ptr::null()); |
| 1244 | 1363 | CGContextRelease(ctx); |
| 1245 | 1364 | |
| 1246 | | - // Click-through. Setting an empty event/hit-test shape passes |
| 1247 | | - // mouse events through to the window beneath. We deliberately |
| 1248 | | - // avoid SLSSetWindowTags(kCGSIgnoreForEvents): even when set |
| 1249 | | - // before drawing on Tahoe, the tag-bit-1 flag breaks overlay |
| 1250 | | - // visibility during the rapid sync_overlay/recreate churn that |
| 1251 | | - // tiling produces. Empty event shape + zero event mask is the |
| 1252 | | - // only combination that gives both click-through AND persistent |
| 1253 | | - // borders on Tahoe. |
| 1254 | | - let empty = CGRect::new(0.0, 0.0, 0.0, 0.0); |
| 1255 | | - let mut empty_region: CFTypeRef = ptr::null(); |
| 1256 | | - if CGSNewRegionWithRect(&empty, &mut empty_region) == kCGErrorSuccess |
| 1257 | | - && !empty_region.is_null() |
| 1258 | | - { |
| 1259 | | - SLSSetWindowEventShape(cid, wid, empty_region); |
| 1260 | | - CFRelease(empty_region); |
| 1261 | | - } |
| 1262 | | - SLSSetWindowEventMask(cid, wid, 0); |
| 1263 | | - |
| 1264 | 1365 | Some((cid, wid, bounds, scale)) |
| 1265 | 1366 | } |
| 1266 | 1367 | } |