- CGEvent cghidEventTap 全局监听 Fn 键 - Apple Speech 流式语音识别(zh-CN 默认) - 实时音频 RMS 电平驱动波形动画 - NSPanel 胶囊浮动窗口 - 剪贴板 + Cmd+V 文本注入,CJK 输入法自动切换 - OpenAI 兼容 LLM 纠错(DeepSeek 预设) - LSUIElement 菜单栏运行 - SPM + Makefile 构建
143 lines
4.2 KiB
Swift
143 lines
4.2 KiB
Swift
import Cocoa
|
||
|
||
/// 菜单栏控制器:管理状态栏图标和菜单
|
||
final class MenuBarController: NSObject {
|
||
|
||
private let statusItem: NSStatusItem
|
||
private let menu = NSMenu()
|
||
private var languageMenuItems: [NSMenuItem] = []
|
||
|
||
// 子菜单引用
|
||
private let llmSubmenu = NSMenu()
|
||
private let llmEnabledItem: NSMenuItem
|
||
private let settingsWindow = SettingsWindow()
|
||
|
||
override init() {
|
||
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
||
|
||
// LLM 启用/禁用
|
||
llmEnabledItem = NSMenuItem(
|
||
title: "Enable LLM Refinement",
|
||
action: #selector(toggleLLM),
|
||
keyEquivalent: ""
|
||
)
|
||
|
||
super.init()
|
||
|
||
configureStatusItem()
|
||
buildMenu()
|
||
}
|
||
|
||
private func configureStatusItem() {
|
||
if let button = statusItem.button {
|
||
button.image = NSImage(
|
||
systemSymbolName: "mic.fill",
|
||
accessibilityDescription: "Voice Input"
|
||
)
|
||
button.toolTip = "Voice Input — 按住 Fn 键录音"
|
||
}
|
||
}
|
||
|
||
private func buildMenu() {
|
||
// 当前语言显示
|
||
let currentLanguageItem = NSMenuItem(
|
||
title: "Language: \(Config.shared.language.displayName)",
|
||
action: nil,
|
||
keyEquivalent: ""
|
||
)
|
||
currentLanguageItem.isEnabled = false
|
||
menu.addItem(currentLanguageItem)
|
||
menu.addItem(.separator())
|
||
|
||
// 语言选择子菜单
|
||
let languageMenu = NSMenu()
|
||
for lang in RecognitionLanguage.allCases {
|
||
let item = NSMenuItem(
|
||
title: lang.displayName,
|
||
action: #selector(selectLanguage(_:)),
|
||
keyEquivalent: ""
|
||
)
|
||
item.representedObject = lang
|
||
item.state = lang == Config.shared.language ? .on : .off
|
||
languageMenu.addItem(item)
|
||
languageMenuItems.append(item)
|
||
}
|
||
|
||
let languageSubmenuItem = NSMenuItem()
|
||
languageSubmenuItem.title = "Language"
|
||
languageSubmenuItem.submenu = languageMenu
|
||
menu.addItem(languageSubmenuItem)
|
||
|
||
menu.addItem(.separator())
|
||
|
||
// LLM 子菜单
|
||
llmSubmenu.addItem(llmEnabledItem)
|
||
llmEnabledItem.target = self
|
||
llmEnabledItem.action = #selector(toggleLLM)
|
||
updateLLMMenuState()
|
||
|
||
let settingsItem = NSMenuItem(
|
||
title: "LLM Settings...",
|
||
action: #selector(openSettings),
|
||
keyEquivalent: ","
|
||
)
|
||
settingsItem.target = self
|
||
llmSubmenu.addItem(settingsItem)
|
||
|
||
let llmSubmenuItem = NSMenuItem()
|
||
llmSubmenuItem.title = "LLM Refinement"
|
||
llmSubmenuItem.submenu = llmSubmenu
|
||
menu.addItem(llmSubmenuItem)
|
||
|
||
menu.addItem(.separator())
|
||
|
||
// 退出
|
||
let quitItem = NSMenuItem(
|
||
title: "Quit",
|
||
action: #selector(quit),
|
||
keyEquivalent: "q"
|
||
)
|
||
quitItem.target = self
|
||
menu.addItem(quitItem)
|
||
|
||
statusItem.menu = menu
|
||
}
|
||
|
||
private func updateLLMMenuState() {
|
||
llmEnabledItem.state = Config.shared.llmEnabled ? .on : .off
|
||
}
|
||
|
||
private func updateLanguageMenuState() {
|
||
for item in languageMenuItems {
|
||
guard let lang = item.representedObject as? RecognitionLanguage else { continue }
|
||
item.state = lang == Config.shared.language ? .on : .off
|
||
}
|
||
// 更新第一项的显示
|
||
if let first = menu.items.first {
|
||
first.title = "Language: \(Config.shared.language.displayName)"
|
||
}
|
||
}
|
||
|
||
@objc private func selectLanguage(_ sender: NSMenuItem) {
|
||
guard let lang = sender.representedObject as? RecognitionLanguage else { return }
|
||
Config.shared.language = lang
|
||
updateLanguageMenuState()
|
||
print("[Menu] Language switched to: \(lang.displayName)")
|
||
}
|
||
|
||
@objc private func toggleLLM() {
|
||
Config.shared.llmEnabled.toggle()
|
||
updateLLMMenuState()
|
||
print("[Menu] LLM Refinement: \(Config.shared.llmEnabled ? "ON" : "OFF")")
|
||
}
|
||
|
||
@objc private func openSettings() {
|
||
settingsWindow.makeKeyAndOrderFront(nil)
|
||
NSApp.activate(ignoringOtherApps: true)
|
||
}
|
||
|
||
@objc private func quit() {
|
||
NSApp.terminate(nil)
|
||
}
|
||
}
|