Remove EvdevCaptureProvider components from non-root build

This commit is contained in:
Cameron Gutman
2017-12-01 00:37:25 -08:00
parent 5c69f6716c
commit 3171256c6e
6 changed files with 27 additions and 8 deletions

View File

@@ -4,7 +4,7 @@ import android.app.Activity;
import com.limelight.LimeLog;
import com.limelight.R;
import com.limelight.binding.input.evdev.EvdevCaptureProvider;
import com.limelight.binding.input.evdev.EvdevCaptureProviderShim;
import com.limelight.binding.input.evdev.EvdevListener;
public class InputCaptureManager {
@@ -17,9 +17,9 @@ public class InputCaptureManager {
LimeLog.info("Using NVIDIA mouse capture extension");
return new ShieldCaptureProvider(activity);
}
else if (EvdevCaptureProvider.isCaptureProviderSupported()) {
else if (EvdevCaptureProviderShim.isCaptureProviderSupported()) {
LimeLog.info("Using Evdev mouse capture");
return new EvdevCaptureProvider(activity, rootListener);
return EvdevCaptureProviderShim.createEvdevCaptureProvider(activity, rootListener);
}
else if (AndroidPointerIconCaptureProvider.isCaptureProviderSupported()) {
// Android N's native capture can't capture over system UI elements

View File

@@ -1,327 +0,0 @@
package com.limelight.binding.input.evdev;
import android.app.Activity;
import android.os.Build;
import android.os.Looper;
import android.widget.Toast;
import com.limelight.LimeLog;
import com.limelight.LimelightBuildProps;
import com.limelight.binding.input.capture.InputCaptureProvider;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class EvdevCaptureProvider extends InputCaptureProvider {
private final EvdevListener listener;
private final String libraryPath;
private boolean shutdown = false;
private InputStream evdevIn;
private OutputStream evdevOut;
private Process su;
private ServerSocket servSock;
private Socket evdevSock;
private Activity activity;
private boolean started = false;
private static final byte UNGRAB_REQUEST = 1;
private static final byte REGRAB_REQUEST = 2;
private final Thread handlerThread = new Thread() {
@Override
public void run() {
int deltaX = 0;
int deltaY = 0;
byte deltaScroll = 0;
// Bind a local listening socket for evdevreader to connect to
try {
servSock = new ServerSocket(0, 1);
} catch (IOException e) {
e.printStackTrace();
return;
}
final String evdevReaderCmd = libraryPath+File.separatorChar+"libevdev_reader.so "+servSock.getLocalPort();
// On Nougat and later, we'll need to pass the command directly to SU.
// Writing to SU's input stream after it has started doesn't seem to work anymore.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// Launch evdev_reader directly via SU
try {
su = Runtime.getRuntime().exec("su -c "+evdevReaderCmd);
} catch (IOException e) {
reportDeviceNotRooted();
e.printStackTrace();
return;
}
}
else {
// Launch a SU shell on Marshmallow and earlier
ProcessBuilder builder = new ProcessBuilder("su");
builder.redirectErrorStream(true);
try {
su = builder.start();
} catch (IOException e) {
reportDeviceNotRooted();
e.printStackTrace();
return;
}
// Start evdevreader
DataOutputStream suOut = new DataOutputStream(su.getOutputStream());
try {
suOut.writeChars(evdevReaderCmd+"\n");
} catch (IOException e) {
reportDeviceNotRooted();
e.printStackTrace();
return;
}
}
// Wait for evdevreader's connection
LimeLog.info("Waiting for EvdevReader connection to port "+servSock.getLocalPort());
try {
evdevSock = servSock.accept();
evdevIn = evdevSock.getInputStream();
evdevOut = evdevSock.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
return;
}
LimeLog.info("EvdevReader connected from port "+evdevSock.getPort());
while (!isInterrupted() && !shutdown) {
EvdevEvent event;
try {
event = EvdevReader.read(evdevIn);
} catch (IOException e) {
event = null;
}
if (event == null) {
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_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 EvdevCaptureProvider(Activity activity, EvdevListener listener) {
this.listener = listener;
this.activity = activity;
this.libraryPath = activity.getApplicationInfo().nativeLibraryDir;
}
public static boolean isCaptureProviderSupported() {
return LimelightBuildProps.ROOT_BUILD;
}
private void reportDeviceNotRooted() {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(activity, "This device is not rooted - Mouse capture is unavailable", Toast.LENGTH_LONG).show();
}
});
}
private void runInNetworkSafeContextSynchronously(Runnable runnable) {
// This function is used to avoid Android's strict NetworkOnMainThreadException.
// For our usage, it is highly unlikely to cause problems since we only do
// write operations and only to localhost sockets.
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
Thread t = new Thread(runnable);
t.start();
try {
t.join();
} catch (InterruptedException e) {
// The main thread should never be interrupted
e.printStackTrace();
}
}
else {
// Run the runnable directly
runnable.run();
}
}
@Override
public void enableCapture() {
super.enableCapture();
if (!started) {
// Start the handler thread if it's our first time
// capturing
handlerThread.start();
started = true;
}
else {
// This may be called on the main thread
runInNetworkSafeContextSynchronously(new Runnable() {
@Override
public void run() {
// Send a request to regrab if we're already capturing
if (!shutdown && evdevOut != null) {
try {
evdevOut.write(REGRAB_REQUEST);
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
}
}
@Override
public void disableCapture() {
super.disableCapture();
// This may be called on the main thread
runInNetworkSafeContextSynchronously(new Runnable() {
@Override
public void run() {
if (started && !shutdown && evdevOut != null) {
try {
evdevOut.write(UNGRAB_REQUEST);
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
}
@Override
public void destroy() {
// 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.
//
// This may be called on the main thread.
if (!started) {
return;
}
shutdown = true;
handlerThread.interrupt();
runInNetworkSafeContextSynchronously(new Runnable() {
@Override
public void run() {
if (servSock != null) {
try {
servSock.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (evdevSock != null) {
try {
evdevSock.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (evdevIn != null) {
try {
evdevIn.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (evdevOut != null) {
try {
evdevOut.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
if (su != null) {
su.destroy();
}
try {
handlerThread.join();
} catch (InterruptedException ignored) {}
}
}

View File

@@ -0,0 +1,24 @@
package com.limelight.binding.input.evdev;
import android.app.Activity;
import com.limelight.LimelightBuildProps;
import com.limelight.binding.input.capture.InputCaptureProvider;
public class EvdevCaptureProviderShim {
public static boolean isCaptureProviderSupported() {
return LimelightBuildProps.ROOT_BUILD;
}
// We need to construct our capture provider using reflection because it isn't included in non-root builds
public static InputCaptureProvider createEvdevCaptureProvider(Activity activity, EvdevListener listener) {
try {
Class providerClass = Class.forName("com.limelight.binding.input.evdev.EvdevCaptureProvider");
return (InputCaptureProvider) providerClass.getConstructors()[0].newInstance(activity, listener);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}

View File

@@ -1,37 +0,0 @@
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 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,58 +0,0 @@
package com.limelight.binding.input.evdev;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import com.limelight.LimeLog;
public class EvdevReader {
private static void readAll(InputStream in, ByteBuffer bb) throws IOException {
byte[] buf = bb.array();
int ret;
int offset = 0;
while (offset < buf.length) {
ret = in.read(buf, offset, buf.length-offset);
if (ret <= 0) {
throw new IOException("Read failed: "+ret);
}
offset += ret;
}
}
// 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;
}
// 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,139 +0,0 @@
package com.limelight.binding.input.evdev;
import android.view.KeyEvent;
public class EvdevTranslator {
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;
}
}