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