patches: restructure per reviewer feedback
Major changes: 1. Zoom separated into standalone patch 0000 - UAZoomChangeFocus in ns_draw_window_cursor - Fallback in ns_update_end for window-switch tracking - No overlayZoomActive (source of split/switch/move bug) 2. VoiceOver patches 0001-0008 are now Zoom-free - All UAZoom*, overlayZoom*, kUAZoomFocus references removed - lastAccessibilityCursorRect kept for VoiceOver bounds queries - Commit messages cleaned of Zoom references 3. README.txt and TESTING.txt rewritten for new structure Addresses reviewer (Stéphane Marks) feedback: - Keep Zoom patch separate from VoiceOver work - Design discussion needed for non-Zoom patches - Performance: ns-accessibility-enabled=nil for zero overhead
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
From aed0e5447ad6bfb5dc15f7d47b1793e735afd995 Mon Sep 17 00:00:00 2001
|
||||
From 992bd78124b769fdcb4c1e3231afd13349e066c9 Mon Sep 17 00:00:00 2001
|
||||
From: Martin Sukany <martin@sukany.cz>
|
||||
Date: Sat, 28 Feb 2026 16:01:29 +0100
|
||||
Subject: [PATCH 8/8] ns: announce child frame completion candidates for
|
||||
@@ -31,9 +31,7 @@ the child frame handler and cleared on the parent's next accessibility
|
||||
cycle when no child frame is visible (via FOR_EACH_FRAME).
|
||||
|
||||
Announce via AnnouncementRequested to NSApp with High priority.
|
||||
Use direct UAZoomChangeFocus because the child frame renders
|
||||
independently --- its ns_update_end runs after the parent's
|
||||
draw_window_cursor, so the last Zoom call wins.
|
||||
|
||||
* src/nsterm.h (EmacsView): Add announceChildFrameCompletion,
|
||||
childFrameCompletionActive flag.
|
||||
@@ -42,23 +40,66 @@ childFrameCompletionActive flag.
|
||||
(EmacsView postAccessibilityUpdates): Dispatch to child frame handler,
|
||||
refocus parent buffer element when child frame closes.
|
||||
---
|
||||
src/nsterm.h | 2 +
|
||||
src/nsterm.m | 255 ++++++++++++++++++++++++++++++++++++++++++++++++++-
|
||||
2 files changed, 256 insertions(+), 1 deletion(-)
|
||||
doc/emacs/macos.texi | 6 -
|
||||
etc/NEWS | 4 +-
|
||||
src/nsterm.h | 4 +-
|
||||
src/nsterm.m | 310 ++++++++++++++++++++++++++++++++-----------
|
||||
4 files changed, 238 insertions(+), 86 deletions(-)
|
||||
|
||||
diff --git a/doc/emacs/macos.texi b/doc/emacs/macos.texi
|
||||
index 4825cf9..97777e2 100644
|
||||
--- a/doc/emacs/macos.texi
|
||||
+++ b/doc/emacs/macos.texi
|
||||
@@ -278,7 +278,6 @@ restart Emacs to access newly-available services.
|
||||
@cindex VoiceOver
|
||||
@cindex accessibility (macOS)
|
||||
@cindex screen reader (macOS)
|
||||
-@cindex Zoom, cursor tracking (macOS)
|
||||
|
||||
When built with the Cocoa interface on macOS, Emacs exposes buffer
|
||||
content, cursor position, mode lines, and interactive elements to the
|
||||
@@ -309,11 +308,6 @@ Shift-modified movement announces selected or deselected text.
|
||||
The @file{*Completions*} buffer announces each completion candidate
|
||||
as you navigate, even while keyboard focus remains in the minibuffer.
|
||||
|
||||
- macOS Zoom (System Settings, Accessibility, Zoom) tracks the Emacs
|
||||
-cursor automatically when set to follow keyboard focus. The cursor
|
||||
-position is communicated via @code{UAZoomChangeFocus} and the
|
||||
-@code{AXBoundsForRange} accessibility attribute.
|
||||
-
|
||||
@vindex ns-accessibility-enabled
|
||||
To disable the accessibility interface entirely (for instance, to
|
||||
eliminate overhead on systems where assistive technology is not in
|
||||
diff --git a/etc/NEWS b/etc/NEWS
|
||||
index e76ee93..c3e0b40 100644
|
||||
--- a/etc/NEWS
|
||||
+++ b/etc/NEWS
|
||||
@@ -4393,8 +4393,8 @@ send user data to Apple's speech recognition servers.
|
||||
** VoiceOver accessibility support on macOS.
|
||||
Emacs now exposes buffer content, cursor position, and interactive
|
||||
elements to the macOS accessibility subsystem (VoiceOver). This
|
||||
-includes AXBoundsForRange for macOS Zoom cursor tracking, line and
|
||||
-word navigation announcements, Tab-navigable interactive spans
|
||||
+includes line and word navigation announcements, Tab-navigable
|
||||
+interactive spans
|
||||
(buttons, links, completion candidates), and completion announcements
|
||||
for the *Completions* buffer. The implementation uses a virtual
|
||||
accessibility tree with per-window elements, hybrid SelectedTextChanged
|
||||
diff --git a/src/nsterm.h b/src/nsterm.h
|
||||
index a007925..1a8a84d 100644
|
||||
index a007925..80bc8ff 100644
|
||||
--- a/src/nsterm.h
|
||||
+++ b/src/nsterm.h
|
||||
@@ -598,6 +598,7 @@ typedef NS_ENUM (NSInteger, EmacsAXSpanType)
|
||||
@@ -596,8 +596,7 @@ typedef NS_ENUM (NSInteger, EmacsAXSpanType)
|
||||
BOOL accessibilityUpdating;
|
||||
@public /* Accessed by ns_draw_phys_cursor (C function). */
|
||||
NSRect lastAccessibilityCursorRect;
|
||||
BOOL overlayZoomActive;
|
||||
NSRect overlayZoomRect;
|
||||
- BOOL overlayZoomActive;
|
||||
- NSRect overlayZoomRect;
|
||||
+ BOOL childFrameCompletionActive;
|
||||
#endif
|
||||
BOOL font_panel_active;
|
||||
NSFont *font_panel_result;
|
||||
@@ -661,6 +662,7 @@ typedef NS_ENUM (NSInteger, EmacsAXSpanType)
|
||||
@@ -661,6 +660,7 @@ typedef NS_ENUM (NSInteger, EmacsAXSpanType)
|
||||
- (void)rebuildAccessibilityTree;
|
||||
- (void)invalidateAccessibilityTree;
|
||||
- (void)postAccessibilityUpdates;
|
||||
@@ -67,10 +108,60 @@ index a007925..1a8a84d 100644
|
||||
@end
|
||||
|
||||
diff --git a/src/nsterm.m b/src/nsterm.m
|
||||
index 7025e6e..dba0e49 100644
|
||||
index 7025e6e..d5d33b0 100644
|
||||
--- a/src/nsterm.m
|
||||
+++ b/src/nsterm.m
|
||||
@@ -7058,6 +7058,112 @@ visual line index for Zoom (skip whitespace-only lines
|
||||
@@ -3239,44 +3239,14 @@ Note that CURSOR_WIDTH is meaningful only for (h)bar cursors.
|
||||
r = NSIntersectionRect (r, ns_row_rect (w, glyph_row, TEXT_AREA));
|
||||
|
||||
#ifdef NS_IMPL_COCOA
|
||||
- /* Accessibility: store cursor rect for Zoom and bounds queries.
|
||||
- Skipped when ns-accessibility-enabled is nil to avoid overhead.
|
||||
- VoiceOver notifications are handled solely by
|
||||
- postAccessibilityUpdates (called from ns_update_end)
|
||||
- to avoid duplicate notifications and mid-redisplay fragility. */
|
||||
+ /* Accessibility: store cursor rect for VoiceOver bounds queries.
|
||||
+ accessibilityBoundsForRange: / accessibilityFrameForRange:
|
||||
+ use this as a fallback when no valid window/glyph data is
|
||||
+ available. Skipped when ns-accessibility-enabled is nil. */
|
||||
{
|
||||
EmacsView *view = FRAME_NS_VIEW (f);
|
||||
if (view && on_p && active_p && ns_accessibility_enabled)
|
||||
- {
|
||||
- view->lastAccessibilityCursorRect = r;
|
||||
-
|
||||
- /* Tell macOS Zoom where the cursor is. UAZoomChangeFocus()
|
||||
- expects top-left origin (CG coordinate space).
|
||||
- These APIs are available since macOS 10.4 (Universal Access
|
||||
- framework, linked via ApplicationServices umbrella). */
|
||||
-#if defined (MAC_OS_X_VERSION_MIN_REQUIRED) \
|
||||
- && MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
|
||||
- if (UAZoomEnabled ())
|
||||
- {
|
||||
- /* When overlay completion is active (e.g. Vertico),
|
||||
- focus Zoom on the selected candidate row instead
|
||||
- of the text cursor. */
|
||||
- NSRect zoomSrc = view->overlayZoomActive
|
||||
- ? view->overlayZoomRect : r;
|
||||
- NSRect windowRect = [view convertRect:zoomSrc toView:nil];
|
||||
- NSRect screenRect = [[view window] convertRectToScreen:windowRect];
|
||||
- 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);
|
||||
- }
|
||||
-#endif /* MAC_OS_X_VERSION_MIN_REQUIRED >= 101000 */
|
||||
- }
|
||||
+ view->lastAccessibilityCursorRect = r;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -7058,6 +7028,112 @@ visual line index for Zoom (skip whitespace-only lines
|
||||
|
||||
return nil;
|
||||
}
|
||||
@@ -183,7 +274,63 @@ index 7025e6e..dba0e49 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
|
||||
@@ -12336,6 +12442,105 @@ - (id)accessibilityFocusedUIElement
|
||||
@@ -8988,7 +9064,6 @@ Text property changes (e.g. face updates from
|
||||
if (chars_modiff != self.cachedCharsModiff)
|
||||
{
|
||||
self.cachedCharsModiff = chars_modiff;
|
||||
- self.emacsView->overlayZoomActive = NO;
|
||||
[self postTextChangedNotification:point];
|
||||
}
|
||||
}
|
||||
@@ -9036,47 +9111,8 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property
|
||||
NSAccessibilityAnnouncementRequestedNotification,
|
||||
annInfo);
|
||||
|
||||
- /* --- Zoom tracking for overlay candidates ---
|
||||
- Store the candidate row rect so draw_window_cursor
|
||||
- focuses Zoom there instead of on the text cursor.
|
||||
- Cleared when the user types (chars_modiff change).
|
||||
-
|
||||
- Use default line height to compute the Y offset:
|
||||
- row 0 is the input line, overlay candidates start
|
||||
- from row 1. This avoids fragile glyph matrix row
|
||||
- index mapping which can be off when group titles
|
||||
- or wrapped lines shift row numbering. */
|
||||
- if (selected_line >= 0)
|
||||
- {
|
||||
- struct window *w2 = [self validWindow];
|
||||
- if (w2)
|
||||
- {
|
||||
- EmacsView *view = self.emacsView;
|
||||
- struct frame *f2 = XFRAME (w2->frame);
|
||||
- int line_h = FRAME_LINE_HEIGHT (f2);
|
||||
- int y_off = (selected_line + 1) * line_h;
|
||||
-
|
||||
- if (y_off < w2->pixel_height)
|
||||
- {
|
||||
- view->overlayZoomRect = NSMakeRect (
|
||||
- WINDOW_TEXT_TO_FRAME_PIXEL_X (w2, 0),
|
||||
- WINDOW_TO_FRAME_PIXEL_Y (w2, y_off),
|
||||
- FRAME_COLUMN_WIDTH (f2),
|
||||
- line_h);
|
||||
- view->overlayZoomActive = YES;
|
||||
- }
|
||||
- }
|
||||
- }
|
||||
}
|
||||
}
|
||||
- else
|
||||
- {
|
||||
- /* No selected candidate --- overlay completion ended
|
||||
- (minibuffer exit, C-g, etc.) or overlay has no
|
||||
- recognizable selection face. Return Zoom to the
|
||||
- text cursor. */
|
||||
- self.emacsView->overlayZoomActive = NO;
|
||||
- }
|
||||
}
|
||||
|
||||
/* --- Cursor moved or selection changed ---
|
||||
@@ -12336,6 +12372,80 @@ - (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. */
|
||||
@@ -191,9 +338,7 @@ index 7025e6e..dba0e49 100644
|
||||
+/* Announce the selected candidate in a child frame completion popup.
|
||||
+ Handles Corfu, Company-box, and similar frameworks that render
|
||||
+ candidates in a separate child frame rather than as overlay strings
|
||||
+ in the minibuffer. Uses direct UAZoomChangeFocus (not the
|
||||
+ overlayZoomRect flag) because the child frame's ns_update_end runs
|
||||
+ after the parent's draw_window_cursor. */
|
||||
+ in the minibuffer. */
|
||||
+- (void)announceChildFrameCompletion
|
||||
+{
|
||||
+ static char *lastCandidate;
|
||||
@@ -261,35 +406,12 @@ index 7025e6e..dba0e49 100644
|
||||
+ parentView->childFrameCompletionActive = YES;
|
||||
+ }
|
||||
+
|
||||
+ /* 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 ())
|
||||
+ {
|
||||
+ 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);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
- (void)postAccessibilityUpdates
|
||||
{
|
||||
NSTRACE ("[EmacsView postAccessibilityUpdates]");
|
||||
@@ -12346,11 +12551,59 @@ - (void)postAccessibilityUpdates
|
||||
@@ -12346,11 +12456,59 @@ - (void)postAccessibilityUpdates
|
||||
|
||||
/* Re-entrance guard: VoiceOver callbacks during notification posting
|
||||
can trigger redisplay, which calls ns_update_end, which calls us
|
||||
|
||||
Reference in New Issue
Block a user