fix: 完善权限引导 — 辅助功能+输入监控+麦克风+语音识别,统一文案

This commit is contained in:
JOJO 2026-04-25 11:37:40 +08:00
parent ad72907e89
commit e1cfb27e16

View File

@ -1,4 +1,6 @@
import Cocoa import Cocoa
import AVFoundation
import Speech
/// Fn Control AI /// Fn Control AI
final class AppDelegate: NSObject, NSApplicationDelegate { final class AppDelegate: NSObject, NSApplicationDelegate {
@ -15,57 +17,118 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) { func applicationDidFinishLaunching(_ notification: Notification) {
NSApp.setActivationPolicy(.accessory) NSApp.setActivationPolicy(.accessory)
beginPermissionFlow()
//
if !CGPreflightListenEventAccess() {
requestAccessibilityPermission()
} else {
startKeyMonitor()
}
print("[App] Ready") print("[App] Ready")
} }
// MARK: - Permissions // MARK: - Permissions
private func requestAccessibilityPermission() { private func beginPermissionFlow() {
NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility")!) // 1
guard CGPreflightListenEventAccess() else {
openPrefsAndWait(panel: "Privacy_Accessibility", label: "辅助功能") { [weak self] in
self?.beginPermissionFlow()
}
return
}
// 2
guard canCreateHIDEventTap() else {
openPrefsAndWait(panel: "Privacy_InputMonitoring", label: "输入监控") { [weak self] in
self?.beginPermissionFlow()
}
return
}
//
startKeyMonitor()
showReadyAlert()
}
///
private func openPrefsAndWait(panel: String, label: String, onGranted: @escaping () -> Void) {
NSWorkspace.shared.open(
URL(string: "x-apple.systempreferences:com.apple.preference.security?\(panel)")!
)
let alert = NSAlert() let alert = NSAlert()
alert.messageText = "需要辅助功能权限" alert.messageText = "需要\(label)权限"
alert.informativeText = """ alert.informativeText = "点击 + 号,选择 VoiceInput 应用,点击打开。"
Voice Input Fn Control
1.
2. VoiceInput
3.
2
"""
alert.alertStyle = .informational alert.alertStyle = .informational
alert.addButton(withTitle: "继续") alert.addButton(withTitle: "继续")
alert.addButton(withTitle: "退出") alert.addButton(withTitle: "退出")
if alert.runModal() == .alertFirstButtonReturn { guard alert.runModal() == .alertFirstButtonReturn else {
Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { [weak self] timer in
if CGPreflightListenEventAccess() {
timer.invalidate()
DispatchQueue.main.async {
self?.startKeyMonitor()
let successAlert = NSAlert()
successAlert.messageText = "权限已授权"
successAlert.informativeText = "辅助功能权限已开启Voice Input 正在运行。"
successAlert.alertStyle = .informational
successAlert.runModal()
}
}
}
} else {
NSApp.terminate(nil) NSApp.terminate(nil)
return
}
//
let check: () -> Bool = {
if panel == "Privacy_Accessibility" {
return CGPreflightListenEventAccess()
} else {
return self.canCreateHIDEventTap()
}
}
Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { timer in
guard check() else { return }
timer.invalidate()
DispatchQueue.main.async {
print("[Permission] ✅ \(label) granted")
onGranted()
}
} }
} }
///
private func canCreateHIDEventTap() -> Bool {
let eventMask = (1 << CGEventType.flagsChanged.rawValue)
guard let tap = CGEvent.tapCreate(
tap: .cghidEventTap, place: .headInsertEventTap,
options: .defaultTap, eventsOfInterest: CGEventMask(eventMask),
callback: { _, _, _, _ in return nil },
userInfo: nil
) else { return false }
CFMachPortInvalidate(tap)
return true
}
private func showReadyAlert() {
let alert = NSAlert()
alert.messageText = "权限已就绪"
alert.informativeText = "Voice Input 正在运行。首次使用时还需授权麦克风和语音识别。"
alert.alertStyle = .informational
alert.runModal()
}
// MARK: - /
private func showMicrophonePermissionAlert() {
let alert = NSAlert()
alert.messageText = "需要麦克风权限"
alert.informativeText = "请前往 系统设置 > 隐私与安全性 > 麦克风,开启 VoiceInput 的权限。"
alert.alertStyle = .warning
alert.addButton(withTitle: "打开系统设置")
alert.addButton(withTitle: "取消")
if alert.runModal() == .alertFirstButtonReturn {
NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone")!)
}
}
private func showSpeechPermissionAlert() {
let alert = NSAlert()
alert.messageText = "需要语音识别权限"
alert.informativeText = "请前往 系统设置 > 隐私与安全性 > 语音识别,开启 VoiceInput 的权限。"
alert.alertStyle = .warning
alert.addButton(withTitle: "打开系统设置")
alert.addButton(withTitle: "取消")
if alert.runModal() == .alertFirstButtonReturn {
NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_SpeechRecognition")!)
}
}
// MARK: - Key Monitor
private func startKeyMonitor() { private func startKeyMonitor() {
let started = keyMonitor.start() let started = keyMonitor.start()
if !started { if !started {
@ -101,6 +164,37 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
isRecording = true isRecording = true
finalTranscription = "" finalTranscription = ""
//
let micStatus = AVCaptureDevice.authorizationStatus(for: .audio)
if micStatus == .denied || micStatus == .restricted {
showMicrophonePermissionAlert()
isRecording = false
return
}
//
let speechStatus = SFSpeechRecognizer.authorizationStatus()
if speechStatus == .denied || speechStatus == .restricted {
showSpeechPermissionAlert()
isRecording = false
return
}
if speechStatus == .notDetermined {
//
SFSpeechRecognizer.requestAuthorization { [weak self] status in
DispatchQueue.main.async {
self?.isRecording = false
guard status == .authorized else {
self?.showSpeechPermissionAlert()
return
}
//
self?.startRecording()
}
}
return
}
recordingPanel.showAnimated() recordingPanel.showAnimated()
// //