diff --git a/src/VoiceCommandExecutor.swift b/src/VoiceCommandExecutor.swift index c2e3b1c..a34d113 100644 --- a/src/VoiceCommandExecutor.swift +++ b/src/VoiceCommandExecutor.swift @@ -419,19 +419,44 @@ public final class VoiceCommandExecutor { private func matchAntigravity(normalized: String) -> String? { let hasAnti = normalized.contains("انتی") || normalized.contains("anti") || normalized.contains("on the") || normalized.contains("in the") || normalized.contains("and the") - let hasGravity = normalized.contains("گراویتی") || normalized.contains("گرویتی") || normalized.contains("gravity") || normalized.contains("graphity") - guard (hasAnti && hasGravity) || normalized.contains("antigravity") || normalized.contains("انتیگراویتی") || normalized.contains("انتیگرویتی") else { + let hasGravity = normalized.contains("گراویتی") || + normalized.contains("گرویتی") || + normalized.contains("گریویتی") || + normalized.contains("گریتی") || + normalized.contains("گرایتی") || + normalized.contains("گروتی") || + normalized.contains("گویتی") || + normalized.contains("gravity") || + normalized.contains("graphity") || + normalized.contains("graviti") || + normalized.contains("gravty") + + let isFusedAntiGravity = normalized.contains("antigravity") || + normalized.contains("انتیگراویتی") || + normalized.contains("انتیگرویتی") || + normalized.contains("انتیگریویتی") || + normalized.contains("انتیگریتی") || + normalized.contains("انتیگرایتی") + + guard (hasAnti && hasGravity) || isFusedAntiGravity else { return nil } // Strict explicit check for IDE / Editor (Black Icon App) let isIDE = normalized.contains("ای دی ای") || normalized.contains("ایدی ای") || + normalized.contains("ای دی") || + normalized.contains("ایدی") || normalized.contains("ایدیای") || + normalized.contains("ایدیا") || normalized.contains("آیدی ای") || + normalized.contains("آی دی ای") || + normalized.contains("آیدی") || + normalized.contains("آی دی") || normalized.contains("ide") || - normalized.contains("ادیتور") + normalized.contains("ادیتور") || + normalized.contains("کد") // If IDE is mentioned -> "Antigravity IDE" (Black icon) // If standalone / "2" -> "Antigravity" (White icon app, Antigravity 2) @@ -640,9 +665,9 @@ public final class VoiceCommandExecutor { // 2. Word numbers (Persian & English cardinal and ordinal) let numberWords: [(names: [String], num: Int)] = [ - (["یک", "اول", "نخست", "one", "first"], 1), - (["دو", "دوم", "two", "second"], 2), - (["سه", "سوم", "three", "third"], 3), + (["یک", "اول", "نخست", "ی", "one", "first"], 1), + (["دو", "دوم", "د", "two", "second"], 2), + (["سه", "سوم", "س", "three", "third"], 3), (["چهار", "چهارم", "four", "fourth"], 4), (["پنج", "پنجم", "five", "fifth"], 5), (["شش", "ششم", "six", "sixth"], 6), @@ -768,7 +793,7 @@ public final class VoiceCommandExecutor { guard let code = keyCodes[number] else { return } // For Telegram Desktop: Send Cmd+[number] and Cmd+Shift+[number] (configured via shortcuts-custom.json) pressHotkey(keyCode: code, modifiers: .maskCommand) - DispatchQueue.main.asyncAfter(deadline: .now() + 0.12) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.02) { self.pressHotkey(keyCode: code, modifiers: [.maskCommand, .maskShift]) } } @@ -1080,15 +1105,13 @@ public final class VoiceCommandExecutor { } private func pressHotkey(keyCode: CGKeyCode, modifiers: CGEventFlags) { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.08) { - let source = CGEventSource(stateID: .combinedSessionState) - if let down = CGEvent(keyboardEventSource: source, virtualKey: keyCode, keyDown: true), - let up = CGEvent(keyboardEventSource: source, virtualKey: keyCode, keyDown: false) { - down.flags = modifiers - up.flags = modifiers - down.post(tap: .cghidEventTap) - up.post(tap: .cghidEventTap) - } + let source = CGEventSource(stateID: .combinedSessionState) + if let down = CGEvent(keyboardEventSource: source, virtualKey: keyCode, keyDown: true), + let up = CGEvent(keyboardEventSource: source, virtualKey: keyCode, keyDown: false) { + down.flags = modifiers + up.flags = modifiers + down.post(tap: .cghidEventTap) + up.post(tap: .cghidEventTap) } } @@ -1114,6 +1137,13 @@ public final class VoiceCommandExecutor { }) if let running = runningApp { + // Instant path: if the app is ALREADY frontmost (e.g. user is in Telegram switching accounts) + if running.isActive { + ensureWindowFullScreen(app: running) + completion?() + return + } + // Check if app has any open windows let appElem = AXUIElementCreateApplication(running.processIdentifier) var winListObj: CFTypeRef? @@ -1121,18 +1151,22 @@ public final class VoiceCommandExecutor { ((winListObj as? [AXUIElement])?.isEmpty == false) if hasWindows { - // Force macOS WindowServer to switch Spaces - runAppleScript("tell application \"\(appName)\" to activate") - let task = Process() - task.executableURL = URL(fileURLWithPath: "/usr/bin/open") - task.arguments = ["-a", appName] - try? task.run() - running.activate(options: [.activateIgnoringOtherApps]) + + // Force macOS WindowServer to switch Spaces asynchronously + DispatchQueue.global(qos: .userInteractive).async { + let task = Process() + task.executableURL = URL(fileURLWithPath: "/usr/bin/open") + task.arguments = ["-a", appName] + try? task.run() + } + ensureWindowFullScreen(app: running) - DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { - running.activate(options: [.activateIgnoringOtherApps]) - completion?() + + if let comp = completion { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.03) { + comp() + } } return } else { @@ -1144,7 +1178,7 @@ public final class VoiceCommandExecutor { task.arguments = ["-a", appName] try? task.run() - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.08) { self.pollAndMakeFullScreen(app: running, targetBid: targetBid, appName: appName, completion: completion) } return