Files

145 lines
4.9 KiB
Swift

import AppKit
@MainActor
final class AppDelegate: NSObject, NSApplicationDelegate {
private let prefs = Prefs.shared
private let controller = DictationController.shared
private let hud = HUDController()
private let settings = SettingsWindowController()
private var status: StatusItemController!
private var hotkey: HotkeyMonitor!
private var permissionTimer: Timer?
func applicationDidFinishLaunching(_ notification: Notification) {
NSApp.setActivationPolicy(.accessory)
Log.info("launched v\(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "dev"), hotkey=\(prefs.hotkey.label), mode=\(prefs.triggerMode.rawValue)")
controller.attach(hud: hud)
NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.willSleepNotification, object: nil, queue: .main) { [weak self] _ in
MainActor.assumeIsolated { self?.controller.cancel() }
}
status = StatusItemController()
status.onToggle = { [weak self] in self?.controller.toggle() }
status.onCancel = { [weak self] in self?.controller.cancel() }
status.onSettings = { [weak self] in self?.settings.show() }
controller.onStateChange = { [weak self] state in
self?.status.update(state: state)
}
controller.onError = { [weak self] message in
self?.presentError(message)
}
hotkey = HotkeyMonitor(spec: prefs.hotkey)
hotkey.handler = { [weak self] event in
self?.handle(event)
}
startHotkeyWithRetry()
NotificationCenter.default.addObserver(
forName: .tinysttHotkeyChanged,
object: nil,
queue: .main
) { [weak self] _ in
MainActor.assumeIsolated {
guard let self else { return }
self.controller.cancel()
self.hotkey.update(spec: self.prefs.hotkey)
self.startHotkeyWithRetry()
self.status.update(state: self.controller.state)
}
}
NotificationCenter.default.addObserver(forName: .init("tinystt.recorder"), object: nil, queue: .main) { [weak self] notification in
MainActor.assumeIsolated {
self?.hotkey.suspended = notification.object as? Bool ?? false
self?.controller.cancel()
}
}
Task {
_ = await Permissions.requestMicrophone()
await ModelManager.shared.refresh()
if ModelManager.shared.state == .needsDownload {
await ModelManager.shared.download()
}
SpeechEngine.warmUp()
}
if !UserDefaults.standard.bool(forKey: "didShowOnboarding") || CommandLine.arguments.contains("--settings") {
UserDefaults.standard.set(true, forKey: "didShowOnboarding")
settings.show()
}
if !Permissions.accessibility {
Permissions.promptAccessibility()
}
}
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
settings.show()
return true
}
func applicationWillTerminate(_ notification: Notification) {
controller.cancel()
SpeechEngine.resetWarm()
hotkey?.stop()
permissionTimer?.invalidate()
}
// MARK: - 热键
private func startHotkeyWithRetry() {
if hotkey.start() {
Log.info("hotkey tap running")
permissionTimer?.invalidate()
permissionTimer = nil
return
}
Log.error("hotkey tap failed (no accessibility permission?)")
// 没有辅助功能权限时,定期重试(授权后无需重启即可生效)
guard permissionTimer == nil else { return }
permissionTimer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { [weak self] _ in
MainActor.assumeIsolated {
guard let self else { return }
if self.hotkey.start() {
self.permissionTimer?.invalidate()
self.permissionTimer = nil
}
}
}
}
private func handle(_ event: HotkeyMonitor.Event) {
switch event {
case .pressed:
Log.info("hotkey pressed, state=\(controller.state)")
if prefs.triggerMode == .toggle {
controller.toggle()
} else {
controller.start()
}
case .released:
if prefs.triggerMode == .hold {
controller.stop()
}
case .cancel:
controller.cancel()
}
}
private func presentError(_ message: String) {
NSApp.activate(ignoringOtherApps: true)
let alert = NSAlert()
alert.messageText = "TinySTT"
alert.informativeText = message
alert.alertStyle = .warning
alert.addButton(withTitle: "知道了")
alert.runModal()
}
}