diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 206b17e8..71d0d7f5 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2,7 +2,7 @@ + android:versionName="2.5.6" > 0) { + return true; + } + + // Pass through keyboard input if we're not grabbing + if (!grabbedInput) { + return super.onKeyDown(keyCode, event); + } + keybTranslator.sendKeyDown(translated, getModifierState(event)); } @@ -360,16 +459,6 @@ public class Game extends Activity implements SurfaceHolder.Callback, @Override public boolean onKeyUp(int keyCode, KeyEvent event) { - // Pressing a volume button drops the immersive flag so the UI shows up again and doesn't - // go away. I'm not sure if that's a bug or a feature, but we're working around it here - if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) { - Handler h = getWindow().getDecorView().getHandler(); - if (h != null) { - h.removeCallbacks(hideSystemUi); - h.postDelayed(hideSystemUi, 2000); - } - } - InputDevice dev = event.getDevice(); if (dev == null) { return super.onKeyUp(keyCode, event); @@ -389,6 +478,15 @@ public class Game extends Activity implements SurfaceHolder.Callback, return super.onKeyUp(keyCode, event); } + if (handleMagicKeyCombos(translated, false)) { + return true; + } + + // Pass through keyboard input if we're not grabbing + if (!grabbedInput) { + return super.onKeyUp(keyCode, event); + } + keybTranslator.sendKeyUp(translated, getModifierState(event)); } @@ -405,25 +503,35 @@ public class Game extends Activity implements SurfaceHolder.Callback, return null; } } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) + + // Returns true if the event was consumed + private boolean handleMotionEvent(MotionEvent event) { + // Pass through keyboard input if we're not grabbing + if (!grabbedInput) { + return false; + } + + if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { + if (controllerHandler.handleMotionEvent(event)) { + return true; + } + } + else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { // This case is for touch-based input devices if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN || - event.getSource() == InputDevice.SOURCE_STYLUS) + event.getSource() == InputDevice.SOURCE_STYLUS) { int actionIndex = event.getActionIndex(); - + int eventX = (int)event.getX(actionIndex); int eventY = (int)event.getY(actionIndex); - + TouchContext context = getTouchContext(actionIndex); if (context == null) { - return super.onTouchEvent(event); + return false; } - + switch (event.getActionMasked()) { case MotionEvent.ACTION_POINTER_DOWN: @@ -445,15 +553,24 @@ public class Game extends Activity implements SurfaceHolder.Callback, touchContextMap[i].touchMoveEvent(eventX, eventY); } break; + case MotionEvent.ACTION_HOVER_MOVE: + // Send a mouse move update (if neccessary) + updateMousePosition((int)event.getX(), (int)event.getY()); + break; + case MotionEvent.ACTION_SCROLL: + // Send the vertical scroll packet + byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL); + conn.sendMouseScroll(vScrollClicks); + break; default: - return super.onTouchEvent(event); + return false; } } // This case is for mice else if (event.getSource() == InputDevice.SOURCE_MOUSE) { int changedButtons = event.getButtonState() ^ lastButtonState; - + if ((changedButtons & MotionEvent.BUTTON_PRIMARY) != 0) { if ((event.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0) { conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_LEFT); @@ -462,7 +579,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT); } } - + if ((changedButtons & MotionEvent.BUTTON_SECONDARY) != 0) { if ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) { conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT); @@ -471,7 +588,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT); } } - + if ((changedButtons & MotionEvent.BUTTON_TERTIARY) != 0) { if ((event.getButtonState() & MotionEvent.BUTTON_TERTIARY) != 0) { conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_MIDDLE); @@ -480,47 +597,41 @@ public class Game extends Activity implements SurfaceHolder.Callback, conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_MIDDLE); } } - + updateMousePosition((int)event.getX(), (int)event.getY()); - + lastButtonState = event.getButtonState(); } else { - return super.onTouchEvent(event); + // Unknown source + return false; } + // Handled a known source return true; } + + // Unknown class + return false; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (!handleMotionEvent(event)) { + return super.onTouchEvent(event); + } - return super.onTouchEvent(event); + return true; } @Override public boolean onGenericMotionEvent(MotionEvent event) { - if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { - if (controllerHandler.handleMotionEvent(event)) { - return true; - } - } - else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) - { - switch (event.getActionMasked()) - { - case MotionEvent.ACTION_HOVER_MOVE: - // Send a mouse move update (if neccessary) - updateMousePosition((int)event.getX(), (int)event.getY()); - break; - case MotionEvent.ACTION_SCROLL: - // Send the vertical scroll packet - byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL); - conn.sendMouseScroll(vScrollClicks); - break; - } - return true; + if (!handleMotionEvent(event)) { + return super.onGenericMotionEvent(event); } - return super.onGenericMotionEvent(event); + return true; } private void updateMousePosition(int eventX, int eventY) { @@ -548,15 +659,13 @@ public class Game extends Activity implements SurfaceHolder.Callback, @Override public boolean onGenericMotion(View v, MotionEvent event) { - // Send it to the activity's motion event handler - return onGenericMotionEvent(event); + return handleMotionEvent(event); } @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouch(View v, MotionEvent event) { - // Send it to the activity's touch event handler - return onTouchEvent(event); + return handleMotionEvent(event); } @Override @@ -592,8 +701,8 @@ public class Game extends Activity implements SurfaceHolder.Callback, if (!displayedFailureDialog) { displayedFailureDialog = true; - Dialog.displayDialog(this, "Connection Error", "Starting "+stage.getName()+" failed", true); stopConnection(); + Dialog.displayDialog(this, "Connection Error", "Starting "+stage.getName()+" failed", true); } } @@ -603,8 +712,8 @@ public class Game extends Activity implements SurfaceHolder.Callback, displayedFailureDialog = true; e.printStackTrace(); - Dialog.displayDialog(this, "Connection Terminated", "The connection failed unexpectedly", true); stopConnection(); + Dialog.displayDialog(this, "Connection Terminated", "The connection failed unexpectedly", true); } } @@ -618,7 +727,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, connecting = false; connected = true; - hideSystemUi(); + hideSystemUi(1000); } @Override @@ -712,6 +821,10 @@ public class Game extends Activity implements SurfaceHolder.Callback, public void keyboardEvent(boolean buttonDown, short keyCode) { short keyMap = keybTranslator.translate(keyCode); if (keyMap != 0) { + if (handleMagicKeyCombos(keyMap, buttonDown)) { + return; + } + if (buttonDown) { keybTranslator.sendKeyDown(keyMap, (byte) 0); } @@ -720,4 +833,27 @@ public class Game extends Activity implements SurfaceHolder.Callback, } } } + + @Override + public void onSystemUiVisibilityChange(int visibility) { + // Don't do anything if we're not connected + if (!connected) { + return; + } + + // This flag is set for all devices + if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { + hideSystemUi(2000); + } + // This flag is only set on 4.4+ + else if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && + (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) { + hideSystemUi(2000); + } + // This flag is only set before 4.4+ + else if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.KITKAT && + (visibility & View.SYSTEM_UI_FLAG_LOW_PROFILE) == 0) { + hideSystemUi(2000); + } + } } diff --git a/src/com/limelight/binding/input/ControllerHandler.java b/src/com/limelight/binding/input/ControllerHandler.java index 26d47a18..a84ed130 100644 --- a/src/com/limelight/binding/input/ControllerHandler.java +++ b/src/com/limelight/binding/input/ControllerHandler.java @@ -47,6 +47,9 @@ public class ControllerHandler { public ControllerHandler(NvConnection conn) { this.conn = conn; + + // We want limelight-common to scale the axis values to match Xinput values + ControllerPacket.enableAxisScaling = true; } private ControllerMapping createMappingForDevice(InputDevice dev) { @@ -135,6 +138,15 @@ public class ControllerHandler { InputDevice.MotionRange lsYRange = dev.getMotionRange(mapping.leftStickYAxis); if (lsXRange != null && lsYRange != null) { mapping.leftStickDeadzoneRadius = Math.max(lsXRange.getFlat(), lsYRange.getFlat()); + + // If there isn't a (reasonable) deadzone at all, use 20% + if (mapping.leftStickDeadzoneRadius < 0.02f) { + mapping.leftStickDeadzoneRadius = 0.20f; + } + // Check that the deadzone is 12% at minimum + else if (mapping.leftStickDeadzoneRadius < 0.12f) { + mapping.leftStickDeadzoneRadius = 0.12f; + } } } @@ -143,6 +155,15 @@ public class ControllerHandler { InputDevice.MotionRange rsYRange = dev.getMotionRange(mapping.rightStickYAxis); if (rsXRange != null && rsYRange != null) { mapping.rightStickDeadzoneRadius = Math.max(rsXRange.getFlat(), rsYRange.getFlat()); + + // If there isn't a (reasonable) deadzone at all, use 20% + if (mapping.rightStickDeadzoneRadius < 0.02f) { + mapping.rightStickDeadzoneRadius = 0.20f; + } + // Check that the deadzone is 12% at minimum + else if (mapping.rightStickDeadzoneRadius < 0.12f) { + mapping.rightStickDeadzoneRadius = 0.12f; + } } } @@ -242,7 +263,7 @@ public class ControllerHandler { // Scale the input based on the distance from the deadzone inputVector.getNormalized(normalizedInputVector); normalizedInputVector.scalarMultiply((inputVector.getMagnitude() - deadzoneRadius) / (1.0f - deadzoneRadius)); - + // Bound the X value to -1.0 to 1.0 if (normalizedInputVector.getX() > 1.0f) { normalizedInputVector.setX(1.0f); @@ -284,7 +305,7 @@ public class ControllerHandler { event.getAxisValue(mapping.rightStickYAxis), mapping.rightStickDeadzoneRadius); rightStickX = (short)(rightStickVector.getX() * 0x7FFE); - rightStickY = (short)(-rightStickVector.getY() * 0x7FFE); + rightStickY = (short)(-rightStickVector.getY() * 0x7FFE); } // Handle controllers with analog triggers diff --git a/src/com/limelight/binding/input/evdev/EvdevWatcher.java b/src/com/limelight/binding/input/evdev/EvdevWatcher.java index 0f584300..8d90b83b 100644 --- a/src/com/limelight/binding/input/evdev/EvdevWatcher.java +++ b/src/com/limelight/binding/input/evdev/EvdevWatcher.java @@ -2,6 +2,7 @@ package com.limelight.binding.input.evdev; import java.io.File; import java.util.HashMap; +import java.util.Map; import com.limelight.LimeLog; @@ -14,6 +15,7 @@ public class EvdevWatcher { private HashMap handlers = new HashMap(); private boolean shutdown = false; private boolean init = false; + private boolean ungrabbed = false; private EvdevListener listener; private Thread startThread; @@ -42,7 +44,11 @@ public class EvdevWatcher { } EvdevHandler handler = new EvdevHandler(PATH + "/" + fileName, listener); - handler.start(); + + // If we're ungrabbed now, don't start the handler + if (!ungrabbed) { + handler.start(); + } handlers.put(fileName, handler); } @@ -81,6 +87,31 @@ public class EvdevWatcher { return files; } + public void ungrabAll() { + synchronized (handlers) { + // Note that we're ungrabbed for now + ungrabbed = true; + + // Stop all handlers + for (EvdevHandler handler : handlers.values()) { + handler.stop(); + } + } + } + + public void regrabAll() { + synchronized (handlers) { + // We're regrabbing everything now + ungrabbed = false; + + for (Map.Entry entry : handlers.entrySet()) { + // We need to recreate each entry since we can't reuse a stopped one + entry.setValue(new EvdevHandler(PATH + "/" + entry.getKey(), listener)); + entry.getValue().start(); + } + } + } + public void start() { startThread = new Thread() { @Override @@ -122,10 +153,15 @@ public class EvdevWatcher { // Stop the observer observer.stopWatching(); - synchronized (handlers) { + synchronized (handlers) { // Stop creating new handlers shutdown = true; + // If we've already ungrabbed, there's nothing else to do + if (ungrabbed) { + return; + } + // Stop all handlers for (EvdevHandler handler : handlers.values()) { handler.stop();