Merge pull request #11385 from JosJuice/android-input-overhaul

Android input overhaul
This commit is contained in:
JosJuice
2023-03-11 12:37:44 +01:00
committed by GitHub
121 changed files with 5881 additions and 4990 deletions
@@ -1,104 +0,0 @@
[GCPad1]
Device = Android/0/Touchscreen
Buttons/A = `Button 0`
Buttons/B = `Button 1`
Buttons/Start = `Button 2`
Buttons/X = `Button 3`
Buttons/Y = `Button 4`
Buttons/Z = `Button 5`
D-Pad/Up = `Button 6`
D-Pad/Down = `Button 7`
D-Pad/Left = `Button 8`
D-Pad/Right = `Button 9`
Main Stick/Up = `Axis 11`
Main Stick/Down = `Axis 12`
Main Stick/Left = `Axis 13`
Main Stick/Right = `Axis 14`
C-Stick/Up = `Axis 16`
C-Stick/Down = `Axis 17`
C-Stick/Left = `Axis 18`
C-Stick/Right = `Axis 19`
Triggers/L = `Axis 20`
Triggers/R = `Axis 21`
Triggers/L-Analog = `Axis 20`
Triggers/R-Analog = `Axis 21`
Triggers/Threshold = 90,000000
Rumble/Motor = `Rumble 700`
[GCPad2]
Device = Android/1/Touchscreen
Buttons/A = `Button 0`
Buttons/B = `Button 1`
Buttons/Start = `Button 2`
Buttons/X = `Button 3`
Buttons/Y = `Button 4`
Buttons/Z = `Button 5`
D-Pad/Up = `Button 6`
D-Pad/Down = `Button 7`
D-Pad/Left = `Button 8`
D-Pad/Right = `Button 9`
Main Stick/Up = `Axis 11`
Main Stick/Down = `Axis 12`
Main Stick/Left = `Axis 13`
Main Stick/Right = `Axis 14`
C-Stick/Up = `Axis 16`
C-Stick/Down = `Axis 17`
C-Stick/Left = `Axis 18`
C-Stick/Right = `Axis 19`
Triggers/L = `Axis 20`
Triggers/R = `Axis 21`
Triggers/L-Analog = `Axis 20`
Triggers/R-Analog = `Axis 21`
Triggers/Threshold = 90,000000
Rumble/Motor = `Rumble 700`
[GCPad3]
Device = Android/2/Touchscreen
Buttons/A = `Button 0`
Buttons/B = `Button 1`
Buttons/Start = `Button 2`
Buttons/X = `Button 3`
Buttons/Y = `Button 4`
Buttons/Z = `Button 5`
D-Pad/Up = `Button 6`
D-Pad/Down = `Button 7`
D-Pad/Left = `Button 8`
D-Pad/Right = `Button 9`
Main Stick/Up = `Axis 11`
Main Stick/Down = `Axis 12`
Main Stick/Left = `Axis 13`
Main Stick/Right = `Axis 14`
C-Stick/Up = `Axis 16`
C-Stick/Down = `Axis 17`
C-Stick/Left = `Axis 18`
C-Stick/Right = `Axis 19`
Triggers/L = `Axis 20`
Triggers/R = `Axis 21`
Triggers/L-Analog = `Axis 20`
Triggers/R-Analog = `Axis 21`
Triggers/Threshold = 90,000000
Rumble/Motor = `Rumble 700`
[GCPad4]
Device = Android/3/Touchscreen
Buttons/A = `Button 0`
Buttons/B = `Button 1`
Buttons/Start = `Button 2`
Buttons/X = `Button 3`
Buttons/Y = `Button 4`
Buttons/Z = `Button 5`
D-Pad/Up = `Button 6`
D-Pad/Down = `Button 7`
D-Pad/Left = `Button 8`
D-Pad/Right = `Button 9`
Main Stick/Up = `Axis 11`
Main Stick/Down = `Axis 12`
Main Stick/Left = `Axis 13`
Main Stick/Right = `Axis 14`
C-Stick/Up = `Axis 16`
C-Stick/Down = `Axis 17`
C-Stick/Left = `Axis 18`
C-Stick/Right = `Axis 19`
Triggers/L = `Axis 20`
Triggers/R = `Axis 21`
Triggers/L-Analog = `Axis 20`
Triggers/R-Analog = `Axis 21`
Triggers/Threshold = 90,000000
Rumble/Motor = `Rumble 700`
File diff suppressed because it is too large Load Diff
@@ -1,144 +0,0 @@
[Profile]
Device = Android/4/Touchscreen
Buttons/A = `Button 100`
Buttons/B = `Button 101`
Buttons/- = `Button 102`
Buttons/+ = `Button 103`
Buttons/Home = `Button 104`
Buttons/1 = `Button 105`
Buttons/2 = `Button 106`
D-Pad/Up = `Button 107`
D-Pad/Down = `Button 108`
D-Pad/Left = `Button 109`
D-Pad/Right = `Button 110`
IR/Up = `Axis 112`
IR/Down = `Axis 113`
IR/Left = `Axis 114`
IR/Right = `Axis 115`
IR/Forward = `Axis 116`
IR/Backward = `Axis 117`
IR/Hide = `Button 118`
IR/Total Pitch = 20
IR/Total Yaw = 25
IR/Vertical Offset = 10
Swing/Up = `Axis 120`
Swing/Down = `Axis 121`
Swing/Left = `Axis 122`
Swing/Right = `Axis 123`
Swing/Forward = `Axis 124`
Swing/Backward = `Axis 125`
Tilt/Forward = `Axis 127`
Tilt/Backward = `Axis 128`
Tilt/Left = `Axis 129`
Tilt/Right = `Axis 130`
Tilt/Modifier = `Button 131`
Tilt/Modifier/Range = 50,000000
Shake/X = `Button 132`
Shake/Y = `Button 133`
Shake/Z = `Button 134`
Extension = Nunchuk
Nunchuk/Buttons/C = `Button 200`
Nunchuk/Buttons/Z = `Button 201`
Nunchuk/Stick/Up = `Axis 203`
Nunchuk/Stick/Down = `Axis 204`
Nunchuk/Stick/Left = `Axis 205`
Nunchuk/Stick/Right = `Axis 206`
Nunchuk/Swing/Up = `Axis 208`
Nunchuk/Swing/Down = `Axis 209`
Nunchuk/Swing/Left = `Axis 210`
Nunchuk/Swing/Right = `Axis 211`
Nunchuk/Swing/Forward = `Axis 212`
Nunchuk/Swing/Backward = `Axis 213`
Nunchuk/Tilt/Forward = `Axis 215`
Nunchuk/Tilt/Backward = `Axis 216`
Nunchuk/Tilt/Left = `Axis 217`
Nunchuk/Tilt/Right = `Axis 218`
Nunchuk/Tilt/Modifier = `Button 219`
Nunchuk/Tilt/Modifier/Range = 50,000000
Nunchuk/Shake/X = `Button 220`
Nunchuk/Shake/Y = `Button 221`
Nunchuk/Shake/Z = `Button 222`
Classic/Buttons/A = `Button 300`
Classic/Buttons/B = `Button 301`
Classic/Buttons/X = `Button 302`
Classic/Buttons/Y = `Button 303`
Classic/Buttons/- = `Button 304`
Classic/Buttons/+ = `Button 305`
Classic/Buttons/Home = `Button 306`
Classic/Buttons/ZL = `Button 307`
Classic/Buttons/ZR = `Button 308`
Classic/D-Pad/Up = `Button 309`
Classic/D-Pad/Down = `Button 310`
Classic/D-Pad/Left = `Button 311`
Classic/D-Pad/Right = `Button 312`
Classic/Left Stick/Up = `Axis 314`
Classic/Left Stick/Down = `Axis 315`
Classic/Left Stick/Left = `Axis 316`
Classic/Left Stick/Right = `Axis 317`
Classic/Right Stick/Up = `Axis 319`
Classic/Right Stick/Down = `Axis 320`
Classic/Right Stick/Left = `Axis 321`
Classic/Right Stick/Right = `Axis 322`
Classic/Triggers/L = `Axis 323`
Classic/Triggers/R = `Axis 324`
Classic/Triggers/Threshold = 90,000000
Guitar/Buttons/- = `Button 400`
Guitar/Buttons/+ = `Button 401`
Guitar/Frets/Green = `Button 402`
Guitar/Frets/Red = `Button 403`
Guitar/Frets/Yellow = `Button 404`
Guitar/Frets/Blue = `Button 405`
Guitar/Frets/Orange = `Button 406`
Guitar/Strum/Up = `Button 407`
Guitar/Strum/Down = `Button 408`
Guitar/Stick/Up = `Axis 410`
Guitar/Stick/Down = `Axis 411`
Guitar/Stick/Left = `Axis 412`
Guitar/Stick/Right = `Axis 413`
Guitar/Whammy/Bar = `Axis 414`
Drums/Buttons/- = `Button 500`
Drums/Buttons/+ = `Button 501`
Drums/Pads/Red = `Button 502`
Drums/Pads/Yellow = `Button 503`
Drums/Pads/Blue = `Button 504`
Drums/Pads/Green = `Button 505`
Drums/Pads/Orange = `Button 506`
Drums/Pads/Bass = `Button 507`
Drums/Stick/Up = `Axis 509`
Drums/Stick/Down = `Axis 510`
Drums/Stick/Left = `Axis 511`
Drums/Stick/Right = `Axis 512`
Turntable/Buttons/Green Left = `Button 600`
Turntable/Buttons/Red Left = `Button 601`
Turntable/Buttons/Blue Left = `Button 602`
Turntable/Buttons/Green Right = `Button 603`
Turntable/Buttons/Red Right = `Button 604`
Turntable/Buttons/Blue Right = `Button 605`
Turntable/Buttons/- = `Button 606`
Turntable/Buttons/+ = `Button 607`
Turntable/Buttons/Home = `Button 608`
Turntable/Buttons/Euphoria = `Button 609`
Turntable/Table Left/Left = `Axis 611`
Turntable/Table Left/Right = `Axis 612`
Turntable/Table Right/Left = `Axis 614`
Turntable/Table Right/Right = `Axis 615`
Turntable/Stick/Up = `Axis 617`
Turntable/Stick/Down = `Axis 618`
Turntable/Stick/Left = `Axis 619`
Turntable/Stick/Right = `Axis 620`
Turntable/Effect/Dial = `Axis 621`
Turntable/Crossfade/Left = `Axis 623`
Turntable/Crossfade/Right = `Axis 624`
IMUAccelerometer/Left = `Axis 625`
IMUAccelerometer/Right = `Axis 626`
IMUAccelerometer/Forward = `Axis 627`
IMUAccelerometer/Backward = `Axis 628`
IMUAccelerometer/Up = `Axis 629`
IMUAccelerometer/Down = `Axis 630`
IMUGyroscope/Pitch Up = `Axis 631`
IMUGyroscope/Pitch Down = `Axis 632`
IMUGyroscope/Roll Left = `Axis 633`
IMUGyroscope/Roll Right = `Axis 634`
IMUGyroscope/Yaw Left = `Axis 635`
IMUGyroscope/Yaw Right = `Axis 636`
Rumble/Motor = `Rumble 700`
@@ -18,7 +18,6 @@ import org.dolphinemu.dolphinemu.activities.EmulationActivity;
import org.dolphinemu.dolphinemu.dialogs.AlertMessage;
import org.dolphinemu.dolphinemu.utils.CompressCallback;
import org.dolphinemu.dolphinemu.utils.Log;
import org.dolphinemu.dolphinemu.utils.Rumble;
import java.lang.ref.WeakReference;
import java.util.LinkedHashMap;
@@ -44,7 +43,7 @@ public final class NativeLibrary
}
/**
* Button type for use in onTouchEvent
* Button type, for legacy use only
*/
public static final class ButtonType
{
@@ -234,52 +233,6 @@ public final class NativeLibrary
// Disallows instantiation.
}
/**
* Default touchscreen device
*/
public static final String TouchScreenDevice = "Touchscreen";
/**
* Handles button press events for a gamepad.
*
* @param Device The input descriptor of the gamepad.
* @param Button Key code identifying which button was pressed.
* @param Action Mask identifying which action is happening (button pressed down, or button released).
* @return If we handled the button press.
*/
public static native boolean onGamePadEvent(String Device, int Button, int Action);
/**
* Handles gamepad movement events.
*
* @param Device The device ID of the gamepad.
* @param Axis The axis ID
* @param Value The value of the axis represented by the given ID.
*/
public static native void onGamePadMoveEvent(String Device, int Axis, float Value);
/**
* Rumble sent from native. Currently only supports phone rumble.
*
* @param padID Ignored for now. Future use would be to pass rumble to a connected controller
* @param state Ignored for now since phone rumble can't just be 'turned' on/off
*/
@Keep
public static void rumble(int padID, double state)
{
final EmulationActivity emulationActivity = sEmulationActivity.get();
if (emulationActivity == null)
{
Log.warning("[NativeLibrary] EmulationActivity is null");
return;
}
Rumble.checkRumble(padID, state);
}
public static native void SetMotionSensorsEnabled(boolean accelerometerEnabled,
boolean gyroscopeEnabled);
/**
* Gets the Dolphin version string.
*
@@ -451,8 +404,6 @@ public final class NativeLibrary
*/
public static native void RefreshWiimotes();
public static native void ReloadWiimoteConfig();
public static native LinkedHashMap<String, String> GetLogTypeNames();
public static native void ReloadLoggerConfig();
@@ -1,171 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.dialogs;
import android.content.Context;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import org.dolphinemu.dolphinemu.features.settings.model.view.InputBindingSetting;
import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter;
import org.dolphinemu.dolphinemu.utils.ControllerMappingHelper;
import org.dolphinemu.dolphinemu.utils.Log;
import java.util.ArrayList;
import java.util.List;
/**
* {@link AlertDialog} derivative that listens for
* motion events from controllers and joysticks.
*/
public final class MotionAlertDialog extends AlertDialog
{
// The selected input preference
private final InputBindingSetting setting;
private final ArrayList<Float> mPreviousValues = new ArrayList<>();
private int mPrevDeviceId = 0;
private boolean mWaitingForEvent = true;
private SettingsAdapter mAdapter;
/**
* Constructor
*
* @param context The current {@link Context}.
* @param setting The Preference to show this dialog for.
*/
public MotionAlertDialog(Context context, InputBindingSetting setting, SettingsAdapter adapter)
{
super(context);
this.setting = setting;
mAdapter = adapter;
}
public boolean onKeyEvent(int keyCode, KeyEvent event)
{
Log.debug("[MotionAlertDialog] Received key event: " + event.getAction());
if (event.getAction() == KeyEvent.ACTION_UP)
{
if (!ControllerMappingHelper.shouldKeyBeIgnored(event.getDevice(), keyCode))
{
setting.onKeyInput(mAdapter.getSettings(), event);
dismiss();
}
// Even if we ignore the key, we still consume it. Thus return true regardless.
return true;
}
return false;
}
@Override
public boolean onKeyLongPress(int keyCode, @NonNull KeyEvent event)
{
// Intended for devices with no touchscreen or mouse
if (keyCode == KeyEvent.KEYCODE_BACK)
{
setting.clearValue(mAdapter.getSettings());
dismiss();
return true;
}
return super.onKeyLongPress(keyCode, event);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event)
{
// Handle this key if we care about it, otherwise pass it down the framework
return onKeyEvent(event.getKeyCode(), event) || super.dispatchKeyEvent(event);
}
@Override
public boolean dispatchGenericMotionEvent(@NonNull MotionEvent event)
{
// Handle this event if we care about it, otherwise pass it down the framework
return onMotionEvent(event) || super.dispatchGenericMotionEvent(event);
}
private boolean onMotionEvent(MotionEvent event)
{
if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) == 0)
return false;
if (event.getAction() != MotionEvent.ACTION_MOVE)
return false;
InputDevice input = event.getDevice();
List<InputDevice.MotionRange> motionRanges = input.getMotionRanges();
if (input.getId() != mPrevDeviceId)
{
mPreviousValues.clear();
}
mPrevDeviceId = input.getId();
boolean firstEvent = mPreviousValues.isEmpty();
int numMovedAxis = 0;
float axisMoveValue = 0.0f;
InputDevice.MotionRange lastMovedRange = null;
char lastMovedDir = '?';
if (mWaitingForEvent)
{
for (int i = 0; i < motionRanges.size(); i++)
{
InputDevice.MotionRange range = motionRanges.get(i);
int axis = range.getAxis();
float origValue = event.getAxisValue(axis);
float value = ControllerMappingHelper.scaleAxis(input, axis, origValue);
if (firstEvent)
{
mPreviousValues.add(value);
}
else
{
float previousValue = mPreviousValues.get(i);
// Only handle the axes that are not neutral (more than 0.5)
// but ignore any axis that has a constant value (e.g. always 1)
if (Math.abs(value) > 0.5f && value != previousValue)
{
// It is common to have multiple axes with the same physical input. For example,
// shoulder butters are provided as both AXIS_LTRIGGER and AXIS_BRAKE.
// To handle this, we ignore an axis motion that's the exact same as a motion
// we already saw. This way, we ignore axes with two names, but catch the case
// where a joystick is moved in two directions.
// ref: bottom of https://developer.android.com/training/game-controllers/controller-input.html
if (value != axisMoveValue)
{
axisMoveValue = value;
numMovedAxis++;
lastMovedRange = range;
lastMovedDir = value < 0.0f ? '-' : '+';
}
}
// Special case for d-pads (axis value jumps between 0 and 1 without any values
// in between). Without this, the user would need to press the d-pad twice
// due to the first press being caught by the "if (firstEvent)" case further up.
else if (Math.abs(value) < 0.25f && Math.abs(previousValue) > 0.75f)
{
numMovedAxis++;
lastMovedRange = range;
lastMovedDir = previousValue < 0.0f ? '-' : '+';
}
}
mPreviousValues.set(i, value);
}
// If only one axis moved, that's the winner.
if (numMovedAxis == 1)
{
mWaitingForEvent = false;
setting.onMotionInput(mAdapter.getSettings(), input, lastMovedRange, lastMovedDir);
dismiss();
}
}
return true;
}
}
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.input.model;
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.ControlGroup;
import org.dolphinemu.dolphinemu.features.settings.model.AbstractBooleanSetting;
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
public class ControlGroupEnabledSetting implements AbstractBooleanSetting
{
private final ControlGroup mControlGroup;
public ControlGroupEnabledSetting(ControlGroup controlGroup)
{
mControlGroup = controlGroup;
}
@Override
public boolean getBoolean(Settings settings)
{
return mControlGroup.getEnabled();
}
@Override
public void setBoolean(Settings settings, boolean newValue)
{
mControlGroup.setEnabled(newValue);
}
@Override
public boolean isOverridden(Settings settings)
{
return false;
}
@Override
public boolean isRuntimeEditable()
{
return true;
}
@Override
public boolean delete(Settings settings)
{
boolean newValue = mControlGroup.getDefaultEnabledValue() != ControlGroup.DEFAULT_ENABLED_NO;
mControlGroup.setEnabled(newValue);
return true;
}
}
@@ -0,0 +1,178 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.input.model;
import android.content.Context;
import android.hardware.input.InputManager;
import android.os.Build;
import android.os.Handler;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorManager;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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.
*
* @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.
*/
public static native boolean dispatchKeyEvent(KeyEvent event);
/**
* Activities which want to pass on inputs to native code
* should call this in their own dispatchGenericMotionEvent method.
*
* @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.
*/
public static native boolean dispatchGenericMotionEvent(MotionEvent event);
/**
* {@link DolphinSensorEventListener} calls this for each axis of a received SensorEvent.
*
* @return true if the emulator core seems to be interested in this event.
* false if the sensor can be suspended to save battery.
*/
public static native boolean dispatchSensorEvent(String deviceQualifier, String axisName,
float value);
/**
* Called when a sensor is suspended or unsuspended.
*
* @param deviceQualifier A string used by native code for uniquely identifying devices.
* @param axisNames The name of all axes for the sensor.
* @param suspended Whether the sensor is now suspended.
*/
public static native void notifySensorSuspendedState(String deviceQualifier, String[] axisNames,
boolean suspended);
/**
* Rescans for input devices.
*/
public static native void refreshDevices();
public static native String[] getAllDeviceStrings();
@Nullable
public static native CoreDevice getDevice(String deviceString);
@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;
}
}
@Keep @NonNull
private static DolphinVibratorManager getVibratorManager(InputDevice device)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
{
return new DolphinVibratorManagerPassthrough(device.getVibratorManager());
}
else
{
return new DolphinVibratorManagerCompat(device.getVibrator());
}
}
@Keep @NonNull
private static DolphinVibratorManager getSystemVibratorManager()
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
{
VibratorManager vibratorManager = (VibratorManager)
DolphinApplication.getAppContext().getSystemService(Context.VIBRATOR_MANAGER_SERVICE);
if (vibratorManager != null)
return new DolphinVibratorManagerPassthrough(vibratorManager);
}
Vibrator vibrator = (Vibrator)
DolphinApplication.getAppContext().getSystemService(Context.VIBRATOR_SERVICE);
return new DolphinVibratorManagerCompat(vibrator);
}
@Keep
private static void vibrate(@NonNull Vibrator vibrator)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
vibrator.vibrate(VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
}
else
{
vibrator.vibrate(100);
}
}
}
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.input.model;
import androidx.annotation.Keep;
/**
* Represents a C++ ciface::Core::Device.
*/
public final class CoreDevice
{
/**
* Represents a C++ ciface::Core::Device::Control.
*
* This class is non-static to ensure that the CoreDevice parent does not get garbage collected
* while a Control is still accessible. (CoreDevice's finalizer may delete the native controls.)
*/
@SuppressWarnings("InnerClassMayBeStatic")
public final class Control
{
@Keep
private final long mPointer;
@Keep
private Control(long pointer)
{
mPointer = pointer;
}
public native String getName();
}
@Keep
private final long mPointer;
@Keep
private CoreDevice(long pointer)
{
mPointer = pointer;
}
@Override
protected native void finalize();
public native Control[] getInputs();
public native Control[] getOutputs();
}
@@ -0,0 +1,440 @@
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.InputDevice;
import android.view.Surface;
import androidx.annotation.Keep;
import org.dolphinemu.dolphinemu.DolphinApplication;
import org.dolphinemu.dolphinemu.utils.Log;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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 boolean isSuspended = true;
public SensorDetails(int sensorType, String[] axisNames, AxisSetDetails[] axisSetDetails)
{
this.sensorType = sensorType;
this.axisNames = axisNames;
this.axisSetDetails = axisSetDetails;
}
}
private static int sDeviceRotation = Surface.ROTATION_0;
private final SensorManager mSensorManager;
private final HashMap<Sensor, SensorDetails> mSensorDetails = new HashMap<>();
private final boolean mRotateCoordinatesForScreenOrientation;
private String mDeviceQualifier = "";
// 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);
mRotateCoordinatesForScreenOrientation = true;
addSensors();
}
@Keep
public DolphinSensorEventListener(InputDevice inputDevice)
{
mRotateCoordinatesForScreenOrientation = false;
if (Build.VERSION.SDK_INT >= 31)
{
mSensorManager = inputDevice.getSensorManager();
// TODO: There is a bug where after suspending sensors, onSensorChanged can get called for
// a sensor that we never registered as a listener for. The way our code is currently written,
// this causes a NullPointerException, but if we checked for null we would instead have the
// problem of being spammed with onSensorChanged calls even though the sensor shouldn't be
// enabled. For now, let's comment out the ability to use InputDevice sensors.
//addSensors();
}
else
{
mSensorManager = null;
}
}
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;
boolean keepSensorAlive = false;
while (eventAxisIndex < values.length && detailsAxisIndex < axisNames.length)
{
if (detailsAxisSetIndex < axisSetDetails.length &&
axisSetDetails[detailsAxisSetIndex].firstAxisOfSet == eventAxisIndex)
{
int rotation = Surface.ROTATION_0;
if (mRotateCoordinatesForScreenOrientation &&
axisSetDetails[detailsAxisSetIndex].axisSetType == AXIS_SET_TYPE_DEVICE_COORDINATES)
{
rotation = sDeviceRotation;
}
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];
keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
axisNames[detailsAxisIndex], x);
keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
axisNames[detailsAxisIndex + 1], x);
keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
axisNames[detailsAxisIndex + 2], y);
keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
axisNames[detailsAxisIndex + 3], y);
keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
axisNames[detailsAxisIndex + 4], z);
keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
axisNames[detailsAxisIndex + 5], z);
eventAxisIndex += 3;
detailsAxisIndex += 6;
detailsAxisSetIndex++;
}
else
{
keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
axisNames[detailsAxisIndex], values[eventAxisIndex]);
eventAxisIndex++;
detailsAxisIndex++;
}
}
if (!keepSensorAlive)
{
setSensorSuspended(sensorEvent.sensor, sensorDetails, true);
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int i)
{
// We don't care about this
}
/**
* The device qualifier set here will be passed on to native code,
* for the purpose of letting native code identify which device this object belongs to.
*/
@Keep
public void setDeviceQualifier(String deviceQualifier)
{
mDeviceQualifier = deviceQualifier;
}
/**
* If a sensor has been suspended to save battery, this unsuspends it.
* If the sensor isn't currently suspended, nothing happens.
*
* @param axisName The name of any of the sensor's axes.
*/
@Keep
public void requestUnsuspendSensor(String axisName)
{
for (Map.Entry<Sensor, SensorDetails> entry : mSensorDetails.entrySet())
{
if (Arrays.asList(entry.getValue().axisNames).contains(axisName))
{
setSensorSuspended(entry.getKey(), entry.getValue(), false);
}
}
}
private void setSensorSuspended(Sensor sensor, SensorDetails sensorDetails, boolean suspend)
{
boolean changeOccurred = false;
synchronized (sensorDetails)
{
if (sensorDetails.isSuspended != suspend)
{
ControllerInterface.notifySensorSuspendedState(mDeviceQualifier, sensorDetails.axisNames,
suspend);
if (suspend)
mSensorManager.unregisterListener(this, sensor);
else
mSensorManager.registerListener(this, sensor, SAMPLING_PERIOD_US);
sensorDetails.isSuspended = suspend;
changeOccurred = true;
}
}
if (changeOccurred)
{
Log.info((suspend ? "Suspended sensor " : "Unsuspended sensor ") + sensor.getName());
}
}
@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;
}
/**
* Should be called when an activity or other component that uses sensor events is resumed.
*
* Sensor events that contain device coordinates will have the coordinates rotated by the value
* passed to this function.
*
* @param deviceRotation The current rotation of the device (i.e. rotation of the default display)
*/
public static void setDeviceRotation(int deviceRotation)
{
sDeviceRotation = deviceRotation;
}
}
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.input.model;
import android.os.Vibrator;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
/**
* A wrapper around {@link android.os.VibratorManager}, for backwards compatibility.
*/
public interface DolphinVibratorManager
{
@Keep @NonNull
Vibrator getVibrator(int vibratorId);
@Keep @NonNull
int[] getVibratorIds();
}
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.input.model;
import android.os.Vibrator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public final class DolphinVibratorManagerCompat implements DolphinVibratorManager
{
private final Vibrator mVibrator;
private final int[] mIds;
public DolphinVibratorManagerCompat(@Nullable Vibrator vibrator)
{
mVibrator = vibrator;
mIds = vibrator != null && vibrator.hasVibrator() ? new int[]{0} : new int[]{};
}
@Override @NonNull
public Vibrator getVibrator(int vibratorId)
{
if (vibratorId > mIds.length)
throw new IndexOutOfBoundsException();
return mVibrator;
}
@Override @NonNull
public int[] getVibratorIds()
{
return mIds;
}
}
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.input.model;
import android.os.Build;
import android.os.Vibrator;
import android.os.VibratorManager;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
@RequiresApi(api = Build.VERSION_CODES.S)
public final class DolphinVibratorManagerPassthrough implements DolphinVibratorManager
{
private final VibratorManager mVibratorManager;
public DolphinVibratorManagerPassthrough(@NonNull VibratorManager vibratorManager)
{
mVibratorManager = vibratorManager;
}
@Override @NonNull
public Vibrator getVibrator(int vibratorId)
{
return mVibratorManager.getVibrator(vibratorId);
}
@Override @NonNull
public int[] getVibratorIds()
{
return mVibratorManager.getVibratorIds();
}
}
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.input.model;
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting;
import org.dolphinemu.dolphinemu.features.settings.model.AbstractBooleanSetting;
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
public class InputMappingBooleanSetting implements AbstractBooleanSetting
{
private final NumericSetting mNumericSetting;
public InputMappingBooleanSetting(NumericSetting numericSetting)
{
mNumericSetting = numericSetting;
}
@Override
public boolean getBoolean(Settings settings)
{
return mNumericSetting.getBooleanValue();
}
@Override
public void setBoolean(Settings settings, boolean newValue)
{
mNumericSetting.setBooleanValue(newValue);
}
@Override
public boolean isOverridden(Settings settings)
{
return false;
}
@Override
public boolean isRuntimeEditable()
{
return true;
}
@Override
public boolean delete(Settings settings)
{
mNumericSetting.setBooleanValue(mNumericSetting.getBooleanDefaultValue());
return true;
}
}
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.input.model;
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting;
import org.dolphinemu.dolphinemu.features.settings.model.AbstractFloatSetting;
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
// Yes, floats are not the same thing as doubles... They're close enough, though
public class InputMappingDoubleSetting implements AbstractFloatSetting
{
private final NumericSetting mNumericSetting;
public InputMappingDoubleSetting(NumericSetting numericSetting)
{
mNumericSetting = numericSetting;
}
@Override
public float getFloat(Settings settings)
{
return (float) mNumericSetting.getDoubleValue();
}
@Override
public void setFloat(Settings settings, float newValue)
{
mNumericSetting.setDoubleValue(newValue);
}
@Override
public boolean isOverridden(Settings settings)
{
return false;
}
@Override
public boolean isRuntimeEditable()
{
return true;
}
@Override
public boolean delete(Settings settings)
{
mNumericSetting.setDoubleValue(mNumericSetting.getDoubleDefaultValue());
return true;
}
}
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.input.model;
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting;
import org.dolphinemu.dolphinemu.features.settings.model.AbstractIntSetting;
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
public class InputMappingIntSetting implements AbstractIntSetting
{
private final NumericSetting mNumericSetting;
public InputMappingIntSetting(NumericSetting numericSetting)
{
mNumericSetting = numericSetting;
}
@Override
public int getInt(Settings settings)
{
return mNumericSetting.getIntValue();
}
@Override
public void setInt(Settings settings, int newValue)
{
mNumericSetting.setIntValue(newValue);
}
@Override
public boolean isOverridden(Settings settings)
{
return false;
}
@Override
public boolean isRuntimeEditable()
{
return true;
}
@Override
public boolean delete(Settings settings)
{
mNumericSetting.setIntValue(mNumericSetting.getIntDefaultValue());
return true;
}
}
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.input.model;
import androidx.annotation.NonNull;
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController;
public final class MappingCommon
{
private MappingCommon()
{
}
/**
* Waits until the user presses one or more inputs or until a timeout,
* then returns the pressed inputs.
*
* When this is being called, a separate thread must be calling ControllerInterface's
* dispatchKeyEvent and dispatchGenericMotionEvent, otherwise no inputs will be registered.
*
* @param controller The device to detect inputs from.
* @param allDevices Whether to also detect inputs from devices other than the specified one.
* @return The input(s) pressed by the user in the form of an InputCommon expression,
* or an empty string if there were no inputs.
*/
public static native String detectInput(@NonNull EmulatedController controller,
boolean allDevices);
public static native String getExpressionForControl(String control, String device,
String defaultDevice);
public static native void save();
}
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.input.model.controlleremu;
import androidx.annotation.Keep;
/**
* Represents a C++ ControllerEmu::Control.
*
* The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed
* in C++ is undefined behavior!
*/
public class Control
{
@Keep
private final long mPointer;
@Keep
private Control(long pointer)
{
mPointer = pointer;
}
public native String getUiName();
public native ControlReference getControlReference();
}
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.input.model.controlleremu;
import androidx.annotation.Keep;
/**
* Represents a C++ ControllerEmu::ControlGroup.
*
* The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed
* in C++ is undefined behavior!
*/
public class ControlGroup
{
public static final int TYPE_OTHER = 0;
public static final int TYPE_STICK = 1;
public static final int TYPE_MIXED_TRIGGERS = 2;
public static final int TYPE_BUTTONS = 3;
public static final int TYPE_FORCE = 4;
public static final int TYPE_ATTACHMENTS = 5;
public static final int TYPE_TILT = 6;
public static final int TYPE_CURSOR = 7;
public static final int TYPE_TRIGGERS = 8;
public static final int TYPE_SLIDER = 9;
public static final int TYPE_SHAKE = 10;
public static final int TYPE_IMU_ACCELEROMETER = 11;
public static final int TYPE_IMU_GYROSCOPE = 12;
public static final int TYPE_IMU_CURSOR = 13;
public static final int DEFAULT_ENABLED_ALWAYS = 0;
public static final int DEFAULT_ENABLED_YES = 1;
public static final int DEFAULT_ENABLED_NO = 2;
@Keep
private final long mPointer;
@Keep
private ControlGroup(long pointer)
{
mPointer = pointer;
}
public native String getUiName();
public native int getGroupType();
public native int getDefaultEnabledValue();
public native boolean getEnabled();
public native void setEnabled(boolean value);
public native int getControlCount();
public native Control getControl(int i);
public native int getNumericSettingCount();
public native NumericSetting getNumericSetting(int i);
/**
* If getGroupType returns TYPE_ATTACHMENTS, this returns the attachment selection setting.
* Otherwise, undefined behavior!
*/
public native NumericSetting getAttachmentSetting();
}
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.input.model.controlleremu;
import androidx.annotation.Keep;
import androidx.annotation.Nullable;
/**
* Represents a C++ ControlReference.
*
* The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed
* in C++ is undefined behavior!
*/
public class ControlReference
{
@Keep
private final long mPointer;
@Keep
private ControlReference(long pointer)
{
mPointer = pointer;
}
public native double getState();
public native String getExpression();
/**
* Sets the expression for this control reference.
*
* @param expr The new expression
* @return null on success, a human-readable error on failure
*/
@Nullable
public native String setExpression(String expr);
public native boolean isInput();
}

Some files were not shown because too many files have changed in this diff Show More