From 5284fbbe6af1d493fdc3f4f7b28c47fa03573e6b Mon Sep 17 00:00:00 2001 From: Nathan Nguyen <146415969+NathanDrake2406@users.noreply.github.com> Date: Tue, 21 Apr 2026 16:59:23 +1000 Subject: [PATCH] Cap pasteboard-hold to 200ms when paste verification has no AX target When withTemporaryPasteboardString dispatches a paste into an app that exposes no accessible text element, waitForFocusedTextVerification falls through to an unconditional usleep(timeoutMicros). All three clipboard entry points pass 5_000_000, so the session semaphore is held for 5 seconds on Electron apps, web-based editors, many terminals, and games. A second dictation triggered during that window blocks for up to 5s waiting on pasteboardSessionSemaphore. timeoutMicros is sized for AX verification polling, which cannot run when no snapshot exists. The degenerate branch reused the verification budget as a pasteboard-hold budget, which is the wrong invariant: verification needs seconds of headroom for polling, while the pasteboard hold only needs enough time for the target to consume Cmd+V before restore. Introduce pasteboardReadWindowMicros (200_000) and cap the nil-snapshot wait to min(timeoutMicros, pasteboardReadWindowMicros). The verified path and all four PasteVerificationResult values are unchanged. A unit test would either mirror the patch constant or mock captureFocusedTextSnapshot, both of which restate the implementation. Verified manually by pasting into an Electron target and immediately retriggering dictation; the second trigger no longer blocks. --- Sources/Fluid/Services/TypingService.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/Fluid/Services/TypingService.swift b/Sources/Fluid/Services/TypingService.swift index b0976577..7f95dc5a 100644 --- a/Sources/Fluid/Services/TypingService.swift +++ b/Sources/Fluid/Services/TypingService.swift @@ -53,6 +53,7 @@ final class TypingService { private static let focusSnapshotQueue = DispatchQueue(label: "TypingService.FocusSnapshot") private static let pasteboardSessionSemaphore = DispatchSemaphore(value: 1) private static let pasteboardRestoreQueue = DispatchQueue(label: "TypingService.PasteboardRestore", qos: .utility) + private static let pasteboardReadWindowMicros: useconds_t = 200_000 private static var focusSnapshot: FocusSnapshot? private var textInsertionMode: SettingsStore.TextInsertionMode { @@ -940,7 +941,9 @@ final class TypingService { timeoutMicros: useconds_t ) -> PasteVerificationResult { guard let snapshot else { - usleep(timeoutMicros) + // timeoutMicros is sized for AX verification; with no snapshot, only the target's pasteboard-read window matters. + let fallbackWaitMicros = min(timeoutMicros, Self.pasteboardReadWindowMicros) + usleep(fallbackWaitMicros) return .unavailable }