From c22a9a4e9145ace1b207b822b5f23a61595af13f Mon Sep 17 00:00:00 2001 From: Daneel Date: Sat, 28 Feb 2026 14:16:41 +0100 Subject: [PATCH] patches: add 0007 overlay display string support Appends overlay before-string/after-string to AX text extraction. Fires AXValueChanged on BUF_OVERLAY_MODIFF changes. Enables VoiceOver for Vertico, Ivy, Icomplete and other overlay-based completion frameworks. --- ...ay-display-strings-in-accessibility-.patch | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 patches/0007-ns-include-overlay-display-strings-in-accessibility-.patch diff --git a/patches/0007-ns-include-overlay-display-strings-in-accessibility-.patch b/patches/0007-ns-include-overlay-display-strings-in-accessibility-.patch new file mode 100644 index 0000000..a4404ba --- /dev/null +++ b/patches/0007-ns-include-overlay-display-strings-in-accessibility-.patch @@ -0,0 +1,128 @@ +From 23e2549bd2ce3c3180f0f9a5ead326bc0183c1fb Mon Sep 17 00:00:00 2001 +From: Martin Sukany +Date: Sat, 28 Feb 2026 14:16:29 +0100 +Subject: [PATCH] ns: include overlay display strings in accessibility text + +Completion frameworks like Vertico, Ivy, and Icomplete render +candidates via overlay before-string / after-string properties +rather than as buffer text. The accessibility text extraction +in ns_ax_buffer_text only reads buffer content, making these +overlay-based UIs invisible to VoiceOver. + +This patch adds two enhancements: + +1. Walk overlays in the visible range after buffer text extraction + and append their before-string / after-string content. Each + overlay string gets a virtual visible-run entry mapped to the + overlay's buffer position for cursor tracking. + +2. Detect overlay-only changes (BUF_OVERLAY_MODIFF) in the + notification dispatch and fire AXValueChanged, so VoiceOver + re-reads content when completion candidates change. + +* src/nsterm.m (ns_ax_buffer_text): Append overlay before-string +and after-string content with virtual visible-run entries. +(EmacsAccessibilityBuffer postAccessibilityNotificationsForFrame:): +Check BUF_OVERLAY_MODIFF and fire postTextChangedNotification when +overlays change. + +Tested on macOS 14 with VoiceOver and Vertico completion framework. +Verified: M-x candidate navigation announced, file finder candidates +read by VoiceOver when moving up/down. +--- + src/nsterm.m | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 72 insertions(+) + +diff --git a/src/nsterm.m b/src/nsterm.m +index 1780194..88458aa 100644 +--- a/src/nsterm.m ++++ b/src/nsterm.m +@@ -7021,6 +7021,68 @@ ns_ax_buffer_text (struct window *w, ptrdiff_t *out_start, + pos = run_end; + } + ++ /* Append overlay display strings (before-string, after-string). ++ Many completion frameworks (Vertico, Ivy, Icomplete) render ++ candidates via overlay properties rather than buffer text. ++ Without this, VoiceOver cannot read overlay-based content. ++ ++ We append overlay strings after the buffer text and give each ++ a virtual run mapped to the overlay's buffer position, so cursor ++ tracking and index mapping remain functional. */ ++ { ++ Lisp_Object ov_list = Foverlays_in (make_fixnum (begv), ++ make_fixnum (zv)); ++ for (Lisp_Object tail = ov_list; CONSP (tail); tail = XCDR (tail)) ++ { ++ Lisp_Object ov = XCAR (tail); ++ Lisp_Object props[2]; ++ ptrdiff_t anchors[2]; ++ ++ props[0] = Foverlay_get (ov, intern_c_string ("before-string")); ++ anchors[0] = XFIXNUM (Foverlay_start (ov)); ++ props[1] = Foverlay_get (ov, intern_c_string ("after-string")); ++ anchors[1] = XFIXNUM (Foverlay_end (ov)); ++ ++ for (int k = 0; k < 2; k++) ++ { ++ if (!STRINGP (props[k])) ++ continue; ++ ++ /* Cap total text. */ ++ if (ax_offset >= NS_AX_TEXT_CAP) ++ break; ++ ++ NSString *nsstr ++ = [NSString stringWithLispString:props[k]]; ++ NSUInteger ns_len = [nsstr length]; ++ if (ns_len == 0) ++ continue; ++ ++ if (ax_offset + ns_len > NS_AX_TEXT_CAP) ++ ns_len = NS_AX_TEXT_CAP - ax_offset; ++ ++ if (ns_len < [nsstr length]) ++ nsstr = [nsstr substringToIndex:ns_len]; ++ ++ [result appendString:nsstr]; ++ ++ if (nruns >= run_capacity) ++ { ++ run_capacity *= 2; ++ runs = xrealloc (runs, run_capacity ++ * sizeof (ns_ax_visible_run)); ++ } ++ runs[nruns].charpos = anchors[k]; ++ runs[nruns].length = 0; /* virtual — no buffer chars */ ++ runs[nruns].ax_start = ax_offset; ++ runs[nruns].ax_length = ns_len; ++ nruns++; ++ ++ ax_offset += ns_len; ++ } ++ } ++ } ++ + unbind_to (count, Qnil); + + *out_runs = runs; +@@ -8795,6 +8857,16 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem, + [self postTextChangedNotification:point]; + } + ++ /* --- Overlay content changed (e.g. Vertico/Ivy candidate switch) --- ++ Overlay-only changes (before-string, after-string, display) bump ++ BUF_OVERLAY_MODIFF but not BUF_MODIFF. Fire ValueChanged so ++ VoiceOver re-reads the buffer content including overlay strings. */ ++ else if (BUF_OVERLAY_MODIFF (b) != self.cachedOverlayModiff) ++ { ++ self.cachedOverlayModiff = BUF_OVERLAY_MODIFF (b); ++ [self postTextChangedNotification:point]; ++ } ++ + /* --- Cursor moved or selection changed --- + Use 'else if' — edits and selection moves are mutually exclusive + per the WebKit/Chromium pattern. */ +-- +2.43.0 +