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 08f1a3a..3cbd5f0 100644 --- a/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch +++ b/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch @@ -73,7 +73,7 @@ index 7c1ee4c..4abeafe 100644 diff --git a/src/nsterm.m b/src/nsterm.m -index 932d209..8cf4f33 100644 +index 932d209..c8a615f 100644 --- a/src/nsterm.m +++ b/src/nsterm.m @@ -1104,6 +1104,11 @@ ns_update_end (struct frame *f) @@ -126,7 +126,7 @@ index 932d209..8cf4f33 100644 ns_focus (f, NULL, 0); NSGraphicsContext *ctx = [NSGraphicsContext currentContext]; -@@ -6847,6 +6883,770 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action) +@@ -6847,6 +6883,889 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action) } #endif @@ -481,6 +481,86 @@ index 932d209..8cf4f33 100644 + return NSMakeRange (start_idx, end_idx - start_idx); +} + ++- (void)setAccessibilitySelectedTextRange:(NSRange)range ++{ ++ struct window *w = self.emacsWindow; ++ if (!w || !WINDOW_LEAF_P (w)) ++ return; ++ ++ struct buffer *b = XBUFFER (w->contents); ++ if (!b) ++ return; ++ ++ [self ensureTextCache]; ++ ++ /* Convert accessibility index to buffer charpos. */ ++ ptrdiff_t charpos = cachedTextStart + (ptrdiff_t) range.location; ++ ++ /* Clamp to buffer bounds. */ ++ if (charpos < BUF_BEGV (b)) ++ charpos = BUF_BEGV (b); ++ if (charpos > BUF_ZV (b)) ++ charpos = BUF_ZV (b); ++ ++ block_input (); ++ ++ /* Move point directly in the buffer. Use set_point_both which ++ operates on the current buffer — temporarily switch if needed. */ ++ struct buffer *oldb = current_buffer; ++ if (b != current_buffer) ++ set_buffer_internal_1 (b); ++ ++ SET_PT_BOTH (charpos, CHAR_TO_BYTE (charpos)); ++ ++ /* If range has nonzero length, activate the mark. */ ++ if (range.length > 0) ++ { ++ ptrdiff_t mark_charpos = cachedTextStart ++ + (ptrdiff_t) (range.location + range.length); ++ if (mark_charpos > BUF_ZV (b)) ++ mark_charpos = BUF_ZV (b); ++ Fset_mark (make_fixnum (mark_charpos)); ++ } ++ ++ if (b != oldb) ++ set_buffer_internal_1 (oldb); ++ ++ unblock_input (); ++ ++ /* Update cached state so the next notification cycle doesn't ++ re-announce this movement. */ ++ self.cachedPoint = charpos; ++} ++ ++- (void)setAccessibilityFocused:(BOOL)flag ++{ ++ if (!flag) ++ return; ++ ++ struct window *w = self.emacsWindow; ++ if (!w || !WINDOW_LEAF_P (w)) ++ return; ++ ++ EmacsView *view = self.emacsView; ++ if (!view || !view->emacsframe) ++ return; ++ ++ block_input (); ++ ++ /* Raise the frame's NS window to ensure keyboard focus. */ ++ NSWindow *nswin = [view window]; ++ if (nswin && ![nswin isKeyWindow]) ++ [nswin makeKeyAndOrderFront:nil]; ++ ++ unblock_input (); ++ ++ /* Post SelectedTextChanged so VoiceOver reads the current line ++ upon entering text interaction mode. */ ++ NSDictionary *info = @{@"AXTextStateChangeType": @2}; ++ NSAccessibilityPostNotificationWithUserInfo ( ++ self, NSAccessibilitySelectedTextChangedNotification, info); ++} ++ +- (NSInteger)accessibilityInsertionPointLineNumber +{ + struct window *w = self.emacsWindow; @@ -826,6 +906,45 @@ index 932d209..8cf4f33 100644 + self, + NSAccessibilitySelectedTextChangedNotification, + moveInfo); ++ ++ /* --- Completions announcement --- ++ When point changes in a non-focused buffer (e.g. *Completions* ++ while the minibuffer has keyboard focus), VoiceOver won't read ++ the change because it's tracking the focused element. Post an ++ announcement so the user hears the selected completion. */ ++ if (![self isAccessibilityFocused] && cachedText) ++ { ++ /* Extract the current line at point for the announcement. */ ++ NSUInteger point_idx = (NSUInteger) (point - cachedTextStart); ++ 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]; ++ /* Trim trailing newline for cleaner speech. */ ++ lineText = [lineText stringByTrimmingCharactersInSet: ++ [NSCharacterSet newlineCharacterSet]]; ++ if ([lineText length] > 0) ++ { ++ NSDictionary *annInfo = @{ ++ NSAccessibilityAnnouncementKey: lineText, ++ NSAccessibilityPriorityKey: ++ @(NSAccessibilityPriorityHigh) ++ }; ++ NSAccessibilityPostNotificationWithUserInfo ( ++ NSApp, ++ NSAccessibilityAnnouncementRequestedNotification, ++ annInfo); ++ } ++ } ++ } ++ } + } +} + @@ -897,7 +1016,7 @@ index 932d209..8cf4f33 100644 /* ========================================================================== EmacsView implementation -@@ -6889,6 +7689,7 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action) +@@ -6889,6 +7808,7 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action) [layer release]; #endif @@ -905,7 +1024,7 @@ index 932d209..8cf4f33 100644 [[self menu] release]; [super dealloc]; } -@@ -8237,6 +9038,27 @@ ns_in_echo_area (void) +@@ -8237,6 +9157,27 @@ ns_in_echo_area (void) XSETFRAME (event.frame_or_window, emacsframe); kbd_buffer_store_event (&event); ns_send_appdefined (-1); // Kick main loop @@ -933,7 +1052,7 @@ index 932d209..8cf4f33 100644 } -@@ -9474,6 +10296,297 @@ ns_in_echo_area (void) +@@ -9474,6 +10415,297 @@ ns_in_echo_area (void) return fs_state; } @@ -1231,7 +1350,7 @@ index 932d209..8cf4f33 100644 @end /* EmacsView */ -@@ -9941,6 +11054,14 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c) +@@ -9941,6 +11173,14 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c) return [super accessibilityAttributeValue:attribute]; }