patches: fix C-n/C-p VoiceOver regression - exclude isCtrlNP from re-anchor

When Emacs moves the cursor (emacsMovedCursor=YES), we post
FocusedUIElementChanged on the NSWindow to re-anchor VoiceOver's
browse cursor.  For C-n/C-p this notification races with
AXSelectedTextChanged(granularity=line) and causes VoiceOver to
drop the line-read speech.

Arrow key movement works because VoiceOver intercepts those as AX
selection changes (setAccessibilitySelectedTextRange:), making
voiceoverSetPoint=YES and emacsMovedCursor=NO, so no
FocusedUIElementChanged is posted.

Fix: skip FocusedUIElementChanged for sequential C-n/C-p moves
(isCtrlNP).  AXSelectedTextChanged with direction=next/previous +
granularity=line is sufficient for VoiceOver to read the new line.
FocusedUIElementChanged is only needed for discontiguous jumps
(]], M-<, isearch, xref etc.) where VoiceOver must re-anchor.

Also merge duplicate comment blocks and fix two compile errors
from a64d24c that Martin caught during testing.
This commit is contained in:
2026-03-02 20:48:57 +01:00
parent a64d24cbd9
commit 7a0b4f6cf2
9 changed files with 84 additions and 92 deletions

View File

@@ -1,4 +1,4 @@
From 7c042d446bddee16a83e4cd8f0050e24e262ef77 Mon Sep 17 00:00:00 2001
From d4cda4bda0bee73c14946f20322975edd1580d46 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
@@ -23,8 +23,8 @@ com.apple.accessibility.api distributed notification.
(accessibilityAttributeValue:forParameter:): New methods.
---
etc/NEWS | 13 ++
src/nsterm.m | 430 +++++++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 431 insertions(+), 12 deletions(-)
src/nsterm.m | 474 +++++++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 475 insertions(+), 12 deletions(-)
diff --git a/etc/NEWS b/etc/NEWS
index 4c149e41d6..7f917f93b2 100644
@@ -51,7 +51,7 @@ index 4c149e41d6..7f917f93b2 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 b460beb00c..95a5b378c1 100644
index b460beb00c..7c118045bd 100644
--- a/src/nsterm.m
+++ b/src/nsterm.m
@@ -1275,7 +1275,7 @@ If a completion candidate is selected (overlay or child frame),
@@ -165,7 +165,58 @@ index b460beb00c..95a5b378c1 100644
/* ===================================================================
EmacsAccessibilityBuffer (Notifications) — AX event dispatch
@@ -9347,7 +9396,6 @@ - (NSRect)accessibilityFrame
@@ -9235,6 +9284,50 @@ - (void)postAccessibilityNotificationsForFrame:(struct frame *)f
granularity = ns_ax_text_selection_granularity_line;
}
+ /* Programmatic jumps that cross a line boundary (]], [[, M-<,
+ xref, imenu, …) are discontiguous: the cursor teleported to an
+ arbitrary position, not one sequential step forward/backward.
+ Reporting AXTextSelectionDirectionDiscontiguous causes VoiceOver
+ to re-anchor its rotor browse cursor at the new
+ accessibilitySelectedTextRange rather than advancing linearly
+ from its previous internal position. */
+ if (!isCtrlNP && granularity == ns_ax_text_selection_granularity_line)
+ direction = ns_ax_text_selection_direction_discontiguous;
+
+ /* 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; those are already sequential and VoiceOver
+ handles them correctly. */
+ if (emacsMovedCursor && !isCtrlNP)
+ direction = ns_ax_text_selection_direction_discontiguous;
+
+ /* Re-anchor VoiceOver's browse cursor for discontiguous (teleport)
+ moves only. For sequential C-n/C-p (isCtrlNP), posting
+ FocusedUIElementChanged on the window races with the
+ AXSelectedTextChanged(granularity=line) notification and
+ causes VoiceOver to drop the line-read speech. Sequential
+ moves are already handled correctly by AXSelectedTextChanged
+ with direction=next/previous + granularity=line. */
+ if (emacsMovedCursor && !isCtrlNP && [self isAccessibilityFocused])
+ {
+ NSWindow *win = [self.emacsView window];
+ if (win)
+ ns_ax_post_notification (
+ win,
+ NSAccessibilityFocusedUIElementChangedNotification);
+
+ NSDictionary *layoutInfo = @{
+ NSAccessibilityUIElementsKey: @[self]
+ };
+ ns_ax_post_notification_with_info (
+ self.emacsView,
+ NSAccessibilityLayoutChangedNotification,
+ layoutInfo);
+ }
+
/* Post notifications for focused and non-focused elements. */
if ([self isAccessibilityFocused])
[self postFocusedCursorNotification:point
@@ -9347,7 +9440,6 @@ - (NSRect)accessibilityFrame
@end
@@ -173,7 +224,7 @@ index b460beb00c..95a5b378c1 100644
/* ===================================================================
EmacsAccessibilityInteractiveSpan --- helpers and implementation
=================================================================== */
@@ -9682,6 +9730,7 @@ - (void)dealloc
@@ -9682,6 +9774,7 @@ - (void)dealloc
[layer release];
#endif
@@ -181,7 +232,7 @@ index b460beb00c..95a5b378c1 100644
[[self menu] release];
[super dealloc];
}
@@ -11030,6 +11079,32 @@ - (void)windowDidBecomeKey /* for direct calls */
@@ -11030,6 +11123,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 +265,7 @@ index b460beb00c..95a5b378c1 100644
}
@@ -12267,6 +12342,332 @@ - (int) fullscreenState
@@ -12267,6 +12386,332 @@ - (int) fullscreenState
return fs_state;
}
@@ -547,7 +598,7 @@ index b460beb00c..95a5b378c1 100644
@end /* EmacsView */
@@ -14263,12 +14664,17 @@ Nil means use fullscreen the old (< 10.7) way. The old way works better with
@@ -14263,12 +14708,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,