Skip to content

Commit 3404070

Browse files
committed
Pre-release 0.27.92
1 parent a9ad824 commit 3404070

24 files changed

+310
-66
lines changed

Diff for: Copilot for Xcode.xcworkspace/xcshareddata/swiftpm/Package.resolved

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
"kind" : "remoteSourceControl",
66
"location" : "https://github.com/devm33/CGEventOverride",
77
"state" : {
8-
"revision" : "571d36d63e68fac30e4a350600cd186697936f74",
9-
"version" : "1.2.3"
8+
"branch" : "devm33/fix-stale-AXIsProcessTrusted",
9+
"revision" : "06a9bf1f8f8d47cca221344101cc0274f04cc513"
1010
}
1111
},
1212
{

Diff for: Core/Package.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ let package = Package(
5050
// quick hack to support custom UserDefaults
5151
// https://github.com/sindresorhus/KeyboardShortcuts
5252
.package(url: "https://github.com/devm33/KeyboardShortcuts", branch: "main"),
53-
.package(url: "https://github.com/devm33/CGEventOverride", from: "1.2.1"),
53+
.package(url: "https://github.com/devm33/CGEventOverride", branch: "devm33/fix-stale-AXIsProcessTrusted"),
5454
.package(url: "https://github.com/devm33/Highlightr", branch: "master"),
5555
],
5656
targets: [
@@ -83,6 +83,7 @@ let package = Package(
8383
.product(name: "UserDefaultsObserver", package: "Tool"),
8484
.product(name: "AppMonitoring", package: "Tool"),
8585
.product(name: "SuggestionBasic", package: "Tool"),
86+
.product(name: "Status", package: "Tool"),
8687
.product(name: "ChatTab", package: "Tool"),
8788
.product(name: "Logger", package: "Tool"),
8889
.product(name: "ChatAPIService", package: "Tool"),

Diff for: Core/Sources/HostApp/General.swift

+4-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import Client
22
import ComposableArchitecture
33
import Foundation
44
import LaunchAgentManager
5+
import Status
56
import SwiftUI
67
import XPCShared
78
import Logger
@@ -11,7 +12,7 @@ struct General {
1112
@ObservableState
1213
struct State: Equatable {
1314
var xpcServiceVersion: String?
14-
var isAccessibilityPermissionGranted: Bool?
15+
var isAccessibilityPermissionGranted: ObservedAXStatus = .unknown
1516
var isReloading = false
1617
}
1718

@@ -20,7 +21,7 @@ struct General {
2021
case setupLaunchAgentIfNeeded
2122
case openExtensionManager
2223
case reloadStatus
23-
case finishReloading(xpcServiceVersion: String, permissionGranted: Bool)
24+
case finishReloading(xpcServiceVersion: String, permissionGranted: ObservedAXStatus)
2425
case failedReloading
2526
case retryReloading
2627
}
@@ -35,7 +36,7 @@ struct General {
3536
case .appear:
3637
return .run { send in
3738
await send(.setupLaunchAgentIfNeeded)
38-
for await _ in DistributedNotificationCenter.default().notifications(named: NSNotification.Name("com.apple.accessibility.api")) {
39+
for await _ in DistributedNotificationCenter.default().notifications(named: .serviceStatusDidChange) {
3940
await send(.reloadStatus)
4041
}
4142
}

Diff for: Core/Sources/HostApp/GeneralSettings/CopilotConnectionView.swift

+3-17
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,6 @@
11
import ComposableArchitecture
22
import SwiftUI
33

4-
struct ActivityIndicatorView: NSViewRepresentable {
5-
func makeNSView(context _: Context) -> NSProgressIndicator {
6-
let progressIndicator = NSProgressIndicator()
7-
progressIndicator.style = .spinning
8-
progressIndicator.controlSize = .small
9-
progressIndicator.startAnimation(nil)
10-
return progressIndicator
11-
}
12-
13-
func updateNSView(_: NSProgressIndicator, context _: Context) {
14-
// No-op
15-
}
16-
}
17-
184
struct CopilotConnectionView: View {
195
@AppStorage("username") var username: String = ""
206
@Environment(\.toast) var toast
@@ -38,6 +24,9 @@ struct CopilotConnectionView: View {
3824
title: "GitHub Account Status Permissions",
3925
subtitle: "GitHub Connection: \(viewModel.status?.description ?? "Loading...")"
4026
) {
27+
if viewModel.isRunningAction || waitingForSignIn {
28+
ProgressView().controlSize(.small)
29+
}
4130
Button("Refresh Connection") {
4231
viewModel.checkStatus()
4332
}
@@ -72,9 +61,6 @@ struct CopilotConnectionView: View {
7261
viewModel.isSignInAlertPresented = false
7362
}
7463
}
75-
if viewModel.isRunningAction || waitingForSignIn {
76-
ActivityIndicatorView()
77-
}
7864
}
7965
}
8066

Diff for: Core/Sources/HostApp/GeneralSettings/GeneralSettingsView.swift

+8-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,14 @@ struct GeneralSettingsView: View {
99
let store: StoreOf<General>
1010

1111
var accessibilityPermissionSubtitle: String {
12-
guard let granted = store.isAccessibilityPermissionGranted else { return "Loading..." }
13-
return granted ? "Granted" : "Not Granted. Required to run. Click to open System Preferences."
12+
switch store.isAccessibilityPermissionGranted {
13+
case .granted:
14+
return "Granted"
15+
case .notGranted:
16+
return "Not Granted. Required to run. Click to open System Preferences."
17+
case .unknown:
18+
return ""
19+
}
1420
}
1521

1622
var body: some View {

Diff for: Core/Sources/Service/RealtimeSuggestionController.swift

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Combine
66
import Foundation
77
import Logger
88
import Preferences
9+
import Status
910
import QuartzCore
1011
import Workspace
1112
import XcodeInspector
@@ -124,10 +125,12 @@ public actor RealtimeSuggestionController {
124125
do {
125126
try await XcodeInspector.shared.safe.latestActiveXcode?
126127
.triggerCopilotCommand(name: "Sync Text Settings")
128+
await Status.shared.updateExtensionStatus(.succeeded)
127129
} catch {
128130
if filespace.codeMetadata.uti?.isEmpty ?? true {
129131
filespace.codeMetadata.uti = nil
130132
}
133+
await Status.shared.updateExtensionStatus(.failed)
131134
}
132135
}
133136
}

Diff for: Core/Sources/Service/Service.swift

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public final class Service {
3232
let globalShortcutManager: GlobalShortcutManager
3333
let keyBindingManager: KeyBindingManager
3434
let xcodeThemeController: XcodeThemeController = .init()
35+
public var markAsProcessing: (Bool) -> Void = { _ in }
3536

3637
@Dependency(\.toast) var toast
3738
var cancellable = Set<AnyCancellable>()

Diff for: Core/Sources/Service/SuggestionPresenter/PresentInWindowSuggestionPresenter.swift

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ struct PresentInWindowSuggestionPresenter {
3030
Task { @MainActor in
3131
let controller = Service.shared.guiController.widgetController
3232
controller.markAsProcessing(isProcessing)
33+
Service.shared.markAsProcessing(isProcessing)
3334
}
3435
}
3536

Diff for: Core/Sources/Service/XPCService.swift

+5-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import GitHubCopilotService
44
import LanguageServerProtocol
55
import Logger
66
import Preferences
7+
import Status
78
import XPCShared
89

910
public class XPCService: NSObject, XPCServiceProtocol {
@@ -16,8 +17,10 @@ public class XPCService: NSObject, XPCServiceProtocol {
1617
)
1718
}
1819

19-
public func getXPCServiceAccessibilityPermission(withReply reply: @escaping (Bool) -> Void) {
20-
reply(AXIsProcessTrusted())
20+
public func getXPCServiceAccessibilityPermission(withReply reply: @escaping (ObservedAXStatus) -> Void) {
21+
Task {
22+
reply(await Status.shared.getAXStatus())
23+
}
2124
}
2225

2326
// MARK: - Suggestion

Diff for: ExtensionService/AppDelegate+Menu.swift

+16-18
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import AppKit
22
import Foundation
33
import Preferences
4+
import Status
45
import SuggestionBasic
56
import XcodeInspector
67
import Logger
@@ -14,10 +15,6 @@ extension AppDelegate {
1415
.init("xcodeInspectorDebugMenu")
1516
}
1617

17-
fileprivate var accessibilityAPIPermissionMenuItemIdentifier: NSUserInterfaceItemIdentifier {
18-
.init("accessibilityAPIPermissionMenuItem")
19-
}
20-
2118
fileprivate var sourceEditorDebugMenu: NSUserInterfaceItemIdentifier {
2219
.init("sourceEditorDebugMenu")
2320
}
@@ -72,12 +69,12 @@ extension AppDelegate {
7269
xcodeInspectorDebug.submenu = xcodeInspectorDebugMenu
7370
xcodeInspectorDebug.isHidden = false
7471

75-
let accessibilityAPIPermission = NSMenuItem(
76-
title: "Accessibility Permission: N/A",
77-
action: nil,
72+
statusMenuItem = NSMenuItem(
73+
title: "",
74+
action: #selector(openStatusLink),
7875
keyEquivalent: ""
7976
)
80-
accessibilityAPIPermission.identifier = accessibilityAPIPermissionMenuItemIdentifier
77+
statusMenuItem.isHidden = true
8178

8279
let quitItem = NSMenuItem(
8380
title: "Quit",
@@ -126,7 +123,7 @@ extension AppDelegate {
126123
statusBarMenu.addItem(toggleIgnoreLanguage)
127124
statusBarMenu.addItem(.separator())
128125
statusBarMenu.addItem(copilotStatus)
129-
statusBarMenu.addItem(accessibilityAPIPermission)
126+
statusBarMenu.addItem(statusMenuItem)
130127
statusBarMenu.addItem(.separator())
131128
statusBarMenu.addItem(openDocs)
132129
statusBarMenu.addItem(openForum)
@@ -160,22 +157,14 @@ extension AppDelegate: NSMenuDelegate {
160157
item.identifier == toggleIgnoreLanguageMenuItemIdentifier
161158
}) {
162159
if let lang = DisabledLanguageList.shared.activeDocumentLanguage {
163-
toggleLanguage.title = "\(DisabledLanguageList.shared.isEnabled(lang) ? "Disable" : "Enable") Completions For \(lang.rawValue)"
160+
toggleLanguage.title = "\(DisabledLanguageList.shared.isEnabled(lang) ? "Disable" : "Enable") Completions for \(lang.rawValue)"
164161
toggleLanguage.action = #selector(toggleIgnoreLanguage)
165162
} else {
166163
toggleLanguage.title = "No Active Document"
167164
toggleLanguage.action = nil
168165
}
169166
}
170167

171-
if let accessibilityAPIPermission = menu.items.first(where: { item in
172-
item.identifier == accessibilityAPIPermissionMenuItemIdentifier
173-
}) {
174-
AXIsProcessTrusted()
175-
accessibilityAPIPermission.title =
176-
"Accessibility Permission: \(AXIsProcessTrusted() ? "Granted" : "Not Granted")"
177-
}
178-
179168
statusChecker.updateStatusInBackground(notify: { (status: String, isOk: Bool) in
180169
if let statusItem = menu.items.first(where: { item in
181170
item.identifier == self.copilotStatusMenuItemIdentifier
@@ -321,6 +310,15 @@ private extension AppDelegate {
321310
}
322311
}
323312
}
313+
314+
@objc func openStatusLink() {
315+
Task {
316+
let status = await Status.shared.getStatus()
317+
if let s = status.url, let url = URL(string: s) {
318+
NSWorkspace.shared.open(url)
319+
}
320+
}
321+
}
324322
}
325323

326324
private extension NSMenuItem {

Diff for: ExtensionService/AppDelegate.swift

+75-10
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Logger
66
import Preferences
77
import Service
88
import ServiceManagement
9+
import Status
910
import SwiftUI
1011
import UpdateChecker
1112
import UserDefaultsObserver
@@ -30,6 +31,7 @@ class ExtensionUpdateCheckerDelegate: UpdateCheckerDelegate {
3031
class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
3132
let service = Service.shared
3233
var statusBarItem: NSStatusItem!
34+
var statusMenuItem: NSMenuItem!
3335
var xpcController: XPCController?
3436
let updateChecker =
3537
UpdateChecker(
@@ -39,21 +41,29 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
3941
let statusChecker: AuthStatusChecker = AuthStatusChecker()
4042
var xpcExtensionService: XPCExtensionService?
4143
private var cancellables = Set<AnyCancellable>()
44+
private var progressView: NSProgressIndicator?
45+
private var idleIcon = NSImage(named: "MenuBarIcon")
4246

4347
func applicationDidFinishLaunching(_: Notification) {
4448
if ProcessInfo.processInfo.environment["IS_UNIT_TEST"] == "YES" { return }
4549
_ = XcodeInspector.shared
50+
service.markAsProcessing = { [weak self] in
51+
guard let self = self else { return }
52+
self.markAsProcessing($0)
53+
}
4654
service.start()
4755
AXIsProcessTrustedWithOptions([
4856
kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true,
4957
] as CFDictionary)
5058
setupQuitOnUpdate()
5159
setupQuitOnUserTerminated()
52-
setupQuitOnFeatureFlag()
5360
xpcController = .init()
5461
Logger.service.info("XPC Service started.")
5562
NSApp.setActivationPolicy(.accessory)
5663
buildStatusBarMenu()
64+
watchServiceStatus()
65+
watchAXStatus()
66+
updateStatusBarItem() // set the initial status
5767
}
5868

5969
@objc func quit() {
@@ -132,15 +142,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
132142
}
133143
}
134144

135-
func setupQuitOnFeatureFlag() {
136-
FeatureFlagNotifierImpl.shared.featureFlagsDidChange.sink { [weak self] (flags) in
137-
if flags.x != true {
138-
Logger.service.info("Xcode feature flag not granted, quitting.")
139-
self?.quit()
140-
}
141-
}.store(in: &cancellables)
142-
}
143-
144145
func requestAccessoryAPIPermission() {
145146
AXIsProcessTrustedWithOptions([
146147
kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true,
@@ -161,6 +162,70 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
161162
xpcExtensionService = service
162163
return service
163164
}
165+
166+
func watchServiceStatus() {
167+
let notifications = NotificationCenter.default.notifications(named: .serviceStatusDidChange)
168+
Task { [weak self] in
169+
for await _ in notifications {
170+
guard let self else { return }
171+
self.updateStatusBarItem()
172+
}
173+
}
174+
}
175+
176+
func watchAXStatus() {
177+
let osNotifications = DistributedNotificationCenter.default().notifications(named: NSNotification.Name("com.apple.accessibility.api"))
178+
Task { [weak self] in
179+
for await _ in osNotifications {
180+
guard let self else { return }
181+
self.updateStatusBarItem()
182+
}
183+
}
184+
}
185+
186+
func updateStatusBarItem() {
187+
Task { @MainActor in
188+
let status = await Status.shared.getStatus()
189+
let image = if status.system {
190+
NSImage(systemSymbolName: status.icon, accessibilityDescription: nil)
191+
} else {
192+
NSImage(named: status.icon)
193+
}
194+
idleIcon = image
195+
self.statusBarItem.button?.image = image
196+
if let message = status.message {
197+
// TODO switch to attributedTitle to enable line breaks and color.
198+
self.statusMenuItem.title = message
199+
self.statusMenuItem.isHidden = false
200+
self.statusMenuItem.isEnabled = status.url != nil
201+
} else {
202+
self.statusMenuItem.isHidden = true
203+
}
204+
}
205+
}
206+
207+
func markAsProcessing(_ isProcessing: Bool) {
208+
if !isProcessing {
209+
// No longer in progress
210+
progressView?.removeFromSuperview()
211+
progressView = nil
212+
statusBarItem.button?.image = idleIcon
213+
return
214+
}
215+
if progressView != nil {
216+
// Already in progress
217+
return
218+
}
219+
let progress = NSProgressIndicator()
220+
progress.style = .spinning
221+
progress.sizeToFit()
222+
progress.frame = statusBarItem.button?.bounds ?? .zero
223+
progress.isIndeterminate = true
224+
progress.startAnimation(nil)
225+
statusBarItem.button?.addSubview(progress)
226+
statusBarItem.button?.image = nil
227+
progressView = progress
228+
}
164229
}
165230

166231
extension NSRunningApplication {

0 commit comments

Comments
 (0)