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:
@@ -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>
|
||||
Date: Fri, 27 Feb 2026 14:54:57 +0100
|
||||
Subject: [PATCH] ns: implement VoiceOver accessibility
|
||||
Date: Fri, 27 Feb 2026 15:09:15 +0100
|
||||
Subject: [PATCH] ns: implement VoiceOver accessibility for macOS
|
||||
|
||||
All AX protocol methods now dispatch_sync to the main thread,
|
||||
including accessibilityFrame, accessibilityLabel, accessibilityRole,
|
||||
accessibilityRoleDescription, accessibilityPlaceholderValue,
|
||||
isAccessibilityFocused on EmacsAccessibilityBuffer, and
|
||||
accessibilityValue/accessibilityFrame/accessibilityLabel on
|
||||
EmacsAccessibilityModeLine. setAccessibilitySelectedTextRange
|
||||
uses record_unwind_current_buffer for exception safety.
|
||||
Add comprehensive macOS VoiceOver accessibility support to the NS
|
||||
(Cocoa) port. Before this patch, Emacs exposed only a minimal,
|
||||
largely broken accessibility interface to macOS assistive technology
|
||||
clients.
|
||||
|
||||
* 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 +
|
||||
src/nsterm.h | 108 ++
|
||||
src/nsterm.m | 2870 +++++++++++++++++++++++++++++++++++++++++++++++---
|
||||
3 files changed, 2847 insertions(+), 142 deletions(-)
|
||||
src/nsterm.m | 2869 +++++++++++++++++++++++++++++++++++++++++++++++---
|
||||
3 files changed, 2840 insertions(+), 148 deletions(-)
|
||||
|
||||
diff --git a/etc/NEWS b/etc/NEWS
|
||||
index 7367e3c..0e4480a 100644
|
||||
index 7367e3cc..0e4480ad 100644
|
||||
--- a/etc/NEWS
|
||||
+++ b/etc/NEWS
|
||||
@@ -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).
|
||||
We lost macOS dictation in v30 when migrating to NSTextInputClient.
|
||||
diff --git a/src/nsterm.h b/src/nsterm.h
|
||||
index 7c1ee4c..6455547 100644
|
||||
index 7c1ee4cf..542e7d59 100644
|
||||
--- a/src/nsterm.h
|
||||
+++ b/src/nsterm.h
|
||||
@@ -453,6 +453,100 @@ enum ns_return_frame_mode
|
||||
@@ -79,7 +149,7 @@ index 7c1ee4c..6455547 100644
|
||||
+} ns_ax_visible_run;
|
||||
+
|
||||
+/* Virtual AXTextArea element — one per visible Emacs window (buffer). */
|
||||
+@interface EmacsAccessibilityBuffer : EmacsAccessibilityElement
|
||||
+@interface EmacsAccessibilityBuffer : EmacsAccessibilityElement <NSAccessibility>
|
||||
+{
|
||||
+ ns_ax_visible_run *visibleRuns;
|
||||
+ NSUInteger visibleRunCount;
|
||||
@@ -174,14 +244,14 @@ index 7c1ee4c..6455547 100644
|
||||
|
||||
|
||||
diff --git a/src/nsterm.m b/src/nsterm.m
|
||||
index 932d209..078465a 100644
|
||||
index 932d209f..ea2de6f2 100644
|
||||
--- a/src/nsterm.m
|
||||
+++ b/src/nsterm.m
|
||||
@@ -46,6 +46,7 @@ Updated by Christian Limpach (chris@nice.ch)
|
||||
#include "blockinput.h"
|
||||
#include "sysselect.h"
|
||||
#include "nsterm.h"
|
||||
+#include "intervals.h"
|
||||
+#include "intervals.h" /* TEXT_PROP_MEANS_INVISIBLE */
|
||||
#include "systime.h"
|
||||
#include "character.h"
|
||||
#include "xwidget.h"
|
||||
@@ -240,7 +310,7 @@ index 932d209..078465a 100644
|
||||
ns_focus (f, NULL, 0);
|
||||
|
||||
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. */
|
||||
-- (void) setWindowClosing: (BOOL)closing
|
||||
-{
|
||||
- NSTRACE ("[EmacsView setWindowClosing:%d]", closing);
|
||||
+/* Build accessibility text for window W, skipping invisible text.
|
||||
+ Populates *OUT_START with the buffer start charpos.
|
||||
+ Populates *OUT_RUNS with an array of visible runs and *OUT_NRUNS
|
||||
+ 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;
|
||||
-}
|
||||
+ if (!w || !WINDOW_LEAF_P (w))
|
||||
+ {
|
||||
+ *out_start = 0;
|
||||
+ return @"";
|
||||
+ }
|
||||
+static NSString *
|
||||
+ns_ax_buffer_text (struct window *w, ptrdiff_t *out_start,
|
||||
+ ns_ax_visible_run **out_runs, NSUInteger *out_nruns)
|
||||
+{
|
||||
+ *out_runs = NULL;
|
||||
+ *out_nruns = 0;
|
||||
|
||||
+ struct buffer *b = XBUFFER (w->contents);
|
||||
+ if (!b)
|
||||
+ if (!w || !WINDOW_LEAF_P (w))
|
||||
+ {
|
||||
+ *out_start = 0;
|
||||
+ return @"";
|
||||
@@ -297,34 +361,41 @@ index 932d209..078465a 100644
|
||||
-- (void)dealloc
|
||||
-{
|
||||
- NSTRACE ("[EmacsView dealloc]");
|
||||
+ ptrdiff_t begv = BUF_BEGV (b);
|
||||
+ ptrdiff_t zv = BUF_ZV (b);
|
||||
+ struct buffer *b = XBUFFER (w->contents);
|
||||
+ if (!b)
|
||||
+ {
|
||||
+ *out_start = 0;
|
||||
+ return @"";
|
||||
+ }
|
||||
|
||||
- /* Clear the view resize notification. */
|
||||
- [[NSNotificationCenter defaultCenter]
|
||||
- removeObserver:self
|
||||
- name:NSViewFrameDidChangeNotification
|
||||
- object:nil];
|
||||
+ *out_start = begv;
|
||||
+ ptrdiff_t begv = BUF_BEGV (b);
|
||||
+ ptrdiff_t zv = BUF_ZV (b);
|
||||
|
||||
- if (fs_state == FULLSCREEN_BOTH)
|
||||
- [nonfs_window release];
|
||||
+ if (zv <= begv)
|
||||
+ return @"";
|
||||
+ *out_start = begv;
|
||||
|
||||
-#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MIN_REQUIRED >= 101400
|
||||
- /* Release layer and menu */
|
||||
- EmacsLayer *layer = (EmacsLayer *)[self layer];
|
||||
- [layer release];
|
||||
-#endif
|
||||
+ if (zv <= begv)
|
||||
+ return @"";
|
||||
|
||||
- [[self menu] release];
|
||||
- [super dealloc];
|
||||
-}
|
||||
+ specpdl_ref count = SPECPDL_INDEX ();
|
||||
+ record_unwind_current_buffer ();
|
||||
+ if (b != current_buffer)
|
||||
+ set_buffer_internal_1 (b);
|
||||
|
||||
- [[self menu] release];
|
||||
- [super dealloc];
|
||||
-}
|
||||
+ /* First pass: count visible runs to allocate the mapping array. */
|
||||
+ NSUInteger run_capacity = 64;
|
||||
+ ns_ax_visible_run *runs = xmalloc (run_capacity
|
||||
@@ -332,14 +403,19 @@ index 932d209..078465a 100644
|
||||
+ NSUInteger nruns = 0;
|
||||
+ NSUInteger ax_offset = 0;
|
||||
|
||||
+ NSMutableString *result = [NSMutableString string];
|
||||
+ ptrdiff_t pos = begv;
|
||||
|
||||
-/* Called on font panel selection. */
|
||||
-- (void) changeFont: (id) sender
|
||||
-{
|
||||
- struct font *font = FRAME_OUTPUT_DATA (emacsframe)->font;
|
||||
- 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)
|
||||
+ {
|
||||
+ /* Check invisible property (text properties + overlays).
|
||||
@@ -358,18 +434,15 @@ index 932d209..078465a 100644
|
||||
+ continue;
|
||||
+ }
|
||||
|
||||
-#ifdef NS_IMPL_GNUSTEP
|
||||
- nsfont = ((struct nsfont_info *) font)->nsfont;
|
||||
-#else
|
||||
- nsfont = (NSFont *) macfont_get_nsctfont (font);
|
||||
-#endif
|
||||
- if (!font_panel_active)
|
||||
- return;
|
||||
+ /* Find end of this visible run: where invisible property changes. */
|
||||
+ Lisp_Object next = Fnext_single_char_property_change (
|
||||
+ make_fixnum (pos), Qinvisible, Qnil, make_fixnum (zv));
|
||||
+ ptrdiff_t run_end = FIXNUMP (next) ? XFIXNUM (next) : zv;
|
||||
|
||||
- if (!font_panel_active)
|
||||
- return;
|
||||
- if (font_panel_result)
|
||||
- [font_panel_result release];
|
||||
+ /* Cap total text at NS_AX_TEXT_CAP. */
|
||||
+ ptrdiff_t run_len = run_end - pos;
|
||||
+ if (ax_offset + (NSUInteger) run_len > NS_AX_TEXT_CAP)
|
||||
@@ -401,44 +474,52 @@ index 932d209..078465a 100644
|
||||
+ runs[nruns].ax_length = ns_len;
|
||||
+ nruns++;
|
||||
|
||||
- if (font_panel_result)
|
||||
- [font_panel_result release];
|
||||
- font_panel_result = (NSFont *) [sender convertFont: nsfont];
|
||||
+ ax_offset += ns_len;
|
||||
+ pos = run_end;
|
||||
+ }
|
||||
|
||||
- font_panel_result = (NSFont *) [sender convertFont: nsfont];
|
||||
+ unbind_to (count, Qnil);
|
||||
|
||||
- if (font_panel_result)
|
||||
- [font_panel_result retain];
|
||||
+ *out_runs = runs;
|
||||
+ *out_nruns = nruns;
|
||||
+ return result;
|
||||
+}
|
||||
+ unbind_to (count, Qnil);
|
||||
|
||||
-#ifndef NS_IMPL_COCOA
|
||||
- font_panel_active = NO;
|
||||
- [NSApp stop: self];
|
||||
-#endif
|
||||
+ *out_runs = runs;
|
||||
+ *out_nruns = nruns;
|
||||
+ return result;
|
||||
}
|
||||
|
||||
-#ifdef NS_IMPL_COCOA
|
||||
-- (void) noteUserSelectedFont
|
||||
+
|
||||
+/* ---- Helper: extract mode line text from glyph rows ---- */
|
||||
+
|
||||
+static NSString *
|
||||
+ns_ax_mode_line_text (struct window *w)
|
||||
+{
|
||||
{
|
||||
- font_panel_active = NO;
|
||||
+ if (!w || !w->current_matrix)
|
||||
+ return @"";
|
||||
+
|
||||
|
||||
- /* If no font was previously selected, use the currently selected
|
||||
- font. */
|
||||
+ struct glyph_matrix *matrix = w->current_matrix;
|
||||
+ NSMutableString *text = [NSMutableString string];
|
||||
+
|
||||
|
||||
- if (!font_panel_result && FRAME_FONT (emacsframe))
|
||||
+ for (int i = 0; i < matrix->nrows; i++)
|
||||
+ {
|
||||
{
|
||||
- font_panel_result
|
||||
- = macfont_get_nsctfont (FRAME_FONT (emacsframe));
|
||||
+ struct glyph_row *row = matrix->rows + i;
|
||||
+ if (!row->enabled_p || !row->mode_line_p)
|
||||
+ continue;
|
||||
+
|
||||
|
||||
- if (font_panel_result)
|
||||
- [font_panel_result retain];
|
||||
+ struct glyph *g = row->glyphs[TEXT_AREA];
|
||||
+ struct glyph *end = g + row->used[TEXT_AREA];
|
||||
+ for (; g < end; g++)
|
||||
@@ -450,12 +531,13 @@ index 932d209..078465a 100644
|
||||
+ length:1]];
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
-
|
||||
- [NSApp stop: self];
|
||||
+ return text;
|
||||
}
|
||||
|
||||
-#ifdef NS_IMPL_COCOA
|
||||
-- (void) noteUserSelectedFont
|
||||
-- (void) noteUserCancelledSelection
|
||||
+
|
||||
+/* ---- Helper: screen rect for a character range via glyph matrix ---- */
|
||||
+
|
||||
@@ -468,31 +550,28 @@ index 932d209..078465a 100644
|
||||
+ if (!w || !w->current_matrix || !view)
|
||||
+ return NSZeroRect;
|
||||
|
||||
- /* If no font was previously selected, use the currently selected
|
||||
- font. */
|
||||
- if (font_panel_result)
|
||||
- [font_panel_result release];
|
||||
- font_panel_result = nil;
|
||||
+ /* charpos_start and charpos_len are already in buffer charpos
|
||||
+ space — the caller maps AX string indices through
|
||||
+ charposForAccessibilityIndex which handles invisible text. */
|
||||
+ ptrdiff_t cp_start = charpos_start;
|
||||
+ 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;
|
||||
+ NSRect result = NSZeroRect;
|
||||
+ BOOL found = NO;
|
||||
+
|
||||
+ for (int i = 0; i < matrix->nrows; i++)
|
||||
{
|
||||
- font_panel_result
|
||||
- = macfont_get_nsctfont (FRAME_FONT (emacsframe));
|
||||
+ {
|
||||
+ struct glyph_row *row = matrix->rows + i;
|
||||
+ if (!row->enabled_p || row->mode_line_p)
|
||||
+ continue;
|
||||
+ if (!row->displays_text_p && !row->ends_at_zv_p)
|
||||
+ continue;
|
||||
|
||||
- if (font_panel_result)
|
||||
- [font_panel_result retain];
|
||||
+
|
||||
+ ptrdiff_t row_start = MATRIX_ROW_START_CHARPOS (row);
|
||||
+ ptrdiff_t row_end = MATRIX_ROW_END_CHARPOS (row);
|
||||
+
|
||||
@@ -517,9 +596,8 @@ index 932d209..078465a 100644
|
||||
+ else
|
||||
+ result = NSUnionRect (result, rowRect);
|
||||
+ }
|
||||
}
|
||||
|
||||
- [NSApp stop: self];
|
||||
+ }
|
||||
+
|
||||
+ if (!found)
|
||||
+ return NSZeroRect;
|
||||
+
|
||||
@@ -537,8 +615,9 @@ index 932d209..078465a 100644
|
||||
+ NSRect winRect = [view convertRect:result toView:nil];
|
||||
+ return [[view window] convertRectToScreen:winRect];
|
||||
}
|
||||
-#endif
|
||||
|
||||
-- (void) noteUserCancelledSelection
|
||||
-- (Lisp_Object) showFontPanel
|
||||
+/* AX enum numeric compatibility for NSAccessibility notifications.
|
||||
+ Values match WebKit AXObjectCacheMac fallback enums
|
||||
+ (AXTextStateChangeType / AXTextEditType / AXTextSelectionDirection /
|
||||
@@ -561,35 +640,6 @@ index 932d209..078465a 100644
|
||||
+ 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,
|
||||
+ ptrdiff_t *out_start,
|
||||
+ ptrdiff_t *out_end)
|
||||
@@ -903,10 +953,14 @@ index 932d209..078465a 100644
|
||||
|
||||
-/*****************************************************************************/
|
||||
-/* Keyboard handling. */
|
||||
-#define NS_KEYLOG 0
|
||||
+ Lisp_Object buf_obj = ns_ax_window_buffer_object (w);
|
||||
+ if (NILP (buf_obj))
|
||||
+ return @[];
|
||||
+
|
||||
|
||||
-- (void)keyDown: (NSEvent *)theEvent
|
||||
-{
|
||||
- Mouse_HLInfo *hlinfo = MOUSE_HL_INFO (emacsframe);
|
||||
+ struct buffer *b = XBUFFER (buf_obj);
|
||||
+ ptrdiff_t vis_start = marker_position (w->start);
|
||||
+ ptrdiff_t vis_end = ns_ax_window_end_charpos (w, b);
|
||||
@@ -1008,7 +1062,7 @@ index 932d209..078465a 100644
|
||||
+ pos = span_end;
|
||||
+ }
|
||||
+
|
||||
+ return [spans copy];
|
||||
+ return [[spans copy] autorelease];
|
||||
+}
|
||||
+
|
||||
+@implementation EmacsAccessibilityInteractiveSpan
|
||||
@@ -1066,23 +1120,22 @@ index 932d209..078465a 100644
|
||||
+ window being deleted between capture and execution. */
|
||||
+ if (!WINDOWP (lwin) || NILP (Fwindow_live_p (lwin)))
|
||||
+ return;
|
||||
+ /* block_input prevents SIGIO delivery while we modify point/buffer.
|
||||
+ It is safe here because this GCD block runs on the main thread
|
||||
+ during the Cocoa run loop, outside of any Emacs event processing. */
|
||||
+ /* Use specpdl unwind protection so that block_input is always
|
||||
+ matched by unblock_input, even if Fselect_window signals. */
|
||||
+ specpdl_ref count = SPECPDL_INDEX ();
|
||||
+ record_unwind_protect_void (unblock_input);
|
||||
+ block_input ();
|
||||
+ record_unwind_current_buffer ();
|
||||
+ Fselect_window (lwin, Qnil);
|
||||
+ struct window *w = XWINDOW (lwin);
|
||||
+ struct buffer *b = XBUFFER (w->contents);
|
||||
+ struct buffer *oldb = current_buffer;
|
||||
+ if (b != current_buffer)
|
||||
+ set_buffer_internal_1 (b);
|
||||
+ ptrdiff_t pos = target;
|
||||
+ if (pos < BUF_BEGV (b)) pos = BUF_BEGV (b);
|
||||
+ if (pos > BUF_ZV (b)) pos = BUF_ZV (b);
|
||||
+ SET_PT_BOTH (pos, CHAR_TO_BYTE (pos));
|
||||
+ if (b != oldb)
|
||||
+ set_buffer_internal_1 (oldb);
|
||||
+ unblock_input ();
|
||||
+ unbind_to (count, Qnil);
|
||||
+ });
|
||||
+}
|
||||
+
|
||||
@@ -2784,10 +2837,15 @@ index 932d209..078465a 100644
|
||||
+
|
||||
+/*****************************************************************************/
|
||||
+/* Keyboard handling. */
|
||||
#define NS_KEYLOG 0
|
||||
|
||||
- (void)keyDown: (NSEvent *)theEvent
|
||||
@@ -8237,6 +10470,31 @@ - (void)windowDidBecomeKey /* for direct calls */
|
||||
+#define NS_KEYLOG 0
|
||||
+
|
||||
+- (void)keyDown: (NSEvent *)theEvent
|
||||
+{
|
||||
+ 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);
|
||||
kbd_buffer_store_event (&event);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -3068,8 +3126,18 @@ index 932d209..078465a 100644
|
||||
+
|
||||
+- (NSRect)accessibilityBoundsForRange:(NSRange)range
|
||||
+{
|
||||
+ /* Return cursor screen rect. AT tools call this with the
|
||||
+ selectedTextRange to locate the insertion point. */
|
||||
+ /* Delegate to the focused buffer element for accurate per-range
|
||||
+ 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;
|
||||
+
|
||||
+ if (viewRect.size.width < 1)
|
||||
@@ -3142,7 +3210,7 @@ index 932d209..078465a 100644
|
||||
@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_handle_drag_motion, "ns-handle-drag-motion");
|
||||
|
||||
|
||||
@@ -131,7 +131,9 @@ THREADING MODEL
|
||||
- postAccessibilityNotificationsForFrame: (full notify logic)
|
||||
- setAccessibilitySelectedTextRange: (SET_PT_BOTH, marker moves)
|
||||
- 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,
|
||||
UAZoomChangeFocus)
|
||||
|
||||
@@ -343,8 +345,9 @@ INTERACTIVE SPANS
|
||||
and referenced directly -- no repeated intern() calls.
|
||||
|
||||
Each span is allocated, configured, added to the spans array, then
|
||||
released (the array retains it). Label priority: completion--string
|
||||
> buffer substring > help-echo.
|
||||
released (the array retains it). The function returns an autoreleased
|
||||
immutable copy of the spans array. Label priority:
|
||||
completion--string > buffer substring > help-echo.
|
||||
|
||||
Tab navigation: -accessibilityChildrenInNavigationOrder returns the
|
||||
cached span array, rebuilt lazily when interactiveSpansDirty is set.
|
||||
@@ -381,9 +384,13 @@ ZOOM INTEGRATION
|
||||
2. EmacsView -accessibilityBoundsForRange: /
|
||||
-accessibilityFrameForRange:
|
||||
AT tools (including Zoom) call these with the selectedTextRange
|
||||
to locate the insertion point. The implementation returns the
|
||||
screen rect stored in lastAccessibilityCursorRect, with a minimum
|
||||
size of 1x8 pixels. The legacy parameterized-attribute API
|
||||
to locate the insertion point. The implementation first delegates
|
||||
to the focused EmacsAccessibilityBuffer element for accurate
|
||||
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
|
||||
via -accessibilityAttributeValue:forParameter: for older AT
|
||||
clients.
|
||||
@@ -479,6 +486,13 @@ KNOWN LIMITATIONS
|
||||
- GNUstep is explicitly excluded (#ifdef NS_IMPL_COCOA). GNUstep
|
||||
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
|
||||
regardless of cursor style (box, bar, hbar). This is cosmetically
|
||||
imprecise but functionally correct.
|
||||
|
||||
Reference in New Issue
Block a user