patches: review fixes — memory leak, dead code, unwind-protect, protocol conformance

B1: Fix memory leak in ns_ax_scan_interactive_spans — [spans copy]
    returned +1 retained object never released by caller.
    Now returns [[spans copy] autorelease].

B2: Remove dead function ns_ax_utf16_length_for_buffer_range —
    defined but never called anywhere in the patch.

B3: Add specpdl unwind protection in
    EmacsAccessibilityInteractiveSpan setAccessibilityFocused: —
    if Fselect_window signals, block_input is now always matched
    by unblock_input via record_unwind_protect_void.

W2: Document ns_ax_event_is_line_nav_key fragility in README
    Known Limitations (raw keycodes vs command symbols).

W4: Add comment for #include intervals.h (TEXT_PROP_MEANS_INVISIBLE).

M3: accessibilityBoundsForRange: on EmacsView now delegates to the
    focused EmacsAccessibilityBuffer for accurate per-range geometry,
    with cursor-rect fallback for Zoom.

M4: Add <NSAccessibility> protocol conformance to
    EmacsAccessibilityBuffer @interface declaration.

W1: Expanded commit message listing all new types, functions, DEFSYM
    additions, and threading model.
This commit is contained in:
2026-02-27 15:12:40 +01:00
parent 6994403014
commit 0b43fd25e3
2 changed files with 216 additions and 134 deletions

View File

@@ -1,25 +1,95 @@
From 6878620f894a738eb11f9742a3920434cfb00e1e Mon Sep 17 00:00:00 2001 From 17a100d99a31e0fae9b641c7ce163efd9bf5945b Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz> From: Martin Sukany <martin@sukany.cz>
Date: Fri, 27 Feb 2026 14:54:57 +0100 Date: Fri, 27 Feb 2026 15:09:15 +0100
Subject: [PATCH] ns: implement VoiceOver accessibility Subject: [PATCH] ns: implement VoiceOver accessibility for macOS
All AX protocol methods now dispatch_sync to the main thread, Add comprehensive macOS VoiceOver accessibility support to the NS
including accessibilityFrame, accessibilityLabel, accessibilityRole, (Cocoa) port. Before this patch, Emacs exposed only a minimal,
accessibilityRoleDescription, accessibilityPlaceholderValue, largely broken accessibility interface to macOS assistive technology
isAccessibilityFocused on EmacsAccessibilityBuffer, and clients.
accessibilityValue/accessibilityFrame/accessibilityLabel on
EmacsAccessibilityModeLine. setAccessibilitySelectedTextRange
uses record_unwind_current_buffer for exception safety.
* src/nsterm.m, src/nsterm.h, etc/NEWS: As described. New types and classes:
ns_ax_visible_run: maps buffer character positions to UTF-16
accessibility string indices, skipping invisible text.
EmacsAccessibilityElement: base class for virtual AX elements,
stores Lisp_Object lispWindow (GC-safe) and EmacsView reference.
EmacsAccessibilityBuffer <NSAccessibility>: AXTextArea element per
visible Emacs window; full text protocol (value, selectedTextRange,
line/index conversions, frameForRange, rangeForPosition), text cache
with visible-run mapping, hybrid SelectedTextChanged and
AnnouncementRequested notifications, completion announcements for
*Completions* buffer.
EmacsAccessibilityModeLine: AXStaticText per mode line.
EmacsAccessibilityInteractiveSpan: lightweight AX child elements
for Tab-navigable interactive spans (buttons, links, checkboxes,
completion candidates, Org-mode links, keymap overlays).
EmacsAXSpanType: enum for span classification.
New functions:
ns_ax_buffer_text: build accessibility string with visible-run
mapping, using TEXT_PROP_MEANS_INVISIBLE and
Fbuffer_substring_no_properties (handles buffer gap correctly).
ns_ax_mode_line_text: extract mode line text from glyph matrix.
ns_ax_frame_for_range: screen rect for a character range via glyph
matrix lookup.
ns_ax_event_is_line_nav_key: detect C-n/C-p/Tab/backtab for
forced line-granularity announcements.
ns_ax_scan_interactive_spans: scan visible range for interactive
text properties (widget, button, follow-link, org-link,
completion--string, keymap overlay).
ns_ax_completion_string_from_prop: extract completion candidate
from completion--string property (handles both string and cons).
ns_ax_find_completion_overlay_range: locate nearest
completions-highlight overlay for completion announcements.
ns_ax_completion_text_for_span: extract announcement text for a
completion overlay span.
EmacsView extensions:
rebuildAccessibilityTree, invalidateAccessibilityTree,
postAccessibilityUpdates (called from ns_update_end after every
redisplay cycle), accessibilityBoundsForRange (delegates to focused
buffer element with cursor-rect fallback for Zoom),
accessibilityFrameForRange, accessibilityStringForRange,
accessibilityParameterizedAttributeNames,
accessibilityAttributeValue:forParameter: (legacy API for Zoom).
ns_draw_phys_cursor: stores cursor rect for Zoom, calls
UAZoomChangeFocus with correct CG coordinate-space transform.
DEFSYM additions in syms_of_nsterm: Qwidget, Qbutton, Qfollow_link,
Qorg_link, Qcompletion_list_mode, Qcompletion__string, Qcompletion,
Qcompletions_highlight, Qbacktab.
Threading model: all Lisp calls on main thread; AX getters use
dispatch_sync to main; index mapping methods are thread-safe (no
Lisp calls, read only immutable NSString and scalar cache).
* src/nsterm.m: New accessibility implementation.
* src/nsterm.h: New class declarations and EmacsView ivar extensions.
* etc/NEWS: Document VoiceOver accessibility support.
--- ---
etc/NEWS | 11 + etc/NEWS | 11 +
src/nsterm.h | 108 ++ src/nsterm.h | 108 ++
src/nsterm.m | 2870 +++++++++++++++++++++++++++++++++++++++++++++++--- src/nsterm.m | 2869 +++++++++++++++++++++++++++++++++++++++++++++++---
3 files changed, 2847 insertions(+), 142 deletions(-) 3 files changed, 2840 insertions(+), 148 deletions(-)
diff --git a/etc/NEWS b/etc/NEWS diff --git a/etc/NEWS b/etc/NEWS
index 7367e3c..0e4480a 100644 index 7367e3cc..0e4480ad 100644
--- a/etc/NEWS --- a/etc/NEWS
+++ b/etc/NEWS +++ b/etc/NEWS
@@ -4374,6 +4374,17 @@ allowing Emacs users access to speech recognition utilities. @@ -4374,6 +4374,17 @@ allowing Emacs users access to speech recognition utilities.
@@ -41,7 +111,7 @@ index 7367e3c..0e4480a 100644
** Re-introduced dictation, lost in Emacs v30 (macOS). ** Re-introduced dictation, lost in Emacs v30 (macOS).
We lost macOS dictation in v30 when migrating to NSTextInputClient. We lost macOS dictation in v30 when migrating to NSTextInputClient.
diff --git a/src/nsterm.h b/src/nsterm.h diff --git a/src/nsterm.h b/src/nsterm.h
index 7c1ee4c..6455547 100644 index 7c1ee4cf..542e7d59 100644
--- a/src/nsterm.h --- a/src/nsterm.h
+++ b/src/nsterm.h +++ b/src/nsterm.h
@@ -453,6 +453,100 @@ enum ns_return_frame_mode @@ -453,6 +453,100 @@ enum ns_return_frame_mode
@@ -79,7 +149,7 @@ index 7c1ee4c..6455547 100644
+} ns_ax_visible_run; +} ns_ax_visible_run;
+ +
+/* Virtual AXTextArea element — one per visible Emacs window (buffer). */ +/* Virtual AXTextArea element — one per visible Emacs window (buffer). */
+@interface EmacsAccessibilityBuffer : EmacsAccessibilityElement +@interface EmacsAccessibilityBuffer : EmacsAccessibilityElement <NSAccessibility>
+{ +{
+ ns_ax_visible_run *visibleRuns; + ns_ax_visible_run *visibleRuns;
+ NSUInteger visibleRunCount; + NSUInteger visibleRunCount;
@@ -174,14 +244,14 @@ index 7c1ee4c..6455547 100644
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index 932d209..078465a 100644 index 932d209f..ea2de6f2 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -46,6 +46,7 @@ Updated by Christian Limpach (chris@nice.ch) @@ -46,6 +46,7 @@ Updated by Christian Limpach (chris@nice.ch)
#include "blockinput.h" #include "blockinput.h"
#include "sysselect.h" #include "sysselect.h"
#include "nsterm.h" #include "nsterm.h"
+#include "intervals.h" +#include "intervals.h" /* TEXT_PROP_MEANS_INVISIBLE */
#include "systime.h" #include "systime.h"
#include "character.h" #include "character.h"
#include "xwidget.h" #include "xwidget.h"
@@ -240,7 +310,7 @@ index 932d209..078465a 100644
ns_focus (f, NULL, 0); ns_focus (f, NULL, 0);
NSGraphicsContext *ctx = [NSGraphicsContext currentContext]; NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
@@ -6849,213 +6891,2404 @@ - (BOOL)fulfillService: (NSString *)name withArg: (NSString *)arg @@ -6849,218 +6891,2386 @@ - (BOOL)fulfillService: (NSString *)name withArg: (NSString *)arg
/* ========================================================================== /* ==========================================================================
@@ -266,29 +336,23 @@ index 932d209..078465a 100644
-/* Needed to inform when window closed from lisp. */ -/* Needed to inform when window closed from lisp. */
-- (void) setWindowClosing: (BOOL)closing -- (void) setWindowClosing: (BOOL)closing
-{
- NSTRACE ("[EmacsView setWindowClosing:%d]", closing);
+/* 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
+ with the count. Caller must free *OUT_RUNS with xfree(). */ + with the count. Caller must free *OUT_RUNS with xfree(). */
+
+static NSString *
+ns_ax_buffer_text (struct window *w, ptrdiff_t *out_start,
+ ns_ax_visible_run **out_runs, NSUInteger *out_nruns)
{
- NSTRACE ("[EmacsView setWindowClosing:%d]", closing);
+ *out_runs = NULL;
+ *out_nruns = 0;
- windowClosing = closing; - windowClosing = closing;
-} -}
+ if (!w || !WINDOW_LEAF_P (w)) +static NSString *
+ { +ns_ax_buffer_text (struct window *w, ptrdiff_t *out_start,
+ *out_start = 0; + ns_ax_visible_run **out_runs, NSUInteger *out_nruns)
+ return @""; +{
+ } + *out_runs = NULL;
+ *out_nruns = 0;
+ struct buffer *b = XBUFFER (w->contents); + if (!w || !WINDOW_LEAF_P (w))
+ if (!b)
+ { + {
+ *out_start = 0; + *out_start = 0;
+ return @""; + return @"";
@@ -297,34 +361,41 @@ index 932d209..078465a 100644
-- (void)dealloc -- (void)dealloc
-{ -{
- NSTRACE ("[EmacsView dealloc]"); - NSTRACE ("[EmacsView dealloc]");
+ ptrdiff_t begv = BUF_BEGV (b); + struct buffer *b = XBUFFER (w->contents);
+ ptrdiff_t zv = BUF_ZV (b); + if (!b)
+ {
+ *out_start = 0;
+ return @"";
+ }
- /* Clear the view resize notification. */ - /* Clear the view resize notification. */
- [[NSNotificationCenter defaultCenter] - [[NSNotificationCenter defaultCenter]
- removeObserver:self - removeObserver:self
- name:NSViewFrameDidChangeNotification - name:NSViewFrameDidChangeNotification
- object:nil]; - object:nil];
+ *out_start = begv; + ptrdiff_t begv = BUF_BEGV (b);
+ ptrdiff_t zv = BUF_ZV (b);
- if (fs_state == FULLSCREEN_BOTH) - if (fs_state == FULLSCREEN_BOTH)
- [nonfs_window release]; - [nonfs_window release];
+ if (zv <= begv) + *out_start = begv;
+ return @"";
-#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MIN_REQUIRED >= 101400 -#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MIN_REQUIRED >= 101400
- /* Release layer and menu */ - /* Release layer and menu */
- EmacsLayer *layer = (EmacsLayer *)[self layer]; - EmacsLayer *layer = (EmacsLayer *)[self layer];
- [layer release]; - [layer release];
-#endif -#endif
+ if (zv <= begv)
+ return @"";
- [[self menu] release];
- [super dealloc];
-}
+ specpdl_ref count = SPECPDL_INDEX (); + specpdl_ref count = SPECPDL_INDEX ();
+ record_unwind_current_buffer (); + record_unwind_current_buffer ();
+ if (b != current_buffer) + if (b != current_buffer)
+ set_buffer_internal_1 (b); + set_buffer_internal_1 (b);
- [[self menu] release];
- [super dealloc];
-}
+ /* First pass: count visible runs to allocate the mapping array. */ + /* First pass: count visible runs to allocate the mapping array. */
+ NSUInteger run_capacity = 64; + NSUInteger run_capacity = 64;
+ ns_ax_visible_run *runs = xmalloc (run_capacity + ns_ax_visible_run *runs = xmalloc (run_capacity
@@ -332,14 +403,19 @@ index 932d209..078465a 100644
+ NSUInteger nruns = 0; + NSUInteger nruns = 0;
+ NSUInteger ax_offset = 0; + NSUInteger ax_offset = 0;
+ NSMutableString *result = [NSMutableString string];
+ ptrdiff_t pos = begv;
-/* Called on font panel selection. */ -/* Called on font panel selection. */
-- (void) changeFont: (id) sender -- (void) changeFont: (id) sender
-{ -{
- struct font *font = FRAME_OUTPUT_DATA (emacsframe)->font; - struct font *font = FRAME_OUTPUT_DATA (emacsframe)->font;
- NSFont *nsfont; - NSFont *nsfont;
+ NSMutableString *result = [NSMutableString string];
+ ptrdiff_t pos = begv;
-#ifdef NS_IMPL_GNUSTEP
- nsfont = ((struct nsfont_info *) font)->nsfont;
-#else
- nsfont = (NSFont *) macfont_get_nsctfont (font);
-#endif
+ while (pos < zv) + while (pos < zv)
+ { + {
+ /* Check invisible property (text properties + overlays). + /* Check invisible property (text properties + overlays).
@@ -358,18 +434,15 @@ index 932d209..078465a 100644
+ continue; + continue;
+ } + }
-#ifdef NS_IMPL_GNUSTEP - if (!font_panel_active)
- nsfont = ((struct nsfont_info *) font)->nsfont; - return;
-#else
- nsfont = (NSFont *) macfont_get_nsctfont (font);
-#endif
+ /* Find end of this visible run: where invisible property changes. */ + /* Find end of this visible run: where invisible property changes. */
+ Lisp_Object next = Fnext_single_char_property_change ( + Lisp_Object next = Fnext_single_char_property_change (
+ make_fixnum (pos), Qinvisible, Qnil, make_fixnum (zv)); + make_fixnum (pos), Qinvisible, Qnil, make_fixnum (zv));
+ ptrdiff_t run_end = FIXNUMP (next) ? XFIXNUM (next) : zv; + ptrdiff_t run_end = FIXNUMP (next) ? XFIXNUM (next) : zv;
- if (!font_panel_active) - if (font_panel_result)
- return; - [font_panel_result release];
+ /* Cap total text at NS_AX_TEXT_CAP. */ + /* Cap total text at NS_AX_TEXT_CAP. */
+ ptrdiff_t run_len = run_end - pos; + ptrdiff_t run_len = run_end - pos;
+ if (ax_offset + (NSUInteger) run_len > NS_AX_TEXT_CAP) + if (ax_offset + (NSUInteger) run_len > NS_AX_TEXT_CAP)
@@ -401,44 +474,52 @@ index 932d209..078465a 100644
+ runs[nruns].ax_length = ns_len; + runs[nruns].ax_length = ns_len;
+ nruns++; + nruns++;
- if (font_panel_result) - font_panel_result = (NSFont *) [sender convertFont: nsfont];
- [font_panel_result release];
+ ax_offset += ns_len; + ax_offset += ns_len;
+ pos = run_end; + pos = run_end;
+ } + }
- font_panel_result = (NSFont *) [sender convertFont: nsfont];
+ unbind_to (count, Qnil);
- if (font_panel_result) - if (font_panel_result)
- [font_panel_result retain]; - [font_panel_result retain];
+ *out_runs = runs; + unbind_to (count, Qnil);
+ *out_nruns = nruns;
+ return result;
+}
-#ifndef NS_IMPL_COCOA -#ifndef NS_IMPL_COCOA
- font_panel_active = NO; - font_panel_active = NO;
- [NSApp stop: self]; - [NSApp stop: self];
-#endif -#endif
+ *out_runs = runs;
+ *out_nruns = nruns;
+ return result;
}
-#ifdef NS_IMPL_COCOA
-- (void) noteUserSelectedFont
+ +
+/* ---- Helper: extract mode line text from glyph rows ---- */ +/* ---- Helper: extract mode line text from glyph rows ---- */
+ +
+static NSString * +static NSString *
+ns_ax_mode_line_text (struct window *w) +ns_ax_mode_line_text (struct window *w)
+{ {
- font_panel_active = NO;
+ if (!w || !w->current_matrix) + if (!w || !w->current_matrix)
+ return @""; + return @"";
+
- /* If no font was previously selected, use the currently selected
- font. */
+ struct glyph_matrix *matrix = w->current_matrix; + struct glyph_matrix *matrix = w->current_matrix;
+ NSMutableString *text = [NSMutableString string]; + NSMutableString *text = [NSMutableString string];
+
- if (!font_panel_result && FRAME_FONT (emacsframe))
+ for (int i = 0; i < matrix->nrows; i++) + for (int i = 0; i < matrix->nrows; i++)
+ { {
- font_panel_result
- = macfont_get_nsctfont (FRAME_FONT (emacsframe));
+ struct glyph_row *row = matrix->rows + i; + struct glyph_row *row = matrix->rows + i;
+ if (!row->enabled_p || !row->mode_line_p) + if (!row->enabled_p || !row->mode_line_p)
+ continue; + continue;
+
- if (font_panel_result)
- [font_panel_result retain];
+ struct glyph *g = row->glyphs[TEXT_AREA]; + struct glyph *g = row->glyphs[TEXT_AREA];
+ struct glyph *end = g + row->used[TEXT_AREA]; + struct glyph *end = g + row->used[TEXT_AREA];
+ for (; g < end; g++) + for (; g < end; g++)
@@ -450,12 +531,13 @@ index 932d209..078465a 100644
+ length:1]]; + length:1]];
+ } + }
+ } + }
+ } }
-
- [NSApp stop: self];
+ return text; + return text;
} }
-#ifdef NS_IMPL_COCOA -- (void) noteUserCancelledSelection
-- (void) noteUserSelectedFont
+ +
+/* ---- Helper: screen rect for a character range via glyph matrix ---- */ +/* ---- Helper: screen rect for a character range via glyph matrix ---- */
+ +
@@ -468,31 +550,28 @@ index 932d209..078465a 100644
+ if (!w || !w->current_matrix || !view) + if (!w || !w->current_matrix || !view)
+ return NSZeroRect; + return NSZeroRect;
- /* If no font was previously selected, use the currently selected - if (font_panel_result)
- font. */ - [font_panel_result release];
- font_panel_result = nil;
+ /* charpos_start and charpos_len are already in buffer charpos + /* charpos_start and charpos_len are already in buffer charpos
+ space — the caller maps AX string indices through + space — the caller maps AX string indices through
+ charposForAccessibilityIndex which handles invisible text. */ + charposForAccessibilityIndex which handles invisible text. */
+ ptrdiff_t cp_start = charpos_start; + ptrdiff_t cp_start = charpos_start;
+ ptrdiff_t cp_end = cp_start + charpos_len; + ptrdiff_t cp_end = cp_start + charpos_len;
- if (!font_panel_result && FRAME_FONT (emacsframe)) - [NSApp stop: self];
+ struct glyph_matrix *matrix = w->current_matrix; + struct glyph_matrix *matrix = w->current_matrix;
+ NSRect result = NSZeroRect; + NSRect result = NSZeroRect;
+ BOOL found = NO; + BOOL found = NO;
+ +
+ for (int i = 0; i < matrix->nrows; i++) + for (int i = 0; i < matrix->nrows; i++)
{ + {
- font_panel_result
- = macfont_get_nsctfont (FRAME_FONT (emacsframe));
+ struct glyph_row *row = matrix->rows + i; + struct glyph_row *row = matrix->rows + i;
+ if (!row->enabled_p || row->mode_line_p) + if (!row->enabled_p || row->mode_line_p)
+ continue; + continue;
+ if (!row->displays_text_p && !row->ends_at_zv_p) + if (!row->displays_text_p && !row->ends_at_zv_p)
+ continue; + continue;
+
- if (font_panel_result)
- [font_panel_result retain];
+ ptrdiff_t row_start = MATRIX_ROW_START_CHARPOS (row); + ptrdiff_t row_start = MATRIX_ROW_START_CHARPOS (row);
+ ptrdiff_t row_end = MATRIX_ROW_END_CHARPOS (row); + ptrdiff_t row_end = MATRIX_ROW_END_CHARPOS (row);
+ +
@@ -517,9 +596,8 @@ index 932d209..078465a 100644
+ else + else
+ result = NSUnionRect (result, rowRect); + result = NSUnionRect (result, rowRect);
+ } + }
} + }
+
- [NSApp stop: self];
+ if (!found) + if (!found)
+ return NSZeroRect; + return NSZeroRect;
+ +
@@ -537,8 +615,9 @@ index 932d209..078465a 100644
+ NSRect winRect = [view convertRect:result toView:nil]; + NSRect winRect = [view convertRect:result toView:nil];
+ return [[view window] convertRectToScreen:winRect]; + return [[view window] convertRectToScreen:winRect];
} }
-#endif
-- (void) noteUserCancelledSelection -- (Lisp_Object) showFontPanel
+/* AX enum numeric compatibility for NSAccessibility notifications. +/* AX enum numeric compatibility for NSAccessibility notifications.
+ Values match WebKit AXObjectCacheMac fallback enums + Values match WebKit AXObjectCacheMac fallback enums
+ (AXTextStateChangeType / AXTextEditType / AXTextSelectionDirection / + (AXTextStateChangeType / AXTextEditType / AXTextSelectionDirection /
@@ -561,35 +640,6 @@ index 932d209..078465a 100644
+ ns_ax_text_selection_granularity_line = 3, + ns_ax_text_selection_granularity_line = 3,
+}; +};
+ +
+static NSUInteger
+ns_ax_utf16_length_for_buffer_range (struct buffer *b, ptrdiff_t start,
+ ptrdiff_t end)
{
- font_panel_active = NO;
+ if (!b || end <= start)
+ return 0;
- if (font_panel_result)
- [font_panel_result release];
- font_panel_result = nil;
+ specpdl_ref count = SPECPDL_INDEX ();
+ record_unwind_current_buffer ();
+ if (b != current_buffer)
+ set_buffer_internal_1 (b);
- [NSApp stop: self];
+ Lisp_Object lstr = Fbuffer_substring_no_properties (make_fixnum (start),
+ make_fixnum (end));
+ NSString *nsstr = [NSString stringWithLispString:lstr];
+ NSUInteger len = [nsstr length];
+
+ unbind_to (count, Qnil);
+ return len;
}
-#endif
-- (Lisp_Object) showFontPanel
+static BOOL
+ns_ax_find_completion_overlay_range (struct buffer *b, ptrdiff_t point, +ns_ax_find_completion_overlay_range (struct buffer *b, ptrdiff_t point,
+ ptrdiff_t *out_start, + ptrdiff_t *out_start,
+ ptrdiff_t *out_end) + ptrdiff_t *out_end)
@@ -903,10 +953,14 @@ index 932d209..078465a 100644
-/*****************************************************************************/ -/*****************************************************************************/
-/* Keyboard handling. */ -/* Keyboard handling. */
-#define NS_KEYLOG 0
+ Lisp_Object buf_obj = ns_ax_window_buffer_object (w); + Lisp_Object buf_obj = ns_ax_window_buffer_object (w);
+ if (NILP (buf_obj)) + if (NILP (buf_obj))
+ return @[]; + return @[];
+
-- (void)keyDown: (NSEvent *)theEvent
-{
- Mouse_HLInfo *hlinfo = MOUSE_HL_INFO (emacsframe);
+ struct buffer *b = XBUFFER (buf_obj); + struct buffer *b = XBUFFER (buf_obj);
+ ptrdiff_t vis_start = marker_position (w->start); + ptrdiff_t vis_start = marker_position (w->start);
+ ptrdiff_t vis_end = ns_ax_window_end_charpos (w, b); + ptrdiff_t vis_end = ns_ax_window_end_charpos (w, b);
@@ -1008,7 +1062,7 @@ index 932d209..078465a 100644
+ pos = span_end; + pos = span_end;
+ } + }
+ +
+ return [spans copy]; + return [[spans copy] autorelease];
+} +}
+ +
+@implementation EmacsAccessibilityInteractiveSpan +@implementation EmacsAccessibilityInteractiveSpan
@@ -1066,23 +1120,22 @@ index 932d209..078465a 100644
+ window being deleted between capture and execution. */ + window being deleted between capture and execution. */
+ if (!WINDOWP (lwin) || NILP (Fwindow_live_p (lwin))) + if (!WINDOWP (lwin) || NILP (Fwindow_live_p (lwin)))
+ return; + return;
+ /* block_input prevents SIGIO delivery while we modify point/buffer. + /* Use specpdl unwind protection so that block_input is always
+ It is safe here because this GCD block runs on the main thread + matched by unblock_input, even if Fselect_window signals. */
+ during the Cocoa run loop, outside of any Emacs event processing. */ + specpdl_ref count = SPECPDL_INDEX ();
+ record_unwind_protect_void (unblock_input);
+ block_input (); + block_input ();
+ record_unwind_current_buffer ();
+ Fselect_window (lwin, Qnil); + Fselect_window (lwin, Qnil);
+ struct window *w = XWINDOW (lwin); + struct window *w = XWINDOW (lwin);
+ struct buffer *b = XBUFFER (w->contents); + struct buffer *b = XBUFFER (w->contents);
+ struct buffer *oldb = current_buffer;
+ if (b != current_buffer) + if (b != current_buffer)
+ set_buffer_internal_1 (b); + set_buffer_internal_1 (b);
+ ptrdiff_t pos = target; + ptrdiff_t pos = target;
+ if (pos < BUF_BEGV (b)) pos = BUF_BEGV (b); + if (pos < BUF_BEGV (b)) pos = BUF_BEGV (b);
+ if (pos > BUF_ZV (b)) pos = BUF_ZV (b); + if (pos > BUF_ZV (b)) pos = BUF_ZV (b);
+ SET_PT_BOTH (pos, CHAR_TO_BYTE (pos)); + SET_PT_BOTH (pos, CHAR_TO_BYTE (pos));
+ if (b != oldb) + unbind_to (count, Qnil);
+ set_buffer_internal_1 (oldb);
+ unblock_input ();
+ }); + });
+} +}
+ +
@@ -2784,10 +2837,15 @@ index 932d209..078465a 100644
+ +
+/*****************************************************************************/ +/*****************************************************************************/
+/* Keyboard handling. */ +/* Keyboard handling. */
#define NS_KEYLOG 0 +#define NS_KEYLOG 0
+
- (void)keyDown: (NSEvent *)theEvent +- (void)keyDown: (NSEvent *)theEvent
@@ -8237,6 +10470,31 @@ - (void)windowDidBecomeKey /* for direct calls */ +{
+ Mouse_HLInfo *hlinfo = MOUSE_HL_INFO (emacsframe);
int code;
unsigned fnKeysym = 0;
static NSMutableArray *nsEvArray;
@@ -8237,6 +10447,31 @@ - (void)windowDidBecomeKey /* for direct calls */
XSETFRAME (event.frame_or_window, emacsframe); XSETFRAME (event.frame_or_window, emacsframe);
kbd_buffer_store_event (&event); kbd_buffer_store_event (&event);
ns_send_appdefined (-1); // Kick main loop ns_send_appdefined (-1); // Kick main loop
@@ -2819,7 +2877,7 @@ index 932d209..078465a 100644
} }
@@ -9474,6 +11732,322 @@ - (int) fullscreenState @@ -9474,6 +11709,332 @@ - (int) fullscreenState
return fs_state; return fs_state;
} }
@@ -3068,8 +3126,18 @@ index 932d209..078465a 100644
+ +
+- (NSRect)accessibilityBoundsForRange:(NSRange)range +- (NSRect)accessibilityBoundsForRange:(NSRange)range
+{ +{
+ /* Return cursor screen rect. AT tools call this with the + /* Delegate to the focused buffer element for accurate per-range
+ selectedTextRange to locate the insertion point. */ + geometry when possible. Fall back to the cached cursor rect
+ (set by ns_draw_phys_cursor) for Zoom and simple AT queries. */
+ id focused = [self accessibilityFocusedUIElement];
+ if ([focused isKindOfClass:[EmacsAccessibilityBuffer class]])
+ {
+ NSRect bufRect = [(EmacsAccessibilityBuffer *) focused
+ accessibilityFrameForRange:range];
+ if (!NSIsEmptyRect (bufRect))
+ return bufRect;
+ }
+
+ NSRect viewRect = lastAccessibilityCursorRect; + NSRect viewRect = lastAccessibilityCursorRect;
+ +
+ if (viewRect.size.width < 1) + if (viewRect.size.width < 1)
@@ -3142,7 +3210,7 @@ index 932d209..078465a 100644
@end /* EmacsView */ @end /* EmacsView */
@@ -11303,6 +13877,18 @@ Convert an X font name (XLFD) to an NS font name. @@ -11303,6 +13864,18 @@ Convert an X font name (XLFD) to an NS font name.
DEFSYM (Qns_drag_operation_generic, "ns-drag-operation-generic"); DEFSYM (Qns_drag_operation_generic, "ns-drag-operation-generic");
DEFSYM (Qns_handle_drag_motion, "ns-handle-drag-motion"); DEFSYM (Qns_handle_drag_motion, "ns-handle-drag-motion");

View File

@@ -131,7 +131,9 @@ THREADING MODEL
- postAccessibilityNotificationsForFrame: (full notify logic) - postAccessibilityNotificationsForFrame: (full notify logic)
- setAccessibilitySelectedTextRange: (SET_PT_BOTH, marker moves) - setAccessibilitySelectedTextRange: (SET_PT_BOTH, marker moves)
- setAccessibilityFocused: on EmacsAccessibilityInteractiveSpan - setAccessibilityFocused: on EmacsAccessibilityInteractiveSpan
(dispatches to main queue via dispatch_async) (dispatches to main queue via dispatch_async; uses specpdl
unwind protection so block_input is always matched by
unblock_input even if Fselect_window signals an error)
- ns_draw_phys_cursor partial update (lastAccessibilityCursorRect, - ns_draw_phys_cursor partial update (lastAccessibilityCursorRect,
UAZoomChangeFocus) UAZoomChangeFocus)
@@ -343,8 +345,9 @@ INTERACTIVE SPANS
and referenced directly -- no repeated intern() calls. and referenced directly -- no repeated intern() calls.
Each span is allocated, configured, added to the spans array, then Each span is allocated, configured, added to the spans array, then
released (the array retains it). Label priority: completion--string released (the array retains it). The function returns an autoreleased
> buffer substring > help-echo. immutable copy of the spans array. Label priority:
completion--string > buffer substring > help-echo.
Tab navigation: -accessibilityChildrenInNavigationOrder returns the Tab navigation: -accessibilityChildrenInNavigationOrder returns the
cached span array, rebuilt lazily when interactiveSpansDirty is set. cached span array, rebuilt lazily when interactiveSpansDirty is set.
@@ -381,9 +384,13 @@ ZOOM INTEGRATION
2. EmacsView -accessibilityBoundsForRange: / 2. EmacsView -accessibilityBoundsForRange: /
-accessibilityFrameForRange: -accessibilityFrameForRange:
AT tools (including Zoom) call these with the selectedTextRange AT tools (including Zoom) call these with the selectedTextRange
to locate the insertion point. The implementation returns the to locate the insertion point. The implementation first delegates
screen rect stored in lastAccessibilityCursorRect, with a minimum to the focused EmacsAccessibilityBuffer element for accurate
size of 1x8 pixels. The legacy parameterized-attribute API per-range geometry via its accessibilityFrameForRange: method.
If the buffer element returns an empty rect (no valid window or
glyph data), the fallback uses the cached cursor rect stored in
lastAccessibilityCursorRect (minimum size 1x8 pixels). The legacy
parameterized-attribute API
(NSAccessibilityBoundsForRangeParameterizedAttribute) is supported (NSAccessibilityBoundsForRangeParameterizedAttribute) is supported
via -accessibilityAttributeValue:forParameter: for older AT via -accessibilityAttributeValue:forParameter: for older AT
clients. clients.
@@ -479,6 +486,13 @@ KNOWN LIMITATIONS
- GNUstep is explicitly excluded (#ifdef NS_IMPL_COCOA). GNUstep - GNUstep is explicitly excluded (#ifdef NS_IMPL_COCOA). GNUstep
has a different accessibility model and requires separate work. has a different accessibility model and requires separate work.
- Line navigation detection (ns_ax_event_is_line_nav_key) checks
raw key codes (C-n = 14, C-p = 16, Tab = 9, backtab symbol).
Users who remap keys to navigation commands (e.g. C-j -> next-line)
will not get forced line-granularity announcements for those
bindings. A future improvement would inspect Vthis_command
against known navigation command symbols instead.
- UAZoomChangeFocus always uses kUAZoomFocusTypeInsertionPoint - UAZoomChangeFocus always uses kUAZoomFocusTypeInsertionPoint
regardless of cursor style (box, bar, hbar). This is cosmetically regardless of cursor style (box, bar, hbar). This is cosmetically
imprecise but functionally correct. imprecise but functionally correct.