Files
emacs-doom/patches
Daneel b6a576a312 patches: fix discontiguous moves reading only first word
For discontiguous moves (teleports, org-agenda items separated by blank
lines, multi-line jumps), AXSelectedTextChanged was sent with
AXTextSelectionDirection=discontiguous.  VoiceOver interprets an
explicit discontiguous direction as 're-anchor only' and reads only the
word at the cursor, ignoring VoiceOver's own line-browse mode.

The pre-review code (51f5944) omitted direction/granularity for all
moves and let VoiceOver determine what to read from its navigation state.
This correctly reads the full line when the VoiceOver rotor is in line
mode, which is the typical setting for text navigation.

Fix: omit AXTextSelectionDirection and AXTextSelectionGranularity from
AXSelectedTextChanged when direction=discontiguous.  Include them only
for sequential moves (direction=next/previous), where the explicit hint
ensures VoiceOver reads the correct unit without an extra state query.

This fixes:
- org-agenda / org-super-agenda j/k: items separated by blank lines
  cause singleLineMove=NO (non-adjacent AX indices), so direction was
  discontiguous -> only first word read.
- Any other navigation that crosses blank or invisible lines.

Sequential moves (C-n/C-p, single adjacent j/k) still include
direction + granularity=line for reliable full-line reads.
2026-03-02 21:30:42 +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 --