From 69e1a7fccb3d347b25f531388c524ebde8e6e269 Mon Sep 17 00:00:00 2001 From: TZGyn Date: Wed, 9 Sep 2026 11:51:13 +0800 Subject: [PATCH] Show overlays only after activation completes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The local probe confirmed activation is asynchronous on macOS 26: isActive flips on a later runloop tick, so overlays shown in the same tick as activate() belong to a still-inactive app and misses clicks. Delay overlay creation by a tick and log the activation state at show time. 💘 Generated with Crush Assisted-by: Crush:deepseek-v4-flash-0731 --- Sources/Kapture/CaptureController.swift | 33 ++++++++++++++----------- build-app.sh | 4 +-- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/Sources/Kapture/CaptureController.swift b/Sources/Kapture/CaptureController.swift index ae3d1eb..a7c2703 100644 --- a/Sources/Kapture/CaptureController.swift +++ b/Sources/Kapture/CaptureController.swift @@ -61,21 +61,26 @@ final class CaptureController: NSObject { } isSelecting = true previousApp = NSWorkspace.shared.frontmostApplication - // Force activation and log whether it stuck — inactive apps don't - // receive overlay clicks. - let activated = NSApp.activate(ignoringOtherApps: true) - CaptureController.log("begin: activate(ignoringOtherApps)=\(activated) active=\(NSApp.isActive)") - overlays = NSScreen.screens.map { screen in - OverlayWindow( - screen: screen, - onComplete: { [weak self] rect in self?.finishSelection(rect) }, - onCancel: { [weak self] in self?.cancelSelection() }, - onWindowPick: { [weak self] rect in self?.finishWindowPick(rect) } - ) + // Activation is ASYNC: isActive only flips on a later runloop tick + // (verified on macOS 26). Show overlays only once active, otherwise + // the first click can pass through to the windows below. + NSApp.activate(ignoringOtherApps: true) + CaptureController.log("begin: activate issued, active=\(NSApp.isActive)") + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self] in + guard let self, self.isSelecting else { return } + CaptureController.log("begin: active=\(NSApp.isActive) — showing overlays") + self.overlays = NSScreen.screens.map { screen in + OverlayWindow( + screen: screen, + onComplete: { [weak self] rect in self?.finishSelection(rect) }, + onCancel: { [weak self] in self?.cancelSelection() }, + onWindowPick: { [weak self] rect in self?.finishWindowPick(rect) } + ) + } + self.overlays.forEach { $0.makeKeyAndOrderFront(nil) } + self.overlayIDs = self.overlays.map { $0.windowNumber } + CaptureController.log("begin: \(self.overlays.count) overlays, ids=\(self.overlayIDs), active=\(NSApp.isActive)") } - overlays.forEach { $0.makeKeyAndOrderFront(nil) } - overlayIDs = overlays.map { $0.windowNumber } - CaptureController.log("begin: \(overlays.count) overlays, ids=\(overlayIDs), active=\(NSApp.isActive)") } /// A window was picked from hover — capture it with rounded corners. diff --git a/build-app.sh b/build-app.sh index 571ff51..db88268 100755 --- a/build-app.sh +++ b/build-app.sh @@ -108,9 +108,9 @@ cat > "$APP/Contents/Info.plist" <<'PLIST' CFBundleIconFile Icon CFBundleShortVersionString - 1.6.3 + 1.6.4 CFBundleVersion - 63 + 64 LSUIElement NSHighResolutionCapable