add physical controller support

This commit is contained in:
Emmanuel Hansen 2023-07-15 20:13:35 +00:00
parent 4f1bf2d0cc
commit 61ba5e7bff
7 changed files with 147 additions and 9 deletions

View File

@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.GraphicsLayerScope import androidx.compose.ui.graphics.GraphicsLayerScope
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.flowWithLifecycle
@ -45,6 +46,7 @@ typealias GamePad = RadialGamePad
typealias GamePadConfig = RadialGamePadConfig typealias GamePadConfig = RadialGamePadConfig
class GameController(var activity: Activity, var ryujinxNative: RyujinxNative = RyujinxNative()) { class GameController(var activity: Activity, var ryujinxNative: RyujinxNative = RyujinxNative()) {
private var controllerView: View? = null
var leftGamePad: GamePad var leftGamePad: GamePad
var rightGamePad: GamePad var rightGamePad: GamePad
var controllerId: Int = -1 var controllerId: Int = -1
@ -65,7 +67,6 @@ class GameController(var activity: Activity, var ryujinxNative: RyujinxNative =
@Composable @Composable
fun Compose(lifecycleScope: LifecycleCoroutineScope, lifecycle:Lifecycle) : Unit fun Compose(lifecycleScope: LifecycleCoroutineScope, lifecycle:Lifecycle) : Unit
{ {
AndroidView( AndroidView(
modifier = Modifier.fillMaxSize(), factory = { context -> Create(context)}) modifier = Modifier.fillMaxSize(), factory = { context -> Create(context)})
@ -81,14 +82,22 @@ class GameController(var activity: Activity, var ryujinxNative: RyujinxNative =
} }
} }
private fun Create(context: Context) : View private fun Create(context: Context) : View
{ {
var inflator = LayoutInflater.from(context); var inflator = LayoutInflater.from(context);
var view = inflator.inflate(R.layout.game_layout, null) var view = inflator.inflate(R.layout.game_layout, null)
view.findViewById<FrameLayout>(R.id.leftcontainer)!!.addView(leftGamePad); view.findViewById<FrameLayout>(R.id.leftcontainer)!!.addView(leftGamePad);
view.findViewById<FrameLayout>(R.id.rightcontainer)!!.addView(rightGamePad); view.findViewById<FrameLayout>(R.id.rightcontainer)!!.addView(rightGamePad);
return view as View controllerView = view
return controllerView as View
}
fun setVisible(isVisible: Boolean){
controllerView?.apply {
this.isVisible = isVisible
}
} }
fun connect(){ fun connect(){

View File

@ -132,7 +132,16 @@ class GameHost(context: Context?, val controller: GameController, val mainViewMo
_nativeRyujinx.inputInitialize(width, height) _nativeRyujinx.inputInitialize(width, height)
controller.connect() if(!settings.useVirtualController){
controller.setVisible(false)
}
else{
controller.connect()
}
mainViewModel.activity.physicalControllerManager.connect()
//
_nativeRyujinx.graphicsRendererSetSize( _nativeRyujinx.graphicsRendererSetSize(
surfaceHolder.surfaceFrame.width(), surfaceHolder.surfaceFrame.width(),

View File

@ -1,5 +1,6 @@
package org.ryujinx.android package org.ryujinx.android
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo
@ -8,6 +9,8 @@ import android.media.AudioManager
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Environment import android.os.Environment
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.WindowManager import android.view.WindowManager
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
@ -29,6 +32,7 @@ import org.ryujinx.android.views.MainView
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
var physicalControllerManager: PhysicalControllerManager
private var mainViewModel: MainViewModel? = null private var mainViewModel: MainViewModel? = null
private var _isInit: Boolean = false private var _isInit: Boolean = false
var storageHelper: SimpleStorageHelper? = null var storageHelper: SimpleStorageHelper? = null
@ -41,6 +45,7 @@ class MainActivity : ComponentActivity() {
} }
init { init {
physicalControllerManager = PhysicalControllerManager(this)
storageHelper = SimpleStorageHelper(this) storageHelper = SimpleStorageHelper(this)
StorageHelper = storageHelper StorageHelper = storageHelper
} }
@ -79,6 +84,21 @@ class MainActivity : ComponentActivity() {
} }
} }
@SuppressLint("RestrictedApi")
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
event?.apply {
return physicalControllerManager.onKeyEvent(this)
}
return super.dispatchKeyEvent(event)
}
override fun dispatchGenericMotionEvent(ev: MotionEvent?): Boolean {
ev?.apply {
physicalControllerManager.onMotionEvent(this)
}
return super.dispatchGenericMotionEvent(ev)
}
private fun initialize() : Unit private fun initialize() : Unit
{ {
if(_isInit) if(_isInit)

View File

@ -0,0 +1,69 @@
package org.ryujinx.android
import android.view.KeyEvent
import android.view.MotionEvent
class PhysicalControllerManager(val activity: MainActivity) {
private var controllerId: Int = -1
private var ryujinxNative: RyujinxNative = RyujinxNative()
fun onKeyEvent(event: KeyEvent) : Boolean{
if(controllerId != -1) {
var id = GetGamePadButtonInputId(event.keyCode)
if(id != GamePadButtonInputId.None) {
when (event.action) {
KeyEvent.ACTION_UP -> {
ryujinxNative.inputSetButtonReleased(id.ordinal, controllerId)
}
KeyEvent.ACTION_DOWN -> {
ryujinxNative.inputSetButtonPressed(id.ordinal, controllerId)
}
}
return true;
}
}
return false
}
fun onMotionEvent(ev: MotionEvent) {
if(controllerId != -1) {
if(ev.action == MotionEvent.ACTION_MOVE) {
var leftStickX = ev.getAxisValue(MotionEvent.AXIS_X);
var leftStickY = ev.getAxisValue(MotionEvent.AXIS_Y);
var rightStickX = ev.getAxisValue(MotionEvent.AXIS_Z);
var rightStickY = ev.getAxisValue(MotionEvent.AXIS_RZ);
ryujinxNative.inputSetStickAxis(1, leftStickX, -leftStickY ,controllerId)
ryujinxNative.inputSetStickAxis(2, rightStickX, -rightStickY ,controllerId)
}
}
}
fun connect(){
controllerId = ryujinxNative.inputConnectGamepad(0)
}
fun GetGamePadButtonInputId(keycode: Int): GamePadButtonInputId {
return when (keycode) {
KeyEvent.KEYCODE_BUTTON_A -> GamePadButtonInputId.B
KeyEvent.KEYCODE_BUTTON_B -> GamePadButtonInputId.A
KeyEvent.KEYCODE_BUTTON_X -> GamePadButtonInputId.X
KeyEvent.KEYCODE_BUTTON_Y -> GamePadButtonInputId.Y
KeyEvent.KEYCODE_BUTTON_L1 -> GamePadButtonInputId.LeftShoulder
KeyEvent.KEYCODE_BUTTON_L2 -> GamePadButtonInputId.LeftTrigger
KeyEvent.KEYCODE_BUTTON_R1 -> GamePadButtonInputId.RightShoulder
KeyEvent.KEYCODE_BUTTON_R2 -> GamePadButtonInputId.RightTrigger
KeyEvent.KEYCODE_BUTTON_THUMBL -> GamePadButtonInputId.LeftStick
KeyEvent.KEYCODE_BUTTON_THUMBR -> GamePadButtonInputId.RightStick
KeyEvent.KEYCODE_DPAD_UP -> GamePadButtonInputId.DpadUp
KeyEvent.KEYCODE_DPAD_DOWN -> GamePadButtonInputId.DpadDown
KeyEvent.KEYCODE_DPAD_LEFT -> GamePadButtonInputId.DpadLeft
KeyEvent.KEYCODE_DPAD_RIGHT -> GamePadButtonInputId.DpadRight
KeyEvent.KEYCODE_BUTTON_START -> GamePadButtonInputId.Plus
KeyEvent.KEYCODE_BUTTON_SELECT -> GamePadButtonInputId.Minus
else -> GamePadButtonInputId.None
}
}
}

View File

@ -10,6 +10,7 @@ class QuickSettings(val activity: MainActivity) {
var enableDocked: Boolean var enableDocked: Boolean
var enableVsync: Boolean var enableVsync: Boolean
var useNce: Boolean var useNce: Boolean
var useVirtualController: Boolean
var isHostMapped: Boolean var isHostMapped: Boolean
var enableShaderCache: Boolean var enableShaderCache: Boolean
var enableTextureRecompression: Boolean var enableTextureRecompression: Boolean
@ -27,5 +28,6 @@ class QuickSettings(val activity: MainActivity) {
enableShaderCache = sharedPref.getBoolean("enableShaderCache", true) enableShaderCache = sharedPref.getBoolean("enableShaderCache", true)
enableTextureRecompression = sharedPref.getBoolean("enableTextureRecompression", false) enableTextureRecompression = sharedPref.getBoolean("enableTextureRecompression", false)
resScale = sharedPref.getFloat("resScale", 1f) resScale = sharedPref.getFloat("resScale", 1f)
useVirtualController = sharedPref.getBoolean("useVirtualController", true)
} }
} }

View File

@ -26,7 +26,8 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
ignoreMissingServices: MutableState<Boolean>, ignoreMissingServices: MutableState<Boolean>,
enableShaderCache: MutableState<Boolean>, enableShaderCache: MutableState<Boolean>,
enableTextureRecompression: MutableState<Boolean>, enableTextureRecompression: MutableState<Boolean>,
resScale: MutableState<Float> resScale: MutableState<Float>,
useVirtualController: MutableState<Boolean>
) )
{ {
@ -39,6 +40,7 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
enableShaderCache.value = sharedPref.getBoolean("enableShaderCache", true) enableShaderCache.value = sharedPref.getBoolean("enableShaderCache", true)
enableTextureRecompression.value = sharedPref.getBoolean("enableTextureRecompression", false) enableTextureRecompression.value = sharedPref.getBoolean("enableTextureRecompression", false)
resScale.value = sharedPref.getFloat("resScale", 1f) resScale.value = sharedPref.getFloat("resScale", 1f)
useVirtualController.value = sharedPref.getBoolean("useVirtualController", true)
} }
fun save( fun save(
@ -50,7 +52,8 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
ignoreMissingServices: MutableState<Boolean>, ignoreMissingServices: MutableState<Boolean>,
enableShaderCache: MutableState<Boolean>, enableShaderCache: MutableState<Boolean>,
enableTextureRecompression: MutableState<Boolean>, enableTextureRecompression: MutableState<Boolean>,
resScale: MutableState<Float> resScale: MutableState<Float>,
useVirtualController: MutableState<Boolean>
){ ){
var editor = sharedPref.edit() var editor = sharedPref.edit()
@ -63,6 +66,7 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
editor.putBoolean("enableShaderCache", enableShaderCache?.value ?: true) editor.putBoolean("enableShaderCache", enableShaderCache?.value ?: true)
editor.putBoolean("enableTextureRecompression", enableTextureRecompression?.value ?: false) editor.putBoolean("enableTextureRecompression", enableTextureRecompression?.value ?: false)
editor.putFloat("resScale", resScale?.value ?: 1f) editor.putFloat("resScale", resScale?.value ?: 1f)
editor.putBoolean("useVirtualController", useVirtualController?.value ?: true)
editor.apply() editor.apply()
} }

View File

@ -92,6 +92,9 @@ class SettingViews {
var resScale = remember { var resScale = remember {
mutableStateOf(1f) mutableStateOf(1f)
} }
var useVirtualController = remember {
mutableStateOf(true)
}
if (!loaded.value) { if (!loaded.value) {
settingsViewModel.initializeState( settingsViewModel.initializeState(
@ -100,7 +103,8 @@ class SettingViews {
enableVsync, enableDocked, enablePtc, ignoreMissingServices, enableVsync, enableDocked, enablePtc, ignoreMissingServices,
enableShaderCache, enableShaderCache,
enableTextureRecompression, enableTextureRecompression,
resScale resScale,
useVirtualController
) )
loaded.value = true loaded.value = true
} }
@ -121,7 +125,8 @@ class SettingViews {
ignoreMissingServices, ignoreMissingServices,
enableShaderCache, enableShaderCache,
enableTextureRecompression, enableTextureRecompression,
resScale resScale,
useVirtualController
) )
settingsViewModel.navController.popBackStack() settingsViewModel.navController.popBackStack()
}) { }) {
@ -136,7 +141,8 @@ class SettingViews {
useNce, enableVsync, enableDocked, enablePtc, ignoreMissingServices, useNce, enableVsync, enableDocked, enablePtc, ignoreMissingServices,
enableShaderCache, enableShaderCache,
enableTextureRecompression, enableTextureRecompression,
resScale resScale,
useVirtualController
) )
} }
ExpandableView(onCardArrowClick = { }, title = "System") { ExpandableView(onCardArrowClick = { }, title = "System") {
@ -431,6 +437,25 @@ class SettingViews {
*/ */
} }
} }
ExpandableView(onCardArrowClick = { }, title = "Input") {
Column(modifier = Modifier.fillMaxWidth()) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Show virtual controller",
modifier = Modifier.align(Alignment.CenterVertically)
)
Switch(checked = useVirtualController.value, onCheckedChange = {
useVirtualController.value = !useVirtualController.value
})
}
}
}
} }
} }
} }