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.
136 lines
5.4 KiB
Diff
136 lines
5.4 KiB
Diff
From b863369a4d33a3488c3fa9de0cf0eca687b40447 Mon Sep 17 00:00:00 2001
|
|
From: Martin Sukany <martin@sukany.cz>
|
|
Date: Sat, 28 Feb 2026 12:58:11 +0100
|
|
Subject: [PATCH 6/8] doc: add VoiceOver accessibility section to macOS
|
|
appendix
|
|
|
|
* doc/emacs/macos.texi (VoiceOver Accessibility): New node between
|
|
'Mac / GNUstep Events' and 'GNUstep Support'. Document screen reader
|
|
usage, keyboard navigation, completion announcements, ns-accessibility-
|
|
enabled, and known limitations. Use @xref for cross-reference at
|
|
sentence start. Correct description of ns-accessibility-enabled
|
|
default: initial value is nil, set automatically at startup.
|
|
---
|
|
doc/emacs/macos.texi | 76 ++++++++++++++++++++++++++++++++++++++++++++
|
|
src/nsterm.m | 10 ++++--
|
|
2 files changed, 83 insertions(+), 3 deletions(-)
|
|
|
|
diff --git a/doc/emacs/macos.texi b/doc/emacs/macos.texi
|
|
index 6bd334f48e..8d4a7825d8 100644
|
|
--- a/doc/emacs/macos.texi
|
|
+++ b/doc/emacs/macos.texi
|
|
@@ -36,6 +36,7 @@ Support}), but we hope to improve it in the future.
|
|
* Mac / GNUstep Basics:: Basic Emacs usage under GNUstep or macOS.
|
|
* Mac / GNUstep Customization:: Customizations under GNUstep or macOS.
|
|
* Mac / GNUstep Events:: How window system events are handled.
|
|
+* VoiceOver Accessibility:: Screen reader support on macOS.
|
|
* GNUstep Support:: Details on status of GNUstep support.
|
|
@end menu
|
|
|
|
@@ -272,6 +273,81 @@ and return the result as a string. You can also use the Lisp function
|
|
services and receive the results back. Note that you may need to
|
|
restart Emacs to access newly-available services.
|
|
|
|
+@node VoiceOver Accessibility
|
|
+@section VoiceOver Accessibility (macOS)
|
|
+@cindex VoiceOver
|
|
+@cindex accessibility (macOS)
|
|
+@cindex screen reader (macOS)
|
|
+@cindex Zoom, cursor tracking (macOS)
|
|
+
|
|
+ When built with the Cocoa interface on macOS, Emacs exposes buffer
|
|
+content, cursor position, mode lines, and interactive elements to the
|
|
+macOS accessibility subsystem. This enables use with VoiceOver,
|
|
+Apple's built-in screen reader, and with other assistive technology
|
|
+such as macOS Zoom.
|
|
+
|
|
+ Toggle VoiceOver with @kbd{Cmd-F5} (or via System Settings,
|
|
+Accessibility, VoiceOver). When Emacs is focused, VoiceOver announces
|
|
+the buffer name and current line. Standard Emacs navigation produces
|
|
+speech feedback:
|
|
+
|
|
+@itemize @bullet
|
|
+@item
|
|
+Arrow keys read individual characters (left/right) or full lines
|
|
+(up/down).
|
|
+@item
|
|
+@kbd{M-f} and @kbd{M-b} announce words.
|
|
+@item
|
|
+@kbd{C-n} and @kbd{C-p} read the destination line.
|
|
+@item
|
|
+Shift-modified movement announces selected or deselected text.
|
|
+@item
|
|
+@key{TAB} and @kbd{S-@key{TAB}} navigate interactive elements
|
|
+(buttons, links, completion candidates) within a buffer.
|
|
+@end itemize
|
|
+
|
|
+ The @file{*Completions*} buffer announces each completion candidate
|
|
+as you navigate, even while keyboard focus remains in the minibuffer.
|
|
+
|
|
+ macOS Zoom (System Settings, Accessibility, Zoom) tracks the Emacs
|
|
+cursor automatically when set to follow keyboard focus. The cursor
|
|
+position is communicated via @code{UAZoomChangeFocus} and the
|
|
+@code{AXBoundsForRange} accessibility attribute.
|
|
+
|
|
+@vindex ns-accessibility-enabled
|
|
+ To disable the accessibility interface entirely (for instance, to
|
|
+eliminate overhead on systems where assistive technology is not in
|
|
+use), set @code{ns-accessibility-enabled} to @code{nil}. Emacs
|
|
+detects the presence of assistive technology at startup and sets this
|
|
+variable automatically; the initial value is @code{nil}.
|
|
+
|
|
+@subheading Known Limitations
|
|
+
|
|
+@itemize @bullet
|
|
+@item
|
|
+Very large buffers (tens of megabytes) may cause slow initial
|
|
+accessibility text extraction. Once cached, subsequent queries
|
|
+are fast.
|
|
+@item
|
|
+Mode-line text extraction handles only character glyphs. Mode lines
|
|
+using icon fonts (e.g., icon-based mode-lines)
|
|
+produce incomplete accessibility text.
|
|
+@item
|
|
+The accessibility virtual element tree is rebuilt automatically on
|
|
+window configuration changes (splits, deletions, new buffers).
|
|
+@item
|
|
+Right-to-left (bidi) text is exposed correctly as buffer content,
|
|
+but @code{accessibilityRangeForPosition} hit-testing assumes
|
|
+left-to-right glyph layout.
|
|
+@end itemize
|
|
+
|
|
+ This support is available only on the Cocoa build. GNUstep has a
|
|
+different accessibility model and is not yet supported.
|
|
+Block-style cursors are handled
|
|
+correctly: character navigation announces the character at the cursor
|
|
+position, not the character before it.
|
|
+
|
|
+
|
|
@node GNUstep Support
|
|
@section GNUstep Support
|
|
|
|
diff --git a/src/nsterm.m b/src/nsterm.m
|
|
index 7c118045bd..a0598a73c2 100644
|
|
--- a/src/nsterm.m
|
|
+++ b/src/nsterm.m
|
|
@@ -14709,9 +14709,13 @@ Nil means use fullscreen the old (< 10.7) way. The old way works better with
|
|
|
|
DEFVAR_BOOL ("ns-accessibility-enabled", ns_accessibility_enabled,
|
|
doc: /* Non-nil enables Zoom cursor tracking and VoiceOver support.
|
|
-Emacs sets this automatically at startup when macOS Zoom is active or
|
|
-any assistive technology (VoiceOver, Switch Control, etc.) is connected,
|
|
-and updates it whenever that state changes. You can override manually:
|
|
+Emacs detects at startup whether macOS Zoom is active or an assistive
|
|
+technology (VoiceOver, Switch Control, etc.) is connected, and sets
|
|
+this variable accordingly. It updates automatically when accessibility
|
|
+state changes. The initial value is nil; it becomes non-nil only when
|
|
+an AT is detected.
|
|
+
|
|
+You can override the auto-detection:
|
|
|
|
(setq ns-accessibility-enabled t) ; always on
|
|
(setq ns-accessibility-enabled nil) ; always off
|
|
--
|
|
2.43.0
|
|
|