Browse Source

fix: strictly suppress input-box typing on slash commands and add universal bilingual app matching

main
Ali Alavi 15 hours ago
parent
commit
73987c9aaf
  1. 7
      src/AppDelegate.swift
  2. 430
      src/VoiceCommandExecutor.swift

7
src/AppDelegate.swift

@ -124,11 +124,8 @@ public final class AppDelegate: NSObject, NSApplicationDelegate {
// 1. Check for Executive Voice Slash Commands
if SonioxSettings.shared.voiceCommands && VoiceCommandExecutor.shared.isSlashCommand(text: trimmed) {
let handled = VoiceCommandExecutor.shared.execute(rawText: trimmed)
if handled {
print("AppDelegate: Successfully executed slash command for: \(trimmed)")
return // Handled as executive command do not paste text!
}
VoiceCommandExecutor.shared.execute(rawText: trimmed)
return // STRICT: If speech starts with slash, NEVER paste into any active input box!
}
// 2. Normal dictation processing & paste

430
src/VoiceCommandExecutor.swift

@ -1,73 +1,182 @@
import Cocoa
import Carbon
public struct InstalledAppInfo {
public let name: String
public let normalizedName: String
public let url: URL
}
public final class VoiceCommandExecutor {
public static let shared = VoiceCommandExecutor()
private init() {}
private var cachedInstalledApps: [InstalledAppInfo] = []
private var lastAppsScanTime: TimeInterval = 0
// Comprehensive Persian to Canonical English Application Names
private let persianAppDictionary: [String: String] = [
// System & Apple Apps
"ستینگ": "System Settings",
"ستینگز": "System Settings",
"تنظیمات": "System Settings",
"تنظیمات سیستم": "System Settings",
"سیستم ستینگ": "System Settings",
"فایندر": "Finder",
"ترمینال": "Terminal",
"سافاری": "Safari",
"ماشین حساب": "Calculator",
"کلکولیتور": "Calculator",
"تقویم": "Calendar",
"کلندر": "Calendar",
"یادداشت": "Notes",
"یادداشت‌ها": "Notes",
"نوت": "Notes",
"نوتس": "Notes",
"اپ استور": "App Store",
"فوتوز": "Photos",
"عکس": "Photos",
"موزیک": "Music",
"آهنگ": "Music",
"پادکست": "Podcasts",
"مخاطبین": "Contacts",
"پیام‌ها": "Messages",
"مسج": "Messages",
"ایمیل": "Mail",
"میل": "Mail",
"شورتکات": "Shortcuts",
"میانبرها": "Shortcuts",
"فری فرم": "Freeform",
"پریویو": "Preview",
"اکتیویتی مانیتور": "Activity Monitor",
"دیسک یوتیلیتی": "Disk Utility",
"پسورد": "Passwords",
// Developer & Custom Apps on this Mac
"آنتی گرویتی آیدی ای": "Antigravity IDE",
"آنتی گرویتی آی دی ای": "Antigravity IDE",
"آنتی گرویتی ادیتور": "Antigravity IDE",
"آنتی گرویتی کد": "Antigravity IDE",
"آنتیگرویتی آیدی ای": "Antigravity IDE",
"انتی گرویتی ایدی ای": "Antigravity IDE",
"آنتی گرویتی": "Antigravity",
"انتی گرویتی": "Antigravity",
"آنتیگرویتی": "Antigravity",
"فایرفاکس": "Firefox",
"کروم": "Google Chrome",
"گوگل کروم": "Google Chrome",
"تلگرام": "Telegram",
"تیم بی": "TeamBy",
"تیم بای": "TeamBy",
"تیم‌بای": "TeamBy",
"تیمبی": "TeamBy",
"فیگما": "Figma",
"وی تو باکس": "V2Box",
"وی۲باکس": "V2Box",
"وی اس کد": "Visual Studio Code",
"کد": "Visual Studio Code",
"ویژوال استودیو": "Visual Studio Code",
"سانی اوکس": "Soniox Voice",
// Utilities installed on this Mac
"روزگار": "Roozegar",
"رزگار": "Roozegar",
"شمسی کال": "ShamsiCal",
"چند": "Chand",
"میدل کلیک": "MiddleClick"
]
private init() {
refreshInstalledApps()
}
public func isSlashCommand(text: String) -> Bool {
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
return trimmed.hasPrefix("اسلش") ||
trimmed.hasPrefix("slash") ||
trimmed.hasPrefix("/") ||
trimmed.hasPrefix("\\")
if trimmed.hasPrefix("اسلش") ||
trimmed.hasPrefix("اصلش") ||
trimmed.hasPrefix("slash") ||
trimmed.hasPrefix("slsh") ||
trimmed.hasPrefix("/") ||
trimmed.hasPrefix("\\") {
return true
}
let firstWord = trimmed.components(separatedBy: .whitespacesAndNewlines).first ?? ""
let cleanFirst = firstWord.trimmingCharacters(in: CharacterSet(charactersIn: ":,،.؛-_/\\"))
return cleanFirst == "اسلش" || cleanFirst == "اصلش" || cleanFirst == "slash" || cleanFirst == "slsh"
}
@discardableResult
public func execute(rawText: String) -> Bool {
let trimmed = rawText.trimmingCharacters(in: .whitespacesAndNewlines)
guard isSlashCommand(text: trimmed) else { return false }
public func extractCommandBody(rawText: String) -> String {
var body = rawText.trimmingCharacters(in: .whitespacesAndNewlines)
let lower = body.lowercased()
// Strip trigger prefix
var body = trimmed
if body.lowercased().hasPrefix("اسلش") {
if lower.hasPrefix("اسلش") || lower.hasPrefix("اصلش") {
body = String(body.dropFirst(4)).trimmingCharacters(in: .whitespacesAndNewlines)
} else if body.lowercased().hasPrefix("slash") {
} else if lower.hasPrefix("slash") {
body = String(body.dropFirst(5)).trimmingCharacters(in: .whitespacesAndNewlines)
} else if lower.hasPrefix("slsh") {
body = String(body.dropFirst(4)).trimmingCharacters(in: .whitespacesAndNewlines)
} else if body.hasPrefix("/") || body.hasPrefix("\\") {
body = String(body.dropFirst(1)).trimmingCharacters(in: .whitespacesAndNewlines)
if body.hasPrefix("/") {
body = String(body.dropFirst(1)).trimmingCharacters(in: .whitespacesAndNewlines)
}
}
body = body.trimmingCharacters(in: CharacterSet(charactersIn: ":,،.؛ -_"))
return body.trimmingCharacters(in: CharacterSet(charactersIn: ":,،.؛ -_"))
}
@discardableResult
public func execute(rawText: String) -> Bool {
let trimmed = rawText.trimmingCharacters(in: .whitespacesAndNewlines)
guard isSlashCommand(text: trimmed) else { return false }
let body = extractCommandBody(rawText: trimmed)
let lower = body.lowercased()
print("VoiceCommandExecutor: executing command for body: '\(body)'")
// 1. App with Tab combinations (e.g. "فایرفاکس تب ۱", "کروم تب ۲")
// 1. App + Tab combinations (e.g. "فایرفاکس تب ۱", "کروم تب ۲", "firefox tab 1", "chrome tab 3")
if handleAppWithTab(lower: lower, original: body) {
playConfirmationSound()
return true
}
// 2. Standalone Tab commands (e.g. "تب ۱", "تب جدید", "بستن تب")
// 2. Standalone Tab & Navigation commands (e.g. "تب ۱", "تب جدید", "بستن تب", "tab 1", "new tab")
if handleStandaloneTab(lower: lower) {
playConfirmationSound()
return true
}
// 3. System controls (volume, lock, screenshot, dark mode, clipboard)
// 3. System controls (volume, mute, lock, screenshot, dark mode, clipboard)
if handleSystemControls(lower: lower) {
playConfirmationSound()
return true
}
// 4. Named applications (Antigravity IDE, Antigravity, Settings, Firefox, etc.)
if handleNamedApp(lower: lower, original: body) {
// 4. Match App from Persian dictionary or exact names
if handleKnownApp(lower: lower, original: body) {
playConfirmationSound()
return true
}
// 5. Generic application launch fallback
if handleGenericApp(original: body) {
// 5. Universal fuzzy resolver across all installed apps in /Applications and /System/Applications
if handleUniversalInstalledApp(query: body) {
playConfirmationSound()
return true
}
// Command was prefixed with slash but body was not recognized
print("VoiceCommandExecutor: slash command body unrecognized: '\(body)'")
HistoryManager.shared.addEntry("⚡️ [Command] Unrecognized: \(body)")
playWarningSound()
return false
}
private func handleAppWithTab(lower: String, original: String) -> Bool {
guard lower.contains("تب") || lower.contains("tab") else { return false }
var targetApp: String? = nil
if lower.contains("فایرفاکس") || lower.contains("firefox") {
targetApp = "Firefox"
@ -75,6 +184,10 @@ public final class VoiceCommandExecutor {
targetApp = "Google Chrome"
} else if lower.contains("سافاری") || lower.contains("safari") {
targetApp = "Safari"
} else if lower.contains("آنتی گرویتی آیدی ای") || lower.contains("antigravity ide") {
targetApp = "Antigravity IDE"
} else if lower.contains("آنتی گرویتی") || lower.contains("antigravity") {
targetApp = "Antigravity"
}
guard let appName = targetApp else { return false }
@ -85,13 +198,13 @@ public final class VoiceCommandExecutor {
}
HistoryManager.shared.addEntry("⚡️ [Command] \(appName): Tab \(tabNum)")
return true
} else if lower.contains("تب جدید") || lower.contains("new tab") {
} else if lower.contains("تب جدید") || lower.contains("نیو تب") || lower.contains("new tab") {
activateApp(named: appName) {
self.pressHotkey(keyCode: 17, modifiers: .maskCommand) // Cmd+T
}
HistoryManager.shared.addEntry("⚡️ [Command] \(appName): New Tab")
return true
} else if lower.contains("بستن تب") || lower.contains("close tab") {
} else if lower.contains("بستن تب") || lower.contains("کلوز تب") || lower.contains("close tab") {
activateApp(named: appName) {
self.pressHotkey(keyCode: 13, modifiers: .maskCommand) // Cmd+W
}
@ -104,31 +217,49 @@ public final class VoiceCommandExecutor {
private func extractTabNumber(from text: String) -> Int? {
let t = text.lowercased()
if t.contains("تب ۱") || t.contains("تب 1") || t.contains("تب یک") || t.contains("تب اول") || t.contains("tab 1") || t.contains("tab one") {
// Tab 1
if t.contains("تب ۱") || t.contains("تب 1") || t.contains("تب یک") || t.contains("تب اول") ||
t.contains("tab 1") || t.contains("tab one") || t.contains("tab first") {
return 1
}
if t.contains("تب ۲") || t.contains("تب 2") || t.contains("تب دو") || t.contains("تب دوم") || t.contains("tab 2") || t.contains("tab two") {
// Tab 2
if t.contains("تب ۲") || t.contains("تب 2") || t.contains("تب دو") || t.contains("تب دوم") ||
t.contains("tab 2") || t.contains("tab two") || t.contains("tab second") {
return 2
}
if t.contains("تب ۳") || t.contains("تب 3") || t.contains("تب سه") || t.contains("تب سوم") || t.contains("tab 3") || t.contains("tab three") {
// Tab 3
if t.contains("تب ۳") || t.contains("تب 3") || t.contains("تب سه") || t.contains("تب سوم") ||
t.contains("tab 3") || t.contains("tab three") || t.contains("tab third") {
return 3
}
if t.contains("تب ۴") || t.contains("تب 4") || t.contains("تب چهار") || t.contains("تب چهارم") || t.contains("tab 4") || t.contains("tab four") {
// Tab 4
if t.contains("تب ۴") || t.contains("تب 4") || t.contains("تب چهار") || t.contains("تب چهارم") ||
t.contains("tab 4") || t.contains("tab four") {
return 4
}
if t.contains("تب ۵") || t.contains("تب 5") || t.contains("تب پنج") || t.contains("تب پنجم") || t.contains("tab 5") || t.contains("tab five") {
// Tab 5
if t.contains("تب ۵") || t.contains("تب 5") || t.contains("تب پنج") || t.contains("تب پنجم") ||
t.contains("tab 5") || t.contains("tab five") {
return 5
}
if t.contains("تب ۶") || t.contains("تب 6") || t.contains("تب شش") || t.contains("تب ششم") || t.contains("tab 6") || t.contains("tab six") {
// Tab 6
if t.contains("تب ۶") || t.contains("تب 6") || t.contains("تب شش") || t.contains("تب ششم") ||
t.contains("tab 6") || t.contains("tab six") {
return 6
}
if t.contains("تب ۷") || t.contains("تب 7") || t.contains("تب هفت") || t.contains("تب هفتم") || t.contains("tab 7") || t.contains("tab seven") {
// Tab 7
if t.contains("تب ۷") || t.contains("تب 7") || t.contains("تب هفت") || t.contains("تب هفتم") ||
t.contains("tab 7") || t.contains("tab seven") {
return 7
}
if t.contains("تب ۸") || t.contains("تب 8") || t.contains("تب هشت") || t.contains("تب هشتم") || t.contains("tab 8") || t.contains("tab eight") {
// Tab 8
if t.contains("تب ۸") || t.contains("تب 8") || t.contains("تب هشت") || t.contains("تب هشتم") ||
t.contains("tab 8") || t.contains("tab eight") {
return 8
}
if t.contains("تب ۹") || t.contains("تب 9") || t.contains("تب نه") || t.contains("تب آخر") || t.contains("tab 9") || t.contains("tab last") {
// Tab 9 / Last Tab
if t.contains("تب ۹") || t.contains("تب 9") || t.contains("تب نه") || t.contains("تب نهم") || t.contains("تب آخر") ||
t.contains("tab 9") || t.contains("tab nine") || t.contains("tab last") {
return 9
}
return nil
@ -168,115 +299,6 @@ public final class VoiceCommandExecutor {
return false
}
private func handleNamedApp(lower: String, original: String) -> Bool {
// System Settings
if lower.contains("ستینگ") || lower.contains("settings") || lower.contains("تنظیمات") || lower.contains("سیستم ستینگ") {
launchOrActivateApp(named: "System Settings")
HistoryManager.shared.addEntry("⚡️ [Command] Open System Settings")
return true
}
// Antigravity IDE
if lower.contains("آنتی گرویتی آیدی ای") || lower.contains("آنتی گرویتی آی دی ای") || lower.contains("antigravity ide") || lower.contains("آنتیگرویتی آیدی ای") {
launchOrActivateApp(named: "Antigravity IDE")
HistoryManager.shared.addEntry("⚡️ [Command] Open Antigravity IDE")
return true
}
// Antigravity
if lower.contains("آنتی گرویتی") || lower.contains("انتی گرویتی") || lower.contains("آنتیگرویتی") || lower.contains("antigravity") {
launchOrActivateApp(named: "Antigravity")
HistoryManager.shared.addEntry("⚡️ [Command] Open Antigravity")
return true
}
// Firefox
if lower.contains("فایرفاکس") || lower.contains("firefox") {
launchOrActivateApp(named: "Firefox")
HistoryManager.shared.addEntry("⚡️ [Command] Open Firefox")
return true
}
// Google Chrome
if lower.contains("گوگل کروم") || lower.contains("کروم") || lower.contains("chrome") {
launchOrActivateApp(named: "Google Chrome")
HistoryManager.shared.addEntry("⚡️ [Command] Open Google Chrome")
return true
}
// Telegram
if lower.contains("تلگرام") || lower.contains("telegram") {
launchOrActivateApp(named: "Telegram")
HistoryManager.shared.addEntry("⚡️ [Command] Open Telegram")
return true
}
// TeamBy
if lower.contains("تیم بای") || lower.contains("تیمبی") || lower.contains("teamby") {
launchOrActivateApp(named: "TeamBy")
HistoryManager.shared.addEntry("⚡️ [Command] Open TeamBy")
return true
}
// Terminal
if lower.contains("ترمینال") || lower.contains("terminal") {
launchOrActivateApp(named: "Terminal")
HistoryManager.shared.addEntry("⚡️ [Command] Open Terminal")
return true
}
// Finder
if lower.contains("فایندر") || lower.contains("finder") {
launchOrActivateApp(named: "Finder")
HistoryManager.shared.addEntry("⚡️ [Command] Open Finder")
return true
}
// VS Code
if lower.contains("وی اس کد") || lower.contains("vscode") || lower == "کد" || lower == "code" {
launchOrActivateApp(named: "Visual Studio Code")
HistoryManager.shared.addEntry("⚡️ [Command] Open Visual Studio Code")
return true
}
// Figma
if lower.contains("فیگما") || lower.contains("figma") {
launchOrActivateApp(named: "Figma")
HistoryManager.shared.addEntry("⚡️ [Command] Open Figma")
return true
}
// Safari
if lower.contains("سافاری") || lower.contains("safari") {
launchOrActivateApp(named: "Safari")
HistoryManager.shared.addEntry("⚡️ [Command] Open Safari")
return true
}
// Calculator
if lower.contains("ماشین حساب") || lower.contains("calculator") {
launchOrActivateApp(named: "Calculator")
HistoryManager.shared.addEntry("⚡️ [Command] Open Calculator")
return true
}
// Calendar
if lower.contains("تقویم") || lower.contains("calendar") {
launchOrActivateApp(named: "Calendar")
HistoryManager.shared.addEntry("⚡️ [Command] Open Calendar")
return true
}
// Notes
if lower.contains("یادداشت") || lower.contains("نوت") || lower.contains("notes") {
launchOrActivateApp(named: "Notes")
HistoryManager.shared.addEntry("⚡️ [Command] Open Notes")
return true
}
return false
}
private func handleSystemControls(lower: String) -> Bool {
// Volume controls
if lower.contains("صدا زیاد") || lower.contains("افزایش صدا") || lower.contains("volume up") {
@ -296,7 +318,7 @@ public final class VoiceCommandExecutor {
}
// Lock screen
if lower.contains("قفل") || lower.contains("لاک") || lower.contains("lock screen") || lower.contains("lock") {
if lower.contains("قفل") || lower.contains("لاک") || lower.contains("lock screen") || lower == "lock" {
let task = Process()
task.executableURL = URL(fileURLWithPath: "/usr/bin/pmset")
task.arguments = ["displaysleepnow"]
@ -316,63 +338,147 @@ public final class VoiceCommandExecutor {
}
// Dark Mode toggle
if lower.contains("دارک مود") || lower.contains("لایت مود") || lower.contains("حالت شب") || lower.contains("dark mode") {
if lower.contains("دارک مود") || lower.contains("لایت مود") || lower.contains("حالت شب") || lower.contains("حالت روز") || lower.contains("dark mode") {
runAppleScript("tell application \"System Events\" to tell appearance preferences to set dark mode to not dark mode")
HistoryManager.shared.addEntry("⚡️ [Command] Toggle Dark Mode")
return true
}
// Editing controls
// Clipboard & Editing hotkeys
if lower == "کپی" || lower == "copy" {
pressHotkey(keyCode: 8, modifiers: .maskCommand) // Cmd+C
HistoryManager.shared.addEntry("⚡️ [Command] Copy")
return true
}
if lower == "پیست" || lower == "paste" {
pressHotkey(keyCode: 9, modifiers: .maskCommand) // Cmd+V
HistoryManager.shared.addEntry("⚡️ [Command] Paste")
return true
}
if lower == "کات" || lower == "cut" {
pressHotkey(keyCode: 7, modifiers: .maskCommand) // Cmd+X
HistoryManager.shared.addEntry("⚡️ [Command] Cut")
return true
}
if lower.contains("انتخاب همه") || lower.contains("سلکت آل") || lower == "select all" {
pressHotkey(keyCode: 0, modifiers: .maskCommand) // Cmd+A
HistoryManager.shared.addEntry("⚡️ [Command] Select All")
return true
}
if lower == "آندو" || lower == "برگرد" || lower == "undo" {
pressHotkey(keyCode: 6, modifiers: .maskCommand) // Cmd+Z
HistoryManager.shared.addEntry("⚡️ [Command] Undo")
return true
}
if lower == "اینتر" || lower == "enter" {
pressHotkey(keyCode: 36, modifiers: []) // Return
pressHotkey(keyCode: 36, modifiers: []) // Return key
HistoryManager.shared.addEntry("⚡️ [Command] Enter")
return true
}
return false
}
private func handleGenericApp(original: String) -> Bool {
var appCandidate = original
if appCandidate.hasPrefix("باز کن ") || appCandidate.hasPrefix("بازکن ") {
appCandidate = String(appCandidate.dropFirst(7)).trimmingCharacters(in: .whitespacesAndNewlines)
} else if appCandidate.lowercased().hasPrefix("open ") {
appCandidate = String(appCandidate.dropFirst(5)).trimmingCharacters(in: .whitespacesAndNewlines)
private func handleKnownApp(lower: String, original: String) -> Bool {
// Strip common prefix like "باز کن" or "open"
var query = lower
for prefix in ["باز کن ", "بازکن ", "برو به ", "برو تو ", "اجرا کن ", "open ", "launch ", "goto "] {
if query.hasPrefix(prefix) {
query = String(query.dropFirst(prefix.count)).trimmingCharacters(in: .whitespacesAndNewlines)
break
}
}
guard !appCandidate.isEmpty else { return false }
// Check exact or partial match in Persian App Dictionary
for (persianKey, englishAppName) in persianAppDictionary {
if query == persianKey || query.contains(persianKey) {
launchOrActivateApp(named: englishAppName)
HistoryManager.shared.addEntry("⚡️ [Command] Open \(englishAppName)")
return true
}
}
let ws = NSWorkspace.shared
let appURL = ws.urlForApplication(withBundleIdentifier: appCandidate) ??
URL(fileURLWithPath: "/Applications/\(appCandidate).app")
if FileManager.default.fileExists(atPath: appURL.path) || ws.urlForApplication(withBundleIdentifier: appCandidate) != nil {
launchOrActivateApp(named: appCandidate)
HistoryManager.shared.addEntry("⚡️ [Command] Open \(appCandidate)")
return false
}
private func handleUniversalInstalledApp(query: String) -> Bool {
var cleanQuery = query.trimmingCharacters(in: .whitespacesAndNewlines)
for prefix in ["باز کن ", "بازکن ", "open ", "launch "] {
if cleanQuery.lowercased().hasPrefix(prefix) {
cleanQuery = String(cleanQuery.dropFirst(prefix.count)).trimmingCharacters(in: .whitespacesAndNewlines)
break
}
}
guard !cleanQuery.isEmpty else { return false }
let normalizedQuery = normalizeForMatching(cleanQuery)
refreshInstalledApps()
// 1. Check exact normalized match
if let match = cachedInstalledApps.first(where: { $0.normalizedName == normalizedQuery }) {
launchApp(at: match.url, name: match.name)
return true
}
// 2. Check prefix match
if let match = cachedInstalledApps.first(where: { $0.normalizedName.hasPrefix(normalizedQuery) }) {
launchApp(at: match.url, name: match.name)
return true
}
// 3. Check contains match
if let match = cachedInstalledApps.first(where: { $0.normalizedName.contains(normalizedQuery) || normalizedQuery.contains($0.normalizedName) }) {
launchApp(at: match.url, name: match.name)
return true
}
return false
}
private func launchApp(at url: URL, name: String) {
let ws = NSWorkspace.shared
if let running = ws.runningApplications.first(where: { $0.bundleURL == url }) {
running.activate(options: [.activateIgnoringOtherApps])
} else {
let conf = NSWorkspace.OpenConfiguration()
conf.activates = true
ws.openApplication(at: url, configuration: conf, completionHandler: nil)
}
HistoryManager.shared.addEntry("⚡️ [Command] Open \(name)")
}
private func normalizeForMatching(_ text: String) -> String {
return text.lowercased()
.components(separatedBy: CharacterSet.alphanumerics.inverted)
.joined()
}
private func refreshInstalledApps() {
let now = Date().timeIntervalSince1970
guard (now - lastAppsScanTime) > 60 || cachedInstalledApps.isEmpty else { return }
lastAppsScanTime = now
var apps: [InstalledAppInfo] = []
let searchDirs = [
URL(fileURLWithPath: "/Applications"),
URL(fileURLWithPath: "/Applications/Utilities"),
URL(fileURLWithPath: "/System/Applications"),
URL(fileURLWithPath: "/System/Applications/Utilities"),
FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Applications")
]
let fm = FileManager.default
for dir in searchDirs {
guard let contents = try? fm.contentsOfDirectory(at: dir, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]) else { continue }
for item in contents where item.pathExtension == "app" {
let name = item.deletingPathExtension().lastPathComponent
let normalized = normalizeForMatching(name)
apps.append(InstalledAppInfo(name: name, normalizedName: normalized, url: item))
}
}
cachedInstalledApps = apps
}
public func switchToTab(number: Int) {
let keyCodes: [Int: CGKeyCode] = [
1: 18, 2: 19, 3: 20, 4: 21, 5: 23,
@ -432,4 +538,12 @@ public final class VoiceCommandExecutor {
}
}
}
private func playWarningSound() {
if SonioxSettings.shared.playSounds {
if !(NSSound(named: NSSound.Name("Basso"))?.play() ?? false) {
NSSound(named: NSSound.Name("Pop"))?.play()
}
}
}
}
Loading…
Cancel
Save