gardesk/ers / 40e1370

Browse files

Exclude overlays from screen capture via donut bounds path

Replaces the overlay's bounds region with a CGPath containing an outer
rect and an inner cutout. SLS evaluates the path with even-odd winding,
so the interior is treated as outside the window: screenshots taken
inside the bordered area capture the underlying app window instead of
the overlay. Pairs with SharingState/ClientPerceivedType advisories for
capture clients that honor them.
Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
40e13701b51ec1a6d45cfaf3aba3eb2f935fa825
Parents
f34604f
Tree
fdbee2a

2 changed files

StatusFile+-
M src/main.rs 30 0
M src/skylight.rs 28 0
src/main.rsmodified
@@ -1251,6 +1251,36 @@ fn create_overlay(
12511251
         }
12521252
         SLSSetWindowEventMask(cid, wid, 0);
12531253
 
1254
+        // Capture-exclusion advisories. Tahoe's screen-capture picker
1255
+        // ignores both for SLS overlays but they're harmless and may help
1256
+        // capture clients that do honor them.
1257
+        SLSSetWindowSharingState(cid, wid, 0);
1258
+        SLSSetWindowClientPerceivedType(cid, wid, 2);
1259
+
1260
+        // Replace the window's bounds region with a donut path: outer
1261
+        // rect plus an inner subpath for the interior. SLS interprets
1262
+        // the path with even-odd winding so the interior is *not* part
1263
+        // of the window — the screenshot picker hit-tests through to
1264
+        // the underlying app window. Done via SLSTransaction because
1265
+        // SLSTransactionSetWindowBoundsPath is the only path-based bounds
1266
+        // API exported on Tahoe; CGS region union APIs aren't linkable.
1267
+        let interior = CGRect::new(bw, bw, (ow - 2.0 * bw).max(0.0), (oh - 2.0 * bw).max(0.0));
1268
+        if interior.size.width > 0.0 && interior.size.height > 0.0 {
1269
+            let path = CGPathCreateMutable();
1270
+            if !path.is_null() {
1271
+                let outer = CGRect::new(0.0, 0.0, ow, oh);
1272
+                CGPathAddRect(path, ptr::null(), outer);
1273
+                CGPathAddRect(path, ptr::null(), interior);
1274
+                let txn = SLSTransactionCreate(cid);
1275
+                if !txn.is_null() {
1276
+                    SLSTransactionSetWindowBoundsPath(txn, cid, wid, path as CGPathRef);
1277
+                    SLSTransactionCommit(txn, 1);
1278
+                    CFRelease(txn);
1279
+                }
1280
+                CGPathRelease(path as CGPathRef);
1281
+            }
1282
+        }
1283
+
12541284
         Some((cid, wid, bounds, scale))
12551285
     }
12561286
 }
src/skylight.rsmodified
@@ -208,6 +208,22 @@ unsafe extern "C" {
208208
     /// through to the window beneath. Equivalent to NSWindow's
209209
     /// `setIgnoresMouseEvents(true)` at the SLS layer.
210210
     pub fn SLSSetWindowEventShape(cid: CGSConnectionID, wid: u32, shape: CFTypeRef) -> CGError;
211
+    /// NSWindowSharingType for the SLS window: 0 = None (excluded from
212
+    /// screen capture / picker / recording), 1 = ReadOnly, 2 = ReadWrite.
213
+    /// Tahoe's exported symbol is `SLSSetWindowSharingState` (not `Type`).
214
+    /// Setting 0 makes the overlay invisible to ScreenCaptureKit, the
215
+    /// cmd+shift+4 + space picker, etc., so user screenshots fall through
216
+    /// to the underlying app window.
217
+    pub fn SLSSetWindowSharingState(cid: CGSConnectionID, wid: u32, state: u32) -> CGError;
218
+    /// Marks the SLS window as a non-standard "perceived type" so that
219
+    /// screen-capture clients can choose to skip it. Yabai/skhd use this
220
+    /// to keep borders out of screenshots without poisoning SLSNewWindow
221
+    /// (unlike tag bits). Type 2 = "popup", 13/14 = system overlays.
222
+    pub fn SLSSetWindowClientPerceivedType(
223
+        cid: CGSConnectionID,
224
+        wid: u32,
225
+        kind: u32,
226
+    ) -> CGError;
211227
     pub fn SLSSetWindowAlpha(cid: CGSConnectionID, wid: u32, alpha: f32) -> CGError;
212228
     pub fn SLSSetWindowBackgroundBlurRadius(cid: CGSConnectionID, wid: u32, radius: u32)
213229
     -> CGError;
@@ -255,6 +271,18 @@ unsafe extern "C" {
255271
         y_offset: f32,
256272
         shape: CFTypeRef,
257273
     ) -> CGError;
274
+    /// Sets a window's actual bounds region (what the window-server
275
+    /// considers part of the window for hit-testing, capture, and
276
+    /// rendering) using a CGPath instead of a CGS region. Lets us
277
+    /// install a donut-shaped region without needing a multi-rect
278
+    /// region constructor — none of which exist in linkable form on
279
+    /// Tahoe except `CGSNewRegionWithRect` (single rect).
280
+    pub fn SLSTransactionSetWindowBoundsPath(
281
+        transaction: CFTypeRef,
282
+        cid: CGSConnectionID,
283
+        wid: u32,
284
+        path: CGPathRef,
285
+    ) -> CGError;
258286
 
259287
     // Flicker suppression
260288
     pub fn SLSDisableUpdate(cid: CGSConnectionID) -> CGError;