Key changes from v9:
- SelectedTextChanged now includes rich userInfo:
direction (Next/Previous/Discontiguous) and
granularity (Character/Word/Line) inferred from
old vs new cursor position comparison
- Track lastAccessibilityCursorPos + lastAccessibilityCursorLine
ivars for position delta detection
- accessibilityInsertionPointLineNumber: buffer lines (count_lines)
instead of visual vpos
- accessibilityLineForIndex: buffer lines via count_lines
- accessibilityRangeForLine: buffer lines via find_newline_no_quit
- Skip notification on unchanged position (avoids VO repeating)
- Word granularity heuristic: same line, >1 char delta
Replace extern NSString* declarations with raw string literals
(@"AXTextStateChangeType" etc.) to avoid redeclaration type conflict
with NSAccessibilityConstants.h on macOS 26 Tahoe SDK where these
symbols were added to public headers.
Guard accessibility notifications with on_p && active_p so they only
fire when drawing the cursor in the selected window. Without this,
ns_draw_window_cursor is called for both old and new windows during
redisplay, and UAZoomChangeFocus fires for the old window last.
UAZoomChangeFocus is Apple's documented API for Zoom focus control.
NSAccessibility serves VoiceOver and other AT tools.
Both are needed - same approach as iTerm2.
References Apple developer documentation URLs in comments.
Based on verified research:
- UAZoomChangeFocus IS officially documented by Apple (UniversalAccess.h)
- iTerm2 uses UAZoomChangeFocus for Zoom tracking (PTYTextView.m)
- NSAccessibility alone insufficient for Zoom custom view tracking
- Added Apple documentation URLs in code comments
UAZoomChangeFocus (documented Apple API) + NSAccessibility.
References to official Apple documentation in comments.
Pattern matches iTerm2 implementation.
Key changes:
- Add accessibilityFrame on EmacsView returning cursor screen rect
(the standard mechanism used by Terminal.app, iTerm2, Firefox)
- Guard UAZoomChangeFocus with bundleIdentifier check (bare binaries
are silently ignored by the window server)
- Extensive English comments explaining both mechanisms
- Deduplicate legacy parameterized attribute via accessibilityBoundsForRange:
Root cause of Stéphane's failure: UAZoomChangeFocus communicates via the
window server which identifies apps by CFBundleIdentifier. Bare binaries
(src/emacs, symlinks) have no bundle ID and are ignored. NSAccessibility
notifications + accessibilityFrame work for ALL launch methods.
Compilation error: EmacsView does not declare NSAccessibility conformance
so accessibilityConvertScreenRect: is not visible to the compiler.
Replaced with manual coordinate conversion:
primaryH = NSScreen.screens.first.frame.height
cgRect.origin.y = primaryH - cgRect.origin.y - cgRect.size.height
Root cause found via research pipeline:
NSAccessibility notifications alone are insufficient for custom NSView.
macOS Zoom 'Follow keyboard focus' requires UAZoomChangeFocus() from
HIServices/UniversalAccess.h — same as iTerm2 and Chromium.
Squashed into single clean patch. 159 insertions, 2 files.
macOS Zoom is event-driven -- it only queries AXBoundsForRange after
receiving NSAccessibilitySelectedTextChangedNotification. Previous
version implemented the bounds methods but never posted notifications,
so Zoom never triggered a cursor position query.
Added:
- NSAccessibilityPostNotification(SelectedTextChanged) in ns_draw_window_cursor
- NSAccessibilityPostNotification(FocusedUIElementChanged) in windowDidBecomeKey
- accessibilityAttributeNames + accessibilityAttributeValue: for
NSAccessibilitySelectedTextRangeAttribute -> returns {0,0}
Old parameterized attribute API alone insufficient — macOS Zoom prefers
the new NSAccessibilityProtocol method accessibilityBoundsForRange:.
Also adds isAccessibilityElement returning YES.
Both APIs now implemented for compatibility across macOS versions.