mirror of
https://github.com/BigMarc/grammargem.git
synced 2026-10-13 17:01:55 +02:00
Stability (the "super buggy" feel): - Move the Accessibility-tree traversal OFF the main thread with a per-message timeout, so a busy/wedged target app can't beachball or hang GrammaGem. - Replace every unchecked `as! AXUIElement` force-cast with a CFTypeID-checked safe cast (AXSupport), eliminating crashes from misbehaving AX servers. - Never scan GrammaGem's own windows (self-PID exclusion). - Generation tokens + in-flight guard so superseded scans/downloads can't clobber state. Text safety: - Live fixes now read the element's CURRENT value before writing (and only apply a span edit when the field still matches the snapshot), so edits made since the last scan are never overwritten. Cap per-field check length. - Stable per-field identity (AX id / position) so the Live Check list no longer churns. - Include focus + identity in change-detection so tabbing updates the focused field. Other audited fixes: - Page blocker: conservative skip when a browser's URL can't be read. - Model download: validate on-disk size (reject HTML/truncated 200s), clean up partials on cancel/failure, handle URLError.cancelled. - Licensing: clear a server-confirmed-invalid license instead of honoring grace; show the real device limit instead of "0 Macs". - Hotkey re-register falls back to the default if a chosen combo is taken. - Daily free-tier counter tracks the live timezone. - "Fix focused field" ignores non-text controls. Feature: auto-install the on-device model in the background on first launch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
40 lines
1.7 KiB
Swift
40 lines
1.7 KiB
Swift
import Foundation
|
|
import ApplicationServices
|
|
|
|
/// Safe Accessibility helpers shared across the System layer.
|
|
///
|
|
/// `AXUIElementCopyAttributeValue` returning `.success` only guarantees a
|
|
/// non-null `CFTypeRef` — NOT that it is an `AXUIElement`. A misbehaving AX
|
|
/// server (Electron/Java/custom) can return a different CF type, and a forced
|
|
/// `as!` cast then traps and crashes the whole app. These helpers cast safely.
|
|
enum AX {
|
|
/// Safely bridge a CFTypeRef to AXUIElement, returning nil on type mismatch.
|
|
static func element(_ ref: CFTypeRef?) -> AXUIElement? {
|
|
guard let ref, CFGetTypeID(ref) == AXUIElementGetTypeID() else { return nil }
|
|
return (ref as! AXUIElement) // safe: type id verified above
|
|
}
|
|
|
|
/// Copy an attribute as an `AXUIElement` (nil on failure/type mismatch).
|
|
static func copyElement(_ element: AXUIElement, _ attribute: String) -> AXUIElement? {
|
|
var ref: CFTypeRef?
|
|
guard AXUIElementCopyAttributeValue(element, attribute as CFString, &ref) == .success else {
|
|
return nil
|
|
}
|
|
return AX.element(ref)
|
|
}
|
|
|
|
/// Copy an attribute as a `String` (nil on failure/wrong type).
|
|
static func copyString(_ element: AXUIElement, _ attribute: String) -> String? {
|
|
var ref: CFTypeRef?
|
|
guard AXUIElementCopyAttributeValue(element, attribute as CFString, &ref) == .success else {
|
|
return nil
|
|
}
|
|
return ref as? String
|
|
}
|
|
|
|
/// Bound each cross-process AX message so a wedged target app can't hang us.
|
|
static func setMessagingTimeout(_ element: AXUIElement, _ seconds: Float) {
|
|
AXUIElementSetMessagingTimeout(element, seconds)
|
|
}
|
|
}
|