From af5f558aaffe0987e64706f577fd901141eb0de8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 12 Apr 2026 12:36:27 +0000 Subject: [PATCH 1/4] #2045 Add 'Perform IME action' action (Android 13+) Adds a new keyboard action that triggers the IME action (e.g. Send, Submit, Done) on the currently focused input field. Uses the accessibility service's ACTION_IME_ENTER node action, which requires Android 13+ (API 33 / TIRAMISU). https://claude.ai/code/session_01WYByQnTBo1Hudb7zmGkfj3 --- .../github/sds100/keymapper/base/actions/ActionData.kt | 5 +++++ .../keymapper/base/actions/ActionDataEntityMapper.kt | 2 ++ .../github/sds100/keymapper/base/actions/ActionId.kt | 1 + .../sds100/keymapper/base/actions/ActionUiHelper.kt | 1 + .../sds100/keymapper/base/actions/ActionUtils.kt | 8 ++++++++ .../keymapper/base/actions/PerformActionsUseCase.kt | 10 ++++++++++ base/src/main/res/values/strings.xml | 2 ++ 7 files changed, 29 insertions(+) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionData.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionData.kt index eb24dd579a..e3903bd094 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionData.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionData.kt @@ -833,6 +833,11 @@ sealed class ActionData : Comparable { override val id = ActionId.SHOW_KEYBOARD_PICKER } + @Serializable + data object PerformImeAction : ActionData() { + override val id = ActionId.PERFORM_IME_ACTION + } + @Serializable data object CopyText : ActionData() { override val id = ActionId.TEXT_COPY diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt index a4485f9813..a8fc7a24d0 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt @@ -545,6 +545,7 @@ object ActionDataEntityMapper { ActionId.SHOW_KEYBOARD -> ActionData.ShowKeyboard ActionId.HIDE_KEYBOARD -> ActionData.HideKeyboard ActionId.SHOW_KEYBOARD_PICKER -> ActionData.ShowKeyboardPicker + ActionId.PERFORM_IME_ACTION -> ActionData.PerformImeAction ActionId.TEXT_CUT -> ActionData.CutText ActionId.TEXT_COPY -> ActionData.CopyText ActionId.TEXT_PASTE -> ActionData.PasteText @@ -1322,6 +1323,7 @@ object ActionDataEntityMapper { ActionId.SHOW_KEYBOARD to "show_keyboard", ActionId.HIDE_KEYBOARD to "hide_keyboard", ActionId.SHOW_KEYBOARD_PICKER to "show_keyboard_picker", + ActionId.PERFORM_IME_ACTION to "perform_ime_action", ActionId.TEXT_CUT to "text_cut", ActionId.TEXT_COPY to "text_copy", ActionId.TEXT_PASTE to "text_paste", diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionId.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionId.kt index 70cbf87a59..2ce2fd0eb3 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionId.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionId.kt @@ -125,6 +125,7 @@ enum class ActionId { SHOW_KEYBOARD, HIDE_KEYBOARD, SHOW_KEYBOARD_PICKER, + PERFORM_IME_ACTION, SWITCH_KEYBOARD, diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUiHelper.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUiHelper.kt index 98b53064b4..b97425adf2 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUiHelper.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUiHelper.kt @@ -595,6 +595,7 @@ class ActionUiHelper( ActionData.SelectWordAtCursor -> getString(R.string.action_select_word_at_cursor) ActionData.ShowKeyboard -> getString(R.string.action_show_keyboard) ActionData.ShowKeyboardPicker -> getString(R.string.action_show_keyboard_picker) + ActionData.PerformImeAction -> getString(R.string.action_perform_ime_action) ActionData.ShowPowerMenu -> getString(R.string.action_show_power_menu) ActionData.StatusBar.Collapse -> getString(R.string.action_collapse_status_bar) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt index a5bfaa6454..e71653eba6 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt @@ -8,6 +8,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowBack import androidx.compose.material.icons.automirrored.outlined.Message import androidx.compose.material.icons.automirrored.outlined.OpenInNew +import androidx.compose.material.icons.automirrored.outlined.Send import androidx.compose.material.icons.automirrored.outlined.ShortText import androidx.compose.material.icons.automirrored.outlined.Undo import androidx.compose.material.icons.automirrored.outlined.VolumeDown @@ -225,6 +226,7 @@ object ActionUtils { ActionId.HIDE_KEYBOARD -> ActionCategory.KEYBOARD ActionId.SHOW_KEYBOARD_PICKER -> ActionCategory.KEYBOARD ActionId.SELECT_WORD_AT_CURSOR -> ActionCategory.KEYBOARD + ActionId.PERFORM_IME_ACTION -> ActionCategory.KEYBOARD ActionId.SWITCH_KEYBOARD -> ActionCategory.KEYBOARD ActionId.LOCK_DEVICE -> ActionCategory.INTERFACE ActionId.POWER_ON_OFF_DEVICE -> ActionCategory.INTERFACE @@ -424,6 +426,8 @@ object ActionUtils { ActionId.SELECT_WORD_AT_CURSOR -> R.string.action_select_word_at_cursor + ActionId.PERFORM_IME_ACTION -> R.string.action_perform_ime_action + ActionId.SWITCH_KEYBOARD -> R.string.action_switch_keyboard ActionId.TOGGLE_AIRPLANE_MODE -> R.string.action_toggle_airplane_mode @@ -599,6 +603,7 @@ object ActionUtils { ActionId.TEXT_COPY -> R.drawable.ic_content_copy ActionId.TEXT_PASTE -> R.drawable.ic_content_paste ActionId.SELECT_WORD_AT_CURSOR -> null + ActionId.PERFORM_IME_ACTION -> null ActionId.SWITCH_KEYBOARD -> R.drawable.ic_outline_keyboard_24 ActionId.TOGGLE_AIRPLANE_MODE -> R.drawable.ic_outline_airplanemode_active_24 ActionId.ENABLE_AIRPLANE_MODE -> R.drawable.ic_outline_airplanemode_active_24 @@ -677,6 +682,8 @@ object ActionUtils { ActionId.SELECT_WORD_AT_CURSOR, -> Build.VERSION_CODES.JELLY_BEAN_MR2 + ActionId.PERFORM_IME_ACTION -> Build.VERSION_CODES.TIRAMISU + ActionId.SHOW_POWER_MENU -> Build.VERSION_CODES.LOLLIPOP ActionId.DEVICE_CONTROLS -> Build.VERSION_CODES.S @@ -1029,6 +1036,7 @@ object ActionUtils { ActionId.TEXT_COPY -> Icons.Rounded.ContentCopy ActionId.TEXT_PASTE -> Icons.Rounded.ContentPaste ActionId.SELECT_WORD_AT_CURSOR -> KeyMapperIcons.MatchWord + ActionId.PERFORM_IME_ACTION -> Icons.AutoMirrored.Outlined.Send ActionId.SWITCH_KEYBOARD -> Icons.Outlined.Keyboard ActionId.TOGGLE_AIRPLANE_MODE -> Icons.Outlined.AirplanemodeActive ActionId.ENABLE_AIRPLANE_MODE -> Icons.Outlined.AirplanemodeActive diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt index dc4d735993..b0b36eac3f 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt @@ -764,6 +764,16 @@ class PerformActionsUseCaseImpl @AssistedInject constructor( result = inputMethodAdapter.showImePicker(fromForeground = false) } + is ActionData.PerformImeAction -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + result = service.performActionOnNode({ it.isFocused }) { + AccessibilityNodeAction(AccessibilityNodeInfo.ACTION_IME_ENTER) + } + } else { + result = SdkVersionTooLow(minSdk = Build.VERSION_CODES.TIRAMISU) + } + } + is ActionData.CutText -> { result = service.performActionOnNode({ it.isFocused }) { AccessibilityNodeAction(AccessibilityNodeInfo.ACTION_CUT) diff --git a/base/src/main/res/values/strings.xml b/base/src/main/res/values/strings.xml index 9bb7dc5d2f..20f88e678c 100644 --- a/base/src/main/res/values/strings.xml +++ b/base/src/main/res/values/strings.xml @@ -1074,6 +1074,8 @@ Show keyboard picker + Perform IME action + Switch keyboard Switch to %s From b79c0c428e18bba6d5faf650cc84d3a0dc2206dc Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 12 Apr 2026 12:49:12 +0000 Subject: [PATCH 2/4] #2045 Use inputMethod.currentInputConnection.performEditorAction() Replace the AccessibilityNodeInfo.ACTION_IME_ENTER node action with inputMethod.currentInputConnection.performEditorAction(), following the same pattern as injectText(). Adds performImeAction() to IAccessibilityService and implements it in BaseAccessibilityService. https://claude.ai/code/session_01WYByQnTBo1Hudb7zmGkfj3 --- .../sds100/keymapper/base/actions/PerformActionsUseCase.kt | 5 ++--- .../base/system/accessibility/BaseAccessibilityService.kt | 4 ++++ .../base/system/accessibility/IAccessibilityService.kt | 3 +++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt index b0b36eac3f..e2dd348dde 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt @@ -766,9 +766,8 @@ class PerformActionsUseCaseImpl @AssistedInject constructor( is ActionData.PerformImeAction -> { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - result = service.performActionOnNode({ it.isFocused }) { - AccessibilityNodeAction(AccessibilityNodeInfo.ACTION_IME_ENTER) - } + service.performImeAction() + result = Success(Unit) } else { result = SdkVersionTooLow(minSdk = Build.VERSION_CODES.TIRAMISU) } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/system/accessibility/BaseAccessibilityService.kt b/base/src/main/java/io/github/sds100/keymapper/base/system/accessibility/BaseAccessibilityService.kt index ccb7d38ff1..c1d5991a29 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/system/accessibility/BaseAccessibilityService.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/system/accessibility/BaseAccessibilityService.kt @@ -569,4 +569,8 @@ abstract class BaseAccessibilityService : null, ) } + + override fun performImeAction() { + inputMethod?.currentInputConnection?.performEditorAction(EditorInfo.IME_ACTION_UNSPECIFIED) + } } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/system/accessibility/IAccessibilityService.kt b/base/src/main/java/io/github/sds100/keymapper/base/system/accessibility/IAccessibilityService.kt index 3eaa73ca40..7ae1761d8c 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/system/accessibility/IAccessibilityService.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/system/accessibility/IAccessibilityService.kt @@ -66,4 +66,7 @@ interface IAccessibilityService : SwitchImeInterface { @RequiresApi(Build.VERSION_CODES.TIRAMISU) fun injectText(text: String) + + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + fun performImeAction() } From 0c04c9f821c236187584277535d171cb110b4880 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 12 Apr 2026 13:04:17 +0000 Subject: [PATCH 3/4] #2045 Use Keyboard icon for PerformImeAction (fix build) Replace Icons.AutoMirrored.Outlined.Send (which may not resolve in this icon set) with Icons.Outlined.Keyboard which is already imported and used by other keyboard actions. https://claude.ai/code/session_01WYByQnTBo1Hudb7zmGkfj3 --- .../io/github/sds100/keymapper/base/actions/ActionUtils.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt index e71653eba6..85586fe539 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt @@ -8,7 +8,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowBack import androidx.compose.material.icons.automirrored.outlined.Message import androidx.compose.material.icons.automirrored.outlined.OpenInNew -import androidx.compose.material.icons.automirrored.outlined.Send import androidx.compose.material.icons.automirrored.outlined.ShortText import androidx.compose.material.icons.automirrored.outlined.Undo import androidx.compose.material.icons.automirrored.outlined.VolumeDown @@ -1036,7 +1035,7 @@ object ActionUtils { ActionId.TEXT_COPY -> Icons.Rounded.ContentCopy ActionId.TEXT_PASTE -> Icons.Rounded.ContentPaste ActionId.SELECT_WORD_AT_CURSOR -> KeyMapperIcons.MatchWord - ActionId.PERFORM_IME_ACTION -> Icons.AutoMirrored.Outlined.Send + ActionId.PERFORM_IME_ACTION -> Icons.Outlined.Keyboard ActionId.SWITCH_KEYBOARD -> Icons.Outlined.Keyboard ActionId.TOGGLE_AIRPLANE_MODE -> Icons.Outlined.AirplanemodeActive ActionId.ENABLE_AIRPLANE_MODE -> Icons.Outlined.AirplanemodeActive From 6c0cc61af4a46db8a81b47ce50996d0182bb6dc6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 12 Apr 2026 13:22:45 +0000 Subject: [PATCH 4/4] #2045 Fix missing PERFORM_IME_ACTION case in CreateActionDelegate The exhaustive when(actionId) block in configAction() was missing a case for the new PERFORM_IME_ACTION, causing build and test failures. https://claude.ai/code/session_01WYByQnTBo1Hudb7zmGkfj3 --- .../sds100/keymapper/base/actions/CreateActionDelegate.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt index 3525167f70..e1d2124db5 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt @@ -1066,6 +1066,8 @@ class CreateActionDelegate( ActionId.SHOW_KEYBOARD_PICKER -> return ActionData.ShowKeyboardPicker + ActionId.PERFORM_IME_ACTION -> return ActionData.PerformImeAction + ActionId.TEXT_CUT -> return ActionData.CutText ActionId.TEXT_COPY -> return ActionData.CopyText