diff --git a/patches/0008-ns-announce-child-frame-completion-candidates-for-Vo.patch b/patches/0008-ns-announce-child-frame-completion-candidates-for-Vo.patch index 295eff0..a02f305 100644 --- a/patches/0008-ns-announce-child-frame-completion-candidates-for-Vo.patch +++ b/patches/0008-ns-announce-child-frame-completion-candidates-for-Vo.patch @@ -385,7 +385,7 @@ index 8d44b5f..29b646d 100644 specpdl_ref count = SPECPDL_INDEX (); record_unwind_current_buffer (); /* Ensure block_input is always matched by unblock_input even if -@@ -9064,11 +9196,13 @@ - (void)postFocusedCursorNotification:(ptrdiff_t)point +@@ -9064,14 +9196,22 @@ - (void)postFocusedCursorNotification:(ptrdiff_t)point = @(ns_ax_text_state_change_selection_move); moveInfo[@"AXTextSelectionDirection"] = @(direction); moveInfo[@"AXTextChangeElement"] = self; @@ -403,8 +403,18 @@ index 8d44b5f..29b646d 100644 + && direction != ns_ax_text_selection_direction_discontiguous) moveInfo[@"AXTextSelectionGranularity"] = @(granularity); ++ /* Post SelectedTextChanged from the parent EmacsView (an NSView) ++ rather than from self (a custom NSObject element). VoiceOver ++ processes text-change notifications more reliably from view-based ++ elements. Include UIElementsKey so VoiceOver knows which child ++ element's selectedTextRange to re-query. */ ++ moveInfo[NSAccessibilityUIElementsKey] = @[self]; ns_ax_post_notification_with_info ( -@@ -9111,12 +9245,17 @@ derive its own speech (it would read the wrong character +- self, ++ self.emacsView, + NSAccessibilitySelectedTextChangedNotification, + moveInfo); +@@ -9111,12 +9254,17 @@ derive its own speech (it would read the wrong character } } @@ -427,7 +437,7 @@ index 8d44b5f..29b646d 100644 if (cachedText && granularity == ns_ax_text_selection_granularity_line) { -@@ -9179,7 +9318,14 @@ - (void)postCompletionAnnouncementForBuffer:(struct buffer *)b +@@ -9179,7 +9327,14 @@ - (void)postCompletionAnnouncementForBuffer:(struct buffer *)b ptrdiff_t currentOverlayStart = 0; ptrdiff_t currentOverlayEnd = 0; @@ -442,7 +452,7 @@ index 8d44b5f..29b646d 100644 record_unwind_current_buffer (); if (b != current_buffer) set_buffer_internal_1 (b); -@@ -9356,12 +9502,29 @@ - (void)postAccessibilityNotificationsForFrame:(struct frame *)f +@@ -9356,12 +9511,29 @@ - (void)postAccessibilityNotificationsForFrame:(struct frame *)f if (!b) return; @@ -472,7 +482,7 @@ index 8d44b5f..29b646d 100644 if (modiff != self.cachedModiff) { self.cachedModiff = modiff; -@@ -9375,6 +9538,7 @@ Text property changes (e.g. face updates from +@@ -9375,6 +9547,7 @@ Text property changes (e.g. face updates from { self.cachedCharsModiff = chars_modiff; [self postTextChangedNotification:point]; @@ -480,7 +490,7 @@ index 8d44b5f..29b646d 100644 } } -@@ -9397,8 +9561,15 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property +@@ -9397,8 +9570,15 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property displayed in the minibuffer. In normal editing buffers, font-lock and other modes change BUF_OVERLAY_MODIFF on every redisplay, triggering O(overlays) work per keystroke. @@ -498,7 +508,7 @@ index 8d44b5f..29b646d 100644 goto skip_overlay_scan; int selected_line = -1; -@@ -9444,7 +9615,18 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property +@@ -9444,7 +9624,18 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property self.cachedPoint = point; self.cachedMarkActive = markActive; @@ -518,7 +528,7 @@ index 8d44b5f..29b646d 100644 NSInteger direction = ns_ax_text_selection_direction_discontiguous; if (point > oldPoint) direction = ns_ax_text_selection_direction_next; -@@ -9492,6 +9674,38 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property +@@ -9492,6 +9683,58 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property granularity = ns_ax_text_selection_granularity_line; } @@ -542,22 +552,42 @@ index 8d44b5f..29b646d 100644 + if (emacsMovedCursor && !isCtrlNP) + direction = ns_ax_text_selection_direction_discontiguous; + -+ /* Post FocusedUIElementChangedNotification on the buffer element -+ itself (not the parent view) so VoiceOver re-queries -+ accessibilitySelectedTextRange and re-anchors its rotor browse -+ cursor to Emacs' current point. Posting on self (already -+ focused) causes VO to silently re-anchor without re-announcing -+ the element name. Required for all granularities (char, word, -+ line). */ ++ /* When Emacs moves the cursor (not VoiceOver-initiated), ++ VoiceOver's browse cursor must re-anchor to the new ++ insertion point. Posting FocusedUIElementChanged on self ++ (a custom NSObject-based element, not an NSView) is ++ insufficient \u2014 VoiceOver only re-anchors its browse cursor ++ when it receives FocusedUIElementChanged from the NSWindow, ++ which triggers accessibilityFocusedUIElement to walk the ++ hierarchy and re-anchor at the returned element. ++ ++ Post on the window so VoiceOver calls ++ accessibilityFocusedUIElement on it, receives our buffer ++ element, and re-anchors its rotor browse cursor. Also ++ post LayoutChanged with UIElementsKey on the parent view ++ so VoiceOver re-examines our element's properties ++ (including accessibilitySelectedTextRange). */ + if (emacsMovedCursor && [self isAccessibilityFocused]) -+ ns_ax_post_notification ( -+ self, -+ NSAccessibilityFocusedUIElementChangedNotification); ++ { ++ NSWindow *win = [self.emacsView window]; ++ if (win) ++ ns_ax_post_notification ( ++ win, ++ NSAccessibilityFocusedUIElementChangedNotification); ++ ++ NSDictionary *layoutInfo = @{ ++ NSAccessibilityUIElementsKey: @[self] ++ }; ++ ns_ax_post_notification_with_info ( ++ self.emacsView, ++ NSAccessibilityLayoutChangedNotification, ++ layoutInfo); ++ } + /* Post notifications for focused and non-focused elements. */ if ([self isAccessibilityFocused]) [self postFocusedCursorNotification:point -@@ -9634,6 +9836,17 @@ - (NSRect)accessibilityFrame +@@ -9634,6 +9865,17 @@ - (NSRect)accessibilityFrame if (vis_start >= vis_end) return @[]; @@ -575,7 +605,7 @@ index 8d44b5f..29b646d 100644 /* Symbols are interned once at startup via DEFSYM in syms_of_nsterm; reference them directly here (GC-safe, no repeated obarray lookup). */ -@@ -9754,6 +9967,7 @@ than O(chars). Fall back to pos+1 as safety net. */ +@@ -9754,6 +9996,7 @@ than O(chars). Fall back to pos+1 as safety net. */ pos = span_end; } @@ -583,7 +613,7 @@ index 8d44b5f..29b646d 100644 return [[spans copy] autorelease]; } -@@ -9935,6 +10149,10 @@ - (void)dealloc +@@ -9935,6 +10178,10 @@ - (void)dealloc #endif [accessibilityElements release]; @@ -594,7 +624,7 @@ index 8d44b5f..29b646d 100644 [[self menu] release]; [super dealloc]; } -@@ -11384,6 +11602,9 @@ - (instancetype) initFrameFromEmacs: (struct frame *)f +@@ -11384,6 +11631,9 @@ - (instancetype) initFrameFromEmacs: (struct frame *)f windowClosing = NO; processingCompose = NO; @@ -604,7 +634,7 @@ index 8d44b5f..29b646d 100644 scrollbarsNeedingUpdate = 0; fs_state = FULLSCREEN_NONE; fs_before_fs = next_maximized = -1; -@@ -12692,6 +12913,154 @@ - (id)accessibilityFocusedUIElement +@@ -12692,6 +12942,154 @@ - (id)accessibilityFocusedUIElement The existing elements carry cached state (modiff, point) from the previous redisplay cycle. Rebuilding first would create fresh elements with current values, making change detection impossible. */ @@ -759,7 +789,7 @@ index 8d44b5f..29b646d 100644 - (void)postAccessibilityUpdates { NSTRACE ("[EmacsView postAccessibilityUpdates]"); -@@ -12702,11 +13069,64 @@ - (void)postAccessibilityUpdates +@@ -12702,11 +13098,64 @@ - (void)postAccessibilityUpdates /* Re-entrance guard: VoiceOver callbacks during notification posting can trigger redisplay, which calls ns_update_end, which calls us