Add an unified input capture interface

This commit is contained in:
Cameron Gutman 2016-06-13 20:33:43 -05:00
parent 117b555fcd
commit 541ac44be4
6 changed files with 137 additions and 57 deletions

View File

@ -4,10 +4,12 @@ package com.limelight;
import com.limelight.binding.PlatformBinding; 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.NvMouseHelper; import com.limelight.binding.input.capture.InputCaptureManager;
import com.limelight.binding.input.capture.InputCaptureProvider;
import com.limelight.binding.input.capture.ShieldCaptureProvider;
import com.limelight.binding.input.TouchContext; import com.limelight.binding.input.TouchContext;
import com.limelight.binding.input.driver.UsbDriverService; import com.limelight.binding.input.driver.UsbDriverService;
import com.limelight.binding.input.evdev.EvdevHandler; import com.limelight.binding.input.evdev.EvdevCaptureProvider;
import com.limelight.binding.input.evdev.EvdevListener; import com.limelight.binding.input.evdev.EvdevListener;
import com.limelight.binding.input.virtual_controller.VirtualController; import com.limelight.binding.input.virtual_controller.VirtualController;
import com.limelight.binding.video.EnhancedDecoderRenderer; import com.limelight.binding.video.EnhancedDecoderRenderer;
@ -48,12 +50,10 @@ import android.view.InputDevice;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View; import android.view.View;
import android.view.View.OnGenericMotionListener; import android.view.View.OnGenericMotionListener;
import android.view.View.OnSystemUiVisibilityChangeListener; import android.view.View.OnSystemUiVisibilityChangeListener;
import android.view.View.OnTouchListener; import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.Window; import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.FrameLayout; import android.widget.FrameLayout;
@ -92,7 +92,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
private boolean connecting = false; private boolean connecting = false;
private boolean connected = false; private boolean connected = false;
private EvdevHandler evdevHandler; private InputCaptureProvider inputCaptureProvider;
private int modifierFlags = 0; private int modifierFlags = 0;
private boolean grabbedInput = true; private boolean grabbedInput = true;
private boolean grabComboDown = false; private boolean grabComboDown = false;
@ -284,11 +284,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
streamView); streamView);
} }
if (LimelightBuildProps.ROOT_BUILD) { inputCaptureProvider = InputCaptureManager.getInputCaptureProvider(this, this);
// Start watching for raw input
evdevHandler = new EvdevHandler(this, this);
evdevHandler.start();
}
if (prefConfig.onscreenController) { if (prefConfig.onscreenController) {
// create virtual onscreen controller // create virtual onscreen controller
@ -406,16 +402,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
@Override @Override
public void run() { public void run() {
if (grabbedInput) { if (grabbedInput) {
NvMouseHelper.setCursorVisibility(Game.this, true); inputCaptureProvider.disableCapture();
if (evdevHandler != null) {
evdevHandler.ungrabAll();
}
} }
else { else {
NvMouseHelper.setCursorVisibility(Game.this, false); inputCaptureProvider.enableCapture();
if (evdevHandler != null) {
evdevHandler.regrabAll();
}
} }
grabbedInput = !grabbedInput; grabbedInput = !grabbedInput;
@ -638,10 +628,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
} }
// Get relative axis values if we can // Get relative axis values if we can
if (NvMouseHelper.eventHasRelativeMouseAxes(event)) { if (inputCaptureProvider.eventHasRelativeMouseAxes(event)) {
// Send the deltas straight from the motion event // Send the deltas straight from the motion event
conn.sendMouseMove((short)NvMouseHelper.getRelativeAxisX(event), conn.sendMouseMove((short) inputCaptureProvider.getRelativeAxisX(event),
(short)NvMouseHelper.getRelativeAxisY(event)); (short) inputCaptureProvider.getRelativeAxisY(event));
// We have to also update the position Android thinks the cursor is at // We have to also update the position Android thinks the cursor is at
// in order to avoid jumping when we stop moving or click. // in order to avoid jumping when we stop moving or click.
@ -818,14 +808,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
conn.stop(); conn.stop();
} }
// Close the Evdev reader to allow use of captured input devices
if (evdevHandler != null) {
evdevHandler.stop();
evdevHandler = null;
}
// Enable cursor visibility again // Enable cursor visibility again
NvMouseHelper.setCursorVisibility(this, true); inputCaptureProvider.disableCapture();
// Destroy the capture provider
inputCaptureProvider.destroy();
} }
@Override @Override
@ -871,7 +858,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// Hide the mouse cursor now. Doing it before // Hide the mouse cursor now. Doing it before
// dismissing the spinner seems to be undone // dismissing the spinner seems to be undone
// when the spinner gets displayed. // when the spinner gets displayed.
NvMouseHelper.setCursorVisibility(Game.this, false); inputCaptureProvider.enableCapture();
} }
}); });

View File

@ -0,0 +1,28 @@
package com.limelight.binding.input.capture;
import android.app.Activity;
import com.limelight.LimeLog;
import com.limelight.binding.input.evdev.EvdevCaptureProvider;
import com.limelight.binding.input.evdev.EvdevListener;
public class InputCaptureManager {
public static InputCaptureProvider getInputCaptureProvider(Activity activity, EvdevListener rootListener) {
/*if (AndroidCaptureProvider.isCaptureProviderSupported()) {
LimeLog.info("Using Android N+ native mouse capture");
return new AndroidCaptureProvider();
}
else*/ if (ShieldCaptureProvider.isCaptureProviderSupported()) {
LimeLog.info("Using NVIDIA mouse capture extension");
return new ShieldCaptureProvider(activity);
}
else if (EvdevCaptureProvider.isCaptureProviderSupported()) {
LimeLog.info("Using Evdev mouse capture");
return new EvdevCaptureProvider(activity, rootListener);
}
else {
LimeLog.info("Mouse capture not available");
return new NullCaptureProvider();
}
}
}

View File

@ -0,0 +1,21 @@
package com.limelight.binding.input.capture;
import android.view.MotionEvent;
public abstract class InputCaptureProvider {
public abstract void enableCapture();
public abstract void disableCapture();
public void destroy() {}
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
return false;
}
public float getRelativeAxisX(MotionEvent event) {
return 0;
}
public float getRelativeAxisY(MotionEvent event) {
return 0;
}
}

View File

@ -0,0 +1,10 @@
package com.limelight.binding.input.capture;
public class NullCaptureProvider extends InputCaptureProvider {
@Override
public void enableCapture() {}
@Override
public void disableCapture() {}
}

View File

@ -1,4 +1,4 @@
package com.limelight.binding.input; package com.limelight.binding.input.capture;
import android.content.Context; import android.content.Context;
@ -18,12 +18,14 @@ import java.lang.reflect.Method;
// //
// http://docs.nvidia.com/gameworks/index.html#technologies/mobile/game_controller_handling_mouse.htm // http://docs.nvidia.com/gameworks/index.html#technologies/mobile/game_controller_handling_mouse.htm
public class NvMouseHelper { public class ShieldCaptureProvider extends InputCaptureProvider {
private static boolean nvExtensionSupported; private static boolean nvExtensionSupported;
private static Method methodSetCursorVisibility; private static Method methodSetCursorVisibility;
private static int AXIS_RELATIVE_X; private static int AXIS_RELATIVE_X;
private static int AXIS_RELATIVE_Y; private static int AXIS_RELATIVE_Y;
private Context context;
static { static {
try { try {
methodSetCursorVisibility = InputManager.class.getMethod("setCursorVisibility", boolean.class); methodSetCursorVisibility = InputManager.class.getMethod("setCursorVisibility", boolean.class);
@ -36,16 +38,19 @@ public class NvMouseHelper {
nvExtensionSupported = true; nvExtensionSupported = true;
} catch (Exception e) { } catch (Exception e) {
LimeLog.info("NvMouseHelper not supported");
nvExtensionSupported = false; nvExtensionSupported = false;
} }
} }
public static boolean setCursorVisibility(Context context, boolean visible) { public ShieldCaptureProvider(Context context) {
if (!nvExtensionSupported) { this.context = context;
return false;
} }
public static boolean isCaptureProviderSupported() {
return nvExtensionSupported;
}
private boolean setCursorVisibility(boolean visible) {
try { try {
methodSetCursorVisibility.invoke(context.getSystemService(Context.INPUT_SERVICE), visible); methodSetCursorVisibility.invoke(context.getSystemService(Context.INPUT_SERVICE), visible);
return true; return true;
@ -58,19 +63,29 @@ public class NvMouseHelper {
return false; return false;
} }
public static boolean eventHasRelativeMouseAxes(MotionEvent event) { @Override
if (!nvExtensionSupported) { public void enableCapture() {
return false; setCursorVisibility(false);
} }
@Override
public void disableCapture() {
setCursorVisibility(true);
}
@Override
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
return event.getAxisValue(AXIS_RELATIVE_X) != 0 || return event.getAxisValue(AXIS_RELATIVE_X) != 0 ||
event.getAxisValue(AXIS_RELATIVE_Y) != 0; event.getAxisValue(AXIS_RELATIVE_Y) != 0;
} }
public static float getRelativeAxisX(MotionEvent event) { @Override
public float getRelativeAxisX(MotionEvent event) {
return event.getAxisValue(AXIS_RELATIVE_X); return event.getAxisValue(AXIS_RELATIVE_X);
} }
public static float getRelativeAxisY(MotionEvent event) { @Override
public float getRelativeAxisY(MotionEvent event) {
return event.getAxisValue(AXIS_RELATIVE_Y); return event.getAxisValue(AXIS_RELATIVE_Y);
} }
} }

View File

@ -4,6 +4,8 @@ import android.app.Activity;
import android.widget.Toast; import android.widget.Toast;
import com.limelight.LimeLog; import com.limelight.LimeLog;
import com.limelight.LimelightBuildProps;
import com.limelight.binding.input.capture.InputCaptureProvider;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.File; import java.io.File;
@ -13,7 +15,7 @@ import java.io.OutputStream;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
public class EvdevHandler { public class EvdevCaptureProvider extends InputCaptureProvider {
private final EvdevListener listener; private final EvdevListener listener;
private final String libraryPath; private final String libraryPath;
@ -25,6 +27,7 @@ public class EvdevHandler {
private ServerSocket servSock; private ServerSocket servSock;
private Socket evdevSock; private Socket evdevSock;
private Activity activity; private Activity activity;
private boolean started = false;
private static final byte UNGRAB_REQUEST = 1; private static final byte UNGRAB_REQUEST = 1;
private static final byte REGRAB_REQUEST = 2; private static final byte REGRAB_REQUEST = 2;
@ -163,13 +166,26 @@ public class EvdevHandler {
} }
}; };
public EvdevHandler(Activity activity, EvdevListener listener) { public EvdevCaptureProvider(Activity activity, EvdevListener listener) {
this.listener = listener; this.listener = listener;
this.activity = activity; this.activity = activity;
this.libraryPath = activity.getApplicationInfo().nativeLibraryDir; this.libraryPath = activity.getApplicationInfo().nativeLibraryDir;
} }
public void regrabAll() { public static boolean isCaptureProviderSupported() {
return LimelightBuildProps.ROOT_BUILD;
}
@Override
public void enableCapture() {
if (!started) {
// Start the handler thread if it's our first time
// capturing
handlerThread.start();
started = true;
}
else {
// Send a request to regrab if we're already capturing
if (!shutdown && evdevOut != null) { if (!shutdown && evdevOut != null) {
try { try {
evdevOut.write(REGRAB_REQUEST); evdevOut.write(REGRAB_REQUEST);
@ -178,9 +194,11 @@ public class EvdevHandler {
} }
} }
} }
}
public void ungrabAll() { @Override
if (!shutdown && evdevOut != null) { public void disableCapture() {
if (started && !shutdown && evdevOut != null) {
try { try {
evdevOut.write(UNGRAB_REQUEST); evdevOut.write(UNGRAB_REQUEST);
} catch (IOException e) { } catch (IOException e) {
@ -189,15 +207,16 @@ public class EvdevHandler {
} }
} }
public void start() { @Override
handlerThread.start(); public void destroy() {
}
public void stop() {
// We need to stop the process in this context otherwise // We need to stop the process in this context otherwise
// we could get stuck waiting on output from the process // we could get stuck waiting on output from the process
// in order to terminate it. // in order to terminate it.
if (!started) {
return;
}
shutdown = true; shutdown = true;
handlerThread.interrupt(); handlerThread.interrupt();