From fb15ff99ca00585540b3ae9f84625dfeb0930e8d Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 30 Jan 2016 04:21:20 -0500 Subject: [PATCH] Add support for the NVIDIA relative mouse extensions for Shield devices --- app/src/main/java/com/limelight/Game.java | 44 ++++++++--- .../binding/input/NvMouseHelper.java | 76 +++++++++++++++++++ 2 files changed, 111 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/com/limelight/binding/input/NvMouseHelper.java diff --git a/app/src/main/java/com/limelight/Game.java b/app/src/main/java/com/limelight/Game.java index 99f34d57..b2b79b06 100644 --- a/app/src/main/java/com/limelight/Game.java +++ b/app/src/main/java/com/limelight/Game.java @@ -4,6 +4,7 @@ package com.limelight; import com.limelight.binding.PlatformBinding; import com.limelight.binding.input.ControllerHandler; import com.limelight.binding.input.KeyboardTranslator; +import com.limelight.binding.input.NvMouseHelper; import com.limelight.binding.input.TouchContext; import com.limelight.binding.input.driver.UsbDriverService; import com.limelight.binding.input.evdev.EvdevHandler; @@ -422,11 +423,15 @@ public class Game extends Activity implements SurfaceHolder.Callback, private final Runnable toggleGrab = new Runnable() { @Override public void run() { - if (evdevHandler != null) { - if (grabbedInput) { + if (grabbedInput) { + NvMouseHelper.setCursorVisibility(Game.this, true); + if (evdevHandler != null) { evdevHandler.ungrabAll(); } - else { + } + else { + NvMouseHelper.setCursorVisibility(Game.this, false); + if (evdevHandler != null) { evdevHandler.regrabAll(); } } @@ -655,13 +660,26 @@ public class Game extends Activity implements SurfaceHolder.Callback, } } - // First process the history - for (int i = 0; i < event.getHistorySize(); i++) { - updateMousePosition((int)event.getHistoricalX(i), (int)event.getHistoricalY(i)); - } + // Get relative axis values if we can + if (NvMouseHelper.eventHasRelativeMouseAxes(event)) { + // Send the deltas straight from the motion event + conn.sendMouseMove((short)NvMouseHelper.getRelativeAxisX(event), + (short)NvMouseHelper.getRelativeAxisY(event)); - // Now process the current values - updateMousePosition((int)event.getX(), (int)event.getY()); + // We have to also update the position Android thinks the cursor is at + // in order to avoid jumping when we stop moving or click. + lastMouseX = (int)event.getX(); + lastMouseY = (int)event.getY(); + } + else { + // First process the history + for (int i = 0; i < event.getHistorySize(); i++) { + updateMousePosition((int)event.getHistoricalX(i), (int)event.getHistoricalY(i)); + } + + // Now process the current values + updateMousePosition((int)event.getX(), (int)event.getY()); + } lastButtonState = event.getButtonState(); } @@ -828,6 +846,9 @@ public class Game extends Activity implements SurfaceHolder.Callback, evdevHandler.stop(); evdevHandler = null; } + + // Enable cursor visibility again + NvMouseHelper.setCursorVisibility(this, true); } @Override @@ -867,6 +888,11 @@ public class Game extends Activity implements SurfaceHolder.Callback, connecting = false; connected = true; + // Hide the mouse cursor now. Doing it before + // dismissing the spinner seems to be undone + // when the spinner gets displayed. + NvMouseHelper.setCursorVisibility(this, false); + hideSystemUi(1000); } diff --git a/app/src/main/java/com/limelight/binding/input/NvMouseHelper.java b/app/src/main/java/com/limelight/binding/input/NvMouseHelper.java new file mode 100644 index 00000000..c709a44c --- /dev/null +++ b/app/src/main/java/com/limelight/binding/input/NvMouseHelper.java @@ -0,0 +1,76 @@ +package com.limelight.binding.input; + + +import android.content.Context; +import android.hardware.input.InputManager; +import android.view.MotionEvent; + +import com.limelight.LimeLog; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +// NVIDIA extended the Android input APIs with support for using an attached mouse in relative +// mode without having to grab the input device (which requires root). The data comes in the form +// of new AXIS_RELATIVE_X and AXIS_RELATIVE_Y constants in the mouse's MotionEvent objects and +// a new function, InputManager.setCursorVisibility(), that allows the cursor to be hidden. +// +// http://docs.nvidia.com/gameworks/index.html#technologies/mobile/game_controller_handling_mouse.htm + +public class NvMouseHelper { + private static boolean nvExtensionSupported; + private static Method methodSetCursorVisibility; + private static int AXIS_RELATIVE_X; + private static int AXIS_RELATIVE_Y; + + static { + try { + methodSetCursorVisibility = InputManager.class.getMethod("setCursorVisibility", boolean.class); + + Field fieldRelX = MotionEvent.class.getField("AXIS_RELATIVE_X"); + Field fieldRelY = MotionEvent.class.getField("AXIS_RELATIVE_Y"); + + AXIS_RELATIVE_X = (Integer) fieldRelX.get(null); + AXIS_RELATIVE_Y = (Integer) fieldRelY.get(null); + + nvExtensionSupported = true; + } catch (Exception e) { + LimeLog.info("NvMouseHelper not supported"); + nvExtensionSupported = false; + } + } + + public static boolean setCursorVisibility(Context context, boolean visible) { + if (!nvExtensionSupported) { + return false; + } + + try { + methodSetCursorVisibility.invoke(context.getSystemService(Context.INPUT_SERVICE), visible); + return true; + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + + return false; + } + + public static boolean eventHasRelativeMouseAxes(MotionEvent event) { + if (!nvExtensionSupported) { + return false; + } + return event.getAxisValue(AXIS_RELATIVE_X) != 0 || + event.getAxisValue(AXIS_RELATIVE_Y) != 0; + } + + public static float getRelativeAxisX(MotionEvent event) { + return event.getAxisValue(AXIS_RELATIVE_X); + } + + public static float getRelativeAxisY(MotionEvent event) { + return event.getAxisValue(AXIS_RELATIVE_Y); + } +}