summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt16
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt70
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt55
3 files changed, 54 insertions, 87 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
index e96a2059b..f37875ffe 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
@@ -45,7 +45,6 @@ import org.yuzu.yuzu_emu.features.settings.model.IntSetting
45import org.yuzu.yuzu_emu.features.settings.model.Settings 45import org.yuzu.yuzu_emu.features.settings.model.Settings
46import org.yuzu.yuzu_emu.model.EmulationViewModel 46import org.yuzu.yuzu_emu.model.EmulationViewModel
47import org.yuzu.yuzu_emu.model.Game 47import org.yuzu.yuzu_emu.model.Game
48import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
49import org.yuzu.yuzu_emu.utils.ForegroundService 48import org.yuzu.yuzu_emu.utils.ForegroundService
50import org.yuzu.yuzu_emu.utils.InputHandler 49import org.yuzu.yuzu_emu.utils.InputHandler
51import org.yuzu.yuzu_emu.utils.MemoryUtil 50import org.yuzu.yuzu_emu.utils.MemoryUtil
@@ -57,17 +56,16 @@ import kotlin.math.roundToInt
57class EmulationActivity : AppCompatActivity(), SensorEventListener { 56class EmulationActivity : AppCompatActivity(), SensorEventListener {
58 private lateinit var binding: ActivityEmulationBinding 57 private lateinit var binding: ActivityEmulationBinding
59 58
60 private var controllerMappingHelper: ControllerMappingHelper? = null
61
62 var isActivityRecreated = false 59 var isActivityRecreated = false
63 private lateinit var nfcReader: NfcReader 60 private lateinit var nfcReader: NfcReader
64 private lateinit var inputHandler: InputHandler
65 61
66 private val gyro = FloatArray(3) 62 private val gyro = FloatArray(3)
67 private val accel = FloatArray(3) 63 private val accel = FloatArray(3)
68 private var motionTimestamp: Long = 0 64 private var motionTimestamp: Long = 0
69 private var flipMotionOrientation: Boolean = false 65 private var flipMotionOrientation: Boolean = false
70 66
67 private var controllerIds = InputHandler.getGameControllerIds()
68
71 private val actionPause = "ACTION_EMULATOR_PAUSE" 69 private val actionPause = "ACTION_EMULATOR_PAUSE"
72 private val actionPlay = "ACTION_EMULATOR_PLAY" 70 private val actionPlay = "ACTION_EMULATOR_PLAY"
73 private val actionMute = "ACTION_EMULATOR_MUTE" 71 private val actionMute = "ACTION_EMULATOR_MUTE"
@@ -95,8 +93,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
95 93
96 isActivityRecreated = savedInstanceState != null 94 isActivityRecreated = savedInstanceState != null
97 95
98 controllerMappingHelper = ControllerMappingHelper()
99
100 // Set these options now so that the SurfaceView the game renders into is the right size. 96 // Set these options now so that the SurfaceView the game renders into is the right size.
101 enableFullscreenImmersive() 97 enableFullscreenImmersive()
102 98
@@ -105,8 +101,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
105 nfcReader = NfcReader(this) 101 nfcReader = NfcReader(this)
106 nfcReader.initialize() 102 nfcReader.initialize()
107 103
108 inputHandler = InputHandler() 104 InputHandler.initialize()
109 inputHandler.initialize()
110 105
111 val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 106 val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
112 if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) { 107 if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) {
@@ -162,6 +157,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
162 super.onResume() 157 super.onResume()
163 nfcReader.startScanning() 158 nfcReader.startScanning()
164 startMotionSensorListener() 159 startMotionSensorListener()
160 InputHandler.updateControllerIds()
165 161
166 buildPictureInPictureParams() 162 buildPictureInPictureParams()
167 } 163 }
@@ -195,7 +191,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
195 return super.dispatchKeyEvent(event) 191 return super.dispatchKeyEvent(event)
196 } 192 }
197 193
198 return inputHandler.dispatchKeyEvent(event) 194 return InputHandler.dispatchKeyEvent(event)
199 } 195 }
200 196
201 override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean { 197 override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
@@ -210,7 +206,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
210 return true 206 return true
211 } 207 }
212 208
213 return inputHandler.dispatchGenericMotionEvent(event) 209 return InputHandler.dispatchGenericMotionEvent(event)
214 } 210 }
215 211
216 override fun onSensorChanged(event: SensorEvent) { 212 override fun onSensorChanged(event: SensorEvent) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt
deleted file mode 100644
index eeefcdf20..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt
+++ /dev/null
@@ -1,70 +0,0 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6import android.view.InputDevice
7import android.view.KeyEvent
8import android.view.MotionEvent
9
10/**
11 * Some controllers have incorrect mappings. This class has special-case fixes for them.
12 */
13class ControllerMappingHelper {
14 /**
15 * Some controllers report extra button presses that can be ignored.
16 */
17 fun shouldKeyBeIgnored(inputDevice: InputDevice, keyCode: Int): Boolean {
18 return if (isDualShock4(inputDevice)) {
19 // The two analog triggers generate analog motion events as well as a keycode.
20 // We always prefer to use the analog values, so throw away the button press
21 keyCode == KeyEvent.KEYCODE_BUTTON_L2 || keyCode == KeyEvent.KEYCODE_BUTTON_R2
22 } else {
23 false
24 }
25 }
26
27 /**
28 * Scale an axis to be zero-centered with a proper range.
29 */
30 fun scaleAxis(inputDevice: InputDevice, axis: Int, value: Float): Float {
31 if (isDualShock4(inputDevice)) {
32 // Android doesn't have correct mappings for this controller's triggers. It reports them
33 // as RX & RY, centered at -1.0, and with a range of [-1.0, 1.0]
34 // Scale them to properly zero-centered with a range of [0.0, 1.0].
35 if (axis == MotionEvent.AXIS_RX || axis == MotionEvent.AXIS_RY) {
36 return (value + 1) / 2.0f
37 }
38 } else if (isXboxOneWireless(inputDevice)) {
39 // Same as the DualShock 4, the mappings are missing.
40 if (axis == MotionEvent.AXIS_Z || axis == MotionEvent.AXIS_RZ) {
41 return (value + 1) / 2.0f
42 }
43 if (axis == MotionEvent.AXIS_GENERIC_1) {
44 // This axis is stuck at ~.5. Ignore it.
45 return 0.0f
46 }
47 } else if (isMogaPro2Hid(inputDevice)) {
48 // This controller has a broken axis that reports a constant value. Ignore it.
49 if (axis == MotionEvent.AXIS_GENERIC_1) {
50 return 0.0f
51 }
52 }
53 return value
54 }
55
56 // Sony DualShock 4 controller
57 private fun isDualShock4(inputDevice: InputDevice): Boolean {
58 return inputDevice.vendorId == 0x54c && inputDevice.productId == 0x9cc
59 }
60
61 // Microsoft Xbox One controller
62 private fun isXboxOneWireless(inputDevice: InputDevice): Boolean {
63 return inputDevice.vendorId == 0x45e && inputDevice.productId == 0x2e0
64 }
65
66 // Moga Pro 2 HID
67 private fun isMogaPro2Hid(inputDevice: InputDevice): Boolean {
68 return inputDevice.vendorId == 0x20d6 && inputDevice.productId == 0x6271
69 }
70}
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 e963dfbc1..fc6a8b5cb 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
@@ -3,17 +3,24 @@
3 3
4package org.yuzu.yuzu_emu.utils 4package org.yuzu.yuzu_emu.utils
5 5
6import android.view.InputDevice
6import android.view.KeyEvent 7import android.view.KeyEvent
7import android.view.MotionEvent 8import android.view.MotionEvent
8import kotlin.math.sqrt 9import kotlin.math.sqrt
9import org.yuzu.yuzu_emu.NativeLibrary 10import org.yuzu.yuzu_emu.NativeLibrary
10 11
11class InputHandler { 12object InputHandler {
13 private var controllerIds = getGameControllerIds()
14
12 fun initialize() { 15 fun initialize() {
13 // Connect first controller 16 // Connect first controller
14 NativeLibrary.onGamePadConnectEvent(getPlayerNumber(NativeLibrary.Player1Device)) 17 NativeLibrary.onGamePadConnectEvent(getPlayerNumber(NativeLibrary.Player1Device))
15 } 18 }
16 19
20 fun updateControllerIds() {
21 controllerIds = getGameControllerIds()
22 }
23
17 fun dispatchKeyEvent(event: KeyEvent): Boolean { 24 fun dispatchKeyEvent(event: KeyEvent): Boolean {
18 val button: Int = when (event.device.vendorId) { 25 val button: Int = when (event.device.vendorId) {
19 0x045E -> getInputXboxButtonKey(event.keyCode) 26 0x045E -> getInputXboxButtonKey(event.keyCode)
@@ -35,7 +42,7 @@ class InputHandler {
35 } 42 }
36 43
37 return NativeLibrary.onGamePadButtonEvent( 44 return NativeLibrary.onGamePadButtonEvent(
38 getPlayerNumber(event.device.controllerNumber), 45 getPlayerNumber(event.device.controllerNumber, event.deviceId),
39 button, 46 button,
40 action 47 action
41 ) 48 )
@@ -58,9 +65,14 @@ class InputHandler {
58 return true 65 return true
59 } 66 }
60 67
61 private fun getPlayerNumber(index: Int): Int { 68 private fun getPlayerNumber(index: Int, deviceId: Int = -1): Int {
69 var deviceIndex = index
70 if (deviceId != -1) {
71 deviceIndex = controllerIds[deviceId]!!
72 }
73
62 // TODO: Joycons are handled as different controllers. Find a way to merge them. 74 // TODO: Joycons are handled as different controllers. Find a way to merge them.
63 return when (index) { 75 return when (deviceIndex) {
64 2 -> NativeLibrary.Player2Device 76 2 -> NativeLibrary.Player2Device
65 3 -> NativeLibrary.Player3Device 77 3 -> NativeLibrary.Player3Device
66 4 -> NativeLibrary.Player4Device 78 4 -> NativeLibrary.Player4Device
@@ -238,7 +250,7 @@ class InputHandler {
238 } 250 }
239 251
240 private fun setGenericAxisInput(event: MotionEvent, axis: Int) { 252 private fun setGenericAxisInput(event: MotionEvent, axis: Int) {
241 val playerNumber = getPlayerNumber(event.device.controllerNumber) 253 val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId)
242 254
243 when (axis) { 255 when (axis) {
244 MotionEvent.AXIS_X, MotionEvent.AXIS_Y -> 256 MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
@@ -297,7 +309,7 @@ class InputHandler {
297 309
298 private fun setJoyconAxisInput(event: MotionEvent, axis: Int) { 310 private fun setJoyconAxisInput(event: MotionEvent, axis: Int) {
299 // Joycon support is half dead. Right joystick doesn't work 311 // Joycon support is half dead. Right joystick doesn't work
300 val playerNumber = getPlayerNumber(event.device.controllerNumber) 312 val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId)
301 313
302 when (axis) { 314 when (axis) {
303 MotionEvent.AXIS_X, MotionEvent.AXIS_Y -> 315 MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
@@ -325,7 +337,7 @@ class InputHandler {
325 } 337 }
326 338
327 private fun setRazerAxisInput(event: MotionEvent, axis: Int) { 339 private fun setRazerAxisInput(event: MotionEvent, axis: Int) {
328 val playerNumber = getPlayerNumber(event.device.controllerNumber) 340 val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId)
329 341
330 when (axis) { 342 when (axis) {
331 MotionEvent.AXIS_X, MotionEvent.AXIS_Y -> 343 MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
@@ -362,4 +374,33 @@ class InputHandler {
362 ) 374 )
363 } 375 }
364 } 376 }
377
378 fun getGameControllerIds(): Map<Int, Int> {
379 val gameControllerDeviceIds = mutableMapOf<Int, Int>()
380 val deviceIds = InputDevice.getDeviceIds()
381 var controllerSlot = 1
382 deviceIds.forEach { deviceId ->
383 InputDevice.getDevice(deviceId)?.apply {
384 // Don't over-assign controllers
385 if (controllerSlot >= 8) {
386 return gameControllerDeviceIds
387 }
388
389 // Verify that the device has gamepad buttons, control sticks, or both.
390 if (sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD ||
391 sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK
392 ) {
393 // This device is a game controller. Store its device ID.
394 if (deviceId and id and vendorId and productId != 0) {
395 // Additionally filter out devices that have no ID
396 gameControllerDeviceIds
397 .takeIf { !it.contains(deviceId) }
398 ?.put(deviceId, controllerSlot)
399 controllerSlot++
400 }
401 }
402 }
403 }
404 return gameControllerDeviceIds
405 }
365} 406}