Skip to content

Commit 420d57b

Browse files
committed
Add a window that floats next to the Xcode editor
1 parent bb89a64 commit 420d57b

File tree

3 files changed

+103
-1
lines changed

3 files changed

+103
-1
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import AppKit
2+
3+
@MainActor
4+
public final class GraphicalUserInterfaceController {
5+
public nonisolated static let shared = GraphicalUserInterfaceController()
6+
nonisolated let realtimeSuggestionIndicatorController = RealtimeSuggestionIndicatorController()
7+
nonisolated let suggestionPanelController = SuggestionPanelController()
8+
private nonisolated init() {}
9+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import AppKit
2+
import DisplayLink
3+
import SwiftUI
4+
5+
@MainActor
6+
final class SuggestionPanelController {
7+
private lazy var window = {
8+
let it = NSWindow(
9+
contentRect: .zero,
10+
styleMask: .borderless,
11+
backing: .buffered,
12+
defer: false
13+
)
14+
it.isReleasedWhenClosed = false
15+
it.isOpaque = false
16+
it.backgroundColor = .white
17+
it.level = .statusBar
18+
it.contentView = NSHostingView(
19+
rootView: SuggestionPanelView(viewModel: viewModel)
20+
.allowsHitTesting(false)
21+
.frame(width: 500, height: 300)
22+
)
23+
it.setIsVisible(true)
24+
return it
25+
}()
26+
27+
private var displayLinkTask: Task<Void, Never>?
28+
private let viewModel = SuggestionPanelViewModel()
29+
30+
nonisolated init() {
31+
Task { @MainActor in
32+
displayLinkTask = Task {
33+
for await _ in DisplayLink.createStream() {
34+
self.updateWindowLocation()
35+
}
36+
}
37+
}
38+
}
39+
40+
/// Update the window location
41+
///
42+
/// - note:
43+
private func updateWindowLocation() {
44+
if let activeXcode = NSRunningApplication
45+
.runningApplications(withBundleIdentifier: "com.apple.dt.Xcode")
46+
.first(where: \.isActive)
47+
{
48+
let application = AXUIElementCreateApplication(activeXcode.processIdentifier)
49+
if let focusElement: AXUIElement = try? application
50+
.copyValue(key: kAXFocusedUIElementAttribute),
51+
let parent: AXUIElement = try? focusElement.copyValue(key: kAXParentAttribute),
52+
let positionValue: AXValue = try? parent
53+
.copyValue(key: kAXPositionAttribute),
54+
let sizeValue: AXValue = try? parent
55+
.copyValue(key: kAXSizeAttribute)
56+
{
57+
var position: CGPoint = .zero
58+
let foundPosition = AXValueGetValue(positionValue, .cgPoint, &position)
59+
var size: CGSize = .zero
60+
let foundSize = AXValueGetValue(sizeValue, .cgSize, &size)
61+
let screen = NSScreen.screens.first
62+
var frame = CGRect(origin: position, size: size)
63+
if foundSize, foundPosition, let screen {
64+
frame.origin = .init(
65+
x: frame.maxX + 2,
66+
y: screen.frame.height - frame.minY - 300
67+
)
68+
frame.size = .init(width: 500, height: 300)
69+
window.alphaValue = 1
70+
window.setFrame(frame, display: false, animate: true)
71+
return
72+
}
73+
}
74+
}
75+
76+
window.alphaValue = 0
77+
}
78+
}
79+
80+
final class SuggestionPanelViewModel: ObservableObject {
81+
@Published var suggetion: String = "Hello World"
82+
}
83+
84+
struct SuggestionPanelView: View {
85+
@ObservedObject var viewModel: SuggestionPanelViewModel
86+
87+
var body: some View {
88+
Text(viewModel.suggetion)
89+
}
90+
}

ExtensionService/AppDelegate.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
2020
private var xpcListener: (NSXPCListener, ServiceDelegate)?
2121

2222
func applicationDidFinishLaunching(_: Notification) {
23+
_ = GraphicalUserInterfaceController.shared
2324
// setup real-time suggestion controller
2425
_ = RealtimeSuggestionController.shared
2526
setupQuitOnUpdate()
@@ -28,7 +29,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
2829
os_log(.info, "XPC Service started.")
2930
NSApp.setActivationPolicy(.accessory)
3031
buildStatusBarMenu()
31-
AXIsProcessTrustedWithOptions(nil)
32+
AXIsProcessTrustedWithOptions([
33+
kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true
34+
] as NSDictionary)
3235
}
3336

3437
@objc private func buildStatusBarMenu() {

0 commit comments

Comments
 (0)