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 e058067c97..b30544aaa2 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 @@ -33,8 +33,10 @@ object SoftwareKeyboard { val emulationActivity = NativeLibrary.sEmulationActivity.get() val overlayView = emulationActivity!!.findViewById(R.id.surface_input_overlay) + overlayView.requestFocus() val im = overlayView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + im.restartInput(overlayView) im.showSoftInput(overlayView, InputMethodManager.SHOW_FORCED) // There isn't a good way to know that the IMM is dismissed, so poll every 500ms to submit inline keyboard result. 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 d3b5d86174..7ff9c78923 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 @@ -17,16 +17,23 @@ import android.graphics.drawable.VectorDrawable import android.os.Build import android.os.Handler import android.os.Looper +import android.text.Editable +import android.text.InputType import android.util.AttributeSet import android.view.HapticFeedbackConstants +import android.view.KeyEvent import android.view.MotionEvent import android.view.View import android.view.View.OnTouchListener import android.view.WindowInsets +import android.view.inputmethod.BaseInputConnection +import android.view.inputmethod.EditorInfo +import android.view.inputmethod.InputConnection import androidx.core.content.ContextCompat import androidx.window.layout.WindowMetricsCalculator import kotlin.math.max import kotlin.math.min +import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.features.input.NativeInput import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.features.input.model.NativeAnalog @@ -49,6 +56,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : private val overlayButtons: MutableSet = HashSet() private val overlayDpads: MutableSet = HashSet() private val overlayJoysticks: MutableSet = HashSet() + private val imeEditable = Editable.Factory.getInstance().newEditable("") private var inEditMode = false private var gamelessMode = false @@ -75,6 +83,63 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : // External listener for EmulationFragment joypad overlay auto-hide var touchEventListener: ((MotionEvent) -> Unit)? = null + override fun onCheckIsTextEditor(): Boolean = true + + override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection { + imeEditable.clear() + 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 + + return object : BaseInputConnection(this, true) { + override fun getEditable(): Editable = imeEditable + + override fun commitText(text: CharSequence?, newCursorPosition: Int): Boolean { + if (!text.isNullOrEmpty()) { + forwardCommittedText(text) + } + return super.commitText(text, newCursorPosition) + } + + override fun deleteSurroundingText(beforeLength: Int, afterLength: Int): Boolean { + repeat(beforeLength.coerceAtLeast(0)) { + NativeLibrary.submitInlineKeyboardInput(KeyEvent.KEYCODE_DEL) + } + return super.deleteSurroundingText(beforeLength, afterLength) + } + + override fun sendKeyEvent(event: KeyEvent): Boolean { + if (event.action != KeyEvent.ACTION_DOWN) { + return true + } + + when (event.keyCode) { + KeyEvent.KEYCODE_BACK, + KeyEvent.KEYCODE_DEL, + KeyEvent.KEYCODE_ENTER -> { + NativeLibrary.submitInlineKeyboardInput(event.keyCode) + } + else -> { + val textChar = event.unicodeChar + if (textChar != 0) { + NativeLibrary.submitInlineKeyboardText(textChar.toChar().toString()) + } + } + } + return true + } + + override fun performEditorAction(actionCode: Int): Boolean { + NativeLibrary.submitInlineKeyboardInput(KeyEvent.KEYCODE_ENTER) + return true + } + } + } + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) @@ -119,6 +184,25 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : } } + private fun forwardCommittedText(text: CharSequence) { + val builder = StringBuilder() + text.forEach { character -> + when (character) { + '\n' -> { + if (builder.isNotEmpty()) { + NativeLibrary.submitInlineKeyboardText(builder.toString()) + builder.clear() + } + NativeLibrary.submitInlineKeyboardInput(KeyEvent.KEYCODE_ENTER) + } + else -> builder.append(character) + } + } + if (builder.isNotEmpty()) { + NativeLibrary.submitInlineKeyboardText(builder.toString()) + } + } + private fun drawGrid(canvas: Canvas) { val gridSize = IntSetting.OVERLAY_GRID_SIZE.getInt() val width = canvas.width diff --git a/src/common/android/applets/software_keyboard.cpp b/src/common/android/applets/software_keyboard.cpp index db76db91a6..8967811bd3 100644 --- a/src/common/android/applets/software_keyboard.cpp +++ b/src/common/android/applets/software_keyboard.cpp @@ -243,6 +243,9 @@ void AndroidKeyboard::SubmitInlineKeyboardInput(int key_code) { static_cast(m_current_text.size())); break; case KEYCODE_DEL: + if (m_current_text.empty()) { + return; + } m_current_text.pop_back(); submit_inline_callback(Service::AM::Frontend::SwkbdReplyType::ChangedString, m_current_text, static_cast(m_current_text.size()));