Initial migration to Android Studio

This commit is contained in:
Cameron Gutman
2014-10-29 21:16:09 -07:00
parent 57d919798a
commit d84b4bcf9a
541 changed files with 458 additions and 3371 deletions

View File

@@ -0,0 +1,642 @@
package com.limelight.binding.input;
import java.util.HashMap;
import android.os.SystemClock;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.input.ControllerPacket;
import com.limelight.utils.Vector2d;
public class ControllerHandler {
private short inputMap = 0x0000;
private byte leftTrigger = 0x00;
private byte rightTrigger = 0x00;
private short rightStickX = 0x0000;
private short rightStickY = 0x0000;
private short leftStickX = 0x0000;
private short leftStickY = 0x0000;
private int emulatingButtonFlags = 0;
// Used for OUYA bumper state tracking since they force all buttons
// up when the OUYA button goes down. We watch the last time we get
// a bumper up and compare that to our maximum delay when we receive
// a Start button press to see if we should activate one of our
// emulated button combos.
private long lastLbUpTime = 0;
private long lastRbUpTime = 0;
private static final int MAXIMUM_BUMPER_UP_DELAY_MS = 100;
private static final int MINIMUM_BUTTON_DOWN_TIME_MS = 25;
private static final int EMULATING_SPECIAL = 0x1;
private static final int EMULATING_SELECT = 0x2;
private static final int EMULATED_SPECIAL_UP_DELAY_MS = 100;
private static final int EMULATED_SELECT_UP_DELAY_MS = 30;
private Vector2d inputVector = new Vector2d();
private Vector2d normalizedInputVector = new Vector2d();
private HashMap<String, ControllerMapping> mappings = new HashMap<String, ControllerMapping>();
private NvConnection conn;
public ControllerHandler(NvConnection conn) {
this.conn = conn;
// We want limelight-common to scale the axis values to match Xinput values
ControllerPacket.enableAxisScaling = true;
}
private static InputDevice.MotionRange getMotionRangeForJoystickAxis(InputDevice dev, int axis) {
InputDevice.MotionRange range;
// First get the axis for SOURCE_JOYSTICK
range = dev.getMotionRange(axis, InputDevice.SOURCE_JOYSTICK);
if (range == null) {
// Now try the axis for SOURCE_GAMEPAD
range = dev.getMotionRange(axis, InputDevice.SOURCE_GAMEPAD);
}
return range;
}
private ControllerMapping createMappingForDevice(InputDevice dev) {
ControllerMapping mapping = new ControllerMapping();
mapping.leftStickXAxis = MotionEvent.AXIS_X;
mapping.leftStickYAxis = MotionEvent.AXIS_Y;
InputDevice.MotionRange leftTriggerRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_LTRIGGER);
InputDevice.MotionRange rightTriggerRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RTRIGGER);
InputDevice.MotionRange brakeRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_BRAKE);
InputDevice.MotionRange gasRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_GAS);
if (leftTriggerRange != null && rightTriggerRange != null)
{
// Some controllers use LTRIGGER and RTRIGGER (like Ouya)
mapping.leftTriggerAxis = MotionEvent.AXIS_LTRIGGER;
mapping.rightTriggerAxis = MotionEvent.AXIS_RTRIGGER;
}
else if (brakeRange != null && gasRange != null)
{
// Others use GAS and BRAKE (like Moga)
mapping.leftTriggerAxis = MotionEvent.AXIS_BRAKE;
mapping.rightTriggerAxis = MotionEvent.AXIS_GAS;
}
else
{
InputDevice.MotionRange rxRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RX);
InputDevice.MotionRange ryRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RY);
if (rxRange != null && ryRange != null) {
String devName = dev.getName();
if (devName.contains("Xbox") || devName.contains("XBox") || devName.contains("X-Box")) {
// Xbox controllers use RX and RY for right stick
mapping.rightStickXAxis = MotionEvent.AXIS_RX;
mapping.rightStickYAxis = MotionEvent.AXIS_RY;
// Xbox controllers use Z and RZ for triggers
mapping.leftTriggerAxis = MotionEvent.AXIS_Z;
mapping.rightTriggerAxis = MotionEvent.AXIS_RZ;
mapping.triggersIdleNegative = true;
mapping.isXboxController = true;
}
else {
// DS4 controller uses RX and RY for triggers
mapping.leftTriggerAxis = MotionEvent.AXIS_RX;
mapping.rightTriggerAxis = MotionEvent.AXIS_RY;
mapping.triggersIdleNegative = true;
mapping.isDualShock4 = true;
}
}
}
if (mapping.rightStickXAxis == -1 && mapping.rightStickYAxis == -1) {
InputDevice.MotionRange zRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_Z);
InputDevice.MotionRange rzRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RZ);
// Most other controllers use Z and RZ for the right stick
if (zRange != null && rzRange != null) {
mapping.rightStickXAxis = MotionEvent.AXIS_Z;
mapping.rightStickYAxis = MotionEvent.AXIS_RZ;
}
else {
InputDevice.MotionRange rxRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RX);
InputDevice.MotionRange ryRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RY);
// Try RX and RY now
if (rxRange != null && ryRange != null) {
mapping.rightStickXAxis = MotionEvent.AXIS_RX;
mapping.rightStickYAxis = MotionEvent.AXIS_RY;
}
}
}
// Some devices have "hats" for d-pads
InputDevice.MotionRange hatXRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_HAT_X);
InputDevice.MotionRange hatYRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_HAT_Y);
if (hatXRange != null && hatYRange != null) {
mapping.hatXAxis = MotionEvent.AXIS_HAT_X;
mapping.hatYAxis = MotionEvent.AXIS_HAT_Y;
mapping.hatXDeadzone = hatXRange.getFlat();
mapping.hatYDeadzone = hatYRange.getFlat();
}
if (mapping.leftStickXAxis != -1 && mapping.leftStickYAxis != -1) {
InputDevice.MotionRange lsXRange = getMotionRangeForJoystickAxis(dev, mapping.leftStickXAxis);
InputDevice.MotionRange lsYRange = getMotionRangeForJoystickAxis(dev, mapping.leftStickYAxis);
if (lsXRange != null && lsYRange != null) {
// The flat values should never be negative but we'll deal with it if they are
mapping.leftStickDeadzoneRadius = Math.max(Math.abs(lsXRange.getFlat()),
Math.abs(lsYRange.getFlat()));
// Some devices (certain OUYAs at least) report a deadzone that's larger
// than the entire range of their axis likely due to some system software bug.
// If we see a very large deadzone, simply ignore the value and use our default.
if (mapping.leftStickDeadzoneRadius > 0.5f) {
mapping.leftStickDeadzoneRadius = 0;
}
// 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 15% at minimum
else if (mapping.leftStickDeadzoneRadius < 0.15f) {
mapping.leftStickDeadzoneRadius = 0.15f;
}
}
}
if (mapping.rightStickXAxis != -1 && mapping.rightStickYAxis != -1) {
InputDevice.MotionRange rsXRange = getMotionRangeForJoystickAxis(dev, mapping.rightStickXAxis);
InputDevice.MotionRange rsYRange = getMotionRangeForJoystickAxis(dev, mapping.rightStickYAxis);
if (rsXRange != null && rsYRange != null) {
// The flat values should never be negative but we'll deal with it if they are
mapping.rightStickDeadzoneRadius = Math.max(Math.abs(rsXRange.getFlat()),
Math.abs(rsYRange.getFlat()));
// Some devices (certain OUYAs at least) report a deadzone that's larger
// than the entire range of their axis likely due to some system software bug.
// If we see a very large deadzone, simply ignore the value and use our default.
if (mapping.rightStickDeadzoneRadius > 0.5f) {
mapping.rightStickDeadzoneRadius = 0;
}
// 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 15% at minimum
else if (mapping.rightStickDeadzoneRadius < 0.15f) {
mapping.rightStickDeadzoneRadius = 0.15f;
}
}
}
return mapping;
}
private ControllerMapping getMappingForDevice(InputDevice dev) {
// Unknown devices can't be handled
if (dev == null) {
return null;
}
String descriptor = dev.getDescriptor();
// Return the existing mapping if it exists
ControllerMapping mapping = mappings.get(descriptor);
if (mapping != null) {
return mapping;
}
// Otherwise create a new mapping
mapping = createMappingForDevice(dev);
mappings.put(descriptor, mapping);
return mapping;
}
private void sendControllerInputPacket() {
conn.sendControllerInput(inputMap, leftTrigger, rightTrigger,
leftStickX, leftStickY, rightStickX, rightStickY);
}
private static int handleRemapping(ControllerMapping mapping, KeyEvent event) {
if (mapping.isDualShock4) {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_BUTTON_Y:
return KeyEvent.KEYCODE_BUTTON_L1;
case KeyEvent.KEYCODE_BUTTON_Z:
return KeyEvent.KEYCODE_BUTTON_R1;
case KeyEvent.KEYCODE_BUTTON_C:
return KeyEvent.KEYCODE_BUTTON_B;
case KeyEvent.KEYCODE_BUTTON_X:
return KeyEvent.KEYCODE_BUTTON_Y;
case KeyEvent.KEYCODE_BUTTON_B:
return KeyEvent.KEYCODE_BUTTON_A;
case KeyEvent.KEYCODE_BUTTON_A:
return KeyEvent.KEYCODE_BUTTON_X;
case KeyEvent.KEYCODE_BUTTON_SELECT:
return KeyEvent.KEYCODE_BUTTON_THUMBL;
case KeyEvent.KEYCODE_BUTTON_START:
return KeyEvent.KEYCODE_BUTTON_THUMBR;
case KeyEvent.KEYCODE_BUTTON_L2:
return KeyEvent.KEYCODE_BUTTON_SELECT;
case KeyEvent.KEYCODE_BUTTON_R2:
return KeyEvent.KEYCODE_BUTTON_START;
// These are duplicate trigger events
case KeyEvent.KEYCODE_BUTTON_R1:
case KeyEvent.KEYCODE_BUTTON_L1:
return 0;
}
}
if (mapping.hatXAxis != -1 && mapping.hatYAxis != -1) {
switch (event.getKeyCode()) {
// These are duplicate dpad events for hat input
case KeyEvent.KEYCODE_DPAD_LEFT:
case KeyEvent.KEYCODE_DPAD_RIGHT:
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_DPAD_DOWN:
return 0;
}
}
else if (mapping.hatXAxis == -1 &&
mapping.hatYAxis == -1 &&
mapping.isXboxController &&
event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) {
// If there's not a proper Xbox controller mapping, we'll translate the raw d-pad
// scan codes into proper key codes
switch (event.getScanCode())
{
case 704:
return KeyEvent.KEYCODE_DPAD_LEFT;
case 705:
return KeyEvent.KEYCODE_DPAD_RIGHT;
case 706:
return KeyEvent.KEYCODE_DPAD_UP;
case 707:
return KeyEvent.KEYCODE_DPAD_DOWN;
}
}
return event.getKeyCode();
}
private Vector2d handleDeadZone(float x, float y, float deadzoneRadius) {
// Reinitialize our cached Vector2d object
inputVector.initialize(x, y);
if (inputVector.getMagnitude() <= deadzoneRadius) {
// Deadzone -- return the zero vector
return Vector2d.ZERO;
}
else {
// 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);
}
else if (normalizedInputVector.getX() < -1.0f) {
normalizedInputVector.setX(-1.0f);
}
// Bound the Y value to -1.0 to 1.0
if (normalizedInputVector.getY() > 1.0f) {
normalizedInputVector.setY(1.0f);
}
else if (normalizedInputVector.getY() < -1.0f) {
normalizedInputVector.setY(-1.0f);
}
return normalizedInputVector;
}
}
public boolean handleMotionEvent(MotionEvent event) {
ControllerMapping mapping = getMappingForDevice(event.getDevice());
if (mapping == null) {
return false;
}
// Handle left stick events outside of the deadzone
if (mapping.leftStickXAxis != -1 && mapping.leftStickYAxis != -1) {
Vector2d leftStickVector = handleDeadZone(event.getAxisValue(mapping.leftStickXAxis),
event.getAxisValue(mapping.leftStickYAxis), mapping.leftStickDeadzoneRadius);
leftStickX = (short)(leftStickVector.getX() * 0x7FFE);
leftStickY = (short)(-leftStickVector.getY() * 0x7FFE);
}
// Handle right stick events outside of the deadzone
if (mapping.rightStickXAxis != -1 && mapping.rightStickYAxis != -1) {
Vector2d rightStickVector = handleDeadZone(event.getAxisValue(mapping.rightStickXAxis),
event.getAxisValue(mapping.rightStickYAxis), mapping.rightStickDeadzoneRadius);
rightStickX = (short)(rightStickVector.getX() * 0x7FFE);
rightStickY = (short)(-rightStickVector.getY() * 0x7FFE);
}
// Handle controllers with analog triggers
if (mapping.leftTriggerAxis != -1 && mapping.rightTriggerAxis != -1) {
float L2 = event.getAxisValue(mapping.leftTriggerAxis);
float R2 = event.getAxisValue(mapping.rightTriggerAxis);
if (mapping.triggersIdleNegative) {
L2 = (L2 + 1) / 2;
R2 = (R2 + 1) / 2;
}
leftTrigger = (byte)(L2 * 0xFF);
rightTrigger = (byte)(R2 * 0xFF);
}
// Hats emulate d-pad events
if (mapping.hatXAxis != -1 && mapping.hatYAxis != -1) {
float hatX = event.getAxisValue(MotionEvent.AXIS_HAT_X);
float hatY = event.getAxisValue(MotionEvent.AXIS_HAT_Y);
inputMap &= ~(ControllerPacket.LEFT_FLAG | ControllerPacket.RIGHT_FLAG);
if (hatX < -(0.5 + mapping.hatXDeadzone)) {
inputMap |= ControllerPacket.LEFT_FLAG;
}
else if (hatX > (0.5 + mapping.hatXDeadzone)) {
inputMap |= ControllerPacket.RIGHT_FLAG;
}
inputMap &= ~(ControllerPacket.UP_FLAG | ControllerPacket.DOWN_FLAG);
if (hatY < -(0.5 + mapping.hatYDeadzone)) {
inputMap |= ControllerPacket.UP_FLAG;
}
else if (hatY > (0.5 + mapping.hatYDeadzone)) {
inputMap |= ControllerPacket.DOWN_FLAG;
}
}
sendControllerInputPacket();
return true;
}
public boolean handleButtonUp(int keyCode, KeyEvent event) {
ControllerMapping mapping = getMappingForDevice(event.getDevice());
if (mapping == null) {
return false;
}
keyCode = handleRemapping(mapping, event);
if (keyCode == 0) {
return true;
}
// If the button hasn't been down long enough, sleep for a bit before sending the up event
// This allows "instant" button presses (like OUYA's virtual menu button) to work. This
// path should not be triggered during normal usage.
if (SystemClock.uptimeMillis() - event.getDownTime() < ControllerHandler.MINIMUM_BUTTON_DOWN_TIME_MS)
{
// Since our sleep time is so short (10 ms), it shouldn't cause a problem doing this in the
// UI thread.
try {
Thread.sleep(ControllerHandler.MINIMUM_BUTTON_DOWN_TIME_MS);
} catch (InterruptedException e) {}
}
switch (keyCode) {
case KeyEvent.KEYCODE_BUTTON_MODE:
inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_START:
case KeyEvent.KEYCODE_MENU:
inputMap &= ~ControllerPacket.PLAY_FLAG;
break;
case KeyEvent.KEYCODE_BACK:
case KeyEvent.KEYCODE_BUTTON_SELECT:
inputMap &= ~ControllerPacket.BACK_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
inputMap &= ~ControllerPacket.LEFT_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
inputMap &= ~ControllerPacket.RIGHT_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_UP:
inputMap &= ~ControllerPacket.UP_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
inputMap &= ~ControllerPacket.DOWN_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_B:
inputMap &= ~ControllerPacket.B_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_BUTTON_A:
inputMap &= ~ControllerPacket.A_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_X:
inputMap &= ~ControllerPacket.X_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_Y:
inputMap &= ~ControllerPacket.Y_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_L1:
inputMap &= ~ControllerPacket.LB_FLAG;
lastLbUpTime = SystemClock.uptimeMillis();
break;
case KeyEvent.KEYCODE_BUTTON_R1:
inputMap &= ~ControllerPacket.RB_FLAG;
lastRbUpTime = SystemClock.uptimeMillis();
break;
case KeyEvent.KEYCODE_BUTTON_THUMBL:
inputMap &= ~ControllerPacket.LS_CLK_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_THUMBR:
inputMap &= ~ControllerPacket.RS_CLK_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_L2:
leftTrigger = 0;
break;
case KeyEvent.KEYCODE_BUTTON_R2:
rightTrigger = 0;
break;
default:
return false;
}
// Check if we're emulating the select button
if ((emulatingButtonFlags & ControllerHandler.EMULATING_SELECT) != 0)
{
// If either start or LB is up, select comes up too
if ((inputMap & ControllerPacket.PLAY_FLAG) == 0 ||
(inputMap & ControllerPacket.LB_FLAG) == 0)
{
inputMap &= ~ControllerPacket.BACK_FLAG;
emulatingButtonFlags &= ~ControllerHandler.EMULATING_SELECT;
try {
Thread.sleep(EMULATED_SELECT_UP_DELAY_MS);
} catch (InterruptedException e) {}
}
}
// Check if we're emulating the special button
if ((emulatingButtonFlags & ControllerHandler.EMULATING_SPECIAL) != 0)
{
// If either start or select and RB is up, the special button comes up too
if ((inputMap & ControllerPacket.PLAY_FLAG) == 0 ||
((inputMap & ControllerPacket.BACK_FLAG) == 0 &&
(inputMap & ControllerPacket.RB_FLAG) == 0))
{
inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG;
emulatingButtonFlags &= ~ControllerHandler.EMULATING_SPECIAL;
try {
Thread.sleep(EMULATED_SPECIAL_UP_DELAY_MS);
} catch (InterruptedException e) {}
}
}
sendControllerInputPacket();
return true;
}
public boolean handleButtonDown(int keyCode, KeyEvent event) {
ControllerMapping mapping = getMappingForDevice(event.getDevice());
if (mapping == null) {
return false;
}
keyCode = handleRemapping(mapping, event);
if (keyCode == 0) {
return true;
}
switch (keyCode) {
case KeyEvent.KEYCODE_BUTTON_MODE:
inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_START:
case KeyEvent.KEYCODE_MENU:
inputMap |= ControllerPacket.PLAY_FLAG;
break;
case KeyEvent.KEYCODE_BACK:
case KeyEvent.KEYCODE_BUTTON_SELECT:
inputMap |= ControllerPacket.BACK_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
inputMap |= ControllerPacket.LEFT_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
inputMap |= ControllerPacket.RIGHT_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_UP:
inputMap |= ControllerPacket.UP_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
inputMap |= ControllerPacket.DOWN_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_B:
inputMap |= ControllerPacket.B_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_BUTTON_A:
inputMap |= ControllerPacket.A_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_X:
inputMap |= ControllerPacket.X_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_Y:
inputMap |= ControllerPacket.Y_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_L1:
inputMap |= ControllerPacket.LB_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_R1:
inputMap |= ControllerPacket.RB_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_THUMBL:
inputMap |= ControllerPacket.LS_CLK_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_THUMBR:
inputMap |= ControllerPacket.RS_CLK_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_L2:
leftTrigger = (byte)0xFF;
break;
case KeyEvent.KEYCODE_BUTTON_R2:
rightTrigger = (byte)0xFF;
break;
default:
return false;
}
// Start+LB acts like select for controllers with one button
if ((inputMap & ControllerPacket.PLAY_FLAG) != 0 &&
((inputMap & ControllerPacket.LB_FLAG) != 0 ||
SystemClock.uptimeMillis() - lastLbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS))
{
inputMap &= ~(ControllerPacket.PLAY_FLAG | ControllerPacket.LB_FLAG);
inputMap |= ControllerPacket.BACK_FLAG;
emulatingButtonFlags |= ControllerHandler.EMULATING_SELECT;
}
// We detect select+start or start+RB as the special button combo
if (((inputMap & ControllerPacket.RB_FLAG) != 0 ||
(SystemClock.uptimeMillis() - lastRbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS) ||
(inputMap & ControllerPacket.BACK_FLAG) != 0) &&
(inputMap & ControllerPacket.PLAY_FLAG) != 0)
{
inputMap &= ~(ControllerPacket.BACK_FLAG | ControllerPacket.PLAY_FLAG | ControllerPacket.RB_FLAG);
inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
emulatingButtonFlags |= ControllerHandler.EMULATING_SPECIAL;
}
sendControllerInputPacket();
return true;
}
class ControllerMapping {
public int leftStickXAxis = -1;
public int leftStickYAxis = -1;
public float leftStickDeadzoneRadius;
public int rightStickXAxis = -1;
public int rightStickYAxis = -1;
public float rightStickDeadzoneRadius;
public int leftTriggerAxis = -1;
public int rightTriggerAxis = -1;
public boolean triggersIdleNegative;
public int hatXAxis = -1;
public int hatYAxis = -1;
public float hatXDeadzone;
public float hatYDeadzone;
public boolean isDualShock4;
public boolean isXboxController;
}
}

View File

@@ -0,0 +1,260 @@
package com.limelight.binding.input;
import android.view.KeyEvent;
import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.input.KeycodeTranslator;
/**
* Class to translate a Android key code into the codes GFE is expecting
* @author Diego Waxemberg
* @author Cameron Gutman
*/
public class KeyboardTranslator extends KeycodeTranslator {
/**
* GFE's prefix for every key code
*/
public static final short KEY_PREFIX = (short) 0x80;
public static final int VK_0 = 48;
public static final int VK_9 = 57;
public static final int VK_A = 65;
public static final int VK_Z = 90;
public static final int VK_ALT = 18;
public static final int VK_NUMPAD0 = 96;
public static final int VK_BACK_SLASH = 92;
public static final int VK_CAPS_LOCK = 20;
public static final int VK_CLEAR = 12;
public static final int VK_COMMA = 44;
public static final int VK_CONTROL = 17;
public static final int VK_BACK_SPACE = 8;
public static final int VK_EQUALS = 61;
public static final int VK_ESCAPE = 27;
public static final int VK_F1 = 112;
public static final int VK_PERIOD = 46;
public static final int VK_INSERT = 155;
public static final int VK_OPEN_BRACKET = 91;
public static final int VK_WINDOWS = 524;
public static final int VK_MINUS = 45;
public static final int VK_END = 35;
public static final int VK_HOME = 36;
public static final int VK_NUM_LOCK = 144;
public static final int VK_PAGE_UP = 33;
public static final int VK_PAGE_DOWN = 34;
public static final int VK_PLUS = 521;
public static final int VK_CLOSE_BRACKET = 93;
public static final int VK_SCROLL_LOCK = 145;
public static final int VK_SEMICOLON = 59;
public static final int VK_SHIFT = 16;
public static final int VK_SLASH = 47;
public static final int VK_SPACE = 32;
public static final int VK_PRINTSCREEN = 154;
public static final int VK_TAB = 9;
public static final int VK_LEFT = 37;
public static final int VK_RIGHT = 39;
public static final int VK_UP = 38;
public static final int VK_DOWN = 40;
public static final int VK_BACK_QUOTE = 192;
public static final int VK_QUOTE = 222;
public static final int VK_PAUSE = 19;
/**
* Constructs a new translator for the specified connection
* @param conn the connection to which the translated codes are sent
*/
public KeyboardTranslator(NvConnection conn) {
super(conn);
}
/**
* Translates the given keycode and returns the GFE keycode
* @param keycode the code to be translated
* @return a GFE keycode for the given keycode
*/
@Override
public short translate(int keycode) {
int translated;
/* There seems to be no clean mapping between Android key codes
* and what Nvidia sends over the wire. If someone finds one,
* I'll happily delete this code :)
*/
if (keycode >= KeyEvent.KEYCODE_0 &&
keycode <= KeyEvent.KEYCODE_9) {
translated = (keycode - KeyEvent.KEYCODE_0) + VK_0;
}
else if (keycode >= KeyEvent.KEYCODE_A &&
keycode <= KeyEvent.KEYCODE_Z) {
translated = (keycode - KeyEvent.KEYCODE_A) + VK_A;
}
else if (keycode >= KeyEvent.KEYCODE_NUMPAD_0 &&
keycode <= KeyEvent.KEYCODE_NUMPAD_9) {
translated = (keycode - KeyEvent.KEYCODE_NUMPAD_0) + VK_NUMPAD0;
}
else if (keycode >= KeyEvent.KEYCODE_F1 &&
keycode <= KeyEvent.KEYCODE_F12) {
translated = (keycode - KeyEvent.KEYCODE_F1) + VK_F1;
}
else {
switch (keycode) {
case KeyEvent.KEYCODE_ALT_LEFT:
case KeyEvent.KEYCODE_ALT_RIGHT:
translated = VK_ALT;
break;
case KeyEvent.KEYCODE_BACKSLASH:
translated = 0xdc;
break;
case KeyEvent.KEYCODE_CAPS_LOCK:
translated = VK_CAPS_LOCK;
break;
case KeyEvent.KEYCODE_CLEAR:
translated = VK_CLEAR;
break;
case KeyEvent.KEYCODE_COMMA:
translated = 0xbc;
break;
case KeyEvent.KEYCODE_CTRL_LEFT:
case KeyEvent.KEYCODE_CTRL_RIGHT:
translated = VK_CONTROL;
break;
case KeyEvent.KEYCODE_DEL:
translated = VK_BACK_SPACE;
break;
case KeyEvent.KEYCODE_ENTER:
translated = 0x0d;
break;
case KeyEvent.KEYCODE_EQUALS:
translated = 0xbb;
break;
case KeyEvent.KEYCODE_ESCAPE:
translated = VK_ESCAPE;
break;
case KeyEvent.KEYCODE_FORWARD_DEL:
// Nvidia maps period to delete
translated = VK_PERIOD;
break;
case KeyEvent.KEYCODE_INSERT:
translated = -1;
break;
case KeyEvent.KEYCODE_LEFT_BRACKET:
translated = 0xdb;
break;
case KeyEvent.KEYCODE_META_LEFT:
case KeyEvent.KEYCODE_META_RIGHT:
translated = VK_WINDOWS;
break;
case KeyEvent.KEYCODE_MINUS:
translated = 0xbd;
break;
case KeyEvent.KEYCODE_MOVE_END:
translated = VK_END;
break;
case KeyEvent.KEYCODE_MOVE_HOME:
translated = VK_HOME;
break;
case KeyEvent.KEYCODE_NUM_LOCK:
translated = VK_NUM_LOCK;
break;
case KeyEvent.KEYCODE_PAGE_DOWN:
translated = VK_PAGE_DOWN;
break;
case KeyEvent.KEYCODE_PAGE_UP:
translated = VK_PAGE_UP;
break;
case KeyEvent.KEYCODE_PERIOD:
translated = 0xbe;
break;
case KeyEvent.KEYCODE_RIGHT_BRACKET:
translated = 0xdd;
break;
case KeyEvent.KEYCODE_SCROLL_LOCK:
translated = VK_SCROLL_LOCK;
break;
case KeyEvent.KEYCODE_SEMICOLON:
translated = 0xba;
break;
case KeyEvent.KEYCODE_SHIFT_LEFT:
case KeyEvent.KEYCODE_SHIFT_RIGHT:
translated = VK_SHIFT;
break;
case KeyEvent.KEYCODE_SLASH:
translated = 0xbf;
break;
case KeyEvent.KEYCODE_SPACE:
translated = VK_SPACE;
break;
case KeyEvent.KEYCODE_SYSRQ:
// Android defines this as SysRq/PrntScrn
translated = VK_PRINTSCREEN;
break;
case KeyEvent.KEYCODE_TAB:
translated = VK_TAB;
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
translated = VK_LEFT;
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
translated = VK_RIGHT;
break;
case KeyEvent.KEYCODE_DPAD_UP:
translated = VK_UP;
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
translated = VK_DOWN;
break;
case KeyEvent.KEYCODE_GRAVE:
translated = VK_BACK_QUOTE;
break;
case KeyEvent.KEYCODE_APOSTROPHE:
translated = 0xde;
break;
case KeyEvent.KEYCODE_BREAK:
translated = VK_PAUSE;
break;
default:
System.out.println("No key for "+keycode);
return 0;
}
}
return (short) ((KEY_PREFIX << 8) | translated);
}
}

View File

@@ -0,0 +1,93 @@
package com.limelight.binding.input;
import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.input.MouseButtonPacket;
public class TouchContext {
private int lastTouchX = 0;
private int lastTouchY = 0;
private int originalTouchX = 0;
private int originalTouchY = 0;
private long originalTouchTime = 0;
private NvConnection conn;
private int actionIndex;
private static final int TAP_MOVEMENT_THRESHOLD = 10;
private static final int TAP_TIME_THRESHOLD = 250;
public TouchContext(NvConnection conn, int actionIndex)
{
this.conn = conn;
this.actionIndex = actionIndex;
}
private boolean isTap()
{
int xDelta = Math.abs(lastTouchX - originalTouchX);
int yDelta = Math.abs(lastTouchY - originalTouchY);
long timeDelta = System.currentTimeMillis() - originalTouchTime;
return xDelta <= TAP_MOVEMENT_THRESHOLD &&
yDelta <= TAP_MOVEMENT_THRESHOLD &&
timeDelta <= TAP_TIME_THRESHOLD;
}
private byte getMouseButtonIndex()
{
if (actionIndex == 1) {
return MouseButtonPacket.BUTTON_RIGHT;
}
else {
return MouseButtonPacket.BUTTON_LEFT;
}
}
public boolean touchDownEvent(int eventX, int eventY)
{
originalTouchX = lastTouchX = eventX;
originalTouchY = lastTouchY = eventY;
originalTouchTime = System.currentTimeMillis();
return true;
}
public void touchUpEvent(int eventX, int eventY)
{
if (isTap())
{
byte buttonIndex = getMouseButtonIndex();
// Lower the mouse button
conn.sendMouseButtonDown(buttonIndex);
// We need to sleep a bit here because some games
// do input detection by polling
try {
Thread.sleep(100);
} catch (InterruptedException e) {}
// Raise the mouse button
conn.sendMouseButtonUp(buttonIndex);
}
}
public boolean touchMoveEvent(int eventX, int eventY)
{
if (eventX != lastTouchX || eventY != lastTouchY)
{
// We only send moves for the primary touch point
if (actionIndex == 0) {
conn.sendMouseMove((short)(eventX - lastTouchX),
(short)(eventY - lastTouchY));
}
lastTouchX = eventX;
lastTouchY = eventY;
return true;
}
return false;
}
}

View File

@@ -0,0 +1,41 @@
package com.limelight.binding.input.evdev;
public class EvdevEvent {
public static final int EVDEV_MIN_EVENT_SIZE = 16;
public static final int EVDEV_MAX_EVENT_SIZE = 24;
/* Event types */
public static final short EV_SYN = 0x00;
public static final short EV_KEY = 0x01;
public static final short EV_REL = 0x02;
public static final short EV_MSC = 0x04;
/* Relative axes */
public static final short REL_X = 0x00;
public static final short REL_Y = 0x01;
public static final short REL_WHEEL = 0x08;
/* Buttons */
public static final short BTN_LEFT = 0x110;
public static final short BTN_RIGHT = 0x111;
public static final short BTN_MIDDLE = 0x112;
public static final short BTN_SIDE = 0x113;
public static final short BTN_EXTRA = 0x114;
public static final short BTN_FORWARD = 0x115;
public static final short BTN_BACK = 0x116;
public static final short BTN_TASK = 0x117;
public static final short BTN_GAMEPAD = 0x130;
/* Keys */
public static final short KEY_Q = 16;
public short type;
public short code;
public int value;
public EvdevEvent(short type, short code, int value) {
this.type = type;
this.code = code;
this.value = value;
}
}

View File

@@ -0,0 +1,167 @@
package com.limelight.binding.input.evdev;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import com.limelight.LimeLog;
public class EvdevHandler {
private String absolutePath;
private EvdevListener listener;
private boolean shutdown = false;
private int fd = -1;
private Thread handlerThread = new Thread() {
@Override
public void run() {
// All the finally blocks here make this code look like a mess
// but it's important that we get this right to avoid causing
// system-wide input problems.
// Open the /dev/input/eventX file
fd = EvdevReader.open(absolutePath);
if (fd == -1) {
LimeLog.warning("Unable to open "+absolutePath);
return;
}
try {
// Check if it's a mouse or keyboard, but not a gamepad
if ((!EvdevReader.isMouse(fd) && !EvdevReader.isAlphaKeyboard(fd)) ||
EvdevReader.isGamepad(fd)) {
// We only handle keyboards and mice
return;
}
// Grab it for ourselves
if (!EvdevReader.grab(fd)) {
LimeLog.warning("Unable to grab "+absolutePath);
return;
}
LimeLog.info("Grabbed device for raw keyboard/mouse input: "+absolutePath);
ByteBuffer buffer = ByteBuffer.allocate(EvdevEvent.EVDEV_MAX_EVENT_SIZE).order(ByteOrder.nativeOrder());
try {
int deltaX = 0;
int deltaY = 0;
byte deltaScroll = 0;
while (!isInterrupted() && !shutdown) {
EvdevEvent event = EvdevReader.read(fd, buffer);
if (event == null) {
return;
}
switch (event.type)
{
case EvdevEvent.EV_SYN:
if (deltaX != 0 || deltaY != 0) {
listener.mouseMove(deltaX, deltaY);
deltaX = deltaY = 0;
}
if (deltaScroll != 0) {
listener.mouseScroll(deltaScroll);
deltaScroll = 0;
}
break;
case EvdevEvent.EV_REL:
switch (event.code)
{
case EvdevEvent.REL_X:
deltaX = event.value;
break;
case EvdevEvent.REL_Y:
deltaY = event.value;
break;
case EvdevEvent.REL_WHEEL:
deltaScroll = (byte) event.value;
break;
}
break;
case EvdevEvent.EV_KEY:
switch (event.code)
{
case EvdevEvent.BTN_LEFT:
listener.mouseButtonEvent(EvdevListener.BUTTON_LEFT,
event.value != 0);
break;
case EvdevEvent.BTN_MIDDLE:
listener.mouseButtonEvent(EvdevListener.BUTTON_MIDDLE,
event.value != 0);
break;
case EvdevEvent.BTN_RIGHT:
listener.mouseButtonEvent(EvdevListener.BUTTON_RIGHT,
event.value != 0);
break;
case EvdevEvent.BTN_SIDE:
case EvdevEvent.BTN_EXTRA:
case EvdevEvent.BTN_FORWARD:
case EvdevEvent.BTN_BACK:
case EvdevEvent.BTN_TASK:
// Other unhandled mouse buttons
break;
default:
// We got some unrecognized button. This means
// someone is trying to use the other device in this
// "combination" input device. We'll try to handle
// it via keyboard, but we're not going to disconnect
// if we can't
short keyCode = EvdevTranslator.translateEvdevKeyCode(event.code);
if (keyCode != 0) {
listener.keyboardEvent(event.value != 0, keyCode);
}
break;
}
break;
case EvdevEvent.EV_MSC:
break;
}
}
} finally {
// Release our grab
EvdevReader.ungrab(fd);
}
} finally {
// Close the file
EvdevReader.close(fd);
}
}
};
public EvdevHandler(String absolutePath, EvdevListener listener) {
this.absolutePath = absolutePath;
this.listener = listener;
}
public void start() {
handlerThread.start();
}
public void stop() {
// Close the fd. It doesn't matter if this races
// with the handler thread. We'll close this out from
// under the thread to wake it up
if (fd != -1) {
EvdevReader.close(fd);
}
shutdown = true;
handlerThread.interrupt();
try {
handlerThread.join();
} catch (InterruptedException e) {}
}
public void notifyDeleted() {
stop();
}
}

View File

@@ -0,0 +1,12 @@
package com.limelight.binding.input.evdev;
public interface EvdevListener {
public static final int BUTTON_LEFT = 1;
public static final int BUTTON_MIDDLE = 2;
public static final int BUTTON_RIGHT = 3;
public void mouseMove(int deltaX, int deltaY);
public void mouseButtonEvent(int buttonId, boolean down);
public void mouseScroll(byte amount);
public void keyboardEvent(boolean buttonDown, short keyCode);
}

View File

@@ -0,0 +1,102 @@
package com.limelight.binding.input.evdev;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Locale;
import com.limelight.LimeLog;
public class EvdevReader {
static {
System.loadLibrary("evdev_reader");
}
// Requires root to chmod /dev/input/eventX
public static boolean setPermissions(String[] files, int octalPermissions) {
ProcessBuilder builder = new ProcessBuilder("su");
try {
Process p = builder.start();
OutputStream stdin = p.getOutputStream();
for (String file : files) {
stdin.write(String.format((Locale)null, "chmod %o %s\n", octalPermissions, file).getBytes("UTF-8"));
}
stdin.write("exit\n".getBytes("UTF-8"));
stdin.flush();
p.waitFor();
p.destroy();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
// Returns the fd to be passed to other function or -1 on error
public static native int open(String fileName);
// Prevent other apps (including Android itself) from using the device while "grabbed"
public static native boolean grab(int fd);
public static native boolean ungrab(int fd);
// Used for checking device capabilities
public static native boolean hasRelAxis(int fd, short axis);
public static native boolean hasAbsAxis(int fd, short axis);
public static native boolean hasKey(int fd, short key);
public static boolean isMouse(int fd) {
// This is the same check that Android does in EventHub.cpp
return hasRelAxis(fd, EvdevEvent.REL_X) &&
hasRelAxis(fd, EvdevEvent.REL_Y) &&
hasKey(fd, EvdevEvent.BTN_LEFT);
}
public static boolean isAlphaKeyboard(int fd) {
// This is the same check that Android does in EventHub.cpp
return hasKey(fd, EvdevEvent.KEY_Q);
}
public static boolean isGamepad(int fd) {
return hasKey(fd, EvdevEvent.BTN_GAMEPAD);
}
// Returns the bytes read or -1 on error
private static native int read(int fd, byte[] buffer);
// Takes a byte buffer to use to read the output into.
// This buffer MUST be in native byte order and at least
// EVDEV_MAX_EVENT_SIZE bytes long.
public static EvdevEvent read(int fd, ByteBuffer buffer) {
int bytesRead = read(fd, buffer.array());
if (bytesRead < 0) {
LimeLog.warning("Failed to read: "+bytesRead);
return null;
}
else if (bytesRead < EvdevEvent.EVDEV_MIN_EVENT_SIZE) {
LimeLog.warning("Short read: "+bytesRead);
return null;
}
buffer.limit(bytesRead);
buffer.rewind();
// Throw away the time stamp
if (bytesRead == EvdevEvent.EVDEV_MAX_EVENT_SIZE) {
buffer.getLong();
buffer.getLong();
} else {
buffer.getInt();
buffer.getInt();
}
return new EvdevEvent(buffer.getShort(), buffer.getShort(), buffer.getInt());
}
// Closes the fd from open()
public static native int close(int fd);
}

View File

@@ -0,0 +1,139 @@
package com.limelight.binding.input.evdev;
import android.view.KeyEvent;
public class EvdevTranslator {
public static final short EVDEV_KEY_CODES[] = {
0, //KeyEvent.VK_RESERVED
KeyEvent.KEYCODE_ESCAPE,
KeyEvent.KEYCODE_1,
KeyEvent.KEYCODE_2,
KeyEvent.KEYCODE_3,
KeyEvent.KEYCODE_4,
KeyEvent.KEYCODE_5,
KeyEvent.KEYCODE_6,
KeyEvent.KEYCODE_7,
KeyEvent.KEYCODE_8,
KeyEvent.KEYCODE_9,
KeyEvent.KEYCODE_0,
KeyEvent.KEYCODE_MINUS,
KeyEvent.KEYCODE_EQUALS,
KeyEvent.KEYCODE_DEL,
KeyEvent.KEYCODE_TAB,
KeyEvent.KEYCODE_Q,
KeyEvent.KEYCODE_W,
KeyEvent.KEYCODE_E,
KeyEvent.KEYCODE_R,
KeyEvent.KEYCODE_T,
KeyEvent.KEYCODE_Y,
KeyEvent.KEYCODE_U,
KeyEvent.KEYCODE_I,
KeyEvent.KEYCODE_O,
KeyEvent.KEYCODE_P,
KeyEvent.KEYCODE_LEFT_BRACKET,
KeyEvent.KEYCODE_RIGHT_BRACKET,
KeyEvent.KEYCODE_ENTER,
KeyEvent.KEYCODE_CTRL_LEFT,
KeyEvent.KEYCODE_A,
KeyEvent.KEYCODE_S,
KeyEvent.KEYCODE_D,
KeyEvent.KEYCODE_F,
KeyEvent.KEYCODE_G,
KeyEvent.KEYCODE_H,
KeyEvent.KEYCODE_J,
KeyEvent.KEYCODE_K,
KeyEvent.KEYCODE_L,
KeyEvent.KEYCODE_SEMICOLON,
KeyEvent.KEYCODE_APOSTROPHE,
KeyEvent.KEYCODE_GRAVE,
KeyEvent.KEYCODE_SHIFT_LEFT,
KeyEvent.KEYCODE_BACKSLASH,
KeyEvent.KEYCODE_Z,
KeyEvent.KEYCODE_X,
KeyEvent.KEYCODE_C,
KeyEvent.KEYCODE_V,
KeyEvent.KEYCODE_B,
KeyEvent.KEYCODE_N,
KeyEvent.KEYCODE_M,
KeyEvent.KEYCODE_COMMA,
KeyEvent.KEYCODE_PERIOD,
KeyEvent.KEYCODE_SLASH,
KeyEvent.KEYCODE_SHIFT_RIGHT,
KeyEvent.KEYCODE_NUMPAD_MULTIPLY,
KeyEvent.KEYCODE_ALT_LEFT,
KeyEvent.KEYCODE_SPACE,
KeyEvent.KEYCODE_CAPS_LOCK,
KeyEvent.KEYCODE_F1,
KeyEvent.KEYCODE_F2,
KeyEvent.KEYCODE_F3,
KeyEvent.KEYCODE_F4,
KeyEvent.KEYCODE_F5,
KeyEvent.KEYCODE_F6,
KeyEvent.KEYCODE_F7,
KeyEvent.KEYCODE_F8,
KeyEvent.KEYCODE_F9,
KeyEvent.KEYCODE_F10,
KeyEvent.KEYCODE_NUM_LOCK,
KeyEvent.KEYCODE_SCROLL_LOCK,
KeyEvent.KEYCODE_NUMPAD_7,
KeyEvent.KEYCODE_NUMPAD_8,
KeyEvent.KEYCODE_NUMPAD_9,
KeyEvent.KEYCODE_NUMPAD_SUBTRACT,
KeyEvent.KEYCODE_NUMPAD_4,
KeyEvent.KEYCODE_NUMPAD_5,
KeyEvent.KEYCODE_NUMPAD_6,
KeyEvent.KEYCODE_NUMPAD_ADD,
KeyEvent.KEYCODE_NUMPAD_1,
KeyEvent.KEYCODE_NUMPAD_2,
KeyEvent.KEYCODE_NUMPAD_3,
KeyEvent.KEYCODE_NUMPAD_0,
KeyEvent.KEYCODE_NUMPAD_DOT,
0,
0, //KeyEvent.VK_ZENKAKUHANKAKU,
0, //KeyEvent.VK_102ND,
KeyEvent.KEYCODE_F11,
KeyEvent.KEYCODE_F12,
0, //KeyEvent.VK_RO,
0, //KeyEvent.VK_KATAKANA,
0, //KeyEvent.VK_HIRAGANA,
0, //KeyEvent.VK_HENKAN,
0, //KeyEvent.VK_KATAKANAHIRAGANA,
0, //KeyEvent.VK_MUHENKAN,
0, //KeyEvent.VK_KPJPCOMMA,
KeyEvent.KEYCODE_NUMPAD_ENTER,
KeyEvent.KEYCODE_CTRL_RIGHT,
KeyEvent.KEYCODE_NUMPAD_DIVIDE,
KeyEvent.KEYCODE_SYSRQ,
KeyEvent.KEYCODE_ALT_RIGHT,
0, //KeyEvent.VK_LINEFEED,
KeyEvent.KEYCODE_HOME,
KeyEvent.KEYCODE_DPAD_UP,
KeyEvent.KEYCODE_PAGE_UP,
KeyEvent.KEYCODE_DPAD_LEFT,
KeyEvent.KEYCODE_DPAD_RIGHT,
KeyEvent.KEYCODE_MOVE_END,
KeyEvent.KEYCODE_DPAD_DOWN,
KeyEvent.KEYCODE_PAGE_DOWN,
KeyEvent.KEYCODE_INSERT,
KeyEvent.KEYCODE_FORWARD_DEL,
0, //KeyEvent.VK_MACRO,
0, //KeyEvent.VK_MUTE,
0, //KeyEvent.VK_VOLUMEDOWN,
0, //KeyEvent.VK_VOLUMEUP,
0, //KeyEvent.VK_POWER, /* SC System Power Down */
KeyEvent.KEYCODE_NUMPAD_EQUALS,
0, //KeyEvent.VK_KPPLUSMINUS,
KeyEvent.KEYCODE_BREAK,
0, //KeyEvent.VK_SCALE, /* AL Compiz Scale (Expose) */
};
public static short translateEvdevKeyCode(short evdevKeyCode) {
if (evdevKeyCode < EVDEV_KEY_CODES.length) {
return EVDEV_KEY_CODES[evdevKeyCode];
}
return 0;
}
}

View File

@@ -0,0 +1,171 @@
package com.limelight.binding.input.evdev;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import com.limelight.LimeLog;
import android.os.FileObserver;
public class EvdevWatcher {
private static final String PATH = "/dev/input";
private static final String REQUIRED_FILE_PREFIX = "event";
private HashMap<String, EvdevHandler> handlers = new HashMap<String, EvdevHandler>();
private boolean shutdown = false;
private boolean init = false;
private boolean ungrabbed = false;
private EvdevListener listener;
private Thread startThread;
private FileObserver observer = new FileObserver(PATH, FileObserver.CREATE | FileObserver.DELETE) {
@Override
public void onEvent(int event, String fileName) {
if (fileName == null) {
return;
}
if (!fileName.startsWith(REQUIRED_FILE_PREFIX)) {
return;
}
synchronized (handlers) {
if (shutdown) {
return;
}
if ((event & FileObserver.CREATE) != 0) {
LimeLog.info("Starting evdev handler for "+fileName);
if (!init) {
// If this a real new device, update permissions again so we can read it
EvdevReader.setPermissions(new String[]{PATH + "/" + fileName}, 0666);
}
EvdevHandler handler = new EvdevHandler(PATH + "/" + fileName, listener);
// If we're ungrabbed now, don't start the handler
if (!ungrabbed) {
handler.start();
}
handlers.put(fileName, handler);
}
if ((event & FileObserver.DELETE) != 0) {
LimeLog.info("Halting evdev handler for "+fileName);
EvdevHandler handler = handlers.remove(fileName);
if (handler != null) {
handler.notifyDeleted();
}
}
}
}
};
public EvdevWatcher(EvdevListener listener) {
this.listener = listener;
}
private File[] rundownWithPermissionsChange(int newPermissions) {
// Rundown existing files
File devInputDir = new File(PATH);
File[] files = devInputDir.listFiles();
if (files == null) {
return new File[0];
}
// Set desired permissions
String[] filePaths = new String[files.length];
for (int i = 0; i < files.length; i++) {
filePaths[i] = files[i].getAbsolutePath();
}
EvdevReader.setPermissions(filePaths, newPermissions);
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<String, EvdevHandler> 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
public void run() {
// List all files and allow us access
File[] files = rundownWithPermissionsChange(0666);
init = true;
for (File f : files) {
observer.onEvent(FileObserver.CREATE, f.getName());
}
// Done with initial onEvent calls
init = false;
// Start watching for new files
observer.startWatching();
synchronized (startThread) {
// Wait to be awoken again by shutdown()
try {
startThread.wait();
} catch (InterruptedException e) {}
}
// Giveup eventX permissions
rundownWithPermissionsChange(066);
}
};
startThread.start();
}
public void shutdown() {
// Let start thread cleanup on it's own sweet time
synchronized (startThread) {
startThread.notify();
}
// Stop the observer
observer.stopWatching();
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();
}
}
}
}