From 0bf307c95f3d4b1917eb1cca1c0e48a8b745fd96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20MORAND?= Date: Tue, 20 Jan 2026 10:35:54 +0100 Subject: [PATCH] macOS: Implement dictation support via accessibility and NSTextInputClient This commit enables macOS dictation (triggered by pressing Fn twice) to work in kitty by implementing the necessary accessibility methods. The key fix is changing `selectedRange` to return `NSMakeRange(0, 0)` instead of `kEmptyRange` (NSNotFound, 0). When selectedRange returns NSNotFound, macOS dictation cannot determine where to insert text and fails silently. Additional accessibility methods implemented: - accessibilitySelectedTextRange: Returns cursor position for dictation - accessibilityNumberOfCharacters: Returns 0 (terminal has no fixed buffer) - accessibilityInsertionPointLineNumber: Returns 0 - accessibilityValue: Returns empty string - setAccessibilityValue: Routes dictated text to keyboard input This fix is inspired by the similar fix in Emacs v30 which restored dictation by implementing selectedRange properly after migrating to NSTextInputClient. Fixes: https://github.com/kovidgoyal/kitty/issues/3732 --- glfw/cocoa_window.m | 54 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index deaf82d07..3f5c21d12 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -1400,7 +1400,11 @@ is_modifier_pressed(NSUInteger flags, NSUInteger target_mask, NSUInteger other_m - (NSRange)selectedRange { - return kEmptyRange; + // Return position 0 with no selection to indicate text can be inserted. + // This is required for macOS dictation to work - returning kEmptyRange + // (NSNotFound, 0) causes dictation to fail because the system doesn't + // know where to insert text. See https://github.com/kovidgoyal/kitty/issues/3732 + return NSMakeRange(0, 0); } - (void)setMarkedText:(id)string @@ -1545,7 +1549,15 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) { - (BOOL)isAccessibilitySelectorAllowed:(SEL)selector { - if (selector == @selector(accessibilityRole) || selector == @selector(accessibilitySelectedText)) return YES; + // Allow accessibility selectors needed for dictation and other accessibility features + // See https://github.com/kovidgoyal/kitty/issues/3732 + if (selector == @selector(accessibilityRole) || + selector == @selector(accessibilitySelectedText) || + selector == @selector(accessibilitySelectedTextRange) || + selector == @selector(accessibilityNumberOfCharacters) || + selector == @selector(accessibilityInsertionPointLineNumber) || + selector == @selector(accessibilityValue) || + selector == @selector(setAccessibilityValue:)) return YES; return NO; } @@ -1571,6 +1583,44 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) { return text; } +// Accessibility methods required for dictation support +// See https://github.com/kovidgoyal/kitty/issues/3732 + +- (NSRange)accessibilitySelectedTextRange +{ + // Return position 0 with no selection for dictation support + return NSMakeRange(0, 0); +} + +- (NSInteger)accessibilityNumberOfCharacters +{ + // Terminal doesn't have a fixed text buffer, return 0 + return 0; +} + +- (NSInteger)accessibilityInsertionPointLineNumber +{ + // Return line 0 as the insertion point + return 0; +} + +- (NSString *)accessibilityValue +{ + // Terminal doesn't expose its buffer as an accessibility value + return @""; +} + +- (void)setAccessibilityValue:(NSString *)value +{ + // When dictation or other accessibility features set text, insert it as keyboard input + if (value && [value length] > 0 && window) { + const char *utf8 = [value UTF8String]; + debug_key("Inserting text via setAccessibilityValue: %s\n", utf8); + GLFWkeyevent glfw_keyevent = {.text=utf8, .ime_state=GLFW_IME_COMMIT_TEXT}; + _glfwInputKeyboard(window, &glfw_keyevent); + } +} + // // Support services receiving "public.utf8-plain-text" and "NSStringPboardType"