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 39bb8bf..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 442f21965862e63ddd0234777918b999b97c8f9c Mon Sep 17 00:00:00 2001 +From 6a256eb9269e0cfc4d1270ef61c228dd9f9989da Mon Sep 17 00:00:00 2001 From: Daneel -Date: Thu, 26 Feb 2026 18:36:53 +0100 +Date: Thu, 26 Feb 2026 18:41:28 +0100 Subject: [PATCH] ns: implement AXBoundsForRange and VoiceOver interaction fixes --- nsterm.h | 71 ++ - nsterm.m | 2168 ++++++++++++++++++++++++++++++++++++++++++++++++++---- - 2 files changed, 2096 insertions(+), 143 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..b7f0614 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..b7f0614 100644 ns_focus (f, NULL, 0); NSGraphicsContext *ctx = [NSGraphicsContext currentContext]; -@@ -6849,219 +6885,1743 @@ 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) /* ========================================================================== @@ -585,31 +585,43 @@ index 932d209..b7f0614 100644 } -- (void) noteUserCancelledSelection ++extern Lisp_Object last_command_event; ++ +static bool -+ns_ax_command_is_basic_line_move (void) ++ns_ax_event_is_ctrl_n_or_p (int *which) { - font_panel_active = NO; -+ if (!SYMBOLP (real_this_command)) -+ return false; ++ 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]; -+ Lisp_Object next = intern ("next-line"); -+ Lisp_Object prev = intern ("previous-line"); -+ return EQ (real_this_command, next) || EQ (real_this_command, prev); ++ 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; @@ -619,14 +631,18 @@ index 932d209..b7f0614 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 @@ -636,13 +652,25 @@ index 932d209..b7f0614 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++) @@ -655,16 +683,6 @@ index 932d209..b7f0614 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 @@ -675,11 +693,23 @@ index 932d209..b7f0614 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: @@ -688,30 +718,26 @@ index 932d209..b7f0614 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]; @@ -736,25 +762,20 @@ index 932d209..b7f0614 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 @@ -762,8 +783,17 @@ index 932d209..b7f0614 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; @@ -775,7 +805,7 @@ index 932d209..b7f0614 100644 +@synthesize cachedCompletionOverlayStart; +@synthesize cachedCompletionOverlayEnd; +@synthesize cachedCompletionPoint; - ++ +- (void)dealloc +{ + [cachedText release]; @@ -784,17 +814,11 @@ index 932d209..b7f0614 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; ++{ + [cachedText release]; + cachedText = nil; + if (visibleRuns) @@ -1417,6 +1441,9 @@ index 932d209..b7f0614 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). */ @@ -1447,6 +1474,16 @@ index 932d209..b7f0614 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), @@ -1464,7 +1501,7 @@ index 932d209..b7f0614 100644 + line-motion path so VoiceOver tracks the Emacs point reliably. */ + if ([self isAccessibilityFocused] + && cachedText -+ && ns_ax_command_is_basic_line_move () ++ && (isCtrlNP || ns_ax_command_is_basic_line_move ()) + && (direction == ns_ax_text_selection_direction_next + || direction == ns_ax_text_selection_direction_previous)) + { @@ -2038,15 +2075,10 @@ index 932d209..b7f0614 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 +9797,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 @@ -2075,7 +2107,7 @@ index 932d209..b7f0614 100644 } -@@ -9474,6 +11056,298 @@ ns_in_echo_area (void) +@@ -9474,6 +11097,298 @@ ns_in_echo_area (void) return fs_state; } @@ -2374,7 +2406,7 @@ index 932d209..b7f0614 100644 @end /* EmacsView */ -@@ -9941,6 +11815,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]; }