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.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
From 235fb607dfe06a242044218a2ed0ea82fed4f82f Mon Sep 17 00:00:00 2001
|
||||
From 0cd27cd398ebcbaadd526b404cf7d549bfe53a4a Mon Sep 17 00:00:00 2001
|
||||
From: Daneel <daneel@sukany.cz>
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user