From 73563be72d4fe5ca4bfa483db7c557a7b0e0db66 Mon Sep 17 00:00:00 2001 From: Daneel Date: Tue, 3 Mar 2026 09:43:26 +0100 Subject: [PATCH] patches: fix VoiceOver reads only first word in org-agenda 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. --- ...-with-macOS-Zoom-for-cursor-tracking.patch | 2 +- ...lity-base-classes-and-text-extractio.patch | 2 +- ...fer-accessibility-element-core-proto.patch | 2 +- ...tification-dispatch-and-mode-line-el.patch | 2 +- ...ive-span-elements-for-Tab-navigation.patch | 2 +- ...essibility-with-EmacsView-and-redisp.patch | 2 +- ...r-accessibility-section-to-macOS-app.patch | 2 +- ...lay-completion-candidates-for-VoiceO.patch | 2 +- ...d-frame-completion-candidates-for-Vo.patch | 57 ++++++++++--------- 9 files changed, 39 insertions(+), 34 deletions(-) diff --git a/patches/0000-ns-integrate-with-macOS-Zoom-for-cursor-tracking.patch b/patches/0000-ns-integrate-with-macOS-Zoom-for-cursor-tracking.patch index be5bfe9..d26a89d 100644 --- a/patches/0000-ns-integrate-with-macOS-Zoom-for-cursor-tracking.patch +++ b/patches/0000-ns-integrate-with-macOS-Zoom-for-cursor-tracking.patch @@ -1,4 +1,4 @@ -From e27ae42313eb5cb5cab14de348f83db216a17a53 Mon Sep 17 00:00:00 2001 +From 9b7352fee5782beb5dae88710bb66cf23ea242c3 Mon Sep 17 00:00:00 2001 From: Martin Sukany Date: Sat, 28 Feb 2026 22:39:35 +0100 Subject: [PATCH 0/8] ns: integrate with macOS Zoom for cursor tracking diff --git a/patches/0001-ns-add-accessibility-base-classes-and-text-extractio.patch b/patches/0001-ns-add-accessibility-base-classes-and-text-extractio.patch index bf127a2..c65f23f 100644 --- a/patches/0001-ns-add-accessibility-base-classes-and-text-extractio.patch +++ b/patches/0001-ns-add-accessibility-base-classes-and-text-extractio.patch @@ -1,4 +1,4 @@ -From 9650910e7ac6e423ea9beaa75033d12693b93c89 Mon Sep 17 00:00:00 2001 +From ce2b1aff0e1834bbf9375bdbace7943f0ee83c89 Mon Sep 17 00:00:00 2001 From: Martin Sukany Date: Sat, 28 Feb 2026 12:58:11 +0100 Subject: [PATCH 1/8] ns: add accessibility base classes and text extraction diff --git a/patches/0002-ns-implement-buffer-accessibility-element-core-proto.patch b/patches/0002-ns-implement-buffer-accessibility-element-core-proto.patch index 57200b2..e0fe665 100644 --- a/patches/0002-ns-implement-buffer-accessibility-element-core-proto.patch +++ b/patches/0002-ns-implement-buffer-accessibility-element-core-proto.patch @@ -1,4 +1,4 @@ -From 9ac52a3f3e57628bd06516bc439b8ec388207098 Mon Sep 17 00:00:00 2001 +From 2197610b35f37f20dcc7a01ffba7b1210bb72561 Mon Sep 17 00:00:00 2001 From: Martin Sukany Date: Sat, 28 Feb 2026 12:58:11 +0100 Subject: [PATCH 2/8] ns: implement buffer accessibility element (core diff --git a/patches/0003-ns-add-buffer-notification-dispatch-and-mode-line-el.patch b/patches/0003-ns-add-buffer-notification-dispatch-and-mode-line-el.patch index e004b27..1a68e91 100644 --- a/patches/0003-ns-add-buffer-notification-dispatch-and-mode-line-el.patch +++ b/patches/0003-ns-add-buffer-notification-dispatch-and-mode-line-el.patch @@ -1,4 +1,4 @@ -From 361aecfc858d712943921435454d9a7235d145ed Mon Sep 17 00:00:00 2001 +From d3bd4a705068b2538cda29217eb6ea67c525f9d5 Mon Sep 17 00:00:00 2001 From: Martin Sukany Date: Sat, 28 Feb 2026 12:58:11 +0100 Subject: [PATCH 3/8] ns: add buffer notification dispatch and mode-line diff --git a/patches/0004-ns-add-interactive-span-elements-for-Tab-navigation.patch b/patches/0004-ns-add-interactive-span-elements-for-Tab-navigation.patch index 5d3c78a..64a552c 100644 --- a/patches/0004-ns-add-interactive-span-elements-for-Tab-navigation.patch +++ b/patches/0004-ns-add-interactive-span-elements-for-Tab-navigation.patch @@ -1,4 +1,4 @@ -From a0ea23e5b05e7ab6048d6dd3e9e05aae00dc6939 Mon Sep 17 00:00:00 2001 +From 5d5aca0665a22d3bf3cce54e53ec58fa9defab6e Mon Sep 17 00:00:00 2001 From: Martin Sukany Date: Sat, 28 Feb 2026 12:58:11 +0100 Subject: [PATCH 4/8] ns: add interactive span elements for Tab navigation diff --git a/patches/0005-ns-integrate-accessibility-with-EmacsView-and-redisp.patch b/patches/0005-ns-integrate-accessibility-with-EmacsView-and-redisp.patch index 307626f..b74d5b4 100644 --- a/patches/0005-ns-integrate-accessibility-with-EmacsView-and-redisp.patch +++ b/patches/0005-ns-integrate-accessibility-with-EmacsView-and-redisp.patch @@ -1,4 +1,4 @@ -From ca511140b95caf299ab1b24b7a22de03a2e5b543 Mon Sep 17 00:00:00 2001 +From 31d94ecffbfc42a48c6222a1794b823fd0db7b3c Mon Sep 17 00:00:00 2001 From: Martin Sukany Date: Sat, 28 Feb 2026 12:58:11 +0100 Subject: [PATCH 5/8] ns: integrate accessibility with EmacsView and redisplay diff --git a/patches/0006-doc-add-VoiceOver-accessibility-section-to-macOS-app.patch b/patches/0006-doc-add-VoiceOver-accessibility-section-to-macOS-app.patch index 704314c..1276644 100644 --- a/patches/0006-doc-add-VoiceOver-accessibility-section-to-macOS-app.patch +++ b/patches/0006-doc-add-VoiceOver-accessibility-section-to-macOS-app.patch @@ -1,4 +1,4 @@ -From 2cfc623598b666515fe3cf05ee8c578601e0e587 Mon Sep 17 00:00:00 2001 +From b863369a4d33a3488c3fa9de0cf0eca687b40447 Mon Sep 17 00:00:00 2001 From: Martin Sukany Date: Sat, 28 Feb 2026 12:58:11 +0100 Subject: [PATCH 6/8] doc: add VoiceOver accessibility section to macOS diff --git a/patches/0007-ns-announce-overlay-completion-candidates-for-VoiceO.patch b/patches/0007-ns-announce-overlay-completion-candidates-for-VoiceO.patch index b199aed..24f721a 100644 --- a/patches/0007-ns-announce-overlay-completion-candidates-for-VoiceO.patch +++ b/patches/0007-ns-announce-overlay-completion-candidates-for-VoiceO.patch @@ -1,4 +1,4 @@ -From 239d804cf216a05a2b62aeeda7ab2cc5795c158b Mon Sep 17 00:00:00 2001 +From 6b9c326c56346bc55381d4d5c68cd34b59165417 Mon Sep 17 00:00:00 2001 From: Daneel Date: Mon, 2 Mar 2026 18:39:46 +0100 Subject: [PATCH 7/8] ns: announce overlay completion candidates for VoiceOver diff --git a/patches/0008-ns-announce-child-frame-completion-candidates-for-Vo.patch b/patches/0008-ns-announce-child-frame-completion-candidates-for-Vo.patch index 8a4144b..ecf31a1 100644 --- a/patches/0008-ns-announce-child-frame-completion-candidates-for-Vo.patch +++ b/patches/0008-ns-announce-child-frame-completion-candidates-for-Vo.patch @@ -1,4 +1,4 @@ -From 235fb607dfe06a242044218a2ed0ea82fed4f82f Mon Sep 17 00:00:00 2001 +From 0cd27cd398ebcbaadd526b404cf7d549bfe53a4a Mon Sep 17 00:00:00 2001 From: Daneel Date: Mon, 2 Mar 2026 18:49:13 +0100 Subject: [PATCH 8/8] ns: announce child frame completion candidates for @@ -33,8 +33,8 @@ area announcements. doc/emacs/macos.texi | 14 +- etc/NEWS | 18 +- src/nsterm.h | 20 ++ - src/nsterm.m | 504 +++++++++++++++++++++++++++++++++++++------ - 4 files changed, 482 insertions(+), 74 deletions(-) + src/nsterm.m | 511 +++++++++++++++++++++++++++++++++++++------ + 4 files changed, 489 insertions(+), 74 deletions(-) diff --git a/doc/emacs/macos.texi b/doc/emacs/macos.texi index 8d4a7825d8..03a657f970 100644 @@ -149,7 +149,7 @@ index 21a93bc799..bdd40b8eb7 100644 @end diff --git a/src/nsterm.m b/src/nsterm.m -index 8f744d1bf3..1f3b2ad78a 100644 +index 8f744d1bf3..dc5b965468 100644 --- a/src/nsterm.m +++ b/src/nsterm.m @@ -1126,24 +1126,19 @@ Uses CFAbsoluteTimeGetCurrent() (~5 ns, a VDSO read) for timing. */ @@ -339,7 +339,7 @@ index 8f744d1bf3..1f3b2ad78a 100644 specpdl_ref count = SPECPDL_INDEX (); record_unwind_current_buffer (); /* Ensure block_input is always matched by unblock_input even if -@@ -9053,22 +9159,33 @@ - (void)postFocusedCursorNotification:(ptrdiff_t)point +@@ -9053,20 +9159,38 @@ - (void)postFocusedCursorNotification:(ptrdiff_t)point && granularity == ns_ax_text_selection_granularity_character); @@ -368,6 +368,7 @@ index 8f744d1bf3..1f3b2ad78a 100644 - selection so VoiceOver reads the appropriate text. */ - if (!isCharMove) - moveInfo[@"AXTextSelectionGranularity"] = @(granularity); +- + BOOL isDiscontiguous + = (direction == ns_ax_text_selection_direction_discontiguous); + if (!isDiscontiguous && !isCharMove) @@ -375,15 +376,19 @@ index 8f744d1bf3..1f3b2ad78a 100644 + moveInfo[@"AXTextSelectionDirection"] = @(direction); + moveInfo[@"AXTextSelectionGranularity"] = @(granularity); + } - -+ moveInfo[NSAccessibilityUIElementsKey] = @[self]; ++ ++ /* Post on self (the EmacsAXBuffer element), not on the parent ++ EmacsView. When the notification originates from the element ++ whose selection changed, VoiceOver calls accessibilityLineForIndex: ++ on that element to determine the line to read. Posting from the ++ parent view with UIElementsKey causes VoiceOver to call ++ accessibilityLineForIndex: on the view instead, which returns an ++ incorrect range in specialised buffers (org-agenda, org-super-agenda) ++ where line geometry differs from plain text. */ ns_ax_post_notification_with_info ( -- self, -+ self.emacsView, + self, NSAccessibilitySelectedTextChangedNotification, - moveInfo); - -@@ -9166,12 +9283,17 @@ user expectation ("w" jumps to next word and reads it). */ +@@ -9166,12 +9290,17 @@ user expectation ("w" jumps to next word and reads it). */ } } @@ -406,7 +411,7 @@ index 8f744d1bf3..1f3b2ad78a 100644 if (cachedText && granularity == ns_ax_text_selection_granularity_line) { -@@ -9236,6 +9358,11 @@ - (void)postCompletionAnnouncementForBuffer:(struct buffer *)b +@@ -9236,6 +9365,11 @@ - (void)postCompletionAnnouncementForBuffer:(struct buffer *)b block_input (); specpdl_ref count2 = SPECPDL_INDEX (); @@ -418,7 +423,7 @@ index 8f744d1bf3..1f3b2ad78a 100644 record_unwind_protect_void (unblock_input); record_unwind_current_buffer (); if (b != current_buffer) -@@ -9412,12 +9539,29 @@ - (void)postAccessibilityNotificationsForFrame:(struct frame *)f +@@ -9412,12 +9546,29 @@ - (void)postAccessibilityNotificationsForFrame:(struct frame *)f if (!b) return; @@ -448,7 +453,7 @@ index 8f744d1bf3..1f3b2ad78a 100644 if (modiff != self.cachedModiff) { self.cachedModiff = modiff; -@@ -9431,6 +9575,7 @@ Text property changes (e.g. face updates from +@@ -9431,6 +9582,7 @@ Text property changes (e.g. face updates from { self.cachedCharsModiff = chars_modiff; [self postTextChangedNotification:point]; @@ -456,7 +461,7 @@ index 8f744d1bf3..1f3b2ad78a 100644 } } -@@ -9453,8 +9598,15 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property +@@ -9453,8 +9605,15 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property displayed in the minibuffer. In normal editing buffers, font-lock and other modes change BUF_OVERLAY_MODIFF on every redisplay, triggering O(overlays) work per keystroke. @@ -474,7 +479,7 @@ index 8f744d1bf3..1f3b2ad78a 100644 goto skip_overlay_scan; int selected_line = -1; -@@ -9500,7 +9652,18 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property +@@ -9500,7 +9659,18 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property self.cachedPoint = point; self.cachedMarkActive = markActive; @@ -494,7 +499,7 @@ index 8f744d1bf3..1f3b2ad78a 100644 NSInteger direction = ns_ax_text_selection_direction_discontiguous; if (point > oldPoint) direction = ns_ax_text_selection_direction_next; -@@ -9512,6 +9675,7 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property +@@ -9512,6 +9682,7 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property /* --- Granularity detection --- */ NSInteger granularity = ns_ax_text_selection_granularity_unknown; @@ -502,7 +507,7 @@ index 8f744d1bf3..1f3b2ad78a 100644 [self ensureTextCache]; if (cachedText && oldPoint > 0) { -@@ -9526,7 +9690,18 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property +@@ -9526,7 +9697,18 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property NSRange newLine = [cachedText lineRangeForRange: NSMakeRange (newIdx, 0)]; if (oldLine.location != newLine.location) @@ -522,7 +527,7 @@ index 8f744d1bf3..1f3b2ad78a 100644 else { NSUInteger dist = (newIdx > oldIdx -@@ -9548,34 +9723,23 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property +@@ -9548,34 +9730,23 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property granularity = ns_ax_text_selection_granularity_line; } @@ -570,7 +575,7 @@ index 8f744d1bf3..1f3b2ad78a 100644 { NSWindow *win = [self.emacsView window]; if (win) -@@ -9734,6 +9898,13 @@ - (NSRect)accessibilityFrame +@@ -9734,6 +9905,13 @@ - (NSRect)accessibilityFrame if (vis_start >= vis_end) return @[]; @@ -584,7 +589,7 @@ index 8f744d1bf3..1f3b2ad78a 100644 block_input (); specpdl_ref blk_count = SPECPDL_INDEX (); record_unwind_protect_void (unblock_input); -@@ -9858,6 +10029,7 @@ than O(chars). Fall back to pos+1 as safety net. */ +@@ -9858,6 +10036,7 @@ than O(chars). Fall back to pos+1 as safety net. */ pos = span_end; } @@ -592,7 +597,7 @@ index 8f744d1bf3..1f3b2ad78a 100644 return [[spans copy] autorelease]; } -@@ -10039,6 +10211,10 @@ - (void)dealloc +@@ -10039,6 +10218,10 @@ - (void)dealloc #endif [accessibilityElements release]; @@ -603,7 +608,7 @@ index 8f744d1bf3..1f3b2ad78a 100644 [[self menu] release]; [super dealloc]; } -@@ -11488,6 +11664,9 @@ - (instancetype) initFrameFromEmacs: (struct frame *)f +@@ -11488,6 +11671,9 @@ - (instancetype) initFrameFromEmacs: (struct frame *)f windowClosing = NO; processingCompose = NO; @@ -613,7 +618,7 @@ index 8f744d1bf3..1f3b2ad78a 100644 scrollbarsNeedingUpdate = 0; fs_state = FULLSCREEN_NONE; fs_before_fs = next_maximized = -1; -@@ -12796,6 +12975,154 @@ - (id)accessibilityFocusedUIElement +@@ -12796,6 +12982,154 @@ - (id)accessibilityFocusedUIElement The existing elements carry cached state (modiff, point) from the previous redisplay cycle. Rebuilding first would create fresh elements with current values, making change detection impossible. */ @@ -768,7 +773,7 @@ index 8f744d1bf3..1f3b2ad78a 100644 - (void)postAccessibilityUpdates { NSTRACE ("[EmacsView postAccessibilityUpdates]"); -@@ -12806,11 +13133,64 @@ - (void)postAccessibilityUpdates +@@ -12806,11 +13140,64 @@ - (void)postAccessibilityUpdates /* Re-entrance guard: VoiceOver callbacks during notification posting can trigger redisplay, which calls ns_update_end, which calls us