mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2025-07-01 23:35:28 +00:00
Add trigger rumble support
This commit is contained in:
parent
dca8d93aa8
commit
71169ed740
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user