patches: fix forward dependency (helpers moved to patch 1)
This commit is contained in:
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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 *>.
|
||||||
+
|
+
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(+)
|
||||||
|
|||||||
Reference in New Issue
Block a user