From 9d60030af5535052b8365a5316c8d9e563bcb4e1 Mon Sep 17 00:00:00 2001 From: crueter Date: Sat, 25 Jul 2026 15:09:16 +0200 Subject: [PATCH] [android] Support Joy-Con D-pad buttons (#4225) Closes eden-emulator/Issue-Reports#8 Closes eden-emulator/Issue-Reports#428 Currently, [Android's input layer](https://android.googlesource.com/platform/frameworks/base.git/+/refs/heads/android16-release/data/keyboards/Vendor_057e_Product_2009.kl#54) doesn't return `KEYCODE_DPAD_*` for the left Joy-Con, we fall back to the scan code in this case, this will also fix most third party Joy-Con controllers. - The scan codes are not reliable and may vary from device to another. I searched for (0x220-0x223) values in the [android repo](https://android.googlesource.com/platform/frameworks/base.git/+/refs/heads/android16-release/data/keyboards) and they all have the same DPAD_* key mapping, so not a big issue just might cause future issues if Google change the mapping in another vendor. I could simply limit the fix to the [left Joy-Con](https://github.com/torvalds/linux/blob/master/drivers/hid/hid-ids.h#L1096-L1105) `if (event.keyCode == 0 && event.device.vendorId == 0x057e && event.device.productId == 0x2006)`. - Auto mapping doesn't work, I'm not very familiar with the JNI Specification, I managed to implement the below workaround, it basically checks if it's the left Joy-Con and add the first four DPAD_* keycodes ```cpp // ./src/input_common/drivers/android.cpp ButtonMapping Android::GetButtonMappingForDevice(const Common::ParamPackage& params) { // ... const char *yuzu_device_name = env->GetStringUTFChars((jstring) env->CallObjectMethod(j_device, Common::Android::GetYuzuDeviceGetName()), &isCopy); const char *switch_left_string_name = "Nintendo Switch Left Joy-Con"; bool is_switch_left = strncmp(yuzu_device_name, switch_left_string_name, strlen(switch_left_string_name)) == 0; std::set available_keys; for (size_t i = 0; i < keycode_ids.size(); ++i) { if (j_has_keys[i] || (is_switch_left && i <= 3)) { available_keys.insert(keycode_ids[i]); } } // ... } ``` Signed-off-by: crueter Authored-by: Anas Bouzid Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4225 Reviewed-by: Lizzie Reviewed-by: Maufeat --- .../settings/ui/InputDialogFragment.kt | 4 ++-- .../org/yuzu/yuzu_emu/utils/InputHandler.kt | 21 ++++++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputDialogFragment.kt index d07a6e6aea..0e2f78c49f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputDialogFragment.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2024 yuzu Emulator Project @@ -169,7 +169,7 @@ class InputDialogFragment : DialogFragment() { NativeInput.onGamePadButtonEvent( controllerData.getGUID(), controllerData.getPort(), - event.keyCode, + InputHandler.getButtonIdFromEvent(event), action ) onInputReceived(event.device) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt index 1909a73319..cde084c68f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt @@ -49,6 +49,12 @@ object InputHandler { MotionEvent.AXIS_RTRIGGER ) + // Currently, Android doesn't support Joy-Con D-pad buttons. We fall back to the scan code + private const val LINUX_BUTTON_DPAD_UP = 0x220 + private const val LINUX_BUTTON_DPAD_DOWN = 0x221 + private const val LINUX_BUTTON_DPAD_LEFT = 0x222 + private const val LINUX_BUTTON_DPAD_RIGHT = 0x223 + fun isPhysicalGameController(device: InputDevice?): Boolean { device ?: return false @@ -87,12 +93,25 @@ object InputHandler { NativeInput.onGamePadButtonEvent( controllerData.getGUID(), controllerData.getPort(), - event.keyCode, + getButtonIdFromEvent(event), action ) return true } + fun getButtonIdFromEvent(event: KeyEvent): Int { + if (event.keyCode == 0) { + return when (event.scanCode) { + LINUX_BUTTON_DPAD_UP -> KeyEvent.KEYCODE_DPAD_UP + LINUX_BUTTON_DPAD_DOWN -> KeyEvent.KEYCODE_DPAD_DOWN + LINUX_BUTTON_DPAD_LEFT -> KeyEvent.KEYCODE_DPAD_LEFT + LINUX_BUTTON_DPAD_RIGHT -> KeyEvent.KEYCODE_DPAD_RIGHT + else -> return 0 + } + } + return event.keyCode + } + fun dispatchGenericMotionEvent(event: MotionEvent): Boolean { val controllerData = androidControllers[event.device.controllerNumber] ?: return false