@@ -1,52 +0,0 @@ |
| 1 | -# CLAUDE.md | | |
| 2 | - | | |
| 3 | -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. | | |
| 4 | - | | |
| 5 | -## What this is | | |
| 6 | - | | |
| 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. | | |
| 8 | - | | |
| 9 | -## Build & Run | | |
| 10 | - | | |
| 11 | -```bash | | |
| 12 | -cargo build # debug build | | |
| 13 | -cargo build --release # release build | | |
| 14 | -cargo run # run (borders all windows) | | |
| 15 | -cargo run -- --list # list on-screen windows with IDs and bounds | | |
| 16 | -cargo run -- -w 6.0 # custom border width (default: 4.0) | | |
| 17 | -cargo run -- <wid> # border a specific window ID | | |
| 18 | -``` | | |
| 19 | - | | |
| 20 | -Default log level is `info`. Use `RUST_LOG=ers=debug` for the full trace (focus changes, hide/unhide, sync, hotplug, etc.). | | |
| 21 | - | | |
| 22 | -## Architecture | | |
| 23 | - | | |
| 24 | -Four source files: | | |
| 25 | - | | |
| 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. | | |
| 27 | - | | |
| 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). | | |
| 29 | - | | |
| 30 | -- **`src/skylight.rs`** — FFI bindings: SLS (window discovery, bounds, ordering, events), CoreFoundation (CFArray, CFRunLoop, CFRunLoopTimer), CGDisplayRegisterReconfigurationCallback (hotplug detection). | | |
| 31 | - | | |
| 32 | -- **`src/events.rs`** — SLS event registration. Filters out our own NSWindows by owner pid before forwarding via mpsc. | | |
| 33 | - | | |
| 34 | -## Critical macOS Tahoe constraints | | |
| 35 | - | | |
| 36 | -These are hard-won discoveries from debugging undocumented APIs: | | |
| 37 | - | | |
| 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". | | |
| 39 | - | | |
| 40 | -2. **`SLSCopyManagedDisplaySpaces` poisons `SLSNewWindow`** — calling it on ANY connection corrupts window creation on ALL connections, process-wide. Use `CGWindowListCopyWindowInfo` for window discovery instead. | | |
| 41 | - | | |
| 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]`. | | |
| 43 | - | | |
| 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. | | |
| 49 | - | | |
| 50 | -## Dependencies | | |
| 51 | - | | |
| 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. | | |