From b3a6141831e3cbc7267f2b166290b28406e9983a Mon Sep 17 00:00:00 2001 From: Daneel Date: Thu, 26 Feb 2026 17:49:56 +0100 Subject: [PATCH] patches: robust AX/VoiceOver fix after full audit pipeline --- ...oundsForRange-for-macOS-Zoom-cursor-.patch | 463 +++++++++++------- 1 file changed, 276 insertions(+), 187 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 b9d4224..9a14bf3 100644 --- a/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch +++ b/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch @@ -1,19 +1,19 @@ -From 0084027a7680cbfb1449f091312ac21ed3794a6c Mon Sep 17 00:00:00 2001 +From 991ef1c9f04ccfdd71482ef3df54050f314e8ab5 Mon Sep 17 00:00:00 2001 From: Daneel -Date: Thu, 26 Feb 2026 17:30:09 +0100 +Date: Thu, 26 Feb 2026 17:49:52 +0100 Subject: [PATCH] ns: implement AXBoundsForRange and VoiceOver interaction fixes --- - nsterm.h | 68 ++ - nsterm.m | 1968 +++++++++++++++++++++++++++++++++++++++++++++++++----- - 2 files changed, 1855 insertions(+), 181 deletions(-) + nsterm.h | 70 ++ + nsterm.m | 2058 +++++++++++++++++++++++++++++++++++++++++++++++++----- + 2 files changed, 1960 insertions(+), 168 deletions(-) diff --git a/src/nsterm.h b/src/nsterm.h -index 7c1ee4c..719eeba 100644 +index 7c1ee4c..97da979 100644 --- a/src/nsterm.h +++ b/src/nsterm.h -@@ -453,6 +453,59 @@ enum ns_return_frame_mode +@@ -453,6 +453,61 @@ enum ns_return_frame_mode @end @@ -58,6 +58,8 @@ index 7c1ee4c..719eeba 100644 +@property (nonatomic, assign) ptrdiff_t cachedPoint; +@property (nonatomic, assign) BOOL cachedMarkActive; +@property (nonatomic, copy) NSString *cachedCompletionAnnouncement; ++@property (nonatomic, assign) ptrdiff_t cachedCompletionOverlayStart; ++@property (nonatomic, assign) ptrdiff_t cachedCompletionOverlayEnd; +- (void)invalidateTextCache; +- (void)postAccessibilityNotificationsForFrame:(struct frame *)f; +- (ptrdiff_t)charposForAccessibilityIndex:(NSUInteger)ax_idx; @@ -73,7 +75,7 @@ index 7c1ee4c..719eeba 100644 /* ========================================================================== The main Emacs view -@@ -471,6 +524,14 @@ enum ns_return_frame_mode +@@ -471,6 +526,14 @@ enum ns_return_frame_mode #ifdef NS_IMPL_COCOA char *old_title; BOOL maximizing_resize; @@ -88,7 +90,7 @@ index 7c1ee4c..719eeba 100644 #endif BOOL font_panel_active; NSFont *font_panel_result; -@@ -528,6 +589,13 @@ enum ns_return_frame_mode +@@ -528,6 +591,13 @@ enum ns_return_frame_mode - (void)windowWillExitFullScreen; - (void)windowDidExitFullScreen; - (void)windowDidBecomeKey; @@ -103,7 +105,7 @@ index 7c1ee4c..719eeba 100644 diff --git a/src/nsterm.m b/src/nsterm.m -index 932d209..336150a 100644 +index 932d209..8673194 100644 --- a/src/nsterm.m +++ b/src/nsterm.m @@ -1104,6 +1104,11 @@ ns_update_end (struct frame *f) @@ -156,7 +158,7 @@ index 932d209..336150a 100644 ns_focus (f, NULL, 0); NSGraphicsContext *ctx = [NSGraphicsContext currentContext]; -@@ -6849,261 +6885,1511 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action) +@@ -6849,245 +6885,1611 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action) /* ========================================================================== @@ -383,57 +385,29 @@ index 932d209..336150a 100644 } -- (void) noteUserCancelledSelection --{ -- font_panel_active = NO; -- -- if (font_panel_result) -- [font_panel_result release]; -- font_panel_result = nil; - -- [NSApp stop: self]; --} --#endif ++ +/* ---- Helper: screen rect for a character range via glyph matrix ---- */ - --- (Lisp_Object) showFontPanel ++ +static NSRect +ns_ax_frame_for_range (struct window *w, EmacsView *view, + ptrdiff_t text_start, NSRange range) { -- 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 +- font_panel_active = NO; + if (!w || !w->current_matrix || !view) + return NSZeroRect; --#ifdef NS_IMPL_GNUSTEP -- nsfont = ((struct nsfont_info *) font)->nsfont; --#else -- nsfont = (NSFont *) macfont_get_nsctfont (font); --#endif +- if (font_panel_result) +- [font_panel_result release]; +- font_panel_result = 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; --#ifdef NS_IMPL_COCOA -- buttons -- = ns_create_font_panel_buttons (self, -- @selector (noteUserSelectedFont), -- @selector (noteUserCancelledSelection)); -- [[fm fontPanel: YES] setAccessoryView: buttons]; -- [buttons release]; --#endif +- [NSApp stop: self]; + struct glyph_matrix *matrix = w->current_matrix; + NSRect result = NSZeroRect; + BOOL found = NO; - -- [fm setSelectedFont: nsfont isMultiple: NO]; -- [fm orderFrontFontPanel: NSApp]; ++ + for (int i = 0; i < matrix->nrows; i++) + { + struct glyph_row *row = matrix->rows + i; @@ -441,22 +415,10 @@ index 932d209..336150a 100644 + continue; + if (!row->displays_text_p && !row->ends_at_zv_p) + continue; - -- font_panel_active = YES; -- timeout = make_timespec (0, 100000000); ++ + ptrdiff_t row_start = MATRIX_ROW_START_CHARPOS (row); + ptrdiff_t row_end = MATRIX_ROW_END_CHARPOS (row); - -- 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 (row_start < cp_end && row_end > cp_start) + { + int window_x, window_y, window_width; @@ -479,16 +441,10 @@ index 932d209..336150a 100644 + result = NSUnionRect (result, rowRect); + } + } - -- if (font_panel_result) -- [font_panel_result autorelease]; ++ + if (!found) + return NSZeroRect; - --#ifdef NS_IMPL_COCOA -- if (!canceled) -- font_panel_result = nil; --#endif ++ + /* Clip result to text area bounds. */ + { + int text_area_x, text_area_y, text_area_w, text_area_h; @@ -498,23 +454,147 @@ index 932d209..336150a 100644 + if (NSMaxY (result) > max_y) + result.size.height = max_y - result.origin.y; + } - -- result = font_panel_result; -- font_panel_result = nil; ++ + /* Convert from EmacsView (flipped) coords to screen coords. */ + NSRect winRect = [view convertRect:result toView:nil]; + return [[view window] convertRectToScreen:winRect]; + } +-#endif + +-- (Lisp_Object) showFontPanel ++static NSUInteger ++ns_ax_utf16_length_for_buffer_range (struct buffer *b, ptrdiff_t start, ++ ptrdiff_t end) + { +- 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 (!b || end <= start) ++ return 0; + +-#ifdef NS_IMPL_GNUSTEP +- nsfont = ((struct nsfont_info *) font)->nsfont; +-#else +- nsfont = (NSFont *) macfont_get_nsctfont (font); +-#endif ++ struct buffer *oldb = current_buffer; ++ if (b != current_buffer) ++ set_buffer_internal_1 (b); + +-#ifdef NS_IMPL_COCOA +- buttons +- = ns_create_font_panel_buttons (self, +- @selector (noteUserSelectedFont), +- @selector (noteUserCancelledSelection)); +- [[fm fontPanel: YES] setAccessoryView: buttons]; +- [buttons release]; +-#endif ++ Lisp_Object lstr = Fbuffer_substring_no_properties (make_fixnum (start), ++ make_fixnum (end)); ++ NSString *nsstr = [NSString stringWithLispString:lstr]; ++ NSUInteger len = [nsstr length]; + +- [fm setSelectedFont: nsfont isMultiple: NO]; +- [fm orderFrontFontPanel: NSApp]; ++ if (b != oldb) ++ set_buffer_internal_1 (oldb); + +- font_panel_active = YES; +- timeout = make_timespec (0, 100000000); ++ return len; +} +- 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 (); ++static BOOL ++ns_ax_find_completion_overlay_range (struct buffer *b, ptrdiff_t point, ++ ptrdiff_t *out_start, ++ ptrdiff_t *out_end) ++{ ++ if (!b || !out_start || !out_end) ++ return NO; + +- if (font_panel_result) +- [font_panel_result autorelease]; ++ Lisp_Object faceSym = intern ("completions-highlight"); ++ ptrdiff_t begv = BUF_BEGV (b); ++ ptrdiff_t zv = BUF_ZV (b); ++ ptrdiff_t best_start = 0; ++ ptrdiff_t best_end = 0; ++ ptrdiff_t best_dist = PTRDIFF_MAX; ++ BOOL found = NO; + +-#ifdef NS_IMPL_COCOA +- if (!canceled) +- font_panel_result = nil; +-#endif ++ for (ptrdiff_t scan = begv; scan < zv; scan++) ++ { ++ Lisp_Object overlays = Foverlays_at (make_fixnum (scan), Qnil); ++ Lisp_Object tail; ++ for (tail = overlays; CONSP (tail); tail = XCDR (tail)) ++ { ++ Lisp_Object ov = XCAR (tail); ++ Lisp_Object face = Foverlay_get (ov, Qface); ++ if (!(EQ (face, faceSym) ++ || (CONSP (face) && !NILP (Fmemq (faceSym, face))))) ++ continue; ++ ++ ptrdiff_t ov_start = OVERLAY_START (ov); ++ ptrdiff_t ov_end = OVERLAY_END (ov); ++ if (ov_end <= ov_start) ++ continue; ++ ++ ptrdiff_t dist = 0; ++ if (point < ov_start) ++ dist = ov_start - point; ++ else if (point > ov_end) ++ dist = point - ov_end; ++ ++ if (!found || dist < best_dist ++ || (dist == best_dist ++ && (ov_start < point && best_start >= point))) ++ { ++ best_start = ov_start; ++ best_end = ov_end; ++ best_dist = dist; ++ found = YES; ++ } ++ } ++ } + +- result = font_panel_result; +- font_panel_result = nil; ++ if (!found) ++ return NO; + - [[fm fontPanel: YES] setIsVisible: NO]; - font_panel_active = NO; ++ *out_start = best_start; ++ *out_end = best_end; ++ return YES; ++} - if (result) - return ns_font_desc_to_font_spec ([result fontDescriptor], - result); -+@implementation EmacsAccessibilityElement - return Qnil; ++@implementation EmacsAccessibilityElement ++ +- (NSRect)screenRectFromEmacsX:(int)x y:(int)y width:(int)ew height:(int)eh +{ + EmacsView *view = self.emacsView; @@ -581,6 +661,8 @@ index 932d209..336150a 100644 +@synthesize cachedPoint; +@synthesize cachedMarkActive; +@synthesize cachedCompletionAnnouncement; ++@synthesize cachedCompletionOverlayStart; ++@synthesize cachedCompletionOverlayEnd; +- (void)dealloc +{ @@ -650,41 +732,43 @@ index 932d209..336150a 100644 - most recently updated (I guess), which is not the correct one. */ - [(EmacsView *)[[theEvent window] delegate] keyDown: theEvent]; - return; -- } + ptrdiff_t start; + ns_ax_visible_run *runs = NULL; + NSUInteger nruns = 0; + NSString *text = ns_ax_buffer_text (w, &start, &runs, &nruns); - -- if (nsEvArray == nil) -- nsEvArray = [[NSMutableArray alloc] initWithCapacity: 1]; ++ + [cachedText release]; + cachedText = [text retain]; + cachedTextModiff = modiff; + cachedTextStart = start; - -- [NSCursor setHiddenUntilMouseMoves:! NILP (Vmake_pointer_invisible)]; ++ + if (visibleRuns) + xfree (visibleRuns); + visibleRuns = runs; + visibleRunCount = nruns; +} - -- if (!hlinfo->mouse_face_hidden -- && FIXNUMP (Vmouse_highlight) -- && !EQ (emacsframe->tab_bar_window, hlinfo->mouse_face_window)) ++ +/* ---- Index mapping ---- */ + +/* Convert buffer charpos to accessibility string index. */ +- (NSUInteger)accessibilityIndexForCharpos:(ptrdiff_t)charpos +{ ++ struct window *w = self.emacsWindow; ++ struct buffer *b = (w && WINDOW_LEAF_P (w)) ? XBUFFER (w->contents) : NULL; ++ + for (NSUInteger i = 0; i < visibleRunCount; i++) - { -- clear_mouse_face (hlinfo); -- hlinfo->mouse_face_hidden = true; ++ { + ns_ax_visible_run *r = &visibleRuns[i]; + if (charpos >= r->charpos && charpos < r->charpos + r->length) -+ return r->ax_start + (NSUInteger) (charpos - r->charpos); ++ { ++ if (!b) ++ return r->ax_start; ++ NSUInteger delta = ns_ax_utf16_length_for_buffer_range (b, r->charpos, ++ charpos); ++ if (delta > r->ax_length) ++ delta = r->ax_length; ++ return r->ax_start + delta; ++ } + /* If charpos falls in an invisible gap before the next run, + map it to the start of the next visible run. */ + if (charpos < r->charpos) @@ -695,20 +779,43 @@ index 932d209..336150a 100644 + { + ns_ax_visible_run *last = &visibleRuns[visibleRunCount - 1]; + return last->ax_start + last->ax_length; - } ++ } + return 0; +} - -- if (!processingCompose) ++ +/* Convert accessibility string index to buffer charpos. */ +- (ptrdiff_t)charposForAccessibilityIndex:(NSUInteger)ax_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++) + { + ns_ax_visible_run *r = &visibleRuns[i]; + if (ax_idx >= r->ax_start + && ax_idx < r->ax_start + r->ax_length) -+ return r->charpos + (ptrdiff_t) (ax_idx - r->ax_start); ++ { ++ if (!b) ++ return r->charpos; ++ ++ NSUInteger target = ax_idx - r->ax_start; ++ ptrdiff_t lo = r->charpos; ++ ptrdiff_t hi = r->charpos + r->length; ++ ++ while (lo < hi) ++ { ++ ptrdiff_t mid = lo + (hi - lo) / 2; ++ NSUInteger mid_len = ns_ax_utf16_length_for_buffer_range (b, ++ r->charpos, ++ mid); ++ if (mid_len < target) ++ lo = mid + 1; ++ else ++ hi = mid; ++ } ++ ++ return lo; ++ } + } + /* Past end — return last charpos. */ + if (visibleRunCount > 0) @@ -850,7 +957,7 @@ index 932d209..336150a 100644 + + SET_PT_BOTH (charpos, CHAR_TO_BYTE (charpos)); + -+ /* If range has nonzero length, activate the mark. */ ++ /* Keep mark state aligned with requested selection range. */ + if (range.length > 0) + { + ptrdiff_t mark_charpos = [self charposForAccessibilityIndex: @@ -859,7 +966,10 @@ index 932d209..336150a 100644 + mark_charpos = BUF_ZV (b); + Fset_marker (BVAR (b, mark), make_fixnum (mark_charpos), + Fcurrent_buffer ()); ++ bset_mark_active (b, Qt); + } ++ else ++ bset_mark_active (b, Qnil); + + if (b != oldb) + set_buffer_internal_1 (oldb); @@ -869,6 +979,7 @@ index 932d209..336150a 100644 + /* Update cached state so the next notification cycle doesn't + re-announce this movement. */ + self.cachedPoint = charpos; ++ self.cachedMarkActive = (range.length > 0); +} + +- (void)setAccessibilityFocused:(BOOL)flag @@ -1264,6 +1375,8 @@ index 932d209..336150a 100644 + if (![self isAccessibilityFocused] && cachedText) + { + NSString *announceText = nil; ++ ptrdiff_t currentOverlayStart = 0; ++ ptrdiff_t currentOverlayEnd = 0; + + struct buffer *oldb2 = current_buffer; + if (b != current_buffer) @@ -1351,43 +1464,22 @@ index 932d209..336150a 100644 + } + } + -+ /* 4) Fallback: scan for completions-highlight anywhere in buffer. -+ TAB cycling can move highlight without moving point. */ ++ /* 4) Fallback: select the best completions-highlight overlay. ++ Prefer overlay nearest to point over first-found in buffer. */ + if (!announceText) + { -+ Lisp_Object faceSym = intern ("completions-highlight"); -+ ptrdiff_t begv2 = BUF_BEGV (b); -+ ptrdiff_t zv2 = BUF_ZV (b); -+ ptrdiff_t scanPos; -+ BOOL found = NO; -+ -+ for (scanPos = begv2; scanPos < zv2 && !found; scanPos++) ++ ptrdiff_t ov_start = 0; ++ ptrdiff_t ov_end = 0; ++ if (ns_ax_find_completion_overlay_range (b, point, &ov_start, &ov_end)) + { -+ Lisp_Object overlays = Foverlays_at (make_fixnum (scanPos), Qnil); -+ Lisp_Object tail; -+ for (tail = overlays; CONSP (tail); tail = XCDR (tail)) ++ NSUInteger ax_s = [self accessibilityIndexForCharpos:ov_start]; ++ NSUInteger ax_e = [self accessibilityIndexForCharpos:ov_end]; ++ if (ax_e > ax_s && ax_e <= [cachedText length]) + { -+ Lisp_Object ov = XCAR (tail); -+ Lisp_Object face = Foverlay_get (ov, Qface); -+ if (EQ (face, faceSym) -+ || (CONSP (face) -+ && !NILP (Fmemq (faceSym, face)))) -+ { -+ ptrdiff_t ov_start = OVERLAY_START (ov); -+ ptrdiff_t ov_end = OVERLAY_END (ov); -+ if (ov_end > ov_start) -+ { -+ NSUInteger ax_s = [self accessibilityIndexForCharpos: -+ ov_start]; -+ NSUInteger ax_e = [self accessibilityIndexForCharpos: -+ ov_end]; -+ if (ax_e > ax_s && ax_e <= [cachedText length]) -+ announceText = [cachedText substringWithRange: -+ NSMakeRange (ax_s, ax_e - ax_s)]; -+ } -+ found = YES; -+ break; -+ } ++ announceText = [cachedText substringWithRange: ++ NSMakeRange (ax_s, ax_e - ax_s)]; ++ currentOverlayStart = ov_start; ++ currentOverlayEnd = ov_end; + } + } + } @@ -1418,7 +1510,12 @@ index 932d209..336150a 100644 + [NSCharacterSet whitespaceAndNewlineCharacterSet]]; + if ([announceText length] > 0) + { -+ if (![announceText isEqualToString:self.cachedCompletionAnnouncement]) ++ BOOL textChanged = ![announceText isEqualToString: ++ self.cachedCompletionAnnouncement]; ++ BOOL overlayChanged = ++ (currentOverlayStart != self.cachedCompletionOverlayStart ++ || currentOverlayEnd != self.cachedCompletionOverlayEnd); ++ if (textChanged || overlayChanged) + { + NSDictionary *annInfo = @{ + NSAccessibilityAnnouncementKey: announceText, @@ -1431,63 +1528,56 @@ index 932d209..336150a 100644 + annInfo); + } + self.cachedCompletionAnnouncement = announceText; ++ self.cachedCompletionOverlayStart = currentOverlayStart; ++ self.cachedCompletionOverlayEnd = currentOverlayEnd; + } + else -+ self.cachedCompletionAnnouncement = nil; ++ { ++ self.cachedCompletionAnnouncement = nil; ++ self.cachedCompletionOverlayStart = 0; ++ self.cachedCompletionOverlayEnd = 0; ++ } + } + else -+ self.cachedCompletionAnnouncement = nil; ++ { ++ self.cachedCompletionAnnouncement = nil; ++ self.cachedCompletionOverlayStart = 0; ++ self.cachedCompletionOverlayEnd = 0; ++ } + } + + } + else + { + if ([self isAccessibilityFocused]) -+ self.cachedCompletionAnnouncement = nil; ++ { ++ self.cachedCompletionAnnouncement = nil; ++ self.cachedCompletionOverlayStart = 0; ++ self.cachedCompletionOverlayEnd = 0; ++ } + else + { + [self ensureTextCache]; + if (cachedText) + { + NSString *announceText = nil; ++ ptrdiff_t currentOverlayStart = 0; ++ ptrdiff_t currentOverlayEnd = 0; + struct buffer *oldb2 = current_buffer; + if (b != current_buffer) + set_buffer_internal_1 (b); + -+ Lisp_Object faceSym = intern ("completions-highlight"); -+ ptrdiff_t begv2 = BUF_BEGV (b); -+ ptrdiff_t zv2 = BUF_ZV (b); -+ ptrdiff_t scanPos; -+ BOOL found = NO; -+ -+ for (scanPos = begv2; scanPos < zv2 && !found; scanPos++) ++ if (ns_ax_find_completion_overlay_range (b, point, ++ ¤tOverlayStart, ++ ¤tOverlayEnd)) + { -+ Lisp_Object overlays = Foverlays_at (make_fixnum (scanPos), Qnil); -+ Lisp_Object tail; -+ for (tail = overlays; CONSP (tail); tail = XCDR (tail)) -+ { -+ Lisp_Object ov = XCAR (tail); -+ Lisp_Object face = Foverlay_get (ov, Qface); -+ if (EQ (face, faceSym) -+ || (CONSP (face) -+ && !NILP (Fmemq (faceSym, face)))) -+ { -+ ptrdiff_t ov_start = OVERLAY_START (ov); -+ ptrdiff_t ov_end = OVERLAY_END (ov); -+ if (ov_end > ov_start) -+ { -+ NSUInteger ax_s = [self accessibilityIndexForCharpos: -+ ov_start]; -+ NSUInteger ax_e = [self accessibilityIndexForCharpos: -+ ov_end]; -+ if (ax_e > ax_s && ax_e <= [cachedText length]) -+ announceText = [cachedText substringWithRange: -+ NSMakeRange (ax_s, ax_e - ax_s)]; -+ } -+ found = YES; -+ break; -+ } -+ } ++ NSUInteger ax_s = [self accessibilityIndexForCharpos: ++ currentOverlayStart]; ++ NSUInteger ax_e = [self accessibilityIndexForCharpos: ++ currentOverlayEnd]; ++ if (ax_e > ax_s && ax_e <= [cachedText length]) ++ announceText = [cachedText substringWithRange: ++ NSMakeRange (ax_s, ax_e - ax_s)]; + } + + if (b != oldb2) @@ -1499,7 +1589,12 @@ index 932d209..336150a 100644 + [NSCharacterSet whitespaceAndNewlineCharacterSet]]; + if ([announceText length] > 0) + { -+ if (![announceText isEqualToString:self.cachedCompletionAnnouncement]) ++ BOOL textChanged = ![announceText isEqualToString: ++ self.cachedCompletionAnnouncement]; ++ BOOL overlayChanged = ++ (currentOverlayStart != self.cachedCompletionOverlayStart ++ || currentOverlayEnd != self.cachedCompletionOverlayEnd); ++ if (textChanged || overlayChanged) + { + NSDictionary *annInfo = @{ + NSAccessibilityAnnouncementKey: announceText, @@ -1512,12 +1607,22 @@ index 932d209..336150a 100644 + annInfo); + } + self.cachedCompletionAnnouncement = announceText; ++ self.cachedCompletionOverlayStart = currentOverlayStart; ++ self.cachedCompletionOverlayEnd = currentOverlayEnd; + } + else -+ self.cachedCompletionAnnouncement = nil; ++ { ++ self.cachedCompletionAnnouncement = nil; ++ self.cachedCompletionOverlayStart = 0; ++ self.cachedCompletionOverlayEnd = 0; ++ } + } + else -+ self.cachedCompletionAnnouncement = nil; ++ { ++ self.cachedCompletionAnnouncement = nil; ++ self.cachedCompletionOverlayStart = 0; ++ self.cachedCompletionOverlayEnd = 0; ++ } + } + } + } @@ -1830,26 +1935,10 @@ index 932d209..336150a 100644 + most recently updated (I guess), which is not the correct one. */ + [(EmacsView *)[[theEvent window] delegate] keyDown: theEvent]; + return; -+ } -+ -+ if (nsEvArray == nil) -+ nsEvArray = [[NSMutableArray alloc] initWithCapacity: 1]; -+ -+ [NSCursor setHiddenUntilMouseMoves:! NILP (Vmake_pointer_invisible)]; -+ -+ if (!hlinfo->mouse_face_hidden -+ && FIXNUMP (Vmouse_highlight) -+ && !EQ (emacsframe->tab_bar_window, hlinfo->mouse_face_window)) -+ { -+ clear_mouse_face (hlinfo); -+ hlinfo->mouse_face_hidden = true; -+ } -+ -+ if (!processingCompose) - { - /* FIXME: What should happen for key sequences with more than - one character? */ -@@ -8237,6 +9523,27 @@ ns_in_echo_area (void) + } + + if (nsEvArray == nil) +@@ -8237,6 +9639,27 @@ ns_in_echo_area (void) XSETFRAME (event.frame_or_window, emacsframe); kbd_buffer_store_event (&event); ns_send_appdefined (-1); // Kick main loop @@ -1877,7 +1966,7 @@ index 932d209..336150a 100644 } -@@ -9474,6 +10781,297 @@ ns_in_echo_area (void) +@@ -9474,6 +10897,297 @@ ns_in_echo_area (void) return fs_state; } @@ -2175,7 +2264,7 @@ index 932d209..336150a 100644 @end /* EmacsView */ -@@ -9941,6 +11539,14 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c) +@@ -9941,6 +11655,14 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c) return [super accessibilityAttributeValue:attribute]; }