mirror of
https://github.com/BigMarc/grammargem.git
synced 2026-10-13 17:01:55 +02:00
Fix Accessibility text capture failing system-wide
AXUIElementCreateSystemWide() returns kAXErrorCannotComplete (-25204) for a background/menu-bar app on recent macOS, so the focused-element read returned nil and GrammaGem recognised no text or text areas in any app. Add AX.focusedElement(), which falls back to the frontmost app's PID-based AX element (reliable from an accessory process), and route TextCapture's selected-text and focused-field reads through it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
35ae2065b3
commit
e3ba7ff7e0
@ -1,4 +1,5 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
import AppKit
|
||||||
import ApplicationServices
|
import ApplicationServices
|
||||||
|
|
||||||
/// Safe Accessibility helpers shared across the System layer.
|
/// Safe Accessibility helpers shared across the System layer.
|
||||||
@ -36,4 +37,29 @@ enum AX {
|
|||||||
static func setMessagingTimeout(_ element: AXUIElement, _ seconds: Float) {
|
static func setMessagingTimeout(_ element: AXUIElement, _ seconds: Float) {
|
||||||
AXUIElementSetMessagingTimeout(element, seconds)
|
AXUIElementSetMessagingTimeout(element, seconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The currently-focused UI element across the system — robust to the
|
||||||
|
/// system-wide element failing.
|
||||||
|
///
|
||||||
|
/// On recent macOS, `AXUIElementCreateSystemWide()` returns
|
||||||
|
/// `kAXErrorCannotComplete` (-25204) for *every* query made by a background /
|
||||||
|
/// accessory process (which is exactly what a menu-bar app like GrammaGem is).
|
||||||
|
/// When that happens the system-wide focused-element read yields nil and the
|
||||||
|
/// app "sees" no text or text fields at all. Resolving the focused element
|
||||||
|
/// through the **frontmost application's** own AX element works reliably in
|
||||||
|
/// that context, so we prefer the system-wide read (one IPC hop, correct on
|
||||||
|
/// OSes where it works) and transparently fall back to the per-app read.
|
||||||
|
static func focusedElement() -> AXUIElement? {
|
||||||
|
let systemWide = AXUIElementCreateSystemWide()
|
||||||
|
setMessagingTimeout(systemWide, 0.5)
|
||||||
|
if let element = copyElement(systemWide, kAXFocusedUIElementAttribute as String) {
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
guard let pid = NSWorkspace.shared.frontmostApplication?.processIdentifier else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
let axApp = AXUIElementCreateApplication(pid)
|
||||||
|
setMessagingTimeout(axApp, 0.5)
|
||||||
|
return copyElement(axApp, kAXFocusedUIElementAttribute as String)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,9 +53,7 @@ final class TextCapture {
|
|||||||
|
|
||||||
private func captureViaAccessibility() -> Capture? {
|
private func captureViaAccessibility() -> Capture? {
|
||||||
guard AXIsProcessTrusted() else { return nil }
|
guard AXIsProcessTrusted() else { return nil }
|
||||||
let systemWide = AXUIElementCreateSystemWide()
|
guard let focused = AX.focusedElement() else { return nil }
|
||||||
|
|
||||||
guard let focused = AX.copyElement(systemWide, kAXFocusedUIElementAttribute) else { return nil }
|
|
||||||
|
|
||||||
var valueRef: CFTypeRef?
|
var valueRef: CFTypeRef?
|
||||||
guard AXUIElementCopyAttributeValue(
|
guard AXUIElementCopyAttributeValue(
|
||||||
@ -93,8 +91,7 @@ final class TextCapture {
|
|||||||
/// The full text value of the focused element (not just the selection).
|
/// The full text value of the focused element (not just the selection).
|
||||||
func focusedFieldText() -> (text: String, element: AXUIElement)? {
|
func focusedFieldText() -> (text: String, element: AXUIElement)? {
|
||||||
guard AXIsProcessTrusted() else { return nil }
|
guard AXIsProcessTrusted() else { return nil }
|
||||||
let systemWide = AXUIElementCreateSystemWide()
|
guard let focused = AX.focusedElement() else { return nil }
|
||||||
guard let focused = AX.copyElement(systemWide, kAXFocusedUIElementAttribute) else { return nil }
|
|
||||||
|
|
||||||
// Only treat genuine editable text roles as fixable, so we don't report
|
// Only treat genuine editable text roles as fixable, so we don't report
|
||||||
// "nothing selected" failures on sliders/steppers/custom controls.
|
// "nothing selected" failures on sliders/steppers/custom controls.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user