diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index e6b2c91a..f00fadae 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -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 diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index db04ca10..86fe0b0e 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -32,7 +32,6 @@ 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)' @@ -47,6 +46,7 @@ arguments=( '-M[Use UHID mouse (same as --mouse=uhid)]' '--max-fps=[Limit the frame rate of screen capture]' '--mouse=[Set the mouse input mode]:mode:(disabled sdk uhid aoa)' + '--mouse-bind=[Configure bindings of secondary clicks]' {-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]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 2be9ef59..7a0b3dfb 100644 --- a/app/scrcpy.1 +++ b/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'. + .TP .B \-n, \-\-no\-control diff --git a/app/src/cli.c b/app/src/cli.c index a2eb4254..35230a9a 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -98,6 +98,7 @@ enum { OPT_HID_KEYBOARD_DEPRECATED, OPT_HID_MOUSE_DEPRECATED, OPT_NO_WINDOW, + OPT_MOUSE_BIND, }; struct sc_option { @@ -352,11 +353,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 +489,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'.", + }, { .shortopt = 'n', .longopt = "no-control", @@ -2043,6 +2059,58 @@ parse_pause_on_exit(const char *s, enum sc_pause_on_exit *pause_on_exit) { } +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) { @@ -2125,6 +2193,11 @@ 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_HID_MOUSE_DEPRECATED: LOGE("--hid-mouse has been removed, use --mouse=aoa or " "--mouse=uhid instead."); @@ -2322,7 +2395,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; diff --git a/app/src/input_events.h b/app/src/input_events.h index 5831ba0f..ed77bcb4 100644 --- a/app/src/input_events.h +++ b/app/src/input_events.h @@ -9,6 +9,7 @@ #include #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; diff --git a/app/src/input_manager.c b/app/src/input_manager.c index f5f7992a..3166fbff 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -52,6 +52,14 @@ is_shortcut_key(struct sc_input_manager *im, SDL_Keycode keycode) { || (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 sc_input_manager_init(struct sc_input_manager *im, const struct sc_input_manager_params *params) { @@ -67,7 +75,9 @@ 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; @@ -366,8 +376,8 @@ 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 - : POINTER_ID_VIRTUAL_FINGER; + 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; msg.inject_touch_event.buttons = 0; @@ -652,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_GENERIC_FINGER, + .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); @@ -709,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) { @@ -720,28 +748,42 @@ 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 (control && !paused && !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); - return; - } - if (event->button == SDL_BUTTON_X2 && 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; + enum sc_mouse_binding binding = + sc_input_manager_get_binding(&im->mouse_bindings, event->button); + 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); + } + 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; + default: + assert(binding == SC_MOUSE_BINDING_CLICK); + break; } } @@ -774,11 +816,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_GENERIC_FINGER, - .buttons_state = - sc_mouse_buttons_state_from_sdl(sdl_buttons_state, - im->forward_all_clicks), + .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->mouse_bindings), }; assert(im->mp->ops->process_mouse_click); @@ -854,8 +895,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); diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 8c45c165..03c42fe6 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -22,7 +22,8 @@ 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; @@ -49,7 +50,7 @@ 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; uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values diff --git a/app/src/options.c b/app/src/options.c index 4b75ed6a..939108cf 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -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_BACK, + .middle_click = SC_MOUSE_BINDING_HOME, + .click4 = SC_MOUSE_BINDING_APP_SWITCH, + .click5 = SC_MOUSE_BINDING_EXPAND_NOTIFICATION_PANEL, + }, .camera_facing = SC_CAMERA_FACING_ANY, .port_range = { .first = DEFAULT_LOCAL_PORT_RANGE_FIRST, @@ -68,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, diff --git a/app/src/options.h b/app/src/options.h index 85817341..17615c75 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -155,6 +155,22 @@ enum sc_mouse_input_mode { SC_MOUSE_INPUT_MODE_AOA, }; +enum sc_mouse_binding { + 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. @@ -208,6 +224,7 @@ struct scrcpy_options { enum sc_record_format record_format; enum sc_keyboard_input_mode keyboard_input_mode; enum sc_mouse_input_mode mouse_input_mode; + struct sc_mouse_bindings mouse_bindings; enum sc_camera_facing camera_facing; struct sc_port_range port_range; uint32_t tunnel_host; @@ -250,7 +267,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; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 5f13ee53..85b89935 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -712,7 +712,7 @@ 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, diff --git a/app/src/screen.c b/app/src/screen.c index 56f13f99..55a06ab3 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -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, diff --git a/app/src/screen.h b/app/src/screen.h index 437e7633..079d4fbb 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -79,7 +79,7 @@ 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; uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values diff --git a/app/src/usb/screen_otg.c b/app/src/usb/screen_otg.c index e1d5cb01..33500e0c 100644 --- a/app/src/usb/screen_otg.c +++ b/app/src/usb/screen_otg.c @@ -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); diff --git a/doc/control.md b/doc/control.md index 87897894..34eb7a6a 100644 --- a/doc/control.md +++ b/doc/control.md @@ -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 diff --git a/doc/mouse.md b/doc/mouse.md index d0342954..146956f5 100644 --- a/doc/mouse.md +++ b/doc/mouse.md @@ -68,3 +68,41 @@ 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, 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. + +The shortcuts can be configured using `--mouse-bind=xxxx`. 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 +scrcpy --mouse-bind=++++ # forward all clicks +scrcpy --mouse-bind=++bh # forward right and middle clicks, + # use 4th and 5th for BACK and HOME +```