tarmac multiplexes workspaces by hiding non-current windows; only
windows on the active workspace get enumerated by discover_windows
at startup. When the user switched to a workspace whose windows
weren't tracked, focus would move to a wid with no overlay and the
border would disappear (active-only mode hides the previous overlay
but has nothing to unhide for the new focus).
Trace from /tmp/ers-debug.log:
[focus] 130 -> 139 (tracked targets: [796, 130, 681, 795, 778])
[hide] target=130
(no unhide — 139 not in tracked set, border vanishes)
Fix: in update_focus, if the new front isn't tracked, add_fresh it
on the spot. Same treatment for Event::Unhide so workspace-switch
unhide notifications can pick up newly-visible windows.
Also: CFRunLoopTimerCreate flags arg was u32, should be u64
(CFOptionFlags = unsigned long on 64-bit Darwin) — corrupted ABI for
subsequent args and the timer was firing unreliably as a result.
Overlays were leaking onto every macOS space simultaneously due to
CanJoinAllSpaces, which presents as 'border stuck on the previous
workspace' when the user navigates between tarmac workspaces. Removing
that flag confines each overlay to the space where it was created.
Add diagnostic logging in update_focus / hide / unhide so a workspace-
switch trace can be read with RUST_LOG=ers=debug.
CAShapeLayer strokes are centered on the path, so to render a visible
border_width-thick ring inside the layer bounds we want
line_width = border_width and path inset = border_width/2 (not the
doubled values I had — those produced 2x-thick borders user reported).
BorderMap now stores nswindow_overlay::OverlayWindow per target. The
event-processing loop moves from a background thread to a CFRunLoopTimer
on the main thread, since AppKit calls (NSWindow, CALayer) must originate
from the main thread.
Verified empirically: screencapture -l <ers_wid> returns 'could not
create image from window' for all 5 overlays — Tahoe's screencaptureui
honors NSWindow.sharingType where it ignores SLS sharing-state for
SLS-only windows. Full-screen screencapture cleanly captures app
contents without the borders blocking them.
Empirically disproved approaches for excluding ers overlays from
Tahoe's cmd+shift+4 + space picker. All of these apply *correctly*
to the overlay window — they just don't influence the picker.
Verified via post-create probes:
- SLSNewWindowWithOpaqueShapeAndContext baking tag bits (1<<1)|(1<<9)
at creation succeeds; readback shows tags=0x202.
- SLSSetWindowSharingState(0) succeeds; SLSGetWindowSharingState
reads back state=0.
- CGWindowListCopyWindowInfo for the overlay reports
kCGWindowSharingState=0 (the SLS-side state propagates through to
the CG window list, which SCWindow filters on).
- kCGWindowIsOnscreen is absent from the dictionary entry (CG
doesn't know the on-screen state of an SLS-only window without
an NSWindow wrapper).
Despite all of the above, the screenshot picker still hit-tests onto
the overlay and captures the border. This means Tahoe's picker uses
a query path that bypasses standard CGWindowSharingState and tag-bit
filtering for SLS-only windows.
Approaches still untested but likely dead-ends without docs:
SLSSetWindowType, SLSSetWindowFiltering, SLSSetWindowSubLevel.
Probe helper probe_cg_window_info added for ongoing investigation.
Branch is for research; main remains the working EventShape build.
Setting any SLS window tags (bit 1 or bits 1|9) post-creation breaks
border visibility on Tahoe in our recreate-overlay-on-move lifecycle:
borders flicker on and disappear during the rapid sync_overlay churn
that tiling produces. JankyBorders works with tags 1|9 because it
creates each border window once and moves it via
SLSTransactionMoveWindowWithGroup; ers releases and recreates the SLS
window on every geometry change, and Tahoe's compositor handles the
tagged-then-released window very differently.
Restoring SLSSetWindowEventShape(empty) + SLSSetWindowEventMask(0) for
click-through; screenshots will again include the overlay until the
lifecycle is refactored to match JankyBorders'.
Sets tag bits (1<<1) | (1<<9) on every overlay window: bit 1 is
kCGSIgnoreForEvents (click-through), bit 9 hides the window from
ScreenCaptureKit, the screenshot picker, and screen recordings.
Mirrors JankyBorders' approach (.refs/JankyBorders/src/border.c:290
and .refs/JankyBorders/src/misc/window.h:266).
Tags are now set BEFORE SLWindowContextCreate / drawing, which is the
critical difference from the prior tag-based attempt that poisoned
SLSNewWindow on stack-cycle recreates. Removes the now-redundant
SLSSetWindowEventShape/Mask, SLSSetWindowSharingState, and
SLSSetWindowClientPerceivedType bindings.
The guessed FFI signature for SLSTransactionSetWindowBoundsPath does
not match Tahoe's actual ABI — calling it crashes ers with SIGSEGV
during overlay creation, which leaves tarmac with no border renderer.
Removing the donut path; capture-exclusion now relies solely on the
SharingState/ClientPerceivedType advisories. Screenshots will once
again include the overlay border, but borders themselves come back.
AX-driven window moves during stack cycles often don't emit SLS
WINDOW_MOVE notifications, so the stored overlay frame can be stale
when update_focus runs. Calling sync_overlay against the live
SLSGetWindowBounds for both old and new focused windows before the
hide/unhide pair keeps the active border on the right window during
stack-next/prev navigation.
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.
The kCGSIgnoreForEvents tag bit poisons the shared SLS connection's
SLSNewWindow path during stack-cycle recreates, dropping border
overlays on the inactive stack windows. An empty event shape achieves
the same click-through without mutating tags.