From 00edaaa07dd726ba1b69f7f97a01c65c713819cd Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Sat, 8 Aug 2026 22:20:59 -0700 Subject: [PATCH] Hide the cursor whenever focus is armed, and quit focus if it returns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hide sat behind `#if !MAC_APP_STORE`, so the sandboxed build never hid the cursor itself and leaned entirely on the TrackDrum helper, which only hides once capture starts. Focus armed without the helper — or with a capture that failed to find a pad — left keys and the trackpad being read out from under a pointer the user could still see moving. Arming hides in every mode now, after the mode branch, since local FX idle shows the cursor on its way in and would otherwise undo it. A hidden cursor is the proof this app owns the pointer, so a visible one during focus is not a cosmetic slip, it means the focus is a lie. A watchdog ends the session the same way Escape does rather than re-hiding and papering over whatever showed it. It watches our own hide, which is the case that arises: several focused paths call showSystemCursorIfNeeded mid-session. A show from another process is not observable — the query for it was removed from macOS with no public replacement, and reaching for the private one would cost the sandbox. The helper knows its own hide and can report it over the bridge if that turns out to be the path that bites. --- .../Sources/MenuBand/AppDelegate.swift | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/slab/menuband/Sources/MenuBand/AppDelegate.swift b/slab/menuband/Sources/MenuBand/AppDelegate.swift index c5ed00f114..89e8c47cce 100644 --- a/slab/menuband/Sources/MenuBand/AppDelegate.swift +++ b/slab/menuband/Sources/MenuBand/AppDelegate.swift @@ -581,6 +581,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate { /// hides leak across app sessions, so this guards a 1-to-1 /// pairing. private var pitchBendSystemCursorHidden = false + /// Polls the cursor while focus is armed — see `startFocusCursorWatchdog`. + private var focusCursorWatchdog: Timer? + private var focusCursorGraceUntil: CFTimeInterval = 0 /// Screen position the cursor occupied when the bend lock engaged. The /// display normally anchors beneath Menu Band; this remains its fallback /// if the status item's window is temporarily unavailable. @@ -2046,11 +2049,18 @@ final class AppDelegate: NSObject, NSApplicationDelegate { #else prepareFocusedLocalFXIdle() #endif + // Focus owns the pointer in every mode, and the hidden cursor is what + // says so. This lands after the mode branch because local FX idles + // with an ordinary pointer when unfocused and shows it on the way in. + hideSystemCursorIfNeeded() + startFocusCursorWatchdog() updateIcon() updatePianoWaveformWindow() } private func finishLocalCapture(reason: LocalKeyCapture.EndReason) { + stopFocusCursorWatchdog() + showSystemCursorIfNeeded() debugLog("local capture ended: \(reason)") let shouldRestoreFocus = keyboardPerformanceFocusActive && reason == .cancelled keyboardPerformanceFocusActive = false @@ -5949,6 +5959,43 @@ final class AppDelegate: NSObject, NSApplicationDelegate { pitchBendSystemCursorHidden = false } + /// The hidden cursor is the proof Menu Band owns the pointer. If it comes + /// back while focus is armed the focus is a lie: keys and the trackpad are + /// still being read out from under a pointer the user can see moving. + /// Re-hiding would paper over whatever showed it, so exit instead and let + /// the state not persist. + /// + /// This watches our own hide, which is the case that actually arises — + /// several focused paths (local FX idle, ending a bend session) call + /// `showSystemCursorIfNeeded` mid-session. A show from outside this + /// process cannot be seen: `CGCursorIsVisible` is unavailable now and has + /// no public replacement, so there is nothing to poll. The helper's own + /// hide is balanced against its capture flag on its side. + private func startFocusCursorWatchdog() { + stopFocusCursorWatchdog() + // Arming hides the cursor across several call sites; let them settle + // before the watchdog starts believing what it reads. + focusCursorGraceUntil = CACurrentMediaTime() + 0.6 + let timer = Timer(timeInterval: 0.1, repeats: true) { [weak self] _ in + guard let self else { return } + guard self.localCapture.isArmed else { + self.stopFocusCursorWatchdog() + return + } + guard CACurrentMediaTime() >= self.focusCursorGraceUntil, + !self.pitchBendSystemCursorHidden else { return } + debugLog("focus cursor watchdog: cursor visible while armed — exiting focus") + self.exitPerformanceFocusFromEscape() + } + RunLoop.main.add(timer, forMode: .common) + focusCursorWatchdog = timer + } + + private func stopFocusCursorWatchdog() { + focusCursorWatchdog?.invalidate() + focusCursorWatchdog = nil + } + private func ensurePitchBendOverlay() -> PitchBendCursorOverlayWindow { if let existing = pitchBendOverlay { return existing } let overlay = PitchBendCursorOverlayWindow() -- 2.51.2