diff --git a/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch b/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch index a053047..c50c5ee 100644 --- a/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch +++ b/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch @@ -1,13 +1,13 @@ -From f88649b2e09520d4c6e7ebc041e9e1a6e7ed616b Mon Sep 17 00:00:00 2001 +From 6a256eb9269e0cfc4d1270ef61c228dd9f9989da Mon Sep 17 00:00:00 2001 From: Daneel -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) -- font_panel_result = nil; --#endif +- result = font_panel_result; +- font_panel_result = nil; + 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]; }