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" /> +