Skip to content

Commit be8740a

Browse files
committed
Add package DisplayLink
1 parent 9306079 commit be8740a

File tree

3 files changed

+77
-17
lines changed

3 files changed

+77
-17
lines changed

Core/Package.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ let package = Package(
5151
),
5252
.target(
5353
name: "Service",
54-
dependencies: ["CopilotModel", "CopilotService", "XPCShared", "CGEventObserver"]
54+
dependencies: ["CopilotModel", "CopilotService", "XPCShared", "CGEventObserver",
55+
"DisplayLink"]
5556
),
5657
.target(
5758
name: "XPCShared",
@@ -63,5 +64,6 @@ let package = Package(
6364
),
6465
.target(name: "FileChangeChecker"),
6566
.target(name: "LaunchAgentManager"),
67+
.target(name: "DisplayLink"),
6668
]
6769
)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import Foundation
2+
import QuartzCore
3+
4+
public actor DisplayLink {
5+
private var displayLink: CVDisplayLink!
6+
static let shared = DisplayLink()
7+
private var continuations: [UUID: AsyncStream<Void>.Continuation] = [:]
8+
9+
public static func createStream() -> AsyncStream<Void> {
10+
.init { continuation in
11+
Task {
12+
let id = UUID()
13+
await DisplayLink.shared?.addContinuation(continuation, id: id)
14+
continuation.onTermination = { _ in
15+
Task {
16+
await DisplayLink.shared?.removeContinuation(id: id)
17+
}
18+
}
19+
}
20+
}
21+
}
22+
23+
private init?() {
24+
_ = CVDisplayLinkCreateWithCGDisplay(CGMainDisplayID(), &displayLink)
25+
guard displayLink != nil else { return nil }
26+
CVDisplayLinkSetOutputHandler(displayLink) { [weak self] _, _, _, _, _ in
27+
guard let self else { return kCVReturnSuccess }
28+
Task { await self.notifyContinuations() }
29+
return kCVReturnSuccess
30+
}
31+
}
32+
33+
deinit {
34+
for continuation in continuations {
35+
continuation.value.finish()
36+
}
37+
}
38+
39+
func addContinuation(_ continuation: AsyncStream<Void>.Continuation, id: UUID) {
40+
continuations[id] = continuation
41+
if !continuations.isEmpty {
42+
CVDisplayLinkStart(displayLink)
43+
}
44+
}
45+
46+
func removeContinuation(id: UUID) {
47+
continuations[id] = nil
48+
if continuations.isEmpty {
49+
CVDisplayLinkStop(displayLink)
50+
}
51+
}
52+
53+
private func notifyContinuations() {
54+
for continuation in continuations {
55+
continuation.value.yield()
56+
}
57+
}
58+
}

Core/Sources/Service/RealtimeSuggestionController.swift

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import AppKit
22
import CGEventObserver
3+
import DisplayLink
34
import Foundation
45
import os.log
56
import QuartzCore
@@ -66,7 +67,7 @@ public actor RealtimeSuggestionController {
6667
}
6768
}
6869
if eventObserver.activateIfPossible() {
69-
realtimeSuggestionIndicatorController?.isObserving = true
70+
realtimeSuggestionIndicatorController.isObserving = true
7071
}
7172
}
7273

@@ -78,7 +79,7 @@ public actor RealtimeSuggestionController {
7879
task?.cancel()
7980
task = nil
8081
eventObserver.deactivate()
81-
realtimeSuggestionIndicatorController?.isObserving = false
82+
realtimeSuggestionIndicatorController.isObserving = false
8283
}
8384

8485
func handleKeyboardEvent(event: CGEvent) async {
@@ -132,7 +133,7 @@ public actor RealtimeSuggestionController {
132133
else { return }
133134
if Task.isCancelled { return }
134135
os_log(.info, "Prefetch suggestions.")
135-
realtimeSuggestionIndicatorController?.triggerPrefetchAnimation()
136+
realtimeSuggestionIndicatorController.triggerPrefetchAnimation()
136137
do {
137138
try await Environment.triggerAction("Prefetch Suggestions")
138139
} catch {
@@ -239,8 +240,6 @@ final class RealtimeSuggestionIndicatorController {
239240
}
240241

241242
private let viewModel = IndicatorContentViewModel()
242-
private var displayLink: CVDisplayLink!
243-
private var isDisplayLinkStarted: Bool = false
244243
private var userDefaultsObserver = UserDefaultsObserver()
245244
var isObserving = false {
246245
didSet {
@@ -250,6 +249,8 @@ final class RealtimeSuggestionIndicatorController {
250249
}
251250
}
252251

252+
private var displayLinkTask: Task<Void, Never>?
253+
253254
@MainActor
254255
lazy var window = {
255256
let it = NSWindow(
@@ -269,15 +270,7 @@ final class RealtimeSuggestionIndicatorController {
269270
return it
270271
}()
271272

272-
init?() {
273-
_ = CVDisplayLinkCreateWithCGDisplay(CGMainDisplayID(), &displayLink)
274-
guard displayLink != nil else { return nil }
275-
CVDisplayLinkSetOutputHandler(displayLink) { [weak self] _, _, _, _, _ in
276-
guard let self else { return kCVReturnSuccess }
277-
self.updateIndicatorLocation()
278-
return kCVReturnSuccess
279-
}
280-
273+
init() {
281274
Task {
282275
let sequence = NSWorkspace.shared.notificationCenter
283276
.notifications(named: NSWorkspace.didActivateApplicationNotification)
@@ -327,9 +320,16 @@ final class RealtimeSuggestionIndicatorController {
327320
await { @MainActor in
328321
guard window.isVisible != isVisible else { return }
329322
if isVisible {
330-
CVDisplayLinkStart(self.displayLink)
323+
if displayLinkTask == nil {
324+
displayLinkTask = Task {
325+
for await _ in DisplayLink.createStream() {
326+
self.updateIndicatorLocation()
327+
}
328+
}
329+
}
331330
} else {
332-
CVDisplayLinkStop(self.displayLink)
331+
displayLinkTask?.cancel()
332+
displayLinkTask = nil
333333
}
334334
window.setIsVisible(isVisible)
335335
}()

0 commit comments

Comments
 (0)