Merge remote-tracking branch 'refs/remotes/origin/master'

This commit is contained in:
Martin Sukany
2026-02-26 18:08:03 +01:00

View File

@@ -1,13 +1,13 @@
From 991ef1c9f04ccfdd71482ef3df54050f314e8ab5 Mon Sep 17 00:00:00 2001 From 5fb855925912142401db0c732fff2014d21c3362 Mon Sep 17 00:00:00 2001
From: Daneel <daneel@sukany.cz> From: Daneel <daneel@sukany.cz>
Date: Thu, 26 Feb 2026 17:49:52 +0100 Date: Thu, 26 Feb 2026 18:06:10 +0100
Subject: [PATCH] ns: implement AXBoundsForRange and VoiceOver interaction Subject: [PATCH] ns: implement AXBoundsForRange and VoiceOver interaction
fixes fixes
--- ---
nsterm.h | 70 ++ nsterm.h | 70 ++
nsterm.m | 2058 +++++++++++++++++++++++++++++++++++++++++++++++++----- nsterm.m | 2090 +++++++++++++++++++++++++++++++++++++++++++++++++-----
2 files changed, 1960 insertions(+), 168 deletions(-) 2 files changed, 1997 insertions(+), 163 deletions(-)
diff --git a/src/nsterm.h b/src/nsterm.h diff --git a/src/nsterm.h b/src/nsterm.h
index 7c1ee4c..97da979 100644 index 7c1ee4c..97da979 100644
@@ -105,7 +105,7 @@ index 7c1ee4c..97da979 100644
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index 932d209..8673194 100644 index 932d209..e7af9a3 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -1104,6 +1104,11 @@ ns_update_end (struct frame *f) @@ -1104,6 +1104,11 @@ ns_update_end (struct frame *f)
@@ -158,7 +158,7 @@ index 932d209..8673194 100644
ns_focus (f, NULL, 0); ns_focus (f, NULL, 0);
NSGraphicsContext *ctx = [NSGraphicsContext currentContext]; NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
@@ -6849,245 +6885,1611 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action) @@ -6849,240 +6885,1646 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action)
/* ========================================================================== /* ==========================================================================
@@ -462,6 +462,27 @@ index 932d209..8673194 100644
-#endif -#endif
-- (Lisp_Object) showFontPanel -- (Lisp_Object) showFontPanel
+/* AX enum numeric compatibility for NSAccessibility notifications.
+ Values match WebKit AXObjectCacheMac fallback enums
+ (AXTextStateChangeType / AXTextEditType / AXTextSelectionDirection /
+ AXTextSelectionGranularity). */
+enum {
+ ns_ax_text_state_change_unknown = 0,
+ ns_ax_text_state_change_edit = 1,
+ ns_ax_text_state_change_selection_move = 2,
+
+ ns_ax_text_edit_type_typing = 3,
+
+ ns_ax_text_selection_direction_unknown = 0,
+ ns_ax_text_selection_direction_previous = 3,
+ ns_ax_text_selection_direction_next = 4,
+ ns_ax_text_selection_direction_discontiguous = 5,
+
+ ns_ax_text_selection_granularity_unknown = 0,
+ ns_ax_text_selection_granularity_character = 1,
+ ns_ax_text_selection_granularity_line = 3,
+};
+
+static NSUInteger +static NSUInteger
+ns_ax_utf16_length_for_buffer_range (struct buffer *b, ptrdiff_t start, +ns_ax_utf16_length_for_buffer_range (struct buffer *b, ptrdiff_t start,
+ ptrdiff_t end) + ptrdiff_t end)
@@ -727,11 +748,6 @@ index 932d209..8673194 100644
- /* XXX: There is an occasional condition in which, when Emacs display - /* XXX: There is an occasional condition in which, when Emacs display
- updates a different frame from the current one, and temporarily - updates a different frame from the current one, and temporarily
- selects it, then processes some interrupt-driven input - selects it, then processes some interrupt-driven input
- (dispnew.c:3878), OS will send the event to the correct NSWindow, but
- for some reason that window has its first responder set to the NSView
- most recently updated (I guess), which is not the correct one. */
- [(EmacsView *)[[theEvent window] delegate] keyDown: theEvent];
- return;
+ ptrdiff_t start; + ptrdiff_t start;
+ ns_ax_visible_run *runs = NULL; + ns_ax_visible_run *runs = NULL;
+ NSUInteger nruns = 0; + NSUInteger nruns = 0;
@@ -1006,8 +1022,9 @@ index 932d209..8673194 100644
+ +
+ /* Post SelectedTextChanged so VoiceOver reads the current line + /* Post SelectedTextChanged so VoiceOver reads the current line
+ upon entering text interaction mode. + upon entering text interaction mode.
+ kAXTextStateChangeTypeSelectionMove = 1. */ + WebKit AXObjectCacheMac fallback enum: SelectionMove = 2. */
+ NSDictionary *info = @{@"AXTextStateChangeType": @1}; + NSDictionary *info = @{@"AXTextStateChangeType": @(ns_ax_text_state_change_selection_move),
+ @"AXTextChangeElement": self};
+ NSAccessibilityPostNotificationWithUserInfo ( + NSAccessibilityPostNotificationWithUserInfo (
+ self, NSAccessibilitySelectedTextChangedNotification, info); + self, NSAccessibilitySelectedTextChangedNotification, info);
+} +}
@@ -1261,7 +1278,7 @@ index 932d209..8673194 100644
+ BOOL markActive = !NILP (BVAR (b, mark_active)); + BOOL markActive = !NILP (BVAR (b, mark_active));
+ +
+ /* --- Text changed → typing echo --- + /* --- Text changed → typing echo ---
+ kAXTextStateChangeTypeEdit = 0, kAXTextEditTypeTyping = 3. */ + WebKit AXObjectCacheMac fallback enum: Edit = 1, Typing = 3. */
+ if (modiff != self.cachedModiff) + if (modiff != self.cachedModiff)
+ { + {
+ /* Capture changed char before invalidating cache. */ + /* Capture changed char before invalidating cache. */
@@ -1293,20 +1310,21 @@ index 932d209..8673194 100644
+ self.cachedPoint = point; + self.cachedPoint = point;
+ +
+ NSDictionary *change = @{ + NSDictionary *change = @{
+ @"AXTextEditType": @3, + @"AXTextEditType": @(ns_ax_text_edit_type_typing),
+ @"AXTextChangeValue": changedChar, + @"AXTextChangeValue": changedChar,
+ @"AXTextChangeValueLength": @([changedChar length]) + @"AXTextChangeValueLength": @([changedChar length])
+ }; + };
+ NSDictionary *userInfo = @{ + NSDictionary *userInfo = @{
+ @"AXTextStateChangeType": @0, + @"AXTextStateChangeType": @(ns_ax_text_state_change_edit),
+ @"AXTextChangeValues": @[change] + @"AXTextChangeValues": @[change],
+ @"AXTextChangeElement": self
+ }; + };
+ NSAccessibilityPostNotificationWithUserInfo ( + NSAccessibilityPostNotificationWithUserInfo (
+ self, NSAccessibilityValueChangedNotification, userInfo); + self, NSAccessibilityValueChangedNotification, userInfo);
+ } + }
+ +
+ /* --- Cursor moved or selection changed → line reading --- + /* --- Cursor moved or selection changed → line reading ---
+ kAXTextStateChangeTypeSelectionMove = 1. + WebKit AXObjectCacheMac fallback enum: SelectionMove = 2.
+ Use 'else if' — edits and selection moves are mutually exclusive + Use 'else if' — edits and selection moves are mutually exclusive
+ per the WebKit/Chromium pattern. VoiceOver gets confused if + per the WebKit/Chromium pattern. VoiceOver gets confused if
+ both notifications arrive in the same runloop iteration. */ + both notifications arrive in the same runloop iteration. */
@@ -1316,23 +1334,23 @@ index 932d209..8673194 100644
+ self.cachedPoint = point; + self.cachedPoint = point;
+ self.cachedMarkActive = markActive; + self.cachedMarkActive = markActive;
+ +
+ /* Compute direction: 3=Previous, 4=Next, 5=Discontiguous. */ + /* Compute direction. */
+ NSInteger direction = 5; + NSInteger direction = ns_ax_text_selection_direction_discontiguous;
+ if (point > oldPoint) + if (point > oldPoint)
+ direction = 4; + direction = ns_ax_text_selection_direction_next;
+ else if (point < oldPoint) + else if (point < oldPoint)
+ direction = 3; + direction = ns_ax_text_selection_direction_previous;
+ +
+ /* Compute granularity from movement distance. + /* Compute granularity from movement distance.
+ Prefer robust line-range comparison for vertical movement, + Prefer robust line-range comparison for vertical movement,
+ otherwise single char (1) or unknown (0). */ + otherwise single char (1) or unknown (0). */
+ NSInteger granularity = 0; + NSInteger granularity = ns_ax_text_selection_granularity_unknown;
+ [self ensureTextCache]; + [self ensureTextCache];
+ if (cachedText && oldPoint > 0) + if (cachedText && oldPoint > 0)
+ { + {
+ ptrdiff_t delta = point - oldPoint; + ptrdiff_t delta = point - oldPoint;
+ if (delta == 1 || delta == -1) + if (delta == 1 || delta == -1)
+ granularity = 1; /* Character. */ + granularity = ns_ax_text_selection_granularity_character; /* Character. */
+ else + else
+ { + {
+ NSUInteger tlen = [cachedText length]; + NSUInteger tlen = [cachedText length];
@@ -1348,15 +1366,16 @@ index 932d209..8673194 100644
+ NSRange newLine = [cachedText lineRangeForRange: + NSRange newLine = [cachedText lineRangeForRange:
+ NSMakeRange (newIdx, 0)]; + NSMakeRange (newIdx, 0)];
+ if (oldLine.location != newLine.location) + if (oldLine.location != newLine.location)
+ granularity = 3; /* Line. */ + granularity = ns_ax_text_selection_granularity_line; /* Line. */
+ +
+ } + }
+ } + }
+ +
+ NSDictionary *moveInfo = @{ + NSDictionary *moveInfo = @{
+ @"AXTextStateChangeType": @1, + @"AXTextStateChangeType": @(ns_ax_text_state_change_selection_move),
+ @"AXTextSelectionDirection": @(direction), + @"AXTextSelectionDirection": @(direction),
+ @"AXTextSelectionGranularity": @(granularity) + @"AXTextSelectionGranularity": @(granularity),
+ @"AXTextChangeElement": self
+ }; + };
+ NSAccessibilityPostNotificationWithUserInfo ( + NSAccessibilityPostNotificationWithUserInfo (
+ self, + self,
@@ -1451,13 +1470,20 @@ index 932d209..8673194 100644
+ ptrdiff_t ov_end = OVERLAY_END (ov); + ptrdiff_t ov_end = OVERLAY_END (ov);
+ if (ov_end > ov_start) + if (ov_end > ov_start)
+ { + {
+ NSUInteger ax_s = [self accessibilityIndexForCharpos: + NSUInteger ax_idx = [self accessibilityIndexForCharpos:
+ ov_start]; + ov_start];
+ NSUInteger ax_e = [self accessibilityIndexForCharpos: + if (ax_idx <= [cachedText length])
+ ov_end]; + {
+ if (ax_e > ax_s && ax_e <= [cachedText length]) + NSInteger lineNum = [self accessibilityLineForIndex:ax_idx];
+ announceText = [cachedText substringWithRange: + NSRange lineRange = [self accessibilityRangeForLine:lineNum];
+ NSMakeRange (ax_s, ax_e - ax_s)]; + if (lineRange.location != NSNotFound
+ && lineRange.length > 0
+ && lineRange.location + lineRange.length
+ <= [cachedText length])
+ announceText = [cachedText substringWithRange:lineRange];
+ }
+ currentOverlayStart = ov_start;
+ currentOverlayEnd = ov_end;
+ } + }
+ break; + break;
+ } + }
@@ -1472,15 +1498,19 @@ index 932d209..8673194 100644
+ ptrdiff_t ov_end = 0; + ptrdiff_t ov_end = 0;
+ if (ns_ax_find_completion_overlay_range (b, point, &ov_start, &ov_end)) + if (ns_ax_find_completion_overlay_range (b, point, &ov_start, &ov_end))
+ { + {
+ NSUInteger ax_s = [self accessibilityIndexForCharpos:ov_start]; + NSUInteger ax_idx = [self accessibilityIndexForCharpos:ov_start];
+ NSUInteger ax_e = [self accessibilityIndexForCharpos:ov_end]; + if (ax_idx <= [cachedText length])
+ if (ax_e > ax_s && ax_e <= [cachedText length])
+ { + {
+ announceText = [cachedText substringWithRange: + NSInteger lineNum = [self accessibilityLineForIndex:ax_idx];
+ NSMakeRange (ax_s, ax_e - ax_s)]; + NSRange lineRange = [self accessibilityRangeForLine:lineNum];
+ currentOverlayStart = ov_start; + if (lineRange.location != NSNotFound
+ currentOverlayEnd = ov_end; + && lineRange.length > 0
+ && lineRange.location + lineRange.length
+ <= [cachedText length])
+ announceText = [cachedText substringWithRange:lineRange];
+ } + }
+ currentOverlayStart = ov_start;
+ currentOverlayEnd = ov_end;
+ } + }
+ } + }
+ +
@@ -1571,13 +1601,18 @@ index 932d209..8673194 100644
+ &currentOverlayStart, + &currentOverlayStart,
+ &currentOverlayEnd)) + &currentOverlayEnd))
+ { + {
+ NSUInteger ax_s = [self accessibilityIndexForCharpos: + NSUInteger ax_idx = [self accessibilityIndexForCharpos:
+ currentOverlayStart]; + currentOverlayStart];
+ NSUInteger ax_e = [self accessibilityIndexForCharpos: + if (ax_idx <= [cachedText length])
+ currentOverlayEnd]; + {
+ if (ax_e > ax_s && ax_e <= [cachedText length]) + NSInteger lineNum = [self accessibilityLineForIndex:ax_idx];
+ announceText = [cachedText substringWithRange: + NSRange lineRange = [self accessibilityRangeForLine:lineNum];
+ NSMakeRange (ax_s, ax_e - ax_s)]; + if (lineRange.location != NSNotFound
+ && lineRange.length > 0
+ && lineRange.location + lineRange.length
+ <= [cachedText length])
+ announceText = [cachedText substringWithRange:lineRange];
+ }
+ } + }
+ +
+ if (b != oldb2) + if (b != oldb2)
@@ -1930,15 +1965,10 @@ index 932d209..8673194 100644
+ /* XXX: There is an occasional condition in which, when Emacs display + /* XXX: There is an occasional condition in which, when Emacs display
+ updates a different frame from the current one, and temporarily + updates a different frame from the current one, and temporarily
+ selects it, then processes some interrupt-driven input + selects it, then processes some interrupt-driven input
+ (dispnew.c:3878), OS will send the event to the correct NSWindow, but (dispnew.c:3878), OS will send the event to the correct NSWindow, but
+ for some reason that window has its first responder set to the NSView for some reason that window has its first responder set to the NSView
+ most recently updated (I guess), which is not the correct one. */ most recently updated (I guess), which is not the correct one. */
+ [(EmacsView *)[[theEvent window] delegate] keyDown: theEvent]; @@ -8237,6 +9679,28 @@ ns_in_echo_area (void)
+ return;
}
if (nsEvArray == nil)
@@ -8237,6 +9639,27 @@ ns_in_echo_area (void)
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
@@ -1954,7 +1984,8 @@ index 932d209..8673194 100644
+ { + {
+ NSAccessibilityPostNotification (focused, + NSAccessibilityPostNotification (focused,
+ NSAccessibilityFocusedUIElementChangedNotification); + NSAccessibilityFocusedUIElementChangedNotification);
+ NSDictionary *info = @{@"AXTextStateChangeType": @1}; + NSDictionary *info = @{@"AXTextStateChangeType": @(ns_ax_text_state_change_selection_move),
+ @"AXTextChangeElement": focused};
+ NSAccessibilityPostNotificationWithUserInfo (focused, + NSAccessibilityPostNotificationWithUserInfo (focused,
+ NSAccessibilitySelectedTextChangedNotification, info); + NSAccessibilitySelectedTextChangedNotification, info);
+ } + }
@@ -1966,7 +1997,7 @@ index 932d209..8673194 100644
} }
@@ -9474,6 +10897,297 @@ ns_in_echo_area (void) @@ -9474,6 +10938,298 @@ ns_in_echo_area (void)
return fs_state; return fs_state;
} }
@@ -2184,7 +2215,8 @@ index 932d209..8673194 100644
+ { + {
+ NSAccessibilityPostNotification (focused, + NSAccessibilityPostNotification (focused,
+ NSAccessibilityFocusedUIElementChangedNotification); + NSAccessibilityFocusedUIElementChangedNotification);
+ NSDictionary *info = @{@"AXTextStateChangeType": @1}; + NSDictionary *info = @{@"AXTextStateChangeType": @(ns_ax_text_state_change_selection_move),
+ @"AXTextChangeElement": focused};
+ NSAccessibilityPostNotificationWithUserInfo (focused, + NSAccessibilityPostNotificationWithUserInfo (focused,
+ NSAccessibilitySelectedTextChangedNotification, info); + NSAccessibilitySelectedTextChangedNotification, info);
+ } + }
@@ -2264,7 +2296,7 @@ index 932d209..8673194 100644
@end /* EmacsView */ @end /* EmacsView */
@@ -9941,6 +11655,14 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c) @@ -9941,6 +11697,14 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c)
return [super accessibilityAttributeValue:attribute]; return [super accessibilityAttributeValue:attribute];
} }