From acd3aad8d912da05feb9336274a49a4bb445af54 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 13 Aug 2016 18:52:39 -0700 Subject: [PATCH] Add support for mouse emulation with a gamepad --- app/src/main/java/com/limelight/Game.java | 2 +- .../binding/input/ControllerHandler.java | 108 ++++++++++++++++-- 2 files changed, 97 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/limelight/Game.java b/app/src/main/java/com/limelight/Game.java index 485f6cb5..bbdf7327 100644 --- a/app/src/main/java/com/limelight/Game.java +++ b/app/src/main/java/com/limelight/Game.java @@ -241,7 +241,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, // Initialize the connection conn = new NvConnection(host, uniqueId, Game.this, config, PlatformBinding.getCryptoProvider(this)); keybTranslator = new KeyboardTranslator(conn); - controllerHandler = new ControllerHandler(conn, this, prefConfig.multiController, prefConfig.deadzonePercentage); + controllerHandler = new ControllerHandler(this, conn, this, prefConfig.multiController, prefConfig.deadzonePercentage); InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE); inputManager.registerInputDeviceListener(controllerHandler, null); diff --git a/app/src/main/java/com/limelight/binding/input/ControllerHandler.java b/app/src/main/java/com/limelight/binding/input/ControllerHandler.java index 1ceb310a..0adf5c2b 100644 --- a/app/src/main/java/com/limelight/binding/input/ControllerHandler.java +++ b/app/src/main/java/com/limelight/binding/input/ControllerHandler.java @@ -1,24 +1,30 @@ package com.limelight.binding.input; +import android.content.Context; import android.hardware.input.InputManager; import android.os.SystemClock; import android.util.SparseArray; import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; +import android.widget.Toast; import com.limelight.LimeLog; import com.limelight.binding.input.driver.UsbDriverListener; import com.limelight.nvstream.NvConnection; import com.limelight.nvstream.input.ControllerPacket; +import com.limelight.nvstream.input.MouseButtonPacket; import com.limelight.ui.GameGestures; import com.limelight.utils.Vector2d; +import java.util.Timer; +import java.util.TimerTask; + public class ControllerHandler implements InputManager.InputDeviceListener, UsbDriverListener { private static final int MAXIMUM_BUMPER_UP_DELAY_MS = 100; - private static final int START_DOWN_TIME_KEYB_MS = 750; + private static final int START_DOWN_TIME_MOUSE_MODE_MS = 750; private static final int MINIMUM_BUTTON_DOWN_TIME_MS = 25; @@ -34,6 +40,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD private final SparseArray usbDeviceContexts = new SparseArray<>(); private final NvConnection conn; + private final Context activityContext; private final double stickDeadzone; private final InputDeviceContext defaultContext = new InputDeviceContext(); private final GameGestures gestures; @@ -42,7 +49,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD private final boolean multiControllerEnabled; private short currentControllers; - public ControllerHandler(NvConnection conn, GameGestures gestures, boolean multiControllerEnabled, int deadzonePercentage) { + public ControllerHandler(Context activityContext, NvConnection conn, GameGestures gestures, boolean multiControllerEnabled, int deadzonePercentage) { + this.activityContext = activityContext; this.conn = conn; this.gestures = gestures; this.multiControllerEnabled = multiControllerEnabled; @@ -466,7 +474,9 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD // device before we send it. for (int i = 0; i < inputDeviceContexts.size(); i++) { GenericControllerContext context = inputDeviceContexts.valueAt(i); - if (context.assignedControllerNumber && context.controllerNumber == controllerNumber) { + if (context.assignedControllerNumber && + context.controllerNumber == controllerNumber && + context.mouseEmulationActive == originalContext.mouseEmulationActive) { inputMap |= context.inputMap; leftTrigger |= maxByMagnitude(leftTrigger, context.leftTrigger); rightTrigger |= maxByMagnitude(rightTrigger, context.rightTrigger); @@ -478,7 +488,9 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD } for (int i = 0; i < usbDeviceContexts.size(); i++) { GenericControllerContext context = usbDeviceContexts.valueAt(i); - if (context.assignedControllerNumber && context.controllerNumber == controllerNumber) { + if (context.assignedControllerNumber && + context.controllerNumber == controllerNumber && + context.mouseEmulationActive == originalContext.mouseEmulationActive) { inputMap |= context.inputMap; leftTrigger |= maxByMagnitude(leftTrigger, context.leftTrigger); rightTrigger |= maxByMagnitude(rightTrigger, context.rightTrigger); @@ -498,10 +510,40 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD rightStickY |= maxByMagnitude(rightStickY, defaultContext.rightStickY); } - conn.sendControllerInput(controllerNumber, inputMap, - leftTrigger, rightTrigger, - leftStickX, leftStickY, - rightStickX, rightStickY); + if (originalContext.mouseEmulationActive) { + int changedMask = inputMap ^ originalContext.mouseEmulationLastInputMap; + + boolean aDown = (inputMap & ControllerPacket.A_FLAG) != 0; + boolean bDown = (inputMap & ControllerPacket.B_FLAG) != 0; + + originalContext.mouseEmulationLastInputMap = inputMap; + + if ((changedMask & ControllerPacket.A_FLAG) != 0) { + if (aDown) { + conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_LEFT); + } + else { + conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT); + } + } + if ((changedMask & ControllerPacket.B_FLAG) != 0) { + if (bDown) { + conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT); + } + else { + conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT); + } + } + + conn.sendControllerInput(controllerNumber, + (short)0, (byte)0, (byte)0, (short)0, (short)0, (short)0, (short)0); + } + else { + conn.sendControllerInput(controllerNumber, inputMap, + leftTrigger, rightTrigger, + leftStickX, leftStickY, + rightStickX, rightStickY); + } } // Return a valid keycode, 0 to consume, or -1 to not consume the event @@ -759,6 +801,46 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD return true; } + private short scaleRawStickAxis(float stickValue) { + return (short)Math.pow(stickValue, 3); + } + + private void sendEmulatedMouseEvent(short x, short y) { + Vector2d vector = new Vector2d(); + vector.initialize(x, y); + vector.scalarMultiply(1 / 32766.0f); + vector.scalarMultiply(4); + if (vector.getMagnitude() > 0) { + // Move faster as the stick is pressed further from center + vector.scalarMultiply(Math.pow(vector.getMagnitude(), 2)); + if (vector.getMagnitude() >= 1) { + conn.sendMouseMove((short)vector.getX(), (short)-vector.getY()); + } + } + } + + private void toggleMouseEmulation(final GenericControllerContext context) { + if (context.mouseEmulationTimer != null) { + context.mouseEmulationTimer.cancel(); + context.mouseEmulationTimer = null; + } + + context.mouseEmulationActive = !context.mouseEmulationActive; + Toast.makeText(activityContext, "Mouse emulation is: " + (context.mouseEmulationActive ? "ON" : "OFF"), Toast.LENGTH_SHORT).show(); + + if (context.mouseEmulationActive) { + context.mouseEmulationTimer = new Timer(); + context.mouseEmulationTimer.schedule(new TimerTask() { + @Override + public void run() { + // Send mouse movement events from analog sticks + sendEmulatedMouseEvent(context.leftStickX, context.leftStickY); + sendEmulatedMouseEvent(context.rightStickX, context.rightStickY); + } + }, 50, 50); + } + } + public boolean handleButtonUp(KeyEvent event) { InputDeviceContext context = getContextForDevice(event.getDevice()); @@ -785,10 +867,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD break; case KeyEvent.KEYCODE_BUTTON_START: case KeyEvent.KEYCODE_MENU: - if (SystemClock.uptimeMillis() - context.startDownTime > ControllerHandler.START_DOWN_TIME_KEYB_MS) { - // FIXME: The stock keyboard doesn't have controller focus so isn't usable. I'm not enabling this shortcut - // until we have a custom keyboard or some other fix - //gestures.showKeyboard(); + if (SystemClock.uptimeMillis() - context.startDownTime > ControllerHandler.START_DOWN_TIME_MOUSE_MODE_MS) { + toggleMouseEmulation(context); } context.inputMap &= ~ControllerPacket.PLAY_FLAG; break; @@ -1055,6 +1135,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD public short rightStickY = 0x0000; public short leftStickX = 0x0000; public short leftStickY = 0x0000; + + public boolean mouseEmulationActive; + public Timer mouseEmulationTimer; + public short mouseEmulationLastInputMap; } class InputDeviceContext extends GenericControllerContext {