500ms (2 Hz) was too aggressive — Zoom focus stopped updating during keyboard navigation in Vertico/Corfu lists. 50ms (20 Hz) tracks fast arrow-key navigation while still avoiding per-frame overhead. UAZoomEnabled() is already cached so the main cost is the overlay scan, which is cheap.
585 lines
18 KiB
Diff
585 lines
18 KiB
Diff
From 593b5219ee63b32125efc0e58e9c376c949b463b Mon Sep 17 00:00:00 2001
|
|
From: Martin Sukany <martin@sukany.cz>
|
|
Date: Sat, 28 Feb 2026 12:58:11 +0100
|
|
Subject: [PATCH 4/9] ns: add buffer notification dispatch and mode-line
|
|
element
|
|
|
|
Add VoiceOver notification methods and mode-line readout.
|
|
|
|
* src/nsterm.m (EmacsAccessibilityBuffer(Notifications)): New
|
|
category.
|
|
(postTextChangedNotification:): ValueChanged with edit details.
|
|
(postFocusedCursorNotification:direction:granularity:markActive:
|
|
oldMarkActive:): Hybrid SelectedTextChanged / AnnouncementRequested
|
|
per WebKit pattern.
|
|
(postCompletionAnnouncementForBuffer:point:): Announce completion
|
|
candidates in non-focused buffers.
|
|
(postAccessibilityNotificationsForFrame:): Main dispatch entry point.
|
|
(EmacsAccessibilityModeLine): Implement AXStaticText element.
|
|
|
|
Tested on macOS 14. Verified: cursor movement announcements,
|
|
region selection feedback, completion popups, mode-line reading.
|
|
---
|
|
src/nsterm.m | 545 +++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
1 file changed, 545 insertions(+)
|
|
|
|
diff --git a/src/nsterm.m b/src/nsterm.m
|
|
index 7beedc4..ecc8c54 100644
|
|
--- a/src/nsterm.m
|
|
+++ b/src/nsterm.m
|
|
@@ -8721,6 +8721,551 @@ - (NSRect)accessibilityFrame
|
|
|
|
@end
|
|
|
|
+
|
|
+
|
|
+/* ===================================================================
|
|
+ EmacsAccessibilityBuffer (Notifications) — AX event dispatch
|
|
+
|
|
+ These methods notify VoiceOver of text and selection changes.
|
|
+ Called from the redisplay cycle (postAccessibilityUpdates).
|
|
+ =================================================================== */
|
|
+
|
|
+@implementation EmacsAccessibilityBuffer (Notifications)
|
|
+
|
|
+- (void)postTextChangedNotification:(ptrdiff_t)point
|
|
+{
|
|
+ /* Capture changed char before invalidating cache. */
|
|
+ NSString *changedChar = @"";
|
|
+ if (point > self.cachedPoint
|
|
+ && point - self.cachedPoint == 1)
|
|
+ {
|
|
+ /* Single char inserted — refresh cache and grab it. */
|
|
+ [self invalidateTextCache];
|
|
+ [self ensureTextCache];
|
|
+ if (cachedText)
|
|
+ {
|
|
+ NSUInteger idx = [self accessibilityIndexForCharpos:point - 1];
|
|
+ if (idx < [cachedText length])
|
|
+ changedChar = [cachedText substringWithRange:
|
|
+ NSMakeRange (idx, 1)];
|
|
+ }
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ [self invalidateTextCache];
|
|
+ }
|
|
+
|
|
+ /* Update cachedPoint here so the selection-move branch 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;
|
|
+
|
|
+ NSDictionary *change = @{
|
|
+ @"AXTextEditType": @(ns_ax_text_edit_type_typing),
|
|
+ @"AXTextChangeValue": changedChar,
|
|
+ @"AXTextChangeValueLength": @([changedChar length])
|
|
+ };
|
|
+ NSDictionary *userInfo = @{
|
|
+ @"AXTextStateChangeType": @(ns_ax_text_state_change_edit),
|
|
+ @"AXTextChangeValues": @[change],
|
|
+ @"AXTextChangeElement": self
|
|
+ };
|
|
+ ns_ax_post_notification_with_info (
|
|
+ self, NSAccessibilityValueChangedNotification, userInfo);
|
|
+}
|
|
+
|
|
+/* Post SelectedTextChanged and AnnouncementRequested for the
|
|
+ focused buffer element when point or mark changes. */
|
|
+- (void)postFocusedCursorNotification:(ptrdiff_t)point
|
|
+ direction:(NSInteger)direction
|
|
+ granularity:(NSInteger)granularity
|
|
+ markActive:(BOOL)markActive
|
|
+ oldMarkActive:(BOOL)oldMarkActive
|
|
+{
|
|
+ BOOL isCharMove
|
|
+ = (!markActive && !oldMarkActive
|
|
+ && granularity
|
|
+ == ns_ax_text_selection_granularity_character);
|
|
+
|
|
+ /* Always post SelectedTextChanged to interrupt VoiceOver reading
|
|
+ and update cursor tracking / braille displays. */
|
|
+ NSMutableDictionary *moveInfo = [NSMutableDictionary dictionary];
|
|
+ moveInfo[@"AXTextStateChangeType"]
|
|
+ = @(ns_ax_text_state_change_selection_move);
|
|
+ moveInfo[@"AXTextSelectionDirection"] = @(direction);
|
|
+ moveInfo[@"AXTextChangeElement"] = self;
|
|
+ /* Omit granularity for character moves so VoiceOver does not
|
|
+ derive its own speech (it would read the wrong character
|
|
+ for evil block-cursor mode). Include it for word/line/
|
|
+ selection so VoiceOver reads the appropriate text. */
|
|
+ if (!isCharMove)
|
|
+ moveInfo[@"AXTextSelectionGranularity"] = @(granularity);
|
|
+
|
|
+ ns_ax_post_notification_with_info (
|
|
+ self,
|
|
+ NSAccessibilitySelectedTextChangedNotification,
|
|
+ moveInfo);
|
|
+
|
|
+ /* For character moves: explicit announcement of char AT point.
|
|
+ This is the ONLY speech source for character navigation.
|
|
+ Correct for evil block-cursor (cursor ON the character)
|
|
+ and harmless for insert-mode. */
|
|
+ if (isCharMove && cachedText)
|
|
+ {
|
|
+ NSUInteger point_idx
|
|
+ = [self accessibilityIndexForCharpos:point];
|
|
+ NSUInteger tlen = [cachedText length];
|
|
+ if (point_idx < tlen)
|
|
+ {
|
|
+ NSRange charRange = [cachedText
|
|
+ rangeOfComposedCharacterSequenceAtIndex: point_idx];
|
|
+ if (charRange.location != NSNotFound
|
|
+ && charRange.length > 0
|
|
+ && NSMaxRange (charRange) <= tlen)
|
|
+ {
|
|
+ NSString *ch
|
|
+ = [cachedText substringWithRange: charRange];
|
|
+ if (![ch isEqualToString: @"\n"])
|
|
+ {
|
|
+ NSDictionary *annInfo = @{
|
|
+ NSAccessibilityAnnouncementKey: ch,
|
|
+ NSAccessibilityPriorityKey:
|
|
+ @(NSAccessibilityPriorityHigh)
|
|
+ };
|
|
+ ns_ax_post_notification_with_info (
|
|
+ NSApp,
|
|
+ NSAccessibilityAnnouncementRequestedNotification,
|
|
+ annInfo);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* For focused line moves: always announce line text explicitly.
|
|
+ SelectedTextChanged with granularity=line works for arrow keys,
|
|
+ but C-n/C-p need the explicit announcement (VoiceOver processes
|
|
+ these keystrokes differently from arrows).
|
|
+ In completion-list-mode, read the completion candidate instead
|
|
+ of the whole line. */
|
|
+ if (cachedText
|
|
+ && granularity == ns_ax_text_selection_granularity_line)
|
|
+ {
|
|
+ NSString *announceText = nil;
|
|
+
|
|
+ /* 1. completion--string at point. */
|
|
+ Lisp_Object cstr
|
|
+ = Fget_char_property (make_fixnum (point),
|
|
+ Qns_ax_completion__string, Qnil);
|
|
+ announceText = ns_ax_completion_string_from_prop (cstr);
|
|
+
|
|
+ /* 2. Fallback: full line text. */
|
|
+ if (!announceText)
|
|
+ {
|
|
+ NSUInteger point_idx
|
|
+ = [self accessibilityIndexForCharpos:point];
|
|
+ if (point_idx <= [cachedText length])
|
|
+ {
|
|
+ NSInteger lineNum
|
|
+ = [self accessibilityLineForIndex:point_idx];
|
|
+ NSRange lineRange
|
|
+ = [self accessibilityRangeForLine:lineNum];
|
|
+ if (lineRange.location != NSNotFound
|
|
+ && lineRange.length > 0
|
|
+ && NSMaxRange (lineRange) <= [cachedText length])
|
|
+ announceText
|
|
+ = [cachedText substringWithRange:lineRange];
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (announceText)
|
|
+ {
|
|
+ announceText = [announceText
|
|
+ stringByTrimmingCharactersInSet:
|
|
+ [NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
+ if ([announceText length] > 0)
|
|
+ {
|
|
+ NSDictionary *annInfo = @{
|
|
+ NSAccessibilityAnnouncementKey: announceText,
|
|
+ NSAccessibilityPriorityKey:
|
|
+ @(NSAccessibilityPriorityHigh)
|
|
+ };
|
|
+ ns_ax_post_notification_with_info (
|
|
+ NSApp,
|
|
+ NSAccessibilityAnnouncementRequestedNotification,
|
|
+ annInfo);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+/* Post AnnouncementRequested for non-focused buffers (typically
|
|
+ *Completions* while minibuffer has keyboard focus).
|
|
+ VoiceOver does not automatically read changes in non-focused
|
|
+ elements, so we announce the selected completion explicitly. */
|
|
+- (void)postCompletionAnnouncementForBuffer:(struct buffer *)b
|
|
+ point:(ptrdiff_t)point
|
|
+{
|
|
+ NSString *announceText = nil;
|
|
+ ptrdiff_t currentOverlayStart = 0;
|
|
+ ptrdiff_t currentOverlayEnd = 0;
|
|
+
|
|
+ specpdl_ref count2 = SPECPDL_INDEX ();
|
|
+ record_unwind_current_buffer ();
|
|
+ if (b != current_buffer)
|
|
+ set_buffer_internal_1 (b);
|
|
+
|
|
+ /* 1) Prefer explicit completion candidate property. */
|
|
+ Lisp_Object cstr = Fget_char_property (make_fixnum (point),
|
|
+ Qns_ax_completion__string,
|
|
+ Qnil);
|
|
+ announceText = ns_ax_completion_string_from_prop (cstr);
|
|
+
|
|
+ /* 2) Fallback: mouse-face span at point. */
|
|
+ if (!announceText)
|
|
+ {
|
|
+ Lisp_Object mf = Fget_char_property (make_fixnum (point),
|
|
+ Qmouse_face, Qnil);
|
|
+ if (!NILP (mf))
|
|
+ {
|
|
+ ptrdiff_t begv2 = BUF_BEGV (b);
|
|
+ ptrdiff_t zv2 = BUF_ZV (b);
|
|
+
|
|
+ Lisp_Object prev_change
|
|
+ = Fprevious_single_char_property_change (
|
|
+ make_fixnum (point + 1), Qmouse_face,
|
|
+ Qnil, make_fixnum (begv2));
|
|
+ ptrdiff_t s2
|
|
+ = FIXNUMP (prev_change) ? XFIXNUM (prev_change)
|
|
+ : begv2;
|
|
+
|
|
+ Lisp_Object next_change
|
|
+ = Fnext_single_char_property_change (
|
|
+ make_fixnum (point), Qmouse_face,
|
|
+ Qnil, make_fixnum (zv2));
|
|
+ ptrdiff_t e2
|
|
+ = FIXNUMP (next_change) ? XFIXNUM (next_change)
|
|
+ : zv2;
|
|
+
|
|
+ if (e2 > s2)
|
|
+ {
|
|
+ NSUInteger ax_s = [self accessibilityIndexForCharpos:s2];
|
|
+ NSUInteger ax_e = [self accessibilityIndexForCharpos:e2];
|
|
+ if (ax_e > ax_s && ax_e <= [cachedText length])
|
|
+ announceText = [cachedText substringWithRange:
|
|
+ NSMakeRange (ax_s, ax_e - ax_s)];
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* 3) Fallback: completions-highlight overlay at point. */
|
|
+ if (!announceText)
|
|
+ {
|
|
+ Lisp_Object faceSym = Qns_ax_completions_highlight;
|
|
+ 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);
|
|
+ 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)
|
|
+ {
|
|
+ announceText = ns_ax_completion_text_for_span (self, b,
|
|
+ ov_start,
|
|
+ ov_end,
|
|
+ cachedText);
|
|
+ currentOverlayStart = ov_start;
|
|
+ currentOverlayEnd = ov_end;
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* 4) Fallback: nearest completions-highlight overlay. */
|
|
+ if (!announceText)
|
|
+ {
|
|
+ ptrdiff_t ov_start = 0;
|
|
+ ptrdiff_t ov_end = 0;
|
|
+ if (ns_ax_find_completion_overlay_range (b, point,
|
|
+ &ov_start, &ov_end))
|
|
+ {
|
|
+ announceText = ns_ax_completion_text_for_span (self, b,
|
|
+ ov_start, ov_end,
|
|
+ cachedText);
|
|
+ currentOverlayStart = ov_start;
|
|
+ currentOverlayEnd = ov_end;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ unbind_to (count2, Qnil);
|
|
+
|
|
+ /* Final fallback: read current line at point. */
|
|
+ if (!announceText)
|
|
+ {
|
|
+ NSUInteger point_idx = [self accessibilityIndexForCharpos:point];
|
|
+ if (point_idx <= [cachedText length])
|
|
+ {
|
|
+ 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];
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Deduplicate: post only when text, overlay, or point changed. */
|
|
+ if (announceText)
|
|
+ {
|
|
+ announceText = [announceText stringByTrimmingCharactersInSet:
|
|
+ [NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
+ if ([announceText length] > 0)
|
|
+ {
|
|
+ BOOL textChanged = ![announceText isEqualToString:
|
|
+ self.cachedCompletionAnnouncement];
|
|
+ BOOL overlayChanged =
|
|
+ (currentOverlayStart != self.cachedCompletionOverlayStart
|
|
+ || currentOverlayEnd != self.cachedCompletionOverlayEnd);
|
|
+ BOOL pointChanged = (point != self.cachedCompletionPoint);
|
|
+ if (textChanged || overlayChanged || pointChanged)
|
|
+ {
|
|
+ NSDictionary *annInfo = @{
|
|
+ NSAccessibilityAnnouncementKey: announceText,
|
|
+ NSAccessibilityPriorityKey:
|
|
+ @(NSAccessibilityPriorityHigh)
|
|
+ };
|
|
+ ns_ax_post_notification_with_info (
|
|
+ NSApp,
|
|
+ NSAccessibilityAnnouncementRequestedNotification,
|
|
+ annInfo);
|
|
+ }
|
|
+ self.cachedCompletionAnnouncement = announceText;
|
|
+ self.cachedCompletionOverlayStart = currentOverlayStart;
|
|
+ self.cachedCompletionOverlayEnd = currentOverlayEnd;
|
|
+ self.cachedCompletionPoint = point;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ self.cachedCompletionAnnouncement = nil;
|
|
+ self.cachedCompletionOverlayStart = 0;
|
|
+ self.cachedCompletionOverlayEnd = 0;
|
|
+ self.cachedCompletionPoint = 0;
|
|
+ }
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ self.cachedCompletionAnnouncement = nil;
|
|
+ self.cachedCompletionOverlayStart = 0;
|
|
+ self.cachedCompletionOverlayEnd = 0;
|
|
+ self.cachedCompletionPoint = 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+/* ---- Notification dispatch (main entry point) ---- */
|
|
+
|
|
+/* Dispatch accessibility notifications after a redisplay cycle.
|
|
+ Detects three mutually exclusive events: text edit, cursor/mark
|
|
+ change, or no change. Delegates to helper methods above. */
|
|
+- (void)postAccessibilityNotificationsForFrame:(struct frame *)f
|
|
+{
|
|
+ NSTRACE ("[EmacsView postAccessibilityNotificationsForFrame:]");
|
|
+ struct window *w = [self validWindow];
|
|
+ if (!w || !WINDOW_LEAF_P (w))
|
|
+ return;
|
|
+
|
|
+ struct buffer *b = XBUFFER (w->contents);
|
|
+ if (!b)
|
|
+ return;
|
|
+
|
|
+ ptrdiff_t modiff = BUF_MODIFF (b);
|
|
+ ptrdiff_t point = BUF_PT (b);
|
|
+ BOOL markActive = !NILP (BVAR (b, mark_active));
|
|
+
|
|
+ /* --- Text changed (edit) --- */
|
|
+ if (modiff != self.cachedModiff)
|
|
+ {
|
|
+ self.cachedModiff = modiff;
|
|
+ [self postTextChangedNotification:point];
|
|
+ }
|
|
+
|
|
+ /* --- Cursor moved or selection changed ---
|
|
+ Use 'else if' — edits and selection moves are mutually exclusive
|
|
+ per the WebKit/Chromium pattern. */
|
|
+ else if (point != self.cachedPoint || markActive != self.cachedMarkActive)
|
|
+ {
|
|
+ ptrdiff_t oldPoint = self.cachedPoint;
|
|
+ BOOL oldMarkActive = self.cachedMarkActive;
|
|
+ self.cachedPoint = point;
|
|
+ self.cachedMarkActive = markActive;
|
|
+
|
|
+ /* Compute direction. */
|
|
+ NSInteger direction = ns_ax_text_selection_direction_discontiguous;
|
|
+ if (point > oldPoint)
|
|
+ direction = ns_ax_text_selection_direction_next;
|
|
+ else if (point < oldPoint)
|
|
+ direction = ns_ax_text_selection_direction_previous;
|
|
+
|
|
+ int ctrlNP = 0;
|
|
+ bool isCtrlNP = ns_ax_event_is_line_nav_key (&ctrlNP);
|
|
+
|
|
+ /* --- Granularity detection --- */
|
|
+ NSInteger granularity = ns_ax_text_selection_granularity_unknown;
|
|
+ [self ensureTextCache];
|
|
+ if (cachedText && oldPoint > 0)
|
|
+ {
|
|
+ NSUInteger tlen = [cachedText length];
|
|
+ NSUInteger oldIdx = [self accessibilityIndexForCharpos:oldPoint];
|
|
+ NSUInteger newIdx = [self accessibilityIndexForCharpos:point];
|
|
+ if (oldIdx > tlen) oldIdx = tlen;
|
|
+ if (newIdx > tlen) newIdx = tlen;
|
|
+
|
|
+ NSRange oldLine = [cachedText lineRangeForRange:
|
|
+ NSMakeRange (oldIdx, 0)];
|
|
+ NSRange newLine = [cachedText lineRangeForRange:
|
|
+ NSMakeRange (newIdx, 0)];
|
|
+ if (oldLine.location != newLine.location)
|
|
+ granularity = ns_ax_text_selection_granularity_line;
|
|
+ else
|
|
+ {
|
|
+ NSUInteger dist = (newIdx > oldIdx
|
|
+ ? newIdx - oldIdx
|
|
+ : oldIdx - newIdx);
|
|
+ if (dist > 1)
|
|
+ granularity = ns_ax_text_selection_granularity_word;
|
|
+ else if (dist == 1)
|
|
+ granularity = ns_ax_text_selection_granularity_character;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Force line semantics for explicit C-n/C-p / Tab / backtab. */
|
|
+ if (isCtrlNP)
|
|
+ {
|
|
+ direction = (ctrlNP > 0
|
|
+ ? ns_ax_text_selection_direction_next
|
|
+ : ns_ax_text_selection_direction_previous);
|
|
+ granularity = ns_ax_text_selection_granularity_line;
|
|
+ }
|
|
+
|
|
+ /* Post notifications for focused and non-focused elements. */
|
|
+ if ([self isAccessibilityFocused])
|
|
+ [self postFocusedCursorNotification:point
|
|
+ direction:direction
|
|
+ granularity:granularity
|
|
+ markActive:markActive
|
|
+ oldMarkActive:oldMarkActive];
|
|
+
|
|
+ if (![self isAccessibilityFocused] && cachedText)
|
|
+ [self postCompletionAnnouncementForBuffer:b point:point];
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ /* Nothing changed. Reset completion cache for focused buffer
|
|
+ to avoid stale announcements. */
|
|
+ if ([self isAccessibilityFocused])
|
|
+ {
|
|
+ self.cachedCompletionAnnouncement = nil;
|
|
+ self.cachedCompletionOverlayStart = 0;
|
|
+ self.cachedCompletionOverlayEnd = 0;
|
|
+ self.cachedCompletionPoint = 0;
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+@end
|
|
+
|
|
+
|
|
+@implementation EmacsAccessibilityModeLine
|
|
+
|
|
+- (NSAccessibilityRole)accessibilityRole
|
|
+{
|
|
+ return NSAccessibilityStaticTextRole;
|
|
+}
|
|
+
|
|
+- (NSString *)accessibilityLabel
|
|
+{
|
|
+ if (![NSThread isMainThread])
|
|
+ {
|
|
+ __block NSString *result;
|
|
+ dispatch_sync (dispatch_get_main_queue (), ^{
|
|
+ result = [self accessibilityLabel];
|
|
+ });
|
|
+ return result;
|
|
+ }
|
|
+ struct window *w = [self validWindow];
|
|
+ if (w && WINDOW_LEAF_P (w))
|
|
+ {
|
|
+ struct buffer *b = XBUFFER (w->contents);
|
|
+ if (b)
|
|
+ {
|
|
+ Lisp_Object name = BVAR (b, name);
|
|
+ if (STRINGP (name))
|
|
+ {
|
|
+ NSString *bufName = [NSString stringWithLispString:name];
|
|
+ return [NSString stringWithFormat:@"Mode Line - %@", bufName];
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ return @"Mode Line";
|
|
+}
|
|
+
|
|
+- (id)accessibilityValue
|
|
+{
|
|
+ if (![NSThread isMainThread])
|
|
+ {
|
|
+ __block id result;
|
|
+ dispatch_sync (dispatch_get_main_queue (), ^{
|
|
+ result = [self accessibilityValue];
|
|
+ });
|
|
+ return result;
|
|
+ }
|
|
+ struct window *w = [self validWindow];
|
|
+ if (!w)
|
|
+ return @"";
|
|
+ return ns_ax_mode_line_text (w);
|
|
+}
|
|
+
|
|
+- (NSRect)accessibilityFrame
|
|
+{
|
|
+ if (![NSThread isMainThread])
|
|
+ {
|
|
+ __block NSRect result;
|
|
+ dispatch_sync (dispatch_get_main_queue (), ^{
|
|
+ result = [self accessibilityFrame];
|
|
+ });
|
|
+ return result;
|
|
+ }
|
|
+ struct window *w = [self validWindow];
|
|
+ if (!w || !w->current_matrix)
|
|
+ return NSZeroRect;
|
|
+
|
|
+ /* Find the mode line row and return its screen rect. */
|
|
+ struct glyph_matrix *matrix = w->current_matrix;
|
|
+ for (int i = 0; i < matrix->nrows; i++)
|
|
+ {
|
|
+ struct glyph_row *row = matrix->rows + i;
|
|
+ if (row->enabled_p && row->mode_line_p)
|
|
+ {
|
|
+ return [self screenRectFromEmacsX:w->pixel_left
|
|
+ y:WINDOW_TO_FRAME_PIXEL_Y (w,
|
|
+ MAX (0, row->y))
|
|
+ width:w->pixel_width
|
|
+ height:row->visible_height];
|
|
+ }
|
|
+ }
|
|
+ return NSZeroRect;
|
|
+}
|
|
+
|
|
+@end
|
|
+
|
|
#endif /* NS_IMPL_COCOA */
|
|
|
|
|
|
--
|
|
2.43.0
|
|
|