From 0ba031b1835a9907cc7befaaceb1a90795e39dbe Mon Sep 17 00:00:00 2001 From: Luiz Henrique Laurini Date: Sun, 14 Feb 2021 21:12:15 -0300 Subject: [PATCH] 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 | 127 ++++++++++++++++++ app/src/input_manager.h | 3 + app/src/scrcpy.c | 2 +- .../com/genymobile/scrcpy/ControlMessage.java | 60 +++++++++ .../scrcpy/ControlMessageReader.java | 41 ++++++ .../com/genymobile/scrcpy/Controller.java | 23 ++++ 8 files changed, 286 insertions(+), 1 deletion(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 1257010e..71e59edc 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -134,6 +134,20 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) { case CONTROL_MSG_TYPE_ROTATE_DEVICE: // no additional data return 1; + case 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 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 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 920a493a..5c32687b 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -33,6 +33,9 @@ enum control_msg_type { CONTROL_MSG_TYPE_SET_CLIPBOARD, CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE, CONTROL_MSG_TYPE_ROTATE_DEVICE, + CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_AXIS, + CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_BUTTON, + CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_DEVICE, }; enum screen_power_mode { @@ -76,6 +79,20 @@ struct control_msg { struct { enum 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 a5d0ad07..a8c2744e 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -57,6 +57,7 @@ input_manager_init(struct input_manager *im, struct controller *controller, const struct scrcpy_options *options) { im->controller = controller; im->screen = screen; + memset(im->game_controllers, 0, sizeof(im->game_controllers)); im->repeat = 0; im->control = options->control; @@ -846,7 +847,133 @@ input_manager_handle_event(struct input_manager *im, SDL_Event *event) { case SDL_FINGERUP: input_manager_process_touch(im, &event->tfinger); return true; + case SDL_CONTROLLERAXISMOTION: + if (!options->control) { + break; + } + input_manager_process_controller_axis(&input_manager, &event->caxis); + break; + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: + if (!options->control) { + break; + } + input_manager_process_controller_button(&input_manager, &event->cbutton); + break; + case SDL_CONTROLLERDEVICEADDED: + // case SDL_CONTROLLERDEVICEREMAPPED: + case SDL_CONTROLLERDEVICEREMOVED: + if (!options->control) { + break; + } + input_manager_process_controller_device(&input_manager, &event->cdevice); + break; } return false; } + +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; +} + +static bool +free_game_controller_slot(struct 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, + 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 1dd7825f..ded8aa25 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -12,9 +12,12 @@ #include "scrcpy.h" #include "screen.h" +#define MAX_GAME_CONTROLLERS 16 + struct input_manager { struct controller *controller; struct screen *screen; + SDL_GameController *game_controllers[MAX_GAME_CONTROLLERS]; // SDL reports repeated events as a boolean, but Android expects the actual // number of repetitions. This variable keeps track of the count. diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 6a285788..d68157fd 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -59,7 +59,7 @@ BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) { static bool sdl_init_and_configure(bool display, const char *render_driver, bool disable_screensaver) { - uint32_t flags = display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS; + uint32_t flags = (display ? SDL_INIT_VIDEO : 0) | SDL_INIT_GAMECONTROLLER; if (SDL_Init(flags)) { LOGC("Could not initialize SDL: %s", SDL_GetError()); return false; diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java index f8edd53c..0c87d23a 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; private int type; private String text; @@ -31,6 +34,12 @@ public final class ControlMessage { private int vScroll; private boolean paste; private int repeat; + private int gameControllerId; + private int gameControllerAxis; + private int gameControllerAxisValue; + private int gameControllerButton; + private int gameControllerButtonState; + private int gameControllerDeviceEvent; private ControlMessage() { } @@ -97,6 +106,32 @@ public final class ControlMessage { 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; @@ -154,4 +189,29 @@ public final class ControlMessage { public int getRepeat() { return repeat; } + + 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 e4ab8402..4319ba8e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -14,6 +14,9 @@ public class ControlMessageReader { static final int BACK_OR_SCREEN_ON_LENGTH = 1; static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1; + 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 @@ -83,6 +86,15 @@ public class ControlMessageReader { 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; @@ -182,6 +194,35 @@ public class ControlMessageReader { 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 45882bb9..68b9a24c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -135,6 +135,29 @@ public class Controller { 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 }