Browse Source

feat: pro voice commands engine: window tiling, web search, custom json commands, cheat sheet, and deep system controls

main
Ali Alavi 14 hours ago
parent
commit
894719b08e
  1. 140
      src/CustomCommandManager.swift
  2. 89
      src/StatusBarController.swift
  3. 387
      src/VoiceCommandExecutor.swift

140
src/CustomCommandManager.swift

@ -0,0 +1,140 @@
import Foundation
import Cocoa
public struct CustomCommand: Codable {
public let triggers: [String]
public let action: String // "open_url", "open_app", "shell"
public let value: String // URL, App Name, or Shell command string
public let title: String?
public init(triggers: [String], action: String, value: String, title: String? = nil) {
self.triggers = triggers
self.action = action
self.value = value
self.title = title
}
}
public final class CustomCommandManager {
public static let shared = CustomCommandManager()
public let fileURL: URL
private var commands: [CustomCommand] = []
private let queue = DispatchQueue(label: "com.soniox.customcommands", qos: .utility)
private init() {
let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
let dir = appSupport.appendingPathComponent("SonioxVoice")
try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
self.fileURL = dir.appendingPathComponent("custom_commands.json")
ensureDefaultFile()
loadCommands()
}
private func ensureDefaultFile() {
guard !FileManager.default.fileExists(atPath: fileURL.path) else { return }
let sampleCommands: [CustomCommand] = [
CustomCommand(
triggers: ["گیت ما", "گیت شرکت", "our git", "company git"],
action: "open_url",
value: "https://git.nwhco.ir",
title: "Open NewHorizon Git"
),
CustomCommand(
triggers: ["گیت هاب", "github"],
action: "open_url",
value: "https://github.com",
title: "Open GitHub"
),
CustomCommand(
triggers: ["چت جی پی تی", "chatgpt"],
action: "open_url",
value: "https://chatgpt.com",
title: "Open ChatGPT"
),
CustomCommand(
triggers: ["کلود", "claude"],
action: "open_url",
value: "https://claude.ai",
title: "Open Claude"
),
CustomCommand(
triggers: ["ترمینال جدید", "new terminal"],
action: "open_app",
value: "Terminal",
title: "Open Terminal"
)
]
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
if let data = try? encoder.encode(sampleCommands) {
try? data.write(to: fileURL, options: .atomic)
}
}
public func loadCommands() {
queue.async {
guard let data = try? Data(contentsOf: self.fileURL),
let loaded = try? JSONDecoder().decode([CustomCommand].self, from: data) else {
return
}
self.commands = loaded
print("CustomCommandManager: Loaded \(loaded.count) custom voice commands.")
}
}
public func findMatchingCommand(query: String) -> CustomCommand? {
let clean = query.lowercased().trimmingCharacters(in: .whitespacesAndNewlines)
return queue.sync {
for cmd in commands {
for trigger in cmd.triggers {
let trigLower = trigger.lowercased().trimmingCharacters(in: .whitespacesAndNewlines)
if clean == trigLower || clean.hasPrefix(trigLower) || clean.contains(trigLower) {
return cmd
}
}
}
return nil
}
}
public func executeCommand(_ cmd: CustomCommand) -> Bool {
print("CustomCommandManager: Executing custom action '\(cmd.action)' for value '\(cmd.value)'")
let title = cmd.title ?? "\(cmd.action): \(cmd.value)"
switch cmd.action.lowercased() {
case "open_url":
if let url = URL(string: cmd.value) {
NSWorkspace.shared.open(url)
HistoryManager.shared.addEntry("⚡️ [Custom] \(title)")
return true
}
case "open_app":
let task = Process()
task.executableURL = URL(fileURLWithPath: "/usr/bin/open")
task.arguments = ["-a", cmd.value]
try? task.run()
HistoryManager.shared.addEntry("⚡️ [Custom] Open \(cmd.value)")
return true
case "shell":
let task = Process()
task.executableURL = URL(fileURLWithPath: "/bin/sh")
task.arguments = ["-c", cmd.value]
try? task.run()
HistoryManager.shared.addEntry("⚡️ [Custom] Shell: \(title)")
return true
default:
return false
}
return false
}
public func openConfigFileInEditor() {
NSWorkspace.shared.open(fileURL)
}
}

89
src/StatusBarController.swift

@ -194,6 +194,20 @@ public final class StatusBarController: NSObject, NSMenuDelegate {
soundItem.state = SonioxSettings.shared.playSounds ? .on : .off
advancedMenu.addItem(soundItem)
advancedMenu.addItem(NSMenuItem.separator())
let editCmdsItem = NSMenuItem(title: "📝 Customize Voice Commands (JSON)...", action: #selector(editCustomCommandsAction), keyEquivalent: "")
editCmdsItem.target = self
advancedMenu.addItem(editCmdsItem)
let reloadCmdsItem = NSMenuItem(title: "🔄 Reload Custom Commands", action: #selector(reloadCustomCommandsAction), keyEquivalent: "")
reloadCmdsItem.target = self
advancedMenu.addItem(reloadCmdsItem)
let cheatSheetItem = NSMenuItem(title: "📖 Voice Commands Cheat Sheet...", action: #selector(openCheatSheetAction), keyEquivalent: "")
cheatSheetItem.target = self
advancedMenu.addItem(cheatSheetItem)
let advancedMenuItem = NSMenuItem(title: "🛠️ Advanced Options", action: nil, keyEquivalent: "")
advancedMenuItem.submenu = advancedMenu
menu.addItem(advancedMenuItem)
@ -386,6 +400,81 @@ public final class StatusBarController: NSObject, NSMenuDelegate {
buildMenu()
}
@objc private func editCustomCommandsAction() {
CustomCommandManager.shared.openConfigFileInEditor()
}
@objc private func reloadCustomCommandsAction() {
CustomCommandManager.shared.loadCommands()
buildMenu()
let alert = NSAlert()
alert.messageText = "Custom Commands Reloaded"
alert.informativeText = "Successfully reloaded custom voice commands from custom_commands.json."
alert.alertStyle = .informational
alert.addButton(withTitle: "OK")
alert.runModal()
}
@objc private func openCheatSheetAction() {
let helpText = """
=== Soniox Voice Commands Cheat Sheet ===
[Applications & Windows]
/settings OR /ستینگ -> Open macOS Settings
/antigravity ide OR /آنتی گرویتی آیدی ای -> Open Antigravity IDE
/antigravity OR /آنتی گرویتی -> Open Antigravity
/firefox OR /فایرفاکس -> Open Firefox
/chrome OR /کروم -> Open Google Chrome
/telegram OR /تلگرام -> Open Telegram
/left OR /چپ -> Tile window to Left half
/right OR /راست -> Tile window to Right half
/maximize OR /تمام صفحه -> Maximize window
/center OR /وسط -> Center window on screen
/hide OR /هاید -> Hide frontmost app
/quit OR /ببند -> Quit frontmost app
[Browser & Tabs]
/firefox tab 1 OR /فایرفاکس تب ۱ -> Jump to Firefox Tab 1
/tab 1..9 OR /تب ۱..۹ -> Jump to Tab 1..9 in active app
/new tab OR /تب جدید -> Open new tab (Cmd+T)
/close tab OR /بستن تب -> Close active tab (Cmd+W)
/reopen tab OR /ری اپن تب -> Reopen closed tab (Cmd+Shift+T)
/next tab / /prev tab -> Switch tabs
/reload OR /رفرش -> Reload web page (Cmd+R)
/search [query] OR /سرچ [موضوع] -> Search on Google
/youtube [query] OR /یوتیوب [موضوع] -> Search on YouTube
[System Controls]
/volume up OR /صدا زیاد -> Increase volume (+15%)
/volume down OR /صدا کم -> Decrease volume (-15%)
/volume 50 OR /صدا ۵۰ -> Set volume to 50%
/mute OR /میوت -> Toggle sound mute
/lock OR /قفل -> Lock screen
/screenshot OR /اسکرین شات -> Interactive screenshot
/dark mode OR /دارک مود -> Toggle Dark / Light mode
[Editing & Clipboard]
/copy OR /کپی -> Copy selection (Cmd+C)
/paste OR /پیست -> Paste (Cmd+V)
/cut OR /کات -> Cut selection (Cmd+X)
/select all OR /انتخاب همه -> Select All (Cmd+A)
/undo OR /آندو -> Undo (Cmd+Z)
/redo OR /ریدو -> Redo (Cmd+Shift+Z)
/save OR /سیو -> Save document (Cmd+S)
/enter OR /اینتر -> Press Return key
[Custom Voice Commands]
Customize anytime via 'custom_commands.json' under Advanced Options!
"""
let alert = NSAlert()
alert.messageText = "Soniox Voice Commands Reference"
alert.informativeText = helpText
alert.alertStyle = .informational
alert.addButton(withTitle: "Got it!")
alert.runModal()
}
@objc private func toggleLaunchAtLoginAction(_ sender: NSMenuItem) {
SonioxSettings.shared.launchAtLogin.toggle()
buildMenu()

387
src/VoiceCommandExecutor.swift

@ -1,5 +1,6 @@
import Cocoa
import Carbon
import ApplicationServices
public struct InstalledAppInfo {
public let name: String
@ -7,6 +8,15 @@ public struct InstalledAppInfo {
public let url: URL
}
public enum WindowTileAction {
case left
case right
case top
case bottom
case maximize
case center
}
public final class VoiceCommandExecutor {
public static let shared = VoiceCommandExecutor()
@ -15,7 +25,7 @@ public final class VoiceCommandExecutor {
// Comprehensive Persian to Canonical English Application Names
private let persianAppDictionary: [String: String] = [
// System & Apple Apps
// System & Apple Core Apps
"ستینگ": "System Settings",
"ستینگز": "System Settings",
"تنظیمات": "System Settings",
@ -137,31 +147,51 @@ public final class VoiceCommandExecutor {
print("VoiceCommandExecutor: executing command for body: '\(body)'")
// 1. App + Tab combinations (e.g. "فایرفاکس تب ۱", "کروم تب ۲", "firefox tab 1", "chrome tab 3")
// 1. User-Customized Commands (from custom_commands.json)
if let customCmd = CustomCommandManager.shared.findMatchingCommand(query: body) {
if CustomCommandManager.shared.executeCommand(customCmd) {
playConfirmationSound()
return true
}
}
// 2. Web Search & Direct Navigation ("سرچ X", "search X", "یوتیوب X", "youtube X")
if handleWebSearchAndNavigation(lower: lower, original: body) {
playConfirmationSound()
return true
}
// 3. Window Tiling & Management (چپ, راست, بالا, پایین, تمام صفحه, وسط, مینیمایز, هاید)
if handleWindowTiling(lower: lower) {
playConfirmationSound()
return true
}
// 4. App + Tab combinations (e.g. "فایرفاکس تب ۱", "کروم تب ۲", "firefox tab 1", "chrome tab 3")
if handleAppWithTab(lower: lower, original: body) {
playConfirmationSound()
return true
}
// 2. Standalone Tab & Navigation commands (e.g. "تب ۱", "تب جدید", "بستن تب", "tab 1", "new tab")
// 5. Standalone Tab & Navigation commands (e.g. "تب ۱", "تب جدید", "بستن تب", "tab 1", "new tab")
if handleStandaloneTab(lower: lower) {
playConfirmationSound()
return true
}
// 3. System controls (volume, mute, lock, screenshot, dark mode, clipboard)
// 6. System controls (volume, mute, lock, screenshot, dark mode, clipboard)
if handleSystemControls(lower: lower) {
playConfirmationSound()
return true
}
// 4. Match App from Persian dictionary or exact names
// 7. Match App from Persian dictionary or exact names
if handleKnownApp(lower: lower, original: body) {
playConfirmationSound()
return true
}
// 5. Universal fuzzy resolver across all installed apps in /Applications and /System/Applications
// 8. Universal fuzzy resolver across all installed apps in /Applications and /System/Applications
if handleUniversalInstalledApp(query: body) {
playConfirmationSound()
return true
@ -174,6 +204,190 @@ public final class VoiceCommandExecutor {
return false
}
// MARK: - 1. Web Search & Direct Navigation
private func handleWebSearchAndNavigation(lower: String, original: String) -> Bool {
// Google Search
if lower.hasPrefix("سرچ ") || lower.hasPrefix("جستجو ") || lower.hasPrefix("search ") || lower.hasPrefix("google ") {
var q = original
for p in ["سرچ ", "جستجو ", "search ", "google "] {
if q.lowercased().hasPrefix(p) {
q = String(q.dropFirst(p.count)).trimmingCharacters(in: .whitespacesAndNewlines)
break
}
}
if !q.isEmpty, let enc = q.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
let url = URL(string: "https://www.google.com/search?q=\(enc)") {
NSWorkspace.shared.open(url)
HistoryManager.shared.addEntry("⚡️ [Search] Google: \(q)")
return true
}
}
// YouTube Search
if lower.hasPrefix("یوتیوب ") || lower.hasPrefix("youtube ") {
var q = original
for p in ["یوتیوب ", "youtube "] {
if q.lowercased().hasPrefix(p) {
q = String(q.dropFirst(p.count)).trimmingCharacters(in: .whitespacesAndNewlines)
break
}
}
if !q.isEmpty, let enc = q.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
let url = URL(string: "https://www.youtube.com/results?search_query=\(enc)") {
NSWorkspace.shared.open(url)
HistoryManager.shared.addEntry("⚡️ [Search] YouTube: \(q)")
return true
}
}
// Direct URL navigation ("برو به X" / "go to X")
if lower.hasPrefix("برو به ") || lower.hasPrefix("goto ") || lower.hasPrefix("go to ") {
var target = original
for p in ["برو به ", "goto ", "go to "] {
if target.lowercased().hasPrefix(p) {
target = String(target.dropFirst(p.count)).trimmingCharacters(in: .whitespacesAndNewlines)
break
}
}
if !target.isEmpty {
var urlStr = target
if !urlStr.contains("://") {
urlStr = "https://" + urlStr
}
if let url = URL(string: urlStr) {
NSWorkspace.shared.open(url)
HistoryManager.shared.addEntry("⚡️ [Nav] Go to: \(target)")
return true
}
}
}
return false
}
// MARK: - 2. Window Tiling & Management
private func handleWindowTiling(lower: String) -> Bool {
// Snap Left
if lower == "چپ" || lower.contains("پنجره چپ") || lower == "left" || lower == "window left" {
tileFrontmostWindow(action: .left)
HistoryManager.shared.addEntry("⚡️ [Window] Tile Left Half")
return true
}
// Snap Right
if lower == "راست" || lower.contains("پنجره راست") || lower == "right" || lower == "window right" {
tileFrontmostWindow(action: .right)
HistoryManager.shared.addEntry("⚡️ [Window] Tile Right Half")
return true
}
// Snap Top
if lower == "بالا" || lower.contains("پنجره بالا") || lower == "top" || lower == "window top" {
tileFrontmostWindow(action: .top)
HistoryManager.shared.addEntry("⚡️ [Window] Tile Top Half")
return true
}
// Snap Bottom
if lower == "پایین" || lower.contains("پنجره پایین") || lower == "bottom" || lower == "window bottom" {
tileFrontmostWindow(action: .bottom)
HistoryManager.shared.addEntry("⚡️ [Window] Tile Bottom Half")
return true
}
// Maximize / Full Screen
if lower.contains("تمام صفحه") || lower.contains("فول اسکرین") || lower.contains("ماکزیمایز") || lower == "maximize" || lower == "full screen" {
tileFrontmostWindow(action: .maximize)
HistoryManager.shared.addEntry("⚡️ [Window] Maximize Window")
return true
}
// Center Window
if lower == "وسط" || lower == "مرکز" || lower.contains("پنجره وسط") || lower == "center" || lower == "window center" {
tileFrontmostWindow(action: .center)
HistoryManager.shared.addEntry("⚡️ [Window] Center Window")
return true
}
// Minimize
if lower == "مینیمایز" || lower.contains("کوچک کردن") || lower == "minimize" {
pressHotkey(keyCode: 46, modifiers: .maskCommand) // Cmd+M
HistoryManager.shared.addEntry("⚡️ [Window] Minimize")
return true
}
// Hide Frontmost App
if lower == "هاید" || lower == "مخفی" || lower.contains("مخفی کن") || lower == "hide" {
pressHotkey(keyCode: 4, modifiers: .maskCommand) // Cmd+H
HistoryManager.shared.addEntry("⚡️ [Window] Hide App")
return true
}
// Hide Others
if lower.contains("هاید بقیه") || lower.contains("مخفی کردن بقیه") || lower == "hide others" {
pressHotkey(keyCode: 4, modifiers: [.maskCommand, .maskAlternate]) // Cmd+Option+H
HistoryManager.shared.addEntry("⚡️ [Window] Hide Other Apps")
return true
}
// Close Window
if lower == "بستن پنجره" || lower == "کلوز پنجره" || lower == "close window" {
pressHotkey(keyCode: 13, modifiers: .maskCommand) // Cmd+W
HistoryManager.shared.addEntry("⚡️ [Window] Close Window")
return true
}
// Quit App
if lower == "ببند" || lower.contains("خروج از برنامه") || lower.contains("کویت") || lower == "quit" || lower == "quit app" {
pressHotkey(keyCode: 12, modifiers: .maskCommand) // Cmd+Q
HistoryManager.shared.addEntry("⚡️ [Window] Quit App")
return true
}
return false
}
private func tileFrontmostWindow(action: WindowTileAction) {
guard let frontApp = NSWorkspace.shared.frontmostApplication,
let screen = NSScreen.main else { return }
let appElem = AXUIElementCreateApplication(frontApp.processIdentifier)
var windowObj: CFTypeRef?
guard AXUIElementCopyAttributeValue(appElem, kAXFocusedWindowAttribute as CFString, &windowObj) == .success,
let winElem = windowObj else { return }
let axWin = winElem as! AXUIElement
let screenFrame = screen.visibleFrame
let fullH = NSScreen.screens.first?.frame.height ?? screen.frame.height
let axY = fullH - (screenFrame.origin.y + screenFrame.size.height)
var newPos = CGPoint.zero
var newSize = CGSize.zero
switch action {
case .left:
newPos = CGPoint(x: screenFrame.origin.x, y: axY)
newSize = CGSize(width: screenFrame.width / 2, height: screenFrame.height)
case .right:
newPos = CGPoint(x: screenFrame.origin.x + screenFrame.width / 2, y: axY)
newSize = CGSize(width: screenFrame.width / 2, height: screenFrame.height)
case .top:
newPos = CGPoint(x: screenFrame.origin.x, y: axY)
newSize = CGSize(width: screenFrame.width, height: screenFrame.height / 2)
case .bottom:
newPos = CGPoint(x: screenFrame.origin.x, y: axY + screenFrame.height / 2)
newSize = CGSize(width: screenFrame.width, height: screenFrame.height / 2)
case .maximize:
newPos = CGPoint(x: screenFrame.origin.x, y: axY)
newSize = CGSize(width: screenFrame.width, height: screenFrame.height)
case .center:
let w = min(1100, screenFrame.width * 0.85)
let h = min(750, screenFrame.height * 0.85)
newPos = CGPoint(x: screenFrame.origin.x + (screenFrame.width - w) / 2, y: axY + (screenFrame.height - h) / 2)
newSize = CGSize(width: w, height: h)
}
var size = newSize
var pos = newPos
if let sVal = AXValueCreate(.cgSize, &size) {
AXUIElementSetAttributeValue(axWin, kAXSizeAttribute as CFString, sVal)
}
if let pVal = AXValueCreate(.cgPoint, &pos) {
AXUIElementSetAttributeValue(axWin, kAXPositionAttribute as CFString, pVal)
}
}
// MARK: - 3. App + Tab Combinations
private func handleAppWithTab(lower: String, original: String) -> Bool {
guard lower.contains("تب") || lower.contains("tab") else { return false }
@ -198,18 +412,24 @@ public final class VoiceCommandExecutor {
}
HistoryManager.shared.addEntry("⚡️ [Command] \(appName): Tab \(tabNum)")
return true
} else if lower.contains("تب جدید") || lower.contains("نیو تب") || lower.contains("new tab") {
} else if 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("کلوز تب") || lower.contains("close tab") {
} else if lower.contains("بستن تب") || lower.contains("close tab") {
activateApp(named: appName) {
self.pressHotkey(keyCode: 13, modifiers: .maskCommand) // Cmd+W
}
HistoryManager.shared.addEntry("⚡️ [Command] \(appName): Close Tab")
return true
} else if lower.contains("ری اپن") || lower.contains("reopen tab") || lower.contains("باز کردن مجدد تب") {
activateApp(named: appName) {
self.pressHotkey(keyCode: 17, modifiers: [.maskCommand, .maskShift]) // Cmd+Shift+T
}
HistoryManager.shared.addEntry("⚡️ [Command] \(appName): Reopen Tab")
return true
}
return false
@ -217,47 +437,38 @@ public final class VoiceCommandExecutor {
private func extractTabNumber(from text: String) -> Int? {
let t = text.lowercased()
// 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
}
// 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
}
// 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
}
// Tab 4
if t.contains("تب ۴") || t.contains("تب 4") || t.contains("تب چهار") || t.contains("تب چهارم") ||
t.contains("tab 4") || t.contains("tab four") {
return 4
}
// Tab 5
if t.contains("تب ۵") || t.contains("تب 5") || t.contains("تب پنج") || t.contains("تب پنجم") ||
t.contains("tab 5") || t.contains("tab five") {
return 5
}
// Tab 6
if t.contains("تب ۶") || t.contains("تب 6") || t.contains("تب شش") || t.contains("تب ششم") ||
t.contains("tab 6") || t.contains("tab six") {
return 6
}
// Tab 7
if t.contains("تب ۷") || t.contains("تب 7") || t.contains("تب هفت") || t.contains("تب هفتم") ||
t.contains("tab 7") || t.contains("tab seven") {
return 7
}
// Tab 8
if t.contains("تب ۸") || t.contains("تب 8") || t.contains("تب هشت") || t.contains("تب هشتم") ||
t.contains("tab 8") || t.contains("tab eight") {
return 8
}
// 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
@ -265,6 +476,7 @@ public final class VoiceCommandExecutor {
return nil
}
// MARK: - 4. Standalone Tab Commands
private func handleStandaloneTab(lower: String) -> Bool {
if let num = extractTabNumber(from: lower) {
switchToTab(number: num)
@ -281,6 +493,11 @@ public final class VoiceCommandExecutor {
HistoryManager.shared.addEntry("⚡️ [Command] Close Tab")
return true
}
if lower.contains("ری اپن") || lower.contains("reopen tab") || lower.contains("باز کردن مجدد تب") {
pressHotkey(keyCode: 17, modifiers: [.maskCommand, .maskShift]) // Cmd+Shift+T
HistoryManager.shared.addEntry("⚡️ [Command] Reopen Closed Tab")
return true
}
if lower.contains("تب بعد") || lower.contains("تب بعدی") || lower.contains("next tab") {
pressHotkey(keyCode: 48, modifiers: .maskControl) // Ctrl+Tab
HistoryManager.shared.addEntry("⚡️ [Command] Next Tab")
@ -296,91 +513,175 @@ public final class VoiceCommandExecutor {
HistoryManager.shared.addEntry("⚡️ [Command] Refresh Page")
return true
}
if lower.contains("هارد رفرش") || lower.contains("hard refresh") {
pressHotkey(keyCode: 15, modifiers: [.maskCommand, .maskShift]) // Cmd+Shift+R
HistoryManager.shared.addEntry("⚡️ [Command] Hard Refresh Page")
return true
}
if lower.contains("آدرس بار") || lower.contains("ادرس بار") || lower.contains("address bar") {
pressHotkey(keyCode: 37, modifiers: .maskCommand) // Cmd+L
HistoryManager.shared.addEntry("⚡️ [Command] Focus Address Bar")
return true
}
return false
}
// MARK: - 5. System Controls
private func handleSystemControls(lower: String) -> Bool {
// Volume controls
// Volume exact levels
if lower.contains("صدا ۱۰۰") || lower.contains("صدا 100") || lower == "volume 100" || lower == "max volume" {
runAppleScript("set volume output volume 100")
HistoryManager.shared.addEntry("⚡️ [Audio] Volume 100%")
return true
}
if lower.contains("صدا ۵۰") || lower.contains("صدا 50") || lower == "volume 50" {
runAppleScript("set volume output volume 50")
HistoryManager.shared.addEntry("⚡️ [Audio] Volume 50%")
return true
}
if lower.contains("صدا صفر") || lower.contains("صدا 0") || lower == "volume 0" {
runAppleScript("set volume output volume 0")
HistoryManager.shared.addEntry("⚡️ [Audio] Volume 0%")
return true
}
// Volume up / down
if lower.contains("صدا زیاد") || lower.contains("افزایش صدا") || lower.contains("volume up") {
runAppleScript("set volume output volume ((output volume of (get volume settings)) + 15)")
HistoryManager.shared.addEntry("⚡️ [Command] Volume Up")
HistoryManager.shared.addEntry("⚡️ [Audio] Volume Up")
return true
}
if lower.contains("صدا کم") || lower.contains("کاهش صدا") || lower.contains("volume down") {
runAppleScript("set volume output volume ((output volume of (get volume settings)) - 15)")
HistoryManager.shared.addEntry("⚡️ [Command] Volume Down")
HistoryManager.shared.addEntry("⚡️ [Audio] Volume Down")
return true
}
if lower.contains("میوت") || lower.contains("بی صدا") || lower.contains("بی‌صدا") || lower.contains("mute") {
if lower.contains("میوت") || lower.contains("بی صدا") || lower.contains("بی‌صدا") || lower == "mute" {
runAppleScript("set volume output muted not (output muted of (get volume settings))")
HistoryManager.shared.addEntry("⚡️ [Command] Toggle Mute")
HistoryManager.shared.addEntry("⚡️ [Audio] Toggle Mute")
return true
}
// Media Play / Pause / Next / Prev
if lower == "پلی" || lower == "پاز" || lower == "توقف" || lower == "play" || lower == "pause" || lower == "play pause" {
runAppleScript("tell application \"System Events\" to key code 16 using {}")
HistoryManager.shared.addEntry("⚡️ [Media] Play / Pause")
return true
}
if lower.contains("آهنگ بعد") || lower.contains("اهنگ بعد") || lower == "next track" {
runAppleScript("tell application \"System Events\" to key code 19 using {}")
HistoryManager.shared.addEntry("⚡️ [Media] Next Track")
return true
}
if lower.contains("آهنگ قبل") || lower.contains("اهنگ قبل") || lower == "prev track" {
runAppleScript("tell application \"System Events\" to key code 20 using {}")
HistoryManager.shared.addEntry("⚡️ [Media] Previous Track")
return true
}
// Lock screen
// Lock Screen
if lower.contains("قفل") || lower.contains("لاک") || lower.contains("lock screen") || lower == "lock" {
let task = Process()
task.executableURL = URL(fileURLWithPath: "/usr/bin/pmset")
task.arguments = ["displaysleepnow"]
try? task.run()
HistoryManager.shared.addEntry("⚡️ [Command] Lock Screen")
HistoryManager.shared.addEntry("⚡️ [System] Lock Screen")
return true
}
// Screenshot
if lower.contains("اسکرین شات") || lower.contains("عکس صفحه") || lower.contains("screenshot") {
// Sleep
if lower == "اسلیپ" || lower == "sleep" {
let task = Process()
task.executableURL = URL(fileURLWithPath: "/usr/bin/pmset")
task.arguments = ["sleepnow"]
try? task.run()
HistoryManager.shared.addEntry("⚡️ [System] Sleep Mac")
return true
}
// Screenshot (Interactive)
if lower.contains("اسکرین شات") || lower.contains("عکس صفحه") || lower == "screenshot" {
let task = Process()
task.executableURL = URL(fileURLWithPath: "/usr/sbin/screencapture")
task.arguments = ["-i", "-c"]
try? task.run()
HistoryManager.shared.addEntry("⚡️ [Command] Interactive Screenshot")
HistoryManager.shared.addEntry("⚡️ [System] Interactive Screenshot")
return true
}
// Full Screenshot
if lower.contains("اسکرین شات فول") || lower.contains("اسکرین شات کامل") || lower == "full screenshot" {
let task = Process()
task.executableURL = URL(fileURLWithPath: "/usr/sbin/screencapture")
task.arguments = ["-c"]
try? task.run()
HistoryManager.shared.addEntry("⚡️ [System] Full Screenshot to Clipboard")
return true
}
// Dark Mode toggle
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")
HistoryManager.shared.addEntry("⚡️ [System] Toggle Dark Mode")
return true
}
// Clipboard & Editing hotkeys
if lower == "کپی" || lower == "copy" {
pressHotkey(keyCode: 8, modifiers: .maskCommand) // Cmd+C
HistoryManager.shared.addEntry("⚡️ [Command] Copy")
HistoryManager.shared.addEntry("⚡️ [Edit] Copy")
return true
}
if lower == "پیست" || lower == "paste" {
pressHotkey(keyCode: 9, modifiers: .maskCommand) // Cmd+V
HistoryManager.shared.addEntry("⚡️ [Command] Paste")
HistoryManager.shared.addEntry("⚡️ [Edit] Paste")
return true
}
if lower == "کات" || lower == "cut" {
pressHotkey(keyCode: 7, modifiers: .maskCommand) // Cmd+X
HistoryManager.shared.addEntry("⚡️ [Command] Cut")
HistoryManager.shared.addEntry("⚡️ [Edit] Cut")
return true
}
if lower.contains("انتخاب همه") || lower.contains("سلکت آل") || lower == "select all" {
pressHotkey(keyCode: 0, modifiers: .maskCommand) // Cmd+A
HistoryManager.shared.addEntry("⚡️ [Command] Select All")
HistoryManager.shared.addEntry("⚡️ [Edit] Select All")
return true
}
if lower == "آندو" || lower == "برگرد" || lower == "undo" {
pressHotkey(keyCode: 6, modifiers: .maskCommand) // Cmd+Z
HistoryManager.shared.addEntry("⚡️ [Command] Undo")
HistoryManager.shared.addEntry("⚡️ [Edit] Undo")
return true
}
if lower == "ریدو" || lower == "redo" {
pressHotkey(keyCode: 6, modifiers: [.maskCommand, .maskShift]) // Cmd+Shift+Z
HistoryManager.shared.addEntry("⚡️ [Edit] Redo")
return true
}
if lower == "سیو" || lower == "ذخیره" || lower == "save" {
pressHotkey(keyCode: 1, modifiers: .maskCommand) // Cmd+S
HistoryManager.shared.addEntry("⚡️ [Edit] Save")
return true
}
if lower == "فایند" || lower == "پیدا کن" || lower == "find" {
pressHotkey(keyCode: 3, modifiers: .maskCommand) // Cmd+F
HistoryManager.shared.addEntry("⚡️ [Edit] Find")
return true
}
if lower == "اینتر" || lower == "enter" {
pressHotkey(keyCode: 36, modifiers: []) // Return key
HistoryManager.shared.addEntry("⚡️ [Command] Enter")
pressHotkey(keyCode: 36, modifiers: []) // Return
HistoryManager.shared.addEntry("⚡️ [Edit] Enter")
return true
}
if lower.contains("پاک کردن خط") || lower == "delete line" {
pressHotkey(keyCode: 51, modifiers: .maskCommand) // Cmd+Backspace
HistoryManager.shared.addEntry("⚡️ [Edit] Delete Line")
return true
}
return false
}
// MARK: - 6. Persian Dictionary Apps
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) {
@ -389,11 +690,10 @@ public final class VoiceCommandExecutor {
}
}
// 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)")
HistoryManager.shared.addEntry("⚡️ [App] Open \(englishAppName)")
return true
}
}
@ -401,6 +701,7 @@ public final class VoiceCommandExecutor {
return false
}
// MARK: - 7. Universal Installed App Scanner
private func handleUniversalInstalledApp(query: String) -> Bool {
var cleanQuery = query.trimmingCharacters(in: .whitespacesAndNewlines)
for prefix in ["باز کن ", "بازکن ", "open ", "launch "] {
@ -414,19 +715,17 @@ public final class VoiceCommandExecutor {
let normalizedQuery = normalizeForMatching(cleanQuery)
refreshInstalledApps()
// 1. Check exact normalized match
// Exact match
if let match = cachedInstalledApps.first(where: { $0.normalizedName == normalizedQuery }) {
launchApp(at: match.url, name: match.name)
return true
}
// 2. Check prefix match
// 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
// Substring match
if let match = cachedInstalledApps.first(where: { $0.normalizedName.contains(normalizedQuery) || normalizedQuery.contains($0.normalizedName) }) {
launchApp(at: match.url, name: match.name)
return true
@ -444,7 +743,7 @@ public final class VoiceCommandExecutor {
conf.activates = true
ws.openApplication(at: url, configuration: conf, completionHandler: nil)
}
HistoryManager.shared.addEntry("⚡️ [Command] Open \(name)")
HistoryManager.shared.addEntry("⚡️ [App] Open \(name)")
}
private func normalizeForMatching(_ text: String) -> String {

Loading…
Cancel
Save