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:
@@ -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>
|
||||
Date: Wed, 25 Feb 2026 18:10:58 +0100
|
||||
Subject: [PATCH] ns: implement macOS Zoom cursor tracking + NSAccessibility
|
||||
support
|
||||
Date: Wed, 25 Feb 2026 18:17:42 +0100
|
||||
Subject: [PATCH] ns: implement macOS Zoom cursor tracking via
|
||||
UAZoomChangeFocus + NSAccessibility
|
||||
|
||||
Add cursor tracking for macOS Zoom 'Follow keyboard focus' and
|
||||
full NSAccessibility support for VoiceOver and other AT tools.
|
||||
Add cursor tracking support for macOS Zoom 'Follow keyboard focus' and
|
||||
other assistive technology tools (VoiceOver, etc.).
|
||||
|
||||
Two complementary mechanisms are used:
|
||||
|
||||
1. UAZoomChangeFocus() from ApplicationServices/UniversalAccess.h:
|
||||
Directly tells macOS Zoom where to position its viewport. This
|
||||
is Apple's documented API for applications to control Zoom focus.
|
||||
Same approach used by iTerm2 (PTYTextView.m:refreshAccessibility).
|
||||
|
||||
Ref: developer.apple.com/documentation/applicationservices/universalaccess_h
|
||||
Ref: developer.apple.com/documentation/applicationservices/1458830-uazoomchangefocus
|
||||
Directly tells macOS Zoom where to move its viewport. This is the
|
||||
Apple-documented API for controlling Zoom focus, used by iTerm2
|
||||
(PTYTextView.m, refreshAccessibility method).
|
||||
Ref: https://developer.apple.com/documentation/applicationservices/universalaccess_h
|
||||
Ref: https://developer.apple.com/documentation/applicationservices/1458830-uazoomchangefocus
|
||||
|
||||
2. NSAccessibility protocol on EmacsView: reports as TextArea role,
|
||||
exposes accessibilityFrame / accessibilityBoundsForRange: returning
|
||||
cursor screen coordinates, and posts SelectedTextChanged and
|
||||
FocusedUIElementChanged notifications. Serves VoiceOver and AT
|
||||
tools that query the accessibility tree directly.
|
||||
exposes accessibilityFrame and accessibilityBoundsForRange: returning
|
||||
cursor screen coordinates, posts SelectedTextChanged and
|
||||
FocusedUIElementChanged notifications. Serves VoiceOver and other AT
|
||||
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 serves Zoom's Follow
|
||||
keyboard focus (which does not reliably track custom views through
|
||||
NSAccessibility alone); NSAccessibility serves VoiceOver and other
|
||||
screen readers.
|
||||
Both mechanisms are needed: UAZoomChangeFocus drives Zoom viewport
|
||||
positioning for custom-drawn views; NSAccessibility notifications serve
|
||||
VoiceOver, screen readers, and Accessibility Inspector.
|
||||
---
|
||||
src/nsterm.h | 6 ++
|
||||
src/nsterm.m | 286 +++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
2 files changed, 292 insertions(+)
|
||||
src/nsterm.h | 3 +
|
||||
src/nsterm.m | 221 +++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
2 files changed, 224 insertions(+)
|
||||
|
||||
diff --git a/src/nsterm.h b/src/nsterm.h
|
||||
index 7c1ee4c..664bce3 100644
|
||||
index 7c1ee4c..6c1ff34 100644
|
||||
--- a/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;
|
||||
int scrollbarsNeedingUpdate;
|
||||
NSRect ns_userRect;
|
||||
+#ifdef NS_IMPL_COCOA
|
||||
+ NSRect lastAccessibilityCursorRect;
|
||||
+#endif
|
||||
+#ifdef NS_IMPL_COCOA
|
||||
+ NSRect lastAccessibilityCursorRect;
|
||||
+#endif
|
||||
}
|
||||
|
||||
/* AppKit-side interface. */
|
||||
diff --git a/src/nsterm.m b/src/nsterm.m
|
||||
index 932d209..96b9d65 100644
|
||||
index 932d209..933f8cb 100644
|
||||
--- a/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. */
|
||||
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
|
||||
+ what part of its user interface needs focus."
|
||||
+
|
||||
+ Ref: https://developer.apple.com/documentation/applicationservices/universalaccess_h
|
||||
+ Ref: https://developer.apple.com/documentation/applicationservices/1458830-uazoomchangefocus
|
||||
+ Ref: developer.apple.com/documentation/applicationservices/universalaccess_h
|
||||
+ Ref: developer.apple.com/documentation/applicationservices/1458830-uazoomchangefocus
|
||||
+
|
||||
+ This is the same approach used by iTerm2 (PTYTextView.m,
|
||||
+ method refreshAccessibility).
|
||||
+
|
||||
+ 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
|
||||
+ through NSAccessibility notifications alone. */
|
||||
+ {
|
||||
@@ -108,63 +102,8 @@ index 932d209..96b9d65 100644
|
||||
+ primary screen (CG accessibility coordinate space).
|
||||
+ convertRectToScreen: returns Quartz coordinates (origin at
|
||||
+ bottom-left), so we flip the y axis. This coordinate
|
||||
+ conversion follows the same pattern used by iTerm2's
|
||||
+ accessibilityConvertScreenRect: method. */
|
||||
+ 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. */
|
||||
+ conversion follows the same pattern used by iTerm2
|
||||
+ (PTYTextView.m, accessibilityConvertScreenRect:). */
|
||||
+ if (UAZoomEnabled ())
|
||||
+ {
|
||||
+ NSRect windowRect = [view convertRect:r toView:nil];
|
||||
@@ -187,7 +126,7 @@ index 932d209..96b9d65 100644
|
||||
ns_focus (f, NULL, 0);
|
||||
|
||||
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);
|
||||
kbd_buffer_store_event (&event);
|
||||
ns_send_appdefined (-1); // Kick main loop
|
||||
@@ -199,25 +138,14 @@ index 932d209..96b9d65 100644
|
||||
+ to announce the newly focused element. */
|
||||
+ NSAccessibilityPostNotification (self,
|
||||
+ 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
|
||||
}
|
||||
|
||||
|
||||
@@ -9474,6 +9618,148 @@ - (int) fullscreenState
|
||||
return fs_state;
|
||||
}
|
||||
@@ -9476,6 +9555,148 @@ - (int) fullscreenState
|
||||
|
||||
@end /* EmacsView */
|
||||
|
||||
+
|
||||
+#ifdef NS_IMPL_COCOA
|
||||
+/* ----------------------------------------------------------------
|
||||
+ Accessibility support for macOS Zoom, VoiceOver, and other AT tools.
|
||||
@@ -359,9 +287,10 @@ index 932d209..96b9d65 100644
|
||||
+}
|
||||
+#endif /* NS_IMPL_COCOA */
|
||||
+
|
||||
@end /* EmacsView */
|
||||
+
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
--
|
||||
2.43.0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user