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:
@@ -2,7 +2,7 @@ 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: UAZoomChangeFocus + virtual element tree.
|
||||
Dual accessibility: UAZoomChangeFocus + virtual element tree + typing echo.
|
||||
MRC compatible (unsafe_unretained, proper retain/release).
|
||||
---
|
||||
--- 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
|
||||
+++ 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 @@
|
||||
|
||||
unblock_input ();
|
||||
@@ -82,7 +82,7 @@ MRC compatible (unsafe_unretained, proper retain/release).
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -3232,6 +3237,38 @@
|
||||
@@ -3232,6 +3237,75 @@
|
||||
/* Prevent the cursor from being drawn outside the 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. */
|
||||
+ 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()
|
||||
+ expects top-left origin (CG coordinate space). */
|
||||
+ if (UAZoomEnabled ())
|
||||
@@ -121,7 +158,7 @@ MRC compatible (unsafe_unretained, proper retain/release).
|
||||
ns_focus (f, NULL, 0);
|
||||
|
||||
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];
|
||||
+}
|
||||
+
|
||||
+- (NSAttributedString *)accessibilityAttributedStringForRange:(NSRange)range
|
||||
+{
|
||||
+ NSString *str = [self accessibilityStringForRange:range];
|
||||
+ return [[[NSAttributedString alloc] initWithString:str] autorelease];
|
||||
+}
|
||||
+
|
||||
+- (NSInteger)accessibilityLineForIndex:(NSInteger)index
|
||||
+{
|
||||
+ struct window *w = self.emacsWindow;
|
||||
@@ -753,7 +796,7 @@ MRC compatible (unsafe_unretained, proper retain/release).
|
||||
EmacsView implementation
|
||||
|
||||
========================================================================== */
|
||||
@@ -6889,6 +7551,7 @@
|
||||
@@ -6889,6 +7594,7 @@
|
||||
[layer release];
|
||||
#endif
|
||||
|
||||
@@ -761,7 +804,7 @@ MRC compatible (unsafe_unretained, proper retain/release).
|
||||
[[self menu] release];
|
||||
[super dealloc];
|
||||
}
|
||||
@@ -9474,6 +10137,293 @@
|
||||
@@ -9474,6 +10180,391 @@
|
||||
return fs_state;
|
||||
}
|
||||
|
||||
@@ -1017,6 +1060,104 @@ MRC compatible (unsafe_unretained, proper retain/release).
|
||||
+ 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) ---- */
|
||||
+
|
||||
+- (NSArray *)accessibilityParameterizedAttributeNames
|
||||
@@ -1055,7 +1196,7 @@ MRC compatible (unsafe_unretained, proper retain/release).
|
||||
@end /* EmacsView */
|
||||
|
||||
|
||||
@@ -9941,6 +10891,14 @@
|
||||
@@ -9941,6 +11032,14 @@
|
||||
|
||||
return [super accessibilityAttributeValue:attribute];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user