فهرست منبع

android: Implement gamepad input

german77 3 سال پیش
والد
کامیت
6dfe4240ac

+ 42 - 0
src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt

@@ -43,6 +43,21 @@ object NativeLibrary {
     const val Player8Device = 7
     const val Player8Device = 7
     const val ConsoleDevice = 8
     const val ConsoleDevice = 8
 
 
+    /**
+     * Controller type for each device
+     */
+    const val ProController = 3
+    const val Handheld = 4
+    const val JoyconDual = 5
+    const val JoyconLeft = 6
+    const val JoyconRight = 7
+    const val GameCube = 8
+    const val Pokeball = 9
+    const val NES = 10
+    const val SNES = 11
+    const val N64 = 12
+    const val SegaGenesis = 13
+
     @JvmField
     @JvmField
     var sEmulationActivity = WeakReference<EmulationActivity?>(null)
     var sEmulationActivity = WeakReference<EmulationActivity?>(null)
 
 
@@ -70,6 +85,33 @@ object NativeLibrary {
         } else getFileSize(appContext, path)
         } else getFileSize(appContext, path)
     }
     }
 
 
+    /**
+     * Returns true if pro controller isn't available and handheld is
+     */
+    external fun isHandheldOnly(): Boolean
+
+    /**
+     * Changes controller type for a specific device.
+     *
+     * @param Device The input descriptor of the gamepad.
+     * @param Type The NpadStyleIndex of the gamepad.
+     */
+    external fun setDeviceType(Device: Int, Type: Int): Boolean
+
+    /**
+     * Handles event when a gamepad is connected.
+     *
+     * @param Device The input descriptor of the gamepad.
+     */
+    external fun onGamePadConnectEvent(Device: Int): Boolean
+
+    /**
+     * Handles event when a gamepad is disconnected.
+     *
+     * @param Device The input descriptor of the gamepad.
+     */
+    external fun onGamePadDisconnectEvent(Device: Int): Boolean
+
     /**
     /**
      * Handles button press events for a gamepad.
      * Handles button press events for a gamepad.
      *
      *

+ 34 - 4
src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt

@@ -8,9 +8,9 @@ import android.content.DialogInterface
 import android.content.Intent
 import android.content.Intent
 import android.graphics.Rect
 import android.graphics.Rect
 import android.os.Bundle
 import android.os.Bundle
+import android.view.*
 import android.view.KeyEvent
 import android.view.KeyEvent
-import android.view.View
-import android.view.WindowManager
+import android.view.MotionEvent
 import android.view.inputmethod.InputMethodManager
 import android.view.inputmethod.InputMethodManager
 import androidx.appcompat.app.AppCompatActivity
 import androidx.appcompat.app.AppCompatActivity
 import androidx.preference.PreferenceManager
 import androidx.preference.PreferenceManager
@@ -23,6 +23,7 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings
 import org.yuzu.yuzu_emu.fragments.EmulationFragment
 import org.yuzu.yuzu_emu.fragments.EmulationFragment
 import org.yuzu.yuzu_emu.model.Game
 import org.yuzu.yuzu_emu.model.Game
 import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
 import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
+import org.yuzu.yuzu_emu.utils.InputHandler
 import org.yuzu.yuzu_emu.utils.NfcReader
 import org.yuzu.yuzu_emu.utils.NfcReader
 import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
 import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
 import org.yuzu.yuzu_emu.utils.ThemeHelper
 import org.yuzu.yuzu_emu.utils.ThemeHelper
@@ -38,6 +39,7 @@ open class EmulationActivity : AppCompatActivity() {
     private var menuVisible = false
     private var menuVisible = false
     private var emulationFragment: EmulationFragment? = null
     private var emulationFragment: EmulationFragment? = null
     private lateinit var nfcReader: NfcReader
     private lateinit var nfcReader: NfcReader
+    private lateinit var inputHandler: InputHandler
 
 
     private lateinit var game: Game
     private lateinit var game: Game
 
 
@@ -80,6 +82,9 @@ open class EmulationActivity : AppCompatActivity() {
         nfcReader = NfcReader(this)
         nfcReader = NfcReader(this)
         nfcReader.initialize()
         nfcReader.initialize()
 
 
+        inputHandler = InputHandler()
+        inputHandler.initialize()
+
         // Start a foreground service to prevent the app from getting killed in the background
         // Start a foreground service to prevent the app from getting killed in the background
         // TODO(bunnei): Disable notifications until we support app suspension.
         // TODO(bunnei): Disable notifications until we support app suspension.
         //foregroundService = new Intent(EmulationActivity.this, ForegroundService.class);
         //foregroundService = new Intent(EmulationActivity.this, ForegroundService.class);
@@ -108,6 +113,7 @@ open class EmulationActivity : AppCompatActivity() {
         }
         }
         return super.onKeyDown(keyCode, event)
         return super.onKeyDown(keyCode, event)
     }
     }
+
     override fun onResume() {
     override fun onResume() {
         super.onResume()
         super.onResume()
         nfcReader.startScanning()
         nfcReader.startScanning()
@@ -129,6 +135,29 @@ open class EmulationActivity : AppCompatActivity() {
         super.onSaveInstanceState(outState)
         super.onSaveInstanceState(outState)
     }
     }
 
 
+    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
+        // Handling the case where the back button is pressed.
+        if (event.keyCode == KeyEvent.KEYCODE_BACK) {
+            onBackPressedDispatcher.onBackPressed()
+            return true
+        }
+
+        return inputHandler.dispatchKeyEvent(event)
+    }
+
+    override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
+        if (event.source and InputDevice.SOURCE_CLASS_JOYSTICK === 0) {
+            return super.dispatchGenericMotionEvent(event)
+        }
+
+        // Don't attempt to do anything if we are disconnecting a device.
+        if (event.actionMasked == MotionEvent.ACTION_CANCEL) {
+            return true
+        }
+
+        return inputHandler.dispatchGenericMotionEvent(event)
+    }
+
     private fun restoreState(savedInstanceState: Bundle) {
     private fun restoreState(savedInstanceState: Bundle) {
         game = savedInstanceState.parcelable(EXTRA_SELECTED_GAME)!!
         game = savedInstanceState.parcelable(EXTRA_SELECTED_GAME)!!
     }
     }
@@ -159,8 +188,9 @@ open class EmulationActivity : AppCompatActivity() {
     private fun adjustScale() {
     private fun adjustScale() {
         val sliderBinding = DialogSliderBinding.inflate(layoutInflater)
         val sliderBinding = DialogSliderBinding.inflate(layoutInflater)
         sliderBinding.slider.valueTo = 150F
         sliderBinding.slider.valueTo = 150F
-        sliderBinding.slider.value = PreferenceManager.getDefaultSharedPreferences(applicationContext)
-            .getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat()
+        sliderBinding.slider.value =
+            PreferenceManager.getDefaultSharedPreferences(applicationContext)
+                .getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat()
         sliderBinding.slider.addOnChangeListener(OnChangeListener { _, value, _ ->
         sliderBinding.slider.addOnChangeListener(OnChangeListener { _, value, _ ->
             sliderBinding.textValue.text = value.toString()
             sliderBinding.textValue.text = value.toString()
             setControlScale(value.toInt())
             setControlScale(value.toInt())

+ 9 - 7
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
         }
         }
 
 
         var shouldUpdateView = false
         var shouldUpdateView = false
+        val playerIndex =
+            if (NativeLibrary.isHandheldOnly()) NativeLibrary.ConsoleDevice else NativeLibrary.Player1Device
 
 
         for (button in overlayButtons) {
         for (button in overlayButtons) {
             if (!button.updateStatus(event)) {
             if (!button.updateStatus(event)) {
                 continue
                 continue
             }
             }
             NativeLibrary.onGamePadButtonEvent(
             NativeLibrary.onGamePadButtonEvent(
-                NativeLibrary.Player1Device,
+                playerIndex,
                 button.buttonId,
                 button.buttonId,
                 button.status
                 button.status
             )
             )
@@ -131,22 +133,22 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
                 continue
                 continue
             }
             }
             NativeLibrary.onGamePadButtonEvent(
             NativeLibrary.onGamePadButtonEvent(
-                NativeLibrary.Player1Device,
+                playerIndex,
                 dpad.upId,
                 dpad.upId,
                 dpad.upStatus
                 dpad.upStatus
             )
             )
             NativeLibrary.onGamePadButtonEvent(
             NativeLibrary.onGamePadButtonEvent(
-                NativeLibrary.Player1Device,
+                playerIndex,
                 dpad.downId,
                 dpad.downId,
                 dpad.downStatus
                 dpad.downStatus
             )
             )
             NativeLibrary.onGamePadButtonEvent(
             NativeLibrary.onGamePadButtonEvent(
-                NativeLibrary.Player1Device,
+                playerIndex,
                 dpad.leftId,
                 dpad.leftId,
                 dpad.leftStatus
                 dpad.leftStatus
             )
             )
             NativeLibrary.onGamePadButtonEvent(
             NativeLibrary.onGamePadButtonEvent(
-                NativeLibrary.Player1Device,
+                playerIndex,
                 dpad.rightId,
                 dpad.rightId,
                 dpad.rightStatus
                 dpad.rightStatus
             )
             )
@@ -159,13 +161,13 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
             }
             }
             val axisID = joystick.joystickId
             val axisID = joystick.joystickId
             NativeLibrary.onGamePadJoystickEvent(
             NativeLibrary.onGamePadJoystickEvent(
-                NativeLibrary.Player1Device,
+                playerIndex,
                 axisID,
                 axisID,
                 joystick.xAxis,
                 joystick.xAxis,
                 joystick.realYAxis
                 joystick.realYAxis
             )
             )
             NativeLibrary.onGamePadButtonEvent(
             NativeLibrary.onGamePadButtonEvent(
-                NativeLibrary.Player1Device,
+                playerIndex,
                 joystick.buttonId,
                 joystick.buttonId,
                 joystick.buttonStatus
                 joystick.buttonStatus
             )
             )

+ 323 - 0
src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt

@@ -0,0 +1,323 @@
+package org.yuzu.yuzu_emu.utils
+
+import android.view.InputDevice
+import android.view.KeyEvent
+import android.view.MotionEvent
+import org.yuzu.yuzu_emu.NativeLibrary
+
+class InputHandler {
+    fun initialize() {
+        // Connect first controller
+        NativeLibrary.onGamePadConnectEvent(getPlayerNumber(NativeLibrary.Player1Device));
+    }
+
+    fun dispatchKeyEvent(event: KeyEvent): Boolean {
+        val button: Int = when (event.device.vendorId) {
+            0x045E -> getInputXboxButtonKey(event.keyCode)
+            0x054C -> getInputDS5ButtonKey(event.keyCode)
+            0x057E -> getInputJoyconButtonKey(event.keyCode)
+            0x1532 -> getInputRazerButtonKey(event.keyCode)
+            else -> getInputGenericButtonKey(event.keyCode)
+        }
+
+        val action = when (event.action) {
+            KeyEvent.ACTION_DOWN -> NativeLibrary.ButtonState.PRESSED
+            KeyEvent.ACTION_UP -> NativeLibrary.ButtonState.RELEASED
+            else -> return false
+        }
+
+        // Ignore invalid buttons
+        if (button < 0) {
+            return false
+        }
+
+        return NativeLibrary.onGamePadButtonEvent(
+            getPlayerNumber(event.device.controllerNumber),
+            button,
+            action
+        )
+    }
+
+    fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
+        val device = event.device
+        // Check every axis input available on the controller
+        for (range in device.motionRanges) {
+            val axis = range.axis;
+            when (device.vendorId) {
+                0x045E -> setGenericAxisInput(event, axis)
+                0x054C -> setGenericAxisInput(event, axis)
+                0x057E -> setJoyconAxisInput(event, axis)
+                0x1532 -> setRazerAxisInput(event, axis)
+                else -> setGenericAxisInput(event, axis)
+            }
+        }
+
+        return true
+    }
+
+    private fun getPlayerNumber(index: Int): Int {
+        // TODO: Joycons are handled as different controllers. Find a way to merge them.
+        return when (index) {
+            2 -> NativeLibrary.Player2Device
+            3 -> NativeLibrary.Player3Device
+            4 -> NativeLibrary.Player4Device
+            5 -> NativeLibrary.Player5Device
+            6 -> NativeLibrary.Player6Device
+            7 -> NativeLibrary.Player7Device
+            8 -> NativeLibrary.Player8Device
+            else -> if (NativeLibrary.isHandheldOnly()) NativeLibrary.ConsoleDevice else NativeLibrary.Player1Device
+        }
+    }
+
+    private fun getAxisToButton(axis: Float): Int {
+        return if (axis > 0.5f) NativeLibrary.ButtonState.PRESSED else NativeLibrary.ButtonState.RELEASED
+    }
+
+    private fun setAxisDpadState(playerNumber: Int, xAxis: Float, yAxis: Float) {
+        NativeLibrary.onGamePadButtonEvent(
+            playerNumber,
+            NativeLibrary.ButtonType.DPAD_UP,
+            getAxisToButton(-yAxis)
+        )
+        NativeLibrary.onGamePadButtonEvent(
+            playerNumber,
+            NativeLibrary.ButtonType.DPAD_DOWN,
+            getAxisToButton(yAxis)
+        )
+        NativeLibrary.onGamePadButtonEvent(
+            playerNumber,
+            NativeLibrary.ButtonType.DPAD_LEFT,
+            getAxisToButton(-xAxis)
+        )
+        NativeLibrary.onGamePadButtonEvent(
+            playerNumber,
+            NativeLibrary.ButtonType.DPAD_RIGHT,
+            getAxisToButton(xAxis)
+        )
+    }
+
+    private fun getInputDS5ButtonKey(key: Int): Int {
+        // The missing ds5 buttons are axis
+        return when (key) {
+            KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
+            KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
+            KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y
+            KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X
+            KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
+            KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
+            KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
+            KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
+            KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
+            KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
+            else -> -1
+        }
+    }
+
+    private fun getInputJoyconButtonKey(key: Int): Int {
+        // Joycon support is half dead. A lot of buttons can't be mapped
+        return when (key) {
+            KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
+            KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
+            KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_X
+            KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_Y
+            KeyEvent.KEYCODE_DPAD_UP -> NativeLibrary.ButtonType.DPAD_UP
+            KeyEvent.KEYCODE_DPAD_DOWN -> NativeLibrary.ButtonType.DPAD_DOWN
+            KeyEvent.KEYCODE_DPAD_LEFT -> NativeLibrary.ButtonType.DPAD_LEFT
+            KeyEvent.KEYCODE_DPAD_RIGHT -> NativeLibrary.ButtonType.DPAD_RIGHT
+            KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
+            KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
+            KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL
+            KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR
+            KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
+            KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
+            KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
+            KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
+            else -> -1
+        }
+    }
+
+    private fun getInputXboxButtonKey(key: Int): Int {
+        // The missing xbox buttons are axis
+        return when (key) {
+            KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_A
+            KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_B
+            KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_X
+            KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_Y
+            KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
+            KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
+            KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
+            KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
+            KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
+            KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
+            else -> -1
+        }
+    }
+
+    private fun getInputRazerButtonKey(key: Int): Int {
+        // The missing xbox buttons are axis
+        return when (key) {
+            KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
+            KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
+            KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y
+            KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X
+            KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
+            KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
+            KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
+            KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
+            KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
+            KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
+            else -> -1
+        }
+    }
+
+    private fun getInputGenericButtonKey(key: Int): Int {
+        return when (key) {
+            KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_A
+            KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_B
+            KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_X
+            KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_Y
+            KeyEvent.KEYCODE_DPAD_UP -> NativeLibrary.ButtonType.DPAD_UP
+            KeyEvent.KEYCODE_DPAD_DOWN -> NativeLibrary.ButtonType.DPAD_DOWN
+            KeyEvent.KEYCODE_DPAD_LEFT -> NativeLibrary.ButtonType.DPAD_LEFT
+            KeyEvent.KEYCODE_DPAD_RIGHT -> NativeLibrary.ButtonType.DPAD_RIGHT
+            KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
+            KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
+            KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL
+            KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR
+            KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
+            KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
+            KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
+            KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
+            else -> -1
+        }
+    }
+
+    private fun setGenericAxisInput(event: MotionEvent, axis: Int) {
+        val playerNumber = getPlayerNumber(event.device.controllerNumber)
+
+        when (axis) {
+            MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
+                NativeLibrary.onGamePadJoystickEvent(
+                    playerNumber,
+                    NativeLibrary.StickType.STICK_L,
+                    event.getAxisValue(MotionEvent.AXIS_X),
+                    -event.getAxisValue(MotionEvent.AXIS_Y)
+                )
+            MotionEvent.AXIS_RX, MotionEvent.AXIS_RY ->
+                NativeLibrary.onGamePadJoystickEvent(
+                    playerNumber,
+                    NativeLibrary.StickType.STICK_R,
+                    event.getAxisValue(MotionEvent.AXIS_RX),
+                    -event.getAxisValue(MotionEvent.AXIS_RY)
+                )
+            MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ ->
+                NativeLibrary.onGamePadJoystickEvent(
+                    playerNumber,
+                    NativeLibrary.StickType.STICK_R,
+                    event.getAxisValue(MotionEvent.AXIS_Z),
+                    -event.getAxisValue(MotionEvent.AXIS_RZ)
+                )
+            MotionEvent.AXIS_LTRIGGER ->
+                NativeLibrary.onGamePadButtonEvent(
+                    playerNumber,
+                    NativeLibrary.ButtonType.TRIGGER_ZL,
+                    getAxisToButton(event.getAxisValue(MotionEvent.AXIS_LTRIGGER))
+                )
+            MotionEvent.AXIS_BRAKE ->
+                NativeLibrary.onGamePadButtonEvent(
+                    playerNumber,
+                    NativeLibrary.ButtonType.TRIGGER_ZL,
+                    getAxisToButton(event.getAxisValue(MotionEvent.AXIS_BRAKE))
+                )
+            MotionEvent.AXIS_RTRIGGER ->
+                NativeLibrary.onGamePadButtonEvent(
+                    playerNumber,
+                    NativeLibrary.ButtonType.TRIGGER_ZR,
+                    getAxisToButton(event.getAxisValue(MotionEvent.AXIS_RTRIGGER))
+                )
+            MotionEvent.AXIS_GAS ->
+                NativeLibrary.onGamePadButtonEvent(
+                    playerNumber,
+                    NativeLibrary.ButtonType.TRIGGER_ZR,
+                    getAxisToButton(event.getAxisValue(MotionEvent.AXIS_GAS))
+                )
+            MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y ->
+                setAxisDpadState(
+                    playerNumber,
+                    event.getAxisValue(MotionEvent.AXIS_HAT_X),
+                    event.getAxisValue(MotionEvent.AXIS_HAT_Y)
+                )
+        }
+    }
+
+
+    private fun setJoyconAxisInput(event: MotionEvent, axis: Int) {
+        // Joycon support is half dead. Right joystick doesn't work
+        val playerNumber = getPlayerNumber(event.device.controllerNumber)
+
+        when (axis) {
+            MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
+                NativeLibrary.onGamePadJoystickEvent(
+                    playerNumber,
+                    NativeLibrary.StickType.STICK_L,
+                    event.getAxisValue(MotionEvent.AXIS_X),
+                    -event.getAxisValue(MotionEvent.AXIS_Y)
+                )
+            MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ ->
+                NativeLibrary.onGamePadJoystickEvent(
+                    playerNumber,
+                    NativeLibrary.StickType.STICK_R,
+                    event.getAxisValue(MotionEvent.AXIS_Z),
+                    -event.getAxisValue(MotionEvent.AXIS_RZ)
+                )
+            MotionEvent.AXIS_RX, MotionEvent.AXIS_RY ->
+                NativeLibrary.onGamePadJoystickEvent(
+                    playerNumber,
+                    NativeLibrary.StickType.STICK_R,
+                    event.getAxisValue(MotionEvent.AXIS_RX),
+                    -event.getAxisValue(MotionEvent.AXIS_RY)
+                )
+        }
+    }
+
+    private fun setRazerAxisInput(event: MotionEvent, axis: Int) {
+        val playerNumber = getPlayerNumber(event.device.controllerNumber)
+
+        when (axis) {
+            MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
+                NativeLibrary.onGamePadJoystickEvent(
+                    playerNumber,
+                    NativeLibrary.StickType.STICK_L,
+                    event.getAxisValue(MotionEvent.AXIS_X),
+                    -event.getAxisValue(MotionEvent.AXIS_Y)
+                )
+            MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ ->
+                NativeLibrary.onGamePadJoystickEvent(
+                    playerNumber,
+                    NativeLibrary.StickType.STICK_R,
+                    event.getAxisValue(MotionEvent.AXIS_Z),
+                    -event.getAxisValue(MotionEvent.AXIS_RZ)
+                )
+            MotionEvent.AXIS_BRAKE ->
+                NativeLibrary.onGamePadButtonEvent(
+                    playerNumber,
+                    NativeLibrary.ButtonType.TRIGGER_ZL,
+                    getAxisToButton(event.getAxisValue(MotionEvent.AXIS_BRAKE))
+                )
+            MotionEvent.AXIS_GAS ->
+                NativeLibrary.onGamePadButtonEvent(
+                    playerNumber,
+                    NativeLibrary.ButtonType.TRIGGER_ZR,
+                    getAxisToButton(event.getAxisValue(MotionEvent.AXIS_GAS))
+                )
+            MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y ->
+                setAxisDpadState(
+                    playerNumber,
+                    event.getAxisValue(MotionEvent.AXIS_HAT_X),
+                    event.getAxisValue(MotionEvent.AXIS_HAT_Y)
+                )
+        }
+    }
+
+
+}

+ 90 - 0
src/android/app/src/main/jni/native.cpp

@@ -38,6 +38,8 @@
 #include "core/frontend/applets/software_keyboard.h"
 #include "core/frontend/applets/software_keyboard.h"
 #include "core/frontend/applets/web_browser.h"
 #include "core/frontend/applets/web_browser.h"
 #include "core/hid/hid_core.h"
 #include "core/hid/hid_core.h"
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_types.h"
 #include "core/hle/service/acc/profile_manager.h"
 #include "core/hle/service/acc/profile_manager.h"
 #include "core/hle/service/am/applet_ae.h"
 #include "core/hle/service/am/applet_ae.h"
 #include "core/hle/service/am/applet_oe.h"
 #include "core/hle/service/am/applet_oe.h"
@@ -274,6 +276,60 @@ public:
         m_rom_metadata_cache.clear();
         m_rom_metadata_cache.clear();
     }
     }
 
 
+    bool IsHandheldOnly(){
+        const auto npad_style_set = m_system.HIDCore().GetSupportedStyleTag();
+
+        if (npad_style_set.fullkey == 1) {
+            return false;
+        }
+
+        if (npad_style_set.handheld == 0) {
+            return false;
+        }
+
+        return !Settings::values.use_docked_mode.GetValue();
+    }
+
+    void SetDeviceType(int index, int type){
+        auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
+        controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type));
+    }
+
+    void OnGamepadConnectEvent(int index){
+        auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
+
+        // Ensure that player1 is configured correctly and handheld disconnected
+        if(controller->GetNpadIdType() == Core::HID::NpadIdType::Player1){
+           auto handheld = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
+
+           if(controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) {
+               handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
+               controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
+               handheld->Disconnect();
+           }
+        }
+
+        // Ensure that handheld is configured correctly and player 1 disconnected
+        if(controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld){
+            auto player1 = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
+
+            if(controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) {
+                player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
+                controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
+                player1->Disconnect();
+            }
+        }
+
+        if(!controller->IsConnected()){
+            controller->Connect();
+        }
+    }
+
+    void OnGamepadDisconnectEvent(int index){
+        auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
+        controller->Disconnect();
+    }
+
     SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard() {
     SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard() {
         return m_software_keyboard;
         return m_software_keyboard;
     }
     }
@@ -440,11 +496,45 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning([[maybe_unused]] JNIEnv
     return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning());
     return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning());
 }
 }
 
 
+jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly([[maybe_unused]] JNIEnv* env,
+                                                             [[maybe_unused]] jclass clazz) {
+    return EmulationSession::GetInstance().IsHandheldOnly();
+}
+
+jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType([[maybe_unused]] JNIEnv* env,
+                                                             [[maybe_unused]] jclass clazz,
+                                                             jint j_device,
+                                                             jint j_type) {
+    if (EmulationSession::GetInstance().IsRunning()) {
+        EmulationSession::GetInstance().SetDeviceType(j_device, j_type);
+    }
+    return static_cast<jboolean>(true);
+}
+
+jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent([[maybe_unused]] JNIEnv* env,
+                                                                    [[maybe_unused]] jclass clazz,
+                                                                    jint j_device) {
+    if (EmulationSession::GetInstance().IsRunning()) {
+        EmulationSession::GetInstance().OnGamepadConnectEvent(j_device);
+    }
+    return static_cast<jboolean>(true);
+}
+
+jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent([[maybe_unused]] JNIEnv* env,
+                                                                    [[maybe_unused]] jclass clazz,
+                                                                    jint j_device) {
+    if (EmulationSession::GetInstance().IsRunning()) {
+        EmulationSession::GetInstance().OnGamepadDisconnectEvent(j_device);
+    }
+    return static_cast<jboolean>(true);
+}
 jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent([[maybe_unused]] JNIEnv* env,
 jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent([[maybe_unused]] JNIEnv* env,
                                                                     [[maybe_unused]] jclass clazz,
                                                                     [[maybe_unused]] jclass clazz,
                                                                     [[maybe_unused]] jint j_device,
                                                                     [[maybe_unused]] jint j_device,
                                                                     jint j_button, jint action) {
                                                                     jint j_button, jint action) {
     if (EmulationSession::GetInstance().IsRunning()) {
     if (EmulationSession::GetInstance().IsRunning()) {
+        // Ensure gamepad is connected
+        EmulationSession::GetInstance().OnGamepadConnectEvent(j_device);
         EmulationSession::GetInstance().Window().OnGamepadButtonEvent(j_device, j_button,
         EmulationSession::GetInstance().Window().OnGamepadButtonEvent(j_device, j_button,
                                                                       action != 0);
                                                                       action != 0);
     }
     }

+ 12 - 0
src/android/app/src/main/jni/native.h

@@ -25,6 +25,18 @@ JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_ResetRomMetadata(JN
 JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_IsRunning(JNIEnv* env,
 JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_IsRunning(JNIEnv* env,
                                                                            jclass clazz);
                                                                            jclass clazz);
 
 
+JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(
+        JNIEnv* env, jclass clazz);
+
+JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType(
+        JNIEnv* env, jclass clazz, jstring j_device, jstring j_type);
+
+JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent(
+        JNIEnv* env, jclass clazz, jstring j_device);
+
+JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent(
+        JNIEnv* env, jclass clazz, jstring j_device);
+
 JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadEvent(
 JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadEvent(
     JNIEnv* env, jclass clazz, jstring j_device, jint j_button, jint action);
     JNIEnv* env, jclass clazz, jstring j_device, jint j_button, jint action);