diff --git a/app/src/main/java/com/limelight/binding/input/ControllerHandler.java b/app/src/main/java/com/limelight/binding/input/ControllerHandler.java
index 601a1837..188554c0 100644
--- a/app/src/main/java/com/limelight/binding/input/ControllerHandler.java
+++ b/app/src/main/java/com/limelight/binding/input/ControllerHandler.java
@@ -55,6 +55,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
private final double stickDeadzone;
private final InputDeviceContext defaultContext = new InputDeviceContext();
private final GameGestures gestures;
+ private final Vibrator deviceVibrator;
private boolean hasGameController;
private final PreferenceConfiguration prefConfig;
@@ -65,6 +66,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
this.conn = conn;
this.gestures = gestures;
this.prefConfig = prefConfig;
+ this.deviceVibrator = (Vibrator) activityContext.getSystemService(Context.VIBRATOR_SERVICE);
// HACK: For now we're hardcoding a 10% deadzone. Some deadzone
// is required for controller batching support to work.
@@ -163,6 +165,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
deviceContext.vibrator.cancel();
}
}
+
+ deviceVibrator.cancel();
}
private static boolean hasJoystickAxes(InputDevice device) {
@@ -220,6 +224,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
}
}
+ if (PreferenceConfiguration.readPreferences(context).onscreenController) {
+ LimeLog.info("Counting OSC gamepad");
+ mask |= 1;
+ }
+
LimeLog.info("Enumerated "+count+" gamepads");
return mask;
}
@@ -633,7 +642,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
private short getActiveControllerMask() {
if (prefConfig.multiController) {
- return (short)(currentControllers | initialControllers);
+ return (short)(currentControllers | initialControllers | (prefConfig.onscreenController ? 1 : 0));
}
else {
// Only Player 1 is active with multi-controller disabled
@@ -1072,13 +1081,15 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
// Since we can only use a single amplitude value, compute the desired amplitude
// by taking 75% of the big motor and 25% of the small motor.
// NB: This value is now 0-255 as required by VibrationEffect.
- int simulatedAmplitude = (int)(((lowFreqMotor >> 8) * 0.75) + ((highFreqMotor >> 8) * 0.25));
+ short lowFreqMotorMSB = (short)((lowFreqMotor >> 8) & 0xFF);
+ short highFreqMotorMSB = (short)((lowFreqMotor >> 8) & 0xFF);
+ int simulatedAmplitude = (int)((lowFreqMotorMSB * 0.75) + (highFreqMotorMSB * 0.25));
// Attempt to use amplitude-based control if we're on Oreo and the device
// supports amplitude-based vibration control.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (vibrator.hasAmplitudeControl()) {
- VibrationEffect effect = VibrationEffect.createOneShot(Long.MAX_VALUE, simulatedAmplitude);
+ VibrationEffect effect = VibrationEffect.createOneShot(60000, simulatedAmplitude);
AudioAttributes audioAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_GAME)
.build();
@@ -1089,18 +1100,26 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
// If we reach this point, we don't have amplitude controls available, so
// we must emulate it by PWMing the vibration. Ick.
- long pwmPeriod = 50;
+ long pwmPeriod = 20;
long onTime = (long)((simulatedAmplitude / 255.0) * pwmPeriod);
long offTime = pwmPeriod - onTime;
- vibrator.vibrate(new long[]{offTime, onTime}, 0);
+ vibrator.vibrate(new long[]{0, onTime, offTime}, 0);
}
public void handleRumble(short controllerNumber, short lowFreqMotor, short highFreqMotor) {
+ boolean foundMatchingDevice = false;
+ boolean vibrated = false;
+
for (int i = 0; i < inputDeviceContexts.size(); i++) {
InputDeviceContext deviceContext = inputDeviceContexts.valueAt(i);
- if (deviceContext.controllerNumber == controllerNumber && deviceContext.vibrator != null) {
- rumbleVibrator(deviceContext.vibrator, lowFreqMotor, highFreqMotor);
+ if (deviceContext.controllerNumber == controllerNumber) {
+ foundMatchingDevice = true;
+
+ if (deviceContext.vibrator != null) {
+ vibrated = true;
+ rumbleVibrator(deviceContext.vibrator, lowFreqMotor, highFreqMotor);
+ }
}
}
@@ -1108,7 +1127,23 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
UsbDeviceContext deviceContext = usbDeviceContexts.valueAt(i);
if (deviceContext.controllerNumber == controllerNumber) {
- deviceContext.device.rumble(lowFreqMotor, highFreqMotor);
+ foundMatchingDevice = vibrated = true;
+ deviceContext.device.rumble((short)lowFreqMotor, (short)highFreqMotor);
+ }
+ }
+
+ // We may decide to rumble the device for player 1
+ if (controllerNumber == 0) {
+ // If we didn't find a matching device, it must be the on-screen
+ // controls that triggered the rumble. Vibrate the device if
+ // the user has requested that behavior.
+ if (!foundMatchingDevice && prefConfig.onscreenController && !prefConfig.onlyL3R3 && prefConfig.vibrateOsc) {
+ rumbleVibrator(deviceVibrator, lowFreqMotor, highFreqMotor);
+ }
+ else if (foundMatchingDevice && !vibrated && prefConfig.vibrateFallbackToDevice) {
+ // We found a device to vibrate but it didn't have rumble support. The user
+ // has requested us to vibrate the device in this case.
+ rumbleVibrator(deviceVibrator, lowFreqMotor, highFreqMotor);
}
}
}
diff --git a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java
index 3f7afce3..d4a65b9a 100644
--- a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java
+++ b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java
@@ -35,6 +35,8 @@ public class PreferenceConfiguration {
private static final String MOUSE_EMULATION_STRING = "checkbox_mouse_emulation";
private static final String MOUSE_NAV_BUTTONS_STRING = "checkbox_mouse_nav_buttons";
static final String UNLOCK_FPS_STRING = "checkbox_unlock_fps";
+ private static final String VIBRATE_OSC_PREF_STRING = "checkbox_vibrate_osc";
+ private static final String VIBRATE_FALLBACK_PREF_STRING = "checkbox_vibrate_fallback";
static final String DEFAULT_RESOLUTION = "720p";
static final String DEFAULT_FPS = "60";
@@ -58,6 +60,8 @@ public class PreferenceConfiguration {
private static final boolean DEFAULT_MOUSE_EMULATION = true;
private static final boolean DEFAULT_MOUSE_NAV_BUTTONS = false;
private static final boolean DEFAULT_UNLOCK_FPS = false;
+ private static final boolean DEFAULT_VIBRATE_OSC = true;
+ private static final boolean DEFAULT_VIBRATE_FALLBACK = false;
public static final int FORCE_H265_ON = -1;
public static final int AUTOSELECT_H265 = 0;
@@ -79,6 +83,8 @@ public class PreferenceConfiguration {
public boolean mouseEmulation;
public boolean mouseNavButtons;
public boolean unlockFps;
+ public boolean vibrateOsc;
+ public boolean vibrateFallbackToDevice;
private static int getHeightFromResolutionString(String resString) {
if (resString.equalsIgnoreCase("360p")) {
@@ -329,6 +335,8 @@ public class PreferenceConfiguration {
config.mouseEmulation = prefs.getBoolean(MOUSE_EMULATION_STRING, DEFAULT_MOUSE_EMULATION);
config.mouseNavButtons = prefs.getBoolean(MOUSE_NAV_BUTTONS_STRING, DEFAULT_MOUSE_NAV_BUTTONS);
config.unlockFps = prefs.getBoolean(UNLOCK_FPS_STRING, DEFAULT_UNLOCK_FPS);
+ config.vibrateOsc = prefs.getBoolean(VIBRATE_OSC_PREF_STRING, DEFAULT_VIBRATE_OSC);
+ config.vibrateFallbackToDevice = prefs.getBoolean(VIBRATE_FALLBACK_PREF_STRING, DEFAULT_VIBRATE_FALLBACK);
return config;
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0cf09052..8deb2816 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -136,6 +136,8 @@
Input Settings
Automatic gamepad presence detection
Unchecking this option forces a gamepad to always be present
+ Emulate rumble support with vibration
+ Vibrates your device to emulate rumble if your gamepad does not support it
Adjust analog stick deadzone
%
Xbox 360/One controller driver
@@ -150,6 +152,8 @@
On-screen Controls Settings
Show on-screen controls
Show virtual controller overlay on touchscreen
+ Enable vibration
+ Vibrates your device to emulate rumble for the on-screen controls
Only show L3 and R3
Hide all virtual buttons except L3 and R3
Clear saved on-screen controls layout
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 287e9394..2514610e 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -82,6 +82,11 @@
android:title="@string/title_checkbox_mouse_emulation"
android:summary="@string/summary_checkbox_mouse_emulation"
android:defaultValue="true" />
+
@@ -90,6 +95,12 @@
android:key="checkbox_show_onscreen_controls"
android:summary="@string/summary_checkbox_show_onscreen_controls"
android:title="@string/title_checkbox_show_onscreen_controls" />
+