/ internal / tools / axserver / Sources / FocusManager.swift
FocusManager.swift
 1  import AppKit
 2  
 3  struct FocusManager {
 4      /// Activates an app by name, optionally verifying focus.
 5      static func focusApp(appName: String, windowTitle: String?, verify: Bool) -> (ActionResult?, ErrorInfo?) {
 6          guard let app = findApp(named: appName) else {
 7              return (nil, ErrorInfo(code: -1, message: "App '\(appName)' not found or not running"))
 8          }
 9  
10          app.activate()
11  
12          if verify {
13              // Brief wait for activation
14              Thread.sleep(forTimeInterval: 0.3)
15              guard let frontmost = NSWorkspace.shared.frontmostApplication,
16                    frontmost.processIdentifier == app.processIdentifier else {
17                  return (nil, ErrorInfo(code: -1, message: "Failed to bring '\(appName)' to front"))
18              }
19          }
20  
21          let pid = Int(app.processIdentifier)
22          return (ActionResult(result: "focused \(appName) (pid \(pid))"), nil)
23      }
24  
25      /// Returns the frontmost app's PID and window title.
26      static func frontmost() -> (ActionResult?, ErrorInfo?) {
27          guard let app = NSWorkspace.shared.frontmostApplication else {
28              return (nil, ErrorInfo(code: -1, message: "Cannot determine frontmost application"))
29          }
30          let name = app.localizedName ?? "Unknown"
31          let pid = Int(app.processIdentifier)
32  
33          // Get window title via AX
34          let appRef = AXUIElementCreateApplication(Int32(pid))
35          var windowTitle = ""
36          if let windows = axValue(appRef, "AXWindows") as? [AXUIElement],
37             let win = windows.first {
38              windowTitle = axString(win, "AXTitle") ?? ""
39          }
40  
41          struct FrontmostResult: Encodable {
42              let app: String
43              let pid: Int
44              let window: String
45          }
46          // Return as simple action result with details
47          return (ActionResult(result: "\(name) (pid \(pid), window: \(windowTitle))"), nil)
48      }
49  
50      /// Lists all windows for an app.
51      static func listWindows(pid: Int) -> [[String: String]] {
52          let appRef = AXUIElementCreateApplication(Int32(pid))
53          guard let windows = axValue(appRef, "AXWindows") as? [AXUIElement] else {
54              return []
55          }
56          var result: [[String: String]] = []
57          for (i, win) in windows.enumerated() {
58              let title = axString(win, "AXTitle") ?? ""
59              let role = axString(win, "AXRole") ?? ""
60              result.append(["index": "\(i)", "title": title, "role": role])
61          }
62          return result
63      }
64  
65      private static func findApp(named name: String) -> NSRunningApplication? {
66          let lower = name.lowercased()
67          for app in NSWorkspace.shared.runningApplications {
68              if let n = app.localizedName, n.lowercased() == lower {
69                  return app
70              }
71              if let bundleID = app.bundleIdentifier, bundleID.lowercased().contains(lower) {
72                  return app
73              }
74          }
75          return nil
76      }
77  }