From c0804a4e8624133aedd46bcdff9034938fc971c8 Mon Sep 17 00:00:00 2001 From: CookieTraces Date: Wed, 29 Apr 2026 10:11:17 +0200 Subject: [PATCH] [Android] Fix swkbd inline keyboard initial text sync and cursor position When the game opens an inline keyboard with pre-filled text (e.g. a previously saved Mii name), the text was stored in m_current_text on the C++ side but never communicated to the Android IME. As a result, the Android keyboard field appeared empty, backspace did nothing, and typing replaced the existing text instead of appending to it. The root cause: the game sends InlineTextChanged() with the pre-filled text in the same Calc request as appear(), before ShowInlineKeyboard() is called. m_current_text was not updated by InlineTextChanged, and ShowInlineKeyboard was using the stale parameters.initial_text (set during initialization before the text was known). Fix on the C++ side: - Initialize m_current_text from parameters.initial_text in InitializeKeyboard so each session starts clean. - Update m_current_text in InlineTextChanged so the latest game text is always reflected. - Pass m_current_text (not parameters.initial_text) as initial_text to Kotlin when calling executeInline. Fix on the Kotlin side: - Pre-populate imeEditable with the received initial text. - Call Selection.setSelection to place the cursor at the end so that backspace and new input work correctly from the start. --- .../yuzu_emu/applets/keyboard/SoftwareKeyboard.kt | 2 ++ .../org/yuzu/yuzu_emu/overlay/InputOverlay.kt | 15 +++++++++++++-- src/common/android/applets/software_keyboard.cpp | 11 +++++++++-- src/common/android/applets/software_keyboard.h | 2 +- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt index 5b28c04219..e0d2dfd4eb 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt @@ -20,6 +20,7 @@ import java.io.Serializable import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.applets.keyboard.ui.KeyboardDialogFragment +import org.yuzu.yuzu_emu.overlay.InputOverlay @Keep object SoftwareKeyboard { @@ -37,6 +38,7 @@ object SoftwareKeyboard { val emulationActivity = NativeLibrary.sEmulationActivity.get() val overlayView = emulationActivity!!.findViewById(R.id.surface_input_overlay) + (overlayView as? InputOverlay)?.resetImeBuffer(config.initial_text ?: "") overlayView.requestFocus() val im = overlayView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt index 7ff9c78923..94cfdbde42 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt @@ -19,6 +19,7 @@ import android.os.Handler import android.os.Looper import android.text.Editable import android.text.InputType +import android.text.Selection import android.util.AttributeSet import android.view.HapticFeedbackConstants import android.view.KeyEvent @@ -57,6 +58,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : private val overlayDpads: MutableSet = HashSet() private val overlayJoysticks: MutableSet = HashSet() private val imeEditable = Editable.Factory.getInstance().newEditable("") + private var pendingInitialText: String = "" private var inEditMode = false private var gamelessMode = false @@ -85,15 +87,24 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : override fun onCheckIsTextEditor(): Boolean = true + fun resetImeBuffer(initialText: String = "") { + pendingInitialText = initialText + } + override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection { imeEditable.clear() + if (pendingInitialText.isNotEmpty()) { + imeEditable.append(pendingInitialText) + } + pendingInitialText = "" + Selection.setSelection(imeEditable, imeEditable.length) outAttrs.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI or EditorInfo.IME_ACTION_DONE - outAttrs.initialSelStart = 0 - outAttrs.initialSelEnd = 0 + outAttrs.initialSelStart = imeEditable.length + outAttrs.initialSelEnd = imeEditable.length return object : BaseInputConnection(this, true) { override fun getEditable(): Editable = imeEditable diff --git a/src/common/android/applets/software_keyboard.cpp b/src/common/android/applets/software_keyboard.cpp index 8967811bd3..73b6d9be07 100644 --- a/src/common/android/applets/software_keyboard.cpp +++ b/src/common/android/applets/software_keyboard.cpp @@ -109,6 +109,7 @@ void AndroidKeyboard::InitializeKeyboard( } parameters = std::move(initialize_parameters); + m_current_text = parameters.initial_text; LOG_INFO(Frontend, "\nKeyboardInitializeParameters:" @@ -185,9 +186,13 @@ void AndroidKeyboard::ShowInlineKeyboard( // Pivot to a new thread, as we cannot call GetEnvForThread() from a Fiber. m_is_inline_active = true; - std::thread([&] { + // Pass m_current_text as initial_text so Kotlin receives any text set via InlineTextChanged + // before this call (e.g. game pre-fills the field in the same Calc request as appear). + std::thread([&, current_text = m_current_text] { + Core::Frontend::KeyboardInitializeParameters p = parameters; + p.initial_text = current_text; GetEnvForThread()->CallStaticVoidMethod(s_software_keyboard_class, s_swkbd_execute_inline, - ToJKeyboardParams(parameters)); + ToJKeyboardParams(p)); }).join(); } @@ -207,6 +212,8 @@ void AndroidKeyboard::InlineTextChanged( "\ncursor_position={}", Common::UTF16ToUTF8(text_parameters.input_text), text_parameters.cursor_position); + m_current_text = text_parameters.input_text; + submit_inline_callback(Service::AM::Frontend::SwkbdReplyType::ChangedString, text_parameters.input_text, text_parameters.cursor_position); } diff --git a/src/common/android/applets/software_keyboard.h b/src/common/android/applets/software_keyboard.h index 9fd09d27c5..7b194cd227 100644 --- a/src/common/android/applets/software_keyboard.h +++ b/src/common/android/applets/software_keyboard.h @@ -57,7 +57,7 @@ private: private: mutable bool m_is_inline_active{}; - std::u16string m_current_text; + mutable std::u16string m_current_text; }; // Should be called in JNI_Load