Skip to content

Commit 0408dd6

Browse files
committed
Quit XPC service if Xcode or the host app are all terminated
1 parent 4d83d2c commit 0408dd6

File tree

3 files changed

+81
-53
lines changed

3 files changed

+81
-53
lines changed

Core/Sources/XPCShared/UserDefaults.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ public enum SettingsKey {
88
public static let nodePath = "NodePath"
99
public static let realtimeSuggestionToggle = "RealtimeSuggestionToggle"
1010
public static let realtimeSuggestionDebounce = "RealtimeSuggestionDebounce"
11+
public static let quitXPCServiceOnXcodeAndAppQuit = "QuitXPCServiceOnXcodeAndAppQuit"
1112
}

XPCService/AppDelegate.swift

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import AppKit
2+
import FileChangeChecker
3+
import os.log
24
import Service
35
import ServiceManagement
46
import 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() {
@@ -105,6 +111,70 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
105111
}
106112
UserDefaults.shared.set(isOn, forKey: SettingsKey.realtimeSuggestionToggle)
107113
}
114+
115+
func setupRestartOnUpdate() {
116+
Task {
117+
guard let url = Bundle.main.executableURL else { return }
118+
let checker = await FileChangeChecker(fileURL: url)
119+
120+
// If Xcode or Copilot for Xcode is made active, check if the executable of this program
121+
// is changed. If changed, restart the launch agent.
122+
123+
let sequence = NSWorkspace.shared.notificationCenter
124+
.notifications(named: NSWorkspace.didActivateApplicationNotification)
125+
for await notification in sequence {
126+
try Task.checkCancellation()
127+
guard let app = notification
128+
.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication,
129+
app.isUserOfService
130+
else { continue }
131+
guard await checker.checkIfChanged() else {
132+
os_log(.info, "XPC Service is not updated, no need to restart.")
133+
continue
134+
}
135+
os_log(.info, "XPC Service will be restarted.")
136+
#if DEBUG
137+
#else
138+
let manager = LaunchAgentManager(
139+
serviceIdentifier: serviceIdentifier,
140+
executablePath: Bundle.main.executablePath ?? ""
141+
)
142+
do {
143+
try await manager.restartLaunchAgent()
144+
} catch {
145+
os_log(
146+
.error,
147+
"XPC Service failed to restart. %{public}s",
148+
error.localizedDescription
149+
)
150+
}
151+
#endif
152+
}
153+
}
154+
}
155+
156+
func setupQuitOnUserTerminated() {
157+
Task {
158+
// Whenever Xcode or the host application quits, check if any of the two is running.
159+
// If none, quit the XPC service.
160+
161+
let sequence = NSWorkspace.shared.notificationCenter
162+
.notifications(named: NSWorkspace.didTerminateApplicationNotification)
163+
for await notification in sequence {
164+
try Task.checkCancellation()
165+
guard UserDefaults.shared.bool(forKey: SettingsKey.quitXPCServiceOnXcodeAndAppQuit)
166+
else { continue }
167+
guard let app = notification
168+
.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication,
169+
app.isUserOfService
170+
else { continue }
171+
if NSWorkspace.shared.runningApplications.contains(where: \.isUserOfService) {
172+
continue
173+
}
174+
exit(0)
175+
}
176+
}
177+
}
108178
}
109179

110180
private class UserDefaultsObserver: NSObject {
@@ -119,3 +189,12 @@ private class UserDefaultsObserver: NSObject {
119189
onChange?(keyPath)
120190
}
121191
}
192+
193+
extension NSRunningApplication {
194+
var isUserOfService: Bool {
195+
[
196+
"com.apple.dt.Xcode",
197+
bundleIdentifierBase,
198+
].contains(bundleIdentifier)
199+
}
200+
}

XPCService/main.swift

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import AppKit
2-
import FileChangeChecker
32
import Foundation
43
import LaunchAgentManager
54
import os.log
@@ -18,60 +17,9 @@ func setupXPCListener() -> (NSXPCListener, ServiceDelegate) {
1817
return (listener, delegate)
1918
}
2019

21-
func setupAutoTrigger() {
22-
_ = RealtimeSuggestionController.shared
23-
}
24-
25-
func setupRestartOnUpdate() {
26-
Task {
27-
guard let url = Bundle.main.executableURL else { return }
28-
let checker = await FileChangeChecker(fileURL: url)
29-
30-
// If Xcode or Copilot for Xcode is made active, check if the executable of this program is
31-
// changed.
32-
// If changed, restart the launch agent.
33-
34-
let sequence = NSWorkspace.shared.notificationCenter
35-
.notifications(named: NSWorkspace.didActivateApplicationNotification)
36-
for await notification in sequence {
37-
try Task.checkCancellation()
38-
guard let app = notification
39-
.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication,
40-
[
41-
"com.apple.dt.Xcode",
42-
bundleIdentifierBase,
43-
].contains(app.bundleIdentifier)
44-
else { continue }
45-
guard await checker.checkIfChanged() else {
46-
os_log(.info, "XPC Service is not updated, no need to restart.")
47-
continue
48-
}
49-
os_log(.info, "XPC Service will be restarted.")
50-
#if DEBUG
51-
#else
52-
let manager = LaunchAgentManager(
53-
serviceIdentifier: serviceIdentifier,
54-
executablePath: Bundle.main.executablePath ?? ""
55-
)
56-
do {
57-
try await manager.restartLaunchAgent()
58-
} catch {
59-
os_log(
60-
.error,
61-
"XPC Service failed to restart. %{public}s",
62-
error.localizedDescription
63-
)
64-
}
65-
#endif
66-
}
67-
}
68-
}
69-
7020
let app = NSApplication.shared
7121
let appDelegate = AppDelegate()
7222
app.delegate = appDelegate
7323
let xpcListener = setupXPCListener()
74-
setupAutoTrigger()
75-
setupRestartOnUpdate()
7624
os_log(.info, "XPC Service started.")
7725
app.run()

0 commit comments

Comments
 (0)