summaryrefslogtreecommitdiff
path: root/src/android
diff options
context:
space:
mode:
authorGravatar german772023-04-04 02:08:53 -0600
committerGravatar bunnei2023-06-03 00:05:52 -0700
commit6dfe4240acebe91ee466b970834e71f131d0b349 (patch)
tree8bcdb9370b3101c8db286f60e366d665b9d48c1a /src/android
parentandroid: Bump minimum version to Android 11 (diff)
downloadyuzu-6dfe4240acebe91ee466b970834e71f131d0b349.tar.gz
yuzu-6dfe4240acebe91ee466b970834e71f131d0b349.tar.xz
yuzu-6dfe4240acebe91ee466b970834e71f131d0b349.zip
android: Implement gamepad input
Diffstat (limited to 'src/android')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt42
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt38
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt16
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt323
-rw-r--r--src/android/app/src/main/jni/native.cpp90
-rw-r--r--src/android/app/src/main/jni/native.h12
6 files changed, 510 insertions, 11 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
index cd9bc9ef0..c11b6bc16 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
@@ -43,6 +43,21 @@ object NativeLibrary {
43 const val Player8Device = 7 43 const val Player8Device = 7
44 const val ConsoleDevice = 8 44 const val ConsoleDevice = 8
45 45
46 /**
47 * Controller type for each device
48 */
49 const val ProController = 3
50 const val Handheld = 4
51 const val JoyconDual = 5
52 const val JoyconLeft = 6
53 const val JoyconRight = 7
54 const val GameCube = 8
55 const val Pokeball = 9
56 const val NES = 10
57 const val SNES = 11
58 const val N64 = 12
59 const val SegaGenesis = 13
60
46 @JvmField 61 @JvmField
47 var sEmulationActivity = WeakReference<EmulationActivity?>(null) 62 var sEmulationActivity = WeakReference<EmulationActivity?>(null)
48 63
@@ -71,6 +86,33 @@ object NativeLibrary {
71 } 86 }
72 87
73 /** 88 /**
89 * Returns true if pro controller isn't available and handheld is
90 */
91 external fun isHandheldOnly(): Boolean
92
93 /**
94 * Changes controller type for a specific device.
95 *
96 * @param Device The input descriptor of the gamepad.
97 * @param Type The NpadStyleIndex of the gamepad.
98 */
99 external fun setDeviceType(Device: Int, Type: Int): Boolean
100
101 /**
102 * Handles event when a gamepad is connected.
103 *
104 * @param Device The input descriptor of the gamepad.
105 */
106 external fun onGamePadConnectEvent(Device: Int): Boolean
107
108 /**
109 * Handles event when a gamepad is disconnected.
110 *
111 * @param Device The input descriptor of the gamepad.
112 */
113 external fun onGamePadDisconnectEvent(Device: Int): Boolean
114
115 /**
74 * Handles button press events for a gamepad. 116 * Handles button press events for a gamepad.
75 * 117 *
76 * @param Device The input descriptor of the gamepad. 118 * @param Device The input descriptor of the gamepad.
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 fd174fd2d..4c57de067 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
@@ -8,9 +8,9 @@ import android.content.DialogInterface
8import android.content.Intent 8import android.content.Intent
9import android.graphics.Rect 9import android.graphics.Rect
10import android.os.Bundle 10import android.os.Bundle
11import android.view.*
11import android.view.KeyEvent 12import android.view.KeyEvent
12import android.view.View 13import android.view.MotionEvent
13import android.view.WindowManager
14import android.view.inputmethod.InputMethodManager 14import android.view.inputmethod.InputMethodManager
15import androidx.appcompat.app.AppCompatActivity 15import androidx.appcompat.app.AppCompatActivity
16import androidx.preference.PreferenceManager 16import androidx.preference.PreferenceManager
@@ -23,6 +23,7 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings
23import org.yuzu.yuzu_emu.fragments.EmulationFragment 23import org.yuzu.yuzu_emu.fragments.EmulationFragment
24import org.yuzu.yuzu_emu.model.Game 24import org.yuzu.yuzu_emu.model.Game
25import org.yuzu.yuzu_emu.utils.ControllerMappingHelper 25import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
26import org.yuzu.yuzu_emu.utils.InputHandler
26import org.yuzu.yuzu_emu.utils.NfcReader 27import org.yuzu.yuzu_emu.utils.NfcReader
27import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable 28import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
28import org.yuzu.yuzu_emu.utils.ThemeHelper 29import org.yuzu.yuzu_emu.utils.ThemeHelper
@@ -38,6 +39,7 @@ open class EmulationActivity : AppCompatActivity() {
38 private var menuVisible = false 39 private var menuVisible = false
39 private var emulationFragment: EmulationFragment? = null 40 private var emulationFragment: EmulationFragment? = null
40 private lateinit var nfcReader: NfcReader 41 private lateinit var nfcReader: NfcReader
42 private lateinit var inputHandler: InputHandler
41 43
42 private lateinit var game: Game 44 private lateinit var game: Game
43 45
@@ -80,6 +82,9 @@ open class EmulationActivity : AppCompatActivity() {
80 nfcReader = NfcReader(this) 82 nfcReader = NfcReader(this)
81 nfcReader.initialize() 83 nfcReader.initialize()
82 84
85 inputHandler = InputHandler()
86 inputHandler.initialize()
87
83 // Start a foreground service to prevent the app from getting killed in the background 88 // Start a foreground service to prevent the app from getting killed in the background
84 // TODO(bunnei): Disable notifications until we support app suspension. 89 // TODO(bunnei): Disable notifications until we support app suspension.
85 //foregroundService = new Intent(EmulationActivity.this, ForegroundService.class); 90 //foregroundService = new Intent(EmulationActivity.this, ForegroundService.class);
@@ -108,6 +113,7 @@ open class EmulationActivity : AppCompatActivity() {
108 } 113 }
109 return super.onKeyDown(keyCode, event) 114 return super.onKeyDown(keyCode, event)
110 } 115 }
116
111 override fun onResume() { 117 override fun onResume() {
112 super.onResume() 118 super.onResume()
113 nfcReader.startScanning() 119 nfcReader.startScanning()
@@ -129,6 +135,29 @@ open class EmulationActivity : AppCompatActivity() {
129 super.onSaveInstanceState(outState) 135 super.onSaveInstanceState(outState)
130 } 136 }
131 137
138 override fun dispatchKeyEvent(event: KeyEvent): Boolean {
139 // Handling the case where the back button is pressed.
140 if (event.keyCode == KeyEvent.KEYCODE_BACK) {
141 onBackPressedDispatcher.onBackPressed()
142 return true
143 }
144
145 return inputHandler.dispatchKeyEvent(event)
146 }
147
148 override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
149 if (event.source and InputDevice.SOURCE_CLASS_JOYSTICK === 0) {
150 return super.dispatchGenericMotionEvent(event)
151 }
152
153 // Don't attempt to do anything if we are disconnecting a device.
154 if (event.actionMasked == MotionEvent.ACTION_CANCEL) {
155 return true
156 }
157
158 return inputHandler.dispatchGenericMotionEvent(event)
159 }
160
132 private fun restoreState(savedInstanceState: Bundle) { 161 private fun restoreState(savedInstanceState: Bundle) {
133 game = savedInstanceState.parcelable(EXTRA_SELECTED_GAME)!! 162 game = savedInstanceState.parcelable(EXTRA_SELECTED_GAME)!!
134 } 163 }
@@ -159,8 +188,9 @@ open class EmulationActivity : AppCompatActivity() {
159 private fun adjustScale() { 188 private fun adjustScale() {
160 val sliderBinding = DialogSliderBinding.inflate(layoutInflater) 189 val sliderBinding = DialogSliderBinding.inflate(layoutInflater)
161 sliderBinding.slider.valueTo = 150F 190 sliderBinding.slider.valueTo = 150F
162 sliderBinding.slider.value = PreferenceManager.getDefaultSharedPreferences(applicationContext) 191 sliderBinding.slider.value =
163 .getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat() 192 PreferenceManager.getDefaultSharedPreferences(applicationContext)
193 .getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat()
164 sliderBinding.slider.addOnChangeListener(OnChangeListener { _, value, _ -> 194 sliderBinding.slider.addOnChangeListener(OnChangeListener { _, value, _ ->
165 sliderBinding.textValue.text = value.toString() 195 sliderBinding.textValue.text = value.toString()
166 setControlScale(value.toInt()) 196 setControlScale(value.toInt())
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 c0dc8ce76..c2adf0ec6 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
@@ -113,13 +113,15 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
113 } 113 }
114 114
115 var shouldUpdateView = false 115 var shouldUpdateView = false
116 val playerIndex =
117 if (NativeLibrary.isHandheldOnly()) NativeLibrary.ConsoleDevice else NativeLibrary.Player1Device
116 118
117 for (button in overlayButtons) { 119 for (button in overlayButtons) {
118 if (!button.updateStatus(event)) { 120 if (!button.updateStatus(event)) {
119 continue 121 continue
120 } 122 }
121 NativeLibrary.onGamePadButtonEvent( 123 NativeLibrary.onGamePadButtonEvent(
122 NativeLibrary.Player1Device, 124 playerIndex,
123 button.buttonId, 125 button.buttonId,
124 button.status 126 button.status
125 ) 127 )
@@ -131,22 +133,22 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
131 continue 133 continue
132 } 134 }
133 NativeLibrary.onGamePadButtonEvent( 135 NativeLibrary.onGamePadButtonEvent(
134 NativeLibrary.Player1Device, 136 playerIndex,
135 dpad.upId, 137 dpad.upId,
136 dpad.upStatus 138 dpad.upStatus
137 ) 139 )
138 NativeLibrary.onGamePadButtonEvent( 140 NativeLibrary.onGamePadButtonEvent(
139 NativeLibrary.Player1Device, 141 playerIndex,
140 dpad.downId, 142 dpad.downId,
141 dpad.downStatus 143 dpad.downStatus
142 ) 144 )
143 NativeLibrary.onGamePadButtonEvent( 145 NativeLibrary.onGamePadButtonEvent(
144 NativeLibrary.Player1Device, 146 playerIndex,
145 dpad.leftId, 147 dpad.leftId,
146 dpad.leftStatus 148 dpad.leftStatus
147 ) 149 )
148 NativeLibrary.onGamePadButtonEvent( 150 NativeLibrary.onGamePadButtonEvent(
149 NativeLibrary.Player1Device, 151 playerIndex,
150 dpad.rightId, 152 dpad.rightId,
151 dpad.rightStatus 153 dpad.rightStatus
152 ) 154 )
@@ -159,13 +161,13 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
159 } 161 }
160 val axisID = joystick.joystickId 162 val axisID = joystick.joystickId
161 NativeLibrary.onGamePadJoystickEvent( 163 NativeLibrary.onGamePadJoystickEvent(
162 NativeLibrary.Player1Device, 164 playerIndex,
163 axisID, 165 axisID,
164 joystick.xAxis, 166 joystick.xAxis,
165 joystick.realYAxis 167 joystick.realYAxis
166 ) 168 )
167 NativeLibrary.onGamePadButtonEvent( 169 NativeLibrary.onGamePadButtonEvent(
168 NativeLibrary.Player1Device, 170 playerIndex,
169 joystick.buttonId, 171 joystick.buttonId,
170 joystick.buttonStatus 172 joystick.buttonStatus
171 ) 173 )
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
new file mode 100644
index 000000000..a48b7e00a
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt
@@ -0,0 +1,323 @@
1package org.yuzu.yuzu_emu.utils
2
3import android.view.InputDevice
4import android.view.KeyEvent
5import android.view.MotionEvent
6import org.yuzu.yuzu_emu.NativeLibrary
7
8class InputHandler {
9 fun initialize() {
10 // Connect first controller
11 NativeLibrary.onGamePadConnectEvent(getPlayerNumber(NativeLibrary.Player1Device));
12 }
13
14 fun dispatchKeyEvent(event: KeyEvent): Boolean {
15 val button: Int = when (event.device.vendorId) {
16 0x045E -> getInputXboxButtonKey(event.keyCode)
17 0x054C -> getInputDS5ButtonKey(event.keyCode)
18 0x057E -> getInputJoyconButtonKey(event.keyCode)
19 0x1532 -> getInputRazerButtonKey(event.keyCode)
20 else -> getInputGenericButtonKey(event.keyCode)
21 }
22
23 val action = when (event.action) {
24 KeyEvent.ACTION_DOWN -> NativeLibrary.ButtonState.PRESSED
25 KeyEvent.ACTION_UP -> NativeLibrary.ButtonState.RELEASED
26 else -> return false
27 }
28
29 // Ignore invalid buttons
30 if (button < 0) {
31 return false
32 }
33
34 return NativeLibrary.onGamePadButtonEvent(
35 getPlayerNumber(event.device.controllerNumber),
36 button,
37 action
38 )
39 }
40
41 fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
42 val device = event.device
43 // Check every axis input available on the controller
44 for (range in device.motionRanges) {
45 val axis = range.axis;
46 when (device.vendorId) {
47 0x045E -> setGenericAxisInput(event, axis)
48 0x054C -> setGenericAxisInput(event, axis)
49 0x057E -> setJoyconAxisInput(event, axis)
50 0x1532 -> setRazerAxisInput(event, axis)
51 else -> setGenericAxisInput(event, axis)
52 }
53 }
54
55 return true
56 }
57
58 private fun getPlayerNumber(index: Int): Int {
59 // TODO: Joycons are handled as different controllers. Find a way to merge them.
60 return when (index) {
61 2 -> NativeLibrary.Player2Device
62 3 -> NativeLibrary.Player3Device
63 4 -> NativeLibrary.Player4Device
64 5 -> NativeLibrary.Player5Device
65 6 -> NativeLibrary.Player6Device
66 7 -> NativeLibrary.Player7Device
67 8 -> NativeLibrary.Player8Device
68 else -> if (NativeLibrary.isHandheldOnly()) NativeLibrary.ConsoleDevice else NativeLibrary.Player1Device
69 }
70 }
71
72 private fun getAxisToButton(axis: Float): Int {
73 return if (axis > 0.5f) NativeLibrary.ButtonState.PRESSED else NativeLibrary.ButtonState.RELEASED
74 }
75
76 private fun setAxisDpadState(playerNumber: Int, xAxis: Float, yAxis: Float) {
77 NativeLibrary.onGamePadButtonEvent(
78 playerNumber,
79 NativeLibrary.ButtonType.DPAD_UP,
80 getAxisToButton(-yAxis)
81 )
82 NativeLibrary.onGamePadButtonEvent(
83 playerNumber,
84 NativeLibrary.ButtonType.DPAD_DOWN,
85 getAxisToButton(yAxis)
86 )
87 NativeLibrary.onGamePadButtonEvent(
88 playerNumber,
89 NativeLibrary.ButtonType.DPAD_LEFT,
90 getAxisToButton(-xAxis)
91 )
92 NativeLibrary.onGamePadButtonEvent(
93 playerNumber,
94 NativeLibrary.ButtonType.DPAD_RIGHT,
95 getAxisToButton(xAxis)
96 )
97 }
98
99 private fun getInputDS5ButtonKey(key: Int): Int {
100 // The missing ds5 buttons are axis
101 return when (key) {
102 KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
103 KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
104 KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y
105 KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X
106 KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
107 KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
108 KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
109 KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
110 KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
111 KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
112 else -> -1
113 }
114 }
115
116 private fun getInputJoyconButtonKey(key: Int): Int {
117 // Joycon support is half dead. A lot of buttons can't be mapped
118 return when (key) {
119 KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
120 KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
121 KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_X
122 KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_Y
123 KeyEvent.KEYCODE_DPAD_UP -> NativeLibrary.ButtonType.DPAD_UP
124 KeyEvent.KEYCODE_DPAD_DOWN -> NativeLibrary.ButtonType.DPAD_DOWN
125 KeyEvent.KEYCODE_DPAD_LEFT -> NativeLibrary.ButtonType.DPAD_LEFT
126 KeyEvent.KEYCODE_DPAD_RIGHT -> NativeLibrary.ButtonType.DPAD_RIGHT
127 KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
128 KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
129 KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL
130 KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR
131 KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
132 KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
133 KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
134 KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
135 else -> -1
136 }
137 }
138
139 private fun getInputXboxButtonKey(key: Int): Int {
140 // The missing xbox buttons are axis
141 return when (key) {
142 KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_A
143 KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_B
144 KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_X
145 KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_Y
146 KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
147 KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
148 KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
149 KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
150 KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
151 KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
152 else -> -1
153 }
154 }
155
156 private fun getInputRazerButtonKey(key: Int): Int {
157 // The missing xbox buttons are axis
158 return when (key) {
159 KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
160 KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
161 KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y
162 KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X
163 KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
164 KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
165 KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
166 KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
167 KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
168 KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
169 else -> -1
170 }
171 }
172
173 private fun getInputGenericButtonKey(key: Int): Int {
174 return when (key) {
175 KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_A
176 KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_B
177 KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_X
178 KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_Y
179 KeyEvent.KEYCODE_DPAD_UP -> NativeLibrary.ButtonType.DPAD_UP
180 KeyEvent.KEYCODE_DPAD_DOWN -> NativeLibrary.ButtonType.DPAD_DOWN
181 KeyEvent.KEYCODE_DPAD_LEFT -> NativeLibrary.ButtonType.DPAD_LEFT
182 KeyEvent.KEYCODE_DPAD_RIGHT -> NativeLibrary.ButtonType.DPAD_RIGHT
183 KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
184 KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
185 KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL
186 KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR
187 KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
188 KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
189 KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
190 KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
191 else -> -1
192 }
193 }
194
195 private fun setGenericAxisInput(event: MotionEvent, axis: Int) {
196 val playerNumber = getPlayerNumber(event.device.controllerNumber)
197
198 when (axis) {
199 MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
200 NativeLibrary.onGamePadJoystickEvent(
201 playerNumber,
202 NativeLibrary.StickType.STICK_L,
203 event.getAxisValue(MotionEvent.AXIS_X),
204 -event.getAxisValue(MotionEvent.AXIS_Y)
205 )
206 MotionEvent.AXIS_RX, MotionEvent.AXIS_RY ->
207 NativeLibrary.onGamePadJoystickEvent(
208 playerNumber,
209 NativeLibrary.StickType.STICK_R,
210 event.getAxisValue(MotionEvent.AXIS_RX),
211 -event.getAxisValue(MotionEvent.AXIS_RY)
212 )
213 MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ ->
214 NativeLibrary.onGamePadJoystickEvent(
215 playerNumber,
216 NativeLibrary.StickType.STICK_R,
217 event.getAxisValue(MotionEvent.AXIS_Z),
218 -event.getAxisValue(MotionEvent.AXIS_RZ)
219 )
220 MotionEvent.AXIS_LTRIGGER ->
221 NativeLibrary.onGamePadButtonEvent(
222 playerNumber,
223 NativeLibrary.ButtonType.TRIGGER_ZL,
224 getAxisToButton(event.getAxisValue(MotionEvent.AXIS_LTRIGGER))
225 )
226 MotionEvent.AXIS_BRAKE ->
227 NativeLibrary.onGamePadButtonEvent(
228 playerNumber,
229 NativeLibrary.ButtonType.TRIGGER_ZL,
230 getAxisToButton(event.getAxisValue(MotionEvent.AXIS_BRAKE))
231 )
232 MotionEvent.AXIS_RTRIGGER ->
233 NativeLibrary.onGamePadButtonEvent(
234 playerNumber,
235 NativeLibrary.ButtonType.TRIGGER_ZR,
236 getAxisToButton(event.getAxisValue(MotionEvent.AXIS_RTRIGGER))
237 )
238 MotionEvent.AXIS_GAS ->
239 NativeLibrary.onGamePadButtonEvent(
240 playerNumber,
241 NativeLibrary.ButtonType.TRIGGER_ZR,
242 getAxisToButton(event.getAxisValue(MotionEvent.AXIS_GAS))
243 )
244 MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y ->
245 setAxisDpadState(
246 playerNumber,
247 event.getAxisValue(MotionEvent.AXIS_HAT_X),
248 event.getAxisValue(MotionEvent.AXIS_HAT_Y)
249 )
250 }
251 }
252
253
254 private fun setJoyconAxisInput(event: MotionEvent, axis: Int) {
255 // Joycon support is half dead. Right joystick doesn't work
256 val playerNumber = getPlayerNumber(event.device.controllerNumber)
257
258 when (axis) {
259 MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
260 NativeLibrary.onGamePadJoystickEvent(
261 playerNumber,
262 NativeLibrary.StickType.STICK_L,
263 event.getAxisValue(MotionEvent.AXIS_X),
264 -event.getAxisValue(MotionEvent.AXIS_Y)
265 )
266 MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ ->
267 NativeLibrary.onGamePadJoystickEvent(
268 playerNumber,
269 NativeLibrary.StickType.STICK_R,
270 event.getAxisValue(MotionEvent.AXIS_Z),
271 -event.getAxisValue(MotionEvent.AXIS_RZ)
272 )
273 MotionEvent.AXIS_RX, MotionEvent.AXIS_RY ->
274 NativeLibrary.onGamePadJoystickEvent(
275 playerNumber,
276 NativeLibrary.StickType.STICK_R,
277 event.getAxisValue(MotionEvent.AXIS_RX),
278 -event.getAxisValue(MotionEvent.AXIS_RY)
279 )
280 }
281 }
282
283 private fun setRazerAxisInput(event: MotionEvent, axis: Int) {
284 val playerNumber = getPlayerNumber(event.device.controllerNumber)
285
286 when (axis) {
287 MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
288 NativeLibrary.onGamePadJoystickEvent(
289 playerNumber,
290 NativeLibrary.StickType.STICK_L,
291 event.getAxisValue(MotionEvent.AXIS_X),
292 -event.getAxisValue(MotionEvent.AXIS_Y)
293 )
294 MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ ->
295 NativeLibrary.onGamePadJoystickEvent(
296 playerNumber,
297 NativeLibrary.StickType.STICK_R,
298 event.getAxisValue(MotionEvent.AXIS_Z),
299 -event.getAxisValue(MotionEvent.AXIS_RZ)
300 )
301 MotionEvent.AXIS_BRAKE ->
302 NativeLibrary.onGamePadButtonEvent(
303 playerNumber,
304 NativeLibrary.ButtonType.TRIGGER_ZL,
305 getAxisToButton(event.getAxisValue(MotionEvent.AXIS_BRAKE))
306 )
307 MotionEvent.AXIS_GAS ->
308 NativeLibrary.onGamePadButtonEvent(
309 playerNumber,
310 NativeLibrary.ButtonType.TRIGGER_ZR,
311 getAxisToButton(event.getAxisValue(MotionEvent.AXIS_GAS))
312 )
313 MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y ->
314 setAxisDpadState(
315 playerNumber,
316 event.getAxisValue(MotionEvent.AXIS_HAT_X),
317 event.getAxisValue(MotionEvent.AXIS_HAT_Y)
318 )
319 }
320 }
321
322
323} \ No newline at end of file
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index b10c55a45..a7321af2a 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -38,6 +38,8 @@
38#include "core/frontend/applets/software_keyboard.h" 38#include "core/frontend/applets/software_keyboard.h"
39#include "core/frontend/applets/web_browser.h" 39#include "core/frontend/applets/web_browser.h"
40#include "core/hid/hid_core.h" 40#include "core/hid/hid_core.h"
41#include "core/hid/emulated_controller.h"
42#include "core/hid/hid_types.h"
41#include "core/hle/service/acc/profile_manager.h" 43#include "core/hle/service/acc/profile_manager.h"
42#include "core/hle/service/am/applet_ae.h" 44#include "core/hle/service/am/applet_ae.h"
43#include "core/hle/service/am/applet_oe.h" 45#include "core/hle/service/am/applet_oe.h"
@@ -274,6 +276,60 @@ public:
274 m_rom_metadata_cache.clear(); 276 m_rom_metadata_cache.clear();
275 } 277 }
276 278
279 bool IsHandheldOnly(){
280 const auto npad_style_set = m_system.HIDCore().GetSupportedStyleTag();
281
282 if (npad_style_set.fullkey == 1) {
283 return false;
284 }
285
286 if (npad_style_set.handheld == 0) {
287 return false;
288 }
289
290 return !Settings::values.use_docked_mode.GetValue();
291 }
292
293 void SetDeviceType(int index, int type){
294 auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
295 controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type));
296 }
297
298 void OnGamepadConnectEvent(int index){
299 auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
300
301 // Ensure that player1 is configured correctly and handheld disconnected
302 if(controller->GetNpadIdType() == Core::HID::NpadIdType::Player1){
303 auto handheld = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
304
305 if(controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) {
306 handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
307 controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
308 handheld->Disconnect();
309 }
310 }
311
312 // Ensure that handheld is configured correctly and player 1 disconnected
313 if(controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld){
314 auto player1 = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
315
316 if(controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) {
317 player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
318 controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
319 player1->Disconnect();
320 }
321 }
322
323 if(!controller->IsConnected()){
324 controller->Connect();
325 }
326 }
327
328 void OnGamepadDisconnectEvent(int index){
329 auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
330 controller->Disconnect();
331 }
332
277 SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard() { 333 SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard() {
278 return m_software_keyboard; 334 return m_software_keyboard;
279 } 335 }
@@ -440,11 +496,45 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning([[maybe_unused]] JNIEnv
440 return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning()); 496 return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning());
441} 497}
442 498
499jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly([[maybe_unused]] JNIEnv* env,
500 [[maybe_unused]] jclass clazz) {
501 return EmulationSession::GetInstance().IsHandheldOnly();
502}
503
504jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType([[maybe_unused]] JNIEnv* env,
505 [[maybe_unused]] jclass clazz,
506 jint j_device,
507 jint j_type) {
508 if (EmulationSession::GetInstance().IsRunning()) {
509 EmulationSession::GetInstance().SetDeviceType(j_device, j_type);
510 }
511 return static_cast<jboolean>(true);
512}
513
514jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent([[maybe_unused]] JNIEnv* env,
515 [[maybe_unused]] jclass clazz,
516 jint j_device) {
517 if (EmulationSession::GetInstance().IsRunning()) {
518 EmulationSession::GetInstance().OnGamepadConnectEvent(j_device);
519 }
520 return static_cast<jboolean>(true);
521}
522
523jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent([[maybe_unused]] JNIEnv* env,
524 [[maybe_unused]] jclass clazz,
525 jint j_device) {
526 if (EmulationSession::GetInstance().IsRunning()) {
527 EmulationSession::GetInstance().OnGamepadDisconnectEvent(j_device);
528 }
529 return static_cast<jboolean>(true);
530}
443jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent([[maybe_unused]] JNIEnv* env, 531jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent([[maybe_unused]] JNIEnv* env,
444 [[maybe_unused]] jclass clazz, 532 [[maybe_unused]] jclass clazz,
445 [[maybe_unused]] jint j_device, 533 [[maybe_unused]] jint j_device,
446 jint j_button, jint action) { 534 jint j_button, jint action) {
447 if (EmulationSession::GetInstance().IsRunning()) { 535 if (EmulationSession::GetInstance().IsRunning()) {
536 // Ensure gamepad is connected
537 EmulationSession::GetInstance().OnGamepadConnectEvent(j_device);
448 EmulationSession::GetInstance().Window().OnGamepadButtonEvent(j_device, j_button, 538 EmulationSession::GetInstance().Window().OnGamepadButtonEvent(j_device, j_button,
449 action != 0); 539 action != 0);
450 } 540 }
diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h
index 8336e525a..d1e382a33 100644
--- a/src/android/app/src/main/jni/native.h
+++ b/src/android/app/src/main/jni/native.h
@@ -25,6 +25,18 @@ JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_ResetRomMetadata(JN
25JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_IsRunning(JNIEnv* env, 25JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_IsRunning(JNIEnv* env,
26 jclass clazz); 26 jclass clazz);
27 27
28JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(
29 JNIEnv* env, jclass clazz);
30
31JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType(
32 JNIEnv* env, jclass clazz, jstring j_device, jstring j_type);
33
34JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent(
35 JNIEnv* env, jclass clazz, jstring j_device);
36
37JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent(
38 JNIEnv* env, jclass clazz, jstring j_device);
39
28JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadEvent( 40JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadEvent(
29 JNIEnv* env, jclass clazz, jstring j_device, jint j_button, jint action); 41 JNIEnv* env, jclass clazz, jstring j_device, jint j_button, jint action);
30 42