v14.1: post notifications on focused virtual element, not EmacsView

Root cause of typing echo + cursor movement failure:
VoiceOver tracks the focused element (EmacsAccessibilityBuffer, AXTextArea).
Notifications from EmacsView (AXGroup) were ignored because VoiceOver
doesn't monitor non-focused elements for text changes.

Fix: ns_draw_window_cursor now calls [view accessibilityFocusedUIElement]
to find the right EmacsAccessibilityBuffer, and posts ValueChanged +
SelectedTextChanged on IT instead of on the view.

Also: postAccessibilityUpdatesForWindow now uses [self accessibilityStringForRange:]
instead of [view accessibilityStringForRange:] for typing echo text.
This commit is contained in:
2026-02-26 10:47:12 +01:00
parent 8fbdb2406d
commit 946b138a0a

View File

@@ -2,12 +2,9 @@ From: Martin Sukany <martin@sukany.cz>
Date: Wed, 26 Feb 2026 00:00:00 +0100
Subject: [PATCH] ns: add macOS Zoom cursor tracking and VoiceOver accessibility
Dual accessibility support:
1. UAZoomChangeFocus + accessibilityBoundsForRange on EmacsView for Zoom
2. Virtual element tree (EmacsAccessibilityBuffer) for VoiceOver
3. Typing echo from ns_draw_window_cursor on EmacsView
4. Full hierarchy plumbing: accessibilityWindow, accessibilityTopLevelUIElement,
accessibilityParent, isAccessibilityFocused, FocusedUIElementChanged
Dual accessibility: UAZoomChangeFocus for Zoom + virtual element tree for
VoiceOver. Notifications target the focused virtual element, not EmacsView.
Full hierarchy plumbing (accessibilityWindow, accessibilityParent, etc.).
MRC compatible.
---
--- a/src/nsterm.h 2026-02-26 08:46:18.118172281 +0100
@@ -74,7 +71,7 @@ MRC compatible.
--- a/src/nsterm.m 2026-02-26 08:46:18.124172384 +0100
+++ b/src/nsterm.m 2026-02-26 10:19:39.112307036 +0100
+++ b/src/nsterm.m 2026-02-26 10:46:54.607568839 +0100
@@ -1104,6 +1104,11 @@
unblock_input ();
@@ -87,7 +84,7 @@ MRC compatible.
}
static void
@@ -3232,6 +3237,75 @@
@@ -3232,6 +3237,82 @@
/* Prevent the cursor from being drawn outside the text area. */
r = NSIntersectionRect (r, ns_row_rect (w, glyph_row, TEXT_AREA));
@@ -103,13 +100,20 @@ MRC compatible.
+ /* Store cursor rect for accessibilityBoundsForRange: queries. */
+ view->lastAccessibilityCursorRect = r;
+
+ /* Find the focused virtual element — VoiceOver tracks IT,
+ not the EmacsView (AXGroup). Notifications must come from
+ the element VoiceOver is monitoring. */
+ id axTarget = [view accessibilityFocusedUIElement];
+ if (!axTarget)
+ axTarget = view;
+
+ struct buffer *curbuf
+ = XBUFFER (XWINDOW (f->selected_window)->contents);
+
+ if (curbuf && BUF_MODIFF (curbuf) != view->lastAccessibilityModiff)
+ {
+ /* Buffer content changed — typing echo. Post ValueChanged
+ with rich userInfo on the VIEW so VoiceOver can speak.
+ with rich userInfo on the FOCUSED ELEMENT.
+ kAXTextStateChangeTypeEdit = 1, kAXTextEditTypeTyping = 3. */
+ view->lastAccessibilityModiff = BUF_MODIFF (curbuf);
+
@@ -133,12 +137,12 @@ MRC compatible.
+ @"AXTextChangeValues": @[change]
+ };
+ NSAccessibilityPostNotificationWithUserInfo (
+ view, NSAccessibilityValueChangedNotification, userInfo);
+ axTarget, NSAccessibilityValueChangedNotification, userInfo);
+ }
+
+ /* Always notify cursor movement. */
+ /* Always notify cursor movement on the focused element. */
+ NSAccessibilityPostNotification (
+ view, NSAccessibilitySelectedTextChangedNotification);
+ axTarget, NSAccessibilitySelectedTextChangedNotification);
+
+ /* Tell macOS Zoom where the cursor is. UAZoomChangeFocus()
+ expects top-left origin (CG coordinate space). */
@@ -163,7 +167,7 @@ MRC compatible.
ns_focus (f, NULL, 0);
NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
@@ -6849,6 +6923,650 @@
@@ -6849,6 +6930,646 @@
/* ==========================================================================
@@ -772,15 +776,11 @@ MRC compatible.
+ ptrdiff_t pt = BUF_PT (b);
+ if (pt > BUF_BEGV (b))
+ {
+ EmacsView *view = self.emacsView;
+ if (view)
+ {
+ NSRange charRange = NSMakeRange (
+ (NSUInteger)(pt - BUF_BEGV (b) - 1), 1);
+ changedText = [view accessibilityStringForRange:charRange];
+ if (!changedText)
+ changedText = @"";
+ }
+ NSRange charRange = NSMakeRange (
+ (NSUInteger)(pt - BUF_BEGV (b) - 1), 1);
+ changedText = [self accessibilityStringForRange:charRange];
+ if (!changedText)
+ changedText = @"";
+ }
+
+ NSDictionary *change = @{
@@ -814,7 +814,7 @@ MRC compatible.
EmacsView implementation
========================================================================== */
@@ -6889,6 +7607,7 @@
@@ -6889,6 +7610,7 @@
[layer release];
#endif
@@ -822,7 +822,7 @@ MRC compatible.
[[self menu] release];
[super dealloc];
}
@@ -8237,6 +8956,18 @@
@@ -8237,6 +8959,18 @@
XSETFRAME (event.frame_or_window, emacsframe);
kbd_buffer_store_event (&event);
ns_send_appdefined (-1); // Kick main loop
@@ -841,7 +841,7 @@ MRC compatible.
}
@@ -9474,6 +10205,391 @@
@@ -9474,6 +10208,391 @@
return fs_state;
}
@@ -1233,7 +1233,7 @@ MRC compatible.
@end /* EmacsView */
@@ -9941,6 +11057,14 @@
@@ -9941,6 +11060,14 @@
return [super accessibilityAttributeValue:attribute];
}