Skip to content

Commit b882211

Browse files
committed
Merge branch 'release/0.33.0'
2 parents bc5fc72 + 6b9e956 commit b882211

File tree

95 files changed

+7312
-1352
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+7312
-1352
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import AppKit
2+
import Foundation
3+
import Logger
4+
import XPCShared
5+
6+
class ServiceDelegate: NSObject, NSXPCListenerDelegate {
7+
func listener(
8+
_: NSXPCListener,
9+
shouldAcceptNewConnection newConnection: NSXPCConnection
10+
) -> Bool {
11+
newConnection.exportedInterface = NSXPCInterface(
12+
with: CommunicationBridgeXPCServiceProtocol.self
13+
)
14+
15+
let exportedObject = XPCService()
16+
newConnection.exportedObject = exportedObject
17+
newConnection.resume()
18+
19+
Logger.communicationBridge.info("Accepted new connection.")
20+
21+
return true
22+
}
23+
}
24+
25+
class XPCService: CommunicationBridgeXPCServiceProtocol {
26+
static let eventHandler = EventHandler()
27+
28+
func launchExtensionServiceIfNeeded(
29+
withReply reply: @escaping (NSXPCListenerEndpoint?) -> Void
30+
) {
31+
Task {
32+
await Self.eventHandler.launchExtensionServiceIfNeeded(withReply: reply)
33+
}
34+
}
35+
36+
func quit(withReply reply: @escaping () -> Void) {
37+
Task {
38+
await Self.eventHandler.quit(withReply: reply)
39+
}
40+
}
41+
42+
func updateServiceEndpoint(
43+
endpoint: NSXPCListenerEndpoint,
44+
withReply reply: @escaping () -> Void
45+
) {
46+
Task {
47+
await Self.eventHandler.updateServiceEndpoint(endpoint: endpoint, withReply: reply)
48+
}
49+
}
50+
}
51+
52+
actor EventHandler {
53+
var endpoint: NSXPCListenerEndpoint?
54+
let launcher = ExtensionServiceLauncher()
55+
var exitTask: Task<Void, Error>?
56+
57+
init() {
58+
Task { await rescheduleExitTask() }
59+
}
60+
61+
func launchExtensionServiceIfNeeded(
62+
withReply reply: @escaping (NSXPCListenerEndpoint?) -> Void
63+
) async {
64+
rescheduleExitTask()
65+
#if DEBUG
66+
if let endpoint, !(await testXPCListenerEndpoint(endpoint)) {
67+
self.endpoint = nil
68+
}
69+
reply(endpoint)
70+
#else
71+
if await launcher.isApplicationValid {
72+
reply(endpoint)
73+
} else {
74+
endpoint = nil
75+
await launcher.launch()
76+
reply(nil)
77+
}
78+
#endif
79+
}
80+
81+
func quit(withReply reply: () -> Void) {
82+
Logger.communicationBridge.info("Exiting service.")
83+
listener.invalidate()
84+
exit(0)
85+
}
86+
87+
func updateServiceEndpoint(endpoint: NSXPCListenerEndpoint, withReply reply: () -> Void) {
88+
rescheduleExitTask()
89+
self.endpoint = endpoint
90+
reply()
91+
}
92+
93+
/// The bridge will kill itself when it's not used for a period.
94+
/// It's fine that the bridge is killed because it will be launched again when needed.
95+
private func rescheduleExitTask() {
96+
exitTask?.cancel()
97+
exitTask = Task {
98+
#if DEBUG
99+
try await Task.sleep(nanoseconds: 60_000_000_000)
100+
Logger.communicationBridge.info("Exit will be called in release build.")
101+
#else
102+
try await Task.sleep(nanoseconds: 1_800_000_000_000)
103+
Logger.communicationBridge.info("Exiting service.")
104+
listener.invalidate()
105+
exit(0)
106+
#endif
107+
}
108+
}
109+
}
110+
111+
actor ExtensionServiceLauncher {
112+
let appIdentifier = bundleIdentifierBase.appending(".ExtensionService")
113+
let appURL = Bundle.main.bundleURL.appendingPathComponent(
114+
"CopilotForXcodeExtensionService.app"
115+
)
116+
var isLaunching: Bool = false
117+
var application: NSRunningApplication?
118+
var isApplicationValid: Bool {
119+
if let application, !application.isTerminated { return true }
120+
return false
121+
}
122+
123+
func launch() {
124+
guard !isLaunching else { return }
125+
isLaunching = true
126+
127+
Logger.communicationBridge.info("Launching extension service app.")
128+
NSWorkspace.shared.openApplication(
129+
at: appURL,
130+
configuration: .init()
131+
) { app, error in
132+
if let error = error {
133+
Logger.communicationBridge.error(
134+
"Failed to launch extension service app: \(error)"
135+
)
136+
} else {
137+
Logger.communicationBridge.info(
138+
"Finished launching extension service app."
139+
)
140+
}
141+
142+
self.application = app
143+
self.isLaunching = false
144+
}
145+
}
146+
}
147+

CommunicationBridge/main.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import Foundation
2+
3+
let bundleIdentifierBase = Bundle(url: Bundle.main.bundleURL.appendingPathComponent(
4+
"CopilotForXcodeExtensionService.app"
5+
))?.object(forInfoDictionaryKey: "BUNDLE_IDENTIFIER_BASE") as? String ?? "com.intii.CopilotForXcode"
6+
7+
let serviceIdentifier = bundleIdentifierBase + ".CommunicationBridge"
8+
9+
let delegate = ServiceDelegate()
10+
let listener = NSXPCListener(machServiceName: serviceIdentifier)
11+
listener.delegate = delegate
12+
listener.resume()
13+
RunLoop.main.run()
14+

0 commit comments

Comments
 (0)