ax: fix VoiceOver cursor sync and word double-read

Bug 1 (VO cursor not following Emacs cursor):
- Remove FocusedUIElementChangedNotification on emacsView (was a no-op:
  VO re-queried the same element)
- For Emacs-initiated char/word moves, keep natural next/previous
  direction instead of forcing discontiguous; SelectedTextChanged with
  direction=next advances VO browse cursor sequentially
- Only force discontiguous for line-boundary crossings and large jumps

Bug 2 (word double-read with punctuation):
- Root cause was FocusedUIElementChanged causing VO re-anchor speech
  on top of the explicit word announcement
- Removing FocusedUIElementChanged eliminates the duplicate speech
- Add emacsInitiated parameter to postFocusedCursorNotification;
  omit AXTextSelectionGranularity for Emacs-initiated moves so VO
  does not auto-speak (only explicit announcements provide speech)
- isWordMove now triggers on emacsInitiated flag (Emacs-initiated
  word moves always get explicit announcement)
This commit is contained in:
2026-03-02 12:38:40 +01:00
parent 53ea58725e
commit bc5714b7b7
9 changed files with 124 additions and 118 deletions

View File

@@ -1,4 +1,4 @@
From 6e6c5f4cf33d302e23141ce241c1747cdc0304be Mon Sep 17 00:00:00 2001
From d1adda245af7b5a2b8bc3b0c738f661dcc78b626 Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 12:58:11 +0100
Subject: [PATCH 5/8] ns: integrate accessibility with EmacsView and redisplay
@@ -27,10 +27,10 @@ com.apple.accessibility.api distributed notification.
2 files changed, 431 insertions(+), 12 deletions(-)
diff --git a/etc/NEWS b/etc/NEWS
index 80661a9..2b1f9e6 100644
index 4c149e41d6..7f917f93b2 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -4400,6 +4400,19 @@ allowing Emacs users access to speech recognition utilities.
@@ -4385,6 +4385,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.
@@ -51,7 +51,7 @@ index 80661a9..2b1f9e6 100644
** Re-introduced dictation, lost in Emacs v30 (macOS).
We lost macOS dictation in v30 when migrating to NSTextInputClient.
diff --git a/src/nsterm.m b/src/nsterm.m
index 6d3b87a..7574da4 100644
index 70ec4286bf..1444ff2d13 100644
--- a/src/nsterm.m
+++ b/src/nsterm.m
@@ -1275,7 +1275,7 @@ If a completion candidate is selected (overlay or child frame),
@@ -92,7 +92,7 @@ index 6d3b87a..7574da4 100644
{
NSRect windowRect = [view convertRect:r toView:nil];
NSRect screenRect
@@ -6731,9 +6735,56 @@ - (void)applicationDidFinishLaunching: (NSNotification *)notification
@@ -6722,9 +6726,56 @@ - (void)applicationDidFinishLaunching: (NSNotification *)notification
}
#endif
@@ -149,7 +149,7 @@ index 6d3b87a..7574da4 100644
- (void)antialiasThresholdDidChange:(NSNotification *)notification
{
#ifdef NS_IMPL_COCOA
@@ -7634,7 +7685,6 @@ - (id)accessibilityTopLevelUIElement
@@ -7625,7 +7676,6 @@ - (id)accessibilityTopLevelUIElement
@@ -157,7 +157,7 @@ index 6d3b87a..7574da4 100644
static BOOL
ns_ax_find_completion_overlay_range (struct buffer *b, ptrdiff_t point,
ptrdiff_t *out_start,
@@ -8759,7 +8809,6 @@ - (NSRect)accessibilityFrame
@@ -8738,7 +8788,6 @@ - (NSRect)accessibilityFrame
@end
@@ -165,7 +165,7 @@ index 6d3b87a..7574da4 100644
/* ===================================================================
EmacsAccessibilityBuffer (Notifications) — AX event dispatch
@@ -9305,7 +9354,6 @@ - (NSRect)accessibilityFrame
@@ -9343,7 +9392,6 @@ - (NSRect)accessibilityFrame
@end
@@ -173,7 +173,7 @@ index 6d3b87a..7574da4 100644
/* ===================================================================
EmacsAccessibilityInteractiveSpan — helpers and implementation
=================================================================== */
@@ -9636,6 +9684,7 @@ - (void)dealloc
@@ -9674,6 +9722,7 @@ - (void)dealloc
[layer release];
#endif
@@ -181,7 +181,7 @@ index 6d3b87a..7574da4 100644
[[self menu] release];
[super dealloc];
}
@@ -10984,6 +11033,32 @@ - (void)windowDidBecomeKey /* for direct calls */
@@ -11022,6 +11071,32 @@ - (void)windowDidBecomeKey /* for direct calls */
XSETFRAME (event.frame_or_window, emacsframe);
kbd_buffer_store_event (&event);
ns_send_appdefined (-1); // Kick main loop
@@ -214,7 +214,7 @@ index 6d3b87a..7574da4 100644
}
@@ -12221,6 +12296,332 @@ - (int) fullscreenState
@@ -12259,6 +12334,332 @@ - (int) fullscreenState
return fs_state;
}
@@ -547,7 +547,7 @@ index 6d3b87a..7574da4 100644
@end /* EmacsView */
@@ -14221,12 +14622,17 @@ Nil means use fullscreen the old (< 10.7) way. The old way works better with
@@ -14255,12 +14656,17 @@ Nil means use fullscreen the old (< 10.7) way. The old way works better with
ns_use_srgb_colorspace = YES;
DEFVAR_BOOL ("ns-accessibility-enabled", ns_accessibility_enabled,