When Emacs moves the cursor (emacsMovedCursor=YES), we post
FocusedUIElementChanged on the NSWindow to re-anchor VoiceOver's
browse cursor. For C-n/C-p this notification races with
AXSelectedTextChanged(granularity=line) and causes VoiceOver to
drop the line-read speech.
Arrow key movement works because VoiceOver intercepts those as AX
selection changes (setAccessibilitySelectedTextRange:), making
voiceoverSetPoint=YES and emacsMovedCursor=NO, so no
FocusedUIElementChanged is posted.
Fix: skip FocusedUIElementChanged for sequential C-n/C-p moves
(isCtrlNP). AXSelectedTextChanged with direction=next/previous +
granularity=line is sufficient for VoiceOver to read the new line.
FocusedUIElementChanged is only needed for discontiguous jumps
(]], M-<, isearch, xref etc.) where VoiceOver must re-anchor.
Also merge duplicate comment blocks and fix two compile errors
from a64d24c that Martin caught during testing.
136 lines
5.4 KiB
Diff
136 lines
5.4 KiB
Diff
From 33ed790921c1d78dec79f803807deae65fff365e 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
|
|
|