Browse Source
feat: adaptive menu bar icon, fully English UI, double-tap Fn preset, and advanced settings
main
feat: adaptive menu bar icon, fully English UI, double-tap Fn preset, and advanced settings
main
5 changed files with 295 additions and 54 deletions
-
8src/AppDelegate.swift
-
70src/HotkeyManager.swift
-
14src/SonioxClient.swift
-
135src/SonioxSettings.swift
-
122src/StatusBarController.swift
@ -0,0 +1,135 @@ |
|||||
|
import Foundation |
||||
|
import Cocoa |
||||
|
|
||||
|
public enum RecognitionLanguage: String, CaseIterable { |
||||
|
case multi = "multi" |
||||
|
case persian = "fa" |
||||
|
case english = "en" |
||||
|
case persianEnglish = "fa_en" |
||||
|
|
||||
|
public var displayName: String { |
||||
|
switch self { |
||||
|
case .multi: |
||||
|
return "Multilingual (Persian, English, Arabic)" |
||||
|
case .persian: |
||||
|
return "Persian Only (فارسی)" |
||||
|
case .english: |
||||
|
return "English Only" |
||||
|
case .persianEnglish: |
||||
|
return "Persian + English" |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public var hints: [String] { |
||||
|
switch self { |
||||
|
case .multi: |
||||
|
return ["fa", "en", "ar"] |
||||
|
case .persian: |
||||
|
return ["fa"] |
||||
|
case .english: |
||||
|
return ["en"] |
||||
|
case .persianEnglish: |
||||
|
return ["fa", "en"] |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public final class SonioxSettings { |
||||
|
public static let shared = SonioxSettings() |
||||
|
|
||||
|
private let defaults = UserDefaults.standard |
||||
|
|
||||
|
public var language: RecognitionLanguage { |
||||
|
get { |
||||
|
let val = defaults.string(forKey: "SonioxLanguage") ?? RecognitionLanguage.multi.rawValue |
||||
|
return RecognitionLanguage(rawValue: val) ?? .multi |
||||
|
} |
||||
|
set { |
||||
|
defaults.set(newValue.rawValue, forKey: "SonioxLanguage") |
||||
|
SonioxSessionPool.shared.reconnectAll() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public var addTrailingSpace: Bool { |
||||
|
get { |
||||
|
if defaults.object(forKey: "SonioxAddTrailingSpace") == nil { return true } |
||||
|
return defaults.bool(forKey: "SonioxAddTrailingSpace") |
||||
|
} |
||||
|
set { |
||||
|
defaults.set(newValue, forKey: "SonioxAddTrailingSpace") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public var normalizeText: Bool { |
||||
|
get { |
||||
|
if defaults.object(forKey: "SonioxNormalizeText") == nil { return true } |
||||
|
return defaults.bool(forKey: "SonioxNormalizeText") |
||||
|
} |
||||
|
set { |
||||
|
defaults.set(newValue, forKey: "SonioxNormalizeText") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public var playSounds: Bool { |
||||
|
get { |
||||
|
if defaults.object(forKey: "SonioxPlaySounds") == nil { return true } |
||||
|
return defaults.bool(forKey: "SonioxPlaySounds") |
||||
|
} |
||||
|
set { |
||||
|
defaults.set(newValue, forKey: "SonioxPlaySounds") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public var launchAtLogin: Bool { |
||||
|
get { |
||||
|
return defaults.bool(forKey: "SonioxLaunchAtLogin") |
||||
|
} |
||||
|
set { |
||||
|
defaults.set(newValue, forKey: "SonioxLaunchAtLogin") |
||||
|
updateLaunchAtLogin(enabled: newValue) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public func resetToDefaults() { |
||||
|
defaults.removeObject(forKey: "SonioxHotkeyPreset") |
||||
|
defaults.removeObject(forKey: "SonioxDictationMode") |
||||
|
defaults.removeObject(forKey: "SonioxLanguage") |
||||
|
defaults.removeObject(forKey: "SonioxAddTrailingSpace") |
||||
|
defaults.removeObject(forKey: "SonioxNormalizeText") |
||||
|
defaults.removeObject(forKey: "SonioxPlaySounds") |
||||
|
defaults.removeObject(forKey: "SonioxLaunchAtLogin") |
||||
|
updateLaunchAtLogin(enabled: false) |
||||
|
HotkeyManager.shared.registerHotkeys() |
||||
|
SonioxSessionPool.shared.reconnectAll() |
||||
|
} |
||||
|
|
||||
|
private func updateLaunchAtLogin(enabled: Bool) { |
||||
|
let launchAgentDir = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Library/LaunchAgents") |
||||
|
let plistURL = launchAgentDir.appendingPathComponent("com.soniox.voice.plist") |
||||
|
|
||||
|
if enabled { |
||||
|
try? FileManager.default.createDirectory(at: launchAgentDir, withIntermediateDirectories: true) |
||||
|
let plistContent = """ |
||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||
|
<plist version="1.0"> |
||||
|
<dict> |
||||
|
<key>Label</key> |
||||
|
<string>com.soniox.voice</string> |
||||
|
<key>ProgramArguments</key> |
||||
|
<array> |
||||
|
<string>/Applications/Soniox Voice.app/Contents/MacOS/SonioxVoice</string> |
||||
|
</array> |
||||
|
<key>RunAtLoad</key> |
||||
|
<true/> |
||||
|
<key>KeepAlive</key> |
||||
|
<false/> |
||||
|
</dict> |
||||
|
</plist> |
||||
|
""" |
||||
|
try? plistContent.write(to: plistURL, atomically: true, encoding: .utf8) |
||||
|
} else { |
||||
|
try? FileManager.default.removeItem(at: plistURL) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue