Compare commits
34 Commits
master
...
gamepad.dr
Author | SHA1 | Date | |
---|---|---|---|
|
3ade819cef | ||
|
7828bb2dbd | ||
|
b32406681b | ||
|
b266fdb229 | ||
|
1487bddfa2 | ||
|
519969d3e0 | ||
|
f6a40ecfa9 | ||
|
97352bc21d | ||
|
1c326c9330 | ||
|
9be3a68ac6 | ||
|
a293bf368e | ||
|
0ad8ca2141 | ||
|
0c432bc854 | ||
|
a436a9ec34 | ||
|
3180218b9a | ||
|
3ae38a5c54 | ||
|
b633614d36 | ||
|
5841650aa0 | ||
|
9c382e8169 | ||
|
037009b11c | ||
|
9c5876cc15 | ||
|
ced31229c8 | ||
|
d5f7a5e704 | ||
|
80dc95c215 | ||
|
b04d8eb162 | ||
|
698e4d1bae | ||
|
c3a4b17842 | ||
|
59d80afab7 | ||
|
cb79d97f5d | ||
|
f355672ab2 | ||
|
154507c009 | ||
|
a3405ab62e | ||
|
cdafbec7e3 | ||
|
698faae1ad |
@ -15,6 +15,7 @@ src = [
|
|||||||
'src/demuxer.c',
|
'src/demuxer.c',
|
||||||
'src/device_msg.c',
|
'src/device_msg.c',
|
||||||
'src/display.c',
|
'src/display.c',
|
||||||
|
'src/events.c',
|
||||||
'src/icon.c',
|
'src/icon.c',
|
||||||
'src/file_pusher.c',
|
'src/file_pusher.c',
|
||||||
'src/fps_counter.c',
|
'src/fps_counter.c',
|
||||||
@ -31,10 +32,12 @@ src = [
|
|||||||
'src/screen.c',
|
'src/screen.c',
|
||||||
'src/server.c',
|
'src/server.c',
|
||||||
'src/version.c',
|
'src/version.c',
|
||||||
|
'src/hid/hid_gamepad.c',
|
||||||
'src/hid/hid_keyboard.c',
|
'src/hid/hid_keyboard.c',
|
||||||
'src/hid/hid_mouse.c',
|
'src/hid/hid_mouse.c',
|
||||||
'src/trait/frame_source.c',
|
'src/trait/frame_source.c',
|
||||||
'src/trait/packet_source.c',
|
'src/trait/packet_source.c',
|
||||||
|
'src/uhid/gamepad_uhid.c',
|
||||||
'src/uhid/keyboard_uhid.c',
|
'src/uhid/keyboard_uhid.c',
|
||||||
'src/uhid/mouse_uhid.c',
|
'src/uhid/mouse_uhid.c',
|
||||||
'src/uhid/uhid_output.c',
|
'src/uhid/uhid_output.c',
|
||||||
@ -93,6 +96,7 @@ usb_support = get_option('usb')
|
|||||||
if usb_support
|
if usb_support
|
||||||
src += [
|
src += [
|
||||||
'src/usb/aoa_hid.c',
|
'src/usb/aoa_hid.c',
|
||||||
|
'src/usb/gamepad_aoa.c',
|
||||||
'src/usb/keyboard_aoa.c',
|
'src/usb/keyboard_aoa.c',
|
||||||
'src/usb/mouse_aoa.c',
|
'src/usb/mouse_aoa.c',
|
||||||
'src/usb/scrcpy_otg.c',
|
'src/usb/scrcpy_otg.c',
|
||||||
|
17
app/scrcpy.1
17
app/scrcpy.1
@ -175,6 +175,17 @@ Start in fullscreen.
|
|||||||
.B \-\-force\-adb\-forward
|
.B \-\-force\-adb\-forward
|
||||||
Do not attempt to use "adb reverse" to connect to the device.
|
Do not attempt to use "adb reverse" to connect to the device.
|
||||||
|
|
||||||
|
.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
|
.TP
|
||||||
.B \-h, \-\-help
|
.B \-h, \-\-help
|
||||||
Print this help.
|
Print this help.
|
||||||
@ -200,7 +211,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).
|
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
|
.TP
|
||||||
.B \-\-kill\-adb\-on\-close
|
.B \-\-kill\-adb\-on\-close
|
||||||
@ -267,7 +278,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.
|
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
|
.TP
|
||||||
.BI "\-\-mouse\-bind " xxxx[:xxxx]
|
.BI "\-\-mouse\-bind " xxxx[:xxxx]
|
||||||
@ -369,7 +380,7 @@ If any of \fB\-\-hid\-keyboard\fR or \fB\-\-hid\-mouse\fR is set, only enable ke
|
|||||||
|
|
||||||
It may only work over USB.
|
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
|
.TP
|
||||||
.BI "\-p, \-\-port " port\fR[:\fIport\fR]
|
.BI "\-p, \-\-port " port\fR[:\fIport\fR]
|
||||||
|
@ -101,6 +101,7 @@ enum {
|
|||||||
OPT_MOUSE_BIND,
|
OPT_MOUSE_BIND,
|
||||||
OPT_NO_MOUSE_HOVER,
|
OPT_NO_MOUSE_HOVER,
|
||||||
OPT_AUDIO_DUP,
|
OPT_AUDIO_DUP,
|
||||||
|
OPT_GAMEPAD,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_option {
|
struct sc_option {
|
||||||
@ -372,6 +373,23 @@ static const struct sc_option options[] = {
|
|||||||
.longopt_id = OPT_FORWARD_ALL_CLICKS,
|
.longopt_id = OPT_FORWARD_ALL_CLICKS,
|
||||||
.longopt = "forward-all-clicks",
|
.longopt = "forward-all-clicks",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.shortopt = 'G',
|
||||||
|
.text = "Same as --gamepad=uhid.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.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',
|
.shortopt = 'h',
|
||||||
.longopt = "help",
|
.longopt = "help",
|
||||||
@ -403,7 +421,7 @@ static const struct sc_option options[] = {
|
|||||||
"start -a android.settings.HARD_KEYBOARD_SETTINGS`.\n"
|
"start -a android.settings.HARD_KEYBOARD_SETTINGS`.\n"
|
||||||
"This option is only available when a HID keyboard is enabled "
|
"This option is only available when a HID keyboard is enabled "
|
||||||
"(or a physical keyboard is connected).\n"
|
"(or a physical keyboard is connected).\n"
|
||||||
"Also see --mouse.",
|
"Also see --mouse and --gamepad.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_KILL_ADB_ON_CLOSE,
|
.longopt_id = OPT_KILL_ADB_ON_CLOSE,
|
||||||
@ -502,7 +520,7 @@ static const struct sc_option options[] = {
|
|||||||
"to control the device directly (relative mouse mode).\n"
|
"to control the device directly (relative mouse mode).\n"
|
||||||
"LAlt, LSuper or RSuper toggle the capture mode, to give "
|
"LAlt, LSuper or RSuper toggle the capture mode, to give "
|
||||||
"control of the mouse back to the computer.\n"
|
"control of the mouse back to the computer.\n"
|
||||||
"Also see --keyboard.",
|
"Also see --keyboard and --gamepad.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_MOUSE_BIND,
|
.longopt_id = OPT_MOUSE_BIND,
|
||||||
@ -637,7 +655,7 @@ static const struct sc_option options[] = {
|
|||||||
"Keyboard and mouse may be disabled separately using"
|
"Keyboard and mouse may be disabled separately using"
|
||||||
"--keyboard=disabled and --mouse=disabled.\n"
|
"--keyboard=disabled and --mouse=disabled.\n"
|
||||||
"It may only work over USB.\n"
|
"It may only work over USB.\n"
|
||||||
"See --keyboard and --mouse.",
|
"See --keyboard, --mouse and --gamepad.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.shortopt = 'p',
|
.shortopt = 'p',
|
||||||
@ -2058,6 +2076,32 @@ parse_mouse(const char *optarg, enum sc_mouse_input_mode *mode) {
|
|||||||
return false;
|
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
|
static bool
|
||||||
parse_time_limit(const char *s, sc_tick *tick) {
|
parse_time_limit(const char *s, sc_tick *tick) {
|
||||||
long value;
|
long value;
|
||||||
@ -2626,6 +2670,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
case OPT_AUDIO_DUP:
|
case OPT_AUDIO_DUP:
|
||||||
opts->audio_dup = true;
|
opts->audio_dup = true;
|
||||||
break;
|
break;
|
||||||
|
case 'G':
|
||||||
|
opts->gamepad_input_mode = SC_GAMEPAD_INPUT_MODE_UHID;
|
||||||
|
break;
|
||||||
|
case OPT_GAMEPAD:
|
||||||
|
if (!parse_gamepad(optarg, &opts->gamepad_input_mode)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
return false;
|
return false;
|
||||||
@ -2758,6 +2810,15 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
LOGE("SDK mouse mode requires video playback. Try --mouse=uhid.");
|
LOGE("SDK mouse mode requires video playback. Try --mouse=uhid.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (opts->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_AUTO) {
|
||||||
|
if (otg) {
|
||||||
|
opts->gamepad_input_mode = SC_GAMEPAD_INPUT_MODE_AOA;
|
||||||
|
} else {
|
||||||
|
// UHID does not work on all devices (with old Android
|
||||||
|
// versions), so it cannot be enabled by default
|
||||||
|
opts->gamepad_input_mode = SC_GAMEPAD_INPUT_MODE_DISABLED;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If mouse bindings are not explicitly set, configure default bindings
|
// 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);
|
sc_write16be(&buf[3], msg->uhid_input.size);
|
||||||
memcpy(&buf[5], msg->uhid_input.data, msg->uhid_input.size);
|
memcpy(&buf[5], msg->uhid_input.data, msg->uhid_input.size);
|
||||||
return 5 + 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_NOTIFICATION_PANEL:
|
||||||
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
||||||
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||||
@ -269,6 +272,9 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
|||||||
}
|
}
|
||||||
break;
|
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:
|
case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
||||||
LOG_CMSG("open hard keyboard settings");
|
LOG_CMSG("open hard keyboard settings");
|
||||||
break;
|
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
|
void
|
||||||
sc_control_msg_destroy(struct sc_control_msg *msg) {
|
sc_control_msg_destroy(struct sc_control_msg *msg) {
|
||||||
switch (msg->type) {
|
switch (msg->type) {
|
||||||
|
@ -39,6 +39,7 @@ enum sc_control_msg_type {
|
|||||||
SC_CONTROL_MSG_TYPE_ROTATE_DEVICE,
|
SC_CONTROL_MSG_TYPE_ROTATE_DEVICE,
|
||||||
SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
||||||
SC_CONTROL_MSG_TYPE_UHID_INPUT,
|
SC_CONTROL_MSG_TYPE_UHID_INPUT,
|
||||||
|
SC_CONTROL_MSG_TYPE_UHID_DESTROY,
|
||||||
SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
|
SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -105,6 +106,9 @@ struct sc_control_msg {
|
|||||||
uint16_t size;
|
uint16_t size;
|
||||||
uint8_t data[SC_HID_MAX_SIZE];
|
uint8_t data[SC_HID_MAX_SIZE];
|
||||||
} uhid_input;
|
} 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
|
void
|
||||||
sc_control_msg_log(const struct sc_control_msg *msg);
|
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
|
void
|
||||||
sc_control_msg_destroy(struct sc_control_msg *msg);
|
sc_control_msg_destroy(struct sc_control_msg *msg);
|
||||||
|
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
#include "util/log.h"
|
#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
|
static void
|
||||||
sc_controller_receiver_on_ended(struct sc_receiver *receiver, bool error,
|
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) {
|
void *cbs_userdata) {
|
||||||
sc_vecdeque_init(&controller->queue);
|
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) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -93,20 +96,31 @@ sc_controller_push_msg(struct sc_controller *controller,
|
|||||||
sc_control_msg_log(msg);
|
sc_control_msg_log(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool pushed = false;
|
||||||
|
|
||||||
sc_mutex_lock(&controller->mutex);
|
sc_mutex_lock(&controller->mutex);
|
||||||
bool full = sc_vecdeque_is_full(&controller->queue);
|
size_t size = sc_vecdeque_size(&controller->queue);
|
||||||
if (!full) {
|
if (size < SC_CONTROL_MSG_QUEUE_LIMIT) {
|
||||||
bool was_empty = sc_vecdeque_is_empty(&controller->queue);
|
bool was_empty = sc_vecdeque_is_empty(&controller->queue);
|
||||||
sc_vecdeque_push_noresize(&controller->queue, *msg);
|
sc_vecdeque_push_noresize(&controller->queue, *msg);
|
||||||
|
pushed = true;
|
||||||
if (was_empty) {
|
if (was_empty) {
|
||||||
sc_cond_signal(&controller->msg_cond);
|
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
|
// Otherwise (if the queue is full), the msg is discarded
|
||||||
|
|
||||||
sc_mutex_unlock(&controller->mutex);
|
sc_mutex_unlock(&controller->mutex);
|
||||||
|
|
||||||
return !full;
|
return pushed;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
|
40
app/src/events.c
Normal file
40
app/src/events.c
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#include "events.h"
|
||||||
|
|
||||||
|
#include "util/log.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) {
|
||||||
|
LOGW("Coud not post to main thread: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
@ -1,10 +1,35 @@
|
|||||||
#define SC_EVENT_NEW_FRAME SDL_USEREVENT
|
#ifndef SC_EVENTS
|
||||||
#define SC_EVENT_DEVICE_DISCONNECTED (SDL_USEREVENT + 1)
|
#define SC_EVENTS
|
||||||
#define SC_EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2)
|
|
||||||
#define SC_EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3)
|
#include "common.h"
|
||||||
#define SC_EVENT_USB_DEVICE_DISCONNECTED (SDL_USEREVENT + 4)
|
|
||||||
#define SC_EVENT_DEMUXER_ERROR (SDL_USEREVENT + 5)
|
#include <stdbool.h>
|
||||||
#define SC_EVENT_RECORDER_ERROR (SDL_USEREVENT + 6)
|
#include <stdint.h>
|
||||||
#define SC_EVENT_SCREEN_INIT_SIZE (SDL_USEREVENT + 7)
|
#include <SDL_events.h>
|
||||||
#define SC_EVENT_TIME_LIMIT_REACHED (SDL_USEREVENT + 8)
|
|
||||||
#define SC_EVENT_CONTROLLER_ERROR (SDL_USEREVENT + 9)
|
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);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
@ -5,11 +5,22 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#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 data[SC_HID_MAX_SIZE];
|
||||||
uint8_t 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
|
#endif
|
||||||
|
456
app/src/hid/hid_gamepad.c
Normal file
456
app/src/hid/hid_gamepad.c
Normal file
@ -0,0 +1,456 @@
|
|||||||
|
#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 (65535)
|
||||||
|
0x27, 0xFF, 0xFF, 0x00, 0x00,
|
||||||
|
// 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 (15)
|
||||||
|
0x29, 0x0E,
|
||||||
|
// Logical Minimum (0)
|
||||||
|
0x15, 0x00,
|
||||||
|
// Logical Maximum (1)
|
||||||
|
0x25, 0x01,
|
||||||
|
// Report Count (15)
|
||||||
|
0x95, 0x0F,
|
||||||
|
// Report Size (1)
|
||||||
|
0x75, 0x01,
|
||||||
|
// Input (Data, Variable, Absolute): 15 buttons bits
|
||||||
|
0x81, 0x02,
|
||||||
|
|
||||||
|
// Report Count (1)
|
||||||
|
0x95, 0x01,
|
||||||
|
// Report Size (1)
|
||||||
|
0x75, 0x01,
|
||||||
|
// Input (Constant): 1-bit padding
|
||||||
|
0x81, 0x01,
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
// Buttons are individual bits in the report descriptor, so the resulting
|
||||||
|
// u16 must be written in little-endian
|
||||||
|
sc_write16be(data, slot->axis_left_x);
|
||||||
|
sc_write16be(data + 2, slot->axis_left_y);
|
||||||
|
sc_write16be(data + 4, slot->axis_right_x);
|
||||||
|
sc_write16be(data + 6, slot->axis_right_y);
|
||||||
|
sc_write16be(data + 8, slot->axis_left_trigger);
|
||||||
|
sc_write16be(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]
|
||||||
|
uint16_t value = ((int32_t) event->value) + 0x8000;
|
||||||
|
switch (event->axis) {
|
||||||
|
case SC_GAMEPAD_AXIS_LEFTX:
|
||||||
|
slot->axis_left_x = value;
|
||||||
|
break;
|
||||||
|
case SC_GAMEPAD_AXIS_LEFTY:
|
||||||
|
slot->axis_left_y = value;
|
||||||
|
break;
|
||||||
|
case SC_GAMEPAD_AXIS_RIGHTX:
|
||||||
|
slot->axis_right_x = value;
|
||||||
|
break;
|
||||||
|
case SC_GAMEPAD_AXIS_RIGHTY:
|
||||||
|
slot->axis_right_y = value;
|
||||||
|
break;
|
||||||
|
case SC_GAMEPAD_AXIS_LEFT_TRIGGER:
|
||||||
|
slot->axis_left_trigger = value;
|
||||||
|
break;
|
||||||
|
case SC_GAMEPAD_AXIS_RIGHT_TRIGGER:
|
||||||
|
slot->axis_right_trigger = 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
|
// keyboard support, though OS could support more keys via modifying the report
|
||||||
// desc. 6 should be enough for scrcpy.
|
// desc. 6 should be enough for scrcpy.
|
||||||
#define SC_HID_KEYBOARD_MAX_KEYS 6
|
#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)
|
(SC_HID_KEYBOARD_INDEX_KEYS + SC_HID_KEYBOARD_MAX_KEYS)
|
||||||
|
|
||||||
#define SC_HID_RESERVED 0x00
|
#define SC_HID_RESERVED 0x00
|
||||||
@ -31,13 +31,16 @@
|
|||||||
* For HID, only report descriptor is needed.
|
* For HID, only report descriptor is needed.
|
||||||
*
|
*
|
||||||
* The specification is available here:
|
* 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:
|
* In particular, read:
|
||||||
* - 6.2.2 Report Descriptor
|
* - §6.2.2 Report Descriptor
|
||||||
* - Appendix B.1 Protocol 1 (Keyboard)
|
* - Appendix B.1 Protocol 1 (Keyboard)
|
||||||
* - Appendix C: Keyboard Implementation
|
* - 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:
|
* Normally a basic HID keyboard uses 8 bytes:
|
||||||
* Modifier Reserved Key Key Key Key Key Key
|
* Modifier Reserved Key Key Key Key Key Key
|
||||||
*
|
*
|
||||||
@ -47,7 +50,7 @@
|
|||||||
*
|
*
|
||||||
* (change vid:pid' to your device's vendor ID and product ID).
|
* (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)
|
// Usage Page (Generic Desktop)
|
||||||
0x05, 0x01,
|
0x05, 0x01,
|
||||||
// Usage (Keyboard)
|
// Usage (Keyboard)
|
||||||
@ -121,9 +124,6 @@ const uint8_t SC_HID_KEYBOARD_REPORT_DESC[] = {
|
|||||||
0xC0
|
0xC0
|
||||||
};
|
};
|
||||||
|
|
||||||
const size_t SC_HID_KEYBOARD_REPORT_DESC_LEN =
|
|
||||||
sizeof(SC_HID_KEYBOARD_REPORT_DESC);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A keyboard HID event is 8 bytes long:
|
* A keyboard HID event is 8 bytes long:
|
||||||
*
|
*
|
||||||
@ -199,10 +199,11 @@ const size_t SC_HID_KEYBOARD_REPORT_DESC_LEN =
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) {
|
sc_hid_keyboard_input_init(struct sc_hid_input *hid_input) {
|
||||||
hid_event->size = SC_HID_KEYBOARD_EVENT_SIZE;
|
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[SC_HID_KEYBOARD_INDEX_MODS] = SC_HID_MOD_NONE;
|
||||||
data[1] = SC_HID_RESERVED;
|
data[1] = SC_HID_RESERVED;
|
||||||
@ -250,8 +251,8 @@ scancode_is_modifier(enum sc_scancode scancode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_hid_keyboard_event_from_key(struct sc_hid_keyboard *hid,
|
sc_hid_keyboard_generate_input_from_key(struct sc_hid_keyboard *hid,
|
||||||
struct sc_hid_event *hid_event,
|
struct sc_hid_input *hid_input,
|
||||||
const struct sc_key_event *event) {
|
const struct sc_key_event *event) {
|
||||||
enum sc_scancode scancode = event->scancode;
|
enum sc_scancode scancode = event->scancode;
|
||||||
assert(scancode >= 0);
|
assert(scancode >= 0);
|
||||||
@ -264,7 +265,7 @@ sc_hid_keyboard_event_from_key(struct sc_hid_keyboard *hid,
|
|||||||
return false;
|
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);
|
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->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
|
// Re-calculate pressed keys every time
|
||||||
int keys_pressed_count = 0;
|
int keys_pressed_count = 0;
|
||||||
for (int i = 0; i < SC_HID_KEYBOARD_KEYS; ++i) {
|
for (int i = 0; i < SC_HID_KEYBOARD_KEYS; ++i) {
|
||||||
@ -308,7 +309,7 @@ end:
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
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) {
|
uint16_t mods_state) {
|
||||||
bool capslock = mods_state & SC_MOD_CAPS;
|
bool capslock = mods_state & SC_MOD_CAPS;
|
||||||
bool numlock = mods_state & SC_MOD_NUM;
|
bool numlock = mods_state & SC_MOD_NUM;
|
||||||
@ -317,17 +318,27 @@ sc_hid_keyboard_event_from_mods(struct sc_hid_event *event,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_hid_keyboard_event_init(event);
|
sc_hid_keyboard_input_init(hid_input);
|
||||||
|
|
||||||
unsigned i = 0;
|
unsigned i = 0;
|
||||||
if (capslock) {
|
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;
|
++i;
|
||||||
}
|
}
|
||||||
if (numlock) {
|
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;
|
++i;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
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.
|
// 0x65 is Application, typically AT-101 Keyboard ends here.
|
||||||
#define SC_HID_KEYBOARD_KEYS 0x66
|
#define SC_HID_KEYBOARD_KEYS 0x66
|
||||||
|
|
||||||
extern const uint8_t SC_HID_KEYBOARD_REPORT_DESC[];
|
#define SC_HID_ID_KEYBOARD 1
|
||||||
extern const size_t SC_HID_KEYBOARD_REPORT_DESC_LEN;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HID keyboard events are sequence-based, every time keyboard state changes
|
* HID keyboard events are sequence-based, every time keyboard state changes
|
||||||
@ -36,13 +35,19 @@ struct sc_hid_keyboard {
|
|||||||
void
|
void
|
||||||
sc_hid_keyboard_init(struct sc_hid_keyboard *hid);
|
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
|
bool
|
||||||
sc_hid_keyboard_event_from_key(struct sc_hid_keyboard *hid,
|
sc_hid_keyboard_generate_input_from_key(struct sc_hid_keyboard *hid,
|
||||||
struct sc_hid_event *hid_event,
|
struct sc_hid_input *hid_input,
|
||||||
const struct sc_key_event *event);
|
const struct sc_key_event *event);
|
||||||
|
|
||||||
bool
|
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);
|
uint16_t mods_state);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -2,17 +2,17 @@
|
|||||||
|
|
||||||
// 1 byte for buttons + padding, 1 byte for X position, 1 byte for Y position,
|
// 1 byte for buttons + padding, 1 byte for X position, 1 byte for Y position,
|
||||||
// 1 byte for wheel motion
|
// 1 byte for wheel motion
|
||||||
#define HID_MOUSE_EVENT_SIZE 4
|
#define SC_HID_MOUSE_INPUT_SIZE 4
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mouse descriptor from the specification:
|
* 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)
|
* Appendix E (p71): §E.10 Report Descriptor (Mouse)
|
||||||
*
|
*
|
||||||
* The usage tags (like Wheel) are listed in "HID Usage Tables":
|
* The usage tags (like Wheel) are listed in "HID Usage Tables":
|
||||||
* <https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf>
|
* <https://www.usb.org/document-library/hid-usage-tables-15>
|
||||||
* §4 Generic Desktop Page (0x01) (p26)
|
* §4 Generic Desktop Page (0x01) (p32)
|
||||||
*/
|
*/
|
||||||
const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
|
const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
|
||||||
// Usage Page (Generic Desktop)
|
// Usage Page (Generic Desktop)
|
||||||
@ -62,9 +62,9 @@ const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
|
|||||||
0x09, 0x31,
|
0x09, 0x31,
|
||||||
// Usage (Wheel)
|
// Usage (Wheel)
|
||||||
0x09, 0x38,
|
0x09, 0x38,
|
||||||
// Local Minimum (-127)
|
// Logical Minimum (-127)
|
||||||
0x15, 0x81,
|
0x15, 0x81,
|
||||||
// Local Maximum (127)
|
// Logical Maximum (127)
|
||||||
0x25, 0x7F,
|
0x25, 0x7F,
|
||||||
// Report Size (8)
|
// Report Size (8)
|
||||||
0x75, 0x08,
|
0x75, 0x08,
|
||||||
@ -80,9 +80,6 @@ const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
|
|||||||
0xC0,
|
0xC0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const size_t SC_HID_MOUSE_REPORT_DESC_LEN =
|
|
||||||
sizeof(SC_HID_MOUSE_REPORT_DESC);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A mouse HID event is 4 bytes long:
|
* A mouse HID event is 4 bytes long:
|
||||||
*
|
*
|
||||||
@ -125,10 +122,10 @@ const size_t SC_HID_MOUSE_REPORT_DESC_LEN =
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_hid_mouse_event_init(struct sc_hid_event *hid_event) {
|
sc_hid_mouse_input_init(struct sc_hid_input *hid_input) {
|
||||||
hid_event->size = HID_MOUSE_EVENT_SIZE;
|
hid_input->hid_id = SC_HID_ID_MOUSE;
|
||||||
// Leave hid_event->data uninitialized, it will be fully initialized by
|
hid_input->size = SC_HID_MOUSE_INPUT_SIZE;
|
||||||
// callers
|
// Leave ->data uninitialized, it will be fully initialized by callers
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t
|
static uint8_t
|
||||||
@ -153,11 +150,11 @@ sc_hid_buttons_from_buttons_state(uint8_t buttons_state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
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) {
|
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[0] = sc_hid_buttons_from_buttons_state(event->buttons_state);
|
||||||
data[1] = CLAMP(event->xrel, -127, 127);
|
data[1] = CLAMP(event->xrel, -127, 127);
|
||||||
data[2] = CLAMP(event->yrel, -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
|
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) {
|
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[0] = sc_hid_buttons_from_buttons_state(event->buttons_state);
|
||||||
data[1] = 0; // no x motion
|
data[1] = 0; // no x motion
|
||||||
data[2] = 0; // no y motion
|
data[2] = 0; // no y motion
|
||||||
@ -177,11 +174,11 @@ sc_hid_mouse_event_from_click(struct sc_hid_event *hid_event,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
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) {
|
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[0] = 0; // buttons state irrelevant (and unknown)
|
||||||
data[1] = 0; // no x motion
|
data[1] = 0; // no x motion
|
||||||
data[2] = 0; // no y 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);
|
data[3] = CLAMP(event->vscroll, -127, 127);
|
||||||
// Horizontal scrolling ignored
|
// 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
|
#ifndef SC_HID_MOUSE_H
|
||||||
#define SC_HID_MOUSE_H
|
#define SC_HID_MOUSE_H
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
@ -10,17 +8,24 @@
|
|||||||
#include "hid/hid_event.h"
|
#include "hid/hid_event.h"
|
||||||
#include "input_events.h"
|
#include "input_events.h"
|
||||||
|
|
||||||
extern const uint8_t SC_HID_MOUSE_REPORT_DESC[];
|
#define SC_HID_ID_MOUSE 2
|
||||||
extern const size_t SC_HID_MOUSE_REPORT_DESC_LEN;
|
|
||||||
|
|
||||||
void
|
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);
|
const struct sc_mouse_motion_event *event);
|
||||||
|
|
||||||
void
|
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);
|
const struct sc_mouse_click_event *event);
|
||||||
|
|
||||||
void
|
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);
|
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),
|
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),
|
static_assert(sizeof(enum sc_mod) >= sizeof(SDL_Keymod),
|
||||||
"SDL_Keymod must be convertible to sc_mod");
|
"SDL_Keymod must be convertible to sc_mod");
|
||||||
|
|
||||||
@ -380,6 +412,33 @@ struct sc_touch_event {
|
|||||||
float pressure;
|
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
|
static inline uint16_t
|
||||||
sc_mods_state_from_sdl(uint16_t mods_state) {
|
sc_mods_state_from_sdl(uint16_t mods_state) {
|
||||||
return mods_state;
|
return mods_state;
|
||||||
@ -444,4 +503,43 @@ sc_mouse_buttons_state_from_sdl(uint32_t buttons_state) {
|
|||||||
return 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
|
#endif
|
||||||
|
@ -56,16 +56,18 @@ void
|
|||||||
sc_input_manager_init(struct sc_input_manager *im,
|
sc_input_manager_init(struct sc_input_manager *im,
|
||||||
const struct sc_input_manager_params *params) {
|
const struct sc_input_manager_params *params) {
|
||||||
// A key/mouse processor may not be present if there is no controller
|
// 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
|
// A processor must have ops initialized
|
||||||
assert(!params->kp || params->kp->ops);
|
assert(!params->kp || params->kp->ops);
|
||||||
assert(!params->mp || params->mp->ops);
|
assert(!params->mp || params->mp->ops);
|
||||||
|
assert(!params->gp || params->gp->ops);
|
||||||
|
|
||||||
im->controller = params->controller;
|
im->controller = params->controller;
|
||||||
im->fp = params->fp;
|
im->fp = params->fp;
|
||||||
im->screen = params->screen;
|
im->screen = params->screen;
|
||||||
im->kp = params->kp;
|
im->kp = params->kp;
|
||||||
im->mp = params->mp;
|
im->mp = params->mp;
|
||||||
|
im->gp = params->gp;
|
||||||
|
|
||||||
im->mouse_bindings = params->mouse_bindings;
|
im->mouse_bindings = params->mouse_bindings;
|
||||||
im->legacy_paste = params->legacy_paste;
|
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);
|
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
|
static bool
|
||||||
is_apk(const char *file) {
|
is_apk(const char *file) {
|
||||||
const char *ext = strrchr(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);
|
sc_input_manager_process_touch(im, &event->tfinger);
|
||||||
break;
|
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: {
|
case SDL_DROPFILE: {
|
||||||
if (!control) {
|
if (!control) {
|
||||||
break;
|
break;
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include "file_pusher.h"
|
#include "file_pusher.h"
|
||||||
#include "fps_counter.h"
|
#include "fps_counter.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
|
#include "trait/gamepad_processor.h"
|
||||||
#include "trait/key_processor.h"
|
#include "trait/key_processor.h"
|
||||||
#include "trait/mouse_processor.h"
|
#include "trait/mouse_processor.h"
|
||||||
|
|
||||||
@ -21,6 +22,7 @@ struct sc_input_manager {
|
|||||||
|
|
||||||
struct sc_key_processor *kp;
|
struct sc_key_processor *kp;
|
||||||
struct sc_mouse_processor *mp;
|
struct sc_mouse_processor *mp;
|
||||||
|
struct sc_gamepad_processor *gp;
|
||||||
|
|
||||||
struct sc_mouse_bindings mouse_bindings;
|
struct sc_mouse_bindings mouse_bindings;
|
||||||
bool legacy_paste;
|
bool legacy_paste;
|
||||||
@ -50,6 +52,7 @@ struct sc_input_manager_params {
|
|||||||
struct sc_screen *screen;
|
struct sc_screen *screen;
|
||||||
struct sc_key_processor *kp;
|
struct sc_key_processor *kp;
|
||||||
struct sc_mouse_processor *mp;
|
struct sc_mouse_processor *mp;
|
||||||
|
struct sc_gamepad_processor *gp;
|
||||||
|
|
||||||
struct sc_mouse_bindings mouse_bindings;
|
struct sc_mouse_bindings mouse_bindings;
|
||||||
bool legacy_paste;
|
bool legacy_paste;
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#include "usb/scrcpy_otg.h"
|
#include "usb/scrcpy_otg.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
|
#include "util/thread.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@ -67,6 +68,9 @@ main_scrcpy(int argc, char *argv[]) {
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The current thread is the main thread
|
||||||
|
SC_MAIN_THREAD_ID = sc_thread_get_id();
|
||||||
|
|
||||||
#ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL
|
#ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL
|
||||||
av_register_all();
|
av_register_all();
|
||||||
#endif
|
#endif
|
||||||
|
@ -23,6 +23,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||||||
.record_format = SC_RECORD_FORMAT_AUTO,
|
.record_format = SC_RECORD_FORMAT_AUTO,
|
||||||
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AUTO,
|
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AUTO,
|
||||||
.mouse_input_mode = SC_MOUSE_INPUT_MODE_AUTO,
|
.mouse_input_mode = SC_MOUSE_INPUT_MODE_AUTO,
|
||||||
|
.gamepad_input_mode = SC_GAMEPAD_INPUT_MODE_AUTO,
|
||||||
.mouse_bindings = {
|
.mouse_bindings = {
|
||||||
.pri = {
|
.pri = {
|
||||||
.right_click = SC_MOUSE_BINDING_AUTO,
|
.right_click = SC_MOUSE_BINDING_AUTO,
|
||||||
|
@ -156,6 +156,13 @@ enum sc_mouse_input_mode {
|
|||||||
SC_MOUSE_INPUT_MODE_AOA,
|
SC_MOUSE_INPUT_MODE_AOA,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum sc_gamepad_input_mode {
|
||||||
|
SC_GAMEPAD_INPUT_MODE_AUTO,
|
||||||
|
SC_GAMEPAD_INPUT_MODE_DISABLED,
|
||||||
|
SC_GAMEPAD_INPUT_MODE_UHID,
|
||||||
|
SC_GAMEPAD_INPUT_MODE_AOA,
|
||||||
|
};
|
||||||
|
|
||||||
enum sc_mouse_binding {
|
enum sc_mouse_binding {
|
||||||
SC_MOUSE_BINDING_AUTO,
|
SC_MOUSE_BINDING_AUTO,
|
||||||
SC_MOUSE_BINDING_DISABLED,
|
SC_MOUSE_BINDING_DISABLED,
|
||||||
@ -231,6 +238,7 @@ struct scrcpy_options {
|
|||||||
enum sc_record_format record_format;
|
enum sc_record_format record_format;
|
||||||
enum sc_keyboard_input_mode keyboard_input_mode;
|
enum sc_keyboard_input_mode keyboard_input_mode;
|
||||||
enum sc_mouse_input_mode mouse_input_mode;
|
enum sc_mouse_input_mode mouse_input_mode;
|
||||||
|
enum sc_gamepad_input_mode gamepad_input_mode;
|
||||||
struct sc_mouse_bindings mouse_bindings;
|
struct sc_mouse_bindings mouse_bindings;
|
||||||
enum sc_camera_facing camera_facing;
|
enum sc_camera_facing camera_facing;
|
||||||
struct sc_port_range port_range;
|
struct sc_port_range port_range;
|
||||||
|
@ -6,9 +6,17 @@
|
|||||||
#include <SDL2/SDL_clipboard.h>
|
#include <SDL2/SDL_clipboard.h>
|
||||||
|
|
||||||
#include "device_msg.h"
|
#include "device_msg.h"
|
||||||
|
#include "events.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str.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
|
bool
|
||||||
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
|
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
|
||||||
const struct sc_receiver_callbacks *cbs, void *cbs_userdata) {
|
const struct sc_receiver_callbacks *cbs, void *cbs_userdata) {
|
||||||
@ -34,19 +42,48 @@ sc_receiver_destroy(struct sc_receiver *receiver) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
process_msg(struct sc_receiver *receiver, struct sc_device_msg *msg) {
|
task_set_clipboard(void *userdata) {
|
||||||
switch (msg->type) {
|
char *text = userdata;
|
||||||
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
|
||||||
char *current = SDL_GetClipboardText();
|
char *current = SDL_GetClipboardText();
|
||||||
bool same = current && !strcmp(current, msg->clipboard.text);
|
bool same = current && !strcmp(current, text);
|
||||||
SDL_free(current);
|
SDL_free(current);
|
||||||
if (same) {
|
if (same) {
|
||||||
LOGD("Computer clipboard unchanged");
|
LOGD("Computer clipboard unchanged");
|
||||||
|
free(text);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGI("Device clipboard copied");
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case DEVICE_MSG_TYPE_ACK_CLIPBOARD:
|
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);
|
sc_acksync_ack(receiver->acksync, msg->ack_clipboard.sequence);
|
||||||
|
// No allocation to free in the msg
|
||||||
break;
|
break;
|
||||||
case DEVICE_MSG_TYPE_UHID_OUTPUT:
|
case DEVICE_MSG_TYPE_UHID_OUTPUT:
|
||||||
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
|
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)
|
// Also check at runtime (do not trust the server)
|
||||||
if (!receiver->uhid_devices) {
|
if (!receiver->uhid_devices) {
|
||||||
LOGE("Received unexpected HID output message");
|
LOGE("Received unexpected HID output message");
|
||||||
|
sc_device_msg_destroy(msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_uhid_receiver *uhid_receiver =
|
struct sc_uhid_output_task_data *data = malloc(sizeof(*data));
|
||||||
sc_uhid_devices_get_receiver(receiver->uhid_devices,
|
if (!data) {
|
||||||
msg->uhid_output.id);
|
LOG_OOM();
|
||||||
if (uhid_receiver) {
|
return;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,7 +169,7 @@ process_msgs(struct sc_receiver *receiver, const uint8_t *buf, size_t len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
process_msg(receiver, &msg);
|
process_msg(receiver, &msg);
|
||||||
sc_device_msg_destroy(&msg);
|
// the device msg must be destroyed by process_msg()
|
||||||
|
|
||||||
head += r;
|
head += r;
|
||||||
assert(head <= len);
|
assert(head <= len);
|
||||||
|
122
app/src/scrcpy.c
122
app/src/scrcpy.c
@ -25,10 +25,12 @@
|
|||||||
#include "recorder.h"
|
#include "recorder.h"
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
|
#include "uhid/gamepad_uhid.h"
|
||||||
#include "uhid/keyboard_uhid.h"
|
#include "uhid/keyboard_uhid.h"
|
||||||
#include "uhid/mouse_uhid.h"
|
#include "uhid/mouse_uhid.h"
|
||||||
#ifdef HAVE_USB
|
#ifdef HAVE_USB
|
||||||
# include "usb/aoa_hid.h"
|
# include "usb/aoa_hid.h"
|
||||||
|
# include "usb/gamepad_aoa.h"
|
||||||
# include "usb/keyboard_aoa.h"
|
# include "usb/keyboard_aoa.h"
|
||||||
# include "usb/mouse_aoa.h"
|
# include "usb/mouse_aoa.h"
|
||||||
# include "usb/usb.h"
|
# include "usb/usb.h"
|
||||||
@ -77,27 +79,21 @@ struct scrcpy {
|
|||||||
struct sc_mouse_uhid mouse_uhid;
|
struct sc_mouse_uhid mouse_uhid;
|
||||||
#ifdef HAVE_USB
|
#ifdef HAVE_USB
|
||||||
struct sc_mouse_aoa mouse_aoa;
|
struct sc_mouse_aoa mouse_aoa;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
union {
|
||||||
|
struct sc_gamepad_uhid gamepad_uhid;
|
||||||
|
#ifdef HAVE_USB
|
||||||
|
struct sc_gamepad_aoa gamepad_aoa;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
struct sc_timeout timeout;
|
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
|
#ifdef _WIN32
|
||||||
static BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
|
static BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
|
||||||
if (ctrl_type == CTRL_C_EVENT) {
|
if (ctrl_type == CTRL_C_EVENT) {
|
||||||
PUSH_EVENT(SDL_QUIT);
|
sc_push_event(SDL_QUIT);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
return FALSE;
|
return FALSE;
|
||||||
@ -180,12 +176,20 @@ event_loop(struct scrcpy *s) {
|
|||||||
case SC_EVENT_RECORDER_ERROR:
|
case SC_EVENT_RECORDER_ERROR:
|
||||||
LOGE("Recorder error");
|
LOGE("Recorder error");
|
||||||
return SCRCPY_EXIT_FAILURE;
|
return SCRCPY_EXIT_FAILURE;
|
||||||
|
case SC_EVENT_AOA_OPEN_ERROR:
|
||||||
|
LOGE("AOA open error");
|
||||||
|
return SCRCPY_EXIT_FAILURE;
|
||||||
case SC_EVENT_TIME_LIMIT_REACHED:
|
case SC_EVENT_TIME_LIMIT_REACHED:
|
||||||
LOGI("Time limit reached");
|
LOGI("Time limit reached");
|
||||||
return SCRCPY_EXIT_SUCCESS;
|
return SCRCPY_EXIT_SUCCESS;
|
||||||
case SDL_QUIT:
|
case SDL_QUIT:
|
||||||
LOGD("User requested to quit");
|
LOGD("User requested to quit");
|
||||||
return SCRCPY_EXIT_SUCCESS;
|
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:
|
default:
|
||||||
if (!sc_screen_handle_event(&s->screen, &event)) {
|
if (!sc_screen_handle_event(&s->screen, &event)) {
|
||||||
return SCRCPY_EXIT_FAILURE;
|
return SCRCPY_EXIT_FAILURE;
|
||||||
@ -230,7 +234,7 @@ sc_recorder_on_ended(struct sc_recorder *recorder, bool success,
|
|||||||
(void) userdata;
|
(void) userdata;
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
PUSH_EVENT(SC_EVENT_RECORDER_ERROR);
|
sc_push_event(SC_EVENT_RECORDER_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,9 +248,9 @@ sc_video_demuxer_on_ended(struct sc_demuxer *demuxer,
|
|||||||
assert(status != SC_DEMUXER_STATUS_DISABLED);
|
assert(status != SC_DEMUXER_STATUS_DISABLED);
|
||||||
|
|
||||||
if (status == SC_DEMUXER_STATUS_EOS) {
|
if (status == SC_DEMUXER_STATUS_EOS) {
|
||||||
PUSH_EVENT(SC_EVENT_DEVICE_DISCONNECTED);
|
sc_push_event(SC_EVENT_DEVICE_DISCONNECTED);
|
||||||
} else {
|
} else {
|
||||||
PUSH_EVENT(SC_EVENT_DEMUXER_ERROR);
|
sc_push_event(SC_EVENT_DEMUXER_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,11 +264,11 @@ sc_audio_demuxer_on_ended(struct sc_demuxer *demuxer,
|
|||||||
// Contrary to the video demuxer, keep mirroring if only the audio fails
|
// Contrary to the video demuxer, keep mirroring if only the audio fails
|
||||||
// (unless --require-audio is set).
|
// (unless --require-audio is set).
|
||||||
if (status == SC_DEMUXER_STATUS_EOS) {
|
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
|
} else if (status == SC_DEMUXER_STATUS_ERROR
|
||||||
|| (status == SC_DEMUXER_STATUS_DISABLED
|
|| (status == SC_DEMUXER_STATUS_DISABLED
|
||||||
&& options->require_audio)) {
|
&& options->require_audio)) {
|
||||||
PUSH_EVENT(SC_EVENT_DEMUXER_ERROR);
|
sc_push_event(SC_EVENT_DEMUXER_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,9 +281,9 @@ sc_controller_on_ended(struct sc_controller *controller, bool error,
|
|||||||
(void) userdata;
|
(void) userdata;
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
PUSH_EVENT(SC_EVENT_CONTROLLER_ERROR);
|
sc_push_event(SC_EVENT_CONTROLLER_ERROR);
|
||||||
} else {
|
} else {
|
||||||
PUSH_EVENT(SC_EVENT_DEVICE_DISCONNECTED);
|
sc_push_event(SC_EVENT_DEVICE_DISCONNECTED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,7 +292,7 @@ sc_server_on_connection_failed(struct sc_server *server, void *userdata) {
|
|||||||
(void) server;
|
(void) server;
|
||||||
(void) userdata;
|
(void) userdata;
|
||||||
|
|
||||||
PUSH_EVENT(SC_EVENT_SERVER_CONNECTION_FAILED);
|
sc_push_event(SC_EVENT_SERVER_CONNECTION_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -296,7 +300,7 @@ sc_server_on_connected(struct sc_server *server, void *userdata) {
|
|||||||
(void) server;
|
(void) server;
|
||||||
(void) userdata;
|
(void) userdata;
|
||||||
|
|
||||||
PUSH_EVENT(SC_EVENT_SERVER_CONNECTED);
|
sc_push_event(SC_EVENT_SERVER_CONNECTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -314,7 +318,7 @@ sc_timeout_on_timeout(struct sc_timeout *timeout, void *userdata) {
|
|||||||
(void) timeout;
|
(void) timeout;
|
||||||
(void) userdata;
|
(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
|
// Generate a scrcpy id to differentiate multiple running scrcpy instances
|
||||||
@ -326,6 +330,21 @@ scrcpy_generate_scid(void) {
|
|||||||
return sc_rand_u32(&rand) & 0x7FFFFFFF;
|
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
|
enum scrcpy_exit_code
|
||||||
scrcpy(struct scrcpy_options *options) {
|
scrcpy(struct scrcpy_options *options) {
|
||||||
static struct scrcpy scrcpy;
|
static struct scrcpy scrcpy;
|
||||||
@ -358,6 +377,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
bool aoa_hid_initialized = false;
|
bool aoa_hid_initialized = false;
|
||||||
bool keyboard_aoa_initialized = false;
|
bool keyboard_aoa_initialized = false;
|
||||||
bool mouse_aoa_initialized = false;
|
bool mouse_aoa_initialized = false;
|
||||||
|
bool gamepad_aoa_initialized = false;
|
||||||
#endif
|
#endif
|
||||||
bool controller_initialized = false;
|
bool controller_initialized = false;
|
||||||
bool controller_started = false;
|
bool controller_started = false;
|
||||||
@ -366,7 +386,6 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
bool timeout_started = false;
|
bool timeout_started = false;
|
||||||
|
|
||||||
struct sc_acksync *acksync = NULL;
|
struct sc_acksync *acksync = NULL;
|
||||||
struct sc_uhid_devices *uhid_devices = NULL;
|
|
||||||
|
|
||||||
uint32_t scid = scrcpy_generate_scid();
|
uint32_t scid = scrcpy_generate_scid();
|
||||||
|
|
||||||
@ -473,6 +492,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);
|
sdl_configure(options->video_playback, options->disable_screensaver);
|
||||||
|
|
||||||
// Await for server without blocking Ctrl+C handling
|
// Await for server without blocking Ctrl+C handling
|
||||||
@ -570,6 +596,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
struct sc_controller *controller = NULL;
|
struct sc_controller *controller = NULL;
|
||||||
struct sc_key_processor *kp = NULL;
|
struct sc_key_processor *kp = NULL;
|
||||||
struct sc_mouse_processor *mp = NULL;
|
struct sc_mouse_processor *mp = NULL;
|
||||||
|
struct sc_gamepad_processor *gp = NULL;
|
||||||
|
|
||||||
if (options->control) {
|
if (options->control) {
|
||||||
static const struct sc_controller_callbacks controller_cbs = {
|
static const struct sc_controller_callbacks controller_cbs = {
|
||||||
@ -589,7 +616,9 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA;
|
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA;
|
||||||
bool use_mouse_aoa =
|
bool use_mouse_aoa =
|
||||||
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_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);
|
bool ok = sc_acksync_init(&s->acksync);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
goto end;
|
goto end;
|
||||||
@ -632,12 +661,15 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool aoa_fail = false;
|
||||||
if (use_keyboard_aoa) {
|
if (use_keyboard_aoa) {
|
||||||
if (sc_keyboard_aoa_init(&s->keyboard_aoa, &s->aoa)) {
|
if (sc_keyboard_aoa_init(&s->keyboard_aoa, &s->aoa)) {
|
||||||
keyboard_aoa_initialized = true;
|
keyboard_aoa_initialized = true;
|
||||||
kp = &s->keyboard_aoa.key_processor;
|
kp = &s->keyboard_aoa.key_processor;
|
||||||
} else {
|
} else {
|
||||||
LOGE("Could not initialize HID keyboard");
|
LOGE("Could not initialize HID keyboard");
|
||||||
|
aoa_fail = true;
|
||||||
|
goto aoa_complete;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -647,12 +679,19 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
mp = &s->mouse_aoa.mouse_processor;
|
mp = &s->mouse_aoa.mouse_processor;
|
||||||
} else {
|
} else {
|
||||||
LOGE("Could not initialized HID mouse");
|
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_acksync_destroy(&s->acksync);
|
||||||
sc_usb_disconnect(&s->usb);
|
sc_usb_disconnect(&s->usb);
|
||||||
sc_usb_destroy(&s->usb);
|
sc_usb_destroy(&s->usb);
|
||||||
@ -669,6 +708,9 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_AOA);
|
assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_AOA);
|
||||||
#endif
|
#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) {
|
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_SDK) {
|
||||||
sc_keyboard_sdk_init(&s->keyboard_sdk, &s->controller,
|
sc_keyboard_sdk_init(&s->keyboard_sdk, &s->controller,
|
||||||
options->key_inject_mode,
|
options->key_inject_mode,
|
||||||
@ -676,14 +718,12 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
kp = &s->keyboard_sdk.key_processor;
|
kp = &s->keyboard_sdk.key_processor;
|
||||||
} else if (options->keyboard_input_mode
|
} else if (options->keyboard_input_mode
|
||||||
== SC_KEYBOARD_INPUT_MODE_UHID) {
|
== SC_KEYBOARD_INPUT_MODE_UHID) {
|
||||||
sc_uhid_devices_init(&s->uhid_devices);
|
bool ok = sc_keyboard_uhid_init(&s->keyboard_uhid, &s->controller);
|
||||||
bool ok = sc_keyboard_uhid_init(&s->keyboard_uhid, &s->controller,
|
|
||||||
&s->uhid_devices);
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
uhid_devices = &s->uhid_devices;
|
|
||||||
kp = &s->keyboard_uhid.key_processor;
|
kp = &s->keyboard_uhid.key_processor;
|
||||||
|
uhid_keyboard = &s->keyboard_uhid;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) {
|
if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) {
|
||||||
@ -698,6 +738,17 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
mp = &s->mouse_uhid.mouse_processor;
|
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);
|
sc_controller_configure(&s->controller, acksync, uhid_devices);
|
||||||
|
|
||||||
if (!sc_controller_start(&s->controller)) {
|
if (!sc_controller_start(&s->controller)) {
|
||||||
@ -719,6 +770,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
.fp = fp,
|
.fp = fp,
|
||||||
.kp = kp,
|
.kp = kp,
|
||||||
.mp = mp,
|
.mp = mp,
|
||||||
|
.gp = gp,
|
||||||
.mouse_bindings = options->mouse_bindings,
|
.mouse_bindings = options->mouse_bindings,
|
||||||
.legacy_paste = options->legacy_paste,
|
.legacy_paste = options->legacy_paste,
|
||||||
.clipboard_autosync = options->clipboard_autosync,
|
.clipboard_autosync = options->clipboard_autosync,
|
||||||
@ -830,6 +882,11 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
timeout_started = true;
|
timeout_started = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool use_gamepads = true;
|
||||||
|
if (use_gamepads) {
|
||||||
|
init_sdl_gamepads();
|
||||||
|
}
|
||||||
|
|
||||||
ret = event_loop(s);
|
ret = event_loop(s);
|
||||||
LOGD("quit...");
|
LOGD("quit...");
|
||||||
|
|
||||||
@ -855,6 +912,9 @@ end:
|
|||||||
if (mouse_aoa_initialized) {
|
if (mouse_aoa_initialized) {
|
||||||
sc_mouse_aoa_destroy(&s->mouse_aoa);
|
sc_mouse_aoa_destroy(&s->mouse_aoa);
|
||||||
}
|
}
|
||||||
|
if (gamepad_aoa_initialized) {
|
||||||
|
sc_gamepad_aoa_destroy(&s->gamepad_aoa);
|
||||||
|
}
|
||||||
sc_aoa_stop(&s->aoa);
|
sc_aoa_stop(&s->aoa);
|
||||||
sc_usb_stop(&s->usb);
|
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.width = ctx->width;
|
||||||
screen->frame_size.height = ctx->height;
|
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)
|
// Post the event on the UI thread (the texture must be created from there)
|
||||||
int ret = SDL_PushEvent(&event);
|
bool ok = sc_push_event(SC_EVENT_SCREEN_INIT_SIZE);
|
||||||
if (ret < 0) {
|
if (!ok) {
|
||||||
LOGW("Could not post init size event: %s", SDL_GetError());
|
LOGW("Could not post init size event: %s", SDL_GetError());
|
||||||
return false;
|
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
|
// The SC_EVENT_NEW_FRAME triggered for the previous frame will consume
|
||||||
// this new frame instead
|
// this new frame instead
|
||||||
} else {
|
} else {
|
||||||
static SDL_Event new_frame_event = {
|
|
||||||
.type = SC_EVENT_NEW_FRAME,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Post the event on the UI thread
|
// Post the event on the UI thread
|
||||||
int ret = SDL_PushEvent(&new_frame_event);
|
bool ok = sc_push_event(SC_EVENT_NEW_FRAME);
|
||||||
if (ret < 0) {
|
if (!ok) {
|
||||||
LOGW("Could not post new frame event: %s", SDL_GetError());
|
LOGW("Could not post new frame event: %s", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -481,6 +473,7 @@ sc_screen_init(struct sc_screen *screen,
|
|||||||
.screen = screen,
|
.screen = screen,
|
||||||
.kp = params->kp,
|
.kp = params->kp,
|
||||||
.mp = params->mp,
|
.mp = params->mp,
|
||||||
|
.gp = params->gp,
|
||||||
.mouse_bindings = params->mouse_bindings,
|
.mouse_bindings = params->mouse_bindings,
|
||||||
.legacy_paste = params->legacy_paste,
|
.legacy_paste = params->legacy_paste,
|
||||||
.clipboard_autosync = params->clipboard_autosync,
|
.clipboard_autosync = params->clipboard_autosync,
|
||||||
|
@ -78,6 +78,7 @@ struct sc_screen_params {
|
|||||||
struct sc_file_pusher *fp;
|
struct sc_file_pusher *fp;
|
||||||
struct sc_key_processor *kp;
|
struct sc_key_processor *kp;
|
||||||
struct sc_mouse_processor *mp;
|
struct sc_mouse_processor *mp;
|
||||||
|
struct sc_gamepad_processor *gp;
|
||||||
|
|
||||||
struct sc_mouse_bindings mouse_bindings;
|
struct sc_mouse_bindings mouse_bindings;
|
||||||
bool legacy_paste;
|
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
|
133
app/src/uhid/gamepad_uhid.c
Normal file
133
app/src/uhid/gamepad_uhid.c
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
#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;
|
||||||
|
sc_hid_gamepad_generate_input_from_axis(&gamepad->hid, &hid_input, event);
|
||||||
|
|
||||||
|
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;
|
||||||
|
sc_hid_gamepad_generate_input_from_button(&gamepad->hid, &hid_input, event);
|
||||||
|
|
||||||
|
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) \
|
#define DOWNCAST_RECEIVER(UR) \
|
||||||
container_of(UR, struct sc_keyboard_uhid, uhid_receiver)
|
container_of(UR, struct sc_keyboard_uhid, uhid_receiver)
|
||||||
|
|
||||||
#define UHID_KEYBOARD_ID 1
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_keyboard_uhid_send_input(struct sc_keyboard_uhid *kb,
|
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;
|
struct sc_control_msg msg;
|
||||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT;
|
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);
|
assert(hid_input->size <= SC_HID_MAX_SIZE);
|
||||||
memcpy(msg.uhid_input.data, event->data, event->size);
|
memcpy(msg.uhid_input.data, hid_input->data, hid_input->size);
|
||||||
msg.uhid_input.size = event->size;
|
msg.uhid_input.size = hid_input->size;
|
||||||
|
|
||||||
if (!sc_controller_push_msg(kb->controller, &msg)) {
|
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,20 @@ static void
|
|||||||
sc_keyboard_uhid_synchronize_mod(struct sc_keyboard_uhid *kb) {
|
sc_keyboard_uhid_synchronize_mod(struct sc_keyboard_uhid *kb) {
|
||||||
SDL_Keymod sdl_mod = SDL_GetModState();
|
SDL_Keymod sdl_mod = SDL_GetModState();
|
||||||
uint16_t mod = sc_mods_state_from_sdl(sdl_mod) & (SC_MOD_CAPS | SC_MOD_NUM);
|
uint16_t mod = sc_mods_state_from_sdl(sdl_mod) & (SC_MOD_CAPS | SC_MOD_NUM);
|
||||||
|
uint16_t diff = mod ^ kb->device_mod;
|
||||||
uint16_t device_mod =
|
|
||||||
atomic_load_explicit(&kb->device_mod, memory_order_relaxed);
|
|
||||||
uint16_t diff = mod ^ device_mod;
|
|
||||||
|
|
||||||
if (diff) {
|
if (diff) {
|
||||||
// Inherently racy (the HID output reports arrive asynchronously in
|
// Inherently racy (the HID output reports arrive asynchronously in
|
||||||
// response to key presses), but will re-synchronize on next key press
|
// response to key presses), but will re-synchronize on next key press
|
||||||
// or HID output anyway
|
// or HID output anyway
|
||||||
atomic_store_explicit(&kb->device_mod, mod, memory_order_relaxed);
|
kb->device_mod = mod;
|
||||||
|
|
||||||
struct sc_hid_event hid_event;
|
struct sc_hid_input hid_input;
|
||||||
sc_hid_keyboard_event_from_mods(&hid_event, diff);
|
sc_hid_keyboard_generate_input_from_mods(&hid_input, diff);
|
||||||
|
|
||||||
LOGV("HID keyboard state synchronized");
|
LOGV("HID keyboard state synchronized");
|
||||||
|
|
||||||
sc_keyboard_uhid_send_input(kb, &hid_event);
|
sc_keyboard_uhid_send_input(kb, &hid_input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,6 +52,8 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
|
|||||||
uint64_t ack_to_wait) {
|
uint64_t ack_to_wait) {
|
||||||
(void) ack_to_wait;
|
(void) ack_to_wait;
|
||||||
|
|
||||||
|
assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);
|
||||||
|
|
||||||
if (event->repeat) {
|
if (event->repeat) {
|
||||||
// In USB HID protocol, key repeat is handled by the host (Android), so
|
// In USB HID protocol, key repeat is handled by the host (Android), so
|
||||||
// just ignore key repeat here.
|
// just ignore key repeat here.
|
||||||
@ -65,22 +62,20 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
|
|||||||
|
|
||||||
struct sc_keyboard_uhid *kb = DOWNCAST(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
|
// 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) {
|
if (event->scancode == SC_SCANCODE_CAPSLOCK) {
|
||||||
atomic_fetch_xor_explicit(&kb->device_mod, SC_MOD_CAPS,
|
kb->device_mod ^= SC_MOD_CAPS;
|
||||||
memory_order_relaxed);
|
|
||||||
} else if (event->scancode == SC_SCANCODE_NUMLOCK) {
|
} else if (event->scancode == SC_SCANCODE_NUMLOCK) {
|
||||||
atomic_fetch_xor_explicit(&kb->device_mod, SC_MOD_NUM,
|
kb->device_mod ^= SC_MOD_NUM;
|
||||||
memory_order_relaxed);
|
|
||||||
} else {
|
} else {
|
||||||
// Synchronize modifiers (only if the scancode itself does not
|
// Synchronize modifiers (only if the scancode itself does not
|
||||||
// change the modifiers)
|
// change the modifiers)
|
||||||
sc_keyboard_uhid_synchronize_mod(kb);
|
sc_keyboard_uhid_synchronize_mod(kb);
|
||||||
}
|
}
|
||||||
sc_keyboard_uhid_send_input(kb, &hid_event);
|
sc_keyboard_uhid_send_input(kb, &hid_input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,34 +93,31 @@ sc_keyboard_uhid_to_sc_mod(uint8_t hid_led) {
|
|||||||
return mod;
|
return mod;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
void
|
||||||
sc_uhid_receiver_process_output(struct sc_uhid_receiver *receiver,
|
sc_keyboard_uhid_process_hid_output(struct sc_keyboard_uhid *kb,
|
||||||
const uint8_t *data, size_t len) {
|
const uint8_t *data, size_t size) {
|
||||||
// Called from the thread receiving device messages
|
assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);
|
||||||
|
|
||||||
assert(len);
|
assert(size);
|
||||||
|
|
||||||
// Also check at runtime (do not trust the server)
|
// Also check at runtime (do not trust the server)
|
||||||
if (!len) {
|
if (!size) {
|
||||||
LOGE("Unexpected empty HID output message");
|
LOGE("Unexpected empty HID output message");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_keyboard_uhid *kb = DOWNCAST_RECEIVER(receiver);
|
|
||||||
|
|
||||||
uint8_t hid_led = data[0];
|
uint8_t hid_led = data[0];
|
||||||
uint16_t device_mod = sc_keyboard_uhid_to_sc_mod(hid_led);
|
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
|
bool
|
||||||
sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
|
sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
|
||||||
struct sc_controller *controller,
|
struct sc_controller *controller) {
|
||||||
struct sc_uhid_devices *uhid_devices) {
|
|
||||||
sc_hid_keyboard_init(&kb->hid);
|
sc_hid_keyboard_init(&kb->hid);
|
||||||
|
|
||||||
kb->controller = controller;
|
kb->controller = controller;
|
||||||
atomic_init(&kb->device_mod, 0);
|
kb->device_mod = 0;
|
||||||
|
|
||||||
static const struct sc_key_processor_ops ops = {
|
static const struct sc_key_processor_ops ops = {
|
||||||
.process_key = sc_key_processor_process_key,
|
.process_key = sc_key_processor_process_key,
|
||||||
@ -140,19 +132,15 @@ sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
|
|||||||
kb->key_processor.hid = true;
|
kb->key_processor.hid = true;
|
||||||
kb->key_processor.ops = &ops;
|
kb->key_processor.ops = &ops;
|
||||||
|
|
||||||
static const struct sc_uhid_receiver_ops uhid_receiver_ops = {
|
struct sc_hid_open hid_open;
|
||||||
.process_output = sc_uhid_receiver_process_output,
|
sc_hid_keyboard_generate_open(&hid_open);
|
||||||
};
|
assert(hid_open.hid_id == SC_HID_ID_KEYBOARD);
|
||||||
|
|
||||||
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_control_msg msg;
|
struct sc_control_msg msg;
|
||||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
||||||
msg.uhid_create.id = UHID_KEYBOARD_ID;
|
msg.uhid_create.id = SC_HID_ID_KEYBOARD;
|
||||||
msg.uhid_create.report_desc = SC_HID_KEYBOARD_REPORT_DESC;
|
msg.uhid_create.report_desc = hid_open.report_desc;
|
||||||
msg.uhid_create.report_desc_size = SC_HID_KEYBOARD_REPORT_DESC_LEN;
|
msg.uhid_create.report_desc_size = hid_open.report_desc_size;
|
||||||
if (!sc_controller_push_msg(controller, &msg)) {
|
if (!sc_controller_push_msg(controller, &msg)) {
|
||||||
LOGE("Could not send UHID_CREATE message (keyboard)");
|
LOGE("Could not send UHID_CREATE message (keyboard)");
|
||||||
return false;
|
return false;
|
||||||
|
@ -7,21 +7,22 @@
|
|||||||
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "hid/hid_keyboard.h"
|
#include "hid/hid_keyboard.h"
|
||||||
#include "uhid/uhid_output.h"
|
|
||||||
#include "trait/key_processor.h"
|
#include "trait/key_processor.h"
|
||||||
|
|
||||||
struct sc_keyboard_uhid {
|
struct sc_keyboard_uhid {
|
||||||
struct sc_key_processor key_processor; // key processor trait
|
struct sc_key_processor key_processor; // key processor trait
|
||||||
struct sc_uhid_receiver uhid_receiver;
|
|
||||||
|
|
||||||
struct sc_hid_keyboard hid;
|
struct sc_hid_keyboard hid;
|
||||||
struct sc_controller *controller;
|
struct sc_controller *controller;
|
||||||
atomic_uint_least16_t device_mod;
|
uint16_t device_mod;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
|
sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
|
||||||
struct sc_controller *controller,
|
struct sc_controller *controller);
|
||||||
struct sc_uhid_devices *uhid_devices);
|
|
||||||
|
void
|
||||||
|
sc_keyboard_uhid_process_hid_output(struct sc_keyboard_uhid *kb,
|
||||||
|
const uint8_t *data, size_t size);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -7,21 +7,20 @@
|
|||||||
/** Downcast mouse processor to mouse_uhid */
|
/** Downcast mouse processor to mouse_uhid */
|
||||||
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_uhid, mouse_processor)
|
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_uhid, mouse_processor)
|
||||||
|
|
||||||
#define UHID_MOUSE_ID 2
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_mouse_uhid_send_input(struct sc_mouse_uhid *mouse,
|
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;
|
struct sc_control_msg msg;
|
||||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT;
|
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);
|
assert(hid_input->size <= SC_HID_MAX_SIZE);
|
||||||
memcpy(msg.uhid_input.data, event->data, event->size);
|
memcpy(msg.uhid_input.data, hid_input->data, hid_input->size);
|
||||||
msg.uhid_input.size = event->size;
|
msg.uhid_input.size = hid_input->size;
|
||||||
|
|
||||||
if (!sc_controller_push_msg(mouse->controller, &msg)) {
|
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) {
|
const struct sc_mouse_motion_event *event) {
|
||||||
struct sc_mouse_uhid *mouse = DOWNCAST(mp);
|
struct sc_mouse_uhid *mouse = DOWNCAST(mp);
|
||||||
|
|
||||||
struct sc_hid_event hid_event;
|
struct sc_hid_input hid_input;
|
||||||
sc_hid_mouse_event_from_motion(&hid_event, event);
|
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
|
static void
|
||||||
@ -41,10 +40,10 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
|
|||||||
const struct sc_mouse_click_event *event) {
|
const struct sc_mouse_click_event *event) {
|
||||||
struct sc_mouse_uhid *mouse = DOWNCAST(mp);
|
struct sc_mouse_uhid *mouse = DOWNCAST(mp);
|
||||||
|
|
||||||
struct sc_hid_event hid_event;
|
struct sc_hid_input hid_input;
|
||||||
sc_hid_mouse_event_from_click(&hid_event, event);
|
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
|
static void
|
||||||
@ -52,10 +51,10 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
|
|||||||
const struct sc_mouse_scroll_event *event) {
|
const struct sc_mouse_scroll_event *event) {
|
||||||
struct sc_mouse_uhid *mouse = DOWNCAST(mp);
|
struct sc_mouse_uhid *mouse = DOWNCAST(mp);
|
||||||
|
|
||||||
struct sc_hid_event hid_event;
|
struct sc_hid_input hid_input;
|
||||||
sc_hid_mouse_event_from_scroll(&hid_event, event);
|
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
|
bool
|
||||||
@ -75,13 +74,17 @@ sc_mouse_uhid_init(struct sc_mouse_uhid *mouse,
|
|||||||
|
|
||||||
mouse->mouse_processor.relative_mode = true;
|
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;
|
struct sc_control_msg msg;
|
||||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
||||||
msg.uhid_create.id = UHID_MOUSE_ID;
|
msg.uhid_create.id = SC_HID_ID_MOUSE;
|
||||||
msg.uhid_create.report_desc = SC_HID_MOUSE_REPORT_DESC;
|
msg.uhid_create.report_desc = hid_open.report_desc;
|
||||||
msg.uhid_create.report_desc_size = SC_HID_MOUSE_REPORT_DESC_LEN;
|
msg.uhid_create.report_desc_size = hid_open.report_desc_size;
|
||||||
if (!sc_controller_push_msg(controller, &msg)) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,25 +1,37 @@
|
|||||||
#include "uhid_output.h"
|
#include "uhid_output.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#include "uhid/keyboard_uhid.h"
|
||||||
|
#include "uhid/gamepad_uhid.h"
|
||||||
|
#include "util/log.h"
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_uhid_devices_init(struct sc_uhid_devices *devices) {
|
sc_uhid_devices_init(struct sc_uhid_devices *devices,
|
||||||
devices->count = 0;
|
struct sc_keyboard_uhid *keyboard,
|
||||||
|
struct sc_gamepad_uhid *gamepad) {
|
||||||
|
devices->keyboard = keyboard;
|
||||||
|
devices->gamepad = gamepad;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_uhid_devices_add_receiver(struct sc_uhid_devices *devices,
|
sc_uhid_devices_process_hid_output(struct sc_uhid_devices *devices, uint16_t id,
|
||||||
struct sc_uhid_receiver *receiver) {
|
const uint8_t *data, size_t size) {
|
||||||
assert(devices->count < SC_UHID_MAX_RECEIVERS);
|
if (id == SC_HID_ID_KEYBOARD) {
|
||||||
devices->receivers[devices->count++] = receiver;
|
if (devices->keyboard) {
|
||||||
|
sc_keyboard_uhid_process_hid_output(devices->keyboard, data, size);
|
||||||
|
} else {
|
||||||
|
LOGW("Unexpected keyboard HID output without UHID keyboard");
|
||||||
}
|
}
|
||||||
|
} else if (id >= SC_HID_ID_GAMEPAD_FIRST && id <= SC_HID_ID_GAMEPAD_LAST) {
|
||||||
struct sc_uhid_receiver *
|
if (devices->gamepad) {
|
||||||
sc_uhid_devices_get_receiver(struct sc_uhid_devices *devices, uint16_t id) {
|
sc_gamepad_uhid_process_hid_output(devices->gamepad, id, data,
|
||||||
for (size_t i = 0; i < devices->count; ++i) {
|
size);
|
||||||
if (devices->receivers[i]->id == id) {
|
} else {
|
||||||
return devices->receivers[i];
|
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.
|
* The communication with UHID devices is bidirectional.
|
||||||
*
|
*
|
||||||
* This component manages the registration of receivers to handle UHID output
|
* This component dispatches HID outputs to the expected processor.
|
||||||
* messages (sent from the device to the computer).
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
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_devices {
|
||||||
struct sc_uhid_receiver *receivers[SC_UHID_MAX_RECEIVERS];
|
struct sc_keyboard_uhid *keyboard;
|
||||||
unsigned count;
|
struct sc_gamepad_uhid *gamepad;
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
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
|
void
|
||||||
sc_uhid_devices_add_receiver(struct sc_uhid_devices *devices,
|
sc_uhid_devices_process_hid_output(struct sc_uhid_devices *devices, uint16_t id,
|
||||||
struct sc_uhid_receiver *receiver);
|
const uint8_t *data, size_t size);
|
||||||
|
|
||||||
struct sc_uhid_receiver *
|
|
||||||
sc_uhid_devices_get_receiver(struct sc_uhid_devices *devices, uint16_t id);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <inttypes.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "aoa_hid.h"
|
#include "aoa_hid.h"
|
||||||
|
#include "events.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str.h"
|
#include "util/str.h"
|
||||||
|
#include "util/vector.h"
|
||||||
|
|
||||||
// See <https://source.android.com/devices/accessories/aoa2#hid-support>.
|
// See <https://source.android.com/devices/accessories/aoa2#hid-support>.
|
||||||
#define ACCESSORY_REGISTER_HID 54
|
#define ACCESSORY_REGISTER_HID 54
|
||||||
@ -15,26 +18,49 @@
|
|||||||
|
|
||||||
#define DEFAULT_TIMEOUT 1000
|
#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
|
static void
|
||||||
sc_hid_event_log(uint16_t accessory_id, const struct sc_hid_event *event) {
|
sc_hid_input_log(const struct sc_hid_input *hid_input) {
|
||||||
// HID Event: [00] FF FF FF FF...
|
// HID input: [00] FF FF FF FF...
|
||||||
assert(event->size);
|
assert(hid_input->size);
|
||||||
char *hex = sc_str_to_hex_string(event->data, event->size);
|
char *hex = sc_str_to_hex_string(hid_input->data, hid_input->size);
|
||||||
if (!hex) {
|
if (!hex) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LOGV("HID Event: [%d] %s", accessory_id, hex);
|
LOGV("HID input: [%" PRIu16 "] %s", hid_input->hid_id, hex);
|
||||||
free(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
|
bool
|
||||||
sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb,
|
sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb,
|
||||||
struct sc_acksync *acksync) {
|
struct sc_acksync *acksync) {
|
||||||
sc_vecdeque_init(&aoa->queue);
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,38 +151,18 @@ sc_aoa_set_hid_report_desc(struct sc_aoa *aoa, uint16_t accessory_id,
|
|||||||
return true;
|
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
|
static bool
|
||||||
sc_aoa_send_hid_event(struct sc_aoa *aoa, uint16_t accessory_id,
|
sc_aoa_send_hid_event(struct sc_aoa *aoa,
|
||||||
const struct sc_hid_event *event) {
|
const struct sc_hid_input *hid_input) {
|
||||||
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
|
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
|
||||||
uint8_t request = ACCESSORY_SEND_HID_EVENT;
|
uint8_t request = ACCESSORY_SEND_HID_EVENT;
|
||||||
// <https://source.android.com/devices/accessories/aoa2.html#hid-support>
|
// <https://source.android.com/devices/accessories/aoa2.html#hid-support>
|
||||||
// value (arg0): accessory assigned ID for the HID device
|
// value (arg0): accessory assigned ID for the HID device
|
||||||
// index (arg1): 0 (unused)
|
// index (arg1): 0 (unused)
|
||||||
uint16_t value = accessory_id;
|
uint16_t value = hid_input->hid_id;
|
||||||
uint16_t index = 0;
|
uint16_t index = 0;
|
||||||
unsigned char *data = (uint8_t *) event->data; // discard const
|
unsigned char *data = (uint8_t *) hid_input->data; // discard const
|
||||||
uint16_t length = event->size;
|
uint16_t length = hid_input->size;
|
||||||
int result = libusb_control_transfer(aoa->usb->handle, request_type,
|
int result = libusb_control_transfer(aoa->usb->handle, request_type,
|
||||||
request, value, index, data, length,
|
request, value, index, data, length,
|
||||||
DEFAULT_TIMEOUT);
|
DEFAULT_TIMEOUT);
|
||||||
@ -169,7 +175,7 @@ sc_aoa_send_hid_event(struct sc_aoa *aoa, uint16_t accessory_id,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
static bool
|
||||||
sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id) {
|
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_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
|
||||||
uint8_t request = ACCESSORY_UNREGISTER_HID;
|
uint8_t request = ACCESSORY_UNREGISTER_HID;
|
||||||
@ -192,13 +198,32 @@ sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id) {
|
|||||||
return true;
|
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
|
bool
|
||||||
sc_aoa_push_hid_event_with_ack_to_wait(struct sc_aoa *aoa,
|
sc_aoa_push_input_with_ack_to_wait(struct sc_aoa *aoa,
|
||||||
uint16_t accessory_id,
|
const struct sc_hid_input *hid_input,
|
||||||
const struct sc_hid_event *event,
|
|
||||||
uint64_t ack_to_wait) {
|
uint64_t ack_to_wait) {
|
||||||
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
|
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);
|
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 =
|
struct sc_aoa_event *aoa_event =
|
||||||
sc_vecdeque_push_hole_noresize(&aoa->queue);
|
sc_vecdeque_push_hole_noresize(&aoa->queue);
|
||||||
aoa_event->hid = *event;
|
aoa_event->type = SC_AOA_EVENT_TYPE_INPUT;
|
||||||
aoa_event->accessory_id = accessory_id;
|
aoa_event->input.hid = *hid_input;
|
||||||
aoa_event->ack_to_wait = ack_to_wait;
|
aoa_event->input.ack_to_wait = ack_to_wait;
|
||||||
|
|
||||||
if (was_empty) {
|
if (was_empty) {
|
||||||
sc_cond_signal(&aoa->event_cond);
|
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;
|
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
|
static int
|
||||||
run_aoa_thread(void *data) {
|
run_aoa_thread(void *data) {
|
||||||
struct sc_aoa *aoa = 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 (;;) {
|
for (;;) {
|
||||||
sc_mutex_lock(&aoa->mutex);
|
sc_mutex_lock(&aoa->mutex);
|
||||||
while (!aoa->stopped && sc_vecdeque_is_empty(&aoa->queue)) {
|
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));
|
assert(!sc_vecdeque_is_empty(&aoa->queue));
|
||||||
struct sc_aoa_event event = sc_vecdeque_pop(&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);
|
sc_mutex_unlock(&aoa->mutex);
|
||||||
|
|
||||||
if (ack_to_wait != SC_SEQUENCE_INVALID) {
|
bool cont = sc_aoa_process_event(aoa, &event, &vec_open);
|
||||||
LOGD("Waiting ack from server sequence=%" PRIu64_, ack_to_wait);
|
if (!cont) {
|
||||||
|
|
||||||
// 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) {
|
|
||||||
// stopped
|
// stopped
|
||||||
break;
|
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) {
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,12 +13,27 @@
|
|||||||
#include "util/tick.h"
|
#include "util/tick.h"
|
||||||
#include "util/vecdeque.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_aoa_event {
|
||||||
struct sc_hid_event hid;
|
enum sc_aoa_event_type type;
|
||||||
uint16_t accessory_id;
|
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;
|
uint64_t ack_to_wait;
|
||||||
|
} input;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_aoa_event_queue SC_VECDEQUE(struct sc_aoa_event);
|
struct sc_aoa_event_queue SC_VECDEQUE(struct sc_aoa_event);
|
||||||
@ -49,23 +64,30 @@ sc_aoa_stop(struct sc_aoa *aoa);
|
|||||||
void
|
void
|
||||||
sc_aoa_join(struct sc_aoa *aoa);
|
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
|
bool
|
||||||
sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id,
|
sc_aoa_push_open(struct sc_aoa *aoa, const struct sc_hid_open *hid_open,
|
||||||
const uint8_t *report_desc, uint16_t report_desc_size);
|
bool exit_on_open_error);
|
||||||
|
|
||||||
bool
|
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
|
bool
|
||||||
sc_aoa_push_hid_event_with_ack_to_wait(struct sc_aoa *aoa,
|
sc_aoa_push_input_with_ack_to_wait(struct sc_aoa *aoa,
|
||||||
uint16_t accessory_id,
|
const struct sc_hid_input *hid_input,
|
||||||
const struct sc_hid_event *event,
|
|
||||||
uint64_t ack_to_wait);
|
uint64_t ack_to_wait);
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
sc_aoa_push_hid_event(struct sc_aoa *aoa, uint16_t accessory_id,
|
sc_aoa_push_input(struct sc_aoa *aoa, const struct sc_hid_input *hid_input) {
|
||||||
const struct sc_hid_event *event) {
|
return sc_aoa_push_input_with_ack_to_wait(aoa, hid_input,
|
||||||
return sc_aoa_push_hid_event_with_ack_to_wait(aoa, accessory_id, event,
|
|
||||||
SC_SEQUENCE_INVALID);
|
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 */
|
/** Downcast key processor to keyboard_aoa */
|
||||||
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_aoa, key_processor)
|
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_aoa, key_processor)
|
||||||
|
|
||||||
#define HID_KEYBOARD_ACCESSORY_ID 1
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
push_mod_lock_state(struct sc_keyboard_aoa *kb, uint16_t mods_state) {
|
push_mod_lock_state(struct sc_keyboard_aoa *kb, uint16_t mods_state) {
|
||||||
struct sc_hid_event hid_event;
|
struct sc_hid_input hid_input;
|
||||||
if (!sc_hid_keyboard_event_from_mods(&hid_event, mods_state)) {
|
if (!sc_hid_keyboard_generate_input_from_mods(&hid_input, mods_state)) {
|
||||||
// Nothing to do
|
// Nothing to do
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sc_aoa_push_hid_event(kb->aoa, HID_KEYBOARD_ACCESSORY_ID,
|
if (!sc_aoa_push_input(kb->aoa, &hid_input)) {
|
||||||
&hid_event)) {
|
LOGW("Could not push AOA HID input (mod lock state)");
|
||||||
LOGW("Could not request HID event (mod lock state)");
|
|
||||||
return false;
|
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_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
|
// 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) {
|
if (!kb->mod_lock_synchronized) {
|
||||||
// Inject CAPSLOCK and/or NUMLOCK if necessary to synchronize
|
// Inject CAPSLOCK and/or NUMLOCK if necessary to synchronize
|
||||||
// keyboard state
|
// 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
|
// synchronization is acknowledged by the server, otherwise it could
|
||||||
// paste the old clipboard content.
|
// paste the old clipboard content.
|
||||||
|
|
||||||
if (!sc_aoa_push_hid_event_with_ack_to_wait(kb->aoa,
|
if (!sc_aoa_push_input_with_ack_to_wait(kb->aoa, &hid_input,
|
||||||
HID_KEYBOARD_ACCESSORY_ID,
|
|
||||||
&hid_event,
|
|
||||||
ack_to_wait)) {
|
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) {
|
sc_keyboard_aoa_init(struct sc_keyboard_aoa *kb, struct sc_aoa *aoa) {
|
||||||
kb->aoa = aoa;
|
kb->aoa = aoa;
|
||||||
|
|
||||||
bool ok = sc_aoa_setup_hid(aoa, HID_KEYBOARD_ACCESSORY_ID,
|
struct sc_hid_open hid_open;
|
||||||
SC_HID_KEYBOARD_REPORT_DESC,
|
sc_hid_keyboard_generate_open(&hid_open);
|
||||||
SC_HID_KEYBOARD_REPORT_DESC_LEN);
|
|
||||||
|
bool ok = sc_aoa_push_open(aoa, &hid_open, true);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGW("Register HID keyboard failed");
|
LOGW("Could not push AOA HID open (keyboard)");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,9 +98,6 @@ sc_keyboard_aoa_init(struct sc_keyboard_aoa *kb, struct sc_aoa *aoa) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
sc_keyboard_aoa_destroy(struct sc_keyboard_aoa *kb) {
|
sc_keyboard_aoa_destroy(struct sc_keyboard_aoa *kb) {
|
||||||
// Unregister HID keyboard so the soft keyboard shows again on Android
|
(void) kb;
|
||||||
bool ok = sc_aoa_unregister_hid(kb->aoa, HID_KEYBOARD_ACCESSORY_ID);
|
// Do nothing, kb->aoa will automatically unregister all devices
|
||||||
if (!ok) {
|
|
||||||
LOGW("Could not unregister HID keyboard");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -9,19 +9,16 @@
|
|||||||
/** Downcast mouse processor to mouse_aoa */
|
/** Downcast mouse processor to mouse_aoa */
|
||||||
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_aoa, mouse_processor)
|
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_aoa, mouse_processor)
|
||||||
|
|
||||||
#define HID_MOUSE_ACCESSORY_ID 2
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
||||||
const struct sc_mouse_motion_event *event) {
|
const struct sc_mouse_motion_event *event) {
|
||||||
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
|
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
|
||||||
|
|
||||||
struct sc_hid_event hid_event;
|
struct sc_hid_input hid_input;
|
||||||
sc_hid_mouse_event_from_motion(&hid_event, event);
|
sc_hid_mouse_generate_input_from_motion(&hid_input, event);
|
||||||
|
|
||||||
if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID,
|
if (!sc_aoa_push_input(mouse->aoa, &hid_input)) {
|
||||||
&hid_event)) {
|
LOGW("Could not push AOA HID input (mouse motion)");
|
||||||
LOGW("Could not request HID event (mouse motion)");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,12 +27,11 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
|
|||||||
const struct sc_mouse_click_event *event) {
|
const struct sc_mouse_click_event *event) {
|
||||||
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
|
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
|
||||||
|
|
||||||
struct sc_hid_event hid_event;
|
struct sc_hid_input hid_input;
|
||||||
sc_hid_mouse_event_from_click(&hid_event, event);
|
sc_hid_mouse_generate_input_from_click(&hid_input, event);
|
||||||
|
|
||||||
if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID,
|
if (!sc_aoa_push_input(mouse->aoa, &hid_input)) {
|
||||||
&hid_event)) {
|
LOGW("Could not push AOA HID input (mouse click)");
|
||||||
LOGW("Could not request HID event (mouse click)");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,12 +40,11 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
|
|||||||
const struct sc_mouse_scroll_event *event) {
|
const struct sc_mouse_scroll_event *event) {
|
||||||
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
|
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
|
||||||
|
|
||||||
struct sc_hid_event hid_event;
|
struct sc_hid_input hid_input;
|
||||||
sc_hid_mouse_event_from_scroll(&hid_event, event);
|
sc_hid_mouse_generate_input_from_scroll(&hid_input, event);
|
||||||
|
|
||||||
if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID,
|
if (!sc_aoa_push_input(mouse->aoa, &hid_input)) {
|
||||||
&hid_event)) {
|
LOGW("Could not push AOA HID input (mouse scroll)");
|
||||||
LOGW("Could not request HID event (mouse scroll)");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,11 +52,12 @@ bool
|
|||||||
sc_mouse_aoa_init(struct sc_mouse_aoa *mouse, struct sc_aoa *aoa) {
|
sc_mouse_aoa_init(struct sc_mouse_aoa *mouse, struct sc_aoa *aoa) {
|
||||||
mouse->aoa = aoa;
|
mouse->aoa = aoa;
|
||||||
|
|
||||||
bool ok = sc_aoa_setup_hid(aoa, HID_MOUSE_ACCESSORY_ID,
|
struct sc_hid_open hid_open;
|
||||||
SC_HID_MOUSE_REPORT_DESC,
|
sc_hid_mouse_generate_open(&hid_open);
|
||||||
SC_HID_MOUSE_REPORT_DESC_LEN);
|
|
||||||
|
bool ok = sc_aoa_push_open(aoa, &hid_open, true);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGW("Register HID mouse failed");
|
LOGW("Could not push AOA HID open (mouse)");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,8 +78,6 @@ sc_mouse_aoa_init(struct sc_mouse_aoa *mouse, struct sc_aoa *aoa) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
sc_mouse_aoa_destroy(struct sc_mouse_aoa *mouse) {
|
sc_mouse_aoa_destroy(struct sc_mouse_aoa *mouse) {
|
||||||
bool ok = sc_aoa_unregister_hid(mouse->aoa, HID_MOUSE_ACCESSORY_ID);
|
(void) mouse;
|
||||||
if (!ok) {
|
// Do nothing, mouse->aoa will automatically unregister all devices
|
||||||
LOGW("Could not unregister HID mouse");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ struct scrcpy_otg {
|
|||||||
struct sc_aoa aoa;
|
struct sc_aoa aoa;
|
||||||
struct sc_keyboard_aoa keyboard;
|
struct sc_keyboard_aoa keyboard;
|
||||||
struct sc_mouse_aoa mouse;
|
struct sc_mouse_aoa mouse;
|
||||||
|
struct sc_gamepad_aoa gamepad;
|
||||||
|
|
||||||
struct sc_screen_otg screen_otg;
|
struct sc_screen_otg screen_otg;
|
||||||
};
|
};
|
||||||
@ -21,10 +22,8 @@ sc_usb_on_disconnected(struct sc_usb *usb, void *userdata) {
|
|||||||
(void) usb;
|
(void) usb;
|
||||||
(void) userdata;
|
(void) userdata;
|
||||||
|
|
||||||
SDL_Event event;
|
bool ok = sc_push_event(SC_EVENT_USB_DEVICE_DISCONNECTED);
|
||||||
event.type = SC_EVENT_USB_DEVICE_DISCONNECTED;
|
if (!ok) {
|
||||||
int ret = SDL_PushEvent(&event);
|
|
||||||
if (ret < 0) {
|
|
||||||
LOGE("Could not post USB disconnection event: %s", SDL_GetError());
|
LOGE("Could not post USB disconnection event: %s", SDL_GetError());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -37,9 +36,17 @@ event_loop(struct scrcpy_otg *s) {
|
|||||||
case SC_EVENT_USB_DEVICE_DISCONNECTED:
|
case SC_EVENT_USB_DEVICE_DISCONNECTED:
|
||||||
LOGW("Device disconnected");
|
LOGW("Device disconnected");
|
||||||
return SCRCPY_EXIT_DISCONNECTED;
|
return SCRCPY_EXIT_DISCONNECTED;
|
||||||
|
case SC_EVENT_AOA_OPEN_ERROR:
|
||||||
|
LOGE("AOA open error");
|
||||||
|
return SCRCPY_EXIT_FAILURE;
|
||||||
case SDL_QUIT:
|
case SDL_QUIT:
|
||||||
LOGD("User requested to quit");
|
LOGD("User requested to quit");
|
||||||
return SCRCPY_EXIT_SUCCESS;
|
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:
|
default:
|
||||||
sc_screen_otg_handle_event(&s->screen_otg, &event);
|
sc_screen_otg_handle_event(&s->screen_otg, &event);
|
||||||
break;
|
break;
|
||||||
@ -65,6 +72,13 @@ scrcpy_otg(struct scrcpy_options *options) {
|
|||||||
return SCRCPY_EXIT_FAILURE;
|
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);
|
atexit(SDL_Quit);
|
||||||
|
|
||||||
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
|
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
|
||||||
@ -75,6 +89,7 @@ scrcpy_otg(struct scrcpy_options *options) {
|
|||||||
|
|
||||||
struct sc_keyboard_aoa *keyboard = NULL;
|
struct sc_keyboard_aoa *keyboard = NULL;
|
||||||
struct sc_mouse_aoa *mouse = NULL;
|
struct sc_mouse_aoa *mouse = NULL;
|
||||||
|
struct sc_gamepad_aoa *gamepad = NULL;
|
||||||
bool usb_device_initialized = false;
|
bool usb_device_initialized = false;
|
||||||
bool usb_connected = false;
|
bool usb_connected = false;
|
||||||
bool aoa_started = false;
|
bool aoa_started = false;
|
||||||
@ -121,11 +136,15 @@ scrcpy_otg(struct scrcpy_options *options) {
|
|||||||
|| options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_DISABLED);
|
|| options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_DISABLED);
|
||||||
assert(options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA
|
assert(options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA
|
||||||
|| options->mouse_input_mode == SC_MOUSE_INPUT_MODE_DISABLED);
|
|| 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 =
|
bool enable_keyboard =
|
||||||
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA;
|
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA;
|
||||||
bool enable_mouse =
|
bool enable_mouse =
|
||||||
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA;
|
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA;
|
||||||
|
bool enable_gamepad =
|
||||||
|
options->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_AOA;
|
||||||
|
|
||||||
if (enable_keyboard) {
|
if (enable_keyboard) {
|
||||||
ok = sc_keyboard_aoa_init(&s->keyboard, &s->aoa);
|
ok = sc_keyboard_aoa_init(&s->keyboard, &s->aoa);
|
||||||
@ -143,6 +162,11 @@ scrcpy_otg(struct scrcpy_options *options) {
|
|||||||
mouse = &s->mouse;
|
mouse = &s->mouse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (enable_gamepad) {
|
||||||
|
sc_gamepad_aoa_init(&s->gamepad, &s->aoa);
|
||||||
|
gamepad = &s->gamepad;
|
||||||
|
}
|
||||||
|
|
||||||
ok = sc_aoa_start(&s->aoa);
|
ok = sc_aoa_start(&s->aoa);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
goto end;
|
goto end;
|
||||||
@ -157,6 +181,7 @@ scrcpy_otg(struct scrcpy_options *options) {
|
|||||||
struct sc_screen_otg_params params = {
|
struct sc_screen_otg_params params = {
|
||||||
.keyboard = keyboard,
|
.keyboard = keyboard,
|
||||||
.mouse = mouse,
|
.mouse = mouse,
|
||||||
|
.gamepad = gamepad,
|
||||||
.window_title = window_title,
|
.window_title = window_title,
|
||||||
.always_on_top = options->always_on_top,
|
.always_on_top = options->always_on_top,
|
||||||
.window_x = options->window_x,
|
.window_x = options->window_x,
|
||||||
@ -190,6 +215,9 @@ end:
|
|||||||
if (keyboard) {
|
if (keyboard) {
|
||||||
sc_keyboard_aoa_destroy(&s->keyboard);
|
sc_keyboard_aoa_destroy(&s->keyboard);
|
||||||
}
|
}
|
||||||
|
if (gamepad) {
|
||||||
|
sc_gamepad_aoa_destroy(&s->gamepad);
|
||||||
|
}
|
||||||
|
|
||||||
if (aoa_initialized) {
|
if (aoa_initialized) {
|
||||||
sc_aoa_join(&s->aoa);
|
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) {
|
const struct sc_screen_otg_params *params) {
|
||||||
screen->keyboard = params->keyboard;
|
screen->keyboard = params->keyboard;
|
||||||
screen->mouse = params->mouse;
|
screen->mouse = params->mouse;
|
||||||
|
screen->gamepad = params->gamepad;
|
||||||
|
|
||||||
screen->mouse_capture_key_pressed = 0;
|
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);
|
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
|
void
|
||||||
sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) {
|
sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) {
|
||||||
switch (event->type) {
|
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);
|
sc_screen_otg_process_mouse_wheel(screen, &event->wheel);
|
||||||
}
|
}
|
||||||
break;
|
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 "keyboard_aoa.h"
|
||||||
#include "mouse_aoa.h"
|
#include "mouse_aoa.h"
|
||||||
|
#include "gamepad_aoa.h"
|
||||||
|
|
||||||
struct sc_screen_otg {
|
struct sc_screen_otg {
|
||||||
struct sc_keyboard_aoa *keyboard;
|
struct sc_keyboard_aoa *keyboard;
|
||||||
struct sc_mouse_aoa *mouse;
|
struct sc_mouse_aoa *mouse;
|
||||||
|
struct sc_gamepad_aoa *gamepad;
|
||||||
|
|
||||||
SDL_Window *window;
|
SDL_Window *window;
|
||||||
SDL_Renderer *renderer;
|
SDL_Renderer *renderer;
|
||||||
@ -24,6 +26,7 @@ struct sc_screen_otg {
|
|||||||
struct sc_screen_otg_params {
|
struct sc_screen_otg_params {
|
||||||
struct sc_keyboard_aoa *keyboard;
|
struct sc_keyboard_aoa *keyboard;
|
||||||
struct sc_mouse_aoa *mouse;
|
struct sc_mouse_aoa *mouse;
|
||||||
|
struct sc_gamepad_aoa *gamepad;
|
||||||
|
|
||||||
const char *window_title;
|
const char *window_title;
|
||||||
bool always_on_top;
|
bool always_on_top;
|
||||||
|
@ -13,6 +13,12 @@ sc_write16be(uint8_t *buf, uint16_t value) {
|
|||||||
buf[1] = 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
|
static inline void
|
||||||
sc_write32be(uint8_t *buf, uint32_t value) {
|
sc_write32be(uint8_t *buf, uint32_t value) {
|
||||||
buf[0] = value >> 24;
|
buf[0] = value >> 24;
|
||||||
@ -21,12 +27,26 @@ sc_write32be(uint8_t *buf, uint32_t value) {
|
|||||||
buf[3] = 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
|
static inline void
|
||||||
sc_write64be(uint8_t *buf, uint64_t value) {
|
sc_write64be(uint8_t *buf, uint64_t value) {
|
||||||
sc_write32be(buf, value >> 32);
|
sc_write32be(buf, value >> 32);
|
||||||
sc_write32be(&buf[4], (uint32_t) value);
|
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
|
static inline uint16_t
|
||||||
sc_read16be(const uint8_t *buf) {
|
sc_read16be(const uint8_t *buf) {
|
||||||
return (buf[0] << 8) | buf[1];
|
return (buf[0] << 8) | buf[1];
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
|
sc_thread_id SC_MAIN_THREAD_ID;
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
|
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
|
||||||
void *userdata) {
|
void *userdata) {
|
||||||
|
@ -39,6 +39,8 @@ typedef struct sc_cond {
|
|||||||
SDL_cond *cond;
|
SDL_cond *cond;
|
||||||
} sc_cond;
|
} sc_cond;
|
||||||
|
|
||||||
|
extern sc_thread_id SC_MAIN_THREAD_ID;
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
|
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
|
||||||
void *userdata);
|
void *userdata);
|
||||||
|
@ -370,6 +370,25 @@ static void test_serialize_uhid_input(void) {
|
|||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
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) {
|
static void test_serialize_open_hard_keyboard(void) {
|
||||||
struct sc_control_msg msg = {
|
struct sc_control_msg msg = {
|
||||||
.type = SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
|
.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_rotate_device();
|
||||||
test_serialize_uhid_create();
|
test_serialize_uhid_create();
|
||||||
test_serialize_uhid_input();
|
test_serialize_uhid_input();
|
||||||
|
test_serialize_uhid_destroy();
|
||||||
test_serialize_open_hard_keyboard();
|
test_serialize_open_hard_keyboard();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,8 @@ public final class ControlMessage {
|
|||||||
public static final int TYPE_ROTATE_DEVICE = 11;
|
public static final int TYPE_ROTATE_DEVICE = 11;
|
||||||
public static final int TYPE_UHID_CREATE = 12;
|
public static final int TYPE_UHID_CREATE = 12;
|
||||||
public static final int TYPE_UHID_INPUT = 13;
|
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;
|
public static final long SEQUENCE_INVALID = 0;
|
||||||
|
|
||||||
@ -146,6 +147,13 @@ public final class ControlMessage {
|
|||||||
return msg;
|
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() {
|
public int getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ public class ControlMessageReader {
|
|||||||
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 9;
|
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 9;
|
||||||
static final int UHID_CREATE_FIXED_PAYLOAD_LENGTH = 4;
|
static final int UHID_CREATE_FIXED_PAYLOAD_LENGTH = 4;
|
||||||
static final int UHID_INPUT_FIXED_PAYLOAD_LENGTH = 4;
|
static final int UHID_INPUT_FIXED_PAYLOAD_LENGTH = 4;
|
||||||
|
static final int UHID_DESTROY_PAYLOAD_LENGTH = 2;
|
||||||
|
|
||||||
private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
|
private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
|
||||||
|
|
||||||
@ -99,6 +100,9 @@ public class ControlMessageReader {
|
|||||||
case ControlMessage.TYPE_UHID_INPUT:
|
case ControlMessage.TYPE_UHID_INPUT:
|
||||||
msg = parseUhidInput();
|
msg = parseUhidInput();
|
||||||
break;
|
break;
|
||||||
|
case ControlMessage.TYPE_UHID_DESTROY:
|
||||||
|
msg = parseUhidDestroy();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
Ln.w("Unknown event type: " + type);
|
Ln.w("Unknown event type: " + type);
|
||||||
msg = null;
|
msg = null;
|
||||||
@ -249,6 +253,14 @@ public class ControlMessageReader {
|
|||||||
return ControlMessage.createUhidInput(id, data);
|
return ControlMessage.createUhidInput(id, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ControlMessage parseUhidDestroy() {
|
||||||
|
if (buffer.remaining() < UHID_DESTROY_PAYLOAD_LENGTH) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int id = buffer.getShort();
|
||||||
|
return ControlMessage.createUhidDestroy(id);
|
||||||
|
}
|
||||||
|
|
||||||
private static Position readPosition(ByteBuffer buffer) {
|
private static Position readPosition(ByteBuffer buffer) {
|
||||||
int x = buffer.getInt();
|
int x = buffer.getInt();
|
||||||
int y = buffer.getInt();
|
int y = buffer.getInt();
|
||||||
|
@ -215,6 +215,9 @@ public class Controller implements AsyncProcessor {
|
|||||||
case ControlMessage.TYPE_UHID_INPUT:
|
case ControlMessage.TYPE_UHID_INPUT:
|
||||||
getUhidManager().writeInput(msg.getId(), msg.getData());
|
getUhidManager().writeInput(msg.getId(), msg.getData());
|
||||||
break;
|
break;
|
||||||
|
case ControlMessage.TYPE_UHID_DESTROY:
|
||||||
|
getUhidManager().close(msg.getId());
|
||||||
|
break;
|
||||||
case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
||||||
openHardKeyboardSettings();
|
openHardKeyboardSettings();
|
||||||
break;
|
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) {
|
private static byte[] extractHidOutputData(ByteBuffer buffer) {
|
||||||
/*
|
/*
|
||||||
* #define UHID_DATA_MAX 4096
|
* #define UHID_DATA_MAX 4096
|
||||||
@ -199,9 +205,15 @@ public final class UhidManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void close(int id) {
|
public void close(int id) {
|
||||||
FileDescriptor fd = fds.get(id);
|
// Linux: Documentation/hid/uhid.rst
|
||||||
assert fd != null;
|
// 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);
|
close(fd);
|
||||||
|
} else {
|
||||||
|
Ln.w("Closing unknown UHID device: " + id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void closeAll() {
|
public void closeAll() {
|
||||||
|
@ -368,6 +368,24 @@ public class ControlMessageReaderTest {
|
|||||||
Assert.assertArrayEquals(data, event.getData());
|
Assert.assertArrayEquals(data, event.getData());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseUhidDestroy() throws IOException {
|
||||||
|
ControlMessageReader reader = new ControlMessageReader();
|
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
|
dos.writeByte(ControlMessage.TYPE_UHID_DESTROY);
|
||||||
|
dos.writeShort(42); // id
|
||||||
|
|
||||||
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
|
reader.readFrom(new ByteArrayInputStream(packet));
|
||||||
|
ControlMessage event = reader.next();
|
||||||
|
|
||||||
|
Assert.assertEquals(ControlMessage.TYPE_UHID_DESTROY, event.getType());
|
||||||
|
Assert.assertEquals(42, event.getId());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseOpenHardKeyboardSettings() throws IOException {
|
public void testParseOpenHardKeyboardSettings() throws IOException {
|
||||||
ControlMessageReader reader = new ControlMessageReader();
|
ControlMessageReader reader = new ControlMessageReader();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user