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
155 lines
5.4 KiB
Diff
155 lines
5.4 KiB
Diff
From 402d2959cc569ebe740f07687c37283323fce314 Mon Sep 17 00:00:00 2001
|
||
From: Martin Sukany <martin@sukany.cz>
|
||
Date: Sat, 28 Feb 2026 22:08:55 +0100
|
||
Subject: [PATCH] ns: integrate with macOS Zoom for cursor tracking
|
||
|
||
Inform macOS Zoom of the text cursor position so the zoomed viewport
|
||
follows keyboard focus in Emacs.
|
||
|
||
* src/nsterm.h (EmacsView): Add lastZoomCursorRect ivar.
|
||
* src/nsterm.m (ns_draw_window_cursor): Store cursor rect in
|
||
lastZoomCursorRect; call UAZoomChangeFocus with CG-space
|
||
coordinates when UAZoomEnabled returns true.
|
||
(ns_update_end): Call UAZoomChangeFocus as fallback after each
|
||
redisplay cycle to ensure Zoom tracks cursor across window
|
||
switches (C-x o) where the physical cursor may not be redrawn.
|
||
|
||
Coordinate conversion: EmacsView pixels (AppKit, flipped) ->
|
||
NSWindow -> NSScreen -> CGRect with y-flip for CoreGraphics
|
||
top-left origin. UAZoomChangeFocus is available since macOS 10.4
|
||
(ApplicationServices umbrella framework).
|
||
|
||
Tested on macOS 14 with Zoom enabled: cursor tracking works across
|
||
window splits, switches, and normal navigation.
|
||
---
|
||
etc/NEWS | 8 ++++++
|
||
src/nsterm.h | 4 +++
|
||
src/nsterm.m | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||
3 files changed, 82 insertions(+)
|
||
|
||
diff --git a/etc/NEWS b/etc/NEWS
|
||
index ef36df5..e80e124 100644
|
||
--- a/etc/NEWS
|
||
+++ b/etc/NEWS
|
||
@@ -82,6 +82,14 @@ other directory on your system. You can also invoke the
|
||
|
||
* Changes in Emacs 31.1
|
||
|
||
++++
|
||
+** The macOS NS port now integrates with macOS Zoom.
|
||
+When macOS Zoom is enabled (System Settings -> Accessibility -> Zoom ->
|
||
+Follow keyboard focus), Emacs informs Zoom of the text cursor position
|
||
+after every cursor redraw via 'UAZoomChangeFocus'. The zoomed viewport
|
||
+automatically tracks the insertion point across window splits and
|
||
+switches.
|
||
+
|
||
+++
|
||
** 'line-spacing' now supports specifying spacing above the line.
|
||
Previously, only spacing below the line could be specified. The user
|
||
diff --git a/src/nsterm.h b/src/nsterm.h
|
||
index 7c1ee4c..f2755e4 100644
|
||
--- a/src/nsterm.h
|
||
+++ b/src/nsterm.h
|
||
@@ -484,6 +484,10 @@ enum ns_return_frame_mode
|
||
@public
|
||
struct frame *emacsframe;
|
||
int scrollbarsNeedingUpdate;
|
||
+#ifdef NS_IMPL_COCOA
|
||
+ /* Cached cursor rect for macOS Zoom integration. */
|
||
+ NSRect lastZoomCursorRect;
|
||
+#endif
|
||
NSRect ns_userRect;
|
||
}
|
||
|
||
diff --git a/src/nsterm.m b/src/nsterm.m
|
||
index 74e4ad5..eb1649b 100644
|
||
--- a/src/nsterm.m
|
||
+++ b/src/nsterm.m
|
||
@@ -1104,6 +1104,34 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen)
|
||
|
||
unblock_input ();
|
||
ns_updating_frame = NULL;
|
||
+
|
||
+#ifdef NS_IMPL_COCOA
|
||
+ /* Zoom fallback: ensure Zoom tracks the cursor after window
|
||
+ switches (C-x o) and other operations where the physical cursor
|
||
+ may not be redrawn but the focused window changed. This uses
|
||
+ lastZoomCursorRect which was set by ns_draw_window_cursor
|
||
+ during the current or a previous redisplay cycle. */
|
||
+#if defined (MAC_OS_X_VERSION_MIN_REQUIRED) \
|
||
+ && MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
|
||
+ if (view && UAZoomEnabled ()
|
||
+ && !NSIsEmptyRect (view->lastZoomCursorRect))
|
||
+ {
|
||
+ NSRect r = view->lastZoomCursorRect;
|
||
+ 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 /* MAC_OS_X_VERSION_MIN_REQUIRED >= 101000 */
|
||
+#endif /* NS_IMPL_COCOA */
|
||
}
|
||
|
||
static void
|
||
@@ -3232,6 +3260,48 @@ 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
|
||
+ /* Accessibility: store cursor rect for macOS Zoom integration.
|
||
+ Zoom (System Settings -> Accessibility -> Zoom) tracks a focus
|
||
+ element to keep the zoomed viewport centered on the cursor.
|
||
+ UAZoomChangeFocus() informs Zoom of the cursor position after
|
||
+ every physical cursor redraw.
|
||
+
|
||
+ The coordinate conversion chain:
|
||
+ EmacsView pixels (AppKit, flipped, origin at top-left)
|
||
+ -> NSWindow coordinates (convertRect:toView:nil)
|
||
+ -> NSScreen coordinates (convertRectToScreen:)
|
||
+ -> CGRect (NSRectToCGRect, same values)
|
||
+ -> CG y-flip (CoreGraphics uses top-left origin on
|
||
+ the primary screen; AppKit uses bottom-left). */
|
||
+ {
|
||
+ EmacsView *view = FRAME_NS_VIEW (f);
|
||
+ if (view && on_p && active_p)
|
||
+ {
|
||
+ view->lastZoomCursorRect = r;
|
||
+
|
||
+#if defined (MAC_OS_X_VERSION_MIN_REQUIRED) \
|
||
+ && MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
|
||
+ 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 /* MAC_OS_X_VERSION_MIN_REQUIRED >= 101000 */
|
||
+ }
|
||
+ }
|
||
+#endif /* NS_IMPL_COCOA */
|
||
+
|
||
ns_focus (f, NULL, 0);
|
||
|
||
NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
|
||
--
|
||
2.43.0
|
||
|