mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-03-18 18:14:29 +01:00
Compare commits
36 Commits
gamepad.dr
...
gamepad.dr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b54b0a6958 | ||
|
|
e5c7840530 | ||
|
|
ccc030625b | ||
|
|
b0b05909c0 | ||
|
|
330d844231 | ||
|
|
ab6732f32d | ||
|
|
32c73a5448 | ||
|
|
c72c772c81 | ||
|
|
d2e1d96fbd | ||
|
|
07d5a6ca80 | ||
|
|
2d929fa4e5 | ||
|
|
623346b558 | ||
|
|
5b52afd980 | ||
|
|
4c5d43d41d | ||
|
|
2351eed46f | ||
|
|
2595bcfa75 | ||
|
|
93dad86634 | ||
|
|
282e6503e1 | ||
|
|
9c86cf74e6 | ||
|
|
2caf593960 | ||
|
|
1344b76f42 | ||
|
|
32b1a8093a | ||
|
|
4416aa29f1 | ||
|
|
4b95a9674a | ||
|
|
c7c8d71de5 | ||
|
|
de6ee25d09 | ||
|
|
70a9bbbc06 | ||
|
|
97e83fa3d6 | ||
|
|
0215693725 | ||
|
|
220cad2e7a | ||
|
|
0b6119a594 | ||
|
|
9cae029ef2 | ||
|
|
72d5bcc12a | ||
|
|
d56b8a6a95 | ||
|
|
580cba73c0 | ||
|
|
523299743c |
@@ -37,7 +37,6 @@ Its features include:
|
||||
- [camera mirroring](doc/camera.md) (Android 12+)
|
||||
- [mirroring as a webcam (V4L2)](doc/v4l2.md) (Linux-only)
|
||||
- physical [keyboard][hid-keyboard] and [mouse][hid-mouse] simulation (HID)
|
||||
- [gamepad](doc/gamepad.md) support
|
||||
- [OTG mode](doc/otg.md)
|
||||
- and more…
|
||||
|
||||
@@ -112,13 +111,6 @@ Here are just some common examples.
|
||||
scrcpy --otg
|
||||
```
|
||||
|
||||
- Control the device using gamepad controllers plugged into the computer:
|
||||
|
||||
```bash
|
||||
scrcpy --gamepad=uhid
|
||||
scrcpy -G # short version
|
||||
```
|
||||
|
||||
## User documentation
|
||||
|
||||
The application provides a lot of features and configuration options. They are
|
||||
@@ -130,7 +122,6 @@ documented in the following pages:
|
||||
- [Control](doc/control.md)
|
||||
- [Keyboard](doc/keyboard.md)
|
||||
- [Mouse](doc/mouse.md)
|
||||
- [Gamepad](doc/gamepad.md)
|
||||
- [Device](doc/device.md)
|
||||
- [Window](doc/window.md)
|
||||
- [Recording](doc/recording.md)
|
||||
|
||||
@@ -26,8 +26,6 @@ _scrcpy() {
|
||||
-e --select-tcpip
|
||||
-f --fullscreen
|
||||
--force-adb-forward
|
||||
-G
|
||||
--gamepad=
|
||||
-h --help
|
||||
-K
|
||||
--keyboard=
|
||||
@@ -129,10 +127,6 @@ _scrcpy() {
|
||||
COMPREPLY=($(compgen -W 'disabled sdk uhid aoa' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--gamepad)
|
||||
COMPREPLY=($(compgen -W 'disabled uhid aoa' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--orientation|--display-orientation)
|
||||
COMPREPLY=($(compgen -W '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur"))
|
||||
return
|
||||
|
||||
@@ -33,10 +33,8 @@ arguments=(
|
||||
{-e,--select-tcpip}'[Use TCP/IP device]'
|
||||
{-f,--fullscreen}'[Start in fullscreen]'
|
||||
'--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]'
|
||||
'-G[Use UHID/AOA gamepad (same as --gamepad=uhid or --gamepad=aoa, depending on OTG mode)]'
|
||||
'--gamepad=[Set the gamepad input mode]:mode:(disabled uhid aoa)'
|
||||
{-h,--help}'[Print the help]'
|
||||
'-K[Use UHID/AOA keyboard (same as --keyboard=uhid or --keyboard=aoa, depending on OTG mode)]'
|
||||
'-K[Use UHID keyboard (same as --keyboard=uhid)]'
|
||||
'--keyboard=[Set the keyboard input mode]:mode:(disabled sdk uhid aoa)'
|
||||
'--kill-adb-on-close[Kill adb when scrcpy terminates]'
|
||||
'--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]'
|
||||
@@ -46,7 +44,7 @@ arguments=(
|
||||
'--list-encoders[List video and audio encoders available on the device]'
|
||||
'--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 90 180 270)'
|
||||
{-m,--max-size=}'[Limit both the width and height of the video to value]'
|
||||
'-M[Use UHID/AOA mouse (same as --mouse=uhid or --mouse=aoa, depending on OTG mode)]'
|
||||
'-M[Use UHID mouse (same as --mouse=uhid)]'
|
||||
'--max-fps=[Limit the frame rate of screen capture]'
|
||||
'--mouse=[Set the mouse input mode]:mode:(disabled sdk uhid aoa)'
|
||||
'--mouse-bind=[Configure bindings of secondary clicks]'
|
||||
|
||||
@@ -185,9 +185,9 @@ Select how to send gamepad inputs to the device.
|
||||
|
||||
Possible values are "disabled", "uhid" and "aoa":
|
||||
|
||||
- "disabled" does not send gamepad inputs to the device.
|
||||
- "uhid" simulates physical HID gamepads using the Linux HID kernel module on the device.
|
||||
- "aoa" simulates physical HID gamepads using the AOAv2 protocol. It may only work over USB.
|
||||
- "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
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
//#define SC_AUDIO_PLAYER_DEBUG // uncomment to debug
|
||||
#define SC_AUDIO_PLAYER_NDEBUG // comment to debug
|
||||
|
||||
/**
|
||||
* Real-time audio player with configurable latency
|
||||
@@ -72,7 +72,7 @@ sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
|
||||
size_t len = len_int;
|
||||
uint32_t count = TO_SAMPLES(len);
|
||||
|
||||
#ifdef SC_AUDIO_PLAYER_DEBUG
|
||||
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||
LOGD("[Audio] SDL callback requests %" PRIu32 " samples", count);
|
||||
#endif
|
||||
|
||||
@@ -162,7 +162,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
||||
// swr_convert() returns the number of samples which would have been
|
||||
// written if the buffer was big enough.
|
||||
uint32_t samples = MIN(ret, dst_nb_samples);
|
||||
#ifdef SC_AUDIO_PLAYER_DEBUG
|
||||
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||
LOGD("[Audio] %" PRIu32 " samples written to buffer", samples);
|
||||
#endif
|
||||
|
||||
@@ -244,7 +244,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
||||
if (played) {
|
||||
LOGD("[Audio] Buffering threshold exceeded, skipping %" PRIu32
|
||||
" samples", skip_samples);
|
||||
#ifdef SC_AUDIO_PLAYER_DEBUG
|
||||
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||
} else {
|
||||
LOGD("[Audio] Playback not started, skipping %" PRIu32
|
||||
" samples", skip_samples);
|
||||
@@ -282,7 +282,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
||||
// However, the buffering level must be smoothed
|
||||
sc_average_push(&ap->avg_buffering, can_read);
|
||||
|
||||
#ifdef SC_AUDIO_PLAYER_DEBUG
|
||||
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||
LOGD("[Audio] can_read=%" PRIu32 " avg_buffering=%f",
|
||||
can_read, sc_average_get(&ap->avg_buffering));
|
||||
#endif
|
||||
|
||||
@@ -384,10 +384,10 @@ static const struct sc_option options[] = {
|
||||
.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 physical HID gamepads using the Linux UHID "
|
||||
"kernel module on the device.\n"
|
||||
"\"aoa\" simulates physical gamepads using the AOAv2 protocol."
|
||||
"It may only work over USB.\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.",
|
||||
},
|
||||
{
|
||||
@@ -1465,26 +1465,6 @@ parse_integers_arg(const char *s, const char sep, size_t max_items, long *out,
|
||||
return count;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_float_arg(const char *s, float *out, float min, float max,
|
||||
const char *name) {
|
||||
float value;
|
||||
bool ok = sc_str_parse_float(s, &value);
|
||||
if (!ok) {
|
||||
LOGE("Could not parse %s: %s", name, s);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value < min || value > max) {
|
||||
LOGE("Could not parse %s: value (%f) out-of-range (%f; %f)",
|
||||
name, value, min, max);
|
||||
return false;
|
||||
}
|
||||
|
||||
*out = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_bit_rate(const char *s, uint32_t *bit_rate) {
|
||||
long value;
|
||||
@@ -1512,14 +1492,14 @@ parse_max_size(const char *s, uint16_t *max_size) {
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_max_fps(const char *s, float *max_fps) {
|
||||
float value;
|
||||
bool ok = parse_float_arg(s, &value, 0, (float) (1 << 16), "max fps");
|
||||
parse_max_fps(const char *s, uint16_t *max_fps) {
|
||||
long value;
|
||||
bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF, "max fps");
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*max_fps = value;
|
||||
*max_fps = (uint16_t) value;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2832,13 +2812,20 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
}
|
||||
} else if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_UHID_OR_AOA) {
|
||||
opts->mouse_input_mode = otg ? SC_MOUSE_INPUT_MODE_AOA
|
||||
: SC_MOUSE_INPUT_MODE_UHID;
|
||||
: SC_MOUSE_INPUT_MODE_SDK;
|
||||
} else if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK
|
||||
&& !opts->video_playback) {
|
||||
LOGE("SDK mouse mode requires video playback. Try --mouse=uhid.");
|
||||
return false;
|
||||
}
|
||||
if (opts->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_UHID_OR_AOA) {
|
||||
|
||||
if (opts->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_AUTO) {
|
||||
// UHID does not work on all devices (with old Android
|
||||
// versions), so it cannot be enabled by default
|
||||
opts->gamepad_input_mode = otg ? SC_GAMEPAD_INPUT_MODE_AOA
|
||||
: SC_GAMEPAD_INPUT_MODE_DISABLED;
|
||||
} else if (opts->gamepad_input_mode
|
||||
== SC_GAMEPAD_INPUT_MODE_UHID_OR_AOA) {
|
||||
opts->gamepad_input_mode = otg ? SC_GAMEPAD_INPUT_MODE_AOA
|
||||
: SC_GAMEPAD_INPUT_MODE_UHID;
|
||||
}
|
||||
@@ -2898,17 +2885,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
return false;
|
||||
}
|
||||
|
||||
enum sc_gamepad_input_mode gmode = opts->gamepad_input_mode;
|
||||
if (gmode != SC_GAMEPAD_INPUT_MODE_AOA
|
||||
&& gmode != SC_GAMEPAD_INPUT_MODE_DISABLED) {
|
||||
LOGE("In OTG mode, --gamepad only supports aoa or disabled.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (kmode == SC_KEYBOARD_INPUT_MODE_DISABLED
|
||||
&& mmode == SC_MOUSE_INPUT_MODE_DISABLED
|
||||
&& gmode == SC_GAMEPAD_INPUT_MODE_DISABLED) {
|
||||
LOGE("Cannot not disable all inputs in OTG mode.");
|
||||
&& mmode == SC_MOUSE_INPUT_MODE_DISABLED) {
|
||||
LOGE("Could not disable both keyboard and mouse in OTG mode.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -2949,18 +2928,18 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
}
|
||||
|
||||
if (opts->camera_id && opts->camera_facing != SC_CAMERA_FACING_ANY) {
|
||||
LOGE("Cannot specify both --camera-id and --camera-facing");
|
||||
LOGE("Could not specify both --camera-id and --camera-facing");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts->camera_size) {
|
||||
if (opts->max_size) {
|
||||
LOGE("Cannot specify both --camera-size and -m/--max-size");
|
||||
LOGE("Could not specify both --camera-size and -m/--max-size");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts->camera_ar) {
|
||||
LOGE("Cannot specify both --camera-size and --camera-ar");
|
||||
LOGE("Could not specify both --camera-size and --camera-ar");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -3101,19 +3080,19 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
|
||||
if (!opts->control) {
|
||||
if (opts->turn_screen_off) {
|
||||
LOGE("Cannot request to turn screen off if control is disabled");
|
||||
LOGE("Could not request to turn screen off if control is disabled");
|
||||
return false;
|
||||
}
|
||||
if (opts->stay_awake) {
|
||||
LOGE("Cannot request to stay awake if control is disabled");
|
||||
LOGE("Could not request to stay awake if control is disabled");
|
||||
return false;
|
||||
}
|
||||
if (opts->show_touches) {
|
||||
LOGE("Cannot request to show touches if control is disabled");
|
||||
LOGE("Could not request to show touches if control is disabled");
|
||||
return false;
|
||||
}
|
||||
if (opts->power_off_on_close) {
|
||||
LOGE("Cannot request power off on close if control is disabled");
|
||||
LOGE("Could not request power off on close if control is disabled");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -3138,7 +3117,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
// OTG mode is compatible with only very few options.
|
||||
// Only report obvious errors.
|
||||
if (opts->record_filename) {
|
||||
LOGE("OTG mode: cannot record");
|
||||
LOGE("OTG mode: could not record");
|
||||
return false;
|
||||
}
|
||||
if (opts->turn_screen_off) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
//#define SC_CLOCK_DEBUG // uncomment to debug
|
||||
#define SC_CLOCK_NDEBUG // comment to debug
|
||||
|
||||
#define SC_CLOCK_RANGE 32
|
||||
|
||||
@@ -21,12 +21,10 @@ sc_clock_update(struct sc_clock *clock, sc_tick system, sc_tick stream) {
|
||||
}
|
||||
|
||||
sc_tick offset = system - stream;
|
||||
unsigned clock_weight = clock->range - 1;
|
||||
unsigned value_weight = SC_CLOCK_RANGE - clock->range + 1;
|
||||
clock->offset = (clock->offset * clock_weight + offset * value_weight)
|
||||
/ SC_CLOCK_RANGE;
|
||||
clock->offset = ((clock->range - 1) * clock->offset + offset)
|
||||
/ clock->range;
|
||||
|
||||
#ifdef SC_CLOCK_DEBUG
|
||||
#ifndef SC_CLOCK_NDEBUG
|
||||
LOGD("Clock estimation: pts + %" PRItick, clock->offset);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -83,34 +83,15 @@ write_position(uint8_t *buf, const struct sc_position *position) {
|
||||
sc_write16be(&buf[10], position->screen_size.height);
|
||||
}
|
||||
|
||||
// Write truncated string, and return the size
|
||||
// write length (4 bytes) + string (non null-terminated)
|
||||
static size_t
|
||||
write_string_payload(uint8_t *payload, const char *utf8, size_t max_len) {
|
||||
if (!utf8) {
|
||||
return 0;
|
||||
}
|
||||
write_string(const char *utf8, size_t max_len, uint8_t *buf) {
|
||||
size_t len = sc_str_utf8_truncation_index(utf8, max_len);
|
||||
memcpy(payload, utf8, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
// Write length (4 bytes) + string (non null-terminated)
|
||||
static size_t
|
||||
write_string(uint8_t *buf, const char *utf8, size_t max_len) {
|
||||
size_t len = write_string_payload(buf + 4, utf8, max_len);
|
||||
sc_write32be(buf, len);
|
||||
memcpy(&buf[4], utf8, len);
|
||||
return 4 + len;
|
||||
}
|
||||
|
||||
// Write length (1 byte) + string (non null-terminated)
|
||||
static size_t
|
||||
write_string_tiny(uint8_t *buf, const char *utf8, size_t max_len) {
|
||||
assert(max_len <= 0xFF);
|
||||
size_t len = write_string_payload(buf + 1, utf8, max_len);
|
||||
buf[0] = len;
|
||||
return 1 + len;
|
||||
}
|
||||
|
||||
size_t
|
||||
sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
|
||||
buf[0] = msg->type;
|
||||
@@ -122,8 +103,9 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
|
||||
sc_write32be(&buf[10], msg->inject_keycode.metastate);
|
||||
return 14;
|
||||
case SC_CONTROL_MSG_TYPE_INJECT_TEXT: {
|
||||
size_t len = write_string(&buf[1], msg->inject_text.text,
|
||||
SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
|
||||
size_t len =
|
||||
write_string(msg->inject_text.text,
|
||||
SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH, &buf[1]);
|
||||
return 1 + len;
|
||||
}
|
||||
case SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
|
||||
@@ -155,26 +137,19 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
|
||||
case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
||||
sc_write64be(&buf[1], msg->set_clipboard.sequence);
|
||||
buf[9] = !!msg->set_clipboard.paste;
|
||||
size_t len = write_string(&buf[10], msg->set_clipboard.text,
|
||||
SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH);
|
||||
size_t len = write_string(msg->set_clipboard.text,
|
||||
SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
|
||||
&buf[10]);
|
||||
return 10 + len;
|
||||
case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
||||
buf[1] = msg->set_screen_power_mode.mode;
|
||||
return 2;
|
||||
case SC_CONTROL_MSG_TYPE_UHID_CREATE:
|
||||
sc_write16be(&buf[1], msg->uhid_create.id);
|
||||
|
||||
size_t index = 3;
|
||||
index += write_string_tiny(&buf[index], msg->uhid_create.name, 127);
|
||||
|
||||
sc_write16be(&buf[index], msg->uhid_create.report_desc_size);
|
||||
index += 2;
|
||||
|
||||
memcpy(&buf[index], msg->uhid_create.report_desc,
|
||||
msg->uhid_create.report_desc_size);
|
||||
index += msg->uhid_create.report_desc_size;
|
||||
|
||||
return index;
|
||||
sc_write16be(&buf[3], msg->uhid_create.report_desc_size);
|
||||
memcpy(&buf[5], msg->uhid_create.report_desc,
|
||||
msg->uhid_create.report_desc_size);
|
||||
return 5 + msg->uhid_create.report_desc_size;
|
||||
case SC_CONTROL_MSG_TYPE_UHID_INPUT:
|
||||
sc_write16be(&buf[1], msg->uhid_input.id);
|
||||
sc_write16be(&buf[3], msg->uhid_input.size);
|
||||
@@ -280,15 +255,10 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
||||
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||
LOG_CMSG("rotate device");
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_UHID_CREATE: {
|
||||
// Quote only if name is not null
|
||||
const char *name = msg->uhid_create.name;
|
||||
const char *quote = name ? "\"" : "";
|
||||
LOG_CMSG("UHID create [%" PRIu16 "] name=%s%s%s "
|
||||
"report_desc_size=%" PRIu16, msg->uhid_create.id,
|
||||
quote, name, quote, msg->uhid_create.report_desc_size);
|
||||
case SC_CONTROL_MSG_TYPE_UHID_CREATE:
|
||||
LOG_CMSG("UHID create [%" PRIu16 "] report_desc_size=%" PRIu16,
|
||||
msg->uhid_create.id, msg->uhid_create.report_desc_size);
|
||||
break;
|
||||
}
|
||||
case SC_CONTROL_MSG_TYPE_UHID_INPUT: {
|
||||
char *hex = sc_str_to_hex_string(msg->uhid_input.data,
|
||||
msg->uhid_input.size);
|
||||
|
||||
@@ -98,7 +98,6 @@ struct sc_control_msg {
|
||||
} set_screen_power_mode;
|
||||
struct {
|
||||
uint16_t id;
|
||||
const char *name; // pointer to static data
|
||||
uint16_t report_desc_size;
|
||||
const uint8_t *report_desc; // pointer to static data
|
||||
} uhid_create;
|
||||
|
||||
@@ -116,7 +116,7 @@ sc_controller_push_msg(struct sc_controller *controller,
|
||||
LOG_OOM();
|
||||
}
|
||||
}
|
||||
// Otherwise, the msg is discarded
|
||||
// Otherwise (if the queue is full), the msg is discarded
|
||||
|
||||
sc_mutex_unlock(&controller->mutex);
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
#define SC_BUFFERING_NDEBUG // comment to debug
|
||||
|
||||
/** Downcast frame_sink to sc_delay_buffer */
|
||||
#define DOWNCAST(SINK) container_of(SINK, struct sc_delay_buffer, frame_sink)
|
||||
|
||||
@@ -78,7 +80,7 @@ run_buffering(void *data) {
|
||||
goto stopped;
|
||||
}
|
||||
|
||||
#ifdef SC_BUFFERING_DEBUG
|
||||
#ifndef SC_BUFFERING_NDEBUG
|
||||
LOGD("Buffering: %" PRItick ";%" PRItick ";%" PRItick,
|
||||
pts, dframe.push_date, sc_tick_now());
|
||||
#endif
|
||||
@@ -132,7 +134,6 @@ sc_delay_buffer_frame_sink_open(struct sc_frame_sink *sink,
|
||||
|
||||
sc_clock_init(&db->clock);
|
||||
sc_vecdeque_init(&db->queue);
|
||||
db->stopped = false;
|
||||
|
||||
if (!sc_frame_source_sinks_open(&db->frame_source, ctx)) {
|
||||
goto error_destroy_wait_cond;
|
||||
@@ -205,7 +206,7 @@ sc_delay_buffer_frame_sink_push(struct sc_frame_sink *sink,
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef SC_BUFFERING_DEBUG
|
||||
#ifndef SC_BUFFERING_NDEBUG
|
||||
dframe.push_date = sc_tick_now();
|
||||
#endif
|
||||
|
||||
|
||||
@@ -12,14 +12,12 @@
|
||||
#include "util/tick.h"
|
||||
#include "util/vecdeque.h"
|
||||
|
||||
//#define SC_BUFFERING_DEBUG // uncomment to debug
|
||||
|
||||
// forward declarations
|
||||
typedef struct AVFrame AVFrame;
|
||||
|
||||
struct sc_delayed_frame {
|
||||
AVFrame *frame;
|
||||
#ifdef SC_BUFFERING_DEBUG
|
||||
#ifndef NDEBUG
|
||||
sc_tick push_date;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -38,7 +38,7 @@ sc_post_to_main_thread(sc_runnable_fn run, void *userdata) {
|
||||
LOGD("Could not post runnable to main thread (filtered)");
|
||||
} else {
|
||||
assert(ret < 0);
|
||||
LOGW("Could not post runnable to main thread: %s", SDL_GetError());
|
||||
LOGW("Coud not post to main thread: %s", SDL_GetError());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ struct sc_hid_input {
|
||||
|
||||
struct sc_hid_open {
|
||||
uint16_t hid_id;
|
||||
const char *name; // pointer to static memory
|
||||
const uint8_t *report_desc; // pointer to static memory
|
||||
size_t report_desc_size;
|
||||
};
|
||||
|
||||
@@ -6,17 +6,17 @@
|
||||
#include "util/binary.h"
|
||||
#include "util/log.h"
|
||||
|
||||
// 2x2 bytes for left stick (X, Y)
|
||||
// 2x2 bytes for right stick (Z, Rz)
|
||||
// 2x2 bytes for L2/R2 triggers
|
||||
// 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) buttons are
|
||||
// stored locally in the MSB of this field, but not transmitted as is: they are
|
||||
// transformed to generate another specific byte.
|
||||
// (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
|
||||
@@ -26,7 +26,7 @@
|
||||
#define SC_GAMEPAD_BUTTONS_BIT_DPAD_RIGHT UINT32_C(0x80000)
|
||||
|
||||
/**
|
||||
* Gamepad descriptor manually crafted to transmit the input reports.
|
||||
* 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>
|
||||
@@ -90,7 +90,7 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
|
||||
// Usage Minimum (1)
|
||||
0x19, 0x01,
|
||||
// Usage Maximum (16)
|
||||
0x29, 0x10,
|
||||
0x29, 0x0F,
|
||||
// Logical Minimum (0)
|
||||
0x15, 0x00,
|
||||
// Logical Maximum (1)
|
||||
@@ -117,6 +117,7 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
|
||||
// Input (Data, Variable, Null State): 4-bit value
|
||||
0x81, 0x42,
|
||||
|
||||
|
||||
// End Collection
|
||||
0xC0,
|
||||
|
||||
@@ -125,7 +126,7 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
|
||||
};
|
||||
|
||||
/**
|
||||
* A gamepad HID input report is 15 bytes long:
|
||||
* 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
|
||||
@@ -134,28 +135,28 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
|
||||
*
|
||||
* +---------------+
|
||||
* byte 0: |. . . . . . . .|
|
||||
* | | left stick x (0-65535, little-endian)
|
||||
* | | left stick x state (0-65535)
|
||||
* byte 1: |. . . . . . . .|
|
||||
* +---------------+
|
||||
* byte 2: |. . . . . . . .|
|
||||
* | | left stick y (0-65535, little-endian)
|
||||
* | | left stick y state (0-65535)
|
||||
* byte 3: |. . . . . . . .|
|
||||
* +---------------+
|
||||
* byte 4: |. . . . . . . .|
|
||||
* | | right stick x (0-65535, little-endian)
|
||||
* | | right stick x state (0-65535)
|
||||
* byte 5: |. . . . . . . .|
|
||||
* +---------------+
|
||||
* byte 6: |. . . . . . . .|
|
||||
* | | right stick y (0-65535, little-endian)
|
||||
* | | right stick y state (0-65535)
|
||||
* byte 7: |. . . . . . . .|
|
||||
* +---------------+
|
||||
* byte 8: |. . . . . . . .|
|
||||
* | | L2 trigger (0-32767, little-endian)
|
||||
* byte 9: |0 . . . . . . .|
|
||||
* | | L2 trigger state (0-65535)
|
||||
* byte 9: |. . . . . . . .|
|
||||
* +---------------+
|
||||
* byte 10: |. . . . . . . .|
|
||||
* | | R2 trigger (0-32767, little-endian)
|
||||
* byte 11: |0 . . . . . . .|
|
||||
* | | R2 trigger state (0-65535)
|
||||
* byte 11: |. . . . . . . .|
|
||||
* +---------------+
|
||||
*
|
||||
* ,--------------- SC_GAMEPAD_BUTTON_RIGHT_SHOULDER
|
||||
@@ -243,14 +244,8 @@ sc_hid_gamepad_generate_open(struct sc_hid_gamepad *hid,
|
||||
|
||||
sc_hid_gamepad_slot_init(&hid->slots[slot_idx], gamepad_id);
|
||||
|
||||
SDL_GameController* game_controller =
|
||||
SDL_GameControllerFromInstanceID(gamepad_id);
|
||||
assert(game_controller);
|
||||
const char *name = SDL_GameControllerName(game_controller);
|
||||
|
||||
uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx);
|
||||
hid_open->hid_id = hid_id;
|
||||
hid_open->name = name;
|
||||
hid_open->report_desc = SC_HID_GAMEPAD_REPORT_DESC;
|
||||
hid_open->report_desc_size = sizeof(SC_HID_GAMEPAD_REPORT_DESC);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "input_events.h"
|
||||
|
||||
#define SC_MAX_GAMEPADS 8
|
||||
#define SC_HID_ID_GAMEPAD_FIRST 3
|
||||
#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 {
|
||||
|
||||
@@ -125,7 +125,7 @@ static const uint8_t SC_HID_KEYBOARD_REPORT_DESC[] = {
|
||||
};
|
||||
|
||||
/**
|
||||
* A keyboard HID input report is 8 bytes long:
|
||||
* A keyboard HID event is 8 bytes long:
|
||||
*
|
||||
* - byte 0: modifiers (1 flag per modifier key, 8 possible modifier keys)
|
||||
* - byte 1: reserved (always 0)
|
||||
@@ -335,7 +335,6 @@ sc_hid_keyboard_generate_input_from_mods(struct sc_hid_input *hid_input,
|
||||
|
||||
void sc_hid_keyboard_generate_open(struct sc_hid_open *hid_open) {
|
||||
hid_open->hid_id = SC_HID_ID_KEYBOARD;
|
||||
hid_open->name = NULL; // No name specified after "scrcpy"
|
||||
hid_open->report_desc = SC_HID_KEYBOARD_REPORT_DESC;
|
||||
hid_open->report_desc_size = sizeof(SC_HID_KEYBOARD_REPORT_DESC);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* <https://www.usb.org/document-library/hid-usage-tables-15>
|
||||
* §4 Generic Desktop Page (0x01) (p32)
|
||||
*/
|
||||
static const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
|
||||
const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
|
||||
// Usage Page (Generic Desktop)
|
||||
0x05, 0x01,
|
||||
// Usage (Mouse)
|
||||
@@ -81,7 +81,7 @@ static const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
|
||||
};
|
||||
|
||||
/**
|
||||
* A mouse HID input report is 4 bytes long:
|
||||
* A mouse HID event is 4 bytes long:
|
||||
*
|
||||
* - byte 0: buttons state
|
||||
* - byte 1: relative x motion (signed byte from -127 to 127)
|
||||
@@ -190,7 +190,6 @@ sc_hid_mouse_generate_input_from_scroll(struct sc_hid_input *hid_input,
|
||||
|
||||
void sc_hid_mouse_generate_open(struct sc_hid_open *hid_open) {
|
||||
hid_open->hid_id = SC_HID_ID_MOUSE;
|
||||
hid_open->name = NULL; // No name specified after "scrcpy"
|
||||
hid_open->report_desc = SC_HID_MOUSE_REPORT_DESC;
|
||||
hid_open->report_desc_size = sizeof(SC_HID_MOUSE_REPORT_DESC);
|
||||
}
|
||||
|
||||
@@ -516,7 +516,7 @@ sc_gamepad_device_event_type_from_sdl_type(uint32_t type) {
|
||||
static inline enum sc_gamepad_axis
|
||||
sc_gamepad_axis_from_sdl(uint8_t axis) {
|
||||
if (axis <= SDL_CONTROLLER_AXIS_TRIGGERRIGHT) {
|
||||
// SC_GAMEPAD_AXIS_* constants are initialized from
|
||||
// SDL_GAMEPAD_AXIS_* constants are initialized from
|
||||
// SDL_CONTROLLER_AXIS_*
|
||||
return axis;
|
||||
}
|
||||
|
||||
@@ -402,7 +402,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
bool paused = im->screen->paused;
|
||||
bool video = im->screen->video;
|
||||
|
||||
SDL_Keycode sdl_keycode = event->keysym.sym;
|
||||
SDL_Keycode keycode = event->keysym.sym;
|
||||
uint16_t mod = event->keysym.mod;
|
||||
bool down = event->type == SDL_KEYDOWN;
|
||||
bool ctrl = event->keysym.mod & KMOD_CTRL;
|
||||
@@ -414,21 +414,21 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
// The second condition is necessary to ignore the release of the modifier
|
||||
// key (because in this case mod is 0).
|
||||
bool is_shortcut = is_shortcut_mod(im, mod)
|
||||
|| is_shortcut_key(im, sdl_keycode);
|
||||
|| is_shortcut_key(im, keycode);
|
||||
|
||||
if (down && !repeat) {
|
||||
if (sdl_keycode == im->last_keycode && mod == im->last_mod) {
|
||||
if (keycode == im->last_keycode && mod == im->last_mod) {
|
||||
++im->key_repeat;
|
||||
} else {
|
||||
im->key_repeat = 0;
|
||||
im->last_keycode = sdl_keycode;
|
||||
im->last_keycode = keycode;
|
||||
im->last_mod = mod;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_shortcut) {
|
||||
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
|
||||
switch (sdl_keycode) {
|
||||
switch (keycode) {
|
||||
case SDLK_h:
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
action_home(im, action);
|
||||
@@ -587,7 +587,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
}
|
||||
|
||||
uint64_t ack_to_wait = SC_SEQUENCE_INVALID;
|
||||
bool is_ctrl_v = ctrl && !shift && sdl_keycode == SDLK_v && down && !repeat;
|
||||
bool is_ctrl_v = ctrl && !shift && keycode == SDLK_v && down && !repeat;
|
||||
if (im->clipboard_autosync && is_ctrl_v) {
|
||||
if (im->legacy_paste) {
|
||||
// inject the text as input events
|
||||
@@ -615,20 +615,10 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
}
|
||||
}
|
||||
|
||||
enum sc_keycode keycode = sc_keycode_from_sdl(sdl_keycode);
|
||||
if (keycode == SC_KEYCODE_UNKNOWN) {
|
||||
return;
|
||||
}
|
||||
|
||||
enum sc_scancode scancode = sc_scancode_from_sdl(event->keysym.scancode);
|
||||
if (scancode == SC_SCANCODE_UNKNOWN) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_key_event evt = {
|
||||
.action = sc_action_from_sdl_keyboard_type(event->type),
|
||||
.keycode = keycode,
|
||||
.scancode = scancode,
|
||||
.keycode = sc_keycode_from_sdl(event->keysym.sym),
|
||||
.scancode = sc_scancode_from_sdl(event->keysym.scancode),
|
||||
.repeat = event->repeat,
|
||||
.mods_state = sc_mods_state_from_sdl(event->keysym.mod),
|
||||
};
|
||||
@@ -751,10 +741,6 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
||||
bool down = event->type == SDL_MOUSEBUTTONDOWN;
|
||||
|
||||
enum sc_mouse_button button = sc_mouse_button_from_sdl(event->button);
|
||||
if (button == SC_MOUSE_BUTTON_UNKNOWN) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!down) {
|
||||
// Mark the button as released
|
||||
im->mouse_buttons_state &= ~button;
|
||||
@@ -843,7 +829,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
||||
struct sc_mouse_click_event evt = {
|
||||
.position = sc_input_manager_get_position(im, event->x, event->y),
|
||||
.action = sc_action_from_sdl_mousebutton_type(event->type),
|
||||
.button = button,
|
||||
.button = sc_mouse_button_from_sdl(event->button),
|
||||
.pointer_id = use_finger ? SC_POINTER_ID_GENERIC_FINGER
|
||||
: SC_POINTER_ID_MOUSE,
|
||||
.buttons_state = im->mouse_buttons_state,
|
||||
@@ -964,15 +950,10 @@ sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
|
||||
|
||||
static void
|
||||
sc_input_manager_process_gamepad_axis(struct sc_input_manager *im,
|
||||
const SDL_ControllerAxisEvent *event) {
|
||||
enum sc_gamepad_axis axis = sc_gamepad_axis_from_sdl(event->axis);
|
||||
if (axis == SC_GAMEPAD_AXIS_UNKNOWN) {
|
||||
return;
|
||||
}
|
||||
|
||||
const SDL_ControllerAxisEvent *event) {
|
||||
struct sc_gamepad_axis_event evt = {
|
||||
.gamepad_id = event->which,
|
||||
.axis = axis,
|
||||
.axis = sc_gamepad_axis_from_sdl(event->axis),
|
||||
.value = event->value,
|
||||
};
|
||||
im->gp->ops->process_gamepad_axis(im->gp, &evt);
|
||||
@@ -981,15 +962,10 @@ sc_input_manager_process_gamepad_axis(struct sc_input_manager *im,
|
||||
static void
|
||||
sc_input_manager_process_gamepad_button(struct sc_input_manager *im,
|
||||
const SDL_ControllerButtonEvent *event) {
|
||||
enum sc_gamepad_button button = sc_gamepad_button_from_sdl(event->button);
|
||||
if (button == SC_GAMEPAD_BUTTON_UNKNOWN) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_gamepad_button_event evt = {
|
||||
.gamepad_id = event->which,
|
||||
.action = sc_action_from_sdl_controllerbutton_type(event->type),
|
||||
.button = button,
|
||||
.button = sc_gamepad_button_from_sdl(event->button),
|
||||
};
|
||||
im->gp->ops->process_gamepad_button(im->gp, &evt);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
.record_format = SC_RECORD_FORMAT_AUTO,
|
||||
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AUTO,
|
||||
.mouse_input_mode = SC_MOUSE_INPUT_MODE_AUTO,
|
||||
.gamepad_input_mode = SC_GAMEPAD_INPUT_MODE_DISABLED,
|
||||
.gamepad_input_mode = SC_GAMEPAD_INPUT_MODE_AUTO,
|
||||
.mouse_bindings = {
|
||||
.pri = {
|
||||
.right_click = SC_MOUSE_BINDING_AUTO,
|
||||
|
||||
@@ -159,8 +159,9 @@ enum sc_mouse_input_mode {
|
||||
};
|
||||
|
||||
enum sc_gamepad_input_mode {
|
||||
SC_GAMEPAD_INPUT_MODE_DISABLED,
|
||||
SC_GAMEPAD_INPUT_MODE_AUTO,
|
||||
SC_GAMEPAD_INPUT_MODE_UHID_OR_AOA, // normal vs otg mode
|
||||
SC_GAMEPAD_INPUT_MODE_DISABLED,
|
||||
SC_GAMEPAD_INPUT_MODE_UHID,
|
||||
SC_GAMEPAD_INPUT_MODE_AOA,
|
||||
};
|
||||
@@ -250,7 +251,7 @@ struct scrcpy_options {
|
||||
uint16_t max_size;
|
||||
uint32_t video_bit_rate;
|
||||
uint32_t audio_bit_rate;
|
||||
float max_fps;
|
||||
uint16_t max_fps;
|
||||
enum sc_lock_video_orientation lock_video_orientation;
|
||||
enum sc_orientation display_orientation;
|
||||
enum sc_orientation record_orientation;
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "events.h"
|
||||
#include "util/log.h"
|
||||
#include "util/str.h"
|
||||
#include "util/thread.h"
|
||||
|
||||
struct sc_uhid_output_task_data {
|
||||
struct sc_uhid_devices *uhid_devices;
|
||||
@@ -44,8 +43,6 @@ sc_receiver_destroy(struct sc_receiver *receiver) {
|
||||
|
||||
static void
|
||||
task_set_clipboard(void *userdata) {
|
||||
assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);
|
||||
|
||||
char *text = userdata;
|
||||
|
||||
char *current = SDL_GetClipboardText();
|
||||
@@ -53,18 +50,17 @@ task_set_clipboard(void *userdata) {
|
||||
SDL_free(current);
|
||||
if (same) {
|
||||
LOGD("Computer clipboard unchanged");
|
||||
} else {
|
||||
LOGI("Device clipboard copied");
|
||||
SDL_SetClipboardText(text);
|
||||
free(text);
|
||||
return;
|
||||
}
|
||||
|
||||
LOGI("Device clipboard copied");
|
||||
SDL_SetClipboardText(text);
|
||||
free(text);
|
||||
}
|
||||
|
||||
static void
|
||||
task_uhid_output(void *userdata) {
|
||||
assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);
|
||||
|
||||
struct sc_uhid_output_task_data *data = userdata;
|
||||
|
||||
sc_uhid_devices_process_hid_output(data->uhid_devices, data->id, data->data,
|
||||
@@ -121,6 +117,11 @@ process_msg(struct sc_receiver *receiver, struct sc_device_msg *msg) {
|
||||
}
|
||||
}
|
||||
|
||||
// This is a programming error to receive this message if there is
|
||||
// no uhid_devices instance
|
||||
assert(receiver->uhid_devices);
|
||||
|
||||
// Also check at runtime (do not trust the server)
|
||||
if (!receiver->uhid_devices) {
|
||||
LOGE("Received unexpected HID output message");
|
||||
sc_device_msg_destroy(msg);
|
||||
|
||||
@@ -65,8 +65,8 @@ struct scrcpy {
|
||||
struct sc_aoa aoa;
|
||||
// sequence/ack helper to synchronize clipboard and Ctrl+v via HID
|
||||
struct sc_acksync acksync;
|
||||
#endif
|
||||
struct sc_uhid_devices uhid_devices;
|
||||
#endif
|
||||
union {
|
||||
struct sc_keyboard_sdk keyboard_sdk;
|
||||
struct sc_keyboard_uhid keyboard_uhid;
|
||||
@@ -136,10 +136,6 @@ sdl_set_hints(const char *render_driver) {
|
||||
if (!SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0")) {
|
||||
LOGW("Could not disable minimize on focus loss");
|
||||
}
|
||||
|
||||
if (!SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1")) {
|
||||
LOGW("Could not allow joystick background events");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -728,6 +724,7 @@ aoa_complete:
|
||||
#endif
|
||||
|
||||
struct sc_keyboard_uhid *uhid_keyboard = NULL;
|
||||
struct sc_gamepad_uhid *uhid_gamepad = NULL;
|
||||
|
||||
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_SDK) {
|
||||
sc_keyboard_sdk_init(&s->keyboard_sdk, &s->controller,
|
||||
@@ -762,8 +759,8 @@ aoa_complete:
|
||||
}
|
||||
|
||||
struct sc_uhid_devices *uhid_devices = NULL;
|
||||
if (uhid_keyboard) {
|
||||
sc_uhid_devices_init(&s->uhid_devices, uhid_keyboard);
|
||||
if (uhid_keyboard || uhid_gamepad) {
|
||||
sc_uhid_devices_init(&s->uhid_devices, uhid_keyboard, uhid_gamepad);
|
||||
uhid_devices = &s->uhid_devices;
|
||||
}
|
||||
|
||||
@@ -900,8 +897,8 @@ aoa_complete:
|
||||
timeout_started = true;
|
||||
}
|
||||
|
||||
if (options->control
|
||||
&& options->gamepad_input_mode != SC_GAMEPAD_INPUT_MODE_DISABLED) {
|
||||
bool use_gamepads = true;
|
||||
if (use_gamepads) {
|
||||
init_sdl_gamepads();
|
||||
}
|
||||
|
||||
|
||||
@@ -299,12 +299,6 @@ sc_screen_frame_sink_open(struct sc_frame_sink *sink,
|
||||
|
||||
struct sc_screen *screen = DOWNCAST(sink);
|
||||
|
||||
if (ctx->width <= 0 || ctx->width > 0xFFFF
|
||||
|| ctx->height <= 0 || ctx->height > 0xFFFF) {
|
||||
LOGE("Invalid video size: %dx%d", ctx->width, ctx->height);
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(ctx->width > 0 && ctx->width <= 0xFFFF);
|
||||
assert(ctx->height > 0 && ctx->height <= 0xFFFF);
|
||||
// screen->frame_size is never used before the event is pushed, and the
|
||||
@@ -315,6 +309,7 @@ sc_screen_frame_sink_open(struct sc_frame_sink *sink,
|
||||
// Post the event on the UI thread (the texture must be created from there)
|
||||
bool ok = sc_push_event(SC_EVENT_SCREEN_INIT_SIZE);
|
||||
if (!ok) {
|
||||
LOGW("Could not post init size event: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -356,6 +351,7 @@ sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
|
||||
// Post the event on the UI thread
|
||||
bool ok = sc_push_event(SC_EVENT_NEW_FRAME);
|
||||
if (!ok) {
|
||||
LOGW("Could not post new frame event: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,21 +218,6 @@ sc_server_get_audio_source_name(enum sc_audio_source audio_source) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
validate_string(const char *s) {
|
||||
// The parameters values are passed as command line arguments to adb, so
|
||||
// they must either be properly escaped, or they must not contain any
|
||||
// special shell characters.
|
||||
// Since they are not properly escaped on Windows anyway (see
|
||||
// sys/win/process.c), just forbid special shell characters.
|
||||
if (strpbrk(s, " ;'\"*$?&`#\\|<>[]{}()!~")) {
|
||||
LOGE("Invalid server param: [%s]", s);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static sc_pid
|
||||
execute_server(struct sc_server *server,
|
||||
const struct sc_server_params *params) {
|
||||
@@ -275,11 +260,6 @@ execute_server(struct sc_server *server,
|
||||
} \
|
||||
cmd[count++] = p; \
|
||||
} while(0)
|
||||
#define VALIDATE_STRING(s) do { \
|
||||
if (!validate_string(s)) { \
|
||||
goto end; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
ADD_PARAM("scid=%08x", params->scid);
|
||||
ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level));
|
||||
@@ -321,7 +301,7 @@ execute_server(struct sc_server *server,
|
||||
ADD_PARAM("max_size=%" PRIu16, params->max_size);
|
||||
}
|
||||
if (params->max_fps) {
|
||||
ADD_PARAM("max_fps=%f" , params->max_fps);
|
||||
ADD_PARAM("max_fps=%" PRIu16, params->max_fps);
|
||||
}
|
||||
if (params->lock_video_orientation != SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
|
||||
ADD_PARAM("lock_video_orientation=%" PRIi8,
|
||||
@@ -331,7 +311,6 @@ execute_server(struct sc_server *server,
|
||||
ADD_PARAM("tunnel_forward=true");
|
||||
}
|
||||
if (params->crop) {
|
||||
VALIDATE_STRING(params->crop);
|
||||
ADD_PARAM("crop=%s", params->crop);
|
||||
}
|
||||
if (!params->control) {
|
||||
@@ -342,11 +321,9 @@ execute_server(struct sc_server *server,
|
||||
ADD_PARAM("display_id=%" PRIu32, params->display_id);
|
||||
}
|
||||
if (params->camera_id) {
|
||||
VALIDATE_STRING(params->camera_id);
|
||||
ADD_PARAM("camera_id=%s", params->camera_id);
|
||||
}
|
||||
if (params->camera_size) {
|
||||
VALIDATE_STRING(params->camera_size);
|
||||
ADD_PARAM("camera_size=%s", params->camera_size);
|
||||
}
|
||||
if (params->camera_facing != SC_CAMERA_FACING_ANY) {
|
||||
@@ -354,7 +331,6 @@ execute_server(struct sc_server *server,
|
||||
sc_server_get_camera_facing_name(params->camera_facing));
|
||||
}
|
||||
if (params->camera_ar) {
|
||||
VALIDATE_STRING(params->camera_ar);
|
||||
ADD_PARAM("camera_ar=%s", params->camera_ar);
|
||||
}
|
||||
if (params->camera_fps) {
|
||||
@@ -370,19 +346,15 @@ execute_server(struct sc_server *server,
|
||||
ADD_PARAM("stay_awake=true");
|
||||
}
|
||||
if (params->video_codec_options) {
|
||||
VALIDATE_STRING(params->video_codec_options);
|
||||
ADD_PARAM("video_codec_options=%s", params->video_codec_options);
|
||||
}
|
||||
if (params->audio_codec_options) {
|
||||
VALIDATE_STRING(params->audio_codec_options);
|
||||
ADD_PARAM("audio_codec_options=%s", params->audio_codec_options);
|
||||
}
|
||||
if (params->video_encoder) {
|
||||
VALIDATE_STRING(params->video_encoder);
|
||||
ADD_PARAM("video_encoder=%s", params->video_encoder);
|
||||
}
|
||||
if (params->audio_encoder) {
|
||||
VALIDATE_STRING(params->audio_encoder);
|
||||
ADD_PARAM("audio_encoder=%s", params->audio_encoder);
|
||||
}
|
||||
if (params->power_off_on_close) {
|
||||
|
||||
@@ -44,7 +44,7 @@ struct sc_server_params {
|
||||
uint16_t max_size;
|
||||
uint32_t video_bit_rate;
|
||||
uint32_t audio_bit_rate;
|
||||
float max_fps;
|
||||
uint16_t max_fps;
|
||||
int8_t lock_video_orientation;
|
||||
bool control;
|
||||
uint32_t display_id;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#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)
|
||||
@@ -30,7 +31,6 @@ sc_gamepad_uhid_send_open(struct sc_gamepad_uhid *gamepad,
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
||||
msg.uhid_create.id = hid_open->hid_id;
|
||||
msg.uhid_create.name = hid_open->name;
|
||||
msg.uhid_create.report_desc = hid_open->report_desc;
|
||||
msg.uhid_create.report_desc_size = hid_open->report_desc_size;
|
||||
|
||||
@@ -106,6 +106,22 @@ sc_gamepad_processor_process_gamepad_button(struct sc_gamepad_processor *gp,
|
||||
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -20,4 +20,9 @@ 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
|
||||
|
||||
@@ -141,7 +141,6 @@ sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
||||
msg.uhid_create.id = SC_HID_ID_KEYBOARD;
|
||||
msg.uhid_create.name = hid_open.name;
|
||||
msg.uhid_create.report_desc = hid_open.report_desc;
|
||||
msg.uhid_create.report_desc_size = hid_open.report_desc_size;
|
||||
if (!sc_controller_push_msg(controller, &msg)) {
|
||||
|
||||
@@ -81,7 +81,6 @@ sc_mouse_uhid_init(struct sc_mouse_uhid *mouse,
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
||||
msg.uhid_create.id = SC_HID_ID_MOUSE;
|
||||
msg.uhid_create.name = hid_open.name;
|
||||
msg.uhid_create.report_desc = hid_open.report_desc;
|
||||
msg.uhid_create.report_desc_size = hid_open.report_desc_size;
|
||||
if (!sc_controller_push_msg(controller, &msg)) {
|
||||
|
||||
@@ -4,12 +4,15 @@
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "uhid/keyboard_uhid.h"
|
||||
#include "uhid/gamepad_uhid.h"
|
||||
#include "util/log.h"
|
||||
|
||||
void
|
||||
sc_uhid_devices_init(struct sc_uhid_devices *devices,
|
||||
struct sc_keyboard_uhid *keyboard) {
|
||||
struct sc_keyboard_uhid *keyboard,
|
||||
struct sc_gamepad_uhid *gamepad) {
|
||||
devices->keyboard = keyboard;
|
||||
devices->gamepad = gamepad;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -21,6 +24,13 @@ sc_uhid_devices_process_hid_output(struct sc_uhid_devices *devices, uint16_t id,
|
||||
} else {
|
||||
LOGW("Unexpected keyboard HID output without UHID keyboard");
|
||||
}
|
||||
} else if (id >= SC_HID_ID_GAMEPAD_FIRST && id <= SC_HID_ID_GAMEPAD_LAST) {
|
||||
if (devices->gamepad) {
|
||||
sc_gamepad_uhid_process_hid_output(devices->gamepad, id, data,
|
||||
size);
|
||||
} else {
|
||||
LOGW("Unexpected gamepad HID output without UHID gamepad");
|
||||
}
|
||||
} else {
|
||||
LOGW("HID output ignored for id %" PRIu16, id);
|
||||
}
|
||||
|
||||
@@ -14,11 +14,13 @@
|
||||
|
||||
struct sc_uhid_devices {
|
||||
struct sc_keyboard_uhid *keyboard;
|
||||
struct sc_gamepad_uhid *gamepad;
|
||||
};
|
||||
|
||||
void
|
||||
sc_uhid_devices_init(struct sc_uhid_devices *devices,
|
||||
struct sc_keyboard_uhid *keyboard);
|
||||
struct sc_keyboard_uhid *keyboard,
|
||||
struct sc_gamepad_uhid *gamepad);
|
||||
|
||||
void
|
||||
sc_uhid_devices_process_hid_output(struct sc_uhid_devices *devices, uint16_t id,
|
||||
|
||||
@@ -51,7 +51,7 @@ sc_hid_open_log(const struct sc_hid_open *hid_open) {
|
||||
static void
|
||||
sc_hid_close_log(const struct sc_hid_close *hid_close) {
|
||||
// HID close: [00]
|
||||
LOGV("HID close: [%" PRIu16 "]", hid_close->hid_id);
|
||||
LOGV("HD close: [%" PRIu16 "]", hid_close->hid_id);
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -227,11 +227,8 @@ sc_aoa_push_input_with_ack_to_wait(struct sc_aoa *aoa,
|
||||
}
|
||||
|
||||
sc_mutex_lock(&aoa->mutex);
|
||||
|
||||
bool pushed = false;
|
||||
|
||||
size_t size = sc_vecdeque_size(&aoa->queue);
|
||||
if (size < SC_AOA_EVENT_QUEUE_LIMIT) {
|
||||
bool full = sc_vecdeque_is_full(&aoa->queue);
|
||||
if (!full) {
|
||||
bool was_empty = sc_vecdeque_is_empty(&aoa->queue);
|
||||
|
||||
struct sc_aoa_event *aoa_event =
|
||||
@@ -239,17 +236,16 @@ sc_aoa_push_input_with_ack_to_wait(struct sc_aoa *aoa,
|
||||
aoa_event->type = SC_AOA_EVENT_TYPE_INPUT;
|
||||
aoa_event->input.hid = *hid_input;
|
||||
aoa_event->input.ack_to_wait = ack_to_wait;
|
||||
pushed = true;
|
||||
|
||||
if (was_empty) {
|
||||
sc_cond_signal(&aoa->event_cond);
|
||||
}
|
||||
}
|
||||
// Otherwise, the event is discarded
|
||||
// Otherwise (if the queue is full), the event is discarded
|
||||
|
||||
sc_mutex_unlock(&aoa->mutex);
|
||||
|
||||
return pushed;
|
||||
return !full;
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -293,7 +289,7 @@ sc_aoa_push_close(struct sc_aoa *aoa, const struct sc_hid_close *hid_close) {
|
||||
sc_mutex_lock(&aoa->mutex);
|
||||
bool was_empty = sc_vecdeque_is_empty(&aoa->queue);
|
||||
|
||||
// a CLOSE event is non-droppable, so push it to the queue even above the
|
||||
// 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) {
|
||||
|
||||
@@ -22,7 +22,10 @@ sc_usb_on_disconnected(struct sc_usb *usb, void *userdata) {
|
||||
(void) usb;
|
||||
(void) userdata;
|
||||
|
||||
sc_push_event(SC_EVENT_USB_DEVICE_DISCONNECTED);
|
||||
bool ok = sc_push_event(SC_EVENT_USB_DEVICE_DISCONNECTED);
|
||||
if (!ok) {
|
||||
LOGE("Could not post USB disconnection event: %s", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
static enum scrcpy_exit_code
|
||||
@@ -58,10 +61,6 @@ scrcpy_otg(struct scrcpy_options *options) {
|
||||
LOGW("Could not enable linear filtering");
|
||||
}
|
||||
|
||||
if (!SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1")) {
|
||||
LOGW("Could not allow joystick background events");
|
||||
}
|
||||
|
||||
// Minimal SDL initialization
|
||||
if (SDL_Init(SDL_INIT_EVENTS)) {
|
||||
LOGE("Could not initialize SDL: %s", SDL_GetError());
|
||||
|
||||
@@ -264,14 +264,9 @@ sc_screen_otg_process_gamepad_axis(struct sc_screen_otg *screen,
|
||||
assert(screen->gamepad);
|
||||
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
|
||||
|
||||
enum sc_gamepad_axis axis = sc_gamepad_axis_from_sdl(event->axis);
|
||||
if (axis == SC_GAMEPAD_AXIS_UNKNOWN) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_gamepad_axis_event evt = {
|
||||
.gamepad_id = event->which,
|
||||
.axis = axis,
|
||||
.axis = sc_gamepad_axis_from_sdl(event->axis),
|
||||
.value = event->value,
|
||||
};
|
||||
gp->ops->process_gamepad_axis(gp, &evt);
|
||||
@@ -283,15 +278,10 @@ sc_screen_otg_process_gamepad_button(struct sc_screen_otg *screen,
|
||||
assert(screen->gamepad);
|
||||
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
|
||||
|
||||
enum sc_gamepad_button button = sc_gamepad_button_from_sdl(event->button);
|
||||
if (button == SC_GAMEPAD_BUTTON_UNKNOWN) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_gamepad_button_event evt = {
|
||||
.gamepad_id = event->which,
|
||||
.action = sc_action_from_sdl_controllerbutton_type(event->type),
|
||||
.button = button,
|
||||
.button = sc_gamepad_button_from_sdl(event->button),
|
||||
};
|
||||
gp->ops->process_gamepad_button(gp, &evt);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ sc_write64be(uint8_t *buf, uint64_t value) {
|
||||
static inline void
|
||||
sc_write64le(uint8_t *buf, uint64_t value) {
|
||||
sc_write32le(buf, (uint32_t) value);
|
||||
sc_write32le(&buf[4], value >> 32);
|
||||
sc_write32le(buf, value >> 32);
|
||||
}
|
||||
|
||||
static inline uint16_t
|
||||
|
||||
@@ -147,25 +147,6 @@ sc_str_parse_integer_with_suffix(const char *s, long *out) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_str_parse_float(const char *s, float *out) {
|
||||
char *endptr;
|
||||
if (*s == '\0') {
|
||||
return false;
|
||||
}
|
||||
errno = 0;
|
||||
float value = strtof(s, &endptr);
|
||||
if (errno == ERANGE) {
|
||||
return false;
|
||||
}
|
||||
if (*endptr != '\0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
*out = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_str_list_contains(const char *list, char sep, const char *s) {
|
||||
char *p;
|
||||
|
||||
@@ -66,14 +66,6 @@ sc_str_parse_integers(const char *s, const char sep, size_t max_items,
|
||||
bool
|
||||
sc_str_parse_integer_with_suffix(const char *s, long *out);
|
||||
|
||||
/**
|
||||
* `Parse `s` as a float into `out`
|
||||
*
|
||||
* Return true if the conversion succeeded, false otherwise.
|
||||
*/
|
||||
bool
|
||||
sc_str_parse_float(const char *s, float *out);
|
||||
|
||||
/**
|
||||
* Search `s` in the list separated by `sep`
|
||||
*
|
||||
|
||||
@@ -42,44 +42,6 @@ static void test_write64be(void) {
|
||||
assert(buf[7] == 0xEF);
|
||||
}
|
||||
|
||||
static void test_write16le(void) {
|
||||
uint16_t val = 0xABCD;
|
||||
uint8_t buf[2];
|
||||
|
||||
sc_write16le(buf, val);
|
||||
|
||||
assert(buf[0] == 0xCD);
|
||||
assert(buf[1] == 0xAB);
|
||||
}
|
||||
|
||||
static void test_write32le(void) {
|
||||
uint32_t val = 0xABCD1234;
|
||||
uint8_t buf[4];
|
||||
|
||||
sc_write32le(buf, val);
|
||||
|
||||
assert(buf[0] == 0x34);
|
||||
assert(buf[1] == 0x12);
|
||||
assert(buf[2] == 0xCD);
|
||||
assert(buf[3] == 0xAB);
|
||||
}
|
||||
|
||||
static void test_write64le(void) {
|
||||
uint64_t val = 0xABCD1234567890EF;
|
||||
uint8_t buf[8];
|
||||
|
||||
sc_write64le(buf, val);
|
||||
|
||||
assert(buf[0] == 0xEF);
|
||||
assert(buf[1] == 0x90);
|
||||
assert(buf[2] == 0x78);
|
||||
assert(buf[3] == 0x56);
|
||||
assert(buf[4] == 0x34);
|
||||
assert(buf[5] == 0x12);
|
||||
assert(buf[6] == 0xCD);
|
||||
assert(buf[7] == 0xAB);
|
||||
}
|
||||
|
||||
static void test_read16be(void) {
|
||||
uint8_t buf[2] = {0xAB, 0xCD};
|
||||
|
||||
@@ -146,10 +108,6 @@ int main(int argc, char *argv[]) {
|
||||
test_read32be();
|
||||
test_read64be();
|
||||
|
||||
test_write16le();
|
||||
test_write32le();
|
||||
test_write64le();
|
||||
|
||||
test_float_to_u16fp();
|
||||
test_float_to_i16fp();
|
||||
return 0;
|
||||
|
||||
@@ -329,7 +329,6 @@ static void test_serialize_uhid_create(void) {
|
||||
.type = SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
||||
.uhid_create = {
|
||||
.id = 42,
|
||||
.name = "ABC",
|
||||
.report_desc_size = sizeof(report_desc),
|
||||
.report_desc = report_desc,
|
||||
},
|
||||
@@ -337,14 +336,12 @@ static void test_serialize_uhid_create(void) {
|
||||
|
||||
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
|
||||
size_t size = sc_control_msg_serialize(&msg, buf);
|
||||
assert(size == 20);
|
||||
assert(size == 16);
|
||||
|
||||
const uint8_t expected[] = {
|
||||
SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
||||
0, 42, // id
|
||||
3, // name size
|
||||
65, 66, 67, // "ABC"
|
||||
0, 11, // report desc size
|
||||
0, 11, // size
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
|
||||
};
|
||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
# Gamepad
|
||||
|
||||
Several gamepad input modes are available:
|
||||
|
||||
- `--gamepad=disabled` (default)
|
||||
- `--gamepad=uhid` (or `-G`): simulates physical HID gamepads using the UHID
|
||||
kernel module on the device
|
||||
- `--gamepad=aoa`: simulates physical HID gamepads using the AOAv2 protocol
|
||||
|
||||
|
||||
## Physical gamepad simulation
|
||||
|
||||
Two modes allow to simulate physical HID gamepads on the device, one for each
|
||||
physical gamepad plugged into the computer.
|
||||
|
||||
|
||||
### UHID
|
||||
|
||||
This mode simulates physical HID gamepads using the [UHID] kernel module on the
|
||||
device.
|
||||
|
||||
[UHID]: https://kernel.org/doc/Documentation/hid/uhid.txt
|
||||
|
||||
To enable UHID gamepads, use:
|
||||
|
||||
```bash
|
||||
scrcpy --gamepad=uhid
|
||||
scrcpy -G # short version
|
||||
```
|
||||
|
||||
|
||||
### AOA
|
||||
|
||||
This mode simulates physical HID gamepads using the [AOAv2] protocol.
|
||||
|
||||
[AOAv2]: https://source.android.com/devices/accessories/aoa2#hid-support
|
||||
|
||||
To enable AOA gamepads, use:
|
||||
|
||||
```bash
|
||||
scrcpy --gamepad=aoa
|
||||
```
|
||||
|
||||
Contrary to the other mode, it works at the USB level directly (so it only works
|
||||
over USB).
|
||||
|
||||
It does not use the scrcpy server, and does not require `adb` (USB debugging).
|
||||
Therefore, it is possible to control the device (but not mirror) even with USB
|
||||
debugging disabled (see [OTG](otg.md)).
|
||||
|
||||
Note: On Windows, it may only work in [OTG mode](otg.md), not while mirroring
|
||||
(it is not possible to open a USB device if it is already open by another
|
||||
process like the _adb daemon_).
|
||||
29
doc/otg.md
29
doc/otg.md
@@ -6,18 +6,16 @@ was a [physical keyboard] and/or a [physical mouse] connected to the Android
|
||||
device (see [keyboard](keyboard.md) and [mouse](mouse.md)).
|
||||
|
||||
[physical keyboard]: keyboard.md#physical-keyboard-simulation
|
||||
[physical mouse]: mouse.md#physical-mouse-simulation
|
||||
[physical mouse]: physical-keyboard-simulation
|
||||
|
||||
A special mode (OTG) allows to control the device using AOA
|
||||
[keyboard](keyboard.md#aoa), [mouse](mouse.md#aoa) and
|
||||
[gamepad](gamepad.md#aoa), without using _adb_ at all (so USB debugging is not
|
||||
necessary). In this mode, video and audio are disabled, and `--keyboard=aoa` and
|
||||
`--mouse=aoa` are implicitly set. However, gamepads are disabled by default, so
|
||||
`--gamepad=aoa` (or `-G` in OTG mode) must be explicitly set.
|
||||
[keyboard](keyboard.md#aoa) and [mouse](mouse.md#aoa), without using _adb_ at
|
||||
all (so USB debugging is not necessary). In this mode, video and audio are
|
||||
disabled, and `--keyboard=aoa and `--mouse=aoa` are implicitly set.
|
||||
|
||||
Therefore, it is possible to run _scrcpy_ with only physical keyboard, mouse and
|
||||
gamepad simulation, as if the computer keyboard, mouse and gamepads were plugged
|
||||
directly to the device via an OTG cable.
|
||||
Therefore, it is possible to run _scrcpy_ with only physical keyboard and mouse
|
||||
simulation, as if the computer keyboard and mouse were plugged directly to the
|
||||
device via an OTG cable.
|
||||
|
||||
To enable OTG mode:
|
||||
|
||||
@@ -34,13 +32,6 @@ scrcpy --otg --keyboard=disabled
|
||||
scrcpy --otg --mouse=disabled
|
||||
```
|
||||
|
||||
and to enable gamepads:
|
||||
|
||||
```bash
|
||||
scrcpy --otg --gamepad=aoa
|
||||
scrcpy --otg -G # short version
|
||||
```
|
||||
|
||||
It only works if the device is connected over USB.
|
||||
|
||||
## OTG issues on Windows
|
||||
@@ -59,9 +50,9 @@ is enabled, then OTG mode is not necessary.
|
||||
Instead, disable video and audio, and select UHID (or AOA):
|
||||
|
||||
```bash
|
||||
scrcpy --no-video --no-audio --keyboard=uhid --mouse=uhid --gamepad=uhid
|
||||
scrcpy --no-video --no-audio -KMG # short version
|
||||
scrcpy --no-video --no-audio --keyboard=aoa --mouse=aoa --gamepad=aoa
|
||||
scrcpy --no-video --no-audio --keyboard=uhid --mouse=uhid
|
||||
scrcpy --no-video --no-audio -KM # short version
|
||||
scrcpy --no-video --no-audio --keyboard=aoa --mouse=aoa
|
||||
```
|
||||
|
||||
One benefit of UHID is that it also works wirelessly.
|
||||
|
||||
@@ -29,7 +29,7 @@ public class Options {
|
||||
private boolean audioDup;
|
||||
private int videoBitRate = 8000000;
|
||||
private int audioBitRate = 128000;
|
||||
private float maxFps;
|
||||
private int maxFps;
|
||||
private int lockVideoOrientation = -1;
|
||||
private boolean tunnelForward;
|
||||
private Rect crop;
|
||||
@@ -113,7 +113,7 @@ public class Options {
|
||||
return audioBitRate;
|
||||
}
|
||||
|
||||
public float getMaxFps() {
|
||||
public int getMaxFps() {
|
||||
return maxFps;
|
||||
}
|
||||
|
||||
@@ -321,7 +321,7 @@ public class Options {
|
||||
options.audioBitRate = Integer.parseInt(value);
|
||||
break;
|
||||
case "max_fps":
|
||||
options.maxFps = parseFloat("max_fps", value);
|
||||
options.maxFps = Integer.parseInt(value);
|
||||
break;
|
||||
case "lock_video_orientation":
|
||||
options.lockVideoOrientation = Integer.parseInt(value);
|
||||
@@ -456,14 +456,8 @@ public class Options {
|
||||
}
|
||||
int width = Integer.parseInt(tokens[0]);
|
||||
int height = Integer.parseInt(tokens[1]);
|
||||
if (width <= 0 || height <= 0) {
|
||||
throw new IllegalArgumentException("Invalid crop size: " + width + "x" + height);
|
||||
}
|
||||
int x = Integer.parseInt(tokens[2]);
|
||||
int y = Integer.parseInt(tokens[3]);
|
||||
if (x < 0 || y < 0) {
|
||||
throw new IllegalArgumentException("Invalid crop offset: " + x + ":" + y);
|
||||
}
|
||||
return new Rect(x, y, x + width, y + height);
|
||||
}
|
||||
|
||||
@@ -493,12 +487,4 @@ public class Options {
|
||||
float floatAr = Float.parseFloat(tokens[0]);
|
||||
return CameraAspectRatio.fromFloat(floatAr);
|
||||
}
|
||||
|
||||
private static float parseFloat(String key, String value) {
|
||||
try {
|
||||
return Float.parseFloat(value);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Invalid float value for " + key + ": \"" + value + "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,11 +56,8 @@ public class AudioDirectCapture implements AudioCapture {
|
||||
builder.setAudioSource(audioSource);
|
||||
builder.setAudioFormat(AudioConfig.createAudioFormat());
|
||||
int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, ENCODING);
|
||||
if (minBufferSize > 0) {
|
||||
// This buffer size does not impact latency
|
||||
builder.setBufferSizeInBytes(8 * minBufferSize);
|
||||
}
|
||||
|
||||
// This buffer size does not impact latency
|
||||
builder.setBufferSizeInBytes(8 * minBufferSize);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -131,11 +131,10 @@ public final class ControlMessage {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static ControlMessage createUhidCreate(int id, String name, byte[] reportDesc) {
|
||||
public static ControlMessage createUhidCreate(int id, byte[] reportDesc) {
|
||||
ControlMessage msg = new ControlMessage();
|
||||
msg.type = TYPE_UHID_CREATE;
|
||||
msg.id = id;
|
||||
msg.text = name;
|
||||
msg.data = reportDesc;
|
||||
return msg;
|
||||
}
|
||||
|
||||
@@ -75,14 +75,9 @@ public class ControlMessageReader {
|
||||
return value;
|
||||
}
|
||||
|
||||
private String parseString(int sizeBytes) throws IOException {
|
||||
assert sizeBytes > 0 && sizeBytes <= 4;
|
||||
byte[] data = parseByteArray(sizeBytes);
|
||||
return new String(data, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private String parseString() throws IOException {
|
||||
return parseString(4);
|
||||
byte[] data = parseByteArray(4);
|
||||
return new String(data, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private byte[] parseByteArray(int sizeBytes) throws IOException {
|
||||
@@ -139,9 +134,8 @@ public class ControlMessageReader {
|
||||
|
||||
private ControlMessage parseUhidCreate() throws IOException {
|
||||
int id = dis.readUnsignedShort();
|
||||
String name = parseString(1);
|
||||
byte[] data = parseByteArray(2);
|
||||
return ControlMessage.createUhidCreate(id, name, data);
|
||||
return ControlMessage.createUhidCreate(id, data);
|
||||
}
|
||||
|
||||
private ControlMessage parseUhidInput() throws IOException {
|
||||
|
||||
@@ -210,7 +210,7 @@ public class Controller implements AsyncProcessor {
|
||||
device.rotateDevice();
|
||||
break;
|
||||
case ControlMessage.TYPE_UHID_CREATE:
|
||||
getUhidManager().open(msg.getId(), msg.getText(), msg.getData());
|
||||
getUhidManager().open(msg.getId(), msg.getData());
|
||||
break;
|
||||
case ControlMessage.TYPE_UHID_INPUT:
|
||||
getUhidManager().writeInput(msg.getId(), msg.getData());
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.genymobile.scrcpy.control;
|
||||
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
import com.genymobile.scrcpy.util.StringUtils;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.HandlerThread;
|
||||
@@ -47,7 +46,7 @@ public final class UhidManager {
|
||||
}
|
||||
}
|
||||
|
||||
public void open(int id, String name, byte[] reportDesc) throws IOException {
|
||||
public void open(int id, byte[] reportDesc) throws IOException {
|
||||
try {
|
||||
FileDescriptor fd = Os.open("/dev/uhid", OsConstants.O_RDWR, 0);
|
||||
try {
|
||||
@@ -57,7 +56,7 @@ public final class UhidManager {
|
||||
close(old);
|
||||
}
|
||||
|
||||
byte[] req = buildUhidCreate2Req(name, reportDesc);
|
||||
byte[] req = buildUhidCreate2Req(reportDesc);
|
||||
Os.write(fd, req, 0, req.length);
|
||||
|
||||
registerUhidListener(id, fd);
|
||||
@@ -147,7 +146,7 @@ public final class UhidManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] buildUhidCreate2Req(String name, byte[] reportDesc) {
|
||||
private static byte[] buildUhidCreate2Req(byte[] reportDesc) {
|
||||
/*
|
||||
* struct uhid_event {
|
||||
* uint32_t type;
|
||||
@@ -172,14 +171,8 @@ public final class UhidManager {
|
||||
byte[] empty = new byte[256];
|
||||
ByteBuffer buf = ByteBuffer.allocate(280 + reportDesc.length).order(ByteOrder.nativeOrder());
|
||||
buf.putInt(UHID_CREATE2);
|
||||
|
||||
String actualName = name.isEmpty() ? "scrcpy" : "scrcpy: " + name;
|
||||
byte[] utf8Name = actualName.getBytes(StandardCharsets.UTF_8);
|
||||
int len = StringUtils.getUtf8TruncationIndex(utf8Name, 127);
|
||||
assert len <= 127;
|
||||
buf.put(utf8Name, 0, len);
|
||||
buf.put(empty, 0, 256 - len);
|
||||
|
||||
buf.put("scrcpy".getBytes(StandardCharsets.US_ASCII));
|
||||
buf.put(empty, 0, 256 - "scrcpy".length());
|
||||
buf.putShort((short) reportDesc.length);
|
||||
buf.putShort(BUS_VIRTUAL);
|
||||
buf.putInt(0); // vendor id
|
||||
|
||||
@@ -39,7 +39,7 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||
private final String encoderName;
|
||||
private final List<CodecOption> codecOptions;
|
||||
private final int videoBitRate;
|
||||
private final float maxFps;
|
||||
private final int maxFps;
|
||||
private final boolean downsizeOnError;
|
||||
|
||||
private boolean firstFrameSent;
|
||||
@@ -48,8 +48,8 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||
private Thread thread;
|
||||
private final AtomicBoolean stopped = new AtomicBoolean();
|
||||
|
||||
public SurfaceEncoder(SurfaceCapture capture, Streamer streamer, int videoBitRate, float maxFps, List<CodecOption> codecOptions,
|
||||
String encoderName, boolean downsizeOnError) {
|
||||
public SurfaceEncoder(SurfaceCapture capture, Streamer streamer, int videoBitRate, int maxFps, List<CodecOption> codecOptions, String encoderName,
|
||||
boolean downsizeOnError) {
|
||||
this.capture = capture;
|
||||
this.streamer = streamer;
|
||||
this.videoBitRate = videoBitRate;
|
||||
@@ -225,7 +225,7 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
private static MediaFormat createFormat(String videoMimeType, int bitRate, float maxFps, List<CodecOption> codecOptions) {
|
||||
private static MediaFormat createFormat(String videoMimeType, int bitRate, int maxFps, List<CodecOption> codecOptions) {
|
||||
MediaFormat format = new MediaFormat();
|
||||
format.setString(MediaFormat.KEY_MIME, videoMimeType);
|
||||
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
|
||||
|
||||
@@ -324,10 +324,8 @@ public class ControlMessageReaderTest {
|
||||
DataOutputStream dos = new DataOutputStream(bos);
|
||||
dos.writeByte(ControlMessage.TYPE_UHID_CREATE);
|
||||
dos.writeShort(42); // id
|
||||
dos.writeByte(3); // name size
|
||||
dos.write("ABC".getBytes(StandardCharsets.US_ASCII));
|
||||
byte[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
|
||||
dos.writeShort(data.length); // report desc size
|
||||
dos.writeShort(data.length); // size
|
||||
dos.write(data);
|
||||
byte[] packet = bos.toByteArray();
|
||||
|
||||
@@ -337,7 +335,6 @@ public class ControlMessageReaderTest {
|
||||
ControlMessage event = reader.read();
|
||||
Assert.assertEquals(ControlMessage.TYPE_UHID_CREATE, event.getType());
|
||||
Assert.assertEquals(42, event.getId());
|
||||
Assert.assertEquals("ABC", event.getText());
|
||||
Assert.assertArrayEquals(data, event.getData());
|
||||
|
||||
Assert.assertEquals(-1, bis.read()); // EOS
|
||||
|
||||
Reference in New Issue
Block a user