mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2025-07-20 19:42:45 +00:00
Raw mouse input WIP
This commit is contained in:
parent
5de2a8f6ec
commit
f4546ba188
@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.limelight"
|
package="com.limelight"
|
||||||
android:versionCode="31"
|
android:versionCode="32"
|
||||||
android:versionName="2.5.3.1" >
|
android:versionName="2.5.4" >
|
||||||
|
|
||||||
<uses-sdk
|
<uses-sdk
|
||||||
android:minSdkVersion="16"
|
android:minSdkVersion="16"
|
||||||
@ -14,6 +14,9 @@
|
|||||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_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.touchscreen" android:required="false" />
|
||||||
<uses-feature android:name="android.hardware.wifi" android:required="false" />
|
<uses-feature android:name="android.hardware.wifi" android:required="false" />
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@ import com.limelight.binding.PlatformBinding;
|
|||||||
import com.limelight.binding.input.ControllerHandler;
|
import com.limelight.binding.input.ControllerHandler;
|
||||||
import com.limelight.binding.input.KeyboardTranslator;
|
import com.limelight.binding.input.KeyboardTranslator;
|
||||||
import com.limelight.binding.input.TouchContext;
|
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.binding.video.ConfigurableDecoderRenderer;
|
||||||
import com.limelight.nvstream.NvConnection;
|
import com.limelight.nvstream.NvConnection;
|
||||||
import com.limelight.nvstream.NvConnectionListener;
|
import com.limelight.nvstream.NvConnectionListener;
|
||||||
@ -40,7 +42,9 @@ import android.view.WindowManager;
|
|||||||
import android.widget.Toast;
|
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 lastMouseX = Integer.MIN_VALUE;
|
||||||
private int lastMouseY = Integer.MIN_VALUE;
|
private int lastMouseY = Integer.MIN_VALUE;
|
||||||
private int lastButtonState = 0;
|
private int lastButtonState = 0;
|
||||||
@ -64,6 +68,8 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
|
|||||||
private boolean stretchToFit;
|
private boolean stretchToFit;
|
||||||
private boolean toastsDisabled;
|
private boolean toastsDisabled;
|
||||||
|
|
||||||
|
private EvdevWatcher evdevWatcher;
|
||||||
|
|
||||||
private ConfigurableDecoderRenderer decoderRenderer;
|
private ConfigurableDecoderRenderer decoderRenderer;
|
||||||
|
|
||||||
private WifiManager.WifiLock wifiLock;
|
private WifiManager.WifiLock wifiLock;
|
||||||
@ -205,6 +211,12 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
|
|||||||
touchContextMap[i] = new TouchContext(conn, i);
|
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
|
// The connection will be started when the surface gets created
|
||||||
sh.addCallback(this);
|
sh.addCallback(this);
|
||||||
}
|
}
|
||||||
@ -289,6 +301,10 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
|
|||||||
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (LimelightBuildProps.ROOT_BUILD) {
|
||||||
|
evdevWatcher.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -631,4 +647,37 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
|
|||||||
connected = false;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
5
src/com/limelight/LimelightBuildProps.java
Normal file
5
src/com/limelight/LimelightBuildProps.java
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package com.limelight;
|
||||||
|
|
||||||
|
public class LimelightBuildProps {
|
||||||
|
public static final boolean ROOT_BUILD = false;
|
||||||
|
}
|
30
src/com/limelight/binding/input/evdev/EvdevEvent.java
Normal file
30
src/com/limelight/binding/input/evdev/EvdevEvent.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
128
src/com/limelight/binding/input/evdev/EvdevHandler.java
Normal file
128
src/com/limelight/binding/input/evdev/EvdevHandler.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
10
src/com/limelight/binding/input/evdev/EvdevListener.java
Normal file
10
src/com/limelight/binding/input/evdev/EvdevListener.java
Normal 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);
|
||||||
|
}
|
55
src/com/limelight/binding/input/evdev/EvdevReader.java
Normal file
55
src/com/limelight/binding/input/evdev/EvdevReader.java
Normal 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);
|
||||||
|
}
|
75
src/com/limelight/binding/input/evdev/EvdevWatcher.java
Normal file
75
src/com/limelight/binding/input/evdev/EvdevWatcher.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user