Revert "ax: fix VoiceOver cursor sync and word double-read"

This reverts commit bc5714b7b7.
This commit is contained in:
2026-03-02 14:28:09 +01:00
parent bc5714b7b7
commit 8196205c3d
9 changed files with 118 additions and 124 deletions

View File

@@ -1,4 +1,4 @@
From a6249e8ad3512f1e80e509c21111f6e3a241405f Mon Sep 17 00:00:00 2001
From 830710de8a7bac560d71ae802dcf7df60517c57b Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 16:01:29 +0100
Subject: [PATCH 8/8] ns: announce child frame completion candidates for
@@ -20,11 +20,11 @@ element when a child frame completion closes.
doc/emacs/macos.texi | 18 +-
etc/NEWS | 18 +-
src/nsterm.h | 21 ++
src/nsterm.m | 505 +++++++++++++++++++++++++++++++++++++++----
4 files changed, 509 insertions(+), 53 deletions(-)
src/nsterm.m | 496 +++++++++++++++++++++++++++++++++++++++----
4 files changed, 501 insertions(+), 52 deletions(-)
diff --git a/doc/emacs/macos.texi b/doc/emacs/macos.texi
index f3671354b5..03a657f970 100644
index 6514dfc..bcf74b3 100644
--- a/doc/emacs/macos.texi
+++ b/doc/emacs/macos.texi
@@ -278,7 +278,6 @@ restart Emacs to access newly-available services.
@@ -67,10 +67,10 @@ index f3671354b5..03a657f970 100644
correctly: character navigation announces the character at the cursor
position, not the character before it.
diff --git a/etc/NEWS b/etc/NEWS
index 7f917f93b2..d7631fa6c7 100644
index 2b1f9e6..5766428 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -4385,16 +4385,20 @@ allowing Emacs users access to speech recognition utilities.
@@ -4400,15 +4400,19 @@ allowing Emacs users access to speech recognition utilities.
Note: Accepting this permission allows the use of system APIs, which may
send user data to Apple's speech recognition servers.
@@ -99,7 +99,7 @@ index 7f917f93b2..d7631fa6c7 100644
interface and eliminate the associated overhead.
diff --git a/src/nsterm.h b/src/nsterm.h
index 21a93bc799..d435e42998 100644
index 21a93bc..d435e42 100644
--- a/src/nsterm.h
+++ b/src/nsterm.h
@@ -504,9 +504,21 @@ typedef struct ns_ax_visible_run
@@ -148,7 +148,7 @@ index 21a93bc799..d435e42998 100644
@end
diff --git a/src/nsterm.m b/src/nsterm.m
index b3daec31f1..33b72a0825 100644
index 8d44b5f..29b646d 100644
--- a/src/nsterm.m
+++ b/src/nsterm.m
@@ -1126,24 +1126,19 @@ Uses CFAbsoluteTimeGetCurrent() (~5 ns, a VDSO read) for timing. */
@@ -199,7 +199,7 @@ index b3daec31f1..33b72a0825 100644
}
#endif /* MAC_OS_X_VERSION_MIN_REQUIRED >= 101000 */
@@ -7406,6 +7407,112 @@ visual line index for Zoom (skip whitespace-only lines
@@ -7415,6 +7416,112 @@ visual line index for Zoom (skip whitespace-only lines
return nil;
}
@@ -312,7 +312,7 @@ index b3daec31f1..33b72a0825 100644
/* Build accessibility text for window W, skipping invisible text.
Populates *OUT_START with the buffer start charpos.
Populates *OUT_RUNS with an array of visible runs and *OUT_NRUNS
@@ -8033,6 +8140,7 @@ that remapped bindings (e.g., C-j -> next-line) are recognized.
@@ -8046,6 +8153,7 @@ that remapped bindings (e.g., C-j -> next-line) are recognized.
@implementation EmacsAccessibilityBuffer
@synthesize cachedText;
@synthesize cachedTextModiff;
@@ -320,7 +320,7 @@ index b3daec31f1..33b72a0825 100644
@synthesize cachedOverlayModiff;
@synthesize cachedTextStart;
@synthesize cachedModiff;
@@ -8146,16 +8254,34 @@ - (void)ensureTextCache
@@ -8159,16 +8267,34 @@ - (void)ensureTextCache
if (!b)
return;
@@ -363,7 +363,7 @@ index b3daec31f1..33b72a0825 100644
&& cachedTextStart == BUF_BEGV (b)
&& pt >= cachedTextStart
&& (textLen == 0
@@ -8171,7 +8297,8 @@ included in the cached AX text (it is handled separately via
@@ -8184,7 +8310,8 @@ included in the cached AX text (it is handled separately via
{
[cachedText release];
cachedText = [text retain];
@@ -373,7 +373,7 @@ index b3daec31f1..33b72a0825 100644
cachedTextStart = start;
if (visibleRuns)
@@ -8584,6 +8711,11 @@ - (void)setAccessibilitySelectedTextRange:(NSRange)range
@@ -8597,6 +8724,11 @@ - (void)setAccessibilitySelectedTextRange:(NSRange)range
[self ensureTextCache];
@@ -385,7 +385,7 @@ index b3daec31f1..33b72a0825 100644
specpdl_ref count = SPECPDL_INDEX ();
record_unwind_current_buffer ();
/* Ensure block_input is always matched by unblock_input even if
@@ -9040,11 +9172,14 @@ - (void)postFocusedCursorNotification:(ptrdiff_t)point
@@ -9064,11 +9196,13 @@ - (void)postFocusedCursorNotification:(ptrdiff_t)point
= @(ns_ax_text_state_change_selection_move);
moveInfo[@"AXTextSelectionDirection"] = @(direction);
moveInfo[@"AXTextChangeElement"] = self;
@@ -394,18 +394,17 @@ index b3daec31f1..33b72a0825 100644
- for block-cursor mode). Include it for word/line/
- selection so VoiceOver reads the appropriate text. */
- if (!isCharMove)
+ /* Include granularity for sequential VO-initiated moves so VoiceOver
+ reads the appropriate unit. Omit for character moves (announced
+ explicitly below), discontiguous jumps (destination line announced
+ explicitly), and Emacs-initiated moves (announced explicitly;
+ including granularity would cause VoiceOver to auto-speak,
+ doubling the explicit announcement). */
+ if (!isCharMove && !emacsInitiated
+ /* Include granularity for sequential moves so VoiceOver reads the
+ appropriate unit. Omit for character moves (announced explicitly
+ below) and for discontiguous jumps (destination line announced
+ explicitly; omitting granularity lets VoiceOver use its default
+ behaviour and re-anchor its browse cursor). */
+ if (!isCharMove
+ && direction != ns_ax_text_selection_direction_discontiguous)
moveInfo[@"AXTextSelectionGranularity"] = @(granularity);
ns_ax_post_notification_with_info (
@@ -9145,12 +9280,17 @@ user expectation ("w" jumps to next word and reads it). */
@@ -9111,12 +9245,17 @@ derive its own speech (it would read the wrong character
}
}
@@ -428,7 +427,7 @@ index b3daec31f1..33b72a0825 100644
if (cachedText
&& granularity == ns_ax_text_selection_granularity_line)
{
@@ -9213,7 +9353,14 @@ - (void)postCompletionAnnouncementForBuffer:(struct buffer *)b
@@ -9179,7 +9318,14 @@ - (void)postCompletionAnnouncementForBuffer:(struct buffer *)b
ptrdiff_t currentOverlayStart = 0;
ptrdiff_t currentOverlayEnd = 0;
@@ -443,7 +442,7 @@ index b3daec31f1..33b72a0825 100644
record_unwind_current_buffer ();
if (b != current_buffer)
set_buffer_internal_1 (b);
@@ -9389,12 +9536,29 @@ - (void)postAccessibilityNotificationsForFrame:(struct frame *)f
@@ -9356,12 +9502,29 @@ - (void)postAccessibilityNotificationsForFrame:(struct frame *)f
if (!b)
return;
@@ -473,7 +472,7 @@ index b3daec31f1..33b72a0825 100644
if (modiff != self.cachedModiff)
{
self.cachedModiff = modiff;
@@ -9408,6 +9572,7 @@ Text property changes (e.g. face updates from
@@ -9375,6 +9538,7 @@ Text property changes (e.g. face updates from
{
self.cachedCharsModiff = chars_modiff;
[self postTextChangedNotification:point];
@@ -481,7 +480,7 @@ index b3daec31f1..33b72a0825 100644
}
}
@@ -9430,8 +9595,15 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property
@@ -9397,8 +9561,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.
@@ -499,7 +498,7 @@ index b3daec31f1..33b72a0825 100644
goto skip_overlay_scan;
int selected_line = -1;
@@ -9477,7 +9649,18 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property
@@ -9444,7 +9615,18 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property
self.cachedPoint = point;
self.cachedMarkActive = markActive;
@@ -519,7 +518,7 @@ index b3daec31f1..33b72a0825 100644
NSInteger direction = ns_ax_text_selection_direction_discontiguous;
if (point > oldPoint)
direction = ns_ax_text_selection_direction_next;
@@ -9525,6 +9708,30 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property
@@ -9492,6 +9674,36 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property
granularity = ns_ax_text_selection_granularity_line;
}
@@ -533,33 +532,30 @@ index b3daec31f1..33b72a0825 100644
+ if (!isCtrlNP && granularity == ns_ax_text_selection_granularity_line)
+ direction = ns_ax_text_selection_direction_discontiguous;
+
+ /* If Emacs moved the cursor (not VoiceOver) and the move
+ crosses a line boundary or jumps to an unknown position, force
+ discontiguous so VoiceOver re-anchors its browse cursor.
+ For character and word moves within a line, keep the natural
+ next/previous direction: SelectedTextChanged with direction=next
+ advances VoiceOver's browse cursor sequentially, and the explicit
+ announcement in postFocusedCursorNotification provides speech.
+ /* If Emacs moved the cursor (not VoiceOver), force discontiguous
+ so VoiceOver re-anchors its browse cursor to the current
+ accessibilitySelectedTextRange. This covers all Emacs-initiated
+ moves: editing commands, ELisp, isearch, etc.
+ Exception: C-n/C-p (isCtrlNP) already uses next/previous with
+ line granularity; VoiceOver handles them correctly. */
+ if (emacsMovedCursor && !isCtrlNP
+ && (granularity == ns_ax_text_selection_granularity_line
+ || granularity == ns_ax_text_selection_granularity_unknown))
+ line granularity; those are already sequential and VoiceOver
+ handles them correctly. */
+ if (emacsMovedCursor && !isCtrlNP)
+ direction = ns_ax_text_selection_direction_discontiguous;
+
+ /* Post FocusedUIElementChanged on the containing NSView whenever
+ Emacs moves the cursor independently of VoiceOver. Posting on
+ the view causes VoiceOver to re-query accessibilityFocusedUIElement
+ and re-anchor its browse cursor at the new selection range, without
+ re-announcing the element name. Required for all granularities. */
+ if (emacsMovedCursor && [self isAccessibilityFocused])
+ ns_ax_post_notification (
+ self.emacsView,
+ NSAccessibilityFocusedUIElementChangedNotification);
+
/* Post notifications for focused and non-focused elements. */
if ([self isAccessibilityFocused])
[self postFocusedCursorNotification:point
@@ -9532,7 +9739,7 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property
granularity:granularity
markActive:markActive
oldMarkActive:oldMarkActive
- emacsInitiated:NO];
+ emacsInitiated:emacsMovedCursor];
if (![self isAccessibilityFocused] && cachedText)
[self postCompletionAnnouncementForBuffer:b point:point];
@@ -9668,6 +9875,17 @@ - (NSRect)accessibilityFrame
@@ -9634,6 +9836,17 @@ - (NSRect)accessibilityFrame
if (vis_start >= vis_end)
return @[];
@@ -577,7 +573,7 @@ index b3daec31f1..33b72a0825 100644
/* Symbols are interned once at startup via DEFSYM in syms_of_nsterm;
reference them directly here (GC-safe, no repeated obarray lookup). */
@@ -9788,6 +10006,7 @@ than O(chars). Fall back to pos+1 as safety net. */
@@ -9754,6 +9967,7 @@ than O(chars). Fall back to pos+1 as safety net. */
pos = span_end;
}
@@ -585,7 +581,7 @@ index b3daec31f1..33b72a0825 100644
return [[spans copy] autorelease];
}
@@ -9969,6 +10188,10 @@ - (void)dealloc
@@ -9935,6 +10149,10 @@ - (void)dealloc
#endif
[accessibilityElements release];
@@ -596,7 +592,7 @@ index b3daec31f1..33b72a0825 100644
[[self menu] release];
[super dealloc];
}
@@ -11418,6 +11641,9 @@ - (instancetype) initFrameFromEmacs: (struct frame *)f
@@ -11384,6 +11602,9 @@ - (instancetype) initFrameFromEmacs: (struct frame *)f
windowClosing = NO;
processingCompose = NO;
@@ -606,7 +602,7 @@ index b3daec31f1..33b72a0825 100644
scrollbarsNeedingUpdate = 0;
fs_state = FULLSCREEN_NONE;
fs_before_fs = next_maximized = -1;
@@ -12726,6 +12952,154 @@ - (id)accessibilityFocusedUIElement
@@ -12692,6 +12913,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. */
@@ -761,7 +757,7 @@ index b3daec31f1..33b72a0825 100644
- (void)postAccessibilityUpdates
{
NSTRACE ("[EmacsView postAccessibilityUpdates]");
@@ -12736,11 +13110,64 @@ - (void)postAccessibilityUpdates
@@ -12702,11 +13069,64 @@ - (void)postAccessibilityUpdates
/* Re-entrance guard: VoiceOver callbacks during notification posting
can trigger redisplay, which calls ns_update_end, which calls us