From 22977a4c5bf3ae705567bc55d26e49e164a7c0ea Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 7 Jan 2016 12:49:30 -0600 Subject: [PATCH] Use a socket for communication from EvdevReader to Moonlight rather than stdin/stdout. On some devices, fwrite(stdout) hangs for unknown reasons. --- .../binding/input/evdev/EvdevHandler.java | 69 +++++++++++++--- app/src/main/jni/evdev_reader/evdev_reader.c | 80 ++++++++++++++++--- 2 files changed, 126 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/limelight/binding/input/evdev/EvdevHandler.java b/app/src/main/java/com/limelight/binding/input/evdev/EvdevHandler.java index ddb3c029..e753a409 100644 --- a/app/src/main/java/com/limelight/binding/input/evdev/EvdevHandler.java +++ b/app/src/main/java/com/limelight/binding/input/evdev/EvdevHandler.java @@ -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 { diff --git a/app/src/main/jni/evdev_reader/evdev_reader.c b/app/src/main/jni/evdev_reader/evdev_reader.c index bdd6b30d..32fe2c5d 100644 --- a/app/src/main/jni/evdev_reader/evdev_reader.c +++ b/app/src/main/jni/evdev_reader/evdev_reader.c @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -11,6 +12,8 @@ #include #include #include +#include +#include #include @@ -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;