diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b301979c..a43a2e23 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -29,6 +29,12 @@
+
+
= Build.VERSION_CODES.S) {
@@ -416,6 +418,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
LimeLog.info("Not reserving a controller number");
context.controllerNumber = 0;
}
+
+ // If the gamepad doesn't have motion sensors, use the on-device sensors as a fallback for player 1
+ if (prefConfig.gamepadMotionSensorsFallbackToDevice && context.controllerNumber == 0 && devContext.sensorManager == null) {
+ devContext.sensorManager = deviceSensorManager;
+ }
}
else {
if (prefConfig.multiController) {
@@ -648,6 +655,15 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
}
}
+ // On Android 12, we can try to use the InputDevice's sensors. This may not work if the
+ // Linux kernel version doesn't have motion sensor support, which is common for third-party
+ // gamepads.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && prefConfig.gamepadMotionSensors) {
+ if (dev.getSensorManager().getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null || dev.getSensorManager().getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null) {
+ context.sensorManager = dev.getSensorManager();
+ }
+ }
+
// Detect if the gamepad has Mode and Select buttons according to the Android key layouts.
// We do this first because other codepaths below may override these defaults.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
@@ -1955,11 +1971,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
}
public void handleSetMotionEventState(final short controllerNumber, final byte motionType, short reportRateHz) {
- // Don't use motion sensors if the user turned them off
- if (!prefConfig.gamepadMotionSensors) {
- return;
- }
-
// Report rate is restricted to <= 200 Hz without the HIGH_SAMPLING_RATE_SENSORS permission
reportRateHz = (short) Math.min(200, reportRateHz);
@@ -2004,51 +2015,52 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
public void onAccuracyChanged(Sensor sensor, int accuracy) {}
};
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- for (int i = 0; i < inputDeviceContexts.size(); i++) {
- InputDeviceContext deviceContext = inputDeviceContexts.valueAt(i);
+ for (int i = 0; i < inputDeviceContexts.size(); i++) {
+ InputDeviceContext deviceContext = inputDeviceContexts.valueAt(i);
- if (deviceContext.controllerNumber == controllerNumber) {
- SensorManager sm = deviceContext.inputDevice.getSensorManager();
-
- switch (motionType) {
- case MoonBridge.LI_MOTION_TYPE_ACCEL:
- if (deviceContext.accelListener != null) {
- sm.unregisterListener(deviceContext.accelListener);
- deviceContext.accelListener = null;
- }
-
- // Enable the accelerometer if requested
- Sensor accelSensor = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER, false);
- if (reportRateHz != 0 && accelSensor != null) {
- sm.registerListener(newSensorListener, accelSensor, 1000000 / reportRateHz);
- deviceContext.accelListener = newSensorListener;
- deviceContext.accelReportRateHz = reportRateHz;
- }
- else {
- deviceContext.accelReportRateHz = 0;
- }
- break;
- case MoonBridge.LI_MOTION_TYPE_GYRO:
- if (deviceContext.gyroListener != null) {
- sm.unregisterListener(deviceContext.gyroListener);
- deviceContext.gyroListener = null;
- }
-
- // Enable the gyroscope if requested
- Sensor gyroSensor = sm.getDefaultSensor(Sensor.TYPE_GYROSCOPE, false);
- if (reportRateHz != 0 && gyroSensor != null) {
- sm.registerListener(newSensorListener, gyroSensor,1000000 / reportRateHz);
- deviceContext.gyroListener = newSensorListener;
- deviceContext.gyroReportRateHz = reportRateHz;
- }
- else {
- deviceContext.gyroReportRateHz = 0;
- }
- break;
- }
- break;
+ if (deviceContext.controllerNumber == controllerNumber) {
+ SensorManager sm = deviceContext.sensorManager;
+ if (sm == null) {
+ continue;
}
+
+ switch (motionType) {
+ case MoonBridge.LI_MOTION_TYPE_ACCEL:
+ if (deviceContext.accelListener != null) {
+ sm.unregisterListener(deviceContext.accelListener);
+ deviceContext.accelListener = null;
+ }
+
+ // Enable the accelerometer if requested
+ Sensor accelSensor = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER, false);
+ if (reportRateHz != 0 && accelSensor != null) {
+ sm.registerListener(newSensorListener, accelSensor, 1000000 / reportRateHz);
+ deviceContext.accelListener = newSensorListener;
+ deviceContext.accelReportRateHz = reportRateHz;
+ }
+ else {
+ deviceContext.accelReportRateHz = 0;
+ }
+ break;
+ case MoonBridge.LI_MOTION_TYPE_GYRO:
+ if (deviceContext.gyroListener != null) {
+ sm.unregisterListener(deviceContext.gyroListener);
+ deviceContext.gyroListener = null;
+ }
+
+ // Enable the gyroscope if requested
+ Sensor gyroSensor = sm.getDefaultSensor(Sensor.TYPE_GYROSCOPE, false);
+ if (reportRateHz != 0 && gyroSensor != null) {
+ sm.registerListener(newSensorListener, gyroSensor,1000000 / reportRateHz);
+ deviceContext.gyroListener = newSensorListener;
+ deviceContext.gyroReportRateHz = reportRateHz;
+ }
+ else {
+ deviceContext.gyroReportRateHz = 0;
+ }
+ break;
+ }
+ break;
}
}
}
@@ -2663,6 +2675,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
public short lowFreqMotor, highFreqMotor;
public short leftTriggerMotor, rightTriggerMotor;
+ public SensorManager sensorManager;
public SensorEventListener gyroListener;
public short gyroReportRateHz;
public SensorEventListener accelListener;
@@ -2743,14 +2756,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
vibrator.cancel();
}
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- if (gyroListener != null) {
- inputDevice.getSensorManager().unregisterListener(gyroListener);
- }
- if (accelListener != null) {
- inputDevice.getSensorManager().unregisterListener(accelListener);
- }
+ if (gyroListener != null) {
+ sensorManager.unregisterListener(gyroListener);
+ }
+ if (accelListener != null) {
+ sensorManager.unregisterListener(accelListener);
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (lightsSession != null) {
lightsSession.close();
}
@@ -2820,13 +2833,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
capabilities |= MoonBridge.LI_CCAP_RUMBLE;
}
- if (inputDevice.getSensorManager().getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null) {
- capabilities |= MoonBridge.LI_CCAP_ACCEL;
- }
- if (inputDevice.getSensorManager().getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null) {
- capabilities |= MoonBridge.LI_CCAP_GYRO;
- }
-
if (inputDevice.getBatteryState().isPresent()) {
capabilities |= MoonBridge.LI_CCAP_BATTERY_STATE;
}
@@ -2848,6 +2854,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
capabilities |= MoonBridge.LI_CCAP_ANALOG_TRIGGERS;
}
+ // Report sensors if the input device has them or we're using built-in sensors for a built-in controller
+ if (sensorManager != null && sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null) {
+ capabilities |= MoonBridge.LI_CCAP_ACCEL;
+ }
+ if (sensorManager != null && sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null) {
+ capabilities |= MoonBridge.LI_CCAP_GYRO;
+ }
+
// We can perform basic rumble with any vibrator
if (vibrator != null) {
capabilities |= MoonBridge.LI_CCAP_RUMBLE;
@@ -2878,15 +2892,15 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
public void migrateContext(InputDeviceContext oldContext) {
// Take ownership of the sensor and light sessions
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- this.gyroReportRateHz = oldContext.gyroReportRateHz;
- this.accelReportRateHz = oldContext.accelReportRateHz;
this.lightsSession = oldContext.lightsSession;
- this.gyroListener = oldContext.gyroListener;
- this.accelListener = oldContext.accelListener;
oldContext.lightsSession = null;
- oldContext.gyroListener = null;
- oldContext.accelListener = null;
}
+ this.gyroListener = oldContext.gyroListener;
+ this.accelListener = oldContext.accelListener;
+ this.gyroReportRateHz = oldContext.gyroReportRateHz;
+ this.accelReportRateHz = oldContext.accelReportRateHz;
+ oldContext.gyroListener = null;
+ oldContext.accelListener = null;
// Don't release the controller number, because we will carry it over if it is present.
// We also want to make sure the change is invisible to the host PC to avoid an add/remove
@@ -2898,6 +2912,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
this.reservedControllerNumber = oldContext.reservedControllerNumber;
this.controllerNumber = oldContext.controllerNumber;
+ // We may have set this device to use the built-in sensor manager. If so, do that again.
+ if (oldContext.sensorManager == deviceSensorManager) {
+ this.sensorManager = deviceSensorManager;
+ }
+
// Refresh battery state and start the battery state polling again
sendControllerBatteryPacket(this);
handler.postDelayed(batteryStateUpdateRunnable, BATTERY_RECHECK_INTERVAL_MS);
@@ -2905,20 +2924,18 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
public void disableSensors() {
// Unregister all sensor listeners
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- if (gyroListener != null) {
- inputDevice.getSensorManager().unregisterListener(gyroListener);
- gyroListener = null;
+ if (gyroListener != null) {
+ sensorManager.unregisterListener(gyroListener);
+ gyroListener = null;
- // Send a gyro event to ensure the virtual controller is stationary
- conn.sendControllerMotionEvent((byte) controllerNumber, MoonBridge.LI_MOTION_TYPE_GYRO, 0.f, 0.f, 0.f);
- }
- if (accelListener != null) {
- inputDevice.getSensorManager().unregisterListener(accelListener);
- accelListener = null;
+ // Send a gyro event to ensure the virtual controller is stationary
+ conn.sendControllerMotionEvent((byte) controllerNumber, MoonBridge.LI_MOTION_TYPE_GYRO, 0.f, 0.f, 0.f);
+ }
+ if (accelListener != null) {
+ sensorManager.unregisterListener(accelListener);
+ accelListener = null;
- // We leave the acceleration as-is to preserve the attitude of the controller
- }
+ // We leave the acceleration as-is to preserve the attitude of the controller
}
}
diff --git a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java
index a23eea68..8d64a892 100644
--- a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java
+++ b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java
@@ -58,6 +58,7 @@ public class PreferenceConfiguration {
private static final String FULL_RANGE_PREF_STRING = "checkbox_full_range";
private static final String GAMEPAD_TOUCHPAD_AS_MOUSE_PREF_STRING = "checkbox_gamepad_touchpad_as_mouse";
private static final String GAMEPAD_MOTION_SENSORS_PREF_STRING = "checkbox_gamepad_motion_sensors";
+ private static final String GAMEPAD_MOTION_FALLBACK_PREF_STRING = "checkbox_gamepad_motion_fallback";
static final String DEFAULT_RESOLUTION = "1280x720";
static final String DEFAULT_FPS = "60";
@@ -94,6 +95,7 @@ public class PreferenceConfiguration {
private static final boolean DEFAULT_FULL_RANGE = false;
private static final boolean DEFAULT_GAMEPAD_TOUCHPAD_AS_MOUSE = false;
private static final boolean DEFAULT_GAMEPAD_MOTION_SENSORS = true;
+ private static final boolean DEFAULT_GAMEPAD_MOTION_FALLBACK = false;
public static final int FRAME_PACING_MIN_LATENCY = 0;
public static final int FRAME_PACING_BALANCED = 1;
@@ -137,6 +139,7 @@ public class PreferenceConfiguration {
public boolean fullRange;
public boolean gamepadMotionSensors;
public boolean gamepadTouchpadAsMouse;
+ public boolean gamepadMotionSensorsFallbackToDevice;
public static boolean isNativeResolution(int width, int height) {
// It's not a native resolution if it matches an existing resolution option
@@ -525,6 +528,7 @@ public class PreferenceConfiguration {
config.fullRange = prefs.getBoolean(FULL_RANGE_PREF_STRING, DEFAULT_FULL_RANGE);
config.gamepadTouchpadAsMouse = prefs.getBoolean(GAMEPAD_TOUCHPAD_AS_MOUSE_PREF_STRING, DEFAULT_GAMEPAD_TOUCHPAD_AS_MOUSE);
config.gamepadMotionSensors = prefs.getBoolean(GAMEPAD_MOTION_SENSORS_PREF_STRING, DEFAULT_GAMEPAD_MOTION_SENSORS);
+ config.gamepadMotionSensorsFallbackToDevice = prefs.getBoolean(GAMEPAD_MOTION_FALLBACK_PREF_STRING, DEFAULT_GAMEPAD_MOTION_FALLBACK);
return config;
}
diff --git a/app/src/main/java/com/limelight/preferences/StreamSettings.java b/app/src/main/java/com/limelight/preferences/StreamSettings.java
index 651a657a..588f3267 100644
--- a/app/src/main/java/com/limelight/preferences/StreamSettings.java
+++ b/app/src/main/java/com/limelight/preferences/StreamSettings.java
@@ -296,6 +296,14 @@ public class StreamSettings extends Activity {
category.removePreference(findPreference("checkbox_gamepad_motion_sensors"));
}
+ // Hide gamepad motion sensor fallback option if the device has no gyro or accelerometer
+ if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_SENSOR_ACCELEROMETER) &&
+ !getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_SENSOR_GYROSCOPE)) {
+ PreferenceCategory category =
+ (PreferenceCategory) findPreference("category_gamepad_settings");
+ category.removePreference(findPreference("checkbox_gamepad_motion_fallback"));
+ }
+
// Remove PiP mode on devices pre-Oreo, where the feature is not available (some low RAM devices),
// and on Fire OS where it violates the Amazon App Store guidelines for some reason.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 307c0076..36acd907 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -187,6 +187,8 @@
Forces gamepad touchpad input to control the host mouse, even when emulating a gamepad with a touchpad.
Allow use of gamepad motion sensors
Enables supported hosts to request motion sensor data when emulating a gamepad with motion sensors. Disabling may slightly reduce power and network usage if motion sensors are not being used in game.
+ Emulate gamepad motion support using device sensors
+ Uses your device\'s built-in motion sensors if gamepad sensors are not supported by your connected gamepad or your Android version.\nNote: Enabling this option may cause your controller to appear on the host as a PlayStation controller.
Input Settings
Use the touchscreen as a trackpad
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index d169b555..5edc62fa 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -105,7 +105,11 @@
android:title="@string/title_checkbox_gamepad_motion_sensors"
android:summary="@string/summary_checkbox_gamepad_motion_sensors"
android:defaultValue="true" />
-
+