From 2dc41828566ddfb57df350bfebe6972609179adc Mon Sep 17 00:00:00 2001 From: Daneel Date: Sat, 28 Feb 2026 16:29:26 +0100 Subject: [PATCH] patches: 0008 - Zoom via parent overlayZoomRect + suppress window announcement --- ...d-frame-completion-candidates-for-Vo.patch | 95 ++++++++++++------- 1 file changed, 62 insertions(+), 33 deletions(-) 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 37ae1db..515c3f0 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 @@ -1,4 +1,4 @@ -From e11d0688f119046827cd1895008d3a93e22f6d0d Mon Sep 17 00:00:00 2001 +From a06955c951aaad2330add250f815c34d60128948 Mon Sep 17 00:00:00 2001 From: Martin Sukany Date: Sat, 28 Feb 2026 16:01:29 +0100 Subject: [PATCH 2/2] ns: announce child frame completion candidates for @@ -31,11 +31,14 @@ Safety measures: - Buffer size limit (10000 chars) skips non-completion child frames such as eldoc documentation or which-key popups. -Announce via AnnouncementRequested to NSApp with High priority. -Use direct UAZoomChangeFocus (not the overlayZoomRect flag used -for minibuffer overlay completion) because the child frame renders -independently --- its ns_update_end runs after the parent frame's -draw_window_cursor, so the last Zoom call wins. +Suppress VoiceOver's automatic "X window" announcement by setting +the child frame's accessibility subrole to FloatingWindow (popovers +and floating windows don't trigger window-appeared announcements). + +Zoom tracking stores the candidate rect in the PARENT frame's +overlayZoomRect (via screen coordinate conversion), so that the +parent's draw_window_cursor focuses Zoom on the candidate instead +of the text cursor. Cleared when no candidate is found. * src/nsterm.h (EmacsView): Add announceChildFrameCompletion. * src/nsterm.m (ns_ax_selected_child_frame_text): New function. @@ -44,8 +47,8 @@ draw_window_cursor, so the last Zoom call wins. handler for FRAME_PARENT_FRAME frames, under re-entrance guard. --- src/nsterm.h | 1 + - src/nsterm.m | 205 ++++++++++++++++++++++++++++++++++++++++++++++++++- - 2 files changed, 205 insertions(+), 1 deletion(-) + src/nsterm.m | 231 ++++++++++++++++++++++++++++++++++++++++++++++++++- + 2 files changed, 231 insertions(+), 1 deletion(-) diff --git a/src/nsterm.h b/src/nsterm.h index 5c15639..21b2823 100644 @@ -60,7 +63,7 @@ index 5c15639..21b2823 100644 @end diff --git a/src/nsterm.m b/src/nsterm.m -index d13c5c7..092bc11 100644 +index d13c5c7..ea2a914 100644 --- a/src/nsterm.m +++ b/src/nsterm.m @@ -7066,6 +7066,110 @@ ns_ax_selected_overlay_text (struct buffer *b, @@ -174,7 +177,7 @@ index d13c5c7..092bc11 100644 /* Build accessibility text for window W, skipping invisible text. Populates *OUT_START with the buffer start charpos. Populates *OUT_RUNS with an array of visible runs and *OUT_NRUNS -@@ -12299,6 +12403,93 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view, +@@ -12299,6 +12403,119 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view, 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. */ @@ -220,8 +223,19 @@ index d13c5c7..092bc11 100644 + NSString *candidate + = ns_ax_selected_child_frame_text (b, w->contents, &selected_line); + ++ struct frame *parent = FRAME_PARENT_FRAME (emacsframe); ++ + if (!candidate) -+ return; ++ { ++ /* No selected candidate --- clear parent Zoom override. */ ++ if (parent) ++ { ++ EmacsView *parentView = FRAME_NS_VIEW (parent); ++ if (parentView) ++ parentView->overlayZoomActive = NO; ++ } ++ return; ++ } + + /* Deduplicate --- avoid re-announcing the same candidate. */ + const char *cstr = [candidate UTF8String]; @@ -230,6 +244,12 @@ index d13c5c7..092bc11 100644 + xfree (lastCandidate); + lastCandidate = xstrdup (cstr); + ++ /* Suppress VoiceOver's automatic "X window" announcement for ++ the child frame. Completion popups are semantically popovers, ++ not windows. Setting the subrole once is idempotent. */ ++ [[self window] ++ setAccessibilitySubrole:NSAccessibilityFloatingWindowSubrole]; ++ + NSDictionary *annInfo = @{ + NSAccessibilityAnnouncementKey: candidate, + NSAccessibilityPriorityKey: @@ -240,35 +260,44 @@ index d13c5c7..092bc11 100644 + NSAccessibilityAnnouncementRequestedNotification, + annInfo); + -+ /* Zoom tracking: focus on the selected row in the child frame. -+ Use direct UAZoomChangeFocus rather than overlayZoomRect because -+ the child frame renders independently of the parent. */ -+ if (selected_line >= 0 && UAZoomEnabled ()) ++ /* Zoom tracking: store the candidate rect in the PARENT frame's ++ overlayZoomRect so that draw_window_cursor focuses Zoom on the ++ candidate instead of the text cursor. Convert through screen ++ coordinates to handle arbitrary child frame positioning. */ ++ if (selected_line >= 0 && parent) + { -+ int line_h = FRAME_LINE_HEIGHT (emacsframe); -+ int y_off = selected_line * line_h; -+ NSRect r = NSMakeRect ( -+ WINDOW_TEXT_TO_FRAME_PIXEL_X (w, 0), -+ WINDOW_TO_FRAME_PIXEL_Y (w, y_off), -+ FRAME_COLUMN_WIDTH (emacsframe), -+ line_h); -+ NSRect winRect = [self convertRect:r toView:nil]; -+ NSRect screenRect -+ = [[self window] convertRectToScreen:winRect]; -+ CGRect cgRect = NSRectToCGRect (screenRect); -+ CGFloat primaryH -+ = [[[NSScreen screens] firstObject] frame].size.height; -+ cgRect.origin.y -+ = primaryH - cgRect.origin.y - cgRect.size.height; -+ UAZoomChangeFocus (&cgRect, &cgRect, -+ kUAZoomFocusTypeInsertionPoint); ++ EmacsView *parentView = FRAME_NS_VIEW (parent); ++ if (parentView) ++ { ++ int line_h = FRAME_LINE_HEIGHT (emacsframe); ++ int y_off = selected_line * line_h; ++ NSRect childRect = NSMakeRect ( ++ WINDOW_TEXT_TO_FRAME_PIXEL_X (w, 0), ++ WINDOW_TO_FRAME_PIXEL_Y (w, y_off), ++ FRAME_COLUMN_WIDTH (emacsframe), ++ line_h); ++ ++ /* Child view → screen → parent view. */ ++ NSRect childWinR ++ = [self convertRect:childRect toView:nil]; ++ NSRect screenR ++ = [[self window] convertRectToScreen:childWinR]; ++ NSRect parentWinR ++ = [[parentView window] ++ convertRectFromScreen:screenR]; ++ NSRect parentViewR ++ = [parentView convertRect:parentWinR fromView:nil]; ++ ++ parentView->overlayZoomRect = parentViewR; ++ parentView->overlayZoomActive = YES; ++ } + } +} + - (void)postAccessibilityUpdates { NSTRACE ("[EmacsView postAccessibilityUpdates]"); -@@ -12309,11 +12500,23 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view, +@@ -12309,11 +12526,23 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view, /* Re-entrance guard: VoiceOver callbacks during notification posting can trigger redisplay, which calls ns_update_end, which calls us