Merge remote-tracking branch 'refs/remotes/origin/master'
This commit is contained in:
@@ -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>
|
||||
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
|
||||
fixes
|
||||
|
||||
---
|
||||
nsterm.h | 70 ++
|
||||
nsterm.m | 2058 +++++++++++++++++++++++++++++++++++++++++++++++++-----
|
||||
2 files changed, 1960 insertions(+), 168 deletions(-)
|
||||
nsterm.m | 2090 +++++++++++++++++++++++++++++++++++++++++++++++++-----
|
||||
2 files changed, 1997 insertions(+), 163 deletions(-)
|
||||
|
||||
diff --git a/src/nsterm.h b/src/nsterm.h
|
||||
index 7c1ee4c..97da979 100644
|
||||
@@ -105,7 +105,7 @@ index 7c1ee4c..97da979 100644
|
||||
|
||||
|
||||
diff --git a/src/nsterm.m b/src/nsterm.m
|
||||
index 932d209..8673194 100644
|
||||
index 932d209..e7af9a3 100644
|
||||
--- a/src/nsterm.m
|
||||
+++ b/src/nsterm.m
|
||||
@@ -1104,6 +1104,11 @@ ns_update_end (struct frame *f)
|
||||
@@ -158,7 +158,7 @@ index 932d209..8673194 100644
|
||||
ns_focus (f, NULL, 0);
|
||||
|
||||
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
|
||||
|
||||
-- (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
|
||||
+ns_ax_utf16_length_for_buffer_range (struct buffer *b, ptrdiff_t start,
|
||||
+ ptrdiff_t end)
|
||||
@@ -727,11 +748,6 @@ index 932d209..8673194 100644
|
||||
- /* XXX: There is an occasional condition in which, when Emacs display
|
||||
- updates a different frame from the current one, and temporarily
|
||||
- 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;
|
||||
+ ns_ax_visible_run *runs = NULL;
|
||||
+ NSUInteger nruns = 0;
|
||||
@@ -1006,8 +1022,9 @@ index 932d209..8673194 100644
|
||||
+
|
||||
+ /* Post SelectedTextChanged so VoiceOver reads the current line
|
||||
+ upon entering text interaction mode.
|
||||
+ kAXTextStateChangeTypeSelectionMove = 1. */
|
||||
+ NSDictionary *info = @{@"AXTextStateChangeType": @1};
|
||||
+ WebKit AXObjectCacheMac fallback enum: SelectionMove = 2. */
|
||||
+ NSDictionary *info = @{@"AXTextStateChangeType": @(ns_ax_text_state_change_selection_move),
|
||||
+ @"AXTextChangeElement": self};
|
||||
+ NSAccessibilityPostNotificationWithUserInfo (
|
||||
+ self, NSAccessibilitySelectedTextChangedNotification, info);
|
||||
+}
|
||||
@@ -1261,7 +1278,7 @@ index 932d209..8673194 100644
|
||||
+ BOOL markActive = !NILP (BVAR (b, mark_active));
|
||||
+
|
||||
+ /* --- Text changed → typing echo ---
|
||||
+ kAXTextStateChangeTypeEdit = 0, kAXTextEditTypeTyping = 3. */
|
||||
+ WebKit AXObjectCacheMac fallback enum: Edit = 1, Typing = 3. */
|
||||
+ if (modiff != self.cachedModiff)
|
||||
+ {
|
||||
+ /* Capture changed char before invalidating cache. */
|
||||
@@ -1293,20 +1310,21 @@ index 932d209..8673194 100644
|
||||
+ self.cachedPoint = point;
|
||||
+
|
||||
+ NSDictionary *change = @{
|
||||
+ @"AXTextEditType": @3,
|
||||
+ @"AXTextEditType": @(ns_ax_text_edit_type_typing),
|
||||
+ @"AXTextChangeValue": changedChar,
|
||||
+ @"AXTextChangeValueLength": @([changedChar length])
|
||||
+ };
|
||||
+ NSDictionary *userInfo = @{
|
||||
+ @"AXTextStateChangeType": @0,
|
||||
+ @"AXTextChangeValues": @[change]
|
||||
+ @"AXTextStateChangeType": @(ns_ax_text_state_change_edit),
|
||||
+ @"AXTextChangeValues": @[change],
|
||||
+ @"AXTextChangeElement": self
|
||||
+ };
|
||||
+ NSAccessibilityPostNotificationWithUserInfo (
|
||||
+ self, NSAccessibilityValueChangedNotification, userInfo);
|
||||
+ }
|
||||
+
|
||||
+ /* --- 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
|
||||
+ per the WebKit/Chromium pattern. VoiceOver gets confused if
|
||||
+ both notifications arrive in the same runloop iteration. */
|
||||
@@ -1316,23 +1334,23 @@ index 932d209..8673194 100644
|
||||
+ self.cachedPoint = point;
|
||||
+ self.cachedMarkActive = markActive;
|
||||
+
|
||||
+ /* Compute direction: 3=Previous, 4=Next, 5=Discontiguous. */
|
||||
+ NSInteger direction = 5;
|
||||
+ /* Compute direction. */
|
||||
+ NSInteger direction = ns_ax_text_selection_direction_discontiguous;
|
||||
+ if (point > oldPoint)
|
||||
+ direction = 4;
|
||||
+ direction = ns_ax_text_selection_direction_next;
|
||||
+ else if (point < oldPoint)
|
||||
+ direction = 3;
|
||||
+ direction = ns_ax_text_selection_direction_previous;
|
||||
+
|
||||
+ /* Compute granularity from movement distance.
|
||||
+ Prefer robust line-range comparison for vertical movement,
|
||||
+ otherwise single char (1) or unknown (0). */
|
||||
+ NSInteger granularity = 0;
|
||||
+ NSInteger granularity = ns_ax_text_selection_granularity_unknown;
|
||||
+ [self ensureTextCache];
|
||||
+ if (cachedText && oldPoint > 0)
|
||||
+ {
|
||||
+ ptrdiff_t delta = point - oldPoint;
|
||||
+ if (delta == 1 || delta == -1)
|
||||
+ granularity = 1; /* Character. */
|
||||
+ granularity = ns_ax_text_selection_granularity_character; /* Character. */
|
||||
+ else
|
||||
+ {
|
||||
+ NSUInteger tlen = [cachedText length];
|
||||
@@ -1348,15 +1366,16 @@ index 932d209..8673194 100644
|
||||
+ NSRange newLine = [cachedText lineRangeForRange:
|
||||
+ NSMakeRange (newIdx, 0)];
|
||||
+ if (oldLine.location != newLine.location)
|
||||
+ granularity = 3; /* Line. */
|
||||
+ granularity = ns_ax_text_selection_granularity_line; /* Line. */
|
||||
+
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ NSDictionary *moveInfo = @{
|
||||
+ @"AXTextStateChangeType": @1,
|
||||
+ @"AXTextStateChangeType": @(ns_ax_text_state_change_selection_move),
|
||||
+ @"AXTextSelectionDirection": @(direction),
|
||||
+ @"AXTextSelectionGranularity": @(granularity)
|
||||
+ @"AXTextSelectionGranularity": @(granularity),
|
||||
+ @"AXTextChangeElement": self
|
||||
+ };
|
||||
+ NSAccessibilityPostNotificationWithUserInfo (
|
||||
+ self,
|
||||
@@ -1451,13 +1470,20 @@ index 932d209..8673194 100644
|
||||
+ ptrdiff_t ov_end = OVERLAY_END (ov);
|
||||
+ if (ov_end > ov_start)
|
||||
+ {
|
||||
+ NSUInteger ax_s = [self accessibilityIndexForCharpos:
|
||||
+ ov_start];
|
||||
+ NSUInteger ax_e = [self accessibilityIndexForCharpos:
|
||||
+ ov_end];
|
||||
+ if (ax_e > ax_s && ax_e <= [cachedText length])
|
||||
+ announceText = [cachedText substringWithRange:
|
||||
+ NSMakeRange (ax_s, ax_e - ax_s)];
|
||||
+ NSUInteger ax_idx = [self accessibilityIndexForCharpos:
|
||||
+ ov_start];
|
||||
+ if (ax_idx <= [cachedText length])
|
||||
+ {
|
||||
+ NSInteger lineNum = [self accessibilityLineForIndex:ax_idx];
|
||||
+ NSRange lineRange = [self accessibilityRangeForLine:lineNum];
|
||||
+ if (lineRange.location != NSNotFound
|
||||
+ && lineRange.length > 0
|
||||
+ && lineRange.location + lineRange.length
|
||||
+ <= [cachedText length])
|
||||
+ announceText = [cachedText substringWithRange:lineRange];
|
||||
+ }
|
||||
+ currentOverlayStart = ov_start;
|
||||
+ currentOverlayEnd = ov_end;
|
||||
+ }
|
||||
+ break;
|
||||
+ }
|
||||
@@ -1472,15 +1498,19 @@ index 932d209..8673194 100644
|
||||
+ ptrdiff_t ov_end = 0;
|
||||
+ if (ns_ax_find_completion_overlay_range (b, point, &ov_start, &ov_end))
|
||||
+ {
|
||||
+ NSUInteger ax_s = [self accessibilityIndexForCharpos:ov_start];
|
||||
+ NSUInteger ax_e = [self accessibilityIndexForCharpos:ov_end];
|
||||
+ if (ax_e > ax_s && ax_e <= [cachedText length])
|
||||
+ NSUInteger ax_idx = [self accessibilityIndexForCharpos:ov_start];
|
||||
+ if (ax_idx <= [cachedText length])
|
||||
+ {
|
||||
+ announceText = [cachedText substringWithRange:
|
||||
+ NSMakeRange (ax_s, ax_e - ax_s)];
|
||||
+ currentOverlayStart = ov_start;
|
||||
+ currentOverlayEnd = ov_end;
|
||||
+ NSInteger lineNum = [self accessibilityLineForIndex:ax_idx];
|
||||
+ NSRange lineRange = [self accessibilityRangeForLine:lineNum];
|
||||
+ if (lineRange.location != NSNotFound
|
||||
+ && 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
|
||||
+ ¤tOverlayStart,
|
||||
+ ¤tOverlayEnd))
|
||||
+ {
|
||||
+ NSUInteger ax_s = [self accessibilityIndexForCharpos:
|
||||
+ NSUInteger ax_idx = [self accessibilityIndexForCharpos:
|
||||
+ currentOverlayStart];
|
||||
+ NSUInteger ax_e = [self accessibilityIndexForCharpos:
|
||||
+ currentOverlayEnd];
|
||||
+ if (ax_e > ax_s && ax_e <= [cachedText length])
|
||||
+ announceText = [cachedText substringWithRange:
|
||||
+ NSMakeRange (ax_s, ax_e - ax_s)];
|
||||
+ if (ax_idx <= [cachedText length])
|
||||
+ {
|
||||
+ NSInteger lineNum = [self accessibilityLineForIndex:ax_idx];
|
||||
+ NSRange lineRange = [self accessibilityRangeForLine:lineNum];
|
||||
+ if (lineRange.location != NSNotFound
|
||||
+ && lineRange.length > 0
|
||||
+ && lineRange.location + lineRange.length
|
||||
+ <= [cachedText length])
|
||||
+ announceText = [cachedText substringWithRange:lineRange];
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (b != oldb2)
|
||||
@@ -1930,15 +1965,10 @@ index 932d209..8673194 100644
|
||||
+ /* XXX: There is an occasional condition in which, when Emacs display
|
||||
+ updates a different frame from the current one, and temporarily
|
||||
+ 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;
|
||||
}
|
||||
|
||||
if (nsEvArray == nil)
|
||||
@@ -8237,6 +9639,27 @@ ns_in_echo_area (void)
|
||||
(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. */
|
||||
@@ -8237,6 +9679,28 @@ ns_in_echo_area (void)
|
||||
XSETFRAME (event.frame_or_window, emacsframe);
|
||||
kbd_buffer_store_event (&event);
|
||||
ns_send_appdefined (-1); // Kick main loop
|
||||
@@ -1954,7 +1984,8 @@ index 932d209..8673194 100644
|
||||
+ {
|
||||
+ NSAccessibilityPostNotification (focused,
|
||||
+ NSAccessibilityFocusedUIElementChangedNotification);
|
||||
+ NSDictionary *info = @{@"AXTextStateChangeType": @1};
|
||||
+ NSDictionary *info = @{@"AXTextStateChangeType": @(ns_ax_text_state_change_selection_move),
|
||||
+ @"AXTextChangeElement": focused};
|
||||
+ NSAccessibilityPostNotificationWithUserInfo (focused,
|
||||
+ 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;
|
||||
}
|
||||
|
||||
@@ -2184,7 +2215,8 @@ index 932d209..8673194 100644
|
||||
+ {
|
||||
+ NSAccessibilityPostNotification (focused,
|
||||
+ NSAccessibilityFocusedUIElementChangedNotification);
|
||||
+ NSDictionary *info = @{@"AXTextStateChangeType": @1};
|
||||
+ NSDictionary *info = @{@"AXTextStateChangeType": @(ns_ax_text_state_change_selection_move),
|
||||
+ @"AXTextChangeElement": focused};
|
||||
+ NSAccessibilityPostNotificationWithUserInfo (focused,
|
||||
+ NSAccessibilitySelectedTextChangedNotification, info);
|
||||
+ }
|
||||
@@ -2264,7 +2296,7 @@ index 932d209..8673194 100644
|
||||
@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];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user