mirror of
https://github.com/izzy2lost/dolphin.git
synced 2026-06-19 01:16:48 -07:00
Merge pull request #11385 from JosJuice/android-input-overhaul
Android input overhaul
This commit is contained in:
@@ -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();
|
||||
|
||||
+76
-249
File diff suppressed because it is too large
Load Diff
-171
@@ -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;
|
||||
}
|
||||
}
|
||||
+50
@@ -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;
|
||||
}
|
||||
}
|
||||
+178
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
@@ -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();
|
||||
}
|
||||
+440
@@ -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;
|
||||
}
|
||||
}
|
||||
+20
@@ -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();
|
||||
}
|
||||
+35
@@ -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;
|
||||
}
|
||||
}
|
||||
+33
@@ -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();
|
||||
}
|
||||
}
|
||||
+48
@@ -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;
|
||||
}
|
||||
}
|
||||
+49
@@ -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;
|
||||
}
|
||||
}
|
||||
+48
@@ -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;
|
||||
}
|
||||
}
|
||||
+34
@@ -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();
|
||||
}
|
||||
+27
@@ -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();
|
||||
}
|
||||
+66
@@ -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();
|
||||
}
|
||||
+39
@@ -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
Reference in New Issue
Block a user