Compare commits
21 Commits
exec_path
...
gamepad_fi
Author | SHA1 | Date | |
---|---|---|---|
328bb74f80 | |||
7418fd0662 | |||
0a09518a49 | |||
27a5934a1d | |||
86a68fac6c | |||
1786f28e6f | |||
9cf4d52721 | |||
4bd1c5981d | |||
c59a3c3169 | |||
2780e0bd7b | |||
6c6607d404 | |||
988174805c | |||
f90dc216d1 | |||
97fa77c76c | |||
baa10ed0a3 | |||
2ed2247e8f | |||
5febb1e9fb | |||
5c3626ed47 | |||
0e473eb005 | |||
b26b4fb745 | |||
9555d3a537 |
@ -2,7 +2,7 @@
|
|||||||
source for the project. Do not download releases from random websites, even if
|
source for the project. Do not download releases from random websites, even if
|
||||||
their name contains `scrcpy`.**
|
their name contains `scrcpy`.**
|
||||||
|
|
||||||
# scrcpy (v3.0)
|
# scrcpy (v3.0.2)
|
||||||
|
|
||||||
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
|
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
|
||||||
|
|
||||||
|
@ -57,6 +57,7 @@ _scrcpy() {
|
|||||||
--no-mipmaps
|
--no-mipmaps
|
||||||
--no-mouse-hover
|
--no-mouse-hover
|
||||||
--no-power-on
|
--no-power-on
|
||||||
|
--no-vd-destroy-content
|
||||||
--no-vd-system-decorations
|
--no-vd-system-decorations
|
||||||
--no-video
|
--no-video
|
||||||
--no-video-playback
|
--no-video-playback
|
||||||
|
@ -63,6 +63,7 @@ arguments=(
|
|||||||
'--no-mipmaps[Disable the generation of mipmaps]'
|
'--no-mipmaps[Disable the generation of mipmaps]'
|
||||||
'--no-mouse-hover[Do not forward mouse hover events]'
|
'--no-mouse-hover[Do not forward mouse hover events]'
|
||||||
'--no-power-on[Do not power on the device on start]'
|
'--no-power-on[Do not power on the device on start]'
|
||||||
|
'--no-vd-destroy-content[Disable virtual display "destroy content on removal" flag]'
|
||||||
'--no-vd-system-decorations[Disable virtual display system decorations flag]'
|
'--no-vd-system-decorations[Disable virtual display system decorations flag]'
|
||||||
'--no-video[Disable video forwarding]'
|
'--no-video[Disable video forwarding]'
|
||||||
'--no-video-playback[Disable video playback]'
|
'--no-video-playback[Disable video playback]'
|
||||||
|
@ -13,7 +13,7 @@ BEGIN
|
|||||||
VALUE "LegalCopyright", "Romain Vimont, Genymobile"
|
VALUE "LegalCopyright", "Romain Vimont, Genymobile"
|
||||||
VALUE "OriginalFilename", "scrcpy.exe"
|
VALUE "OriginalFilename", "scrcpy.exe"
|
||||||
VALUE "ProductName", "scrcpy"
|
VALUE "ProductName", "scrcpy"
|
||||||
VALUE "ProductVersion", "3.0"
|
VALUE "ProductVersion", "3.0.2"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
12
app/scrcpy.1
12
app/scrcpy.1
@ -369,6 +369,12 @@ Do not forward mouse hover (mouse motion without any clicks) events.
|
|||||||
.B \-\-no\-power\-on
|
.B \-\-no\-power\-on
|
||||||
Do not power on the device on start.
|
Do not power on the device on start.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-\-no\-vd\-destroy\-content
|
||||||
|
Disable virtual display "destroy content on removal" flag.
|
||||||
|
|
||||||
|
With this option, when the virtual display is closed, the running apps are moved to the main display rather than being destroyed.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-\-no\-vd\-system\-decorations
|
.B \-\-no\-vd\-system\-decorations
|
||||||
Disable virtual display system decorations flag.
|
Disable virtual display system decorations flag.
|
||||||
@ -518,13 +524,15 @@ Enable "show touches" on start, restore the initial value on exit.
|
|||||||
It only shows physical touches (not clicks from scrcpy).
|
It only shows physical touches (not clicks from scrcpy).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-tcpip\fR[=\fIip\fR[:\fIport\fR]]
|
.BI "\-\-tcpip\fR[=[+]\fIip\fR[:\fIport\fR]]
|
||||||
Configure and reconnect the device over TCP/IP.
|
Configure and connect the device over TCP/IP.
|
||||||
|
|
||||||
If a destination address is provided, then scrcpy connects to this address before starting. The device must listen on the given TCP port (default is 5555).
|
If a destination address is provided, then scrcpy connects to this address before starting. The device must listen on the given TCP port (default is 5555).
|
||||||
|
|
||||||
If no destination address is provided, then scrcpy attempts to find the IP address and adb port of the current device (typically connected over USB), enables TCP/IP mode if necessary, then connects to this address before starting.
|
If no destination address is provided, then scrcpy attempts to find the IP address and adb port of the current device (typically connected over USB), enables TCP/IP mode if necessary, then connects to this address before starting.
|
||||||
|
|
||||||
|
Prefix the address with a '+' to force a reconnection.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-time\-limit " seconds
|
.BI "\-\-time\-limit " seconds
|
||||||
Set the maximum mirroring time, in seconds.
|
Set the maximum mirroring time, in seconds.
|
||||||
|
@ -412,7 +412,7 @@ sc_adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags) {
|
|||||||
|
|
||||||
// "adb connect" always returns successfully (with exit code 0), even in
|
// "adb connect" always returns successfully (with exit code 0), even in
|
||||||
// case of failure. As a workaround, check if its output starts with
|
// case of failure. As a workaround, check if its output starts with
|
||||||
// "connected".
|
// "connected" or "already connected".
|
||||||
char buf[128];
|
char buf[128];
|
||||||
ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf) - 1);
|
ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf) - 1);
|
||||||
sc_pipe_close(pout);
|
sc_pipe_close(pout);
|
||||||
@ -429,7 +429,8 @@ sc_adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags) {
|
|||||||
assert((size_t) r < sizeof(buf));
|
assert((size_t) r < sizeof(buf));
|
||||||
buf[r] = '\0';
|
buf[r] = '\0';
|
||||||
|
|
||||||
ok = !strncmp("connected", buf, sizeof("connected") - 1);
|
ok = !strncmp("connected", buf, sizeof("connected") - 1)
|
||||||
|
|| !strncmp("already connected", buf, sizeof("already connected") - 1);
|
||||||
if (!ok && !(flags & SC_ADB_NO_STDERR)) {
|
if (!ok && !(flags & SC_ADB_NO_STDERR)) {
|
||||||
// "adb connect" also prints errors to stdout. Since we capture it,
|
// "adb connect" also prints errors to stdout. Since we capture it,
|
||||||
// re-print the error to stderr.
|
// re-print the error to stderr.
|
||||||
|
@ -110,6 +110,7 @@ enum {
|
|||||||
OPT_CAPTURE_ORIENTATION,
|
OPT_CAPTURE_ORIENTATION,
|
||||||
OPT_ANGLE,
|
OPT_ANGLE,
|
||||||
OPT_NO_VD_SYSTEM_DECORATIONS,
|
OPT_NO_VD_SYSTEM_DECORATIONS,
|
||||||
|
OPT_NO_VD_DESTROY_CONTENT,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_option {
|
struct sc_option {
|
||||||
@ -659,6 +660,15 @@ static const struct sc_option options[] = {
|
|||||||
.longopt = "no-power-on",
|
.longopt = "no-power-on",
|
||||||
.text = "Do not power on the device on start.",
|
.text = "Do not power on the device on start.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.longopt_id = OPT_NO_VD_DESTROY_CONTENT,
|
||||||
|
.longopt = "no-vd-destroy-content",
|
||||||
|
.text = "Disable virtual display \"destroy content on removal\" "
|
||||||
|
"flag.\n"
|
||||||
|
"With this option, when the virtual display is closed, the "
|
||||||
|
"running apps are moved to the main display rather than being "
|
||||||
|
"destroyed.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_NO_VD_SYSTEM_DECORATIONS,
|
.longopt_id = OPT_NO_VD_SYSTEM_DECORATIONS,
|
||||||
.longopt = "no-vd-system-decorations",
|
.longopt = "no-vd-system-decorations",
|
||||||
@ -860,16 +870,17 @@ static const struct sc_option options[] = {
|
|||||||
{
|
{
|
||||||
.longopt_id = OPT_TCPIP,
|
.longopt_id = OPT_TCPIP,
|
||||||
.longopt = "tcpip",
|
.longopt = "tcpip",
|
||||||
.argdesc = "ip[:port]",
|
.argdesc = "[+]ip[:port]",
|
||||||
.optional_arg = true,
|
.optional_arg = true,
|
||||||
.text = "Configure and reconnect the device over TCP/IP.\n"
|
.text = "Configure and connect the device over TCP/IP.\n"
|
||||||
"If a destination address is provided, then scrcpy connects to "
|
"If a destination address is provided, then scrcpy connects to "
|
||||||
"this address before starting. The device must listen on the "
|
"this address before starting. The device must listen on the "
|
||||||
"given TCP port (default is 5555).\n"
|
"given TCP port (default is 5555).\n"
|
||||||
"If no destination address is provided, then scrcpy attempts "
|
"If no destination address is provided, then scrcpy attempts "
|
||||||
"to find the IP address of the current device (typically "
|
"to find the IP address of the current device (typically "
|
||||||
"connected over USB), enables TCP/IP mode, then connects to "
|
"connected over USB), enables TCP/IP mode, then connects to "
|
||||||
"this address before starting.",
|
"this address before starting.\n"
|
||||||
|
"Prefix the address with a '+' to force a reconnection.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_TIME_LIMIT,
|
.longopt_id = OPT_TIME_LIMIT,
|
||||||
@ -2704,8 +2715,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
case OPT_ANGLE:
|
case OPT_ANGLE:
|
||||||
opts->angle = optarg;
|
opts->angle = optarg;
|
||||||
break;
|
break;
|
||||||
|
case OPT_NO_VD_DESTROY_CONTENT:
|
||||||
|
opts->vd_destroy_content = false;
|
||||||
|
break;
|
||||||
case OPT_NO_VD_SYSTEM_DECORATIONS:
|
case OPT_NO_VD_SYSTEM_DECORATIONS:
|
||||||
opts->vd_system_decorations = optarg;
|
opts->vd_system_decorations = false;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
|
@ -152,8 +152,10 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
|
|||||||
return 2;
|
return 2;
|
||||||
case SC_CONTROL_MSG_TYPE_UHID_CREATE:
|
case SC_CONTROL_MSG_TYPE_UHID_CREATE:
|
||||||
sc_write16be(&buf[1], msg->uhid_create.id);
|
sc_write16be(&buf[1], msg->uhid_create.id);
|
||||||
|
sc_write16be(&buf[3], msg->uhid_create.vendor_id);
|
||||||
|
sc_write16be(&buf[5], msg->uhid_create.product_id);
|
||||||
|
|
||||||
size_t index = 3;
|
size_t index = 7;
|
||||||
index += write_string_tiny(&buf[index], msg->uhid_create.name, 127);
|
index += write_string_tiny(&buf[index], msg->uhid_create.name, 127);
|
||||||
|
|
||||||
sc_write16be(&buf[index], msg->uhid_create.report_desc_size);
|
sc_write16be(&buf[index], msg->uhid_create.report_desc_size);
|
||||||
@ -278,9 +280,13 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
|||||||
// Quote only if name is not null
|
// Quote only if name is not null
|
||||||
const char *name = msg->uhid_create.name;
|
const char *name = msg->uhid_create.name;
|
||||||
const char *quote = name ? "\"" : "";
|
const char *quote = name ? "\"" : "";
|
||||||
LOG_CMSG("UHID create [%" PRIu16 "] name=%s%s%s "
|
LOG_CMSG("UHID create [%" PRIu16 "] %04" PRIx16 ":%04" PRIx16
|
||||||
"report_desc_size=%" PRIu16, msg->uhid_create.id,
|
" name=%s%s%s report_desc_size=%" PRIu16,
|
||||||
quote, name, quote, msg->uhid_create.report_desc_size);
|
msg->uhid_create.id,
|
||||||
|
msg->uhid_create.vendor_id,
|
||||||
|
msg->uhid_create.product_id,
|
||||||
|
quote, name, quote,
|
||||||
|
msg->uhid_create.report_desc_size);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SC_CONTROL_MSG_TYPE_UHID_INPUT: {
|
case SC_CONTROL_MSG_TYPE_UHID_INPUT: {
|
||||||
|
@ -94,6 +94,8 @@ struct sc_control_msg {
|
|||||||
} set_display_power;
|
} set_display_power;
|
||||||
struct {
|
struct {
|
||||||
uint16_t id;
|
uint16_t id;
|
||||||
|
uint16_t vendor_id;
|
||||||
|
uint16_t product_id;
|
||||||
const char *name; // pointer to static data
|
const char *name; // pointer to static data
|
||||||
uint16_t report_desc_size;
|
uint16_t report_desc_size;
|
||||||
const uint8_t *report_desc; // pointer to static data
|
const uint8_t *report_desc; // pointer to static data
|
||||||
|
@ -15,7 +15,6 @@ struct sc_hid_input {
|
|||||||
|
|
||||||
struct sc_hid_open {
|
struct sc_hid_open {
|
||||||
uint16_t hid_id;
|
uint16_t hid_id;
|
||||||
const char *name; // pointer to static memory
|
|
||||||
const uint8_t *report_desc; // pointer to static memory
|
const uint8_t *report_desc; // pointer to static memory
|
||||||
size_t report_desc_size;
|
size_t report_desc_size;
|
||||||
};
|
};
|
||||||
|
@ -52,10 +52,10 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
|
|||||||
0x09, 0x30,
|
0x09, 0x30,
|
||||||
// Usage (Y) Left stick y
|
// Usage (Y) Left stick y
|
||||||
0x09, 0x31,
|
0x09, 0x31,
|
||||||
// Usage (Z) Right stick x
|
// Usage (Rx) Right stick x
|
||||||
0x09, 0x32,
|
0x09, 0x33,
|
||||||
// Usage (Rz) Right stick y
|
// Usage (Ry) Right stick y
|
||||||
0x09, 0x35,
|
0x09, 0x34,
|
||||||
// Logical Minimum (0)
|
// Logical Minimum (0)
|
||||||
0x15, 0x00,
|
0x15, 0x00,
|
||||||
// Logical Maximum (65535)
|
// Logical Maximum (65535)
|
||||||
@ -65,15 +65,15 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
|
|||||||
0x75, 0x10,
|
0x75, 0x10,
|
||||||
// Report Count (4)
|
// Report Count (4)
|
||||||
0x95, 0x04,
|
0x95, 0x04,
|
||||||
// Input (Data, Variable, Absolute): 4 bytes (X, Y, Z, Rz)
|
// Input (Data, Variable, Absolute): 4x2 bytes (X, Y, Z, Rz)
|
||||||
0x81, 0x02,
|
0x81, 0x02,
|
||||||
|
|
||||||
// Usage Page (Simulation Controls)
|
// Usage Page (Generic Desktop)
|
||||||
0x05, 0x02,
|
0x05, 0x01,
|
||||||
// Usage (Brake)
|
// Usage (Z)
|
||||||
0x09, 0xC5,
|
0x09, 0x32,
|
||||||
// Usage (Accelerator)
|
// Usage (Rz)
|
||||||
0x09, 0xC4,
|
0x09, 0x35,
|
||||||
// Logical Minimum (0)
|
// Logical Minimum (0)
|
||||||
0x15, 0x00,
|
0x15, 0x00,
|
||||||
// Logical Maximum (32767)
|
// Logical Maximum (32767)
|
||||||
@ -82,7 +82,7 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
|
|||||||
0x75, 0x10,
|
0x75, 0x10,
|
||||||
// Report Count (2)
|
// Report Count (2)
|
||||||
0x95, 0x02,
|
0x95, 0x02,
|
||||||
// Input (Data, Variable, Absolute): 2 bytes (L2, R2)
|
// Input (Data, Variable, Absolute): 2x2 bytes (L2, R2)
|
||||||
0x81, 0x02,
|
0x81, 0x02,
|
||||||
|
|
||||||
// Usage Page (Buttons)
|
// Usage Page (Buttons)
|
||||||
@ -182,7 +182,7 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
|
|||||||
* `------------- SC_GAMEPAD_BUTTON_RIGHT_STICK
|
* `------------- SC_GAMEPAD_BUTTON_RIGHT_STICK
|
||||||
*
|
*
|
||||||
* +---------------+
|
* +---------------+
|
||||||
* byte 14: |0 0 0 . . . . .| hat switch (dpad) position (0-8)
|
* byte 14: |0 0 0 0 . . . .| hat switch (dpad) position (0-8)
|
||||||
* +---------------+
|
* +---------------+
|
||||||
* 9 possible positions and their values:
|
* 9 possible positions and their values:
|
||||||
* 8 1 2
|
* 8 1 2
|
||||||
@ -191,16 +191,19 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
|
|||||||
* (8 is top-left, 1 is top, 2 is top-right, etc.)
|
* (8 is top-left, 1 is top, 2 is top-right, etc.)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// [-32768 to 32767] -> [0 to 65535]
|
||||||
|
#define AXIS_RESCALE(V) (uint16_t) (((int32_t) V) + 0x8000)
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_hid_gamepad_slot_init(struct sc_hid_gamepad_slot *slot,
|
sc_hid_gamepad_slot_init(struct sc_hid_gamepad_slot *slot,
|
||||||
uint32_t gamepad_id) {
|
uint32_t gamepad_id) {
|
||||||
assert(gamepad_id != SC_GAMEPAD_ID_INVALID);
|
assert(gamepad_id != SC_GAMEPAD_ID_INVALID);
|
||||||
slot->gamepad_id = gamepad_id;
|
slot->gamepad_id = gamepad_id;
|
||||||
slot->buttons = 0;
|
slot->buttons = 0;
|
||||||
slot->axis_left_x = 0;
|
slot->axis_left_x = AXIS_RESCALE(0);
|
||||||
slot->axis_left_y = 0;
|
slot->axis_left_y = AXIS_RESCALE(0);
|
||||||
slot->axis_right_x = 0;
|
slot->axis_right_x = AXIS_RESCALE(0);
|
||||||
slot->axis_right_y = 0;
|
slot->axis_right_y = AXIS_RESCALE(0);
|
||||||
slot->axis_left_trigger = 0;
|
slot->axis_left_trigger = 0;
|
||||||
slot->axis_right_trigger = 0;
|
slot->axis_right_trigger = 0;
|
||||||
}
|
}
|
||||||
@ -243,14 +246,8 @@ sc_hid_gamepad_generate_open(struct sc_hid_gamepad *hid,
|
|||||||
|
|
||||||
sc_hid_gamepad_slot_init(&hid->slots[slot_idx], gamepad_id);
|
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);
|
uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx);
|
||||||
hid_open->hid_id = hid_id;
|
hid_open->hid_id = hid_id;
|
||||||
hid_open->name = name;
|
|
||||||
hid_open->report_desc = SC_HID_GAMEPAD_REPORT_DESC;
|
hid_open->report_desc = SC_HID_GAMEPAD_REPORT_DESC;
|
||||||
hid_open->report_desc_size = sizeof(SC_HID_GAMEPAD_REPORT_DESC);
|
hid_open->report_desc_size = sizeof(SC_HID_GAMEPAD_REPORT_DESC);
|
||||||
|
|
||||||
@ -423,8 +420,6 @@ sc_hid_gamepad_generate_input_from_axis(struct sc_hid_gamepad *hid,
|
|||||||
|
|
||||||
struct sc_hid_gamepad_slot *slot = &hid->slots[slot_idx];
|
struct sc_hid_gamepad_slot *slot = &hid->slots[slot_idx];
|
||||||
|
|
||||||
// [-32768 to 32767] -> [0 to 65535]
|
|
||||||
#define AXIS_RESCALE(V) (uint16_t) (((int32_t) V) + 0x8000)
|
|
||||||
switch (event->axis) {
|
switch (event->axis) {
|
||||||
case SC_GAMEPAD_AXIS_LEFTX:
|
case SC_GAMEPAD_AXIS_LEFTX:
|
||||||
slot->axis_left_x = AXIS_RESCALE(event->value);
|
slot->axis_left_x = AXIS_RESCALE(event->value);
|
||||||
|
@ -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) {
|
void sc_hid_keyboard_generate_open(struct sc_hid_open *hid_open) {
|
||||||
hid_open->hid_id = SC_HID_ID_KEYBOARD;
|
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 = SC_HID_KEYBOARD_REPORT_DESC;
|
||||||
hid_open->report_desc_size = sizeof(SC_HID_KEYBOARD_REPORT_DESC);
|
hid_open->report_desc_size = sizeof(SC_HID_KEYBOARD_REPORT_DESC);
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
void sc_hid_mouse_generate_open(struct sc_hid_open *hid_open) {
|
||||||
hid_open->hid_id = SC_HID_ID_MOUSE;
|
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 = SC_HID_MOUSE_REPORT_DESC;
|
||||||
hid_open->report_desc_size = sizeof(SC_HID_MOUSE_REPORT_DESC);
|
hid_open->report_desc_size = sizeof(SC_HID_MOUSE_REPORT_DESC);
|
||||||
}
|
}
|
||||||
|
@ -412,18 +412,12 @@ 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>:
|
// 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
|
// The ID value starts at 0 and increments from there. The value -1 is an
|
||||||
// invalid ID.
|
// invalid ID.
|
||||||
#define SC_GAMEPAD_ID_INVALID UINT32_C(-1)
|
#define SC_GAMEPAD_ID_INVALID UINT32_C(-1)
|
||||||
|
|
||||||
struct sc_gamepad_device_event {
|
struct sc_gamepad_device_event {
|
||||||
enum sc_gamepad_device_event_type type;
|
|
||||||
uint32_t gamepad_id;
|
uint32_t gamepad_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -503,16 +497,6 @@ 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
|
static inline enum sc_gamepad_axis
|
||||||
sc_gamepad_axis_from_sdl(uint8_t axis) {
|
sc_gamepad_axis_from_sdl(uint8_t axis) {
|
||||||
if (axis <= SDL_CONTROLLER_AXIS_TRIGGERRIGHT) {
|
if (axis <= SDL_CONTROLLER_AXIS_TRIGGERRIGHT) {
|
||||||
|
@ -908,7 +908,6 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
|
|||||||
static void
|
static void
|
||||||
sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
|
sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
|
||||||
const SDL_ControllerDeviceEvent *event) {
|
const SDL_ControllerDeviceEvent *event) {
|
||||||
SDL_JoystickID id;
|
|
||||||
if (event->type == SDL_CONTROLLERDEVICEADDED) {
|
if (event->type == SDL_CONTROLLERDEVICEADDED) {
|
||||||
SDL_GameController *gc = SDL_GameControllerOpen(event->which);
|
SDL_GameController *gc = SDL_GameControllerOpen(event->which);
|
||||||
if (!gc) {
|
if (!gc) {
|
||||||
@ -923,9 +922,12 @@ sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
id = SDL_JoystickInstanceID(joystick);
|
struct sc_gamepad_device_event evt = {
|
||||||
|
.gamepad_id = SDL_JoystickInstanceID(joystick),
|
||||||
|
};
|
||||||
|
im->gp->ops->process_gamepad_added(im->gp, &evt);
|
||||||
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
|
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||||
id = event->which;
|
SDL_JoystickID id = event->which;
|
||||||
|
|
||||||
SDL_GameController *gc = SDL_GameControllerFromInstanceID(id);
|
SDL_GameController *gc = SDL_GameControllerFromInstanceID(id);
|
||||||
if (gc) {
|
if (gc) {
|
||||||
@ -933,16 +935,15 @@ sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
|
|||||||
} else {
|
} else {
|
||||||
LOGW("Unknown gamepad device removed");
|
LOGW("Unknown gamepad device removed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct sc_gamepad_device_event evt = {
|
||||||
|
.gamepad_id = id,
|
||||||
|
};
|
||||||
|
im->gp->ops->process_gamepad_removed(im->gp, &evt);
|
||||||
} else {
|
} else {
|
||||||
// Nothing to do
|
// Nothing to do
|
||||||
return;
|
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
|
static void
|
||||||
|
@ -108,6 +108,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||||||
.new_display = NULL,
|
.new_display = NULL,
|
||||||
.start_app = NULL,
|
.start_app = NULL,
|
||||||
.angle = NULL,
|
.angle = NULL,
|
||||||
|
.vd_destroy_content = true,
|
||||||
.vd_system_decorations = true,
|
.vd_system_decorations = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -310,6 +310,7 @@ struct scrcpy_options {
|
|||||||
bool audio_dup;
|
bool audio_dup;
|
||||||
const char *new_display; // [<width>x<height>][/<dpi>] parsed by the server
|
const char *new_display; // [<width>x<height>][/<dpi>] parsed by the server
|
||||||
const char *start_app;
|
const char *start_app;
|
||||||
|
bool vd_destroy_content;
|
||||||
bool vd_system_decorations;
|
bool vd_system_decorations;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -458,6 +458,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
.power_on = options->power_on,
|
.power_on = options->power_on,
|
||||||
.kill_adb_on_close = options->kill_adb_on_close,
|
.kill_adb_on_close = options->kill_adb_on_close,
|
||||||
.camera_high_speed = options->camera_high_speed,
|
.camera_high_speed = options->camera_high_speed,
|
||||||
|
.vd_destroy_content = options->vd_destroy_content,
|
||||||
.vd_system_decorations = options->vd_system_decorations,
|
.vd_system_decorations = options->vd_system_decorations,
|
||||||
.list = options->list,
|
.list = options->list,
|
||||||
};
|
};
|
||||||
|
@ -377,6 +377,9 @@ execute_server(struct sc_server *server,
|
|||||||
VALIDATE_STRING(params->new_display);
|
VALIDATE_STRING(params->new_display);
|
||||||
ADD_PARAM("new_display=%s", params->new_display);
|
ADD_PARAM("new_display=%s", params->new_display);
|
||||||
}
|
}
|
||||||
|
if (!params->vd_destroy_content) {
|
||||||
|
ADD_PARAM("vd_destroy_content=false");
|
||||||
|
}
|
||||||
if (!params->vd_system_decorations) {
|
if (!params->vd_system_decorations) {
|
||||||
ADD_PARAM("vd_system_decorations=false");
|
ADD_PARAM("vd_system_decorations=false");
|
||||||
}
|
}
|
||||||
@ -829,11 +832,14 @@ sc_server_switch_to_tcpip(struct sc_server *server, const char *serial) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_server_connect_to_tcpip(struct sc_server *server, const char *ip_port) {
|
sc_server_connect_to_tcpip(struct sc_server *server, const char *ip_port,
|
||||||
|
bool disconnect) {
|
||||||
struct sc_intr *intr = &server->intr;
|
struct sc_intr *intr = &server->intr;
|
||||||
|
|
||||||
|
if (disconnect) {
|
||||||
// Error expected if not connected, do not report any error
|
// Error expected if not connected, do not report any error
|
||||||
sc_adb_disconnect(intr, ip_port, SC_ADB_SILENT);
|
sc_adb_disconnect(intr, ip_port, SC_ADB_SILENT);
|
||||||
|
}
|
||||||
|
|
||||||
LOGI("Connecting to %s...", ip_port);
|
LOGI("Connecting to %s...", ip_port);
|
||||||
|
|
||||||
@ -849,7 +855,7 @@ sc_server_connect_to_tcpip(struct sc_server *server, const char *ip_port) {
|
|||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_server_configure_tcpip_known_address(struct sc_server *server,
|
sc_server_configure_tcpip_known_address(struct sc_server *server,
|
||||||
const char *addr) {
|
const char *addr, bool disconnect) {
|
||||||
// Append ":5555" if no port is present
|
// Append ":5555" if no port is present
|
||||||
bool contains_port = strchr(addr, ':');
|
bool contains_port = strchr(addr, ':');
|
||||||
char *ip_port = contains_port ? strdup(addr)
|
char *ip_port = contains_port ? strdup(addr)
|
||||||
@ -860,7 +866,7 @@ sc_server_configure_tcpip_known_address(struct sc_server *server,
|
|||||||
}
|
}
|
||||||
|
|
||||||
server->serial = ip_port;
|
server->serial = ip_port;
|
||||||
return sc_server_connect_to_tcpip(server, ip_port);
|
return sc_server_connect_to_tcpip(server, ip_port, disconnect);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
@ -885,7 +891,7 @@ sc_server_configure_tcpip_unknown_address(struct sc_server *server,
|
|||||||
}
|
}
|
||||||
|
|
||||||
server->serial = ip_port;
|
server->serial = ip_port;
|
||||||
return sc_server_connect_to_tcpip(server, ip_port);
|
return sc_server_connect_to_tcpip(server, ip_port, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -972,7 +978,13 @@ run_server(void *data) {
|
|||||||
sc_adb_device_destroy(&device);
|
sc_adb_device_destroy(&device);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ok = sc_server_configure_tcpip_known_address(server, params->tcpip_dst);
|
// If the user passed a '+' (--tcpip=+ip), then disconnect first
|
||||||
|
const char *tcpip_dst = params->tcpip_dst;
|
||||||
|
bool plus = tcpip_dst[0] == '+';
|
||||||
|
if (plus) {
|
||||||
|
++tcpip_dst;
|
||||||
|
}
|
||||||
|
ok = sc_server_configure_tcpip_known_address(server, tcpip_dst, plus);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
goto error_connection_failed;
|
goto error_connection_failed;
|
||||||
}
|
}
|
||||||
|
@ -69,6 +69,7 @@ struct sc_server_params {
|
|||||||
bool power_on;
|
bool power_on;
|
||||||
bool kill_adb_on_close;
|
bool kill_adb_on_close;
|
||||||
bool camera_high_speed;
|
bool camera_high_speed;
|
||||||
|
bool vd_destroy_content;
|
||||||
bool vd_system_decorations;
|
bool vd_system_decorations;
|
||||||
uint8_t list;
|
uint8_t list;
|
||||||
};
|
};
|
||||||
|
@ -20,12 +20,21 @@ struct sc_gamepad_processor {
|
|||||||
struct sc_gamepad_processor_ops {
|
struct sc_gamepad_processor_ops {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process a gamepad device added or removed
|
* Process a gamepad device added event
|
||||||
*
|
*
|
||||||
* This function is mandatory.
|
* This function is mandatory.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
(*process_gamepad_device)(struct sc_gamepad_processor *gp,
|
(*process_gamepad_added)(struct sc_gamepad_processor *gp,
|
||||||
|
const struct sc_gamepad_device_event *event);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a gamepad device removed event
|
||||||
|
*
|
||||||
|
* This function is mandatory.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
(*process_gamepad_removed)(struct sc_gamepad_processor *gp,
|
||||||
const struct sc_gamepad_device_event *event);
|
const struct sc_gamepad_device_event *event);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -7,6 +7,11 @@
|
|||||||
/** Downcast gamepad processor to sc_gamepad_uhid */
|
/** Downcast gamepad processor to sc_gamepad_uhid */
|
||||||
#define DOWNCAST(GP) container_of(GP, struct sc_gamepad_uhid, gamepad_processor)
|
#define DOWNCAST(GP) container_of(GP, struct sc_gamepad_uhid, gamepad_processor)
|
||||||
|
|
||||||
|
// Xbox 360
|
||||||
|
#define SC_GAMEPAD_UHID_VENDOR_ID UINT16_C(0x045e)
|
||||||
|
#define SC_GAMEPAD_UHID_PRODUCT_ID UINT16_C(0x028e)
|
||||||
|
#define SC_GAMEPAD_UHID_NAME "Microsoft X-Box 360 Pad"
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_gamepad_uhid_send_input(struct sc_gamepad_uhid *gamepad,
|
sc_gamepad_uhid_send_input(struct sc_gamepad_uhid *gamepad,
|
||||||
const struct sc_hid_input *hid_input,
|
const struct sc_hid_input *hid_input,
|
||||||
@ -30,7 +35,9 @@ sc_gamepad_uhid_send_open(struct sc_gamepad_uhid *gamepad,
|
|||||||
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 = hid_open->hid_id;
|
msg.uhid_create.id = hid_open->hid_id;
|
||||||
msg.uhid_create.name = hid_open->name;
|
msg.uhid_create.vendor_id = SC_GAMEPAD_UHID_VENDOR_ID;
|
||||||
|
msg.uhid_create.product_id = SC_GAMEPAD_UHID_PRODUCT_ID;
|
||||||
|
msg.uhid_create.name = SC_GAMEPAD_UHID_NAME;
|
||||||
msg.uhid_create.report_desc = hid_open->report_desc;
|
msg.uhid_create.report_desc = hid_open->report_desc;
|
||||||
msg.uhid_create.report_desc_size = hid_open->report_desc_size;
|
msg.uhid_create.report_desc_size = hid_open->report_desc_size;
|
||||||
|
|
||||||
@ -52,20 +59,29 @@ sc_gamepad_uhid_send_close(struct sc_gamepad_uhid *gamepad,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_gamepad_processor_process_gamepad_device(struct sc_gamepad_processor *gp,
|
sc_gamepad_processor_process_gamepad_added(struct sc_gamepad_processor *gp,
|
||||||
const struct sc_gamepad_device_event *event) {
|
const struct sc_gamepad_device_event *event) {
|
||||||
struct sc_gamepad_uhid *gamepad = DOWNCAST(gp);
|
struct sc_gamepad_uhid *gamepad = DOWNCAST(gp);
|
||||||
|
|
||||||
if (event->type == SC_GAMEPAD_DEVICE_ADDED) {
|
|
||||||
struct sc_hid_open hid_open;
|
struct sc_hid_open hid_open;
|
||||||
if (!sc_hid_gamepad_generate_open(&gamepad->hid, &hid_open,
|
if (!sc_hid_gamepad_generate_open(&gamepad->hid, &hid_open,
|
||||||
event->gamepad_id)) {
|
event->gamepad_id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SDL_GameController* game_controller =
|
||||||
|
SDL_GameControllerFromInstanceID(event->gamepad_id);
|
||||||
|
assert(game_controller);
|
||||||
|
const char *name = SDL_GameControllerName(game_controller);
|
||||||
|
LOGI("Gamepad added: [%" PRIu32 "] %s", event->gamepad_id, name);
|
||||||
|
|
||||||
sc_gamepad_uhid_send_open(gamepad, &hid_open);
|
sc_gamepad_uhid_send_open(gamepad, &hid_open);
|
||||||
} else {
|
}
|
||||||
assert(event->type == SC_GAMEPAD_DEVICE_REMOVED);
|
|
||||||
|
static void
|
||||||
|
sc_gamepad_processor_process_gamepad_removed(struct sc_gamepad_processor *gp,
|
||||||
|
const struct sc_gamepad_device_event *event) {
|
||||||
|
struct sc_gamepad_uhid *gamepad = DOWNCAST(gp);
|
||||||
|
|
||||||
struct sc_hid_close hid_close;
|
struct sc_hid_close hid_close;
|
||||||
if (!sc_hid_gamepad_generate_close(&gamepad->hid, &hid_close,
|
if (!sc_hid_gamepad_generate_close(&gamepad->hid, &hid_close,
|
||||||
@ -73,9 +89,10 @@ sc_gamepad_processor_process_gamepad_device(struct sc_gamepad_processor *gp,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOGI("Gamepad removed: [%" PRIu32 "]", event->gamepad_id);
|
||||||
|
|
||||||
sc_gamepad_uhid_send_close(gamepad, &hid_close);
|
sc_gamepad_uhid_send_close(gamepad, &hid_close);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_gamepad_processor_process_gamepad_axis(struct sc_gamepad_processor *gp,
|
sc_gamepad_processor_process_gamepad_axis(struct sc_gamepad_processor *gp,
|
||||||
@ -114,7 +131,8 @@ sc_gamepad_uhid_init(struct sc_gamepad_uhid *gamepad,
|
|||||||
gamepad->controller = controller;
|
gamepad->controller = controller;
|
||||||
|
|
||||||
static const struct sc_gamepad_processor_ops ops = {
|
static const struct sc_gamepad_processor_ops ops = {
|
||||||
.process_gamepad_device = sc_gamepad_processor_process_gamepad_device,
|
.process_gamepad_added = sc_gamepad_processor_process_gamepad_added,
|
||||||
|
.process_gamepad_removed = sc_gamepad_processor_process_gamepad_removed,
|
||||||
.process_gamepad_axis = sc_gamepad_processor_process_gamepad_axis,
|
.process_gamepad_axis = sc_gamepad_processor_process_gamepad_axis,
|
||||||
.process_gamepad_button = sc_gamepad_processor_process_gamepad_button,
|
.process_gamepad_button = sc_gamepad_processor_process_gamepad_button,
|
||||||
};
|
};
|
||||||
|
@ -141,7 +141,9 @@ sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
|
|||||||
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 = SC_HID_ID_KEYBOARD;
|
msg.uhid_create.id = SC_HID_ID_KEYBOARD;
|
||||||
msg.uhid_create.name = hid_open.name;
|
msg.uhid_create.vendor_id = 0;
|
||||||
|
msg.uhid_create.product_id = 0;
|
||||||
|
msg.uhid_create.name = NULL;
|
||||||
msg.uhid_create.report_desc = hid_open.report_desc;
|
msg.uhid_create.report_desc = hid_open.report_desc;
|
||||||
msg.uhid_create.report_desc_size = hid_open.report_desc_size;
|
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)) {
|
||||||
|
@ -81,7 +81,9 @@ sc_mouse_uhid_init(struct sc_mouse_uhid *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 = SC_HID_ID_MOUSE;
|
msg.uhid_create.id = SC_HID_ID_MOUSE;
|
||||||
msg.uhid_create.name = hid_open.name;
|
msg.uhid_create.vendor_id = 0;
|
||||||
|
msg.uhid_create.product_id = 0;
|
||||||
|
msg.uhid_create.name = NULL;
|
||||||
msg.uhid_create.report_desc = hid_open.report_desc;
|
msg.uhid_create.report_desc = hid_open.report_desc;
|
||||||
msg.uhid_create.report_desc_size = hid_open.report_desc_size;
|
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)) {
|
||||||
|
@ -7,11 +7,10 @@
|
|||||||
#define DOWNCAST(GP) container_of(GP, struct sc_gamepad_aoa, gamepad_processor)
|
#define DOWNCAST(GP) container_of(GP, struct sc_gamepad_aoa, gamepad_processor)
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_gamepad_processor_process_gamepad_device(struct sc_gamepad_processor *gp,
|
sc_gamepad_processor_process_gamepad_added(struct sc_gamepad_processor *gp,
|
||||||
const struct sc_gamepad_device_event *event) {
|
const struct sc_gamepad_device_event *event) {
|
||||||
struct sc_gamepad_aoa *gamepad = DOWNCAST(gp);
|
struct sc_gamepad_aoa *gamepad = DOWNCAST(gp);
|
||||||
|
|
||||||
if (event->type == SC_GAMEPAD_DEVICE_ADDED) {
|
|
||||||
struct sc_hid_open hid_open;
|
struct sc_hid_open hid_open;
|
||||||
if (!sc_hid_gamepad_generate_open(&gamepad->hid, &hid_open,
|
if (!sc_hid_gamepad_generate_open(&gamepad->hid, &hid_open,
|
||||||
event->gamepad_id)) {
|
event->gamepad_id)) {
|
||||||
@ -22,8 +21,12 @@ sc_gamepad_processor_process_gamepad_device(struct sc_gamepad_processor *gp,
|
|||||||
if (!sc_aoa_push_open(gamepad->aoa, &hid_open, false)) {
|
if (!sc_aoa_push_open(gamepad->aoa, &hid_open, false)) {
|
||||||
LOGW("Could not push AOA HID open (gamepad)");
|
LOGW("Could not push AOA HID open (gamepad)");
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
assert(event->type == SC_GAMEPAD_DEVICE_REMOVED);
|
|
||||||
|
static void
|
||||||
|
sc_gamepad_processor_process_gamepad_removed(struct sc_gamepad_processor *gp,
|
||||||
|
const struct sc_gamepad_device_event *event) {
|
||||||
|
struct sc_gamepad_aoa *gamepad = DOWNCAST(gp);
|
||||||
|
|
||||||
struct sc_hid_close hid_close;
|
struct sc_hid_close hid_close;
|
||||||
if (!sc_hid_gamepad_generate_close(&gamepad->hid, &hid_close,
|
if (!sc_hid_gamepad_generate_close(&gamepad->hid, &hid_close,
|
||||||
@ -35,7 +38,6 @@ sc_gamepad_processor_process_gamepad_device(struct sc_gamepad_processor *gp,
|
|||||||
LOGW("Could not push AOA HID close (gamepad)");
|
LOGW("Could not push AOA HID close (gamepad)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_gamepad_processor_process_gamepad_axis(struct sc_gamepad_processor *gp,
|
sc_gamepad_processor_process_gamepad_axis(struct sc_gamepad_processor *gp,
|
||||||
@ -76,7 +78,8 @@ sc_gamepad_aoa_init(struct sc_gamepad_aoa *gamepad, struct sc_aoa *aoa) {
|
|||||||
sc_hid_gamepad_init(&gamepad->hid);
|
sc_hid_gamepad_init(&gamepad->hid);
|
||||||
|
|
||||||
static const struct sc_gamepad_processor_ops ops = {
|
static const struct sc_gamepad_processor_ops ops = {
|
||||||
.process_gamepad_device = sc_gamepad_processor_process_gamepad_device,
|
.process_gamepad_added = sc_gamepad_processor_process_gamepad_added,
|
||||||
|
.process_gamepad_removed = sc_gamepad_processor_process_gamepad_removed,
|
||||||
.process_gamepad_axis = sc_gamepad_processor_process_gamepad_axis,
|
.process_gamepad_axis = sc_gamepad_processor_process_gamepad_axis,
|
||||||
.process_gamepad_button = sc_gamepad_processor_process_gamepad_button,
|
.process_gamepad_button = sc_gamepad_processor_process_gamepad_button,
|
||||||
};
|
};
|
||||||
|
@ -175,7 +175,6 @@ sc_screen_otg_process_gamepad_device(struct sc_screen_otg *screen,
|
|||||||
assert(screen->gamepad);
|
assert(screen->gamepad);
|
||||||
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
|
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
|
||||||
|
|
||||||
SDL_JoystickID id;
|
|
||||||
if (event->type == SDL_CONTROLLERDEVICEADDED) {
|
if (event->type == SDL_CONTROLLERDEVICEADDED) {
|
||||||
SDL_GameController *gc = SDL_GameControllerOpen(event->which);
|
SDL_GameController *gc = SDL_GameControllerOpen(event->which);
|
||||||
if (!gc) {
|
if (!gc) {
|
||||||
@ -190,9 +189,12 @@ sc_screen_otg_process_gamepad_device(struct sc_screen_otg *screen,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
id = SDL_JoystickInstanceID(joystick);
|
struct sc_gamepad_device_event evt = {
|
||||||
|
.gamepad_id = SDL_JoystickInstanceID(joystick),
|
||||||
|
};
|
||||||
|
gp->ops->process_gamepad_added(gp, &evt);
|
||||||
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
|
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||||
id = event->which;
|
SDL_JoystickID id = event->which;
|
||||||
|
|
||||||
SDL_GameController *gc = SDL_GameControllerFromInstanceID(id);
|
SDL_GameController *gc = SDL_GameControllerFromInstanceID(id);
|
||||||
if (gc) {
|
if (gc) {
|
||||||
@ -200,16 +202,12 @@ sc_screen_otg_process_gamepad_device(struct sc_screen_otg *screen,
|
|||||||
} else {
|
} else {
|
||||||
LOGW("Unknown gamepad device removed");
|
LOGW("Unknown gamepad device removed");
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Nothing to do
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct sc_gamepad_device_event evt = {
|
struct sc_gamepad_device_event evt = {
|
||||||
.type = sc_gamepad_device_event_type_from_sdl_type(event->type),
|
|
||||||
.gamepad_id = id,
|
.gamepad_id = id,
|
||||||
};
|
};
|
||||||
gp->ops->process_gamepad_device(gp, &evt);
|
gp->ops->process_gamepad_removed(gp, &evt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -329,6 +329,8 @@ static void test_serialize_uhid_create(void) {
|
|||||||
.type = SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
.type = SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
||||||
.uhid_create = {
|
.uhid_create = {
|
||||||
.id = 42,
|
.id = 42,
|
||||||
|
.vendor_id = 0x1234,
|
||||||
|
.product_id = 0x5678,
|
||||||
.name = "ABC",
|
.name = "ABC",
|
||||||
.report_desc_size = sizeof(report_desc),
|
.report_desc_size = sizeof(report_desc),
|
||||||
.report_desc = report_desc,
|
.report_desc = report_desc,
|
||||||
@ -337,11 +339,13 @@ static void test_serialize_uhid_create(void) {
|
|||||||
|
|
||||||
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
|
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
|
||||||
size_t size = sc_control_msg_serialize(&msg, buf);
|
size_t size = sc_control_msg_serialize(&msg, buf);
|
||||||
assert(size == 20);
|
assert(size == 24);
|
||||||
|
|
||||||
const uint8_t expected[] = {
|
const uint8_t expected[] = {
|
||||||
SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
||||||
0, 42, // id
|
0, 42, // id
|
||||||
|
0x12, 0x34, // vendor id
|
||||||
|
0x56, 0x78, // product id
|
||||||
3, // name size
|
3, // name size
|
||||||
65, 66, 67, // "ABC"
|
65, 66, 67, // "ABC"
|
||||||
0, 11, // report desc size
|
0, 11, // report desc size
|
||||||
|
@ -233,10 +233,10 @@ install` must be run as root)._
|
|||||||
|
|
||||||
#### Option 2: Use prebuilt server
|
#### Option 2: Use prebuilt server
|
||||||
|
|
||||||
- [`scrcpy-server-v3.0`][direct-scrcpy-server]
|
- [`scrcpy-server-v3.0.2`][direct-scrcpy-server]
|
||||||
<sub>SHA-256: `800044c62a94d5fc16f5ab9c86d45b1050eae3eb436514d1b0d2fe2646b894ea`</sub>
|
<sub>SHA-256: `e19fe024bfa3367809494407ad6ca809a6f6e77dac95e99f85ba75144e0ba35d`</sub>
|
||||||
|
|
||||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-server-v3.0
|
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.0.2/scrcpy-server-v3.0.2
|
||||||
|
|
||||||
Download the prebuilt server somewhere, and specify its path during the Meson
|
Download the prebuilt server somewhere, and specify its path during the Meson
|
||||||
configuration:
|
configuration:
|
||||||
|
@ -85,6 +85,12 @@ scrcpy --tcpip=192.168.1.1 # default port is 5555
|
|||||||
scrcpy --tcpip=192.168.1.1:5555
|
scrcpy --tcpip=192.168.1.1:5555
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Prefix the address with a '+' to force a reconnection:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --tcpip=+192.168.1.1
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Manual
|
### Manual
|
||||||
|
|
||||||
|
@ -6,11 +6,11 @@
|
|||||||
|
|
||||||
Download a static build of the [latest release]:
|
Download a static build of the [latest release]:
|
||||||
|
|
||||||
- [`scrcpy-linux-v3.0.tar.gz`][direct-linux] (x86_64)
|
- [`scrcpy-linux-x86_64-v3.0.2.tar.gz`][direct-linux-x86_64] (x86_64)
|
||||||
<sub>SHA-256: `06cb74e22f758228c944cea048b78e42b2925c2affe2b5aca901cfd6a649e503`</sub>
|
<sub>SHA-256: `20b69dcd379bb7d7208bf1e4858cf04162fc856697be0e6c03863d7b3c1e734a`</sub>
|
||||||
|
|
||||||
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
|
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
|
||||||
[direct-linux]: https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-linux-v3.0.tar.gz
|
[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.0.2/scrcpy-linux-x86_64-v3.0.2.tar.gz
|
||||||
|
|
||||||
and extract it.
|
and extract it.
|
||||||
|
|
||||||
|
10
doc/macos.md
10
doc/macos.md
@ -6,11 +6,15 @@
|
|||||||
|
|
||||||
Download a static build of the [latest release]:
|
Download a static build of the [latest release]:
|
||||||
|
|
||||||
- [`scrcpy-macos-v3.0.tar.gz`][direct-macos] (arm64)
|
- [`scrcpy-macos-aarch64-v3.0.2.tar.gz`][direct-macos-aarch64] (aarch64)
|
||||||
<sub>SHA-256: `5db9821918537eb3aaf0333cdd05baf85babdd851972d5f1b71f86da0530b4bf`</sub>
|
<sub>SHA-256: `811ba2f4e856146bdd161e24c3490d78efbec2339ca783fac791d041c0aecfb6`</sub>
|
||||||
|
|
||||||
|
- [`scrcpy-macos-x86_64-v3.0.2.tar.gz`][direct-macos-x86_64] (x86_64)
|
||||||
|
<sub>SHA-256: `8effff54dca3a3e46eaaec242771a13a7f81af2e18670b3d0d8ed6b461bb4f79`</sub>
|
||||||
|
|
||||||
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
|
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
|
||||||
[direct-macos]: https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-macos-v3.0.tar.gz
|
[direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.0.2/scrcpy-macos-aarch64-v3.0.2.tar.gz
|
||||||
|
[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.0.2/scrcpy-macos-x86_64-v3.0.2.tar.gz
|
||||||
|
|
||||||
and extract it.
|
and extract it.
|
||||||
|
|
||||||
|
@ -15,8 +15,10 @@ scrcpy --new-display=/240 # use the main display size and 240 dpi
|
|||||||
|
|
||||||
On some devices, a launcher is available in the virtual display.
|
On some devices, a launcher is available in the virtual display.
|
||||||
|
|
||||||
When no launcher is available, the virtual display is empty. In that case, you
|
When no launcher is available (or if is explicitly disabled by
|
||||||
must [start an Android app](device.md#start-android-app).
|
[`--no-vd-system-decorations`](#system-decorations)), the virtual display is
|
||||||
|
empty. In that case, you must [start an Android
|
||||||
|
app](device.md#start-android-app).
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
@ -24,12 +26,38 @@ For example:
|
|||||||
scrcpy --new-display=1920x1080 --start-app=org.videolan.vlc
|
scrcpy --new-display=1920x1080 --start-app=org.videolan.vlc
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The app may itself be a launcher. For example, to run the open source [Fossify
|
||||||
|
Launcher]:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --new-display=1920x1080 --no-vd-system-decorations --start-app=org.fossify.home
|
||||||
|
```
|
||||||
|
|
||||||
|
[Fossify Launcher]: https://f-droid.org/en/packages/org.fossify.home/
|
||||||
|
|
||||||
|
|
||||||
## System decorations
|
## System decorations
|
||||||
|
|
||||||
By default, virtual display system decorations are enabled. But some devices
|
By default, virtual display system decorations are enabled. To disable them, use
|
||||||
might display a broken UI;
|
`--no-vd-system-decorations`:
|
||||||
|
|
||||||
Use `--no-vd-system-decorations` to disable it.
|
```
|
||||||
|
scrcpy --new-display --no-vd-system-decorations
|
||||||
|
```
|
||||||
|
|
||||||
|
This is useful for some devices which might display a broken UI, or to disable
|
||||||
|
any default launcher UI available in virtual displays.
|
||||||
|
|
||||||
Note that if no app is started, no content will be rendered, so no video frame
|
Note that if no app is started, no content will be rendered, so no video frame
|
||||||
will be produced at all.
|
will be produced at all.
|
||||||
|
|
||||||
|
|
||||||
|
## Destroy on close
|
||||||
|
|
||||||
|
By default, when the virtual display is closed, the running apps are destroyed.
|
||||||
|
|
||||||
|
To move them to the main display instead, use:
|
||||||
|
|
||||||
|
```
|
||||||
|
scrcpy --new-display --no-vd-destroy-content
|
||||||
|
```
|
||||||
|
@ -6,14 +6,14 @@
|
|||||||
|
|
||||||
Download the [latest release]:
|
Download the [latest release]:
|
||||||
|
|
||||||
- [`scrcpy-win64-v3.0.zip`][direct-win64] (64-bit)
|
- [`scrcpy-win64-v3.0.2.zip`][direct-win64] (64-bit)
|
||||||
<sub>SHA-256: `dfbe8a8fef6535197acc506936bfd59d0aa0427e9b44fb2e5c550eae642f72be`</sub>
|
<sub>SHA-256: `f0de59f5d46127c87cd822d39d6665e016b86db4cd048101b262f6adb6766832`</sub>
|
||||||
- [`scrcpy-win32-v3.0.zip`][direct-win32] (32-bit)
|
- [`scrcpy-win32-v3.0.2.zip`][direct-win32] (32-bit)
|
||||||
<sub>SHA-256: `7cbf8d7a6ebfdca7b3b161e29a481c11088305f3e0a89d28e8e62f70c7bd0028`</sub>
|
<sub>SHA-256: `8db8d4984d642012c55802de71f507f8ff9f68a8cfed456d7a1982d47e065f64`</sub>
|
||||||
|
|
||||||
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
|
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
|
||||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-win64-v3.0.zip
|
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.0.2/scrcpy-win64-v3.0.2.zip
|
||||||
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-win32-v3.0.zip
|
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.0.2/scrcpy-win32-v3.0.2.zip
|
||||||
|
|
||||||
and extract it.
|
and extract it.
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
BUILDDIR=build-auto
|
BUILDDIR=build-auto
|
||||||
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-server-v3.0
|
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.0.2/scrcpy-server-v3.0.2
|
||||||
PREBUILT_SERVER_SHA256=800044c62a94d5fc16f5ab9c86d45b1050eae3eb436514d1b0d2fe2646b894ea
|
PREBUILT_SERVER_SHA256=e19fe024bfa3367809494407ad6ca809a6f6e77dac95e99f85ba75144e0ba35d
|
||||||
|
|
||||||
echo "[scrcpy] Downloading prebuilt server..."
|
echo "[scrcpy] Downloading prebuilt server..."
|
||||||
wget "$PREBUILT_SERVER_URL" -O scrcpy-server
|
wget "$PREBUILT_SERVER_URL" -O scrcpy-server
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
project('scrcpy', 'c',
|
project('scrcpy', 'c',
|
||||||
version: '3.0',
|
version: '3.0.2',
|
||||||
meson_version: '>= 0.48',
|
meson_version: '>= 0.48',
|
||||||
default_options: [
|
default_options: [
|
||||||
'c_std=c11',
|
'c_std=c11',
|
||||||
|
@ -7,8 +7,8 @@ android {
|
|||||||
applicationId "com.genymobile.scrcpy"
|
applicationId "com.genymobile.scrcpy"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 35
|
targetSdkVersion 35
|
||||||
versionCode 30000
|
versionCode 30002
|
||||||
versionName "3.0"
|
versionName "3.0.2"
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
SCRCPY_DEBUG=false
|
SCRCPY_DEBUG=false
|
||||||
SCRCPY_VERSION_NAME=3.0
|
SCRCPY_VERSION_NAME=3.0.2
|
||||||
|
|
||||||
PLATFORM=${ANDROID_PLATFORM:-35}
|
PLATFORM=${ANDROID_PLATFORM:-35}
|
||||||
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-35.0.0}
|
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-35.0.0}
|
||||||
|
@ -10,6 +10,8 @@ import android.os.BatteryManager;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle the cleanup of scrcpy, even if the main process is killed.
|
* Handle the cleanup of scrcpy, even if the main process is killed.
|
||||||
@ -24,6 +26,7 @@ public final class CleanUp {
|
|||||||
private boolean pendingRestoreDisplayPower;
|
private boolean pendingRestoreDisplayPower;
|
||||||
|
|
||||||
private Thread thread;
|
private Thread thread;
|
||||||
|
private boolean interrupted;
|
||||||
|
|
||||||
private CleanUp(Options options) {
|
private CleanUp(Options options) {
|
||||||
thread = new Thread(() -> runCleanUp(options), "cleanup");
|
thread = new Thread(() -> runCleanUp(options), "cleanup");
|
||||||
@ -34,8 +37,10 @@ public final class CleanUp {
|
|||||||
return new CleanUp(options);
|
return new CleanUp(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void interrupt() {
|
public synchronized void interrupt() {
|
||||||
thread.interrupt();
|
// Do not use thread.interrupt() because only the wait() call must be interrupted, not Command.exec()
|
||||||
|
interrupted = true;
|
||||||
|
notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void join() throws InterruptedException {
|
public void join() throws InterruptedException {
|
||||||
@ -97,25 +102,29 @@ public final class CleanUp {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
run(displayId, restoreStayOn, disableShowTouches, powerOffScreen, restoreScreenOffTimeout);
|
run(displayId, restoreStayOn, disableShowTouches, powerOffScreen, restoreScreenOffTimeout);
|
||||||
} catch (InterruptedException e) {
|
|
||||||
// ignore
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Ln.e("Clean up I/O exception", e);
|
Ln.e("Clean up I/O exception", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void run(int displayId, int restoreStayOn, boolean disableShowTouches, boolean powerOffScreen, int restoreScreenOffTimeout)
|
private void run(int displayId, int restoreStayOn, boolean disableShowTouches, boolean powerOffScreen, int restoreScreenOffTimeout)
|
||||||
throws IOException, InterruptedException {
|
throws IOException {
|
||||||
String[] cmd = {
|
|
||||||
"app_process",
|
List<String> cmd = new ArrayList<>();
|
||||||
"/",
|
if (new File("/system/bin/setsid").exists()) {
|
||||||
CleanUp.class.getName(),
|
cmd.add("/system/bin/setsid");
|
||||||
String.valueOf(displayId),
|
} else if (new File("/system/bin/nohup").exists()) {
|
||||||
String.valueOf(restoreStayOn),
|
cmd.add("/system/bin/nohup");
|
||||||
String.valueOf(disableShowTouches),
|
}
|
||||||
String.valueOf(powerOffScreen),
|
|
||||||
String.valueOf(restoreScreenOffTimeout),
|
cmd.add("app_process");
|
||||||
};
|
cmd.add("/");
|
||||||
|
cmd.add(CleanUp.class.getName());
|
||||||
|
cmd.add(String.valueOf(displayId));
|
||||||
|
cmd.add(String.valueOf(restoreStayOn));
|
||||||
|
cmd.add(String.valueOf(disableShowTouches));
|
||||||
|
cmd.add(String.valueOf(powerOffScreen));
|
||||||
|
cmd.add(String.valueOf(restoreScreenOffTimeout));
|
||||||
|
|
||||||
ProcessBuilder builder = new ProcessBuilder(cmd);
|
ProcessBuilder builder = new ProcessBuilder(cmd);
|
||||||
builder.environment().put("CLASSPATH", Server.SERVER_PATH);
|
builder.environment().put("CLASSPATH", Server.SERVER_PATH);
|
||||||
@ -126,8 +135,15 @@ public final class CleanUp {
|
|||||||
int localPendingChanges;
|
int localPendingChanges;
|
||||||
boolean localPendingRestoreDisplayPower;
|
boolean localPendingRestoreDisplayPower;
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
while (pendingChanges == 0) {
|
while (!interrupted && pendingChanges == 0) {
|
||||||
|
try {
|
||||||
wait();
|
wait();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new AssertionError("Clean up thread MUST NOT be interrupted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (interrupted) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
localPendingChanges = pendingChanges;
|
localPendingChanges = pendingChanges;
|
||||||
localPendingRestoreDisplayPower = pendingRestoreDisplayPower;
|
localPendingRestoreDisplayPower = pendingRestoreDisplayPower;
|
||||||
|
@ -60,6 +60,7 @@ public class Options {
|
|||||||
private boolean powerOn = true;
|
private boolean powerOn = true;
|
||||||
|
|
||||||
private NewDisplay newDisplay;
|
private NewDisplay newDisplay;
|
||||||
|
private boolean vdDestroyContent = true;
|
||||||
private boolean vdSystemDecorations = true;
|
private boolean vdSystemDecorations = true;
|
||||||
|
|
||||||
private Orientation.Lock captureOrientationLock = Orientation.Lock.Unlocked;
|
private Orientation.Lock captureOrientationLock = Orientation.Lock.Unlocked;
|
||||||
@ -233,6 +234,10 @@ public class Options {
|
|||||||
return captureOrientationLock;
|
return captureOrientationLock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getVDDestroyContent() {
|
||||||
|
return vdDestroyContent;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean getVDSystemDecorations() {
|
public boolean getVDSystemDecorations() {
|
||||||
return vdSystemDecorations;
|
return vdSystemDecorations;
|
||||||
}
|
}
|
||||||
@ -466,6 +471,9 @@ public class Options {
|
|||||||
case "new_display":
|
case "new_display":
|
||||||
options.newDisplay = parseNewDisplay(value);
|
options.newDisplay = parseNewDisplay(value);
|
||||||
break;
|
break;
|
||||||
|
case "vd_destroy_content":
|
||||||
|
options.vdDestroyContent = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
case "vd_system_decorations":
|
case "vd_system_decorations":
|
||||||
options.vdSystemDecorations = Boolean.parseBoolean(value);
|
options.vdSystemDecorations = Boolean.parseBoolean(value);
|
||||||
break;
|
break;
|
||||||
|
@ -51,6 +51,8 @@ public final class ControlMessage {
|
|||||||
private int id;
|
private int id;
|
||||||
private byte[] data;
|
private byte[] data;
|
||||||
private boolean on;
|
private boolean on;
|
||||||
|
private int vendorId;
|
||||||
|
private int productId;
|
||||||
|
|
||||||
private ControlMessage() {
|
private ControlMessage() {
|
||||||
}
|
}
|
||||||
@ -131,10 +133,12 @@ public final class ControlMessage {
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ControlMessage createUhidCreate(int id, String name, byte[] reportDesc) {
|
public static ControlMessage createUhidCreate(int id, int vendorId, int productId, String name, byte[] reportDesc) {
|
||||||
ControlMessage msg = new ControlMessage();
|
ControlMessage msg = new ControlMessage();
|
||||||
msg.type = TYPE_UHID_CREATE;
|
msg.type = TYPE_UHID_CREATE;
|
||||||
msg.id = id;
|
msg.id = id;
|
||||||
|
msg.vendorId = vendorId;
|
||||||
|
msg.productId = productId;
|
||||||
msg.text = name;
|
msg.text = name;
|
||||||
msg.data = reportDesc;
|
msg.data = reportDesc;
|
||||||
return msg;
|
return msg;
|
||||||
@ -237,4 +241,12 @@ public final class ControlMessage {
|
|||||||
public boolean getOn() {
|
public boolean getOn() {
|
||||||
return on;
|
return on;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getVendorId() {
|
||||||
|
return vendorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getProductId() {
|
||||||
|
return productId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,9 +142,11 @@ public class ControlMessageReader {
|
|||||||
|
|
||||||
private ControlMessage parseUhidCreate() throws IOException {
|
private ControlMessage parseUhidCreate() throws IOException {
|
||||||
int id = dis.readUnsignedShort();
|
int id = dis.readUnsignedShort();
|
||||||
|
int vendorId = dis.readUnsignedShort();
|
||||||
|
int productId = dis.readUnsignedShort();
|
||||||
String name = parseString(1);
|
String name = parseString(1);
|
||||||
byte[] data = parseByteArray(2);
|
byte[] data = parseByteArray(2);
|
||||||
return ControlMessage.createUhidCreate(id, name, data);
|
return ControlMessage.createUhidCreate(id, vendorId, productId, name, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseUhidInput() throws IOException {
|
private ControlMessage parseUhidInput() throws IOException {
|
||||||
|
@ -290,7 +290,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||||||
Device.rotateDevice(getActionDisplayId());
|
Device.rotateDevice(getActionDisplayId());
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_UHID_CREATE:
|
case ControlMessage.TYPE_UHID_CREATE:
|
||||||
getUhidManager().open(msg.getId(), msg.getText(), msg.getData());
|
getUhidManager().open(msg.getId(), msg.getVendorId(), msg.getProductId(), msg.getText(), msg.getData());
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_UHID_INPUT:
|
case ControlMessage.TYPE_UHID_INPUT:
|
||||||
getUhidManager().writeInput(msg.getId(), msg.getData());
|
getUhidManager().writeInput(msg.getId(), msg.getData());
|
||||||
|
@ -48,7 +48,7 @@ public final class UhidManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void open(int id, String name, byte[] reportDesc) throws IOException {
|
public void open(int id, int vendorId, int productId, String name, byte[] reportDesc) throws IOException {
|
||||||
try {
|
try {
|
||||||
FileDescriptor fd = Os.open("/dev/uhid", OsConstants.O_RDWR, 0);
|
FileDescriptor fd = Os.open("/dev/uhid", OsConstants.O_RDWR, 0);
|
||||||
try {
|
try {
|
||||||
@ -58,7 +58,7 @@ public final class UhidManager {
|
|||||||
close(old);
|
close(old);
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] req = buildUhidCreate2Req(name, reportDesc);
|
byte[] req = buildUhidCreate2Req(vendorId, productId, name, reportDesc);
|
||||||
Os.write(fd, req, 0, req.length);
|
Os.write(fd, req, 0, req.length);
|
||||||
|
|
||||||
registerUhidListener(id, fd);
|
registerUhidListener(id, fd);
|
||||||
@ -148,7 +148,7 @@ public final class UhidManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] buildUhidCreate2Req(String name, byte[] reportDesc) {
|
private static byte[] buildUhidCreate2Req(int vendorId, int productId, String name, byte[] reportDesc) {
|
||||||
/*
|
/*
|
||||||
* struct uhid_event {
|
* struct uhid_event {
|
||||||
* uint32_t type;
|
* uint32_t type;
|
||||||
@ -174,7 +174,7 @@ public final class UhidManager {
|
|||||||
ByteBuffer buf = ByteBuffer.allocate(280 + reportDesc.length).order(ByteOrder.nativeOrder());
|
ByteBuffer buf = ByteBuffer.allocate(280 + reportDesc.length).order(ByteOrder.nativeOrder());
|
||||||
buf.putInt(UHID_CREATE2);
|
buf.putInt(UHID_CREATE2);
|
||||||
|
|
||||||
String actualName = name.isEmpty() ? "scrcpy" : "scrcpy: " + name;
|
String actualName = name.isEmpty() ? "scrcpy" : name;
|
||||||
byte[] utf8Name = actualName.getBytes(StandardCharsets.UTF_8);
|
byte[] utf8Name = actualName.getBytes(StandardCharsets.UTF_8);
|
||||||
int len = StringUtils.getUtf8TruncationIndex(utf8Name, 127);
|
int len = StringUtils.getUtf8TruncationIndex(utf8Name, 127);
|
||||||
assert len <= 127;
|
assert len <= 127;
|
||||||
@ -183,8 +183,8 @@ public final class UhidManager {
|
|||||||
|
|
||||||
buf.putShort((short) reportDesc.length);
|
buf.putShort((short) reportDesc.length);
|
||||||
buf.putShort(BUS_VIRTUAL);
|
buf.putShort(BUS_VIRTUAL);
|
||||||
buf.putInt(0); // vendor id
|
buf.putInt(vendorId);
|
||||||
buf.putInt(0); // product id
|
buf.putInt(productId);
|
||||||
buf.putInt(0); // version
|
buf.putInt(0); // version
|
||||||
buf.putInt(0); // country;
|
buf.putInt(0); // country;
|
||||||
buf.put(reportDesc);
|
buf.put(reportDesc);
|
||||||
|
@ -72,4 +72,8 @@ public final class IO {
|
|||||||
Throwable cause = e.getCause();
|
Throwable cause = e.getCause();
|
||||||
return cause instanceof ErrnoException && ((ErrnoException) cause).errno == OsConstants.EPIPE;
|
return cause instanceof ErrnoException && ((ErrnoException) cause).errno == OsConstants.EPIPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isBrokenPipe(Exception e) {
|
||||||
|
return e instanceof IOException && isBrokenPipe((IOException) e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,7 @@ public class NewDisplayCapture extends SurfaceCapture {
|
|||||||
private final boolean captureOrientationLocked;
|
private final boolean captureOrientationLocked;
|
||||||
private final Orientation captureOrientation;
|
private final Orientation captureOrientation;
|
||||||
private final float angle;
|
private final float angle;
|
||||||
|
private final boolean vdDestroyContent;
|
||||||
private final boolean vdSystemDecorations;
|
private final boolean vdSystemDecorations;
|
||||||
|
|
||||||
private VirtualDisplay virtualDisplay;
|
private VirtualDisplay virtualDisplay;
|
||||||
@ -73,6 +74,7 @@ public class NewDisplayCapture extends SurfaceCapture {
|
|||||||
this.captureOrientation = options.getCaptureOrientation();
|
this.captureOrientation = options.getCaptureOrientation();
|
||||||
assert captureOrientation != null;
|
assert captureOrientation != null;
|
||||||
this.angle = options.getAngle();
|
this.angle = options.getAngle();
|
||||||
|
this.vdDestroyContent = options.getVDDestroyContent();
|
||||||
this.vdSystemDecorations = options.getVDSystemDecorations();
|
this.vdSystemDecorations = options.getVDSystemDecorations();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,8 +169,10 @@ public class NewDisplayCapture extends SurfaceCapture {
|
|||||||
int flags = VIRTUAL_DISPLAY_FLAG_PUBLIC
|
int flags = VIRTUAL_DISPLAY_FLAG_PUBLIC
|
||||||
| VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
|
| VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
|
||||||
| VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
|
| VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
|
||||||
| VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT
|
| VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
|
||||||
| VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
|
if (vdDestroyContent) {
|
||||||
|
flags |= VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
|
||||||
|
}
|
||||||
if (vdSystemDecorations) {
|
if (vdSystemDecorations) {
|
||||||
flags |= VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
|
flags |= VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
|
||||||
}
|
}
|
||||||
|
@ -124,15 +124,9 @@ public class ScreenCapture extends SurfaceCapture {
|
|||||||
inputSize = videoSize;
|
inputSize = videoSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
int virtualDisplayId;
|
|
||||||
PositionMapper positionMapper;
|
|
||||||
try {
|
try {
|
||||||
virtualDisplay = ServiceManager.getDisplayManager()
|
virtualDisplay = ServiceManager.getDisplayManager()
|
||||||
.createVirtualDisplay("scrcpy", inputSize.getWidth(), inputSize.getHeight(), displayId, surface);
|
.createVirtualDisplay("scrcpy", inputSize.getWidth(), inputSize.getHeight(), displayId, surface);
|
||||||
virtualDisplayId = virtualDisplay.getDisplay().getDisplayId();
|
|
||||||
|
|
||||||
// The positions are relative to the virtual display, not the original display (so use inputSize, not deviceSize!)
|
|
||||||
positionMapper = PositionMapper.create(videoSize, transform, inputSize);
|
|
||||||
Ln.d("Display: using DisplayManager API");
|
Ln.d("Display: using DisplayManager API");
|
||||||
} catch (Exception displayManagerException) {
|
} catch (Exception displayManagerException) {
|
||||||
try {
|
try {
|
||||||
@ -140,11 +134,7 @@ public class ScreenCapture extends SurfaceCapture {
|
|||||||
|
|
||||||
Size deviceSize = displayInfo.getSize();
|
Size deviceSize = displayInfo.getSize();
|
||||||
int layerStack = displayInfo.getLayerStack();
|
int layerStack = displayInfo.getLayerStack();
|
||||||
|
|
||||||
setDisplaySurface(display, surface, deviceSize.toRect(), inputSize.toRect(), layerStack);
|
setDisplaySurface(display, surface, deviceSize.toRect(), inputSize.toRect(), layerStack);
|
||||||
virtualDisplayId = displayId;
|
|
||||||
|
|
||||||
positionMapper = PositionMapper.create(videoSize, transform, deviceSize);
|
|
||||||
Ln.d("Display: using SurfaceControl API");
|
Ln.d("Display: using SurfaceControl API");
|
||||||
} catch (Exception surfaceControlException) {
|
} catch (Exception surfaceControlException) {
|
||||||
Ln.e("Could not create display using DisplayManager", displayManagerException);
|
Ln.e("Could not create display using DisplayManager", displayManagerException);
|
||||||
@ -154,6 +144,18 @@ public class ScreenCapture extends SurfaceCapture {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (vdListener != null) {
|
if (vdListener != null) {
|
||||||
|
int virtualDisplayId;
|
||||||
|
PositionMapper positionMapper;
|
||||||
|
if (virtualDisplay == null || displayId == 0) {
|
||||||
|
// Surface control or main display: send all events to the original display, relative to the device size
|
||||||
|
Size deviceSize = displayInfo.getSize();
|
||||||
|
positionMapper = PositionMapper.create(videoSize, transform, deviceSize);
|
||||||
|
virtualDisplayId = displayId;
|
||||||
|
} else {
|
||||||
|
// The positions are relative to the virtual display, not the original display (so use inputSize, not deviceSize!)
|
||||||
|
positionMapper = PositionMapper.create(videoSize, transform, inputSize);
|
||||||
|
virtualDisplayId = virtualDisplay.getDisplay().getDisplayId();
|
||||||
|
}
|
||||||
vdListener.onNewVirtualDisplay(virtualDisplayId, positionMapper);
|
vdListener.onNewVirtualDisplay(virtualDisplayId, positionMapper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,8 +112,12 @@ public class SurfaceEncoder implements AsyncProcessor {
|
|||||||
// The capture might have been closed internally (for example if the camera is disconnected)
|
// The capture might have been closed internally (for example if the camera is disconnected)
|
||||||
alive = !stopped.get() && !capture.isClosed();
|
alive = !stopped.get() && !capture.isClosed();
|
||||||
}
|
}
|
||||||
} catch (IllegalStateException | IllegalArgumentException e) {
|
} catch (IllegalStateException | IllegalArgumentException | IOException e) {
|
||||||
Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage());
|
if (IO.isBrokenPipe(e)) {
|
||||||
|
// Do not retry on broken pipe, which is expected on close because the socket is closed by the client
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
Ln.e("Capture/encoding error: " + e.getClass().getName() + ": " + e.getMessage());
|
||||||
if (!prepareRetry(size)) {
|
if (!prepareRetry(size)) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
@ -322,6 +322,8 @@ public class ControlMessageReaderTest {
|
|||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_UHID_CREATE);
|
dos.writeByte(ControlMessage.TYPE_UHID_CREATE);
|
||||||
dos.writeShort(42); // id
|
dos.writeShort(42); // id
|
||||||
|
dos.writeShort(0x1234); // vendorId
|
||||||
|
dos.writeShort(0x5678); // productId
|
||||||
dos.writeByte(3); // name size
|
dos.writeByte(3); // name size
|
||||||
dos.write("ABC".getBytes(StandardCharsets.US_ASCII));
|
dos.write("ABC".getBytes(StandardCharsets.US_ASCII));
|
||||||
byte[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
|
byte[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
|
||||||
@ -335,6 +337,8 @@ public class ControlMessageReaderTest {
|
|||||||
ControlMessage event = reader.read();
|
ControlMessage event = reader.read();
|
||||||
Assert.assertEquals(ControlMessage.TYPE_UHID_CREATE, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_UHID_CREATE, event.getType());
|
||||||
Assert.assertEquals(42, event.getId());
|
Assert.assertEquals(42, event.getId());
|
||||||
|
Assert.assertEquals(0x1234, event.getVendorId());
|
||||||
|
Assert.assertEquals(0x5678, event.getProductId());
|
||||||
Assert.assertEquals("ABC", event.getText());
|
Assert.assertEquals("ABC", event.getText());
|
||||||
Assert.assertArrayEquals(data, event.getData());
|
Assert.assertArrayEquals(data, event.getData());
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user