Skip to content

Commit 535cbba

Browse files
committed
WIP
1 parent 8419b80 commit 535cbba

File tree

4 files changed

+133
-5
lines changed

4 files changed

+133
-5
lines changed

Core/Sources/HostApp/DebugView.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ final class DebugSettings: ObservableObject {
1919
@AppStorage(\.disableGitIgnoreCheck) var disableGitIgnoreCheck
2020
@AppStorage(\.disableFileContentManipulationByCheatsheet)
2121
var disableFileContentManipulationByCheatsheet
22+
@AppStorage(\.restartXcodeInspectorIfAccessibilityAPIIsMalfunctioning)
23+
var restartXcodeInspectorIfAccessibilityAPIIsMalfunctioning
2224
init() {}
2325
}
2426

@@ -74,6 +76,10 @@ struct DebugSettingsView: View {
7476
Toggle(isOn: $settings.disableFileContentManipulationByCheatsheet) {
7577
Text("Disable file content manipulation by cheatsheet")
7678
}
79+
80+
Toggle(isOn: $settings.restartXcodeInspectorIfAccessibilityAPIIsMalfunctioning) {
81+
Text("Re-activate Xcode Inspector when Accessibility API malfunctioning detected")
82+
}
7783

7884
Button("Reset migration version to 0") {
7985
UserDefaults.shared.set(nil, forKey: "OldMigrationVersion")

ExtensionService/AppDelegate+Menu.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,15 +126,15 @@ extension AppDelegate: NSMenuDelegate {
126126
.append(.text("Active Workspace: \(inspector.activeWorkspaceURL?.path ?? "N/A")"))
127127
menu.items
128128
.append(.text("Active Document: \(inspector.activeDocumentURL?.path ?? "N/A")"))
129-
129+
130130
if let focusedWindow = inspector.focusedWindow {
131131
menu.items.append(.text(
132132
"Active Window: \(focusedWindow.uiElement.identifier)"
133133
))
134134
} else {
135135
menu.items.append(.text("Active Window: N/A"))
136136
}
137-
137+
138138
if let focusedElement = inspector.focusedElement {
139139
menu.items.append(.text(
140140
"Focused Element: \(focusedElement.description)"
@@ -144,9 +144,9 @@ extension AppDelegate: NSMenuDelegate {
144144
}
145145

146146
if let sourceEditor = inspector.focusedEditor {
147-
menu.items.append(.text(
148-
"Active Source Editor: \(sourceEditor.element.isSourceEditor ? "Found" : "Error")"
149-
))
147+
let label = sourceEditor.element.description
148+
menu.items
149+
.append(.text("Active Source Editor: \(label.isEmpty ? "Unknown" : label)"))
150150
} else {
151151
menu.items.append(.text("Active Source Editor: N/A"))
152152
}

Tool/Sources/Preferences/Keys.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,5 +569,12 @@ public extension UserDefaultPreferenceKeys {
569569
key: "FeatureFlag-DisableEnhancedWorkspace"
570570
)
571571
}
572+
573+
var restartXcodeInspectorIfAccessibilityAPIIsMalfunctioning: FeatureFlag {
574+
.init(
575+
defaultValue: false,
576+
key: "FeatureFlag-RestartXcodeInspectorIfAccessibilityAPIIsMalfunctioning"
577+
)
578+
}
572579
}
573580

Tool/Sources/XcodeInspector/XcodeInspector.swift

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,17 @@ import Foundation
66
import Logger
77
import Preferences
88
import SuggestionModel
9+
import Toast
10+
11+
public extension Notification.Name {
12+
static let accessibilityAPIMalfunctioning = Notification.Name("accessibilityAPIMalfunctioning")
13+
}
914

1015
public final class XcodeInspector: ObservableObject {
1116
public static let shared = XcodeInspector()
1217

18+
private var toast: ToastController { ToastControllerDependencyKey.liveValue }
19+
1320
private var cancellable = Set<AnyCancellable>()
1421
private var activeXcodeObservations = Set<Task<Void, Error>>()
1522
private var appChangeObservations = Set<Task<Void, Never>>()
@@ -182,6 +189,110 @@ public final class XcodeInspector: ObservableObject {
182189
}
183190
}
184191
}
192+
193+
if UserDefaults.shared
194+
.value(for: \.restartXcodeInspectorIfAccessibilityAPIIsMalfunctioning)
195+
{
196+
group.addTask { [weak self] in
197+
while true {
198+
guard let self else { return }
199+
try await Task.sleep(nanoseconds: 10_000_000_000)
200+
Logger.service.debug("""
201+
Check for Accessibility Malfunctioning:
202+
Source Editor: \({
203+
if let editor = self.focusedEditor {
204+
return editor.element.description
205+
}
206+
return "Not Found"
207+
}())
208+
Focused Element: \({
209+
if let element = self.focusedElement {
210+
return "\(element.description), \(element.identifier), \(element.role)"
211+
}
212+
return "Not Found"
213+
}())
214+
215+
Accessibility API Permission: \(
216+
AXIsProcessTrusted() ? "Granted" :
217+
"Not Granted"
218+
)
219+
App: \(
220+
self.activeApplication?.runningApplication
221+
.bundleIdentifier ?? ""
222+
)
223+
Focused Element: \({
224+
guard let element = self.activeApplication?.appElement
225+
.focusedElement
226+
else {
227+
return "Not Found"
228+
}
229+
return "\(element.description), \(element.identifier), \(element.role)"
230+
}())
231+
First Source Editor: \({
232+
guard let element = self.activeApplication?.appElement
233+
.firstChild(where: \.isSourceEditor)
234+
else {
235+
return "Not Found"
236+
}
237+
return "\(element.description), \(element.identifier), \(element.role)"
238+
}())
239+
""")
240+
241+
if let editor = self.focusedEditor, !editor.element.isSourceEditor {
242+
NSWorkspace.shared.notificationCenter.post(
243+
name: .accessibilityAPIMalfunctioning,
244+
object: "Source Editor Element Corrupted"
245+
)
246+
} else if let element = self.activeXcode?.appElement.focusedElement {
247+
if element.description != self.focusedElement?.description ||
248+
element.identifier != self.focusedElement?.role
249+
{
250+
NSWorkspace.shared.notificationCenter.post(
251+
name: .accessibilityAPIMalfunctioning,
252+
object: "Element Inconsistency"
253+
)
254+
}
255+
}
256+
}
257+
}
258+
259+
group.addTask {
260+
let sequence = DistributedNotificationCenter.default()
261+
.notifications(named: .init("com.apple.accessibility.api"))
262+
for await notification in sequence {
263+
if AXIsProcessTrusted() {
264+
Logger.service.debug("Accessibility API Permission Granted")
265+
} else {
266+
Logger.service.debug("Accessibility API Permission Not Granted")
267+
NSWorkspace.shared.notificationCenter.post(
268+
name: .accessibilityAPIMalfunctioning,
269+
object: "Accessibility API Permission Check"
270+
)
271+
}
272+
}
273+
}
274+
}
275+
276+
277+
278+
group.addTask { [weak self] in // malfunctioning
279+
let sequence = NSWorkspace.shared.notificationCenter
280+
.notifications(named: .accessibilityAPIMalfunctioning)
281+
for await notification in sequence {
282+
guard let self else { return }
283+
let toast = self.toast
284+
toast.toast(
285+
content: "Accessibility API malfunction detected: \(notification.object as? String ?? "")",
286+
type: .warning
287+
)
288+
if let activeXcode {
289+
toast.toast(content: "Resetting active Xcode", type: .warning)
290+
await MainActor.run {
291+
self.setActiveXcode(activeXcode)
292+
}
293+
}
294+
}
295+
}
185296
}
186297
}
187298

@@ -210,19 +321,23 @@ public final class XcodeInspector: ObservableObject {
210321
let setFocusedElement = { [weak self] in
211322
guard let self else { return }
212323
focusedElement = xcode.appElement.focusedElement
324+
Logger.service.debug("Update focused element.")
213325
if let editorElement = focusedElement, editorElement.isSourceEditor {
326+
Logger.service.debug("Focused on source editor.")
214327
focusedEditor = .init(
215328
runningApplication: xcode.runningApplication,
216329
element: editorElement
217330
)
218331
} else if let element = focusedElement,
219332
let editorElement = element.firstParent(where: \.isSourceEditor)
220333
{
334+
Logger.service.debug("Focused on child of source editor.")
221335
focusedEditor = .init(
222336
runningApplication: xcode.runningApplication,
223337
element: editorElement
224338
)
225339
} else {
340+
Logger.service.debug("No source editor found.")
226341
focusedEditor = nil
227342
}
228343
}

0 commit comments

Comments
 (0)