Android: Make input state changes observable

This commit is contained in:
JosJuice
2025-05-13 16:55:48 +02:00
parent 903eafcf65
commit 4214cb6eb8
2 changed files with 47 additions and 6 deletions

View File

@ -6,6 +6,7 @@ import android.content.Context
import android.hardware.input.InputManager
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.VibrationEffect
import android.os.Vibrator
import android.os.VibratorManager
@ -13,8 +14,11 @@ import android.view.InputDevice
import android.view.KeyEvent
import android.view.MotionEvent
import androidx.annotation.Keep
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import org.dolphinemu.dolphinemu.DolphinApplication
import org.dolphinemu.dolphinemu.utils.LooperThread
import java.util.concurrent.atomic.AtomicBoolean
/**
* This class interfaces with the native ControllerInterface,
@ -24,6 +28,12 @@ object ControllerInterface {
private var inputDeviceListener: InputDeviceListener? = null
private lateinit var looperThread: LooperThread
private var inputStateUpdatePending = AtomicBoolean(false)
private val inputStateVersion = MutableLiveData(0)
val inputStateChanged: LiveData<Int>
get() = inputStateVersion
/**
* Activities which want to pass on inputs to native code
* should call this in their own dispatchKeyEvent method.
@ -31,7 +41,13 @@ object ControllerInterface {
* @return true if the emulator core seems to be interested in this event.
* false if the event should be passed on to the default dispatchKeyEvent.
*/
external fun dispatchKeyEvent(event: KeyEvent): Boolean
fun dispatchKeyEvent(event: KeyEvent): Boolean {
val result = dispatchKeyEventNative(event)
onInputStateChanged()
return result
}
private external fun dispatchKeyEventNative(event: KeyEvent): Boolean
/**
* Activities which want to pass on inputs to native code
@ -40,7 +56,13 @@ object ControllerInterface {
* @return true if the emulator core seems to be interested in this event.
* false if the event should be passed on to the default dispatchGenericMotionEvent.
*/
external fun dispatchGenericMotionEvent(event: MotionEvent): Boolean
fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
val result = dispatchGenericMotionEventNative(event)
onInputStateChanged()
return result
}
private external fun dispatchGenericMotionEventNative(event: MotionEvent): Boolean
/**
* [DolphinSensorEventListener] calls this for each axis of a received SensorEvent.
@ -48,7 +70,13 @@ object ControllerInterface {
* @return true if the emulator core seems to be interested in this event.
* false if the sensor can be suspended to save battery.
*/
external fun dispatchSensorEvent(
fun dispatchSensorEvent(deviceQualifier: String, axisName: String, value: Float): Boolean {
val result = dispatchSensorEventNative(deviceQualifier, axisName, value)
onInputStateChanged()
return result
}
private external fun dispatchSensorEventNative(
deviceQualifier: String,
axisName: String,
value: Float
@ -76,6 +104,19 @@ object ControllerInterface {
external fun getDevice(deviceString: String): CoreDevice?
private fun onInputStateChanged() {
// When a single SensorEvent is dispatched, this method is likely to get called many times.
// For the sake of performance, let's batch input state updates so that observers only have
// to process one update.
if (!inputStateUpdatePending.getAndSet(true)) {
Handler(Looper.getMainLooper()).post {
if (inputStateUpdatePending.getAndSet(false)) {
inputStateVersion.value = inputStateVersion.value?.plus(1)
}
}
}
}
@Keep
@JvmStatic
private fun registerInputDeviceListener() {

View File

@ -1002,7 +1002,7 @@ void InputBackend::PopulateDevices()
extern "C" {
JNIEXPORT jboolean JNICALL
Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_dispatchKeyEvent(
Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_dispatchKeyEventNative(
JNIEnv* env, jclass, jobject key_event)
{
const jint action = env->CallIntMethod(key_event, s_key_event_get_action);
@ -1046,7 +1046,7 @@ Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_dispatch
}
JNIEXPORT jboolean JNICALL
Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_dispatchGenericMotionEvent(
Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_dispatchGenericMotionEventNative(
JNIEnv* env, jclass, jobject motion_event)
{
const jint device_id = env->CallIntMethod(motion_event, s_input_event_get_device_id);
@ -1090,7 +1090,7 @@ Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_dispatch
}
JNIEXPORT jboolean JNICALL
Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_dispatchSensorEvent(
Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_dispatchSensorEventNative(
JNIEnv* env, jclass, jstring j_device_qualifier, jstring j_axis_name, jfloat value)
{
ciface::Core::DeviceQualifier device_qualifier;