Raw mouse input WIP

This commit is contained in:
Cameron Gutman 2014-09-01 22:19:12 -07:00
parent 5de2a8f6ec
commit f4546ba188
8 changed files with 358 additions and 3 deletions

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.limelight"
android:versionCode="31"
android:versionName="2.5.3.1" >
android:versionCode="32"
android:versionName="2.5.4" >
<uses-sdk
android:minSdkVersion="16"
@ -14,6 +14,9 @@
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- Root permissions -->
<uses-permission android:name="android.permission.ACCESS_SUPERUSER" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<uses-feature android:name="android.hardware.wifi" android:required="false" />

View File

@ -5,6 +5,8 @@ import com.limelight.binding.PlatformBinding;
import com.limelight.binding.input.ControllerHandler;
import com.limelight.binding.input.KeyboardTranslator;
import com.limelight.binding.input.TouchContext;
import com.limelight.binding.input.evdev.EvdevListener;
import com.limelight.binding.input.evdev.EvdevWatcher;
import com.limelight.binding.video.ConfigurableDecoderRenderer;
import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.NvConnectionListener;
@ -40,7 +42,9 @@ import android.view.WindowManager;
import android.widget.Toast;
public class Game extends Activity implements SurfaceHolder.Callback, OnGenericMotionListener, OnTouchListener, NvConnectionListener {
public class Game extends Activity implements SurfaceHolder.Callback,
OnGenericMotionListener, OnTouchListener, NvConnectionListener, EvdevListener
{
private int lastMouseX = Integer.MIN_VALUE;
private int lastMouseY = Integer.MIN_VALUE;
private int lastButtonState = 0;
@ -64,6 +68,8 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
private boolean stretchToFit;
private boolean toastsDisabled;
private EvdevWatcher evdevWatcher;
private ConfigurableDecoderRenderer decoderRenderer;
private WifiManager.WifiLock wifiLock;
@ -205,6 +211,12 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
touchContextMap[i] = new TouchContext(conn, i);
}
if (LimelightBuildProps.ROOT_BUILD) {
// Start watching for raw input
evdevWatcher = new EvdevWatcher(this);
evdevWatcher.start();
}
// The connection will be started when the surface gets created
sh.addCallback(this);
}
@ -288,6 +300,10 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
if (message != null) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
}
if (LimelightBuildProps.ROOT_BUILD) {
evdevWatcher.shutdown();
}
finish();
}
@ -631,4 +647,37 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
connected = false;
}
}
@Override
public void mouseMove(int deltaX, int deltaY) {
conn.sendMouseMove((short) deltaX, (short) deltaY);
}
@Override
public void mouseButtonEvent(int buttonId, boolean down) {
byte buttonIndex;
switch (buttonId)
{
case EvdevListener.BUTTON_LEFT:
buttonIndex = MouseButtonPacket.BUTTON_LEFT;
break;
case EvdevListener.BUTTON_MIDDLE:
buttonIndex = MouseButtonPacket.BUTTON_MIDDLE;
break;
case EvdevListener.BUTTON_RIGHT:
buttonIndex = MouseButtonPacket.BUTTON_RIGHT;
break;
default:
LimeLog.warning("Unhandled button: "+buttonId);
return;
}
if (down) {
conn.sendMouseButtonDown(buttonIndex);
}
else {
conn.sendMouseButtonUp(buttonIndex);
}
}
}

View File

@ -0,0 +1,5 @@
package com.limelight;
public class LimelightBuildProps {
public static final boolean ROOT_BUILD = false;
}

View File

@ -0,0 +1,30 @@
package com.limelight.binding.input.evdev;
public class EvdevEvent {
public static final int EVDEV_MIN_EVENT_SIZE = 18;
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;
/* Relative axes */
public static final short REL_X = 0x00;
public static final short REL_Y = 0x01;
/* Buttons */
public static final short BTN_LEFT = 0x110;
public static final short BTN_RIGHT = 0x111;
public static final short BTN_MIDDLE = 0x112;
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,128 @@
package com.limelight.binding.input.evdev;
import java.nio.ByteBuffer;
import com.limelight.LimeLog;
public class EvdevHandler {
private String absolutePath;
private EvdevListener listener;
private boolean shutdown = false;
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.
// Modify permissions to allow us access
if (!EvdevReader.setPermissions(absolutePath, 0666)) {
LimeLog.warning("Unable to chmod "+absolutePath);
return;
}
try {
// Open the /dev/input/eventX file
int fd = EvdevReader.open(absolutePath);
if (fd == -1) {
LimeLog.warning("Unable to open "+absolutePath);
return;
}
try {
// Check if it's a mouse
if (!EvdevReader.isMouse(fd)) {
// We only handle mice
return;
}
// Grab it for ourselves
if (!EvdevReader.grab(fd)) {
LimeLog.warning("Unable to grab "+absolutePath);
return;
}
ByteBuffer buffer = ByteBuffer.allocate(EvdevEvent.EVDEV_MAX_EVENT_SIZE);
try {
while (!isInterrupted() && !shutdown) {
EvdevEvent event = EvdevReader.read(fd, buffer);
if (event == null) {
return;
}
switch (event.type)
{
case EvdevEvent.EV_SYN:
// Do nothing
break;
case EvdevEvent.EV_REL:
switch (event.code)
{
case EvdevEvent.REL_X:
listener.mouseMove(event.value, 0);
break;
case EvdevEvent.REL_Y:
listener.mouseMove(0, 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;
}
}
}
} finally {
// Release our grab
EvdevReader.ungrab(fd);
}
} finally {
// Close the file
EvdevReader.close(fd);
}
} finally {
// Set permissions back
EvdevReader.setPermissions(absolutePath, 0066);
}
}
};
public EvdevHandler(String absolutePath, EvdevListener listener) {
this.absolutePath = absolutePath;
this.listener = listener;
}
public void start() {
handlerThread.start();
}
public void stop() {
shutdown = true;
handlerThread.interrupt();
try {
handlerThread.join();
} catch (InterruptedException e) {}
}
public void notifyDeleted() {
stop();
}
}

View File

@ -0,0 +1,10 @@
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);
}

View File

@ -0,0 +1,55 @@
package com.limelight.binding.input.evdev;
import java.nio.ByteBuffer;
import com.limelight.LimeLog;
public class EvdevReader {
// Requires root to chmod /dev/input/eventX
public static native boolean setPermissions(String fileName, int octalPermissions);
// 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);
// Returns true if the device is a mouse
public static native boolean isMouse(int fd);
// 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,75 @@
package com.limelight.binding.input.evdev;
import java.io.File;
import java.util.HashMap;
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 EvdevListener listener;
private FileObserver observer = new FileObserver(PATH, FileObserver.CREATE | FileObserver.DELETE) {
@Override
public void onEvent(int event, String fileName) {
if (!fileName.startsWith(REQUIRED_FILE_PREFIX)) {
return;
}
synchronized (handlers) {
if (shutdown) {
return;
}
if ((event & FileObserver.CREATE) != 0) {
EvdevHandler handler = new EvdevHandler(PATH + "/" + fileName, listener);
handler.start();
handlers.put(fileName, handler);
}
if ((event & FileObserver.DELETE) != 0) {
EvdevHandler handler = handlers.get(fileName);
if (handler != null) {
handler.notifyDeleted();
}
}
}
}
};
public EvdevWatcher(EvdevListener listener) {
this.listener = listener;
}
public void start() {
// Start watching for new files
observer.startWatching();
// Rundown existing files and generate synthetic events
File devInputDir = new File(PATH);
File[] files = devInputDir.listFiles();
for (File f : files) {
observer.onEvent(FileObserver.CREATE, f.getName());
}
}
public void shutdown() {
// Stop the observer
observer.stopWatching();
synchronized (handlers) {
// Stop creating new handlers
shutdown = true;
// Stop all handlers
for (EvdevHandler handler : handlers.values()) {
handler.stop();
}
}
}
}