Skip to content

Commit 947a15a

Browse files
committed
Add ActiveApplicationMonitor
So we don't have to worry about failures in searching for an active Xcode
1 parent cd616aa commit 947a15a

File tree

3 files changed

+82
-8
lines changed

3 files changed

+82
-8
lines changed

Core/Package.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,14 @@ let package = Package(
5151
),
5252
.target(
5353
name: "Service",
54-
dependencies: ["CopilotModel", "CopilotService", "XPCShared", "CGEventObserver",
55-
"DisplayLink"]
54+
dependencies: [
55+
"CopilotModel",
56+
"CopilotService",
57+
"XPCShared",
58+
"CGEventObserver",
59+
"DisplayLink",
60+
"ActiveApplicationMonitor",
61+
]
5662
),
5763
.target(
5864
name: "XPCShared",
@@ -65,5 +71,6 @@ let package = Package(
6571
.target(name: "FileChangeChecker"),
6672
.target(name: "LaunchAgentManager"),
6773
.target(name: "DisplayLink"),
74+
.target(name: "ActiveApplicationMonitor"),
6875
]
6976
)
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import AppKit
2+
3+
public final class ActiveApplicationMonitor {
4+
static let shared = ActiveApplicationMonitor()
5+
var activeApplication = NSWorkspace.shared.runningApplications.first(where: \.isActive)
6+
private var continuations: [UUID: AsyncStream<NSRunningApplication?>.Continuation] = [:]
7+
8+
private init() {
9+
Task {
10+
let sequence = NSWorkspace.shared.notificationCenter
11+
.notifications(named: NSWorkspace.didActivateApplicationNotification)
12+
for await notification in sequence {
13+
guard let app = notification
14+
.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication
15+
else { continue }
16+
activeApplication = app
17+
notifyContinuations()
18+
}
19+
}
20+
}
21+
22+
deinit {
23+
for continuation in continuations {
24+
continuation.value.finish()
25+
}
26+
}
27+
28+
public static var activeApplication: NSRunningApplication? { shared.activeApplication }
29+
30+
public static func createStream() -> AsyncStream<NSRunningApplication?> {
31+
.init { continuation in
32+
let id = UUID()
33+
ActiveApplicationMonitor.shared.addContinuation(continuation, id: id)
34+
continuation.onTermination = { _ in
35+
ActiveApplicationMonitor.shared.removeContinuation(id: id)
36+
}
37+
}
38+
}
39+
40+
func addContinuation(
41+
_ continuation: AsyncStream<NSRunningApplication?>.Continuation,
42+
id: UUID
43+
) {
44+
continuations[id] = continuation
45+
}
46+
47+
func removeContinuation(id: UUID) {
48+
continuations[id] = nil
49+
}
50+
51+
private func notifyContinuations() {
52+
for continuation in continuations {
53+
continuation.value.yield(activeApplication)
54+
}
55+
}
56+
}

Core/Sources/Service/GUI/SuggestionPanelController.swift

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import ActiveApplicationMonitor
12
import AppKit
23
import DisplayLink
34
import SwiftUI
@@ -25,7 +26,11 @@ final class SuggestionPanelController {
2526
}()
2627

2728
private var displayLinkTask: Task<Void, Never>?
29+
private var activeApplicationMonitorTask: Task<Void, Never>?
2830
private let viewModel = SuggestionPanelViewModel()
31+
private var activeApplication: NSRunningApplication? {
32+
ActiveApplicationMonitor.activeApplication
33+
}
2934

3035
nonisolated init() {
3136
Task { @MainActor in
@@ -34,23 +39,29 @@ final class SuggestionPanelController {
3439
self.updateWindowLocation()
3540
}
3641
}
42+
43+
activeApplicationMonitorTask = Task {
44+
for await _ in ActiveApplicationMonitor.createStream() {
45+
self.updateWindowLocation()
46+
}
47+
}
3748
}
3849
}
3950

4051
/// Update the window location
4152
///
4253
/// - note:
4354
private func updateWindowLocation() {
44-
if let activeXcode = NSRunningApplication
45-
.runningApplications(withBundleIdentifier: "com.apple.dt.Xcode")
46-
.first(where: \.isActive)
55+
if let activeXcode = activeApplication,
56+
activeXcode.bundleIdentifier == "com.apple.dt.Xcode"
4757
{
4858
let application = AXUIElementCreateApplication(activeXcode.processIdentifier)
4959
if let focusElement: AXUIElement = try? application
5060
.copyValue(key: kAXFocusedUIElementAttribute),
51-
let focusElementType: String = try? focusElement.copyValue(key: kAXDescriptionAttribute),
52-
focusElementType == "Source Editor",
53-
let parent: AXUIElement = try? focusElement.copyValue(key: kAXParentAttribute),
61+
let focusElementType: String = try? focusElement
62+
.copyValue(key: kAXDescriptionAttribute),
63+
focusElementType == "Source Editor",
64+
let parent: AXUIElement = try? focusElement.copyValue(key: kAXParentAttribute),
5465
let positionValue: AXValue = try? parent
5566
.copyValue(key: kAXPositionAttribute),
5667
let sizeValue: AXValue = try? parent

0 commit comments

Comments
 (0)