forked from MeloNX/MeloNX
add physical controller support
This commit is contained in:
parent
4f1bf2d0cc
commit
61ba5e7bff
@ -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(){
|
||||||
|
@ -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(),
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user