mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2025-07-19 19:13:03 +00:00
Add an Xbox One controller driver developed based on the xpad driver in the Linux kernel
This commit is contained in:
parent
fe3b649fe9
commit
d740e7a521
@ -81,6 +81,9 @@
|
|||||||
<service
|
<service
|
||||||
android:name=".computers.ComputerManagerService"
|
android:name=".computers.ComputerManagerService"
|
||||||
android:label="Computer Management Service" />
|
android:label="Computer Management Service" />
|
||||||
|
<service
|
||||||
|
android:name=".binding.input.driver.UsbDriverService"
|
||||||
|
android:label="Usb Driver Service" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -5,6 +5,7 @@ import com.limelight.binding.PlatformBinding;
|
|||||||
import com.limelight.binding.input.ControllerHandler;
|
import com.limelight.binding.input.ControllerHandler;
|
||||||
import com.limelight.binding.input.KeyboardTranslator;
|
import com.limelight.binding.input.KeyboardTranslator;
|
||||||
import com.limelight.binding.input.TouchContext;
|
import com.limelight.binding.input.TouchContext;
|
||||||
|
import com.limelight.binding.input.driver.UsbDriverService;
|
||||||
import com.limelight.binding.input.evdev.EvdevListener;
|
import com.limelight.binding.input.evdev.EvdevListener;
|
||||||
import com.limelight.binding.input.evdev.EvdevWatcher;
|
import com.limelight.binding.input.evdev.EvdevWatcher;
|
||||||
import com.limelight.binding.video.ConfigurableDecoderRenderer;
|
import com.limelight.binding.video.ConfigurableDecoderRenderer;
|
||||||
@ -22,7 +23,11 @@ import com.limelight.utils.SpinnerDialog;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.hardware.input.InputManager;
|
import android.hardware.input.InputManager;
|
||||||
@ -31,6 +36,7 @@ import android.net.ConnectivityManager;
|
|||||||
import android.net.wifi.WifiManager;
|
import android.net.wifi.WifiManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.IBinder;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.view.Display;
|
import android.view.Display;
|
||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
@ -92,6 +98,21 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
private int drFlags = 0;
|
private int drFlags = 0;
|
||||||
|
|
||||||
|
private boolean connectedToUsbDriverService = false;
|
||||||
|
private ServiceConnection usbDriverServiceConnection = new ServiceConnection() {
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
|
||||||
|
UsbDriverService.UsbDriverBinder binder = (UsbDriverService.UsbDriverBinder) iBinder;
|
||||||
|
binder.setListener(controllerHandler);
|
||||||
|
connectedToUsbDriverService = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName componentName) {
|
||||||
|
connectedToUsbDriverService = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public static final String EXTRA_HOST = "Host";
|
public static final String EXTRA_HOST = "Host";
|
||||||
public static final String EXTRA_APP_NAME = "AppName";
|
public static final String EXTRA_APP_NAME = "AppName";
|
||||||
public static final String EXTRA_APP_ID = "AppId";
|
public static final String EXTRA_APP_ID = "AppId";
|
||||||
@ -248,6 +269,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
evdevWatcher.start();
|
evdevWatcher.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prefConfig.usbDriver) {
|
||||||
|
// Start the USB driver
|
||||||
|
bindService(new Intent(this, UsbDriverService.class),
|
||||||
|
usbDriverServiceConnection, Service.BIND_AUTO_CREATE);
|
||||||
|
}
|
||||||
|
|
||||||
// The connection will be started when the surface gets created
|
// The connection will be started when the surface gets created
|
||||||
sh.addCallback(this);
|
sh.addCallback(this);
|
||||||
}
|
}
|
||||||
@ -315,6 +342,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
|
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
|
||||||
inputManager.unregisterInputDeviceListener(controllerHandler);
|
inputManager.unregisterInputDeviceListener(controllerHandler);
|
||||||
|
|
||||||
|
wifiLock.release();
|
||||||
|
|
||||||
|
if (connectedToUsbDriverService) {
|
||||||
|
// Unbind from the discovery service
|
||||||
|
unbindService(usbDriverServiceConnection);
|
||||||
|
}
|
||||||
|
|
||||||
displayedFailureDialog = true;
|
displayedFailureDialog = true;
|
||||||
stopConnection();
|
stopConnection();
|
||||||
|
|
||||||
@ -338,13 +372,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
|
|
||||||
wifiLock.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Runnable toggleGrab = new Runnable() {
|
private final Runnable toggleGrab = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
@ -8,12 +8,13 @@ import android.view.KeyEvent;
|
|||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
import com.limelight.LimeLog;
|
import com.limelight.LimeLog;
|
||||||
|
import com.limelight.binding.input.driver.UsbDriverListener;
|
||||||
import com.limelight.nvstream.NvConnection;
|
import com.limelight.nvstream.NvConnection;
|
||||||
import com.limelight.nvstream.input.ControllerPacket;
|
import com.limelight.nvstream.input.ControllerPacket;
|
||||||
import com.limelight.ui.GameGestures;
|
import com.limelight.ui.GameGestures;
|
||||||
import com.limelight.utils.Vector2d;
|
import com.limelight.utils.Vector2d;
|
||||||
|
|
||||||
public class ControllerHandler implements InputManager.InputDeviceListener {
|
public class ControllerHandler implements InputManager.InputDeviceListener, UsbDriverListener {
|
||||||
|
|
||||||
private static final int MAXIMUM_BUMPER_UP_DELAY_MS = 100;
|
private static final int MAXIMUM_BUMPER_UP_DELAY_MS = 100;
|
||||||
|
|
||||||
@ -29,11 +30,12 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
|
|||||||
|
|
||||||
private final Vector2d inputVector = new Vector2d();
|
private final Vector2d inputVector = new Vector2d();
|
||||||
|
|
||||||
private final SparseArray<ControllerContext> contexts = new SparseArray<ControllerContext>();
|
private final SparseArray<InputDeviceContext> inputDeviceContexts = new SparseArray<>();
|
||||||
|
private final SparseArray<UsbDeviceContext> usbDeviceContexts = new SparseArray<>();
|
||||||
|
|
||||||
private final NvConnection conn;
|
private final NvConnection conn;
|
||||||
private final double stickDeadzone;
|
private final double stickDeadzone;
|
||||||
private final ControllerContext defaultContext = new ControllerContext();
|
private final InputDeviceContext defaultContext = new InputDeviceContext();
|
||||||
private final GameGestures gestures;
|
private final GameGestures gestures;
|
||||||
private boolean hasGameController;
|
private boolean hasGameController;
|
||||||
|
|
||||||
@ -102,11 +104,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInputDeviceRemoved(int deviceId) {
|
public void onInputDeviceRemoved(int deviceId) {
|
||||||
ControllerContext context = contexts.get(deviceId);
|
InputDeviceContext context = inputDeviceContexts.get(deviceId);
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
LimeLog.info("Removed controller: "+context.name+" ("+deviceId+")");
|
LimeLog.info("Removed controller: "+context.name+" ("+deviceId+")");
|
||||||
releaseControllerNumber(context);
|
releaseControllerNumber(context);
|
||||||
contexts.remove(deviceId);
|
inputDeviceContexts.remove(deviceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,7 +119,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
|
|||||||
onInputDeviceAdded(deviceId);
|
onInputDeviceAdded(deviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void releaseControllerNumber(ControllerContext context) {
|
private void releaseControllerNumber(GenericControllerContext context) {
|
||||||
if (context.reservedControllerNumber) {
|
if (context.reservedControllerNumber) {
|
||||||
LimeLog.info("Controller number "+context.controllerNumber+" is now available");
|
LimeLog.info("Controller number "+context.controllerNumber+" is now available");
|
||||||
currentControllers &= ~(1 << context.controllerNumber);
|
currentControllers &= ~(1 << context.controllerNumber);
|
||||||
@ -126,42 +128,78 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
|
|||||||
|
|
||||||
// Called before sending input but after we've determined that this
|
// Called before sending input but after we've determined that this
|
||||||
// is definitely a controller (not a keyboard, mouse, or something else)
|
// is definitely a controller (not a keyboard, mouse, or something else)
|
||||||
private void assignControllerNumberIfNeeded(ControllerContext context) {
|
private void assignControllerNumberIfNeeded(GenericControllerContext context) {
|
||||||
if (context.assignedControllerNumber) {
|
if (context.assignedControllerNumber) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LimeLog.info(context.name+" ("+context.id+") needs a controller number assigned");
|
if (context instanceof InputDeviceContext) {
|
||||||
if (context.name != null && context.name.contains("gpio-keys")) {
|
InputDeviceContext devContext = (InputDeviceContext) context;
|
||||||
// This is the back button on Shield portable consoles
|
|
||||||
LimeLog.info("Built-in buttons hardcoded as controller 0");
|
|
||||||
context.controllerNumber = 0;
|
|
||||||
}
|
|
||||||
else if (multiControllerEnabled && context.hasJoystickAxes) {
|
|
||||||
context.controllerNumber = 0;
|
|
||||||
|
|
||||||
LimeLog.info("Reserving the next available controller number");
|
LimeLog.info(devContext.name+" ("+context.id+") needs a controller number assigned");
|
||||||
for (short i = 0; i < 4; i++) {
|
if (devContext.name != null && devContext.name.contains("gpio-keys")) {
|
||||||
if ((currentControllers & (1 << i)) == 0) {
|
// This is the back button on Shield portable consoles
|
||||||
// Found an unused controller value
|
LimeLog.info("Built-in buttons hardcoded as controller 0");
|
||||||
currentControllers |= (1 << i);
|
context.controllerNumber = 0;
|
||||||
context.controllerNumber = i;
|
}
|
||||||
context.reservedControllerNumber = true;
|
else if (multiControllerEnabled && devContext.hasJoystickAxes) {
|
||||||
break;
|
context.controllerNumber = 0;
|
||||||
|
|
||||||
|
LimeLog.info("Reserving the next available controller number");
|
||||||
|
for (short i = 0; i < 4; i++) {
|
||||||
|
if ((currentControllers & (1 << i)) == 0) {
|
||||||
|
// Found an unused controller value
|
||||||
|
currentControllers |= (1 << i);
|
||||||
|
context.controllerNumber = i;
|
||||||
|
context.reservedControllerNumber = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
LimeLog.info("Not reserving a controller number");
|
||||||
|
context.controllerNumber = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
LimeLog.info("Not reserving a controller number");
|
if (multiControllerEnabled) {
|
||||||
context.controllerNumber = 0;
|
context.controllerNumber = 0;
|
||||||
|
|
||||||
|
LimeLog.info("Reserving the next available controller number");
|
||||||
|
for (short i = 0; i < 4; i++) {
|
||||||
|
if ((currentControllers & (1 << i)) == 0) {
|
||||||
|
// Found an unused controller value
|
||||||
|
currentControllers |= (1 << i);
|
||||||
|
context.controllerNumber = i;
|
||||||
|
context.reservedControllerNumber = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LimeLog.info("Not reserving a controller number");
|
||||||
|
context.controllerNumber = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LimeLog.info("Assigned as controller "+context.controllerNumber);
|
LimeLog.info("Assigned as controller "+context.controllerNumber);
|
||||||
context.assignedControllerNumber = true;
|
context.assignedControllerNumber = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControllerContext createContextForDevice(InputDevice dev) {
|
private UsbDeviceContext createUsbDeviceContextForDevice(int deviceId) {
|
||||||
ControllerContext context = new ControllerContext();
|
UsbDeviceContext context = new UsbDeviceContext();
|
||||||
|
|
||||||
|
context.id = deviceId;
|
||||||
|
|
||||||
|
context.leftStickDeadzoneRadius = (float) stickDeadzone;
|
||||||
|
context.rightStickDeadzoneRadius = (float) stickDeadzone;
|
||||||
|
context.triggerDeadzone = 0.13f;
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
private InputDeviceContext createInputDeviceContextForDevice(InputDevice dev) {
|
||||||
|
InputDeviceContext context = new InputDeviceContext();
|
||||||
String devName = dev.getName();
|
String devName = dev.getName();
|
||||||
|
|
||||||
LimeLog.info("Creating controller context for device: "+devName);
|
LimeLog.info("Creating controller context for device: "+devName);
|
||||||
@ -332,26 +370,26 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
|
|||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControllerContext getContextForDevice(InputDevice dev) {
|
private InputDeviceContext getContextForDevice(InputDevice dev) {
|
||||||
// Unknown devices use the default context
|
// Unknown devices use the default context
|
||||||
if (dev == null) {
|
if (dev == null) {
|
||||||
return defaultContext;
|
return defaultContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the existing context if it exists
|
// Return the existing context if it exists
|
||||||
ControllerContext context = contexts.get(dev.getId());
|
InputDeviceContext context = inputDeviceContexts.get(dev.getId());
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise create a new context
|
// Otherwise create a new context
|
||||||
context = createContextForDevice(dev);
|
context = createInputDeviceContextForDevice(dev);
|
||||||
contexts.put(dev.getId(), context);
|
inputDeviceContexts.put(dev.getId(), context);
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendControllerInputPacket(ControllerContext context) {
|
private void sendControllerInputPacket(GenericControllerContext context) {
|
||||||
assignControllerNumberIfNeeded(context);
|
assignControllerNumberIfNeeded(context);
|
||||||
conn.sendControllerInput(context.controllerNumber, context.inputMap,
|
conn.sendControllerInput(context.controllerNumber, context.inputMap,
|
||||||
context.leftTrigger, context.rightTrigger,
|
context.leftTrigger, context.rightTrigger,
|
||||||
@ -361,7 +399,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
|
|||||||
|
|
||||||
// Return a valid keycode, 0 to consume, or -1 to not consume the event
|
// Return a valid keycode, 0 to consume, or -1 to not consume the event
|
||||||
// Device MAY BE NULL
|
// Device MAY BE NULL
|
||||||
private int handleRemapping(ControllerContext context, KeyEvent event) {
|
private int handleRemapping(InputDeviceContext context, KeyEvent event) {
|
||||||
// Don't capture the back button if configured
|
// Don't capture the back button if configured
|
||||||
if (context.ignoreBack) {
|
if (context.ignoreBack) {
|
||||||
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
|
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
|
||||||
@ -499,7 +537,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
|
|||||||
// evaluates the deadzone.
|
// evaluates the deadzone.
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleAxisSet(ControllerContext context, float lsX, float lsY, float rsX,
|
private void handleAxisSet(InputDeviceContext context, float lsX, float lsY, float rsX,
|
||||||
float rsY, float lt, float rt, float hatX, float hatY) {
|
float rsY, float lt, float rt, float hatX, float hatY) {
|
||||||
|
|
||||||
if (context.leftStickXAxis != -1 && context.leftStickYAxis != -1) {
|
if (context.leftStickXAxis != -1 && context.leftStickYAxis != -1) {
|
||||||
@ -559,7 +597,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean handleMotionEvent(MotionEvent event) {
|
public boolean handleMotionEvent(MotionEvent event) {
|
||||||
ControllerContext context = getContextForDevice(event.getDevice());
|
InputDeviceContext context = getContextForDevice(event.getDevice());
|
||||||
float lsX = 0, lsY = 0, rsX = 0, rsY = 0, rt = 0, lt = 0, hatX = 0, hatY = 0;
|
float lsX = 0, lsY = 0, rsX = 0, rsY = 0, rt = 0, lt = 0, hatX = 0, hatY = 0;
|
||||||
|
|
||||||
// We purposefully ignore the historical values in the motion event as it makes
|
// We purposefully ignore the historical values in the motion event as it makes
|
||||||
@ -591,7 +629,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean handleButtonUp(KeyEvent event) {
|
public boolean handleButtonUp(KeyEvent event) {
|
||||||
ControllerContext context = getContextForDevice(event.getDevice());
|
InputDeviceContext context = getContextForDevice(event.getDevice());
|
||||||
|
|
||||||
int keyCode = handleRemapping(context, event);
|
int keyCode = handleRemapping(context, event);
|
||||||
if (keyCode == 0) {
|
if (keyCode == 0) {
|
||||||
@ -716,7 +754,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean handleButtonDown(KeyEvent event) {
|
public boolean handleButtonDown(KeyEvent event) {
|
||||||
ControllerContext context = getContextForDevice(event.getDevice());
|
InputDeviceContext context = getContextForDevice(event.getDevice());
|
||||||
|
|
||||||
int keyCode = handleRemapping(context, event);
|
int keyCode = handleRemapping(context, event);
|
||||||
if (keyCode == 0) {
|
if (keyCode == 0) {
|
||||||
@ -816,34 +854,65 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ControllerContext {
|
@Override
|
||||||
public String name;
|
public void reportControllerState(int controllerId, short buttonFlags,
|
||||||
|
float leftStickX, float leftStickY,
|
||||||
|
float rightStickX, float rightStickY,
|
||||||
|
float leftTrigger, float rightTrigger) {
|
||||||
|
UsbDeviceContext context = usbDeviceContexts.get(controllerId);
|
||||||
|
|
||||||
|
Vector2d leftStickVector = populateCachedVector(leftStickX, leftStickY);
|
||||||
|
|
||||||
|
handleDeadZone(leftStickVector, context.leftStickDeadzoneRadius);
|
||||||
|
|
||||||
|
context.leftStickX = (short) (leftStickVector.getX() * 0x7FFE);
|
||||||
|
context.leftStickY = (short) (-leftStickVector.getY() * 0x7FFE);
|
||||||
|
|
||||||
|
Vector2d rightStickVector = populateCachedVector(rightStickX, rightStickY);
|
||||||
|
|
||||||
|
handleDeadZone(rightStickVector, context.rightStickDeadzoneRadius);
|
||||||
|
|
||||||
|
context.rightStickX = (short) (rightStickVector.getX() * 0x7FFE);
|
||||||
|
context.rightStickY = (short) (-rightStickVector.getY() * 0x7FFE);
|
||||||
|
|
||||||
|
if (leftTrigger <= context.triggerDeadzone) {
|
||||||
|
leftTrigger = 0;
|
||||||
|
}
|
||||||
|
if (rightTrigger <= context.triggerDeadzone) {
|
||||||
|
rightTrigger = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.leftTrigger = (byte)(leftTrigger * 0xFF);
|
||||||
|
context.rightTrigger = (byte)(rightTrigger * 0xFF);
|
||||||
|
|
||||||
|
context.inputMap = buttonFlags;
|
||||||
|
|
||||||
|
sendControllerInputPacket(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deviceRemoved(int controllerId) {
|
||||||
|
UsbDeviceContext context = usbDeviceContexts.get(controllerId);
|
||||||
|
if (context != null) {
|
||||||
|
LimeLog.info("Removed controller: "+controllerId);
|
||||||
|
releaseControllerNumber(context);
|
||||||
|
usbDeviceContexts.remove(controllerId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deviceAdded(int controllerId) {
|
||||||
|
UsbDeviceContext context = createUsbDeviceContextForDevice(controllerId);
|
||||||
|
usbDeviceContexts.put(controllerId, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
class GenericControllerContext {
|
||||||
public int id;
|
public int id;
|
||||||
|
|
||||||
public int leftStickXAxis = -1;
|
|
||||||
public int leftStickYAxis = -1;
|
|
||||||
public float leftStickDeadzoneRadius;
|
public float leftStickDeadzoneRadius;
|
||||||
|
|
||||||
public int rightStickXAxis = -1;
|
|
||||||
public int rightStickYAxis = -1;
|
|
||||||
public float rightStickDeadzoneRadius;
|
public float rightStickDeadzoneRadius;
|
||||||
|
|
||||||
public int leftTriggerAxis = -1;
|
|
||||||
public int rightTriggerAxis = -1;
|
|
||||||
public boolean triggersIdleNegative;
|
|
||||||
public float triggerDeadzone;
|
public float triggerDeadzone;
|
||||||
|
|
||||||
public int hatXAxis = -1;
|
|
||||||
public int hatYAxis = -1;
|
|
||||||
|
|
||||||
public boolean isDualShock4;
|
|
||||||
public boolean isXboxController;
|
|
||||||
public boolean isServal;
|
|
||||||
public boolean backIsStart;
|
|
||||||
public boolean modeIsSelect;
|
|
||||||
public boolean ignoreBack;
|
|
||||||
public boolean hasJoystickAxes;
|
|
||||||
|
|
||||||
public boolean assignedControllerNumber;
|
public boolean assignedControllerNumber;
|
||||||
public boolean reservedControllerNumber;
|
public boolean reservedControllerNumber;
|
||||||
public short controllerNumber;
|
public short controllerNumber;
|
||||||
@ -855,6 +924,32 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
|
|||||||
public short rightStickY = 0x0000;
|
public short rightStickY = 0x0000;
|
||||||
public short leftStickX = 0x0000;
|
public short leftStickX = 0x0000;
|
||||||
public short leftStickY = 0x0000;
|
public short leftStickY = 0x0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
class InputDeviceContext extends GenericControllerContext {
|
||||||
|
public String name;
|
||||||
|
|
||||||
|
public int leftStickXAxis = -1;
|
||||||
|
public int leftStickYAxis = -1;
|
||||||
|
|
||||||
|
public int rightStickXAxis = -1;
|
||||||
|
public int rightStickYAxis = -1;
|
||||||
|
|
||||||
|
public int leftTriggerAxis = -1;
|
||||||
|
public int rightTriggerAxis = -1;
|
||||||
|
public boolean triggersIdleNegative;
|
||||||
|
|
||||||
|
public int hatXAxis = -1;
|
||||||
|
public int hatYAxis = -1;
|
||||||
|
|
||||||
|
public boolean isDualShock4;
|
||||||
|
public boolean isXboxController;
|
||||||
|
public boolean isServal;
|
||||||
|
public boolean backIsStart;
|
||||||
|
public boolean modeIsSelect;
|
||||||
|
public boolean ignoreBack;
|
||||||
|
public boolean hasJoystickAxes;
|
||||||
|
|
||||||
public int emulatingButtonFlags = 0;
|
public int emulatingButtonFlags = 0;
|
||||||
|
|
||||||
// Used for OUYA bumper state tracking since they force all buttons
|
// Used for OUYA bumper state tracking since they force all buttons
|
||||||
@ -867,4 +962,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
|
|||||||
|
|
||||||
public long startDownTime = 0;
|
public long startDownTime = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UsbDeviceContext extends GenericControllerContext {}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
package com.limelight.binding.input.driver;
|
||||||
|
|
||||||
|
public interface UsbDriverListener {
|
||||||
|
void reportControllerState(int controllerId, short buttonFlags,
|
||||||
|
float leftStickX, float leftStickY,
|
||||||
|
float rightStickX, float rightStickY,
|
||||||
|
float leftTrigger, float rightTrigger);
|
||||||
|
|
||||||
|
void deviceRemoved(int controllerId);
|
||||||
|
void deviceAdded(int controllerId);
|
||||||
|
}
|
@ -0,0 +1,135 @@
|
|||||||
|
package com.limelight.binding.input.driver;
|
||||||
|
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.hardware.usb.UsbDevice;
|
||||||
|
import android.hardware.usb.UsbDeviceConnection;
|
||||||
|
import android.hardware.usb.UsbManager;
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.IBinder;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class UsbDriverService extends Service {
|
||||||
|
|
||||||
|
private static final String ACTION_USB_PERMISSION =
|
||||||
|
"com.limelight.USB_PERMISSION";
|
||||||
|
|
||||||
|
private UsbManager usbManager;
|
||||||
|
|
||||||
|
private final UsbEventReceiver receiver = new UsbEventReceiver();
|
||||||
|
private final UsbDriverBinder binder = new UsbDriverBinder();
|
||||||
|
|
||||||
|
private final ArrayList<XboxOneController> controllers = new ArrayList<>();
|
||||||
|
|
||||||
|
private UsbDriverListener listener;
|
||||||
|
private static int nextDeviceId;
|
||||||
|
|
||||||
|
public class UsbEventReceiver extends BroadcastReceiver {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
String action = intent.getAction();
|
||||||
|
|
||||||
|
// Initial attachment broadcast
|
||||||
|
if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
|
||||||
|
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||||
|
|
||||||
|
// Continue the state machine
|
||||||
|
handleUsbDeviceState(device);
|
||||||
|
}
|
||||||
|
// Subsequent permission dialog completion intent
|
||||||
|
else if (action.equals(ACTION_USB_PERMISSION)) {
|
||||||
|
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||||
|
|
||||||
|
// If we got this far, we've already found we're able to handle this device
|
||||||
|
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
|
||||||
|
handleUsbDeviceState(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UsbDriverBinder extends Binder {
|
||||||
|
public void setListener(UsbDriverListener listener) {
|
||||||
|
UsbDriverService.this.listener = listener;
|
||||||
|
updateListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateListeners() {
|
||||||
|
for (XboxOneController controller : controllers) {
|
||||||
|
controller.setListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleUsbDeviceState(UsbDevice device) {
|
||||||
|
// Are we able to operate it?
|
||||||
|
if (XboxOneController.canClaimDevice(device)) {
|
||||||
|
// Do we have permission yet?
|
||||||
|
if (!usbManager.hasPermission(device)) {
|
||||||
|
// Let's ask for permission
|
||||||
|
usbManager.requestPermission(device, PendingIntent.getBroadcast(UsbDriverService.this, 0, new Intent(ACTION_USB_PERMISSION), 0));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the device
|
||||||
|
UsbDeviceConnection connection = usbManager.openDevice(device);
|
||||||
|
|
||||||
|
// Try to initialize it
|
||||||
|
XboxOneController controller = new XboxOneController(device, connection, nextDeviceId++);
|
||||||
|
if (!controller.start()) {
|
||||||
|
connection.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to the list
|
||||||
|
controllers.add(controller);
|
||||||
|
|
||||||
|
// Add listener
|
||||||
|
updateListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
|
||||||
|
|
||||||
|
// Register for USB attach broadcasts and permission completions
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
|
||||||
|
filter.addAction(ACTION_USB_PERMISSION);
|
||||||
|
registerReceiver(receiver, filter);
|
||||||
|
|
||||||
|
// Enumerate existing devices
|
||||||
|
for (UsbDevice dev : usbManager.getDeviceList().values()) {
|
||||||
|
if (XboxOneController.canClaimDevice(dev)) {
|
||||||
|
// Start the process of claiming this device
|
||||||
|
handleUsbDeviceState(dev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
// Stop the attachment receiver
|
||||||
|
unregisterReceiver(receiver);
|
||||||
|
|
||||||
|
// Remove all listeners
|
||||||
|
listener = null;
|
||||||
|
updateListeners();
|
||||||
|
|
||||||
|
// Stop all controllers
|
||||||
|
for (XboxOneController controller : controllers) {
|
||||||
|
controller.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return binder;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,231 @@
|
|||||||
|
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 android.media.MediaCodec;
|
||||||
|
|
||||||
|
import com.limelight.LimeLog;
|
||||||
|
import com.limelight.binding.video.MediaCodecHelper;
|
||||||
|
import com.limelight.nvstream.input.ControllerPacket;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
public class XboxOneController {
|
||||||
|
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 XB1_IFACE_SUBCLASS = 71;
|
||||||
|
private static final int XB1_IFACE_PROTOCOL = 208;
|
||||||
|
|
||||||
|
// FIXME: odata_serial
|
||||||
|
private static final byte[] XB1_INIT_DATA = {0x05, 0x20, 0x00, 0x01, 0x00};
|
||||||
|
|
||||||
|
public XboxOneController(UsbDevice device, UsbDeviceConnection connection, int deviceId) {
|
||||||
|
this.device = device;
|
||||||
|
this.connection = connection;
|
||||||
|
this.deviceId = deviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setListener(UsbDriverListener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
|
||||||
|
if (listener != null) {
|
||||||
|
listener.deviceAdded(deviceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setButtonFlag(int buttonFlag, int data) {
|
||||||
|
if (data != 0) {
|
||||||
|
buttonFlags |= buttonFlag;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buttonFlags &= ~buttonFlag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reportInput() {
|
||||||
|
if (listener != null) {
|
||||||
|
listener.reportControllerState(deviceId, buttonFlags, leftStickX, leftStickY,
|
||||||
|
rightStickX, rightStickY, leftTrigger, rightTrigger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processButtons(ByteBuffer buffer) {
|
||||||
|
byte b = buffer.get();
|
||||||
|
|
||||||
|
setButtonFlag(ControllerPacket.PLAY_FLAG, b & 0x04);
|
||||||
|
setButtonFlag(ControllerPacket.BACK_FLAG, b & 0x08);
|
||||||
|
|
||||||
|
setButtonFlag(ControllerPacket.A_FLAG, b & 0x10);
|
||||||
|
setButtonFlag(ControllerPacket.B_FLAG, b & 0x20);
|
||||||
|
setButtonFlag(ControllerPacket.X_FLAG, b & 0x40);
|
||||||
|
setButtonFlag(ControllerPacket.Y_FLAG, b & 0x80);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
setButtonFlag(ControllerPacket.LB_FLAG, b & 0x10);
|
||||||
|
setButtonFlag(ControllerPacket.RB_FLAG, b & 0x20);
|
||||||
|
|
||||||
|
setButtonFlag(ControllerPacket.LS_CLK_FLAG, b & 0x40);
|
||||||
|
setButtonFlag(ControllerPacket.RS_CLK_FLAG, b & 0x80);
|
||||||
|
|
||||||
|
leftTrigger = buffer.getShort() / 1023.0f;
|
||||||
|
rightTrigger = buffer.getShort() / 1023.0f;
|
||||||
|
|
||||||
|
leftStickX = buffer.getShort() / 32767.0f;
|
||||||
|
leftStickY = ~buffer.getShort() / 32767.0f;
|
||||||
|
|
||||||
|
rightStickX = buffer.getShort() / 32767.0f;
|
||||||
|
rightStickY = ~buffer.getShort() / 32767.0f;
|
||||||
|
|
||||||
|
reportInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processPacket(ByteBuffer buffer) {
|
||||||
|
switch (buffer.get())
|
||||||
|
{
|
||||||
|
case 0x20:
|
||||||
|
buffer.position(buffer.position()+3);
|
||||||
|
processButtons(buffer);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x07:
|
||||||
|
buffer.position(buffer.position() + 3);
|
||||||
|
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 (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);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
stopped = true;
|
||||||
|
|
||||||
|
if (inputThread != null) {
|
||||||
|
inputThread.interrupt();
|
||||||
|
inputThread = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listener != null) {
|
||||||
|
listener.deviceRemoved(deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean canClaimDevice(UsbDevice device) {
|
||||||
|
return device.getVendorId() == MICROSOFT_VID &&
|
||||||
|
device.getInterfaceCount() >= 1 &&
|
||||||
|
device.getInterface(0).getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
|
||||||
|
device.getInterface(0).getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
|
||||||
|
device.getInterface(0).getInterfaceProtocol() == XB1_IFACE_PROTOCOL;
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,7 @@ public class PreferenceConfiguration {
|
|||||||
private static final String LIST_MODE_PREF_STRING = "checkbox_list_mode";
|
private static final String LIST_MODE_PREF_STRING = "checkbox_list_mode";
|
||||||
private static final String SMALL_ICONS_PREF_STRING = "checkbox_small_icon_mode";
|
private static final String SMALL_ICONS_PREF_STRING = "checkbox_small_icon_mode";
|
||||||
private static final String MULTI_CONTROLLER_PREF_STRING = "checkbox_multi_controller";
|
private static final String MULTI_CONTROLLER_PREF_STRING = "checkbox_multi_controller";
|
||||||
|
private static final String USB_DRIVER_PREF_SRING = "checkbox_usb_driver";
|
||||||
|
|
||||||
private static final int BITRATE_DEFAULT_720_30 = 5;
|
private static final int BITRATE_DEFAULT_720_30 = 5;
|
||||||
private static final int BITRATE_DEFAULT_720_60 = 10;
|
private static final int BITRATE_DEFAULT_720_60 = 10;
|
||||||
@ -36,6 +37,7 @@ public class PreferenceConfiguration {
|
|||||||
public static final String DEFAULT_LANGUAGE = "default";
|
public static final String DEFAULT_LANGUAGE = "default";
|
||||||
private static final boolean DEFAULT_LIST_MODE = false;
|
private static final boolean DEFAULT_LIST_MODE = false;
|
||||||
private static final boolean DEFAULT_MULTI_CONTROLLER = true;
|
private static final boolean DEFAULT_MULTI_CONTROLLER = true;
|
||||||
|
private static final boolean DEFAULT_USB_DRIVER = true;
|
||||||
|
|
||||||
public static final int FORCE_HARDWARE_DECODER = -1;
|
public static final int FORCE_HARDWARE_DECODER = -1;
|
||||||
public static final int AUTOSELECT_DECODER = 0;
|
public static final int AUTOSELECT_DECODER = 0;
|
||||||
@ -47,7 +49,7 @@ public class PreferenceConfiguration {
|
|||||||
public int deadzonePercentage;
|
public int deadzonePercentage;
|
||||||
public boolean stretchVideo, enableSops, playHostAudio, disableWarnings;
|
public boolean stretchVideo, enableSops, playHostAudio, disableWarnings;
|
||||||
public String language;
|
public String language;
|
||||||
public boolean listMode, smallIconMode, multiController;
|
public boolean listMode, smallIconMode, multiController, usbDriver;
|
||||||
|
|
||||||
public static int getDefaultBitrate(String resFpsString) {
|
public static int getDefaultBitrate(String resFpsString) {
|
||||||
if (resFpsString.equals("720p30")) {
|
if (resFpsString.equals("720p30")) {
|
||||||
@ -159,6 +161,7 @@ public class PreferenceConfiguration {
|
|||||||
config.listMode = prefs.getBoolean(LIST_MODE_PREF_STRING, DEFAULT_LIST_MODE);
|
config.listMode = prefs.getBoolean(LIST_MODE_PREF_STRING, DEFAULT_LIST_MODE);
|
||||||
config.smallIconMode = prefs.getBoolean(SMALL_ICONS_PREF_STRING, getDefaultSmallMode(context));
|
config.smallIconMode = prefs.getBoolean(SMALL_ICONS_PREF_STRING, getDefaultSmallMode(context));
|
||||||
config.multiController = prefs.getBoolean(MULTI_CONTROLLER_PREF_STRING, DEFAULT_MULTI_CONTROLLER);
|
config.multiController = prefs.getBoolean(MULTI_CONTROLLER_PREF_STRING, DEFAULT_MULTI_CONTROLLER);
|
||||||
|
config.usbDriver = prefs.getBoolean(USB_DRIVER_PREF_SRING, DEFAULT_USB_DRIVER);
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
@ -99,6 +99,8 @@
|
|||||||
<string name="summary_checkbox_multi_controller">When unchecked, all controllers appear as one</string>
|
<string name="summary_checkbox_multi_controller">When unchecked, all controllers appear as one</string>
|
||||||
<string name="title_seekbar_deadzone">Adjust analog stick deadzone</string>
|
<string name="title_seekbar_deadzone">Adjust analog stick deadzone</string>
|
||||||
<string name="suffix_seekbar_deadzone">%</string>
|
<string name="suffix_seekbar_deadzone">%</string>
|
||||||
|
<string name="title_checkbox_xb1_driver">Xbox One controller driver</string>
|
||||||
|
<string name="summary_checkbox_xb1_driver">Enables a built-in USB driver for devices without native Xbox One controller support.</string>
|
||||||
|
|
||||||
<string name="category_ui_settings">UI Settings</string>
|
<string name="category_ui_settings">UI Settings</string>
|
||||||
<string name="title_language_list">Language</string>
|
<string name="title_language_list">Language</string>
|
||||||
|
@ -38,6 +38,11 @@
|
|||||||
android:title="@string/title_checkbox_multi_controller"
|
android:title="@string/title_checkbox_multi_controller"
|
||||||
android:summary="@string/summary_checkbox_multi_controller"
|
android:summary="@string/summary_checkbox_multi_controller"
|
||||||
android:defaultValue="true" />
|
android:defaultValue="true" />
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:key="checkbox_usb_driver"
|
||||||
|
android:title="@string/title_checkbox_xb1_driver"
|
||||||
|
android:summary="@string/summary_checkbox_xb1_driver"
|
||||||
|
android:defaultValue="true" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
<PreferenceCategory android:title="@string/category_host_settings">
|
<PreferenceCategory android:title="@string/category_host_settings">
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
|
Loading…
x
Reference in New Issue
Block a user