patches: fix AX enum mapping + completion announcement source
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>
|
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,17 +1498,21 @@ 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];
|
||||||
|
+ if (lineRange.location != NSNotFound
|
||||||
|
+ && lineRange.length > 0
|
||||||
|
+ && lineRange.location + lineRange.length
|
||||||
|
+ <= [cachedText length])
|
||||||
|
+ announceText = [cachedText substringWithRange:lineRange];
|
||||||
|
+ }
|
||||||
+ currentOverlayStart = ov_start;
|
+ currentOverlayStart = ov_start;
|
||||||
+ currentOverlayEnd = ov_end;
|
+ currentOverlayEnd = ov_end;
|
||||||
+ }
|
+ }
|
||||||
+ }
|
+ }
|
||||||
+ }
|
|
||||||
+
|
+
|
||||||
+ if (b != oldb2)
|
+ if (b != oldb2)
|
||||||
+ set_buffer_internal_1 (oldb2);
|
+ set_buffer_internal_1 (oldb2);
|
||||||
@@ -1571,13 +1601,18 @@ index 932d209..8673194 100644
|
|||||||
+ ¤tOverlayStart,
|
+ ¤tOverlayStart,
|
||||||
+ ¤tOverlayEnd))
|
+ ¤tOverlayEnd))
|
||||||
+ {
|
+ {
|
||||||
+ 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];
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user