From 56db072f069def5afdd98ba4150e9aa4914bb47d Mon Sep 17 00:00:00 2001 From: Daneel Date: Mon, 23 Feb 2026 10:03:51 +0100 Subject: [PATCH] fix: add NSAccessibilityPostNotification (key missing piece) macOS Zoom is event-driven -- it only queries AXBoundsForRange after receiving NSAccessibilitySelectedTextChangedNotification. Previous version implemented the bounds methods but never posted notifications, so Zoom never triggered a cursor position query. Added: - NSAccessibilityPostNotification(SelectedTextChanged) in ns_draw_window_cursor - NSAccessibilityPostNotification(FocusedUIElementChanged) in windowDidBecomeKey - accessibilityAttributeNames + accessibilityAttributeValue: for NSAccessibilitySelectedTextRangeAttribute -> returns {0,0} --- ...oundsForRange-for-macOS-Zoom-cursor-.patch | 99 ++++++++++++++----- 1 file changed, 77 insertions(+), 22 deletions(-) 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 6751ab4..2b98e1f 100644 --- a/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch +++ b/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch @@ -1,35 +1,37 @@ -From 23fd2004cb00908a37ad44d0c41a1ca223eb7c55 Mon Sep 17 00:00:00 2001 +From ea7573e32a140ffce0758485abd959954e74c271 Mon Sep 17 00:00:00 2001 From: Daneel -Date: Sun, 22 Feb 2026 23:21:32 +0100 -Subject: [PATCH] ns: implement AXBoundsForRange for macOS Zoom cursor tracking +Date: Mon, 23 Feb 2026 10:03:39 +0100 +Subject: [PATCH] ns: implement NSAccessibility cursor tracking for macOS Zoom -Add NSAccessibilityBoundsForRangeParameterizedAttribute support to -EmacsView so that macOS Zoom and other accessibility tools can track -the text cursor position. +Add full NSAccessibility support to EmacsView so that macOS Zoom +'Follow keyboard focus' tracks the text cursor position. -Implements both the old parameterized attribute API -(accessibilityAttributeValue:forParameter:) and the new -NSAccessibilityProtocol (accessibilityBoundsForRange:) so that Zoom -works on both older and newer macOS versions. +Key insight: macOS Zoom is event-driven. It only calls +accessibilityBoundsForRange: / AXBoundsForRange AFTER receiving +NSAccessibilitySelectedTextChangedNotification. Without posting that +notification, Zoom never queries cursor position -- even if the bounds +methods are correctly implemented. Changes: - Add lastAccessibilityCursorRect ivar to EmacsView (nsterm.h) -- Store cursor rect in ns_draw_window_cursor (nsterm.m) +- Store cursor rect in ns_draw_window_cursor + post SelectedTextChanged +- Post FocusedUIElementChanged in windowDidBecomeKey - Implement accessibilityIsIgnored, accessibilityFocusedUIElement - Implement accessibilityRole (NSAccessibilityTextAreaRole) - Implement isAccessibilityElement (new API, returns YES) -- Implement accessibilityParameterizedAttributeNames (old API) -- Implement accessibilityAttributeValue:forParameter: for BoundsForRange (old API) +- Implement accessibilityAttributeNames + accessibilityAttributeValue: + responds to NSAccessibilitySelectedTextRangeAttribute with {0,0} - Implement accessibilityBoundsForRange: (new NSAccessibilityProtocol) +- Implement accessibilityParameterizedAttributeNames + old API fallback See also: https://github.com/nicowillis/Ghostty/issues/4053 --- - src/nsterm.h | 3 +++ - src/nsterm.m | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++ - 2 files changed, 89 insertions(+) + src/nsterm.h | 3 ++ + src/nsterm.m | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 142 insertions(+) diff --git a/src/nsterm.h b/src/nsterm.h -index 7c1ee4c..6c1ff34 100644 +index 7c1ee4cf535..6c1ff3434a3 100644 --- a/src/nsterm.h +++ b/src/nsterm.h @@ -485,6 +485,9 @@ enum ns_return_frame_mode @@ -43,26 +45,49 @@ index 7c1ee4c..6c1ff34 100644 /* AppKit-side interface. */ diff --git a/src/nsterm.m b/src/nsterm.m -index 932d209..c900d71 100644 +index 932d209f56b..906c76bc468 100644 --- a/src/nsterm.m +++ b/src/nsterm.m -@@ -3232,6 +3232,15 @@ Note that CURSOR_WIDTH is meaningful only for (h)bar cursors. +@@ -3232,6 +3232,23 @@ 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)); +#ifdef NS_IMPL_COCOA -+ /* Store cursor rect for accessibility (AXBoundsForRange). */ ++ /* Store cursor rect for accessibility and notify assistive tools ++ (e.g. macOS Zoom "Follow keyboard focus") that the cursor moved. ++ macOS Zoom is event-driven: it only calls accessibilityBoundsForRange: ++ AFTER receiving NSAccessibilitySelectedTextChangedNotification. ++ Without posting this notification, Zoom never queries cursor position. */ + { + EmacsView *view = FRAME_NS_VIEW (f); + if (view) -+ view->lastAccessibilityCursorRect = r; ++ { ++ view->lastAccessibilityCursorRect = r; ++ NSAccessibilityPostNotification (view, ++ NSAccessibilitySelectedTextChangedNotification); ++ } + } +#endif + ns_focus (f, NULL, 0); NSGraphicsContext *ctx = [NSGraphicsContext currentContext]; -@@ -9474,6 +9483,91 @@ - (int) fullscreenState +@@ -8237,6 +8254,14 @@ - (void)windowDidBecomeKey /* for direct calls */ + XSETFRAME (event.frame_or_window, emacsframe); + kbd_buffer_store_event (&event); + ns_send_appdefined (-1); // Kick main loop ++ ++#ifdef NS_IMPL_COCOA ++ /* Notify accessibility clients (e.g. macOS Zoom) that the focused ++ UI element changed to this Emacs view. Zoom uses this to activate ++ keyboard focus tracking when the window gains focus. */ ++ NSAccessibilityPostNotification (self, ++ NSAccessibilityFocusedUIElementChangedNotification); ++#endif + } + + +@@ -9474,6 +9499,120 @@ - (int) fullscreenState return fs_state; } @@ -92,6 +117,35 @@ index 932d209..c900d71 100644 + return NSAccessibilityTextAreaRole; +} + ++- (NSArray *)accessibilityAttributeNames ++{ ++ NSArray *superAttrs = [super accessibilityAttributeNames]; ++ if (superAttrs == nil) ++ superAttrs = @[]; ++ return [superAttrs arrayByAddingObjectsFromArray: ++ @[NSAccessibilityRoleAttribute, ++ NSAccessibilitySelectedTextRangeAttribute, ++ NSAccessibilityNumberOfCharactersAttribute]]; ++} ++ ++- (id)accessibilityAttributeValue:(NSString *)attribute ++{ ++ if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) ++ return NSAccessibilityTextAreaRole; ++ ++ /* macOS Zoom queries NSAccessibilitySelectedTextRangeAttribute before ++ calling AXBoundsForRange / accessibilityBoundsForRange:. We return ++ {0,0}; our bounds methods ignore the range and always return the ++ actual cursor rect, so any range value here works. */ ++ if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) ++ return [NSValue valueWithRange:NSMakeRange (0, 0)]; ++ ++ if ([attribute isEqualToString:NSAccessibilityNumberOfCharactersAttribute]) ++ return @(0); ++ ++ return [super accessibilityAttributeValue:attribute]; ++} ++ +/* New NSAccessibilityProtocol (macOS 10.10+) — preferred by Zoom. */ +- (NSRect)accessibilityBoundsForRange:(NSRange)range +{ @@ -156,3 +210,4 @@ index 932d209..c900d71 100644 -- 2.43.0 +