2020-03-27 20:36:02 +01:00
|
|
|
/*
|
2020-04-19 23:04:05 +02:00
|
|
|
* SPDX-License-Identifier: MPL-2.0
|
2020-03-27 20:36:02 +01:00
|
|
|
* Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
|
|
|
*/
|
|
|
|
|
2019-12-02 14:39:08 +01:00
|
|
|
package emu.skyline
|
|
|
|
|
2020-04-22 19:02:27 +02:00
|
|
|
import android.annotation.SuppressLint
|
2020-09-05 01:06:07 +02:00
|
|
|
import android.content.Context
|
2020-02-11 07:34:22 +01:00
|
|
|
import android.content.Intent
|
2021-03-03 21:35:24 +01:00
|
|
|
import android.content.res.AssetManager
|
2020-10-03 12:09:35 +02:00
|
|
|
import android.graphics.PointF
|
2020-09-05 01:06:07 +02:00
|
|
|
import android.os.*
|
2019-12-02 14:39:08 +01:00
|
|
|
import android.util.Log
|
2022-01-12 16:24:29 +01:00
|
|
|
import android.util.Rational
|
2020-07-06 22:47:23 +02:00
|
|
|
import android.view.*
|
2019-12-02 14:39:08 +01:00
|
|
|
import androidx.appcompat.app.AppCompatActivity
|
2020-10-05 12:56:38 +02:00
|
|
|
import androidx.core.view.isGone
|
2020-10-03 12:09:35 +02:00
|
|
|
import androidx.core.view.isInvisible
|
2021-01-31 22:11:26 +01:00
|
|
|
import dagger.hilt.android.AndroidEntryPoint
|
|
|
|
import emu.skyline.databinding.EmuActivityBinding
|
2020-08-15 15:51:23 +02:00
|
|
|
import emu.skyline.input.*
|
2020-04-03 13:47:32 +02:00
|
|
|
import emu.skyline.loader.getRomFormat
|
2020-10-04 22:29:50 +02:00
|
|
|
import emu.skyline.utils.Settings
|
2019-12-02 14:39:08 +01:00
|
|
|
import java.io.File
|
2021-01-31 22:11:26 +01:00
|
|
|
import javax.inject.Inject
|
2020-04-26 16:32:24 +02:00
|
|
|
import kotlin.math.abs
|
2019-12-02 14:39:08 +01:00
|
|
|
|
2021-01-31 22:11:26 +01:00
|
|
|
@AndroidEntryPoint
|
2020-09-07 18:39:05 +02:00
|
|
|
class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTouchListener {
|
2020-09-14 15:53:40 +02:00
|
|
|
companion object {
|
2020-10-03 12:09:35 +02:00
|
|
|
private val Tag = EmulationActivity::class.java.simpleName
|
2021-10-13 19:08:48 +02:00
|
|
|
val ReturnToMainTag = "returnToMain"
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The Kotlin thread on which emulation code executes
|
|
|
|
*/
|
|
|
|
private var emulationThread : Thread? = null
|
2020-09-14 15:53:40 +02:00
|
|
|
}
|
|
|
|
|
2021-01-31 22:11:26 +01:00
|
|
|
private val binding by lazy { EmuActivityBinding.inflate(layoutInflater) }
|
|
|
|
|
2020-09-05 01:06:07 +02:00
|
|
|
/**
|
2021-02-07 20:42:03 +01:00
|
|
|
* A map of [Vibrator]s that correspond to [InputManager.controllers]
|
2020-09-05 01:06:07 +02:00
|
|
|
*/
|
|
|
|
private var vibrators = HashMap<Int, Vibrator>()
|
|
|
|
|
2020-04-03 13:47:32 +02:00
|
|
|
/**
|
2021-10-13 19:08:48 +02:00
|
|
|
* If the emulation thread should call [returnToMain] or not
|
2020-04-03 13:47:32 +02:00
|
|
|
*/
|
2020-08-15 15:51:23 +02:00
|
|
|
@Volatile
|
2020-04-24 13:39:13 +02:00
|
|
|
private var shouldFinish : Boolean = true
|
2019-12-02 14:39:08 +01:00
|
|
|
|
2020-04-03 13:47:32 +02:00
|
|
|
/**
|
2021-10-29 17:50:04 +02:00
|
|
|
* If the activity should return to [MainActivity] or just call [finishAffinity]
|
2020-04-03 13:47:32 +02:00
|
|
|
*/
|
2021-10-13 19:08:48 +02:00
|
|
|
var returnToMain : Boolean = false
|
2020-04-03 13:47:32 +02:00
|
|
|
|
2021-02-07 20:42:03 +01:00
|
|
|
@Inject
|
|
|
|
lateinit var settings : Settings
|
2020-10-04 22:29:50 +02:00
|
|
|
|
2021-01-31 22:11:26 +01:00
|
|
|
@Inject
|
|
|
|
lateinit var inputManager : InputManager
|
|
|
|
|
2020-04-03 13:47:32 +02:00
|
|
|
/**
|
|
|
|
* This is the entry point into the emulation code for libskyline
|
|
|
|
*
|
|
|
|
* @param romUri The URI of the ROM as a string, used to print out in the logs
|
|
|
|
* @param romType The type of the ROM as an enum value
|
|
|
|
* @param romFd The file descriptor of the ROM object
|
|
|
|
* @param preferenceFd The file descriptor of the Preference XML
|
2020-08-08 21:38:51 +02:00
|
|
|
* @param appFilesPath The full path to the app files directory
|
2021-03-03 21:35:24 +01:00
|
|
|
* @param assetManager The asset manager used for accessing app assets
|
2020-04-03 13:47:32 +02:00
|
|
|
*/
|
2021-08-28 12:30:34 +02:00
|
|
|
private external fun executeApplication(romUri : String, romType : Int, romFd : Int, preferenceFd : Int, language : Int, appFilesPath : String, assetManager : AssetManager)
|
2020-04-03 13:47:32 +02:00
|
|
|
|
|
|
|
/**
|
2021-10-13 19:08:48 +02:00
|
|
|
* @param join If the function should only return after all the threads join or immediately
|
|
|
|
* @return If it successfully caused [emulationThread] to gracefully stop or do so asynchronously when not joined
|
2020-04-03 13:47:32 +02:00
|
|
|
*/
|
2021-10-13 19:08:48 +02:00
|
|
|
private external fun stopEmulation(join : Boolean) : Boolean
|
2020-04-03 13:47:32 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* This sets the surface object in libskyline to the provided value, emulation is halted if set to null
|
|
|
|
*
|
|
|
|
* @param surface The value to set surface to
|
2020-11-18 20:47:09 +01:00
|
|
|
* @return If the value was successfully set
|
2020-04-03 13:47:32 +02:00
|
|
|
*/
|
2020-11-18 20:47:09 +01:00
|
|
|
private external fun setSurface(surface : Surface?) : Boolean
|
2019-12-02 14:39:08 +01:00
|
|
|
|
2021-10-13 19:08:48 +02:00
|
|
|
/**
|
|
|
|
* @param play If the audio should be playing or be stopped till it is resumed by calling this again
|
|
|
|
*/
|
|
|
|
private external fun changeAudioStatus(play : Boolean)
|
|
|
|
|
2020-11-18 20:47:09 +01:00
|
|
|
var fps : Int = 0
|
2021-06-27 03:26:16 +02:00
|
|
|
var averageFrametime : Float = 0.0f
|
|
|
|
var averageFrametimeDeviation : Float = 0.0f
|
2020-04-18 02:16:09 +02:00
|
|
|
|
|
|
|
/**
|
2021-10-13 19:08:48 +02:00
|
|
|
* Writes the current performance statistics into [fps], [averageFrametime] and [averageFrametimeDeviation] fields
|
2020-04-18 02:16:09 +02:00
|
|
|
*/
|
2020-11-18 20:47:09 +01:00
|
|
|
private external fun updatePerformanceStatistics()
|
2020-04-18 02:16:09 +02:00
|
|
|
|
2020-04-26 16:32:24 +02:00
|
|
|
/**
|
2020-08-15 15:51:23 +02:00
|
|
|
* This initializes a guest controller in libskyline
|
|
|
|
*
|
|
|
|
* @param index The arbitrary index of the controller, this is to handle matching with a partner Joy-Con
|
|
|
|
* @param type The type of the host controller
|
|
|
|
* @param partnerIndex The index of a partner Joy-Con if there is one
|
2020-08-20 20:31:32 +02:00
|
|
|
* @note This is blocking and will stall till input has been initialized on the guest
|
2020-08-15 15:51:23 +02:00
|
|
|
*/
|
|
|
|
private external fun setController(index : Int, type : Int, partnerIndex : Int = -1)
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This flushes the controller updates on the guest
|
2020-08-20 20:31:32 +02:00
|
|
|
*
|
|
|
|
* @note This is blocking and will stall till input has been initialized on the guest
|
2020-04-26 16:32:24 +02:00
|
|
|
*/
|
2020-08-15 15:51:23 +02:00
|
|
|
private external fun updateControllers()
|
2020-04-26 01:34:35 +02:00
|
|
|
|
2020-04-26 16:32:24 +02:00
|
|
|
/**
|
2020-08-15 15:51:23 +02:00
|
|
|
* This sets the state of the buttons specified in the mask on a specific controller
|
|
|
|
*
|
|
|
|
* @param index The index of the controller this is directed to
|
|
|
|
* @param mask The mask of the button that are being set
|
2020-08-21 13:14:27 +02:00
|
|
|
* @param pressed If the buttons are being pressed or released
|
2020-04-26 16:32:24 +02:00
|
|
|
*/
|
2020-08-21 13:14:27 +02:00
|
|
|
private external fun setButtonState(index : Int, mask : Long, pressed : Boolean)
|
2020-08-15 15:51:23 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* This sets the value of a specific axis on a specific controller
|
|
|
|
*
|
|
|
|
* @param index The index of the controller this is directed to
|
|
|
|
* @param axis The ID of the axis that is being modified
|
|
|
|
* @param value The value to set the axis to
|
|
|
|
*/
|
|
|
|
private external fun setAxisValue(index : Int, axis : Int, value : Int)
|
|
|
|
|
2020-09-07 18:39:05 +02:00
|
|
|
/**
|
|
|
|
* This sets the values of the points on the guest touch-screen
|
|
|
|
*
|
|
|
|
* @param points An array of skyline::input::TouchScreenPoint in C++ represented as integers
|
|
|
|
*/
|
|
|
|
private external fun setTouchState(points : IntArray)
|
|
|
|
|
2020-08-15 15:51:23 +02:00
|
|
|
/**
|
2020-10-03 12:09:35 +02:00
|
|
|
* This initializes all of the controllers from [InputManager] on the guest
|
2020-08-15 15:51:23 +02:00
|
|
|
*/
|
2020-10-03 12:09:35 +02:00
|
|
|
@Suppress("unused")
|
2020-08-15 15:51:23 +02:00
|
|
|
private fun initializeControllers() {
|
2021-01-31 22:11:26 +01:00
|
|
|
for (controller in inputManager.controllers.values) {
|
2020-08-15 15:51:23 +02:00
|
|
|
if (controller.type != ControllerType.None) {
|
2020-08-21 13:14:27 +02:00
|
|
|
val type = when (controller.type) {
|
2020-08-15 15:51:23 +02:00
|
|
|
ControllerType.None -> throw IllegalArgumentException()
|
2020-10-04 22:29:50 +02:00
|
|
|
ControllerType.HandheldProController -> if (settings.operationMode) ControllerType.ProController.id else ControllerType.HandheldProController.id
|
2020-08-15 15:51:23 +02:00
|
|
|
ControllerType.ProController, ControllerType.JoyConLeft, ControllerType.JoyConRight -> controller.type.id
|
|
|
|
}
|
|
|
|
|
2020-08-21 13:14:27 +02:00
|
|
|
val partnerIndex = when (controller) {
|
2020-08-15 15:51:23 +02:00
|
|
|
is JoyConLeftController -> controller.partnerId
|
|
|
|
is JoyConRightController -> controller.partnerId
|
|
|
|
else -> null
|
|
|
|
}
|
|
|
|
|
2020-10-03 12:09:35 +02:00
|
|
|
setController(controller.id, type, partnerIndex ?: -1)
|
2020-08-15 15:51:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
updateControllers()
|
|
|
|
}
|
2020-04-26 01:34:35 +02:00
|
|
|
|
2020-04-03 13:47:32 +02:00
|
|
|
/**
|
2021-10-13 19:08:48 +02:00
|
|
|
* Return from emulation to either [MainActivity] or the activity on the back stack
|
|
|
|
*/
|
|
|
|
fun returnFromEmulation() {
|
|
|
|
if (shouldFinish) {
|
|
|
|
runOnUiThread {
|
|
|
|
if (shouldFinish) {
|
|
|
|
shouldFinish = false
|
|
|
|
if (returnToMain)
|
|
|
|
startActivity(Intent(applicationContext, MainActivity::class.java).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP))
|
2021-10-29 17:50:04 +02:00
|
|
|
finishAffinity()
|
2021-10-13 19:08:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @note Any caller has to handle the application potentially being restarted with the supplied intent
|
2020-04-03 13:47:32 +02:00
|
|
|
*/
|
2021-10-13 19:08:48 +02:00
|
|
|
private fun executeApplication(intent : Intent) {
|
|
|
|
if (emulationThread?.isAlive == true) {
|
|
|
|
shouldFinish = false
|
|
|
|
if (stopEmulation(false))
|
|
|
|
emulationThread!!.join(250)
|
|
|
|
|
|
|
|
if (emulationThread!!.isAlive) {
|
2021-10-29 17:50:04 +02:00
|
|
|
finishAffinity()
|
2021-10-13 19:08:48 +02:00
|
|
|
startActivity(intent)
|
|
|
|
Runtime.getRuntime().exit(0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
shouldFinish = true
|
|
|
|
returnToMain = intent.getBooleanExtra(ReturnToMainTag, false)
|
|
|
|
|
|
|
|
val rom = intent.data!!
|
2020-04-03 13:47:32 +02:00
|
|
|
val romType = getRomFormat(rom, contentResolver).ordinal
|
2020-09-29 14:46:17 +02:00
|
|
|
val romFd = contentResolver.openFileDescriptor(rom, "r")!!
|
|
|
|
val preferenceFd = ParcelFileDescriptor.open(File("${applicationInfo.dataDir}/shared_prefs/${applicationInfo.packageName}_preferences.xml"), ParcelFileDescriptor.MODE_READ_WRITE)
|
2020-04-03 13:47:32 +02:00
|
|
|
|
|
|
|
emulationThread = Thread {
|
2021-08-28 11:51:15 +02:00
|
|
|
executeApplication(rom.toString(), romType, romFd.detachFd(), preferenceFd.detachFd(), settings.systemLanguage, applicationContext.filesDir.canonicalPath + "/", assets)
|
2021-10-13 19:08:48 +02:00
|
|
|
returnFromEmulation()
|
2020-02-11 07:34:22 +01:00
|
|
|
}
|
2020-04-03 13:47:32 +02:00
|
|
|
|
2021-10-13 19:08:48 +02:00
|
|
|
emulationThread!!.start()
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onBackPressed() {
|
|
|
|
returnFromEmulation()
|
2020-02-11 07:34:22 +01:00
|
|
|
}
|
|
|
|
|
2021-01-31 22:11:26 +01:00
|
|
|
@SuppressLint("SetTextI18n", "ClickableViewAccessibility")
|
2020-04-24 13:39:13 +02:00
|
|
|
override fun onCreate(savedInstanceState : Bundle?) {
|
2019-12-02 14:39:08 +01:00
|
|
|
super.onCreate(savedInstanceState)
|
2022-01-09 17:20:02 +01:00
|
|
|
requestedOrientation = settings.orientation
|
2021-01-31 22:11:26 +01:00
|
|
|
setContentView(binding.root)
|
2020-04-18 02:16:09 +02:00
|
|
|
|
2020-07-06 22:47:23 +02:00
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
|
|
window.insetsController?.hide(WindowInsets.Type.navigationBars() or WindowInsets.Type.systemBars() or WindowInsets.Type.systemGestures() or WindowInsets.Type.statusBars())
|
|
|
|
window.insetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
|
|
|
}
|
2020-04-03 13:47:32 +02:00
|
|
|
|
2021-01-31 22:11:26 +01:00
|
|
|
binding.gameView.holder.addCallback(this)
|
2020-04-03 13:47:32 +02:00
|
|
|
|
2022-01-12 16:24:29 +01:00
|
|
|
binding.gameView.setAspectRatio(
|
|
|
|
when (settings.aspectRatio) {
|
|
|
|
0 -> Rational(16, 9)
|
|
|
|
1 -> Rational(21, 9)
|
|
|
|
else -> null
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2020-10-04 22:29:50 +02:00
|
|
|
if (settings.perfStats) {
|
2021-01-31 22:11:26 +01:00
|
|
|
binding.perfStats.apply {
|
|
|
|
postDelayed(object : Runnable {
|
|
|
|
override fun run() {
|
|
|
|
updatePerformanceStatistics()
|
2021-06-27 03:26:16 +02:00
|
|
|
text = "$fps FPS\n${"%.1f".format(averageFrametime)}±${"%.2f".format(averageFrametimeDeviation)}ms"
|
2021-01-31 22:11:26 +01:00
|
|
|
postDelayed(this, 250)
|
|
|
|
}
|
|
|
|
}, 250)
|
|
|
|
}
|
2020-04-18 02:16:09 +02:00
|
|
|
}
|
|
|
|
|
2020-08-20 20:31:32 +02:00
|
|
|
@Suppress("DEPRECATION") val display = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) display!! else windowManager.defaultDisplay
|
2021-06-20 00:14:43 +02:00
|
|
|
if (settings.maxRefreshRate)
|
|
|
|
display?.supportedModes?.maxByOrNull { it.refreshRate * it.physicalHeight * it.physicalWidth }?.let { window.attributes.preferredDisplayModeId = it.modeId }
|
|
|
|
else
|
|
|
|
display?.supportedModes?.minByOrNull { abs(it.refreshRate - 60f) }?.let { window.attributes.preferredDisplayModeId = it.modeId }
|
2020-08-15 15:51:23 +02:00
|
|
|
|
2021-01-31 22:11:26 +01:00
|
|
|
binding.gameView.setOnTouchListener(this)
|
2020-09-07 18:39:05 +02:00
|
|
|
|
2020-10-03 12:09:35 +02:00
|
|
|
// Hide on screen controls when first controller is not set
|
2021-01-31 22:11:26 +01:00
|
|
|
binding.onScreenControllerView.apply {
|
2021-04-30 21:38:13 +02:00
|
|
|
inputManager.controllers[0]!!.type.let {
|
|
|
|
controllerType = it
|
|
|
|
isGone = it == ControllerType.None || !settings.onScreenControl
|
|
|
|
}
|
2021-01-31 22:11:26 +01:00
|
|
|
setOnButtonStateChangedListener(::onButtonStateChanged)
|
|
|
|
setOnStickStateChangedListener(::onStickStateChanged)
|
|
|
|
recenterSticks = settings.onScreenControlRecenterSticks
|
|
|
|
}
|
|
|
|
|
|
|
|
binding.onScreenControllerToggle.apply {
|
|
|
|
isGone = binding.onScreenControllerView.isGone
|
|
|
|
setOnClickListener { binding.onScreenControllerView.isInvisible = !binding.onScreenControllerView.isInvisible }
|
2020-10-05 12:56:38 +02:00
|
|
|
}
|
|
|
|
|
2021-10-13 19:08:48 +02:00
|
|
|
executeApplication(intent!!)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onPause() {
|
|
|
|
super.onPause()
|
|
|
|
|
|
|
|
changeAudioStatus(false)
|
2020-02-11 07:34:22 +01:00
|
|
|
}
|
|
|
|
|
2021-02-05 17:37:05 +01:00
|
|
|
override fun onResume() {
|
|
|
|
super.onResume()
|
|
|
|
|
2021-10-13 19:08:48 +02:00
|
|
|
changeAudioStatus(true)
|
|
|
|
|
2021-02-05 17:37:05 +01:00
|
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
|
|
|
@Suppress("DEPRECATION")
|
|
|
|
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
|
|
|
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
|
|
|
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
|
|
|
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
|
|
|
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
|
|
|
or View.SYSTEM_UI_FLAG_FULLSCREEN)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-03 13:47:32 +02:00
|
|
|
/**
|
2021-10-13 19:08:48 +02:00
|
|
|
* Stop the currently executing ROM and replace it with the one specified in the new intent
|
2020-04-03 13:47:32 +02:00
|
|
|
*/
|
2020-04-24 13:39:13 +02:00
|
|
|
override fun onNewIntent(intent : Intent?) {
|
2021-10-13 19:08:48 +02:00
|
|
|
super.onNewIntent(intent!!)
|
2021-10-23 20:41:38 +02:00
|
|
|
if (getIntent().data != intent.data) {
|
|
|
|
setIntent(intent)
|
|
|
|
executeApplication(intent)
|
|
|
|
}
|
2019-12-02 14:39:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun onDestroy() {
|
2020-11-03 10:44:09 +01:00
|
|
|
super.onDestroy()
|
2020-02-11 07:34:22 +01:00
|
|
|
shouldFinish = false
|
2020-04-03 13:47:32 +02:00
|
|
|
|
2021-10-13 19:08:48 +02:00
|
|
|
stopEmulation(false)
|
2020-09-05 01:06:07 +02:00
|
|
|
vibrators.forEach { (_, vibrator) -> vibrator.cancel() }
|
|
|
|
vibrators.clear()
|
2019-12-02 14:39:08 +01:00
|
|
|
}
|
|
|
|
|
2020-06-15 17:37:28 +02:00
|
|
|
override fun surfaceCreated(holder : SurfaceHolder) {
|
2020-09-14 15:53:40 +02:00
|
|
|
Log.d(Tag, "surfaceCreated Holder: $holder")
|
2021-10-13 19:08:48 +02:00
|
|
|
while (emulationThread!!.isAlive)
|
2021-01-30 14:59:11 +01:00
|
|
|
if (setSurface(holder.surface))
|
2020-11-18 20:47:09 +01:00
|
|
|
return
|
2019-12-02 14:39:08 +01:00
|
|
|
}
|
|
|
|
|
2020-04-03 13:47:32 +02:00
|
|
|
/**
|
2020-04-12 22:29:19 +02:00
|
|
|
* This is purely used for debugging surface changes
|
2020-04-03 13:47:32 +02:00
|
|
|
*/
|
2020-06-15 17:37:28 +02:00
|
|
|
override fun surfaceChanged(holder : SurfaceHolder, format : Int, width : Int, height : Int) {
|
2020-09-14 15:53:40 +02:00
|
|
|
Log.d(Tag, "surfaceChanged Holder: $holder, Format: $format, Width: $width, Height: $height")
|
2019-12-02 14:39:08 +01:00
|
|
|
}
|
|
|
|
|
2020-06-15 17:37:28 +02:00
|
|
|
override fun surfaceDestroyed(holder : SurfaceHolder) {
|
2020-09-14 15:53:40 +02:00
|
|
|
Log.d(Tag, "surfaceDestroyed Holder: $holder")
|
2021-10-13 19:08:48 +02:00
|
|
|
while (emulationThread!!.isAlive)
|
2021-01-30 14:59:11 +01:00
|
|
|
if (setSurface(null))
|
2020-11-18 20:47:09 +01:00
|
|
|
return
|
2019-12-02 14:39:08 +01:00
|
|
|
}
|
2020-04-26 16:32:24 +02:00
|
|
|
|
|
|
|
/**
|
2020-08-15 15:51:23 +02:00
|
|
|
* This handles translating any [KeyHostEvent]s to a [GuestEvent] that is passed into libskyline
|
2020-04-26 16:32:24 +02:00
|
|
|
*/
|
|
|
|
override fun dispatchKeyEvent(event : KeyEvent) : Boolean {
|
2020-08-15 15:51:23 +02:00
|
|
|
if (event.repeatCount != 0)
|
|
|
|
return super.dispatchKeyEvent(event)
|
|
|
|
|
2020-08-21 13:14:27 +02:00
|
|
|
val action = when (event.action) {
|
2020-04-26 16:32:24 +02:00
|
|
|
KeyEvent.ACTION_DOWN -> ButtonState.Pressed
|
|
|
|
KeyEvent.ACTION_UP -> ButtonState.Released
|
2020-08-15 15:51:23 +02:00
|
|
|
else -> return super.dispatchKeyEvent(event)
|
2020-04-26 16:32:24 +02:00
|
|
|
}
|
|
|
|
|
2021-01-31 22:11:26 +01:00
|
|
|
return when (val guestEvent = inputManager.eventMap[KeyHostEvent(event.device.descriptor, event.keyCode)]) {
|
2020-08-15 15:51:23 +02:00
|
|
|
is ButtonGuestEvent -> {
|
|
|
|
if (guestEvent.button != ButtonId.Menu)
|
2020-08-21 13:14:27 +02:00
|
|
|
setButtonState(guestEvent.id, guestEvent.button.value(), action.state)
|
2020-08-15 15:51:23 +02:00
|
|
|
true
|
|
|
|
}
|
|
|
|
|
|
|
|
is AxisGuestEvent -> {
|
|
|
|
setAxisValue(guestEvent.id, guestEvent.axis.ordinal, (if (action == ButtonState.Pressed) if (guestEvent.polarity) Short.MAX_VALUE else Short.MIN_VALUE else 0).toInt())
|
|
|
|
true
|
|
|
|
}
|
|
|
|
|
|
|
|
else -> super.dispatchKeyEvent(event)
|
2020-04-26 16:32:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-08-15 15:51:23 +02:00
|
|
|
* The last value of the axes so the stagnant axes can be eliminated to not wastefully look them up
|
2020-04-26 16:32:24 +02:00
|
|
|
*/
|
2020-08-15 15:51:23 +02:00
|
|
|
private val axesHistory = arrayOfNulls<Float>(MotionHostEvent.axes.size)
|
2020-04-26 16:32:24 +02:00
|
|
|
|
|
|
|
/**
|
2020-08-15 15:51:23 +02:00
|
|
|
* The last value of the HAT axes so it can be ignored in [onGenericMotionEvent] so they are handled by [dispatchKeyEvent] instead
|
2020-04-26 16:32:24 +02:00
|
|
|
*/
|
2020-10-03 12:09:35 +02:00
|
|
|
private var oldHat = PointF()
|
2020-04-26 16:32:24 +02:00
|
|
|
|
|
|
|
/**
|
2020-08-15 15:51:23 +02:00
|
|
|
* This handles translating any [MotionHostEvent]s to a [GuestEvent] that is passed into libskyline
|
2020-04-26 16:32:24 +02:00
|
|
|
*/
|
2020-08-15 15:51:23 +02:00
|
|
|
override fun onGenericMotionEvent(event : MotionEvent) : Boolean {
|
|
|
|
if ((event.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK) || event.isFromSource(InputDevice.SOURCE_CLASS_BUTTON)) && event.action == MotionEvent.ACTION_MOVE) {
|
2020-10-03 12:09:35 +02:00
|
|
|
val hat = PointF(event.getAxisValue(MotionEvent.AXIS_HAT_X), event.getAxisValue(MotionEvent.AXIS_HAT_Y))
|
2020-08-15 15:51:23 +02:00
|
|
|
|
|
|
|
if (hat == oldHat) {
|
|
|
|
for (axisItem in MotionHostEvent.axes.withIndex()) {
|
|
|
|
val axis = axisItem.value
|
|
|
|
var value = event.getAxisValue(axis)
|
|
|
|
|
|
|
|
if ((event.historySize != 0 && value != event.getHistoricalAxisValue(axis, 0)) || (axesHistory[axisItem.index]?.let { it == value } == false)) {
|
|
|
|
var polarity = value >= 0
|
|
|
|
|
2020-10-03 12:09:35 +02:00
|
|
|
val guestEvent = MotionHostEvent(event.device.descriptor, axis, polarity).let { hostEvent ->
|
2021-01-31 22:11:26 +01:00
|
|
|
inputManager.eventMap[hostEvent] ?: if (value == 0f) {
|
2020-10-03 12:09:35 +02:00
|
|
|
polarity = false
|
2021-01-31 22:11:26 +01:00
|
|
|
inputManager.eventMap[hostEvent.copy(polarity = false)]
|
2020-10-03 12:09:35 +02:00
|
|
|
} else {
|
|
|
|
null
|
|
|
|
}
|
2020-08-15 15:51:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
when (guestEvent) {
|
|
|
|
is ButtonGuestEvent -> {
|
|
|
|
if (guestEvent.button != ButtonId.Menu)
|
2020-08-21 13:14:27 +02:00
|
|
|
setButtonState(guestEvent.id, guestEvent.button.value(), if (abs(value) >= guestEvent.threshold) ButtonState.Pressed.state else ButtonState.Released.state)
|
2020-08-15 15:51:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
is AxisGuestEvent -> {
|
|
|
|
value = guestEvent.value(value)
|
|
|
|
value = if (polarity) abs(value) else -abs(value)
|
2021-06-21 18:00:15 +02:00
|
|
|
value = if (guestEvent.axis == AxisId.LX || guestEvent.axis == AxisId.RX) value else -value
|
2020-08-15 15:51:23 +02:00
|
|
|
|
|
|
|
setAxisValue(guestEvent.id, guestEvent.axis.ordinal, (value * Short.MAX_VALUE).toInt())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
axesHistory[axisItem.index] = value
|
|
|
|
}
|
2020-04-26 16:32:24 +02:00
|
|
|
|
|
|
|
return true
|
2020-08-15 15:51:23 +02:00
|
|
|
} else {
|
|
|
|
oldHat = hat
|
2020-04-26 16:32:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-15 15:51:23 +02:00
|
|
|
return super.onGenericMotionEvent(event)
|
2020-04-26 16:32:24 +02:00
|
|
|
}
|
2020-09-05 01:06:07 +02:00
|
|
|
|
2020-09-07 18:39:05 +02:00
|
|
|
@SuppressLint("ClickableViewAccessibility")
|
|
|
|
override fun onTouch(view : View, event : MotionEvent) : Boolean {
|
2020-10-03 12:09:35 +02:00
|
|
|
val count = if (event.action != MotionEvent.ACTION_UP && event.action != MotionEvent.ACTION_CANCEL) event.pointerCount else 0
|
2020-09-07 18:39:05 +02:00
|
|
|
val points = IntArray(count * 5) // This is an array of skyline::input::TouchScreenPoint in C++ as that allows for efficient transfer of values to it
|
|
|
|
var offset = 0
|
|
|
|
for (index in 0 until count) {
|
|
|
|
val pointer = MotionEvent.PointerCoords()
|
|
|
|
event.getPointerCoords(index, pointer)
|
|
|
|
|
|
|
|
val x = 0f.coerceAtLeast(pointer.x * 1280 / view.width).toInt()
|
|
|
|
val y = 0f.coerceAtLeast(pointer.y * 720 / view.height).toInt()
|
|
|
|
|
|
|
|
points[offset++] = x
|
|
|
|
points[offset++] = y
|
|
|
|
points[offset++] = pointer.touchMinor.toInt()
|
|
|
|
points[offset++] = pointer.touchMajor.toInt()
|
|
|
|
points[offset++] = (pointer.orientation * 180 / Math.PI).toInt()
|
|
|
|
}
|
|
|
|
|
|
|
|
setTouchState(points)
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2020-10-03 12:10:16 +02:00
|
|
|
private fun onButtonStateChanged(buttonId : ButtonId, state : ButtonState) = setButtonState(0, buttonId.value(), state.state)
|
2020-10-03 12:09:35 +02:00
|
|
|
|
2021-04-30 21:38:13 +02:00
|
|
|
private fun onStickStateChanged(stickId : StickId, position : PointF) {
|
2020-10-03 12:09:35 +02:00
|
|
|
setAxisValue(0, stickId.xAxis.ordinal, (position.x * Short.MAX_VALUE).toInt())
|
2020-10-05 12:04:57 +02:00
|
|
|
setAxisValue(0, stickId.yAxis.ordinal, (-position.y * Short.MAX_VALUE).toInt()) // Y is inverted, since drawing starts from top left
|
2020-10-03 12:09:35 +02:00
|
|
|
}
|
|
|
|
|
2020-09-05 01:06:07 +02:00
|
|
|
@SuppressLint("WrongConstant")
|
2020-10-03 12:09:35 +02:00
|
|
|
@Suppress("unused")
|
2020-09-05 01:06:07 +02:00
|
|
|
fun vibrateDevice(index : Int, timing : LongArray, amplitude : IntArray) {
|
|
|
|
val vibrator = if (vibrators[index] != null) {
|
2021-10-31 18:51:50 +01:00
|
|
|
vibrators[index]
|
2020-09-05 01:06:07 +02:00
|
|
|
} else {
|
2021-01-31 22:11:26 +01:00
|
|
|
inputManager.controllers[index]!!.rumbleDeviceDescriptor?.let {
|
2021-10-31 18:51:50 +01:00
|
|
|
if (it == Controller.BuiltinRumbleDeviceDescriptor) {
|
2021-10-26 07:15:49 +02:00
|
|
|
val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
|
|
val vibratorManager = getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
|
|
|
|
vibratorManager.defaultVibrator
|
|
|
|
} else {
|
|
|
|
@Suppress("DEPRECATION")
|
|
|
|
getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
|
|
|
}
|
2020-09-05 01:06:07 +02:00
|
|
|
vibrators[index] = vibrator
|
|
|
|
vibrator
|
|
|
|
} else {
|
|
|
|
for (id in InputDevice.getDeviceIds()) {
|
|
|
|
val device = InputDevice.getDevice(id)
|
2021-01-31 22:11:26 +01:00
|
|
|
if (device.descriptor == inputManager.controllers[index]!!.rumbleDeviceDescriptor) {
|
2021-10-26 18:24:00 +02:00
|
|
|
val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
2021-10-26 07:15:49 +02:00
|
|
|
device.vibratorManager.defaultVibrator
|
|
|
|
} else {
|
|
|
|
@Suppress("DEPRECATION")
|
2021-10-26 18:24:00 +02:00
|
|
|
device.vibrator!!
|
2021-10-26 07:15:49 +02:00
|
|
|
}
|
2021-10-26 18:24:00 +02:00
|
|
|
vibrators[index] = vibrator
|
|
|
|
return@let vibrator
|
2020-09-05 01:06:07 +02:00
|
|
|
}
|
|
|
|
}
|
2021-10-31 18:51:50 +01:00
|
|
|
return@let null
|
2020-09-05 01:06:07 +02:00
|
|
|
}
|
2021-10-31 18:51:50 +01:00
|
|
|
}
|
2020-09-05 01:06:07 +02:00
|
|
|
}
|
|
|
|
|
2021-10-31 18:51:50 +01:00
|
|
|
vibrator?.let {
|
|
|
|
val effect = VibrationEffect.createWaveform(timing, amplitude, 0)
|
|
|
|
it.vibrate(effect)
|
|
|
|
}
|
2020-09-05 01:06:07 +02:00
|
|
|
}
|
|
|
|
|
2020-10-03 12:09:35 +02:00
|
|
|
@Suppress("unused")
|
2020-09-05 01:06:07 +02:00
|
|
|
fun clearVibrationDevice(index : Int) {
|
|
|
|
vibrators[index]?.cancel()
|
|
|
|
}
|
2021-03-28 16:35:13 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @return A version code in Vulkan's format with 14-bit patch + 10-bit major and minor components
|
|
|
|
*/
|
|
|
|
@ExperimentalUnsignedTypes
|
|
|
|
@Suppress("unused")
|
|
|
|
fun getVersionCode() : Int {
|
|
|
|
val (major, minor, patch) = BuildConfig.VERSION_NAME.split('.').map { it.toUInt() }
|
|
|
|
return ((major shl 22) or (minor shl 12) or (patch)).toInt()
|
|
|
|
}
|
2019-12-02 14:39:08 +01:00
|
|
|
}
|