Add trigger rumble support

This commit is contained in:
Cameron Gutman 2023-06-24 17:53:02 -05:00
parent dca8d93aa8
commit 71169ed740
6 changed files with 168 additions and 11 deletions

View File

@ -522,8 +522,13 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
context.productId = dev.getProductId();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && hasDualAmplitudeControlledRumbleVibrators(dev.getVibratorManager())) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && hasQuadAmplitudeControlledRumbleVibrators(dev.getVibratorManager())) {
context.vibratorManager = dev.getVibratorManager();
context.quadVibrators = true;
}
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && hasDualAmplitudeControlledRumbleVibrators(dev.getVibratorManager())) {
context.vibratorManager = dev.getVibratorManager();
context.quadVibrators = false;
}
else if (dev.getVibrator().hasVibrator()) {
context.vibrator = dev.getVibrator();
@ -1363,6 +1368,64 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
vm.vibrate(combo.combine(), vibrationAttributes.build());
}
@TargetApi(31)
private boolean hasQuadAmplitudeControlledRumbleVibrators(VibratorManager vm) {
int[] vibratorIds = vm.getVibratorIds();
// There must be exactly 4 vibrators on this device
if (vibratorIds.length != 4) {
return false;
}
// All vibrators must have amplitude control
for (int vid : vibratorIds) {
if (!vm.getVibrator(vid).hasAmplitudeControl()) {
return false;
}
}
return true;
}
// This must only be called if hasQuadAmplitudeControlledRumbleVibrators() is true!
@TargetApi(31)
private void rumbleQuadVibrators(VibratorManager vm, short lowFreqMotor, short highFreqMotor, short leftTrigger, short rightTrigger) {
// Normalize motor values to 0-255 amplitudes for VibrationManager
highFreqMotor = (short)((highFreqMotor >> 8) & 0xFF);
lowFreqMotor = (short)((lowFreqMotor >> 8) & 0xFF);
leftTrigger = (short)((leftTrigger >> 8) & 0xFF);
rightTrigger = (short)((rightTrigger >> 8) & 0xFF);
// If they're all zero, we can just call cancel().
if (lowFreqMotor == 0 && highFreqMotor == 0 && leftTrigger == 0 && rightTrigger == 0) {
vm.cancel();
return;
}
// This is a guess based upon the behavior of FF_RUMBLE, but untested due to lack of Linux
// support for trigger rumble!
int[] vibratorIds = vm.getVibratorIds();
int[] vibratorAmplitudes = new int[] { highFreqMotor, lowFreqMotor, leftTrigger, rightTrigger };
CombinedVibration.ParallelCombination combo = CombinedVibration.startParallel();
for (int i = 0; i < vibratorIds.length; i++) {
// It's illegal to create a VibrationEffect with an amplitude of 0.
// Simply excluding that vibrator from our ParallelCombination will turn it off.
if (vibratorAmplitudes[i] != 0) {
combo.addVibrator(vibratorIds[i], VibrationEffect.createOneShot(60000, vibratorAmplitudes[i]));
}
}
VibrationAttributes.Builder vibrationAttributes = new VibrationAttributes.Builder();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
vibrationAttributes.setUsage(VibrationAttributes.USAGE_MEDIA);
}
vm.vibrate(combo.combine(), vibrationAttributes.build());
}
private void rumbleSingleVibrator(Vibrator vibrator, short lowFreqMotor, short highFreqMotor) {
// Since we can only use a single amplitude value, compute the desired amplitude
// by taking 80% of the big motor and 33% of the small motor, then capping to 255.
@ -1433,19 +1496,30 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
if (deviceContext.controllerNumber == controllerNumber) {
foundMatchingDevice = true;
deviceContext.lowFreqMotor = lowFreqMotor;
deviceContext.highFreqMotor = highFreqMotor;
// Prefer the documented Android 12 rumble API which can handle dual vibrators on PS/Xbox controllers
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && deviceContext.vibratorManager != null) {
vibrated = true;
rumbleDualVibrators(deviceContext.vibratorManager, lowFreqMotor, highFreqMotor);
if (deviceContext.quadVibrators) {
rumbleQuadVibrators(deviceContext.vibratorManager,
deviceContext.lowFreqMotor, deviceContext.highFreqMotor,
deviceContext.leftTriggerMotor, deviceContext.rightTriggerMotor);
}
else {
rumbleDualVibrators(deviceContext.vibratorManager,
deviceContext.lowFreqMotor, deviceContext.highFreqMotor);
}
}
// On Shield devices, we can use their special API to rumble Shield controllers
else if (sceManager.rumble(deviceContext.inputDevice, lowFreqMotor, highFreqMotor)) {
else if (sceManager.rumble(deviceContext.inputDevice, deviceContext.lowFreqMotor, deviceContext.highFreqMotor)) {
vibrated = true;
}
// If all else fails, we have to try the old Vibrator API
else if (deviceContext.vibrator != null) {
vibrated = true;
rumbleSingleVibrator(deviceContext.vibrator, lowFreqMotor, highFreqMotor);
rumbleSingleVibrator(deviceContext.vibrator, deviceContext.lowFreqMotor, deviceContext.highFreqMotor);
}
}
}
@ -1455,7 +1529,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
if (deviceContext.controllerNumber == controllerNumber) {
foundMatchingDevice = vibrated = true;
deviceContext.device.rumble((short)lowFreqMotor, (short)highFreqMotor);
deviceContext.device.rumble(lowFreqMotor, highFreqMotor);
}
}
@ -1476,7 +1550,28 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
}
public void handleRumbleTriggers(short controllerNumber, short leftTrigger, short rightTrigger) {
// TODO
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
for (int i = 0; i < inputDeviceContexts.size(); i++) {
InputDeviceContext deviceContext = inputDeviceContexts.valueAt(i);
deviceContext.leftTriggerMotor = leftTrigger;
deviceContext.rightTriggerMotor = rightTrigger;
if (deviceContext.controllerNumber == controllerNumber && deviceContext.quadVibrators) {
rumbleQuadVibrators(deviceContext.vibratorManager,
deviceContext.lowFreqMotor, deviceContext.highFreqMotor,
deviceContext.leftTriggerMotor, deviceContext.rightTriggerMotor);
}
}
}
for (int i = 0; i < usbDeviceContexts.size(); i++) {
UsbDeviceContext deviceContext = usbDeviceContexts.valueAt(i);
if (deviceContext.controllerNumber == controllerNumber) {
deviceContext.device.rumbleTriggers(leftTrigger, rightTrigger);
}
}
}
public void handleSetMotionEventState(short controllerNumber, byte motionType, short reportRateHz) {
@ -1947,6 +2042,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
public String name;
public VibratorManager vibratorManager;
public Vibrator vibrator;
public boolean quadVibrators;
public short lowFreqMotor, highFreqMotor;
public short leftTriggerMotor, rightTriggerMotor;
public InputDevice inputDevice;
public int leftStickXAxis = -1;

View File

@ -8,10 +8,12 @@ public abstract class AbstractController {
private UsbDriverListener listener;
protected short buttonFlags;
protected int buttonFlags, supportedButtonFlags;
protected float leftTrigger, rightTrigger;
protected float rightStickX, rightStickY;
protected float leftStickX, leftStickY;
protected short capabilities;
protected byte type;
public int getControllerId() {
return deviceId;
@ -25,6 +27,18 @@ public abstract class AbstractController {
return productId;
}
public int getSupportedButtonFlags() {
return supportedButtonFlags;
}
public short getCapabilities() {
return capabilities;
}
public byte getType() {
return type;
}
protected void setButtonFlag(int buttonFlag, int data) {
if (data != 0) {
buttonFlags |= buttonFlag;
@ -51,6 +65,8 @@ public abstract class AbstractController {
public abstract void rumble(short lowFreqMotor, short highFreqMotor);
public abstract void rumbleTriggers(short leftTrigger, short rightTrigger);
protected void notifyDeviceRemoved() {
listener.deviceRemoved(this);
}

View File

@ -8,6 +8,8 @@ import android.hardware.usb.UsbInterface;
import android.os.SystemClock;
import com.limelight.LimeLog;
import com.limelight.nvstream.input.ControllerPacket;
import com.limelight.nvstream.jni.MoonBridge;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@ -25,6 +27,14 @@ public abstract class AbstractXboxController extends AbstractController {
super(deviceId, listener, device.getVendorId(), device.getProductId());
this.device = device;
this.connection = connection;
this.type = MoonBridge.LI_CTYPE_XBOX;
this.capabilities = MoonBridge.LI_CCAP_ANALOG_TRIGGERS | MoonBridge.LI_CCAP_RUMBLE;
this.buttonFlags =
ControllerPacket.A_FLAG | ControllerPacket.B_FLAG | ControllerPacket.X_FLAG | ControllerPacket.Y_FLAG |
ControllerPacket.UP_FLAG | ControllerPacket.DOWN_FLAG | ControllerPacket.LEFT_FLAG | ControllerPacket.RIGHT_FLAG |
ControllerPacket.LB_FLAG | ControllerPacket.RB_FLAG |
ControllerPacket.LS_CLK_FLAG | ControllerPacket.RS_CLK_FLAG |
ControllerPacket.BACK_FLAG | ControllerPacket.PLAY_FLAG | ControllerPacket.SPECIAL_BUTTON_FLAG;
}
private Thread createInputThread() {

View File

@ -156,4 +156,9 @@ public class Xbox360Controller extends AbstractXboxController {
LimeLog.warning("Rumble transfer failed: "+res);
}
}
@Override
public void rumbleTriggers(short leftTrigger, short rightTrigger) {
// Trigger motors not present on Xbox 360 controllers
}
}

View File

@ -142,4 +142,9 @@ public class Xbox360WirelessDongle extends AbstractController {
public void rumble(short lowFreqMotor, short highFreqMotor) {
// Unreachable.
}
@Override
public void rumbleTriggers(short leftTrigger, short rightTrigger) {
// Unreachable.
}
}

View File

@ -6,6 +6,7 @@ import android.hardware.usb.UsbDeviceConnection;
import com.limelight.LimeLog;
import com.limelight.nvstream.input.ControllerPacket;
import com.limelight.nvstream.jni.MoonBridge;
import java.nio.ByteBuffer;
import java.util.Arrays;
@ -54,9 +55,14 @@ public class XboxOneController extends AbstractXboxController {
};
private byte seqNum = 0;
private short lowFreqMotor = 0;
private short highFreqMotor = 0;
private short leftTriggerMotor = 0;
private short rightTriggerMotor = 0;
public XboxOneController(UsbDevice device, UsbDeviceConnection connection, int deviceId, UsbDriverListener listener) {
super(device, connection, deviceId, listener);
capabilities |= MoonBridge.LI_CCAP_TRIGGER_RUMBLE;
}
private void processButtons(ByteBuffer buffer) {
@ -176,12 +182,14 @@ public class XboxOneController extends AbstractXboxController {
return true;
}
@Override
public void rumble(short lowFreqMotor, short highFreqMotor) {
private void sendRumblePacket() {
byte[] data = {
0x09, 0x00, seqNum++, 0x09, 0x00,
0x0F, 0x00, 0x00,
(byte)(lowFreqMotor >> 9), (byte)(highFreqMotor >> 9),
0x0F,
(byte)(leftTriggerMotor >> 9),
(byte)(rightTriggerMotor >> 9),
(byte)(lowFreqMotor >> 9),
(byte)(highFreqMotor >> 9),
(byte)0xFF, 0x00, (byte)0xFF
};
int res = connection.bulkTransfer(outEndpt, data, data.length, 100);
@ -190,6 +198,20 @@ public class XboxOneController extends AbstractXboxController {
}
}
@Override
public void rumble(short lowFreqMotor, short highFreqMotor) {
this.lowFreqMotor = lowFreqMotor;
this.highFreqMotor = highFreqMotor;
sendRumblePacket();
}
@Override
public void rumbleTriggers(short leftTrigger, short rightTrigger) {
this.leftTriggerMotor = leftTrigger;
this.rightTriggerMotor = rightTrigger;
sendRumblePacket();
}
private static class InitPacket {
final int vendorId;
final int productId;