mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-01-10 16:19:28 +01:00
ControllerInterface/Android: Implement sensor input
This commit is contained in:
parent
104ea09892
commit
5e51b56d72
@ -11,6 +11,7 @@ import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Pair;
|
||||
import android.util.SparseIntArray;
|
||||
import android.view.Display;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@ -42,6 +43,7 @@ import org.dolphinemu.dolphinemu.databinding.DialogInputAdjustBinding;
|
||||
import org.dolphinemu.dolphinemu.databinding.DialogIrSensitivityBinding;
|
||||
import org.dolphinemu.dolphinemu.databinding.DialogSkylandersManagerBinding;
|
||||
import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface;
|
||||
import org.dolphinemu.dolphinemu.features.input.model.SensorEventRequester;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.IntSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
|
||||
@ -442,12 +444,15 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
|
||||
}
|
||||
|
||||
updateOrientation();
|
||||
|
||||
ControllerInterface.enableSensorEvents(() -> getWindowManager().getDefaultDisplay());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause()
|
||||
{
|
||||
super.onPause();
|
||||
ControllerInterface.disableSensorEvents();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -64,6 +64,26 @@ public final class ControllerInterface
|
||||
*/
|
||||
public static native boolean dispatchGenericMotionEvent(MotionEvent event);
|
||||
|
||||
/**
|
||||
* {@link DolphinSensorEventListener} calls this for each axis of a received SensorEvent.
|
||||
*/
|
||||
public static native void dispatchSensorEvent(String axisName, float value);
|
||||
|
||||
/**
|
||||
* Enables delivering sensor events to native code.
|
||||
*
|
||||
* @param requester The activity or other component which is requesting sensor events to be
|
||||
* delivered.
|
||||
*/
|
||||
public static native void enableSensorEvents(SensorEventRequester requester);
|
||||
|
||||
/**
|
||||
* Disables delivering sensor events to native code.
|
||||
*
|
||||
* Calling this when sensor events are no longer needed will save battery.
|
||||
*/
|
||||
public static native void disableSensorEvents();
|
||||
|
||||
/**
|
||||
* Rescans for input devices.
|
||||
*/
|
||||
|
@ -0,0 +1,361 @@
|
||||
package org.dolphinemu.dolphinemu.features.input.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Build;
|
||||
import android.view.Surface;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
import org.dolphinemu.dolphinemu.DolphinApplication;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class DolphinSensorEventListener implements SensorEventListener
|
||||
{
|
||||
// Set of three axes. Creates a negative companion to each axis, and corrects for device rotation.
|
||||
private static final int AXIS_SET_TYPE_DEVICE_COORDINATES = 0;
|
||||
// Set of three axes. Creates a negative companion to each axis.
|
||||
private static final int AXIS_SET_TYPE_OTHER_COORDINATES = 1;
|
||||
|
||||
private static class AxisSetDetails
|
||||
{
|
||||
public final int firstAxisOfSet;
|
||||
public final int axisSetType;
|
||||
|
||||
public AxisSetDetails(int firstAxisOfSet, int axisSetType)
|
||||
{
|
||||
this.firstAxisOfSet = firstAxisOfSet;
|
||||
this.axisSetType = axisSetType;
|
||||
}
|
||||
}
|
||||
|
||||
private static class SensorDetails
|
||||
{
|
||||
public final int sensorType;
|
||||
public final String[] axisNames;
|
||||
public final AxisSetDetails[] axisSetDetails;
|
||||
|
||||
public SensorDetails(int sensorType, String[] axisNames, AxisSetDetails[] axisSetDetails)
|
||||
{
|
||||
this.sensorType = sensorType;
|
||||
this.axisNames = axisNames;
|
||||
this.axisSetDetails = axisSetDetails;
|
||||
}
|
||||
}
|
||||
|
||||
private final SensorManager mSensorManager;
|
||||
|
||||
private final HashMap<Sensor, SensorDetails> mSensorDetails = new HashMap<>();
|
||||
|
||||
private SensorEventRequester mRequester = null;
|
||||
|
||||
// The fastest sampling rate Android lets us use without declaring the HIGH_SAMPLING_RATE_SENSORS
|
||||
// permission is 200 Hz. This is also the sampling rate of a Wii Remote, so it fits us perfectly.
|
||||
private static final int SAMPLING_PERIOD_US = 1000000 / 200;
|
||||
|
||||
@Keep
|
||||
public DolphinSensorEventListener()
|
||||
{
|
||||
mSensorManager = (SensorManager)
|
||||
DolphinApplication.getAppContext().getSystemService(Context.SENSOR_SERVICE);
|
||||
|
||||
addSensors();
|
||||
}
|
||||
|
||||
private void addSensors()
|
||||
{
|
||||
tryAddSensor(Sensor.TYPE_ACCELEROMETER, new String[]{"Accel Right", "Accel Left",
|
||||
"Accel Forward", "Accel Backward", "Accel Up", "Accel Down"},
|
||||
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)});
|
||||
|
||||
tryAddSensor(Sensor.TYPE_GYROSCOPE, new String[]{"Gyro Pitch Up", "Gyro Pitch Down",
|
||||
"Gyro Roll Right", "Gyro Roll Left", "Gyro Yaw Left", "Gyro Yaw Right"},
|
||||
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)});
|
||||
|
||||
tryAddSensor(Sensor.TYPE_LIGHT, "Light");
|
||||
|
||||
tryAddSensor(Sensor.TYPE_PRESSURE, "Pressure");
|
||||
|
||||
tryAddSensor(Sensor.TYPE_TEMPERATURE, "Device Temperature");
|
||||
|
||||
tryAddSensor(Sensor.TYPE_PROXIMITY, "Proximity");
|
||||
|
||||
tryAddSensor(Sensor.TYPE_GRAVITY, new String[]{"Gravity Right", "Gravity Left",
|
||||
"Gravity Forward", "Gravity Backward", "Gravity Up", "Gravity Down"},
|
||||
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)});
|
||||
|
||||
tryAddSensor(Sensor.TYPE_LINEAR_ACCELERATION,
|
||||
new String[]{"Linear Acceleration Right", "Linear Acceleration Left",
|
||||
"Linear Acceleration Forward", "Linear Acceleration Backward",
|
||||
"Linear Acceleration Up", "Linear Acceleration Down"},
|
||||
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)});
|
||||
|
||||
// The values provided by this sensor can be interpreted as an Euler vector or a quaternion.
|
||||
// The directions of X and Y are flipped to match the Wii Remote coordinate system.
|
||||
tryAddSensor(Sensor.TYPE_ROTATION_VECTOR,
|
||||
new String[]{"Rotation Vector X-", "Rotation Vector X+", "Rotation Vector Y-",
|
||||
"Rotation Vector Y+", "Rotation Vector Z+",
|
||||
"Rotation Vector Z-", "Rotation Vector R", "Rotation Vector Heading Accuracy"},
|
||||
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)});
|
||||
|
||||
tryAddSensor(Sensor.TYPE_RELATIVE_HUMIDITY, "Relative Humidity");
|
||||
|
||||
tryAddSensor(Sensor.TYPE_AMBIENT_TEMPERATURE, "Ambient Temperature");
|
||||
|
||||
// The values provided by this sensor can be interpreted as an Euler vector or a quaternion.
|
||||
// The directions of X and Y are flipped to match the Wii Remote coordinate system.
|
||||
tryAddSensor(Sensor.TYPE_GAME_ROTATION_VECTOR,
|
||||
new String[]{"Game Rotation Vector X-", "Game Rotation Vector X+",
|
||||
"Game Rotation Vector Y-", "Game Rotation Vector Y+", "Game Rotation Vector Z+",
|
||||
"Game Rotation Vector Z-", "Game Rotation Vector R"},
|
||||
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)});
|
||||
|
||||
tryAddSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED,
|
||||
new String[]{"Gyro Uncalibrated Pitch Up", "Gyro Uncalibrated Pitch Down",
|
||||
"Gyro Uncalibrated Roll Right", "Gyro Uncalibrated Roll Left",
|
||||
"Gyro Uncalibrated Yaw Left", "Gyro Uncalibrated Yaw Right",
|
||||
"Gyro Drift Pitch Up", "Gyro Drift Pitch Down", "Gyro Drift Roll Right",
|
||||
"Gyro Drift Roll Left", "Gyro Drift Yaw Left", "Gyro Drift Yaw Right"},
|
||||
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES),
|
||||
new AxisSetDetails(3, AXIS_SET_TYPE_DEVICE_COORDINATES)});
|
||||
|
||||
tryAddSensor(Sensor.TYPE_HEART_RATE, "Heart Rate");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 24)
|
||||
{
|
||||
tryAddSensor(Sensor.TYPE_HEART_BEAT, "Heart Beat");
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 26)
|
||||
{
|
||||
tryAddSensor(Sensor.TYPE_ACCELEROMETER_UNCALIBRATED,
|
||||
new String[]{"Accel Uncalibrated Right", "Accel Uncalibrated Left",
|
||||
"Accel Uncalibrated Forward", "Accel Uncalibrated Backward",
|
||||
"Accel Uncalibrated Up", "Accel Uncalibrated Down",
|
||||
"Accel Bias Right", "Accel Bias Left", "Accel Bias Forward",
|
||||
"Accel Bias Backward", "Accel Bias Up", "Accel Bias Down"},
|
||||
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES),
|
||||
new AxisSetDetails(3, AXIS_SET_TYPE_DEVICE_COORDINATES)});
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 30)
|
||||
{
|
||||
tryAddSensor(Sensor.TYPE_HINGE_ANGLE, "Hinge Angle");
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 33)
|
||||
{
|
||||
// The values provided by this sensor can be interpreted as an Euler vector.
|
||||
// The directions of X and Y are flipped to match the Wii Remote coordinate system.
|
||||
tryAddSensor(Sensor.TYPE_HEAD_TRACKER,
|
||||
new String[]{"Head Rotation Vector X-", "Head Rotation Vector X+",
|
||||
"Head Rotation Vector Y-", "Head Rotation Vector Y+",
|
||||
"Head Rotation Vector Z+", "Head Rotation Vector Z-",
|
||||
"Head Pitch Up", "Head Pitch Down", "Head Roll Right", "Head Roll Left",
|
||||
"Head Yaw Left", "Head Yaw Right"},
|
||||
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_OTHER_COORDINATES),
|
||||
new AxisSetDetails(3, AXIS_SET_TYPE_OTHER_COORDINATES)});
|
||||
|
||||
tryAddSensor(Sensor.TYPE_HEADING, new String[]{"Heading", "Heading Accuracy"},
|
||||
new AxisSetDetails[]{});
|
||||
}
|
||||
}
|
||||
|
||||
private void tryAddSensor(int sensorType, String axisName)
|
||||
{
|
||||
tryAddSensor(sensorType, new String[]{axisName}, new AxisSetDetails[]{});
|
||||
}
|
||||
|
||||
private void tryAddSensor(int sensorType, String[] axisNames, AxisSetDetails[] axisSetDetails)
|
||||
{
|
||||
Sensor sensor = mSensorManager.getDefaultSensor(sensorType);
|
||||
if (sensor != null)
|
||||
{
|
||||
mSensorDetails.put(sensor, new SensorDetails(sensorType, axisNames, axisSetDetails));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent sensorEvent)
|
||||
{
|
||||
final SensorDetails sensorDetails = mSensorDetails.get(sensorEvent.sensor);
|
||||
|
||||
final float[] values = sensorEvent.values;
|
||||
final String[] axisNames = sensorDetails.axisNames;
|
||||
final AxisSetDetails[] axisSetDetails = sensorDetails.axisSetDetails;
|
||||
|
||||
int eventAxisIndex = 0;
|
||||
int detailsAxisIndex = 0;
|
||||
int detailsAxisSetIndex = 0;
|
||||
while (eventAxisIndex < values.length && detailsAxisIndex < axisNames.length)
|
||||
{
|
||||
if (detailsAxisSetIndex < axisSetDetails.length &&
|
||||
axisSetDetails[detailsAxisSetIndex].firstAxisOfSet == eventAxisIndex)
|
||||
{
|
||||
int rotation = Surface.ROTATION_0;
|
||||
if (axisSetDetails[detailsAxisSetIndex].axisSetType == AXIS_SET_TYPE_DEVICE_COORDINATES)
|
||||
{
|
||||
rotation = mRequester.getDisplay().getRotation();
|
||||
}
|
||||
|
||||
float x, y;
|
||||
switch (rotation)
|
||||
{
|
||||
default:
|
||||
case Surface.ROTATION_0:
|
||||
x = values[eventAxisIndex];
|
||||
y = values[eventAxisIndex + 1];
|
||||
break;
|
||||
case Surface.ROTATION_90:
|
||||
x = -values[eventAxisIndex + 1];
|
||||
y = values[eventAxisIndex];
|
||||
break;
|
||||
case Surface.ROTATION_180:
|
||||
x = -values[eventAxisIndex];
|
||||
y = -values[eventAxisIndex + 1];
|
||||
break;
|
||||
case Surface.ROTATION_270:
|
||||
x = values[eventAxisIndex + 1];
|
||||
y = -values[eventAxisIndex];
|
||||
break;
|
||||
}
|
||||
|
||||
float z = values[eventAxisIndex + 2];
|
||||
|
||||
ControllerInterface.dispatchSensorEvent(axisNames[detailsAxisIndex], x);
|
||||
ControllerInterface.dispatchSensorEvent(axisNames[detailsAxisIndex + 1], x);
|
||||
ControllerInterface.dispatchSensorEvent(axisNames[detailsAxisIndex + 2], y);
|
||||
ControllerInterface.dispatchSensorEvent(axisNames[detailsAxisIndex + 3], y);
|
||||
ControllerInterface.dispatchSensorEvent(axisNames[detailsAxisIndex + 4], z);
|
||||
ControllerInterface.dispatchSensorEvent(axisNames[detailsAxisIndex + 5], z);
|
||||
|
||||
eventAxisIndex += 3;
|
||||
detailsAxisIndex += 6;
|
||||
detailsAxisSetIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ControllerInterface.dispatchSensorEvent(axisNames[detailsAxisIndex],
|
||||
values[eventAxisIndex]);
|
||||
|
||||
eventAxisIndex++;
|
||||
detailsAxisIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int i)
|
||||
{
|
||||
// We don't care about this
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables delivering sensor events to native code.
|
||||
*
|
||||
* @param requester The activity or other component which is requesting sensor events to be
|
||||
* delivered.
|
||||
*/
|
||||
@Keep
|
||||
public void enableSensorEvents(SensorEventRequester requester)
|
||||
{
|
||||
if (mRequester != null)
|
||||
{
|
||||
throw new IllegalStateException("Attempted to enable sensor events when someone else" +
|
||||
"had already enabled them");
|
||||
}
|
||||
|
||||
mRequester = requester;
|
||||
|
||||
for (Sensor sensor : mSensorDetails.keySet())
|
||||
{
|
||||
mSensorManager.registerListener(this, sensor, SAMPLING_PERIOD_US);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables delivering sensor events to native code.
|
||||
*
|
||||
* Calling this when sensor events are no longer needed will save battery.
|
||||
*/
|
||||
@Keep
|
||||
public void disableSensorEvents()
|
||||
{
|
||||
mRequester = null;
|
||||
|
||||
mSensorManager.unregisterListener(this);
|
||||
}
|
||||
|
||||
@Keep
|
||||
public String[] getAxisNames()
|
||||
{
|
||||
ArrayList<String> axisNames = new ArrayList<>();
|
||||
|
||||
for (SensorDetails sensorDetails : getSensorDetailsSorted())
|
||||
{
|
||||
Collections.addAll(axisNames, sensorDetails.axisNames);
|
||||
}
|
||||
|
||||
return axisNames.toArray(new String[]{});
|
||||
}
|
||||
|
||||
@Keep
|
||||
public boolean[] getNegativeAxes()
|
||||
{
|
||||
ArrayList<Boolean> negativeAxes = new ArrayList<>();
|
||||
|
||||
for (SensorDetails sensorDetails : getSensorDetailsSorted())
|
||||
{
|
||||
int eventAxisIndex = 0;
|
||||
int detailsAxisIndex = 0;
|
||||
int detailsAxisSetIndex = 0;
|
||||
while (detailsAxisIndex < sensorDetails.axisNames.length)
|
||||
{
|
||||
if (detailsAxisSetIndex < sensorDetails.axisSetDetails.length &&
|
||||
sensorDetails.axisSetDetails[detailsAxisSetIndex].firstAxisOfSet == eventAxisIndex)
|
||||
{
|
||||
negativeAxes.add(false);
|
||||
negativeAxes.add(true);
|
||||
negativeAxes.add(false);
|
||||
negativeAxes.add(true);
|
||||
negativeAxes.add(false);
|
||||
negativeAxes.add(true);
|
||||
|
||||
eventAxisIndex += 3;
|
||||
detailsAxisIndex += 6;
|
||||
detailsAxisSetIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
negativeAxes.add(false);
|
||||
|
||||
eventAxisIndex++;
|
||||
detailsAxisIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean[] result = new boolean[negativeAxes.size()];
|
||||
for (int i = 0; i < result.length; i++)
|
||||
{
|
||||
result[i] = negativeAxes.get(i);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<SensorDetails> getSensorDetailsSorted()
|
||||
{
|
||||
ArrayList<SensorDetails> sensorDetails = new ArrayList<>(mSensorDetails.values());
|
||||
Collections.sort(sensorDetails, Comparator.comparingInt(s -> s.sensorType));
|
||||
return sensorDetails;
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package org.dolphinemu.dolphinemu.features.input.model;
|
||||
|
||||
import android.view.Display;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public interface SensorEventRequester
|
||||
{
|
||||
/**
|
||||
* Returns the display the activity is shown on.
|
||||
*
|
||||
* This is used for getting the display orientation for rotating the axes of motion events.
|
||||
*/
|
||||
@NonNull
|
||||
Display getDisplay();
|
||||
}
|
@ -17,6 +17,7 @@
|
||||
#include <android/keycodes.h>
|
||||
#include <jni.h>
|
||||
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
@ -62,12 +63,20 @@ jclass s_controller_interface_class;
|
||||
jmethodID s_controller_interface_register_input_device_listener;
|
||||
jmethodID s_controller_interface_unregister_input_device_listener;
|
||||
|
||||
jclass s_sensor_event_listener_class;
|
||||
jmethodID s_sensor_event_listener_constructor;
|
||||
jmethodID s_sensor_event_listener_enable_sensor_events;
|
||||
jmethodID s_sensor_event_listener_disable_sensor_events;
|
||||
jmethodID s_sensor_event_listener_get_axis_names;
|
||||
jmethodID s_sensor_event_listener_get_negative_axes;
|
||||
|
||||
jintArray s_keycodes_array;
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
constexpr Clock::duration ACTIVE_INPUT_TIMEOUT = std::chrono::milliseconds(1000);
|
||||
|
||||
std::unordered_map<jint, ciface::Core::DeviceQualifier> s_device_id_to_device_qualifier;
|
||||
ciface::Core::DeviceQualifier s_sensor_device_qualifier;
|
||||
|
||||
constexpr int MAX_KEYCODE = AKEYCODE_PROFILE_SWITCH; // Up to date as of SDK 31
|
||||
|
||||
@ -460,7 +469,7 @@ public:
|
||||
explicit AndroidKey(int keycode) : AndroidInput(ConstructKeyName(keycode)) {}
|
||||
};
|
||||
|
||||
class AndroidAxis final : public AndroidInput
|
||||
class AndroidAxis : public AndroidInput
|
||||
{
|
||||
public:
|
||||
AndroidAxis(int source, int axis, bool negative)
|
||||
@ -468,6 +477,10 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
AndroidAxis(std::string name, bool negative) : AndroidInput(std::move(name)), m_negative(negative)
|
||||
{
|
||||
}
|
||||
|
||||
ControlState GetState() const override
|
||||
{
|
||||
return m_negative ? -AndroidInput::GetState() : AndroidInput::GetState();
|
||||
@ -477,12 +490,21 @@ private:
|
||||
bool m_negative;
|
||||
};
|
||||
|
||||
class AndroidSensorAxis final : public AndroidAxis
|
||||
{
|
||||
public:
|
||||
AndroidSensorAxis(std::string name, bool negative) : AndroidAxis(std::move(name), negative) {}
|
||||
|
||||
bool IsDetectable() const override { return false; }
|
||||
};
|
||||
|
||||
class AndroidDevice final : public Core::Device
|
||||
{
|
||||
public:
|
||||
AndroidDevice(JNIEnv* env, jobject input_device)
|
||||
: m_source(env->CallIntMethod(input_device, s_input_device_get_sources)),
|
||||
m_controller_number(env->CallIntMethod(input_device, s_input_device_get_controller_number)
|
||||
: m_sensor_event_listener(nullptr),
|
||||
m_source(env->CallIntMethod(input_device, s_input_device_get_sources)),
|
||||
m_controller_number(env->CallIntMethod(input_device, s_input_device_get_controller_number))
|
||||
{
|
||||
jstring j_name =
|
||||
reinterpret_cast<jstring>(env->CallObjectMethod(input_device, s_input_device_get_name));
|
||||
@ -495,6 +517,19 @@ public:
|
||||
AddAxes(env, input_device);
|
||||
}
|
||||
|
||||
// Constructor for the device added by Dolphin to contain sensor inputs
|
||||
AndroidDevice(JNIEnv* env, std::string name)
|
||||
: m_sensor_event_listener(AddSensors(env)), m_source(AINPUT_SOURCE_SENSOR),
|
||||
m_controller_number(0), m_name(std::move(name))
|
||||
{
|
||||
}
|
||||
|
||||
~AndroidDevice()
|
||||
{
|
||||
if (m_sensor_event_listener)
|
||||
IDCache::GetEnvForThread()->DeleteGlobalRef(m_sensor_event_listener);
|
||||
}
|
||||
|
||||
std::string GetName() const override { return m_name; }
|
||||
|
||||
std::string GetSource() const override { return "Android"; }
|
||||
@ -519,6 +554,8 @@ public:
|
||||
return -3;
|
||||
}
|
||||
|
||||
jobject GetSensorEventListener() { return m_sensor_event_listener; }
|
||||
|
||||
private:
|
||||
void AddKeys(JNIEnv* env, jobject input_device)
|
||||
{
|
||||
@ -571,6 +608,35 @@ private:
|
||||
env->DeleteLocalRef(motion_ranges_list);
|
||||
}
|
||||
|
||||
jobject AddSensors(JNIEnv* env)
|
||||
{
|
||||
jobject sensor_event_listener =
|
||||
env->NewObject(s_sensor_event_listener_class, s_sensor_event_listener_constructor);
|
||||
|
||||
jobjectArray j_axis_names = reinterpret_cast<jobjectArray>(
|
||||
env->CallObjectMethod(sensor_event_listener, s_sensor_event_listener_get_axis_names));
|
||||
std::vector<std::string> axis_names = JStringArrayToVector(env, j_axis_names);
|
||||
env->DeleteLocalRef(j_axis_names);
|
||||
|
||||
jbooleanArray j_negative_axes = reinterpret_cast<jbooleanArray>(
|
||||
env->CallObjectMethod(sensor_event_listener, s_sensor_event_listener_get_negative_axes));
|
||||
jboolean* negative_axes = env->GetBooleanArrayElements(j_negative_axes, nullptr);
|
||||
|
||||
ASSERT(axis_names.size() == env->GetArrayLength(j_negative_axes));
|
||||
for (size_t i = 0; i < axis_names.size(); ++i)
|
||||
AddInput(new AndroidSensorAxis(axis_names[i], negative_axes[i]));
|
||||
|
||||
env->ReleaseBooleanArrayElements(j_negative_axes, negative_axes, 0);
|
||||
env->DeleteLocalRef(j_negative_axes);
|
||||
|
||||
jobject global_sensor_event_listener = env->NewGlobalRef(sensor_event_listener);
|
||||
|
||||
env->DeleteLocalRef(sensor_event_listener);
|
||||
|
||||
return global_sensor_event_listener;
|
||||
}
|
||||
|
||||
const jobject m_sensor_event_listener;
|
||||
const int m_source;
|
||||
const int m_controller_number;
|
||||
std::string m_name;
|
||||
@ -648,6 +714,22 @@ void Init()
|
||||
s_controller_interface_unregister_input_device_listener =
|
||||
env->GetStaticMethodID(s_controller_interface_class, "unregisterInputDeviceListener", "()V");
|
||||
|
||||
const jclass sensor_event_listener_class =
|
||||
env->FindClass("org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener");
|
||||
s_sensor_event_listener_class =
|
||||
reinterpret_cast<jclass>(env->NewGlobalRef(sensor_event_listener_class));
|
||||
s_sensor_event_listener_constructor =
|
||||
env->GetMethodID(s_sensor_event_listener_class, "<init>", "()V");
|
||||
s_sensor_event_listener_enable_sensor_events =
|
||||
env->GetMethodID(s_sensor_event_listener_class, "enableSensorEvents",
|
||||
"(Lorg/dolphinemu/dolphinemu/features/input/model/SensorEventRequester;)V");
|
||||
s_sensor_event_listener_disable_sensor_events =
|
||||
env->GetMethodID(s_sensor_event_listener_class, "disableSensorEvents", "()V");
|
||||
s_sensor_event_listener_get_axis_names =
|
||||
env->GetMethodID(s_sensor_event_listener_class, "getAxisNames", "()[Ljava/lang/String;");
|
||||
s_sensor_event_listener_get_negative_axes =
|
||||
env->GetMethodID(s_sensor_event_listener_class, "getNegativeAxes", "()[Z");
|
||||
|
||||
jintArray keycodes_array = CreateKeyCodesArray(env);
|
||||
s_keycodes_array = reinterpret_cast<jintArray>(env->NewGlobalRef(keycodes_array));
|
||||
env->DeleteLocalRef(keycodes_array);
|
||||
@ -669,6 +751,7 @@ void Shutdown()
|
||||
env->DeleteGlobalRef(s_key_event_class);
|
||||
env->DeleteGlobalRef(s_motion_event_class);
|
||||
env->DeleteGlobalRef(s_controller_interface_class);
|
||||
env->DeleteGlobalRef(s_sensor_event_listener_class);
|
||||
env->DeleteGlobalRef(s_keycodes_array);
|
||||
}
|
||||
|
||||
@ -694,6 +777,25 @@ static void AddDevice(JNIEnv* env, int device_id)
|
||||
s_device_id_to_device_qualifier.emplace(device_id, qualifier);
|
||||
}
|
||||
|
||||
static void AddSensorDevice(JNIEnv* env)
|
||||
{
|
||||
// Device sensors (accelerometer, etc.) aren't associated with any Android InputDevice.
|
||||
// Create an otherwise empty Dolphin input device so that they have somewhere to live.
|
||||
|
||||
auto device = std::make_shared<AndroidDevice>(env, "Device Sensors");
|
||||
|
||||
if (device->Inputs().empty() && device->Outputs().empty())
|
||||
return;
|
||||
|
||||
g_controller_interface.AddDevice(device);
|
||||
|
||||
Core::DeviceQualifier qualifier;
|
||||
qualifier.FromDevice(device.get());
|
||||
|
||||
INFO_LOG_FMT(CONTROLLERINTERFACE, "Added sensor device as {}", device->GetQualifiedName());
|
||||
s_sensor_device_qualifier = qualifier;
|
||||
}
|
||||
|
||||
void PopulateDevices()
|
||||
{
|
||||
INFO_LOG_FMT(CONTROLLERINTERFACE, "Android populating devices");
|
||||
@ -708,6 +810,8 @@ void PopulateDevices()
|
||||
AddDevice(env, device_ids[i]);
|
||||
env->ReleaseIntArrayElements(device_ids_array, device_ids, JNI_ABORT);
|
||||
env->DeleteLocalRef(device_ids_array);
|
||||
|
||||
AddSensorDevice(env);
|
||||
}
|
||||
|
||||
} // namespace ciface::Android
|
||||
@ -806,6 +910,56 @@ 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_dispatchSensorEvent(
|
||||
JNIEnv* env, jclass, jstring j_axis_name, jfloat value)
|
||||
{
|
||||
const std::shared_ptr<ciface::Core::Device> device =
|
||||
g_controller_interface.FindDevice(s_sensor_device_qualifier);
|
||||
if (!device)
|
||||
return;
|
||||
|
||||
const std::string axis_name = GetJString(env, j_axis_name);
|
||||
|
||||
for (ciface::Core::Device::Input* input : device->Inputs())
|
||||
{
|
||||
const std::string input_name = input->GetName();
|
||||
if (input_name == axis_name)
|
||||
{
|
||||
auto casted_input = static_cast<ciface::Android::AndroidInput*>(input);
|
||||
casted_input->SetState(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_enableSensorEvents(
|
||||
JNIEnv* env, jclass, jobject j_sensor_event_requester)
|
||||
{
|
||||
const std::shared_ptr<ciface::Core::Device> device =
|
||||
g_controller_interface.FindDevice(s_sensor_device_qualifier);
|
||||
if (!device)
|
||||
return;
|
||||
|
||||
env->CallVoidMethod(
|
||||
static_cast<ciface::Android::AndroidDevice*>(device.get())->GetSensorEventListener(),
|
||||
s_sensor_event_listener_enable_sensor_events, j_sensor_event_requester);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_disableSensorEvents(
|
||||
JNIEnv* env, jclass)
|
||||
{
|
||||
const std::shared_ptr<ciface::Core::Device> device =
|
||||
g_controller_interface.FindDevice(s_sensor_device_qualifier);
|
||||
if (!device)
|
||||
return;
|
||||
|
||||
env->CallVoidMethod(
|
||||
static_cast<ciface::Android::AndroidDevice*>(device.get())->GetSensorEventListener(),
|
||||
s_sensor_event_listener_disable_sensor_events);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_refreshDevices(JNIEnv* env,
|
||||
jclass)
|
||||
|
Loading…
x
Reference in New Issue
Block a user