You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
172 lines
6.9 KiB
172 lines
6.9 KiB
import Cocoa
|
|
|
|
public final class StatusBarController {
|
|
private var statusItem: NSStatusItem?
|
|
public var onToggleRecording: (() -> Void)?
|
|
|
|
public init() {
|
|
setupStatusItem()
|
|
}
|
|
|
|
private func setupStatusItem() {
|
|
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
|
updateIcon(state: .idle)
|
|
buildMenu()
|
|
}
|
|
|
|
public enum State {
|
|
case idle
|
|
case recording
|
|
case transcribing
|
|
}
|
|
|
|
public func updateIcon(state: State) {
|
|
guard let button = statusItem?.button else { return }
|
|
|
|
switch state {
|
|
case .idle:
|
|
if let image = NSImage(systemSymbolName: "mic", accessibilityDescription: "Soniox Voice") {
|
|
image.isTemplate = true
|
|
button.image = image
|
|
}
|
|
button.toolTip = "Soniox Voice (آماده)"
|
|
case .recording:
|
|
if let image = NSImage(systemSymbolName: "mic.fill", accessibilityDescription: "Recording") {
|
|
image.isTemplate = false
|
|
button.image = image
|
|
button.contentTintColor = NSColor.systemRed
|
|
}
|
|
button.toolTip = "در حال ضبط صدا..."
|
|
case .transcribing:
|
|
if let image = NSImage(systemSymbolName: "waveform", accessibilityDescription: "Transcribing") {
|
|
image.isTemplate = false
|
|
button.image = image
|
|
button.contentTintColor = NSColor.systemOrange
|
|
}
|
|
button.toolTip = "در حال تبدیل به متن..."
|
|
}
|
|
}
|
|
|
|
public func buildMenu(isRecording: Bool = false) {
|
|
let menu = NSMenu()
|
|
menu.autoenablesItems = false
|
|
|
|
// 1. Record Action
|
|
let recordTitle = isRecording ? "⏹️ توقف ضبط و درج متن" : "🎙️ شروع ضبط صدا (\(HotkeyManager.shared.currentPreset.rawValue))"
|
|
let recordItem = NSMenuItem(title: recordTitle, action: #selector(toggleRecordAction), keyEquivalent: "")
|
|
recordItem.target = self
|
|
menu.addItem(recordItem)
|
|
|
|
menu.addItem(NSMenuItem.separator())
|
|
|
|
// 2. Mode Submenu
|
|
let modeMenu = NSMenu()
|
|
for mode in DictationMode.allCases {
|
|
let item = NSMenuItem(title: mode.localizedTitle, action: #selector(selectModeAction(_:)), keyEquivalent: "")
|
|
item.target = self
|
|
item.representedObject = mode
|
|
item.state = (HotkeyManager.shared.currentMode == mode) ? .on : .off
|
|
modeMenu.addItem(item)
|
|
}
|
|
let modeMenuItem = NSMenuItem(title: "⚙️ حالت کارکرد", action: nil, keyEquivalent: "")
|
|
modeMenuItem.submenu = modeMenu
|
|
menu.addItem(modeMenuItem)
|
|
|
|
// 3. Hotkey Submenu
|
|
let hotkeyMenu = NSMenu()
|
|
for preset in HotkeyPreset.allCases {
|
|
let item = NSMenuItem(title: preset.rawValue, action: #selector(selectHotkeyAction(_:)), keyEquivalent: "")
|
|
item.target = self
|
|
item.representedObject = preset
|
|
item.state = (HotkeyManager.shared.currentPreset == preset) ? .on : .off
|
|
hotkeyMenu.addItem(item)
|
|
}
|
|
let hotkeyMenuItem = NSMenuItem(title: "⌨️ کلید میانبر (Hotkey)", action: nil, keyEquivalent: "")
|
|
hotkeyMenuItem.submenu = hotkeyMenu
|
|
menu.addItem(hotkeyMenuItem)
|
|
|
|
menu.addItem(NSMenuItem.separator())
|
|
|
|
// 4. Sound Effects Toggle
|
|
let soundsEnabled = UserDefaults.standard.bool(forKey: "SonioxPlaySounds")
|
|
let soundItem = NSMenuItem(title: "🔊 پخش افکت صوتی", action: #selector(toggleSoundsAction(_:)), keyEquivalent: "")
|
|
soundItem.target = self
|
|
soundItem.state = soundsEnabled ? .on : .off
|
|
menu.addItem(soundItem)
|
|
|
|
// 5. Accessibility Permission Check
|
|
let isAxTrusted = AXIsProcessTrusted()
|
|
let axTitle = isAxTrusted ? "✅ دسترسی Accessibility فعال است" : "🔑 اعطای دسترسی Accessibility..."
|
|
let axItem = NSMenuItem(title: axTitle, action: #selector(openAccessibilitySettings), keyEquivalent: "")
|
|
axItem.target = self
|
|
menu.addItem(axItem)
|
|
|
|
// 6. Launch at Login
|
|
let launchLogin = UserDefaults.standard.bool(forKey: "SonioxLaunchAtLogin")
|
|
let launchItem = NSMenuItem(title: "🚀 اجرا هنگام بالا آمدن سیستم", action: #selector(toggleLaunchAtLoginAction(_:)), keyEquivalent: "")
|
|
launchItem.target = self
|
|
launchItem.state = launchLogin ? .on : .off
|
|
menu.addItem(launchItem)
|
|
|
|
menu.addItem(NSMenuItem.separator())
|
|
|
|
// 7. About & Quit
|
|
let aboutItem = NSMenuItem(title: "ℹ️ درباره Soniox Voice", action: #selector(aboutAction), keyEquivalent: "")
|
|
aboutItem.target = self
|
|
menu.addItem(aboutItem)
|
|
|
|
let quitItem = NSMenuItem(title: "❌ خروج", action: #selector(quitAction), keyEquivalent: "q")
|
|
quitItem.target = self
|
|
menu.addItem(quitItem)
|
|
|
|
statusItem?.menu = menu
|
|
}
|
|
|
|
@objc private func toggleRecordAction() {
|
|
onToggleRecording?()
|
|
}
|
|
|
|
@objc private func selectModeAction(_ sender: NSMenuItem) {
|
|
if let mode = sender.representedObject as? DictationMode {
|
|
HotkeyManager.shared.currentMode = mode
|
|
buildMenu()
|
|
}
|
|
}
|
|
|
|
@objc private func selectHotkeyAction(_ sender: NSMenuItem) {
|
|
if let preset = sender.representedObject as? HotkeyPreset {
|
|
HotkeyManager.shared.currentPreset = preset
|
|
buildMenu()
|
|
}
|
|
}
|
|
|
|
@objc private func toggleSoundsAction(_ sender: NSMenuItem) {
|
|
let current = UserDefaults.standard.bool(forKey: "SonioxPlaySounds")
|
|
UserDefaults.standard.set(!current, forKey: "SonioxPlaySounds")
|
|
buildMenu()
|
|
}
|
|
|
|
@objc private func toggleLaunchAtLoginAction(_ sender: NSMenuItem) {
|
|
let current = UserDefaults.standard.bool(forKey: "SonioxLaunchAtLogin")
|
|
UserDefaults.standard.set(!current, forKey: "SonioxLaunchAtLogin")
|
|
buildMenu()
|
|
}
|
|
|
|
@objc private func openAccessibilitySettings() {
|
|
let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility")!
|
|
NSWorkspace.shared.open(url)
|
|
}
|
|
|
|
@objc private func aboutAction() {
|
|
let alert = NSAlert()
|
|
alert.messageText = "Soniox Voice v1.0"
|
|
alert.informativeText = "تبدیل بلادرنگ گفتار به متن فارسی و انگلیسی با موتور ابری فوق سریع Soniox.\n\nتوسعه یافته برای macOS."
|
|
alert.alertStyle = .informational
|
|
alert.addButton(withTitle: "باشه")
|
|
alert.runModal()
|
|
}
|
|
|
|
@objc private func quitAction() {
|
|
NSApplication.shared.terminate(nil)
|
|
}
|
|
}
|