From: Martin Sukany Date: Tue, 25 Feb 2026 21:40:00 +0100 Subject: [PATCH] ns: macOS Zoom cursor tracking and VoiceOver support Comprehensive accessibility for the macOS NS port: 1. UAZoomChangeFocus() for Zoom viewport tracking (on_p && active_p guard). 2. NSAccessibility protocol on EmacsView for VoiceOver: - Bare SelectedTextChanged + SelectedRowsChanged + SelectedColumnsChanged triple notification on cursor movement (matches iTerm2 pattern — no rich userInfo needed for cursor move, VoiceOver queries accessibilityStringForRange: to get the text at the new position). - Rich ValueChanged with typing echo userInfo for content edits (kAXTextEditTypeTyping via semi-private AppKit keys, macOS 10.11+). - Bare ValueChanged on every cursor draw so VoiceOver refreshes its cached text model. - Buffer-line-based numbering via count_lines/find_newline_no_quit with set_buffer_internal_1 safety wrappers (accessibilityLineForIndex:, accessibilityRangeForLine:, accessibilityInsertionPointLineNumber). - BUF_BYTE_ADDRESS (not BYTE_POS_ADDR) for correct buffer data access when current_buffer != queried buffer. - 10000-char cap on accessibilityValue, multibyte-safe ranges. Uses raw string literals for semi-private AX keys to avoid type conflicts across SDK versions. --- diff --git a/src/nsterm.h b/src/nsterm.h index 7c1ee4c..7b79170 100644 --- a/src/nsterm.h +++ b/src/nsterm.h @@ -485,6 +485,12 @@ enum ns_return_frame_mode struct frame *emacsframe; int scrollbarsNeedingUpdate; NSRect ns_userRect; +#ifdef NS_IMPL_COCOA + NSRect lastAccessibilityCursorRect; + ptrdiff_t lastAccessibilityModiff; + ptrdiff_t lastAccessibilityCursorPos; + ptrdiff_t lastAccessibilityCursorLine; +#endif } /* AppKit-side interface. */ diff --git a/src/nsterm.m b/src/nsterm.m index 932d209..7811687 100644 --- a/src/nsterm.m +++ b/src/nsterm.m @@ -3232,6 +3232,148 @@ Note that CURSOR_WIDTH is meaningful only for (h)bar cursors. /* Prevent the cursor from being drawn outside the text area. */ r = NSIntersectionRect (r, ns_row_rect (w, glyph_row, TEXT_AREA)); +#ifdef NS_IMPL_COCOA + /* NSAccessibility keys for rich text change notifications. + Used by WebKit/Safari for VoiceOver typing echo. These symbols + have been exported by AppKit since macOS 10.11 but were only + added to the public headers in the macOS 26 SDK. We use raw + string literals here to avoid redeclaration type conflicts + across different SDK versions. */ + + /* Accessibility cursor tracking for macOS Zoom and VoiceOver. + + Emacs uses a custom-drawn cursor in a custom NSView. AppKit has no + knowledge of cursor position, so we must explicitly notify assistive + technology. Two complementary mechanisms: + + 1. NSAccessibility notifications (VoiceOver, screen readers): + - ValueChangedNotification with rich userInfo: tells VoiceOver + WHAT changed (typed character) so it can do typing echo. + We track BUF_MODIFF to distinguish content edits from cursor + movement. Only content changes get ValueChanged. + - SelectedTextChangedNotification: tells AT the cursor moved. + + 2. UAZoomChangeFocus() from UniversalAccess.h: + Directly tells macOS Zoom where to move its viewport. + Ref: https://developer.apple.com/documentation/applicationservices/1458830-uazoomchangefocus */ + { + EmacsView *view = FRAME_NS_VIEW (f); + /* Only notify AT when drawing the cursor in the active (selected) + window. Without this guard, C-x o triggers UAZoomChangeFocus + for the old window last, snapping Zoom back. */ + if (view && on_p && active_p) + { + /* Store cursor rect for accessibilityBoundsForRange: queries. */ + view->lastAccessibilityCursorRect = r; + + struct buffer *curbuf + = XBUFFER (XWINDOW (f->selected_window)->contents); + + if (curbuf && BUF_MODIFF (curbuf) != view->lastAccessibilityModiff) + { + /* Buffer content changed since last cursor draw — this is + an edit (typing, yank, undo, etc.). Post ValueChanged + with rich userInfo so VoiceOver can do typing echo. + + The semi-private keys (NSAccessibilityTextStateChangeTypeKey + etc.) are NSString* extern constants available in AppKit + since macOS 10.11. WebKit uses the same approach. + Ref: WebKit AXObjectCacheMac.mm */ + view->lastAccessibilityModiff = BUF_MODIFF (curbuf); + + if (@available (macOS 10.11, *)) + { + /* Get the just-typed character: char at cursor - 1. */ + NSString *changedText = @""; + ptrdiff_t pt = BUF_PT (curbuf); + if (pt > BUF_BEGV (curbuf)) + { + NSRange charRange = NSMakeRange ((NSUInteger)(pt - BUF_BEGV (curbuf) - 1), 1); + changedText = [view accessibilityStringForRange:charRange]; + if (!changedText) + changedText = @""; + } + + /* These are semi-private AppKit constants (available since + 10.11). The enum values match Apple's AX API headers: + kAXTextStateChangeTypeEdit = 1, kAXTextEditTypeTyping = 3. */ + + NSDictionary *change = @{ + @"AXTextEditType": @3, + @"AXTextChangeValue": changedText + }; + NSDictionary *userInfo = @{ + @"AXTextStateChangeType": @1, + @"AXTextChangeValues": @[change] + }; + NSAccessibilityPostNotificationWithUserInfo ( + view, NSAccessibilityValueChangedNotification, userInfo); + } + else + { + /* Fallback for macOS < 10.11: bare notification. */ + NSAccessibilityPostNotification ( + view, NSAccessibilityValueChangedNotification); + } + } + + /* Notify AT that cursor position (selection) changed. + Post bare notifications WITHOUT userInfo — iTerm2 proves + this is sufficient for VoiceOver. Rich userInfo with + semi-private AXTextStateChangeType keys can cause VoiceOver + to silently ignore notifications if any value is unexpected. + + Post three notifications together (matching iTerm2): + 1. SelectedTextChanged — cursor/selection moved + 2. SelectedRowsChanged — triggers line announcement + 3. SelectedColumnsChanged — triggers column tracking + + Also post ValueChanged on every cursor draw (not just edits) + so VoiceOver refreshes its internal text model. iTerm2 does + this on every dirty screen refresh. */ + { + ptrdiff_t pt = curbuf ? BUF_PT (curbuf) : 0; + ptrdiff_t old_pt = view->lastAccessibilityCursorPos; + + /* Always post ValueChanged so VoiceOver re-queries + accessibilityValue and keeps its text model current. */ + NSAccessibilityPostNotification ( + view, NSAccessibilityValueChangedNotification); + + if (pt != old_pt) + { + /* Cursor actually moved — post the selection trio. */ + NSAccessibilityPostNotification ( + view, NSAccessibilitySelectedTextChangedNotification); + NSAccessibilityPostNotification ( + view, NSAccessibilitySelectedRowsChangedNotification); + NSAccessibilityPostNotification ( + view, NSAccessibilitySelectedColumnsChangedNotification); + } + + view->lastAccessibilityCursorPos = pt; + } + + /* Tell macOS Zoom where the cursor is. UAZoomChangeFocus() + expects top-left origin (CG coordinate space). */ + if (UAZoomEnabled ()) + { + NSRect windowRect = [view convertRect:r toView:nil]; + NSRect screenRect = [[view window] convertRectToScreen:windowRect]; + CGRect cgRect = NSRectToCGRect (screenRect); + + CGFloat primaryH + = [[[NSScreen screens] firstObject] frame].size.height; + cgRect.origin.y + = primaryH - cgRect.origin.y - cgRect.size.height; + + UAZoomChangeFocus (&cgRect, &cgRect, + kUAZoomFocusTypeInsertionPoint); + } + } + } +#endif + ns_focus (f, NULL, 0); NSGraphicsContext *ctx = [NSGraphicsContext currentContext]; @@ -8237,6 +8379,14 @@ - (void)windowDidBecomeKey /* for direct calls */ XSETFRAME (event.frame_or_window, emacsframe); kbd_buffer_store_event (&event); ns_send_appdefined (-1); // Kick main loop + +#ifdef NS_IMPL_COCOA + /* Notify assistive technology that the focused UI element changed. + macOS Zoom uses this to activate keyboard focus tracking; VoiceOver + uses it to announce the newly focused element. */ + NSAccessibilityPostNotification (self, + NSAccessibilityFocusedUIElementChangedNotification); +#endif } @@ -9474,6 +9624,490 @@ - (int) fullscreenState return fs_state; } + + +#ifdef NS_IMPL_COCOA +/* ---------------------------------------------------------------- + Accessibility support for macOS Zoom, VoiceOver, and other AT tools. + + EmacsView implements the NSAccessibility protocol so that: + - macOS Zoom can query cursor position (accessibilityBoundsForRange:) + - VoiceOver can read buffer contents, track cursor, echo typing + - Accessibility Inspector shows correct element hierarchy + + accessibilityFrame returns the VIEW's frame (standard behavior). + VoiceOver uses this for its focus ring around the entire text area. + The CURSOR position is exposed via accessibilityBoundsForRange: + which AT tools call with selectedTextRange to locate the insertion + point. Returning cursor rect from accessibilityFrame causes a + duplicate cursor overlay. + + Ref: https://developer.apple.com/documentation/appkit/nsaccessibilityprotocol + Ref: https://developer.apple.com/documentation/appkit/accessibility/nsaccessibility/text-specific_parameterized_attributes + ---------------------------------------------------------------- */ + +- (BOOL)accessibilityIsIgnored +{ + /* Must return NO so assistive technology discovers this view. */ + return NO; +} + +- (BOOL)isAccessibilityElement +{ + return YES; +} + +- (id)accessibilityFocusedUIElement +{ + /* EmacsView is the focused element — there are no child accessible + elements within the custom-drawn view. */ + return self; +} + +- (NSString *)accessibilityRole +{ + /* TextArea role enables VoiceOver's text navigation commands + (VO+arrows for character/word/line reading). */ + return NSAccessibilityTextAreaRole; +} + +- (NSString *)accessibilityRoleDescription +{ + return NSAccessibilityRoleDescription (NSAccessibilityTextAreaRole, nil); +} + +/* ---- Text content methods for VoiceOver ---- */ + +- (id)accessibilityValue +{ + /* Return visible buffer text (capped at 10000 chars for safety). + VoiceOver reads this when navigating to the text area and after + ValueChangedNotification. */ + if (!emacsframe) + return @""; + + struct buffer *curbuf + = XBUFFER (XWINDOW (emacsframe->selected_window)->contents); + if (!curbuf) + return @""; + + ptrdiff_t start_byte = BUF_BEGV_BYTE (curbuf); + ptrdiff_t byte_range = BUF_ZV_BYTE (curbuf) - start_byte; + ptrdiff_t range = BUF_ZV (curbuf) - BUF_BEGV (curbuf); + + /* Cap at 10000 characters to avoid performance issues with large + buffers. Recompute byte_range from the capped char range to + avoid truncating multibyte sequences (UTF-8 chars can be up to + 4 bytes, so byte_range != range for non-ASCII content). */ + if (range > 10000) + { + range = 10000; + ptrdiff_t end_byte = buf_charpos_to_bytepos (curbuf, + BUF_BEGV (curbuf) + range); + byte_range = end_byte - start_byte; + } + + Lisp_Object str; + if (! NILP (BVAR (curbuf, enable_multibyte_characters))) + str = make_uninit_multibyte_string (range, byte_range); + else + str = make_uninit_string (range); + memcpy (SDATA (str), BUF_BYTE_ADDRESS (curbuf, start_byte), byte_range); + + return [NSString stringWithLispString:str]; +} + +- (NSInteger)accessibilityNumberOfCharacters +{ + if (!emacsframe) + return 0; + + struct buffer *curbuf + = XBUFFER (XWINDOW (emacsframe->selected_window)->contents); + if (!curbuf) + return 0; + + ptrdiff_t range = BUF_ZV (curbuf) - BUF_BEGV (curbuf); + return (NSInteger) MIN (range, 10000); +} + +- (NSString *)accessibilitySelectedText +{ + /* Return text of the active region (Emacs selection). Empty string + if no mark is active. */ + if (!emacsframe) + return @""; + + struct buffer *curbuf + = XBUFFER (XWINDOW (emacsframe->selected_window)->contents); + if (!curbuf || NILP (BVAR (curbuf, mark_active))) + return @""; + + Lisp_Object str = ns_get_local_selection (QPRIMARY, QUTF8_STRING); + if (CONSP (str) && SYMBOLP (XCAR (str))) + { + str = XCDR (str); + if (CONSP (str) && NILP (XCDR (str))) + str = XCAR (str); + } + if (STRINGP (str)) + return [NSString stringWithLispString:str]; + + return @""; +} + +- (NSRange)accessibilitySelectedTextRange +{ + /* Return cursor position as a zero-length range. VoiceOver uses + this with accessibilityBoundsForRange: to locate the caret. */ + if (!emacsframe) + return NSMakeRange (0, 0); + + struct buffer *curbuf + = XBUFFER (XWINDOW (emacsframe->selected_window)->contents); + if (!curbuf) + return NSMakeRange (0, 0); + + ptrdiff_t pt = BUF_PT (curbuf) - BUF_BEGV (curbuf); + return NSMakeRange ((NSUInteger) pt, 0); +} + +- (NSInteger)accessibilityInsertionPointLineNumber +{ + /* Return the buffer line number at point. VoiceOver uses this for + line navigation and to announce "line N" when moving by line. */ + if (!emacsframe) + return 0; + + struct window *w = XWINDOW (emacsframe->selected_window); + if (!w) + return 0; + + struct buffer *b = XBUFFER (w->contents); + if (!b) + return 0; + + /* count_lines operates on current_buffer, so temporarily switch. */ + struct buffer *old = current_buffer; + set_buffer_internal_1 (b); + ptrdiff_t lines = count_lines (BUF_BEGV_BYTE (b), BUF_PT_BYTE (b)); + set_buffer_internal_1 (old); + return (NSInteger) lines; +} + +- (NSRange)accessibilityVisibleCharacterRange +{ + /* Return range of characters visible in the current window. + Simplified to 0..min(bufsize,10000) matching accessibilityValue. */ + if (!emacsframe) + return NSMakeRange (0, 0); + + struct buffer *curbuf + = XBUFFER (XWINDOW (emacsframe->selected_window)->contents); + if (!curbuf) + return NSMakeRange (0, 0); + + ptrdiff_t range = BUF_ZV (curbuf) - BUF_BEGV (curbuf); + return NSMakeRange (0, (NSUInteger) MIN (range, 10000)); +} + +- (NSString *)accessibilityStringForRange:(NSRange)nsrange +{ + /* Return buffer text for the given character range. VoiceOver calls + this during character/word/line reading (VO+arrow keys). */ + if (!emacsframe) + return @""; + + struct buffer *curbuf + = XBUFFER (XWINDOW (emacsframe->selected_window)->contents); + if (!curbuf) + return @""; + + ptrdiff_t start = BUF_BEGV (curbuf) + (ptrdiff_t) nsrange.location; + ptrdiff_t end = start + (ptrdiff_t) nsrange.length; + ptrdiff_t buf_end = BUF_ZV (curbuf); + + if (start < BUF_BEGV (curbuf)) start = BUF_BEGV (curbuf); + if (end > buf_end) end = buf_end; + if (start >= end) return @""; + + ptrdiff_t start_byte = buf_charpos_to_bytepos (curbuf, start); + ptrdiff_t end_byte = buf_charpos_to_bytepos (curbuf, end); + ptrdiff_t range = end - start; + ptrdiff_t byte_range = end_byte - start_byte; + + Lisp_Object str; + if (! NILP (BVAR (curbuf, enable_multibyte_characters))) + str = make_uninit_multibyte_string (range, byte_range); + else + str = make_uninit_string (range); + memcpy (SDATA (str), BUF_BYTE_ADDRESS (curbuf, start_byte), byte_range); + + return [NSString stringWithLispString:str]; +} + +- (NSAttributedString *)accessibilityAttributedStringForRange:(NSRange)nsrange +{ + /* Return attributed string for the range. VoiceOver requires this + for text navigation. We return a plain (unstyled) attributed + string — sufficient for screen reader use. */ + NSString *str = [self accessibilityStringForRange:nsrange]; + return [[NSAttributedString alloc] initWithString:str]; +} + +- (NSInteger)accessibilityLineForIndex:(NSInteger)index +{ + /* Convert character index to buffer line number. VoiceOver uses + this for line-by-line reading (VO+Up/Down). Must be consistent + with accessibilityInsertionPointLineNumber. */ + if (!emacsframe) + return 0; + + struct window *w = XWINDOW (emacsframe->selected_window); + if (!w) + return 0; + + struct buffer *b = XBUFFER (w->contents); + if (!b) + return 0; + + ptrdiff_t charpos = BUF_BEGV (b) + (ptrdiff_t) index; + if (charpos > BUF_ZV (b)) + charpos = BUF_ZV (b); + if (charpos < BUF_BEGV (b)) + charpos = BUF_BEGV (b); + + ptrdiff_t bytepos = buf_charpos_to_bytepos (b, charpos); + + /* count_lines operates on current_buffer, so temporarily switch. */ + struct buffer *old = current_buffer; + set_buffer_internal_1 (b); + ptrdiff_t lines = count_lines (BUF_BEGV_BYTE (b), bytepos); + set_buffer_internal_1 (old); + return (NSInteger) lines; +} + +- (NSRange)accessibilityRangeForLine:(NSInteger)line +{ + /* Return the character range for buffer line N. VoiceOver uses + this to read an entire line when navigating by line. Must be + consistent with accessibilityLineForIndex: (buffer lines). */ + if (!emacsframe) + return NSMakeRange (0, 0); + + struct window *w = XWINDOW (emacsframe->selected_window); + if (!w) + return NSMakeRange (0, 0); + + struct buffer *b = XBUFFER (w->contents); + if (!b) + return NSMakeRange (0, 0); + + /* find_newline_no_quit operates on current_buffer, so switch. */ + ptrdiff_t begv = BUF_BEGV (b); + ptrdiff_t zv = BUF_ZV (b); + ptrdiff_t pos = begv; + ptrdiff_t pos_byte = BUF_BEGV_BYTE (b); + + struct buffer *old = current_buffer; + set_buffer_internal_1 (b); + + /* Skip N newlines to reach line N. */ + if (line > 0) + { + pos = find_newline_no_quit (pos, pos_byte, line, &pos_byte); + /* find_newline_no_quit returns position AFTER the newline. */ + } + + ptrdiff_t line_start = pos; + + /* Find end of this line (next newline or buffer end). */ + ptrdiff_t end_byte = pos_byte; + ptrdiff_t line_end = find_newline_no_quit (pos, pos_byte, 1, &end_byte); + if (line_end == 0) + line_end = zv; /* No newline found — last line. */ + + set_buffer_internal_1 (old); + + /* Convert to 0-based accessibility indices. */ + ptrdiff_t start_idx = line_start - begv; + ptrdiff_t end_idx = line_end - begv; + if (start_idx < 0) start_idx = 0; + if (end_idx < start_idx) end_idx = start_idx; + + return NSMakeRange ((NSUInteger) start_idx, + (NSUInteger) (end_idx - start_idx)); +} + +- (NSRect)accessibilityFrameForRange:(NSRange)range +{ + /* Return screen rect for a character range. This is the modern + NSAccessibilityProtocol equivalent of accessibilityBoundsForRange:. + We delegate to the same implementation. */ + return [self accessibilityBoundsForRange:range]; +} + +- (NSRange)accessibilityRangeForPosition:(NSPoint)point +{ + /* Return character range at a screen point. VoiceOver uses this + for mouse-based exploration. Stub: returns empty range at 0. + A full implementation would need hit-testing against the glyph + matrix, which Emacs doesn't expose to AppKit. */ + (void) point; + return NSMakeRange (0, 0); +} + +- (NSRange)accessibilityRangeForIndex:(NSInteger)index +{ + /* Return the range of the character at the given index. VoiceOver + uses this for character-by-character navigation. */ + if (!emacsframe) + return NSMakeRange (0, 0); + + struct buffer *curbuf + = XBUFFER (XWINDOW (emacsframe->selected_window)->contents); + if (!curbuf) + return NSMakeRange (0, 0); + + ptrdiff_t range = BUF_ZV (curbuf) - BUF_BEGV (curbuf); + ptrdiff_t maxrange = MIN (range, 10000); + + if (index < 0 || index >= maxrange) + return NSMakeRange (0, 0); + + return NSMakeRange ((NSUInteger) index, 1); +} + +- (NSArray *)accessibilitySelectedTextRanges +{ + /* Return array of selected ranges. VoiceOver may query this + (plural) form instead of the singular selectedTextRange. */ + NSRange r = [self accessibilitySelectedTextRange]; + return @[[NSValue valueWithRange:r]]; +} + +- (BOOL)isAccessibilityFocused +{ + /* Always report focused — matches iTerm2 behavior. */ + return YES; +} + +- (NSString *)accessibilityLabel +{ + /* Provide an identifying label for VoiceOver. */ + return @"Emacs"; +} + +/* ---- Cursor position for Zoom (via accessibilityBoundsForRange:) ---- + + accessibilityFrame intentionally returns the VIEW's frame (standard + behavior) so VoiceOver draws its focus ring around the text area. + The cursor location is exposed through accessibilityBoundsForRange: + which AT tools query using the selectedTextRange. */ + +- (NSRect)accessibilityFrame +{ + /* View's screen frame — standard NSView behavior. Do NOT return + the cursor rect here; that causes a duplicate cursor overlay. */ + return [super accessibilityFrame]; +} + +- (NSRect)accessibilityBoundsForRange:(NSRange)range +{ + /* Return cursor screen rect. AT tools call this with the + selectedTextRange to locate the insertion point. We return the + cursor position regardless of requested range because Emacs does + not expose character-level geometry to AppKit. */ + NSRect viewRect = lastAccessibilityCursorRect; + + if (viewRect.size.width < 1) + viewRect.size.width = 1; + if (viewRect.size.height < 1) + viewRect.size.height = 8; + + NSWindow *win = [self window]; + if (win == nil) + return NSZeroRect; + + NSRect windowRect = [self convertRect:viewRect toView:nil]; + return [win convertRectToScreen:windowRect]; +} + +/* ---- Legacy attribute APIs (pre-10.10 compatibility) ---- */ + +- (NSArray *)accessibilityAttributeNames +{ + NSArray *superAttrs = [super accessibilityAttributeNames]; + if (superAttrs == nil) + superAttrs = @[]; + return [superAttrs arrayByAddingObjectsFromArray: + @[NSAccessibilityRoleAttribute, + NSAccessibilityValueAttribute, + NSAccessibilitySelectedTextAttribute, + NSAccessibilitySelectedTextRangeAttribute, + NSAccessibilityNumberOfCharactersAttribute, + NSAccessibilityVisibleCharacterRangeAttribute, + NSAccessibilityInsertionPointLineNumberAttribute]]; +} + +- (id)accessibilityAttributeValue:(NSString *)attribute +{ + if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) + return NSAccessibilityTextAreaRole; + + if ([attribute isEqualToString:NSAccessibilityValueAttribute]) + return [self accessibilityValue]; + + if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]) + return [self accessibilitySelectedText]; + + if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) + return [NSValue valueWithRange:[self accessibilitySelectedTextRange]]; + + if ([attribute isEqualToString:NSAccessibilityNumberOfCharactersAttribute]) + return @([self accessibilityNumberOfCharacters]); + + if ([attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute]) + return [NSValue valueWithRange:[self accessibilityVisibleCharacterRange]]; + + if ([attribute isEqualToString:NSAccessibilityInsertionPointLineNumberAttribute]) + return @([self accessibilityInsertionPointLineNumber]); + + return [super accessibilityAttributeValue:attribute]; +} + +- (NSArray *)accessibilityParameterizedAttributeNames +{ + NSArray *superAttrs = [super accessibilityParameterizedAttributeNames]; + if (superAttrs == nil) + superAttrs = @[]; + return [superAttrs arrayByAddingObjectsFromArray: + @[NSAccessibilityBoundsForRangeParameterizedAttribute, + NSAccessibilityStringForRangeParameterizedAttribute]]; +} + +- (id)accessibilityAttributeValue:(NSString *)attribute + forParameter:(id)parameter +{ + if ([attribute isEqualToString: + NSAccessibilityBoundsForRangeParameterizedAttribute]) + { + NSRange range = [(NSValue *) parameter rangeValue]; + return [NSValue valueWithRect: + [self accessibilityBoundsForRange:range]]; + } + + if ([attribute isEqualToString: + NSAccessibilityStringForRangeParameterizedAttribute]) + { + NSRange range = [(NSValue *) parameter rangeValue]; + return [self accessibilityStringForRange:range]; + } + + return [super accessibilityAttributeValue:attribute forParameter:parameter]; +} +#endif /* NS_IMPL_COCOA */ + @end /* EmacsView */