ControllerInterface/Android: Implement hotplug

This commit is contained in:
JosJuice 2022-03-06 20:27:25 +01:00
parent d6af294a23
commit 104ea09892
3 changed files with 157 additions and 0 deletions

View File

@ -2,15 +2,50 @@
package org.dolphinemu.dolphinemu.features.input.model;
import android.content.Context;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.view.KeyEvent;
import android.view.MotionEvent;
import androidx.annotation.Keep;
import org.dolphinemu.dolphinemu.DolphinApplication;
import org.dolphinemu.dolphinemu.utils.LooperThread;
/**
* This class interfaces with the native ControllerInterface,
* which is where the emulator core gets inputs from.
*/
public final class ControllerInterface
{
private static final class InputDeviceListener implements InputManager.InputDeviceListener
{
@Override
public void onInputDeviceAdded(int deviceId)
{
// Simple implementation for now. We could do something fancier if we wanted to.
refreshDevices();
}
@Override
public void onInputDeviceRemoved(int deviceId)
{
// Simple implementation for now. We could do something fancier if we wanted to.
refreshDevices();
}
@Override
public void onInputDeviceChanged(int deviceId)
{
// Simple implementation for now. We could do something fancier if we wanted to.
refreshDevices();
}
}
private static InputDeviceListener mInputDeviceListener;
private static LooperThread mLooperThread;
/**
* Activities which want to pass on inputs to native code
* should call this in their own dispatchKeyEvent method.
@ -28,4 +63,41 @@ public final class ControllerInterface
* false if the event should be passed on to the default dispatchGenericMotionEvent.
*/
public static native boolean dispatchGenericMotionEvent(MotionEvent event);
/**
* Rescans for input devices.
*/
public static native void refreshDevices();
@Keep
private static void registerInputDeviceListener()
{
if (mLooperThread == null)
{
mLooperThread = new LooperThread("Hotplug thread");
mLooperThread.start();
}
if (mInputDeviceListener == null)
{
InputManager im = (InputManager)
DolphinApplication.getAppContext().getSystemService(Context.INPUT_SERVICE);
mInputDeviceListener = new InputDeviceListener();
im.registerInputDeviceListener(mInputDeviceListener, new Handler(mLooperThread.getLooper()));
}
}
@Keep
private static void unregisterInputDeviceListener()
{
if (mInputDeviceListener != null)
{
InputManager im = (InputManager)
DolphinApplication.getAppContext().getSystemService(Context.INPUT_SERVICE);
im.unregisterInputDeviceListener(mInputDeviceListener);
mInputDeviceListener = null;
}
}
}

View File

@ -0,0 +1,58 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.utils;
import android.os.Looper;
public class LooperThread extends Thread
{
private Looper mLooper;
public LooperThread()
{
super();
}
public LooperThread(String name)
{
super(name);
}
@Override
public void run()
{
Looper.prepare();
synchronized (this)
{
mLooper = Looper.myLooper();
notifyAll();
}
Looper.loop();
}
public Looper getLooper()
{
if (!isAlive())
{
throw new IllegalStateException();
}
synchronized (this)
{
while (mLooper == null)
{
try
{
wait();
}
catch (InterruptedException ignored)
{
}
}
}
return mLooper;
}
}

View File

@ -58,6 +58,10 @@ jclass s_motion_event_class;
jmethodID s_motion_event_get_axis_value;
jmethodID s_motion_event_get_source;
jclass s_controller_interface_class;
jmethodID s_controller_interface_register_input_device_listener;
jmethodID s_controller_interface_unregister_input_device_listener;
jintArray s_keycodes_array;
using Clock = std::chrono::steady_clock;
@ -635,20 +639,36 @@ void Init()
s_motion_event_get_axis_value = env->GetMethodID(s_motion_event_class, "getAxisValue", "(I)F");
s_motion_event_get_source = env->GetMethodID(s_motion_event_class, "getSource", "()I");
const jclass controller_interface_class =
env->FindClass("org/dolphinemu/dolphinemu/features/input/model/ControllerInterface");
s_controller_interface_class =
reinterpret_cast<jclass>(env->NewGlobalRef(controller_interface_class));
s_controller_interface_register_input_device_listener =
env->GetStaticMethodID(s_controller_interface_class, "registerInputDeviceListener", "()V");
s_controller_interface_unregister_input_device_listener =
env->GetStaticMethodID(s_controller_interface_class, "unregisterInputDeviceListener", "()V");
jintArray keycodes_array = CreateKeyCodesArray(env);
s_keycodes_array = reinterpret_cast<jintArray>(env->NewGlobalRef(keycodes_array));
env->DeleteLocalRef(keycodes_array);
env->CallStaticVoidMethod(s_controller_interface_class,
s_controller_interface_register_input_device_listener);
}
void Shutdown()
{
JNIEnv* env = IDCache::GetEnvForThread();
env->CallStaticVoidMethod(s_controller_interface_class,
s_controller_interface_unregister_input_device_listener);
env->DeleteGlobalRef(s_input_device_class);
env->DeleteGlobalRef(s_motion_range_class);
env->DeleteGlobalRef(s_input_event_class);
env->DeleteGlobalRef(s_key_event_class);
env->DeleteGlobalRef(s_motion_event_class);
env->DeleteGlobalRef(s_controller_interface_class);
env->DeleteGlobalRef(s_keycodes_array);
}
@ -785,4 +805,11 @@ Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_dispatch
return last_polled >= Clock::now() - ACTIVE_INPUT_TIMEOUT;
}
JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_refreshDevices(JNIEnv* env,
jclass)
{
g_controller_interface.RefreshDevices();
}
}