fix: mic permission check, Fn keycode filter, longer clipboard restore, error icon, TCC hint

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 09:58:10 +02:00
co-authored by Claude Sonnet 5.5
parent ebc113ee07
commit 56385c7ad8
4 changed files with 22 additions and 3 deletions
+18 -2
View File
@@ -25,10 +25,17 @@ final class AppModel: ObservableObject {
cleaner: PassthroughCleaner(), cleaner: PassthroughCleaner(),
inserter: TextInserter(pasteboard: SystemPasteboard(), paster: CGEventPaster()) 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 } controller.onError = { [weak self] in self?.lastError = $0 }
hotkey.onEvent = { [weak self] event in hotkey.onEvent = { [weak self] event in
guard let self, self.modelReady else { return } 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 self.controller.handle(event) }
} }
Task { await start() } Task { await start() }
@@ -46,7 +53,9 @@ final class AppModel: ObservableObject {
var iconName: String { var iconName: String {
switch state { 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 .recording: return "mic.fill"
case .transcribing, .inserting: return "waveform" case .transcribing, .inserting: return "waveform"
} }
@@ -75,6 +84,13 @@ final class AppModel: ObservableObject {
_ = AXIsProcessTrustedWithOptions(["AXTrustedCheckOptionPrompt": true] as CFDictionary) _ = AXIsProcessTrustedWithOptions(["AXTrustedCheckOptionPrompt": true] as CFDictionary)
} }
retryHotkey() retryHotkey()
transcriber.onProgress = { [weak self] fraction in
Task { @MainActor in
self?.modelStatus = fraction < 1
? "Model downloaden… \(Int(fraction * 100))%"
: "Model laden…"
}
}
do { do {
try await transcriber.load() try await transcriber.load()
modelReady = true modelReady = true
+2
View File
@@ -56,6 +56,8 @@ public final class HotkeyMonitor {
case .tapDisabledByTimeout, .tapDisabledByUserInput: case .tapDisabledByTimeout, .tapDisabledByUserInput:
if let tap { CGEvent.tapEnable(tap: tap, enable: true) } if let tap { CGEvent.tapEnable(tap: tap, enable: true) }
case .flagsChanged: 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) let fnDown = event.flags.contains(.maskSecondaryFn)
if let hotkeyEvent = tracker.process(fnDown: fnDown) { if let hotkeyEvent = tracker.process(fnDown: fnDown) {
onEvent?(hotkeyEvent) onEvent?(hotkeyEvent)
+1 -1
View File
@@ -20,7 +20,7 @@ public struct TextInserter: TextInserting {
public init( public init(
pasteboard: PasteboardProviding, pasteboard: PasteboardProviding,
paster: KeystrokePosting, paster: KeystrokePosting,
restoreDelay: Duration = .milliseconds(150) restoreDelay: Duration = .milliseconds(400)
) { ) {
self.pasteboard = pasteboard self.pasteboard = pasteboard
self.paster = paster self.paster = paster
+1
View File
@@ -35,3 +35,4 @@ PLIST
codesign --force --sign - --identifier local.sst.app "$APP" codesign --force --sign - --identifier local.sst.app "$APP"
echo "Gebouwd: $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)."