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

This commit is contained in:
Martin Sukany
2026-02-26 19:14:20 +01:00

View File

@@ -1,13 +1,13 @@
From f88649b2e09520d4c6e7ebc041e9e1a6e7ed616b Mon Sep 17 00:00:00 2001
From 6a256eb9269e0cfc4d1270ef61c228dd9f9989da Mon Sep 17 00:00:00 2001
From: Daneel <daneel@sukany.cz>
Date: Thu, 26 Feb 2026 18:24:58 +0100
Date: Thu, 26 Feb 2026 18:41:28 +0100
Subject: [PATCH] ns: implement AXBoundsForRange and VoiceOver interaction
fixes
---
nsterm.h | 71 ++
nsterm.m | 2126 ++++++++++++++++++++++++++++++++++++++++++++++++++----
2 files changed, 2051 insertions(+), 146 deletions(-)
nsterm.m | 2201 ++++++++++++++++++++++++++++++++++++++++++++++++++----
2 files changed, 2133 insertions(+), 139 deletions(-)
diff --git a/src/nsterm.h b/src/nsterm.h
index 7c1ee4c..22828f2 100644
@@ -106,7 +106,7 @@ index 7c1ee4c..22828f2 100644
diff --git a/src/nsterm.m b/src/nsterm.m
index 932d209..add827f 100644
index 932d209..da40369 100644
--- a/src/nsterm.m
+++ b/src/nsterm.m
@@ -1104,6 +1104,11 @@ ns_update_end (struct frame *f)
@@ -159,7 +159,7 @@ index 932d209..add827f 100644
ns_focus (f, NULL, 0);
NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
@@ -6849,220 +6885,1696 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action)
@@ -6849,214 +6885,1779 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action)
/* ==========================================================================
@@ -538,11 +538,6 @@ index 932d209..add827f 100644
}
- [NSApp stop: self];
-}
-
-- (void) noteUserCancelledSelection
-{
- font_panel_active = NO;
+ if (!found)
+ {
+ for (ptrdiff_t scan = begv; scan < zv; scan++)
@@ -580,27 +575,53 @@ index 932d209..add827f 100644
+ }
+ }
+ }
- if (font_panel_result)
- [font_panel_result release];
- font_panel_result = nil;
+
+ if (!found)
+ return NO;
- [NSApp stop: self];
+
+ *out_start = best_start;
+ *out_end = best_end;
+ return YES;
}
-- (void) noteUserCancelledSelection
+extern Lisp_Object last_command_event;
+
+static bool
+ns_ax_event_is_ctrl_n_or_p (int *which)
{
- font_panel_active = NO;
+ Lisp_Object ev = last_command_event;
+ if (CONSP (ev))
+ ev = EVENT_HEAD (ev);
- if (font_panel_result)
- [font_panel_result release];
- font_panel_result = nil;
+ if (!FIXNUMP (ev))
+ return false;
- [NSApp stop: self];
+ EMACS_INT c = XFIXNUM (ev);
+ if (c == '\C-n')
+ {
+ if (which)
+ *which = 1;
+ return true;
+ }
+ if (c == '\C-p')
+ {
+ if (which)
+ *which = -1;
+ return true;
+ }
+ return false;
}
-#endif
-- (Lisp_Object) showFontPanel
+static NSString *
+ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
+ struct buffer *b,
+ ptrdiff_t start,
+ ptrdiff_t end,
+ NSString *cachedText)
+static bool
+ns_ax_command_is_basic_line_move (void)
{
- id fm = [NSFontManager sharedFontManager];
- struct font *font = FRAME_OUTPUT_DATA (emacsframe)->font;
@@ -610,14 +631,18 @@ index 932d209..add827f 100644
- NSView *buttons;
- BOOL canceled;
-#endif
-
+ if (!SYMBOLP (real_this_command))
+ return false;
-#ifdef NS_IMPL_GNUSTEP
- nsfont = ((struct nsfont_info *) font)->nsfont;
-#else
- nsfont = (NSFont *) macfont_get_nsctfont (font);
-#endif
+ if (!elem || !b || !cachedText || end <= start)
+ return nil;
+ Lisp_Object next = intern ("next-line");
+ Lisp_Object prev = intern ("previous-line");
+ return EQ (real_this_command, next) || EQ (real_this_command, prev);
+}
-#ifdef NS_IMPL_COCOA
- buttons
@@ -627,13 +652,25 @@ index 932d209..add827f 100644
- [[fm fontPanel: YES] setAccessoryView: buttons];
- [buttons release];
-#endif
+static NSString *
+ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
+ struct buffer *b,
+ ptrdiff_t start,
+ ptrdiff_t end,
+ NSString *cachedText)
+{
+ if (!elem || !b || !cachedText || end <= start)
+ return nil;
- [fm setSelectedFont: nsfont isMultiple: NO];
- [fm orderFrontFontPanel: NSApp];
+ NSString *text = nil;
+ struct buffer *oldb = current_buffer;
+ if (b != current_buffer)
+ set_buffer_internal_1 (b);
- [fm setSelectedFont: nsfont isMultiple: NO];
- [fm orderFrontFontPanel: NSApp];
- font_panel_active = YES;
- timeout = make_timespec (0, 100000000);
+ /* Prefer canonical completion candidate string from text property. */
+ ptrdiff_t probes[2] = { start, end - 1 };
+ for (int i = 0; i < 2 && !text; i++)
@@ -646,16 +683,6 @@ index 932d209..add827f 100644
+ text = [NSString stringWithLispString:cstr];
+ }
- font_panel_active = YES;
- timeout = make_timespec (0, 100000000);
+ if (!text)
+ {
+ NSUInteger ax_s = [elem accessibilityIndexForCharpos:start];
+ NSUInteger ax_e = [elem accessibilityIndexForCharpos:end];
+ if (ax_e > ax_s && ax_e <= [cachedText length])
+ text = [cachedText substringWithRange:NSMakeRange (ax_s, ax_e - ax_s)];
+ }
- block_input ();
- while (font_panel_active
-#ifdef NS_IMPL_COCOA
@@ -666,11 +693,23 @@ index 932d209..add827f 100644
- )
- ns_select_1 (0, NULL, NULL, NULL, &timeout, NULL, YES);
- unblock_input ();
+ if (b != oldb)
+ set_buffer_internal_1 (oldb);
+ if (!text)
+ {
+ NSUInteger ax_s = [elem accessibilityIndexForCharpos:start];
+ NSUInteger ax_e = [elem accessibilityIndexForCharpos:end];
+ if (ax_e > ax_s && ax_e <= [cachedText length])
+ text = [cachedText substringWithRange:NSMakeRange (ax_s, ax_e - ax_s)];
+ }
- if (font_panel_result)
- [font_panel_result autorelease];
+ if (b != oldb)
+ set_buffer_internal_1 (oldb);
-#ifdef NS_IMPL_COCOA
- if (!canceled)
- font_panel_result = nil;
-#endif
+ if (text)
+ {
+ text = [text stringByTrimmingCharactersInSet:
@@ -679,30 +718,26 @@ index 932d209..add827f 100644
+ text = nil;
+ }
-#ifdef NS_IMPL_COCOA
- if (!canceled)
- result = font_panel_result;
- font_panel_result = nil;
-#endif
+ return text;
+}
- result = font_panel_result;
- font_panel_result = nil;
- [[fm fontPanel: YES] setIsVisible: NO];
- font_panel_active = NO;
+@implementation EmacsAccessibilityElement
- if (result)
- return ns_font_desc_to_font_spec ([result fontDescriptor],
- result);
+@implementation EmacsAccessibilityElement
- return Qnil;
+- (NSRect)screenRectFromEmacsX:(int)x y:(int)y width:(int)ew height:(int)eh
+{
+ EmacsView *view = self.emacsView;
+ if (!view || ![view window])
+ return NSZeroRect;
- return Qnil;
+
+ NSRect r = NSMakeRect (x, y, ew, eh);
+ NSRect winRect = [view convertRect:r toView:nil];
+ return [[view window] convertRectToScreen:winRect];
@@ -727,25 +762,20 @@ index 932d209..add827f 100644
+ return NSAccessibilityUnignoredAncestor (self.emacsView);
}
-- (void)resetCursorRects
+
+- (id)accessibilityWindow
{
-{
- NSRect visible = [self visibleRect];
- NSCursor *currentCursor = FRAME_POINTER_TYPE (emacsframe);
- NSTRACE ("[EmacsView resetCursorRects]");
+ return [self.emacsView window];
+}
-
- if (currentCursor == nil)
- currentCursor = [NSCursor arrowCursor];
+- (id)accessibilityTopLevelUIElement
+{
+ return [self.emacsView window];
+}
- if (!NSIsEmptyRect (visible))
- [self addCursorRect: visible cursor: currentCursor];
+@end
+- (id)accessibilityWindow
+{
+ return [self.emacsView window];
+}
-#if defined (NS_IMPL_GNUSTEP) || MAC_OS_X_VERSION_MIN_REQUIRED < 101300
-#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
@@ -753,8 +783,17 @@ index 932d209..add827f 100644
-#endif
- [currentCursor setOnMouseEntered: YES];
-#endif
-}
+- (id)accessibilityTopLevelUIElement
+{
+ return [self.emacsView window];
}
+@end
-/*****************************************************************************/
-/* Keyboard handling. */
-#define NS_KEYLOG 0
+@implementation EmacsAccessibilityBuffer
+@synthesize cachedText;
+@synthesize cachedTextModiff;
@@ -766,7 +805,7 @@ index 932d209..add827f 100644
+@synthesize cachedCompletionOverlayStart;
+@synthesize cachedCompletionOverlayEnd;
+@synthesize cachedCompletionPoint;
+
+- (void)dealloc
+{
+ [cachedText release];
@@ -775,18 +814,11 @@ index 932d209..add827f 100644
+ xfree (visibleRuns);
+ [super dealloc];
+}
-/*****************************************************************************/
-/* Keyboard handling. */
-#define NS_KEYLOG 0
+
+/* ---- Text cache ---- */
-- (void)keyDown: (NSEvent *)theEvent
+
+- (void)invalidateTextCache
{
- Mouse_HLInfo *hlinfo = MOUSE_HL_INFO (emacsframe);
- int code;
- unsigned fnKeysym = 0;
+{
+ [cachedText release];
+ cachedText = nil;
+ if (visibleRuns)
@@ -1409,6 +1441,9 @@ index 932d209..add827f 100644
+ else if (point < oldPoint)
+ direction = ns_ax_text_selection_direction_previous;
+
+ int ctrlNP = 0;
+ bool isCtrlNP = ns_ax_event_is_ctrl_n_or_p (&ctrlNP);
+
+ /* Compute granularity from movement distance.
+ Prefer robust line-range comparison for vertical movement,
+ otherwise single char (1) or unknown (0). */
@@ -1439,6 +1474,16 @@ index 932d209..add827f 100644
+ }
+ }
+
+ /* Force line semantics for explicit C-n/C-p keystrokes.
+ This isolates the key-path difference from arrow-down/up. */
+ if (isCtrlNP)
+ {
+ direction = (ctrlNP > 0
+ ? ns_ax_text_selection_direction_next
+ : ns_ax_text_selection_direction_previous);
+ granularity = ns_ax_text_selection_granularity_line;
+ }
+
+ NSDictionary *moveInfo = @{
+ @"AXTextStateChangeType": @(ns_ax_text_state_change_selection_move),
+ @"AXTextSelectionDirection": @(direction),
@@ -1450,6 +1495,43 @@ index 932d209..add827f 100644
+ NSAccessibilitySelectedTextChangedNotification,
+ moveInfo);
+
+ /* C-n/C-p (`next-line' / `previous-line') can diverge from
+ arrow-down/up command paths in some major modes (completion list,
+ Dired, etc.). Emit an explicit line announcement for this basic
+ line-motion path so VoiceOver tracks the Emacs point reliably. */
+ if ([self isAccessibilityFocused]
+ && cachedText
+ && (isCtrlNP || ns_ax_command_is_basic_line_move ())
+ && (direction == ns_ax_text_selection_direction_next
+ || direction == ns_ax_text_selection_direction_previous))
+ {
+ NSUInteger point_idx = [self accessibilityIndexForCharpos:point];
+ if (point_idx <= [cachedText length])
+ {
+ NSInteger lineNum = [self accessibilityLineForIndex:point_idx];
+ NSRange lineRange = [self accessibilityRangeForLine:lineNum];
+ if (lineRange.location != NSNotFound
+ && lineRange.length > 0
+ && lineRange.location + lineRange.length <= [cachedText length])
+ {
+ NSString *lineText = [cachedText substringWithRange:lineRange];
+ lineText = [lineText stringByTrimmingCharactersInSet:
+ [NSCharacterSet whitespaceAndNewlineCharacterSet]];
+ if ([lineText length] > 0)
+ {
+ NSDictionary *annInfo = @{
+ NSAccessibilityAnnouncementKey: lineText,
+ NSAccessibilityPriorityKey: @(NSAccessibilityPriorityHigh)
+ };
+ NSAccessibilityPostNotificationWithUserInfo (
+ NSApp,
+ NSAccessibilityAnnouncementRequestedNotification,
+ annInfo);
+ }
+ }
+ }
+ }
+
+ /* --- Completions announcement ---
+ When point changes in a non-focused buffer (e.g. *Completions*
+ while the minibuffer has keyboard focus), VoiceOver won't read
@@ -1993,16 +2075,10 @@ index 932d209..add827f 100644
+/*****************************************************************************/
+/* Keyboard handling. */
+#define NS_KEYLOG 0
+
+- (void)keyDown: (NSEvent *)theEvent
+{
+ Mouse_HLInfo *hlinfo = MOUSE_HL_INFO (emacsframe);
+ int code;
+ unsigned fnKeysym = 0;
static NSMutableArray *nsEvArray;
unsigned int flags = [theEvent modifierFlags];
@@ -8237,6 +9749,28 @@ ns_in_echo_area (void)
- (void)keyDown: (NSEvent *)theEvent
{
@@ -8237,6 +9838,28 @@ ns_in_echo_area (void)
XSETFRAME (event.frame_or_window, emacsframe);
kbd_buffer_store_event (&event);
ns_send_appdefined (-1); // Kick main loop
@@ -2031,7 +2107,7 @@ index 932d209..add827f 100644
}
@@ -9474,6 +11008,298 @@ ns_in_echo_area (void)
@@ -9474,6 +11097,298 @@ ns_in_echo_area (void)
return fs_state;
}
@@ -2330,7 +2406,7 @@ index 932d209..add827f 100644
@end /* EmacsView */
@@ -9941,6 +11767,14 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c)
@@ -9941,6 +11856,14 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c)
return [super accessibilityAttributeValue:attribute];
}