Compare commits
36 Commits
master
...
gamepad.dr
Author | SHA1 | Date | |
---|---|---|---|
|
b54b0a6958 | ||
|
e5c7840530 | ||
|
ccc030625b | ||
|
b0b05909c0 | ||
|
330d844231 | ||
|
ab6732f32d | ||
|
32c73a5448 | ||
|
c72c772c81 | ||
|
d2e1d96fbd | ||
|
07d5a6ca80 | ||
|
2d929fa4e5 | ||
|
623346b558 | ||
|
5b52afd980 | ||
|
4c5d43d41d | ||
|
2351eed46f | ||
|
2595bcfa75 | ||
|
93dad86634 | ||
|
282e6503e1 | ||
|
9c86cf74e6 | ||
|
2caf593960 | ||
|
1344b76f42 | ||
|
32b1a8093a | ||
|
4416aa29f1 | ||
|
4b95a9674a | ||
|
c7c8d71de5 | ||
|
de6ee25d09 | ||
|
70a9bbbc06 | ||
|
97e83fa3d6 | ||
|
0215693725 | ||
|
220cad2e7a | ||
|
0b6119a594 | ||
|
9cae029ef2 | ||
|
72d5bcc12a | ||
|
d56b8a6a95 | ||
|
580cba73c0 | ||
|
523299743c |
@ -15,6 +15,7 @@ src = [
|
||||
'src/demuxer.c',
|
||||
'src/device_msg.c',
|
||||
'src/display.c',
|
||||
'src/events.c',
|
||||
'src/icon.c',
|
||||
'src/file_pusher.c',
|
||||
'src/fps_counter.c',
|
||||
@ -31,10 +32,12 @@ src = [
|
||||
'src/screen.c',
|
||||
'src/server.c',
|
||||
'src/version.c',
|
||||
'src/hid/hid_gamepad.c',
|
||||
'src/hid/hid_keyboard.c',
|
||||
'src/hid/hid_mouse.c',
|
||||
'src/trait/frame_source.c',
|
||||
'src/trait/packet_source.c',
|
||||
'src/uhid/gamepad_uhid.c',
|
||||
'src/uhid/keyboard_uhid.c',
|
||||
'src/uhid/mouse_uhid.c',
|
||||
'src/uhid/uhid_output.c',
|
||||
@ -93,6 +96,7 @@ usb_support = get_option('usb')
|
||||
if usb_support
|
||||
src += [
|
||||
'src/usb/aoa_hid.c',
|
||||
'src/usb/gamepad_aoa.c',
|
||||
'src/usb/keyboard_aoa.c',
|
||||
'src/usb/mouse_aoa.c',
|
||||
'src/usb/scrcpy_otg.c',
|
||||
|
25
app/scrcpy.1
25
app/scrcpy.1
@ -175,13 +175,28 @@ Start in fullscreen.
|
||||
.B \-\-force\-adb\-forward
|
||||
Do not attempt to use "adb reverse" to connect to the device.
|
||||
|
||||
.TP
|
||||
.B \-G
|
||||
Same as \fB\-\-gamepad=uhid\fR, or \fB\-\-keyboard=aoa\fR if \fB\-\-otg\fR is set.
|
||||
|
||||
.TP
|
||||
.BI "\-\-gamepad " mode
|
||||
Select how to send gamepad inputs to the device.
|
||||
|
||||
Possible values are "disabled", "uhid" and "aoa":
|
||||
|
||||
- "disabled" does not send keyboard inputs to the device.
|
||||
- "uhid" simulates a physical HID gamepad using the Linux HID kernel module on the device.
|
||||
- "aoa" simulates a physical HID gamepad using the AOAv2 protocol. It may only work over USB.
|
||||
|
||||
Also see \fB\-\-keyboard\f and R\fB\-\-mouse\fR.
|
||||
.TP
|
||||
.B \-h, \-\-help
|
||||
Print this help.
|
||||
|
||||
.TP
|
||||
.B \-K
|
||||
Same as \fB\-\-keyboard=uhid\fR.
|
||||
Same as \fB\-\-keyboard=uhid\fR, or \fB\-\-keyboard=aoa\fR if \fB\-\-otg\fR is set.
|
||||
|
||||
.TP
|
||||
.BI "\-\-keyboard " mode
|
||||
@ -200,7 +215,7 @@ For "uhid" and "aoa", the keyboard layout must be configured (once and for all)
|
||||
|
||||
This option is only available when the HID keyboard is enabled (or a physical keyboard is connected).
|
||||
|
||||
Also see \fB\-\-mouse\fR.
|
||||
Also see \fB\-\-mouse\fR and \fB\-\-gamepad\fR.
|
||||
|
||||
.TP
|
||||
.B \-\-kill\-adb\-on\-close
|
||||
@ -246,7 +261,7 @@ Default is 0 (unlimited).
|
||||
|
||||
.TP
|
||||
.B \-M
|
||||
Same as \fB\-\-mouse=uhid\fR.
|
||||
Same as \fB\-\-mouse=uhid\fR, or \fB\-\-mouse=aoa\fR if \fB\-\-otg\fR is set.
|
||||
|
||||
.TP
|
||||
.BI "\-\-max\-fps " value
|
||||
@ -267,7 +282,7 @@ In "uhid" and "aoa" modes, the computer mouse is captured to control the device
|
||||
|
||||
LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse back to the computer.
|
||||
|
||||
Also see \fB\-\-keyboard\fR.
|
||||
Also see \fB\-\-keyboard\fR and \fB\-\-gamepad\fR.
|
||||
|
||||
.TP
|
||||
.BI "\-\-mouse\-bind " xxxx[:xxxx]
|
||||
@ -369,7 +384,7 @@ If any of \fB\-\-hid\-keyboard\fR or \fB\-\-hid\-mouse\fR is set, only enable ke
|
||||
|
||||
It may only work over USB.
|
||||
|
||||
See \fB\-\-hid\-keyboard\fR and \fB\-\-hid\-mouse\fR.
|
||||
See \fB\-\-keyboard\fR, \fB\-\-mouse\fR and \fB\-\-gamepad\fR.
|
||||
|
||||
.TP
|
||||
.BI "\-p, \-\-port " port\fR[:\fIport\fR]
|
||||
|
@ -101,6 +101,7 @@ enum {
|
||||
OPT_MOUSE_BIND,
|
||||
OPT_NO_MOUSE_HOVER,
|
||||
OPT_AUDIO_DUP,
|
||||
OPT_GAMEPAD,
|
||||
};
|
||||
|
||||
struct sc_option {
|
||||
@ -372,6 +373,23 @@ static const struct sc_option options[] = {
|
||||
.longopt_id = OPT_FORWARD_ALL_CLICKS,
|
||||
.longopt = "forward-all-clicks",
|
||||
},
|
||||
{
|
||||
.shortopt = 'G',
|
||||
.text = "Same as --gamepad=uhid, or --gamepad=aoa if --otg is set.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_GAMEPAD,
|
||||
.longopt = "gamepad",
|
||||
.argdesc = "mode",
|
||||
.text = "Select how to send gamepad inputs to the device.\n"
|
||||
"Possible values are \"disabled\", \"uhid\" and \"aoa\".\n"
|
||||
"\"disabled\" does not send gamepad inputs to the device.\n"
|
||||
"\"uhid\" simulates a physical HID gamepad using the Linux "
|
||||
"UHID kernel module on the device.\n"
|
||||
"\"aoa\" simulates a physical gamepad using the AOAv2 "
|
||||
"protocol. It may only work over USB.\n"
|
||||
"Also see --keyboard and --mouse.",
|
||||
},
|
||||
{
|
||||
.shortopt = 'h',
|
||||
.longopt = "help",
|
||||
@ -379,7 +397,7 @@ static const struct sc_option options[] = {
|
||||
},
|
||||
{
|
||||
.shortopt = 'K',
|
||||
.text = "Same as --keyboard=uhid.",
|
||||
.text = "Same as --keyboard=uhid, or --keyboard=aoa if --otg is set.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_KEYBOARD,
|
||||
@ -403,7 +421,7 @@ static const struct sc_option options[] = {
|
||||
"start -a android.settings.HARD_KEYBOARD_SETTINGS`.\n"
|
||||
"This option is only available when a HID keyboard is enabled "
|
||||
"(or a physical keyboard is connected).\n"
|
||||
"Also see --mouse.",
|
||||
"Also see --mouse and --gamepad.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_KILL_ADB_ON_CLOSE,
|
||||
@ -475,7 +493,7 @@ static const struct sc_option options[] = {
|
||||
},
|
||||
{
|
||||
.shortopt = 'M',
|
||||
.text = "Same as --mouse=uhid.",
|
||||
.text = "Same as --mouse=uhid, or --mouse=aoa if --otg is set.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_MAX_FPS,
|
||||
@ -502,7 +520,7 @@ static const struct sc_option options[] = {
|
||||
"to control the device directly (relative mouse mode).\n"
|
||||
"LAlt, LSuper or RSuper toggle the capture mode, to give "
|
||||
"control of the mouse back to the computer.\n"
|
||||
"Also see --keyboard.",
|
||||
"Also see --keyboard and --gamepad.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_MOUSE_BIND,
|
||||
@ -637,7 +655,7 @@ static const struct sc_option options[] = {
|
||||
"Keyboard and mouse may be disabled separately using"
|
||||
"--keyboard=disabled and --mouse=disabled.\n"
|
||||
"It may only work over USB.\n"
|
||||
"See --keyboard and --mouse.",
|
||||
"See --keyboard, --mouse and --gamepad.",
|
||||
},
|
||||
{
|
||||
.shortopt = 'p',
|
||||
@ -2058,6 +2076,32 @@ parse_mouse(const char *optarg, enum sc_mouse_input_mode *mode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_gamepad(const char *optarg, enum sc_gamepad_input_mode *mode) {
|
||||
if (!strcmp(optarg, "disabled")) {
|
||||
*mode = SC_GAMEPAD_INPUT_MODE_DISABLED;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(optarg, "uhid")) {
|
||||
*mode = SC_GAMEPAD_INPUT_MODE_UHID;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(optarg, "aoa")) {
|
||||
#ifdef HAVE_USB
|
||||
*mode = SC_GAMEPAD_INPUT_MODE_AOA;
|
||||
return true;
|
||||
#else
|
||||
LOGE("--gamepad=aoa is disabled.");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
LOGE("Unsupported gamepad: %s (expected disabled or aoa)", optarg);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_time_limit(const char *s, sc_tick *tick) {
|
||||
long value;
|
||||
@ -2220,7 +2264,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
args->help = true;
|
||||
break;
|
||||
case 'K':
|
||||
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_UHID;
|
||||
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_UHID_OR_AOA;
|
||||
break;
|
||||
case OPT_KEYBOARD:
|
||||
if (!parse_keyboard(optarg, &opts->keyboard_input_mode)) {
|
||||
@ -2242,7 +2286,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
}
|
||||
break;
|
||||
case 'M':
|
||||
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_UHID;
|
||||
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_UHID_OR_AOA;
|
||||
break;
|
||||
case OPT_MOUSE:
|
||||
if (!parse_mouse(optarg, &opts->mouse_input_mode)) {
|
||||
@ -2626,6 +2670,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
case OPT_AUDIO_DUP:
|
||||
opts->audio_dup = true;
|
||||
break;
|
||||
case 'G':
|
||||
opts->gamepad_input_mode = SC_GAMEPAD_INPUT_MODE_UHID_OR_AOA;
|
||||
break;
|
||||
case OPT_GAMEPAD:
|
||||
if (!parse_gamepad(optarg, &opts->gamepad_input_mode)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// getopt prints the error message on stderr
|
||||
return false;
|
||||
@ -2743,7 +2795,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
if (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AUTO) {
|
||||
opts->keyboard_input_mode = otg ? SC_KEYBOARD_INPUT_MODE_AOA
|
||||
: SC_KEYBOARD_INPUT_MODE_SDK;
|
||||
} else if (opts->keyboard_input_mode
|
||||
== SC_KEYBOARD_INPUT_MODE_UHID_OR_AOA) {
|
||||
opts->keyboard_input_mode = otg ? SC_KEYBOARD_INPUT_MODE_AOA
|
||||
: SC_KEYBOARD_INPUT_MODE_UHID;
|
||||
}
|
||||
|
||||
if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_AUTO) {
|
||||
if (otg) {
|
||||
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_AOA;
|
||||
@ -2753,11 +2810,25 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
} else {
|
||||
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_SDK;
|
||||
}
|
||||
} else if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_UHID_OR_AOA) {
|
||||
opts->mouse_input_mode = otg ? SC_MOUSE_INPUT_MODE_AOA
|
||||
: SC_MOUSE_INPUT_MODE_SDK;
|
||||
} else if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK
|
||||
&& !opts->video_playback) {
|
||||
LOGE("SDK mouse mode requires video playback. Try --mouse=uhid.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_AUTO) {
|
||||
// UHID does not work on all devices (with old Android
|
||||
// versions), so it cannot be enabled by default
|
||||
opts->gamepad_input_mode = otg ? SC_GAMEPAD_INPUT_MODE_AOA
|
||||
: SC_GAMEPAD_INPUT_MODE_DISABLED;
|
||||
} else if (opts->gamepad_input_mode
|
||||
== SC_GAMEPAD_INPUT_MODE_UHID_OR_AOA) {
|
||||
opts->gamepad_input_mode = otg ? SC_GAMEPAD_INPUT_MODE_AOA
|
||||
: SC_GAMEPAD_INPUT_MODE_UHID;
|
||||
}
|
||||
}
|
||||
|
||||
// If mouse bindings are not explicitly set, configure default bindings
|
||||
|
@ -155,6 +155,9 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
|
||||
sc_write16be(&buf[3], msg->uhid_input.size);
|
||||
memcpy(&buf[5], msg->uhid_input.data, msg->uhid_input.size);
|
||||
return 5 + msg->uhid_input.size;
|
||||
case SC_CONTROL_MSG_TYPE_UHID_DESTROY:
|
||||
sc_write16be(&buf[1], msg->uhid_destroy.id);
|
||||
return 3;
|
||||
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
||||
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||
@ -269,6 +272,9 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SC_CONTROL_MSG_TYPE_UHID_DESTROY:
|
||||
LOG_CMSG("UHID destroy [%" PRIu16 "]", msg->uhid_destroy.id);
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
||||
LOG_CMSG("open hard keyboard settings");
|
||||
break;
|
||||
@ -278,6 +284,16 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
sc_control_msg_is_droppable(const struct sc_control_msg *msg) {
|
||||
// Cannot drop UHID_CREATE messages, because it would cause all further
|
||||
// UHID_INPUT messages for this device to be invalid.
|
||||
// Cannot drop UHID_DESTROY messages either, because a further UHID_CREATE
|
||||
// with the same id may fail.
|
||||
return msg->type != SC_CONTROL_MSG_TYPE_UHID_CREATE
|
||||
&& msg->type != SC_CONTROL_MSG_TYPE_UHID_DESTROY;
|
||||
}
|
||||
|
||||
void
|
||||
sc_control_msg_destroy(struct sc_control_msg *msg) {
|
||||
switch (msg->type) {
|
||||
|
@ -39,6 +39,7 @@ enum sc_control_msg_type {
|
||||
SC_CONTROL_MSG_TYPE_ROTATE_DEVICE,
|
||||
SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
||||
SC_CONTROL_MSG_TYPE_UHID_INPUT,
|
||||
SC_CONTROL_MSG_TYPE_UHID_DESTROY,
|
||||
SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
|
||||
};
|
||||
|
||||
@ -105,6 +106,9 @@ struct sc_control_msg {
|
||||
uint16_t size;
|
||||
uint8_t data[SC_HID_MAX_SIZE];
|
||||
} uhid_input;
|
||||
struct {
|
||||
uint16_t id;
|
||||
} uhid_destroy;
|
||||
};
|
||||
};
|
||||
|
||||
@ -116,6 +120,11 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf);
|
||||
void
|
||||
sc_control_msg_log(const struct sc_control_msg *msg);
|
||||
|
||||
// Even when the buffer is "full", some messages must absolutely not be dropped
|
||||
// to avoid inconsistencies.
|
||||
bool
|
||||
sc_control_msg_is_droppable(const struct sc_control_msg *msg);
|
||||
|
||||
void
|
||||
sc_control_msg_destroy(struct sc_control_msg *msg);
|
||||
|
||||
|
@ -4,7 +4,8 @@
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
#define SC_CONTROL_MSG_QUEUE_MAX 64
|
||||
// Drop droppable events above this limit
|
||||
#define SC_CONTROL_MSG_QUEUE_LIMIT 60
|
||||
|
||||
static void
|
||||
sc_controller_receiver_on_ended(struct sc_receiver *receiver, bool error,
|
||||
@ -22,7 +23,9 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
|
||||
void *cbs_userdata) {
|
||||
sc_vecdeque_init(&controller->queue);
|
||||
|
||||
bool ok = sc_vecdeque_reserve(&controller->queue, SC_CONTROL_MSG_QUEUE_MAX);
|
||||
// Add 4 to support 4 non-droppable events without re-allocation
|
||||
bool ok = sc_vecdeque_reserve(&controller->queue,
|
||||
SC_CONTROL_MSG_QUEUE_LIMIT + 4);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
@ -93,20 +96,31 @@ sc_controller_push_msg(struct sc_controller *controller,
|
||||
sc_control_msg_log(msg);
|
||||
}
|
||||
|
||||
bool pushed = false;
|
||||
|
||||
sc_mutex_lock(&controller->mutex);
|
||||
bool full = sc_vecdeque_is_full(&controller->queue);
|
||||
if (!full) {
|
||||
size_t size = sc_vecdeque_size(&controller->queue);
|
||||
if (size < SC_CONTROL_MSG_QUEUE_LIMIT) {
|
||||
bool was_empty = sc_vecdeque_is_empty(&controller->queue);
|
||||
sc_vecdeque_push_noresize(&controller->queue, *msg);
|
||||
pushed = true;
|
||||
if (was_empty) {
|
||||
sc_cond_signal(&controller->msg_cond);
|
||||
}
|
||||
} else if (!sc_control_msg_is_droppable(msg)) {
|
||||
bool ok = sc_vecdeque_push(&controller->queue, *msg);
|
||||
if (ok) {
|
||||
pushed = true;
|
||||
} else {
|
||||
// A non-droppable event must be dropped anyway
|
||||
LOG_OOM();
|
||||
}
|
||||
}
|
||||
// Otherwise (if the queue is full), the msg is discarded
|
||||
|
||||
sc_mutex_unlock(&controller->mutex);
|
||||
|
||||
return !full;
|
||||
return pushed;
|
||||
}
|
||||
|
||||
static bool
|
||||
|
66
app/src/events.c
Normal file
66
app/src/events.c
Normal file
@ -0,0 +1,66 @@
|
||||
#include "events.h"
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/thread.h"
|
||||
|
||||
bool
|
||||
sc_push_event_impl(uint32_t type, const char *name) {
|
||||
SDL_Event event;
|
||||
event.type = type;
|
||||
int ret = SDL_PushEvent(&event);
|
||||
// ret < 0: error (queue full)
|
||||
// ret == 0: event was filtered
|
||||
// ret == 1: success
|
||||
if (ret != 1) {
|
||||
LOGE("Could not post %s event: %s", name, SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_post_to_main_thread(sc_runnable_fn run, void *userdata) {
|
||||
SDL_Event event = {
|
||||
.user = {
|
||||
.type = SC_EVENT_RUN_ON_MAIN_THREAD,
|
||||
.data1 = run,
|
||||
.data2 = userdata,
|
||||
},
|
||||
};
|
||||
int ret = SDL_PushEvent(&event);
|
||||
// ret < 0: error (queue full)
|
||||
// ret == 0: event was filtered
|
||||
// ret == 1: success
|
||||
if (ret != 1) {
|
||||
if (ret == 0) {
|
||||
// if ret == 0, this is expected on exit, log in debug mode
|
||||
LOGD("Could not post runnable to main thread (filtered)");
|
||||
} else {
|
||||
assert(ret < 0);
|
||||
LOGW("Coud not post to main thread: %s", SDL_GetError());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int SDLCALL
|
||||
task_event_filter(void *userdata, SDL_Event *event) {
|
||||
(void) userdata;
|
||||
|
||||
if (event->type == SC_EVENT_RUN_ON_MAIN_THREAD) {
|
||||
// Reject this event type from now on
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void
|
||||
sc_reject_new_runnables(void) {
|
||||
assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);
|
||||
|
||||
SDL_SetEventFilter(task_event_filter, NULL);
|
||||
}
|
@ -1,10 +1,38 @@
|
||||
#define SC_EVENT_NEW_FRAME SDL_USEREVENT
|
||||
#define SC_EVENT_DEVICE_DISCONNECTED (SDL_USEREVENT + 1)
|
||||
#define SC_EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2)
|
||||
#define SC_EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3)
|
||||
#define SC_EVENT_USB_DEVICE_DISCONNECTED (SDL_USEREVENT + 4)
|
||||
#define SC_EVENT_DEMUXER_ERROR (SDL_USEREVENT + 5)
|
||||
#define SC_EVENT_RECORDER_ERROR (SDL_USEREVENT + 6)
|
||||
#define SC_EVENT_SCREEN_INIT_SIZE (SDL_USEREVENT + 7)
|
||||
#define SC_EVENT_TIME_LIMIT_REACHED (SDL_USEREVENT + 8)
|
||||
#define SC_EVENT_CONTROLLER_ERROR (SDL_USEREVENT + 9)
|
||||
#ifndef SC_EVENTS_H
|
||||
#define SC_EVENTS_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <SDL_events.h>
|
||||
|
||||
enum {
|
||||
SC_EVENT_NEW_FRAME = SDL_USEREVENT,
|
||||
SC_EVENT_RUN_ON_MAIN_THREAD,
|
||||
SC_EVENT_DEVICE_DISCONNECTED,
|
||||
SC_EVENT_SERVER_CONNECTION_FAILED,
|
||||
SC_EVENT_SERVER_CONNECTED,
|
||||
SC_EVENT_USB_DEVICE_DISCONNECTED,
|
||||
SC_EVENT_DEMUXER_ERROR,
|
||||
SC_EVENT_RECORDER_ERROR,
|
||||
SC_EVENT_SCREEN_INIT_SIZE,
|
||||
SC_EVENT_TIME_LIMIT_REACHED,
|
||||
SC_EVENT_CONTROLLER_ERROR,
|
||||
SC_EVENT_AOA_OPEN_ERROR,
|
||||
};
|
||||
|
||||
bool
|
||||
sc_push_event_impl(uint32_t type, const char *name);
|
||||
|
||||
#define sc_push_event(TYPE) sc_push_event_impl(TYPE, # TYPE)
|
||||
|
||||
typedef void (*sc_runnable_fn)(void *userdata);
|
||||
|
||||
bool
|
||||
sc_post_to_main_thread(sc_runnable_fn run, void *userdata);
|
||||
|
||||
void
|
||||
sc_reject_new_runnables(void);
|
||||
|
||||
#endif
|
||||
|
@ -5,11 +5,22 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define SC_HID_MAX_SIZE 8
|
||||
#define SC_HID_MAX_SIZE 15
|
||||
|
||||
struct sc_hid_event {
|
||||
struct sc_hid_input {
|
||||
uint16_t hid_id;
|
||||
uint8_t data[SC_HID_MAX_SIZE];
|
||||
uint8_t size;
|
||||
};
|
||||
|
||||
struct sc_hid_open {
|
||||
uint16_t hid_id;
|
||||
const uint8_t *report_desc; // pointer to static memory
|
||||
size_t report_desc_size;
|
||||
};
|
||||
|
||||
struct sc_hid_close {
|
||||
uint16_t hid_id;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
452
app/src/hid/hid_gamepad.c
Normal file
452
app/src/hid/hid_gamepad.c
Normal file
@ -0,0 +1,452 @@
|
||||
#include "hid_gamepad.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "util/binary.h"
|
||||
#include "util/log.h"
|
||||
|
||||
// 2 bytes for left stick (X, Y)
|
||||
// 2 bytes for right stick (Z, Rz)
|
||||
// 2 bytes for L2/R2 triggers
|
||||
// 2 bytes for buttons + padding,
|
||||
// 1 byte for hat switch (dpad) + padding
|
||||
#define SC_HID_GAMEPAD_EVENT_SIZE 15
|
||||
|
||||
// The ->buttons field stores the state for all buttons, but only some of them
|
||||
// (the 16 LSB) must be transmitted "as is". The DPAD (hat switch) are stored
|
||||
// locally in the MSB, but not transmitted as is: they are transformed to
|
||||
// generate another specific byte.
|
||||
#define SC_HID_BUTTONS_MASK 0xFFFF
|
||||
|
||||
// outside SC_HID_BUTTONS_MASK
|
||||
#define SC_GAMEPAD_BUTTONS_BIT_DPAD_UP UINT32_C(0x10000)
|
||||
#define SC_GAMEPAD_BUTTONS_BIT_DPAD_DOWN UINT32_C(0x20000)
|
||||
#define SC_GAMEPAD_BUTTONS_BIT_DPAD_LEFT UINT32_C(0x40000)
|
||||
#define SC_GAMEPAD_BUTTONS_BIT_DPAD_RIGHT UINT32_C(0x80000)
|
||||
|
||||
/**
|
||||
* Gamepad descriptor manually crafted to transmit the events.
|
||||
*
|
||||
* The HID specification is available here:
|
||||
* <https://www.usb.org/document-library/device-class-definition-hid-111>
|
||||
*
|
||||
* The HID Usage Tables is also useful:
|
||||
* <https://www.usb.org/document-library/hid-usage-tables-15>
|
||||
*/
|
||||
static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
|
||||
// Usage Page (Generic Desktop)
|
||||
0x05, 0x01,
|
||||
// Usage (Gamepad)
|
||||
0x09, 0x05,
|
||||
|
||||
// Collection (Application)
|
||||
0xA1, 0x01,
|
||||
|
||||
// Collection (Physical)
|
||||
0xA1, 0x00,
|
||||
|
||||
// Usage Page (Generic Desktop)
|
||||
0x05, 0x01,
|
||||
// Usage (X) Left stick x
|
||||
0x09, 0x30,
|
||||
// Usage (Y) Left stick y
|
||||
0x09, 0x31,
|
||||
// Usage (Z) Right stick x
|
||||
0x09, 0x32,
|
||||
// Usage (Rz) Right stick y
|
||||
0x09, 0x35,
|
||||
// Logical Minimum (0)
|
||||
0x15, 0x00,
|
||||
// Logical Maximum (65535)
|
||||
// Cannot use 26 FF FF because 0xFFFF is interpreted as signed 16-bit
|
||||
0x27, 0xFF, 0xFF, 0x00, 0x00, // little-endian
|
||||
// Report Size (16)
|
||||
0x75, 0x10,
|
||||
// Report Count (4)
|
||||
0x95, 0x04,
|
||||
// Input (Data, Variable, Absolute): 4 bytes (X, Y, Z, Rz)
|
||||
0x81, 0x02,
|
||||
|
||||
// Usage Page (Simulation Controls)
|
||||
0x05, 0x02,
|
||||
// Usage (Brake)
|
||||
0x09, 0xC5,
|
||||
// Usage (Accelerator)
|
||||
0x09, 0xC4,
|
||||
// Logical Minimum (0)
|
||||
0x15, 0x00,
|
||||
// Logical Maximum (32767)
|
||||
0x26, 0xFF, 0x7F,
|
||||
// Report Size (16)
|
||||
0x75, 0x10,
|
||||
// Report Count (2)
|
||||
0x95, 0x02,
|
||||
// Input (Data, Variable, Absolute): 2 bytes (L2, R2)
|
||||
0x81, 0x02,
|
||||
|
||||
// Usage Page (Buttons)
|
||||
0x05, 0x09,
|
||||
// Usage Minimum (1)
|
||||
0x19, 0x01,
|
||||
// Usage Maximum (16)
|
||||
0x29, 0x0F,
|
||||
// Logical Minimum (0)
|
||||
0x15, 0x00,
|
||||
// Logical Maximum (1)
|
||||
0x25, 0x01,
|
||||
// Report Count (16)
|
||||
0x95, 0x10,
|
||||
// Report Size (1)
|
||||
0x75, 0x01,
|
||||
// Input (Data, Variable, Absolute): 16 buttons bits
|
||||
0x81, 0x02,
|
||||
|
||||
// Usage Page (Generic Desktop)
|
||||
0x05, 0x01,
|
||||
// Usage (Hat switch)
|
||||
0x09, 0x39,
|
||||
// Logical Minimum (1)
|
||||
0x15, 0x01,
|
||||
// Logical Maximum (8)
|
||||
0x25, 0x08,
|
||||
// Report Size (4)
|
||||
0x75, 0x04,
|
||||
// Report Count (1)
|
||||
0x95, 0x01,
|
||||
// Input (Data, Variable, Null State): 4-bit value
|
||||
0x81, 0x42,
|
||||
|
||||
|
||||
// End Collection
|
||||
0xC0,
|
||||
|
||||
// End Collection
|
||||
0xC0,
|
||||
};
|
||||
|
||||
/**
|
||||
* A gamepad HID event is 15 bytes long:
|
||||
* - bytes 0-3: left stick state
|
||||
* - bytes 4-7: right stick state
|
||||
* - bytes 8-11: L2/R2 triggers state
|
||||
* - bytes 12-13: buttons state
|
||||
* - bytes 14: hat switch position (dpad)
|
||||
*
|
||||
* +---------------+
|
||||
* byte 0: |. . . . . . . .|
|
||||
* | | left stick x state (0-65535)
|
||||
* byte 1: |. . . . . . . .|
|
||||
* +---------------+
|
||||
* byte 2: |. . . . . . . .|
|
||||
* | | left stick y state (0-65535)
|
||||
* byte 3: |. . . . . . . .|
|
||||
* +---------------+
|
||||
* byte 4: |. . . . . . . .|
|
||||
* | | right stick x state (0-65535)
|
||||
* byte 5: |. . . . . . . .|
|
||||
* +---------------+
|
||||
* byte 6: |. . . . . . . .|
|
||||
* | | right stick y state (0-65535)
|
||||
* byte 7: |. . . . . . . .|
|
||||
* +---------------+
|
||||
* byte 8: |. . . . . . . .|
|
||||
* | | L2 trigger state (0-65535)
|
||||
* byte 9: |. . . . . . . .|
|
||||
* +---------------+
|
||||
* byte 10: |. . . . . . . .|
|
||||
* | | R2 trigger state (0-65535)
|
||||
* byte 11: |. . . . . . . .|
|
||||
* +---------------+
|
||||
*
|
||||
* ,--------------- SC_GAMEPAD_BUTTON_RIGHT_SHOULDER
|
||||
* | ,------------- SC_GAMEPAD_BUTTON_LEFT_SHOULDER
|
||||
* | |
|
||||
* | | ,--------- SC_GAMEPAD_BUTTON_NORTH
|
||||
* | | | ,------- SC_GAMEPAD_BUTTON_WEST
|
||||
* | | | |
|
||||
* | | | | ,--- SC_GAMEPAD_BUTTON_EAST
|
||||
* | | | | | ,- SC_GAMEPAD_BUTTON_SOUTH
|
||||
* v v v v v v
|
||||
* +---------------+
|
||||
* byte 12: |. . 0 . . 0 . .|
|
||||
* | | Buttons (16-bit little-endian)
|
||||
* byte 13: |0 . . . . . 0 0|
|
||||
* +---------------+
|
||||
* ^ ^ ^ ^ ^
|
||||
* | | | | |
|
||||
* | | | | |
|
||||
* | | | | `----- SC_GAMEPAD_BUTTON_BACK
|
||||
* | | | `------- SC_GAMEPAD_BUTTON_START
|
||||
* | | `--------- SC_GAMEPAD_BUTTON_GUIDE
|
||||
* | `----------- SC_GAMEPAD_BUTTON_LEFT_STICK
|
||||
* `------------- SC_GAMEPAD_BUTTON_RIGHT_STICK
|
||||
*
|
||||
* +---------------+
|
||||
* byte 14: |0 0 0 . . . . .| hat switch (dpad) position (0-8)
|
||||
* +---------------+
|
||||
* 9 possible positions and their values:
|
||||
* 8 1 2
|
||||
* 7 0 3
|
||||
* 6 5 4
|
||||
* (8 is top-left, 1 is top, 2 is top-right, etc.)
|
||||
*/
|
||||
|
||||
static void
|
||||
sc_hid_gamepad_slot_init(struct sc_hid_gamepad_slot *slot,
|
||||
uint32_t gamepad_id) {
|
||||
assert(gamepad_id != SC_GAMEPAD_ID_INVALID);
|
||||
slot->gamepad_id = gamepad_id;
|
||||
slot->buttons = 0;
|
||||
slot->axis_left_x = 0;
|
||||
slot->axis_left_y = 0;
|
||||
slot->axis_right_x = 0;
|
||||
slot->axis_right_y = 0;
|
||||
slot->axis_left_trigger = 0;
|
||||
slot->axis_right_trigger = 0;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
sc_hid_gamepad_slot_find(struct sc_hid_gamepad *hid, uint32_t gamepad_id) {
|
||||
for (size_t i = 0; i < SC_MAX_GAMEPADS; ++i) {
|
||||
if (gamepad_id == hid->slots[i].gamepad_id) {
|
||||
// found
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void
|
||||
sc_hid_gamepad_init(struct sc_hid_gamepad *hid) {
|
||||
for (size_t i = 0; i < SC_MAX_GAMEPADS; ++i) {
|
||||
hid->slots[i].gamepad_id = SC_GAMEPAD_ID_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint16_t
|
||||
sc_hid_gamepad_slot_get_id(size_t slot_idx) {
|
||||
assert(slot_idx < SC_MAX_GAMEPADS);
|
||||
return SC_HID_ID_GAMEPAD_FIRST + slot_idx;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_hid_gamepad_generate_open(struct sc_hid_gamepad *hid,
|
||||
struct sc_hid_open *hid_open,
|
||||
uint32_t gamepad_id) {
|
||||
assert(gamepad_id != SC_GAMEPAD_ID_INVALID);
|
||||
ssize_t slot_idx = sc_hid_gamepad_slot_find(hid, SC_GAMEPAD_ID_INVALID);
|
||||
if (slot_idx == -1) {
|
||||
LOGW("No gamepad slot available for new gamepad %" PRIu32, gamepad_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
sc_hid_gamepad_slot_init(&hid->slots[slot_idx], gamepad_id);
|
||||
|
||||
uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx);
|
||||
hid_open->hid_id = hid_id;
|
||||
hid_open->report_desc = SC_HID_GAMEPAD_REPORT_DESC;
|
||||
hid_open->report_desc_size = sizeof(SC_HID_GAMEPAD_REPORT_DESC);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_hid_gamepad_generate_close(struct sc_hid_gamepad *hid,
|
||||
struct sc_hid_close *hid_close,
|
||||
uint32_t gamepad_id) {
|
||||
assert(gamepad_id != SC_GAMEPAD_ID_INVALID);
|
||||
ssize_t slot_idx = sc_hid_gamepad_slot_find(hid, gamepad_id);
|
||||
if (slot_idx == -1) {
|
||||
LOGW("Unknown gamepad removed %" PRIu32, gamepad_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
hid->slots[slot_idx].gamepad_id = SC_GAMEPAD_ID_INVALID;
|
||||
|
||||
uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx);
|
||||
hid_close->hid_id = hid_id;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint8_t
|
||||
sc_hid_gamepad_get_dpad_value(uint32_t buttons) {
|
||||
// Value depending on direction:
|
||||
// 8 1 2
|
||||
// 7 0 3
|
||||
// 6 5 4
|
||||
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_UP) {
|
||||
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_LEFT) {
|
||||
return 8;
|
||||
}
|
||||
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_RIGHT) {
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_DOWN) {
|
||||
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_LEFT) {
|
||||
return 6;
|
||||
}
|
||||
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_RIGHT) {
|
||||
return 4;
|
||||
}
|
||||
return 5;
|
||||
}
|
||||
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_LEFT) {
|
||||
return 7;
|
||||
}
|
||||
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_RIGHT) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
sc_hid_gamepad_event_from_slot(uint16_t hid_id,
|
||||
const struct sc_hid_gamepad_slot *slot,
|
||||
struct sc_hid_input *hid_input) {
|
||||
hid_input->hid_id = hid_id;
|
||||
hid_input->size = SC_HID_GAMEPAD_EVENT_SIZE;
|
||||
|
||||
uint8_t *data = hid_input->data;
|
||||
// Values must be written in little-endian
|
||||
sc_write16le(data, slot->axis_left_x);
|
||||
sc_write16le(data + 2, slot->axis_left_y);
|
||||
sc_write16le(data + 4, slot->axis_right_x);
|
||||
sc_write16le(data + 6, slot->axis_right_y);
|
||||
sc_write16le(data + 8, slot->axis_left_trigger);
|
||||
sc_write16le(data + 10, slot->axis_right_trigger);
|
||||
sc_write16le(data + 12, slot->buttons & SC_HID_BUTTONS_MASK);
|
||||
data[14] = sc_hid_gamepad_get_dpad_value(slot->buttons);
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
sc_hid_gamepad_get_button_id(enum sc_gamepad_button button) {
|
||||
switch (button) {
|
||||
case SC_GAMEPAD_BUTTON_SOUTH:
|
||||
return 0x0001;
|
||||
case SC_GAMEPAD_BUTTON_EAST:
|
||||
return 0x0002;
|
||||
case SC_GAMEPAD_BUTTON_WEST:
|
||||
return 0x0008;
|
||||
case SC_GAMEPAD_BUTTON_NORTH:
|
||||
return 0x0010;
|
||||
case SC_GAMEPAD_BUTTON_BACK:
|
||||
return 0x0400;
|
||||
case SC_GAMEPAD_BUTTON_GUIDE:
|
||||
return 0x1000;
|
||||
case SC_GAMEPAD_BUTTON_START:
|
||||
return 0x0800;
|
||||
case SC_GAMEPAD_BUTTON_LEFT_STICK:
|
||||
return 0x2000;
|
||||
case SC_GAMEPAD_BUTTON_RIGHT_STICK:
|
||||
return 0x4000;
|
||||
case SC_GAMEPAD_BUTTON_LEFT_SHOULDER:
|
||||
return 0x0040;
|
||||
case SC_GAMEPAD_BUTTON_RIGHT_SHOULDER:
|
||||
return 0x0080;
|
||||
case SC_GAMEPAD_BUTTON_DPAD_UP:
|
||||
return SC_GAMEPAD_BUTTONS_BIT_DPAD_UP;
|
||||
case SC_GAMEPAD_BUTTON_DPAD_DOWN:
|
||||
return SC_GAMEPAD_BUTTONS_BIT_DPAD_DOWN;
|
||||
case SC_GAMEPAD_BUTTON_DPAD_LEFT:
|
||||
return SC_GAMEPAD_BUTTONS_BIT_DPAD_LEFT;
|
||||
case SC_GAMEPAD_BUTTON_DPAD_RIGHT:
|
||||
return SC_GAMEPAD_BUTTONS_BIT_DPAD_RIGHT;
|
||||
default:
|
||||
// unknown button, ignore
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
sc_hid_gamepad_generate_input_from_button(struct sc_hid_gamepad *hid,
|
||||
struct sc_hid_input *hid_input,
|
||||
const struct sc_gamepad_button_event *event) {
|
||||
if ((event->button < 0) || (event->button > 15)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t gamepad_id = event->gamepad_id;
|
||||
|
||||
ssize_t slot_idx = sc_hid_gamepad_slot_find(hid, gamepad_id);
|
||||
if (slot_idx == -1) {
|
||||
LOGW("Axis event for unknown gamepad %" PRIu32, gamepad_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(slot_idx < SC_MAX_GAMEPADS);
|
||||
|
||||
struct sc_hid_gamepad_slot *slot = &hid->slots[slot_idx];
|
||||
|
||||
uint32_t button = sc_hid_gamepad_get_button_id(event->button);
|
||||
if (!button) {
|
||||
// unknown button, ignore
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event->action == SC_ACTION_DOWN) {
|
||||
slot->buttons |= button;
|
||||
} else {
|
||||
assert(event->action == SC_ACTION_UP);
|
||||
slot->buttons &= ~button;
|
||||
}
|
||||
|
||||
uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx);
|
||||
sc_hid_gamepad_event_from_slot(hid_id, slot, hid_input);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_hid_gamepad_generate_input_from_axis(struct sc_hid_gamepad *hid,
|
||||
struct sc_hid_input *hid_input,
|
||||
const struct sc_gamepad_axis_event *event) {
|
||||
uint32_t gamepad_id = event->gamepad_id;
|
||||
|
||||
ssize_t slot_idx = sc_hid_gamepad_slot_find(hid, gamepad_id);
|
||||
if (slot_idx == -1) {
|
||||
LOGW("Button event for unknown gamepad %" PRIu32, gamepad_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(slot_idx < SC_MAX_GAMEPADS);
|
||||
|
||||
struct sc_hid_gamepad_slot *slot = &hid->slots[slot_idx];
|
||||
|
||||
// [-32768 to 32767] -> [0 to 65535]
|
||||
#define AXIS_RESCALE(V) (uint16_t) (((int32_t) V) + 0x8000)
|
||||
switch (event->axis) {
|
||||
case SC_GAMEPAD_AXIS_LEFTX:
|
||||
slot->axis_left_x = AXIS_RESCALE(event->value);
|
||||
break;
|
||||
case SC_GAMEPAD_AXIS_LEFTY:
|
||||
slot->axis_left_y = AXIS_RESCALE(event->value);
|
||||
break;
|
||||
case SC_GAMEPAD_AXIS_RIGHTX:
|
||||
slot->axis_right_x = AXIS_RESCALE(event->value);
|
||||
break;
|
||||
case SC_GAMEPAD_AXIS_RIGHTY:
|
||||
slot->axis_right_y = AXIS_RESCALE(event->value);
|
||||
break;
|
||||
case SC_GAMEPAD_AXIS_LEFT_TRIGGER:
|
||||
// Trigger is always positive between 0 and 32767
|
||||
slot->axis_left_trigger = MAX(0, event->value);
|
||||
break;
|
||||
case SC_GAMEPAD_AXIS_RIGHT_TRIGGER:
|
||||
// Trigger is always positive between 0 and 32767
|
||||
slot->axis_right_trigger = MAX(0, event->value);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx);
|
||||
sc_hid_gamepad_event_from_slot(hid_id, slot, hid_input);
|
||||
|
||||
return true;
|
||||
}
|
53
app/src/hid/hid_gamepad.h
Normal file
53
app/src/hid/hid_gamepad.h
Normal file
@ -0,0 +1,53 @@
|
||||
#ifndef SC_HID_GAMEPAD_H
|
||||
#define SC_HID_GAMEPAD_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "hid/hid_event.h"
|
||||
#include "input_events.h"
|
||||
|
||||
#define SC_MAX_GAMEPADS 8
|
||||
#define SC_HID_ID_GAMEPAD_FIRST 2
|
||||
#define SC_HID_ID_GAMEPAD_LAST (SC_HID_ID_GAMEPAD_FIRST + SC_MAX_GAMEPADS - 1)
|
||||
|
||||
struct sc_hid_gamepad_slot {
|
||||
uint32_t gamepad_id;
|
||||
uint32_t buttons;
|
||||
uint16_t axis_left_x;
|
||||
uint16_t axis_left_y;
|
||||
uint16_t axis_right_x;
|
||||
uint16_t axis_right_y;
|
||||
uint16_t axis_left_trigger;
|
||||
uint16_t axis_right_trigger;
|
||||
};
|
||||
|
||||
struct sc_hid_gamepad {
|
||||
struct sc_hid_gamepad_slot slots[SC_MAX_GAMEPADS];
|
||||
};
|
||||
|
||||
void
|
||||
sc_hid_gamepad_init(struct sc_hid_gamepad *hid);
|
||||
|
||||
bool
|
||||
sc_hid_gamepad_generate_open(struct sc_hid_gamepad *hid,
|
||||
struct sc_hid_open *hid_open,
|
||||
uint32_t gamepad_id);
|
||||
|
||||
bool
|
||||
sc_hid_gamepad_generate_close(struct sc_hid_gamepad *hid,
|
||||
struct sc_hid_close *hid_close,
|
||||
uint32_t gamepad_id);
|
||||
|
||||
bool
|
||||
sc_hid_gamepad_generate_input_from_button(struct sc_hid_gamepad *hid,
|
||||
struct sc_hid_input *hid_input,
|
||||
const struct sc_gamepad_button_event *event);
|
||||
|
||||
bool
|
||||
sc_hid_gamepad_generate_input_from_axis(struct sc_hid_gamepad *hid,
|
||||
struct sc_hid_input *hid_input,
|
||||
const struct sc_gamepad_axis_event *event);
|
||||
|
||||
#endif
|
@ -21,7 +21,7 @@
|
||||
// keyboard support, though OS could support more keys via modifying the report
|
||||
// desc. 6 should be enough for scrcpy.
|
||||
#define SC_HID_KEYBOARD_MAX_KEYS 6
|
||||
#define SC_HID_KEYBOARD_EVENT_SIZE \
|
||||
#define SC_HID_KEYBOARD_INPUT_SIZE \
|
||||
(SC_HID_KEYBOARD_INDEX_KEYS + SC_HID_KEYBOARD_MAX_KEYS)
|
||||
|
||||
#define SC_HID_RESERVED 0x00
|
||||
@ -31,13 +31,16 @@
|
||||
* For HID, only report descriptor is needed.
|
||||
*
|
||||
* The specification is available here:
|
||||
* <https://www.usb.org/sites/default/files/hid1_11.pdf>
|
||||
* <https://www.usb.org/document-library/device-class-definition-hid-111>
|
||||
*
|
||||
* In particular, read:
|
||||
* - 6.2.2 Report Descriptor
|
||||
* - §6.2.2 Report Descriptor
|
||||
* - Appendix B.1 Protocol 1 (Keyboard)
|
||||
* - Appendix C: Keyboard Implementation
|
||||
*
|
||||
* The HID Usage Tables is also useful:
|
||||
* <https://www.usb.org/document-library/hid-usage-tables-15>
|
||||
*
|
||||
* Normally a basic HID keyboard uses 8 bytes:
|
||||
* Modifier Reserved Key Key Key Key Key Key
|
||||
*
|
||||
@ -47,7 +50,7 @@
|
||||
*
|
||||
* (change vid:pid' to your device's vendor ID and product ID).
|
||||
*/
|
||||
const uint8_t SC_HID_KEYBOARD_REPORT_DESC[] = {
|
||||
static const uint8_t SC_HID_KEYBOARD_REPORT_DESC[] = {
|
||||
// Usage Page (Generic Desktop)
|
||||
0x05, 0x01,
|
||||
// Usage (Keyboard)
|
||||
@ -121,9 +124,6 @@ const uint8_t SC_HID_KEYBOARD_REPORT_DESC[] = {
|
||||
0xC0
|
||||
};
|
||||
|
||||
const size_t SC_HID_KEYBOARD_REPORT_DESC_LEN =
|
||||
sizeof(SC_HID_KEYBOARD_REPORT_DESC);
|
||||
|
||||
/**
|
||||
* A keyboard HID event is 8 bytes long:
|
||||
*
|
||||
@ -199,10 +199,11 @@ const size_t SC_HID_KEYBOARD_REPORT_DESC_LEN =
|
||||
*/
|
||||
|
||||
static void
|
||||
sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) {
|
||||
hid_event->size = SC_HID_KEYBOARD_EVENT_SIZE;
|
||||
sc_hid_keyboard_input_init(struct sc_hid_input *hid_input) {
|
||||
hid_input->hid_id = SC_HID_ID_KEYBOARD;
|
||||
hid_input->size = SC_HID_KEYBOARD_INPUT_SIZE;
|
||||
|
||||
uint8_t *data = hid_event->data;
|
||||
uint8_t *data = hid_input->data;
|
||||
|
||||
data[SC_HID_KEYBOARD_INDEX_MODS] = SC_HID_MOD_NONE;
|
||||
data[1] = SC_HID_RESERVED;
|
||||
@ -250,8 +251,8 @@ scancode_is_modifier(enum sc_scancode scancode) {
|
||||
}
|
||||
|
||||
bool
|
||||
sc_hid_keyboard_event_from_key(struct sc_hid_keyboard *hid,
|
||||
struct sc_hid_event *hid_event,
|
||||
sc_hid_keyboard_generate_input_from_key(struct sc_hid_keyboard *hid,
|
||||
struct sc_hid_input *hid_input,
|
||||
const struct sc_key_event *event) {
|
||||
enum sc_scancode scancode = event->scancode;
|
||||
assert(scancode >= 0);
|
||||
@ -264,7 +265,7 @@ sc_hid_keyboard_event_from_key(struct sc_hid_keyboard *hid,
|
||||
return false;
|
||||
}
|
||||
|
||||
sc_hid_keyboard_event_init(hid_event);
|
||||
sc_hid_keyboard_input_init(hid_input);
|
||||
|
||||
uint16_t mods = sc_hid_mod_from_sdl_keymod(event->mods_state);
|
||||
|
||||
@ -275,9 +276,9 @@ sc_hid_keyboard_event_from_key(struct sc_hid_keyboard *hid,
|
||||
hid->keys[scancode] ? "true" : "false");
|
||||
}
|
||||
|
||||
hid_event->data[SC_HID_KEYBOARD_INDEX_MODS] = mods;
|
||||
hid_input->data[SC_HID_KEYBOARD_INDEX_MODS] = mods;
|
||||
|
||||
uint8_t *keys_data = &hid_event->data[SC_HID_KEYBOARD_INDEX_KEYS];
|
||||
uint8_t *keys_data = &hid_input->data[SC_HID_KEYBOARD_INDEX_KEYS];
|
||||
// Re-calculate pressed keys every time
|
||||
int keys_pressed_count = 0;
|
||||
for (int i = 0; i < SC_HID_KEYBOARD_KEYS; ++i) {
|
||||
@ -308,7 +309,7 @@ end:
|
||||
}
|
||||
|
||||
bool
|
||||
sc_hid_keyboard_event_from_mods(struct sc_hid_event *event,
|
||||
sc_hid_keyboard_generate_input_from_mods(struct sc_hid_input *hid_input,
|
||||
uint16_t mods_state) {
|
||||
bool capslock = mods_state & SC_MOD_CAPS;
|
||||
bool numlock = mods_state & SC_MOD_NUM;
|
||||
@ -317,17 +318,27 @@ sc_hid_keyboard_event_from_mods(struct sc_hid_event *event,
|
||||
return false;
|
||||
}
|
||||
|
||||
sc_hid_keyboard_event_init(event);
|
||||
sc_hid_keyboard_input_init(hid_input);
|
||||
|
||||
unsigned i = 0;
|
||||
if (capslock) {
|
||||
event->data[SC_HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK;
|
||||
hid_input->data[SC_HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK;
|
||||
++i;
|
||||
}
|
||||
if (numlock) {
|
||||
event->data[SC_HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_NUMLOCK;
|
||||
hid_input->data[SC_HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_NUMLOCK;
|
||||
++i;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void sc_hid_keyboard_generate_open(struct sc_hid_open *hid_open) {
|
||||
hid_open->hid_id = SC_HID_ID_KEYBOARD;
|
||||
hid_open->report_desc = SC_HID_KEYBOARD_REPORT_DESC;
|
||||
hid_open->report_desc_size = sizeof(SC_HID_KEYBOARD_REPORT_DESC);
|
||||
}
|
||||
|
||||
void sc_hid_keyboard_generate_close(struct sc_hid_close *hid_close) {
|
||||
hid_close->hid_id = SC_HID_ID_KEYBOARD;
|
||||
}
|
||||
|
@ -14,8 +14,7 @@
|
||||
// 0x65 is Application, typically AT-101 Keyboard ends here.
|
||||
#define SC_HID_KEYBOARD_KEYS 0x66
|
||||
|
||||
extern const uint8_t SC_HID_KEYBOARD_REPORT_DESC[];
|
||||
extern const size_t SC_HID_KEYBOARD_REPORT_DESC_LEN;
|
||||
#define SC_HID_ID_KEYBOARD 1
|
||||
|
||||
/**
|
||||
* HID keyboard events are sequence-based, every time keyboard state changes
|
||||
@ -36,13 +35,19 @@ struct sc_hid_keyboard {
|
||||
void
|
||||
sc_hid_keyboard_init(struct sc_hid_keyboard *hid);
|
||||
|
||||
void
|
||||
sc_hid_keyboard_generate_open(struct sc_hid_open *hid_open);
|
||||
|
||||
void
|
||||
sc_hid_keyboard_generate_close(struct sc_hid_close *hid_close);
|
||||
|
||||
bool
|
||||
sc_hid_keyboard_event_from_key(struct sc_hid_keyboard *hid,
|
||||
struct sc_hid_event *hid_event,
|
||||
sc_hid_keyboard_generate_input_from_key(struct sc_hid_keyboard *hid,
|
||||
struct sc_hid_input *hid_input,
|
||||
const struct sc_key_event *event);
|
||||
|
||||
bool
|
||||
sc_hid_keyboard_event_from_mods(struct sc_hid_event *event,
|
||||
sc_hid_keyboard_generate_input_from_mods(struct sc_hid_input *hid_input,
|
||||
uint16_t mods_state);
|
||||
|
||||
#endif
|
||||
|
@ -2,17 +2,17 @@
|
||||
|
||||
// 1 byte for buttons + padding, 1 byte for X position, 1 byte for Y position,
|
||||
// 1 byte for wheel motion
|
||||
#define HID_MOUSE_EVENT_SIZE 4
|
||||
#define SC_HID_MOUSE_INPUT_SIZE 4
|
||||
|
||||
/**
|
||||
* Mouse descriptor from the specification:
|
||||
* <https://www.usb.org/sites/default/files/hid1_11.pdf>
|
||||
* <https://www.usb.org/document-library/device-class-definition-hid-111>
|
||||
*
|
||||
* Appendix E (p71): §E.10 Report Descriptor (Mouse)
|
||||
*
|
||||
* The usage tags (like Wheel) are listed in "HID Usage Tables":
|
||||
* <https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf>
|
||||
* §4 Generic Desktop Page (0x01) (p26)
|
||||
* <https://www.usb.org/document-library/hid-usage-tables-15>
|
||||
* §4 Generic Desktop Page (0x01) (p32)
|
||||
*/
|
||||
const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
|
||||
// Usage Page (Generic Desktop)
|
||||
@ -62,9 +62,9 @@ const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
|
||||
0x09, 0x31,
|
||||
// Usage (Wheel)
|
||||
0x09, 0x38,
|
||||
// Local Minimum (-127)
|
||||
// Logical Minimum (-127)
|
||||
0x15, 0x81,
|
||||
// Local Maximum (127)
|
||||
// Logical Maximum (127)
|
||||
0x25, 0x7F,
|
||||
// Report Size (8)
|
||||
0x75, 0x08,
|
||||
@ -80,9 +80,6 @@ const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
|
||||
0xC0,
|
||||
};
|
||||
|
||||
const size_t SC_HID_MOUSE_REPORT_DESC_LEN =
|
||||
sizeof(SC_HID_MOUSE_REPORT_DESC);
|
||||
|
||||
/**
|
||||
* A mouse HID event is 4 bytes long:
|
||||
*
|
||||
@ -125,10 +122,10 @@ const size_t SC_HID_MOUSE_REPORT_DESC_LEN =
|
||||
*/
|
||||
|
||||
static void
|
||||
sc_hid_mouse_event_init(struct sc_hid_event *hid_event) {
|
||||
hid_event->size = HID_MOUSE_EVENT_SIZE;
|
||||
// Leave hid_event->data uninitialized, it will be fully initialized by
|
||||
// callers
|
||||
sc_hid_mouse_input_init(struct sc_hid_input *hid_input) {
|
||||
hid_input->hid_id = SC_HID_ID_MOUSE;
|
||||
hid_input->size = SC_HID_MOUSE_INPUT_SIZE;
|
||||
// Leave ->data uninitialized, it will be fully initialized by callers
|
||||
}
|
||||
|
||||
static uint8_t
|
||||
@ -153,11 +150,11 @@ sc_hid_buttons_from_buttons_state(uint8_t buttons_state) {
|
||||
}
|
||||
|
||||
void
|
||||
sc_hid_mouse_event_from_motion(struct sc_hid_event *hid_event,
|
||||
sc_hid_mouse_generate_input_from_motion(struct sc_hid_input *hid_input,
|
||||
const struct sc_mouse_motion_event *event) {
|
||||
sc_hid_mouse_event_init(hid_event);
|
||||
sc_hid_mouse_input_init(hid_input);
|
||||
|
||||
uint8_t *data = hid_event->data;
|
||||
uint8_t *data = hid_input->data;
|
||||
data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state);
|
||||
data[1] = CLAMP(event->xrel, -127, 127);
|
||||
data[2] = CLAMP(event->yrel, -127, 127);
|
||||
@ -165,11 +162,11 @@ sc_hid_mouse_event_from_motion(struct sc_hid_event *hid_event,
|
||||
}
|
||||
|
||||
void
|
||||
sc_hid_mouse_event_from_click(struct sc_hid_event *hid_event,
|
||||
sc_hid_mouse_generate_input_from_click(struct sc_hid_input *hid_input,
|
||||
const struct sc_mouse_click_event *event) {
|
||||
sc_hid_mouse_event_init(hid_event);
|
||||
sc_hid_mouse_input_init(hid_input);
|
||||
|
||||
uint8_t *data = hid_event->data;
|
||||
uint8_t *data = hid_input->data;
|
||||
data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state);
|
||||
data[1] = 0; // no x motion
|
||||
data[2] = 0; // no y motion
|
||||
@ -177,11 +174,11 @@ sc_hid_mouse_event_from_click(struct sc_hid_event *hid_event,
|
||||
}
|
||||
|
||||
void
|
||||
sc_hid_mouse_event_from_scroll(struct sc_hid_event *hid_event,
|
||||
sc_hid_mouse_generate_input_from_scroll(struct sc_hid_input *hid_input,
|
||||
const struct sc_mouse_scroll_event *event) {
|
||||
sc_hid_mouse_event_init(hid_event);
|
||||
sc_hid_mouse_input_init(hid_input);
|
||||
|
||||
uint8_t *data = hid_event->data;
|
||||
uint8_t *data = hid_input->data;
|
||||
data[0] = 0; // buttons state irrelevant (and unknown)
|
||||
data[1] = 0; // no x motion
|
||||
data[2] = 0; // no y motion
|
||||
@ -190,3 +187,13 @@ sc_hid_mouse_event_from_scroll(struct sc_hid_event *hid_event,
|
||||
data[3] = CLAMP(event->vscroll, -127, 127);
|
||||
// Horizontal scrolling ignored
|
||||
}
|
||||
|
||||
void sc_hid_mouse_generate_open(struct sc_hid_open *hid_open) {
|
||||
hid_open->hid_id = SC_HID_ID_MOUSE;
|
||||
hid_open->report_desc = SC_HID_MOUSE_REPORT_DESC;
|
||||
hid_open->report_desc_size = sizeof(SC_HID_MOUSE_REPORT_DESC);
|
||||
}
|
||||
|
||||
void sc_hid_mouse_generate_close(struct sc_hid_close *hid_close) {
|
||||
hid_close->hid_id = SC_HID_ID_MOUSE;
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
#ifndef SC_HID_MOUSE_H
|
||||
#define SC_HID_MOUSE_H
|
||||
|
||||
#endif
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
@ -10,17 +8,24 @@
|
||||
#include "hid/hid_event.h"
|
||||
#include "input_events.h"
|
||||
|
||||
extern const uint8_t SC_HID_MOUSE_REPORT_DESC[];
|
||||
extern const size_t SC_HID_MOUSE_REPORT_DESC_LEN;
|
||||
#define SC_HID_ID_MOUSE 2
|
||||
|
||||
void
|
||||
sc_hid_mouse_event_from_motion(struct sc_hid_event *hid_event,
|
||||
sc_hid_mouse_generate_open(struct sc_hid_open *hid_open);
|
||||
|
||||
void
|
||||
sc_hid_mouse_generate_close(struct sc_hid_close *hid_close);
|
||||
|
||||
void
|
||||
sc_hid_mouse_generate_input_from_motion(struct sc_hid_input *hid_input,
|
||||
const struct sc_mouse_motion_event *event);
|
||||
|
||||
void
|
||||
sc_hid_mouse_event_from_click(struct sc_hid_event *hid_event,
|
||||
sc_hid_mouse_generate_input_from_click(struct sc_hid_input *hid_input,
|
||||
const struct sc_mouse_click_event *event);
|
||||
|
||||
void
|
||||
sc_hid_mouse_event_from_scroll(struct sc_hid_event *hid_event,
|
||||
sc_hid_mouse_generate_input_from_scroll(struct sc_hid_input *hid_input,
|
||||
const struct sc_mouse_scroll_event *event);
|
||||
|
||||
#endif
|
||||
|
@ -323,6 +323,38 @@ enum sc_mouse_button {
|
||||
SC_MOUSE_BUTTON_X2 = SDL_BUTTON(SDL_BUTTON_X2),
|
||||
};
|
||||
|
||||
// Use the naming from SDL3 for gamepad axis and buttons:
|
||||
// <https://wiki.libsdl.org/SDL3/README/migration>
|
||||
|
||||
enum sc_gamepad_axis {
|
||||
SC_GAMEPAD_AXIS_UNKNOWN = -1,
|
||||
SC_GAMEPAD_AXIS_LEFTX = SDL_CONTROLLER_AXIS_LEFTX,
|
||||
SC_GAMEPAD_AXIS_LEFTY = SDL_CONTROLLER_AXIS_LEFTY,
|
||||
SC_GAMEPAD_AXIS_RIGHTX = SDL_CONTROLLER_AXIS_RIGHTX,
|
||||
SC_GAMEPAD_AXIS_RIGHTY = SDL_CONTROLLER_AXIS_RIGHTY,
|
||||
SC_GAMEPAD_AXIS_LEFT_TRIGGER = SDL_CONTROLLER_AXIS_TRIGGERLEFT,
|
||||
SC_GAMEPAD_AXIS_RIGHT_TRIGGER = SDL_CONTROLLER_AXIS_TRIGGERRIGHT,
|
||||
};
|
||||
|
||||
enum sc_gamepad_button {
|
||||
SC_GAMEPAD_BUTTON_UNKNOWN = -1,
|
||||
SC_GAMEPAD_BUTTON_SOUTH = SDL_CONTROLLER_BUTTON_A,
|
||||
SC_GAMEPAD_BUTTON_EAST = SDL_CONTROLLER_BUTTON_B,
|
||||
SC_GAMEPAD_BUTTON_WEST = SDL_CONTROLLER_BUTTON_X,
|
||||
SC_GAMEPAD_BUTTON_NORTH = SDL_CONTROLLER_BUTTON_Y,
|
||||
SC_GAMEPAD_BUTTON_BACK = SDL_CONTROLLER_BUTTON_BACK,
|
||||
SC_GAMEPAD_BUTTON_GUIDE = SDL_CONTROLLER_BUTTON_GUIDE,
|
||||
SC_GAMEPAD_BUTTON_START = SDL_CONTROLLER_BUTTON_START,
|
||||
SC_GAMEPAD_BUTTON_LEFT_STICK = SDL_CONTROLLER_BUTTON_LEFTSTICK,
|
||||
SC_GAMEPAD_BUTTON_RIGHT_STICK = SDL_CONTROLLER_BUTTON_RIGHTSTICK,
|
||||
SC_GAMEPAD_BUTTON_LEFT_SHOULDER = SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
|
||||
SC_GAMEPAD_BUTTON_RIGHT_SHOULDER = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
|
||||
SC_GAMEPAD_BUTTON_DPAD_UP = SDL_CONTROLLER_BUTTON_DPAD_UP,
|
||||
SC_GAMEPAD_BUTTON_DPAD_DOWN = SDL_CONTROLLER_BUTTON_DPAD_DOWN,
|
||||
SC_GAMEPAD_BUTTON_DPAD_LEFT = SDL_CONTROLLER_BUTTON_DPAD_LEFT,
|
||||
SC_GAMEPAD_BUTTON_DPAD_RIGHT = SDL_CONTROLLER_BUTTON_DPAD_RIGHT,
|
||||
};
|
||||
|
||||
static_assert(sizeof(enum sc_mod) >= sizeof(SDL_Keymod),
|
||||
"SDL_Keymod must be convertible to sc_mod");
|
||||
|
||||
@ -380,6 +412,33 @@ struct sc_touch_event {
|
||||
float pressure;
|
||||
};
|
||||
|
||||
enum sc_gamepad_device_event_type {
|
||||
SC_GAMEPAD_DEVICE_ADDED,
|
||||
SC_GAMEPAD_DEVICE_REMOVED,
|
||||
};
|
||||
|
||||
// As documented in <https://wiki.libsdl.org/SDL2/SDL_JoystickID>:
|
||||
// The ID value starts at 0 and increments from there. The value -1 is an
|
||||
// invalid ID.
|
||||
#define SC_GAMEPAD_ID_INVALID UINT32_C(-1)
|
||||
|
||||
struct sc_gamepad_device_event {
|
||||
enum sc_gamepad_device_event_type type;
|
||||
uint32_t gamepad_id;
|
||||
};
|
||||
|
||||
struct sc_gamepad_button_event {
|
||||
uint32_t gamepad_id;
|
||||
enum sc_action action;
|
||||
enum sc_gamepad_button button;
|
||||
};
|
||||
|
||||
struct sc_gamepad_axis_event {
|
||||
uint32_t gamepad_id;
|
||||
enum sc_gamepad_axis axis;
|
||||
int16_t value;
|
||||
};
|
||||
|
||||
static inline uint16_t
|
||||
sc_mods_state_from_sdl(uint16_t mods_state) {
|
||||
return mods_state;
|
||||
@ -444,4 +503,43 @@ sc_mouse_buttons_state_from_sdl(uint32_t buttons_state) {
|
||||
return buttons_state;
|
||||
}
|
||||
|
||||
static inline enum sc_gamepad_device_event_type
|
||||
sc_gamepad_device_event_type_from_sdl_type(uint32_t type) {
|
||||
assert(type == SDL_CONTROLLERDEVICEADDED
|
||||
|| type == SDL_CONTROLLERDEVICEREMOVED);
|
||||
if (type == SDL_CONTROLLERDEVICEADDED) {
|
||||
return SC_GAMEPAD_DEVICE_ADDED;
|
||||
}
|
||||
return SC_GAMEPAD_DEVICE_REMOVED;
|
||||
}
|
||||
|
||||
static inline enum sc_gamepad_axis
|
||||
sc_gamepad_axis_from_sdl(uint8_t axis) {
|
||||
if (axis <= SDL_CONTROLLER_AXIS_TRIGGERRIGHT) {
|
||||
// SDL_GAMEPAD_AXIS_* constants are initialized from
|
||||
// SDL_CONTROLLER_AXIS_*
|
||||
return axis;
|
||||
}
|
||||
return SC_GAMEPAD_AXIS_UNKNOWN;
|
||||
}
|
||||
|
||||
static inline enum sc_gamepad_button
|
||||
sc_gamepad_button_from_sdl(uint8_t button) {
|
||||
if (button <= SDL_CONTROLLER_BUTTON_DPAD_RIGHT) {
|
||||
// SC_GAMEPAD_BUTTON_* constants are initialized from
|
||||
// SDL_CONTROLLER_BUTTON_*
|
||||
return button;
|
||||
}
|
||||
return SC_GAMEPAD_BUTTON_UNKNOWN;
|
||||
}
|
||||
|
||||
static inline enum sc_action
|
||||
sc_action_from_sdl_controllerbutton_type(uint32_t type) {
|
||||
assert(type == SDL_CONTROLLERBUTTONDOWN || type == SDL_CONTROLLERBUTTONUP);
|
||||
if (type == SDL_CONTROLLERBUTTONDOWN) {
|
||||
return SC_ACTION_DOWN;
|
||||
}
|
||||
return SC_ACTION_UP;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -56,16 +56,18 @@ void
|
||||
sc_input_manager_init(struct sc_input_manager *im,
|
||||
const struct sc_input_manager_params *params) {
|
||||
// A key/mouse processor may not be present if there is no controller
|
||||
assert((!params->kp && !params->mp) || params->controller);
|
||||
assert((!params->kp && !params->mp && !params->gp) || params->controller);
|
||||
// A processor must have ops initialized
|
||||
assert(!params->kp || params->kp->ops);
|
||||
assert(!params->mp || params->mp->ops);
|
||||
assert(!params->gp || params->gp->ops);
|
||||
|
||||
im->controller = params->controller;
|
||||
im->fp = params->fp;
|
||||
im->screen = params->screen;
|
||||
im->kp = params->kp;
|
||||
im->mp = params->mp;
|
||||
im->gp = params->gp;
|
||||
|
||||
im->mouse_bindings = params->mouse_bindings;
|
||||
im->legacy_paste = params->legacy_paste;
|
||||
@ -906,6 +908,68 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
|
||||
im->mp->ops->process_mouse_scroll(im->mp, &evt);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
|
||||
const SDL_ControllerDeviceEvent *event) {
|
||||
SDL_JoystickID id;
|
||||
if (event->type == SDL_CONTROLLERDEVICEADDED) {
|
||||
SDL_GameController *gc = SDL_GameControllerOpen(event->which);
|
||||
if (!gc) {
|
||||
LOGW("Could not open game controller");
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gc);
|
||||
if (!joystick) {
|
||||
LOGW("Could not get controller joystick");
|
||||
SDL_GameControllerClose(gc);
|
||||
return;
|
||||
}
|
||||
|
||||
id = SDL_JoystickInstanceID(joystick);
|
||||
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||
id = event->which;
|
||||
|
||||
SDL_GameController *gc = SDL_GameControllerFromInstanceID(id);
|
||||
if (gc) {
|
||||
SDL_GameControllerClose(gc);
|
||||
} else {
|
||||
LOGW("Unknown gamepad device removed");
|
||||
}
|
||||
} else {
|
||||
// Nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_gamepad_device_event evt = {
|
||||
.type = sc_gamepad_device_event_type_from_sdl_type(event->type),
|
||||
.gamepad_id = id,
|
||||
};
|
||||
im->gp->ops->process_gamepad_device(im->gp, &evt);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_input_manager_process_gamepad_axis(struct sc_input_manager *im,
|
||||
const SDL_ControllerAxisEvent *event) {
|
||||
struct sc_gamepad_axis_event evt = {
|
||||
.gamepad_id = event->which,
|
||||
.axis = sc_gamepad_axis_from_sdl(event->axis),
|
||||
.value = event->value,
|
||||
};
|
||||
im->gp->ops->process_gamepad_axis(im->gp, &evt);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_input_manager_process_gamepad_button(struct sc_input_manager *im,
|
||||
const SDL_ControllerButtonEvent *event) {
|
||||
struct sc_gamepad_button_event evt = {
|
||||
.gamepad_id = event->which,
|
||||
.action = sc_action_from_sdl_controllerbutton_type(event->type),
|
||||
.button = sc_gamepad_button_from_sdl(event->button),
|
||||
};
|
||||
im->gp->ops->process_gamepad_button(im->gp, &evt);
|
||||
}
|
||||
|
||||
static bool
|
||||
is_apk(const char *file) {
|
||||
const char *ext = strrchr(file, '.');
|
||||
@ -978,6 +1042,27 @@ sc_input_manager_handle_event(struct sc_input_manager *im,
|
||||
}
|
||||
sc_input_manager_process_touch(im, &event->tfinger);
|
||||
break;
|
||||
case SDL_CONTROLLERDEVICEADDED:
|
||||
case SDL_CONTROLLERDEVICEREMOVED:
|
||||
// Handle device added or removed even if paused
|
||||
if (!im->gp) {
|
||||
break;
|
||||
}
|
||||
sc_input_manager_process_gamepad_device(im, &event->cdevice);
|
||||
break;
|
||||
case SDL_CONTROLLERAXISMOTION:
|
||||
if (!im->gp || paused) {
|
||||
break;
|
||||
}
|
||||
sc_input_manager_process_gamepad_axis(im, &event->caxis);
|
||||
break;
|
||||
case SDL_CONTROLLERBUTTONDOWN:
|
||||
case SDL_CONTROLLERBUTTONUP:
|
||||
if (!im->gp || paused) {
|
||||
break;
|
||||
}
|
||||
sc_input_manager_process_gamepad_button(im, &event->cbutton);
|
||||
break;
|
||||
case SDL_DROPFILE: {
|
||||
if (!control) {
|
||||
break;
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "file_pusher.h"
|
||||
#include "fps_counter.h"
|
||||
#include "options.h"
|
||||
#include "trait/gamepad_processor.h"
|
||||
#include "trait/key_processor.h"
|
||||
#include "trait/mouse_processor.h"
|
||||
|
||||
@ -21,6 +22,7 @@ struct sc_input_manager {
|
||||
|
||||
struct sc_key_processor *kp;
|
||||
struct sc_mouse_processor *mp;
|
||||
struct sc_gamepad_processor *gp;
|
||||
|
||||
struct sc_mouse_bindings mouse_bindings;
|
||||
bool legacy_paste;
|
||||
@ -50,6 +52,7 @@ struct sc_input_manager_params {
|
||||
struct sc_screen *screen;
|
||||
struct sc_key_processor *kp;
|
||||
struct sc_mouse_processor *mp;
|
||||
struct sc_gamepad_processor *gp;
|
||||
|
||||
struct sc_mouse_bindings mouse_bindings;
|
||||
bool legacy_paste;
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "usb/scrcpy_otg.h"
|
||||
#include "util/log.h"
|
||||
#include "util/net.h"
|
||||
#include "util/thread.h"
|
||||
#include "version.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
@ -67,6 +68,9 @@ main_scrcpy(int argc, char *argv[]) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
// The current thread is the main thread
|
||||
SC_MAIN_THREAD_ID = sc_thread_get_id();
|
||||
|
||||
#ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL
|
||||
av_register_all();
|
||||
#endif
|
||||
|
@ -23,6 +23,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
.record_format = SC_RECORD_FORMAT_AUTO,
|
||||
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AUTO,
|
||||
.mouse_input_mode = SC_MOUSE_INPUT_MODE_AUTO,
|
||||
.gamepad_input_mode = SC_GAMEPAD_INPUT_MODE_AUTO,
|
||||
.mouse_bindings = {
|
||||
.pri = {
|
||||
.right_click = SC_MOUSE_BINDING_AUTO,
|
||||
|
@ -142,6 +142,7 @@ enum sc_lock_video_orientation {
|
||||
|
||||
enum sc_keyboard_input_mode {
|
||||
SC_KEYBOARD_INPUT_MODE_AUTO,
|
||||
SC_KEYBOARD_INPUT_MODE_UHID_OR_AOA, // normal vs otg mode
|
||||
SC_KEYBOARD_INPUT_MODE_DISABLED,
|
||||
SC_KEYBOARD_INPUT_MODE_SDK,
|
||||
SC_KEYBOARD_INPUT_MODE_UHID,
|
||||
@ -150,12 +151,21 @@ enum sc_keyboard_input_mode {
|
||||
|
||||
enum sc_mouse_input_mode {
|
||||
SC_MOUSE_INPUT_MODE_AUTO,
|
||||
SC_MOUSE_INPUT_MODE_UHID_OR_AOA, // normal vs otg mode
|
||||
SC_MOUSE_INPUT_MODE_DISABLED,
|
||||
SC_MOUSE_INPUT_MODE_SDK,
|
||||
SC_MOUSE_INPUT_MODE_UHID,
|
||||
SC_MOUSE_INPUT_MODE_AOA,
|
||||
};
|
||||
|
||||
enum sc_gamepad_input_mode {
|
||||
SC_GAMEPAD_INPUT_MODE_AUTO,
|
||||
SC_GAMEPAD_INPUT_MODE_UHID_OR_AOA, // normal vs otg mode
|
||||
SC_GAMEPAD_INPUT_MODE_DISABLED,
|
||||
SC_GAMEPAD_INPUT_MODE_UHID,
|
||||
SC_GAMEPAD_INPUT_MODE_AOA,
|
||||
};
|
||||
|
||||
enum sc_mouse_binding {
|
||||
SC_MOUSE_BINDING_AUTO,
|
||||
SC_MOUSE_BINDING_DISABLED,
|
||||
@ -231,6 +241,7 @@ struct scrcpy_options {
|
||||
enum sc_record_format record_format;
|
||||
enum sc_keyboard_input_mode keyboard_input_mode;
|
||||
enum sc_mouse_input_mode mouse_input_mode;
|
||||
enum sc_gamepad_input_mode gamepad_input_mode;
|
||||
struct sc_mouse_bindings mouse_bindings;
|
||||
enum sc_camera_facing camera_facing;
|
||||
struct sc_port_range port_range;
|
||||
|
@ -6,9 +6,17 @@
|
||||
#include <SDL2/SDL_clipboard.h>
|
||||
|
||||
#include "device_msg.h"
|
||||
#include "events.h"
|
||||
#include "util/log.h"
|
||||
#include "util/str.h"
|
||||
|
||||
struct sc_uhid_output_task_data {
|
||||
struct sc_uhid_devices *uhid_devices;
|
||||
uint16_t id;
|
||||
uint16_t size;
|
||||
uint8_t *data;
|
||||
};
|
||||
|
||||
bool
|
||||
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
|
||||
const struct sc_receiver_callbacks *cbs, void *cbs_userdata) {
|
||||
@ -34,19 +42,48 @@ sc_receiver_destroy(struct sc_receiver *receiver) {
|
||||
}
|
||||
|
||||
static void
|
||||
process_msg(struct sc_receiver *receiver, struct sc_device_msg *msg) {
|
||||
switch (msg->type) {
|
||||
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
||||
task_set_clipboard(void *userdata) {
|
||||
char *text = userdata;
|
||||
|
||||
char *current = SDL_GetClipboardText();
|
||||
bool same = current && !strcmp(current, msg->clipboard.text);
|
||||
bool same = current && !strcmp(current, text);
|
||||
SDL_free(current);
|
||||
if (same) {
|
||||
LOGD("Computer clipboard unchanged");
|
||||
free(text);
|
||||
return;
|
||||
}
|
||||
|
||||
LOGI("Device clipboard copied");
|
||||
SDL_SetClipboardText(msg->clipboard.text);
|
||||
SDL_SetClipboardText(text);
|
||||
free(text);
|
||||
}
|
||||
|
||||
static void
|
||||
task_uhid_output(void *userdata) {
|
||||
struct sc_uhid_output_task_data *data = userdata;
|
||||
|
||||
sc_uhid_devices_process_hid_output(data->uhid_devices, data->id, data->data,
|
||||
data->size);
|
||||
|
||||
free(data->data);
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void
|
||||
process_msg(struct sc_receiver *receiver, struct sc_device_msg *msg) {
|
||||
switch (msg->type) {
|
||||
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
||||
// Take ownership of the text (do not destroy the msg)
|
||||
char *text = msg->clipboard.text;
|
||||
|
||||
bool ok = sc_post_to_main_thread(task_set_clipboard, text);
|
||||
if (!ok) {
|
||||
LOGW("Could not post clipboard to main thread");
|
||||
free(text);
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case DEVICE_MSG_TYPE_ACK_CLIPBOARD:
|
||||
@ -64,6 +101,7 @@ process_msg(struct sc_receiver *receiver, struct sc_device_msg *msg) {
|
||||
}
|
||||
|
||||
sc_acksync_ack(receiver->acksync, msg->ack_clipboard.sequence);
|
||||
// No allocation to free in the msg
|
||||
break;
|
||||
case DEVICE_MSG_TYPE_UHID_OUTPUT:
|
||||
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
|
||||
@ -86,19 +124,33 @@ process_msg(struct sc_receiver *receiver, struct sc_device_msg *msg) {
|
||||
// Also check at runtime (do not trust the server)
|
||||
if (!receiver->uhid_devices) {
|
||||
LOGE("Received unexpected HID output message");
|
||||
sc_device_msg_destroy(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_uhid_receiver *uhid_receiver =
|
||||
sc_uhid_devices_get_receiver(receiver->uhid_devices,
|
||||
msg->uhid_output.id);
|
||||
if (uhid_receiver) {
|
||||
uhid_receiver->ops->process_output(uhid_receiver,
|
||||
msg->uhid_output.data,
|
||||
msg->uhid_output.size);
|
||||
} else {
|
||||
LOGW("No UHID receiver for id %" PRIu16, msg->uhid_output.id);
|
||||
struct sc_uhid_output_task_data *data = malloc(sizeof(*data));
|
||||
if (!data) {
|
||||
LOG_OOM();
|
||||
return;
|
||||
}
|
||||
|
||||
// It is guaranteed that these pointers will still be valid when
|
||||
// the main thread will process them (the main thread will stop
|
||||
// processing SC_EVENT_RUN_ON_MAIN_THREAD on exit, when everything
|
||||
// gets deinitialized)
|
||||
data->uhid_devices = receiver->uhid_devices;
|
||||
data->id = msg->uhid_output.id;
|
||||
data->data = msg->uhid_output.data; // take ownership
|
||||
data->size = msg->uhid_output.size;
|
||||
|
||||
bool ok = sc_post_to_main_thread(task_uhid_output, data);
|
||||
if (!ok) {
|
||||
LOGW("Could not post UHID output to main thread");
|
||||
free(data->data);
|
||||
free(data);
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -117,7 +169,7 @@ process_msgs(struct sc_receiver *receiver, const uint8_t *buf, size_t len) {
|
||||
}
|
||||
|
||||
process_msg(receiver, &msg);
|
||||
sc_device_msg_destroy(&msg);
|
||||
// the device msg must be destroyed by process_msg()
|
||||
|
||||
head += r;
|
||||
assert(head <= len);
|
||||
|
138
app/src/scrcpy.c
138
app/src/scrcpy.c
@ -25,10 +25,12 @@
|
||||
#include "recorder.h"
|
||||
#include "screen.h"
|
||||
#include "server.h"
|
||||
#include "uhid/gamepad_uhid.h"
|
||||
#include "uhid/keyboard_uhid.h"
|
||||
#include "uhid/mouse_uhid.h"
|
||||
#ifdef HAVE_USB
|
||||
# include "usb/aoa_hid.h"
|
||||
# include "usb/gamepad_aoa.h"
|
||||
# include "usb/keyboard_aoa.h"
|
||||
# include "usb/mouse_aoa.h"
|
||||
# include "usb/usb.h"
|
||||
@ -77,27 +79,21 @@ struct scrcpy {
|
||||
struct sc_mouse_uhid mouse_uhid;
|
||||
#ifdef HAVE_USB
|
||||
struct sc_mouse_aoa mouse_aoa;
|
||||
#endif
|
||||
};
|
||||
union {
|
||||
struct sc_gamepad_uhid gamepad_uhid;
|
||||
#ifdef HAVE_USB
|
||||
struct sc_gamepad_aoa gamepad_aoa;
|
||||
#endif
|
||||
};
|
||||
struct sc_timeout timeout;
|
||||
};
|
||||
|
||||
static inline void
|
||||
push_event(uint32_t type, const char *name) {
|
||||
SDL_Event event;
|
||||
event.type = type;
|
||||
int ret = SDL_PushEvent(&event);
|
||||
if (ret < 0) {
|
||||
LOGE("Could not post %s event: %s", name, SDL_GetError());
|
||||
// What could we do?
|
||||
}
|
||||
}
|
||||
#define PUSH_EVENT(TYPE) push_event(TYPE, # TYPE)
|
||||
|
||||
#ifdef _WIN32
|
||||
static BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
|
||||
if (ctrl_type == CTRL_C_EVENT) {
|
||||
PUSH_EVENT(SDL_QUIT);
|
||||
sc_push_event(SDL_QUIT);
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
@ -180,12 +176,20 @@ event_loop(struct scrcpy *s) {
|
||||
case SC_EVENT_RECORDER_ERROR:
|
||||
LOGE("Recorder error");
|
||||
return SCRCPY_EXIT_FAILURE;
|
||||
case SC_EVENT_AOA_OPEN_ERROR:
|
||||
LOGE("AOA open error");
|
||||
return SCRCPY_EXIT_FAILURE;
|
||||
case SC_EVENT_TIME_LIMIT_REACHED:
|
||||
LOGI("Time limit reached");
|
||||
return SCRCPY_EXIT_SUCCESS;
|
||||
case SDL_QUIT:
|
||||
LOGD("User requested to quit");
|
||||
return SCRCPY_EXIT_SUCCESS;
|
||||
case SC_EVENT_RUN_ON_MAIN_THREAD:
|
||||
sc_runnable_fn run = event.user.data1;
|
||||
void *userdata = event.user.data2;
|
||||
run(userdata);
|
||||
break;
|
||||
default:
|
||||
if (!sc_screen_handle_event(&s->screen, &event)) {
|
||||
return SCRCPY_EXIT_FAILURE;
|
||||
@ -196,6 +200,21 @@ event_loop(struct scrcpy *s) {
|
||||
return SCRCPY_EXIT_FAILURE;
|
||||
}
|
||||
|
||||
static void
|
||||
terminate_event_loop(void) {
|
||||
sc_reject_new_runnables();
|
||||
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
if (event.type == SC_EVENT_RUN_ON_MAIN_THREAD) {
|
||||
// Make sure all posted runnables are run, to avoid memory leaks
|
||||
sc_runnable_fn run = event.user.data1;
|
||||
void *userdata = event.user.data2;
|
||||
run(userdata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return true on success, false on error
|
||||
static bool
|
||||
await_for_server(bool *connected) {
|
||||
@ -230,7 +249,7 @@ sc_recorder_on_ended(struct sc_recorder *recorder, bool success,
|
||||
(void) userdata;
|
||||
|
||||
if (!success) {
|
||||
PUSH_EVENT(SC_EVENT_RECORDER_ERROR);
|
||||
sc_push_event(SC_EVENT_RECORDER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@ -244,9 +263,9 @@ sc_video_demuxer_on_ended(struct sc_demuxer *demuxer,
|
||||
assert(status != SC_DEMUXER_STATUS_DISABLED);
|
||||
|
||||
if (status == SC_DEMUXER_STATUS_EOS) {
|
||||
PUSH_EVENT(SC_EVENT_DEVICE_DISCONNECTED);
|
||||
sc_push_event(SC_EVENT_DEVICE_DISCONNECTED);
|
||||
} else {
|
||||
PUSH_EVENT(SC_EVENT_DEMUXER_ERROR);
|
||||
sc_push_event(SC_EVENT_DEMUXER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@ -260,11 +279,11 @@ sc_audio_demuxer_on_ended(struct sc_demuxer *demuxer,
|
||||
// Contrary to the video demuxer, keep mirroring if only the audio fails
|
||||
// (unless --require-audio is set).
|
||||
if (status == SC_DEMUXER_STATUS_EOS) {
|
||||
PUSH_EVENT(SC_EVENT_DEVICE_DISCONNECTED);
|
||||
sc_push_event(SC_EVENT_DEVICE_DISCONNECTED);
|
||||
} else if (status == SC_DEMUXER_STATUS_ERROR
|
||||
|| (status == SC_DEMUXER_STATUS_DISABLED
|
||||
&& options->require_audio)) {
|
||||
PUSH_EVENT(SC_EVENT_DEMUXER_ERROR);
|
||||
sc_push_event(SC_EVENT_DEMUXER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@ -277,9 +296,9 @@ sc_controller_on_ended(struct sc_controller *controller, bool error,
|
||||
(void) userdata;
|
||||
|
||||
if (error) {
|
||||
PUSH_EVENT(SC_EVENT_CONTROLLER_ERROR);
|
||||
sc_push_event(SC_EVENT_CONTROLLER_ERROR);
|
||||
} else {
|
||||
PUSH_EVENT(SC_EVENT_DEVICE_DISCONNECTED);
|
||||
sc_push_event(SC_EVENT_DEVICE_DISCONNECTED);
|
||||
}
|
||||
}
|
||||
|
||||
@ -288,7 +307,7 @@ sc_server_on_connection_failed(struct sc_server *server, void *userdata) {
|
||||
(void) server;
|
||||
(void) userdata;
|
||||
|
||||
PUSH_EVENT(SC_EVENT_SERVER_CONNECTION_FAILED);
|
||||
sc_push_event(SC_EVENT_SERVER_CONNECTION_FAILED);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -296,7 +315,7 @@ sc_server_on_connected(struct sc_server *server, void *userdata) {
|
||||
(void) server;
|
||||
(void) userdata;
|
||||
|
||||
PUSH_EVENT(SC_EVENT_SERVER_CONNECTED);
|
||||
sc_push_event(SC_EVENT_SERVER_CONNECTED);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -314,7 +333,7 @@ sc_timeout_on_timeout(struct sc_timeout *timeout, void *userdata) {
|
||||
(void) timeout;
|
||||
(void) userdata;
|
||||
|
||||
PUSH_EVENT(SC_EVENT_TIME_LIMIT_REACHED);
|
||||
sc_push_event(SC_EVENT_TIME_LIMIT_REACHED);
|
||||
}
|
||||
|
||||
// Generate a scrcpy id to differentiate multiple running scrcpy instances
|
||||
@ -326,6 +345,21 @@ scrcpy_generate_scid(void) {
|
||||
return sc_rand_u32(&rand) & 0x7FFFFFFF;
|
||||
}
|
||||
|
||||
static void
|
||||
init_sdl_gamepads(void) {
|
||||
// Trigger a SDL_CONTROLLERDEVICEADDED event for all gamepads already
|
||||
// connected
|
||||
int num_joysticks = SDL_NumJoysticks();
|
||||
for (int i = 0; i < num_joysticks; ++i) {
|
||||
if (SDL_IsGameController(i)) {
|
||||
SDL_Event event;
|
||||
event.cdevice.type = SDL_CONTROLLERDEVICEADDED;
|
||||
event.cdevice.which = i;
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum scrcpy_exit_code
|
||||
scrcpy(struct scrcpy_options *options) {
|
||||
static struct scrcpy scrcpy;
|
||||
@ -358,6 +392,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
bool aoa_hid_initialized = false;
|
||||
bool keyboard_aoa_initialized = false;
|
||||
bool mouse_aoa_initialized = false;
|
||||
bool gamepad_aoa_initialized = false;
|
||||
#endif
|
||||
bool controller_initialized = false;
|
||||
bool controller_started = false;
|
||||
@ -366,7 +401,6 @@ scrcpy(struct scrcpy_options *options) {
|
||||
bool timeout_started = false;
|
||||
|
||||
struct sc_acksync *acksync = NULL;
|
||||
struct sc_uhid_devices *uhid_devices = NULL;
|
||||
|
||||
uint32_t scid = scrcpy_generate_scid();
|
||||
|
||||
@ -473,6 +507,13 @@ scrcpy(struct scrcpy_options *options) {
|
||||
}
|
||||
}
|
||||
|
||||
if (options->gamepad_input_mode != SC_GAMEPAD_INPUT_MODE_DISABLED) {
|
||||
if (SDL_Init(SDL_INIT_GAMECONTROLLER)) {
|
||||
LOGE("Could not initialize SDL gamepad: %s", SDL_GetError());
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
sdl_configure(options->video_playback, options->disable_screensaver);
|
||||
|
||||
// Await for server without blocking Ctrl+C handling
|
||||
@ -570,6 +611,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
struct sc_controller *controller = NULL;
|
||||
struct sc_key_processor *kp = NULL;
|
||||
struct sc_mouse_processor *mp = NULL;
|
||||
struct sc_gamepad_processor *gp = NULL;
|
||||
|
||||
if (options->control) {
|
||||
static const struct sc_controller_callbacks controller_cbs = {
|
||||
@ -589,7 +631,9 @@ scrcpy(struct scrcpy_options *options) {
|
||||
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA;
|
||||
bool use_mouse_aoa =
|
||||
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA;
|
||||
if (use_keyboard_aoa || use_mouse_aoa) {
|
||||
bool use_gamepad_aoa =
|
||||
options->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_AOA;
|
||||
if (use_keyboard_aoa || use_mouse_aoa || use_gamepad_aoa) {
|
||||
bool ok = sc_acksync_init(&s->acksync);
|
||||
if (!ok) {
|
||||
goto end;
|
||||
@ -632,12 +676,15 @@ scrcpy(struct scrcpy_options *options) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
bool aoa_fail = false;
|
||||
if (use_keyboard_aoa) {
|
||||
if (sc_keyboard_aoa_init(&s->keyboard_aoa, &s->aoa)) {
|
||||
keyboard_aoa_initialized = true;
|
||||
kp = &s->keyboard_aoa.key_processor;
|
||||
} else {
|
||||
LOGE("Could not initialize HID keyboard");
|
||||
aoa_fail = true;
|
||||
goto aoa_complete;
|
||||
}
|
||||
}
|
||||
|
||||
@ -647,12 +694,19 @@ scrcpy(struct scrcpy_options *options) {
|
||||
mp = &s->mouse_aoa.mouse_processor;
|
||||
} else {
|
||||
LOGE("Could not initialized HID mouse");
|
||||
aoa_fail = true;
|
||||
goto aoa_complete;
|
||||
}
|
||||
}
|
||||
|
||||
bool need_aoa = keyboard_aoa_initialized || mouse_aoa_initialized;
|
||||
if (use_gamepad_aoa) {
|
||||
sc_gamepad_aoa_init(&s->gamepad_aoa, &s->aoa);
|
||||
gp = &s->gamepad_aoa.gamepad_processor;
|
||||
gamepad_aoa_initialized = true;
|
||||
}
|
||||
|
||||
if (!need_aoa || !sc_aoa_start(&s->aoa)) {
|
||||
aoa_complete:
|
||||
if (aoa_fail || !sc_aoa_start(&s->aoa)) {
|
||||
sc_acksync_destroy(&s->acksync);
|
||||
sc_usb_disconnect(&s->usb);
|
||||
sc_usb_destroy(&s->usb);
|
||||
@ -669,6 +723,9 @@ scrcpy(struct scrcpy_options *options) {
|
||||
assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_AOA);
|
||||
#endif
|
||||
|
||||
struct sc_keyboard_uhid *uhid_keyboard = NULL;
|
||||
struct sc_gamepad_uhid *uhid_gamepad = NULL;
|
||||
|
||||
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_SDK) {
|
||||
sc_keyboard_sdk_init(&s->keyboard_sdk, &s->controller,
|
||||
options->key_inject_mode,
|
||||
@ -676,14 +733,12 @@ scrcpy(struct scrcpy_options *options) {
|
||||
kp = &s->keyboard_sdk.key_processor;
|
||||
} else if (options->keyboard_input_mode
|
||||
== SC_KEYBOARD_INPUT_MODE_UHID) {
|
||||
sc_uhid_devices_init(&s->uhid_devices);
|
||||
bool ok = sc_keyboard_uhid_init(&s->keyboard_uhid, &s->controller,
|
||||
&s->uhid_devices);
|
||||
bool ok = sc_keyboard_uhid_init(&s->keyboard_uhid, &s->controller);
|
||||
if (!ok) {
|
||||
goto end;
|
||||
}
|
||||
uhid_devices = &s->uhid_devices;
|
||||
kp = &s->keyboard_uhid.key_processor;
|
||||
uhid_keyboard = &s->keyboard_uhid;
|
||||
}
|
||||
|
||||
if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) {
|
||||
@ -698,6 +753,17 @@ scrcpy(struct scrcpy_options *options) {
|
||||
mp = &s->mouse_uhid.mouse_processor;
|
||||
}
|
||||
|
||||
if (options->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_UHID) {
|
||||
sc_gamepad_uhid_init(&s->gamepad_uhid, &s->controller);
|
||||
gp = &s->gamepad_uhid.gamepad_processor;
|
||||
}
|
||||
|
||||
struct sc_uhid_devices *uhid_devices = NULL;
|
||||
if (uhid_keyboard || uhid_gamepad) {
|
||||
sc_uhid_devices_init(&s->uhid_devices, uhid_keyboard, uhid_gamepad);
|
||||
uhid_devices = &s->uhid_devices;
|
||||
}
|
||||
|
||||
sc_controller_configure(&s->controller, acksync, uhid_devices);
|
||||
|
||||
if (!sc_controller_start(&s->controller)) {
|
||||
@ -719,6 +785,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
.fp = fp,
|
||||
.kp = kp,
|
||||
.mp = mp,
|
||||
.gp = gp,
|
||||
.mouse_bindings = options->mouse_bindings,
|
||||
.legacy_paste = options->legacy_paste,
|
||||
.clipboard_autosync = options->clipboard_autosync,
|
||||
@ -830,7 +897,13 @@ scrcpy(struct scrcpy_options *options) {
|
||||
timeout_started = true;
|
||||
}
|
||||
|
||||
bool use_gamepads = true;
|
||||
if (use_gamepads) {
|
||||
init_sdl_gamepads();
|
||||
}
|
||||
|
||||
ret = event_loop(s);
|
||||
terminate_event_loop();
|
||||
LOGD("quit...");
|
||||
|
||||
if (options->video_playback) {
|
||||
@ -855,6 +928,9 @@ end:
|
||||
if (mouse_aoa_initialized) {
|
||||
sc_mouse_aoa_destroy(&s->mouse_aoa);
|
||||
}
|
||||
if (gamepad_aoa_initialized) {
|
||||
sc_gamepad_aoa_destroy(&s->gamepad_aoa);
|
||||
}
|
||||
sc_aoa_stop(&s->aoa);
|
||||
sc_usb_stop(&s->usb);
|
||||
}
|
||||
|
@ -306,13 +306,9 @@ sc_screen_frame_sink_open(struct sc_frame_sink *sink,
|
||||
screen->frame_size.width = ctx->width;
|
||||
screen->frame_size.height = ctx->height;
|
||||
|
||||
static SDL_Event event = {
|
||||
.type = SC_EVENT_SCREEN_INIT_SIZE,
|
||||
};
|
||||
|
||||
// Post the event on the UI thread (the texture must be created from there)
|
||||
int ret = SDL_PushEvent(&event);
|
||||
if (ret < 0) {
|
||||
bool ok = sc_push_event(SC_EVENT_SCREEN_INIT_SIZE);
|
||||
if (!ok) {
|
||||
LOGW("Could not post init size event: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
@ -352,13 +348,9 @@ sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
|
||||
// The SC_EVENT_NEW_FRAME triggered for the previous frame will consume
|
||||
// this new frame instead
|
||||
} else {
|
||||
static SDL_Event new_frame_event = {
|
||||
.type = SC_EVENT_NEW_FRAME,
|
||||
};
|
||||
|
||||
// Post the event on the UI thread
|
||||
int ret = SDL_PushEvent(&new_frame_event);
|
||||
if (ret < 0) {
|
||||
bool ok = sc_push_event(SC_EVENT_NEW_FRAME);
|
||||
if (!ok) {
|
||||
LOGW("Could not post new frame event: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
@ -481,6 +473,7 @@ sc_screen_init(struct sc_screen *screen,
|
||||
.screen = screen,
|
||||
.kp = params->kp,
|
||||
.mp = params->mp,
|
||||
.gp = params->gp,
|
||||
.mouse_bindings = params->mouse_bindings,
|
||||
.legacy_paste = params->legacy_paste,
|
||||
.clipboard_autosync = params->clipboard_autosync,
|
||||
|
@ -78,6 +78,7 @@ struct sc_screen_params {
|
||||
struct sc_file_pusher *fp;
|
||||
struct sc_key_processor *kp;
|
||||
struct sc_mouse_processor *mp;
|
||||
struct sc_gamepad_processor *gp;
|
||||
|
||||
struct sc_mouse_bindings mouse_bindings;
|
||||
bool legacy_paste;
|
||||
|
50
app/src/trait/gamepad_processor.h
Normal file
50
app/src/trait/gamepad_processor.h
Normal file
@ -0,0 +1,50 @@
|
||||
#ifndef SC_GAMEPAD_PROCESSOR_H
|
||||
#define SC_GAMEPAD_PROCESSOR_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "input_events.h"
|
||||
|
||||
/**
|
||||
* Gamepad processor trait.
|
||||
*
|
||||
* Component able to handle gamepads devices and inject buttons and axis events.
|
||||
*/
|
||||
struct sc_gamepad_processor {
|
||||
const struct sc_gamepad_processor_ops *ops;
|
||||
};
|
||||
|
||||
struct sc_gamepad_processor_ops {
|
||||
|
||||
/**
|
||||
* Process a gamepad device added or removed
|
||||
*
|
||||
* This function is mandatory.
|
||||
*/
|
||||
void
|
||||
(*process_gamepad_device)(struct sc_gamepad_processor *gp,
|
||||
const struct sc_gamepad_device_event *event);
|
||||
|
||||
/**
|
||||
* Process a gamepad axis event
|
||||
*
|
||||
* This function is mandatory.
|
||||
*/
|
||||
void
|
||||
(*process_gamepad_axis)(struct sc_gamepad_processor *gp,
|
||||
const struct sc_gamepad_axis_event *event);
|
||||
|
||||
/**
|
||||
* Process a gamepad button event
|
||||
*
|
||||
* This function is mandatory.
|
||||
*/
|
||||
void
|
||||
(*process_gamepad_button)(struct sc_gamepad_processor *gp,
|
||||
const struct sc_gamepad_button_event *event);
|
||||
};
|
||||
|
||||
#endif
|
139
app/src/uhid/gamepad_uhid.c
Normal file
139
app/src/uhid/gamepad_uhid.c
Normal file
@ -0,0 +1,139 @@
|
||||
#include "gamepad_uhid.h"
|
||||
|
||||
#include "hid/hid_gamepad.h"
|
||||
#include "input_events.h"
|
||||
#include "util/log.h"
|
||||
#include "util/str.h"
|
||||
|
||||
/** Downcast gamepad processor to sc_gamepad_uhid */
|
||||
#define DOWNCAST(GP) container_of(GP, struct sc_gamepad_uhid, gamepad_processor)
|
||||
|
||||
static void
|
||||
sc_gamepad_uhid_send_input(struct sc_gamepad_uhid *gamepad,
|
||||
const struct sc_hid_input *hid_input,
|
||||
const char *name) {
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT;
|
||||
msg.uhid_input.id = hid_input->hid_id;
|
||||
|
||||
assert(hid_input->size <= SC_HID_MAX_SIZE);
|
||||
memcpy(msg.uhid_input.data, hid_input->data, hid_input->size);
|
||||
msg.uhid_input.size = hid_input->size;
|
||||
|
||||
if (!sc_controller_push_msg(gamepad->controller, &msg)) {
|
||||
LOGE("Could not push UHID_INPUT message (%s)", name);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sc_gamepad_uhid_send_open(struct sc_gamepad_uhid *gamepad,
|
||||
const struct sc_hid_open *hid_open) {
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
||||
msg.uhid_create.id = hid_open->hid_id;
|
||||
msg.uhid_create.report_desc = hid_open->report_desc;
|
||||
msg.uhid_create.report_desc_size = hid_open->report_desc_size;
|
||||
|
||||
if (!sc_controller_push_msg(gamepad->controller, &msg)) {
|
||||
LOGE("Could not push UHID_CREATE message (gamepad)");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sc_gamepad_uhid_send_close(struct sc_gamepad_uhid *gamepad,
|
||||
const struct sc_hid_close *hid_close) {
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_DESTROY;
|
||||
msg.uhid_create.id = hid_close->hid_id;
|
||||
|
||||
if (!sc_controller_push_msg(gamepad->controller, &msg)) {
|
||||
LOGE("Could not push UHID_DESTROY message (gamepad)");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sc_gamepad_processor_process_gamepad_device(struct sc_gamepad_processor *gp,
|
||||
const struct sc_gamepad_device_event *event) {
|
||||
struct sc_gamepad_uhid *gamepad = DOWNCAST(gp);
|
||||
|
||||
if (event->type == SC_GAMEPAD_DEVICE_ADDED) {
|
||||
struct sc_hid_open hid_open;
|
||||
if (!sc_hid_gamepad_generate_open(&gamepad->hid, &hid_open,
|
||||
event->gamepad_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
sc_gamepad_uhid_send_open(gamepad, &hid_open);
|
||||
} else {
|
||||
assert(event->type == SC_GAMEPAD_DEVICE_REMOVED);
|
||||
|
||||
struct sc_hid_close hid_close;
|
||||
if (!sc_hid_gamepad_generate_close(&gamepad->hid, &hid_close,
|
||||
event->gamepad_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
sc_gamepad_uhid_send_close(gamepad, &hid_close);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sc_gamepad_processor_process_gamepad_axis(struct sc_gamepad_processor *gp,
|
||||
const struct sc_gamepad_axis_event *event) {
|
||||
struct sc_gamepad_uhid *gamepad = DOWNCAST(gp);
|
||||
|
||||
struct sc_hid_input hid_input;
|
||||
if (!sc_hid_gamepad_generate_input_from_axis(&gamepad->hid, &hid_input,
|
||||
event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
sc_gamepad_uhid_send_input(gamepad, &hid_input, "gamepad axis");
|
||||
}
|
||||
|
||||
static void
|
||||
sc_gamepad_processor_process_gamepad_button(struct sc_gamepad_processor *gp,
|
||||
const struct sc_gamepad_button_event *event) {
|
||||
struct sc_gamepad_uhid *gamepad = DOWNCAST(gp);
|
||||
|
||||
struct sc_hid_input hid_input;
|
||||
if (!sc_hid_gamepad_generate_input_from_button(&gamepad->hid, &hid_input,
|
||||
event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
sc_gamepad_uhid_send_input(gamepad, &hid_input, "gamepad button");
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
sc_gamepad_uhid_process_hid_output(struct sc_gamepad_uhid *gamepad,
|
||||
uint16_t hid_id, const uint8_t *data,
|
||||
size_t size) {
|
||||
(void) gamepad;
|
||||
char *hex = sc_str_to_hex_string(data, size);
|
||||
if (hex) {
|
||||
LOGI("==== HID output [%" PRIu16 "] %s", hid_id, hex);
|
||||
free(hex);
|
||||
} else {
|
||||
LOGI("==== HID output [%" PRIu16 "]", hid_id);
|
||||
}
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
||||
void
|
||||
sc_gamepad_uhid_init(struct sc_gamepad_uhid *gamepad,
|
||||
struct sc_controller *controller) {
|
||||
sc_hid_gamepad_init(&gamepad->hid);
|
||||
|
||||
gamepad->controller = controller;
|
||||
|
||||
static const struct sc_gamepad_processor_ops ops = {
|
||||
.process_gamepad_device = sc_gamepad_processor_process_gamepad_device,
|
||||
.process_gamepad_axis = sc_gamepad_processor_process_gamepad_axis,
|
||||
.process_gamepad_button = sc_gamepad_processor_process_gamepad_button,
|
||||
};
|
||||
|
||||
gamepad->gamepad_processor.ops = &ops;
|
||||
}
|
28
app/src/uhid/gamepad_uhid.h
Normal file
28
app/src/uhid/gamepad_uhid.h
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef SC_GAMEPAD_UHID_H
|
||||
#define SC_GAMEPAD_UHID_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "controller.h"
|
||||
#include "hid/hid_gamepad.h"
|
||||
#include "trait/gamepad_processor.h"
|
||||
|
||||
struct sc_gamepad_uhid {
|
||||
struct sc_gamepad_processor gamepad_processor; // gamepad processor trait
|
||||
|
||||
struct sc_hid_gamepad hid;
|
||||
struct sc_controller *controller;
|
||||
};
|
||||
|
||||
void
|
||||
sc_gamepad_uhid_init(struct sc_gamepad_uhid *mouse,
|
||||
struct sc_controller *controller);
|
||||
|
||||
void
|
||||
sc_gamepad_uhid_process_hid_output(struct sc_gamepad_uhid *gamepad,
|
||||
uint16_t hid_id, const uint8_t *data,
|
||||
size_t size);
|
||||
|
||||
#endif
|
@ -9,21 +9,19 @@
|
||||
#define DOWNCAST_RECEIVER(UR) \
|
||||
container_of(UR, struct sc_keyboard_uhid, uhid_receiver)
|
||||
|
||||
#define UHID_KEYBOARD_ID 1
|
||||
|
||||
static void
|
||||
sc_keyboard_uhid_send_input(struct sc_keyboard_uhid *kb,
|
||||
const struct sc_hid_event *event) {
|
||||
const struct sc_hid_input *hid_input) {
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT;
|
||||
msg.uhid_input.id = UHID_KEYBOARD_ID;
|
||||
msg.uhid_input.id = hid_input->hid_id;
|
||||
|
||||
assert(event->size <= SC_HID_MAX_SIZE);
|
||||
memcpy(msg.uhid_input.data, event->data, event->size);
|
||||
msg.uhid_input.size = event->size;
|
||||
assert(hid_input->size <= SC_HID_MAX_SIZE);
|
||||
memcpy(msg.uhid_input.data, hid_input->data, hid_input->size);
|
||||
msg.uhid_input.size = hid_input->size;
|
||||
|
||||
if (!sc_controller_push_msg(kb->controller, &msg)) {
|
||||
LOGE("Could not send UHID_INPUT message (key)");
|
||||
LOGE("Could not push UHID_INPUT message (key)");
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,23 +29,22 @@ static void
|
||||
sc_keyboard_uhid_synchronize_mod(struct sc_keyboard_uhid *kb) {
|
||||
SDL_Keymod sdl_mod = SDL_GetModState();
|
||||
uint16_t mod = sc_mods_state_from_sdl(sdl_mod) & (SC_MOD_CAPS | SC_MOD_NUM);
|
||||
|
||||
uint16_t device_mod =
|
||||
atomic_load_explicit(&kb->device_mod, memory_order_relaxed);
|
||||
uint16_t diff = mod ^ device_mod;
|
||||
uint16_t diff = mod ^ kb->device_mod;
|
||||
|
||||
if (diff) {
|
||||
// Inherently racy (the HID output reports arrive asynchronously in
|
||||
// response to key presses), but will re-synchronize on next key press
|
||||
// or HID output anyway
|
||||
atomic_store_explicit(&kb->device_mod, mod, memory_order_relaxed);
|
||||
kb->device_mod = mod;
|
||||
|
||||
struct sc_hid_event hid_event;
|
||||
sc_hid_keyboard_event_from_mods(&hid_event, diff);
|
||||
struct sc_hid_input hid_input;
|
||||
if (!sc_hid_keyboard_generate_input_from_mods(&hid_input, diff)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOGV("HID keyboard state synchronized");
|
||||
|
||||
sc_keyboard_uhid_send_input(kb, &hid_event);
|
||||
sc_keyboard_uhid_send_input(kb, &hid_input);
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,6 +54,8 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
|
||||
uint64_t ack_to_wait) {
|
||||
(void) ack_to_wait;
|
||||
|
||||
assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);
|
||||
|
||||
if (event->repeat) {
|
||||
// In USB HID protocol, key repeat is handled by the host (Android), so
|
||||
// just ignore key repeat here.
|
||||
@ -65,22 +64,20 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
|
||||
|
||||
struct sc_keyboard_uhid *kb = DOWNCAST(kp);
|
||||
|
||||
struct sc_hid_event hid_event;
|
||||
struct sc_hid_input hid_input;
|
||||
|
||||
// Not all keys are supported, just ignore unsupported keys
|
||||
if (sc_hid_keyboard_event_from_key(&kb->hid, &hid_event, event)) {
|
||||
if (sc_hid_keyboard_generate_input_from_key(&kb->hid, &hid_input, event)) {
|
||||
if (event->scancode == SC_SCANCODE_CAPSLOCK) {
|
||||
atomic_fetch_xor_explicit(&kb->device_mod, SC_MOD_CAPS,
|
||||
memory_order_relaxed);
|
||||
kb->device_mod ^= SC_MOD_CAPS;
|
||||
} else if (event->scancode == SC_SCANCODE_NUMLOCK) {
|
||||
atomic_fetch_xor_explicit(&kb->device_mod, SC_MOD_NUM,
|
||||
memory_order_relaxed);
|
||||
kb->device_mod ^= SC_MOD_NUM;
|
||||
} else {
|
||||
// Synchronize modifiers (only if the scancode itself does not
|
||||
// change the modifiers)
|
||||
sc_keyboard_uhid_synchronize_mod(kb);
|
||||
}
|
||||
sc_keyboard_uhid_send_input(kb, &hid_event);
|
||||
sc_keyboard_uhid_send_input(kb, &hid_input);
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,34 +95,31 @@ sc_keyboard_uhid_to_sc_mod(uint8_t hid_led) {
|
||||
return mod;
|
||||
}
|
||||
|
||||
static void
|
||||
sc_uhid_receiver_process_output(struct sc_uhid_receiver *receiver,
|
||||
const uint8_t *data, size_t len) {
|
||||
// Called from the thread receiving device messages
|
||||
void
|
||||
sc_keyboard_uhid_process_hid_output(struct sc_keyboard_uhid *kb,
|
||||
const uint8_t *data, size_t size) {
|
||||
assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);
|
||||
|
||||
assert(len);
|
||||
assert(size);
|
||||
|
||||
// Also check at runtime (do not trust the server)
|
||||
if (!len) {
|
||||
if (!size) {
|
||||
LOGE("Unexpected empty HID output message");
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_keyboard_uhid *kb = DOWNCAST_RECEIVER(receiver);
|
||||
|
||||
uint8_t hid_led = data[0];
|
||||
uint16_t device_mod = sc_keyboard_uhid_to_sc_mod(hid_led);
|
||||
atomic_store_explicit(&kb->device_mod, device_mod, memory_order_relaxed);
|
||||
kb->device_mod = device_mod;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
|
||||
struct sc_controller *controller,
|
||||
struct sc_uhid_devices *uhid_devices) {
|
||||
struct sc_controller *controller) {
|
||||
sc_hid_keyboard_init(&kb->hid);
|
||||
|
||||
kb->controller = controller;
|
||||
atomic_init(&kb->device_mod, 0);
|
||||
kb->device_mod = 0;
|
||||
|
||||
static const struct sc_key_processor_ops ops = {
|
||||
.process_key = sc_key_processor_process_key,
|
||||
@ -140,19 +134,15 @@ sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
|
||||
kb->key_processor.hid = true;
|
||||
kb->key_processor.ops = &ops;
|
||||
|
||||
static const struct sc_uhid_receiver_ops uhid_receiver_ops = {
|
||||
.process_output = sc_uhid_receiver_process_output,
|
||||
};
|
||||
|
||||
kb->uhid_receiver.id = UHID_KEYBOARD_ID;
|
||||
kb->uhid_receiver.ops = &uhid_receiver_ops;
|
||||
sc_uhid_devices_add_receiver(uhid_devices, &kb->uhid_receiver);
|
||||
struct sc_hid_open hid_open;
|
||||
sc_hid_keyboard_generate_open(&hid_open);
|
||||
assert(hid_open.hid_id == SC_HID_ID_KEYBOARD);
|
||||
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
||||
msg.uhid_create.id = UHID_KEYBOARD_ID;
|
||||
msg.uhid_create.report_desc = SC_HID_KEYBOARD_REPORT_DESC;
|
||||
msg.uhid_create.report_desc_size = SC_HID_KEYBOARD_REPORT_DESC_LEN;
|
||||
msg.uhid_create.id = SC_HID_ID_KEYBOARD;
|
||||
msg.uhid_create.report_desc = hid_open.report_desc;
|
||||
msg.uhid_create.report_desc_size = hid_open.report_desc_size;
|
||||
if (!sc_controller_push_msg(controller, &msg)) {
|
||||
LOGE("Could not send UHID_CREATE message (keyboard)");
|
||||
return false;
|
||||
|
@ -7,21 +7,22 @@
|
||||
|
||||
#include "controller.h"
|
||||
#include "hid/hid_keyboard.h"
|
||||
#include "uhid/uhid_output.h"
|
||||
#include "trait/key_processor.h"
|
||||
|
||||
struct sc_keyboard_uhid {
|
||||
struct sc_key_processor key_processor; // key processor trait
|
||||
struct sc_uhid_receiver uhid_receiver;
|
||||
|
||||
struct sc_hid_keyboard hid;
|
||||
struct sc_controller *controller;
|
||||
atomic_uint_least16_t device_mod;
|
||||
uint16_t device_mod;
|
||||
};
|
||||
|
||||
bool
|
||||
sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
|
||||
struct sc_controller *controller,
|
||||
struct sc_uhid_devices *uhid_devices);
|
||||
struct sc_controller *controller);
|
||||
|
||||
void
|
||||
sc_keyboard_uhid_process_hid_output(struct sc_keyboard_uhid *kb,
|
||||
const uint8_t *data, size_t size);
|
||||
|
||||
#endif
|
||||
|
@ -7,21 +7,20 @@
|
||||
/** Downcast mouse processor to mouse_uhid */
|
||||
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_uhid, mouse_processor)
|
||||
|
||||
#define UHID_MOUSE_ID 2
|
||||
|
||||
static void
|
||||
sc_mouse_uhid_send_input(struct sc_mouse_uhid *mouse,
|
||||
const struct sc_hid_event *event, const char *name) {
|
||||
const struct sc_hid_input *hid_input,
|
||||
const char *name) {
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT;
|
||||
msg.uhid_input.id = UHID_MOUSE_ID;
|
||||
msg.uhid_input.id = hid_input->hid_id;
|
||||
|
||||
assert(event->size <= SC_HID_MAX_SIZE);
|
||||
memcpy(msg.uhid_input.data, event->data, event->size);
|
||||
msg.uhid_input.size = event->size;
|
||||
assert(hid_input->size <= SC_HID_MAX_SIZE);
|
||||
memcpy(msg.uhid_input.data, hid_input->data, hid_input->size);
|
||||
msg.uhid_input.size = hid_input->size;
|
||||
|
||||
if (!sc_controller_push_msg(mouse->controller, &msg)) {
|
||||
LOGE("Could not send UHID_INPUT message (%s)", name);
|
||||
LOGE("Could not push UHID_INPUT message (%s)", name);
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,10 +29,10 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
||||
const struct sc_mouse_motion_event *event) {
|
||||
struct sc_mouse_uhid *mouse = DOWNCAST(mp);
|
||||
|
||||
struct sc_hid_event hid_event;
|
||||
sc_hid_mouse_event_from_motion(&hid_event, event);
|
||||
struct sc_hid_input hid_input;
|
||||
sc_hid_mouse_generate_input_from_motion(&hid_input, event);
|
||||
|
||||
sc_mouse_uhid_send_input(mouse, &hid_event, "mouse motion");
|
||||
sc_mouse_uhid_send_input(mouse, &hid_input, "mouse motion");
|
||||
}
|
||||
|
||||
static void
|
||||
@ -41,10 +40,10 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
|
||||
const struct sc_mouse_click_event *event) {
|
||||
struct sc_mouse_uhid *mouse = DOWNCAST(mp);
|
||||
|
||||
struct sc_hid_event hid_event;
|
||||
sc_hid_mouse_event_from_click(&hid_event, event);
|
||||
struct sc_hid_input hid_input;
|
||||
sc_hid_mouse_generate_input_from_click(&hid_input, event);
|
||||
|
||||
sc_mouse_uhid_send_input(mouse, &hid_event, "mouse click");
|
||||
sc_mouse_uhid_send_input(mouse, &hid_input, "mouse click");
|
||||
}
|
||||
|
||||
static void
|
||||
@ -52,10 +51,10 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
|
||||
const struct sc_mouse_scroll_event *event) {
|
||||
struct sc_mouse_uhid *mouse = DOWNCAST(mp);
|
||||
|
||||
struct sc_hid_event hid_event;
|
||||
sc_hid_mouse_event_from_scroll(&hid_event, event);
|
||||
struct sc_hid_input hid_input;
|
||||
sc_hid_mouse_generate_input_from_scroll(&hid_input, event);
|
||||
|
||||
sc_mouse_uhid_send_input(mouse, &hid_event, "mouse scroll");
|
||||
sc_mouse_uhid_send_input(mouse, &hid_input, "mouse scroll");
|
||||
}
|
||||
|
||||
bool
|
||||
@ -75,13 +74,17 @@ sc_mouse_uhid_init(struct sc_mouse_uhid *mouse,
|
||||
|
||||
mouse->mouse_processor.relative_mode = true;
|
||||
|
||||
struct sc_hid_open hid_open;
|
||||
sc_hid_mouse_generate_open(&hid_open);
|
||||
assert(hid_open.hid_id == SC_HID_ID_MOUSE);
|
||||
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
||||
msg.uhid_create.id = UHID_MOUSE_ID;
|
||||
msg.uhid_create.report_desc = SC_HID_MOUSE_REPORT_DESC;
|
||||
msg.uhid_create.report_desc_size = SC_HID_MOUSE_REPORT_DESC_LEN;
|
||||
msg.uhid_create.id = SC_HID_ID_MOUSE;
|
||||
msg.uhid_create.report_desc = hid_open.report_desc;
|
||||
msg.uhid_create.report_desc_size = hid_open.report_desc_size;
|
||||
if (!sc_controller_push_msg(controller, &msg)) {
|
||||
LOGE("Could not send UHID_CREATE message (mouse)");
|
||||
LOGE("Could not push UHID_CREATE message (mouse)");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1,25 +1,37 @@
|
||||
#include "uhid_output.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "uhid/keyboard_uhid.h"
|
||||
#include "uhid/gamepad_uhid.h"
|
||||
#include "util/log.h"
|
||||
|
||||
void
|
||||
sc_uhid_devices_init(struct sc_uhid_devices *devices) {
|
||||
devices->count = 0;
|
||||
sc_uhid_devices_init(struct sc_uhid_devices *devices,
|
||||
struct sc_keyboard_uhid *keyboard,
|
||||
struct sc_gamepad_uhid *gamepad) {
|
||||
devices->keyboard = keyboard;
|
||||
devices->gamepad = gamepad;
|
||||
}
|
||||
|
||||
void
|
||||
sc_uhid_devices_add_receiver(struct sc_uhid_devices *devices,
|
||||
struct sc_uhid_receiver *receiver) {
|
||||
assert(devices->count < SC_UHID_MAX_RECEIVERS);
|
||||
devices->receivers[devices->count++] = receiver;
|
||||
sc_uhid_devices_process_hid_output(struct sc_uhid_devices *devices, uint16_t id,
|
||||
const uint8_t *data, size_t size) {
|
||||
if (id == SC_HID_ID_KEYBOARD) {
|
||||
if (devices->keyboard) {
|
||||
sc_keyboard_uhid_process_hid_output(devices->keyboard, data, size);
|
||||
} else {
|
||||
LOGW("Unexpected keyboard HID output without UHID keyboard");
|
||||
}
|
||||
|
||||
struct sc_uhid_receiver *
|
||||
sc_uhid_devices_get_receiver(struct sc_uhid_devices *devices, uint16_t id) {
|
||||
for (size_t i = 0; i < devices->count; ++i) {
|
||||
if (devices->receivers[i]->id == id) {
|
||||
return devices->receivers[i];
|
||||
} else if (id >= SC_HID_ID_GAMEPAD_FIRST && id <= SC_HID_ID_GAMEPAD_LAST) {
|
||||
if (devices->gamepad) {
|
||||
sc_gamepad_uhid_process_hid_output(devices->gamepad, id, data,
|
||||
size);
|
||||
} else {
|
||||
LOGW("Unexpected gamepad HID output without UHID gamepad");
|
||||
}
|
||||
} else {
|
||||
LOGW("HID output ignored for id %" PRIu16, id);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
@ -9,37 +9,21 @@
|
||||
/**
|
||||
* The communication with UHID devices is bidirectional.
|
||||
*
|
||||
* This component manages the registration of receivers to handle UHID output
|
||||
* messages (sent from the device to the computer).
|
||||
* This component dispatches HID outputs to the expected processor.
|
||||
*/
|
||||
|
||||
struct sc_uhid_receiver {
|
||||
uint16_t id;
|
||||
|
||||
const struct sc_uhid_receiver_ops *ops;
|
||||
};
|
||||
|
||||
struct sc_uhid_receiver_ops {
|
||||
void
|
||||
(*process_output)(struct sc_uhid_receiver *receiver,
|
||||
const uint8_t *data, size_t len);
|
||||
};
|
||||
|
||||
#define SC_UHID_MAX_RECEIVERS 1
|
||||
|
||||
struct sc_uhid_devices {
|
||||
struct sc_uhid_receiver *receivers[SC_UHID_MAX_RECEIVERS];
|
||||
unsigned count;
|
||||
struct sc_keyboard_uhid *keyboard;
|
||||
struct sc_gamepad_uhid *gamepad;
|
||||
};
|
||||
|
||||
void
|
||||
sc_uhid_devices_init(struct sc_uhid_devices *devices);
|
||||
sc_uhid_devices_init(struct sc_uhid_devices *devices,
|
||||
struct sc_keyboard_uhid *keyboard,
|
||||
struct sc_gamepad_uhid *gamepad);
|
||||
|
||||
void
|
||||
sc_uhid_devices_add_receiver(struct sc_uhid_devices *devices,
|
||||
struct sc_uhid_receiver *receiver);
|
||||
|
||||
struct sc_uhid_receiver *
|
||||
sc_uhid_devices_get_receiver(struct sc_uhid_devices *devices, uint16_t id);
|
||||
sc_uhid_devices_process_hid_output(struct sc_uhid_devices *devices, uint16_t id,
|
||||
const uint8_t *data, size_t size);
|
||||
|
||||
#endif
|
||||
|
@ -1,11 +1,14 @@
|
||||
#include "util/log.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "aoa_hid.h"
|
||||
#include "events.h"
|
||||
#include "util/log.h"
|
||||
#include "util/str.h"
|
||||
#include "util/vector.h"
|
||||
|
||||
// See <https://source.android.com/devices/accessories/aoa2#hid-support>.
|
||||
#define ACCESSORY_REGISTER_HID 54
|
||||
@ -15,26 +18,49 @@
|
||||
|
||||
#define DEFAULT_TIMEOUT 1000
|
||||
|
||||
#define SC_AOA_EVENT_QUEUE_MAX 64
|
||||
// Drop droppable events above this limit
|
||||
#define SC_AOA_EVENT_QUEUE_LIMIT 60
|
||||
|
||||
struct sc_vec_hid_ids SC_VECTOR(uint16_t);
|
||||
|
||||
static void
|
||||
sc_hid_event_log(uint16_t accessory_id, const struct sc_hid_event *event) {
|
||||
// HID Event: [00] FF FF FF FF...
|
||||
assert(event->size);
|
||||
char *hex = sc_str_to_hex_string(event->data, event->size);
|
||||
sc_hid_input_log(const struct sc_hid_input *hid_input) {
|
||||
// HID input: [00] FF FF FF FF...
|
||||
assert(hid_input->size);
|
||||
char *hex = sc_str_to_hex_string(hid_input->data, hid_input->size);
|
||||
if (!hex) {
|
||||
return;
|
||||
}
|
||||
LOGV("HID Event: [%d] %s", accessory_id, hex);
|
||||
LOGV("HID input: [%" PRIu16 "] %s", hid_input->hid_id, hex);
|
||||
free(hex);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_hid_open_log(const struct sc_hid_open *hid_open) {
|
||||
// HID open: [00] FF FF FF FF...
|
||||
assert(hid_open->report_desc_size);
|
||||
char *hex = sc_str_to_hex_string(hid_open->report_desc,
|
||||
hid_open->report_desc_size);
|
||||
if (!hex) {
|
||||
return;
|
||||
}
|
||||
LOGV("HID open: [%" PRIu16 "] %s", hid_open->hid_id, hex);
|
||||
free(hex);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_hid_close_log(const struct sc_hid_close *hid_close) {
|
||||
// HID close: [00]
|
||||
LOGV("HD close: [%" PRIu16 "]", hid_close->hid_id);
|
||||
}
|
||||
|
||||
bool
|
||||
sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb,
|
||||
struct sc_acksync *acksync) {
|
||||
sc_vecdeque_init(&aoa->queue);
|
||||
|
||||
if (!sc_vecdeque_reserve(&aoa->queue, SC_AOA_EVENT_QUEUE_MAX)) {
|
||||
// Add 4 to support 4 non-droppable events without re-allocation
|
||||
if (!sc_vecdeque_reserve(&aoa->queue, SC_AOA_EVENT_QUEUE_LIMIT + 4)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -125,38 +151,18 @@ sc_aoa_set_hid_report_desc(struct sc_aoa *aoa, uint16_t accessory_id,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id,
|
||||
const uint8_t *report_desc, uint16_t report_desc_size) {
|
||||
bool ok = sc_aoa_register_hid(aoa, accessory_id, report_desc_size);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = sc_aoa_set_hid_report_desc(aoa, accessory_id, report_desc,
|
||||
report_desc_size);
|
||||
if (!ok) {
|
||||
if (!sc_aoa_unregister_hid(aoa, accessory_id)) {
|
||||
LOGW("Could not unregister HID");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_aoa_send_hid_event(struct sc_aoa *aoa, uint16_t accessory_id,
|
||||
const struct sc_hid_event *event) {
|
||||
sc_aoa_send_hid_event(struct sc_aoa *aoa,
|
||||
const struct sc_hid_input *hid_input) {
|
||||
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
|
||||
uint8_t request = ACCESSORY_SEND_HID_EVENT;
|
||||
// <https://source.android.com/devices/accessories/aoa2.html#hid-support>
|
||||
// value (arg0): accessory assigned ID for the HID device
|
||||
// index (arg1): 0 (unused)
|
||||
uint16_t value = accessory_id;
|
||||
uint16_t value = hid_input->hid_id;
|
||||
uint16_t index = 0;
|
||||
unsigned char *data = (uint8_t *) event->data; // discard const
|
||||
uint16_t length = event->size;
|
||||
unsigned char *data = (uint8_t *) hid_input->data; // discard const
|
||||
uint16_t length = hid_input->size;
|
||||
int result = libusb_control_transfer(aoa->usb->handle, request_type,
|
||||
request, value, index, data, length,
|
||||
DEFAULT_TIMEOUT);
|
||||
@ -169,7 +175,7 @@ sc_aoa_send_hid_event(struct sc_aoa *aoa, uint16_t accessory_id,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
static bool
|
||||
sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id) {
|
||||
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
|
||||
uint8_t request = ACCESSORY_UNREGISTER_HID;
|
||||
@ -192,13 +198,32 @@ sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id,
|
||||
const uint8_t *report_desc, uint16_t report_desc_size) {
|
||||
bool ok = sc_aoa_register_hid(aoa, accessory_id, report_desc_size);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = sc_aoa_set_hid_report_desc(aoa, accessory_id, report_desc,
|
||||
report_desc_size);
|
||||
if (!ok) {
|
||||
if (!sc_aoa_unregister_hid(aoa, accessory_id)) {
|
||||
LOGW("Could not unregister HID");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_aoa_push_hid_event_with_ack_to_wait(struct sc_aoa *aoa,
|
||||
uint16_t accessory_id,
|
||||
const struct sc_hid_event *event,
|
||||
sc_aoa_push_input_with_ack_to_wait(struct sc_aoa *aoa,
|
||||
const struct sc_hid_input *hid_input,
|
||||
uint64_t ack_to_wait) {
|
||||
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
|
||||
sc_hid_event_log(accessory_id, event);
|
||||
sc_hid_input_log(hid_input);
|
||||
}
|
||||
|
||||
sc_mutex_lock(&aoa->mutex);
|
||||
@ -208,9 +233,9 @@ sc_aoa_push_hid_event_with_ack_to_wait(struct sc_aoa *aoa,
|
||||
|
||||
struct sc_aoa_event *aoa_event =
|
||||
sc_vecdeque_push_hole_noresize(&aoa->queue);
|
||||
aoa_event->hid = *event;
|
||||
aoa_event->accessory_id = accessory_id;
|
||||
aoa_event->ack_to_wait = ack_to_wait;
|
||||
aoa_event->type = SC_AOA_EVENT_TYPE_INPUT;
|
||||
aoa_event->input.hid = *hid_input;
|
||||
aoa_event->input.ack_to_wait = ack_to_wait;
|
||||
|
||||
if (was_empty) {
|
||||
sc_cond_signal(&aoa->event_cond);
|
||||
@ -223,10 +248,159 @@ sc_aoa_push_hid_event_with_ack_to_wait(struct sc_aoa *aoa,
|
||||
return !full;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_aoa_push_open(struct sc_aoa *aoa, const struct sc_hid_open *hid_open,
|
||||
bool exit_on_open_error) {
|
||||
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
|
||||
sc_hid_open_log(hid_open);
|
||||
}
|
||||
|
||||
sc_mutex_lock(&aoa->mutex);
|
||||
bool was_empty = sc_vecdeque_is_empty(&aoa->queue);
|
||||
|
||||
// an OPEN event is non-droppable, so push it to the queue even above the
|
||||
// SC_AOA_EVENT_QUEUE_LIMIT
|
||||
struct sc_aoa_event *aoa_event = sc_vecdeque_push_hole(&aoa->queue);
|
||||
if (!aoa_event) {
|
||||
LOG_OOM();
|
||||
sc_mutex_unlock(&aoa->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
aoa_event->type = SC_AOA_EVENT_TYPE_OPEN;
|
||||
aoa_event->open.hid = *hid_open;
|
||||
aoa_event->open.exit_on_error = exit_on_open_error;
|
||||
|
||||
if (was_empty) {
|
||||
sc_cond_signal(&aoa->event_cond);
|
||||
}
|
||||
|
||||
sc_mutex_unlock(&aoa->mutex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_aoa_push_close(struct sc_aoa *aoa, const struct sc_hid_close *hid_close) {
|
||||
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
|
||||
sc_hid_close_log(hid_close);
|
||||
}
|
||||
|
||||
sc_mutex_lock(&aoa->mutex);
|
||||
bool was_empty = sc_vecdeque_is_empty(&aoa->queue);
|
||||
|
||||
// an OPEN event is non-droppable, so push it to the queue even above the
|
||||
// SC_AOA_EVENT_QUEUE_LIMIT
|
||||
struct sc_aoa_event *aoa_event = sc_vecdeque_push_hole(&aoa->queue);
|
||||
if (!aoa_event) {
|
||||
LOG_OOM();
|
||||
sc_mutex_unlock(&aoa->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
aoa_event->type = SC_AOA_EVENT_TYPE_CLOSE;
|
||||
aoa_event->close.hid = *hid_close;
|
||||
|
||||
if (was_empty) {
|
||||
sc_cond_signal(&aoa->event_cond);
|
||||
}
|
||||
|
||||
sc_mutex_unlock(&aoa->mutex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_aoa_process_event(struct sc_aoa *aoa, struct sc_aoa_event *event,
|
||||
struct sc_vec_hid_ids *vec_open) {
|
||||
switch (event->type) {
|
||||
case SC_AOA_EVENT_TYPE_INPUT: {
|
||||
uint64_t ack_to_wait = event->input.ack_to_wait;
|
||||
if (ack_to_wait != SC_SEQUENCE_INVALID) {
|
||||
LOGD("Waiting ack from server sequence=%" PRIu64_, ack_to_wait);
|
||||
|
||||
// If some events have ack_to_wait set, then sc_aoa must have
|
||||
// been initialized with a non NULL acksync
|
||||
assert(aoa->acksync);
|
||||
|
||||
// Do not block the loop indefinitely if the ack never comes (it
|
||||
// should never happen)
|
||||
sc_tick deadline = sc_tick_now() + SC_TICK_FROM_MS(500);
|
||||
enum sc_acksync_wait_result result =
|
||||
sc_acksync_wait(aoa->acksync, ack_to_wait, deadline);
|
||||
|
||||
if (result == SC_ACKSYNC_WAIT_TIMEOUT) {
|
||||
LOGW("Ack not received after 500ms, discarding HID event");
|
||||
// continue to process events
|
||||
return true;
|
||||
} else if (result == SC_ACKSYNC_WAIT_INTR) {
|
||||
// stopped
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
struct sc_hid_input *hid_input = &event->input.hid;
|
||||
bool ok = sc_aoa_send_hid_event(aoa, hid_input);
|
||||
if (!ok) {
|
||||
LOGW("Could not send HID event to USB device: %" PRIu16,
|
||||
hid_input->hid_id);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case SC_AOA_EVENT_TYPE_OPEN: {
|
||||
struct sc_hid_open *hid_open = &event->open.hid;
|
||||
bool ok = sc_aoa_setup_hid(aoa, hid_open->hid_id,
|
||||
hid_open->report_desc,
|
||||
hid_open->report_desc_size);
|
||||
if (ok) {
|
||||
// The device is now open, add it to the list of devices to
|
||||
// close automatically on exit
|
||||
bool pushed = sc_vector_push(vec_open, hid_open->hid_id);
|
||||
if (!pushed) {
|
||||
LOG_OOM();
|
||||
// this is not fatal, the HID device will just not be
|
||||
// explicitly unregistered
|
||||
}
|
||||
} else {
|
||||
LOGW("Could not open AOA device: %" PRIu16, hid_open->hid_id);
|
||||
if (event->open.exit_on_error) {
|
||||
// Notify the error to the main thread, which will exit
|
||||
sc_push_event(SC_EVENT_AOA_OPEN_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case SC_AOA_EVENT_TYPE_CLOSE: {
|
||||
struct sc_hid_close *hid_close = &event->close.hid;
|
||||
bool ok = sc_aoa_unregister_hid(aoa, hid_close->hid_id);
|
||||
if (ok) {
|
||||
// The device is not open anymore, remove it from the list of
|
||||
// devices to close automatically on exit
|
||||
ssize_t idx = sc_vector_index_of(vec_open, hid_close->hid_id);
|
||||
if (idx >= 0) {
|
||||
sc_vector_remove(vec_open, idx);
|
||||
}
|
||||
} else {
|
||||
LOGW("Could not close AOA device: %" PRIu16, hid_close->hid_id);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// continue to process events
|
||||
return true;
|
||||
}
|
||||
|
||||
static int
|
||||
run_aoa_thread(void *data) {
|
||||
struct sc_aoa *aoa = data;
|
||||
|
||||
// Store the HID ids of opened devices to unregister them all before exiting
|
||||
struct sc_vec_hid_ids vec_open = SC_VECTOR_INITIALIZER;
|
||||
|
||||
for (;;) {
|
||||
sc_mutex_lock(&aoa->mutex);
|
||||
while (!aoa->stopped && sc_vecdeque_is_empty(&aoa->queue)) {
|
||||
@ -240,36 +414,26 @@ run_aoa_thread(void *data) {
|
||||
|
||||
assert(!sc_vecdeque_is_empty(&aoa->queue));
|
||||
struct sc_aoa_event event = sc_vecdeque_pop(&aoa->queue);
|
||||
uint64_t ack_to_wait = event.ack_to_wait;
|
||||
sc_mutex_unlock(&aoa->mutex);
|
||||
|
||||
if (ack_to_wait != SC_SEQUENCE_INVALID) {
|
||||
LOGD("Waiting ack from server sequence=%" PRIu64_, ack_to_wait);
|
||||
|
||||
// If some events have ack_to_wait set, then sc_aoa must have been
|
||||
// initialized with a non NULL acksync
|
||||
assert(aoa->acksync);
|
||||
|
||||
// Do not block the loop indefinitely if the ack never comes (it
|
||||
// should never happen)
|
||||
sc_tick deadline = sc_tick_now() + SC_TICK_FROM_MS(500);
|
||||
enum sc_acksync_wait_result result =
|
||||
sc_acksync_wait(aoa->acksync, ack_to_wait, deadline);
|
||||
|
||||
if (result == SC_ACKSYNC_WAIT_TIMEOUT) {
|
||||
LOGW("Ack not received after 500ms, discarding HID event");
|
||||
continue;
|
||||
} else if (result == SC_ACKSYNC_WAIT_INTR) {
|
||||
bool cont = sc_aoa_process_event(aoa, &event, &vec_open);
|
||||
if (!cont) {
|
||||
// stopped
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool ok = sc_aoa_send_hid_event(aoa, event.accessory_id, &event.hid);
|
||||
// Explicitly unregister all registered HID ids before exiting
|
||||
for (size_t i = 0; i < vec_open.size; ++i) {
|
||||
uint16_t hid_id = vec_open.data[i];
|
||||
LOGD("Unregistering AOA device %" PRIu16 "...", hid_id);
|
||||
bool ok = sc_aoa_unregister_hid(aoa, hid_id);
|
||||
if (!ok) {
|
||||
LOGW("Could not send HID event to USB device");
|
||||
LOGW("Could not close AOA device: %" PRIu16, hid_id);
|
||||
}
|
||||
}
|
||||
sc_vector_destroy(&vec_open);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -13,12 +13,27 @@
|
||||
#include "util/tick.h"
|
||||
#include "util/vecdeque.h"
|
||||
|
||||
#define SC_HID_MAX_SIZE 8
|
||||
enum sc_aoa_event_type {
|
||||
SC_AOA_EVENT_TYPE_OPEN,
|
||||
SC_AOA_EVENT_TYPE_INPUT,
|
||||
SC_AOA_EVENT_TYPE_CLOSE,
|
||||
};
|
||||
|
||||
struct sc_aoa_event {
|
||||
struct sc_hid_event hid;
|
||||
uint16_t accessory_id;
|
||||
enum sc_aoa_event_type type;
|
||||
union {
|
||||
struct {
|
||||
struct sc_hid_open hid;
|
||||
bool exit_on_error;
|
||||
} open;
|
||||
struct {
|
||||
struct sc_hid_close hid;
|
||||
} close;
|
||||
struct {
|
||||
struct sc_hid_input hid;
|
||||
uint64_t ack_to_wait;
|
||||
} input;
|
||||
};
|
||||
};
|
||||
|
||||
struct sc_aoa_event_queue SC_VECDEQUE(struct sc_aoa_event);
|
||||
@ -49,23 +64,30 @@ sc_aoa_stop(struct sc_aoa *aoa);
|
||||
void
|
||||
sc_aoa_join(struct sc_aoa *aoa);
|
||||
|
||||
//bool
|
||||
//sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id,
|
||||
// const uint8_t *report_desc, uint16_t report_desc_size);
|
||||
//
|
||||
//bool
|
||||
//sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id);
|
||||
|
||||
// report_desc must be a pointer to static memory, accessed at any time from
|
||||
// another thread
|
||||
bool
|
||||
sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id,
|
||||
const uint8_t *report_desc, uint16_t report_desc_size);
|
||||
sc_aoa_push_open(struct sc_aoa *aoa, const struct sc_hid_open *hid_open,
|
||||
bool exit_on_open_error);
|
||||
|
||||
bool
|
||||
sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id);
|
||||
sc_aoa_push_close(struct sc_aoa *aoa, const struct sc_hid_close *hid_close);
|
||||
|
||||
bool
|
||||
sc_aoa_push_hid_event_with_ack_to_wait(struct sc_aoa *aoa,
|
||||
uint16_t accessory_id,
|
||||
const struct sc_hid_event *event,
|
||||
sc_aoa_push_input_with_ack_to_wait(struct sc_aoa *aoa,
|
||||
const struct sc_hid_input *hid_input,
|
||||
uint64_t ack_to_wait);
|
||||
|
||||
static inline bool
|
||||
sc_aoa_push_hid_event(struct sc_aoa *aoa, uint16_t accessory_id,
|
||||
const struct sc_hid_event *event) {
|
||||
return sc_aoa_push_hid_event_with_ack_to_wait(aoa, accessory_id, event,
|
||||
sc_aoa_push_input(struct sc_aoa *aoa, const struct sc_hid_input *hid_input) {
|
||||
return sc_aoa_push_input_with_ack_to_wait(aoa, hid_input,
|
||||
SC_SEQUENCE_INVALID);
|
||||
}
|
||||
|
||||
|
91
app/src/usb/gamepad_aoa.c
Normal file
91
app/src/usb/gamepad_aoa.c
Normal file
@ -0,0 +1,91 @@
|
||||
#include "gamepad_aoa.h"
|
||||
|
||||
#include "input_events.h"
|
||||
#include "util/log.h"
|
||||
|
||||
/** Downcast gamepad processor to gamepad_aoa */
|
||||
#define DOWNCAST(GP) container_of(GP, struct sc_gamepad_aoa, gamepad_processor)
|
||||
|
||||
static void
|
||||
sc_gamepad_processor_process_gamepad_device(struct sc_gamepad_processor *gp,
|
||||
const struct sc_gamepad_device_event *event) {
|
||||
struct sc_gamepad_aoa *gamepad = DOWNCAST(gp);
|
||||
|
||||
if (event->type == SC_GAMEPAD_DEVICE_ADDED) {
|
||||
struct sc_hid_open hid_open;
|
||||
if (!sc_hid_gamepad_generate_open(&gamepad->hid, &hid_open,
|
||||
event->gamepad_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// exit_on_error: false (a gamepad open failure should not exit scrcpy)
|
||||
if (!sc_aoa_push_open(gamepad->aoa, &hid_open, false)) {
|
||||
LOGW("Could not push AOA HID open (gamepad)");
|
||||
}
|
||||
} else {
|
||||
assert(event->type == SC_GAMEPAD_DEVICE_REMOVED);
|
||||
|
||||
struct sc_hid_close hid_close;
|
||||
if (!sc_hid_gamepad_generate_close(&gamepad->hid, &hid_close,
|
||||
event->gamepad_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sc_aoa_push_close(gamepad->aoa, &hid_close)) {
|
||||
LOGW("Could not push AOA HID close (gamepad)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sc_gamepad_processor_process_gamepad_axis(struct sc_gamepad_processor *gp,
|
||||
const struct sc_gamepad_axis_event *event) {
|
||||
struct sc_gamepad_aoa *gamepad = DOWNCAST(gp);
|
||||
|
||||
struct sc_hid_input hid_input;
|
||||
if (!sc_hid_gamepad_generate_input_from_axis(&gamepad->hid, &hid_input,
|
||||
event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sc_aoa_push_input(gamepad->aoa, &hid_input)) {
|
||||
LOGW("Could not push AOA HID input (gamepad axis)");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sc_gamepad_processor_process_gamepad_button(struct sc_gamepad_processor *gp,
|
||||
const struct sc_gamepad_button_event *event) {
|
||||
struct sc_gamepad_aoa *gamepad = DOWNCAST(gp);
|
||||
|
||||
struct sc_hid_input hid_input;
|
||||
if (!sc_hid_gamepad_generate_input_from_button(&gamepad->hid, &hid_input,
|
||||
event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sc_aoa_push_input(gamepad->aoa, &hid_input)) {
|
||||
LOGW("Could not push AOA HID input (gamepad button)");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sc_gamepad_aoa_init(struct sc_gamepad_aoa *gamepad, struct sc_aoa *aoa) {
|
||||
gamepad->aoa = aoa;
|
||||
|
||||
sc_hid_gamepad_init(&gamepad->hid);
|
||||
|
||||
static const struct sc_gamepad_processor_ops ops = {
|
||||
.process_gamepad_device = sc_gamepad_processor_process_gamepad_device,
|
||||
.process_gamepad_axis = sc_gamepad_processor_process_gamepad_axis,
|
||||
.process_gamepad_button = sc_gamepad_processor_process_gamepad_button,
|
||||
};
|
||||
|
||||
gamepad->gamepad_processor.ops = &ops;
|
||||
}
|
||||
|
||||
void
|
||||
sc_gamepad_aoa_destroy(struct sc_gamepad_aoa *gamepad) {
|
||||
(void) gamepad;
|
||||
// Do nothing, gamepad->aoa will automatically unregister all devices
|
||||
}
|
25
app/src/usb/gamepad_aoa.h
Normal file
25
app/src/usb/gamepad_aoa.h
Normal file
@ -0,0 +1,25 @@
|
||||
#ifndef SC_GAMEPAD_AOA_H
|
||||
#define SC_GAMEPAD_AOA_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "aoa_hid.h"
|
||||
#include "hid/hid_gamepad.h"
|
||||
#include "trait/gamepad_processor.h"
|
||||
|
||||
struct sc_gamepad_aoa {
|
||||
struct sc_gamepad_processor gamepad_processor; // gamepad processor trait
|
||||
|
||||
struct sc_hid_gamepad hid;
|
||||
struct sc_aoa *aoa;
|
||||
};
|
||||
|
||||
void
|
||||
sc_gamepad_aoa_init(struct sc_gamepad_aoa *gamepad, struct sc_aoa *aoa);
|
||||
|
||||
void
|
||||
sc_gamepad_aoa_destroy(struct sc_gamepad_aoa *gamepad);
|
||||
|
||||
#endif
|
@ -8,19 +8,16 @@
|
||||
/** Downcast key processor to keyboard_aoa */
|
||||
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_aoa, key_processor)
|
||||
|
||||
#define HID_KEYBOARD_ACCESSORY_ID 1
|
||||
|
||||
static bool
|
||||
push_mod_lock_state(struct sc_keyboard_aoa *kb, uint16_t mods_state) {
|
||||
struct sc_hid_event hid_event;
|
||||
if (!sc_hid_keyboard_event_from_mods(&hid_event, mods_state)) {
|
||||
struct sc_hid_input hid_input;
|
||||
if (!sc_hid_keyboard_generate_input_from_mods(&hid_input, mods_state)) {
|
||||
// Nothing to do
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!sc_aoa_push_hid_event(kb->aoa, HID_KEYBOARD_ACCESSORY_ID,
|
||||
&hid_event)) {
|
||||
LOGW("Could not request HID event (mod lock state)");
|
||||
if (!sc_aoa_push_input(kb->aoa, &hid_input)) {
|
||||
LOGW("Could not push AOA HID input (mod lock state)");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -41,10 +38,10 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
|
||||
|
||||
struct sc_keyboard_aoa *kb = DOWNCAST(kp);
|
||||
|
||||
struct sc_hid_event hid_event;
|
||||
struct sc_hid_input hid_input;
|
||||
|
||||
// Not all keys are supported, just ignore unsupported keys
|
||||
if (sc_hid_keyboard_event_from_key(&kb->hid, &hid_event, event)) {
|
||||
if (sc_hid_keyboard_generate_input_from_key(&kb->hid, &hid_input, event)) {
|
||||
if (!kb->mod_lock_synchronized) {
|
||||
// Inject CAPSLOCK and/or NUMLOCK if necessary to synchronize
|
||||
// keyboard state
|
||||
@ -58,11 +55,9 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
|
||||
// synchronization is acknowledged by the server, otherwise it could
|
||||
// paste the old clipboard content.
|
||||
|
||||
if (!sc_aoa_push_hid_event_with_ack_to_wait(kb->aoa,
|
||||
HID_KEYBOARD_ACCESSORY_ID,
|
||||
&hid_event,
|
||||
if (!sc_aoa_push_input_with_ack_to_wait(kb->aoa, &hid_input,
|
||||
ack_to_wait)) {
|
||||
LOGW("Could not request HID event (key)");
|
||||
LOGW("Could not push AOA HID input (key)");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -71,11 +66,12 @@ bool
|
||||
sc_keyboard_aoa_init(struct sc_keyboard_aoa *kb, struct sc_aoa *aoa) {
|
||||
kb->aoa = aoa;
|
||||
|
||||
bool ok = sc_aoa_setup_hid(aoa, HID_KEYBOARD_ACCESSORY_ID,
|
||||
SC_HID_KEYBOARD_REPORT_DESC,
|
||||
SC_HID_KEYBOARD_REPORT_DESC_LEN);
|
||||
struct sc_hid_open hid_open;
|
||||
sc_hid_keyboard_generate_open(&hid_open);
|
||||
|
||||
bool ok = sc_aoa_push_open(aoa, &hid_open, true);
|
||||
if (!ok) {
|
||||
LOGW("Register HID keyboard failed");
|
||||
LOGW("Could not push AOA HID open (keyboard)");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -102,9 +98,6 @@ sc_keyboard_aoa_init(struct sc_keyboard_aoa *kb, struct sc_aoa *aoa) {
|
||||
|
||||
void
|
||||
sc_keyboard_aoa_destroy(struct sc_keyboard_aoa *kb) {
|
||||
// Unregister HID keyboard so the soft keyboard shows again on Android
|
||||
bool ok = sc_aoa_unregister_hid(kb->aoa, HID_KEYBOARD_ACCESSORY_ID);
|
||||
if (!ok) {
|
||||
LOGW("Could not unregister HID keyboard");
|
||||
}
|
||||
(void) kb;
|
||||
// Do nothing, kb->aoa will automatically unregister all devices
|
||||
}
|
||||
|
@ -9,19 +9,16 @@
|
||||
/** Downcast mouse processor to mouse_aoa */
|
||||
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_aoa, mouse_processor)
|
||||
|
||||
#define HID_MOUSE_ACCESSORY_ID 2
|
||||
|
||||
static void
|
||||
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
||||
const struct sc_mouse_motion_event *event) {
|
||||
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
|
||||
|
||||
struct sc_hid_event hid_event;
|
||||
sc_hid_mouse_event_from_motion(&hid_event, event);
|
||||
struct sc_hid_input hid_input;
|
||||
sc_hid_mouse_generate_input_from_motion(&hid_input, event);
|
||||
|
||||
if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID,
|
||||
&hid_event)) {
|
||||
LOGW("Could not request HID event (mouse motion)");
|
||||
if (!sc_aoa_push_input(mouse->aoa, &hid_input)) {
|
||||
LOGW("Could not push AOA HID input (mouse motion)");
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,12 +27,11 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
|
||||
const struct sc_mouse_click_event *event) {
|
||||
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
|
||||
|
||||
struct sc_hid_event hid_event;
|
||||
sc_hid_mouse_event_from_click(&hid_event, event);
|
||||
struct sc_hid_input hid_input;
|
||||
sc_hid_mouse_generate_input_from_click(&hid_input, event);
|
||||
|
||||
if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID,
|
||||
&hid_event)) {
|
||||
LOGW("Could not request HID event (mouse click)");
|
||||
if (!sc_aoa_push_input(mouse->aoa, &hid_input)) {
|
||||
LOGW("Could not push AOA HID input (mouse click)");
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,12 +40,11 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
|
||||
const struct sc_mouse_scroll_event *event) {
|
||||
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
|
||||
|
||||
struct sc_hid_event hid_event;
|
||||
sc_hid_mouse_event_from_scroll(&hid_event, event);
|
||||
struct sc_hid_input hid_input;
|
||||
sc_hid_mouse_generate_input_from_scroll(&hid_input, event);
|
||||
|
||||
if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID,
|
||||
&hid_event)) {
|
||||
LOGW("Could not request HID event (mouse scroll)");
|
||||
if (!sc_aoa_push_input(mouse->aoa, &hid_input)) {
|
||||
LOGW("Could not push AOA HID input (mouse scroll)");
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,11 +52,12 @@ bool
|
||||
sc_mouse_aoa_init(struct sc_mouse_aoa *mouse, struct sc_aoa *aoa) {
|
||||
mouse->aoa = aoa;
|
||||
|
||||
bool ok = sc_aoa_setup_hid(aoa, HID_MOUSE_ACCESSORY_ID,
|
||||
SC_HID_MOUSE_REPORT_DESC,
|
||||
SC_HID_MOUSE_REPORT_DESC_LEN);
|
||||
struct sc_hid_open hid_open;
|
||||
sc_hid_mouse_generate_open(&hid_open);
|
||||
|
||||
bool ok = sc_aoa_push_open(aoa, &hid_open, true);
|
||||
if (!ok) {
|
||||
LOGW("Register HID mouse failed");
|
||||
LOGW("Could not push AOA HID open (mouse)");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -82,8 +78,6 @@ sc_mouse_aoa_init(struct sc_mouse_aoa *mouse, struct sc_aoa *aoa) {
|
||||
|
||||
void
|
||||
sc_mouse_aoa_destroy(struct sc_mouse_aoa *mouse) {
|
||||
bool ok = sc_aoa_unregister_hid(mouse->aoa, HID_MOUSE_ACCESSORY_ID);
|
||||
if (!ok) {
|
||||
LOGW("Could not unregister HID mouse");
|
||||
}
|
||||
(void) mouse;
|
||||
// Do nothing, mouse->aoa will automatically unregister all devices
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ struct scrcpy_otg {
|
||||
struct sc_aoa aoa;
|
||||
struct sc_keyboard_aoa keyboard;
|
||||
struct sc_mouse_aoa mouse;
|
||||
struct sc_gamepad_aoa gamepad;
|
||||
|
||||
struct sc_screen_otg screen_otg;
|
||||
};
|
||||
@ -21,10 +22,8 @@ sc_usb_on_disconnected(struct sc_usb *usb, void *userdata) {
|
||||
(void) usb;
|
||||
(void) userdata;
|
||||
|
||||
SDL_Event event;
|
||||
event.type = SC_EVENT_USB_DEVICE_DISCONNECTED;
|
||||
int ret = SDL_PushEvent(&event);
|
||||
if (ret < 0) {
|
||||
bool ok = sc_push_event(SC_EVENT_USB_DEVICE_DISCONNECTED);
|
||||
if (!ok) {
|
||||
LOGE("Could not post USB disconnection event: %s", SDL_GetError());
|
||||
}
|
||||
}
|
||||
@ -37,6 +36,9 @@ event_loop(struct scrcpy_otg *s) {
|
||||
case SC_EVENT_USB_DEVICE_DISCONNECTED:
|
||||
LOGW("Device disconnected");
|
||||
return SCRCPY_EXIT_DISCONNECTED;
|
||||
case SC_EVENT_AOA_OPEN_ERROR:
|
||||
LOGE("AOA open error");
|
||||
return SCRCPY_EXIT_FAILURE;
|
||||
case SDL_QUIT:
|
||||
LOGD("User requested to quit");
|
||||
return SCRCPY_EXIT_SUCCESS;
|
||||
@ -65,6 +67,13 @@ scrcpy_otg(struct scrcpy_options *options) {
|
||||
return SCRCPY_EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (options->gamepad_input_mode != SC_GAMEPAD_INPUT_MODE_DISABLED) {
|
||||
if (SDL_Init(SDL_INIT_GAMECONTROLLER)) {
|
||||
LOGE("Could not initialize SDL controller: %s", SDL_GetError());
|
||||
// Not fatal, keyboard/mouse should still work
|
||||
}
|
||||
}
|
||||
|
||||
atexit(SDL_Quit);
|
||||
|
||||
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
|
||||
@ -75,6 +84,7 @@ scrcpy_otg(struct scrcpy_options *options) {
|
||||
|
||||
struct sc_keyboard_aoa *keyboard = NULL;
|
||||
struct sc_mouse_aoa *mouse = NULL;
|
||||
struct sc_gamepad_aoa *gamepad = NULL;
|
||||
bool usb_device_initialized = false;
|
||||
bool usb_connected = false;
|
||||
bool aoa_started = false;
|
||||
@ -121,11 +131,15 @@ scrcpy_otg(struct scrcpy_options *options) {
|
||||
|| options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_DISABLED);
|
||||
assert(options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA
|
||||
|| options->mouse_input_mode == SC_MOUSE_INPUT_MODE_DISABLED);
|
||||
assert(options->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_AOA
|
||||
|| options->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_DISABLED);
|
||||
|
||||
bool enable_keyboard =
|
||||
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA;
|
||||
bool enable_mouse =
|
||||
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA;
|
||||
bool enable_gamepad =
|
||||
options->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_AOA;
|
||||
|
||||
if (enable_keyboard) {
|
||||
ok = sc_keyboard_aoa_init(&s->keyboard, &s->aoa);
|
||||
@ -143,6 +157,11 @@ scrcpy_otg(struct scrcpy_options *options) {
|
||||
mouse = &s->mouse;
|
||||
}
|
||||
|
||||
if (enable_gamepad) {
|
||||
sc_gamepad_aoa_init(&s->gamepad, &s->aoa);
|
||||
gamepad = &s->gamepad;
|
||||
}
|
||||
|
||||
ok = sc_aoa_start(&s->aoa);
|
||||
if (!ok) {
|
||||
goto end;
|
||||
@ -157,6 +176,7 @@ scrcpy_otg(struct scrcpy_options *options) {
|
||||
struct sc_screen_otg_params params = {
|
||||
.keyboard = keyboard,
|
||||
.mouse = mouse,
|
||||
.gamepad = gamepad,
|
||||
.window_title = window_title,
|
||||
.always_on_top = options->always_on_top,
|
||||
.window_x = options->window_x,
|
||||
@ -190,6 +210,9 @@ end:
|
||||
if (keyboard) {
|
||||
sc_keyboard_aoa_destroy(&s->keyboard);
|
||||
}
|
||||
if (gamepad) {
|
||||
sc_gamepad_aoa_destroy(&s->gamepad);
|
||||
}
|
||||
|
||||
if (aoa_initialized) {
|
||||
sc_aoa_join(&s->aoa);
|
||||
|
@ -59,6 +59,7 @@ sc_screen_otg_init(struct sc_screen_otg *screen,
|
||||
const struct sc_screen_otg_params *params) {
|
||||
screen->keyboard = params->keyboard;
|
||||
screen->mouse = params->mouse;
|
||||
screen->gamepad = params->gamepad;
|
||||
|
||||
screen->mouse_capture_key_pressed = 0;
|
||||
|
||||
@ -214,6 +215,77 @@ sc_screen_otg_process_mouse_wheel(struct sc_screen_otg *screen,
|
||||
mp->ops->process_mouse_scroll(mp, &evt);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_otg_process_gamepad_device(struct sc_screen_otg *screen,
|
||||
const SDL_ControllerDeviceEvent *event) {
|
||||
assert(screen->gamepad);
|
||||
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
|
||||
|
||||
SDL_JoystickID id;
|
||||
if (event->type == SDL_CONTROLLERDEVICEADDED) {
|
||||
SDL_GameController *gc = SDL_GameControllerOpen(event->which);
|
||||
if (!gc) {
|
||||
LOGW("Could not open game controller");
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gc);
|
||||
if (!joystick) {
|
||||
LOGW("Could not get controller joystick");
|
||||
SDL_GameControllerClose(gc);
|
||||
return;
|
||||
}
|
||||
|
||||
id = SDL_JoystickInstanceID(joystick);
|
||||
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||
id = event->which;
|
||||
|
||||
SDL_GameController *gc = SDL_GameControllerFromInstanceID(id);
|
||||
if (gc) {
|
||||
SDL_GameControllerClose(gc);
|
||||
} else {
|
||||
LOGW("Unknown gamepad device removed");
|
||||
}
|
||||
} else {
|
||||
// Nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_gamepad_device_event evt = {
|
||||
.type = sc_gamepad_device_event_type_from_sdl_type(event->type),
|
||||
.gamepad_id = id,
|
||||
};
|
||||
gp->ops->process_gamepad_device(gp, &evt);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_otg_process_gamepad_axis(struct sc_screen_otg *screen,
|
||||
const SDL_ControllerAxisEvent *event) {
|
||||
assert(screen->gamepad);
|
||||
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
|
||||
|
||||
struct sc_gamepad_axis_event evt = {
|
||||
.gamepad_id = event->which,
|
||||
.axis = sc_gamepad_axis_from_sdl(event->axis),
|
||||
.value = event->value,
|
||||
};
|
||||
gp->ops->process_gamepad_axis(gp, &evt);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_otg_process_gamepad_button(struct sc_screen_otg *screen,
|
||||
const SDL_ControllerButtonEvent *event) {
|
||||
assert(screen->gamepad);
|
||||
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
|
||||
|
||||
struct sc_gamepad_button_event evt = {
|
||||
.gamepad_id = event->which,
|
||||
.action = sc_action_from_sdl_controllerbutton_type(event->type),
|
||||
.button = sc_gamepad_button_from_sdl(event->button),
|
||||
};
|
||||
gp->ops->process_gamepad_button(gp, &evt);
|
||||
}
|
||||
|
||||
void
|
||||
sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) {
|
||||
switch (event->type) {
|
||||
@ -293,5 +365,23 @@ sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) {
|
||||
sc_screen_otg_process_mouse_wheel(screen, &event->wheel);
|
||||
}
|
||||
break;
|
||||
case SDL_CONTROLLERDEVICEADDED:
|
||||
case SDL_CONTROLLERDEVICEREMOVED:
|
||||
// Handle device added or removed even if paused
|
||||
if (screen->gamepad) {
|
||||
sc_screen_otg_process_gamepad_device(screen, &event->cdevice);
|
||||
}
|
||||
break;
|
||||
case SDL_CONTROLLERAXISMOTION:
|
||||
if (screen->gamepad) {
|
||||
sc_screen_otg_process_gamepad_axis(screen, &event->caxis);
|
||||
}
|
||||
break;
|
||||
case SDL_CONTROLLERBUTTONDOWN:
|
||||
case SDL_CONTROLLERBUTTONUP:
|
||||
if (screen->gamepad) {
|
||||
sc_screen_otg_process_gamepad_button(screen, &event->cbutton);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -8,10 +8,12 @@
|
||||
|
||||
#include "keyboard_aoa.h"
|
||||
#include "mouse_aoa.h"
|
||||
#include "gamepad_aoa.h"
|
||||
|
||||
struct sc_screen_otg {
|
||||
struct sc_keyboard_aoa *keyboard;
|
||||
struct sc_mouse_aoa *mouse;
|
||||
struct sc_gamepad_aoa *gamepad;
|
||||
|
||||
SDL_Window *window;
|
||||
SDL_Renderer *renderer;
|
||||
@ -24,6 +26,7 @@ struct sc_screen_otg {
|
||||
struct sc_screen_otg_params {
|
||||
struct sc_keyboard_aoa *keyboard;
|
||||
struct sc_mouse_aoa *mouse;
|
||||
struct sc_gamepad_aoa *gamepad;
|
||||
|
||||
const char *window_title;
|
||||
bool always_on_top;
|
||||
|
@ -13,6 +13,12 @@ sc_write16be(uint8_t *buf, uint16_t value) {
|
||||
buf[1] = value;
|
||||
}
|
||||
|
||||
static inline void
|
||||
sc_write16le(uint8_t *buf, uint16_t value) {
|
||||
buf[0] = value;
|
||||
buf[1] = value >> 8;
|
||||
}
|
||||
|
||||
static inline void
|
||||
sc_write32be(uint8_t *buf, uint32_t value) {
|
||||
buf[0] = value >> 24;
|
||||
@ -21,12 +27,26 @@ sc_write32be(uint8_t *buf, uint32_t value) {
|
||||
buf[3] = value;
|
||||
}
|
||||
|
||||
static inline void
|
||||
sc_write32le(uint8_t *buf, uint32_t value) {
|
||||
buf[0] = value;
|
||||
buf[1] = value >> 8;
|
||||
buf[2] = value >> 16;
|
||||
buf[3] = value >> 24;
|
||||
}
|
||||
|
||||
static inline void
|
||||
sc_write64be(uint8_t *buf, uint64_t value) {
|
||||
sc_write32be(buf, value >> 32);
|
||||
sc_write32be(&buf[4], (uint32_t) value);
|
||||
}
|
||||
|
||||
static inline void
|
||||
sc_write64le(uint8_t *buf, uint64_t value) {
|
||||
sc_write32le(buf, (uint32_t) value);
|
||||
sc_write32le(buf, value >> 32);
|
||||
}
|
||||
|
||||
static inline uint16_t
|
||||
sc_read16be(const uint8_t *buf) {
|
||||
return (buf[0] << 8) | buf[1];
|
||||
|
@ -6,6 +6,8 @@
|
||||
|
||||
#include "log.h"
|
||||
|
||||
sc_thread_id SC_MAIN_THREAD_ID;
|
||||
|
||||
bool
|
||||
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
|
||||
void *userdata) {
|
||||
|
@ -39,6 +39,8 @@ typedef struct sc_cond {
|
||||
SDL_cond *cond;
|
||||
} sc_cond;
|
||||
|
||||
extern sc_thread_id SC_MAIN_THREAD_ID;
|
||||
|
||||
bool
|
||||
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
|
||||
void *userdata);
|
||||
|
@ -370,6 +370,25 @@ static void test_serialize_uhid_input(void) {
|
||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||
}
|
||||
|
||||
static void test_serialize_uhid_destroy(void) {
|
||||
struct sc_control_msg msg = {
|
||||
.type = SC_CONTROL_MSG_TYPE_UHID_DESTROY,
|
||||
.uhid_destroy = {
|
||||
.id = 42,
|
||||
},
|
||||
};
|
||||
|
||||
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
|
||||
size_t size = sc_control_msg_serialize(&msg, buf);
|
||||
assert(size == 3);
|
||||
|
||||
const uint8_t expected[] = {
|
||||
SC_CONTROL_MSG_TYPE_UHID_DESTROY,
|
||||
0, 42, // id
|
||||
};
|
||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||
}
|
||||
|
||||
static void test_serialize_open_hard_keyboard(void) {
|
||||
struct sc_control_msg msg = {
|
||||
.type = SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
|
||||
@ -405,6 +424,7 @@ int main(int argc, char *argv[]) {
|
||||
test_serialize_rotate_device();
|
||||
test_serialize_uhid_create();
|
||||
test_serialize_uhid_input();
|
||||
test_serialize_uhid_destroy();
|
||||
test_serialize_open_hard_keyboard();
|
||||
return 0;
|
||||
}
|
||||
|
@ -21,7 +21,8 @@ public final class ControlMessage {
|
||||
public static final int TYPE_ROTATE_DEVICE = 11;
|
||||
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_UHID_DESTROY = 14;
|
||||
public static final int TYPE_OPEN_HARD_KEYBOARD_SETTINGS = 15;
|
||||
|
||||
public static final long SEQUENCE_INVALID = 0;
|
||||
|
||||
@ -146,6 +147,13 @@ public final class ControlMessage {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static ControlMessage createUhidDestroy(int id) {
|
||||
ControlMessage msg = new ControlMessage();
|
||||
msg.type = TYPE_UHID_DESTROY;
|
||||
msg.id = id;
|
||||
return msg;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
@ -51,6 +51,8 @@ public class ControlMessageReader {
|
||||
return parseUhidCreate();
|
||||
case ControlMessage.TYPE_UHID_INPUT:
|
||||
return parseUhidInput();
|
||||
case ControlMessage.TYPE_UHID_DESTROY:
|
||||
return parseUhidDestroy();
|
||||
default:
|
||||
throw new ControlProtocolException("Unknown event type: " + type);
|
||||
}
|
||||
@ -142,6 +144,11 @@ public class ControlMessageReader {
|
||||
return ControlMessage.createUhidInput(id, data);
|
||||
}
|
||||
|
||||
private ControlMessage parseUhidDestroy() throws IOException {
|
||||
int id = dis.readUnsignedShort();
|
||||
return ControlMessage.createUhidDestroy(id);
|
||||
}
|
||||
|
||||
private Position parsePosition() throws IOException {
|
||||
int x = dis.readInt();
|
||||
int y = dis.readInt();
|
||||
|
@ -215,6 +215,9 @@ public class Controller implements AsyncProcessor {
|
||||
case ControlMessage.TYPE_UHID_INPUT:
|
||||
getUhidManager().writeInput(msg.getId(), msg.getData());
|
||||
break;
|
||||
case ControlMessage.TYPE_UHID_DESTROY:
|
||||
getUhidManager().close(msg.getId());
|
||||
break;
|
||||
case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
||||
openHardKeyboardSettings();
|
||||
break;
|
||||
|
@ -95,6 +95,12 @@ public final class UhidManager {
|
||||
}
|
||||
}
|
||||
|
||||
private void unregisterUhidListener(FileDescriptor fd) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
queue.removeOnFileDescriptorEventListener(fd);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] extractHidOutputData(ByteBuffer buffer) {
|
||||
/*
|
||||
* #define UHID_DATA_MAX 4096
|
||||
@ -199,9 +205,15 @@ public final class UhidManager {
|
||||
}
|
||||
|
||||
public void close(int id) {
|
||||
FileDescriptor fd = fds.get(id);
|
||||
assert fd != null;
|
||||
// Linux: Documentation/hid/uhid.rst
|
||||
// If you close() the fd, the device is automatically unregistered and destroyed internally.
|
||||
FileDescriptor fd = fds.remove(id);
|
||||
if (fd != null) {
|
||||
unregisterUhidListener(fd);
|
||||
close(fd);
|
||||
} else {
|
||||
Ln.w("Closing unknown UHID device: " + id);
|
||||
}
|
||||
}
|
||||
|
||||
public void closeAll() {
|
||||
|
@ -362,6 +362,24 @@ public class ControlMessageReaderTest {
|
||||
Assert.assertEquals(-1, bis.read()); // EOS
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseUhidDestroy() throws IOException {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(bos);
|
||||
dos.writeByte(ControlMessage.TYPE_UHID_DESTROY);
|
||||
dos.writeShort(42); // id
|
||||
byte[] packet = bos.toByteArray();
|
||||
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(packet);
|
||||
ControlMessageReader reader = new ControlMessageReader(bis);
|
||||
|
||||
ControlMessage event = reader.read();
|
||||
Assert.assertEquals(ControlMessage.TYPE_UHID_DESTROY, event.getType());
|
||||
Assert.assertEquals(42, event.getId());
|
||||
|
||||
Assert.assertEquals(-1, bis.read()); // EOS
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseOpenHardKeyboardSettings() throws IOException {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
Loading…
x
Reference in New Issue
Block a user