patches: address all maintainer review issues

- Issue 1: Add explicit ApplicationServices import for UAZoomEnabled/
  UAZoomChangeFocus (was implicit via Carbon.h, now explicit)
- Issue 2: Rename FOR_EACH_FRAME variable 'frames' -> 'frame' (plural
  was misleading; matches Emacs convention)
- Issue 3: Move unblock_input before ObjC calls in
  postCompletionAnnouncementForBuffer: to avoid holding block_input
  during @synchronized operations
- Issue 4: Fix DEFVAR_BOOL doc and Texinfo: initial value is nil,
  not t; auto-detection sets it at startup
- Issue 5: Replace magic 10000 with NS_AX_MAX_COMPLETION_BUFFER_CHARS
  constant with explanatory comment
- Issue 6: Add comment to lineStartOffsets loop explaining it is gated
  on BUF_CHARS_MODIFF and never runs on the hot path
- Issue 8: Rewrite all 9 commit messages to GNU ChangeLog format with
  '* file (symbol): description' entries
- Issue 9: Break 81-char @interface line in nsterm.h
- Issue 10: Add WINDOWP/BUFFERP guards before dereferencing
  cf->selected_window and cw->contents in ns_zoom_find_child_frame_candidate
- Issue 11: Fix @pxref -> @xref at sentence start in macos.texi
This commit is contained in:
2026-03-01 09:44:47 +01:00
parent e0343db56c
commit 71c81abcae
9 changed files with 275 additions and 279 deletions

View File

@@ -1,45 +1,35 @@
From 03a3e77f9ff5f46429964863a2f320e119c0686c Mon Sep 17 00:00:00 2001
From 5538b9a843f1c56607235fe399562d48541ca4e8 Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 22:39:35 +0100
Subject: [PATCH 0/8] 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.
follows keyboard focus in Emacs. Also track completion candidates so
Zoom follows the selected item (Vertico, Corfu, etc.) during completion.
Basic cursor tracking:
* etc/NEWS: Document Zoom integration.
* 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 (e.g. after C-x o window switch).
Gated by zoomCursorUpdated to avoid double calls.
Completion candidate tracking:
* src/nsterm.m (ns_zoom_face_is_selected): New predicate.
Match 'current', 'selected', and 'selection' in face symbol
names to identify the highlighted completion candidate.
(ns_zoom_find_overlay_candidate_line): Scan overlay
before-string/after-string for the selected candidate line.
Handles Vertico, Icomplete, Ivy, and similar overlay frameworks.
(ns_zoom_find_child_frame_candidate): Scan child frame buffer
text for the selected candidate. Handles Corfu, Company-box,
and similar child frame frameworks.
(ns_zoom_track_completion): Called from ns_update_end after
cursor tracking. Overrides Zoom focus to the selected
completion candidate when one is found.
Coordinate conversion: EmacsView pixels (AppKit, flipped) ->
NSWindow -> NSScreen -> CGRect with y-flip for CoreGraphics
top-left origin.
Tested on macOS 14 with Zoom enabled: cursor tracking works across
window splits, switches (C-x o), and completion frameworks.
* src/nsterm.m: Include ApplicationServices for UAZoomEnabled and
UAZoomChangeFocus (UniversalAccess sub-framework).
[NS_IMPL_COCOA]: Define NS_AX_MAX_COMPLETION_BUFFER_CHARS.
(ns_zoom_enabled_p): New static function; caches UAZoomEnabled with
1-second TTL to avoid per-frame Mach IPC overhead.
(ns_zoom_face_is_selected): New static predicate; matches 'current',
'selected', 'selection' in face symbol names.
(ns_zoom_find_overlay_candidate_line): New static function; scans
minibuffer overlays for the selected completion candidate line.
(ns_zoom_find_child_frame_candidate): New static function; scans
child frame buffers for a selected candidate; guards against partially
initialized frames with WINDOWP and BUFFERP checks.
(ns_zoom_track_completion): New static function; overrides Zoom focus
to the selected completion candidate after normal cursor tracking.
(ns_update_end): Call ns_zoom_track_completion.
(ns_draw_window_cursor): Store cursor rect; call UAZoomChangeFocus.
---
etc/NEWS | 11 ++
src/nsterm.h | 6 +
src/nsterm.m | 336 +++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 353 insertions(+)
src/nsterm.m | 353 +++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 370 insertions(+)
diff --git a/etc/NEWS b/etc/NEWS
index ef36df5..80661a9 100644
@@ -81,10 +71,22 @@ index 7c1ee4c..ea6e7ba 100644
}
diff --git a/src/nsterm.m b/src/nsterm.m
index 74e4ad5..5498d7a 100644
index 74e4ad5..fc75910 100644
--- a/src/nsterm.m
+++ b/src/nsterm.m
@@ -1081,6 +1081,268 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen)
@@ -71,6 +71,11 @@ Updated by Christian Limpach (chris@nice.ch)
#include "macfont.h"
#include <Carbon/Carbon.h>
#include <IOSurface/IOSurface.h>
+/* ApplicationServices provides UAZoomEnabled and UAZoomChangeFocus
+ (UniversalAccess sub-framework). Carbon.h already pulls in
+ ApplicationServices on most SDK versions, but the explicit import
+ makes the dependency visible and guards against SDK changes. */
+#import <ApplicationServices/ApplicationServices.h>
#endif
static EmacsMenu *dockMenu;
@@ -1081,6 +1086,280 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen)
}
@@ -93,6 +95,12 @@ index 74e4ad5..5498d7a 100644
+#if defined (MAC_OS_X_VERSION_MIN_REQUIRED) \
+ && MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
+
+/* Maximum buffer size (in characters) for a window that we consider
+ a candidate for a completion popup. Completion popups are small;
+ if the buffer is larger than this, it is not a popup and we skip it
+ to avoid O(buffer-size) work per redisplay cycle. */
+#define NS_AX_MAX_COMPLETION_BUFFER_CHARS 10000
+
+/* Cached wrapper around ns_zoom_enabled_p ().
+ ns_zoom_enabled_p () performs a synchronous Mach IPC roundtrip to the
+ macOS Accessibility server (~50-200 µs per call). With call sites
@@ -209,19 +217,25 @@ index 74e4ad5..5498d7a 100644
+ns_zoom_find_child_frame_candidate (struct frame *f,
+ struct frame **child_frame)
+{
+ Lisp_Object frames, tail;
+ Lisp_Object frame, tail;
+
+ FOR_EACH_FRAME (tail, frames)
+ FOR_EACH_FRAME (tail, frame)
+ {
+ struct frame *cf = XFRAME (frames);
+ struct frame *cf = XFRAME (frame);
+ if (!FRAME_NS_P (cf) || !FRAME_LIVE_P (cf))
+ continue;
+ if (FRAME_PARENT_FRAME (cf) != f)
+ continue;
+ /* Small buffer = likely completion popup. */
+ /* Small buffer = likely completion popup. Guard against
+ partially initialized frames where selected_window or its
+ buffer may not yet be live. */
+ if (!WINDOWP (cf->selected_window))
+ continue;
+ struct window *cw = XWINDOW (cf->selected_window);
+ if (!BUFFERP (cw->contents))
+ continue;
+ struct buffer *b = XBUFFER (cw->contents);
+ if (BUF_ZV (b) - BUF_BEGV (b) > 10000)
+ if (BUF_ZV (b) - BUF_BEGV (b) > NS_AX_MAX_COMPLETION_BUFFER_CHARS)
+ continue;
+
+ ptrdiff_t beg = BUF_BEGV (b);
@@ -353,7 +367,7 @@ index 74e4ad5..5498d7a 100644
static void
ns_update_end (struct frame *f)
/* --------------------------------------------------------------------------
@@ -1104,6 +1366,41 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen)
@@ -1104,6 +1383,41 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen)
unblock_input ();
ns_updating_frame = NULL;
@@ -395,7 +409,7 @@ index 74e4ad5..5498d7a 100644
}
static void
@@ -3232,6 +3529,45 @@ Note that CURSOR_WIDTH is meaningful only for (h)bar cursors.
@@ -3232,6 +3546,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));