From acdc5e2088d6d385508a65da66943875cd5d5773 Mon Sep 17 00:00:00 2001 From: User <16398290+PimpinPumpkin@users.noreply.github.com> Date: Wed, 3 Jun 2026 11:42:35 -0700 Subject: [PATCH] Finish voice input on D-pad center / Enter instead of canceling On devices navigated by a D-pad or remote (e.g. Android TV) the IME window is focusable and initial focus lands on the action bar's back button. Pressing the D-pad center then activated that button and canceled dictation, with no way to "tap" the window to submit the result, since there is no touchscreen. Add an open ActionWindow.onKeyEvent hook, invoked via onPreviewKeyEvent on the action window container so it runs before focused composables consume the event. The voice input window overrides it to treat D-pad center / Enter / numpad Enter as a tap on the window: finish recognition and submit the result. Other windows are unaffected (the default implementation returns false). Cancel remains available via the system Back button. --- .../org/futo/inputmethod/latin/uix/Action.kt | 9 ++++++++ .../futo/inputmethod/latin/uix/UixManager.kt | 3 ++- .../latin/uix/actions/VoiceInputAction.kt | 21 +++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/java/src/org/futo/inputmethod/latin/uix/Action.kt b/java/src/org/futo/inputmethod/latin/uix/Action.kt index 1572fa8a17..6cf4d66149 100644 --- a/java/src/org/futo/inputmethod/latin/uix/Action.kt +++ b/java/src/org/futo/inputmethod/latin/uix/Action.kt @@ -14,6 +14,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.input.key.KeyEvent import androidx.compose.ui.layout.LayoutCoordinates import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -149,6 +150,14 @@ abstract class ActionWindow { open fun close(): CloseResult { return CloseResult.Default } + + /** + * Called for a hardware key event while this window is open, before the event reaches focused + * composables in the window. This lets a window respond to e.g. a TV remote's D-pad center / + * Enter, where there is no touchscreen to tap the window. Return true to consume the event; + * the default implementation ignores all key events. + */ + open fun onKeyEvent(event: KeyEvent): Boolean = false } interface PersistentActionState { diff --git a/java/src/org/futo/inputmethod/latin/uix/UixManager.kt b/java/src/org/futo/inputmethod/latin/uix/UixManager.kt index 6191a1e09c..74b92ff61b 100644 --- a/java/src/org/futo/inputmethod/latin/uix/UixManager.kt +++ b/java/src/org/futo/inputmethod/latin/uix/UixManager.kt @@ -75,6 +75,7 @@ import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.drawscope.scale import androidx.compose.ui.graphics.drawscope.translate +import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.LayoutCoordinates import androidx.compose.ui.layout.onGloballyPositioned @@ -808,7 +809,7 @@ class UixManager(private val latinIME: LatinIME) { } val showingAboveKeyboard = !mainKeyboardHidden.value - Column { + Column(Modifier.onPreviewKeyEvent { windowImpl.onKeyEvent(it) }) { Column( Modifier.background( if (showingAboveKeyboard) { diff --git a/java/src/org/futo/inputmethod/latin/uix/actions/VoiceInputAction.kt b/java/src/org/futo/inputmethod/latin/uix/actions/VoiceInputAction.kt index 560e4dad18..b854e6e373 100644 --- a/java/src/org/futo/inputmethod/latin/uix/actions/VoiceInputAction.kt +++ b/java/src/org/futo/inputmethod/latin/uix/actions/VoiceInputAction.kt @@ -13,6 +13,11 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.KeyEvent +import androidx.compose.ui.input.key.KeyEventType +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.type import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.semantics @@ -236,6 +241,22 @@ private class VoiceInputActionWindow( return CloseResult.Default } + override fun onKeyEvent(event: KeyEvent): Boolean { + // On a device driven by a D-pad / remote (e.g. Android TV) there is no touchscreen to tap + // the window to finish, and the D-pad center would otherwise activate the focused "back" + // button in the action bar and cancel dictation. Treat center/enter as a tap on the + // window: finish recognition and submit the result. + return when (event.key) { + Key.DirectionCenter, Key.Enter, Key.NumPadEnter -> { + if (event.type == KeyEventType.KeyUp) { + recognizerView.value?.finish() + } + true + } + else -> false + } + } + private var wasFinished = false private var cancelPlayed = false override fun cancelled() {