mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2025-07-20 03:23:07 +00:00
Add support for wired Xbox 360 controllers (pending testing)
This commit is contained in:
parent
ceef4510fb
commit
23c54f6813
@ -0,0 +1,55 @@
|
|||||||
|
package com.limelight.binding.input.driver;
|
||||||
|
|
||||||
|
import android.hardware.usb.UsbEndpoint;
|
||||||
|
|
||||||
|
import com.limelight.LimeLog;
|
||||||
|
import com.limelight.binding.video.MediaCodecHelper;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
public abstract class AbstractController {
|
||||||
|
|
||||||
|
private final int deviceId;
|
||||||
|
|
||||||
|
private UsbDriverListener listener;
|
||||||
|
|
||||||
|
protected short buttonFlags;
|
||||||
|
protected float leftTrigger, rightTrigger;
|
||||||
|
protected float rightStickX, rightStickY;
|
||||||
|
protected float leftStickX, leftStickY;
|
||||||
|
|
||||||
|
public int getControllerId() {
|
||||||
|
return deviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setButtonFlag(int buttonFlag, int data) {
|
||||||
|
if (data != 0) {
|
||||||
|
buttonFlags |= buttonFlag;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buttonFlags &= ~buttonFlag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void reportInput() {
|
||||||
|
listener.reportControllerState(deviceId, buttonFlags, leftStickX, leftStickY,
|
||||||
|
rightStickX, rightStickY, leftTrigger, rightTrigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract boolean start();
|
||||||
|
public abstract void stop();
|
||||||
|
|
||||||
|
public AbstractController(int deviceId, UsbDriverListener listener) {
|
||||||
|
this.deviceId = deviceId;
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void notifyDeviceRemoved() {
|
||||||
|
listener.deviceRemoved(deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void notifyDeviceAdded() {
|
||||||
|
listener.deviceAdded(deviceId);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,149 @@
|
|||||||
|
package com.limelight.binding.input.driver;
|
||||||
|
|
||||||
|
import android.hardware.usb.UsbConstants;
|
||||||
|
import android.hardware.usb.UsbDevice;
|
||||||
|
import android.hardware.usb.UsbDeviceConnection;
|
||||||
|
import android.hardware.usb.UsbEndpoint;
|
||||||
|
import android.hardware.usb.UsbInterface;
|
||||||
|
|
||||||
|
import com.limelight.LimeLog;
|
||||||
|
import com.limelight.binding.video.MediaCodecHelper;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
public abstract class AbstractXboxController extends AbstractController {
|
||||||
|
protected final UsbDevice device;
|
||||||
|
protected final UsbDeviceConnection connection;
|
||||||
|
|
||||||
|
private Thread inputThread;
|
||||||
|
private boolean stopped;
|
||||||
|
|
||||||
|
protected UsbEndpoint inEndpt, outEndpt;
|
||||||
|
|
||||||
|
public AbstractXboxController(UsbDevice device, UsbDeviceConnection connection, int deviceId, UsbDriverListener listener) {
|
||||||
|
super(deviceId, listener);
|
||||||
|
this.device = device;
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Thread createInputThread() {
|
||||||
|
return new Thread() {
|
||||||
|
public void run() {
|
||||||
|
while (!isInterrupted() && !stopped) {
|
||||||
|
byte[] buffer = new byte[64];
|
||||||
|
|
||||||
|
int res;
|
||||||
|
|
||||||
|
//
|
||||||
|
// There's no way that I can tell to determine if a device has failed
|
||||||
|
// or if the timeout has simply expired. We'll check how long the transfer
|
||||||
|
// took to fail and assume the device failed if it happened before the timeout
|
||||||
|
// expired.
|
||||||
|
//
|
||||||
|
|
||||||
|
do {
|
||||||
|
// Read the next input state packet
|
||||||
|
long lastMillis = MediaCodecHelper.getMonotonicMillis();
|
||||||
|
res = connection.bulkTransfer(inEndpt, buffer, buffer.length, 3000);
|
||||||
|
|
||||||
|
// If we get a zero length response, treat it as an error
|
||||||
|
if (res == 0) {
|
||||||
|
res = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res == -1 && MediaCodecHelper.getMonotonicMillis() - lastMillis < 1000) {
|
||||||
|
LimeLog.warning("Detected device I/O error");
|
||||||
|
AbstractXboxController.this.stop();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (res == -1 && !isInterrupted() && !stopped);
|
||||||
|
|
||||||
|
if (res == -1 || stopped) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handleRead(ByteBuffer.wrap(buffer, 0, res).order(ByteOrder.LITTLE_ENDIAN))) {
|
||||||
|
// Report input if handleRead() returns true
|
||||||
|
reportInput();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean start() {
|
||||||
|
// Force claim all interfaces
|
||||||
|
for (int i = 0; i < device.getInterfaceCount(); i++) {
|
||||||
|
UsbInterface iface = device.getInterface(i);
|
||||||
|
|
||||||
|
if (!connection.claimInterface(iface, true)) {
|
||||||
|
LimeLog.warning("Failed to claim interfaces");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the endpoints
|
||||||
|
UsbInterface iface = device.getInterface(0);
|
||||||
|
for (int i = 0; i < iface.getEndpointCount(); i++) {
|
||||||
|
UsbEndpoint endpt = iface.getEndpoint(i);
|
||||||
|
if (endpt.getDirection() == UsbConstants.USB_DIR_IN) {
|
||||||
|
if (inEndpt != null) {
|
||||||
|
LimeLog.warning("Found duplicate IN endpoint");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
inEndpt = endpt;
|
||||||
|
}
|
||||||
|
else if (endpt.getDirection() == UsbConstants.USB_DIR_OUT) {
|
||||||
|
if (outEndpt != null) {
|
||||||
|
LimeLog.warning("Found duplicate OUT endpoint");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
outEndpt = endpt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the required endpoints were present
|
||||||
|
if (inEndpt == null || outEndpt == null) {
|
||||||
|
LimeLog.warning("Missing required endpoint");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the init function
|
||||||
|
if (!doInit()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start listening for controller input
|
||||||
|
inputThread = createInputThread();
|
||||||
|
inputThread.start();
|
||||||
|
|
||||||
|
// Now report we're added
|
||||||
|
notifyDeviceAdded();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
if (stopped) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stopped = true;
|
||||||
|
|
||||||
|
// Stop the input thread
|
||||||
|
if (inputThread != null) {
|
||||||
|
inputThread.interrupt();
|
||||||
|
inputThread = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report the device removed
|
||||||
|
notifyDeviceRemoved();
|
||||||
|
|
||||||
|
// Close the USB connection
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract boolean handleRead(ByteBuffer buffer);
|
||||||
|
protected abstract boolean doInit();
|
||||||
|
}
|
@ -26,7 +26,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
private final UsbEventReceiver receiver = new UsbEventReceiver();
|
private final UsbEventReceiver receiver = new UsbEventReceiver();
|
||||||
private final UsbDriverBinder binder = new UsbDriverBinder();
|
private final UsbDriverBinder binder = new UsbDriverBinder();
|
||||||
|
|
||||||
private final ArrayList<XboxOneController> controllers = new ArrayList<>();
|
private final ArrayList<AbstractController> controllers = new ArrayList<>();
|
||||||
|
|
||||||
private UsbDriverListener listener;
|
private UsbDriverListener listener;
|
||||||
private static int nextDeviceId;
|
private static int nextDeviceId;
|
||||||
@ -42,7 +42,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
@Override
|
@Override
|
||||||
public void deviceRemoved(int controllerId) {
|
public void deviceRemoved(int controllerId) {
|
||||||
// Remove the the controller from our list (if not removed already)
|
// Remove the the controller from our list (if not removed already)
|
||||||
for (XboxOneController controller : controllers) {
|
for (AbstractController controller : controllers) {
|
||||||
if (controller.getControllerId() == controllerId) {
|
if (controller.getControllerId() == controllerId) {
|
||||||
controllers.remove(controller);
|
controllers.remove(controller);
|
||||||
break;
|
break;
|
||||||
@ -93,7 +93,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
|
|
||||||
// Report all controllerMap that already exist
|
// Report all controllerMap that already exist
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
for (XboxOneController controller : controllers) {
|
for (AbstractController controller : controllers) {
|
||||||
listener.deviceAdded(controller.getControllerId());
|
listener.deviceAdded(controller.getControllerId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,8 +117,21 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to initialize it
|
|
||||||
XboxOneController controller = new XboxOneController(device, connection, nextDeviceId++, this);
|
AbstractController controller;
|
||||||
|
|
||||||
|
if (XboxOneController.canClaimDevice(device)) {
|
||||||
|
controller = new XboxOneController(device, connection, nextDeviceId++, this);
|
||||||
|
}
|
||||||
|
else if (Xbox360Controller.canClaimDevice(device)) {
|
||||||
|
controller = new Xbox360Controller(device, connection, nextDeviceId++, this);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Unreachable
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the controller
|
||||||
if (!controller.start()) {
|
if (!controller.start()) {
|
||||||
connection.close();
|
connection.close();
|
||||||
return;
|
return;
|
||||||
@ -141,7 +154,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
|
|
||||||
// Enumerate existing devices
|
// Enumerate existing devices
|
||||||
for (UsbDevice dev : usbManager.getDeviceList().values()) {
|
for (UsbDevice dev : usbManager.getDeviceList().values()) {
|
||||||
if (XboxOneController.canClaimDevice(dev)) {
|
if (XboxOneController.canClaimDevice(dev) || Xbox360Controller.canClaimDevice(dev)) {
|
||||||
// Start the process of claiming this device
|
// Start the process of claiming this device
|
||||||
handleUsbDeviceState(dev);
|
handleUsbDeviceState(dev);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,137 @@
|
|||||||
|
package com.limelight.binding.input.driver;
|
||||||
|
|
||||||
|
import android.hardware.usb.UsbConstants;
|
||||||
|
import android.hardware.usb.UsbDevice;
|
||||||
|
import android.hardware.usb.UsbDeviceConnection;
|
||||||
|
|
||||||
|
import com.limelight.LimeLog;
|
||||||
|
import com.limelight.nvstream.input.ControllerPacket;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
public class Xbox360Controller extends AbstractXboxController {
|
||||||
|
|
||||||
|
// This list is taken from the Xpad driver in the Linux kernel.
|
||||||
|
// I've excluded the devices that aren't "controllers" in the traditional sense, but
|
||||||
|
// if people really want to use their dancepads or fight sticks with Moonlight, I can
|
||||||
|
// put them in.
|
||||||
|
private static final DeviceIdTuple[] supportedDeviceTuples = {
|
||||||
|
new DeviceIdTuple(0x045e, 0x028e, "Microsoft X-Box 360 pad"),
|
||||||
|
new DeviceIdTuple(0x044f, 0xb326, "Thrustmaster Gamepad GP XID"),
|
||||||
|
new DeviceIdTuple(0x046d, 0xc21d, "Logitech Gamepad F310"),
|
||||||
|
new DeviceIdTuple(0x046d, 0xc21e, "Logitech Gamepad F510"),
|
||||||
|
new DeviceIdTuple(0x046d, 0xc21f, "Logitech Gamepad F710"),
|
||||||
|
new DeviceIdTuple(0x046d, 0xc242, "Logitech Chillstream Controller"),
|
||||||
|
new DeviceIdTuple(0x0738, 0x4716, "Mad Catz Wired Xbox 360 Controller"),
|
||||||
|
new DeviceIdTuple(0x0738, 0x4726, "Mad Catz Xbox 360 Controller"),
|
||||||
|
new DeviceIdTuple(0x0738, 0xb726, "Mad Catz Xbox controller - MW2"),
|
||||||
|
new DeviceIdTuple(0x0738, 0xbeef, "Mad Catz JOYTECH NEO SE Advanced GamePad"),
|
||||||
|
new DeviceIdTuple(0x0738, 0xcb02, "Saitek Cyborg Rumble Pad - PC/Xbox 360"),
|
||||||
|
new DeviceIdTuple(0x0738, 0xcb03, "Saitek P3200 Rumble Pad - PC/Xbox 360"),
|
||||||
|
new DeviceIdTuple(0x0e6f, 0x0113, "Afterglow AX.1 Gamepad for Xbox 360"),
|
||||||
|
new DeviceIdTuple(0x0e6f, 0x0201, "Pelican PL-3601 'TSZ' Wired Xbox 360 Controller"),
|
||||||
|
new DeviceIdTuple(0x0e6f, 0x0213, "Afterglow Gamepad for Xbox 360"),
|
||||||
|
new DeviceIdTuple(0x0e6f, 0x021f, "Rock Candy Gamepad for Xbox 360"),
|
||||||
|
new DeviceIdTuple(0x0e6f, 0x0301, "Logic3 Controller"),
|
||||||
|
new DeviceIdTuple(0x0e6f, 0x0401, "Logic3 Controller"),
|
||||||
|
new DeviceIdTuple(0x12ab, 0x0301, "PDP AFTERGLOW AX.1"),
|
||||||
|
new DeviceIdTuple(0x146b, 0x0601, "BigBen Interactive XBOX 360 Controller"),
|
||||||
|
new DeviceIdTuple(0x1532, 0x0037, "Razer Sabertooth"),
|
||||||
|
new DeviceIdTuple(0x15e4, 0x3f00, "Power A Mini Pro Elite"),
|
||||||
|
new DeviceIdTuple(0x15e4, 0x3f0a, "Xbox Airflo wired controller"),
|
||||||
|
new DeviceIdTuple(0x15e4, 0x3f10, "Batarang Xbox 360 controller"),
|
||||||
|
new DeviceIdTuple(0x162e, 0xbeef, "Joytech Neo-Se Take2"),
|
||||||
|
new DeviceIdTuple(0x1689, 0xfd00, "Razer Onza Tournament Edition"),
|
||||||
|
new DeviceIdTuple(0x1689, 0xfd01, "Razer Onza Classic Edition"),
|
||||||
|
new DeviceIdTuple(0x24c6, 0x5d04, "Razer Sabertooth"),
|
||||||
|
new DeviceIdTuple(0x1bad, 0xf016, "Mad Catz Xbox 360 Controller"),
|
||||||
|
new DeviceIdTuple(0x1bad, 0xf023, "MLG Pro Circuit Controller (Xbox)"),
|
||||||
|
new DeviceIdTuple(0x1bad, 0xf900, "Harmonix Xbox 360 Controller"),
|
||||||
|
new DeviceIdTuple(0x1bad, 0xf901, "Gamestop Xbox 360 Controller"),
|
||||||
|
new DeviceIdTuple(0x1bad, 0xf903, "Tron Xbox 360 controller"),
|
||||||
|
new DeviceIdTuple(0x24c6, 0x5300, "PowerA MINI PROEX Controller"),
|
||||||
|
new DeviceIdTuple(0x24c6, 0x5303, "Xbox Airflo wired controller"),
|
||||||
|
};
|
||||||
|
|
||||||
|
public static boolean canClaimDevice(UsbDevice device) {
|
||||||
|
for (DeviceIdTuple tuple : supportedDeviceTuples) {
|
||||||
|
if (device.getVendorId() == tuple.vid && device.getProductId() == tuple.pid) {
|
||||||
|
LimeLog.info("XB360 can claim device: " + tuple.name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Xbox360Controller(UsbDevice device, UsbDeviceConnection connection, int deviceId, UsbDriverListener listener) {
|
||||||
|
super(device, connection, deviceId, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean handleRead(ByteBuffer buffer) {
|
||||||
|
// Skip first byte
|
||||||
|
buffer.position(buffer.position()+1);
|
||||||
|
|
||||||
|
// DPAD
|
||||||
|
byte b = buffer.get();
|
||||||
|
setButtonFlag(ControllerPacket.LEFT_FLAG, b & 0x04);
|
||||||
|
setButtonFlag(ControllerPacket.RIGHT_FLAG, b & 0x08);
|
||||||
|
setButtonFlag(ControllerPacket.UP_FLAG, b & 0x01);
|
||||||
|
setButtonFlag(ControllerPacket.DOWN_FLAG, b & 0x02);
|
||||||
|
|
||||||
|
// Start/Select
|
||||||
|
setButtonFlag(ControllerPacket.PLAY_FLAG, b & 0x10);
|
||||||
|
setButtonFlag(ControllerPacket.BACK_FLAG, b & 0x20);
|
||||||
|
|
||||||
|
// LS/RS
|
||||||
|
setButtonFlag(ControllerPacket.LS_CLK_FLAG, b & 0x40);
|
||||||
|
setButtonFlag(ControllerPacket.RS_CLK_FLAG, b & 0x80);
|
||||||
|
|
||||||
|
// ABXY buttons
|
||||||
|
b = buffer.get();
|
||||||
|
setButtonFlag(ControllerPacket.A_FLAG, b & 0x10);
|
||||||
|
setButtonFlag(ControllerPacket.B_FLAG, b & 0x20);
|
||||||
|
setButtonFlag(ControllerPacket.X_FLAG, b & 0x40);
|
||||||
|
setButtonFlag(ControllerPacket.Y_FLAG, b & 0x80);
|
||||||
|
|
||||||
|
// LB/RB
|
||||||
|
setButtonFlag(ControllerPacket.LB_FLAG, b & 0x01);
|
||||||
|
setButtonFlag(ControllerPacket.RB_FLAG, b & 0x02);
|
||||||
|
|
||||||
|
// Xbox button
|
||||||
|
setButtonFlag(ControllerPacket.SPECIAL_BUTTON_FLAG, b & 0x04);
|
||||||
|
|
||||||
|
// Triggers
|
||||||
|
leftTrigger = buffer.get() / 255.0f;
|
||||||
|
rightTrigger = buffer.get() / 255.0f;
|
||||||
|
|
||||||
|
// Left stick
|
||||||
|
leftStickX = buffer.getShort() / 32767.0f;
|
||||||
|
leftStickY = ~buffer.getShort() / 32767.0f;
|
||||||
|
|
||||||
|
// Right stick
|
||||||
|
rightStickX = buffer.getShort() / 32767.0f;
|
||||||
|
rightStickY = ~buffer.getShort() / 32767.0f;
|
||||||
|
|
||||||
|
// Return true to send input
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doInit() {
|
||||||
|
// Xbox 360 wired controller requires no initialization
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DeviceIdTuple {
|
||||||
|
public final int vid;
|
||||||
|
public final int pid;
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
public DeviceIdTuple(int vid, int pid, String name) {
|
||||||
|
this.vid = vid;
|
||||||
|
this.pid = pid;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,19 +13,7 @@ import com.limelight.nvstream.input.ControllerPacket;
|
|||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
public class XboxOneController {
|
public class XboxOneController extends AbstractXboxController {
|
||||||
private final UsbDevice device;
|
|
||||||
private final UsbDeviceConnection connection;
|
|
||||||
private final int deviceId;
|
|
||||||
|
|
||||||
private Thread inputThread;
|
|
||||||
private UsbDriverListener listener;
|
|
||||||
private boolean stopped;
|
|
||||||
|
|
||||||
private short buttonFlags;
|
|
||||||
private float leftTrigger, rightTrigger;
|
|
||||||
private float rightStickX, rightStickY;
|
|
||||||
private float leftStickX, leftStickY;
|
|
||||||
|
|
||||||
private static final int MICROSOFT_VID = 0x045e;
|
private static final int MICROSOFT_VID = 0x045e;
|
||||||
private static final int XB1_IFACE_SUBCLASS = 71;
|
private static final int XB1_IFACE_SUBCLASS = 71;
|
||||||
@ -35,28 +23,7 @@ public class XboxOneController {
|
|||||||
private static final byte[] XB1_INIT_DATA = {0x05, 0x20, 0x00, 0x01, 0x00};
|
private static final byte[] XB1_INIT_DATA = {0x05, 0x20, 0x00, 0x01, 0x00};
|
||||||
|
|
||||||
public XboxOneController(UsbDevice device, UsbDeviceConnection connection, int deviceId, UsbDriverListener listener) {
|
public XboxOneController(UsbDevice device, UsbDeviceConnection connection, int deviceId, UsbDriverListener listener) {
|
||||||
this.device = device;
|
super(device, connection, deviceId, listener);
|
||||||
this.connection = connection;
|
|
||||||
this.deviceId = deviceId;
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getControllerId() {
|
|
||||||
return this.deviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setButtonFlag(int buttonFlag, int data) {
|
|
||||||
if (data != 0) {
|
|
||||||
buttonFlags |= buttonFlag;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
buttonFlags &= ~buttonFlag;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reportInput() {
|
|
||||||
listener.reportControllerState(deviceId, buttonFlags, leftStickX, leftStickY,
|
|
||||||
rightStickX, rightStickY, leftTrigger, rightTrigger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processButtons(ByteBuffer buffer) {
|
private void processButtons(ByteBuffer buffer) {
|
||||||
@ -90,143 +57,24 @@ public class XboxOneController {
|
|||||||
|
|
||||||
rightStickX = buffer.getShort() / 32767.0f;
|
rightStickX = buffer.getShort() / 32767.0f;
|
||||||
rightStickY = ~buffer.getShort() / 32767.0f;
|
rightStickY = ~buffer.getShort() / 32767.0f;
|
||||||
|
|
||||||
reportInput();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processPacket(ByteBuffer buffer) {
|
@Override
|
||||||
|
protected boolean handleRead(ByteBuffer buffer) {
|
||||||
switch (buffer.get())
|
switch (buffer.get())
|
||||||
{
|
{
|
||||||
case 0x20:
|
case 0x20:
|
||||||
buffer.position(buffer.position()+3);
|
buffer.position(buffer.position()+3);
|
||||||
processButtons(buffer);
|
processButtons(buffer);
|
||||||
break;
|
return true;
|
||||||
|
|
||||||
case 0x07:
|
case 0x07:
|
||||||
buffer.position(buffer.position() + 3);
|
buffer.position(buffer.position() + 3);
|
||||||
setButtonFlag(ControllerPacket.SPECIAL_BUTTON_FLAG, buffer.get() & 0x01);
|
setButtonFlag(ControllerPacket.SPECIAL_BUTTON_FLAG, buffer.get() & 0x01);
|
||||||
reportInput();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startInputThread(final UsbEndpoint inEndpt) {
|
|
||||||
inputThread = new Thread() {
|
|
||||||
public void run() {
|
|
||||||
while (!isInterrupted() && !stopped) {
|
|
||||||
byte[] buffer = new byte[64];
|
|
||||||
|
|
||||||
int res;
|
|
||||||
|
|
||||||
//
|
|
||||||
// There's no way that I can tell to determine if a device has failed
|
|
||||||
// or if the timeout has simply expired. We'll check how long the transfer
|
|
||||||
// took to fail and assume the device failed if it happened before the timeout
|
|
||||||
// expired.
|
|
||||||
//
|
|
||||||
|
|
||||||
do {
|
|
||||||
// Read the next input state packet
|
|
||||||
long lastMillis = MediaCodecHelper.getMonotonicMillis();
|
|
||||||
res = connection.bulkTransfer(inEndpt, buffer, buffer.length, 3000);
|
|
||||||
|
|
||||||
// If we get a zero length response, treat it as an error
|
|
||||||
if (res == 0) {
|
|
||||||
res = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res == -1 && MediaCodecHelper.getMonotonicMillis() - lastMillis < 1000) {
|
|
||||||
LimeLog.warning("Detected device I/O error");
|
|
||||||
XboxOneController.this.stop();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while (res == -1 && !isInterrupted() && !stopped);
|
|
||||||
|
|
||||||
if (res == -1 || stopped) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
processPacket(ByteBuffer.wrap(buffer, 0, res).order(ByteOrder.LITTLE_ENDIAN));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
inputThread.setName("Xbox One Controller - Input Thread");
|
|
||||||
inputThread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean start() {
|
|
||||||
// Force claim all interfaces
|
|
||||||
for (int i = 0; i < device.getInterfaceCount(); i++) {
|
|
||||||
UsbInterface iface = device.getInterface(i);
|
|
||||||
|
|
||||||
if (!connection.claimInterface(iface, true)) {
|
|
||||||
LimeLog.warning("Failed to claim interfaces");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the endpoints
|
|
||||||
UsbEndpoint outEndpt = null;
|
|
||||||
UsbEndpoint inEndpt = null;
|
|
||||||
UsbInterface iface = device.getInterface(0);
|
|
||||||
for (int i = 0; i < iface.getEndpointCount(); i++) {
|
|
||||||
UsbEndpoint endpt = iface.getEndpoint(i);
|
|
||||||
if (endpt.getDirection() == UsbConstants.USB_DIR_IN) {
|
|
||||||
if (inEndpt != null) {
|
|
||||||
LimeLog.warning("Found duplicate IN endpoint");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
inEndpt = endpt;
|
|
||||||
}
|
|
||||||
else if (endpt.getDirection() == UsbConstants.USB_DIR_OUT) {
|
|
||||||
if (outEndpt != null) {
|
|
||||||
LimeLog.warning("Found duplicate OUT endpoint");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
outEndpt = endpt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the required endpoints were present
|
|
||||||
if (inEndpt == null || outEndpt == null) {
|
|
||||||
LimeLog.warning("Missing required endpoint");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the initialization packet
|
|
||||||
int res = connection.bulkTransfer(outEndpt, XB1_INIT_DATA, XB1_INIT_DATA.length, 3000);
|
|
||||||
if (res != XB1_INIT_DATA.length) {
|
|
||||||
LimeLog.warning("Initialization transfer failed: "+res);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start listening for controller input
|
|
||||||
startInputThread(inEndpt);
|
|
||||||
|
|
||||||
// Report this device added via the listener
|
|
||||||
listener.deviceAdded(deviceId);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
return false;
|
||||||
if (stopped) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
stopped = true;
|
|
||||||
|
|
||||||
// Stop the input thread
|
|
||||||
if (inputThread != null) {
|
|
||||||
inputThread.interrupt();
|
|
||||||
inputThread = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Report the device removed
|
|
||||||
listener.deviceRemoved(deviceId);
|
|
||||||
|
|
||||||
// Close the USB connection
|
|
||||||
connection.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean canClaimDevice(UsbDevice device) {
|
public static boolean canClaimDevice(UsbDevice device) {
|
||||||
@ -236,4 +84,16 @@ public class XboxOneController {
|
|||||||
device.getInterface(0).getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
|
device.getInterface(0).getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
|
||||||
device.getInterface(0).getInterfaceProtocol() == XB1_IFACE_PROTOCOL;
|
device.getInterface(0).getInterfaceProtocol() == XB1_IFACE_PROTOCOL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doInit() {
|
||||||
|
// Send the initialization packet
|
||||||
|
int res = connection.bulkTransfer(outEndpt, XB1_INIT_DATA, XB1_INIT_DATA.length, 3000);
|
||||||
|
if (res != XB1_INIT_DATA.length) {
|
||||||
|
LimeLog.warning("Initialization transfer failed: "+res);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user