From f306599d943dfbddfab0a645415090dc1ca2e73c Mon Sep 17 00:00:00 2001 From: Daneel Date: Fri, 27 Feb 2026 11:01:22 +0100 Subject: [PATCH] patches: fix completion announcement (CONSP completion--string, focused+unfocused paths) --- ...oundsForRange-for-macOS-Zoom-cursor-.patch | 121 ++++++++++++------ 1 file changed, 79 insertions(+), 42 deletions(-) diff --git a/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch b/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch index e852b32..c37b302 100644 --- a/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch +++ b/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch @@ -1,13 +1,13 @@ -From 7d2c92820f1f7b716f5123e6d371eb051bd99eaa Mon Sep 17 00:00:00 2001 +From 507eb75584d56337a9f7edd8259c62aa40f3945a Mon Sep 17 00:00:00 2001 From: Martin Sukany -Date: Fri, 27 Feb 2026 10:47:48 +0100 +Date: Fri, 27 Feb 2026 11:01:18 +0100 Subject: [PATCH] ns: implement VoiceOver accessibility (AXBoundsForRange, line nav, completions, interactive spans) --- - src/nsterm.h | 109 +++ - src/nsterm.m | 2646 +++++++++++++++++++++++++++++++++++++++++++++++--- - 2 files changed, 2605 insertions(+), 150 deletions(-) + src/nsterm.h | 109 ++ + src/nsterm.m | 2683 +++++++++++++++++++++++++++++++++++++++++++++++--- + 2 files changed, 2642 insertions(+), 150 deletions(-) diff --git a/src/nsterm.h b/src/nsterm.h index 7c1ee4c..6c95673 100644 @@ -144,7 +144,7 @@ index 7c1ee4c..6c95673 100644 diff --git a/src/nsterm.m b/src/nsterm.m -index 932d209..603688d 100644 +index 932d209..76b1ae8 100644 --- a/src/nsterm.m +++ b/src/nsterm.m @@ -46,6 +46,7 @@ Updated by Christian Limpach (chris@nice.ch) @@ -205,7 +205,7 @@ index 932d209..603688d 100644 ns_focus (f, NULL, 0); NSGraphicsContext *ctx = [NSGraphicsContext currentContext]; -@@ -6849,220 +6886,2206 @@ - (BOOL)fulfillService: (NSString *)name withArg: (NSString *)arg +@@ -6849,220 +6886,2243 @@ - (BOOL)fulfillService: (NSString *)name withArg: (NSString *)arg /* ========================================================================== @@ -780,6 +780,20 @@ index 932d209..603688d 100644 -#endif +/* Return the Emacs buffer Lisp object for window W, or Qnil. */ +static Lisp_Object ++/* Extract announcement string from completion--string property value. ++ The property can be a plain Lisp string (simple completion) or ++ a list ("candidate" "annotation") for annotated completions. ++ Returns nil on failure. */ ++static NSString * ++ns_ax_completion_string_from_prop (Lisp_Object cstr) ++{ ++ if (STRINGP (cstr)) ++ return [NSString stringWithLispString: cstr]; ++ if (CONSP (cstr) && STRINGP (XCAR (cstr))) ++ return [NSString stringWithLispString: XCAR (cstr)]; ++ return nil; + } + +ns_ax_window_buffer_object (struct window *w) +{ + if (!w) @@ -787,7 +801,7 @@ index 932d209..603688d 100644 + if (!BUFFERP (w->contents)) + return Qnil; + return w->contents; - } ++} +/* Compute visible-end charpos for window W. + Emacs stores it as BUF_Z - window_end_pos. */ @@ -797,6 +811,9 @@ index 932d209..603688d 100644 + return BUF_Z (b) - w->window_end_pos; +} +-/*****************************************************************************/ +-/* Keyboard handling. */ +-#define NS_KEYLOG 0 +/* Fetch text property PROP at charpos POS in BUF_OBJ. */ +static Lisp_Object +ns_ax_text_prop_at (ptrdiff_t pos, Lisp_Object prop, Lisp_Object buf_obj) @@ -805,30 +822,27 @@ index 932d209..603688d 100644 + return Fplist_get (plist, prop, Qnil); +} --/*****************************************************************************/ --/* Keyboard handling. */ --#define NS_KEYLOG 0 +-- (void)keyDown: (NSEvent *)theEvent +/* Next charpos where PROP changes, capped at LIMIT. */ +static ptrdiff_t +ns_ax_next_prop_change (ptrdiff_t pos, Lisp_Object prop, + Lisp_Object buf_obj, ptrdiff_t limit) -+{ + { +- Mouse_HLInfo *hlinfo = MOUSE_HL_INFO (emacsframe); +- int code; +- unsigned fnKeysym = 0; + Lisp_Object result + = Fnext_single_property_change (make_fixnum (pos), prop, + buf_obj, make_fixnum (limit)); + return FIXNUMP (result) ? XFIXNUM (result) : limit; +} - --- (void)keyDown: (NSEvent *)theEvent ++ +/* Build label for span [START, END) in BUF_OBJ. + Priority: completion--string → buffer text → help-echo. */ +static NSString * +ns_ax_get_span_label (ptrdiff_t start, ptrdiff_t end, + Lisp_Object buf_obj) - { -- Mouse_HLInfo *hlinfo = MOUSE_HL_INFO (emacsframe); -- int code; -- unsigned fnKeysym = 0; ++{ + Lisp_Object cs = ns_ax_text_prop_at (start, intern ("completion--string"), + buf_obj); + if (STRINGP (cs)) @@ -1983,31 +1997,52 @@ index 932d209..603688d 100644 + && (direction == ns_ax_text_selection_direction_next + || direction == ns_ax_text_selection_direction_previous)) + { -+ NSUInteger point_idx = [self accessibilityIndexForCharpos:point]; -+ if (point_idx <= [cachedText length]) ++ /* In completion-list-mode, prefer the completion--string ++ text property over the raw line text. The horizontal ++ layout places two candidates per line; reading the whole ++ line would announce both at once. */ ++ NSString *announceText = nil; ++ ++ struct buffer *cb = XBUFFER (w->contents); ++ Lisp_Object cs_sym = intern ("completion--string"); ++ Lisp_Object cstr = Fget_char_property (make_fixnum (point), ++ cs_sym, Qnil); ++ announceText = ns_ax_completion_string_from_prop (cstr); ++ ++ 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]) ++ /* Fallback: read full line (non-completion buffers). */ ++ NSUInteger point_idx = [self accessibilityIndexForCharpos:point]; ++ if (point_idx <= [cachedText length]) + { -+ NSString *lineText = [cachedText substringWithRange:lineRange]; -+ lineText = [lineText stringByTrimmingCharactersInSet: -+ [NSCharacterSet whitespaceAndNewlineCharacterSet]]; -+ if ([lineText length] > 0) ++ NSInteger lineNum = [self accessibilityLineForIndex:point_idx]; ++ NSRange lineRange = [self accessibilityRangeForLine:lineNum]; ++ if (lineRange.location != NSNotFound ++ && lineRange.length > 0 ++ && lineRange.location + lineRange.length <= [cachedText length]) + { -+ NSDictionary *annInfo = @{ -+ NSAccessibilityAnnouncementKey: lineText, -+ NSAccessibilityPriorityKey: @(NSAccessibilityPriorityHigh) -+ }; -+ NSAccessibilityPostNotificationWithUserInfo ( -+ NSApp, -+ NSAccessibilityAnnouncementRequestedNotification, -+ annInfo); ++ announceText = [cachedText substringWithRange:lineRange]; + } + } + } ++ ++ if (announceText) ++ { ++ announceText = [announceText stringByTrimmingCharactersInSet: ++ [NSCharacterSet whitespaceAndNewlineCharacterSet]]; ++ if ([announceText length] > 0) ++ { ++ NSDictionary *annInfo = @{ ++ NSAccessibilityAnnouncementKey: announceText, ++ NSAccessibilityPriorityKey: @(NSAccessibilityPriorityHigh) ++ }; ++ NSAccessibilityPostNotificationWithUserInfo ( ++ NSApp, ++ NSAccessibilityAnnouncementRequestedNotification, ++ annInfo); ++ } ++ } ++ (void) cb; + } + + /* --- Completions announcement --- @@ -2029,12 +2064,14 @@ index 932d209..603688d 100644 + if (b != current_buffer) + set_buffer_internal_1 (b); + -+ /* 1) Prefer explicit completion candidate property when present. */ ++ /* 1) Prefer explicit completion candidate property when present. ++ completion--string can be a plain string (simple completion) ++ or a list ("candidate" "annotation") for annotated completions. ++ In the list case, use car (the completion itself). */ + Lisp_Object cstr = Fget_char_property (make_fixnum (point), + intern ("completion--string"), + Qnil); -+ if (STRINGP (cstr)) -+ announceText = [NSString stringWithLispString:cstr]; ++ announceText = ns_ax_completion_string_from_prop (cstr); + + /* 2) Fallback: announce the mouse-face span at point. + completion-list-mode often marks the active candidate this way. */ @@ -2562,7 +2599,7 @@ index 932d209..603688d 100644 static NSMutableArray *nsEvArray; unsigned int flags = [theEvent modifierFlags]; -@@ -8237,6 +10260,28 @@ - (void)windowDidBecomeKey /* for direct calls */ +@@ -8237,6 +10297,28 @@ - (void)windowDidBecomeKey /* for direct calls */ XSETFRAME (event.frame_or_window, emacsframe); kbd_buffer_store_event (&event); ns_send_appdefined (-1); // Kick main loop @@ -2591,7 +2628,7 @@ index 932d209..603688d 100644 } -@@ -9474,6 +11519,307 @@ - (int) fullscreenState +@@ -9474,6 +11556,307 @@ - (int) fullscreenState return fs_state; }