Files
emacs-doom/patches
Daneel 73563be72d patches: fix VoiceOver reads only first word in org-agenda
When AXSelectedTextChanged is posted from the parent EmacsView (NSView)
with UIElementsKey pointing to the EmacsAXBuffer element, VoiceOver calls
accessibilityLineForIndex: on the VIEW rather than on the focused element.
In specialised buffers (org-agenda, org-super-agenda) where line geometry
differs from plain text, the view returns an incorrect range and VoiceOver
reads only the first word at the cursor (e.g. 'La' or 'Liga') instead of
the full line.

Plain text buffers were unaffected because the fallback geometry happened
to be correct for simple line layouts.

Fix: post AXSelectedTextChanged on self (the EmacsAXBuffer element)
instead of on self.emacsView.  This causes VoiceOver to call
accessibilityLineForIndex: on the element that owns the selection, which
returns the correct line range in all buffer types.  Remove UIElementsKey
(unnecessary when posting from the element itself).

This aligns with the pre-review code (51f5944) which always posted
AX notifications directly on the focused element.
2026-03-03 09:43:26 +01:00
..

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 t):
    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 static lastCandidate leaks at exit (minor)


TESTING
-------

  See TESTING.txt for the full test matrix and results.


-- end of README --