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 0bf5e0b..89b24f4 100644 --- a/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch +++ b/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch @@ -1,8 +1,8 @@ diff --git a/src/nsterm.h b/src/nsterm.h -index 7c1ee4c..4abeafe 100644 +index 7c1ee4c..855d302 100644 --- a/src/nsterm.h +++ b/src/nsterm.h -@@ -453,6 +453,40 @@ enum ns_return_frame_mode +@@ -453,6 +453,57 @@ enum ns_return_frame_mode @end @@ -22,8 +22,23 @@ index 7c1ee4c..4abeafe 100644 +- (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 ++ while charpos values may have gaps. */ ++typedef struct ns_ax_visible_run ++{ ++ ptrdiff_t charpos; /* Buffer charpos where this visible run starts. */ ++ ptrdiff_t length; /* Number of visible characters in this run. */ ++ NSUInteger ax_start; /* Starting index in the accessibility string. */ ++} ns_ax_visible_run; ++ +/* Virtual AXTextArea element — one per visible Emacs window (buffer). */ +@interface EmacsAccessibilityBuffer : EmacsAccessibilityElement ++{ ++ ns_ax_visible_run *visibleRuns; ++ NSUInteger visibleRunCount; ++} +@property (nonatomic, retain) NSString *cachedText; +@property (nonatomic, assign) ptrdiff_t cachedTextModiff; +@property (nonatomic, assign) ptrdiff_t cachedTextStart; @@ -32,6 +47,8 @@ index 7c1ee4c..4abeafe 100644 +@property (nonatomic, assign) BOOL cachedMarkActive; +- (void)invalidateTextCache; +- (void)postAccessibilityNotificationsForFrame:(struct frame *)f; ++- (ptrdiff_t)charposForAccessibilityIndex:(NSUInteger)ax_idx; ++- (NSUInteger)accessibilityIndexForCharpos:(ptrdiff_t)charpos; +@end + +/* Virtual AXStaticText element — one per mode line. */ @@ -43,7 +60,7 @@ index 7c1ee4c..4abeafe 100644 /* ========================================================================== The main Emacs view -@@ -471,6 +505,14 @@ enum ns_return_frame_mode +@@ -471,6 +522,14 @@ enum ns_return_frame_mode #ifdef NS_IMPL_COCOA char *old_title; BOOL maximizing_resize; @@ -58,7 +75,7 @@ index 7c1ee4c..4abeafe 100644 #endif BOOL font_panel_active; NSFont *font_panel_result; -@@ -528,6 +570,13 @@ enum ns_return_frame_mode +@@ -528,6 +587,13 @@ enum ns_return_frame_mode - (void)windowWillExitFullScreen; - (void)windowDidExitFullScreen; - (void)windowDidBecomeKey; @@ -73,7 +90,7 @@ index 7c1ee4c..4abeafe 100644 diff --git a/src/nsterm.m b/src/nsterm.m -index 932d209..95a360d 100644 +index 932d209..45e480e 100644 --- a/src/nsterm.m +++ b/src/nsterm.m @@ -1104,6 +1104,11 @@ ns_update_end (struct frame *f) @@ -126,88 +143,209 @@ index 932d209..95a360d 100644 ns_focus (f, NULL, 0); NSGraphicsContext *ctx = [NSGraphicsContext currentContext]; -@@ -6847,6 +6883,890 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action) - } - #endif +@@ -6849,265 +6885,1329 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action) -+/* ========================================================================== -+ + /* ========================================================================== + +- EmacsView implementation + Accessibility virtual elements (macOS / Cocoa only) -+ -+ ========================================================================== */ -+ + + ========================================================================== */ + +#ifdef NS_IMPL_COCOA -+ + +-@implementation EmacsView +/* ---- Helper: extract buffer text for accessibility ---- */ -+ + +-- (void)windowDidEndLiveResize:(NSNotification *)notification +-{ +- [self updateFramePosition]; +-} +/* Maximum characters exposed via accessibilityValue. */ +#define NS_AX_TEXT_CAP 100000 + +-/* Needed to inform when window closed from lisp. */ +-- (void) setWindowClosing: (BOOL)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_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 @""; + } -+ + + struct buffer *b = XBUFFER (w->contents); + if (!b) + { + *out_start = 0; + return @""; + } -+ + +-- (void)dealloc +-{ +- NSTRACE ("[EmacsView dealloc]"); + ptrdiff_t begv = BUF_BEGV (b); + ptrdiff_t zv = BUF_ZV (b); -+ ptrdiff_t len = zv - begv; -+ -+ /* Cap at NS_AX_TEXT_CAP characters, centered on point. */ -+ if (len > NS_AX_TEXT_CAP) -+ { -+ ptrdiff_t pt = BUF_PT (b); -+ ptrdiff_t half = NS_AX_TEXT_CAP / 2; -+ ptrdiff_t start = MAX (begv, pt - half); -+ ptrdiff_t end = MIN (zv, start + NS_AX_TEXT_CAP); -+ start = MAX (begv, end - NS_AX_TEXT_CAP); -+ begv = start; -+ zv = end; -+ } -+ + +- /* Clear the view resize notification. */ +- [[NSNotificationCenter defaultCenter] +- removeObserver:self +- name:NSViewFrameDidChangeNotification +- object:nil]; + *out_start = begv; -+ + +- if (fs_state == FULLSCREEN_BOTH) +- [nonfs_window release]; + if (zv <= begv) + return @""; + +-#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MIN_REQUIRED >= 101400 +- /* Release layer and menu */ +- EmacsLayer *layer = (EmacsLayer *)[self layer]; +- [layer release]; +-#endif ++ /* Temporarily switch to buffer b so TEXT_PROP_MEANS_INVISIBLE ++ can access its invisibility_spec. */ ++ struct buffer *oldb = 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 ++ * sizeof (ns_ax_visible_run)); ++ 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; ++ while (pos < zv) ++ { ++ /* Check invisible property (text properties + overlays). */ ++ Lisp_Object invis = Fget_char_property (make_fixnum (pos), ++ Qinvisible, Qnil); ++ if (!NILP (invis) && TEXT_PROP_MEANS_INVISIBLE (invis)) ++ { ++ /* Skip to the next position where invisible changes. */ ++ Lisp_Object next = Fnext_single_char_property_change ( ++ make_fixnum (pos), Qinvisible, Qnil, make_fixnum (zv)); ++ pos = FIXNUMP (next) ? XFIXNUM (next) : zv; ++ continue; ++ } + +-#ifdef NS_IMPL_GNUSTEP +- nsfont = ((struct nsfont_info *) font)->nsfont; +-#else +- nsfont = (NSFont *) macfont_get_nsctfont (font); +-#endif ++ /* 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; ++ /* 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) ++ run_len = (ptrdiff_t) (NS_AX_TEXT_CAP - ax_offset); ++ if (run_len <= 0) ++ break; ++ run_end = pos + run_len; + +- if (font_panel_result) +- [font_panel_result release]; ++ /* Extract this visible run's text. */ ++ ptrdiff_t pos_byte = buf_charpos_to_bytepos (b, pos); ++ ptrdiff_t end_byte = buf_charpos_to_bytepos (b, run_end); ++ unsigned char *data = BUF_BYTE_ADDRESS (b, pos_byte); ++ ptrdiff_t nbytes = end_byte - pos_byte; + +- font_panel_result = (NSFont *) [sender convertFont: nsfont]; ++ Lisp_Object lstr = make_string_from_bytes ((char *) data, ++ run_len, nbytes); ++ [result appendString:[NSString stringWithLispString:lstr]]; + +- if (font_panel_result) +- [font_panel_result retain]; ++ /* Record this visible run in the mapping. */ ++ if (nruns >= run_capacity) ++ { ++ run_capacity *= 2; ++ runs = xrealloc (runs, run_capacity ++ * sizeof (ns_ax_visible_run)); ++ } ++ runs[nruns].charpos = pos; ++ runs[nruns].length = run_len; ++ runs[nruns].ax_start = ax_offset; ++ nruns++; + +-#ifndef NS_IMPL_COCOA +- font_panel_active = NO; +- [NSApp stop: self]; +-#endif ++ ax_offset += (NSUInteger) run_len; ++ pos = run_end; ++ } + -+ ptrdiff_t begv_byte = buf_charpos_to_bytepos (b, begv); -+ ptrdiff_t zv_byte = buf_charpos_to_bytepos (b, zv); -+ unsigned char *data = BUF_BYTE_ADDRESS (b, begv_byte); -+ ptrdiff_t nbytes = zv_byte - begv_byte; -+ -+ Lisp_Object lstr = make_string_from_bytes ((char *) data, -+ zv - begv, nbytes); -+ return [NSString stringWithLispString:lstr]; -+} ++ if (b != oldb) ++ set_buffer_internal_1 (oldb); + ++ *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++) @@ -219,28 +357,64 @@ index 932d209..95a360d 100644 + length:1]]; + } + } -+ } + } +- +- [NSApp stop: self]; + return text; -+} -+ -+ + } + +-- (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 + 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 + /* 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 + 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; @@ -248,10 +422,22 @@ index 932d209..95a360d 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; @@ -274,10 +460,16 @@ index 932d209..95a360d 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; @@ -287,15 +479,23 @@ index 932d209..95a360d 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]; +} -+ -+ + +- [[fm fontPanel: YES] setIsVisible: NO]; +- font_panel_active = NO; + +- if (result) +- return ns_font_desc_to_font_spec ([result fontDescriptor], +- result); +@implementation EmacsAccessibilityElement -+ + +- return Qnil; +- (NSRect)screenRectFromEmacsX:(int)x y:(int)y width:(int)ew height:(int)eh +{ + EmacsView *view = self.emacsView; @@ -305,33 +505,55 @@ index 932d209..95a360d 100644 + NSRect r = NSMakeRect (x, y, ew, eh); + NSRect winRect = [view convertRect:r toView:nil]; + return [[view window] convertRectToScreen:winRect]; -+} -+ + } + +-- (BOOL)acceptsFirstResponder +- (BOOL)isAccessibilityElement -+{ -+ return YES; -+} -+ + { +- NSTRACE ("[EmacsView acceptsFirstResponder]"); + return YES; + } + +-/* Tell NS we want to accept clicks that activate the window */ +-- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent +/* ---- Hierarchy plumbing (required for VoiceOver to find us) ---- */ + +- (id)accessibilityParent -+{ + { +- NSTRACE_MSG ("First mouse event: type=%ld, clickCount=%ld", +- [theEvent type], [theEvent clickCount]); +- return ns_click_through; + return NSAccessibilityUnignoredAncestor (self.emacsView); -+} + } +-- (void)resetCursorRects + +- (id)accessibilityWindow -+{ + { +- NSRect visible = [self visibleRect]; +- NSCursor *currentCursor = FRAME_POINTER_TYPE (emacsframe); +- NSTRACE ("[EmacsView resetCursorRects]"); + return [self.emacsView window]; +} -+ + +- if (currentCursor == nil) +- currentCursor = [NSCursor arrowCursor]; +- (id)accessibilityTopLevelUIElement +{ + return [self.emacsView window]; +} -+ + +- if (!NSIsEmptyRect (visible)) +- [self addCursorRect: visible cursor: currentCursor]; +@end -+ -+ + +-#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 +-} + +@implementation EmacsAccessibilityBuffer +@synthesize cachedText; +@synthesize cachedTextModiff; @@ -339,46 +561,145 @@ index 932d209..95a360d 100644 +@synthesize cachedModiff; +@synthesize cachedPoint; +@synthesize cachedMarkActive; -+ + +- (void)dealloc +{ + [cachedText release]; ++ if (visibleRuns) ++ xfree (visibleRuns); + [super dealloc]; +} -+ + +-/*****************************************************************************/ +-/* Keyboard handling. */ +-#define NS_KEYLOG 0 +/* ---- Text cache ---- */ -+ + +-- (void)keyDown: (NSEvent *)theEvent +- (void)invalidateTextCache -+{ + { +- Mouse_HLInfo *hlinfo = MOUSE_HL_INFO (emacsframe); +- int code; +- unsigned fnKeysym = 0; +- static NSMutableArray *nsEvArray; +- unsigned int flags = [theEvent modifierFlags]; + [cachedText release]; + cachedText = nil; ++ if (visibleRuns) ++ { ++ xfree (visibleRuns); ++ visibleRuns = NULL; ++ } ++ visibleRunCount = 0; +} -+ + +- NSTRACE ("[EmacsView keyDown:]"); +- (void)ensureTextCache +{ + struct window *w = self.emacsWindow; + if (!w || !WINDOW_LEAF_P (w)) + return; -+ + +- /* Rhapsody and macOS give up and down events for the arrow keys. */ +- if ([theEvent type] != NSEventTypeKeyDown) + struct buffer *b = XBUFFER (w->contents); + if (!b) -+ return; -+ + return; + +- if (!emacs_event) + ptrdiff_t modiff = BUF_MODIFF (b); + ptrdiff_t pt = BUF_PT (b); + NSUInteger textLen = cachedText ? [cachedText length] : 0; + if (cachedText && cachedTextModiff == modiff + && pt >= cachedTextStart -+ && pt <= cachedTextStart + (ptrdiff_t)textLen) -+ return; -+ ++ && (textLen == 0 ++ || [self accessibilityIndexForCharpos:pt] <= textLen)) + return; + +- if (![[self window] isKeyWindow] +- && [[theEvent window] isKindOfClass: [EmacsWindow class]] +- /* We must avoid an infinite loop here. */ +- && (EmacsView *)[[theEvent window] delegate] != self) +- { +- /* XXX: There is an occasional condition in which, when Emacs display +- updates a different frame from the current one, and temporarily +- selects it, then processes some interrupt-driven input +- (dispnew.c:3878), OS will send the event to the correct NSWindow, but +- for some reason that window has its first responder set to the NSView +- most recently updated (I guess), which is not the correct one. */ +- [(EmacsView *)[[theEvent window] delegate] keyDown: theEvent]; +- return; +- } + ptrdiff_t start; -+ NSString *text = ns_ax_buffer_text (w, &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 ++{ ++ 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 charpos falls in an invisible gap before the next run, ++ map it to the start of the next visible run. */ ++ if (charpos < r->charpos) ++ return r->ax_start; + } +- +- if (!processingCompose) ++ /* Past end — return total length. */ ++ if (visibleRunCount > 0) + { +- /* FIXME: What should happen for key sequences with more than +- one character? */ +- code = ([[theEvent charactersIgnoringModifiers] length] == 0) ? ++ ns_ax_visible_run *last = &visibleRuns[visibleRunCount - 1]; ++ return last->ax_start + (NSUInteger) last->length; ++ } ++ return 0; ++} ++ ++/* Convert accessibility string index to buffer charpos. */ ++- (ptrdiff_t)charposForAccessibilityIndex:(NSUInteger)ax_idx ++{ ++ 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 + (NSUInteger) r->length) ++ return r->charpos + (ptrdiff_t) (ax_idx - r->ax_start); ++ } ++ /* Past end — return last charpos. */ ++ if (visibleRunCount > 0) ++ { ++ ns_ax_visible_run *last = &visibleRuns[visibleRunCount - 1]; ++ return last->charpos + last->length; ++ } ++ return cachedTextStart; +} + +/* ---- NSAccessibility protocol ---- */ @@ -469,13 +790,13 @@ index 932d209..95a360d 100644 + + [self ensureTextCache]; + ptrdiff_t pt = BUF_PT (b); -+ NSUInteger point_idx = (NSUInteger) (pt - cachedTextStart); ++ NSUInteger point_idx = [self accessibilityIndexForCharpos:pt]; + + if (NILP (BVAR (b, mark_active))) + return NSMakeRange (point_idx, 0); + + ptrdiff_t mark_pos = marker_position (BVAR (b, mark)); -+ NSUInteger mark_idx = (NSUInteger) (mark_pos - cachedTextStart); ++ NSUInteger mark_idx = [self accessibilityIndexForCharpos:mark_pos]; + NSUInteger start_idx = MIN (point_idx, mark_idx); + NSUInteger end_idx = MAX (point_idx, mark_idx); + return NSMakeRange (start_idx, end_idx - start_idx); @@ -493,8 +814,8 @@ index 932d209..95a360d 100644 + + [self ensureTextCache]; + -+ /* Convert accessibility index to buffer charpos. */ -+ ptrdiff_t charpos = cachedTextStart + (ptrdiff_t) range.location; ++ /* Convert accessibility index to buffer charpos via mapping. */ ++ ptrdiff_t charpos = [self charposForAccessibilityIndex:range.location]; + + /* Clamp to buffer bounds. */ + if (charpos < BUF_BEGV (b)) @@ -515,8 +836,8 @@ index 932d209..95a360d 100644 + /* If range has nonzero length, activate the mark. */ + if (range.length > 0) + { -+ ptrdiff_t mark_charpos = cachedTextStart -+ + (ptrdiff_t) (range.location + range.length); ++ ptrdiff_t mark_charpos = [self charposForAccessibilityIndex: ++ range.location + range.length]; + if (mark_charpos > BUF_ZV (b)) + mark_charpos = BUF_ZV (b); + Fset_marker (BVAR (b, mark), make_fixnum (mark_charpos), @@ -577,7 +898,7 @@ index 932d209..95a360d 100644 + return 0; + + ptrdiff_t pt = BUF_PT (b); -+ NSUInteger point_idx = (NSUInteger) (pt - cachedTextStart); ++ NSUInteger point_idx = [self accessibilityIndexForCharpos:pt]; + if (point_idx > [cachedText length]) + point_idx = [cachedText length]; + @@ -682,7 +1003,13 @@ index 932d209..95a360d 100644 + EmacsView *view = self.emacsView; + if (!w || !view) + return NSZeroRect; -+ return ns_ax_frame_for_range (w, view, cachedTextStart, range); ++ /* Convert ax-index range to charpos range for glyph lookup. */ ++ [self ensureTextCache]; ++ ptrdiff_t cp_start = [self charposForAccessibilityIndex:range.location]; ++ ptrdiff_t cp_end = [self charposForAccessibilityIndex: ++ 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 @@ -745,9 +1072,9 @@ index 932d209..95a360d 100644 + glyph_x += glyph->pixel_width; + } + -+ /* Convert buffer charpos to accessibility index. */ ++ /* Convert buffer charpos to accessibility index via mapping. */ + [self ensureTextCache]; -+ NSUInteger ax_idx = (NSUInteger) (best_charpos - cachedTextStart); ++ NSUInteger ax_idx = [self accessibilityIndexForCharpos:best_charpos]; + if (cachedText && ax_idx > [cachedText length]) + ax_idx = [cachedText length]; + return NSMakeRange (ax_idx, 1); @@ -818,7 +1145,7 @@ index 932d209..95a360d 100644 + [self ensureTextCache]; + if (cachedText) + { -+ NSUInteger idx = (NSUInteger) (point - 1 - cachedTextStart); ++ NSUInteger idx = [self accessibilityIndexForCharpos:point - 1]; + if (idx < [cachedText length]) + changedChar = [cachedText substringWithRange: + NSMakeRange (idx, 1)]; @@ -881,10 +1208,10 @@ index 932d209..95a360d 100644 + { + /* Check for line crossing by looking for newlines + between old and new position. */ -+ NSUInteger lo = (NSUInteger) -+ (MIN (oldPoint, point) - cachedTextStart); -+ NSUInteger hi = (NSUInteger) -+ (MAX (oldPoint, point) - cachedTextStart); ++ NSUInteger lo = [self accessibilityIndexForCharpos: ++ MIN (oldPoint, point)]; ++ NSUInteger hi = [self accessibilityIndexForCharpos: ++ MAX (oldPoint, point)]; + NSUInteger tlen = [cachedText length]; + if (lo < tlen && hi <= tlen) + { @@ -912,37 +1239,88 @@ index 932d209..95a360d 100644 + When point changes in a non-focused buffer (e.g. *Completions* + while the minibuffer has keyboard focus), VoiceOver won't read + the change because it's tracking the focused element. Post an -+ announcement so the user hears the selected completion. */ ++ announcement so the user hears the selected completion. ++ ++ If there is a `completions-highlight` overlay at point (Emacs ++ highlights the selected completion candidate), read its full ++ text instead of just the current line. */ + if (![self isAccessibilityFocused] && cachedText) + { -+ /* Extract the current line at point for the announcement. */ -+ NSUInteger point_idx = (NSUInteger) (point - cachedTextStart); -+ if (point_idx <= [cachedText length]) ++ NSString *announceText = nil; ++ ++ /* Check for completions-highlight overlay at point. */ ++ { ++ struct buffer *oldb2 = current_buffer; ++ if (b != current_buffer) ++ set_buffer_internal_1 (b); ++ ++ Lisp_Object overlays = Foverlays_at (make_fixnum (point), Qnil); ++ Lisp_Object tail; ++ for (tail = overlays; CONSP (tail); tail = XCDR (tail)) ++ { ++ Lisp_Object ov = XCAR (tail); ++ Lisp_Object face = Foverlay_get (ov, Qface); ++ /* The completions-highlight face is applied as the ++ symbol `completions-highlight`. */ ++ if (EQ (face, intern ("completions-highlight")) ++ || (CONSP (face) ++ && !NILP (Fmemq (intern ("completions-highlight"), ++ face)))) ++ { ++ ptrdiff_t ov_start = OVERLAY_START (ov); ++ ptrdiff_t ov_end = OVERLAY_END (ov); ++ if (ov_end > ov_start) ++ { ++ /* Extract overlay text from visible cached text. */ ++ 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)]; ++ } ++ break; ++ } ++ } ++ ++ if (b != oldb2) ++ set_buffer_internal_1 (oldb2); ++ } ++ ++ /* Fallback: read the current line at point. */ ++ if (!announceText) + { -+ NSInteger lineNum = [self accessibilityLineForIndex:point_idx]; -+ NSRange lineRange = [self accessibilityRangeForLine:lineNum]; -+ if (lineRange.location != NSNotFound -+ && lineRange.length > 0 -+ && lineRange.location + lineRange.length -+ <= [cachedText length]) ++ NSUInteger point_idx = [self accessibilityIndexForCharpos:point]; ++ if (point_idx <= [cachedText length]) + { -+ NSString *lineText = -+ [cachedText substringWithRange:lineRange]; -+ /* Trim trailing newline for cleaner speech. */ -+ lineText = [lineText stringByTrimmingCharactersInSet: -+ [NSCharacterSet newlineCharacterSet]]; -+ if ([lineText length] > 0) -+ { -+ NSDictionary *annInfo = @{ -+ NSAccessibilityAnnouncementKey: lineText, -+ NSAccessibilityPriorityKey: -+ @(NSAccessibilityPriorityHigh) -+ }; -+ NSAccessibilityPostNotificationWithUserInfo ( -+ NSApp, -+ NSAccessibilityAnnouncementRequestedNotification, -+ annInfo); -+ } ++ NSInteger lineNum = [self accessibilityLineForIndex: ++ point_idx]; ++ NSRange lineRange = [self accessibilityRangeForLine:lineNum]; ++ if (lineRange.location != NSNotFound ++ && lineRange.length > 0 ++ && lineRange.location + lineRange.length ++ <= [cachedText length]) ++ announceText = [cachedText substringWithRange:lineRange]; ++ } ++ } ++ ++ if (announceText) ++ { ++ /* Trim trailing newline for cleaner speech. */ ++ announceText = [announceText stringByTrimmingCharactersInSet: ++ [NSCharacterSet newlineCharacterSet]]; ++ if ([announceText length] > 0) ++ { ++ NSDictionary *annInfo = @{ ++ NSAccessibilityAnnouncementKey: announceText, ++ NSAccessibilityPriorityKey: ++ @(NSAccessibilityPriorityHigh) ++ }; ++ NSAccessibilityPostNotificationWithUserInfo ( ++ NSApp, ++ NSAccessibilityAnnouncementRequestedNotification, ++ annInfo); + } + } + } @@ -1014,18 +1392,272 @@ index 932d209..95a360d 100644 +#endif /* NS_IMPL_COCOA */ + + - /* ========================================================================== - - EmacsView implementation -@@ -6889,6 +7809,7 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action) - [layer release]; - #endif - ++/* ========================================================================== ++ ++ 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]; - } -@@ -8237,6 +9158,27 @@ ns_in_echo_area (void) ++ [[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]; ++ ++ NSTRACE ("[EmacsView keyDown:]"); ++ ++ /* Rhapsody and macOS give up and down events for the arrow keys. */ ++ if ([theEvent type] != NSEventTypeKeyDown) ++ return; ++ ++ if (!emacs_event) ++ return; ++ ++ if (![[self window] isKeyWindow] ++ && [[theEvent window] isKindOfClass: [EmacsWindow class]] ++ /* We must avoid an infinite loop here. */ ++ && (EmacsView *)[[theEvent window] delegate] != self) ++ { ++ /* XXX: There is an occasional condition in which, when Emacs display ++ updates a different frame from the current one, and temporarily ++ selects it, then processes some interrupt-driven input ++ (dispnew.c:3878), OS will send the event to the correct NSWindow, but ++ for some reason that window has its first responder set to the NSView ++ 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? */ ++ code = ([[theEvent charactersIgnoringModifiers] length] == 0) ? + 0 : [[theEvent charactersIgnoringModifiers] characterAtIndex: 0]; + + /* Is it a "function key"? */ +@@ -8237,6 +9337,27 @@ ns_in_echo_area (void) XSETFRAME (event.frame_or_window, emacsframe); kbd_buffer_store_event (&event); ns_send_appdefined (-1); // Kick main loop @@ -1053,7 +1685,7 @@ index 932d209..95a360d 100644 } -@@ -9474,6 +10416,297 @@ ns_in_echo_area (void) +@@ -9474,6 +10595,297 @@ ns_in_echo_area (void) return fs_state; } @@ -1351,7 +1983,7 @@ index 932d209..95a360d 100644 @end /* EmacsView */ -@@ -9941,6 +11174,14 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c) +@@ -9941,6 +11353,14 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c) return [super accessibilityAttributeValue:attribute]; }