@@ -6,6 +6,7 @@ import Logger
6
6
import Preferences
7
7
import Service
8
8
import ServiceManagement
9
+ import Status
9
10
import SwiftUI
10
11
import UpdateChecker
11
12
import UserDefaultsObserver
@@ -30,6 +31,7 @@ class ExtensionUpdateCheckerDelegate: UpdateCheckerDelegate {
30
31
class AppDelegate : NSObject , NSApplicationDelegate , NSWindowDelegate {
31
32
let service = Service . shared
32
33
var statusBarItem : NSStatusItem !
34
+ var statusMenuItem : NSMenuItem !
33
35
var xpcController : XPCController ?
34
36
let updateChecker =
35
37
UpdateChecker (
@@ -39,21 +41,29 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
39
41
let statusChecker : AuthStatusChecker = AuthStatusChecker ( )
40
42
var xpcExtensionService : XPCExtensionService ?
41
43
private var cancellables = Set < AnyCancellable > ( )
44
+ private var progressView : NSProgressIndicator ?
45
+ private var idleIcon = NSImage ( named: " MenuBarIcon " )
42
46
43
47
func applicationDidFinishLaunching( _: Notification ) {
44
48
if ProcessInfo . processInfo. environment [ " IS_UNIT_TEST " ] == " YES " { return }
45
49
_ = XcodeInspector . shared
50
+ service. markAsProcessing = { [ weak self] in
51
+ guard let self = self else { return }
52
+ self . markAsProcessing ( $0)
53
+ }
46
54
service. start ( )
47
55
AXIsProcessTrustedWithOptions ( [
48
56
kAXTrustedCheckOptionPrompt. takeRetainedValue ( ) as NSString : true ,
49
57
] as CFDictionary )
50
58
setupQuitOnUpdate ( )
51
59
setupQuitOnUserTerminated ( )
52
- setupQuitOnFeatureFlag ( )
53
60
xpcController = . init( )
54
61
Logger . service. info ( " XPC Service started. " )
55
62
NSApp . setActivationPolicy ( . accessory)
56
63
buildStatusBarMenu ( )
64
+ watchServiceStatus ( )
65
+ watchAXStatus ( )
66
+ updateStatusBarItem ( ) // set the initial status
57
67
}
58
68
59
69
@objc func quit( ) {
@@ -132,15 +142,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
132
142
}
133
143
}
134
144
135
- func setupQuitOnFeatureFlag( ) {
136
- FeatureFlagNotifierImpl . shared. featureFlagsDidChange. sink { [ weak self] ( flags) in
137
- if flags. x != true {
138
- Logger . service. info ( " Xcode feature flag not granted, quitting. " )
139
- self ? . quit ( )
140
- }
141
- } . store ( in: & cancellables)
142
- }
143
-
144
145
func requestAccessoryAPIPermission( ) {
145
146
AXIsProcessTrustedWithOptions ( [
146
147
kAXTrustedCheckOptionPrompt. takeRetainedValue ( ) as NSString : true ,
@@ -161,6 +162,70 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
161
162
xpcExtensionService = service
162
163
return service
163
164
}
165
+
166
+ func watchServiceStatus( ) {
167
+ let notifications = NotificationCenter . default. notifications ( named: . serviceStatusDidChange)
168
+ Task { [ weak self] in
169
+ for await _ in notifications {
170
+ guard let self else { return }
171
+ self . updateStatusBarItem ( )
172
+ }
173
+ }
174
+ }
175
+
176
+ func watchAXStatus( ) {
177
+ let osNotifications = DistributedNotificationCenter . default ( ) . notifications ( named: NSNotification . Name ( " com.apple.accessibility.api " ) )
178
+ Task { [ weak self] in
179
+ for await _ in osNotifications {
180
+ guard let self else { return }
181
+ self . updateStatusBarItem ( )
182
+ }
183
+ }
184
+ }
185
+
186
+ func updateStatusBarItem( ) {
187
+ Task { @MainActor in
188
+ let status = await Status . shared. getStatus ( )
189
+ let image = if status. system {
190
+ NSImage ( systemSymbolName: status. icon, accessibilityDescription: nil )
191
+ } else {
192
+ NSImage ( named: status. icon)
193
+ }
194
+ idleIcon = image
195
+ self . statusBarItem. button? . image = image
196
+ if let message = status. message {
197
+ // TODO switch to attributedTitle to enable line breaks and color.
198
+ self . statusMenuItem. title = message
199
+ self . statusMenuItem. isHidden = false
200
+ self . statusMenuItem. isEnabled = status. url != nil
201
+ } else {
202
+ self . statusMenuItem. isHidden = true
203
+ }
204
+ }
205
+ }
206
+
207
+ func markAsProcessing( _ isProcessing: Bool ) {
208
+ if !isProcessing {
209
+ // No longer in progress
210
+ progressView? . removeFromSuperview ( )
211
+ progressView = nil
212
+ statusBarItem. button? . image = idleIcon
213
+ return
214
+ }
215
+ if progressView != nil {
216
+ // Already in progress
217
+ return
218
+ }
219
+ let progress = NSProgressIndicator ( )
220
+ progress. style = . spinning
221
+ progress. sizeToFit ( )
222
+ progress. frame = statusBarItem. button? . bounds ?? . zero
223
+ progress. isIndeterminate = true
224
+ progress. startAnimation ( nil )
225
+ statusBarItem. button? . addSubview ( progress)
226
+ statusBarItem. button? . image = nil
227
+ progressView = progress
228
+ }
164
229
}
165
230
166
231
extension NSRunningApplication {
0 commit comments