gardesk/ers / dc06373

Browse files

Update CLAUDE.md for NSWindow architecture and Tahoe constraints

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
dc0637395d96048ff13d1d15c5fce121bb3c49d2
Parents
63e4df1
Tree
f534eac

1 changed file

StatusFile+-
M CLAUDE.md 16 12
CLAUDE.mdmodified
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
44
 
55
 ## What this is
66
 
7
-ers is a macOS window border renderer for the tarmac window manager. It draws colored overlay borders around application windows using private SkyLight framework APIs. macOS Tahoe only.
7
+ers is a macOS window border renderer for the tarmac window manager. It draws colored overlay borders around application windows. Borders are NSWindows backed by a CAShapeLayer; window discovery and event subscription still go through private SkyLight (SLS) FFI. macOS Tahoe only, Apple Silicon.
88
 
99
 ## Build & Run
1010
 
@@ -17,32 +17,36 @@ cargo run -- -w 6.0 # custom border width (default: 4.0)
1717
 cargo run -- <wid>       # border a specific window ID
1818
 ```
1919
 
20
-No tests — verification is visual. Use `RUST_LOG=debug` for tracing output.
20
+Default log level is `info`. Use `RUST_LOG=ers=debug` for the full trace (focus changes, hide/unhide, sync, hotplug, etc.).
2121
 
2222
 ## Architecture
2323
 
24
-Three source files (~1300 lines total):
24
+Four source files:
2525
 
26
-- **`src/main.rs`** — `BorderMap` struct manages overlay lifecycle. Event loop batches window events with 150ms debounce, then processes creates/destroys/moves/resizes. Focus detection recolors borders (active=white, inactive=gray). Main thread runs CFRunLoop; events dispatch from a background thread via mpsc.
26
+- **`src/main.rs`** — `BorderMap` manages overlay lifecycle: discover, add_fresh, sync_overlay, hide/unhide, reconcile. Main thread runs a CFRunLoop with a CFRunLoopTimer; events dispatched from SLS event handlers go through an mpsc channel and get processed in a 16–120ms batch. update_focus polls the front window each tick.
2727
 
28
-- **`src/skylight.rs`** — FFI bindings for private macOS frameworks: SkyLight (CGS window creation, event registration), CoreGraphics (drawing), CoreFoundation (collections, RunLoop). All types `repr(C)`.
28
+- **`src/nswindow_overlay.rs`** — `OverlayWindow` wraps `NSWindow + CAShapeLayer`. The NSWindow has `sharingType = .none` (the only mechanism Tahoe's screenshot picker honors — see "screenshot exclusion" below). The CAShapeLayer draws a stroked rounded-rect border that matches the target window's bounds plus the configured `border_width`. Coordinate conversion CG→Cocoa uses `CGDisplayBounds(CGMainDisplayID())` for the primary screen height (NSScreen caches and returns stale data after monitor hotplug).
2929
 
30
-- **`src/events.rs`** — Event enum and SLSRegisterNotifyProc callbacks. Filters out the renderer's own windows to prevent feedback loops. Sends events over mpsc channel.
30
+- **`src/skylight.rs`** — FFI bindings: SLS (window discovery, bounds, ordering, events), CoreFoundation (CFArray, CFRunLoop, CFRunLoopTimer), CGDisplayRegisterReconfigurationCallback (hotplug detection).
3131
 
32
-- **`build.rs`** — Links SkyLight (private framework), CoreGraphics, CoreFoundation.
32
+- **`src/events.rs`** — SLS event registration. Filters out our own NSWindows by owner pid before forwarding via mpsc.
3333
 
3434
 ## Critical macOS Tahoe constraints
3535
 
3636
 These are hard-won discoveries from debugging undocumented APIs:
3737
 
38
-1. **SLSCopyManagedDisplaySpaces poisons SLSNewWindow** — calling it on ANY connection corrupts window creation on ALL connections. Use `CGWindowListCopyWindowInfo` instead.
38
+1. **Screenshot exclusion requires NSWindow + sharingType=.none**. Tahoe's `screencaptureui` enumerates windows via `_SLSCopyWindowsWithOptionsAndTagsAndSpaceOptions` + `_CGSGetWindowTags` (verified by `otool` against the binary), and the SLS-side `SLSSetWindowSharingState` / SLS tag bits do NOT propagate to that path for raw SLS-only windows. Verified empirically with `screencapture -l <wid>`: SLS overlays are captured normally; NSWindows with `.none` sharingType return "could not create image from window".
3939
 
40
-2. **Use the process main connection** — `SLSMainConnectionID()` for window creation, drawing, ordering, and event registration (current code already does this). Past notes mentioned a "fresh SLSNewConnection per border" requirement; that has not been needed since the current architecture stabilized.
40
+2. **`SLSCopyManagedDisplaySpaces` poisons `SLSNewWindow`** — calling it on ANY connection corrupts window creation on ALL connections, process-wide. Use `CGWindowListCopyWindowInfo` for window discovery instead.
4141
 
42
-3. **Create windows at final size** — the 1×1-then-reshape pattern breaks on Tahoe. Create at correct position/size immediately.
42
+3. **`NSScreen.screens` returns stale data after monitor hotplug** until something internal triggers a refresh. For the CG→Cocoa Y-flip we need the live primary-screen height, so use `CGDisplayBounds(CGMainDisplayID())` directly. Don't rely on `[NSScreen screens][0]`.
4343
 
44
-4. **Draw before setting tags** — CGContext from `SLWindowContextCreate` must be used to draw BEFORE setting window tags/shadow. Re-obtaining context later for redraws uses the border's own connection.
44
+4. **`orderWindow:relativeTo:` re-shows an off-screen NSWindow as a side effect.** In active-only mode, `sync_overlay` must NOT call `order_above` on hidden non-focused overlays just because their target moved (e.g. tarmac stack peek-out positions shift on every cycle) — otherwise every stacked window's overlay pops back onto the screen.
45
+
46
+5. **CAShapeLayer state can be reset by macOS during display sleep/wake**, leaving the layer at a default tiny frame at the layer origin even though the NSWindow's `frame` survives. `BorderMap::refresh_all_layers` is called once a second from the periodic reconcile (and on hotplug) to re-apply each layer's frame and path.
47
+
48
+6. **AX-driven moves don't reliably fire SLS WINDOW_MOVE notifications**, so during stack cycles a stored overlay can be at stale coordinates relative to its target. `update_focus` calls `sync_overlay` on both the old and new focused targets to pull live SLS bounds before un/hiding.
4549
 
4650
 ## Dependencies
4751
 
48
-Only `serde`/`serde_json` (JSON parsing of window info) and `tracing`/`tracing-subscriber` (logging). No external runtime dependencies beyond macOS frameworks.
52
+`serde`/`serde_json` (window-info parsing), `tracing`/`tracing-subscriber` (logging), and the `objc2` family for AppKit/QuartzCore/CoreGraphics/CoreFoundation bindings. No runtime dependencies beyond macOS frameworks.