v15.7: fix buffer gap corruption in visible text extraction

Root cause: ns_ax_buffer_text used BUF_BYTE_ADDRESS + raw pointer
read which crosses the buffer gap when visible runs span it. The gap
follows point, so completion cycling and dired navigation reliably
trigger corruption — VoiceOver reads wrong text.

Fix: Replace raw pointer extraction with Fbuffer_substring_no_properties
which handles the gap internally. Add ax_length field to visible run
struct for accurate UTF-16 length tracking (fixes supplementary
Unicode character offset drift).

Secondary: ax_offset accumulation now uses NSString length (UTF-16
units) instead of Emacs char count, preventing progressive drift in
index mapping for subsequent visible runs.
This commit is contained in:
2026-02-26 16:07:42 +01:00
parent f09f6dd0f3
commit 25e2a21245

View File

@@ -1,8 +1,8 @@
diff --git a/src/nsterm.h b/src/nsterm.h
index 7c1ee4c..855d302 100644
index 7c1ee4c..2e2c80f 100644
--- a/src/nsterm.h
+++ b/src/nsterm.h
@@ -453,6 +453,57 @@ enum ns_return_frame_mode
@@ -453,6 +453,58 @@ enum ns_return_frame_mode
@end
@@ -29,8 +29,9 @@ index 7c1ee4c..855d302 100644
+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. */
+ ptrdiff_t length; /* Number of visible Emacs characters in this run. */
+ NSUInteger ax_start; /* Starting index in the accessibility string. */
+ NSUInteger ax_length; /* Length in accessibility string (UTF-16 units). */
+} ns_ax_visible_run;
+
+/* Virtual AXTextArea element — one per visible Emacs window (buffer). */
@@ -60,7 +61,7 @@ index 7c1ee4c..855d302 100644
/* ==========================================================================
The main Emacs view
@@ -471,6 +522,14 @@ enum ns_return_frame_mode
@@ -471,6 +523,14 @@ enum ns_return_frame_mode
#ifdef NS_IMPL_COCOA
char *old_title;
BOOL maximizing_resize;
@@ -75,7 +76,7 @@ index 7c1ee4c..855d302 100644
#endif
BOOL font_panel_active;
NSFont *font_panel_result;
@@ -528,6 +587,13 @@ enum ns_return_frame_mode
@@ -528,6 +588,13 @@ enum ns_return_frame_mode
- (void)windowWillExitFullScreen;
- (void)windowDidExitFullScreen;
- (void)windowDidBecomeKey;
@@ -90,7 +91,7 @@ index 7c1ee4c..855d302 100644
diff --git a/src/nsterm.m b/src/nsterm.m
index 932d209..ca6b969 100644
index 932d209..043477b 100644
--- a/src/nsterm.m
+++ b/src/nsterm.m
@@ -1104,6 +1104,11 @@ ns_update_end (struct frame *f)
@@ -143,29 +144,23 @@ index 932d209..ca6b969 100644
ns_focus (f, NULL, 0);
NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
@@ -6849,265 +6885,1333 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action)
@@ -6847,6 +6883,1074 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action)
}
#endif
/* ==========================================================================
- 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
@@ -174,71 +169,45 @@ index 932d209..ca6b969 100644
+static NSString *
+ns_ax_buffer_text (struct window *w, ptrdiff_t *out_start,
+ ns_ax_visible_run **out_runs, NSUInteger *out_nruns)
{
- NSTRACE ("[EmacsView setWindowClosing:%d]", closing);
+{
+ *out_runs = NULL;
+ *out_nruns = 0;
- windowClosing = closing;
-}
+
+ if (!w || !WINDOW_LEAF_P (w))
+ {
+ *out_start = 0;
+ return @"";
+ }
+
+ 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);
- /* 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
+
+ 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). */
@@ -258,19 +227,12 @@ index 932d209..ca6b969 100644
+ 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)
@@ -278,22 +240,17 @@ index 932d209..ca6b969 100644
+ 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];
+
+ /* Extract this visible run's text. Use
+ Fbuffer_substring_no_properties which correctly handles the
+ buffer gap — raw BUF_BYTE_ADDRESS reads across the gap would
+ include garbage bytes when the run spans the gap position. */
+ Lisp_Object lstr = Fbuffer_substring_no_properties (
+ make_fixnum (pos), make_fixnum (run_end));
+ NSString *nsstr = [NSString stringWithLispString:lstr];
+ NSUInteger ns_len = [nsstr length];
+ [result appendString:nsstr];
+
+ /* Record this visible run in the mapping. */
+ if (nruns >= run_capacity)
+ {
@@ -304,13 +261,10 @@ index 932d209..ca6b969 100644
+ runs[nruns].charpos = pos;
+ runs[nruns].length = run_len;
+ runs[nruns].ax_start = ax_offset;
+ runs[nruns].ax_length = ns_len;
+ nruns++;
-#ifndef NS_IMPL_COCOA
- font_panel_active = NO;
- [NSApp stop: self];
-#endif
+ ax_offset += (NSUInteger) run_len;
+
+ ax_offset += ns_len;
+ pos = run_end;
+ }
+
@@ -320,36 +274,26 @@ index 932d209..ca6b969 100644
+ *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++)
@@ -361,64 +305,28 @@ index 932d209..ca6b969 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;
@@ -426,22 +334,10 @@ index 932d209..ca6b969 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;
@@ -464,16 +360,10 @@ index 932d209..ca6b969 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;
@@ -483,23 +373,15 @@ index 932d209..ca6b969 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;
@@ -509,55 +391,33 @@ index 932d209..ca6b969 100644
+ NSRect r = NSMakeRect (x, y, ew, eh);
+ NSRect winRect = [view convertRect:r toView:nil];
+ return [[view window] convertRectToScreen:winRect];
}
-- (BOOL)acceptsFirstResponder
+}
+
+- (BOOL)isAccessibilityElement
{
- NSTRACE ("[EmacsView acceptsFirstResponder]");
return YES;
}
-/* Tell NS we want to accept clicks that activate the window */
-- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
+{
+ return YES;
+}
+
+/* ---- 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;
@@ -565,7 +425,7 @@ index 932d209..ca6b969 100644
+@synthesize cachedModiff;
+@synthesize cachedPoint;
+@synthesize cachedMarkActive;
+
+- (void)dealloc
+{
+ [cachedText release];
@@ -573,20 +433,11 @@ index 932d209..ca6b969 100644
+ 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)
@@ -596,21 +447,17 @@ index 932d209..ca6b969 100644
+ }
+ 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;
- if (!emacs_event)
+ return;
+
+ ptrdiff_t modiff = BUF_MODIFF (b);
+ ptrdiff_t pt = BUF_PT (b);
+ NSUInteger textLen = cachedText ? [cachedText length] : 0;
@@ -618,53 +465,31 @@ index 932d209..ca6b969 100644
+ && pt >= cachedTextStart
+ && (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;
- }
+ 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
+{
+ 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);
@@ -672,17 +497,12 @@ index 932d209..ca6b969 100644
+ 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 last->ax_start + last->ax_length;
+ }
+ return 0;
+}
@@ -694,7 +514,7 @@ index 932d209..ca6b969 100644
+ {
+ ns_ax_visible_run *r = &visibleRuns[i];
+ if (ax_idx >= r->ax_start
+ && ax_idx < r->ax_start + (NSUInteger) r->length)
+ && ax_idx < r->ax_start + r->ax_length)
+ return r->charpos + (ptrdiff_t) (ax_idx - r->ax_start);
+ }
+ /* Past end — return last charpos. */
@@ -1396,272 +1216,18 @@ index 932d209..ca6b969 100644
+#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];
+}
+
+
+/* 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 +9341,27 @@ ns_in_echo_area (void)
EmacsView implementation
@@ -6889,6 +7993,7 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action)
[layer release];
#endif
+ [accessibilityElements release];
[[self menu] release];
[super dealloc];
}
@@ -8237,6 +9342,27 @@ ns_in_echo_area (void)
XSETFRAME (event.frame_or_window, emacsframe);
kbd_buffer_store_event (&event);
ns_send_appdefined (-1); // Kick main loop
@@ -1689,7 +1255,7 @@ index 932d209..ca6b969 100644
}
@@ -9474,6 +10599,297 @@ ns_in_echo_area (void)
@@ -9474,6 +10600,297 @@ ns_in_echo_area (void)
return fs_state;
}
@@ -1987,7 +1553,7 @@ index 932d209..ca6b969 100644
@end /* EmacsView */
@@ -9941,6 +11357,14 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c)
@@ -9941,6 +11358,14 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c)
return [super accessibilityAttributeValue:attribute];
}