From 56385c7ad88d9bdd863ad8db88d3e05bc720fa82 Mon Sep 17 00:00:00 2001 From: Theo Weijmer Date: Thu, 1 Oct 2026 09:58:10 +0200 Subject: [PATCH] fix: mic permission check, Fn keycode filter, longer clipboard restore, error icon, TCC hint Co-Authored-By: Claude Sonnet 5.5 --- Sources/Sst/AppModel.swift | 20 ++++++++++++++++++-- Sources/SstCore/HotkeyMonitor.swift | 2 ++ Sources/SstCore/TextInserter.swift | 2 +- scripts/make-app.sh | 1 + 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Sources/Sst/AppModel.swift b/Sources/Sst/AppModel.swift index 4502627..64a56b7 100644 --- a/Sources/Sst/AppModel.swift +++ b/Sources/Sst/AppModel.swift @@ -25,10 +25,17 @@ final class AppModel: ObservableObject { cleaner: PassthroughCleaner(), inserter: TextInserter(pasteboard: SystemPasteboard(), paster: CGEventPaster()) ) - controller.onStateChange = { [weak self] in self?.state = $0 } + controller.onStateChange = { [weak self] in + self?.state = $0 + if $0 == .recording { self?.lastError = nil } + } controller.onError = { [weak self] in self?.lastError = $0 } hotkey.onEvent = { [weak self] event in guard let self, self.modelReady else { return } + if event == .pressed, AVCaptureDevice.authorizationStatus(for: .audio) != .authorized { + self.lastError = "Microfoon-toegang ontbreekt" + return + } Task { await self.controller.handle(event) } } Task { await start() } @@ -46,7 +53,9 @@ final class AppModel: ObservableObject { var iconName: String { switch state { - case .idle: return modelReady ? "mic" : "mic.slash" + case .idle: + if lastError != nil { return "exclamationmark.triangle" } + return modelReady ? "mic" : "mic.slash" case .recording: return "mic.fill" case .transcribing, .inserting: return "waveform" } @@ -75,6 +84,13 @@ final class AppModel: ObservableObject { _ = AXIsProcessTrustedWithOptions(["AXTrustedCheckOptionPrompt": true] as CFDictionary) } retryHotkey() + transcriber.onProgress = { [weak self] fraction in + Task { @MainActor in + self?.modelStatus = fraction < 1 + ? "Model downloaden… \(Int(fraction * 100))%" + : "Model laden…" + } + } do { try await transcriber.load() modelReady = true diff --git a/Sources/SstCore/HotkeyMonitor.swift b/Sources/SstCore/HotkeyMonitor.swift index 3a744dc..07d4f22 100644 --- a/Sources/SstCore/HotkeyMonitor.swift +++ b/Sources/SstCore/HotkeyMonitor.swift @@ -56,6 +56,8 @@ public final class HotkeyMonitor { case .tapDisabledByTimeout, .tapDisabledByUserInput: if let tap { CGEvent.tapEnable(tap: tap, enable: true) } case .flagsChanged: + // .maskSecondaryFn is also set while arrows/F-keys are held; only react to the Fn key itself. + guard event.getIntegerValueField(.keyboardEventKeycode) == 63 else { return } let fnDown = event.flags.contains(.maskSecondaryFn) if let hotkeyEvent = tracker.process(fnDown: fnDown) { onEvent?(hotkeyEvent) diff --git a/Sources/SstCore/TextInserter.swift b/Sources/SstCore/TextInserter.swift index 52acdee..f852bc8 100644 --- a/Sources/SstCore/TextInserter.swift +++ b/Sources/SstCore/TextInserter.swift @@ -20,7 +20,7 @@ public struct TextInserter: TextInserting { public init( pasteboard: PasteboardProviding, paster: KeystrokePosting, - restoreDelay: Duration = .milliseconds(150) + restoreDelay: Duration = .milliseconds(400) ) { self.pasteboard = pasteboard self.paster = paster diff --git a/scripts/make-app.sh b/scripts/make-app.sh index c7661a2..55b449a 100755 --- a/scripts/make-app.sh +++ b/scripts/make-app.sh @@ -35,3 +35,4 @@ PLIST codesign --force --sign - --identifier local.sst.app "$APP" echo "Gebouwd: $APP" +echo "Na een herbouw: tccutil reset Accessibility local.sst.app; tccutil reset ListenEvent local.sst.app; tccutil reset Microphone local.sst.app (en geef de rechten opnieuw)."