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
This commit is contained in:
Sébastien MORAND 2026-01-20 10:35:54 +01:00
parent 60cee4d9a9
commit 0bf307c95f
No known key found for this signature in database
GPG key ID: 4F30474244A9B1D2

View file

@ -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);
}
}
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/SysServices/Articles/using.html>
// Support services receiving "public.utf8-plain-text" and "NSStringPboardType"