Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GamePad support #5079

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7577e0f
Read controller and send control_msgs to device
yume-chan Feb 7, 2023
e1b2fb1
Use uinput to emulate game controllers
yume-chan Feb 7, 2023
1a98348
Fix axes not working
yume-chan Feb 7, 2023
9616c1d
Fix errors caused by improperly done rebase
yume-chan Feb 7, 2023
cedacc1
Remove some unused imports
LHLaurini Feb 18, 2021
c2b8b6f
Fix checkstyle errors unrelated to naming scheme
LHLaurini Feb 20, 2021
fe88111
Avoid calls unavailable on API level 21
LHLaurini Feb 20, 2021
6e7e6cd
Write tests for new messages
LHLaurini Feb 20, 2021
aff3d85
Fix naming scheme
LHLaurini Feb 20, 2021
22d64e1
Fix mistake when renaming things
LHLaurini Feb 21, 2021
ade5d50
Move uinput stuff to UinputDevice
LHLaurini Mar 31, 2021
dda73d6
Cleanup initialization
LHLaurini Mar 31, 2021
b8c81bb
Add --no-game-controller option
yume-chan Feb 7, 2023
55ca08f
Use jna.boot.library.path instd of LD_LIBRARY_PATH
LHLaurini Apr 30, 2021
ecf2066
Fix no input working if uinput is not supported
LHLaurini Apr 30, 2021
80887ed
Fix style violations
LHLaurini May 1, 2021
abedd0b
Use LastErrorException for native error handling
LHLaurini May 4, 2021
ae83fe3
Add support for the legacy uinput interface
LHLaurini May 8, 2021
8df26fe
Fix issues in rebase
yume-chan Feb 7, 2023
88b8f1c
Fix SDL game controller init timing
yume-chan Feb 7, 2023
d0ebb02
Merge branch 'feat/game-controller'
BinaryQuantumSoul Jul 9, 2024
2fc1bd1
fix compile and device event
BinaryQuantumSoul Jul 12, 2024
da16734
Rename controller
BinaryQuantumSoul Jul 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions app/src/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ enum {
OPT_NO_WINDOW,
OPT_MOUSE_BIND,
OPT_NO_MOUSE_HOVER,
OPT_NO_GAME_CONTROLLER
};

struct sc_option {
Expand Down Expand Up @@ -557,6 +558,11 @@ static const struct sc_option options[] = {
.longopt_id = OPT_NO_DISPLAY,
.longopt = "no-display",
},
{
.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",
Expand Down Expand Up @@ -2370,6 +2376,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;
Expand Down
14 changes: 14 additions & 0 deletions app/src/control_msg.c
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,20 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
// no additional data
return 1;
case SC_CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_AXIS:
sc_write16be(&buf[1], msg->inject_game_controller_axis.id);
buf[3] = msg->inject_game_controller_axis.axis;
sc_write16be(&buf[4], msg->inject_game_controller_axis.value);
return 6;
case SC_CONTROL_MSG_TYPE_INJECT_GAME_CONTROLLER_BUTTON:
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:
sc_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;
Expand Down
17 changes: 17 additions & 0 deletions app/src/control_msg.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ enum sc_control_msg_type {
SC_CONTROL_MSG_TYPE_UHID_CREATE,
SC_CONTROL_MSG_TYPE_UHID_INPUT,
SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
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 {
Expand Down Expand Up @@ -106,6 +109,20 @@ 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;
} 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;
};
};

Expand Down
133 changes: 131 additions & 2 deletions app/src/input_manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,15 @@ 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;

im->mouse_bindings = params->mouse_bindings;
im->has_secondary_click =
mouse_bindings_has_secondary_click(&im->mouse_bindings);
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;

Expand Down Expand Up @@ -931,9 +934,113 @@ sc_input_manager_process_file(struct sc_input_manager *im,
}
}

static void
input_manager_process_controller_axis(struct sc_input_manager *im,
const SDL_ControllerAxisEvent *event) {
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;
sc_controller_push_msg(im->controller, &msg);
}

static void
input_manager_process_controller_button(struct sc_input_manager *im,
const SDL_ControllerButtonEvent *event) {
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;
sc_controller_push_msg(im->controller, &msg);
}

static SDL_GameController **
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];
}
}

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;
}

static void
input_manager_process_controller_device(struct sc_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 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;
sc_controller_push_msg(im->controller, &msg);
}

void
sc_input_manager_handle_event(struct sc_input_manager *im,
const SDL_Event *event) {
sc_input_manager_handle_event(struct sc_input_manager *im, const SDL_Event *event) {
bool control = im->controller;
bool paused = im->screen->paused;
switch (event->type) {
Expand Down Expand Up @@ -980,6 +1087,28 @@ sc_input_manager_handle_event(struct sc_input_manager *im,
break;
}
sc_input_manager_process_file(im, &event->drop);
break;
}
case SDL_CONTROLLERAXISMOTION:
if (!control || !im->forward_game_controllers) {
break;
}
input_manager_process_controller_axis(im, &event->caxis);
break;
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
if (!control || !im->forward_game_controllers) {
break;
}
input_manager_process_controller_button(im, &event->cbutton);
break;
case SDL_CONTROLLERDEVICEADDED:
// case SDL_CONTROLLERDEVICEREMAPPED:
case SDL_CONTROLLERDEVICEREMOVED:
if (!control || !im->forward_game_controllers) {
break;
}
input_manager_process_controller_device(im, &event->cdevice);
break;
}
}
7 changes: 7 additions & 0 deletions app/src/input_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,21 @@
#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;

struct sc_mouse_bindings mouse_bindings;
bool has_secondary_click;
bool forward_game_controllers;
bool forward_all_clicks;
bool legacy_paste;
bool clipboard_autosync;

Expand Down Expand Up @@ -51,6 +56,8 @@ struct sc_input_manager_params {
struct sc_mouse_processor *mp;

struct sc_mouse_bindings mouse_bindings;
bool forward_game_controllers;
bool forward_all_clicks;
bool legacy_paste;
bool clipboard_autosync;
uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values
Expand Down
1 change: 1 addition & 0 deletions app/src/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,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,
.legacy_paste = false,
.power_off_on_close = false,
Expand Down
2 changes: 2 additions & 0 deletions app/src/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,9 @@ 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;
bool power_off_on_close;
bool clipboard_autosync;
Expand Down
9 changes: 9 additions & 0 deletions app/src/scrcpy.c
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,13 @@ scrcpy(struct scrcpy_options *options) {
}

LOGD("Server connected");
// 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;
Expand Down Expand Up @@ -714,6 +721,8 @@ scrcpy(struct scrcpy_options *options) {
.kp = kp,
.mp = mp,
.mouse_bindings = options->mouse_bindings,
.forward_game_controllers = options->forward_game_controllers,
.forward_all_clicks = options->forward_all_clicks,
.legacy_paste = options->legacy_paste,
.clipboard_autosync = options->clipboard_autosync,
.shortcut_mods = options->shortcut_mods,
Expand Down
2 changes: 2 additions & 0 deletions app/src/screen.c
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,8 @@ sc_screen_init(struct sc_screen *screen,
.kp = params->kp,
.mp = params->mp,
.mouse_bindings = params->mouse_bindings,
.forward_game_controllers = params->forward_game_controllers,
.forward_all_clicks = params->forward_all_clicks,
.legacy_paste = params->legacy_paste,
.clipboard_autosync = params->clipboard_autosync,
.shortcut_mods = params->shortcut_mods,
Expand Down
2 changes: 2 additions & 0 deletions app/src/screen.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ struct sc_screen_params {
struct sc_mouse_processor *mp;

struct sc_mouse_bindings mouse_bindings;
bool forward_game_controllers;
bool forward_all_clicks;
bool legacy_paste;
bool clipboard_autosync;
uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values
Expand Down
1 change: 1 addition & 0 deletions app/src/server.c
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ execute_server(struct sc_server *server,
cmd[count++] = "shell";
cmd[count++] = "CLASSPATH=" SC_DEVICE_SERVER_PATH;
cmd[count++] = "app_process";
cmd[count++] = "-Djna.boot.library.path=/data/local/tmp";

#ifdef SERVER_DEBUGGER
# define SERVER_DEBUGGER_PORT "5005"
Expand Down
Loading