Faruq Rasid
2015-12-31 10:14:30 +08:00
parent 1d6b5a35bd
commit f8ab7b8e13
450 changed files with 6859 additions and 101095 deletions

View File

@@ -15,7 +15,7 @@ public class KeyboardTranslator extends KeycodeTranslator {
/**
* GFE's prefix for every key code
*/
public static final short KEY_PREFIX = (short) 0x80;
private static final short KEY_PREFIX = (short) 0x80;
public static final int VK_0 = 48;
public static final int VK_9 = 57;
@@ -23,8 +23,8 @@ public class KeyboardTranslator extends KeycodeTranslator {
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_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;

View File

@@ -3,114 +3,226 @@ package com.limelight.binding.input;
import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.input.MouseButtonPacket;
import java.util.Timer;
import java.util.TimerTask;
public class TouchContext {
private int lastTouchX = 0;
private int lastTouchY = 0;
private int originalTouchX = 0;
private int originalTouchY = 0;
private long originalTouchTime = 0;
private int lastTouchX = 0;
private int lastTouchY = 0;
private int originalTouchX = 0;
private int originalTouchY = 0;
private long originalTouchTime = 0;
private boolean cancelled;
private NvConnection conn;
private int actionIndex;
private double xFactor, yFactor;
private static final int TAP_MOVEMENT_THRESHOLD = 10;
private static final int TAP_TIME_THRESHOLD = 250;
public TouchContext(NvConnection conn, int actionIndex, double xFactor, double yFactor)
{
this.conn = conn;
this.actionIndex = actionIndex;
private boolean confirmedMove;
private boolean confirmedDrag;
private Timer dragTimer;
private double distanceMoved;
private final NvConnection conn;
private final int actionIndex;
private final double xFactor;
private final double yFactor;
private static final int TAP_MOVEMENT_THRESHOLD = 20;
private static final int TAP_DISTANCE_THRESHOLD = 25;
private static final int TAP_TIME_THRESHOLD = 250;
private static final int DRAG_TIME_THRESHOLD = 650;
public TouchContext(NvConnection conn, int actionIndex, double xFactor, double yFactor)
{
this.conn = conn;
this.actionIndex = actionIndex;
this.xFactor = xFactor;
this.yFactor = yFactor;
}
}
public int getActionIndex()
{
return 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();
cancelled = false;
private boolean isWithinTapBounds(int touchX, int touchY)
{
int xDelta = Math.abs(touchX - originalTouchX);
int yDelta = Math.abs(touchY - originalTouchY);
return xDelta <= TAP_MOVEMENT_THRESHOLD &&
yDelta <= TAP_MOVEMENT_THRESHOLD;
}
private boolean isTap()
{
long timeDelta = System.currentTimeMillis() - originalTouchTime;
return isWithinTapBounds(lastTouchX, lastTouchY) && 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();
cancelled = confirmedDrag = confirmedMove = false;
distanceMoved = 0;
if (actionIndex == 0) {
// Start the timer for engaging a drag
startDragTimer();
}
return true;
}
public void touchUpEvent(int eventX, int eventY)
{
}
public void touchUpEvent(int eventX, int eventY)
{
if (cancelled) {
return;
}
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 ignored) {}
// Raise the mouse button
conn.sendMouseButtonUp(buttonIndex);
}
}
public boolean touchMoveEvent(int eventX, int eventY)
// Cancel the drag timer
cancelDragTimer();
byte buttonIndex = getMouseButtonIndex();
if (confirmedDrag) {
// Raise the button after a drag
conn.sendMouseButtonUp(buttonIndex);
}
else if (isTap())
{
// 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 ignored) {}
// Raise the mouse button
conn.sendMouseButtonUp(buttonIndex);
}
}
private synchronized void startDragTimer() {
dragTimer = new Timer(true);
dragTimer.schedule(new TimerTask() {
@Override
public void run() {
synchronized (TouchContext.this) {
// Check if someone already set move
if (confirmedMove) {
return;
}
// Check if someone cancelled us
if (dragTimer == null) {
return;
}
// Uncancellable now
dragTimer = null;
// We haven't been cancelled before the timer expired so begin dragging
confirmedDrag = true;
conn.sendMouseButtonDown(getMouseButtonIndex());
}
}
}, DRAG_TIME_THRESHOLD);
}
private synchronized void cancelDragTimer() {
if (dragTimer != null) {
dragTimer.cancel();
dragTimer = null;
}
}
private synchronized void checkForConfirmedMove(int eventX, int eventY) {
// If we've already confirmed something, get out now
if (confirmedMove || confirmedDrag) {
return;
}
// If it leaves the tap bounds before the drag time expires, it's a move.
if (!isWithinTapBounds(eventX, eventY)) {
confirmedMove = true;
cancelDragTimer();
return;
}
// Check if we've exceeded the maximum distance moved
distanceMoved += Math.sqrt(Math.pow(eventX - lastTouchX, 2) + Math.pow(eventY - lastTouchY, 2));
if (distanceMoved >= TAP_DISTANCE_THRESHOLD) {
confirmedMove = true;
cancelDragTimer();
return;
}
}
public boolean touchMoveEvent(int eventX, int eventY)
{
if (eventX != lastTouchX || eventY != lastTouchY)
{
// We only send moves for the primary touch point
if (actionIndex == 0) {
{
// We only send moves and drags for the primary touch point
if (actionIndex == 0) {
checkForConfirmedMove(eventX, eventY);
int deltaX = eventX - lastTouchX;
int deltaY = eventY - lastTouchY;
// Scale the deltas based on the factors passed to our constructor
deltaX = (int)Math.round((double)deltaX * xFactor);
deltaY = (int)Math.round((double)deltaY * yFactor);
deltaX = (int)Math.round((double)Math.abs(deltaX) * xFactor);
deltaY = (int)Math.round((double)Math.abs(deltaY) * yFactor);
conn.sendMouseMove((short)deltaX, (short)deltaY);
}
lastTouchX = eventX;
lastTouchY = eventY;
}
return true;
}
// Fix up the signs
if (eventX < lastTouchX) {
deltaX = -deltaX;
}
if (eventY < lastTouchY) {
deltaY = -deltaY;
}
// If the scaling factor ended up rounding deltas to zero, wait until they are
// non-zero to update lastTouch that way devices that report small touch events often
// will work correctly
if (deltaX != 0) {
lastTouchX = eventX;
}
if (deltaY != 0) {
lastTouchY = eventY;
}
conn.sendMouseMove((short)deltaX, (short)deltaY);
}
else {
lastTouchX = eventX;
lastTouchY = eventY;
}
}
return true;
}
public void cancelTouch() {
cancelled = true;
// Cancel the drag timer
cancelDragTimer();
// If it was a confirmed drag, we'll need to raise the button now
if (confirmedDrag) {
conn.sendMouseButtonUp(getMouseButtonIndex());
}
}
public boolean isCancelled() {

View File

@@ -0,0 +1,11 @@
package com.limelight.binding.input.driver;
public interface UsbDriverListener {
void reportControllerState(int controllerId, short buttonFlags,
float leftStickX, float leftStickY,
float rightStickX, float rightStickY,
float leftTrigger, float rightTrigger);
void deviceRemoved(int controllerId);
void deviceAdded(int controllerId);
}

View File

@@ -0,0 +1,170 @@
package com.limelight.binding.input.driver;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.os.Binder;
import android.os.IBinder;
import com.limelight.LimeLog;
import java.util.ArrayList;
public class UsbDriverService extends Service implements UsbDriverListener {
private static final String ACTION_USB_PERMISSION =
"com.limelight.USB_PERMISSION";
private UsbManager usbManager;
private final UsbEventReceiver receiver = new UsbEventReceiver();
private final UsbDriverBinder binder = new UsbDriverBinder();
private final ArrayList<XboxOneController> controllers = new ArrayList<>();
private UsbDriverListener listener;
private static int nextDeviceId;
@Override
public void reportControllerState(int controllerId, short buttonFlags, float leftStickX, float leftStickY, float rightStickX, float rightStickY, float leftTrigger, float rightTrigger) {
// Call through to the client's listener
if (listener != null) {
listener.reportControllerState(controllerId, buttonFlags, leftStickX, leftStickY, rightStickX, rightStickY, leftTrigger, rightTrigger);
}
}
@Override
public void deviceRemoved(int controllerId) {
// Remove the the controller from our list (if not removed already)
for (XboxOneController controller : controllers) {
if (controller.getControllerId() == controllerId) {
controllers.remove(controller);
break;
}
}
// Call through to the client's listener
if (listener != null) {
listener.deviceRemoved(controllerId);
}
}
@Override
public void deviceAdded(int controllerId) {
// Call through to the client's listener
if (listener != null) {
listener.deviceAdded(controllerId);
}
}
public class UsbEventReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// Initial attachment broadcast
if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
// Continue the state machine
handleUsbDeviceState(device);
}
// Subsequent permission dialog completion intent
else if (action.equals(ACTION_USB_PERMISSION)) {
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
// If we got this far, we've already found we're able to handle this device
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
handleUsbDeviceState(device);
}
}
}
}
public class UsbDriverBinder extends Binder {
public void setListener(UsbDriverListener listener) {
UsbDriverService.this.listener = listener;
// Report all controllerMap that already exist
if (listener != null) {
for (XboxOneController controller : controllers) {
listener.deviceAdded(controller.getControllerId());
}
}
}
}
private void handleUsbDeviceState(UsbDevice device) {
// Are we able to operate it?
if (XboxOneController.canClaimDevice(device)) {
// Do we have permission yet?
if (!usbManager.hasPermission(device)) {
// Let's ask for permission
usbManager.requestPermission(device, PendingIntent.getBroadcast(UsbDriverService.this, 0, new Intent(ACTION_USB_PERMISSION), 0));
return;
}
// Open the device
UsbDeviceConnection connection = usbManager.openDevice(device);
if (connection == null) {
LimeLog.warning("Unable to open USB device: "+device.getDeviceName());
return;
}
// Try to initialize it
XboxOneController controller = new XboxOneController(device, connection, nextDeviceId++, this);
if (!controller.start()) {
connection.close();
return;
}
// Add this controller to the list
controllers.add(controller);
}
}
@Override
public void onCreate() {
this.usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
// Register for USB attach broadcasts and permission completions
IntentFilter filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
filter.addAction(ACTION_USB_PERMISSION);
registerReceiver(receiver, filter);
// Enumerate existing devices
for (UsbDevice dev : usbManager.getDeviceList().values()) {
if (XboxOneController.canClaimDevice(dev)) {
// Start the process of claiming this device
handleUsbDeviceState(dev);
}
}
}
@Override
public void onDestroy() {
// Stop the attachment receiver
unregisterReceiver(receiver);
// Remove listeners
listener = null;
// Stop all controllers
while (controllers.size() > 0) {
// Stop and remove the controller
controllers.remove(0).stop();
}
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
}

View File

@@ -0,0 +1,233 @@
package com.limelight.binding.input.driver;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import com.limelight.LimeLog;
import com.limelight.binding.video.MediaCodecHelper;
import com.limelight.nvstream.input.ControllerPacket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class XboxOneController {
private final UsbDevice device;
private final UsbDeviceConnection connection;
private final int deviceId;
private Thread inputThread;
private UsbDriverListener listener;
private boolean stopped;
private short buttonFlags;
private float leftTrigger, rightTrigger;
private float rightStickX, rightStickY;
private float leftStickX, leftStickY;
private static final int MICROSOFT_VID = 0x045e;
private static final int XB1_IFACE_SUBCLASS = 71;
private static final int XB1_IFACE_PROTOCOL = 208;
// FIXME: odata_serial
private static final byte[] XB1_INIT_DATA = {0x05, 0x20, 0x00, 0x01, 0x00};
public XboxOneController(UsbDevice device, UsbDeviceConnection connection, int deviceId, UsbDriverListener listener) {
this.device = device;
this.connection = connection;
this.deviceId = deviceId;
this.listener = listener;
}
public int getControllerId() {
return this.deviceId;
}
private void setButtonFlag(int buttonFlag, int data) {
if (data != 0) {
buttonFlags |= buttonFlag;
}
else {
buttonFlags &= ~buttonFlag;
}
}
private void reportInput() {
listener.reportControllerState(deviceId, buttonFlags, leftStickX, leftStickY,
rightStickX, rightStickY, leftTrigger, rightTrigger);
}
private void processButtons(ByteBuffer buffer) {
byte b = buffer.get();
setButtonFlag(ControllerPacket.PLAY_FLAG, b & 0x04);
setButtonFlag(ControllerPacket.BACK_FLAG, b & 0x08);
setButtonFlag(ControllerPacket.A_FLAG, b & 0x10);
setButtonFlag(ControllerPacket.B_FLAG, b & 0x20);
setButtonFlag(ControllerPacket.X_FLAG, b & 0x40);
setButtonFlag(ControllerPacket.Y_FLAG, b & 0x80);
b = buffer.get();
setButtonFlag(ControllerPacket.LEFT_FLAG, b & 0x04);
setButtonFlag(ControllerPacket.RIGHT_FLAG, b & 0x08);
setButtonFlag(ControllerPacket.UP_FLAG, b & 0x01);
setButtonFlag(ControllerPacket.DOWN_FLAG, b & 0x02);
setButtonFlag(ControllerPacket.LB_FLAG, b & 0x10);
setButtonFlag(ControllerPacket.RB_FLAG, b & 0x20);
setButtonFlag(ControllerPacket.LS_CLK_FLAG, b & 0x40);
setButtonFlag(ControllerPacket.RS_CLK_FLAG, b & 0x80);
leftTrigger = buffer.getShort() / 1023.0f;
rightTrigger = buffer.getShort() / 1023.0f;
leftStickX = buffer.getShort() / 32767.0f;
leftStickY = ~buffer.getShort() / 32767.0f;
rightStickX = buffer.getShort() / 32767.0f;
rightStickY = ~buffer.getShort() / 32767.0f;
reportInput();
}
private void processPacket(ByteBuffer buffer) {
switch (buffer.get())
{
case 0x20:
buffer.position(buffer.position()+3);
processButtons(buffer);
break;
case 0x07:
buffer.position(buffer.position() + 3);
setButtonFlag(ControllerPacket.SPECIAL_BUTTON_FLAG, buffer.get() & 0x01);
reportInput();
break;
}
}
private void startInputThread(final UsbEndpoint inEndpt) {
inputThread = new Thread() {
public void run() {
while (!isInterrupted() && !stopped) {
byte[] buffer = new byte[64];
int res;
//
// There's no way that I can tell to determine if a device has failed
// or if the timeout has simply expired. We'll check how long the transfer
// took to fail and assume the device failed if it happened before the timeout
// expired.
//
do {
// Read the next input state packet
long lastMillis = MediaCodecHelper.getMonotonicMillis();
res = connection.bulkTransfer(inEndpt, buffer, buffer.length, 3000);
if (res == -1 && MediaCodecHelper.getMonotonicMillis() - lastMillis < 1000) {
LimeLog.warning("Detected device I/O error");
XboxOneController.this.stop();
break;
}
} while (res == -1 && !isInterrupted() && !stopped);
if (res == -1 || stopped) {
break;
}
processPacket(ByteBuffer.wrap(buffer, 0, res).order(ByteOrder.LITTLE_ENDIAN));
}
}
};
inputThread.setName("Xbox One Controller - Input Thread");
inputThread.start();
}
public boolean start() {
// Force claim all interfaces
for (int i = 0; i < device.getInterfaceCount(); i++) {
UsbInterface iface = device.getInterface(i);
if (!connection.claimInterface(iface, true)) {
LimeLog.warning("Failed to claim interfaces");
return false;
}
}
// Find the endpoints
UsbEndpoint outEndpt = null;
UsbEndpoint inEndpt = null;
UsbInterface iface = device.getInterface(0);
for (int i = 0; i < iface.getEndpointCount(); i++) {
UsbEndpoint endpt = iface.getEndpoint(i);
if (endpt.getDirection() == UsbConstants.USB_DIR_IN) {
if (inEndpt != null) {
LimeLog.warning("Found duplicate IN endpoint");
return false;
}
inEndpt = endpt;
}
else if (endpt.getDirection() == UsbConstants.USB_DIR_OUT) {
if (outEndpt != null) {
LimeLog.warning("Found duplicate OUT endpoint");
return false;
}
outEndpt = endpt;
}
}
// Make sure the required endpoints were present
if (inEndpt == null || outEndpt == null) {
LimeLog.warning("Missing required endpoint");
return false;
}
// Send the initialization packet
int res = connection.bulkTransfer(outEndpt, XB1_INIT_DATA, XB1_INIT_DATA.length, 3000);
if (res != XB1_INIT_DATA.length) {
LimeLog.warning("Initialization transfer failed: "+res);
return false;
}
// Start listening for controller input
startInputThread(inEndpt);
// Report this device added via the listener
listener.deviceAdded(deviceId);
return true;
}
public void stop() {
if (stopped) {
return;
}
stopped = true;
// Stop the input thread
if (inputThread != null) {
inputThread.interrupt();
inputThread = null;
}
// Report the device removed
listener.deviceRemoved(deviceId);
// Close the USB connection
connection.close();
}
public static boolean canClaimDevice(UsbDevice device) {
return device.getVendorId() == MICROSOFT_VID &&
device.getInterfaceCount() >= 1 &&
device.getInterface(0).getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
device.getInterface(0).getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
device.getInterface(0).getInterfaceProtocol() == XB1_IFACE_PROTOCOL;
}
}

View File

@@ -1,41 +1,37 @@
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;
}
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 final short type;
public final short code;
public final int value;
public EvdevEvent(short type, short code, int value) {
this.type = type;
this.code = code;
this.value = value;
}
}

View File

@@ -1,167 +1,186 @@
package com.limelight.binding.input.evdev;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import android.content.Context;
import com.limelight.LimeLog;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
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;
}
private final EvdevListener listener;
private final String libraryPath;
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;
}
private boolean shutdown = false;
private InputStream evdevIn;
private OutputStream evdevOut;
private Process reader;
// Grab it for ourselves
if (!EvdevReader.grab(fd)) {
LimeLog.warning("Unable to grab "+absolutePath);
return;
}
private static final byte UNGRAB_REQUEST = 1;
private static final byte REGRAB_REQUEST = 2;
LimeLog.info("Grabbed device for raw keyboard/mouse input: "+absolutePath);
private final Thread handlerThread = new Thread() {
@Override
public void run() {
int deltaX = 0;
int deltaY = 0;
byte deltaScroll = 0;
ByteBuffer buffer = ByteBuffer.allocate(EvdevEvent.EVDEV_MAX_EVENT_SIZE).order(ByteOrder.nativeOrder());
// Launch the evdev reader shell
ProcessBuilder builder = new ProcessBuilder("su", "-c", libraryPath+File.separatorChar+"libevdev_reader.so");
builder.redirectErrorStream(false);
try {
int deltaX = 0;
int deltaY = 0;
byte deltaScroll = 0;
try {
reader = builder.start();
} catch (IOException e) {
e.printStackTrace();
return;
}
while (!isInterrupted() && !shutdown) {
EvdevEvent event = EvdevReader.read(fd, buffer);
if (event == null) {
return;
}
evdevIn = reader.getInputStream();
evdevOut = reader.getOutputStream();
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;
while (!isInterrupted() && !shutdown) {
EvdevEvent event;
try {
event = EvdevReader.read(evdevIn);
} catch (IOException e) {
event = null;
}
if (event == null) {
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;
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_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 ignored) {}
}
public void notifyDeleted() {
stop();
}
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;
}
}
}
};
public EvdevHandler(Context context, EvdevListener listener) {
this.listener = listener;
this.libraryPath = context.getApplicationInfo().nativeLibraryDir;
}
public void regrabAll() {
if (!shutdown && evdevOut != null) {
try {
evdevOut.write(REGRAB_REQUEST);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void ungrabAll() {
if (!shutdown && evdevOut != null) {
try {
evdevOut.write(UNGRAB_REQUEST);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void start() {
handlerThread.start();
}
public void stop() {
// We need to stop the process in this context otherwise
// we could get stuck waiting on output from the process
// in order to terminate it.
shutdown = true;
handlerThread.interrupt();
if (evdevIn != null) {
try {
evdevIn.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (evdevOut != null) {
try {
evdevOut.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
reader.destroy();
}
try {
handlerThread.join();
} catch (InterruptedException ignored) {}
}
}

View File

@@ -1,12 +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);
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

@@ -1,105 +1,58 @@
package com.limelight.binding.input.evdev;
import android.os.Build;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.Locale;
import java.nio.ByteOrder;
import com.limelight.LimeLog;
public class EvdevReader {
static {
System.loadLibrary("evdev_reader");
}
private static void readAll(InputStream in, ByteBuffer bb) throws IOException {
byte[] buf = bb.array();
int ret;
int offset = 0;
public static void patchSeLinuxPolicies() {
//
// FIXME: We REALLY shouldn't being changing permissions on the input devices like this.
// We should probably do something clever with a separate daemon and talk via a localhost
// socket. We don't return the SELinux policies back to default after we're done which I feel
// bad about, but we do chmod the input devices back so I don't think any additional attack surface
// remains opened after streaming other than listing the /dev/input directory which you wouldn't
// normally be able to do with SELinux enforcing on Lollipop.
//
// We need to modify SELinux policies to allow us to capture input devices on Lollipop and possibly other
// more restrictive ROMs. Per Chainfire's SuperSU documentation, the supolicy binary is provided on
// 4.4 and later to do live SELinux policy changes.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
EvdevShell shell = EvdevShell.getInstance();
shell.runCommand("supolicy --live \"allow untrusted_app input_device dir { getattr read search }\" " +
"\"allow untrusted_app input_device chr_file { open read write ioctl }\"");
while (offset < buf.length) {
ret = in.read(buf, offset, buf.length-offset);
if (ret <= 0) {
throw new IOException("Read failed: "+ret);
}
offset += ret;
}
}
// Requires root to chmod /dev/input/eventX
public static void setPermissions(String[] files, int octalPermissions) {
EvdevShell shell = EvdevShell.getInstance();
for (String file : files) {
shell.runCommand(String.format((Locale)null, "chmod %o %s", octalPermissions, file));
// 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(InputStream input) throws IOException {
ByteBuffer bb;
int packetLength;
// Read the packet length
bb = ByteBuffer.allocate(4).order(ByteOrder.nativeOrder());
readAll(input, bb);
packetLength = bb.getInt();
if (packetLength < EvdevEvent.EVDEV_MIN_EVENT_SIZE) {
LimeLog.warning("Short read: "+packetLength);
return null;
}
}
// 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);
// Read the rest of the packet
bb = ByteBuffer.allocate(packetLength).order(ByteOrder.nativeOrder());
readAll(input, bb);
// Throw away the time stamp
if (packetLength == EvdevEvent.EVDEV_MAX_EVENT_SIZE) {
bb.getLong();
bb.getLong();
} else {
bb.getInt();
bb.getInt();
}
return new EvdevEvent(bb.getShort(), bb.getShort(), bb.getInt());
}
}

View File

@@ -1,116 +0,0 @@
package com.limelight.binding.input.evdev;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Scanner;
import java.util.UUID;
public class EvdevShell {
private OutputStream stdin;
private InputStream stdout;
private Process shell;
private final String uuidString = UUID.randomUUID().toString();
private static final EvdevShell globalShell = new EvdevShell();
public static EvdevShell getInstance() {
return globalShell;
}
public void startShell() {
ProcessBuilder builder = new ProcessBuilder("su");
try {
// Redirect stderr to stdout
builder.redirectErrorStream(true);
shell = builder.start();
stdin = shell.getOutputStream();
stdout = shell.getInputStream();
} catch (IOException e) {
// This is unexpected
e.printStackTrace();
// Kill the shell if it spawned
if (stdin != null) {
try {
stdin.close();
} catch (IOException e1) {
e1.printStackTrace();
} finally {
stdin = null;
}
}
if (stdout != null) {
try {
stdout.close();
} catch (IOException e1) {
e1.printStackTrace();
} finally {
stdout = null;
}
}
if (shell != null) {
shell.destroy();
shell = null;
}
}
}
public void runCommand(String command) {
if (shell == null) {
// Shell never started
return;
}
try {
// Write the command followed by an echo with our UUID
stdin.write((command+'\n').getBytes("UTF-8"));
stdin.write(("echo "+uuidString+'\n').getBytes("UTF-8"));
stdin.flush();
// This is the only command in flight so we can use a scanner
// without worrying about it eating too many characters
Scanner scanner = new Scanner(stdout);
while (scanner.hasNext()) {
if (scanner.next().contains(uuidString)) {
// Our command ran
return;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void stopShell() throws InterruptedException {
boolean exitWritten = false;
if (shell == null) {
// Shell never started
return;
}
try {
stdin.write("exit\n".getBytes("UTF-8"));
exitWritten = true;
} catch (IOException e) {
// We'll destroy the process without
// waiting for it to terminate since
// we don't know whether our exit command made it
e.printStackTrace();
}
if (exitWritten) {
try {
shell.waitFor();
} finally {
shell.destroy();
}
}
else {
shell.destroy();
}
}
}

View File

@@ -4,136 +4,136 @@ 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;
}
private 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

@@ -1,188 +0,0 @@
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;
@SuppressWarnings("ALL")
public class EvdevWatcher {
private static final String PATH = "/dev/input";
private static final String REQUIRED_FILE_PREFIX = "event";
private final 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 static boolean patchedSeLinuxPolicies = false;
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() {
// Initialize the root shell
EvdevShell.getInstance().startShell();
// Patch SELinux policies (if needed)
if (!patchedSeLinuxPolicies) {
EvdevReader.patchSeLinuxPolicies();
patchedSeLinuxPolicies = true;
}
// 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(0660);
// Kill the root shell
try {
EvdevShell.getInstance().stopShell();
} catch (InterruptedException e) {}
}
};
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();
}
}
}
}