patches: fix forward dependency (helpers moved to patch 1)

This commit is contained in:
2026-02-28 10:24:39 +01:00
parent 5016155c8a
commit fa28bb52e1
5 changed files with 197 additions and 192 deletions

View File

@@ -1,36 +1,39 @@
From 53a22c5d2014002256acf1c1bc14af2dfbb26469 Mon Sep 17 00:00:00 2001 From de04e84b555706cfe0d39ed4a388895443e19e02 Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz> From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 10:10:55 +0100 Date: Sat, 28 Feb 2026 10:24:30 +0100
Subject: [PATCH 1/5] ns: add accessibility base classes and text extraction Subject: [PATCH 1/5] ns: add accessibility base classes and text extraction
Add the foundation for macOS VoiceOver accessibility in the NS Add the foundation for macOS VoiceOver accessibility in the NS
(Cocoa) port. No existing code paths are modified. (Cocoa) port. No existing code paths are modified.
* src/nsterm.h (ns_ax_visible_run): New struct for buffer-to-UTF-16 * src/nsterm.h (ns_ax_visible_run): New struct.
index mapping. (EmacsAccessibilityElement): New base class.
(EmacsAccessibilityElement): New base class for virtual AX elements.
(EmacsAccessibilityBuffer, EmacsAccessibilityModeLine) (EmacsAccessibilityBuffer, EmacsAccessibilityModeLine)
(EmacsAccessibilityInteractiveSpan): Forward declarations. (EmacsAccessibilityInteractiveSpan): Forward declarations.
(EmacsAXSpanType): New enum for span classification. (EmacsAXSpanType): New enum.
(EmacsView): New ivars accessibilityElements, lastSelectedWindow, (EmacsView): New ivars for accessibility state.
accessibilityTreeValid, lastAccessibilityCursorRect.
* src/nsterm.m: Include intervals.h for TEXT_PROP_MEANS_INVISIBLE. * src/nsterm.m: Include intervals.h for TEXT_PROP_MEANS_INVISIBLE.
(ns_ax_buffer_text): New function. Build accessibility string with (NS_AX_TEXT_CAP): New macro, 100000.
visible-run mapping; skip invisible text per spec. (ns_ax_buffer_text): New function.
(ns_ax_mode_line_text): New function. Extract CHAR_GLYPH text. (ns_ax_mode_line_text): New function.
(ns_ax_frame_for_range): New function. Screen rect via glyph matrix. (ns_ax_frame_for_range): New function.
(ns_ax_post_notification, ns_ax_post_notification_with_info): New (ns_ax_completion_string_from_prop): New function.
functions. dispatch_async wrappers preventing VoiceOver deadlock. (ns_ax_window_buffer_object): New function.
(ns_ax_completion_string_from_prop, ns_ax_window_buffer_object) (ns_ax_window_end_charpos): New function.
(ns_ax_window_end_charpos, ns_ax_text_prop_at) (ns_ax_text_prop_at): New function.
(ns_ax_next_prop_change, ns_ax_get_span_label): New utility helpers. (ns_ax_next_prop_change): New function.
(ns_ax_get_span_label): New function.
(ns_ax_post_notification): New function.
(ns_ax_post_notification_with_info): New function.
(EmacsAccessibilityElement): Implement base class. (EmacsAccessibilityElement): Implement base class.
(syms_of_nsterm): Register accessibility DEFSYM and DEFVAR (syms_of_nsterm): Register accessibility DEFSYM and DEFVAR
ns-accessibility-enabled. ns-accessibility-enabled.
Tested on macOS 14 Sonoma. Builds cleanly; no functional change.
--- ---
src/nsterm.h | 119 +++++++++++++++++ src/nsterm.h | 119 +++++++++++++
src/nsterm.m | 355 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/nsterm.m | 468 +++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 474 insertions(+) 2 files changed, 587 insertions(+)
diff --git a/src/nsterm.h b/src/nsterm.h diff --git a/src/nsterm.h b/src/nsterm.h
index 7c1ee4c..393fc4c 100644 index 7c1ee4c..393fc4c 100644
@@ -177,7 +180,7 @@ index 7c1ee4c..393fc4c 100644
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index 74e4ad5..c91ec90 100644 index 74e4ad5..935919f 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -46,6 +46,7 @@ GNUstep port and post-20 update by Adrian Robert (arobert@cogsci.ucsd.edu) @@ -46,6 +46,7 @@ GNUstep port and post-20 update by Adrian Robert (arobert@cogsci.ucsd.edu)
@@ -188,7 +191,7 @@ index 74e4ad5..c91ec90 100644
#include "systime.h" #include "systime.h"
#include "character.h" #include "character.h"
#include "xwidget.h" #include "xwidget.h"
@@ -6856,6 +6857,329 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action) @@ -6856,6 +6857,442 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action)
} }
#endif #endif
@@ -453,9 +456,122 @@ index 74e4ad5..c91ec90 100644
+ ns_ax_text_selection_granularity_line = 3, + ns_ax_text_selection_granularity_line = 3,
+}; +};
+ +
+/* Extract announcement string from completion--string property value.
+ The property can be a plain Lisp string (simple completion) or
+ a list ("candidate" "annotation") for annotated completions.
+ Returns nil on failure. */
+static NSString *
+ns_ax_completion_string_from_prop (Lisp_Object cstr)
+{
+ if (STRINGP (cstr))
+ return [NSString stringWithLispString: cstr];
+ if (CONSP (cstr) && STRINGP (XCAR (cstr)))
+ return [NSString stringWithLispString: XCAR (cstr)];
+ return nil;
+}
+ +
+/* Return the Emacs buffer Lisp object for window W, or Qnil. */
+static Lisp_Object
+ns_ax_window_buffer_object (struct window *w)
+{
+ if (!w)
+ return Qnil;
+ if (!BUFFERP (w->contents))
+ return Qnil;
+ return w->contents;
+}
+ +
+/* Compute visible-end charpos for window W.
+ Emacs stores it as BUF_Z - window_end_pos.
+ Falls back to BUF_ZV when window_end_valid is false (e.g., when
+ called from an AX getter before the next redisplay cycle). */
+static ptrdiff_t
+ns_ax_window_end_charpos (struct window *w, struct buffer *b)
+{
+ if (!w->window_end_valid)
+ return BUF_ZV (b);
+ return BUF_Z (b) - w->window_end_pos;
+}
+ +
+/* Fetch text property PROP at charpos POS in BUF_OBJ. */
+static Lisp_Object
+ns_ax_text_prop_at (ptrdiff_t pos, Lisp_Object prop, Lisp_Object buf_obj)
+{
+ Lisp_Object plist = Ftext_properties_at (make_fixnum (pos), buf_obj);
+ /* Third argument to Fplist_get is PREDICATE (Emacs 29+), not a
+ default value. Qnil selects the default `eq' comparison. */
+ return Fplist_get (plist, prop, Qnil);
+}
+
+/* Next charpos where PROP changes, capped at LIMIT. */
+static ptrdiff_t
+ns_ax_next_prop_change (ptrdiff_t pos, Lisp_Object prop,
+ Lisp_Object buf_obj, ptrdiff_t limit)
+{
+ Lisp_Object result
+ = Fnext_single_property_change (make_fixnum (pos), prop,
+ buf_obj, make_fixnum (limit));
+ return FIXNUMP (result) ? XFIXNUM (result) : limit;
+}
+
+/* Build label for span [START, END) in BUF_OBJ.
+ Priority: completion--string → buffer text → help-echo. */
+static NSString *
+ns_ax_get_span_label (ptrdiff_t start, ptrdiff_t end,
+ Lisp_Object buf_obj)
+{
+ Lisp_Object cs = ns_ax_text_prop_at (start, Qns_ax_completion__string,
+ buf_obj);
+ if (STRINGP (cs))
+ return [NSString stringWithLispString: cs];
+
+ if (end > start)
+ {
+ Lisp_Object substr = Fbuffer_substring_no_properties (
+ make_fixnum (start), make_fixnum (end));
+ if (STRINGP (substr))
+ {
+ NSString *s = [NSString stringWithLispString: substr];
+ s = [s stringByTrimmingCharactersInSet:
+ [NSCharacterSet whitespaceAndNewlineCharacterSet]];
+ if (s.length > 0)
+ return s;
+ }
+ }
+
+ Lisp_Object he = ns_ax_text_prop_at (start, Qhelp_echo, buf_obj);
+ if (STRINGP (he))
+ return [NSString stringWithLispString: he];
+
+ return @"";
+}
+
+/* Post AX notifications asynchronously to prevent deadlock.
+ NSAccessibilityPostNotification may synchronously invoke VoiceOver
+ callbacks that dispatch_sync back to the main queue. If we are
+ already on the main queue (e.g., inside postAccessibilityUpdates
+ called from ns_update_end), that dispatch_sync deadlocks.
+ Deferring via dispatch_async lets the current method return first,
+ freeing the main queue for VoiceOver's dispatch_sync calls. */
+
+static inline void
+ns_ax_post_notification (id element,
+ NSAccessibilityNotificationName name)
+{
+ dispatch_async (dispatch_get_main_queue (), ^{
+ NSAccessibilityPostNotification (element, name);
+ });
+}
+
+static inline void
+ns_ax_post_notification_with_info (id element,
+ NSAccessibilityNotificationName name,
+ NSDictionary *info)
+{
+ dispatch_async (dispatch_get_main_queue (), ^{
+ NSAccessibilityPostNotificationWithUserInfo (element, name, info);
+ });
+}
+ +
+@implementation EmacsAccessibilityElement +@implementation EmacsAccessibilityElement
+ +
@@ -518,7 +634,7 @@ index 74e4ad5..c91ec90 100644
/* ========================================================================== /* ==========================================================================
EmacsView implementation EmacsView implementation
@@ -11312,6 +11636,28 @@ syms_of_nsterm (void) @@ -11312,6 +11749,28 @@ syms_of_nsterm (void)
DEFSYM (Qns_drag_operation_generic, "ns-drag-operation-generic"); DEFSYM (Qns_drag_operation_generic, "ns-drag-operation-generic");
DEFSYM (Qns_handle_drag_motion, "ns-handle-drag-motion"); DEFSYM (Qns_handle_drag_motion, "ns-handle-drag-motion");
@@ -547,7 +663,7 @@ index 74e4ad5..c91ec90 100644
Fput (Qalt, Qmodifier_value, make_fixnum (alt_modifier)); Fput (Qalt, Qmodifier_value, make_fixnum (alt_modifier));
Fput (Qhyper, Qmodifier_value, make_fixnum (hyper_modifier)); Fput (Qhyper, Qmodifier_value, make_fixnum (hyper_modifier));
Fput (Qmeta, Qmodifier_value, make_fixnum (meta_modifier)); Fput (Qmeta, Qmodifier_value, make_fixnum (meta_modifier));
@@ -11460,6 +11806,15 @@ Note that this does not apply to images. @@ -11460,6 +11919,15 @@ Note that this does not apply to images.
This variable is ignored on Mac OS X < 10.7 and GNUstep. */); This variable is ignored on Mac OS X < 10.7 and GNUstep. */);
ns_use_srgb_colorspace = YES; ns_use_srgb_colorspace = YES;

View File

@@ -1,38 +1,39 @@
From 757988a19f7aef1f03ec277f898d92bdd4f2607e Mon Sep 17 00:00:00 2001 From af5c91f537511e4d3f5d6a7d86cf63bd89482f56 Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz> From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 10:10:55 +0100 Date: Sat, 28 Feb 2026 10:24:31 +0100
Subject: [PATCH 2/5] ns: implement buffer and mode-line accessibility elements Subject: [PATCH 2/5] ns: implement buffer and mode-line accessibility elements
Add the full NSAccessibility text protocol for Emacs windows and
mode-line readout.
* src/nsterm.m (ns_ax_find_completion_overlay_range): New function. * src/nsterm.m (ns_ax_find_completion_overlay_range): New function.
(ns_ax_event_is_line_nav_key): New function. (ns_ax_event_is_line_nav_key): New function.
(ns_ax_completion_text_for_span): New function. (ns_ax_completion_text_for_span): New function.
(EmacsAccessibilityBuffer): Implement NSAccessibility protocol: (EmacsAccessibilityBuffer): Implement NSAccessibility protocol.
text cache with @synchronized, visible-run binary search O(log n), (EmacsAccessibilityBuffer ensureTextCache): Text cache with
selectedTextRange, lineForIndex/indexForLine, frameForRange, visible-run array, invalidated on modiff/overlay/window changes.
rangeForPosition, setAccessibilitySelectedTextRange, setAccessibility- (EmacsAccessibilityBuffer accessibilityIndexForCharpos:): Binary
Focused. search O(log n) index mapping.
(EmacsAccessibilityBuffer postTextChangedNotification:): New method. (EmacsAccessibilityBuffer charposForAccessibilityIndex:): Inverse.
ValueChanged with edit details. (EmacsAccessibilityBuffer postTextChangedNotification:): ValueChanged
with edit type details.
(EmacsAccessibilityBuffer postFocusedCursorNotification:direction: (EmacsAccessibilityBuffer postFocusedCursorNotification:direction:
granularity:markActive:oldMarkActive:): New method. Hybrid granularity:markActive:oldMarkActive:): Hybrid SelectedTextChanged /
SelectedTextChanged / AnnouncementRequested per WebKit pattern. AnnouncementRequested per WebKit pattern.
(EmacsAccessibilityBuffer postCompletionAnnouncementForBuffer:point:): (EmacsAccessibilityBuffer postCompletionAnnouncementForBuffer:
New method. Announce completion candidates in non-focused buffers. point:): Announce non-focused buffer completions.
(EmacsAccessibilityBuffer postAccessibilityNotificationsForFrame:): (EmacsAccessibilityBuffer postAccessibilityNotificationsForFrame:):
New method. Main dispatch: edit vs cursor-move vs no-change. Main dispatch: edit vs cursor-move vs no-change.
(EmacsAccessibilityModeLine): Implement AXStaticText element. (EmacsAccessibilityModeLine): Implement AXStaticText element.
Tested on macOS 14 with VoiceOver. Verified: buffer reading, line
navigation, word/character announcements, completions, mode-line.
--- ---
src/nsterm.m | 1620 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/nsterm.m | 1620 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 1620 insertions(+) 1 file changed, 1620 insertions(+)
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index c91ec90..90db3b7 100644 index 935919f..c1cb602 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -7177,6 +7177,1626 @@ enum { @@ -7290,6 +7290,1626 @@ ns_ax_post_notification_with_info (id element,
@end @end

View File

@@ -1,28 +1,29 @@
From 7c1ad53ed32fc4b2650f0af51dd4d8b5ed87bdf4 Mon Sep 17 00:00:00 2001 From 70909599d2a8355784c3510652037e5726d02ff8 Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz> From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 10:10:55 +0100 Date: Sat, 28 Feb 2026 10:24:31 +0100
Subject: [PATCH 3/5] ns: add interactive span elements for Tab navigation Subject: [PATCH 3/5] ns: add interactive span elements for Tab navigation
Add lightweight child elements for Tab-navigable interactive text.
* src/nsterm.m (ns_ax_scan_interactive_spans): New function. Scan * src/nsterm.m (ns_ax_scan_interactive_spans): New function. Scan
visible range with O(n/skip) property-skip optimization. Priority: visible range with O(n/skip) property-skip optimization. Priority:
widget > button > follow-link > org-link > completion-candidate > widget > button > follow-link > org-link > completion-candidate >
keymap-overlay. keymap-overlay.
(EmacsAccessibilityInteractiveSpan): Implement AXButton/AXLink (EmacsAccessibilityInteractiveSpan): Implement AXButton/AXLink
elements with AXPress action. elements with AXPress action for buttons and links.
(EmacsAccessibilityBuffer(InteractiveSpans)): New category. (EmacsAccessibilityBuffer(InteractiveSpans)): New category.
accessibilityChildrenInNavigationOrder for Tab/Shift-Tab cycling accessibilityChildrenInNavigationOrder for Tab/Shift-Tab cycling
with wrap-around. with wrap-around and VoiceOver focus notification.
Tested on macOS 14 with VoiceOver. Verified: Tab-cycling through
org-mode links, *Completions* candidates, widget buttons.
--- ---
src/nsterm.m | 403 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/nsterm.m | 286 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 403 insertions(+) 1 file changed, 286 insertions(+)
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index 90db3b7..db5e4b3 100644 index c1cb602..10b63f4 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -8797,6 +8797,409 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem, @@ -8910,6 +8910,292 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
@end @end
@@ -32,123 +33,6 @@ index 90db3b7..db5e4b3 100644
+ EmacsAccessibilityInteractiveSpan — helpers and implementation + EmacsAccessibilityInteractiveSpan — helpers and implementation
+ =================================================================== */ + =================================================================== */
+ +
+/* Extract announcement string from completion--string property value.
+ The property can be a plain Lisp string (simple completion) or
+ a list ("candidate" "annotation") for annotated completions.
+ Returns nil on failure. */
+static NSString *
+ns_ax_completion_string_from_prop (Lisp_Object cstr)
+{
+ if (STRINGP (cstr))
+ return [NSString stringWithLispString: cstr];
+ if (CONSP (cstr) && STRINGP (XCAR (cstr)))
+ return [NSString stringWithLispString: XCAR (cstr)];
+ return nil;
+}
+
+/* Return the Emacs buffer Lisp object for window W, or Qnil. */
+static Lisp_Object
+ns_ax_window_buffer_object (struct window *w)
+{
+ if (!w)
+ return Qnil;
+ if (!BUFFERP (w->contents))
+ return Qnil;
+ return w->contents;
+}
+
+/* Compute visible-end charpos for window W.
+ Emacs stores it as BUF_Z - window_end_pos.
+ Falls back to BUF_ZV when window_end_valid is false (e.g., when
+ called from an AX getter before the next redisplay cycle). */
+static ptrdiff_t
+ns_ax_window_end_charpos (struct window *w, struct buffer *b)
+{
+ if (!w->window_end_valid)
+ return BUF_ZV (b);
+ return BUF_Z (b) - w->window_end_pos;
+}
+
+/* Fetch text property PROP at charpos POS in BUF_OBJ. */
+static Lisp_Object
+ns_ax_text_prop_at (ptrdiff_t pos, Lisp_Object prop, Lisp_Object buf_obj)
+{
+ Lisp_Object plist = Ftext_properties_at (make_fixnum (pos), buf_obj);
+ /* Third argument to Fplist_get is PREDICATE (Emacs 29+), not a
+ default value. Qnil selects the default `eq' comparison. */
+ return Fplist_get (plist, prop, Qnil);
+}
+
+/* Next charpos where PROP changes, capped at LIMIT. */
+static ptrdiff_t
+ns_ax_next_prop_change (ptrdiff_t pos, Lisp_Object prop,
+ Lisp_Object buf_obj, ptrdiff_t limit)
+{
+ Lisp_Object result
+ = Fnext_single_property_change (make_fixnum (pos), prop,
+ buf_obj, make_fixnum (limit));
+ return FIXNUMP (result) ? XFIXNUM (result) : limit;
+}
+
+/* Build label for span [START, END) in BUF_OBJ.
+ Priority: completion--string → buffer text → help-echo. */
+static NSString *
+ns_ax_get_span_label (ptrdiff_t start, ptrdiff_t end,
+ Lisp_Object buf_obj)
+{
+ Lisp_Object cs = ns_ax_text_prop_at (start, Qns_ax_completion__string,
+ buf_obj);
+ if (STRINGP (cs))
+ return [NSString stringWithLispString: cs];
+
+ if (end > start)
+ {
+ Lisp_Object substr = Fbuffer_substring_no_properties (
+ make_fixnum (start), make_fixnum (end));
+ if (STRINGP (substr))
+ {
+ NSString *s = [NSString stringWithLispString: substr];
+ s = [s stringByTrimmingCharactersInSet:
+ [NSCharacterSet whitespaceAndNewlineCharacterSet]];
+ if (s.length > 0)
+ return s;
+ }
+ }
+
+ Lisp_Object he = ns_ax_text_prop_at (start, Qhelp_echo, buf_obj);
+ if (STRINGP (he))
+ return [NSString stringWithLispString: he];
+
+ return @"";
+}
+
+/* Post AX notifications asynchronously to prevent deadlock.
+ NSAccessibilityPostNotification may synchronously invoke VoiceOver
+ callbacks that dispatch_sync back to the main queue. If we are
+ already on the main queue (e.g., inside postAccessibilityUpdates
+ called from ns_update_end), that dispatch_sync deadlocks.
+ Deferring via dispatch_async lets the current method return first,
+ freeing the main queue for VoiceOver's dispatch_sync calls. */
+
+static inline void
+ns_ax_post_notification (id element,
+ NSAccessibilityNotificationName name)
+{
+ dispatch_async (dispatch_get_main_queue (), ^{
+ NSAccessibilityPostNotification (element, name);
+ });
+}
+
+static inline void
+ns_ax_post_notification_with_info (id element,
+ NSAccessibilityNotificationName name,
+ NSDictionary *info)
+{
+ dispatch_async (dispatch_get_main_queue (), ^{
+ NSAccessibilityPostNotificationWithUserInfo (element, name, info);
+ });
+}
+
+/* Scan visible range of window W for interactive spans. +/* Scan visible range of window W for interactive spans.
+ Returns NSArray<EmacsAccessibilityInteractiveSpan *>. + Returns NSArray<EmacsAccessibilityInteractiveSpan *>.
+ +

View File

@@ -1,29 +1,34 @@
From 9ec896a226144676a36908938899fc90c2f5730b Mon Sep 17 00:00:00 2001 From a846de7dda7051a80f363b5f5db916db7868fde6 Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz> From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 10:10:55 +0100 Date: Sat, 28 Feb 2026 10:24:31 +0100
Subject: [PATCH 4/5] ns: integrate accessibility with EmacsView and redisplay Subject: [PATCH 4/5] ns: integrate accessibility with EmacsView and redisplay
Wire the accessibility infrastructure into EmacsView and the Wire the accessibility infrastructure into EmacsView and the
redisplay cycle. redisplay cycle. After this patch, VoiceOver and Zoom are active.
* src/nsterm.m (ns_update_end): Call [view postAccessibilityUpdates]. * src/nsterm.m (ns_update_end): Call [view postAccessibilityUpdates].
(ns_draw_phys_cursor): Store cursor rect; call UAZoomChangeFocus. (ns_draw_phys_cursor): Store cursor rect; call UAZoomChangeFocus.
(EmacsView dealloc): Release accessibilityElements. (EmacsView dealloc): Release accessibilityElements.
(EmacsView windowDidBecomeKey): Post FocusedUIElementChanged and (EmacsView windowDidBecomeKey): Post accessibility focus notification.
SelectedTextChanged. (ns_ax_collect_windows): New function.
(ns_ax_collect_windows): New function. Walk window tree creating
virtual elements.
(EmacsView rebuildAccessibilityTree): New method. (EmacsView rebuildAccessibilityTree): New method.
(EmacsView invalidateAccessibilityTree): New method. (EmacsView invalidateAccessibilityTree): New method.
(EmacsView accessibilityChildren): New method. (EmacsView accessibilityChildren): New method.
(EmacsView accessibilityFocusedUIElement): New method. (EmacsView accessibilityFocusedUIElement): New method.
(EmacsView postAccessibilityUpdates): New method. Detect tree (EmacsView postAccessibilityUpdates): New method.
change, window switch, per-buffer changes; re-entrance guard. (EmacsView accessibilityBoundsForRange:): New method.
(EmacsView accessibilityBoundsForRange:): New method. Cursor rect
for Zoom with focused-element delegation.
(EmacsView accessibilityParameterizedAttributeNames): New method. (EmacsView accessibilityParameterizedAttributeNames): New method.
(EmacsView accessibilityAttributeValue:forParameter:): New method. (EmacsView accessibilityAttributeValue:forParameter:): New method.
* etc/NEWS: Document VoiceOver accessibility support. * etc/NEWS: Document VoiceOver accessibility support.
Tested on macOS 14 with VoiceOver and Zoom. Full end-to-end: buffer
navigation, cursor tracking, window switching, completions, evil-mode
block cursor, org-mode folded headings, indirect buffers.
Known limitations:
- Bidi: accessibilityRangeForPosition assumes LTR glyph layout.
- Mode-line icons: image/stretch glyphs not extracted.
- Text cap: buffers exceeding NS_AX_TEXT_CAP (100K) truncated.
--- ---
etc/NEWS | 13 ++ etc/NEWS | 13 ++
src/nsterm.m | 397 ++++++++++++++++++++++++++++++++++++++++++++++++++- src/nsterm.m | 397 ++++++++++++++++++++++++++++++++++++++++++++++++++-
@@ -54,7 +59,7 @@ index 7367e3c..608650e 100644
** Re-introduced dictation, lost in Emacs v30 (macOS). ** Re-introduced dictation, lost in Emacs v30 (macOS).
We lost macOS dictation in v30 when migrating to NSTextInputClient. We lost macOS dictation in v30 when migrating to NSTextInputClient.
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index db5e4b3..421a6a4 100644 index 10b63f4..d7ff6c3 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -1105,6 +1105,11 @@ ns_update_end (struct frame *f) @@ -1105,6 +1105,11 @@ ns_update_end (struct frame *f)
@@ -113,7 +118,7 @@ index db5e4b3..421a6a4 100644
ns_focus (f, NULL, 0); ns_focus (f, NULL, 0);
NSGraphicsContext *ctx = [NSGraphicsContext currentContext]; NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
@@ -7180,7 +7222,6 @@ enum { @@ -7293,7 +7335,6 @@ ns_ax_post_notification_with_info (id element,
@@ -121,7 +126,7 @@ index db5e4b3..421a6a4 100644
static BOOL static BOOL
ns_ax_find_completion_overlay_range (struct buffer *b, ptrdiff_t point, ns_ax_find_completion_overlay_range (struct buffer *b, ptrdiff_t point,
ptrdiff_t *out_start, ptrdiff_t *out_start,
@@ -8798,7 +8839,6 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem, @@ -8911,7 +8952,6 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
@end @end
@@ -129,7 +134,7 @@ index db5e4b3..421a6a4 100644
/* =================================================================== /* ===================================================================
EmacsAccessibilityInteractiveSpan — helpers and implementation EmacsAccessibilityInteractiveSpan — helpers and implementation
=================================================================== */ =================================================================== */
@@ -9245,6 +9285,7 @@ ns_ax_scan_interactive_spans (struct window *w, @@ -9241,6 +9281,7 @@ ns_ax_scan_interactive_spans (struct window *w,
[layer release]; [layer release];
#endif #endif
@@ -137,7 +142,7 @@ index db5e4b3..421a6a4 100644
[[self menu] release]; [[self menu] release];
[super dealloc]; [super dealloc];
} }
@@ -10593,6 +10634,32 @@ ns_in_echo_area (void) @@ -10589,6 +10630,32 @@ ns_in_echo_area (void)
XSETFRAME (event.frame_or_window, emacsframe); XSETFRAME (event.frame_or_window, emacsframe);
kbd_buffer_store_event (&event); kbd_buffer_store_event (&event);
ns_send_appdefined (-1); // Kick main loop ns_send_appdefined (-1); // Kick main loop
@@ -170,7 +175,7 @@ index db5e4b3..421a6a4 100644
} }
@@ -11830,6 +11897,332 @@ ns_in_echo_area (void) @@ -11826,6 +11893,332 @@ ns_in_echo_area (void)
return fs_state; return fs_state;
} }

View File

@@ -1,13 +1,12 @@
From 0e09f3e4d8edac1c157130f733f3bff18d758c5f Mon Sep 17 00:00:00 2001 From 30dd99a0530092614b47ac7b30b19728359475db Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz> From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 10:10:55 +0100 Date: Sat, 28 Feb 2026 10:24:31 +0100
Subject: [PATCH 5/5] doc: add VoiceOver accessibility section to macOS Subject: [PATCH 5/5] doc: add VoiceOver accessibility section to macOS
appendix appendix
* doc/emacs/macos.texi (VoiceOver Accessibility): New node. Document * doc/emacs/macos.texi (VoiceOver Accessibility): New node. Document
screen reader usage, keyboard navigation feedback, completion screen reader usage, keyboard navigation, completion announcements,
announcements, Zoom cursor tracking, ns-accessibility-enabled, and Zoom cursor tracking, ns-accessibility-enabled, known limitations.
known limitations.
--- ---
doc/emacs/macos.texi | 75 ++++++++++++++++++++++++++++++++++++++++++++ doc/emacs/macos.texi | 75 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 75 insertions(+) 1 file changed, 75 insertions(+)