v13.3: restore typing echo + add full text protocol on EmacsView

Key changes:
- Typing echo notifications back in ns_draw_window_cursor (on EmacsView,
  synchronous with cursor draw — this is how v9 did it and it worked)
- Rich userInfo: AXTextEditType=3, AXTextChangeValues with actual char
- SelectedTextChangedNotification on every cursor draw
- Full text protocol on EmacsView: accessibilityNumberOfCharacters,
  accessibilitySelectedText, accessibilityInsertionPointLineNumber,
  accessibilityVisibleCharacterRange, accessibilityLineForIndex:,
  accessibilityRangeForLine:, accessibilityAttributedStringForRange:
- accessibilityAttributedStringForRange: on EmacsAccessibilityBuffer too
- Virtual tree kept for VoiceOver window/buffer detection
This commit is contained in:
2026-02-26 09:23:39 +01:00
parent f41fe14772
commit ebf611bf19

View File

@@ -2,7 +2,7 @@ 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: UAZoomChangeFocus + virtual element tree. Dual accessibility: UAZoomChangeFocus + virtual element tree + typing echo.
MRC compatible (unsafe_unretained, proper retain/release). MRC compatible (unsafe_unretained, proper retain/release).
--- ---
--- a/src/nsterm.h 2026-02-26 08:46:18.118172281 +0100 --- a/src/nsterm.h 2026-02-26 08:46:18.118172281 +0100
@@ -69,7 +69,7 @@ MRC compatible (unsafe_unretained, proper retain/release).
--- 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 09:02:44.734005575 +0100 +++ b/src/nsterm.m 2026-02-26 09:23:14.570493387 +0100
@@ -1104,6 +1104,11 @@ @@ -1104,6 +1104,11 @@
unblock_input (); unblock_input ();
@@ -82,7 +82,7 @@ MRC compatible (unsafe_unretained, proper retain/release).
} }
static void static void
@@ -3232,6 +3237,38 @@ @@ -3232,6 +3237,75 @@
/* 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));
@@ -98,6 +98,43 @@ MRC compatible (unsafe_unretained, proper retain/release).
+ /* Store cursor rect for accessibilityBoundsForRange: queries. */ + /* Store cursor rect for accessibilityBoundsForRange: queries. */
+ view->lastAccessibilityCursorRect = r; + view->lastAccessibilityCursorRect = r;
+ +
+ 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.
+ kAXTextStateChangeTypeEdit = 1, kAXTextEditTypeTyping = 3. */
+ view->lastAccessibilityModiff = BUF_MODIFF (curbuf);
+
+ NSString *changedText = @"";
+ ptrdiff_t pt = BUF_PT (curbuf);
+ if (pt > BUF_BEGV (curbuf))
+ {
+ NSRange charRange = NSMakeRange (
+ (NSUInteger)(pt - BUF_BEGV (curbuf) - 1), 1);
+ changedText = [view accessibilityStringForRange:charRange];
+ if (!changedText)
+ changedText = @"";
+ }
+
+ NSDictionary *change = @{
+ @"AXTextEditType": @3,
+ @"AXTextChangeValue": changedText
+ };
+ NSDictionary *userInfo = @{
+ @"AXTextStateChangeType": @1,
+ @"AXTextChangeValues": @[change]
+ };
+ NSAccessibilityPostNotificationWithUserInfo (
+ view, NSAccessibilityValueChangedNotification, userInfo);
+ }
+
+ /* Always notify cursor movement. */
+ NSAccessibilityPostNotification (
+ view, 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). */
+ if (UAZoomEnabled ()) + if (UAZoomEnabled ())
@@ -121,7 +158,7 @@ MRC compatible (unsafe_unretained, proper retain/release).
ns_focus (f, NULL, 0); ns_focus (f, NULL, 0);
NSGraphicsContext *ctx = [NSGraphicsContext currentContext]; NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
@@ -6849,6 +6886,631 @@ @@ -6849,6 +6923,637 @@
/* ========================================================================== /* ==========================================================================
@@ -550,6 +587,12 @@ MRC compatible (unsafe_unretained, proper retain/release).
+ return [text substringWithRange:range]; + return [text substringWithRange:range];
+} +}
+ +
+- (NSAttributedString *)accessibilityAttributedStringForRange:(NSRange)range
+{
+ NSString *str = [self accessibilityStringForRange:range];
+ return [[[NSAttributedString alloc] initWithString:str] autorelease];
+}
+
+- (NSInteger)accessibilityLineForIndex:(NSInteger)index +- (NSInteger)accessibilityLineForIndex:(NSInteger)index
+{ +{
+ struct window *w = self.emacsWindow; + struct window *w = self.emacsWindow;
@@ -753,7 +796,7 @@ MRC compatible (unsafe_unretained, proper retain/release).
EmacsView implementation EmacsView implementation
========================================================================== */ ========================================================================== */
@@ -6889,6 +7551,7 @@ @@ -6889,6 +7594,7 @@
[layer release]; [layer release];
#endif #endif
@@ -761,7 +804,7 @@ MRC compatible (unsafe_unretained, proper retain/release).
[[self menu] release]; [[self menu] release];
[super dealloc]; [super dealloc];
} }
@@ -9474,6 +10137,293 @@ @@ -9474,6 +10180,391 @@
return fs_state; return fs_state;
} }
@@ -1017,6 +1060,104 @@ MRC compatible (unsafe_unretained, proper retain/release).
+ return [NSString stringWithLispString:str]; + return [NSString stringWithLispString:str];
+} +}
+ +
+- (NSInteger)accessibilityNumberOfCharacters
+{
+ if (!emacsframe)
+ return 0;
+ struct buffer *curbuf
+ = XBUFFER (XWINDOW (emacsframe->selected_window)->contents);
+ if (!curbuf)
+ return 0;
+ ptrdiff_t range = BUF_ZV (curbuf) - BUF_BEGV (curbuf);
+ return (NSInteger) MIN (range, 10000);
+}
+
+- (NSString *)accessibilitySelectedText
+{
+ if (!emacsframe)
+ return @"";
+ struct buffer *curbuf
+ = XBUFFER (XWINDOW (emacsframe->selected_window)->contents);
+ if (!curbuf || NILP (BVAR (curbuf, mark_active)))
+ return @"";
+ return @"";
+}
+
+- (NSInteger)accessibilityInsertionPointLineNumber
+{
+ if (!emacsframe)
+ return 0;
+ struct window *w = XWINDOW (emacsframe->selected_window);
+ if (!w)
+ return 0;
+ return (NSInteger) (w->cursor.vpos);
+}
+
+- (NSRange)accessibilityVisibleCharacterRange
+{
+ if (!emacsframe)
+ return NSMakeRange (0, 0);
+ struct buffer *curbuf
+ = XBUFFER (XWINDOW (emacsframe->selected_window)->contents);
+ if (!curbuf)
+ return NSMakeRange (0, 0);
+ ptrdiff_t range = BUF_ZV (curbuf) - BUF_BEGV (curbuf);
+ return NSMakeRange (0, (NSUInteger) MIN (range, 10000));
+}
+
+- (NSAttributedString *)accessibilityAttributedStringForRange:(NSRange)range
+{
+ NSString *str = [self accessibilityStringForRange:range];
+ return [[[NSAttributedString alloc] initWithString:str] autorelease];
+}
+
+- (NSInteger)accessibilityLineForIndex:(NSInteger)index
+{
+ if (!emacsframe)
+ return 0;
+ struct window *w = XWINDOW (emacsframe->selected_window);
+ if (!w || !w->current_matrix)
+ return 0;
+ struct buffer *curbuf = XBUFFER (w->contents);
+ if (!curbuf)
+ return 0;
+ ptrdiff_t charpos = BUF_BEGV (curbuf) + (ptrdiff_t) index;
+ struct glyph_matrix *matrix = w->current_matrix;
+ for (int i = 0; i < matrix->nrows; i++)
+ {
+ struct glyph_row *row = matrix->rows + i;
+ if (!row->enabled_p)
+ continue;
+ if (MATRIX_ROW_START_CHARPOS (row) <= charpos
+ && charpos < MATRIX_ROW_END_CHARPOS (row))
+ return (NSInteger) i;
+ }
+ return 0;
+}
+
+- (NSRange)accessibilityRangeForLine:(NSInteger)line
+{
+ if (!emacsframe)
+ return NSMakeRange (0, 0);
+ struct window *w = XWINDOW (emacsframe->selected_window);
+ if (!w || !w->current_matrix)
+ return NSMakeRange (0, 0);
+ struct buffer *curbuf = XBUFFER (w->contents);
+ if (!curbuf)
+ return NSMakeRange (0, 0);
+ struct glyph_matrix *matrix = w->current_matrix;
+ if (line < 0 || line >= matrix->nrows)
+ return NSMakeRange (0, 0);
+ struct glyph_row *row = matrix->rows + line;
+ if (!row->enabled_p)
+ return NSMakeRange (0, 0);
+ ptrdiff_t start = MATRIX_ROW_START_CHARPOS (row) - BUF_BEGV (curbuf);
+ ptrdiff_t end = MATRIX_ROW_END_CHARPOS (row) - BUF_BEGV (curbuf);
+ if (start < 0) start = 0;
+ if (end < start) end = start;
+ return NSMakeRange ((NSUInteger) start, (NSUInteger) (end - start));
+}
+
+/* ---- Legacy parameterized attribute APIs (Zoom uses these) ---- */ +/* ---- Legacy parameterized attribute APIs (Zoom uses these) ---- */
+ +
+- (NSArray *)accessibilityParameterizedAttributeNames +- (NSArray *)accessibilityParameterizedAttributeNames
@@ -1055,7 +1196,7 @@ MRC compatible (unsafe_unretained, proper retain/release).
@end /* EmacsView */ @end /* EmacsView */
@@ -9941,6 +10891,14 @@ @@ -9941,6 +11032,14 @@
return [super accessibilityAttributeValue:attribute]; return [super accessibilityAttributeValue:attribute];
} }