From f37e06b00f545f08b65278f56bac018616fe34e1 Mon Sep 17 00:00:00 2001 From: Daneel Date: Fri, 27 Feb 2026 07:38:18 +0100 Subject: [PATCH] patches: rebase on upstream f0dbe25 + fix NSRange return type --- ...oundsForRange-for-macOS-Zoom-cursor-.patch | 1482 +++++++++-------- 1 file changed, 747 insertions(+), 735 deletions(-) diff --git a/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch b/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch index d5bed2c..d1722cf 100644 --- a/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch +++ b/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch @@ -1,31 +1,38 @@ -From 7970024f17d83610a4fd58d7ab135b2c71783049 Mon Sep 17 00:00:00 2001 +From 179fedabf11c99b33feb0282fd85ec2704bb795f Mon Sep 17 00:00:00 2001 From: Martin Sukany -Date: Fri, 27 Feb 2026 07:31:34 +0100 +Date: Fri, 27 Feb 2026 07:38:05 +0100 Subject: [PATCH] ns: implement VoiceOver accessibility (AXBoundsForRange, line nav, completions) --- - src/nsterm.h | 47 +- - src/nsterm.m | 2005 +++++++++++++++++++++++++++++++++++++++----------- - 2 files changed, 1628 insertions(+), 424 deletions(-) + src/nsterm.h | 71 ++ + src/nsterm.m | 2194 ++++++++++++++++++++++++++++++++++++++++++++++---- + 2 files changed, 2123 insertions(+), 142 deletions(-) diff --git a/src/nsterm.h b/src/nsterm.h -index 4f9a1b0..22828f2 100644 +index 7c1ee4c..22828f2 100644 --- a/src/nsterm.h +++ b/src/nsterm.h -@@ -462,21 +462,49 @@ enum ns_return_frame_mode - #ifdef NS_IMPL_COCOA - @class EmacsView; - --/* Base class for virtual accessibility elements attached to EmacsView. */ -+/* Base class for virtual accessibility elements attached to EmacsView. */ - @interface EmacsAccessibilityElement : NSAccessibilityElement - @property (nonatomic, unsafe_unretained) EmacsView *emacsView; - @property (nonatomic, assign) struct window *emacsWindow; - - (NSRect)screenRectFromEmacsX:(int)x y:(int)y width:(int)w height:(int)h; +@@ -453,6 +453,62 @@ enum ns_return_frame_mode @end --/* Virtual AXTextArea element — one per visible Emacs window (buffer). */ + ++/* ========================================================================== ++ ++ Accessibility virtual elements (macOS / Cocoa only) ++ ++ ========================================================================== */ ++ ++#ifdef NS_IMPL_COCOA ++@class EmacsView; ++ ++/* Base class for virtual accessibility elements attached to EmacsView. */ ++@interface EmacsAccessibilityElement : NSAccessibilityElement ++@property (nonatomic, unsafe_unretained) EmacsView *emacsView; ++@property (nonatomic, assign) struct window *emacsWindow; ++- (NSRect)screenRectFromEmacsX:(int)x y:(int)y width:(int)w height:(int)h; ++@end ++ +/* A visible run: maps a contiguous range of accessibility indices + to a contiguous range of buffer character positions. Invisible + text is skipped, so ax_start values are consecutive across runs @@ -39,7 +46,7 @@ index 4f9a1b0..22828f2 100644 +} ns_ax_visible_run; + +/* Virtual AXTextArea element — one per visible Emacs window (buffer). */ - @interface EmacsAccessibilityBuffer : EmacsAccessibilityElement ++@interface EmacsAccessibilityBuffer : EmacsAccessibilityElement +{ + ns_ax_visible_run *visibleRuns; + NSUInteger visibleRunCount; @@ -47,12 +54,8 @@ index 4f9a1b0..22828f2 100644 +@property (nonatomic, retain) NSString *cachedText; +@property (nonatomic, assign) ptrdiff_t cachedTextModiff; +@property (nonatomic, assign) ptrdiff_t cachedTextStart; - @property (nonatomic, assign) ptrdiff_t cachedModiff; - @property (nonatomic, assign) ptrdiff_t cachedPoint; --@property (nonatomic, assign) Lisp_Object cachedSelectedWindow; --- (void) -- postAccessibilityUpdatesForWindow:(struct window *)w -- frame:(struct frame *)f; ++@property (nonatomic, assign) ptrdiff_t cachedModiff; ++@property (nonatomic, assign) ptrdiff_t cachedPoint; +@property (nonatomic, assign) BOOL cachedMarkActive; +@property (nonatomic, copy) NSString *cachedCompletionAnnouncement; +@property (nonatomic, assign) ptrdiff_t cachedCompletionOverlayStart; @@ -66,13 +69,19 @@ index 4f9a1b0..22828f2 100644 + +/* Virtual AXStaticText element — one per mode line. */ +@interface EmacsAccessibilityModeLine : EmacsAccessibilityElement - @end - #endif /* NS_IMPL_COCOA */ ++@end ++#endif /* NS_IMPL_COCOA */ ++ ++ + /* ========================================================================== -@@ -501,6 +529,12 @@ enum ns_return_frame_mode + The main Emacs view +@@ -471,6 +527,14 @@ enum ns_return_frame_mode + #ifdef NS_IMPL_COCOA + char *old_title; BOOL maximizing_resize; - NSMutableArray *accessibilityElements; - Lisp_Object lastSelectedWindow; ++ NSMutableArray *accessibilityElements; ++ Lisp_Object lastSelectedWindow; + Lisp_Object lastRootWindow; + BOOL accessibilityTreeValid; + BOOL accessibilityUpdating; @@ -82,19 +91,25 @@ index 4f9a1b0..22828f2 100644 #endif BOOL font_panel_active; NSFont *font_panel_result; -@@ -562,6 +596,7 @@ enum ns_return_frame_mode - #ifdef NS_IMPL_COCOA - /* Accessibility support. */ - - (void)rebuildAccessibilityTree; +@@ -528,6 +592,13 @@ enum ns_return_frame_mode + - (void)windowWillExitFullScreen; + - (void)windowDidExitFullScreen; + - (void)windowDidBecomeKey; ++ ++#ifdef NS_IMPL_COCOA ++/* Accessibility support. */ ++- (void)rebuildAccessibilityTree; +- (void)invalidateAccessibilityTree; - - (void)postAccessibilityUpdates; - #endif ++- (void)postAccessibilityUpdates; ++#endif @end + + diff --git a/src/nsterm.m b/src/nsterm.m -index e67edbe..cfc5b4c 100644 +index 932d209..ed2b5e6 100644 --- a/src/nsterm.m +++ b/src/nsterm.m -@@ -3237,6 +3237,37 @@ Note that CURSOR_WIDTH is meaningful only for (h)bar cursors. +@@ -3232,6 +3232,37 @@ 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)); @@ -132,13 +147,21 @@ index e67edbe..cfc5b4c 100644 ns_focus (f, NULL, 0); NSGraphicsContext *ctx = [NSGraphicsContext currentContext]; -@@ -6860,172 +6891,174 @@ Accessibility virtual elements (macOS / Cocoa only) +@@ -6849,219 +6880,1784 @@ - (BOOL)fulfillService: (NSString *)name withArg: (NSString *)arg - #ifdef NS_IMPL_COCOA + /* ========================================================================== --/* ---- Helper: extract visible text from glyph rows of a window ---- */ +- EmacsView implementation ++ Accessibility virtual elements (macOS / Cocoa only) + + ========================================================================== */ + ++#ifdef NS_IMPL_COCOA + +-@implementation EmacsView +/* ---- Helper: extract buffer text for accessibility ---- */ -+ + +-- (void)windowDidEndLiveResize:(NSNotification *)notification +/* Maximum characters exposed via accessibilityValue. */ +#define NS_AX_TEXT_CAP 100000 + @@ -147,78 +170,35 @@ index e67edbe..cfc5b4c 100644 + 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_text_from_glyph_rows (struct window *w) ++static NSString * +ns_ax_buffer_text (struct window *w, ptrdiff_t *out_start, + ns_ax_visible_run **out_runs, NSUInteger *out_nruns) { -- if (!w || !w->current_matrix) -- return @""; -- -- struct glyph_matrix *matrix = w->current_matrix; -- NSMutableString *text = [NSMutableString stringWithCapacity:4096]; -- int nrows = matrix->nrows; +- [self updateFramePosition]; + *out_runs = NULL; + *out_nruns = 0; - -- for (int i = 0; i < nrows; i++) ++ + if (!w || !WINDOW_LEAF_P (w)) - { -- 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; ++ { + *out_start = 0; + return @""; + } - -- struct glyph *glyph = row->glyphs[TEXT_AREA]; -- struct glyph *end = glyph + row->used[TEXT_AREA]; ++ + struct buffer *b = XBUFFER (w->contents); + if (!b) + { + *out_start = 0; + return @""; + } - -- for (; glyph < end; glyph++) -- { -- if (glyph->type == CHAR_GLYPH && !glyph->padding_p) -- { -- unsigned ch = glyph->u.ch; -- if (ch == '\n' || ch == '\r') -- continue; /* row boundary handles newlines */ -- if (ch >= 32) -- { -- unichar uch = (unichar)ch; -- [text appendString:[NSString stringWithCharacters:&uch -- length:1]]; -- } -- } -- } ++ + ptrdiff_t begv = BUF_BEGV (b); + ptrdiff_t zv = BUF_ZV (b); - -- /* Add newline between rows unless this is the last displayed row. */ -- if (i + 1 < nrows) -- { -- struct glyph_row *next = matrix->rows + i + 1; -- if (next->enabled_p && (next->displays_text_p || next->ends_at_zv_p) -- && !next->mode_line_p) -- [text appendString:@"\n"]; -- } -- } ++ + *out_start = begv; - -- /* Cap at 32KB */ -- if ([text length] > 32768) -- return [text substringToIndex:32768]; ++ + if (zv <= begv) + return @""; - -- return text; --} ++ + struct buffer *oldb = current_buffer; + if (b != current_buffer) + set_buffer_internal_1 (b); @@ -252,13 +232,12 @@ index e67edbe..cfc5b4c 100644 + pos = FIXNUMP (next) ? XFIXNUM (next) : zv; + continue; + } - ++ + /* 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; - --/* ---- Row geometry helpers ---- */ ++ + /* 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) @@ -289,119 +268,42 @@ index e67edbe..cfc5b4c 100644 + runs[nruns].ax_start = ax_offset; + runs[nruns].ax_length = ns_len; + nruns++; - --/* Count the number of visible text rows (excluding mode line). */ --static int --ns_ax_visible_row_count (struct window *w) --{ -- if (!w || !w->current_matrix) -- return 0; -- struct glyph_matrix *matrix = w->current_matrix; -- int count = 0; -- for (int i = 0; i < matrix->nrows; i++) -- { -- struct glyph_row *row = matrix->rows + i; -- if (row->enabled_p && !row->mode_line_p -- && (row->displays_text_p || row->ends_at_zv_p)) -- count++; ++ + ax_offset += ns_len; + pos = run_end; - } -- return count; --} - --/* Map a character index (within the glyph-extracted text) to a visual -- row number (0-based, text rows only). */ --static int --ns_ax_line_for_index (struct window *w, NSUInteger idx) --{ -- if (!w || !w->current_matrix) -- return 0; -- struct glyph_matrix *matrix = w->current_matrix; -- NSUInteger pos = 0; -- int line = 0; ++ } ++ + if (b != oldb) + set_buffer_internal_1 (oldb); - -- for (int i = 0; i < matrix->nrows; i++) -- { -- 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; ++ + *out_runs = runs; + *out_nruns = nruns; + return result; -+} + } -- /* Count characters in this row. */ -- int row_chars = 0; -- struct glyph *g = row->glyphs[TEXT_AREA]; -- struct glyph *gend = g + row->used[TEXT_AREA]; -- for (; g < gend; g++) -- { -- if (g->type == CHAR_GLYPH && !g->padding_p) -- { -- unsigned ch = g->u.ch; -- if (ch != '\n' && ch != '\r' && (ch >= 32)) -- row_chars++; -- } -- } - -- NSUInteger row_end = pos + row_chars + 1; /* +1 for newline */ -- if (idx < row_end) -- return line; -- pos = row_end; -- line++; -- } -- return MAX(0, line - 1); --} +-/* Needed to inform when window closed from lisp. */ +-- (void) setWindowClosing: (BOOL)closing ++ +/* ---- Helper: extract mode line text from glyph rows ---- */ - --/* Return character range for a given visual line number. */ --static NSRange --ns_ax_range_for_line (struct window *w, int target_line) ++ +static NSString * +ns_ax_mode_line_text (struct window *w) { - if (!w || !w->current_matrix) -- return NSMakeRange(0, 0); +- NSTRACE ("[EmacsView setWindowClosing:%d]", closing); ++ if (!w || !w->current_matrix) + return @""; -+ - struct glyph_matrix *matrix = w->current_matrix; -- NSUInteger pos = 0; -- int line = 0; -+ NSMutableString *text = [NSMutableString string]; - for (int i = 0; i < matrix->nrows; i++) - { - 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; +- windowClosing = closing; ++ struct glyph_matrix *matrix = w->current_matrix; ++ NSMutableString *text = [NSMutableString string]; ++ ++ for (int i = 0; i < matrix->nrows; i++) ++ { ++ struct glyph_row *row = matrix->rows + i; + if (!row->enabled_p || !row->mode_line_p) + continue; - -- int row_chars = 0; - struct glyph *g = row->glyphs[TEXT_AREA]; -- struct glyph *gend = g + row->used[TEXT_AREA]; -- for (; g < gend; g++) -- { -- if (g->type == CHAR_GLYPH && !g->padding_p) -- { -- unsigned ch = g->u.ch; -- if (ch != '\n' && ch != '\r' && (ch >= 32)) -- row_chars++; -- } -- } -- -- if (line == target_line) -- return NSMakeRange(pos, row_chars); -- -- pos += row_chars + 1; /* +1 for newline */ -- line++; ++ ++ struct glyph *g = row->glyphs[TEXT_AREA]; + struct glyph *end = g + row->used[TEXT_AREA]; + for (; g < end; g++) + { @@ -412,80 +314,55 @@ index e67edbe..cfc5b4c 100644 + length:1]]; + } + } - } -- return NSMakeRange(NSNotFound, 0); ++ } + return text; } --/* Compute screen rect for a character range by unioning glyph row rects. */ -+ + +-- (void)dealloc +/* ---- Helper: screen rect for a character range via glyph matrix ---- */ + - static NSRect --ns_ax_frame_for_range (struct window *w, EmacsView *view, NSRange range) ++static NSRect +ns_ax_frame_for_range (struct window *w, EmacsView *view, + ptrdiff_t text_start, NSRange range) { - if (!w || !w->current_matrix || !view) - return NSZeroRect; -+ +- NSTRACE ("[EmacsView dealloc]"); ++ if (!w || !w->current_matrix || !view) ++ return NSZeroRect; + +- /* Clear the view resize notification. */ +- [[NSNotificationCenter defaultCenter] +- removeObserver:self +- name:NSViewFrameDidChangeNotification +- object:nil]; + /* Convert range indices back to buffer charpos. */ + ptrdiff_t cp_start = text_start + (ptrdiff_t) range.location; + ptrdiff_t cp_end = cp_start + (ptrdiff_t) range.length; -+ - struct glyph_matrix *matrix = w->current_matrix; -- NSUInteger pos = 0; - NSRect result = NSZeroRect; - BOOL found = NO; -@@ -7033,121 +7066,274 @@ row number (0-based, text rows only). */ - { - struct glyph_row *row = matrix->rows + i; - if (!row->enabled_p || row->mode_line_p) -- continue; +- if (fs_state == FULLSCREEN_BOTH) +- [nonfs_window release]; ++ struct glyph_matrix *matrix = w->current_matrix; ++ NSRect result = NSZeroRect; ++ BOOL found = NO; + +-#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MIN_REQUIRED >= 101400 +- /* Release layer and menu */ +- EmacsLayer *layer = (EmacsLayer *)[self layer]; +- [layer release]; +-#endif ++ for (int i = 0; i < matrix->nrows; i++) ++ { ++ 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 (!row->displays_text_p && !row->ends_at_zv_p) + continue; -- int row_chars = 0; -- struct glyph *g = row->glyphs[TEXT_AREA]; -- struct glyph *gend = g + row->used[TEXT_AREA]; -- for (; g < gend; g++) -- { -- if (g->type == CHAR_GLYPH && !g->padding_p) -- { -- unsigned ch = g->u.ch; -- if (ch != '\n' && ch != '\r' && (ch >= 32)) -- row_chars++; -- } -- } +- [[self menu] release]; +- [super dealloc]; + ptrdiff_t row_start = MATRIX_ROW_START_CHARPOS (row); + ptrdiff_t row_end = MATRIX_ROW_END_CHARPOS (row); - -- NSUInteger row_end = pos + row_chars + 1; -- if (pos < range.location + range.length && row_end > range.location) -- { -- /* This row overlaps the requested range. */ -- int window_x, window_y, window_width; -- window_box (w, TEXT_AREA, &window_x, &window_y, &window_width, 0); -- -- NSRect rowRect; -- rowRect.origin.x = window_x; -- rowRect.origin.y = WINDOW_TO_FRAME_PIXEL_Y (w, MAX(0, row->y)); -- rowRect.origin.y = MAX(rowRect.origin.y, window_y); -- rowRect.size.width = window_width; -- rowRect.size.height = row->visible_height; -- -- if (!found) -- { -- result = rowRect; -- found = YES; -- } -- else -- result = NSUnionRect(result, rowRect); -- } -- pos = row_end; ++ + if (row_start < cp_end && row_end > cp_start) + { + int window_x, window_y, window_width; @@ -507,11 +384,11 @@ index e67edbe..cfc5b4c 100644 + else + result = NSUnionRect (result, rowRect); + } - } - - if (!found) - return NSZeroRect; - ++ } ++ ++ if (!found) ++ return NSZeroRect; ++ + /* Clip result to text area bounds. */ + { + int text_area_x, text_area_y, text_area_w, text_area_h; @@ -522,9 +399,9 @@ index e67edbe..cfc5b4c 100644 + result.size.height = max_y - result.origin.y; + } + - /* Convert from EmacsView (flipped) coords to screen coords. */ - NSRect winRect = [view convertRect:result toView:nil]; - return [[view window] convertRectToScreen:winRect]; ++ /* Convert from EmacsView (flipped) coords to screen coords. */ ++ NSRect winRect = [view convertRect:result toView:nil]; ++ return [[view window] convertRectToScreen:winRect]; } +/* AX enum numeric compatibility for NSAccessibility notifications. @@ -535,60 +412,75 @@ index e67edbe..cfc5b4c 100644 + ns_ax_text_state_change_unknown = 0, + ns_ax_text_state_change_edit = 1, + ns_ax_text_state_change_selection_move = 2, -+ + +-/* Called on font panel selection. */ +-- (void) changeFont: (id) sender +-{ +- struct font *font = FRAME_OUTPUT_DATA (emacsframe)->font; +- NSFont *nsfont; + ns_ax_text_edit_type_typing = 3, -+ + +-#ifdef NS_IMPL_GNUSTEP +- nsfont = ((struct nsfont_info *) font)->nsfont; +-#else +- nsfont = (NSFont *) macfont_get_nsctfont (font); +-#endif + ns_ax_text_selection_direction_unknown = 0, + ns_ax_text_selection_direction_previous = 3, + ns_ax_text_selection_direction_next = 4, + ns_ax_text_selection_direction_discontiguous = 5, -+ + +- if (!font_panel_active) +- return; + ns_ax_text_selection_granularity_unknown = 0, + ns_ax_text_selection_granularity_character = 1, + ns_ax_text_selection_granularity_line = 3, +}; --/* Compute the character index within glyph-extracted text that -- corresponds to the buffer point position. */ - static NSUInteger --ns_ax_index_for_point (struct window *w) +- if (font_panel_result) +- [font_panel_result release]; ++static NSUInteger +ns_ax_utf16_length_for_buffer_range (struct buffer *b, ptrdiff_t start, + ptrdiff_t end) - { -- if (!w || !w->current_matrix || !WINDOW_LEAF_P(w)) ++{ + if (!b || end <= start) - return 0; ++ return 0; -- struct buffer *b = XBUFFER(w->contents); -- if (!b) -- return 0; +- font_panel_result = (NSFont *) [sender convertFont: nsfont]; + struct buffer *oldb = current_buffer; + if (b != current_buffer) + set_buffer_internal_1 (b); -- ptrdiff_t point = BUF_PT(b); -- struct glyph_matrix *matrix = w->current_matrix; -- NSUInteger pos = 0; +- if (font_panel_result) +- [font_panel_result retain]; + Lisp_Object lstr = Fbuffer_substring_no_properties (make_fixnum (start), + make_fixnum (end)); + NSString *nsstr = [NSString stringWithLispString:lstr]; + NSUInteger len = [nsstr length]; -- for (int i = 0; i < matrix->nrows; i++) +-#ifndef NS_IMPL_COCOA +- font_panel_active = NO; +- [NSApp stop: self]; +-#endif + if (b != oldb) + set_buffer_internal_1 (oldb); + + return len; -+} -+ + } + +-#ifdef NS_IMPL_COCOA +-- (void) noteUserSelectedFont +static BOOL +ns_ax_find_completion_overlay_range (struct buffer *b, ptrdiff_t point, + ptrdiff_t *out_start, + ptrdiff_t *out_end) -+{ + { +- font_panel_active = NO; + if (!b || !out_start || !out_end) + return NO; -+ + +- /* If no font was previously selected, use the currently selected +- font. */ + Lisp_Object faceSym = intern ("completions-highlight"); + ptrdiff_t begv = BUF_BEGV (b); + ptrdiff_t zv = BUF_ZV (b); @@ -596,22 +488,20 @@ index e67edbe..cfc5b4c 100644 + ptrdiff_t best_end = 0; + ptrdiff_t best_dist = PTRDIFF_MAX; + BOOL found = NO; -+ + +- if (!font_panel_result && FRAME_FONT (emacsframe)) + /* Fast path: look at point and immediate neighbors first. */ + ptrdiff_t probes[3] = { point, point - 1, point + 1 }; + for (int i = 0; i < 3 && !found; i++) { -- 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; +- font_panel_result +- = macfont_get_nsctfont (FRAME_FONT (emacsframe)); + ptrdiff_t p = probes[i]; + if (p < begv || p > zv) + continue; -- ptrdiff_t row_start = MATRIX_ROW_START_CHARPOS (row); -- ptrdiff_t row_end = MATRIX_ROW_END_CHARPOS (row); +- if (font_panel_result) +- [font_panel_result retain]; + Lisp_Object overlays = Foverlays_at (make_fixnum (p), Qnil); + Lisp_Object tail; + for (tail = overlays; CONSP (tail); tail = XCDR (tail)) @@ -633,28 +523,9 @@ index e67edbe..cfc5b4c 100644 + found = YES; + break; + } -+ } + } -- if (point >= row_start && point < row_end) -- { -- /* Point is within this row. Count visible glyphs whose -- buffer charpos is before point. */ -- int chars_before = 0; -- struct glyph *g = row->glyphs[TEXT_AREA]; -- struct glyph *gend = g + row->used[TEXT_AREA]; -- for (; g < gend; g++) -- { -- if (g->type == CHAR_GLYPH && !g->padding_p -- && g->charpos >= row_start -- && g->charpos < point) -- { -- unsigned ch = g->u.ch; -- if (ch != '\n' && ch != '\r' && ch >= 32) -- chars_before++; -- } -- } -- return pos + chars_before; -- } +- [NSApp stop: self]; + if (!found) + { + for (ptrdiff_t scan = begv; scan < zv; scan++) @@ -692,39 +563,31 @@ index e67edbe..cfc5b4c 100644 + } + } + } - -- /* Count visible chars in this row + newline. */ -- int row_chars = 0; -- struct glyph *g = row->glyphs[TEXT_AREA]; -- struct glyph *gend = g + row->used[TEXT_AREA]; -- for (; g < gend; g++) -- { -- if (g->type == CHAR_GLYPH && !g->padding_p) -- { -- unsigned ch = g->u.ch; -- if (ch != '\n' && ch != '\r' && (ch >= 32)) -- row_chars++; -- } -- } -- pos += row_chars + 1; ++ + if (!found) + return NO; + + *out_start = best_start; + *out_end = best_end; + return YES; -+} -+ + } + +-- (void) noteUserCancelledSelection +static bool +ns_ax_event_is_ctrl_n_or_p (int *which) -+{ + { +- font_panel_active = NO; + Lisp_Object ev = last_command_event; + if (CONSP (ev)) + ev = EVENT_HEAD (ev); -+ + +- if (font_panel_result) +- [font_panel_result release]; +- font_panel_result = nil; + if (!FIXNUMP (ev)) + return false; -+ + +- [NSApp stop: self]; + EMACS_INT c = XFIXNUM (ev); + if (c == 14) /* C-n */ + { @@ -739,19 +602,42 @@ index e67edbe..cfc5b4c 100644 + return true; + } + return false; -+} -+ + } +-#endif + +-- (Lisp_Object) showFontPanel +static bool +ns_ax_command_is_basic_line_move (void) -+{ + { +- id fm = [NSFontManager sharedFontManager]; +- struct font *font = FRAME_OUTPUT_DATA (emacsframe)->font; +- NSFont *nsfont, *result; +- struct timespec timeout; +-#ifdef NS_IMPL_COCOA +- NSView *buttons; +- BOOL canceled; +-#endif + if (!SYMBOLP (real_this_command)) + return false; -+ + +-#ifdef NS_IMPL_GNUSTEP +- nsfont = ((struct nsfont_info *) font)->nsfont; +-#else +- nsfont = (NSFont *) macfont_get_nsctfont (font); +-#endif + Lisp_Object next = intern ("next-line"); + Lisp_Object prev = intern ("previous-line"); + return EQ (real_this_command, next) || EQ (real_this_command, prev); +} -+ + +-#ifdef NS_IMPL_COCOA +- buttons +- = ns_create_font_panel_buttons (self, +- @selector (noteUserSelectedFont), +- @selector (noteUserCancelledSelection)); +- [[fm fontPanel: YES] setAccessoryView: buttons]; +- [buttons release]; +-#endif +static NSString * +ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem, + struct buffer *b, @@ -761,12 +647,16 @@ index e67edbe..cfc5b4c 100644 +{ + if (!elem || !b || !cachedText || end <= start) + return nil; -+ + +- [fm setSelectedFont: nsfont isMultiple: NO]; +- [fm orderFrontFontPanel: NSApp]; + NSString *text = nil; + struct buffer *oldb = current_buffer; + if (b != current_buffer) + set_buffer_internal_1 (b); -+ + +- font_panel_active = YES; +- timeout = make_timespec (0, 100000000); + /* Prefer canonical completion candidate string from text property. */ + ptrdiff_t probes[2] = { start, end - 1 }; + for (int i = 0; i < 2 && !text; i++) @@ -778,7 +668,17 @@ index e67edbe..cfc5b4c 100644 + if (STRINGP (cstr)) + text = [NSString stringWithLispString:cstr]; + } -+ + +- block_input (); +- while (font_panel_active +-#ifdef NS_IMPL_COCOA +- && (canceled = [[fm fontPanel: YES] isVisible]) +-#else +- && [[fm fontPanel: YES] isVisible] +-#endif +- ) +- ns_select_1 (0, NULL, NULL, NULL, &timeout, NULL, YES); +- unblock_input (); + if (!text) + { + NSUInteger ax_s = [elem accessibilityIndexForCharpos:start]; @@ -786,92 +686,100 @@ index e67edbe..cfc5b4c 100644 + if (ax_e > ax_s && ax_e <= [cachedText length]) + text = [cachedText substringWithRange:NSMakeRange (ax_s, ax_e - ax_s)]; + } -+ + +- if (font_panel_result) +- [font_panel_result autorelease]; + if (b != oldb) + set_buffer_internal_1 (oldb); -+ + +-#ifdef NS_IMPL_COCOA +- if (!canceled) +- font_panel_result = nil; +-#endif + if (text) + { + text = [text stringByTrimmingCharactersInSet: + [NSCharacterSet whitespaceAndNewlineCharacterSet]]; + if ([text length] == 0) + text = nil; - } -- return pos > 0 ? pos - 1 : 0; -+ ++ } + +- result = font_panel_result; +- font_panel_result = nil; + return text; - } ++} +- [[fm fontPanel: YES] setIsVisible: NO]; +- font_panel_active = NO; -@@ -7159,7 +7345,7 @@ - (NSRect)screenRectFromEmacsX:(int)x y:(int)y width:(int)ew height:(int)eh - if (!view || ![view window]) - return NSZeroRect; +- if (result) +- return ns_font_desc_to_font_spec ([result fontDescriptor], +- result); ++@implementation EmacsAccessibilityElement -- NSRect r = NSMakeRect(x, y, ew, eh); +- return Qnil; ++- (NSRect)screenRectFromEmacsX:(int)x y:(int)y width:(int)ew height:(int)eh ++{ ++ EmacsView *view = self.emacsView; ++ if (!view || ![view window]) ++ return NSZeroRect; ++ + NSRect r = NSMakeRect (x, y, ew, eh); - NSRect winRect = [view convertRect:r toView:nil]; - return [[view window] convertRectToScreen:winRect]; ++ NSRect winRect = [view convertRect:r toView:nil]; ++ return [[view window] convertRectToScreen:winRect]; } -@@ -7169,130 +7355,475 @@ - (BOOL)isAccessibilityElement + +-- (BOOL)acceptsFirstResponder ++- (BOOL)isAccessibilityElement + { +- NSTRACE ("[EmacsView acceptsFirstResponder]"); return YES; } --@end -- -- --@implementation EmacsAccessibilityBuffer +-/* Tell NS we want to accept clicks that activate the window */ +-- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent +/* ---- Hierarchy plumbing (required for VoiceOver to find us) ---- */ - --/* ---- NSAccessibility protocol ---- */ -- --- (NSAccessibilityRole)accessibilityRole ++ +- (id)accessibilityParent { -- return NSAccessibilityTextAreaRole; +- NSTRACE_MSG ("First mouse event: type=%ld, clickCount=%ld", +- [theEvent type], [theEvent clickCount]); +- return ns_click_through; + return NSAccessibilityUnignoredAncestor (self.emacsView); } - --- (NSString *)accessibilityRoleDescription -+- (id)accessibilityWindow - { -- return @"editor"; -+ return [self.emacsView window]; - } - --- (NSString *)accessibilityLabel -+- (id)accessibilityTopLevelUIElement - { -- struct window *w = self.emacsWindow; -- if (w && WINDOW_LEAF_P(w)) -- { -- struct buffer *b = XBUFFER(w->contents); -- if (b) -- { -- Lisp_Object name = BVAR(b, name); -- if (STRINGP(name)) -- return [NSString stringWithLispString:name]; -- } -- } -- return @"buffer"; -+ return [self.emacsView window]; - } - --- (id)accessibilityValue +-- (void)resetCursorRects -{ -- struct window *w = self.emacsWindow; -- if (!w) -- return @""; -- return ns_ax_text_from_glyph_rows(w); --} +- NSRect visible = [self visibleRect]; +- NSCursor *currentCursor = FRAME_POINTER_TYPE (emacsframe); +- NSTRACE ("[EmacsView resetCursorRects]"); +- +- if (currentCursor == nil) +- currentCursor = [NSCursor arrowCursor]; + +- if (!NSIsEmptyRect (visible)) +- [self addCursorRect: visible cursor: currentCursor]; ++- (id)accessibilityWindow ++{ ++ return [self.emacsView window]; ++} + +-#if defined (NS_IMPL_GNUSTEP) || MAC_OS_X_VERSION_MIN_REQUIRED < 101300 +-#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 +- if ([currentCursor respondsToSelector: @selector(setOnMouseEntered:)]) +-#endif +- [currentCursor setOnMouseEntered: YES]; +-#endif ++- (id)accessibilityTopLevelUIElement ++{ ++ return [self.emacsView window]; + } + +@end --- (NSInteger)accessibilityNumberOfCharacters --{ -- NSString *text = [self accessibilityValue]; -- return [text length]; --} --- (NSString *)accessibilitySelectedText +-/*****************************************************************************/ +-/* Keyboard handling. */ +-#define NS_KEYLOG 0 +@implementation EmacsAccessibilityBuffer +@synthesize cachedText; +@synthesize cachedTextModiff; @@ -883,30 +791,21 @@ index e67edbe..cfc5b4c 100644 +@synthesize cachedCompletionOverlayStart; +@synthesize cachedCompletionOverlayEnd; +@synthesize cachedCompletionPoint; -+ + +-- (void)keyDown: (NSEvent *)theEvent +- (void)dealloc { -- struct window *w = self.emacsWindow; -- if (!w || !WINDOW_LEAF_P(w)) -- return @""; +- Mouse_HLInfo *hlinfo = MOUSE_HL_INFO (emacsframe); +- int code; + [cachedText release]; + [cachedCompletionAnnouncement release]; + if (visibleRuns) + xfree (visibleRuns); + [super dealloc]; +} - -- struct buffer *b = XBUFFER(w->contents); -- if (!b || NILP(BVAR(b, mark_active))) -- return @""; ++ +/* ---- Text cache ---- */ - -- /* Return the selected region text. */ -- NSString *text = [self accessibilityValue]; -- NSRange sel = [self accessibilitySelectedTextRange]; -- if (sel.location == NSNotFound || sel.location + sel.length > [text length]) -- return @""; -- return [text substringWithRange:sel]; ++ +- (void)invalidateTextCache +{ + [cachedText release]; @@ -917,24 +816,18 @@ index e67edbe..cfc5b4c 100644 + visibleRuns = NULL; + } + visibleRunCount = 0; - } - --- (NSRange)accessibilitySelectedTextRange ++} ++ +- (void)ensureTextCache - { - struct window *w = self.emacsWindow; -- if (!w || !WINDOW_LEAF_P(w)) -- return NSMakeRange(0, 0); ++{ ++ struct window *w = self.emacsWindow; + if (!w || !WINDOW_LEAF_P (w)) + return; - -- struct buffer *b = XBUFFER(w->contents); ++ + struct buffer *b = XBUFFER (w->contents); - if (!b) -- return NSMakeRange(0, 0); ++ if (!b) + return; - -- NSUInteger point_idx = ns_ax_index_for_point(w); ++ + ptrdiff_t modiff = BUF_MODIFF (b); + ptrdiff_t pt = BUF_PT (b); + NSUInteger textLen = cachedText ? [cachedText length] : 0; @@ -943,25 +836,12 @@ index e67edbe..cfc5b4c 100644 + && (textLen == 0 + || [self accessibilityIndexForCharpos:pt] <= textLen)) + return; - -- if (NILP(BVAR(b, mark_active))) -- return NSMakeRange(point_idx, 0); ++ + ptrdiff_t start; + ns_ax_visible_run *runs = NULL; + NSUInteger nruns = 0; + NSString *text = ns_ax_buffer_text (w, &start, &runs, &nruns); - -- /* With active mark, report the selection range. Map mark -- position to accessibility index using the same glyph-based -- mapping as point. */ -- ptrdiff_t mark_pos = marker_position (BVAR (b, mark)); -- ptrdiff_t pt_pos = BUF_PT (b); -- ptrdiff_t begv = BUF_BEGV (b); -- ptrdiff_t sel_start = (mark_pos < pt_pos) ? mark_pos : pt_pos; -- ptrdiff_t sel_end = (mark_pos < pt_pos) ? pt_pos : mark_pos; -- NSUInteger start_idx = (NSUInteger) (sel_start - begv); -- NSUInteger len = (NSUInteger) (sel_end - sel_start); -- return NSMakeRange(start_idx, len); ++ + [cachedText release]; + cachedText = [text retain]; + cachedTextModiff = modiff; @@ -971,19 +851,14 @@ index e67edbe..cfc5b4c 100644 + xfree (visibleRuns); + visibleRuns = runs; + visibleRunCount = nruns; - } - --- (NSInteger)accessibilityInsertionPointLineNumber ++} ++ +/* ---- Index mapping ---- */ + +/* Convert buffer charpos to accessibility string index. */ +- (NSUInteger)accessibilityIndexForCharpos:(ptrdiff_t)charpos - { - struct window *w = self.emacsWindow; -- if (!w) -- return 0; -- NSUInteger idx = ns_ax_index_for_point(w); -- return ns_ax_line_for_index(w, idx); ++{ ++ struct window *w = self.emacsWindow; + struct buffer *b = (w && WINDOW_LEAF_P (w)) ? XBUFFER (w->contents) : NULL; + + for (NSUInteger i = 0; i < visibleRunCount; i++) @@ -1272,16 +1147,13 @@ index e67edbe..cfc5b4c 100644 + line++; + } + return line; - } - - - (NSString *)accessibilityStringForRange:(NSRange)range - { -- NSString *text = [self accessibilityValue]; -- if (range.location + range.length > [text length]) ++} ++ ++- (NSString *)accessibilityStringForRange:(NSRange)range ++{ + [self ensureTextCache]; + if (!cachedText || range.location + range.length > [cachedText length]) - return @""; -- return [text substringWithRange:range]; ++ return @""; + return [cachedText substringWithRange:range]; +} + @@ -1289,16 +1161,13 @@ index e67edbe..cfc5b4c 100644 +{ + NSString *str = [self accessibilityStringForRange:range]; + return [[[NSAttributedString alloc] initWithString:str] autorelease]; - } - - - (NSInteger)accessibilityLineForIndex:(NSInteger)index - { -- struct window *w = self.emacsWindow; -- if (!w) ++} ++ ++- (NSInteger)accessibilityLineForIndex:(NSInteger)index ++{ + [self ensureTextCache]; + if (!cachedText || index < 0) - return 0; -- return ns_ax_line_for_index(w, (NSUInteger)index); ++ return 0; + + NSUInteger idx = (NSUInteger) index; + if (idx > [cachedText length]) @@ -1312,14 +1181,10 @@ index e67edbe..cfc5b4c 100644 + line++; + } + return line; - } - - - (NSRange)accessibilityRangeForLine:(NSInteger)line - { -- struct window *w = self.emacsWindow; -- if (!w) -- return NSMakeRange(0, 0); -- return ns_ax_range_for_line(w, (int)line); ++} ++ ++- (NSRange)accessibilityRangeForLine:(NSInteger)line ++{ + [self ensureTextCache]; + if (!cachedText || line < 0) + return NSMakeRange (NSNotFound, 0); @@ -1367,14 +1232,14 @@ index e67edbe..cfc5b4c 100644 + /* Return the range of the current line — simple approach. */ + NSInteger line = [self accessibilityLineForIndex:index]; + return [self accessibilityRangeForLine:line]; - } - - - (NSRect)accessibilityFrameForRange:(NSRange)range -@@ -7301,14 +7832,18 @@ - (NSRect)accessibilityFrameForRange:(NSRange)range - EmacsView *view = self.emacsView; - if (!w || !view) - return NSZeroRect; -- return ns_ax_frame_for_range(w, view, range); ++} ++ ++- (NSRect)accessibilityFrameForRange:(NSRange)range ++{ ++ struct window *w = self.emacsWindow; ++ EmacsView *view = self.emacsView; ++ if (!w || !view) ++ return NSZeroRect; + /* Convert ax-index range to charpos range for glyph lookup. */ + [self ensureTextCache]; + ptrdiff_t cp_start = [self charposForAccessibilityIndex:range.location]; @@ -1382,43 +1247,34 @@ index e67edbe..cfc5b4c 100644 + range.location + range.length]; + NSRange charRange = NSMakeRange (0, (NSUInteger) (cp_end - cp_start)); + return ns_ax_frame_for_range (w, view, cp_start, charRange); - } - -- - - (NSRange)accessibilityRangeForPosition:(NSPoint)screenPoint - { -- /* Hit test: convert screen point to buffer character index. -- Used by VoiceOver for mouse/trackpad exploration. */ ++} ++ ++- (NSRange)accessibilityRangeForPosition:(NSPoint)screenPoint ++{ + /* Hit test: convert screen point to buffer character index. */ - struct window *w = self.emacsWindow; - EmacsView *view = self.emacsView; - if (!w || !view || !w->current_matrix) -@@ -7318,7 +7853,7 @@ - (NSRange)accessibilityRangeForPosition:(NSPoint)screenPoint - NSPoint windowPoint = [[view window] convertPointFromScreen:screenPoint]; - NSPoint viewPoint = [view convertPoint:windowPoint fromView:nil]; - -- /* Convert to Emacs pixel coordinates (EmacsView is flipped). */ ++ struct window *w = self.emacsWindow; ++ EmacsView *view = self.emacsView; ++ if (!w || !view || !w->current_matrix) ++ return NSMakeRange (0, 0); ++ ++ /* Convert screen point to EmacsView coordinates. */ ++ NSPoint windowPoint = [[view window] convertPointFromScreen:screenPoint]; ++ NSPoint viewPoint = [view convertPoint:windowPoint fromView:nil]; ++ + /* Convert to window-relative pixel coordinates. */ - int x = (int) viewPoint.x - w->pixel_left; - int y = (int) viewPoint.y - w->pixel_top; - -@@ -7328,19 +7863,19 @@ - (NSRange)accessibilityRangeForPosition:(NSPoint)screenPoint - /* Find the glyph row at this y coordinate. */ - struct glyph_matrix *matrix = w->current_matrix; - struct glyph_row *hit_row = NULL; -- int row_y = 0; - - for (int i = 0; i < matrix->nrows; i++) - { - struct glyph_row *row = matrix->rows + i; -- if (!row->enabled_p || !row->displays_text_p) -- continue; -- if (y >= row_y && y < row_y + row->visible_height) -- { -- hit_row = row; -- break; -- } -- row_y += row->visible_height; ++ int x = (int) viewPoint.x - w->pixel_left; ++ int y = (int) viewPoint.y - w->pixel_top; ++ ++ if (x < 0 || y < 0 || x >= w->pixel_width || y >= w->pixel_height) ++ return NSMakeRange (0, 0); ++ ++ /* Find the glyph row at this y coordinate. */ ++ struct glyph_matrix *matrix = w->current_matrix; ++ struct glyph_row *hit_row = NULL; ++ ++ for (int i = 0; i < matrix->nrows; i++) ++ { ++ struct glyph_row *row = matrix->rows + i; + if (!row->enabled_p || !row->displays_text_p || row->mode_line_p) + continue; + int row_top = WINDOW_TO_FRAME_PIXEL_Y (w, MAX (0, row->y)); @@ -1428,21 +1284,20 @@ index e67edbe..cfc5b4c 100644 + hit_row = row; + break; + } - } - - if (!hit_row) -@@ -7355,49 +7890,32 @@ - (NSRange)accessibilityRangeForPosition:(NSPoint)screenPoint - for (; glyph < end; glyph++) - { - if (glyph->type == CHAR_GLYPH && glyph->charpos > 0) -- { -- if (x >= glyph_x && x < glyph_x + glyph->pixel_width) -- { -- best_charpos = glyph->charpos; -- break; -- } -- best_charpos = glyph->charpos; -- } ++ } ++ ++ if (!hit_row) ++ return NSMakeRange (0, 0); ++ ++ /* Find the glyph at this x coordinate within the row. */ ++ struct glyph *glyph = hit_row->glyphs[TEXT_AREA]; ++ struct glyph *end = glyph + hit_row->used[TEXT_AREA]; ++ int glyph_x = 0; ++ ptrdiff_t best_charpos = MATRIX_ROW_START_CHARPOS (hit_row); ++ ++ for (; glyph < end; glyph++) ++ { ++ if (glyph->type == CHAR_GLYPH && glyph->charpos > 0) + { + if (x >= glyph_x && x < glyph_x + glyph->pixel_width) + { @@ -1451,59 +1306,31 @@ index e67edbe..cfc5b4c 100644 + } + best_charpos = glyph->charpos; + } - glyph_x += glyph->pixel_width; - } - -- /* Convert buffer charpos to accessibility index. */ -- struct buffer *b = XBUFFER (w->contents); -- if (!b) -- return NSMakeRange (0, 0); -- -- ptrdiff_t idx = best_charpos - BUF_BEGV (b); -- if (idx < 0) idx = 0; -- -- return NSMakeRange ((NSUInteger) idx, 1); ++ glyph_x += glyph->pixel_width; ++ } ++ + /* Convert buffer charpos to accessibility index via mapping. */ + [self ensureTextCache]; + NSUInteger ax_idx = [self accessibilityIndexForCharpos:best_charpos]; + if (cachedText && ax_idx > [cachedText length]) + ax_idx = [cachedText length]; + return NSMakeRange (ax_idx, 1); - } - - - (NSRange)accessibilityVisibleCharacterRange - { -- NSString *text = [self accessibilityValue]; -- return NSMakeRange(0, [text length]); --} -- --- (BOOL)isAccessibilityFocused --{ -- struct window *w = self.emacsWindow; -- if (!w) -- return NO; -- EmacsView *view = self.emacsView; -- if (!view || !view->emacsframe) -- return NO; -- struct frame *f = view->emacsframe; -- return (w == XWINDOW(f->selected_window)); --} -- --- (id)accessibilityParent --{ -- return NSAccessibilityUnignoredAncestor (self.emacsView); ++} ++ ++- (NSRange)accessibilityVisibleCharacterRange ++{ + /* Return the full cached text range. VoiceOver interprets the + visible range boundary as end-of-text, so we must expose the + entire buffer to avoid premature "end of text" announcements. */ + [self ensureTextCache]; + return NSMakeRange (0, cachedText ? [cachedText length] : 0); - } - - - (NSRect)accessibilityFrame -@@ -7405,50 +7923,523 @@ - (NSRect)accessibilityFrame - struct window *w = self.emacsWindow; - if (!w) - return NSZeroRect; ++} ++ ++- (NSRect)accessibilityFrame ++{ ++ struct window *w = self.emacsWindow; ++ if (!w) ++ return NSZeroRect; + + /* Subtract mode line height so the buffer element does not overlap it. */ + int text_h = w->pixel_height; @@ -1519,42 +1346,32 @@ index e67edbe..cfc5b4c 100644 + } + } + } - return [self screenRectFromEmacsX:w->pixel_left -- y:w->pixel_top -- width:w->pixel_width -- height:w->pixel_height]; ++ return [self screenRectFromEmacsX:w->pixel_left + y:w->pixel_top + width:w->pixel_width + height:text_h]; - } - - /* ---- Notification dispatch ---- */ - --- (void)postAccessibilityUpdatesForWindow:(struct window *)w -- frame:(struct frame *)f ++} ++ ++/* ---- Notification dispatch ---- */ ++ +- (void)postAccessibilityNotificationsForFrame:(struct frame *)f - { -- if (!w || !WINDOW_LEAF_P(w)) ++{ + struct window *w = self.emacsWindow; + if (!w || !WINDOW_LEAF_P (w)) - return; - -- struct buffer *b = XBUFFER(w->contents); ++ return; ++ + struct buffer *b = XBUFFER (w->contents); - if (!b) - return; - -- ptrdiff_t modiff = BUF_MODIFF(b); -- ptrdiff_t point = BUF_PT(b); ++ if (!b) ++ return; ++ + ptrdiff_t modiff = BUF_MODIFF (b); + ptrdiff_t point = BUF_PT (b); + BOOL markActive = !NILP (BVAR (b, mark_active)); - -- /* Text content changed? */ ++ + /* --- Text changed → typing echo --- + WebKit AXObjectCacheMac fallback enum: Edit = 1, Typing = 3. */ - if (modiff != self.cachedModiff) - { ++ if (modiff != self.cachedModiff) ++ { + /* Capture changed char before invalidating cache. */ + NSString *changedChar = @""; + if (point > self.cachedPoint @@ -1576,47 +1393,36 @@ index e67edbe..cfc5b4c 100644 + [self invalidateTextCache]; + } + - self.cachedModiff = modiff; -- NSAccessibilityPostNotification(self, -- NSAccessibilityValueChangedNotification); ++ self.cachedModiff = modiff; + /* Update cachedPoint here so the selection-move branch below + does NOT fire for point changes caused by edits. WebKit and + Chromium never send both ValueChanged and SelectedTextChanged + for the same user action — they are mutually exclusive. */ + self.cachedPoint = point; - -- /* Rich typing echo for VoiceOver. */ ++ + NSDictionary *change = @{ + @"AXTextEditType": @(ns_ax_text_edit_type_typing), + @"AXTextChangeValue": changedChar, + @"AXTextChangeValueLength": @([changedChar length]) + }; - NSDictionary *userInfo = @{ -- @"AXTextStateChangeType" : @1, /* AXTextStateChangeTypeEdit */ -- @"AXTextEditType" : @0 /* kAXTextEditTypeTyping */ ++ NSDictionary *userInfo = @{ + @"AXTextStateChangeType": @(ns_ax_text_state_change_edit), + @"AXTextChangeValues": @[change], + @"AXTextChangeElement": self - }; -- NSAccessibilityPostNotificationWithUserInfo( -- self, NSAccessibilityValueChangedNotification, userInfo); ++ }; + NSAccessibilityPostNotificationWithUserInfo ( + self, NSAccessibilityValueChangedNotification, userInfo); - } - -- /* Cursor moved? */ -- if (point != self.cachedPoint) ++ } ++ + /* --- Cursor moved or selection changed → line reading --- + WebKit AXObjectCacheMac fallback enum: SelectionMove = 2. + Use 'else if' — edits and selection moves are mutually exclusive + per the WebKit/Chromium pattern. VoiceOver gets confused if + both notifications arrive in the same runloop iteration. */ + else if (point != self.cachedPoint || markActive != self.cachedMarkActive) - { ++ { + ptrdiff_t oldPoint = self.cachedPoint; - self.cachedPoint = point; -- NSAccessibilityPostNotification(self, -- NSAccessibilitySelectedTextChangedNotification); ++ self.cachedPoint = point; + self.cachedMarkActive = markActive; + + /* Compute direction. */ @@ -2040,20 +1846,235 @@ index e67edbe..cfc5b4c 100644 + width:w->pixel_width + height:row->visible_height]; + } - } ++ } + return NSZeroRect; - } - - @end -@@ -7498,6 +8489,7 @@ - (void)dealloc - [layer release]; - #endif - ++} ++ ++@end ++ ++#endif /* NS_IMPL_COCOA */ ++ ++ ++/* ========================================================================== ++ ++ EmacsView implementation ++ ++ ========================================================================== */ ++ ++ ++@implementation EmacsView ++ ++- (void)windowDidEndLiveResize:(NSNotification *)notification ++{ ++ [self updateFramePosition]; ++} ++ ++/* Needed to inform when window closed from lisp. */ ++- (void) setWindowClosing: (BOOL)closing ++{ ++ NSTRACE ("[EmacsView setWindowClosing:%d]", closing); ++ ++ windowClosing = closing; ++} ++ ++ ++- (void)dealloc ++{ ++ NSTRACE ("[EmacsView dealloc]"); ++ ++ /* Clear the view resize notification. */ ++ [[NSNotificationCenter defaultCenter] ++ removeObserver:self ++ name:NSViewFrameDidChangeNotification ++ object:nil]; ++ ++ if (fs_state == FULLSCREEN_BOTH) ++ [nonfs_window release]; ++ ++#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MIN_REQUIRED >= 101400 ++ /* Release layer and menu */ ++ EmacsLayer *layer = (EmacsLayer *)[self layer]; ++ [layer release]; ++#endif ++ + [accessibilityElements release]; - [[self menu] release]; - [super dealloc]; - } -@@ -8846,6 +9838,28 @@ - (void)windowDidBecomeKey /* for direct calls */ ++ [[self menu] release]; ++ [super dealloc]; ++} ++ ++ ++/* Called on font panel selection. */ ++- (void) changeFont: (id) sender ++{ ++ struct font *font = FRAME_OUTPUT_DATA (emacsframe)->font; ++ NSFont *nsfont; ++ ++#ifdef NS_IMPL_GNUSTEP ++ nsfont = ((struct nsfont_info *) font)->nsfont; ++#else ++ nsfont = (NSFont *) macfont_get_nsctfont (font); ++#endif ++ ++ if (!font_panel_active) ++ return; ++ ++ if (font_panel_result) ++ [font_panel_result release]; ++ ++ font_panel_result = (NSFont *) [sender convertFont: nsfont]; ++ ++ if (font_panel_result) ++ [font_panel_result retain]; ++ ++#ifndef NS_IMPL_COCOA ++ font_panel_active = NO; ++ [NSApp stop: self]; ++#endif ++} ++ ++#ifdef NS_IMPL_COCOA ++- (void) noteUserSelectedFont ++{ ++ font_panel_active = NO; ++ ++ /* If no font was previously selected, use the currently selected ++ font. */ ++ ++ if (!font_panel_result && FRAME_FONT (emacsframe)) ++ { ++ font_panel_result ++ = macfont_get_nsctfont (FRAME_FONT (emacsframe)); ++ ++ if (font_panel_result) ++ [font_panel_result retain]; ++ } ++ ++ [NSApp stop: self]; ++} ++ ++- (void) noteUserCancelledSelection ++{ ++ font_panel_active = NO; ++ ++ if (font_panel_result) ++ [font_panel_result release]; ++ font_panel_result = nil; ++ ++ [NSApp stop: self]; ++} ++#endif ++ ++- (Lisp_Object) showFontPanel ++{ ++ id fm = [NSFontManager sharedFontManager]; ++ struct font *font = FRAME_OUTPUT_DATA (emacsframe)->font; ++ NSFont *nsfont, *result; ++ struct timespec timeout; ++#ifdef NS_IMPL_COCOA ++ NSView *buttons; ++ BOOL canceled; ++#endif ++ ++#ifdef NS_IMPL_GNUSTEP ++ nsfont = ((struct nsfont_info *) font)->nsfont; ++#else ++ nsfont = (NSFont *) macfont_get_nsctfont (font); ++#endif ++ ++#ifdef NS_IMPL_COCOA ++ buttons ++ = ns_create_font_panel_buttons (self, ++ @selector (noteUserSelectedFont), ++ @selector (noteUserCancelledSelection)); ++ [[fm fontPanel: YES] setAccessoryView: buttons]; ++ [buttons release]; ++#endif ++ ++ [fm setSelectedFont: nsfont isMultiple: NO]; ++ [fm orderFrontFontPanel: NSApp]; ++ ++ font_panel_active = YES; ++ timeout = make_timespec (0, 100000000); ++ ++ block_input (); ++ while (font_panel_active ++#ifdef NS_IMPL_COCOA ++ && (canceled = [[fm fontPanel: YES] isVisible]) ++#else ++ && [[fm fontPanel: YES] isVisible] ++#endif ++ ) ++ ns_select_1 (0, NULL, NULL, NULL, &timeout, NULL, YES); ++ unblock_input (); ++ ++ if (font_panel_result) ++ [font_panel_result autorelease]; ++ ++#ifdef NS_IMPL_COCOA ++ if (!canceled) ++ font_panel_result = nil; ++#endif ++ ++ result = font_panel_result; ++ font_panel_result = nil; ++ ++ [[fm fontPanel: YES] setIsVisible: NO]; ++ font_panel_active = NO; ++ ++ if (result) ++ return ns_font_desc_to_font_spec ([result fontDescriptor], ++ result); ++ ++ return Qnil; ++} ++ ++- (BOOL)acceptsFirstResponder ++{ ++ NSTRACE ("[EmacsView acceptsFirstResponder]"); ++ return YES; ++} ++ ++/* Tell NS we want to accept clicks that activate the window */ ++- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent ++{ ++ NSTRACE_MSG ("First mouse event: type=%ld, clickCount=%ld", ++ [theEvent type], [theEvent clickCount]); ++ return ns_click_through; ++} ++- (void)resetCursorRects ++{ ++ NSRect visible = [self visibleRect]; ++ NSCursor *currentCursor = FRAME_POINTER_TYPE (emacsframe); ++ NSTRACE ("[EmacsView resetCursorRects]"); ++ ++ if (currentCursor == nil) ++ currentCursor = [NSCursor arrowCursor]; ++ ++ if (!NSIsEmptyRect (visible)) ++ [self addCursorRect: visible cursor: currentCursor]; ++ ++#if defined (NS_IMPL_GNUSTEP) || MAC_OS_X_VERSION_MIN_REQUIRED < 101300 ++#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 ++ if ([currentCursor respondsToSelector: @selector(setOnMouseEntered:)]) ++#endif ++ [currentCursor setOnMouseEntered: YES]; ++#endif ++} ++ ++ ++ ++/*****************************************************************************/ ++/* Keyboard handling. */ ++#define NS_KEYLOG 0 ++ ++- (void)keyDown: (NSEvent *)theEvent ++{ ++ Mouse_HLInfo *hlinfo = MOUSE_HL_INFO (emacsframe); ++ int code; + unsigned fnKeysym = 0; + static NSMutableArray *nsEvArray; + unsigned int flags = [theEvent modifierFlags]; +@@ -8237,6 +9833,28 @@ - (void)windowDidBecomeKey /* for direct calls */ XSETFRAME (event.frame_or_window, emacsframe); kbd_buffer_store_event (&event); ns_send_appdefined (-1); // Kick main loop @@ -2082,22 +2103,26 @@ index e67edbe..cfc5b4c 100644 } -@@ -10089,7 +11103,8 @@ - (int) fullscreenState +@@ -9474,6 +11092,298 @@ - (int) fullscreenState + return fs_state; + } - static void - ns_ax_collect_windows (Lisp_Object window, EmacsView *view, -- NSMutableArray *elements) ++#ifdef NS_IMPL_COCOA ++ ++/* ---- Accessibility: walk the Emacs window tree ---- */ ++ ++static void ++ns_ax_collect_windows (Lisp_Object window, EmacsView *view, + NSMutableArray *elements, + NSDictionary *existing) - { - if (NILP (window)) - return; -@@ -10098,32 +11113,47 @@ - (int) fullscreenState - - if (WINDOW_LEAF_P (w)) - { -- if (MINI_WINDOW_P (w)) -- return; /* Skip minibuffer for MVP. */ ++{ ++ if (NILP (window)) ++ return; ++ ++ struct window *w = XWINDOW (window); ++ ++ if (WINDOW_LEAF_P (w)) ++ { + /* Buffer element — reuse existing if available. */ + EmacsAccessibilityBuffer *elem + = [existing objectForKey:[NSValue valueWithPointer:w]]; @@ -2105,9 +2130,7 @@ index e67edbe..cfc5b4c 100644 + { + elem = [[EmacsAccessibilityBuffer alloc] init]; + elem.emacsView = view; - -- EmacsAccessibilityBuffer *elem = [[EmacsAccessibilityBuffer alloc] init]; -- elem.emacsView = view; ++ + /* Initialize cached state to -1 to force first notification. */ + elem.cachedModiff = -1; + elem.cachedPoint = -1; @@ -2117,17 +2140,8 @@ index e67edbe..cfc5b4c 100644 + { + [elem retain]; + } - elem.emacsWindow = w; -- -- /* Initialize cached state to trigger first notification. */ -- struct buffer *b = XBUFFER (w->contents); -- if (b) -- { -- elem.cachedModiff = BUF_MODIFF (b); -- elem.cachedPoint = BUF_PT (b); -- } -- - [elements addObject:elem]; ++ elem.emacsWindow = w; ++ [elements addObject:elem]; + [elem release]; + + /* Mode line element (skip for minibuffer). */ @@ -2140,28 +2154,24 @@ index e67edbe..cfc5b4c 100644 + [elements addObject:ml]; + [ml release]; + } - } - else - { - /* Internal (combination) window — recurse into children. */ - Lisp_Object child = w->contents; - while (!NILP (child)) -- { -- ns_ax_collect_windows (child, view, elements); -- child = XWINDOW (child)->next; -- } ++ } ++ else ++ { ++ /* Internal (combination) window — recurse into children. */ ++ Lisp_Object child = w->contents; ++ while (!NILP (child)) + { + ns_ax_collect_windows (child, view, elements, existing); + child = XWINDOW (child)->next; + } - } - } - -@@ -10132,10 +11162,39 @@ - (void)rebuildAccessibilityTree - if (!emacsframe) - return; - -- NSMutableArray *newElements = [NSMutableArray arrayWithCapacity:4]; ++ } ++} ++ ++- (void)rebuildAccessibilityTree ++{ ++ if (!emacsframe) ++ return; ++ + /* Build map of existing elements by window pointer for reuse. */ + NSMutableDictionary *existing = [NSMutableDictionary dictionary]; + if (accessibilityElements) @@ -2179,9 +2189,7 @@ index e67edbe..cfc5b4c 100644 + NSMutableArray *newElements = [NSMutableArray arrayWithCapacity:8]; + + /* Collect from main window tree. */ - Lisp_Object root = FRAME_ROOT_WINDOW (emacsframe); -- ns_ax_collect_windows (root, self, newElements); -- accessibilityElements = newElements; ++ Lisp_Object root = FRAME_ROOT_WINDOW (emacsframe); + ns_ax_collect_windows (root, self, newElements, existing); + + /* Include minibuffer. */ @@ -2197,44 +2205,59 @@ index e67edbe..cfc5b4c 100644 +- (void)invalidateAccessibilityTree +{ + accessibilityTreeValid = NO; - } - - - (NSAccessibilityRole)accessibilityRole -@@ -10155,7 +11214,7 @@ - (BOOL)isAccessibilityElement - - - (NSArray *)accessibilityChildren - { -- if (!accessibilityElements || [accessibilityElements count] == 0) ++} ++ ++- (NSAccessibilityRole)accessibilityRole ++{ ++ return NSAccessibilityGroupRole; ++} ++ ++- (NSString *)accessibilityLabel ++{ ++ return @"Emacs"; ++} ++ ++- (BOOL)isAccessibilityElement ++{ ++ return YES; ++} ++ ++- (NSArray *)accessibilityChildren ++{ + if (!accessibilityElements || !accessibilityTreeValid) - [self rebuildAccessibilityTree]; - return accessibilityElements; - } -@@ -10165,16 +11224,15 @@ - (id)accessibilityFocusedUIElement - if (!emacsframe) - return self; - -- /* Ensure tree exists (lazy init); avoid redundant rebuild since -- postAccessibilityUpdates already rebuilds each cycle. */ -- if (!accessibilityElements || [accessibilityElements count] == 0) ++ [self rebuildAccessibilityTree]; ++ return accessibilityElements; ++} ++ ++- (id)accessibilityFocusedUIElement ++{ ++ if (!emacsframe) ++ return self; ++ + if (!accessibilityElements || !accessibilityTreeValid) - [self rebuildAccessibilityTree]; - - struct window *sel = XWINDOW (emacsframe->selected_window); -- for (EmacsAccessibilityBuffer *elem in accessibilityElements) ++ [self rebuildAccessibilityTree]; ++ ++ struct window *sel = XWINDOW (emacsframe->selected_window); + for (EmacsAccessibilityElement *elem in accessibilityElements) - { -- if (elem.emacsWindow == sel) -- return elem; ++ { + if ([elem isKindOfClass:[EmacsAccessibilityBuffer class]] + && elem.emacsWindow == sel) + return elem; - } - return self; - } -@@ -10190,32 +11248,143 @@ - (void)postAccessibilityUpdates - if (!emacsframe) - return; - ++ } ++ return self; ++} ++ ++/* Called from ns_update_end to post AX notifications. ++ ++ Important: post notifications BEFORE rebuilding the tree. ++ The existing elements carry cached state (modiff, point) from the ++ previous redisplay cycle. Rebuilding first would create fresh ++ elements with current values, making change detection impossible. */ ++- (void)postAccessibilityUpdates ++{ ++ if (!emacsframe) ++ return; ++ + /* Re-entrance guard: VoiceOver callbacks during notification posting + can trigger redisplay, which calls ns_update_end, which calls us + again. Prevent infinite recursion. */ @@ -2271,16 +2294,11 @@ index e67edbe..cfc5b4c 100644 + return; + } + - /* Post per-buffer notifications using EXISTING elements that have -- cached state from the previous cycle. */ -- for (EmacsAccessibilityBuffer *elem in accessibilityElements) ++ /* Post per-buffer notifications using EXISTING elements that have + cached state from the previous cycle. Validate each window + pointer before use. */ + for (EmacsAccessibilityElement *elem in accessibilityElements) - { -- struct window *w = elem.emacsWindow; -- if (w && WINDOW_LEAF_P (w)) -- [elem postAccessibilityUpdatesForWindow:w frame:emacsframe]; ++ { + if ([elem isKindOfClass:[EmacsAccessibilityBuffer class]]) + { + struct window *w = elem.emacsWindow; @@ -2289,14 +2307,12 @@ index e67edbe..cfc5b4c 100644 + [(EmacsAccessibilityBuffer *) elem + postAccessibilityNotificationsForFrame:emacsframe]; + } - } - -- /* Check for window switch (C-x o) before rebuild. */ ++ } ++ + /* Check for window switch (C-x o). */ - Lisp_Object curSel = emacsframe->selected_window; - BOOL windowSwitched = !EQ (curSel, lastSelectedWindow); - if (windowSwitched) -- lastSelectedWindow = curSel; ++ Lisp_Object curSel = emacsframe->selected_window; ++ BOOL windowSwitched = !EQ (curSel, lastSelectedWindow); ++ if (windowSwitched) + { + lastSelectedWindow = curSel; + id focused = [self accessibilityFocusedUIElement]; @@ -2317,13 +2333,9 @@ index e67edbe..cfc5b4c 100644 + + accessibilityUpdating = NO; +} - -- /* Now rebuild tree to pick up window configuration changes. */ -- [self rebuildAccessibilityTree]; ++ +/* ---- Cursor position for Zoom (via accessibilityBoundsForRange:) ---- - -- /* Post focus change AFTER rebuild so the new element exists. */ -- if (windowSwitched) ++ + accessibilityFrame returns the VIEW's frame (standard behavior). + The cursor location is exposed through accessibilityBoundsForRange: + which AT tools query using the selectedTextRange. */ @@ -2369,11 +2381,7 @@ index e67edbe..cfc5b4c 100644 +{ + if ([attribute isEqualToString: + NSAccessibilityBoundsForRangeParameterizedAttribute]) - { -- id focused = [self accessibilityFocusedUIElement]; -- if (focused && focused != self) -- NSAccessibilityPostNotification (focused, -- NSAccessibilityFocusedUIElementChangedNotification); ++ { + NSRange range = [(NSValue *) parameter rangeValue]; + return [NSValue valueWithRect: + [self accessibilityBoundsForRange:range]]; @@ -2384,12 +2392,16 @@ index e67edbe..cfc5b4c 100644 + { + NSRange range = [(NSValue *) parameter rangeValue]; + return [self accessibilityStringForRange:range]; - } ++ } + + return [super accessibilityAttributeValue:attribute forParameter:parameter]; - } ++} ++ ++#endif /* NS_IMPL_COCOA */ ++ + @end /* EmacsView */ + - #endif /* NS_IMPL_COCOA */ -- 2.43.0