Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
7633228278 | |||
f5e6b8092a | |||
035d60cf5d | |||
40493dff60 | |||
09ce0307fe | |||
9fa30ab1ae | |||
0b926922bc | |||
24bcc3fa2b | |||
592ca0b59b | |||
30e42af2d4 | |||
9030bd8be4 | |||
24b9e0a970 | |||
9ea4446369 | |||
5d1d5bdc16 | |||
fd9498e07c | |||
09e8c20168 | |||
da484b7ab9 |
@ -25,7 +25,6 @@ _scrcpy() {
|
||||
-e --select-tcpip
|
||||
-f --fullscreen
|
||||
--force-adb-forward
|
||||
--forward-all-clicks
|
||||
-h --help
|
||||
-K
|
||||
--keyboard=
|
||||
@ -41,6 +40,7 @@ _scrcpy() {
|
||||
-M
|
||||
--max-fps=
|
||||
--mouse=
|
||||
--mouse-bind=
|
||||
-n --no-control
|
||||
-N --no-playback
|
||||
--no-audio
|
||||
@ -50,6 +50,7 @@ _scrcpy() {
|
||||
--no-downsize-on-error
|
||||
--no-key-repeat
|
||||
--no-mipmaps
|
||||
--no-mouse-hover
|
||||
--no-power-on
|
||||
--no-video
|
||||
--no-video-playback
|
||||
|
@ -32,10 +32,9 @@ 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]'
|
||||
'--forward-all-clicks[Forward clicks to device]'
|
||||
{-h,--help}'[Print the help]'
|
||||
'-K[Use UHID keyboard (same as --keyboard=uhid)]'
|
||||
'--keyboard[Set the keyboard input mode]:mode:(disabled sdk uhid aoa)'
|
||||
'--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]'
|
||||
'--list-camera-sizes[List the valid camera capture sizes]'
|
||||
@ -46,7 +45,8 @@ arguments=(
|
||||
{-m,--max-size=}'[Limit both the width and height of the video to value]'
|
||||
'-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=[Set the mouse input mode]:mode:(disabled sdk uhid aoa)'
|
||||
'--mouse-bind=[Configure bindings of secondary clicks]'
|
||||
{-n,--no-control}'[Disable device control \(mirror the device in read only\)]'
|
||||
{-N,--no-playback}'[Disable video and audio playback]'
|
||||
'--no-audio[Disable audio forwarding]'
|
||||
@ -56,6 +56,7 @@ arguments=(
|
||||
'--no-downsize-on-error[Disable lowering definition on MediaCodec error]'
|
||||
'--no-key-repeat[Do not forward repeated key events when a key is held down]'
|
||||
'--no-mipmaps[Disable the generation of mipmaps]'
|
||||
'--no-mouse-hover[Do not forward mouse hover events]'
|
||||
'--no-power-on[Do not power on the device on start]'
|
||||
'--no-video[Disable video forwarding]'
|
||||
'--no-video-playback[Disable video playback]'
|
||||
|
29
app/scrcpy.1
29
app/scrcpy.1
@ -163,10 +163,6 @@ Start in fullscreen.
|
||||
.B \-\-force\-adb\-forward
|
||||
Do not attempt to use "adb reverse" to connect to the device.
|
||||
|
||||
.TP
|
||||
.B \-\-forward\-all\-clicks
|
||||
By default, right-click triggers BACK (or POWER on) and middle-click triggers HOME. This option disables these shortcuts and forward the clicks to the device instead.
|
||||
|
||||
.TP
|
||||
.B \-h, \-\-help
|
||||
Print this help.
|
||||
@ -261,6 +257,23 @@ LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse bac
|
||||
|
||||
Also see \fB\-\-keyboard\fR.
|
||||
|
||||
.TP
|
||||
.BI "\-\-mouse\-bind " xxxx
|
||||
Configure bindings of secondary clicks.
|
||||
|
||||
The argument must be exactly 4 characters, one for each secondary click (in order: right click, middle click, 4th click, 5th click).
|
||||
|
||||
Each character must be one of the following:
|
||||
|
||||
- '+': forward the click to the device
|
||||
- '-': ignore the click
|
||||
- 'b': trigger shortcut BACK (or turn screen on if off)
|
||||
- 'h': trigger shortcut HOME
|
||||
- 's': trigger shortcut APP_SWITCH
|
||||
- 'n': trigger shortcut "expand notification panel"
|
||||
|
||||
Default is 'bhsn' for SDK mouse, and '++++' for AOA and UHID.
|
||||
|
||||
|
||||
.TP
|
||||
.B \-n, \-\-no\-control
|
||||
@ -304,6 +317,10 @@ Do not forward repeated key events when a key is held down.
|
||||
.B \-\-no\-mipmaps
|
||||
If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps.
|
||||
|
||||
.TP
|
||||
.B \-\-no\-mouse\-hover
|
||||
Do not forward mouse hover (mouse motion without any clicks) events.
|
||||
|
||||
.TP
|
||||
.B \-\-no\-power\-on
|
||||
Do not power on the device on start.
|
||||
@ -424,9 +441,9 @@ Turn the device screen off immediately.
|
||||
.BI "\-\-shortcut\-mod " key\fR[+...]][,...]
|
||||
Specify the modifiers to use for scrcpy shortcuts. Possible keys are "lctrl", "rctrl", "lalt", "ralt", "lsuper" and "rsuper".
|
||||
|
||||
A shortcut can consist in several keys, separated by '+'. Several shortcuts can be specified, separated by ','.
|
||||
Several shortcut modifiers can be specified, separated by ','.
|
||||
|
||||
For example, to use either LCtrl+LAlt or LSuper for scrcpy shortcuts, pass "lctrl+lalt,lsuper".
|
||||
For example, to use either LCtrl or LSuper for scrcpy shortcuts, pass "lctrl,lsuper".
|
||||
|
||||
Default is "lalt,lsuper" (left-Alt or left-Super).
|
||||
|
||||
|
@ -194,7 +194,11 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
||||
// Still insufficient, drop old samples to make space
|
||||
skipped_samples = sc_audiobuf_read(&ap->buf, NULL, remaining);
|
||||
assert(skipped_samples == remaining);
|
||||
}
|
||||
|
||||
SDL_UnlockAudioDevice(ap->device);
|
||||
|
||||
if (written < samples) {
|
||||
// Now there is enough space
|
||||
uint32_t w = sc_audiobuf_write(&ap->buf,
|
||||
swr_buf + TO_BYTES(written),
|
||||
@ -202,8 +206,6 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
||||
assert(w == remaining);
|
||||
(void) w;
|
||||
}
|
||||
|
||||
SDL_UnlockAudioDevice(ap->device);
|
||||
}
|
||||
|
||||
uint32_t underflow = 0;
|
||||
|
231
app/src/cli.c
231
app/src/cli.c
@ -98,6 +98,8 @@ enum {
|
||||
OPT_HID_KEYBOARD_DEPRECATED,
|
||||
OPT_HID_MOUSE_DEPRECATED,
|
||||
OPT_NO_WINDOW,
|
||||
OPT_MOUSE_BIND,
|
||||
OPT_NO_MOUSE_HOVER,
|
||||
};
|
||||
|
||||
struct sc_option {
|
||||
@ -352,11 +354,9 @@ static const struct sc_option options[] = {
|
||||
"device.",
|
||||
},
|
||||
{
|
||||
// deprecated
|
||||
.longopt_id = OPT_FORWARD_ALL_CLICKS,
|
||||
.longopt = "forward-all-clicks",
|
||||
.text = "By default, right-click triggers BACK (or POWER on) and "
|
||||
"middle-click triggers HOME. This option disables these "
|
||||
"shortcuts and forwards the clicks to the device instead.",
|
||||
},
|
||||
{
|
||||
.shortopt = 'h',
|
||||
@ -490,6 +490,23 @@ static const struct sc_option options[] = {
|
||||
"control of the mouse back to the computer.\n"
|
||||
"Also see --keyboard.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_MOUSE_BIND,
|
||||
.longopt = "mouse-bind",
|
||||
.argdesc = "xxxx",
|
||||
.text = "Configure bindings of secondary clicks.\n"
|
||||
"The argument must be exactly 4 characters, one for each "
|
||||
"secondary click (in order: right click, middle click, 4th "
|
||||
"click, 5th click).\n"
|
||||
"Each character must be one of the following:\n"
|
||||
" '+': forward the click to the device\n"
|
||||
" '-': ignore the click\n"
|
||||
" 'b': trigger shortcut BACK (or turn screen on if off)\n"
|
||||
" 'h': trigger shortcut HOME\n"
|
||||
" 's': trigger shortcut APP_SWITCH\n"
|
||||
" 'n': trigger shortcut \"expand notification panel\"\n"
|
||||
"Default is 'bhsn' for SDK mouse, and '++++' for AOA and UHID.",
|
||||
},
|
||||
{
|
||||
.shortopt = 'n',
|
||||
.longopt = "no-control",
|
||||
@ -552,6 +569,12 @@ static const struct sc_option options[] = {
|
||||
"mipmaps are automatically generated to improve downscaling "
|
||||
"quality. This option disables the generation of mipmaps.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_NO_MOUSE_HOVER,
|
||||
.longopt = "no-mouse-hover",
|
||||
.text = "Do not forward mouse hover (mouse motion without any clicks) "
|
||||
"events.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_NO_POWER_ON,
|
||||
.longopt = "no-power-on",
|
||||
@ -716,10 +739,10 @@ static const struct sc_option options[] = {
|
||||
.text = "Specify the modifiers to use for scrcpy shortcuts.\n"
|
||||
"Possible keys are \"lctrl\", \"rctrl\", \"lalt\", \"ralt\", "
|
||||
"\"lsuper\" and \"rsuper\".\n"
|
||||
"A shortcut can consist in several keys, separated by '+'. "
|
||||
"Several shortcuts can be specified, separated by ','.\n"
|
||||
"For example, to use either LCtrl+LAlt or LSuper for scrcpy "
|
||||
"shortcuts, pass \"lctrl+lalt,lsuper\".\n"
|
||||
"Several shortcut modifiers can be specified, separated by "
|
||||
"','.\n"
|
||||
"For example, to use either LCtrl or LSuper for scrcpy "
|
||||
"shortcuts, pass \"lctrl,lsuper\".\n"
|
||||
"Default is \"lalt,lsuper\" (left-Alt or left-Super).",
|
||||
},
|
||||
{
|
||||
@ -1687,82 +1710,62 @@ parse_log_level(const char *s, enum sc_log_level *log_level) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// item is a list of mod keys separated by '+' (e.g. "lctrl+lalt")
|
||||
// returns a bitwise-or of SC_SHORTCUT_MOD_* constants (or 0 on error)
|
||||
static unsigned
|
||||
static enum sc_shortcut_mod
|
||||
parse_shortcut_mods_item(const char *item, size_t len) {
|
||||
unsigned mod = 0;
|
||||
|
||||
for (;;) {
|
||||
char *plus = strchr(item, '+');
|
||||
// strchr() does not consider the "len" parameter, to it could find an
|
||||
// occurrence too far in the string (there is no strnchr())
|
||||
bool has_plus = plus && plus < item + len;
|
||||
|
||||
assert(!has_plus || plus > item);
|
||||
size_t key_len = has_plus ? (size_t) (plus - item) : len;
|
||||
|
||||
#define STREQ(literal, s, len) \
|
||||
((sizeof(literal)-1 == len) && !memcmp(literal, s, len))
|
||||
|
||||
if (STREQ("lctrl", item, key_len)) {
|
||||
mod |= SC_SHORTCUT_MOD_LCTRL;
|
||||
} else if (STREQ("rctrl", item, key_len)) {
|
||||
mod |= SC_SHORTCUT_MOD_RCTRL;
|
||||
} else if (STREQ("lalt", item, key_len)) {
|
||||
mod |= SC_SHORTCUT_MOD_LALT;
|
||||
} else if (STREQ("ralt", item, key_len)) {
|
||||
mod |= SC_SHORTCUT_MOD_RALT;
|
||||
} else if (STREQ("lsuper", item, key_len)) {
|
||||
mod |= SC_SHORTCUT_MOD_LSUPER;
|
||||
} else if (STREQ("rsuper", item, key_len)) {
|
||||
mod |= SC_SHORTCUT_MOD_RSUPER;
|
||||
} else {
|
||||
LOGE("Unknown modifier key: %.*s "
|
||||
"(must be one of: lctrl, rctrl, lalt, ralt, lsuper, rsuper)",
|
||||
(int) key_len, item);
|
||||
return 0;
|
||||
if (STREQ("lctrl", item, len)) {
|
||||
return SC_SHORTCUT_MOD_LCTRL;
|
||||
}
|
||||
if (STREQ("rctrl", item, len)) {
|
||||
return SC_SHORTCUT_MOD_RCTRL;
|
||||
}
|
||||
if (STREQ("lalt", item, len)) {
|
||||
return SC_SHORTCUT_MOD_LALT;
|
||||
}
|
||||
if (STREQ("ralt", item, len)) {
|
||||
return SC_SHORTCUT_MOD_RALT;
|
||||
}
|
||||
if (STREQ("lsuper", item, len)) {
|
||||
return SC_SHORTCUT_MOD_LSUPER;
|
||||
}
|
||||
if (STREQ("rsuper", item, len)) {
|
||||
return SC_SHORTCUT_MOD_RSUPER;
|
||||
}
|
||||
#undef STREQ
|
||||
|
||||
if (!has_plus) {
|
||||
break;
|
||||
bool has_plus = strchr(item, '+');
|
||||
if (has_plus) {
|
||||
LOGE("Shortcut mod combination with '+' is not supported anymore: "
|
||||
"'%.*s' (see #4741)", (int) len, item);
|
||||
return 0;
|
||||
}
|
||||
|
||||
item = plus + 1;
|
||||
assert(len >= key_len + 1);
|
||||
len -= key_len + 1;
|
||||
}
|
||||
LOGE("Unknown modifier key: %.*s "
|
||||
"(must be one of: lctrl, rctrl, lalt, ralt, lsuper, rsuper)",
|
||||
(int) len, item);
|
||||
|
||||
return mod;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) {
|
||||
unsigned count = 0;
|
||||
unsigned current = 0;
|
||||
parse_shortcut_mods(const char *s, uint8_t *shortcut_mods) {
|
||||
uint8_t mods = 0;
|
||||
|
||||
// LCtrl+LAlt or RCtrl or LCtrl+RSuper: "lctrl+lalt,rctrl,lctrl+rsuper"
|
||||
// A list of shortcut modifiers, for example "lctrl,rctrl,rsuper"
|
||||
|
||||
for (;;) {
|
||||
char *comma = strchr(s, ',');
|
||||
if (comma && count == SC_MAX_SHORTCUT_MODS - 1) {
|
||||
assert(count < SC_MAX_SHORTCUT_MODS);
|
||||
LOGW("Too many shortcut modifiers alternatives");
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(!comma || comma > s);
|
||||
size_t limit = comma ? (size_t) (comma - s) : strlen(s);
|
||||
|
||||
unsigned mod = parse_shortcut_mods_item(s, limit);
|
||||
enum sc_shortcut_mod mod = parse_shortcut_mods_item(s, limit);
|
||||
if (!mod) {
|
||||
LOGE("Invalid modifier keys: %.*s", (int) limit, s);
|
||||
return false;
|
||||
}
|
||||
|
||||
mods->data[current++] = mod;
|
||||
++count;
|
||||
mods |= mod;
|
||||
|
||||
if (!comma) {
|
||||
break;
|
||||
@ -1771,7 +1774,7 @@ parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) {
|
||||
s = comma + 1;
|
||||
}
|
||||
|
||||
mods->count = count;
|
||||
*shortcut_mods = mods;
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -1779,7 +1782,7 @@ parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) {
|
||||
#ifdef SC_TEST
|
||||
// expose the function to unit-tests
|
||||
bool
|
||||
sc_parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) {
|
||||
sc_parse_shortcut_mods(const char *s, uint8_t *mods) {
|
||||
return parse_shortcut_mods(s, mods);
|
||||
}
|
||||
#endif
|
||||
@ -2058,11 +2061,63 @@ parse_pause_on_exit(const char *s, enum sc_pause_on_exit *pause_on_exit) {
|
||||
}
|
||||
|
||||
LOGE("Unsupported pause on exit mode: %s "
|
||||
"(expected true, false or if-error)", optarg);
|
||||
"(expected true, false or if-error)", s);
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_mouse_binding(char c, enum sc_mouse_binding *b) {
|
||||
switch (c) {
|
||||
case '+':
|
||||
*b = SC_MOUSE_BINDING_CLICK;
|
||||
return true;
|
||||
case '-':
|
||||
*b = SC_MOUSE_BINDING_DISABLED;
|
||||
return true;
|
||||
case 'b':
|
||||
*b = SC_MOUSE_BINDING_BACK;
|
||||
return true;
|
||||
case 'h':
|
||||
*b = SC_MOUSE_BINDING_HOME;
|
||||
return true;
|
||||
case 's':
|
||||
*b = SC_MOUSE_BINDING_APP_SWITCH;
|
||||
return true;
|
||||
case 'n':
|
||||
*b = SC_MOUSE_BINDING_EXPAND_NOTIFICATION_PANEL;
|
||||
return true;
|
||||
default:
|
||||
LOGE("Invalid mouse binding: '%c' "
|
||||
"(expected '+', '-', 'b', 'h', 's' or 'n')", c);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_mouse_bindings(const char *s, struct sc_mouse_bindings *mb) {
|
||||
if (strlen(s) != 4) {
|
||||
LOGE("Invalid mouse bindings: '%s' (expected exactly 4 characters from "
|
||||
"{'+', '-', 'b', 'h', 's', 'n'})", s);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!parse_mouse_binding(s[0], &mb->right_click)) {
|
||||
return false;
|
||||
}
|
||||
if (!parse_mouse_binding(s[1], &mb->middle_click)) {
|
||||
return false;
|
||||
}
|
||||
if (!parse_mouse_binding(s[2], &mb->click4)) {
|
||||
return false;
|
||||
}
|
||||
if (!parse_mouse_binding(s[3], &mb->click5)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
const char *optstring, const struct option *longopts) {
|
||||
@ -2145,6 +2200,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case OPT_MOUSE_BIND:
|
||||
if (!parse_mouse_bindings(optarg, &opts->mouse_bindings)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case OPT_NO_MOUSE_HOVER:
|
||||
opts->mouse_hover = false;
|
||||
break;
|
||||
case OPT_HID_MOUSE_DEPRECATED:
|
||||
LOGE("--hid-mouse has been removed, use --mouse=aoa or "
|
||||
"--mouse=uhid instead.");
|
||||
@ -2342,7 +2405,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
}
|
||||
break;
|
||||
case OPT_FORWARD_ALL_CLICKS:
|
||||
opts->forward_all_clicks = true;
|
||||
LOGW("--forward-all-clicks is deprecated, "
|
||||
"use --mouse-bind=++++ instead.");
|
||||
opts->mouse_bindings = (struct sc_mouse_bindings) {
|
||||
.right_click = SC_MOUSE_BINDING_CLICK,
|
||||
.middle_click = SC_MOUSE_BINDING_CLICK,
|
||||
.click4 = SC_MOUSE_BINDING_CLICK,
|
||||
.click5 = SC_MOUSE_BINDING_CLICK,
|
||||
};
|
||||
break;
|
||||
case OPT_LEGACY_PASTE:
|
||||
opts->legacy_paste = true;
|
||||
@ -2630,6 +2700,30 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
}
|
||||
}
|
||||
|
||||
// If mouse bindings are not explictly set, configure default bindings
|
||||
if (opts->mouse_bindings.right_click == SC_MOUSE_BINDING_AUTO) {
|
||||
assert(opts->mouse_bindings.middle_click == SC_MOUSE_BINDING_AUTO);
|
||||
assert(opts->mouse_bindings.click4 == SC_MOUSE_BINDING_AUTO);
|
||||
assert(opts->mouse_bindings.click5 == SC_MOUSE_BINDING_AUTO);
|
||||
|
||||
// By default, forward all clicks only for UHID and AOA
|
||||
if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) {
|
||||
opts->mouse_bindings = (struct sc_mouse_bindings) {
|
||||
.right_click = SC_MOUSE_BINDING_BACK,
|
||||
.middle_click = SC_MOUSE_BINDING_HOME,
|
||||
.click4 = SC_MOUSE_BINDING_APP_SWITCH,
|
||||
.click5 = SC_MOUSE_BINDING_EXPAND_NOTIFICATION_PANEL,
|
||||
};
|
||||
} else {
|
||||
opts->mouse_bindings = (struct sc_mouse_bindings) {
|
||||
.right_click = SC_MOUSE_BINDING_CLICK,
|
||||
.middle_click = SC_MOUSE_BINDING_CLICK,
|
||||
.click4 = SC_MOUSE_BINDING_CLICK,
|
||||
.click5 = SC_MOUSE_BINDING_CLICK,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (otg) {
|
||||
if (!opts->control) {
|
||||
LOGE("--no-control is not allowed in OTG mode");
|
||||
@ -2674,6 +2768,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
}
|
||||
}
|
||||
|
||||
if (opts->mouse_input_mode != SC_MOUSE_INPUT_MODE_SDK
|
||||
&& !opts->mouse_hover) {
|
||||
LOGE("--no-mouse-over is specific to --mouse=sdk");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((opts->tunnel_host || opts->tunnel_port) && !opts->force_adb_forward) {
|
||||
LOGI("Tunnel host/port is set, "
|
||||
"--force-adb-forward automatically enabled.");
|
||||
@ -2738,6 +2838,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
}
|
||||
|
||||
if (opts->record_filename) {
|
||||
if (!opts->video && !opts->audio) {
|
||||
LOGE("Video and audio disabled, nothing to record");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!opts->record_format) {
|
||||
opts->record_format = guess_record_format(opts->record_filename);
|
||||
if (!opts->record_format) {
|
||||
|
@ -28,7 +28,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]);
|
||||
|
||||
#ifdef SC_TEST
|
||||
bool
|
||||
sc_parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods);
|
||||
sc_parse_shortcut_mods(const char *s, uint8_t *shortcut_mods);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
@ -78,7 +78,19 @@ decode_image(const char *path) {
|
||||
goto close_input;
|
||||
}
|
||||
|
||||
int stream = av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
|
||||
|
||||
// In ffmpeg/doc/APIchanges:
|
||||
// 2021-04-27 - 46dac8cf3d - lavf 59.0.100 - avformat.h
|
||||
// av_find_best_stream now uses a const AVCodec ** parameter
|
||||
// for the returned decoder.
|
||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(59, 0, 100)
|
||||
const AVCodec *codec;
|
||||
#else
|
||||
AVCodec *codec;
|
||||
#endif
|
||||
|
||||
int stream =
|
||||
av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);
|
||||
if (stream < 0 ) {
|
||||
LOGE("Could not find best image stream");
|
||||
goto close_input;
|
||||
@ -86,12 +98,6 @@ decode_image(const char *path) {
|
||||
|
||||
AVCodecParameters *params = ctx->streams[stream]->codecpar;
|
||||
|
||||
const AVCodec *codec = avcodec_find_decoder(params->codec_id);
|
||||
if (!codec) {
|
||||
LOGE("Could not find image decoder");
|
||||
goto close_input;
|
||||
}
|
||||
|
||||
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
|
||||
if (!codec_ctx) {
|
||||
LOG_OOM();
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <SDL2/SDL_events.h>
|
||||
|
||||
#include "coords.h"
|
||||
#include "options.h"
|
||||
|
||||
/* The representation of input events in scrcpy is very close to the SDL API,
|
||||
* for simplicity.
|
||||
@ -437,15 +438,21 @@ sc_mouse_button_from_sdl(uint8_t button) {
|
||||
|
||||
static inline uint8_t
|
||||
sc_mouse_buttons_state_from_sdl(uint32_t buttons_state,
|
||||
bool forward_all_clicks) {
|
||||
const struct sc_mouse_bindings *mb) {
|
||||
assert(buttons_state < 0x100); // fits in uint8_t
|
||||
|
||||
uint8_t mask = SC_MOUSE_BUTTON_LEFT;
|
||||
if (forward_all_clicks) {
|
||||
mask |= SC_MOUSE_BUTTON_RIGHT
|
||||
| SC_MOUSE_BUTTON_MIDDLE
|
||||
| SC_MOUSE_BUTTON_X1
|
||||
| SC_MOUSE_BUTTON_X2;
|
||||
if (!mb || mb->right_click == SC_MOUSE_BINDING_CLICK) {
|
||||
mask |= SC_MOUSE_BUTTON_RIGHT;
|
||||
}
|
||||
if (!mb || mb->middle_click == SC_MOUSE_BINDING_CLICK) {
|
||||
mask |= SC_MOUSE_BUTTON_MIDDLE;
|
||||
}
|
||||
if (!mb || mb->click4 == SC_MOUSE_BINDING_CLICK) {
|
||||
mask |= SC_MOUSE_BUTTON_X1;
|
||||
}
|
||||
if (!mb || mb->click5 == SC_MOUSE_BINDING_CLICK) {
|
||||
mask |= SC_MOUSE_BUTTON_X2;
|
||||
}
|
||||
|
||||
return buttons_state & mask;
|
||||
|
@ -10,7 +10,7 @@
|
||||
#define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI)
|
||||
|
||||
static inline uint16_t
|
||||
to_sdl_mod(unsigned shortcut_mod) {
|
||||
to_sdl_mod(uint8_t shortcut_mod) {
|
||||
uint16_t sdl_mod = 0;
|
||||
if (shortcut_mod & SC_SHORTCUT_MOD_LCTRL) {
|
||||
sdl_mod |= KMOD_LCTRL;
|
||||
@ -38,15 +38,26 @@ is_shortcut_mod(struct sc_input_manager *im, uint16_t sdl_mod) {
|
||||
// keep only the relevant modifier keys
|
||||
sdl_mod &= SC_SDL_SHORTCUT_MODS_MASK;
|
||||
|
||||
assert(im->sdl_shortcut_mods.count);
|
||||
assert(im->sdl_shortcut_mods.count < SC_MAX_SHORTCUT_MODS);
|
||||
for (unsigned i = 0; i < im->sdl_shortcut_mods.count; ++i) {
|
||||
if (im->sdl_shortcut_mods.data[i] == sdl_mod) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// at least one shortcut mod pressed?
|
||||
return sdl_mod & im->sdl_shortcut_mods;
|
||||
}
|
||||
|
||||
return false;
|
||||
static bool
|
||||
is_shortcut_key(struct sc_input_manager *im, SDL_Keycode keycode) {
|
||||
return (im->sdl_shortcut_mods & KMOD_LCTRL && keycode == SDLK_LCTRL)
|
||||
|| (im->sdl_shortcut_mods & KMOD_RCTRL && keycode == SDLK_RCTRL)
|
||||
|| (im->sdl_shortcut_mods & KMOD_LALT && keycode == SDLK_LALT)
|
||||
|| (im->sdl_shortcut_mods & KMOD_RALT && keycode == SDLK_RALT)
|
||||
|| (im->sdl_shortcut_mods & KMOD_LGUI && keycode == SDLK_LGUI)
|
||||
|| (im->sdl_shortcut_mods & KMOD_RGUI && keycode == SDLK_RGUI);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
mouse_bindings_has_secondary_click(const struct sc_mouse_bindings *mb) {
|
||||
return mb->right_click == SC_MOUSE_BINDING_CLICK
|
||||
|| mb->middle_click == SC_MOUSE_BINDING_CLICK
|
||||
|| mb->click4 == SC_MOUSE_BINDING_CLICK
|
||||
|| mb->click5 == SC_MOUSE_BINDING_CLICK;
|
||||
}
|
||||
|
||||
void
|
||||
@ -64,19 +75,13 @@ sc_input_manager_init(struct sc_input_manager *im,
|
||||
im->kp = params->kp;
|
||||
im->mp = params->mp;
|
||||
|
||||
im->forward_all_clicks = params->forward_all_clicks;
|
||||
im->mouse_bindings = params->mouse_bindings;
|
||||
im->has_secondary_click =
|
||||
mouse_bindings_has_secondary_click(&im->mouse_bindings);
|
||||
im->legacy_paste = params->legacy_paste;
|
||||
im->clipboard_autosync = params->clipboard_autosync;
|
||||
|
||||
const struct sc_shortcut_mods *shortcut_mods = params->shortcut_mods;
|
||||
assert(shortcut_mods->count);
|
||||
assert(shortcut_mods->count < SC_MAX_SHORTCUT_MODS);
|
||||
for (unsigned i = 0; i < shortcut_mods->count; ++i) {
|
||||
uint16_t sdl_mod = to_sdl_mod(shortcut_mods->data[i]);
|
||||
assert(sdl_mod);
|
||||
im->sdl_shortcut_mods.data[i] = sdl_mod;
|
||||
}
|
||||
im->sdl_shortcut_mods.count = shortcut_mods->count;
|
||||
im->sdl_shortcut_mods = to_sdl_mod(params->shortcut_mods);
|
||||
|
||||
im->vfinger_down = false;
|
||||
im->vfinger_invert_x = false;
|
||||
@ -371,7 +376,7 @@ simulate_virtual_finger(struct sc_input_manager *im,
|
||||
msg.inject_touch_event.position.screen_size = im->screen->frame_size;
|
||||
msg.inject_touch_event.position.point = point;
|
||||
msg.inject_touch_event.pointer_id =
|
||||
im->forward_all_clicks ? POINTER_ID_VIRTUAL_MOUSE
|
||||
im->has_secondary_click ? POINTER_ID_VIRTUAL_MOUSE
|
||||
: POINTER_ID_VIRTUAL_FINGER;
|
||||
msg.inject_touch_event.pressure = up ? 0.0f : 1.0f;
|
||||
msg.inject_touch_event.action_button = 0;
|
||||
@ -412,7 +417,12 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
bool shift = event->keysym.mod & KMOD_SHIFT;
|
||||
bool repeat = event->repeat;
|
||||
|
||||
bool smod = is_shortcut_mod(im, mod);
|
||||
// Either the modifier includes a shortcut modifier, or the key
|
||||
// press/release is a modifier key.
|
||||
// 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, keycode);
|
||||
|
||||
if (down && !repeat) {
|
||||
if (keycode == im->last_keycode && mod == im->last_mod) {
|
||||
@ -424,8 +434,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
}
|
||||
}
|
||||
|
||||
// The shortcut modifier is pressed
|
||||
if (smod) {
|
||||
if (is_shortcut) {
|
||||
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
|
||||
switch (keycode) {
|
||||
case SDLK_h:
|
||||
@ -653,13 +662,12 @@ sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
|
||||
|
||||
struct sc_mouse_motion_event evt = {
|
||||
.position = sc_input_manager_get_position(im, event->x, event->y),
|
||||
.pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE
|
||||
.pointer_id = im->has_secondary_click ? POINTER_ID_MOUSE
|
||||
: POINTER_ID_GENERIC_FINGER,
|
||||
.xrel = event->xrel,
|
||||
.yrel = event->yrel,
|
||||
.buttons_state =
|
||||
sc_mouse_buttons_state_from_sdl(event->state,
|
||||
im->forward_all_clicks),
|
||||
sc_mouse_buttons_state_from_sdl(event->state, &im->mouse_bindings),
|
||||
};
|
||||
|
||||
assert(im->mp->ops->process_mouse_motion);
|
||||
@ -710,6 +718,25 @@ sc_input_manager_process_touch(struct sc_input_manager *im,
|
||||
im->mp->ops->process_touch(im->mp, &evt);
|
||||
}
|
||||
|
||||
static enum sc_mouse_binding
|
||||
sc_input_manager_get_binding(const struct sc_mouse_bindings *bindings,
|
||||
uint8_t sdl_button) {
|
||||
switch (sdl_button) {
|
||||
case SDL_BUTTON_LEFT:
|
||||
return SC_MOUSE_BINDING_CLICK;
|
||||
case SDL_BUTTON_RIGHT:
|
||||
return bindings->right_click;
|
||||
case SDL_BUTTON_MIDDLE:
|
||||
return bindings->middle_click;
|
||||
case SDL_BUTTON_X1:
|
||||
return bindings->click4;
|
||||
case SDL_BUTTON_X2:
|
||||
return bindings->click5;
|
||||
default:
|
||||
return SC_MOUSE_BINDING_DISABLED;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
||||
const SDL_MouseButtonEvent *event) {
|
||||
@ -721,35 +748,51 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
||||
bool control = im->controller;
|
||||
bool paused = im->screen->paused;
|
||||
bool down = event->type == SDL_MOUSEBUTTONDOWN;
|
||||
if (!im->forward_all_clicks) {
|
||||
if (control && !paused) {
|
||||
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
|
||||
|
||||
if (im->kp && event->button == SDL_BUTTON_X1) {
|
||||
action_app_switch(im, action);
|
||||
enum sc_mouse_binding binding =
|
||||
sc_input_manager_get_binding(&im->mouse_bindings, event->button);
|
||||
assert(binding != SC_MOUSE_BINDING_AUTO);
|
||||
switch (binding) {
|
||||
case SC_MOUSE_BINDING_DISABLED:
|
||||
// ignore click
|
||||
return;
|
||||
case SC_MOUSE_BINDING_BACK:
|
||||
if (im->kp) {
|
||||
press_back_or_turn_screen_on(im, action);
|
||||
}
|
||||
if (event->button == SDL_BUTTON_X2 && down) {
|
||||
return;
|
||||
case SC_MOUSE_BINDING_HOME:
|
||||
if (im->kp) {
|
||||
action_home(im, action);
|
||||
}
|
||||
return;
|
||||
case SC_MOUSE_BINDING_APP_SWITCH:
|
||||
if (im->kp) {
|
||||
action_app_switch(im, action);
|
||||
}
|
||||
return;
|
||||
case SC_MOUSE_BINDING_EXPAND_NOTIFICATION_PANEL:
|
||||
if (down) {
|
||||
if (event->clicks < 2) {
|
||||
expand_notification_panel(im);
|
||||
} else {
|
||||
expand_settings_panel(im);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (im->kp && event->button == SDL_BUTTON_RIGHT) {
|
||||
press_back_or_turn_screen_on(im, action);
|
||||
return;
|
||||
}
|
||||
if (im->kp && event->button == SDL_BUTTON_MIDDLE) {
|
||||
action_home(im, action);
|
||||
return;
|
||||
default:
|
||||
assert(binding == SC_MOUSE_BINDING_CLICK);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// double-click on black borders resize to fit the device screen
|
||||
// double-click on black borders resizes to fit the device screen
|
||||
bool video = im->screen->video;
|
||||
if (video && event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
|
||||
bool mouse_relative_mode = im->mp && im->mp->relative_mode;
|
||||
if (video && !mouse_relative_mode && event->button == SDL_BUTTON_LEFT
|
||||
&& event->clicks == 2) {
|
||||
int32_t x = event->x;
|
||||
int32_t y = event->y;
|
||||
sc_screen_hidpi_scale_coords(im->screen, &x, &y);
|
||||
@ -763,8 +806,6 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
||||
return;
|
||||
}
|
||||
}
|
||||
// otherwise, send the click event to the device
|
||||
}
|
||||
|
||||
if (!im->mp || paused) {
|
||||
return;
|
||||
@ -776,11 +817,10 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
||||
.position = sc_input_manager_get_position(im, event->x, event->y),
|
||||
.action = sc_action_from_sdl_mousebutton_type(event->type),
|
||||
.button = sc_mouse_button_from_sdl(event->button),
|
||||
.pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE
|
||||
.pointer_id = im->has_secondary_click ? POINTER_ID_MOUSE
|
||||
: POINTER_ID_GENERIC_FINGER,
|
||||
.buttons_state =
|
||||
sc_mouse_buttons_state_from_sdl(sdl_buttons_state,
|
||||
im->forward_all_clicks),
|
||||
.buttons_state = sc_mouse_buttons_state_from_sdl(sdl_buttons_state,
|
||||
&im->mouse_bindings),
|
||||
};
|
||||
|
||||
assert(im->mp->ops->process_mouse_click);
|
||||
@ -856,8 +896,8 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
|
||||
.hscroll = CLAMP(event->x, -1, 1),
|
||||
.vscroll = CLAMP(event->y, -1, 1),
|
||||
#endif
|
||||
.buttons_state =
|
||||
sc_mouse_buttons_state_from_sdl(buttons, im->forward_all_clicks),
|
||||
.buttons_state = sc_mouse_buttons_state_from_sdl(buttons,
|
||||
&im->mouse_bindings),
|
||||
};
|
||||
|
||||
im->mp->ops->process_mouse_scroll(im->mp, &evt);
|
||||
|
@ -22,14 +22,12 @@ struct sc_input_manager {
|
||||
struct sc_key_processor *kp;
|
||||
struct sc_mouse_processor *mp;
|
||||
|
||||
bool forward_all_clicks;
|
||||
struct sc_mouse_bindings mouse_bindings;
|
||||
bool has_secondary_click;
|
||||
bool legacy_paste;
|
||||
bool clipboard_autosync;
|
||||
|
||||
struct {
|
||||
unsigned data[SC_MAX_SHORTCUT_MODS];
|
||||
unsigned count;
|
||||
} sdl_shortcut_mods;
|
||||
uint16_t sdl_shortcut_mods;
|
||||
|
||||
bool vfinger_down;
|
||||
bool vfinger_invert_x;
|
||||
@ -52,10 +50,10 @@ struct sc_input_manager_params {
|
||||
struct sc_key_processor *kp;
|
||||
struct sc_mouse_processor *mp;
|
||||
|
||||
bool forward_all_clicks;
|
||||
struct sc_mouse_bindings mouse_bindings;
|
||||
bool legacy_paste;
|
||||
bool clipboard_autosync;
|
||||
const struct sc_shortcut_mods *shortcut_mods;
|
||||
uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values
|
||||
};
|
||||
|
||||
void
|
||||
|
@ -58,17 +58,18 @@ convert_touch_action(enum sc_touch_action action) {
|
||||
static void
|
||||
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
||||
const struct sc_mouse_motion_event *event) {
|
||||
if (!event->buttons_state) {
|
||||
struct sc_mouse_sdk *m = DOWNCAST(mp);
|
||||
|
||||
if (!m->mouse_hover && !event->buttons_state) {
|
||||
// Do not send motion events when no click is pressed
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_mouse_sdk *m = DOWNCAST(mp);
|
||||
|
||||
struct sc_control_msg msg = {
|
||||
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
||||
.inject_touch_event = {
|
||||
.action = AMOTION_EVENT_ACTION_MOVE,
|
||||
.action = event->buttons_state ? AMOTION_EVENT_ACTION_MOVE
|
||||
: AMOTION_EVENT_ACTION_HOVER_MOVE,
|
||||
.pointer_id = event->pointer_id,
|
||||
.position = event->position,
|
||||
.pressure = 1.f,
|
||||
@ -145,8 +146,10 @@ sc_mouse_processor_process_touch(struct sc_mouse_processor *mp,
|
||||
}
|
||||
|
||||
void
|
||||
sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller) {
|
||||
sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller,
|
||||
bool mouse_hover) {
|
||||
m->controller = controller;
|
||||
m->mouse_hover = mouse_hover;
|
||||
|
||||
static const struct sc_mouse_processor_ops ops = {
|
||||
.process_mouse_motion = sc_mouse_processor_process_mouse_motion,
|
||||
|
@ -13,9 +13,11 @@ struct sc_mouse_sdk {
|
||||
struct sc_mouse_processor mouse_processor; // mouse processor trait
|
||||
|
||||
struct sc_controller *controller;
|
||||
bool mouse_hover;
|
||||
};
|
||||
|
||||
void
|
||||
sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller);
|
||||
sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller,
|
||||
bool mouse_hover);
|
||||
|
||||
#endif
|
||||
|
@ -23,6 +23,12 @@ 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,
|
||||
.mouse_bindings = {
|
||||
.right_click = SC_MOUSE_BINDING_AUTO,
|
||||
.middle_click = SC_MOUSE_BINDING_AUTO,
|
||||
.click4 = SC_MOUSE_BINDING_AUTO,
|
||||
.click5 = SC_MOUSE_BINDING_AUTO,
|
||||
},
|
||||
.camera_facing = SC_CAMERA_FACING_ANY,
|
||||
.port_range = {
|
||||
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST,
|
||||
@ -30,10 +36,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
},
|
||||
.tunnel_host = 0,
|
||||
.tunnel_port = 0,
|
||||
.shortcut_mods = {
|
||||
.data = {SC_SHORTCUT_MOD_LALT, SC_SHORTCUT_MOD_LSUPER},
|
||||
.count = 2,
|
||||
},
|
||||
.shortcut_mods = SC_SHORTCUT_MOD_LALT | SC_SHORTCUT_MOD_LSUPER,
|
||||
.max_size = 0,
|
||||
.video_bit_rate = 0,
|
||||
.audio_bit_rate = 0,
|
||||
@ -71,7 +74,6 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
.force_adb_forward = false,
|
||||
.disable_screensaver = false,
|
||||
.forward_key_repeat = true,
|
||||
.forward_all_clicks = false,
|
||||
.legacy_paste = false,
|
||||
.power_off_on_close = false,
|
||||
.clipboard_autosync = true,
|
||||
@ -90,6 +92,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
.camera_high_speed = false,
|
||||
.list = 0,
|
||||
.window = true,
|
||||
.mouse_hover = true,
|
||||
};
|
||||
|
||||
enum sc_orientation
|
||||
|
@ -155,6 +155,23 @@ enum sc_mouse_input_mode {
|
||||
SC_MOUSE_INPUT_MODE_AOA,
|
||||
};
|
||||
|
||||
enum sc_mouse_binding {
|
||||
SC_MOUSE_BINDING_AUTO,
|
||||
SC_MOUSE_BINDING_DISABLED,
|
||||
SC_MOUSE_BINDING_CLICK,
|
||||
SC_MOUSE_BINDING_BACK,
|
||||
SC_MOUSE_BINDING_HOME,
|
||||
SC_MOUSE_BINDING_APP_SWITCH,
|
||||
SC_MOUSE_BINDING_EXPAND_NOTIFICATION_PANEL,
|
||||
};
|
||||
|
||||
struct sc_mouse_bindings {
|
||||
enum sc_mouse_binding right_click;
|
||||
enum sc_mouse_binding middle_click;
|
||||
enum sc_mouse_binding click4;
|
||||
enum sc_mouse_binding click5;
|
||||
};
|
||||
|
||||
enum sc_key_inject_mode {
|
||||
// Inject special keys, letters and space as key events.
|
||||
// Inject numbers and punctuation as text events.
|
||||
@ -169,8 +186,6 @@ enum sc_key_inject_mode {
|
||||
SC_KEY_INJECT_MODE_RAW,
|
||||
};
|
||||
|
||||
#define SC_MAX_SHORTCUT_MODS 8
|
||||
|
||||
enum sc_shortcut_mod {
|
||||
SC_SHORTCUT_MOD_LCTRL = 1 << 0,
|
||||
SC_SHORTCUT_MOD_RCTRL = 1 << 1,
|
||||
@ -180,11 +195,6 @@ enum sc_shortcut_mod {
|
||||
SC_SHORTCUT_MOD_RSUPER = 1 << 5,
|
||||
};
|
||||
|
||||
struct sc_shortcut_mods {
|
||||
unsigned data[SC_MAX_SHORTCUT_MODS];
|
||||
unsigned count;
|
||||
};
|
||||
|
||||
struct sc_port_range {
|
||||
uint16_t first;
|
||||
uint16_t last;
|
||||
@ -215,11 +225,12 @@ struct scrcpy_options {
|
||||
enum sc_record_format record_format;
|
||||
enum sc_keyboard_input_mode keyboard_input_mode;
|
||||
enum sc_mouse_input_mode mouse_input_mode;
|
||||
struct sc_mouse_bindings mouse_bindings;
|
||||
enum sc_camera_facing camera_facing;
|
||||
struct sc_port_range port_range;
|
||||
uint32_t tunnel_host;
|
||||
uint16_t tunnel_port;
|
||||
struct sc_shortcut_mods shortcut_mods;
|
||||
uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values
|
||||
uint16_t max_size;
|
||||
uint32_t video_bit_rate;
|
||||
uint32_t audio_bit_rate;
|
||||
@ -257,7 +268,6 @@ struct scrcpy_options {
|
||||
bool force_adb_forward;
|
||||
bool disable_screensaver;
|
||||
bool forward_key_repeat;
|
||||
bool forward_all_clicks;
|
||||
bool legacy_paste;
|
||||
bool power_off_on_close;
|
||||
bool clipboard_autosync;
|
||||
@ -280,6 +290,7 @@ struct scrcpy_options {
|
||||
#define SC_OPTION_LIST_CAMERA_SIZES 0x8
|
||||
uint8_t list;
|
||||
bool window;
|
||||
bool mouse_hover;
|
||||
};
|
||||
|
||||
extern const struct scrcpy_options scrcpy_options_default;
|
||||
|
@ -681,7 +681,8 @@ scrcpy(struct scrcpy_options *options) {
|
||||
}
|
||||
|
||||
if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) {
|
||||
sc_mouse_sdk_init(&s->mouse_sdk, &s->controller);
|
||||
sc_mouse_sdk_init(&s->mouse_sdk, &s->controller,
|
||||
options->mouse_hover);
|
||||
mp = &s->mouse_sdk.mouse_processor;
|
||||
} else if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_UHID) {
|
||||
bool ok = sc_mouse_uhid_init(&s->mouse_uhid, &s->controller);
|
||||
@ -712,10 +713,10 @@ scrcpy(struct scrcpy_options *options) {
|
||||
.fp = fp,
|
||||
.kp = kp,
|
||||
.mp = mp,
|
||||
.forward_all_clicks = options->forward_all_clicks,
|
||||
.mouse_bindings = options->mouse_bindings,
|
||||
.legacy_paste = options->legacy_paste,
|
||||
.clipboard_autosync = options->clipboard_autosync,
|
||||
.shortcut_mods = &options->shortcut_mods,
|
||||
.shortcut_mods = options->shortcut_mods,
|
||||
.window_title = window_title,
|
||||
.always_on_top = options->always_on_top,
|
||||
.window_x = options->window_x,
|
||||
|
@ -481,7 +481,7 @@ sc_screen_init(struct sc_screen *screen,
|
||||
.screen = screen,
|
||||
.kp = params->kp,
|
||||
.mp = params->mp,
|
||||
.forward_all_clicks = params->forward_all_clicks,
|
||||
.mouse_bindings = params->mouse_bindings,
|
||||
.legacy_paste = params->legacy_paste,
|
||||
.clipboard_autosync = params->clipboard_autosync,
|
||||
.shortcut_mods = params->shortcut_mods,
|
||||
|
@ -79,10 +79,10 @@ struct sc_screen_params {
|
||||
struct sc_key_processor *kp;
|
||||
struct sc_mouse_processor *mp;
|
||||
|
||||
bool forward_all_clicks;
|
||||
struct sc_mouse_bindings mouse_bindings;
|
||||
bool legacy_paste;
|
||||
bool clipboard_autosync;
|
||||
const struct sc_shortcut_mods *shortcut_mods;
|
||||
uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values
|
||||
|
||||
const char *window_title;
|
||||
bool always_on_top;
|
||||
|
@ -176,6 +176,8 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags,
|
||||
free(lpAttributeList);
|
||||
}
|
||||
|
||||
CloseHandle(pi.hThread);
|
||||
|
||||
// These handles are used by the child process, close them for this process
|
||||
if (pin) {
|
||||
CloseHandle(stdin_read_handle);
|
||||
|
@ -169,7 +169,7 @@ sc_screen_otg_process_mouse_motion(struct sc_screen_otg *screen,
|
||||
// .position not used for HID events
|
||||
.xrel = event->xrel,
|
||||
.yrel = event->yrel,
|
||||
.buttons_state = sc_mouse_buttons_state_from_sdl(event->state, true),
|
||||
.buttons_state = sc_mouse_buttons_state_from_sdl(event->state, NULL),
|
||||
};
|
||||
|
||||
assert(mp->ops->process_mouse_motion);
|
||||
@ -189,7 +189,7 @@ sc_screen_otg_process_mouse_button(struct sc_screen_otg *screen,
|
||||
.action = sc_action_from_sdl_mousebutton_type(event->type),
|
||||
.button = sc_mouse_button_from_sdl(event->button),
|
||||
.buttons_state =
|
||||
sc_mouse_buttons_state_from_sdl(sdl_buttons_state, true),
|
||||
sc_mouse_buttons_state_from_sdl(sdl_buttons_state, NULL),
|
||||
};
|
||||
|
||||
assert(mp->ops->process_mouse_click);
|
||||
@ -209,7 +209,7 @@ sc_screen_otg_process_mouse_wheel(struct sc_screen_otg *screen,
|
||||
.hscroll = event->x,
|
||||
.vscroll = event->y,
|
||||
.buttons_state =
|
||||
sc_mouse_buttons_state_from_sdl(sdl_buttons_state, true),
|
||||
sc_mouse_buttons_state_from_sdl(sdl_buttons_state, NULL),
|
||||
};
|
||||
|
||||
assert(mp->ops->process_mouse_scroll);
|
||||
|
@ -46,6 +46,9 @@ sc_audiobuf_read(struct sc_audiobuf *buf, void *to_, uint32_t samples_count) {
|
||||
uint32_t head = atomic_load_explicit(&buf->head, memory_order_acquire);
|
||||
|
||||
uint32_t can_read = (buf->alloc_size + head - tail) % buf->alloc_size;
|
||||
if (!can_read) {
|
||||
return 0;
|
||||
}
|
||||
if (samples_count > can_read) {
|
||||
samples_count = can_read;
|
||||
}
|
||||
@ -86,6 +89,9 @@ sc_audiobuf_write(struct sc_audiobuf *buf, const void *from_,
|
||||
uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_acquire);
|
||||
|
||||
uint32_t can_write = (buf->alloc_size + tail - head - 1) % buf->alloc_size;
|
||||
if (!can_write) {
|
||||
return 0;
|
||||
}
|
||||
if (samples_count > can_write) {
|
||||
samples_count = can_write;
|
||||
}
|
||||
|
@ -124,32 +124,22 @@ static void test_options2(void) {
|
||||
}
|
||||
|
||||
static void test_parse_shortcut_mods(void) {
|
||||
struct sc_shortcut_mods mods;
|
||||
uint8_t mods;
|
||||
bool ok;
|
||||
|
||||
ok = sc_parse_shortcut_mods("lctrl", &mods);
|
||||
assert(ok);
|
||||
assert(mods.count == 1);
|
||||
assert(mods.data[0] == SC_SHORTCUT_MOD_LCTRL);
|
||||
|
||||
ok = sc_parse_shortcut_mods("lctrl+lalt", &mods);
|
||||
assert(ok);
|
||||
assert(mods.count == 1);
|
||||
assert(mods.data[0] == (SC_SHORTCUT_MOD_LCTRL | SC_SHORTCUT_MOD_LALT));
|
||||
assert(mods == SC_SHORTCUT_MOD_LCTRL);
|
||||
|
||||
ok = sc_parse_shortcut_mods("rctrl,lalt", &mods);
|
||||
assert(ok);
|
||||
assert(mods.count == 2);
|
||||
assert(mods.data[0] == SC_SHORTCUT_MOD_RCTRL);
|
||||
assert(mods.data[1] == SC_SHORTCUT_MOD_LALT);
|
||||
assert(mods == (SC_SHORTCUT_MOD_RCTRL | SC_SHORTCUT_MOD_LALT));
|
||||
|
||||
ok = sc_parse_shortcut_mods("lsuper,rsuper+lalt,lctrl+rctrl+ralt", &mods);
|
||||
ok = sc_parse_shortcut_mods("lsuper,rsuper,lctrl", &mods);
|
||||
assert(ok);
|
||||
assert(mods.count == 3);
|
||||
assert(mods.data[0] == SC_SHORTCUT_MOD_LSUPER);
|
||||
assert(mods.data[1] == (SC_SHORTCUT_MOD_RSUPER | SC_SHORTCUT_MOD_LALT));
|
||||
assert(mods.data[2] == (SC_SHORTCUT_MOD_LCTRL | SC_SHORTCUT_MOD_RCTRL |
|
||||
SC_SHORTCUT_MOD_RALT));
|
||||
assert(mods == (SC_SHORTCUT_MOD_LSUPER
|
||||
| SC_SHORTCUT_MOD_RSUPER
|
||||
| SC_SHORTCUT_MOD_LCTRL));
|
||||
|
||||
ok = sc_parse_shortcut_mods("", &mods);
|
||||
assert(!ok);
|
||||
|
@ -7,7 +7,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.1.3'
|
||||
classpath 'com.android.tools.build:gradle:8.3.0'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
@ -106,15 +106,6 @@ only inverts _x_.
|
||||
This only works for the default mouse mode (`--mouse=sdk`).
|
||||
|
||||
|
||||
## Right-click and middle-click
|
||||
|
||||
By default, right-click triggers BACK (or POWER on) and middle-click triggers
|
||||
HOME. To disable these shortcuts and forward the clicks to the device instead:
|
||||
|
||||
```bash
|
||||
scrcpy --forward-all-clicks
|
||||
```
|
||||
|
||||
## File drop
|
||||
|
||||
### Install APK
|
||||
|
48
doc/mouse.md
48
doc/mouse.md
@ -18,6 +18,14 @@ Note that on some devices, an additional option must be enabled in developer
|
||||
options for this mouse mode to work. See
|
||||
[prerequisites](/README.md#prerequisites).
|
||||
|
||||
### Mouse hover
|
||||
|
||||
By default, mouse hover (mouse motion without any clicks) events are forwarded
|
||||
to the device. This can be disabled with:
|
||||
|
||||
```
|
||||
scrcpy --no-mouse-hover
|
||||
```
|
||||
|
||||
## Physical mouse simulation
|
||||
|
||||
@ -68,3 +76,43 @@ 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_).
|
||||
|
||||
|
||||
## Mouse bindings
|
||||
|
||||
By default, with SDK mouse, right-click triggers BACK (or POWER on) and
|
||||
middle-click triggers HOME. In addition, the 4th click triggers APP_SWITCH and
|
||||
the 5th click expands the notification panel.
|
||||
|
||||
In AOA and UHID mouse modes, all clicks are forwarded by default.
|
||||
|
||||
The shortcuts can be configured using `--mouse-bind=xxxx` for any mouse mode.
|
||||
The argument must be exactly 4 characters, one for each secondary click:
|
||||
|
||||
```
|
||||
--mouse-bind=xxxx
|
||||
^^^^
|
||||
||||
|
||||
||| `- 5th click
|
||||
|| `-- 4th click
|
||||
| `--- middle click
|
||||
`---- right click
|
||||
```
|
||||
|
||||
Each character must be one of the following:
|
||||
|
||||
- `+`: forward the click to the device
|
||||
- `-`: ignore the click
|
||||
- `b`: trigger shortcut BACK (or turn screen on if off)
|
||||
- `h`: trigger shortcut HOME
|
||||
- `s`: trigger shortcut APP_SWITCH
|
||||
- `n`: trigger shortcut "expand notification panel"
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
scrcpy --mouse-bind=bhsn # the default mode with SDK mouse
|
||||
scrcpy --mouse-bind=++++ # forward all clicks (default for AOA/UHID)
|
||||
scrcpy --mouse-bind=++bh # forward right and middle clicks,
|
||||
# use 4th and 5th for BACK and HOME
|
||||
```
|
||||
|
@ -13,8 +13,8 @@ It can be changed using `--shortcut-mod`. Possible keys are `lctrl`, `rctrl`,
|
||||
# use RCtrl for shortcuts
|
||||
scrcpy --shortcut-mod=rctrl
|
||||
|
||||
# use either LCtrl+LAlt or LSuper for shortcuts
|
||||
scrcpy --shortcut-mod=lctrl+lalt,lsuper
|
||||
# use either LCtrl or LSuper for shortcuts
|
||||
scrcpy --shortcut-mod=lctrl,lsuper
|
||||
```
|
||||
|
||||
_<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
|
||||
|
@ -45,18 +45,18 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList
|
||||
}
|
||||
|
||||
try {
|
||||
display = createDisplay();
|
||||
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
|
||||
Ln.d("Display: using SurfaceControl API");
|
||||
} catch (Exception surfaceControlException) {
|
||||
Rect videoRect = screenInfo.getVideoSize().toRect();
|
||||
try {
|
||||
virtualDisplay = ServiceManager.getDisplayManager()
|
||||
.createVirtualDisplay("scrcpy", videoRect.width(), videoRect.height(), device.getDisplayId(), surface);
|
||||
Ln.d("Display: using DisplayManager API");
|
||||
} catch (Exception displayManagerException) {
|
||||
Ln.e("Could not create display using SurfaceControl", surfaceControlException);
|
||||
try {
|
||||
display = createDisplay();
|
||||
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
|
||||
Ln.d("Display: using SurfaceControl API");
|
||||
} catch (Exception surfaceControlException) {
|
||||
Ln.e("Could not create display using DisplayManager", displayManagerException);
|
||||
Ln.e("Could not create display using SurfaceControl", surfaceControlException);
|
||||
throw new AssertionError("Could not create display");
|
||||
}
|
||||
}
|
||||
@ -68,6 +68,11 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList
|
||||
device.setFoldListener(null);
|
||||
if (display != null) {
|
||||
SurfaceControl.destroyDisplay(display);
|
||||
display = null;
|
||||
}
|
||||
if (virtualDisplay != null) {
|
||||
virtualDisplay.release();
|
||||
virtualDisplay = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,7 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||
this.downsizeOnError = downsizeOnError;
|
||||
}
|
||||
|
||||
private void streamScreen() throws IOException, ConfigurationException {
|
||||
private void streamCapture() throws IOException, ConfigurationException {
|
||||
Codec codec = streamer.getCodec();
|
||||
MediaCodec mediaCodec = createMediaCodec(codec, encoderName);
|
||||
MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions);
|
||||
@ -254,7 +254,7 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||
Looper.prepare();
|
||||
|
||||
try {
|
||||
streamScreen();
|
||||
streamCapture();
|
||||
} catch (ConfigurationException e) {
|
||||
// Do not print stack trace, a user-friendly error-message has already been logged
|
||||
} catch (IOException e) {
|
||||
|
Reference in New Issue
Block a user