EMACS NS ACCESSIBILITY PATCHES
================================
author: Martin Sukany <martin@sukany.cz>
This directory contains two independent patch sets for the Emacs NS
(Cocoa) port:
A. Standalone Zoom patch (0000)
B. VoiceOver accessibility patch series (0001-0008)
Each can be applied independently. They do not depend on each other.
PATCH A: ZOOM CURSOR TRACKING (0000)
-------------------------------------
0000 ns: integrate with macOS Zoom for cursor tracking
A minimal patch that informs macOS Zoom of the text cursor position
after every physical cursor redraw. When Zoom is enabled (System
Settings -> Accessibility -> Zoom -> Follow keyboard focus), the
zoomed viewport automatically tracks the Emacs insertion point.
Files modified:
src/nsterm.h (+4 lines: lastZoomCursorRect ivar)
src/nsterm.m (+66 lines: cursor store + UAZoomChangeFocus)
etc/NEWS (+8 lines)
Implementation:
ns_draw_window_cursor stores the cursor rect in
view->lastZoomCursorRect and calls UAZoomChangeFocus() with
CG-space coordinates. A fallback call in ns_update_end ensures
Zoom tracks the cursor even after window switches (C-x o) where
the physical cursor may not be redrawn.
Coordinate conversion: EmacsView pixels (AppKit, flipped) ->
NSWindow -> NSScreen -> CGRect with y-flip for CoreGraphics
top-left origin.
No user option is needed: UAZoomEnabled() returns false when Zoom
is not active, so the overhead is a single function call per
redisplay cycle.
PATCH B: VOICEOVER ACCESSIBILITY (0001-0008)
----------------------------------------------
0001 ns: add accessibility base classes and text extraction
0002 ns: implement buffer accessibility element (core protocol)
0003 ns: add buffer notification dispatch and mode-line element
0004 ns: add interactive span elements for Tab navigation
0005 ns: integrate accessibility with EmacsView and redisplay
0006 doc: add VoiceOver accessibility section to macOS appendix
0007 ns: announce overlay completion candidates for VoiceOver
0008 ns: announce child frame completion candidates for VoiceOver
Files modified:
src/nsterm.h (~120 lines: class declarations, ivars)
src/nsterm.m (~3400 lines: implementation)
doc/emacs/macos.texi (~50 lines: documentation)
etc/NEWS (~8 lines)
This patch series adds comprehensive VoiceOver accessibility support
to the NS port. Before this patch, Emacs exposed only a minimal,
largely broken accessibility interface: EmacsView identified itself
as a generic NSAccessibilityGroup with no text content, no cursor
tracking, and no notifications.
ARCHITECTURE
------------
Virtual element tree above EmacsView:
EmacsAccessibilityElement (base)
+-- EmacsAccessibilityBuffer (AXTextArea; one per window)
+-- EmacsAccessibilityModeLine (AXStaticText; mode line)
+-- EmacsAccessibilityInteractiveSpan (AXButton/Link; Tab nav)
Each buffer element maintains a text cache with visible-run mapping
(O(log n) index lookup) and a precomputed line index (O(log L) line
queries). Notifications are posted asynchronously via dispatch_async
to prevent VoiceOver deadlocks.
Full details in the commit messages of each patch.
PERFORMANCE
-----------
ns-accessibility-enabled (DEFVAR_BOOL, default nil):
Set automatically at startup when macOS Zoom or an assistive
technology (VoiceOver, Switch Control) is detected.
When nil, no virtual elements are built, no notifications are
posted, and ns_draw_window_cursor skips the cursor rect store.
Zero overhead for users who do not use assistive technology.
When enabled:
- Text cache rebuilds only on BUF_MODIFF change (not per-keystroke)
- Index lookups are O(log n) via binary search on visible runs
- Line queries are O(log L) via precomputed lineStartOffsets
- Interactive span scan runs only when dirty flag is set
- No character cap: full buffer exposed, but cache is lazy
THREADING MODEL
---------------
Main thread: all Lisp calls, buffer mutations, notification posting.
AX thread: VoiceOver queries dispatch_sync to main thread.
Async notifications: dispatch_async prevents deadlock (same pattern
as WebKit's AXObjectCacheMac).
KNOWN LIMITATIONS
-----------------
- Mode line: CHAR_GLYPH only (icon fonts produce incomplete text)
- Overlay face matching: string containment ("current", "selected")
- GNUstep excluded (#ifdef NS_IMPL_COCOA)
- No multi-frame coordination
- Child frame childFrameLastCandidate (per-view ivar, freed in dealloc)
TESTING
-------
See TESTING.txt for the full test matrix and results.
-- end of README --