Browse Source

feat: auto full-screen and instant focus for voice-launched and activated applications

main
Ali Alavi 16 hours ago
parent
commit
87756b7f74
  1. 142
      src/VoiceCommandExecutor.swift

142
src/VoiceCommandExecutor.swift

@ -1022,10 +1022,17 @@ public final class VoiceCommandExecutor {
let ws = NSWorkspace.shared
if let running = ws.runningApplications.first(where: { $0.bundleURL == url }) {
running.activate(options: [.activateIgnoringOtherApps])
ensureWindowFullScreen(app: running)
} else {
let conf = NSWorkspace.OpenConfiguration()
conf.activates = true
ws.openApplication(at: url, configuration: conf, completionHandler: nil)
ws.openApplication(at: url, configuration: conf) { app, _ in
if let launched = app {
DispatchQueue.main.async {
self.pollAndMakeFullScreen(app: launched, targetBid: launched.bundleIdentifier, appName: name)
}
}
}
}
HistoryManager.shared.addEntry("⚡️ [App] Open \(name)")
}
@ -1084,10 +1091,10 @@ public final class VoiceCommandExecutor {
}
}
private func launchOrActivateApp(named appName: String) {
private func launchOrActivateApp(named appName: String, completion: (() -> Void)? = nil) {
let ws = NSWorkspace.shared
// 1. Precise bundle identifier map (prevents "Antigravity" substring matching "Antigravity IDE")
// 1. Precise bundle identifier map
let bundleIDMap: [String: String] = [
"Antigravity": "com.google.antigravity",
"Antigravity IDE": "com.google.antigravity-ide",
@ -1097,40 +1104,135 @@ public final class VoiceCommandExecutor {
"Safari": "com.apple.Safari"
]
if let bid = bundleIDMap[appName] {
// Check currently running app by exact bundleIdentifier
if let running = ws.runningApplications.first(where: { $0.bundleIdentifier == bid }) {
let targetBid = bundleIDMap[appName]
// Check if currently running
let runningApp = ws.runningApplications.first(where: {
if let bid = targetBid, $0.bundleIdentifier == bid { return true }
return $0.localizedName?.caseInsensitiveCompare(appName) == .orderedSame
})
if let running = runningApp {
running.activate(options: [.activateIgnoringOtherApps])
ensureWindowFullScreen(app: running)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
running.activate(options: [.activateIgnoringOtherApps])
completion?()
}
return
}
// If not running, launch via exact URL
if let appURL = ws.urlForApplication(withBundleIdentifier: bid) {
// Cold launch if app was closed
if let bid = targetBid, let appURL = ws.urlForApplication(withBundleIdentifier: bid) {
let conf = NSWorkspace.OpenConfiguration()
conf.activates = true
ws.openApplication(at: appURL, configuration: conf, completionHandler: nil)
return
ws.openApplication(at: appURL, configuration: conf) { app, _ in
DispatchQueue.main.async {
self.pollAndMakeFullScreen(app: app, targetBid: bid, appName: appName, completion: completion)
}
}
// 2. Strict EXACT localized name match (NOT contains!)
if let running = ws.runningApplications.first(where: {
$0.localizedName?.caseInsensitiveCompare(appName) == .orderedSame
}) {
running.activate(options: [.activateIgnoringOtherApps])
return
}
// 3. Fallback: launch via /usr/bin/open -a with exact app name
// Fallback: launch via /usr/bin/open -a
let task = Process()
task.executableURL = URL(fileURLWithPath: "/usr/bin/open")
task.arguments = ["-a", appName]
try? task.run()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.pollAndMakeFullScreen(app: nil, targetBid: targetBid, appName: appName, completion: completion)
}
}
private func activateApp(named appName: String, completion: @escaping () -> Void) {
launchOrActivateApp(named: appName)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.22) {
completion()
launchOrActivateApp(named: appName, completion: completion)
}
@discardableResult
private func ensureWindowFullScreen(app: NSRunningApplication) -> Bool {
let appElem = AXUIElementCreateApplication(app.processIdentifier)
var windowObj: CFTypeRef?
let hasWin = (AXUIElementCopyAttributeValue(appElem, kAXFocusedWindowAttribute as CFString, &windowObj) == .success && windowObj != nil) ||
(AXUIElementCopyAttributeValue(appElem, kAXMainWindowAttribute as CFString, &windowObj) == .success && windowObj != nil)
guard hasWin, let winElem = windowObj else {
var winListObj: CFTypeRef?
if AXUIElementCopyAttributeValue(appElem, kAXWindowsAttribute as CFString, &winListObj) == .success,
let winList = winListObj as? [AXUIElement], let firstWin = winList.first {
return applyFullScreen(to: firstWin, app: app)
}
return false
}
return applyFullScreen(to: winElem as! AXUIElement, app: app)
}
private func applyFullScreen(to axWin: AXUIElement, app: NSRunningApplication) -> Bool {
// 1. Check if already full screen
var isFullObj: CFTypeRef?
if AXUIElementCopyAttributeValue(axWin, "AXFullScreen" as CFString, &isFullObj) == .success,
let isFull = isFullObj as? Bool, isFull {
return true
}
// 2. Set native macOS full-screen space attribute
let setRes = AXUIElementSetAttributeValue(axWin, "AXFullScreen" as CFString, kCFBooleanTrue)
if setRes == .success {
return true
}
// 3. Fallback: Zoom button (kAXFullScreenButton)
var zoomObj: CFTypeRef?
if AXUIElementCopyAttributeValue(axWin, "AXFullScreenButton" as CFString, &zoomObj) == .success, let btn = zoomObj {
AXUIElementPerformAction(btn as! AXUIElement, kAXPressAction as CFString)
return true
}
// 4. Fallback: Cmd+Ctrl+F shortcut
app.activate(options: [.activateIgnoringOtherApps])
pressHotkey(keyCode: 3, modifiers: [.maskCommand, .maskControl]) // 'F' + Cmd + Ctrl
return true
}
private func pollAndMakeFullScreen(
app: NSRunningApplication?,
targetBid: String?,
appName: String,
attempt: Int = 1,
completion: (() -> Void)? = nil
) {
let ws = NSWorkspace.shared
let target = app ?? ws.runningApplications.first(where: {
if let bid = targetBid, $0.bundleIdentifier == bid { return true }
return $0.localizedName?.caseInsensitiveCompare(appName) == .orderedSame
})
if let target = target {
target.activate(options: [.activateIgnoringOtherApps])
if ensureWindowFullScreen(app: target) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
target.activate(options: [.activateIgnoringOtherApps])
completion?()
}
return
}
}
if attempt < 18 {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.pollAndMakeFullScreen(
app: target,
targetBid: targetBid,
appName: appName,
attempt: attempt + 1,
completion: completion
)
}
} else {
target?.activate(options: [.activateIgnoringOtherApps])
completion?()
}
}

Loading…
Cancel
Save