diff --git a/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch b/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch index 996e126..261c9c8 100644 --- a/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch +++ b/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch @@ -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 -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