From 7577e0fe82e711960e20437684ccf2986692ab03 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Tue, 7 Feb 2023 14:36:52 +0800 Subject: [PATCH 01/22] Read controller and send control_msgs to device --- app/src/control_msg.c | 14 ++++ app/src/control_msg.h | 17 ++++ app/src/input_manager.c | 81 +++++++++++++++++++ app/src/input_manager.h | 3 + app/src/scrcpy.c | 5 ++ .../com/genymobile/scrcpy/ControlMessage.java | 60 ++++++++++++++ .../scrcpy/ControlMessageReader.java | 41 ++++++++++ .../com/genymobile/scrcpy/Controller.java | 23 ++++++ 8 files changed, 244 insertions(+) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index d4d6c62a45..091651c418 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -152,6 +152,20 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) { case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE: // no additional data return 1; + case SC_CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_AXIS: + buffer_write16be(&buf[1], msg->inject_game_controller_axis.id); + buf[3] = msg->inject_game_controller_axis.axis; + buffer_write16be(&buf[4], msg->inject_game_controller_axis.value); + return 6; + case SC_CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_BUTTON: + buffer_write16be(&buf[1], msg->inject_game_controller_button.id); + buf[3] = msg->inject_game_controller_button.button; + buf[4] = msg->inject_game_controller_button.state; + return 5; + case SC_CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_DEVICE: + buffer_write16be(&buf[1], msg->inject_game_controller_device.id); + buf[3] = msg->inject_game_controller_device.event; + return 4; default: LOGW("Unknown message type: %u", (unsigned) msg->type); return 0; diff --git a/app/src/control_msg.h b/app/src/control_msg.h index b90a00b34c..a222338416 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -37,6 +37,9 @@ enum sc_control_msg_type { SC_CONTROL_MSG_TYPE_SET_CLIPBOARD, SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE, SC_CONTROL_MSG_TYPE_ROTATE_DEVICE, + SC_CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_AXIS, + SC_CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_BUTTON, + SC_CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_DEVICE, }; enum sc_screen_power_mode { @@ -92,6 +95,20 @@ struct sc_control_msg { struct { enum sc_screen_power_mode mode; } set_screen_power_mode; + struct { + int16_t id; + uint8_t axis; + int16_t value; + } inject_game_controller_axis; + struct { + int16_t id; + uint8_t button; + uint8_t state; + } inject_game_controller_button; + struct { + int16_t id; + uint8_t event; + } inject_game_controller_device; }; }; diff --git a/app/src/input_manager.c b/app/src/input_manager.c index c8098ee761..1e0967c893 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -58,6 +58,7 @@ sc_input_manager_init(struct sc_input_manager *im, im->controller = params->controller; im->fp = params->fp; im->screen = params->screen; + memset(im->game_controllers, 0, sizeof(im->game_controllers)); im->kp = params->kp; im->mp = params->mp; @@ -844,5 +845,85 @@ sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event) { } sc_input_manager_process_file(im, &event->drop); } + case SDL_CONTROLLERAXISMOTION: + if (!control) { + break; + } + input_manager_process_controller_axis(&input_manager, &event->caxis); + break; + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: + if (!control) { + break; + } + input_manager_process_controller_button(&input_manager, &event->cbutton); + break; + case SDL_CONTROLLERDEVICEADDED: + // case SDL_CONTROLLERDEVICEREMAPPED: + case SDL_CONTROLLERDEVICEREMOVED: + if (!control) { + break; + } + input_manager_process_controller_device(&input_manager, &event->cdevice); + break; } } + +void +input_manager_process_controller_device(struct input_manager *im, + const SDL_ControllerDeviceEvent *event) { + SDL_JoystickID id; + + switch (event->type) { + case SDL_CONTROLLERDEVICEADDED: { + SDL_GameController **freeGc = find_free_game_controller_slot(im); + + if (!freeGc) { + LOGW("Controller limit reached."); + return; + } + + SDL_GameController *game_controller; + game_controller = SDL_GameControllerOpen(event->which); + + if (game_controller) { + *freeGc = game_controller; + + SDL_Joystick *joystick; + joystick = SDL_GameControllerGetJoystick(game_controller); + + id = SDL_JoystickInstanceID(joystick); + } else { + LOGW("Could not open game controller #%d", event->which); + return; + } + break; + } + + case SDL_CONTROLLERDEVICEREMOVED: { + id = event->which; + + SDL_GameController *game_controller; + game_controller = SDL_GameControllerFromInstanceID(id); + + SDL_GameControllerClose(game_controller); + + if (!free_game_controller_slot(im, game_controller)) { + LOGW("Could not find removed game controller."); + return; + } + + break; + } + + default: + return; + } + + struct control_msg msg; + msg.type = CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_DEVICE; + msg.inject_game_controller_device.id = id; + msg.inject_game_controller_device.event = event->type; + msg.inject_game_controller_device.event -= SDL_CONTROLLERDEVICEADDED; + controller_push_msg(im->controller, &msg); +} diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 46b1160e83..9233292bad 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -14,10 +14,13 @@ #include "trait/key_processor.h" #include "trait/mouse_processor.h" +#define MAX_GAME_CONTROLLERS 16 + struct sc_input_manager { struct sc_controller *controller; struct sc_file_pusher *fp; struct sc_screen *screen; + SDL_GameController *game_controllers[MAX_GAME_CONTROLLERS]; struct sc_key_processor *kp; struct sc_mouse_processor *mp; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index e85536e6d9..f99e3c0d2c 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -367,6 +367,11 @@ scrcpy(struct scrcpy_options *options) { sdl_configure(options->display, options->disable_screensaver); + if (SDL_Init(SDL_INIT_GAMECONTROLLER)) { + LOGC("Could not initialize SDL: %s", SDL_GetError()); + return false; + } + // Await for server without blocking Ctrl+C handling bool connected; if (!await_for_server(&connected)) { diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java index e180037432..4f70ceca65 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java @@ -17,6 +17,9 @@ public final class ControlMessage { public static final int TYPE_SET_CLIPBOARD = 9; public static final int TYPE_SET_SCREEN_POWER_MODE = 10; public static final int TYPE_ROTATE_DEVICE = 11; + public static final int TYPE_INJECT_GAME_CONTROLLER_AXIS = 12; + public static final int TYPE_INJECT_GAME_CONTROLLER_BUTTON = 13; + public static final int TYPE_INJECT_GAME_CONTROLLER_DEVICE = 14; public static final long SEQUENCE_INVALID = 0; @@ -40,6 +43,12 @@ public final class ControlMessage { private boolean paste; private int repeat; private long sequence; + private int gameControllerId; + private int gameControllerAxis; + private int gameControllerAxisValue; + private int gameControllerButton; + private int gameControllerButtonState; + private int gameControllerDeviceEvent; private ControlMessage() { } @@ -117,6 +126,32 @@ public static ControlMessage createSetScreenPowerMode(int mode) { return msg; } + public static ControlMessage createInjectGameControllerAxis(int id, int axis, int value) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_INJECT_GAME_CONTROLLER_AXIS; + msg.gameControllerId = id; + msg.gameControllerAxis = axis; + msg.gameControllerAxisValue = value; + return msg; + } + + public static ControlMessage createInjectGameControllerButton(int id, int button, int state) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_INJECT_GAME_CONTROLLER_BUTTON; + msg.gameControllerId = id; + msg.gameControllerButton = button; + msg.gameControllerButtonState = state; + return msg; + } + + public static ControlMessage createInjectGameControllerDevice(int id, int event) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_INJECT_GAME_CONTROLLER_DEVICE; + msg.gameControllerId = id; + msg.gameControllerDeviceEvent = event; + return msg; + } + public static ControlMessage createEmpty(int type) { ControlMessage msg = new ControlMessage(); msg.type = type; @@ -186,4 +221,29 @@ public int getRepeat() { public long getSequence() { return sequence; } + + public int getGameControllerId() { + return gameControllerId; + } + + public int getGameControllerAxis() { + return gameControllerAxis; + } + + public int getGameControllerAxisValue() { + return gameControllerAxisValue; + } + + public int getGameControllerButton() { + return gameControllerButton; + } + + public int getGameControllerButtonState() { + return gameControllerButtonState; + } + + public int getGameControllerDeviceEvent() { + return gameControllerDeviceEvent; + } + } diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index d95c36d879..914f4b143b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -15,6 +15,9 @@ public class ControlMessageReader { static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; static final int GET_CLIPBOARD_LENGTH = 1; static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 9; + static final int INJECT_GAME_CONTROLLER_AXIS_PAYLOAD_LENGTH = 5; + static final int INJECT_GAME_CONTROLLER_BUTTON_PAYLOAD_LENGTH = 4; + static final int INJECT_GAME_CONTROLLER_DEVICE_PAYLOAD_LENGTH = 3; private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k @@ -86,6 +89,15 @@ public ControlMessage next() { case ControlMessage.TYPE_ROTATE_DEVICE: msg = ControlMessage.createEmpty(type); break; + case ControlMessage.TYPE_INJECT_GAME_CONTROLLER_AXIS: + msg = parseInjectGameControllerAxis(); + break; + case ControlMessage.TYPE_INJECT_GAME_CONTROLLER_BUTTON: + msg = parseInjectGameControllerButton(); + break; + case ControlMessage.TYPE_INJECT_GAME_CONTROLLER_DEVICE: + msg = parseInjectGameControllerDevice(); + break; default: Ln.w("Unknown event type: " + type); msg = null; @@ -193,6 +205,35 @@ private ControlMessage parseSetScreenPowerMode() { return ControlMessage.createSetScreenPowerMode(mode); } + private ControlMessage parseInjectGameControllerAxis() { + if (buffer.remaining() < INJECT_GAME_CONTROLLER_AXIS_PAYLOAD_LENGTH) { + return null; + } + int id = buffer.getShort(); + int axis = buffer.get(); + int value = buffer.getShort(); + return ControlMessage.createInjectGameControllerAxis(id, axis, value); + } + + private ControlMessage parseInjectGameControllerButton() { + if (buffer.remaining() < INJECT_GAME_CONTROLLER_BUTTON_PAYLOAD_LENGTH) { + return null; + } + int id = buffer.getShort(); + int button = buffer.get(); + int state = buffer.get(); + return ControlMessage.createInjectGameControllerButton(id, button, state); + } + + private ControlMessage parseInjectGameControllerDevice() { + if (buffer.remaining() < INJECT_GAME_CONTROLLER_DEVICE_PAYLOAD_LENGTH) { + return null; + } + int id = buffer.getShort(); + int event = buffer.get(); + return ControlMessage.createInjectGameControllerDevice(id, event); + } + private static Position readPosition(ByteBuffer buffer) { int x = buffer.getInt(); int y = buffer.getInt(); diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 6147e36a5c..fd4f15c476 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -165,6 +165,29 @@ private void handleEvent() throws IOException { case ControlMessage.TYPE_ROTATE_DEVICE: Device.rotateDevice(); break; + case ControlMessage.TYPE_INJECT_GAME_CONTROLLER_AXIS: + { + int id = msg.getGameControllerId(); + int axis = msg.getGameControllerAxis(); + int value = msg.getGameControllerAxisValue(); + Ln.d(String.format("Received gc axis: %d %d %d", id, axis, value)); + } + break; + case ControlMessage.TYPE_INJECT_GAME_CONTROLLER_BUTTON: + { + int id = msg.getGameControllerId(); + int button = msg.getGameControllerButton(); + int state = msg.getGameControllerButtonState(); + Ln.d(String.format("Received gc button: %d %d %d", id, button, state)); + } + break; + case ControlMessage.TYPE_INJECT_GAME_CONTROLLER_DEVICE: + { + int id = msg.getGameControllerId(); + int event = msg.getGameControllerDeviceEvent(); + Ln.d(String.format("Received gc device event: %d %d", id, event)); + } + break; default: // do nothing } From e1b2fb1f42b0f5b19682b2ad3480f6c9a4a0d939 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Tue, 7 Feb 2023 14:40:28 +0800 Subject: [PATCH 02/22] Use uinput to emulate game controllers --- app/src/server.c | 4 +- server/build.gradle | 1 + server/proguard-rules.pro | 4 + .../java/com/genymobile/scrcpy/CleanUp.java | 13 +- .../com/genymobile/scrcpy/Controller.java | 46 ++- .../com/genymobile/scrcpy/GameController.java | 372 ++++++++++++++++++ .../java/com/genymobile/scrcpy/Server.java | 35 ++ 7 files changed, 467 insertions(+), 8 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/GameController.java diff --git a/app/src/server.c b/app/src/server.c index 74d318c89f..afa9375a07 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -18,7 +18,8 @@ #define SC_SERVER_FILENAME "scrcpy-server" #define SC_SERVER_PATH_DEFAULT PREFIX "/share/scrcpy/" SC_SERVER_FILENAME -#define SC_DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar" +#define SC_DEVICE_SERVER_DIR "/data/local/tmp" +#define SC_DEVICE_SERVER_PATH SC_DEVICE_SERVER_DIR "/scrcpy-server.jar" #define SC_ADB_PORT_DEFAULT 5555 #define SC_SOCKET_NAME_PREFIX "scrcpy_" @@ -185,6 +186,7 @@ execute_server(struct sc_server *server, cmd[count++] = serial; cmd[count++] = "shell"; cmd[count++] = "CLASSPATH=" SC_DEVICE_SERVER_PATH; + cmd[count++] = "LD_LIBRARY_PATH=" SC_DEVICE_SERVER_DIR; cmd[count++] = "app_process"; #ifdef SERVER_DEBUGGER diff --git a/server/build.gradle b/server/build.gradle index 44bd78e895..6fab28f095 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -19,6 +19,7 @@ android { } dependencies { + implementation 'net.java.dev.jna:jna:5.6.0@aar' testImplementation 'junit:junit:4.13.2' } diff --git a/server/proguard-rules.pro b/server/proguard-rules.pro index f1b424510d..895e8200a7 100644 --- a/server/proguard-rules.pro +++ b/server/proguard-rules.pro @@ -19,3 +19,7 @@ # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile + +-dontwarn java.awt.* +-keep class com.sun.jna.* { *; } +-keepclassmembers class * extends com.sun.jna.* { public *; } diff --git a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java index 831dc994ad..44887755c3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java +++ b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java @@ -14,7 +14,7 @@ */ public final class CleanUp { - public static final String SERVER_PATH = "/data/local/tmp/scrcpy-server.jar"; + public static final String SERVER_PATH = Server.SERVER_DIR + "/scrcpy-server.jar"; // A simple struct to be passed from the main process to the cleanup process public static class Config implements Parcelable { @@ -147,8 +147,19 @@ private static void unlinkSelf() { } } + private static void unlinkNativeLibs() { + for (String lib : Server.NATIVE_LIBRARIES){ + try { + new File(Server.SERVER_DIR + "/" + lib).delete(); + } catch (Exception e) { + Ln.e("Could not unlink native library " + lib, e); + } + } + } + public static void main(String... args) { unlinkSelf(); + unlinkNativeLibs(); try { // Wait for the server to die diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index fd4f15c476..48b240cb3a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -4,6 +4,7 @@ import android.os.Build; import android.os.SystemClock; +import android.util.SparseArray; import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; @@ -41,6 +42,9 @@ public class Controller { private boolean keepPowerModeOff; + private SparseArray gameControllers = new SparseArray(); + private boolean gameControllersEnabled; + public Controller(Device device, DesktopConnection connection, boolean clipboardAutosync, boolean powerOn) { this.device = device; this.connection = connection; @@ -48,6 +52,14 @@ public Controller(Device device, DesktopConnection connection, boolean clipboard this.powerOn = powerOn; initPointers(); sender = new DeviceMessageSender(connection); + + try { + GameController.load_native_libraries(); + gameControllersEnabled = true; + } catch (UnsatisfiedLinkError e) { + Ln.e("Could not load native libraries. Game controllers will be disabled.", e); + gameControllersEnabled = false; + } } private void initPointers() { @@ -166,26 +178,48 @@ private void handleEvent() throws IOException { Device.rotateDevice(); break; case ControlMessage.TYPE_INJECT_GAME_CONTROLLER_AXIS: - { + if (gameControllersEnabled) { int id = msg.getGameControllerId(); int axis = msg.getGameControllerAxis(); int value = msg.getGameControllerAxisValue(); - Ln.d(String.format("Received gc axis: %d %d %d", id, axis, value)); + if (!gameControllers.contains(id)) { + Ln.w("Received data for non-existant controller."); + break; + } + gameControllers.get(id).setAxis(axis, value); } break; case ControlMessage.TYPE_INJECT_GAME_CONTROLLER_BUTTON: - { + if (gameControllersEnabled) { int id = msg.getGameControllerId(); int button = msg.getGameControllerButton(); int state = msg.getGameControllerButtonState(); - Ln.d(String.format("Received gc button: %d %d %d", id, button, state)); + if (!gameControllers.contains(id)) { + Ln.w("Received data for non-existant controller."); + break; + } + gameControllers.get(id).setButton(button, state); } break; case ControlMessage.TYPE_INJECT_GAME_CONTROLLER_DEVICE: - { + if (gameControllersEnabled) { int id = msg.getGameControllerId(); int event = msg.getGameControllerDeviceEvent(); - Ln.d(String.format("Received gc device event: %d %d", id, event)); + + switch (event) { + case GameController.DEVICE_ADDED: + gameControllers.append(id, new GameController()); + break; + + case GameController.DEVICE_REMOVED: + if (!gameControllers.contains(id)) { + Ln.w("Non-existant game controller removed."); + break; + } + gameControllers.get(id).close(); + gameControllers.delete(id); + break; + } } break; default: diff --git a/server/src/main/java/com/genymobile/scrcpy/GameController.java b/server/src/main/java/com/genymobile/scrcpy/GameController.java new file mode 100644 index 0000000000..a83b3fd0f0 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/GameController.java @@ -0,0 +1,372 @@ +package com.genymobile.scrcpy; + +import java.io.*; +import java.util.Arrays; +import java.util.List; +import com.sun.jna.Library; +import com.sun.jna.Platform; +import com.sun.jna.Native; +import com.sun.jna.Structure; +import com.sun.jna.Pointer; + +public final class GameController { + public static final int DEVICE_ADDED = 0; + public static final int DEVICE_REMOVED = 1; + + private static int UINPUT_MAX_NAME_SIZE = 80; + + public static class input_id extends Structure { + public short bustype = 0; + public short vendor = 0; + public short product = 0; + public short version = 0; + + @Override + protected List getFieldOrder() { + return Arrays.asList("bustype", "vendor", "product", "version"); + } + } + + public static class uinput_setup extends Structure { + public input_id id; + public byte[] name = new byte[UINPUT_MAX_NAME_SIZE]; + public int ff_effects_max = 0; + + @Override + protected List getFieldOrder() { + return Arrays.asList("id", "name", "ff_effects_max"); + } + } + + public static class input_event32 extends Structure { + public long time = 0; + public short type = 0; + public short code = 0; + public int value = 0; + + @Override + protected List getFieldOrder() { + return Arrays.asList("time", "type", "code", "value"); + } + } + + public static class input_event64 extends Structure { + public long sec = 0; + public long usec = 0; + public short type = 0; + public short code = 0; + public int value = 0; + + @Override + protected List getFieldOrder() { + return Arrays.asList("sec", "usec", "type", "code", "value"); + } + } + + private static int _IOC_NONE = 0; + private static int _IOC_WRITE = 1; + + private static int _IOC_DIRSHIFT = 30; + private static int _IOC_TYPESHIFT = 8; + private static int _IOC_NRSHIFT = 0; + private static int _IOC_SIZESHIFT = 16; + + private static int _IOC(int dir, int type, int nr, int size) { + return (dir << _IOC_DIRSHIFT) | + (type << _IOC_TYPESHIFT) | + (nr << _IOC_NRSHIFT) | + (size << _IOC_SIZESHIFT); + } + + private static int _IO(int type, int nr, int size) { + return _IOC(_IOC_NONE, type, nr, size); + } + + private static int _IOW(int type, int nr, int size) { + return _IOC(_IOC_WRITE, type, nr, size); + } + + private static final int O_WRONLY = 01; + private static final int O_NONBLOCK = 04000; + + private static final int BUS_USB = 0x03; + + private static final int UINPUT_IOCTL_BASE = 'U'; + + private static final int UI_SET_EVBIT = _IOW(UINPUT_IOCTL_BASE, 100, 4); + private static final int UI_SET_KEYBIT = _IOW(UINPUT_IOCTL_BASE, 101, 4); + private static final int UI_SET_ABSBIT = _IOW(UINPUT_IOCTL_BASE, 103, 4); + + private static final int UI_DEV_SETUP = _IOW(UINPUT_IOCTL_BASE, 3, new uinput_setup().size()); + private static final int UI_DEV_CREATE = _IO(UINPUT_IOCTL_BASE, 1, 0); + private static final int UI_DEV_DESTROY = _IO(UINPUT_IOCTL_BASE, 2, 0); + + private static final short EV_SYN = 0x00; + private static final short EV_KEY = 0x01; + private static final short EV_ABS = 0x03; + + private static final short SYN_REPORT = 0x00; + + private static final short BTN_A = 0x130; + private static final short BTN_B = 0x131; + private static final short BTN_X = 0x133; + private static final short BTN_Y = 0x134; + private static final short BTN_TL = 0x136; + private static final short BTN_TR = 0x137; + private static final short BTN_SELECT = 0x13a; + private static final short BTN_START = 0x13b; + private static final short BTN_MODE = 0x13c; + private static final short BTN_THUMBL = 0x13d; + private static final short BTN_THUMBR = 0x13e; + + private static final short ABS_X = 0x00; + private static final short ABS_Y = 0x01; + private static final short ABS_Z = 0x02; + private static final short ABS_RX = 0x03; + private static final short ABS_RY = 0x04; + private static final short ABS_RZ = 0x05; + private static final short ABS_HAT0X = 0x10; + private static final short ABS_HAT0Y = 0x11; + + private static final short XBOX_BTN_A = BTN_A; + private static final short XBOX_BTN_B = BTN_B; + private static final short XBOX_BTN_X = BTN_X; + private static final short XBOX_BTN_Y = BTN_Y; + private static final short XBOX_BTN_BACK = BTN_SELECT; + private static final short XBOX_BTN_START = BTN_START; + private static final short XBOX_BTN_LB = BTN_TL; + private static final short XBOX_BTN_RB = BTN_TR; + private static final short XBOX_BTN_GUIDE = BTN_MODE; + private static final short XBOX_BTN_LS = BTN_THUMBL; + private static final short XBOX_BTN_RS = BTN_THUMBR; + + private static final short XBOX_ABS_LSX = ABS_X; + private static final short XBOX_ABS_LSY = ABS_Y; + private static final short XBOX_ABS_RSX = ABS_RX; + private static final short XBOX_ABS_RSY = ABS_RY; + private static final short XBOX_ABS_DPADX = ABS_HAT0X; + private static final short XBOX_ABS_DPADY = ABS_HAT0Y; + private static final short XBOX_ABS_LT = ABS_Z; + private static final short XBOX_ABS_RT = ABS_RZ; + + private static final int SDL_CONTROLLER_AXIS_LEFTX = 0; + private static final int SDL_CONTROLLER_AXIS_LEFTY = 1; + private static final int SDL_CONTROLLER_AXIS_RIGHTX = 2; + private static final int SDL_CONTROLLER_AXIS_RIGHTY = 3; + private static final int SDL_CONTROLLER_AXIS_TRIGGERLEFT = 4; + private static final int SDL_CONTROLLER_AXIS_TRIGGERRIGHT = 5; + + private static final int SDL_CONTROLLER_BUTTON_A = 0; + private static final int SDL_CONTROLLER_BUTTON_B = 1; + private static final int SDL_CONTROLLER_BUTTON_X = 2; + private static final int SDL_CONTROLLER_BUTTON_Y = 3; + private static final int SDL_CONTROLLER_BUTTON_BACK = 4; + private static final int SDL_CONTROLLER_BUTTON_GUIDE = 5; + private static final int SDL_CONTROLLER_BUTTON_START = 6; + private static final int SDL_CONTROLLER_BUTTON_LEFTSTICK = 7; + private static final int SDL_CONTROLLER_BUTTON_RIGHTSTICK = 8; + private static final int SDL_CONTROLLER_BUTTON_LEFTSHOULDER = 9; + private static final int SDL_CONTROLLER_BUTTON_RIGHTSHOULDER = 10; + private static final int SDL_CONTROLLER_BUTTON_DPAD_UP = 11; + private static final int SDL_CONTROLLER_BUTTON_DPAD_DOWN = 12; + private static final int SDL_CONTROLLER_BUTTON_DPAD_LEFT = 13; + private static final int SDL_CONTROLLER_BUTTON_DPAD_RIGHT = 14; + + private int fd; + + public interface LibC extends Library { + LibC fn = (LibC)Native.load("c", LibC.class); + + int open(String pathname, int flags); + int ioctl(int fd, long request, Object... args); + long write(int fd, Pointer buf, long count); + int close(int fd); + } + + static public void load_native_libraries() { + GameController.LibC.fn.write(1, null, 0); + } + + public GameController() { + fd = LibC.fn.open("/dev/uinput", O_WRONLY | O_NONBLOCK); + if (fd == -1) { + throw new RuntimeException("Couldn't open uinput device."); + } + + LibC.fn.ioctl(fd, UI_SET_EVBIT, EV_KEY); + LibC.fn.ioctl(fd, UI_SET_KEYBIT, XBOX_BTN_A); + LibC.fn.ioctl(fd, UI_SET_KEYBIT, XBOX_BTN_B); + LibC.fn.ioctl(fd, UI_SET_KEYBIT, XBOX_BTN_X); + LibC.fn.ioctl(fd, UI_SET_KEYBIT, XBOX_BTN_Y); + LibC.fn.ioctl(fd, UI_SET_KEYBIT, XBOX_BTN_BACK); + LibC.fn.ioctl(fd, UI_SET_KEYBIT, XBOX_BTN_START); + LibC.fn.ioctl(fd, UI_SET_KEYBIT, XBOX_BTN_LB); + LibC.fn.ioctl(fd, UI_SET_KEYBIT, XBOX_BTN_RB); + LibC.fn.ioctl(fd, UI_SET_KEYBIT, XBOX_BTN_GUIDE); + LibC.fn.ioctl(fd, UI_SET_KEYBIT, XBOX_BTN_LS); + LibC.fn.ioctl(fd, UI_SET_KEYBIT, XBOX_BTN_RS); + + LibC.fn.ioctl(fd, UI_SET_EVBIT, EV_ABS); + LibC.fn.ioctl(fd, UI_SET_ABSBIT, XBOX_ABS_LSX); + LibC.fn.ioctl(fd, UI_SET_ABSBIT, XBOX_ABS_LSY); + LibC.fn.ioctl(fd, UI_SET_ABSBIT, XBOX_ABS_RSX); + LibC.fn.ioctl(fd, UI_SET_ABSBIT, XBOX_ABS_RSY); + LibC.fn.ioctl(fd, UI_SET_ABSBIT, XBOX_ABS_DPADX); + LibC.fn.ioctl(fd, UI_SET_ABSBIT, XBOX_ABS_DPADY); + LibC.fn.ioctl(fd, UI_SET_ABSBIT, XBOX_ABS_LT); + LibC.fn.ioctl(fd, UI_SET_ABSBIT, XBOX_ABS_RT); + + uinput_setup usetup = new uinput_setup(); + usetup.id.bustype = BUS_USB; + usetup.id.vendor = 0x045e; + usetup.id.product = 0x028e; + byte[] name = "Microsoft X-Box 360 pad".getBytes(); + System.arraycopy(name, 0, usetup.name, 0, name.length); + + if (LibC.fn.ioctl(fd, UI_DEV_SETUP, usetup) == -1) { + close(); + throw new RuntimeException("Couldn't setup uinput device."); + } + + if (LibC.fn.ioctl(fd, UI_DEV_CREATE) == -1) { + close(); + throw new RuntimeException("Couldn't create uinput device."); + } + } + + public void close() { + if (fd != -1) { + LibC.fn.ioctl(fd, UI_DEV_DESTROY); + LibC.fn.close(fd); + fd = -1; + } + } + + private static void emit32(int fd, short type, short code, int val) { + input_event32 ie = new input_event32(); + + ie.type = type; + ie.code = code; + ie.value = val; + + ie.write(); + + LibC.fn.write(fd, ie.getPointer(), ie.size()); + } + + private static void emit64(int fd, short type, short code, int val) { + input_event64 ie = new input_event64(); + + ie.type = type; + ie.code = code; + ie.value = val; + + ie.write(); + + LibC.fn.write(fd, ie.getPointer(), ie.size()); + } + + private static void emit(int fd, short type, short code, int val) { + if (Platform.is64Bit()) { + emit64(fd, type, code, val); + } else { + emit32(fd, type, code, val); + } + } + + private static short translateAxis(int axis) { + switch (axis) { + case SDL_CONTROLLER_AXIS_LEFTX: + return XBOX_ABS_LSX; + + case SDL_CONTROLLER_AXIS_LEFTY: + return XBOX_ABS_LSY; + + case SDL_CONTROLLER_AXIS_RIGHTX: + return XBOX_ABS_RSX; + + case SDL_CONTROLLER_AXIS_RIGHTY: + return XBOX_ABS_RSY; + + case SDL_CONTROLLER_AXIS_TRIGGERLEFT: + return XBOX_ABS_LT; + + case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: + return XBOX_ABS_RT; + + default: + return 0; + } + } + + private static short translateButton(int button) { + switch (button) { + case SDL_CONTROLLER_BUTTON_A: + return XBOX_BTN_A; + + case SDL_CONTROLLER_BUTTON_B: + return XBOX_BTN_B; + + case SDL_CONTROLLER_BUTTON_X: + return XBOX_BTN_X; + + case SDL_CONTROLLER_BUTTON_Y: + return XBOX_BTN_Y; + + case SDL_CONTROLLER_BUTTON_BACK: + return XBOX_BTN_BACK; + + case SDL_CONTROLLER_BUTTON_GUIDE: + return XBOX_BTN_GUIDE; + + case SDL_CONTROLLER_BUTTON_START: + return XBOX_BTN_START; + + case SDL_CONTROLLER_BUTTON_LEFTSTICK: + return XBOX_BTN_LS; + + case SDL_CONTROLLER_BUTTON_RIGHTSTICK: + return XBOX_BTN_RS; + + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: + return XBOX_BTN_LB; + + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: + return XBOX_BTN_RB; + + default: + return 0; + } + } + + public void setAxis(int axis, int value) { + emit(fd, EV_ABS, translateAxis(axis), (value + 0x8000) >> 8); + emit(fd, EV_SYN, SYN_REPORT, 0); + } + + public void setButton(int button, int state) { + // DPad buttons are usually reported as axes + + switch (button) { + case SDL_CONTROLLER_BUTTON_DPAD_UP: + emit(fd, EV_ABS, XBOX_ABS_DPADY, state != 0 ? 0 : 127); + break; + + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: + emit(fd, EV_ABS, XBOX_ABS_DPADY, state != 0 ? 255 : 127); + break; + + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: + emit(fd, EV_ABS, XBOX_ABS_DPADX, state != 0 ? 0 : 127); + break; + + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: + emit(fd, EV_ABS, XBOX_ABS_DPADX, state != 0 ? 255 : 127); + break; + + default: + emit(fd, EV_KEY, translateButton(button), state); + } + emit(fd, EV_SYN, SYN_REPORT, 0); + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 81c3e813fe..2d4000a717 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -5,12 +5,24 @@ import android.os.BatteryManager; import android.os.Build; +import java.io.InputStream; +import java.io.FileOutputStream; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.List; import java.util.Locale; +import java.util.concurrent.ExecutionException; public final class Server { + public static final String SERVER_DIR = "/data/local/tmp"; + public static final String[] NATIVE_LIBRARIES = { + "libjnidispatch.so", + }; + private Server() { // not instantiable } @@ -61,9 +73,32 @@ private static void initAndCleanUp(Options options) { private static void scrcpy(Options options) throws IOException { Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")"); + Ln.i("Supported ABIs: " + TextUtils.join(", ", Build.SUPPORTED_ABIS)); final Device device = new Device(options); List codecOptions = options.getCodecOptions(); + for (String lib : NATIVE_LIBRARIES) { + for (String abi : Build.SUPPORTED_ABIS) { + try { + InputStream resStream = Server.class.getResourceAsStream("/lib/" + abi + "/" + lib); + FileOutputStream fileStream = new FileOutputStream(SERVER_DIR + "/" + lib); + + byte[] buffer = new byte[1024]; + int length; + while ((length = resStream.read(buffer)) > 0) { + fileStream.write(buffer, 0, length); + } + + resStream.close(); + fileStream.close(); + + break; + } catch (Exception e) { + Ln.e("Could not extract native library for " + abi, e); + } + } + } + Thread initThread = startInitThread(options); int uid = options.getUid(); From 1a983486afb47803701596c4d27762a1b3bc2449 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Tue, 7 Feb 2023 14:41:05 +0800 Subject: [PATCH 03/22] Fix axes not working Also do some minor refactoring. --- .../com/genymobile/scrcpy/GameController.java | 100 +++++++++++++----- 1 file changed, 76 insertions(+), 24 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/GameController.java b/server/src/main/java/com/genymobile/scrcpy/GameController.java index a83b3fd0f0..d9ab996669 100644 --- a/server/src/main/java/com/genymobile/scrcpy/GameController.java +++ b/server/src/main/java/com/genymobile/scrcpy/GameController.java @@ -38,6 +38,31 @@ protected List getFieldOrder() { } } + public static class input_absinfo extends Structure { + public int value = 0; + public int minimum = 0; + public int maximum = 0; + public int fuzz = 0; + public int flat = 0; + public int resolution = 0; + + @Override + protected List getFieldOrder() { + return Arrays.asList("value", "minimum", "maximum", "fuzz", "flat", "resolution"); + } + }; + + public static class uinput_abs_setup extends Structure { + public short code; + public input_absinfo absinfo; + + @Override + protected List getFieldOrder() + { + return Arrays.asList("code", "absinfo"); + } + }; + public static class input_event32 extends Structure { public long time = 0; public short type = 0; @@ -96,6 +121,7 @@ private static int _IOW(int type, int nr, int size) { private static final int UI_SET_EVBIT = _IOW(UINPUT_IOCTL_BASE, 100, 4); private static final int UI_SET_KEYBIT = _IOW(UINPUT_IOCTL_BASE, 101, 4); private static final int UI_SET_ABSBIT = _IOW(UINPUT_IOCTL_BASE, 103, 4); + private static final int UI_ABS_SETUP = _IOW(UINPUT_IOCTL_BASE, 4, new uinput_abs_setup().size()); private static final int UI_DEV_SETUP = _IOW(UINPUT_IOCTL_BASE, 3, new uinput_setup().size()); private static final int UI_DEV_CREATE = _IO(UINPUT_IOCTL_BASE, 1, 0); @@ -194,27 +220,29 @@ public GameController() { } LibC.fn.ioctl(fd, UI_SET_EVBIT, EV_KEY); - LibC.fn.ioctl(fd, UI_SET_KEYBIT, XBOX_BTN_A); - LibC.fn.ioctl(fd, UI_SET_KEYBIT, XBOX_BTN_B); - LibC.fn.ioctl(fd, UI_SET_KEYBIT, XBOX_BTN_X); - LibC.fn.ioctl(fd, UI_SET_KEYBIT, XBOX_BTN_Y); - LibC.fn.ioctl(fd, UI_SET_KEYBIT, XBOX_BTN_BACK); - LibC.fn.ioctl(fd, UI_SET_KEYBIT, XBOX_BTN_START); - LibC.fn.ioctl(fd, UI_SET_KEYBIT, XBOX_BTN_LB); - LibC.fn.ioctl(fd, UI_SET_KEYBIT, XBOX_BTN_RB); - LibC.fn.ioctl(fd, UI_SET_KEYBIT, XBOX_BTN_GUIDE); - LibC.fn.ioctl(fd, UI_SET_KEYBIT, XBOX_BTN_LS); - LibC.fn.ioctl(fd, UI_SET_KEYBIT, XBOX_BTN_RS); + add_key(XBOX_BTN_A); + add_key(XBOX_BTN_B); + add_key(XBOX_BTN_X); + add_key(XBOX_BTN_Y); + add_key(XBOX_BTN_BACK); + add_key(XBOX_BTN_START); + add_key(XBOX_BTN_LB); + add_key(XBOX_BTN_RB); + add_key(XBOX_BTN_GUIDE); + add_key(XBOX_BTN_LS); + add_key(XBOX_BTN_RS); LibC.fn.ioctl(fd, UI_SET_EVBIT, EV_ABS); - LibC.fn.ioctl(fd, UI_SET_ABSBIT, XBOX_ABS_LSX); - LibC.fn.ioctl(fd, UI_SET_ABSBIT, XBOX_ABS_LSY); - LibC.fn.ioctl(fd, UI_SET_ABSBIT, XBOX_ABS_RSX); - LibC.fn.ioctl(fd, UI_SET_ABSBIT, XBOX_ABS_RSY); - LibC.fn.ioctl(fd, UI_SET_ABSBIT, XBOX_ABS_DPADX); - LibC.fn.ioctl(fd, UI_SET_ABSBIT, XBOX_ABS_DPADY); - LibC.fn.ioctl(fd, UI_SET_ABSBIT, XBOX_ABS_LT); - LibC.fn.ioctl(fd, UI_SET_ABSBIT, XBOX_ABS_RT); + add_abs(XBOX_ABS_LSX, -32768, 32767, 16, 128); + add_abs(XBOX_ABS_LSY, -32768, 32767, 16, 128); + add_abs(XBOX_ABS_RSX, -32768, 32767, 16, 128); + add_abs(XBOX_ABS_RSY, -32768, 32767, 16, 128); + add_abs(XBOX_ABS_DPADX, -1, 1, 0, 0); + add_abs(XBOX_ABS_DPADY, -1, 1, 0, 0); + // These values deviate from the real Xbox 360 controller, + // but allow higher precision (eg. Xbox One controller) + add_abs(XBOX_ABS_LT, 0, 32767, 0, 0); + add_abs(XBOX_ABS_RT, 0, 32767, 0, 0); uinput_setup usetup = new uinput_setup(); usetup.id.bustype = BUS_USB; @@ -242,6 +270,30 @@ public void close() { } } + private void add_key(int key) { + if (LibC.fn.ioctl(fd, UI_SET_KEYBIT, key) == -1) { + Ln.e("Could not add key event."); + } + } + + private void add_abs(short code, int minimum, int maximum, int fuzz, int flat) { + if (LibC.fn.ioctl(fd, UI_SET_ABSBIT, code) == -1) { + Ln.e("Could not add absolute event."); + } + + uinput_abs_setup abs_setup = new uinput_abs_setup(); + + abs_setup.code = code; + abs_setup.absinfo.minimum = minimum; + abs_setup.absinfo.maximum = maximum; + abs_setup.absinfo.fuzz = fuzz; + abs_setup.absinfo.flat = flat; + + if (LibC.fn.ioctl(fd, UI_ABS_SETUP, abs_setup) == -1) { + Ln.e("Could not set absolute event info."); + } + } + private static void emit32(int fd, short type, short code, int val) { input_event32 ie = new input_event32(); @@ -340,7 +392,7 @@ private static short translateButton(int button) { } public void setAxis(int axis, int value) { - emit(fd, EV_ABS, translateAxis(axis), (value + 0x8000) >> 8); + emit(fd, EV_ABS, translateAxis(axis), value); emit(fd, EV_SYN, SYN_REPORT, 0); } @@ -349,19 +401,19 @@ public void setButton(int button, int state) { switch (button) { case SDL_CONTROLLER_BUTTON_DPAD_UP: - emit(fd, EV_ABS, XBOX_ABS_DPADY, state != 0 ? 0 : 127); + emit(fd, EV_ABS, XBOX_ABS_DPADY, state != 0 ? -1 : 0); break; case SDL_CONTROLLER_BUTTON_DPAD_DOWN: - emit(fd, EV_ABS, XBOX_ABS_DPADY, state != 0 ? 255 : 127); + emit(fd, EV_ABS, XBOX_ABS_DPADY, state != 0 ? 1 : 0); break; case SDL_CONTROLLER_BUTTON_DPAD_LEFT: - emit(fd, EV_ABS, XBOX_ABS_DPADX, state != 0 ? 0 : 127); + emit(fd, EV_ABS, XBOX_ABS_DPADX, state != 0 ? -1 : 0); break; case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: - emit(fd, EV_ABS, XBOX_ABS_DPADX, state != 0 ? 255 : 127); + emit(fd, EV_ABS, XBOX_ABS_DPADX, state != 0 ? 1 : 0); break; default: From 9616c1daff208882e649274f7ee3bb2d62b87275 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Tue, 7 Feb 2023 14:41:45 +0800 Subject: [PATCH 04/22] Fix errors caused by improperly done rebase --- app/src/input_manager.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 1e0967c893..e99c4de0b6 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -921,7 +921,7 @@ input_manager_process_controller_device(struct input_manager *im, } struct control_msg msg; - msg.type = CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_DEVICE; + msg.type = SC_CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_DEVICE; msg.inject_game_controller_device.id = id; msg.inject_game_controller_device.event = event->type; msg.inject_game_controller_device.event -= SDL_CONTROLLERDEVICEADDED; From cedacc161294e87e240153ee3d6bfb2ac6bd9b11 Mon Sep 17 00:00:00 2001 From: Luiz Henrique Laurini Date: Thu, 18 Feb 2021 17:33:00 -0300 Subject: [PATCH 05/22] Remove some unused imports --- server/src/main/java/com/genymobile/scrcpy/GameController.java | 1 - server/src/main/java/com/genymobile/scrcpy/Server.java | 1 - 2 files changed, 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/GameController.java b/server/src/main/java/com/genymobile/scrcpy/GameController.java index d9ab996669..5723609eaa 100644 --- a/server/src/main/java/com/genymobile/scrcpy/GameController.java +++ b/server/src/main/java/com/genymobile/scrcpy/GameController.java @@ -1,6 +1,5 @@ package com.genymobile.scrcpy; -import java.io.*; import java.util.Arrays; import java.util.List; import com.sun.jna.Library; diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 2d4000a717..292cfbe3f3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -14,7 +14,6 @@ import java.nio.file.StandardCopyOption; import java.util.List; import java.util.Locale; -import java.util.concurrent.ExecutionException; public final class Server { From c2b8b6fb37cd36e6ec7cfd8590a639a5e284576e Mon Sep 17 00:00:00 2001 From: Luiz Henrique Laurini Date: Sat, 20 Feb 2021 13:27:22 -0300 Subject: [PATCH 06/22] Fix checkstyle errors unrelated to naming scheme --- .../java/com/genymobile/scrcpy/CleanUp.java | 2 +- .../com/genymobile/scrcpy/Controller.java | 3 ++ .../com/genymobile/scrcpy/GameController.java | 52 +++++++++---------- 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java index 44887755c3..9f2f7dd413 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java +++ b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java @@ -148,7 +148,7 @@ private static void unlinkSelf() { } private static void unlinkNativeLibs() { - for (String lib : Server.NATIVE_LIBRARIES){ + for (String lib : Server.NATIVE_LIBRARIES) { try { new File(Server.SERVER_DIR + "/" + lib).delete(); } catch (Exception e) { diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 48b240cb3a..a31441e87b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -219,6 +219,9 @@ private void handleEvent() throws IOException { gameControllers.get(id).close(); gameControllers.delete(id); break; + + default: + Ln.w("Unknown game controller event received."); } } break; diff --git a/server/src/main/java/com/genymobile/scrcpy/GameController.java b/server/src/main/java/com/genymobile/scrcpy/GameController.java index 5723609eaa..a2b55382dd 100644 --- a/server/src/main/java/com/genymobile/scrcpy/GameController.java +++ b/server/src/main/java/com/genymobile/scrcpy/GameController.java @@ -1,25 +1,26 @@ package com.genymobile.scrcpy; -import java.util.Arrays; -import java.util.List; import com.sun.jna.Library; -import com.sun.jna.Platform; import com.sun.jna.Native; -import com.sun.jna.Structure; +import com.sun.jna.Platform; import com.sun.jna.Pointer; +import com.sun.jna.Structure; + +import java.util.Arrays; +import java.util.List; public final class GameController { public static final int DEVICE_ADDED = 0; public static final int DEVICE_REMOVED = 1; - private static int UINPUT_MAX_NAME_SIZE = 80; + private static final int UINPUT_MAX_NAME_SIZE = 80; public static class input_id extends Structure { public short bustype = 0; public short vendor = 0; public short product = 0; public short version = 0; - + @Override protected List getFieldOrder() { return Arrays.asList("bustype", "vendor", "product", "version"); @@ -54,10 +55,9 @@ protected List getFieldOrder() { public static class uinput_abs_setup extends Structure { public short code; public input_absinfo absinfo; - + @Override - protected List getFieldOrder() - { + protected List getFieldOrder() { return Arrays.asList("code", "absinfo"); } }; @@ -87,19 +87,19 @@ protected List getFieldOrder() { } } - private static int _IOC_NONE = 0; - private static int _IOC_WRITE = 1; + private static final int _IOC_NONE = 0; + private static final int _IOC_WRITE = 1; - private static int _IOC_DIRSHIFT = 30; - private static int _IOC_TYPESHIFT = 8; - private static int _IOC_NRSHIFT = 0; - private static int _IOC_SIZESHIFT = 16; + private static final int _IOC_DIRSHIFT = 30; + private static final int _IOC_TYPESHIFT = 8; + private static final int _IOC_NRSHIFT = 0; + private static final int _IOC_SIZESHIFT = 16; private static int _IOC(int dir, int type, int nr, int size) { - return (dir << _IOC_DIRSHIFT) | - (type << _IOC_TYPESHIFT) | - (nr << _IOC_NRSHIFT) | - (size << _IOC_SIZESHIFT); + return (dir << _IOC_DIRSHIFT) + | (type << _IOC_TYPESHIFT) + | (nr << _IOC_NRSHIFT) + | (size << _IOC_SIZESHIFT); } private static int _IO(int type, int nr, int size) { @@ -114,7 +114,7 @@ private static int _IOW(int type, int nr, int size) { private static final int O_NONBLOCK = 04000; private static final int BUS_USB = 0x03; - + private static final int UINPUT_IOCTL_BASE = 'U'; private static final int UI_SET_EVBIT = _IOW(UINPUT_IOCTL_BASE, 100, 4); @@ -200,7 +200,7 @@ private static int _IOW(int type, int nr, int size) { private int fd; public interface LibC extends Library { - LibC fn = (LibC)Native.load("c", LibC.class); + LibC fn = (LibC) Native.load("c", LibC.class); int open(String pathname, int flags); int ioctl(int fd, long request, Object... args); @@ -208,7 +208,7 @@ public interface LibC extends Library { int close(int fd); } - static public void load_native_libraries() { + public static void load_native_libraries() { GameController.LibC.fn.write(1, null, 0); } @@ -217,7 +217,7 @@ public GameController() { if (fd == -1) { throw new RuntimeException("Couldn't open uinput device."); } - + LibC.fn.ioctl(fd, UI_SET_EVBIT, EV_KEY); add_key(XBOX_BTN_A); add_key(XBOX_BTN_B); @@ -230,7 +230,7 @@ public GameController() { add_key(XBOX_BTN_GUIDE); add_key(XBOX_BTN_LS); add_key(XBOX_BTN_RS); - + LibC.fn.ioctl(fd, UI_SET_EVBIT, EV_ABS); add_abs(XBOX_ABS_LSX, -32768, 32767, 16, 128); add_abs(XBOX_ABS_LSY, -32768, 32767, 16, 128); @@ -254,7 +254,7 @@ public GameController() { close(); throw new RuntimeException("Couldn't setup uinput device."); } - + if (LibC.fn.ioctl(fd, UI_DEV_CREATE) == -1) { close(); throw new RuntimeException("Couldn't create uinput device."); @@ -414,7 +414,7 @@ public void setButton(int button, int state) { case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: emit(fd, EV_ABS, XBOX_ABS_DPADX, state != 0 ? 1 : 0); break; - + default: emit(fd, EV_KEY, translateButton(button), state); } From fe88111187b0f35e07c682770135e97c8df19cd2 Mon Sep 17 00:00:00 2001 From: Luiz Henrique Laurini Date: Sat, 20 Feb 2021 14:32:16 -0300 Subject: [PATCH 07/22] Avoid calls unavailable on API level 21 --- .../com/genymobile/scrcpy/Controller.java | 30 ++++++++++++------- .../java/com/genymobile/scrcpy/Server.java | 6 ++-- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index a31441e87b..131ceb31fb 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -182,11 +182,15 @@ private void handleEvent() throws IOException { int id = msg.getGameControllerId(); int axis = msg.getGameControllerAxis(); int value = msg.getGameControllerAxisValue(); - if (!gameControllers.contains(id)) { + + GameController controller = gameControllers.get(id); + + if (controller != null) { + controller.setAxis(axis, value); + } else { Ln.w("Received data for non-existant controller."); - break; } - gameControllers.get(id).setAxis(axis, value); + break; } break; case ControlMessage.TYPE_INJECT_GAME_CONTROLLER_BUTTON: @@ -194,11 +198,14 @@ private void handleEvent() throws IOException { int id = msg.getGameControllerId(); int button = msg.getGameControllerButton(); int state = msg.getGameControllerButtonState(); - if (!gameControllers.contains(id)) { + + GameController controller = gameControllers.get(id); + + if (controller != null) { + controller.setButton(button, state); + } else { Ln.w("Received data for non-existant controller."); - break; } - gameControllers.get(id).setButton(button, state); } break; case ControlMessage.TYPE_INJECT_GAME_CONTROLLER_DEVICE: @@ -212,12 +219,15 @@ private void handleEvent() throws IOException { break; case GameController.DEVICE_REMOVED: - if (!gameControllers.contains(id)) { + GameController controller = gameControllers.get(id); + + if (controller != null) { + controller.close(); + gameControllers.delete(id); + } else { Ln.w("Non-existant game controller removed."); - break; } - gameControllers.get(id).close(); - gameControllers.delete(id); + break; default: diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 292cfbe3f3..81228416ed 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -4,14 +4,12 @@ import android.media.MediaCodecInfo; import android.os.BatteryManager; import android.os.Build; +import android.text.TextUtils; import java.io.InputStream; import java.io.FileOutputStream; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; +import java.nio.channels.FileChannel; import java.util.List; import java.util.Locale; From 6e7e6cdabea40f9e7f586d1d1f3f6e95c4b98414 Mon Sep 17 00:00:00 2001 From: Luiz Henrique Laurini Date: Sat, 20 Feb 2021 14:37:21 -0300 Subject: [PATCH 08/22] Write tests for new messages --- app/tests/test_control_msg_serialize.c | 72 +++++++++++++++++++ .../com/genymobile/scrcpy/GameController.java | 44 ++++++------ .../scrcpy/ControlMessageReaderTest.java | 70 ++++++++++++++++++ 3 files changed, 164 insertions(+), 22 deletions(-) diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index b2eef49ce5..d2806735c7 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -2,6 +2,8 @@ #include #include +#include +#include #include "control_msg.h" @@ -322,6 +324,73 @@ static void test_serialize_rotate_device(void) { assert(!memcmp(buf, expected, sizeof(expected))); } +static void test_serialize_inject_game_controller_axis(void) { + struct control_msg msg = { + .type = CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_AXIS, + .inject_game_controller_axis = { + .id = 0x1234, + .axis = SDL_CONTROLLER_AXIS_RIGHTY, + .value = -32768, + }, + }; + + unsigned char buf[CONTROL_MSG_MAX_SIZE]; + size_t size = control_msg_serialize(&msg, buf); + assert(size == 6); + + const unsigned char expected[] = { + CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_AXIS, + 0x12, 0x34, // id = 0x1234 + 0x03, // axis = SDL_CONTROLLER_AXIS_RIGHTY + 0x80, 0x00, // value = -32768 + }; + assert(!memcmp(buf, expected, sizeof(expected))); +} + +static void test_serialize_inject_game_controller_button(void) { + struct control_msg msg = { + .type = CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_BUTTON, + .inject_game_controller_button = { + .id = 0x1234, + .button = SDL_CONTROLLER_BUTTON_START, + .state = 1, + }, + }; + + unsigned char buf[CONTROL_MSG_MAX_SIZE]; + size_t size = control_msg_serialize(&msg, buf); + assert(size == 5); + + const unsigned char expected[] = { + CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_BUTTON, + 0x12, 0x34, // id = 0x1234 + 0x06, // button = SDL_CONTROLLER_BUTTON_START + 0x01, // state = 1 + }; + assert(!memcmp(buf, expected, sizeof(expected))); +} + +static void test_serialize_inject_game_controller_device(void) { + struct control_msg msg = { + .type = CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_DEVICE, + .inject_game_controller_device = { + .id = 0x1234, + .event = (SDL_CONTROLLERDEVICEREMOVED) - SDL_CONTROLLERDEVICEADDED, + }, + }; + + unsigned char buf[CONTROL_MSG_MAX_SIZE]; + size_t size = control_msg_serialize(&msg, buf); + assert(size == 4); + + const unsigned char expected[] = { + CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_DEVICE, + 0x12, 0x34, // id = 0x1234 + 0x01, // event = GameController.DEVICE_REMOVED + }; + assert(!memcmp(buf, expected, sizeof(expected))); +} + int main(int argc, char *argv[]) { (void) argc; (void) argv; @@ -340,5 +409,8 @@ int main(int argc, char *argv[]) { test_serialize_set_clipboard_long(); test_serialize_set_screen_power_mode(); test_serialize_rotate_device(); + test_serialize_inject_game_controller_axis(); + test_serialize_inject_game_controller_button(); + test_serialize_inject_game_controller_device(); return 0; } diff --git a/server/src/main/java/com/genymobile/scrcpy/GameController.java b/server/src/main/java/com/genymobile/scrcpy/GameController.java index a2b55382dd..4e92bf5d35 100644 --- a/server/src/main/java/com/genymobile/scrcpy/GameController.java +++ b/server/src/main/java/com/genymobile/scrcpy/GameController.java @@ -174,28 +174,28 @@ private static int _IOW(int type, int nr, int size) { private static final short XBOX_ABS_LT = ABS_Z; private static final short XBOX_ABS_RT = ABS_RZ; - private static final int SDL_CONTROLLER_AXIS_LEFTX = 0; - private static final int SDL_CONTROLLER_AXIS_LEFTY = 1; - private static final int SDL_CONTROLLER_AXIS_RIGHTX = 2; - private static final int SDL_CONTROLLER_AXIS_RIGHTY = 3; - private static final int SDL_CONTROLLER_AXIS_TRIGGERLEFT = 4; - private static final int SDL_CONTROLLER_AXIS_TRIGGERRIGHT = 5; - - private static final int SDL_CONTROLLER_BUTTON_A = 0; - private static final int SDL_CONTROLLER_BUTTON_B = 1; - private static final int SDL_CONTROLLER_BUTTON_X = 2; - private static final int SDL_CONTROLLER_BUTTON_Y = 3; - private static final int SDL_CONTROLLER_BUTTON_BACK = 4; - private static final int SDL_CONTROLLER_BUTTON_GUIDE = 5; - private static final int SDL_CONTROLLER_BUTTON_START = 6; - private static final int SDL_CONTROLLER_BUTTON_LEFTSTICK = 7; - private static final int SDL_CONTROLLER_BUTTON_RIGHTSTICK = 8; - private static final int SDL_CONTROLLER_BUTTON_LEFTSHOULDER = 9; - private static final int SDL_CONTROLLER_BUTTON_RIGHTSHOULDER = 10; - private static final int SDL_CONTROLLER_BUTTON_DPAD_UP = 11; - private static final int SDL_CONTROLLER_BUTTON_DPAD_DOWN = 12; - private static final int SDL_CONTROLLER_BUTTON_DPAD_LEFT = 13; - private static final int SDL_CONTROLLER_BUTTON_DPAD_RIGHT = 14; + public static final int SDL_CONTROLLER_AXIS_LEFTX = 0; + public static final int SDL_CONTROLLER_AXIS_LEFTY = 1; + public static final int SDL_CONTROLLER_AXIS_RIGHTX = 2; + public static final int SDL_CONTROLLER_AXIS_RIGHTY = 3; + public static final int SDL_CONTROLLER_AXIS_TRIGGERLEFT = 4; + public static final int SDL_CONTROLLER_AXIS_TRIGGERRIGHT = 5; + + public static final int SDL_CONTROLLER_BUTTON_A = 0; + public static final int SDL_CONTROLLER_BUTTON_B = 1; + public static final int SDL_CONTROLLER_BUTTON_X = 2; + public static final int SDL_CONTROLLER_BUTTON_Y = 3; + public static final int SDL_CONTROLLER_BUTTON_BACK = 4; + public static final int SDL_CONTROLLER_BUTTON_GUIDE = 5; + public static final int SDL_CONTROLLER_BUTTON_START = 6; + public static final int SDL_CONTROLLER_BUTTON_LEFTSTICK = 7; + public static final int SDL_CONTROLLER_BUTTON_RIGHTSTICK = 8; + public static final int SDL_CONTROLLER_BUTTON_LEFTSHOULDER = 9; + public static final int SDL_CONTROLLER_BUTTON_RIGHTSHOULDER = 10; + public static final int SDL_CONTROLLER_BUTTON_DPAD_UP = 11; + public static final int SDL_CONTROLLER_BUTTON_DPAD_DOWN = 12; + public static final int SDL_CONTROLLER_BUTTON_DPAD_LEFT = 13; + public static final int SDL_CONTROLLER_BUTTON_DPAD_RIGHT = 14; private int fd; diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java index 8405905ac5..9896f91974 100644 --- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java @@ -323,6 +323,76 @@ public void testParseRotateDevice() throws IOException { Assert.assertEquals(ControlMessage.TYPE_ROTATE_DEVICE, event.getType()); } + @Test + public void testParseGameControllerAxisEvent() throws IOException { + ControlMessageReader reader = new ControlMessageReader(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeByte(ControlMessage.TYPE_INJECT_GAME_CONTROLLER_AXIS); + dos.writeShort(0x1234); + dos.writeByte(GameController.SDL_CONTROLLER_AXIS_RIGHTY); + dos.writeShort(-32768); + byte[] packet = bos.toByteArray(); + + // The message type (1 byte) does not count + Assert.assertEquals(ControlMessageReader.INJECT_GAME_CONTROLLER_AXIS_PAYLOAD_LENGTH, packet.length - 1); + + reader.readFrom(new ByteArrayInputStream(packet)); + ControlMessage event = reader.next(); + + Assert.assertEquals(ControlMessage.TYPE_INJECT_GAME_CONTROLLER_AXIS, event.getType()); + Assert.assertEquals(0x1234, event.getGameControllerId()); + Assert.assertEquals(GameController.SDL_CONTROLLER_AXIS_RIGHTY, event.getGameControllerAxis()); + Assert.assertEquals(-32768, event.getGameControllerAxisValue()); + } + + @Test + public void testParseGameControllerButtonEvent() throws IOException { + ControlMessageReader reader = new ControlMessageReader(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeByte(ControlMessage.TYPE_INJECT_GAME_CONTROLLER_BUTTON); + dos.writeShort(0x1234); + dos.writeByte(GameController.SDL_CONTROLLER_BUTTON_START); + dos.writeByte(1); + byte[] packet = bos.toByteArray(); + + // The message type (1 byte) does not count + Assert.assertEquals(ControlMessageReader.INJECT_GAME_CONTROLLER_BUTTON_PAYLOAD_LENGTH, packet.length - 1); + + reader.readFrom(new ByteArrayInputStream(packet)); + ControlMessage event = reader.next(); + + Assert.assertEquals(ControlMessage.TYPE_INJECT_GAME_CONTROLLER_BUTTON, event.getType()); + Assert.assertEquals(0x1234, event.getGameControllerId()); + Assert.assertEquals(GameController.SDL_CONTROLLER_BUTTON_START, event.getGameControllerButton()); + Assert.assertEquals(1, event.getGameControllerButtonState()); + } + + @Test + public void testParseGameControllerDeviceEvent() throws IOException { + ControlMessageReader reader = new ControlMessageReader(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeByte(ControlMessage.TYPE_INJECT_GAME_CONTROLLER_DEVICE); + dos.writeShort(0x1234); + dos.writeByte(GameController.DEVICE_REMOVED); + byte[] packet = bos.toByteArray(); + + // The message type (1 byte) does not count + Assert.assertEquals(ControlMessageReader.INJECT_GAME_CONTROLLER_DEVICE_PAYLOAD_LENGTH, packet.length - 1); + + reader.readFrom(new ByteArrayInputStream(packet)); + ControlMessage event = reader.next(); + + Assert.assertEquals(ControlMessage.TYPE_INJECT_GAME_CONTROLLER_DEVICE, event.getType()); + Assert.assertEquals(0x1234, event.getGameControllerId()); + Assert.assertEquals(GameController.DEVICE_REMOVED, event.getGameControllerDeviceEvent()); + } + @Test public void testMultiEvents() throws IOException { ControlMessageReader reader = new ControlMessageReader(); From aff3d85cad8e60f7caecedb28e2ff73e1f35ddbf Mon Sep 17 00:00:00 2001 From: Luiz Henrique Laurini Date: Sat, 20 Feb 2021 14:45:54 -0300 Subject: [PATCH 09/22] Fix naming scheme --- .../com/genymobile/scrcpy/Controller.java | 6 +- .../com/genymobile/scrcpy/GameController.java | 162 +++++++++--------- .../java/com/genymobile/scrcpy/Server.java | 1 - 3 files changed, 87 insertions(+), 82 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 131ceb31fb..18933e7bd8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -54,7 +54,7 @@ public Controller(Device device, DesktopConnection connection, boolean clipboard sender = new DeviceMessageSender(connection); try { - GameController.load_native_libraries(); + GameController.loadNativeLibraries(); gameControllersEnabled = true; } catch (UnsatisfiedLinkError e) { Ln.e("Could not load native libraries. Game controllers will be disabled.", e); @@ -198,7 +198,7 @@ private void handleEvent() throws IOException { int id = msg.getGameControllerId(); int button = msg.getGameControllerButton(); int state = msg.getGameControllerButtonState(); - + GameController controller = gameControllers.get(id); if (controller != null) { @@ -227,7 +227,7 @@ private void handleEvent() throws IOException { } else { Ln.w("Non-existant game controller removed."); } - + break; default: diff --git a/server/src/main/java/com/genymobile/scrcpy/GameController.java b/server/src/main/java/com/genymobile/scrcpy/GameController.java index 4e92bf5d35..aef4f28692 100644 --- a/server/src/main/java/com/genymobile/scrcpy/GameController.java +++ b/server/src/main/java/com/genymobile/scrcpy/GameController.java @@ -15,7 +15,8 @@ public final class GameController { private static final int UINPUT_MAX_NAME_SIZE = 80; - public static class input_id extends Structure { + @SuppressWarnings("checkstyle:VisibilityModifier") + public static class InputId extends Structure { public short bustype = 0; public short vendor = 0; public short product = 0; @@ -27,18 +28,20 @@ protected List getFieldOrder() { } } - public static class uinput_setup extends Structure { - public input_id id; + @SuppressWarnings("checkstyle:VisibilityModifier") + public static class UinputSetup extends Structure { + public InputId id; public byte[] name = new byte[UINPUT_MAX_NAME_SIZE]; - public int ff_effects_max = 0; + public int ffEffectsMax = 0; @Override protected List getFieldOrder() { - return Arrays.asList("id", "name", "ff_effects_max"); + return Arrays.asList("id", "name", "ffEffectsMax"); } } - public static class input_absinfo extends Structure { + @SuppressWarnings("checkstyle:VisibilityModifier") + public static class InputAbsinfo extends Structure { public int value = 0; public int minimum = 0; public int maximum = 0; @@ -52,9 +55,10 @@ protected List getFieldOrder() { } }; - public static class uinput_abs_setup extends Structure { + @SuppressWarnings("checkstyle:VisibilityModifier") + public static class UinputAbsSetup extends Structure { public short code; - public input_absinfo absinfo; + public InputAbsinfo absinfo; @Override protected List getFieldOrder() { @@ -62,7 +66,8 @@ protected List getFieldOrder() { } }; - public static class input_event32 extends Structure { + @SuppressWarnings("checkstyle:VisibilityModifier") + public static class InputEvent32 extends Structure { public long time = 0; public short type = 0; public short code = 0; @@ -74,7 +79,8 @@ protected List getFieldOrder() { } } - public static class input_event64 extends Structure { + @SuppressWarnings("checkstyle:VisibilityModifier") + public static class InputEvent64 extends Structure { public long sec = 0; public long usec = 0; public short type = 0; @@ -87,27 +93,27 @@ protected List getFieldOrder() { } } - private static final int _IOC_NONE = 0; - private static final int _IOC_WRITE = 1; + private static final int IOC_NONE = 0; + private static final int IOC_WRITE = 1; - private static final int _IOC_DIRSHIFT = 30; - private static final int _IOC_TYPESHIFT = 8; - private static final int _IOC_NRSHIFT = 0; - private static final int _IOC_SIZESHIFT = 16; + private static final int IOC_DIRSHIFT = 30; + private static final int IOC_TYPESHIFT = 8; + private static final int IOC_NRSHIFT = 0; + private static final int IOC_SIZESHIFT = 16; - private static int _IOC(int dir, int type, int nr, int size) { - return (dir << _IOC_DIRSHIFT) - | (type << _IOC_TYPESHIFT) - | (nr << _IOC_NRSHIFT) - | (size << _IOC_SIZESHIFT); + private static int ioc(int dir, int type, int nr, int size) { + return (dir << IOC_DIRSHIFT) + | (type << IOC_TYPESHIFT) + | (nr << IOC_NRSHIFT) + | (size << IOC_SIZESHIFT); } - private static int _IO(int type, int nr, int size) { - return _IOC(_IOC_NONE, type, nr, size); + private static int io(int type, int nr, int size) { + return ioc(IOC_NONE, type, nr, size); } - private static int _IOW(int type, int nr, int size) { - return _IOC(_IOC_WRITE, type, nr, size); + private static int iow(int type, int nr, int size) { + return ioc(IOC_WRITE, type, nr, size); } private static final int O_WRONLY = 01; @@ -117,14 +123,14 @@ private static int _IOW(int type, int nr, int size) { private static final int UINPUT_IOCTL_BASE = 'U'; - private static final int UI_SET_EVBIT = _IOW(UINPUT_IOCTL_BASE, 100, 4); - private static final int UI_SET_KEYBIT = _IOW(UINPUT_IOCTL_BASE, 101, 4); - private static final int UI_SET_ABSBIT = _IOW(UINPUT_IOCTL_BASE, 103, 4); - private static final int UI_ABS_SETUP = _IOW(UINPUT_IOCTL_BASE, 4, new uinput_abs_setup().size()); + private static final int UI_SET_EVBIT = iow(UINPUT_IOCTL_BASE, 100, 4); + private static final int UI_SET_KEYBIT = iow(UINPUT_IOCTL_BASE, 101, 4); + private static final int UI_SET_ABSBIT = iow(UINPUT_IOCTL_BASE, 103, 4); + private static final int UI_ABS_SETUP = iow(UINPUT_IOCTL_BASE, 4, new UinputAbsSetup().size()); - private static final int UI_DEV_SETUP = _IOW(UINPUT_IOCTL_BASE, 3, new uinput_setup().size()); - private static final int UI_DEV_CREATE = _IO(UINPUT_IOCTL_BASE, 1, 0); - private static final int UI_DEV_DESTROY = _IO(UINPUT_IOCTL_BASE, 2, 0); + private static final int UI_DEV_SETUP = iow(UINPUT_IOCTL_BASE, 3, new UinputSetup().size()); + private static final int UI_DEV_CREATE = iow(UINPUT_IOCTL_BASE, 1, 0); + private static final int UI_DEV_DESTROY = iow(UINPUT_IOCTL_BASE, 2, 0); private static final short EV_SYN = 0x00; private static final short EV_KEY = 0x01; @@ -200,7 +206,7 @@ private static int _IOW(int type, int nr, int size) { private int fd; public interface LibC extends Library { - LibC fn = (LibC) Native.load("c", LibC.class); + LibC FN = (LibC) Native.load("c", LibC.class); int open(String pathname, int flags); int ioctl(int fd, long request, Object... args); @@ -208,54 +214,54 @@ public interface LibC extends Library { int close(int fd); } - public static void load_native_libraries() { - GameController.LibC.fn.write(1, null, 0); + public static void loadNativeLibraries() { + GameController.LibC.FN.write(1, null, 0); } public GameController() { - fd = LibC.fn.open("/dev/uinput", O_WRONLY | O_NONBLOCK); + fd = LibC.FN.open("/dev/uinput", O_WRONLY | O_NONBLOCK); if (fd == -1) { throw new RuntimeException("Couldn't open uinput device."); } - LibC.fn.ioctl(fd, UI_SET_EVBIT, EV_KEY); - add_key(XBOX_BTN_A); - add_key(XBOX_BTN_B); - add_key(XBOX_BTN_X); - add_key(XBOX_BTN_Y); - add_key(XBOX_BTN_BACK); - add_key(XBOX_BTN_START); - add_key(XBOX_BTN_LB); - add_key(XBOX_BTN_RB); - add_key(XBOX_BTN_GUIDE); - add_key(XBOX_BTN_LS); - add_key(XBOX_BTN_RS); - - LibC.fn.ioctl(fd, UI_SET_EVBIT, EV_ABS); - add_abs(XBOX_ABS_LSX, -32768, 32767, 16, 128); - add_abs(XBOX_ABS_LSY, -32768, 32767, 16, 128); - add_abs(XBOX_ABS_RSX, -32768, 32767, 16, 128); - add_abs(XBOX_ABS_RSY, -32768, 32767, 16, 128); - add_abs(XBOX_ABS_DPADX, -1, 1, 0, 0); - add_abs(XBOX_ABS_DPADY, -1, 1, 0, 0); + LibC.FN.ioctl(fd, UI_SET_EVBIT, EV_KEY); + addKey(XBOX_BTN_A); + addKey(XBOX_BTN_B); + addKey(XBOX_BTN_X); + addKey(XBOX_BTN_Y); + addKey(XBOX_BTN_BACK); + addKey(XBOX_BTN_START); + addKey(XBOX_BTN_LB); + addKey(XBOX_BTN_RB); + addKey(XBOX_BTN_GUIDE); + addKey(XBOX_BTN_LS); + addKey(XBOX_BTN_RS); + + LibC.FN.ioctl(fd, UI_SET_EVBIT, EV_ABS); + addAbs(XBOX_ABS_LSX, -32768, 32767, 16, 128); + addAbs(XBOX_ABS_LSY, -32768, 32767, 16, 128); + addAbs(XBOX_ABS_RSX, -32768, 32767, 16, 128); + addAbs(XBOX_ABS_RSY, -32768, 32767, 16, 128); + addAbs(XBOX_ABS_DPADX, -1, 1, 0, 0); + addAbs(XBOX_ABS_DPADY, -1, 1, 0, 0); // These values deviate from the real Xbox 360 controller, // but allow higher precision (eg. Xbox One controller) - add_abs(XBOX_ABS_LT, 0, 32767, 0, 0); - add_abs(XBOX_ABS_RT, 0, 32767, 0, 0); + addAbs(XBOX_ABS_LT, 0, 32767, 0, 0); + addAbs(XBOX_ABS_RT, 0, 32767, 0, 0); - uinput_setup usetup = new uinput_setup(); + UinputSetup usetup = new UinputSetup(); usetup.id.bustype = BUS_USB; usetup.id.vendor = 0x045e; usetup.id.product = 0x028e; byte[] name = "Microsoft X-Box 360 pad".getBytes(); System.arraycopy(name, 0, usetup.name, 0, name.length); - if (LibC.fn.ioctl(fd, UI_DEV_SETUP, usetup) == -1) { + if (LibC.FN.ioctl(fd, UI_DEV_SETUP, usetup) == -1) { close(); throw new RuntimeException("Couldn't setup uinput device."); } - if (LibC.fn.ioctl(fd, UI_DEV_CREATE) == -1) { + if (LibC.FN.ioctl(fd, UI_DEV_CREATE) == -1) { close(); throw new RuntimeException("Couldn't create uinput device."); } @@ -263,38 +269,38 @@ public GameController() { public void close() { if (fd != -1) { - LibC.fn.ioctl(fd, UI_DEV_DESTROY); - LibC.fn.close(fd); + LibC.FN.ioctl(fd, UI_DEV_DESTROY); + LibC.FN.close(fd); fd = -1; } } - private void add_key(int key) { - if (LibC.fn.ioctl(fd, UI_SET_KEYBIT, key) == -1) { + private void addKey(int key) { + if (LibC.FN.ioctl(fd, UI_SET_KEYBIT, key) == -1) { Ln.e("Could not add key event."); } } - private void add_abs(short code, int minimum, int maximum, int fuzz, int flat) { - if (LibC.fn.ioctl(fd, UI_SET_ABSBIT, code) == -1) { + private void addAbs(short code, int minimum, int maximum, int fuzz, int flat) { + if (LibC.FN.ioctl(fd, UI_SET_ABSBIT, code) == -1) { Ln.e("Could not add absolute event."); } - uinput_abs_setup abs_setup = new uinput_abs_setup(); + UinputAbsSetup absSetup = new UinputAbsSetup(); - abs_setup.code = code; - abs_setup.absinfo.minimum = minimum; - abs_setup.absinfo.maximum = maximum; - abs_setup.absinfo.fuzz = fuzz; - abs_setup.absinfo.flat = flat; + absSetup.code = code; + absSetup.absinfo.minimum = minimum; + absSetup.absinfo.maximum = maximum; + absSetup.absinfo.fuzz = fuzz; + absSetup.absinfo.flat = flat; - if (LibC.fn.ioctl(fd, UI_ABS_SETUP, abs_setup) == -1) { + if (LibC.FN.ioctl(fd, UI_ABS_SETUP, absSetup) == -1) { Ln.e("Could not set absolute event info."); } } private static void emit32(int fd, short type, short code, int val) { - input_event32 ie = new input_event32(); + InputEvent32 ie = new InputEvent32(); ie.type = type; ie.code = code; @@ -302,11 +308,11 @@ private static void emit32(int fd, short type, short code, int val) { ie.write(); - LibC.fn.write(fd, ie.getPointer(), ie.size()); + LibC.FN.write(fd, ie.getPointer(), ie.size()); } private static void emit64(int fd, short type, short code, int val) { - input_event64 ie = new input_event64(); + InputEvent64 ie = new InputEvent64(); ie.type = type; ie.code = code; @@ -314,7 +320,7 @@ private static void emit64(int fd, short type, short code, int val) { ie.write(); - LibC.fn.write(fd, ie.getPointer(), ie.size()); + LibC.FN.write(fd, ie.getPointer(), ie.size()); } private static void emit(int fd, short type, short code, int val) { diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 81228416ed..d660e889df 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -9,7 +9,6 @@ import java.io.InputStream; import java.io.FileOutputStream; import java.io.IOException; -import java.nio.channels.FileChannel; import java.util.List; import java.util.Locale; From 22d64e14cadc87ea4771a5b0abbc260a4fb3b8b8 Mon Sep 17 00:00:00 2001 From: Luiz Henrique Laurini Date: Sat, 20 Feb 2021 21:04:10 -0300 Subject: [PATCH 10/22] Fix mistake when renaming things --- .../src/main/java/com/genymobile/scrcpy/GameController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/GameController.java b/server/src/main/java/com/genymobile/scrcpy/GameController.java index aef4f28692..dc139e1790 100644 --- a/server/src/main/java/com/genymobile/scrcpy/GameController.java +++ b/server/src/main/java/com/genymobile/scrcpy/GameController.java @@ -129,8 +129,8 @@ private static int iow(int type, int nr, int size) { private static final int UI_ABS_SETUP = iow(UINPUT_IOCTL_BASE, 4, new UinputAbsSetup().size()); private static final int UI_DEV_SETUP = iow(UINPUT_IOCTL_BASE, 3, new UinputSetup().size()); - private static final int UI_DEV_CREATE = iow(UINPUT_IOCTL_BASE, 1, 0); - private static final int UI_DEV_DESTROY = iow(UINPUT_IOCTL_BASE, 2, 0); + private static final int UI_DEV_CREATE = io(UINPUT_IOCTL_BASE, 1, 0); + private static final int UI_DEV_DESTROY = io(UINPUT_IOCTL_BASE, 2, 0); private static final short EV_SYN = 0x00; private static final short EV_KEY = 0x01; From ade5d50253245c5dc90146adf2fbe52ce1f2f18f Mon Sep 17 00:00:00 2001 From: Luiz Henrique Laurini Date: Wed, 31 Mar 2021 20:22:45 -0300 Subject: [PATCH 11/22] Move uinput stuff to UinputDevice --- .../com/genymobile/scrcpy/Controller.java | 2 +- .../com/genymobile/scrcpy/GameController.java | 278 ++-------------- .../com/genymobile/scrcpy/UinputDevice.java | 296 ++++++++++++++++++ 3 files changed, 323 insertions(+), 253 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/UinputDevice.java diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 18933e7bd8..6aab79d71f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -54,7 +54,7 @@ public Controller(Device device, DesktopConnection connection, boolean clipboard sender = new DeviceMessageSender(connection); try { - GameController.loadNativeLibraries(); + UinputDevice.loadNativeLibraries(); gameControllersEnabled = true; } catch (UnsatisfiedLinkError e) { Ln.e("Could not load native libraries. Game controllers will be disabled.", e); diff --git a/server/src/main/java/com/genymobile/scrcpy/GameController.java b/server/src/main/java/com/genymobile/scrcpy/GameController.java index dc139e1790..2725c49bab 100644 --- a/server/src/main/java/com/genymobile/scrcpy/GameController.java +++ b/server/src/main/java/com/genymobile/scrcpy/GameController.java @@ -9,156 +9,7 @@ import java.util.Arrays; import java.util.List; -public final class GameController { - public static final int DEVICE_ADDED = 0; - public static final int DEVICE_REMOVED = 1; - - private static final int UINPUT_MAX_NAME_SIZE = 80; - - @SuppressWarnings("checkstyle:VisibilityModifier") - public static class InputId extends Structure { - public short bustype = 0; - public short vendor = 0; - public short product = 0; - public short version = 0; - - @Override - protected List getFieldOrder() { - return Arrays.asList("bustype", "vendor", "product", "version"); - } - } - - @SuppressWarnings("checkstyle:VisibilityModifier") - public static class UinputSetup extends Structure { - public InputId id; - public byte[] name = new byte[UINPUT_MAX_NAME_SIZE]; - public int ffEffectsMax = 0; - - @Override - protected List getFieldOrder() { - return Arrays.asList("id", "name", "ffEffectsMax"); - } - } - - @SuppressWarnings("checkstyle:VisibilityModifier") - public static class InputAbsinfo extends Structure { - public int value = 0; - public int minimum = 0; - public int maximum = 0; - public int fuzz = 0; - public int flat = 0; - public int resolution = 0; - - @Override - protected List getFieldOrder() { - return Arrays.asList("value", "minimum", "maximum", "fuzz", "flat", "resolution"); - } - }; - - @SuppressWarnings("checkstyle:VisibilityModifier") - public static class UinputAbsSetup extends Structure { - public short code; - public InputAbsinfo absinfo; - - @Override - protected List getFieldOrder() { - return Arrays.asList("code", "absinfo"); - } - }; - - @SuppressWarnings("checkstyle:VisibilityModifier") - public static class InputEvent32 extends Structure { - public long time = 0; - public short type = 0; - public short code = 0; - public int value = 0; - - @Override - protected List getFieldOrder() { - return Arrays.asList("time", "type", "code", "value"); - } - } - - @SuppressWarnings("checkstyle:VisibilityModifier") - public static class InputEvent64 extends Structure { - public long sec = 0; - public long usec = 0; - public short type = 0; - public short code = 0; - public int value = 0; - - @Override - protected List getFieldOrder() { - return Arrays.asList("sec", "usec", "type", "code", "value"); - } - } - - private static final int IOC_NONE = 0; - private static final int IOC_WRITE = 1; - - private static final int IOC_DIRSHIFT = 30; - private static final int IOC_TYPESHIFT = 8; - private static final int IOC_NRSHIFT = 0; - private static final int IOC_SIZESHIFT = 16; - - private static int ioc(int dir, int type, int nr, int size) { - return (dir << IOC_DIRSHIFT) - | (type << IOC_TYPESHIFT) - | (nr << IOC_NRSHIFT) - | (size << IOC_SIZESHIFT); - } - - private static int io(int type, int nr, int size) { - return ioc(IOC_NONE, type, nr, size); - } - - private static int iow(int type, int nr, int size) { - return ioc(IOC_WRITE, type, nr, size); - } - - private static final int O_WRONLY = 01; - private static final int O_NONBLOCK = 04000; - - private static final int BUS_USB = 0x03; - - private static final int UINPUT_IOCTL_BASE = 'U'; - - private static final int UI_SET_EVBIT = iow(UINPUT_IOCTL_BASE, 100, 4); - private static final int UI_SET_KEYBIT = iow(UINPUT_IOCTL_BASE, 101, 4); - private static final int UI_SET_ABSBIT = iow(UINPUT_IOCTL_BASE, 103, 4); - private static final int UI_ABS_SETUP = iow(UINPUT_IOCTL_BASE, 4, new UinputAbsSetup().size()); - - private static final int UI_DEV_SETUP = iow(UINPUT_IOCTL_BASE, 3, new UinputSetup().size()); - private static final int UI_DEV_CREATE = io(UINPUT_IOCTL_BASE, 1, 0); - private static final int UI_DEV_DESTROY = io(UINPUT_IOCTL_BASE, 2, 0); - - private static final short EV_SYN = 0x00; - private static final short EV_KEY = 0x01; - private static final short EV_ABS = 0x03; - - private static final short SYN_REPORT = 0x00; - - private static final short BTN_A = 0x130; - private static final short BTN_B = 0x131; - private static final short BTN_X = 0x133; - private static final short BTN_Y = 0x134; - private static final short BTN_TL = 0x136; - private static final short BTN_TR = 0x137; - private static final short BTN_SELECT = 0x13a; - private static final short BTN_START = 0x13b; - private static final short BTN_MODE = 0x13c; - private static final short BTN_THUMBL = 0x13d; - private static final short BTN_THUMBR = 0x13e; - - private static final short ABS_X = 0x00; - private static final short ABS_Y = 0x01; - private static final short ABS_Z = 0x02; - private static final short ABS_RX = 0x03; - private static final short ABS_RY = 0x04; - private static final short ABS_RZ = 0x05; - private static final short ABS_HAT0X = 0x10; - private static final short ABS_HAT0Y = 0x11; - +public final class GameController extends UinputDevice { private static final short XBOX_BTN_A = BTN_A; private static final short XBOX_BTN_B = BTN_B; private static final short XBOX_BTN_X = BTN_X; @@ -203,28 +54,11 @@ private static int iow(int type, int nr, int size) { public static final int SDL_CONTROLLER_BUTTON_DPAD_LEFT = 13; public static final int SDL_CONTROLLER_BUTTON_DPAD_RIGHT = 14; - private int fd; - - public interface LibC extends Library { - LibC FN = (LibC) Native.load("c", LibC.class); - - int open(String pathname, int flags); - int ioctl(int fd, long request, Object... args); - long write(int fd, Pointer buf, long count); - int close(int fd); - } - - public static void loadNativeLibraries() { - GameController.LibC.FN.write(1, null, 0); - } - public GameController() { - fd = LibC.FN.open("/dev/uinput", O_WRONLY | O_NONBLOCK); - if (fd == -1) { - throw new RuntimeException("Couldn't open uinput device."); - } + setup(); + } - LibC.FN.ioctl(fd, UI_SET_EVBIT, EV_KEY); + protected void setupKeys() { addKey(XBOX_BTN_A); addKey(XBOX_BTN_B); addKey(XBOX_BTN_X); @@ -236,8 +70,13 @@ public GameController() { addKey(XBOX_BTN_GUIDE); addKey(XBOX_BTN_LS); addKey(XBOX_BTN_RS); + } + + protected boolean hasKeys() { + return true; + } - LibC.FN.ioctl(fd, UI_SET_EVBIT, EV_ABS); + protected void setupAbs() { addAbs(XBOX_ABS_LSX, -32768, 32767, 16, 128); addAbs(XBOX_ABS_LSY, -32768, 32767, 16, 128); addAbs(XBOX_ABS_RSX, -32768, 32767, 16, 128); @@ -248,87 +87,22 @@ public GameController() { // but allow higher precision (eg. Xbox One controller) addAbs(XBOX_ABS_LT, 0, 32767, 0, 0); addAbs(XBOX_ABS_RT, 0, 32767, 0, 0); - - UinputSetup usetup = new UinputSetup(); - usetup.id.bustype = BUS_USB; - usetup.id.vendor = 0x045e; - usetup.id.product = 0x028e; - byte[] name = "Microsoft X-Box 360 pad".getBytes(); - System.arraycopy(name, 0, usetup.name, 0, name.length); - - if (LibC.FN.ioctl(fd, UI_DEV_SETUP, usetup) == -1) { - close(); - throw new RuntimeException("Couldn't setup uinput device."); - } - - if (LibC.FN.ioctl(fd, UI_DEV_CREATE) == -1) { - close(); - throw new RuntimeException("Couldn't create uinput device."); - } - } - - public void close() { - if (fd != -1) { - LibC.FN.ioctl(fd, UI_DEV_DESTROY); - LibC.FN.close(fd); - fd = -1; - } } - private void addKey(int key) { - if (LibC.FN.ioctl(fd, UI_SET_KEYBIT, key) == -1) { - Ln.e("Could not add key event."); - } + protected boolean hasAbs() { + return true; } - private void addAbs(short code, int minimum, int maximum, int fuzz, int flat) { - if (LibC.FN.ioctl(fd, UI_SET_ABSBIT, code) == -1) { - Ln.e("Could not add absolute event."); - } - - UinputAbsSetup absSetup = new UinputAbsSetup(); - - absSetup.code = code; - absSetup.absinfo.minimum = minimum; - absSetup.absinfo.maximum = maximum; - absSetup.absinfo.fuzz = fuzz; - absSetup.absinfo.flat = flat; - - if (LibC.FN.ioctl(fd, UI_ABS_SETUP, absSetup) == -1) { - Ln.e("Could not set absolute event info."); - } + protected short getVendor() { + return 0x045e; } - private static void emit32(int fd, short type, short code, int val) { - InputEvent32 ie = new InputEvent32(); - - ie.type = type; - ie.code = code; - ie.value = val; - - ie.write(); - - LibC.FN.write(fd, ie.getPointer(), ie.size()); + protected short getProduct() { + return 0x028e; } - private static void emit64(int fd, short type, short code, int val) { - InputEvent64 ie = new InputEvent64(); - - ie.type = type; - ie.code = code; - ie.value = val; - - ie.write(); - - LibC.FN.write(fd, ie.getPointer(), ie.size()); - } - - private static void emit(int fd, short type, short code, int val) { - if (Platform.is64Bit()) { - emit64(fd, type, code, val); - } else { - emit32(fd, type, code, val); - } + protected String getName() { + return "Microsoft X-Box 360 pad"; } private static short translateAxis(int axis) { @@ -397,8 +171,8 @@ private static short translateButton(int button) { } public void setAxis(int axis, int value) { - emit(fd, EV_ABS, translateAxis(axis), value); - emit(fd, EV_SYN, SYN_REPORT, 0); + emitAbs(translateAxis(axis), value); + emitReport(); } public void setButton(int button, int state) { @@ -406,24 +180,24 @@ public void setButton(int button, int state) { switch (button) { case SDL_CONTROLLER_BUTTON_DPAD_UP: - emit(fd, EV_ABS, XBOX_ABS_DPADY, state != 0 ? -1 : 0); + emitAbs(XBOX_ABS_DPADY, state != 0 ? -1 : 0); break; case SDL_CONTROLLER_BUTTON_DPAD_DOWN: - emit(fd, EV_ABS, XBOX_ABS_DPADY, state != 0 ? 1 : 0); + emitAbs(XBOX_ABS_DPADY, state != 0 ? 1 : 0); break; case SDL_CONTROLLER_BUTTON_DPAD_LEFT: - emit(fd, EV_ABS, XBOX_ABS_DPADX, state != 0 ? -1 : 0); + emitAbs(XBOX_ABS_DPADX, state != 0 ? -1 : 0); break; case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: - emit(fd, EV_ABS, XBOX_ABS_DPADX, state != 0 ? 1 : 0); + emitAbs(XBOX_ABS_DPADX, state != 0 ? 1 : 0); break; default: - emit(fd, EV_KEY, translateButton(button), state); + emitKey(translateButton(button), state); } - emit(fd, EV_SYN, SYN_REPORT, 0); + emitReport(); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/UinputDevice.java b/server/src/main/java/com/genymobile/scrcpy/UinputDevice.java new file mode 100644 index 0000000000..9cb8498f1e --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/UinputDevice.java @@ -0,0 +1,296 @@ +package com.genymobile.scrcpy; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.Platform; +import com.sun.jna.Pointer; +import com.sun.jna.Structure; + +import java.util.Arrays; +import java.util.List; + +public abstract class UinputDevice { + public static final int DEVICE_ADDED = 0; + public static final int DEVICE_REMOVED = 1; + + private static final int UINPUT_MAX_NAME_SIZE = 80; + + @SuppressWarnings("checkstyle:VisibilityModifier") + public static class InputId extends Structure { + public short bustype = 0; + public short vendor = 0; + public short product = 0; + public short version = 0; + + @Override + protected List getFieldOrder() { + return Arrays.asList("bustype", "vendor", "product", "version"); + } + } + + @SuppressWarnings("checkstyle:VisibilityModifier") + public static class UinputSetup extends Structure { + public InputId id; + public byte[] name = new byte[UINPUT_MAX_NAME_SIZE]; + public int ffEffectsMax = 0; + + @Override + protected List getFieldOrder() { + return Arrays.asList("id", "name", "ffEffectsMax"); + } + } + + @SuppressWarnings("checkstyle:VisibilityModifier") + public static class InputAbsinfo extends Structure { + public int value = 0; + public int minimum = 0; + public int maximum = 0; + public int fuzz = 0; + public int flat = 0; + public int resolution = 0; + + @Override + protected List getFieldOrder() { + return Arrays.asList("value", "minimum", "maximum", "fuzz", "flat", "resolution"); + } + }; + + @SuppressWarnings("checkstyle:VisibilityModifier") + public static class UinputAbsSetup extends Structure { + public short code; + public InputAbsinfo absinfo; + + @Override + protected List getFieldOrder() { + return Arrays.asList("code", "absinfo"); + } + }; + + @SuppressWarnings("checkstyle:VisibilityModifier") + public static class InputEvent32 extends Structure { + public long time = 0; + public short type = 0; + public short code = 0; + public int value = 0; + + @Override + protected List getFieldOrder() { + return Arrays.asList("time", "type", "code", "value"); + } + } + + @SuppressWarnings("checkstyle:VisibilityModifier") + public static class InputEvent64 extends Structure { + public long sec = 0; + public long usec = 0; + public short type = 0; + public short code = 0; + public int value = 0; + + @Override + protected List getFieldOrder() { + return Arrays.asList("sec", "usec", "type", "code", "value"); + } + } + + private static final int IOC_NONE = 0; + private static final int IOC_WRITE = 1; + + private static final int IOC_DIRSHIFT = 30; + private static final int IOC_TYPESHIFT = 8; + private static final int IOC_NRSHIFT = 0; + private static final int IOC_SIZESHIFT = 16; + + private static int ioc(int dir, int type, int nr, int size) { + return (dir << IOC_DIRSHIFT) + | (type << IOC_TYPESHIFT) + | (nr << IOC_NRSHIFT) + | (size << IOC_SIZESHIFT); + } + + private static int io(int type, int nr, int size) { + return ioc(IOC_NONE, type, nr, size); + } + + private static int iow(int type, int nr, int size) { + return ioc(IOC_WRITE, type, nr, size); + } + + private static final int O_WRONLY = 01; + private static final int O_NONBLOCK = 04000; + + private static final int BUS_USB = 0x03; + + private static final int UINPUT_IOCTL_BASE = 'U'; + + private static final int UI_SET_EVBIT = iow(UINPUT_IOCTL_BASE, 100, 4); + private static final int UI_SET_KEYBIT = iow(UINPUT_IOCTL_BASE, 101, 4); + private static final int UI_SET_ABSBIT = iow(UINPUT_IOCTL_BASE, 103, 4); + private static final int UI_ABS_SETUP = iow(UINPUT_IOCTL_BASE, 4, new UinputAbsSetup().size()); + + private static final int UI_DEV_SETUP = iow(UINPUT_IOCTL_BASE, 3, new UinputSetup().size()); + private static final int UI_DEV_CREATE = io(UINPUT_IOCTL_BASE, 1, 0); + private static final int UI_DEV_DESTROY = io(UINPUT_IOCTL_BASE, 2, 0); + + private static final short EV_SYN = 0x00; + private static final short EV_KEY = 0x01; + private static final short EV_ABS = 0x03; + + private static final short SYN_REPORT = 0x00; + + protected static final short BTN_A = 0x130; + protected static final short BTN_B = 0x131; + protected static final short BTN_X = 0x133; + protected static final short BTN_Y = 0x134; + protected static final short BTN_TL = 0x136; + protected static final short BTN_TR = 0x137; + protected static final short BTN_SELECT = 0x13a; + protected static final short BTN_START = 0x13b; + protected static final short BTN_MODE = 0x13c; + protected static final short BTN_THUMBL = 0x13d; + protected static final short BTN_THUMBR = 0x13e; + + protected static final short ABS_X = 0x00; + protected static final short ABS_Y = 0x01; + protected static final short ABS_Z = 0x02; + protected static final short ABS_RX = 0x03; + protected static final short ABS_RY = 0x04; + protected static final short ABS_RZ = 0x05; + protected static final short ABS_HAT0X = 0x10; + protected static final short ABS_HAT0Y = 0x11; + + private int fd = -1; + + public interface LibC extends Library { + LibC FN = (LibC) Native.load("c", LibC.class); + + int open(String pathname, int flags); + int ioctl(int fd, long request, Object... args); + long write(int fd, Pointer buf, long count); + int close(int fd); + } + + public static void loadNativeLibraries() { + UinputDevice.LibC.FN.write(1, null, 0); + } + + protected void setup() { + fd = LibC.FN.open("/dev/uinput", O_WRONLY | O_NONBLOCK); + if (fd == -1) { + throw new RuntimeException("Couldn't open uinput device."); + } + + if (hasKeys()) + { + LibC.FN.ioctl(fd, UI_SET_EVBIT, EV_KEY); + setupKeys(); + } + + if (hasAbs()) + { + LibC.FN.ioctl(fd, UI_SET_EVBIT, EV_ABS); + setupAbs(); + } + + UinputSetup usetup = new UinputSetup(); + usetup.id.bustype = BUS_USB; + usetup.id.vendor = getVendor(); + usetup.id.product = getProduct(); + byte[] name = getName().getBytes(); + System.arraycopy(name, 0, usetup.name, 0, name.length); + + if (LibC.FN.ioctl(fd, UI_DEV_SETUP, usetup) == -1) { + close(); + throw new RuntimeException("Couldn't setup uinput device."); + } + + if (LibC.FN.ioctl(fd, UI_DEV_CREATE) == -1) { + close(); + throw new RuntimeException("Couldn't create uinput device."); + } + } + + public void close() { + if (fd != -1) { + LibC.FN.ioctl(fd, UI_DEV_DESTROY); + LibC.FN.close(fd); + fd = -1; + } + } + + protected abstract void setupKeys(); + protected abstract boolean hasKeys(); + protected abstract void setupAbs(); + protected abstract boolean hasAbs(); + protected abstract short getVendor(); + protected abstract short getProduct(); + protected abstract String getName(); + + protected void addKey(int key) { + if (LibC.FN.ioctl(fd, UI_SET_KEYBIT, key) == -1) { + Ln.e("Could not add key event."); + } + } + + protected void addAbs(short code, int minimum, int maximum, int fuzz, int flat) { + if (LibC.FN.ioctl(fd, UI_SET_ABSBIT, code) == -1) { + Ln.e("Could not add absolute event."); + } + + UinputAbsSetup absSetup = new UinputAbsSetup(); + + absSetup.code = code; + absSetup.absinfo.minimum = minimum; + absSetup.absinfo.maximum = maximum; + absSetup.absinfo.fuzz = fuzz; + absSetup.absinfo.flat = flat; + + if (LibC.FN.ioctl(fd, UI_ABS_SETUP, absSetup) == -1) { + Ln.e("Could not set absolute event info."); + } + } + + private static void emit32(int fd, short type, short code, int val) { + InputEvent32 ie = new InputEvent32(); + + ie.type = type; + ie.code = code; + ie.value = val; + + ie.write(); + + LibC.FN.write(fd, ie.getPointer(), ie.size()); + } + + private static void emit64(int fd, short type, short code, int val) { + InputEvent64 ie = new InputEvent64(); + + ie.type = type; + ie.code = code; + ie.value = val; + + ie.write(); + + LibC.FN.write(fd, ie.getPointer(), ie.size()); + } + + private static void emit(int fd, short type, short code, int val) { + if (Platform.is64Bit()) { + emit64(fd, type, code, val); + } else { + emit32(fd, type, code, val); + } + } + + protected void emitAbs(short abs, int value) { + emit(fd, EV_ABS, abs, value); + } + + protected void emitKey(short key, int state) { + emit(fd, EV_KEY, key, state); + } + + protected void emitReport() { + emit(fd, EV_SYN, SYN_REPORT, 0); + } +} From dda73d6648f11cd2c4945274b888be2cf155d2ed Mon Sep 17 00:00:00 2001 From: Luiz Henrique Laurini Date: Wed, 31 Mar 2021 20:32:00 -0300 Subject: [PATCH 12/22] Cleanup initialization --- .../com/genymobile/scrcpy/UinputDevice.java | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/UinputDevice.java b/server/src/main/java/com/genymobile/scrcpy/UinputDevice.java index 9cb8498f1e..33c40b8a12 100644 --- a/server/src/main/java/com/genymobile/scrcpy/UinputDevice.java +++ b/server/src/main/java/com/genymobile/scrcpy/UinputDevice.java @@ -162,33 +162,34 @@ private static int iow(int type, int nr, int size) { private int fd = -1; public interface LibC extends Library { - LibC FN = (LibC) Native.load("c", LibC.class); - int open(String pathname, int flags); int ioctl(int fd, long request, Object... args); long write(int fd, Pointer buf, long count); int close(int fd); } + private static LibC libC; + + /// Must be the first method called public static void loadNativeLibraries() { - UinputDevice.LibC.FN.write(1, null, 0); + libC = (LibC) Native.load("c", LibC.class); } protected void setup() { - fd = LibC.FN.open("/dev/uinput", O_WRONLY | O_NONBLOCK); + fd = libC.open("/dev/uinput", O_WRONLY | O_NONBLOCK); if (fd == -1) { throw new RuntimeException("Couldn't open uinput device."); } if (hasKeys()) { - LibC.FN.ioctl(fd, UI_SET_EVBIT, EV_KEY); + libC.ioctl(fd, UI_SET_EVBIT, EV_KEY); setupKeys(); } if (hasAbs()) { - LibC.FN.ioctl(fd, UI_SET_EVBIT, EV_ABS); + libC.ioctl(fd, UI_SET_EVBIT, EV_ABS); setupAbs(); } @@ -199,12 +200,12 @@ protected void setup() { byte[] name = getName().getBytes(); System.arraycopy(name, 0, usetup.name, 0, name.length); - if (LibC.FN.ioctl(fd, UI_DEV_SETUP, usetup) == -1) { + if (libC.ioctl(fd, UI_DEV_SETUP, usetup) == -1) { close(); throw new RuntimeException("Couldn't setup uinput device."); } - if (LibC.FN.ioctl(fd, UI_DEV_CREATE) == -1) { + if (libC.ioctl(fd, UI_DEV_CREATE) == -1) { close(); throw new RuntimeException("Couldn't create uinput device."); } @@ -212,8 +213,8 @@ protected void setup() { public void close() { if (fd != -1) { - LibC.FN.ioctl(fd, UI_DEV_DESTROY); - LibC.FN.close(fd); + libC.ioctl(fd, UI_DEV_DESTROY); + libC.close(fd); fd = -1; } } @@ -227,13 +228,13 @@ public void close() { protected abstract String getName(); protected void addKey(int key) { - if (LibC.FN.ioctl(fd, UI_SET_KEYBIT, key) == -1) { + if (libC.ioctl(fd, UI_SET_KEYBIT, key) == -1) { Ln.e("Could not add key event."); } } protected void addAbs(short code, int minimum, int maximum, int fuzz, int flat) { - if (LibC.FN.ioctl(fd, UI_SET_ABSBIT, code) == -1) { + if (libC.ioctl(fd, UI_SET_ABSBIT, code) == -1) { Ln.e("Could not add absolute event."); } @@ -245,7 +246,7 @@ protected void addAbs(short code, int minimum, int maximum, int fuzz, int flat) absSetup.absinfo.fuzz = fuzz; absSetup.absinfo.flat = flat; - if (LibC.FN.ioctl(fd, UI_ABS_SETUP, absSetup) == -1) { + if (libC.ioctl(fd, UI_ABS_SETUP, absSetup) == -1) { Ln.e("Could not set absolute event info."); } } @@ -259,7 +260,7 @@ private static void emit32(int fd, short type, short code, int val) { ie.write(); - LibC.FN.write(fd, ie.getPointer(), ie.size()); + libC.write(fd, ie.getPointer(), ie.size()); } private static void emit64(int fd, short type, short code, int val) { @@ -271,7 +272,7 @@ private static void emit64(int fd, short type, short code, int val) { ie.write(); - LibC.FN.write(fd, ie.getPointer(), ie.size()); + libC.write(fd, ie.getPointer(), ie.size()); } private static void emit(int fd, short type, short code, int val) { From b8c81bb5fdec77cfb1a0cc04fd9e1afa9e5b3f84 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Tue, 7 Feb 2023 14:54:27 +0800 Subject: [PATCH 13/22] Add --no-game-controller option --- app/src/cli.c | 9 +++ app/src/input_manager.c | 164 ++++++++++++++++++++++++---------------- app/src/input_manager.h | 1 + app/src/options.c | 1 + app/src/options.h | 1 + 5 files changed, 111 insertions(+), 65 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 27b0ddef4c..a4eaf89b87 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -58,6 +58,7 @@ #define OPT_PRINT_FPS 1038 #define OPT_NO_POWER_ON 1039 #define OPT_CODEC 1040 +#define OPT_NO_GAME_CONTROLLER 1041 struct sc_option { char shortopt; @@ -298,6 +299,11 @@ static const struct sc_option options[] = { .text = "Do not display device (only when screen recording or V4L2 " "sink is enabled).", }, + { + .longopt_id = OPT_NO_GAME_CONTROLLER, + .longopt = "no-game-controller", + .text = "Disable game controller support.", + }, { .longopt_id = OPT_NO_KEY_REPEAT, .longopt = "no-key-repeat", @@ -1582,6 +1588,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_NO_MIPMAPS: opts->mipmaps = false; break; + case OPT_NO_GAME_CONTROLLER: + opts->forward_game_controllers = false; + break; case OPT_NO_KEY_REPEAT: opts->forward_key_repeat = false; break; diff --git a/app/src/input_manager.c b/app/src/input_manager.c index e99c4de0b6..5cdce72eab 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -62,6 +62,7 @@ sc_input_manager_init(struct sc_input_manager *im, im->kp = params->kp; im->mp = params->mp; + im->forward_game_controllers = options->forward_game_controllers; im->forward_all_clicks = params->forward_all_clicks; im->legacy_paste = params->legacy_paste; im->clipboard_autosync = params->clipboard_autosync; @@ -797,6 +798,98 @@ sc_input_manager_process_file(struct sc_input_manager *im, } } +void +input_manager_process_controller_axis(struct input_manager *im, + const SDL_ControllerAxisEvent *event) { + struct control_msg msg; + msg.type = CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_AXIS; + msg.inject_game_controller_axis.id = event->which; + msg.inject_game_controller_axis.axis = event->axis; + msg.inject_game_controller_axis.value = event->value; + controller_push_msg(im->controller, &msg); +} + +void +input_manager_process_controller_button(struct input_manager *im, + const SDL_ControllerButtonEvent *event) { + struct control_msg msg; + msg.type = CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_BUTTON; + msg.inject_game_controller_button.id = event->which; + msg.inject_game_controller_button.button = event->button; + msg.inject_game_controller_button.state = event->state; + controller_push_msg(im->controller, &msg); +} + +static SDL_GameController ** +find_free_game_controller_slot(struct input_manager *im) { + for (unsigned i = 0; i < MAX_GAME_CONTROLLERS; ++i) { + if (!im->game_controllers[i]) { + return &im->game_controllers[i]; + } + } + + return NULL; +} + +void +input_manager_process_controller_device(struct input_manager *im, + const SDL_ControllerDeviceEvent *event) { + SDL_JoystickID id; + + switch (event->type) { + case SDL_CONTROLLERDEVICEADDED: { + SDL_GameController **freeGc = find_free_game_controller_slot(im); + + if (!freeGc) { + LOGW("Controller limit reached."); + return; + } + + SDL_GameController *game_controller; + game_controller = SDL_GameControllerOpen(event->which); + + if (game_controller) { + *freeGc = game_controller; + + SDL_Joystick *joystick; + joystick = SDL_GameControllerGetJoystick(game_controller); + + id = SDL_JoystickInstanceID(joystick); + } else { + LOGW("Could not open game controller #%d", event->which); + return; + } + break; + } + + case SDL_CONTROLLERDEVICEREMOVED: { + id = event->which; + + SDL_GameController *game_controller; + game_controller = SDL_GameControllerFromInstanceID(id); + + SDL_GameControllerClose(game_controller); + + if (!free_game_controller_slot(im, game_controller)) { + LOGW("Could not find removed game controller."); + return; + } + + break; + } + + default: + return; + } + + struct control_msg msg; + msg.type = SC_CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_DEVICE; + msg.inject_game_controller_device.id = id; + msg.inject_game_controller_device.event = event->type; + msg.inject_game_controller_device.event -= SDL_CONTROLLERDEVICEADDED; + controller_push_msg(im->controller, &msg); +} + void sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event) { bool control = im->controller; @@ -846,84 +939,25 @@ sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event) { sc_input_manager_process_file(im, &event->drop); } case SDL_CONTROLLERAXISMOTION: - if (!control) { + if (!control || !im->forward_game_controllers) { break; } - input_manager_process_controller_axis(&input_manager, &event->caxis); + input_manager_process_controller_axis(im, &event->caxis); break; case SDL_CONTROLLERBUTTONDOWN: case SDL_CONTROLLERBUTTONUP: - if (!control) { + if (!control || !im->forward_game_controllers) { break; } - input_manager_process_controller_button(&input_manager, &event->cbutton); + input_manager_process_controller_button(im, &event->cbutton); break; case SDL_CONTROLLERDEVICEADDED: // case SDL_CONTROLLERDEVICEREMAPPED: case SDL_CONTROLLERDEVICEREMOVED: - if (!control) { + if (!control || !im->forward_game_controllers) { break; } - input_manager_process_controller_device(&input_manager, &event->cdevice); - break; - } -} - -void -input_manager_process_controller_device(struct input_manager *im, - const SDL_ControllerDeviceEvent *event) { - SDL_JoystickID id; - - switch (event->type) { - case SDL_CONTROLLERDEVICEADDED: { - SDL_GameController **freeGc = find_free_game_controller_slot(im); - - if (!freeGc) { - LOGW("Controller limit reached."); - return; - } - - SDL_GameController *game_controller; - game_controller = SDL_GameControllerOpen(event->which); - - if (game_controller) { - *freeGc = game_controller; - - SDL_Joystick *joystick; - joystick = SDL_GameControllerGetJoystick(game_controller); - - id = SDL_JoystickInstanceID(joystick); - } else { - LOGW("Could not open game controller #%d", event->which); - return; - } - break; - } - - case SDL_CONTROLLERDEVICEREMOVED: { - id = event->which; - - SDL_GameController *game_controller; - game_controller = SDL_GameControllerFromInstanceID(id); - - SDL_GameControllerClose(game_controller); - - if (!free_game_controller_slot(im, game_controller)) { - LOGW("Could not find removed game controller."); - return; - } - + input_manager_process_controller_device(im, &event->cdevice); break; - } - - default: - return; } - - struct control_msg msg; - msg.type = SC_CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_DEVICE; - msg.inject_game_controller_device.id = id; - msg.inject_game_controller_device.event = event->type; - msg.inject_game_controller_device.event -= SDL_CONTROLLERDEVICEADDED; - controller_push_msg(im->controller, &msg); } diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 9233292bad..9e836f03f0 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -25,6 +25,7 @@ struct sc_input_manager { struct sc_key_processor *kp; struct sc_mouse_processor *mp; + bool forward_game_controllers; bool forward_all_clicks; bool legacy_paste; bool clipboard_autosync; diff --git a/app/src/options.c b/app/src/options.c index 525795ae1d..37a4109d4e 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -53,6 +53,7 @@ const struct scrcpy_options scrcpy_options_default = { .stay_awake = false, .force_adb_forward = false, .disable_screensaver = false, + .forward_game_controllers = true, .forward_key_repeat = true, .forward_all_clicks = false, .legacy_paste = false, diff --git a/app/src/options.h b/app/src/options.h index b9d237e0b2..b9e51eaaca 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -134,6 +134,7 @@ struct scrcpy_options { bool stay_awake; bool force_adb_forward; bool disable_screensaver; + bool forward_game_controllers; bool forward_key_repeat; bool forward_all_clicks; bool legacy_paste; From 55ca08ff735e1538712f4982e27d1d966906de37 Mon Sep 17 00:00:00 2001 From: Luiz Henrique Laurini Date: Fri, 30 Apr 2021 20:21:07 -0300 Subject: [PATCH 14/22] Use jna.boot.library.path instd of LD_LIBRARY_PATH --- app/src/server.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/server.c b/app/src/server.c index afa9375a07..3663b28b90 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -186,8 +186,8 @@ execute_server(struct sc_server *server, cmd[count++] = serial; cmd[count++] = "shell"; cmd[count++] = "CLASSPATH=" SC_DEVICE_SERVER_PATH; - cmd[count++] = "LD_LIBRARY_PATH=" SC_DEVICE_SERVER_DIR; cmd[count++] = "app_process"; + cmd[count++] = "-Djna.boot.library.path=/data/local/tmp"; #ifdef SERVER_DEBUGGER # define SERVER_DEBUGGER_PORT "5005" From ecf206665ec75ca052e8a4d74a643f53f69e5425 Mon Sep 17 00:00:00 2001 From: Luiz Henrique Laurini Date: Fri, 30 Apr 2021 20:30:23 -0300 Subject: [PATCH 15/22] Fix no input working if uinput is not supported --- server/src/main/java/com/genymobile/scrcpy/Controller.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 6aab79d71f..669f0ed008 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -215,7 +215,12 @@ private void handleEvent() throws IOException { switch (event) { case GameController.DEVICE_ADDED: - gameControllers.append(id, new GameController()); + try { + gameControllers.append(id, new GameController()); + } catch (Exception e) { + Ln.e("It seems your phone doesn't support this feature without root. Game controllers will be disabled.", e); + gameControllersEnabled = false; + } break; case GameController.DEVICE_REMOVED: From 80887ed01cc4039d0eacbbe4d970a4b39d39ea2a Mon Sep 17 00:00:00 2001 From: Luiz Henrique Laurini Date: Sat, 1 May 2021 10:58:32 -0300 Subject: [PATCH 16/22] Fix style violations --- .../main/java/com/genymobile/scrcpy/GameController.java | 9 --------- .../main/java/com/genymobile/scrcpy/UinputDevice.java | 6 ++---- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/GameController.java b/server/src/main/java/com/genymobile/scrcpy/GameController.java index 2725c49bab..9000b879d7 100644 --- a/server/src/main/java/com/genymobile/scrcpy/GameController.java +++ b/server/src/main/java/com/genymobile/scrcpy/GameController.java @@ -1,14 +1,5 @@ package com.genymobile.scrcpy; -import com.sun.jna.Library; -import com.sun.jna.Native; -import com.sun.jna.Platform; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; - -import java.util.Arrays; -import java.util.List; - public final class GameController extends UinputDevice { private static final short XBOX_BTN_A = BTN_A; private static final short XBOX_BTN_B = BTN_B; diff --git a/server/src/main/java/com/genymobile/scrcpy/UinputDevice.java b/server/src/main/java/com/genymobile/scrcpy/UinputDevice.java index 33c40b8a12..01ca6866e3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/UinputDevice.java +++ b/server/src/main/java/com/genymobile/scrcpy/UinputDevice.java @@ -181,14 +181,12 @@ protected void setup() { throw new RuntimeException("Couldn't open uinput device."); } - if (hasKeys()) - { + if (hasKeys()) { libC.ioctl(fd, UI_SET_EVBIT, EV_KEY); setupKeys(); } - if (hasAbs()) - { + if (hasAbs()) { libC.ioctl(fd, UI_SET_EVBIT, EV_ABS); setupAbs(); } From abedd0b478d1a6c90faba3705adfb65c4a400361 Mon Sep 17 00:00:00 2001 From: Luiz Henrique Laurini Date: Tue, 4 May 2021 19:16:52 -0300 Subject: [PATCH 17/22] Use LastErrorException for native error handling --- .../com/genymobile/scrcpy/Controller.java | 2 +- .../com/genymobile/scrcpy/UinputDevice.java | 82 +++++++++++++------ .../scrcpy/UinputUnsupportedException.java | 7 ++ 3 files changed, 65 insertions(+), 26 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/UinputUnsupportedException.java diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 669f0ed008..bf1abba590 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -218,7 +218,7 @@ private void handleEvent() throws IOException { try { gameControllers.append(id, new GameController()); } catch (Exception e) { - Ln.e("It seems your phone doesn't support this feature without root. Game controllers will be disabled.", e); + Ln.e("Failed to add new game controller. Game controllers will be disabled.", e); gameControllersEnabled = false; } break; diff --git a/server/src/main/java/com/genymobile/scrcpy/UinputDevice.java b/server/src/main/java/com/genymobile/scrcpy/UinputDevice.java index 01ca6866e3..46fcbb62fb 100644 --- a/server/src/main/java/com/genymobile/scrcpy/UinputDevice.java +++ b/server/src/main/java/com/genymobile/scrcpy/UinputDevice.java @@ -1,5 +1,6 @@ package com.genymobile.scrcpy; +import com.sun.jna.LastErrorException; import com.sun.jna.Library; import com.sun.jna.Native; import com.sun.jna.Platform; @@ -162,10 +163,10 @@ private static int iow(int type, int nr, int size) { private int fd = -1; public interface LibC extends Library { - int open(String pathname, int flags); - int ioctl(int fd, long request, Object... args); - long write(int fd, Pointer buf, long count); - int close(int fd); + int open(String pathname, int flags) throws LastErrorException; + int ioctl(int fd, long request, Object... args) throws LastErrorException; + long write(int fd, Pointer buf, long count) throws LastErrorException; + int close(int fd) throws LastErrorException; } private static LibC libC; @@ -176,18 +177,27 @@ public static void loadNativeLibraries() { } protected void setup() { - fd = libC.open("/dev/uinput", O_WRONLY | O_NONBLOCK); - if (fd == -1) { - throw new RuntimeException("Couldn't open uinput device."); + try { + fd = libC.open("/dev/uinput", O_WRONLY | O_NONBLOCK); + } catch (LastErrorException e) { + throw new UinputUnsupportedException(e); } if (hasKeys()) { - libC.ioctl(fd, UI_SET_EVBIT, EV_KEY); + try { + libC.ioctl(fd, UI_SET_EVBIT, EV_KEY); + } catch (LastErrorException e) { + throw new RuntimeException("Could not enable key events.", e); + } setupKeys(); } if (hasAbs()) { - libC.ioctl(fd, UI_SET_EVBIT, EV_ABS); + try { + libC.ioctl(fd, UI_SET_EVBIT, EV_ABS); + } catch (LastErrorException e) { + throw new RuntimeException("Could not enable absolute events.", e); + } setupAbs(); } @@ -198,21 +208,33 @@ protected void setup() { byte[] name = getName().getBytes(); System.arraycopy(name, 0, usetup.name, 0, name.length); - if (libC.ioctl(fd, UI_DEV_SETUP, usetup) == -1) { + try { + libC.ioctl(fd, UI_DEV_SETUP, usetup); + } catch (LastErrorException e) { close(); - throw new RuntimeException("Couldn't setup uinput device."); + throw new RuntimeException("Couldn't setup uinput device.", e); } - if (libC.ioctl(fd, UI_DEV_CREATE) == -1) { + try { + libC.ioctl(fd, UI_DEV_CREATE); + } catch (LastErrorException e) { close(); - throw new RuntimeException("Couldn't create uinput device."); + throw new RuntimeException("Couldn't create uinput device.", e); } } public void close() { if (fd != -1) { - libC.ioctl(fd, UI_DEV_DESTROY); - libC.close(fd); + try { + libC.ioctl(fd, UI_DEV_DESTROY); + } catch (LastErrorException e) { + Ln.e("Could not destroy uinput device.", e); + } + try { + libC.close(fd); + } catch (LastErrorException e) { + Ln.e("Could not close uinput device.", e); + } fd = -1; } } @@ -226,14 +248,18 @@ public void close() { protected abstract String getName(); protected void addKey(int key) { - if (libC.ioctl(fd, UI_SET_KEYBIT, key) == -1) { - Ln.e("Could not add key event."); + try { + libC.ioctl(fd, UI_SET_KEYBIT, key); + } catch (LastErrorException e) { + Ln.e("Could not add key event.", e); } } protected void addAbs(short code, int minimum, int maximum, int fuzz, int flat) { - if (libC.ioctl(fd, UI_SET_ABSBIT, code) == -1) { - Ln.e("Could not add absolute event."); + try { + libC.ioctl(fd, UI_SET_ABSBIT, code); + } catch (LastErrorException e) { + Ln.e("Could not add absolute event.", e); } UinputAbsSetup absSetup = new UinputAbsSetup(); @@ -244,8 +270,10 @@ protected void addAbs(short code, int minimum, int maximum, int fuzz, int flat) absSetup.absinfo.fuzz = fuzz; absSetup.absinfo.flat = flat; - if (libC.ioctl(fd, UI_ABS_SETUP, absSetup) == -1) { - Ln.e("Could not set absolute event info."); + try { + libC.ioctl(fd, UI_ABS_SETUP, absSetup); + } catch (LastErrorException e) { + Ln.e("Could not set absolute event info.", e); } } @@ -274,10 +302,14 @@ private static void emit64(int fd, short type, short code, int val) { } private static void emit(int fd, short type, short code, int val) { - if (Platform.is64Bit()) { - emit64(fd, type, code, val); - } else { - emit32(fd, type, code, val); + try { + if (Platform.is64Bit()) { + emit64(fd, type, code, val); + } else { + emit32(fd, type, code, val); + } + } catch (LastErrorException e) { + Ln.e("Could not emit event.", e); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/UinputUnsupportedException.java b/server/src/main/java/com/genymobile/scrcpy/UinputUnsupportedException.java new file mode 100644 index 0000000000..13c6b84991 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/UinputUnsupportedException.java @@ -0,0 +1,7 @@ +package com.genymobile.scrcpy; + +public class UinputUnsupportedException extends RuntimeException { + public UinputUnsupportedException(Exception e) { + super("device does not support uinput without root", e); + } +} From ae83fe32141e1f1bcbf9738df182b20091040f2f Mon Sep 17 00:00:00 2001 From: Luiz Henrique Laurini Date: Sat, 8 May 2021 14:13:22 -0300 Subject: [PATCH 18/22] Add support for the legacy uinput interface --- .../com/genymobile/scrcpy/UinputDevice.java | 136 ++++++++++++++---- 1 file changed, 110 insertions(+), 26 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/UinputDevice.java b/server/src/main/java/com/genymobile/scrcpy/UinputDevice.java index 46fcbb62fb..ac29abdef5 100644 --- a/server/src/main/java/com/genymobile/scrcpy/UinputDevice.java +++ b/server/src/main/java/com/genymobile/scrcpy/UinputDevice.java @@ -6,6 +6,7 @@ import com.sun.jna.Platform; import com.sun.jna.Pointer; import com.sun.jna.Structure; +import com.sun.jna.ptr.IntByReference; import java.util.Arrays; import java.util.List; @@ -15,6 +16,8 @@ public abstract class UinputDevice { public static final int DEVICE_REMOVED = 1; private static final int UINPUT_MAX_NAME_SIZE = 80; + private static final int ABS_MAX = 0x3f; + private static final int ABS_CNT = ABS_MAX + 1; @SuppressWarnings("checkstyle:VisibilityModifier") public static class InputId extends Structure { @@ -41,6 +44,22 @@ protected List getFieldOrder() { } } + @SuppressWarnings("checkstyle:VisibilityModifier") + public static class UinputUserDev extends Structure { + public byte[] name = new byte[UINPUT_MAX_NAME_SIZE]; + public InputId id; + public int ffEffectsMax = 0; + public int[] absmax = new int[ABS_CNT]; + public int[] absmin = new int[ABS_CNT]; + public int[] absfuzz = new int[ABS_CNT]; + public int[] absflat = new int[ABS_CNT]; + + @Override + protected List getFieldOrder() { + return Arrays.asList("name", "id", "ffEffectsMax", "absmax", "absmin", "absfuzz", "absflat"); + } + }; + @SuppressWarnings("checkstyle:VisibilityModifier") public static class InputAbsinfo extends Structure { public int value = 0; @@ -96,6 +115,7 @@ protected List getFieldOrder() { private static final int IOC_NONE = 0; private static final int IOC_WRITE = 1; + private static final int IOC_READ = 2; private static final int IOC_DIRSHIFT = 30; private static final int IOC_TYPESHIFT = 8; @@ -113,6 +133,10 @@ private static int io(int type, int nr, int size) { return ioc(IOC_NONE, type, nr, size); } + private static int ior(int type, int nr, int size) { + return ioc(IOC_READ, type, nr, size); + } + private static int iow(int type, int nr, int size) { return ioc(IOC_WRITE, type, nr, size); } @@ -124,12 +148,13 @@ private static int iow(int type, int nr, int size) { private static final int UINPUT_IOCTL_BASE = 'U'; + private static final int UI_GET_VERSION = ior(UINPUT_IOCTL_BASE, 45, 4); // 0.5+ private static final int UI_SET_EVBIT = iow(UINPUT_IOCTL_BASE, 100, 4); private static final int UI_SET_KEYBIT = iow(UINPUT_IOCTL_BASE, 101, 4); private static final int UI_SET_ABSBIT = iow(UINPUT_IOCTL_BASE, 103, 4); - private static final int UI_ABS_SETUP = iow(UINPUT_IOCTL_BASE, 4, new UinputAbsSetup().size()); + private static final int UI_ABS_SETUP = iow(UINPUT_IOCTL_BASE, 4, new UinputAbsSetup().size()); // 0.5+ - private static final int UI_DEV_SETUP = iow(UINPUT_IOCTL_BASE, 3, new UinputSetup().size()); + private static final int UI_DEV_SETUP = iow(UINPUT_IOCTL_BASE, 3, new UinputSetup().size()); // 0.5+ private static final int UI_DEV_CREATE = io(UINPUT_IOCTL_BASE, 1, 0); private static final int UI_DEV_DESTROY = io(UINPUT_IOCTL_BASE, 2, 0); @@ -161,15 +186,20 @@ private static int iow(int type, int nr, int size) { protected static final short ABS_HAT0Y = 0x11; private int fd = -1; - + private int[] absmax; + private int[] absmin; + private int[] absfuzz; + private int[] absflat; + public interface LibC extends Library { int open(String pathname, int flags) throws LastErrorException; int ioctl(int fd, long request, Object... args) throws LastErrorException; long write(int fd, Pointer buf, long count) throws LastErrorException; int close(int fd) throws LastErrorException; } - + private static LibC libC; + private static int version = 0; /// Must be the first method called public static void loadNativeLibraries() { @@ -183,6 +213,22 @@ protected void setup() { throw new UinputUnsupportedException(e); } + if (version == 0) { + try { + IntByReference versionRef = new IntByReference(); + libC.ioctl(fd, UI_GET_VERSION, versionRef); + version = versionRef.getValue(); + } catch (LastErrorException e) { + version = -1; + } + + if (version >= 0) { + Ln.i(String.format("Using uinput version 0.%d", version)); + } else { + Ln.i(String.format("Using unknown uinput version. Assuming at least 0.3.")); + } + } + if (hasKeys()) { try { libC.ioctl(fd, UI_SET_EVBIT, EV_KEY); @@ -198,28 +244,59 @@ protected void setup() { } catch (LastErrorException e) { throw new RuntimeException("Could not enable absolute events.", e); } + + if (version < 5) { + absmax = new int[ABS_CNT]; + absmin = new int[ABS_CNT]; + absfuzz = new int[ABS_CNT]; + absflat = new int[ABS_CNT]; + } + setupAbs(); } - UinputSetup usetup = new UinputSetup(); - usetup.id.bustype = BUS_USB; - usetup.id.vendor = getVendor(); - usetup.id.product = getProduct(); - byte[] name = getName().getBytes(); - System.arraycopy(name, 0, usetup.name, 0, name.length); + if (version >= 5) { + UinputSetup usetup = new UinputSetup(); + usetup.id.bustype = BUS_USB; + usetup.id.vendor = getVendor(); + usetup.id.product = getProduct(); + byte[] name = getName().getBytes(); + System.arraycopy(name, 0, usetup.name, 0, name.length); - try { - libC.ioctl(fd, UI_DEV_SETUP, usetup); - } catch (LastErrorException e) { - close(); - throw new RuntimeException("Couldn't setup uinput device.", e); + try { + libC.ioctl(fd, UI_DEV_SETUP, usetup); + } catch (LastErrorException e) { + close(); + throw new RuntimeException("Could not setup uinput device.", e); + } + } else { + UinputUserDev userDev = new UinputUserDev(); + userDev.id.bustype = BUS_USB; + userDev.id.vendor = getVendor(); + userDev.id.product = getProduct(); + byte[] name = getName().getBytes(); + System.arraycopy(name, 0, userDev.name, 0, name.length); + + userDev.absmax = absmax; + userDev.absmin = absmin; + userDev.absfuzz = absfuzz; + userDev.absflat = absflat; + + userDev.write(); + + try { + libC.write(fd, userDev.getPointer(), userDev.size()); + } catch (LastErrorException e) { + close(); + throw new RuntimeException("Could not setup uinput device using legacy method.", e); + } } try { libC.ioctl(fd, UI_DEV_CREATE); } catch (LastErrorException e) { close(); - throw new RuntimeException("Couldn't create uinput device.", e); + throw new RuntimeException("Could not create uinput device.", e); } } @@ -262,18 +339,25 @@ protected void addAbs(short code, int minimum, int maximum, int fuzz, int flat) Ln.e("Could not add absolute event.", e); } - UinputAbsSetup absSetup = new UinputAbsSetup(); + if (version >= 5) { + UinputAbsSetup absSetup = new UinputAbsSetup(); - absSetup.code = code; - absSetup.absinfo.minimum = minimum; - absSetup.absinfo.maximum = maximum; - absSetup.absinfo.fuzz = fuzz; - absSetup.absinfo.flat = flat; + absSetup.code = code; + absSetup.absinfo.minimum = minimum; + absSetup.absinfo.maximum = maximum; + absSetup.absinfo.fuzz = fuzz; + absSetup.absinfo.flat = flat; - try { - libC.ioctl(fd, UI_ABS_SETUP, absSetup); - } catch (LastErrorException e) { - Ln.e("Could not set absolute event info.", e); + try { + libC.ioctl(fd, UI_ABS_SETUP, absSetup); + } catch (LastErrorException e) { + Ln.e("Could not set absolute event info.", e); + } + } else { + absmin[code] = minimum; + absmax[code] = maximum; + absfuzz[code] = fuzz; + absflat[code] = flat; } } From 8df26fef2b7527c74f5b10c660d7b522a93eff5d Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Tue, 7 Feb 2023 15:38:39 +0800 Subject: [PATCH 19/22] Fix issues in rebase --- app/src/control_msg.c | 8 ++++---- app/src/input_manager.c | 40 +++++++++++++++++++++++++++------------- app/src/input_manager.h | 1 + app/src/scrcpy.c | 5 +++-- app/src/screen.c | 1 + app/src/screen.h | 1 + 6 files changed, 37 insertions(+), 19 deletions(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 091651c418..7148b6605b 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -153,17 +153,17 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) { // no additional data return 1; case SC_CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_AXIS: - buffer_write16be(&buf[1], msg->inject_game_controller_axis.id); + sc_write16be(&buf[1], msg->inject_game_controller_axis.id); buf[3] = msg->inject_game_controller_axis.axis; - buffer_write16be(&buf[4], msg->inject_game_controller_axis.value); + sc_write16be(&buf[4], msg->inject_game_controller_axis.value); return 6; case SC_CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_BUTTON: - buffer_write16be(&buf[1], msg->inject_game_controller_button.id); + sc_write16be(&buf[1], msg->inject_game_controller_button.id); buf[3] = msg->inject_game_controller_button.button; buf[4] = msg->inject_game_controller_button.state; return 5; case SC_CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_DEVICE: - buffer_write16be(&buf[1], msg->inject_game_controller_device.id); + sc_write16be(&buf[1], msg->inject_game_controller_device.id); buf[3] = msg->inject_game_controller_device.event; return 4; default: diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 5cdce72eab..616337fd68 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -62,7 +62,7 @@ sc_input_manager_init(struct sc_input_manager *im, im->kp = params->kp; im->mp = params->mp; - im->forward_game_controllers = options->forward_game_controllers; + im->forward_game_controllers = params->forward_game_controllers; im->forward_all_clicks = params->forward_all_clicks; im->legacy_paste = params->legacy_paste; im->clipboard_autosync = params->clipboard_autosync; @@ -799,29 +799,29 @@ sc_input_manager_process_file(struct sc_input_manager *im, } void -input_manager_process_controller_axis(struct input_manager *im, +input_manager_process_controller_axis(struct sc_input_manager *im, const SDL_ControllerAxisEvent *event) { - struct control_msg msg; - msg.type = CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_AXIS; + struct sc_control_msg msg; + msg.type = SC_CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_AXIS; msg.inject_game_controller_axis.id = event->which; msg.inject_game_controller_axis.axis = event->axis; msg.inject_game_controller_axis.value = event->value; - controller_push_msg(im->controller, &msg); + sc_controller_push_msg(im->controller, &msg); } void -input_manager_process_controller_button(struct input_manager *im, +input_manager_process_controller_button(struct sc_input_manager *im, const SDL_ControllerButtonEvent *event) { - struct control_msg msg; - msg.type = CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_BUTTON; + struct sc_control_msg msg; + msg.type = SC_CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_BUTTON; msg.inject_game_controller_button.id = event->which; msg.inject_game_controller_button.button = event->button; msg.inject_game_controller_button.state = event->state; - controller_push_msg(im->controller, &msg); + sc_controller_push_msg(im->controller, &msg); } static SDL_GameController ** -find_free_game_controller_slot(struct input_manager *im) { +find_free_game_controller_slot(struct sc_input_manager *im) { for (unsigned i = 0; i < MAX_GAME_CONTROLLERS; ++i) { if (!im->game_controllers[i]) { return &im->game_controllers[i]; @@ -831,8 +831,21 @@ find_free_game_controller_slot(struct input_manager *im) { return NULL; } +static bool +free_game_controller_slot(struct sc_input_manager *im, + SDL_GameController *game_controller) { + for (unsigned i = 0; i < MAX_GAME_CONTROLLERS; ++i) { + if (im->game_controllers[i] == game_controller) { + im->game_controllers[i] = NULL; + return true; + } + } + + return false; +} + void -input_manager_process_controller_device(struct input_manager *im, +input_manager_process_controller_device(struct sc_input_manager *im, const SDL_ControllerDeviceEvent *event) { SDL_JoystickID id; @@ -882,12 +895,12 @@ input_manager_process_controller_device(struct input_manager *im, return; } - struct control_msg msg; + struct sc_control_msg msg; msg.type = SC_CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_DEVICE; msg.inject_game_controller_device.id = id; msg.inject_game_controller_device.event = event->type; msg.inject_game_controller_device.event -= SDL_CONTROLLERDEVICEADDED; - controller_push_msg(im->controller, &msg); + sc_controller_push_msg(im->controller, &msg); } void @@ -937,6 +950,7 @@ sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event) { break; } sc_input_manager_process_file(im, &event->drop); + break; } case SDL_CONTROLLERAXISMOTION: if (!control || !im->forward_game_controllers) { diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 9e836f03f0..fc41981bc5 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -54,6 +54,7 @@ struct sc_input_manager_params { struct sc_key_processor *kp; struct sc_mouse_processor *mp; + bool forward_game_controllers; bool forward_all_clicks; bool legacy_paste; bool clipboard_autosync; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index f99e3c0d2c..d79020f836 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -367,8 +367,8 @@ scrcpy(struct scrcpy_options *options) { sdl_configure(options->display, options->disable_screensaver); - if (SDL_Init(SDL_INIT_GAMECONTROLLER)) { - LOGC("Could not initialize SDL: %s", SDL_GetError()); + if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER)) { + LOGE("Could not initialize SDL: %s", SDL_GetError()); return false; } @@ -602,6 +602,7 @@ scrcpy(struct scrcpy_options *options) { .fp = fp, .kp = kp, .mp = mp, + .forward_game_controllers = options->forward_game_controllers, .forward_all_clicks = options->forward_all_clicks, .legacy_paste = options->legacy_paste, .clipboard_autosync = options->clipboard_autosync, diff --git a/app/src/screen.c b/app/src/screen.c index ae28e6e646..e192ffec80 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -525,6 +525,7 @@ sc_screen_init(struct sc_screen *screen, .screen = screen, .kp = params->kp, .mp = params->mp, + .forward_game_controllers = params->forward_game_controllers, .forward_all_clicks = params->forward_all_clicks, .legacy_paste = params->legacy_paste, .clipboard_autosync = params->clipboard_autosync, diff --git a/app/src/screen.h b/app/src/screen.h index 222e418f80..09e37cd8ba 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -74,6 +74,7 @@ struct sc_screen_params { struct sc_key_processor *kp; struct sc_mouse_processor *mp; + bool forward_game_controllers; bool forward_all_clicks; bool legacy_paste; bool clipboard_autosync; From 88b8f1c6d7d41404e8d27f454bcf2b96da255335 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Tue, 7 Feb 2023 16:41:39 +0800 Subject: [PATCH 20/22] Fix SDL game controller init timing --- app/src/scrcpy.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index d79020f836..f68541cfe1 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -367,11 +367,6 @@ scrcpy(struct scrcpy_options *options) { sdl_configure(options->display, options->disable_screensaver); - if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER)) { - LOGE("Could not initialize SDL: %s", SDL_GetError()); - return false; - } - // Await for server without blocking Ctrl+C handling bool connected; if (!await_for_server(&connected)) { @@ -384,6 +379,14 @@ scrcpy(struct scrcpy_options *options) { goto end; } + // Initialize GAMECONTROLLER subsystem after server connected + // Otherwise the initial CONTROLLERDEVICEADDED event might + // be handled by `await_for_server` + if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER)) { + LOGE("Could not initialize SDL: %s", SDL_GetError()); + return false; + } + // It is necessarily initialized here, since the device is connected struct sc_server_info *info = &s->server.info; From 2fc1bd111ee108ffbbaa3a1e320e9e094dd04da6 Mon Sep 17 00:00:00 2001 From: QuantumSoul Date: Fri, 12 Jul 2024 04:31:15 +0200 Subject: [PATCH 21/22] fix compile and device event --- app/src/cli.c | 43 +------------------ app/src/control_msg.h | 1 + app/src/input_manager.c | 6 +-- app/src/options.h | 1 + app/src/server.c | 3 +- .../com/genymobile/scrcpy/ControlMessage.java | 6 +-- .../com/genymobile/scrcpy/Controller.java | 2 +- .../java/com/genymobile/scrcpy/Server.java | 15 +++---- 8 files changed, 16 insertions(+), 61 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 6ff8f1ee48..fdbd7a2d5c 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -100,49 +100,8 @@ enum { OPT_NO_WINDOW, OPT_MOUSE_BIND, OPT_NO_MOUSE_HOVER, + OPT_NO_GAME_CONTROLLER }; -#define OPT_RENDER_EXPIRED_FRAMES 1000 -#define OPT_WINDOW_TITLE 1001 -#define OPT_PUSH_TARGET 1002 -#define OPT_ALWAYS_ON_TOP 1003 -#define OPT_CROP 1004 -#define OPT_RECORD_FORMAT 1005 -#define OPT_PREFER_TEXT 1006 -#define OPT_WINDOW_X 1007 -#define OPT_WINDOW_Y 1008 -#define OPT_WINDOW_WIDTH 1009 -#define OPT_WINDOW_HEIGHT 1010 -#define OPT_WINDOW_BORDERLESS 1011 -#define OPT_MAX_FPS 1012 -#define OPT_LOCK_VIDEO_ORIENTATION 1013 -#define OPT_DISPLAY_ID 1014 -#define OPT_ROTATION 1015 -#define OPT_RENDER_DRIVER 1016 -#define OPT_NO_MIPMAPS 1017 -#define OPT_CODEC_OPTIONS 1018 -#define OPT_FORCE_ADB_FORWARD 1019 -#define OPT_DISABLE_SCREENSAVER 1020 -#define OPT_SHORTCUT_MOD 1021 -#define OPT_NO_KEY_REPEAT 1022 -#define OPT_FORWARD_ALL_CLICKS 1023 -#define OPT_LEGACY_PASTE 1024 -#define OPT_ENCODER_NAME 1025 -#define OPT_POWER_OFF_ON_CLOSE 1026 -#define OPT_V4L2_SINK 1027 -#define OPT_DISPLAY_BUFFER 1028 -#define OPT_V4L2_BUFFER 1029 -#define OPT_TUNNEL_HOST 1030 -#define OPT_TUNNEL_PORT 1031 -#define OPT_NO_CLIPBOARD_AUTOSYNC 1032 -#define OPT_TCPIP 1033 -#define OPT_RAW_KEY_EVENTS 1034 -#define OPT_NO_DOWNSIZE_ON_ERROR 1035 -#define OPT_OTG 1036 -#define OPT_NO_CLEANUP 1037 -#define OPT_PRINT_FPS 1038 -#define OPT_NO_POWER_ON 1039 -#define OPT_CODEC 1040 -#define OPT_NO_GAME_CONTROLLER 1041 struct sc_option { char shortopt; diff --git a/app/src/control_msg.h b/app/src/control_msg.h index 5e7704f105..449945bddd 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -109,6 +109,7 @@ struct sc_control_msg { uint16_t size; uint8_t data[SC_HID_MAX_SIZE]; } uhid_input; + struct { int16_t id; uint8_t axis; int16_t value; diff --git a/app/src/input_manager.c b/app/src/input_manager.c index e334b60f20..b66c4540ee 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -934,7 +934,7 @@ sc_input_manager_process_file(struct sc_input_manager *im, } } -void +static void input_manager_process_controller_axis(struct sc_input_manager *im, const SDL_ControllerAxisEvent *event) { struct sc_control_msg msg; @@ -945,7 +945,7 @@ input_manager_process_controller_axis(struct sc_input_manager *im, sc_controller_push_msg(im->controller, &msg); } -void +static void input_manager_process_controller_button(struct sc_input_manager *im, const SDL_ControllerButtonEvent *event) { struct sc_control_msg msg; @@ -980,7 +980,7 @@ free_game_controller_slot(struct sc_input_manager *im, return false; } -void +static void input_manager_process_controller_device(struct sc_input_manager *im, const SDL_ControllerDeviceEvent *event) { SDL_JoystickID id; diff --git a/app/src/options.h b/app/src/options.h index 151e825753..49508782cb 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -269,6 +269,7 @@ struct scrcpy_options { bool disable_screensaver; bool forward_game_controllers; bool forward_key_repeat; + bool forward_all_clicks; bool legacy_paste; bool power_off_on_close; bool clipboard_autosync; diff --git a/app/src/server.c b/app/src/server.c index 4dacb77994..ef5df4a253 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -18,8 +18,7 @@ #define SC_SERVER_FILENAME "scrcpy-server" #define SC_SERVER_PATH_DEFAULT PREFIX "/share/scrcpy/" SC_SERVER_FILENAME -#define SC_DEVICE_SERVER_DIR "/data/local/tmp" -#define SC_DEVICE_SERVER_PATH SC_DEVICE_SERVER_DIR "/scrcpy-server.jar" +#define SC_DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar" #define SC_ADB_PORT_DEFAULT 5555 #define SC_SOCKET_NAME_PREFIX "scrcpy_" diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java index 40e581b53f..e95e187430 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java @@ -20,9 +20,9 @@ public final class ControlMessage { public static final int TYPE_UHID_CREATE = 12; public static final int TYPE_UHID_INPUT = 13; public static final int TYPE_OPEN_HARD_KEYBOARD_SETTINGS = 14; - public static final int TYPE_INJECT_GAME_CONTROLLER_AXIS = 12; - public static final int TYPE_INJECT_GAME_CONTROLLER_BUTTON = 13; - public static final int TYPE_INJECT_GAME_CONTROLLER_DEVICE = 14; + public static final int TYPE_INJECT_GAME_CONTROLLER_AXIS = 15; + public static final int TYPE_INJECT_GAME_CONTROLLER_BUTTON = 16; + public static final int TYPE_INJECT_GAME_CONTROLLER_DEVICE = 17; public static final long SEQUENCE_INVALID = 0; diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index afe46a433d..c653568435 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -50,7 +50,7 @@ public class Controller implements AsyncProcessor { private SparseArray gameControllers = new SparseArray(); private boolean gameControllersEnabled; - public Controller(Device device, ControlChannel controlChannel, CleanUp cleanUp, DesktopConnection connection, boolean clipboardAutosync, boolean powerOn) { + public Controller(Device device, ControlChannel controlChannel, CleanUp cleanUp, boolean clipboardAutosync, boolean powerOn) { this.device = device; this.controlChannel = controlChannel; this.cleanUp = cleanUp; diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 74d9401b9d..1f0dcf6ad7 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -21,6 +21,11 @@ public final class Server { SERVER_PATH = classPaths[0]; } + public static final String SERVER_DIR = "/data/local/tmp"; + public static final String[] NATIVE_LIBRARIES = { + "libjnidispatch.so", + }; + private static class Completion { private int running; private boolean fatalError; @@ -49,10 +54,6 @@ synchronized void await() { } } } - public static final String SERVER_DIR = "/data/local/tmp"; - public static final String[] NATIVE_LIBRARIES = { - "libjnidispatch.so", - }; private Server() { // not instantiable @@ -108,10 +109,6 @@ private static void scrcpy(Options options) throws IOException, ConfigurationExc Ln.e("Camera mirroring is not supported before Android 12"); throw new ConfigurationException("Camera mirroring is not supported"); } - Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")"); - Ln.i("Supported ABIs: " + TextUtils.join(", ", Build.SUPPORTED_ABIS)); - final Device device = new Device(options); - List codecOptions = options.getCodecOptions(); for (String lib : NATIVE_LIBRARIES) { for (String abi : Build.SUPPORTED_ABIS) { @@ -144,8 +141,6 @@ private static void scrcpy(Options options) throws IOException, ConfigurationExc } int scid = options.getScid(); - int uid = options.getUid(); - boolean tunnelForward = options.isTunnelForward(); boolean control = options.getControl(); boolean video = options.getVideo(); From da16734632e28f18b5e1d7b06610d2ef7e556b99 Mon Sep 17 00:00:00 2001 From: QuantumSoul Date: Fri, 12 Jul 2024 05:47:34 +0200 Subject: [PATCH 22/22] Rename controller --- server/src/main/java/com/genymobile/scrcpy/GameController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/GameController.java b/server/src/main/java/com/genymobile/scrcpy/GameController.java index 9000b879d7..fb81ea4694 100644 --- a/server/src/main/java/com/genymobile/scrcpy/GameController.java +++ b/server/src/main/java/com/genymobile/scrcpy/GameController.java @@ -93,7 +93,7 @@ protected short getProduct() { } protected String getName() { - return "Microsoft X-Box 360 pad"; + return "scrcpy Xbox Controller"; } private static short translateAxis(int axis) {