VoiceOver patches 0001-0008 now apply cleanly on top of Zoom patch 0000. The full set (git am patches/000*.patch) works without conflicts. Patch 0005 (integration) merges Zoom fallback and VoiceOver postAccessibilityUpdates in ns_update_end.
155 lines
5.2 KiB
Diff
155 lines
5.2 KiB
Diff
From 1c0ca763f978e02dd7b3968c3515dd7b41136f8f Mon Sep 17 00:00:00 2001
|
||
From: Martin Sukany <martin@sukany.cz>
|
||
Date: Sat, 28 Feb 2026 22:39:35 +0100
|
||
Subject: [PATCH 1/9] 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 lastCursorRect, zoomCursorUpdated.
|
||
* src/nsterm.m (ns_draw_window_cursor): Store cursor rect in
|
||
lastCursorRect; call UAZoomChangeFocus with CG-space coordinates
|
||
when UAZoomEnabled returns true. Set zoomCursorUpdated flag.
|
||
(ns_update_end): Call UAZoomChangeFocus as fallback when cursor
|
||
was not physically redrawn in this cycle (e.g., after C-x o window
|
||
switch). Gated by zoomCursorUpdated to avoid double calls.
|
||
|
||
Coordinate conversion: EmacsView pixels (AppKit, flipped) ->
|
||
NSWindow -> NSScreen -> CGRect with y-flip for CoreGraphics
|
||
top-left origin. UAZoomEnabled returns false when Zoom is inactive,
|
||
so overhead is a single function call per redisplay cycle.
|
||
|
||
Tested on macOS 14 with Zoom enabled: cursor tracking works across
|
||
window splits, switches (C-x o), and normal navigation.
|
||
---
|
||
etc/NEWS | 8 +++++++
|
||
src/nsterm.h | 6 +++++
|
||
src/nsterm.m | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||
3 files changed, 82 insertions(+)
|
||
|
||
diff --git a/etc/NEWS b/etc/NEWS
|
||
index ef36df5..f10d17e 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..ea6e7ba 100644
|
||
--- a/src/nsterm.h
|
||
+++ b/src/nsterm.h
|
||
@@ -484,6 +484,12 @@ enum ns_return_frame_mode
|
||
@public
|
||
struct frame *emacsframe;
|
||
int scrollbarsNeedingUpdate;
|
||
+#ifdef NS_IMPL_COCOA
|
||
+ /* Cached cursor rect for macOS Zoom integration. Set by
|
||
+ ns_draw_window_cursor, used by ns_update_end fallback. */
|
||
+ NSRect lastCursorRect;
|
||
+ BOOL zoomCursorUpdated;
|
||
+#endif
|
||
NSRect ns_userRect;
|
||
}
|
||
|
||
diff --git a/src/nsterm.m b/src/nsterm.m
|
||
index 74e4ad5..cd721c8 100644
|
||
--- a/src/nsterm.m
|
||
+++ b/src/nsterm.m
|
||
@@ -1104,6 +1104,35 @@ 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) where the physical cursor may not be redrawn.
|
||
+ Only fires when ns_draw_window_cursor did NOT run in this cycle
|
||
+ (zoomCursorUpdated is NO). */
|
||
+#if defined (MAC_OS_X_VERSION_MIN_REQUIRED) \
|
||
+ && MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
|
||
+ if (view && !view->zoomCursorUpdated && UAZoomEnabled ()
|
||
+ && !NSIsEmptyRect (view->lastCursorRect))
|
||
+ {
|
||
+ NSRect r = view->lastCursorRect;
|
||
+ 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);
|
||
+ }
|
||
+ if (view)
|
||
+ view->zoomCursorUpdated = NO;
|
||
+#endif
|
||
+#endif /* NS_IMPL_COCOA */
|
||
}
|
||
|
||
static void
|
||
@@ -3232,6 +3261,45 @@ 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
|
||
+ /* Zoom integration: inform macOS Zoom of the cursor position.
|
||
+ Zoom (System Settings -> Accessibility -> Zoom) tracks a focus
|
||
+ element to keep the zoomed viewport centered on the cursor.
|
||
+
|
||
+ Coordinate conversion:
|
||
+ EmacsView pixels (AppKit, flipped, top-left origin)
|
||
+ -> NSWindow (convertRect:toView:nil)
|
||
+ -> NSScreen (convertRectToScreen:)
|
||
+ -> CGRect with y-flip for CoreGraphics top-left origin. */
|
||
+ {
|
||
+ EmacsView *view = FRAME_NS_VIEW (f);
|
||
+ if (view && on_p && active_p)
|
||
+ {
|
||
+ view->lastCursorRect = r;
|
||
+ view->zoomCursorUpdated = YES;
|
||
+
|
||
+#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
|
||
+ }
|
||
+ }
|
||
+#endif /* NS_IMPL_COCOA */
|
||
+
|
||
ns_focus (f, NULL, 0);
|
||
|
||
NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
|
||
--
|
||
2.43.0
|
||
|