@@ -5,11 +5,11 @@ import Combine
55import Foundation
66
77public final class XcodeAppInstanceInspector : AppInstanceInspector {
8- @Published public var focusedWindow : XcodeWindowInspector ?
9- @Published public var documentURL : URL ? = nil
10- @Published public var workspaceURL : URL ? = nil
11- @Published public var projectRootURL : URL ? = nil
12- @Published public var workspaces = [ WorkspaceIdentifier: Workspace] ( )
8+ @Published public fileprivate ( set ) var focusedWindow : XcodeWindowInspector ?
9+ @Published public fileprivate ( set ) var documentURL : URL ? = nil
10+ @Published public fileprivate ( set ) var workspaceURL : URL ? = nil
11+ @Published public fileprivate ( set ) var projectRootURL : URL ? = nil
12+ @Published public fileprivate ( set ) var workspaces = [ WorkspaceIdentifier: Workspace] ( )
1313 public var realtimeWorkspaces : [ WorkspaceIdentifier : WorkspaceInfo ] {
1414 updateWorkspaceInfo ( )
1515 return workspaces. mapValues ( \. info)
@@ -72,10 +72,10 @@ public final class XcodeAppInstanceInspector: AppInstanceInspector {
7272 override init ( runningApplication: NSRunningApplication ) {
7373 super. init ( runningApplication: runningApplication)
7474
75- observeFocusedWindow ( )
76- observeAXNotifications ( )
77-
78- Task {
75+ Task { @ MainActor in
76+ observeFocusedWindow ( )
77+ observeAXNotifications ( )
78+
7979 try await Task . sleep ( nanoseconds: 3_000_000_000 )
8080 // Sometimes the focused window may not be ready on app launch.
8181 if !( focusedWindow is WorkspaceXcodeWindowInspector ) {
@@ -84,6 +84,7 @@ public final class XcodeAppInstanceInspector: AppInstanceInspector {
8484 }
8585 }
8686
87+ @MainActor
8788 func observeFocusedWindow( ) {
8889 if let window = appElement. focusedWindow {
8990 if window. identifier == " Xcode.WorkspaceWindow " {
@@ -104,16 +105,19 @@ public final class XcodeAppInstanceInspector: AppInstanceInspector {
104105
105106 window. $documentURL
106107 . filter { $0 != . init( fileURLWithPath: " / " ) }
108+ . receive ( on: DispatchQueue . main)
107109 . sink { [ weak self] url in
108110 self ? . documentURL = url
109111 } . store ( in: & focusedWindowObservations)
110112 window. $workspaceURL
111113 . filter { $0 != . init( fileURLWithPath: " / " ) }
114+ . receive ( on: DispatchQueue . main)
112115 . sink { [ weak self] url in
113116 self ? . workspaceURL = url
114117 } . store ( in: & focusedWindowObservations)
115118 window. $projectRootURL
116119 . filter { $0 != . init( fileURLWithPath: " / " ) }
120+ . receive ( on: DispatchQueue . main)
117121 . sink { [ weak self] url in
118122 self ? . projectRootURL = url
119123 } . store ( in: & focusedWindowObservations)
@@ -127,6 +131,7 @@ public final class XcodeAppInstanceInspector: AppInstanceInspector {
127131 }
128132 }
129133
134+ @MainActor
130135 func refresh( ) {
131136 if let focusedWindow = focusedWindow as? WorkspaceXcodeWindowInspector {
132137 focusedWindow. refresh ( )
@@ -135,16 +140,19 @@ public final class XcodeAppInstanceInspector: AppInstanceInspector {
135140 }
136141 }
137142
143+ @MainActor
138144 func observeAXNotifications( ) {
139145 longRunningTasks. forEach { $0. cancel ( ) }
140146 longRunningTasks = [ ]
141147
142- let focusedWindowChanged = Task {
143- let notification = AXNotificationStream (
144- app: runningApplication,
145- notificationNames: kAXFocusedWindowChangedNotification
146- )
147- for await _ in notification {
148+ let windowChangeNotification = AXNotificationStream (
149+ app: runningApplication,
150+ notificationNames: kAXFocusedWindowChangedNotification
151+ )
152+
153+ let focusedWindowChanged = Task { @MainActor [ weak self] in
154+ for await _ in windowChangeNotification {
155+ guard let self else { return }
148156 try Task . checkCancellation ( )
149157 observeFocusedWindow ( )
150158 }
@@ -153,19 +161,23 @@ public final class XcodeAppInstanceInspector: AppInstanceInspector {
153161 longRunningTasks. insert ( focusedWindowChanged)
154162
155163 updateWorkspaceInfo ( )
156- let updateTabsTask = Task { @MainActor in
157- let notification = AXNotificationStream (
158- app: runningApplication,
159- notificationNames: kAXFocusedUIElementChangedNotification,
160- kAXApplicationDeactivatedNotification
161- )
164+
165+ let elementChangeNotification = AXNotificationStream (
166+ app: runningApplication,
167+ notificationNames: kAXFocusedUIElementChangedNotification,
168+ kAXApplicationDeactivatedNotification
169+ )
170+
171+ let updateTabsTask = Task { @MainActor [ weak self] in
162172 if #available( macOS 13 . 0 , * ) {
163- for await _ in notification. debounce ( for: . seconds( 2 ) ) {
173+ for await _ in elementChangeNotification. debounce ( for: . seconds( 2 ) ) {
174+ guard let self else { return }
164175 try Task . checkCancellation ( )
165176 updateWorkspaceInfo ( )
166177 }
167178 } else {
168- for await _ in notification {
179+ for await _ in elementChangeNotification {
180+ guard let self else { return }
169181 try Task . checkCancellation ( )
170182 updateWorkspaceInfo ( )
171183 }
@@ -174,19 +186,22 @@ public final class XcodeAppInstanceInspector: AppInstanceInspector {
174186
175187 longRunningTasks. insert ( updateTabsTask)
176188
177- let completionPanelTask = Task {
178- let stream = AXNotificationStream (
179- app: runningApplication,
180- notificationNames: kAXCreatedNotification, kAXUIElementDestroyedNotification
181- )
189+ let completionPanelNotification = AXNotificationStream (
190+ app: runningApplication,
191+ notificationNames: kAXCreatedNotification, kAXUIElementDestroyedNotification
192+ )
193+
194+ let completionPanelTask = Task { @MainActor [ weak self] in
195+ for await event in completionPanelNotification {
196+ guard let self else { return }
182197
183- for await event in stream {
184198 // We can only observe the creation and closing of the parent
185199 // of the completion panel.
186200 let isCompletionPanel = {
187- event. element. firstChild { element in
188- element. identifier == " _XC_COMPLETION_TABLE_ "
189- } != nil
201+ event. element. identifier == " _XC_COMPLETION_TABLE_ "
202+ || event. element. firstChild { element in
203+ element. identifier == " _XC_COMPLETION_TABLE_ "
204+ } != nil
190205 }
191206 switch event. name {
192207 case kAXCreatedNotification:
0 commit comments