patches: fix 2 blockers from Opus review

BLOCKER #1: accessibilityUpdating flag exception safety.
A Lisp signal (longjmp) during postAccessibilityUpdates left
the re-entrance flag permanently YES, suppressing all future
AX notifications → VoiceOver goes silent randomly.
Fix: specpdl unwind protection (record_unwind_protect_ptr)
resets the flag on any longjmp. All 3 exit points use unbind_to.

BLOCKER #2: static struct buffer *lastBuffer dangling pointer.
Raw C pointer to buffer struct has no GC protection. After
kill-buffer, the pointer dangles.
Fix: file-scope Lisp_Object lastChildFrameBuffer with staticpro.
EQ comparison instead of pointer equality.

Also: revert accidental em-dash → triple-dash in title bar (0007),
fix README factual error (BUF_OVERLAY_MODIFF cache key).
This commit is contained in:
2026-02-28 18:29:19 +01:00
parent 4f37a8660e
commit 0f7608326c
3 changed files with 72 additions and 40 deletions

View File

@@ -1,4 +1,4 @@
From bb69ef51db4c87dfe88861927264121f95fc627f Mon Sep 17 00:00:00 2001 From 6e907a1000a8b138976d6a906e40449fdf1a61c5 Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz> From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 14:46:25 +0100 Date: Sat, 28 Feb 2026 14:46:25 +0100
Subject: [PATCH 1/2] ns: announce overlay completion candidates for VoiceOver Subject: [PATCH 1/2] ns: announce overlay completion candidates for VoiceOver
@@ -52,8 +52,8 @@ Independent overlay branch, BUF_CHARS_MODIFF gating, candidate
announcement with overlay Zoom rect storage. announcement with overlay Zoom rect storage.
--- ---
src/nsterm.h | 3 + src/nsterm.h | 3 +
src/nsterm.m | 335 +++++++++++++++++++++++++++++++++++++++++++++------ src/nsterm.m | 331 +++++++++++++++++++++++++++++++++++++++++++++------
2 files changed, 300 insertions(+), 38 deletions(-) 2 files changed, 298 insertions(+), 36 deletions(-)
diff --git a/src/nsterm.h b/src/nsterm.h diff --git a/src/nsterm.h b/src/nsterm.h
index 51c30ca..5c15639 100644 index 51c30ca..5c15639 100644
@@ -77,7 +77,7 @@ index 51c30ca..5c15639 100644
BOOL font_panel_active; BOOL font_panel_active;
NSFont *font_panel_result; NSFont *font_panel_result;
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index 1780194..6efeb1d 100644 index 1780194..143e784 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -3258,7 +3258,12 @@ ns_draw_window_cursor (struct window *w, struct glyph_row *glyph_row, @@ -3258,7 +3258,12 @@ ns_draw_window_cursor (struct window *w, struct glyph_row *glyph_row,
@@ -570,22 +570,6 @@ index 1780194..6efeb1d 100644
Methods are kept here (same .m file) so they access the ivars Methods are kept here (same .m file) so they access the ivars
declared in the @interface ivar block. */ declared in the @interface ivar block. */
@implementation EmacsAccessibilityBuffer (InteractiveSpans) @implementation EmacsAccessibilityBuffer (InteractiveSpans)
@@ -10520,13 +10779,13 @@ ns_in_echo_area (void)
if (old_title == 0)
{
char *t = strdup ([[[self window] title] UTF8String]);
- char *pos = strstr (t, " — ");
+ char *pos = strstr (t, " --- ");
if (pos)
*pos = '\0';
old_title = t;
}
size_title = xmalloc (strlen (old_title) + 40);
- esprintf (size_title, "%s — (%d × %d)", old_title, cols, rows);
+ esprintf (size_title, "%s --- (%d × %d)", old_title, cols, rows);
[window setTitle: [NSString stringWithUTF8String: size_title]];
[window display];
xfree (size_title);
@@ -11922,7 +12181,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view, @@ -11922,7 +12181,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
if (WINDOW_LEAF_P (w)) if (WINDOW_LEAF_P (w))

View File

@@ -1,4 +1,4 @@
From bdb5aefe515662fb294b719ad32fabc4008a5cb8 Mon Sep 17 00:00:00 2001 From 8564e4989f5f358092bd1494c3894a42974ee6e1 Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz> From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 16:01:29 +0100 Date: Sat, 28 Feb 2026 16:01:29 +0100
Subject: [PATCH 2/2] ns: announce child frame completion candidates for Subject: [PATCH 2/2] ns: announce child frame completion candidates for
@@ -43,8 +43,8 @@ childFrameCompletionActive flag.
refocus parent buffer element when child frame closes. refocus parent buffer element when child frame closes.
--- ---
src/nsterm.h | 2 + src/nsterm.h | 2 +
src/nsterm.m | 253 ++++++++++++++++++++++++++++++++++++++++++++++++++- src/nsterm.m | 279 ++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 254 insertions(+), 1 deletion(-) 2 files changed, 278 insertions(+), 3 deletions(-)
diff --git a/src/nsterm.h b/src/nsterm.h diff --git a/src/nsterm.h b/src/nsterm.h
index 5c15639..8b34300 100644 index 5c15639..8b34300 100644
@@ -67,7 +67,7 @@ index 5c15639..8b34300 100644
@end @end
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index 6efeb1d..0255239 100644 index 143e784..da1a319 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -7066,6 +7066,110 @@ ns_ax_selected_overlay_text (struct buffer *b, @@ -7066,6 +7066,110 @@ ns_ax_selected_overlay_text (struct buffer *b,
@@ -181,7 +181,7 @@ index 6efeb1d..0255239 100644
/* Build accessibility text for window W, skipping invisible text. /* Build accessibility text for window W, skipping invisible text.
Populates *OUT_START with the buffer start charpos. Populates *OUT_START with the buffer start charpos.
Populates *OUT_RUNS with an array of visible runs and *OUT_NRUNS Populates *OUT_RUNS with an array of visible runs and *OUT_NRUNS
@@ -12311,6 +12415,105 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view, @@ -12311,6 +12415,122 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
The existing elements carry cached state (modiff, point) from the The existing elements carry cached state (modiff, point) from the
previous redisplay cycle. Rebuilding first would create fresh previous redisplay cycle. Rebuilding first would create fresh
elements with current values, making change detection impossible. */ elements with current values, making change detection impossible. */
@@ -194,9 +194,7 @@ index 6efeb1d..0255239 100644
+ after the parent's draw_window_cursor. */ + after the parent's draw_window_cursor. */
+- (void)announceChildFrameCompletion +- (void)announceChildFrameCompletion
+{ +{
+ static char *lastCandidate; +
+ static struct buffer *lastBuffer;
+ static EMACS_INT lastModiff;
+ +
+ /* Validate frame state --- child frames may be partially + /* Validate frame state --- child frames may be partially
+ initialized during creation. */ + initialized during creation. */
@@ -212,10 +210,11 @@ index 6efeb1d..0255239 100644
+ also guards against re-entrance: if Lisp calls below + also guards against re-entrance: if Lisp calls below
+ trigger redisplay, the modiff check short-circuits. */ + trigger redisplay, the modiff check short-circuits. */
+ EMACS_INT modiff = BUF_MODIFF (b); + EMACS_INT modiff = BUF_MODIFF (b);
+ if (b == lastBuffer && modiff == lastModiff) + if (EQ (w->contents, lastChildFrameBuffer)
+ && modiff == lastChildFrameModiff)
+ return; + return;
+ lastBuffer = b; + lastChildFrameBuffer = w->contents;
+ lastModiff = modiff; + lastChildFrameModiff = modiff;
+ +
+ /* Skip buffers larger than a typical completion popup. + /* Skip buffers larger than a typical completion popup.
+ This avoids scanning eldoc, which-key, or other child + This avoids scanning eldoc, which-key, or other child
@@ -232,10 +231,10 @@ index 6efeb1d..0255239 100644
+ +
+ /* Deduplicate --- avoid re-announcing the same candidate. */ + /* Deduplicate --- avoid re-announcing the same candidate. */
+ const char *cstr = [candidate UTF8String]; + const char *cstr = [candidate UTF8String];
+ if (lastCandidate && strcmp (cstr, lastCandidate) == 0) + if (lastChildFrameCandidate && strcmp (cstr, lastChildFrameCandidate) == 0)
+ return; + return;
+ xfree (lastCandidate); + xfree (lastChildFrameCandidate);
+ lastCandidate = xstrdup (cstr); + lastChildFrameCandidate = xstrdup (cstr);
+ +
+ NSDictionary *annInfo = @{ + NSDictionary *annInfo = @{
+ NSAccessibilityAnnouncementKey: candidate, + NSAccessibilityAnnouncementKey: candidate,
@@ -283,11 +282,29 @@ index 6efeb1d..0255239 100644
+ kUAZoomFocusTypeInsertionPoint); + kUAZoomFocusTypeInsertionPoint);
+ } + }
+} +}
+
+/* Child frame completion dedup state. File-scope so that
+ lastChildFrameBuffer can be staticpro'd against GC. */
+static Lisp_Object lastChildFrameBuffer;
+static EMACS_INT lastChildFrameModiff;
+static char *lastChildFrameCandidate;
+
+/* Reset the re-entrance guard when unwinding past
+ postAccessibilityUpdates due to a Lisp signal (longjmp).
+ Without this, a signal during Lisp calls (e.g. Fget_char_property
+ in overlay or child frame scanning) would leave
+ accessibilityUpdating = YES permanently, suppressing all future
+ accessibility notifications. */
+static void
+ns_ax_reset_accessibility_updating (void *view)
+{
+ ((EmacsView *)view)->accessibilityUpdating = NO;
+}
+ +
- (void)postAccessibilityUpdates - (void)postAccessibilityUpdates
{ {
NSTRACE ("[EmacsView postAccessibilityUpdates]"); NSTRACE ("[EmacsView postAccessibilityUpdates]");
@@ -12321,11 +12524,59 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view, @@ -12321,10 +12541,60 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
/* Re-entrance guard: VoiceOver callbacks during notification posting /* Re-entrance guard: VoiceOver callbacks during notification posting
can trigger redisplay, which calls ns_update_end, which calls us can trigger redisplay, which calls ns_update_end, which calls us
@@ -298,14 +315,16 @@ index 6efeb1d..0255239 100644
if (accessibilityUpdating) if (accessibilityUpdating)
return; return;
accessibilityUpdating = YES; accessibilityUpdating = YES;
+ specpdl_ref axCount = SPECPDL_INDEX ();
+ record_unwind_protect_ptr (ns_ax_reset_accessibility_updating, self);
+
+ /* Child frame completion popup (Corfu, Company-box, etc.). + /* Child frame completion popup (Corfu, Company-box, etc.).
+ Child frames don't participate in the accessibility tree; + Child frames don't participate in the accessibility tree;
+ announce the selected candidate directly. */ + announce the selected candidate directly. */
+ if (FRAME_PARENT_FRAME (emacsframe)) + if (FRAME_PARENT_FRAME (emacsframe))
+ { + {
+ [self announceChildFrameCompletion]; + [self announceChildFrameCompletion];
+ accessibilityUpdating = NO; + unbind_to (axCount, Qnil);
+ return; + return;
+ } + }
+ +
@@ -344,10 +363,37 @@ index 6efeb1d..0255239 100644
+ NSAccessibilityFocusedUIElementChangedNotification); + NSAccessibilityFocusedUIElementChangedNotification);
+ } + }
+ } + }
+
/* Detect window tree change (split, delete, new buffer). Compare /* Detect window tree change (split, delete, new buffer). Compare
FRAME_ROOT_WINDOW --- if it changed, the tree structure changed. */ FRAME_ROOT_WINDOW --- if it changed, the tree structure changed. */
Lisp_Object curRoot = FRAME_ROOT_WINDOW (emacsframe); @@ -12355,7 +12625,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
NSAccessibilityFocusedUIElementChangedNotification);
lastSelectedWindow = emacsframe->selected_window;
- accessibilityUpdating = NO;
+ unbind_to (axCount, Qnil);
return;
}
@@ -12399,7 +12669,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
NSAccessibilityFocusedUIElementChangedNotification);
}
- accessibilityUpdating = NO;
+ unbind_to (axCount, Qnil);
}
/* ---- Cursor position for Zoom (via accessibilityBoundsForRange:) ----
@@ -14341,6 +14611,9 @@ syms_of_nsterm (void)
DEFSYM (Qns_ax_completion, "completion");
DEFSYM (Qns_ax_completions_highlight, "completions-highlight");
DEFSYM (Qns_ax_backtab, "backtab");
+
+ lastChildFrameBuffer = Qnil;
+ staticpro (&lastChildFrameBuffer);
/* Qmouse_face and Qkeymap are defined in textprop.c / keymap.c. */
Fput (Qalt, Qmodifier_value, make_fixnum (alt_modifier));
Fput (Qhyper, Qmodifier_value, make_fixnum (hyper_modifier));
-- --
2.43.0 2.43.0

View File

@@ -92,7 +92,9 @@ ARCHITECTURE
text range, line/index/range conversions, frame-for-range, text range, line/index/range conversions, frame-for-range,
range-for-position, and insertion-point-line-number. range-for-position, and insertion-point-line-number.
- Maintains a text cache (cachedText / visibleRuns) keyed on - Maintains a text cache (cachedText / visibleRuns) keyed on
BUF_MODIFF, BUF_OVERLAY_MODIFF, and BUF_BEGV (narrowing). BUF_MODIFF and BUF_BEGV (narrowing). BUF_OVERLAY_MODIFF is
tracked separately for notification dispatch (patch 0007)
but not for cache invalidation.
The cache is the single source of truth for all The cache is the single source of truth for all
index-to-charpos and charpos-to-index mappings. index-to-charpos and charpos-to-index mappings.
- Detects buffer edits (modiff change), cursor movement (point - Detects buffer edits (modiff change), cursor movement (point