11import AppKit
2+ import FileChangeChecker
3+ import os. log
24import Service
35import ServiceManagement
46import SwiftUI
@@ -11,9 +13,13 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
1113 private var statusBarItem : NSStatusItem !
1214
1315 func applicationDidFinishLaunching( _: Notification ) {
16+ // setup real-time suggestion controller
17+ _ = RealtimeSuggestionController . shared
18+ setupRestartOnUpdate ( )
19+ setupQuitOnUserTerminated ( )
20+
1421 NSApp . setActivationPolicy ( . accessory)
1522 buildStatusBarMenu ( )
16- AXIsProcessTrustedWithOptions ( nil )
1723 }
1824
1925 @objc private func buildStatusBarMenu( ) {
@@ -66,12 +72,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
6672 break
6773 }
6874 }
69- UserDefaults . shared. addObserver (
70- userDefaultsObserver,
71- forKeyPath: SettingsKey . realtimeSuggestionToggle,
72- options: . new,
73- context: nil
74- )
7575 }
7676
7777 @objc func quit( ) {
@@ -105,6 +105,70 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
105105 }
106106 UserDefaults . shared. set ( isOn, forKey: SettingsKey . realtimeSuggestionToggle)
107107 }
108+
109+ func setupRestartOnUpdate( ) {
110+ Task {
111+ guard let url = Bundle . main. executableURL else { return }
112+ let checker = await FileChangeChecker ( fileURL: url)
113+
114+ // If Xcode or Copilot for Xcode is made active, check if the executable of this program
115+ // is changed. If changed, restart the launch agent.
116+
117+ let sequence = NSWorkspace . shared. notificationCenter
118+ . notifications ( named: NSWorkspace . didActivateApplicationNotification)
119+ for await notification in sequence {
120+ try Task . checkCancellation ( )
121+ guard let app = notification
122+ . userInfo ? [ NSWorkspace . applicationUserInfoKey] as? NSRunningApplication ,
123+ app. isUserOfService
124+ else { continue }
125+ guard await checker. checkIfChanged ( ) else {
126+ os_log ( . info, " XPC Service is not updated, no need to restart. " )
127+ continue
128+ }
129+ os_log ( . info, " XPC Service will be restarted. " )
130+ #if DEBUG
131+ #else
132+ let manager = LaunchAgentManager (
133+ serviceIdentifier: serviceIdentifier,
134+ executablePath: Bundle . main. executablePath ?? " "
135+ )
136+ do {
137+ try await manager. restartLaunchAgent ( )
138+ } catch {
139+ os_log (
140+ . error,
141+ " XPC Service failed to restart. %{public}s " ,
142+ error. localizedDescription
143+ )
144+ }
145+ #endif
146+ }
147+ }
148+ }
149+
150+ func setupQuitOnUserTerminated( ) {
151+ Task {
152+ // Whenever Xcode or the host application quits, check if any of the two is running.
153+ // If none, quit the XPC service.
154+
155+ let sequence = NSWorkspace . shared. notificationCenter
156+ . notifications ( named: NSWorkspace . didTerminateApplicationNotification)
157+ for await notification in sequence {
158+ try Task . checkCancellation ( )
159+ guard UserDefaults . shared. bool ( forKey: SettingsKey . quitXPCServiceOnXcodeAndAppQuit)
160+ else { continue }
161+ guard let app = notification
162+ . userInfo ? [ NSWorkspace . applicationUserInfoKey] as? NSRunningApplication ,
163+ app. isUserOfService
164+ else { continue }
165+ if NSWorkspace . shared. runningApplications. contains ( where: \. isUserOfService) {
166+ continue
167+ }
168+ exit ( 0 )
169+ }
170+ }
171+ }
108172}
109173
110174private class UserDefaultsObserver : NSObject {
@@ -118,4 +182,22 @@ private class UserDefaultsObserver: NSObject {
118182 ) {
119183 onChange ? ( keyPath)
120184 }
185+
186+ override init ( ) {
187+ super. init ( )
188+ observe ( keyPath: SettingsKey . realtimeSuggestionToggle)
189+ }
190+
191+ func observe( keyPath: String ) {
192+ UserDefaults . shared. addObserver ( self , forKeyPath: keyPath, options: . new, context: nil )
193+ }
194+ }
195+
196+ extension NSRunningApplication {
197+ var isUserOfService : Bool {
198+ [
199+ " com.apple.dt.Xcode " ,
200+ bundleIdentifierBase,
201+ ] . contains ( bundleIdentifier)
202+ }
121203}
0 commit comments