Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
7485ff2
Merge tag '0.30.0' into develop
intitni Jan 22, 2024
4818634
Prevent the chat window size and position being reset when it's hidden
intitni Jan 25, 2024
23b7f3c
Use isWindowHidden to control the opacity of chat window
intitni Jan 25, 2024
ae6c551
Merge branch 'feature/fix-detached-window-resized-repositioned-once-d…
intitni Jan 25, 2024
6e6bc96
Add a dismiss button to non-compact mode suggestion
intitni Jan 26, 2024
97eb465
Truncate file name in the middle when it's too long
intitni Jan 26, 2024
e5293a1
Use accent color instead of indigo
intitni Jan 26, 2024
94bee9d
Bump GitHub Copilot version to 1.17.0
intitni Jan 26, 2024
2ed2634
Merge branch 'feature/bump-github-copilot-1-17-0' into develop
intitni Jan 26, 2024
b516513
Bump Codeium language server to 1.6.9
intitni Jan 26, 2024
4ad9263
Merge branch 'feature/bump-codeium-to-1.6.9' into develop
intitni Jan 26, 2024
9f1d0cf
Add AsyncExtensions to use a concurrency version of passthrough subject
intitni Jan 25, 2024
53d49ef
Adjust AXNotificationStream
intitni Jan 25, 2024
930780f
Handle AXNotification on app in XcodeAppInstanceInspector
intitni Jan 25, 2024
a464a49
Handle source editor AXNotifications in SourceEditor
intitni Jan 25, 2024
efdd979
Replace AXNotificationStream creation
intitni Jan 25, 2024
e564d0f
Update Logger
intitni Jan 25, 2024
0c8c20f
Minor adjustment
intitni Jan 26, 2024
a8537c0
Update
intitni Jan 26, 2024
5bdc538
Update
intitni Jan 26, 2024
8419b80
Merge branch 'feature/handle-ax-notifications-universally' into develop
intitni Jan 26, 2024
535cbba
WIP
intitni Jan 24, 2024
eeff068
Add accessibility api malfunction check
intitni Jan 24, 2024
143db5a
Skip checks within 5 seconds since last recovery
intitni Jan 27, 2024
85ddfe4
Extend debounce interval
intitni Jan 27, 2024
44e4634
Add check when activate Xcode
intitni Jan 27, 2024
a491f45
Update
intitni Jan 27, 2024
5421b6e
Adjust logs
intitni Jan 27, 2024
1c2ee31
Update
intitni Jan 27, 2024
d1fc4d6
Merge branch 'feature/accessibility-api-malfunction-recovery' into de…
intitni Jan 27, 2024
3e8ff22
Prevent getting multiple content from SourceEditor in a single run
intitni Jan 27, 2024
cd124aa
Implement cache for SourceEditor
intitni Jan 27, 2024
8b3ad69
Skip invalidating suggestion early if no suggestion presented
intitni Jan 27, 2024
786e19a
Prevent using split by \.newLine for faster speed
intitni Jan 27, 2024
6654aac
Prevent events to be sent to a store rapidly
intitni Jan 27, 2024
cf9cbd8
Make minimum suggestion debounce to 0.15
intitni Jan 27, 2024
e1231f1
Get content after we check cancellation
intitni Jan 27, 2024
569fb71
Remove logs
intitni Jan 27, 2024
95f2963
Update dependency
intitni Jan 27, 2024
26b8ebe
Adjust logs
intitni Jan 27, 2024
b482d82
Remove optional
intitni Jan 27, 2024
bdc9c0b
Update
intitni Jan 27, 2024
e29dca3
Merge branch 'feature/performance-fix-for-realtime-suggestion' into d…
intitni Jan 27, 2024
eda2e8a
Remove optional
intitni Jan 27, 2024
8d9b257
Bump version to 0.30.1
intitni Jan 28, 2024
20b560f
Add tests for SourceEditor.Cache
intitni Jan 28, 2024
9b75b43
Add todos
intitni Jan 28, 2024
3c72675
Update prompt
intitni Jan 28, 2024
d580cd4
Add missing task cancellation check
intitni Jan 28, 2024
da24d8b
Add todo
intitni Jan 28, 2024
cf9aa83
Update appcasts.xml
intitni Jan 28, 2024
5a62827
Update README.md
intitni Jan 28, 2024
077560c
Merge branch 'release/0.30.1'
intitni Jan 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add accessibility api malfunction check
  • Loading branch information
intitni committed Jan 26, 2024
commit eeff0686fd660491c4e095637d0abd091979428d
31 changes: 28 additions & 3 deletions Core/Sources/HostApp/DebugView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ final class DebugSettings: ObservableObject {
var disableFileContentManipulationByCheatsheet
@AppStorage(\.restartXcodeInspectorIfAccessibilityAPIIsMalfunctioning)
var restartXcodeInspectorIfAccessibilityAPIIsMalfunctioning
@AppStorage(\.restartXcodeInspectorIfAccessibilityAPIIsMalfunctioningNoTimer)
var restartXcodeInspectorIfAccessibilityAPIIsMalfunctioningNoTimer
@AppStorage(\.toastForTheReasonWhyXcodeInspectorNeedsToBeRestarted)
var toastForTheReasonWhyXcodeInspectorNeedsToBeRestarted
init() {}
}

Expand Down Expand Up @@ -76,9 +80,30 @@ struct DebugSettingsView: View {
Toggle(isOn: $settings.disableFileContentManipulationByCheatsheet) {
Text("Disable file content manipulation by cheatsheet")
}

Toggle(isOn: $settings.restartXcodeInspectorIfAccessibilityAPIIsMalfunctioning) {
Text("Re-activate Xcode Inspector when Accessibility API malfunctioning detected")

Group {
Toggle(
isOn: $settings
.restartXcodeInspectorIfAccessibilityAPIIsMalfunctioning
) {
Text(
"Re-activate Xcode Inspector when Accessibility API malfunctioning detected"
)
}

Toggle(
isOn: $settings
.restartXcodeInspectorIfAccessibilityAPIIsMalfunctioningNoTimer
) {
Text("Trigger malfunctioning detection only with events")
}

Toggle(
isOn: $settings
.toastForTheReasonWhyXcodeInspectorNeedsToBeRestarted
) {
Text("Toast for the reason of re-activation of Xcode Inspector")
}
}

Button("Reset migration version to 0") {
Expand Down
2 changes: 0 additions & 2 deletions Core/Sources/Service/RealtimeSuggestionController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,10 @@ public actor RealtimeSuggestionController {

switch notification.kind {
case .valueChanged:
Logger.service.debug("Receive valueChanged from editor")
await cancelInFlightTasks()
await self.triggerPrefetchDebounced()
await self.notifyEditingFileChange(editor: sourceEditor.element)
case .selectedTextChanged:
Logger.service.debug("Receive selectedTextChanged from editor")
guard let fileURL = XcodeInspector.shared.activeDocumentURL
else { break }
await PseudoCommandHandler().invalidateRealtimeSuggestionsIfNeeded(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,8 +356,6 @@ public struct WidgetFeature: ReducerProtocol {
await send(.panel(.switchToAnotherEditorAndUpdateContent))

for await notification in notifications {
Logger.service.debug("Receive \(notification.kind) from Xcode")

try Task.checkCancellation()

// Hide the widgets before switching to another window/editor
Expand Down Expand Up @@ -402,11 +400,10 @@ public struct WidgetFeature: ReducerProtocol {

return .run { send in
if #available(macOS 13.0, *) {
for await notification in merge(
for await _ in merge(
selectionRangeChange.debounce(for: Duration.milliseconds(500)),
scroll
) {
Logger.service.debug("Receive \(notification.kind) from editor")
guard xcodeInspector.latestActiveXcode != nil else { return }
try Task.checkCancellation()
await send(.updateWindowLocation(animated: false))
Expand Down
2 changes: 2 additions & 0 deletions Tool/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ let package = Package(
"SuggestionModel",
"AXNotificationStream",
"Logger",
"Toast",
"Preferences",
.product(name: "AsyncExtensions", package: "AsyncExtensions"),
.product(name: "AsyncAlgorithms", package: "swift-async-algorithms"),
]
Expand Down
16 changes: 15 additions & 1 deletion Tool/Sources/Preferences/Keys.swift
Original file line number Diff line number Diff line change
Expand Up @@ -572,9 +572,23 @@ public extension UserDefaultPreferenceKeys {

var restartXcodeInspectorIfAccessibilityAPIIsMalfunctioning: FeatureFlag {
.init(
defaultValue: false,
defaultValue: true,
key: "FeatureFlag-RestartXcodeInspectorIfAccessibilityAPIIsMalfunctioning"
)
}

var restartXcodeInspectorIfAccessibilityAPIIsMalfunctioningNoTimer: FeatureFlag {
.init(
defaultValue: true,
key: "FeatureFlag-RestartXcodeInspectorIfAccessibilityAPIIsMalfunctioningNoTimer"
)
}

var toastForTheReasonWhyXcodeInspectorNeedsToBeRestarted: FeatureFlag {
.init(
defaultValue: false,
key: "FeatureFlag-ToastForTheReasonWhyXcodeInspectorNeedsToBeRestarted"
)
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ public final class XcodeAppInstanceInspector: AppInstanceInspector {
observeAXNotifications()

try await Task.sleep(nanoseconds: 3_000_000_000)
// Sometimes the focused window may not be rea?dy on app launch.
// Sometimes the focused window may not be ready on app launch.
if !(focusedWindow is WorkspaceXcodeWindowInspector) {
observeFocusedWindow()
}
Expand Down Expand Up @@ -200,7 +200,7 @@ public final class XcodeAppInstanceInspector: AppInstanceInspector {
}

@MainActor
private func observeAXNotifications() {
func observeAXNotifications() {
longRunningTasks.forEach { $0.cancel() }
longRunningTasks = []

Expand Down
183 changes: 98 additions & 85 deletions Tool/Sources/XcodeInspector/XcodeInspector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -196,99 +196,42 @@ public final class XcodeInspector: ObservableObject {
group.addTask { [weak self] in
while true {
guard let self else { return }
try await Task.sleep(nanoseconds: 10_000_000_000)
Logger.service.debug("""
Check for Accessibility Malfunctioning:
Source Editor: \({
if let editor = self.focusedEditor {
return editor.element.description
}
return "Not Found"
}())
Focused Element: \({
if let element = self.focusedElement {
return "\(element.description), \(element.identifier), \(element.role)"
}
return "Not Found"
}())

Accessibility API Permission: \(
AXIsProcessTrusted() ? "Granted" :
"Not Granted"
)
App: \(
self.activeApplication?.runningApplication
.bundleIdentifier ?? ""
)
Focused Element: \({
guard let element = self.activeApplication?.appElement
.focusedElement
else {
return "Not Found"
}
return "\(element.description), \(element.identifier), \(element.role)"
}())
First Source Editor: \({
guard let element = self.activeApplication?.appElement
.firstChild(where: \.isSourceEditor)
else {
return "Not Found"
}
return "\(element.description), \(element.identifier), \(element.role)"
}())
""")

if let editor = self.focusedEditor, !editor.element.isSourceEditor {
NSWorkspace.shared.notificationCenter.post(
name: .accessibilityAPIMalfunctioning,
object: "Source Editor Element Corrupted"
)
} else if let element = self.activeXcode?.appElement.focusedElement {
if element.description != self.focusedElement?.description ||
element.identifier != self.focusedElement?.role
{
NSWorkspace.shared.notificationCenter.post(
name: .accessibilityAPIMalfunctioning,
object: "Element Inconsistency"
)
}
if UserDefaults.shared.value(
for: \.restartXcodeInspectorIfAccessibilityAPIIsMalfunctioningNoTimer
) {
return
}
}
}

group.addTask {
let sequence = DistributedNotificationCenter.default()
.notifications(named: .init("com.apple.accessibility.api"))
for await notification in sequence {
if AXIsProcessTrusted() {
Logger.service.debug("Accessibility API Permission Granted")
} else {
Logger.service.debug("Accessibility API Permission Not Granted")
NSWorkspace.shared.notificationCenter.post(
name: .accessibilityAPIMalfunctioning,
object: "Accessibility API Permission Check"
)

try await Task.sleep(nanoseconds: 10_000_000_000)
await MainActor.run {
self.checkForAccessibilityMalfunction("Timer")
}
}
}
}



group.addTask { [weak self] in // malfunctioning
let sequence = NSWorkspace.shared.notificationCenter
.notifications(named: .accessibilityAPIMalfunctioning)
for await notification in sequence {
guard let self else { return }
let toast = self.toast
toast.toast(
content: "Accessibility API malfunction detected: \(notification.object as? String ?? "")",
type: .warning
)
if UserDefaults.shared
.value(for: \.toastForTheReasonWhyXcodeInspectorNeedsToBeRestarted)
{
toast.toast(
content: """
Accessibility API malfunction detected: \
\(notification.object as? String ?? "").
Resetting active Xcode.
""",
type: .warning
)
}
if let activeXcode {
toast.toast(content: "Resetting active Xcode", type: .warning)
await MainActor.run {
self.setActiveXcode(activeXcode)
activeXcode.observeAXNotifications()
}
}
}
Expand Down Expand Up @@ -318,12 +261,10 @@ public final class XcodeInspector: ObservableObject {
activeWorkspaceURL = xcode.workspaceURL
focusedWindow = xcode.focusedWindow

let setFocusedElement = { [weak self] in
let setFocusedElement = { @MainActor [weak self] in
guard let self else { return }
focusedElement = xcode.appElement.focusedElement
Logger.service.debug("Update focused element.")
if let editorElement = focusedElement, editorElement.isSourceEditor {
Logger.service.debug("Focused on source editor.")
focusedEditor = .init(
runningApplication: xcode.runningApplication,
element: editorElement
Expand All @@ -345,15 +286,35 @@ public final class XcodeInspector: ObservableObject {
setFocusedElement()
let focusedElementChanged = Task { @MainActor in
for await notification in xcode.axNotifications {
guard notification.kind == .focusedUIElementChanged else { continue }
Logger.service.debug("Update focused element")
try Task.checkCancellation()
setFocusedElement()
if notification.kind == .focusedUIElementChanged {
Logger.service.debug("Update focused element")
try Task.checkCancellation()
setFocusedElement()
}
}
}

activeXcodeObservations.insert(focusedElementChanged)

if UserDefaults.shared
.value(for: \.restartXcodeInspectorIfAccessibilityAPIIsMalfunctioning)
{
let malfunctionCheck = Task { @MainActor [weak self] in
if #available(macOS 13.0, *) {
let notifications = xcode.axNotifications.filter {
$0.kind == .uiElementDestroyed
}.debounce(for: .milliseconds(500))
for await _ in notifications {
guard let self else { return }
try Task.checkCancellation()
self.checkForAccessibilityMalfunction("Element Destroyed")
}
}
}

activeXcodeObservations.insert(malfunctionCheck)
}

xcode.$completionPanel.receive(on: DispatchQueue.main).sink { [weak self] element in
self?.completionPanel = element
}.store(in: &activeXcodeCancellable)
Expand All @@ -374,5 +335,57 @@ public final class XcodeInspector: ObservableObject {
self?.focusedWindow = window
}.store(in: &activeXcodeCancellable)
}

@MainActor
private func checkForAccessibilityMalfunction(_ source: String) {
Logger.service.debug("""
Check for Accessibility Malfunctioning:
Source Editor: \({
if let editor = self.focusedEditor {
return editor.element.description
}
return "Not Found"
}())
Focused Element: \({
if let element = self.focusedElement {
return "\(element.description), \(element.identifier), \(element.role)"
}
return "Not Found"
}())

Accessibility API Permission: \(
AXIsProcessTrusted() ? "Granted" :
"Not Granted"
)
App: \(
activeApplication?.runningApplication
.bundleIdentifier ?? ""
)
Focused Element: \({
guard let element = self.activeApplication?.appElement
.focusedElement
else {
return "Not Found"
}
return "\(element.description), \(element.identifier), \(element.role)"
}())
""")

if let editor = focusedEditor, !editor.element.isSourceEditor {
NSWorkspace.shared.notificationCenter.post(
name: .accessibilityAPIMalfunctioning,
object: "Source Editor Element Corrupted: \(source)"
)
} else if let element = activeXcode?.appElement.focusedElement {
if element.description != focusedElement?.description ||
element.role != focusedElement?.role
{
NSWorkspace.shared.notificationCenter.post(
name: .accessibilityAPIMalfunctioning,
object: "Element Inconsistency: \(source)"
)
}
}
}
}