v4 patch: UAZoomChangeFocus (Apple documented) + NSAccessibility, with doc refs

Based on verified research:
- UAZoomChangeFocus IS officially documented by Apple (UniversalAccess.h)
- iTerm2 uses UAZoomChangeFocus for Zoom tracking (PTYTextView.m)
- NSAccessibility alone insufficient for Zoom custom view tracking
- Added Apple documentation URLs in code comments
This commit is contained in:
2026-02-25 18:17:53 +01:00
parent 6933d8f9c0
commit d7aae4b7d1

View File

@@ -1,61 +1,55 @@
From 8d439114e6faf50c053eac290ae0e60176dfb3bf Mon Sep 17 00:00:00 2001 From ea017e0a4427ad3e8d04025ae2ed511a542eeeeb Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz> From: Martin Sukany <martin@sukany.cz>
Date: Wed, 25 Feb 2026 18:10:58 +0100 Date: Wed, 25 Feb 2026 18:17:42 +0100
Subject: [PATCH] ns: implement macOS Zoom cursor tracking + NSAccessibility Subject: [PATCH] ns: implement macOS Zoom cursor tracking via
support UAZoomChangeFocus + NSAccessibility
Add cursor tracking for macOS Zoom 'Follow keyboard focus' and Add cursor tracking support for macOS Zoom 'Follow keyboard focus' and
full NSAccessibility support for VoiceOver and other AT tools. other assistive technology tools (VoiceOver, etc.).
Two complementary mechanisms are used: Two complementary mechanisms are used:
1. UAZoomChangeFocus() from ApplicationServices/UniversalAccess.h: 1. UAZoomChangeFocus() from ApplicationServices/UniversalAccess.h:
Directly tells macOS Zoom where to position its viewport. This Directly tells macOS Zoom where to move its viewport. This is the
is Apple's documented API for applications to control Zoom focus. Apple-documented API for controlling Zoom focus, used by iTerm2
Same approach used by iTerm2 (PTYTextView.m:refreshAccessibility). (PTYTextView.m, refreshAccessibility method).
Ref: https://developer.apple.com/documentation/applicationservices/universalaccess_h
Ref: developer.apple.com/documentation/applicationservices/universalaccess_h Ref: https://developer.apple.com/documentation/applicationservices/1458830-uazoomchangefocus
Ref: developer.apple.com/documentation/applicationservices/1458830-uazoomchangefocus
2. NSAccessibility protocol on EmacsView: reports as TextArea role, 2. NSAccessibility protocol on EmacsView: reports as TextArea role,
exposes accessibilityFrame / accessibilityBoundsForRange: returning exposes accessibilityFrame and accessibilityBoundsForRange: returning
cursor screen coordinates, and posts SelectedTextChanged and cursor screen coordinates, posts SelectedTextChanged and
FocusedUIElementChanged notifications. Serves VoiceOver and AT FocusedUIElementChanged notifications. Serves VoiceOver and other AT
tools that query the accessibility tree directly. tools that query the accessibility tree.
Ref: https://developer.apple.com/documentation/appkit/nsaccessibilityprotocol
Ref: developer.apple.com/documentation/appkit/nsaccessibilityprotocol Both mechanisms are needed: UAZoomChangeFocus drives Zoom viewport
positioning for custom-drawn views; NSAccessibility notifications serve
Both mechanisms are needed: UAZoomChangeFocus serves Zoom's Follow VoiceOver, screen readers, and Accessibility Inspector.
keyboard focus (which does not reliably track custom views through
NSAccessibility alone); NSAccessibility serves VoiceOver and other
screen readers.
--- ---
src/nsterm.h | 6 ++ src/nsterm.h | 3 +
src/nsterm.m | 286 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/nsterm.m | 221 +++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 292 insertions(+) 2 files changed, 224 insertions(+)
diff --git a/src/nsterm.h b/src/nsterm.h diff --git a/src/nsterm.h b/src/nsterm.h
index 7c1ee4c..664bce3 100644 index 7c1ee4c..6c1ff34 100644
--- a/src/nsterm.h --- a/src/nsterm.h
+++ b/src/nsterm.h +++ b/src/nsterm.h
@@ -485,6 +485,12 @@ enum ns_return_frame_mode @@ -485,6 +485,9 @@ enum ns_return_frame_mode
struct frame *emacsframe; struct frame *emacsframe;
int scrollbarsNeedingUpdate; int scrollbarsNeedingUpdate;
NSRect ns_userRect; NSRect ns_userRect;
+#ifdef NS_IMPL_COCOA +#ifdef NS_IMPL_COCOA
+ NSRect lastAccessibilityCursorRect; + NSRect lastAccessibilityCursorRect;
+#endif
+#ifdef NS_IMPL_COCOA
+ NSRect lastAccessibilityCursorRect;
+#endif +#endif
} }
/* AppKit-side interface. */ /* AppKit-side interface. */
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index 932d209..96b9d65 100644 index 932d209..933f8cb 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -3232,6 +3232,131 @@ Note that CURSOR_WIDTH is meaningful only for (h)bar cursors. @@ -3232,6 +3232,76 @@ Note that CURSOR_WIDTH is meaningful only for (h)bar cursors.
/* Prevent the cursor from being drawn outside the text area. */ /* Prevent the cursor from being drawn outside the text area. */
r = NSIntersectionRect (r, ns_row_rect (w, glyph_row, TEXT_AREA)); r = NSIntersectionRect (r, ns_row_rect (w, glyph_row, TEXT_AREA));
@@ -80,14 +74,14 @@ index 932d209..96b9d65 100644
+ application can tell the macOS Universal Access zoom feature + application can tell the macOS Universal Access zoom feature
+ what part of its user interface needs focus." + what part of its user interface needs focus."
+ +
+ Ref: https://developer.apple.com/documentation/applicationservices/universalaccess_h + Ref: developer.apple.com/documentation/applicationservices/universalaccess_h
+ Ref: https://developer.apple.com/documentation/applicationservices/1458830-uazoomchangefocus + Ref: developer.apple.com/documentation/applicationservices/1458830-uazoomchangefocus
+ +
+ This is the same approach used by iTerm2 (PTYTextView.m, + This is the same approach used by iTerm2 (PTYTextView.m,
+ method refreshAccessibility). + method refreshAccessibility).
+ +
+ Both mechanisms are needed: NSAccessibility serves VoiceOver and + Both mechanisms are needed: NSAccessibility serves VoiceOver and
+ screen readers; UAZoomChangeFocus serves macOS Zoom's "Follow + screen readers; UAZoomChangeFocus serves macOS Zoom "Follow
+ keyboard focus" feature, which does not reliably track custom views + keyboard focus" feature, which does not reliably track custom views
+ through NSAccessibility notifications alone. */ + through NSAccessibility notifications alone. */
+ { + {
@@ -108,63 +102,8 @@ index 932d209..96b9d65 100644
+ primary screen (CG accessibility coordinate space). + primary screen (CG accessibility coordinate space).
+ convertRectToScreen: returns Quartz coordinates (origin at + convertRectToScreen: returns Quartz coordinates (origin at
+ bottom-left), so we flip the y axis. This coordinate + bottom-left), so we flip the y axis. This coordinate
+ conversion follows the same pattern used by iTerm2's + conversion follows the same pattern used by iTerm2
+ accessibilityConvertScreenRect: method. */ + (PTYTextView.m, accessibilityConvertScreenRect:). */
+ if (UAZoomEnabled ())
+ {
+ NSRect windowRect = [view convertRect:r 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
+
+
+#ifdef NS_IMPL_COCOA
+ /* Accessibility: update cursor tracking for macOS Zoom and VoiceOver.
+
+ Emacs is a custom-drawn view. AppKit has no knowledge of where the
+ text cursor is, so we must explicitly notify assistive technology.
+
+ Two complementary mechanisms are used, matching the pattern used by
+ iTerm2 (PTYTextView.m:refreshAccessibility):
+
+ 1. NSAccessibility notifications -- inform AT clients (VoiceOver,
+ Zoom, Switch Control) that the selection/cursor changed.
+ See: Apple NSAccessibility Protocol Reference
+ https://developer.apple.com/documentation/appkit/nsaccessibilityprotocol
+
+ 2. UAZoomChangeFocus() -- explicitly tell macOS Zoom where to move
+ its viewport. This is the Apple-documented mechanism for custom
+ views to control the Zoom focus. NSAccessibility notifications
+ alone are NOT sufficient for Zoom cursor tracking in custom views.
+ See: Apple UniversalAccess.h Reference
+ https://developer.apple.com/documentation/applicationservices/universalaccess_h
+ https://developer.apple.com/documentation/applicationservices/1458830-uazoomchangefocus */
+ {
+ EmacsView *view = FRAME_NS_VIEW (f);
+ if (view)
+ {
+ view->lastAccessibilityCursorRect = r;
+
+ /* Post NSAccessibility notifications for VoiceOver and other AT. */
+ NSAccessibilityPostNotification (view,
+ NSAccessibilitySelectedTextChangedNotification);
+
+ /* Tell macOS Zoom where the cursor is. UAZoomChangeFocus expects
+ screen coordinates with origin at top-left of the primary screen
+ (accessibility coordinate space). We convert from Quartz screen
+ coordinates (origin bottom-left) by flipping the y axis, matching
+ the pattern used by iTerm2's accessibilityConvertScreenRect. */
+ if (UAZoomEnabled ()) + if (UAZoomEnabled ())
+ { + {
+ NSRect windowRect = [view convertRect:r toView:nil]; + NSRect windowRect = [view convertRect:r toView:nil];
@@ -187,7 +126,7 @@ index 932d209..96b9d65 100644
ns_focus (f, NULL, 0); ns_focus (f, NULL, 0);
NSGraphicsContext *ctx = [NSGraphicsContext currentContext]; NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
@@ -8237,6 +8362,25 @@ - (void)windowDidBecomeKey /* for direct calls */ @@ -8237,6 +8307,15 @@ - (void)windowDidBecomeKey /* for direct calls */
XSETFRAME (event.frame_or_window, emacsframe); XSETFRAME (event.frame_or_window, emacsframe);
kbd_buffer_store_event (&event); kbd_buffer_store_event (&event);
ns_send_appdefined (-1); // Kick main loop ns_send_appdefined (-1); // Kick main loop
@@ -199,25 +138,14 @@ index 932d209..96b9d65 100644
+ to announce the newly focused element. */ + to announce the newly focused element. */
+ NSAccessibilityPostNotification (self, + NSAccessibilityPostNotification (self,
+ NSAccessibilityFocusedUIElementChangedNotification); + NSAccessibilityFocusedUIElementChangedNotification);
+#endif
+
+#ifdef NS_IMPL_COCOA
+ /* Notify AT that the focused UI element changed to this Emacs view.
+ macOS Zoom uses this to activate keyboard focus tracking when the
+ window gains focus.
+ See: NSAccessibilityFocusedUIElementChangedNotification
+ https://developer.apple.com/documentation/appkit/nsaccessibilityfocuseduielementchangednotification */
+ NSAccessibilityPostNotification (self,
+ NSAccessibilityFocusedUIElementChangedNotification);
+#endif +#endif
} }
@@ -9474,6 +9618,148 @@ - (int) fullscreenState @@ -9476,6 +9555,148 @@ - (int) fullscreenState
return fs_state;
} @end /* EmacsView */
+
+#ifdef NS_IMPL_COCOA +#ifdef NS_IMPL_COCOA
+/* ---------------------------------------------------------------- +/* ----------------------------------------------------------------
+ Accessibility support for macOS Zoom, VoiceOver, and other AT tools. + Accessibility support for macOS Zoom, VoiceOver, and other AT tools.
@@ -359,9 +287,10 @@ index 932d209..96b9d65 100644
+} +}
+#endif /* NS_IMPL_COCOA */ +#endif /* NS_IMPL_COCOA */
+ +
@end /* EmacsView */ +
/* ==========================================================================
-- --
2.43.0 2.43.0