mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2025-07-20 11:33:06 +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"?>
|
||||
<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" />
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
@ -289,6 +301,10 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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