Use a socket for communication from EvdevReader to Moonlight rather than stdin/stdout. On some devices, fwrite(stdout) hangs for unknown reasons.

This commit is contained in:
Cameron Gutman 2016-01-07 12:49:30 -06:00
parent 7da5d5322b
commit 22977a4c5b
2 changed files with 126 additions and 23 deletions

View File

@ -2,10 +2,15 @@ package com.limelight.binding.input.evdev;
import android.content.Context;
import com.limelight.LimeLog;
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 EvdevHandler {
@ -15,7 +20,9 @@ public class EvdevHandler {
private boolean shutdown = false;
private InputStream evdevIn;
private OutputStream evdevOut;
private Process reader;
private Process su;
private ServerSocket servSock;
private Socket evdevSock;
private static final byte UNGRAB_REQUEST = 1;
private static final byte REGRAB_REQUEST = 2;
@ -27,19 +34,45 @@ public class EvdevHandler {
int deltaY = 0;
byte deltaScroll = 0;
// Launch the evdev reader shell
ProcessBuilder builder = new ProcessBuilder("su", "-c", libraryPath+File.separatorChar+"libevdev_reader.so");
builder.redirectErrorStream(false);
// Bind a local listening socket for evdevreader to connect to
try {
reader = builder.start();
servSock = new ServerSocket(0, 1);
} catch (IOException e) {
e.printStackTrace();
return;
}
evdevIn = reader.getInputStream();
evdevOut = reader.getOutputStream();
// Launch a su shell
ProcessBuilder builder = new ProcessBuilder("su");
builder.redirectErrorStream(true);
try {
su = builder.start();
} catch (IOException e) {
e.printStackTrace();
return;
}
// Start evdevreader
DataOutputStream suOut = new DataOutputStream(su.getOutputStream());
try {
suOut.writeChars(libraryPath+File.separatorChar+"libevdev_reader.so "+servSock.getLocalPort()+"\n");
} catch (IOException e) {
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;
@ -159,6 +192,22 @@ public class EvdevHandler {
shutdown = true;
handlerThread.interrupt();
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();
@ -175,8 +224,8 @@ public class EvdevHandler {
}
}
if (reader != null) {
reader.destroy();
if (su != null) {
su.destroy();
}
try {

View File

@ -3,6 +3,7 @@
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/input.h>
@ -11,6 +12,8 @@
#include <errno.h>
#include <dirent.h>
#include <pthread.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <android/log.h>
@ -32,9 +35,11 @@ struct DeviceEntry {
static struct DeviceEntry *DeviceListHead;
static int grabbing = 1;
static pthread_mutex_t DeviceListLock = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t SocketSendLock = PTHREAD_MUTEX_INITIALIZER;
static int sock;
// This is a small executable that runs in a root shell. It reads input
// devices and writes the evdev output packets to stdout. This allows
// devices and writes the evdev output packets to a socket. This allows
// Moonlight to read input devices without having to muck with changing
// device permissions or modifying SELinux policy (which is prevented in
// Marshmallow anyway).
@ -58,13 +63,16 @@ static int hasKey(int fd, short key) {
}
static void outputEvdevData(char *data, int dataSize) {
// We need to lock stdout before writing to prevent
// interleaving of data between threads.
flockfile(stdout);
fwrite(&dataSize, sizeof(dataSize), 1, stdout);
fwrite(data, dataSize, 1, stdout);
fflush(stdout);
funlockfile(stdout);
char packetBuffer[EVDEV_MAX_EVENT_SIZE + sizeof(dataSize)];
// Copy the full packet into our buffer
memcpy(packetBuffer, &dataSize, sizeof(dataSize));
memcpy(&packetBuffer[sizeof(dataSize)], data, dataSize);
// Lock to prevent other threads from sending at the same time
pthread_mutex_lock(&SocketSendLock);
send(sock, packetBuffer, dataSize + sizeof(dataSize), 0);
pthread_mutex_unlock(&SocketSendLock);
}
void* pollThreadFunc(void* context) {
@ -274,6 +282,39 @@ static int enumerateDevices(void) {
return 0;
}
static int connectSocket(int port) {
struct sockaddr_in saddr;
int ret;
int val;
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock < 0) {
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader", "socket() failed: %d", errno);
return -1;
}
memset(&saddr, 0, sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(port);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
ret = connect(sock, (struct sockaddr*)&saddr, sizeof(saddr));
if (ret < 0) {
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader", "connect() failed: %d", errno);
return -1;
}
val = 1;
ret = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&val, sizeof(val));
if (ret < 0) {
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader", "setsockopt() failed: %d", errno);
// We can continue anyways
}
__android_log_print(ANDROID_LOG_INFO, "EvdevReader", "Connection established to port %d", port);
return 0;
}
#define UNGRAB_REQ 1
#define REGRAB_REQ 2
@ -281,9 +322,19 @@ int main(int argc, char* argv[]) {
int ret;
int pollres;
struct pollfd pollinfo;
int port;
__android_log_print(ANDROID_LOG_INFO, "EvdevReader", "Entered main()");
port = atoi(argv[1]);
__android_log_print(ANDROID_LOG_INFO, "EvdevReader", "Requested port number: %d", port);
// Connect to the app's socket
ret = connectSocket(port);
if (ret < 0) {
return ret;
}
// Perform initial enumeration
ret = enumerateDevices();
if (ret < 0) {
@ -297,7 +348,7 @@ int main(int argc, char* argv[]) {
do {
// Every second we poll again for new devices if
// we haven't received any new events
pollinfo.fd = STDIN_FILENO;
pollinfo.fd = sock;
pollinfo.events = POLLIN;
pollinfo.revents = 0;
pollres = poll(&pollinfo, 1, 1000);
@ -310,9 +361,9 @@ int main(int argc, char* argv[]) {
if (pollres > 0 && (pollinfo.revents & POLLIN)) {
// We'll have data available now
ret = fread(&requestId, sizeof(requestId), 1, stdin);
ret = recv(sock, &requestId, sizeof(requestId), 0);
if (ret < sizeof(requestId)) {
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader", "Short read on input");
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader", "Short read on socket");
return errno;
}
@ -337,17 +388,20 @@ int main(int argc, char* argv[]) {
}
pthread_mutex_unlock(&DeviceListLock);
__android_log_print(ANDROID_LOG_INFO, "EvdevReader", "New grab status is: %s",
grabbing ? "enabled" : "disabled");
}
}
else {
// Terminate this thread
if (pollres < 0) {
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader",
"Stdin poll() failed: %d", errno);
"Socket recv poll() failed: %d", errno);
}
else {
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader",
"Stdin unexpected revents: %d", pollinfo.revents);
"Socket poll unexpected revents: %d", pollinfo.revents);
}
return -1;