Customize shortcut modifier

Add --shortcut-mod, and use Alt as default modifier.

This paves the way to forward the Ctrl key to the device.
This commit is contained in:
Romain Vimont 2020-07-17 00:00:42 +02:00
parent 63cb93d7d7
commit 1b76d9fd78
10 changed files with 392 additions and 109 deletions

View file

@ -354,7 +354,7 @@ scrcpy --fullscreen
scrcpy -f # short version scrcpy -f # short version
``` ```
Fullscreen can then be toggled dynamically with `Ctrl`+`f`. Fullscreen can then be toggled dynamically with `MOD`+`f`.
#### Rotation #### Rotation
@ -370,17 +370,17 @@ Possibles values are:
- `2`: 180 degrees - `2`: 180 degrees
- `3`: 90 degrees clockwise - `3`: 90 degrees clockwise
The rotation can also be changed dynamically with `Ctrl`+`←` _(left)_ and The rotation can also be changed dynamically with `MOD`+`←` _(left)_ and
`Ctrl`+`→` _(right)_. `MOD`+`→` _(right)_.
Note that _scrcpy_ manages 3 different rotations: Note that _scrcpy_ manages 3 different rotations:
- `Ctrl`+`r` requests the device to switch between portrait and landscape (the - `MOD`+`r` requests the device to switch between portrait and landscape (the
current running app may refuse, if it does support the requested current running app may refuse, if it does support the requested
orientation). orientation).
- `--lock-video-orientation` changes the mirroring orientation (the orientation - `--lock-video-orientation` changes the mirroring orientation (the orientation
of the video sent from the device to the computer). This affects the of the video sent from the device to the computer). This affects the
recording. recording.
- `--rotation` (or `Ctrl`+`←`/`Ctrl`+`→`) rotates only the window content. This - `--rotation` (or `MOD`+`←`/`MOD`+`→`) rotates only the window content. This
affects only the display, not the recording. affects only the display, not the recording.
@ -437,9 +437,9 @@ scrcpy --turn-screen-off
scrcpy -S scrcpy -S
``` ```
Or by pressing `Ctrl`+`o` at any time. Or by pressing `MOD`+`o` at any time.
To turn it back on, press `Ctrl`+`Shift`+`o` (or `POWER`, `Ctrl`+`p`). To turn it back on, press `MOD`+`Shift`+`o` (or `POWER`, `MOD`+`p`).
It can be useful to also prevent the device to sleep: It can be useful to also prevent the device to sleep:
@ -494,7 +494,7 @@ scrcpy --disable-screensaver
#### Rotate device screen #### Rotate device screen
Press `Ctrl`+`r` to switch between portrait and landscape modes. Press `MOD`+`r` to switch between portrait and landscape modes.
Note that it rotates only if the application in foreground supports the Note that it rotates only if the application in foreground supports the
requested orientation. requested orientation.
@ -504,10 +504,10 @@ requested orientation.
It is possible to synchronize clipboards between the computer and the device, in It is possible to synchronize clipboards between the computer and the device, in
both directions: both directions:
- `Ctrl`+`c` copies the device clipboard to the computer clipboard; - `MOD`+`c` copies the device clipboard to the computer clipboard;
- `Ctrl`+`Shift`+`v` copies the computer clipboard to the device clipboard (and - `MOD`+`Shift`+`v` copies the computer clipboard to the device clipboard (and
pastes if the device runs Android >= 7); pastes if the device runs Android >= 7);
- `Ctrl`+`v` _pastes_ the computer clipboard as a sequence of text events (but - `MOD`+`v` _pastes_ the computer clipboard as a sequence of text events (but
breaks non-ASCII characters). breaks non-ASCII characters).
Moreover, any time the Android clipboard changes, it is automatically Moreover, any time the Android clipboard changes, it is automatically
@ -571,30 +571,48 @@ Also see [issue #14].
## Shortcuts ## Shortcuts
In the following list, `MOD` is the shortcut modifier. By default, it's (left)
`Alt`.
It can be changed using `--shortcut-mod`. Possible keys are `lctrl`, `rctrl`,
`lalt`, `ralt`, `lsuper` and `rsuper`. For example:
```bash
# use RCtrl for shortcuts
scrcpy --shortcut-mod=rctrl
# use either LCtrl+LAlt or LSuper for shortcuts
scrcpy --shortcut-mod=lctrl+lalt,lsuper
```
_[Super] is typically the "Windows" or "Cmd" key._
[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button)
| Action | Shortcut | Action | Shortcut
| ------------------------------------------- |:----------------------------- | ------------------------------------------- |:-----------------------------
| Switch fullscreen mode | `Ctrl`+`f` | Switch fullscreen mode | `MOD`+`f`
| Rotate display left | `Ctrl`+`←` _(left)_ | Rotate display left | `MOD`+`←` _(left)_
| Rotate display right | `Ctrl`+`→` _(right)_ | Rotate display right | `MOD`+`→` _(right)_
| Resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` | Resize window to 1:1 (pixel-perfect) | `MOD`+`g`
| Resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ | Resize window to remove black borders | `MOD`+`x` \| _Double-click¹_
| Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ | Click on `HOME` | `MOD`+`h` \| _Middle-click_
| Click on `BACK` | `Ctrl`+`b` \| _Right-click²_ | Click on `BACK` | `MOD`+`b` \| _Right-click²_
| Click on `APP_SWITCH` | `Ctrl`+`s` | Click on `APP_SWITCH` | `MOD`+`s`
| Click on `MENU` | `Ctrl`+`m` | Click on `MENU` | `MOD`+`m`
| Click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ | Click on `VOLUME_UP` | `MOD`+`↑` _(up)_
| Click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ | Click on `VOLUME_DOWN` | `MOD`+`↓` _(down)_
| Click on `POWER` | `Ctrl`+`p` | Click on `POWER` | `MOD`+`p`
| Power on | _Right-click²_ | Power on | _Right-click²_
| Turn device screen off (keep mirroring) | `Ctrl`+`o` | Turn device screen off (keep mirroring) | `MOD`+`o`
| Turn device screen on | `Ctrl`+`Shift`+`o` | Turn device screen on | `MOD`+`Shift`+`o`
| Rotate device screen | `Ctrl`+`r` | Rotate device screen | `MOD`+`r`
| Expand notification panel | `Ctrl`+`n` | Expand notification panel | `MOD`+`n`
| Collapse notification panel | `Ctrl`+`Shift`+`n` | Collapse notification panel | `MOD`+`Shift`+`n`
| Copy device clipboard to computer | `Ctrl`+`c` | Copy device clipboard to computer | `MOD`+`c`
| Paste computer clipboard to device | `Ctrl`+`v` | Paste computer clipboard to device | `MOD`+`v`
| Copy computer clipboard to device and paste | `Ctrl`+`Shift`+`v` | Copy computer clipboard to device and paste | `MOD`+`Shift`+`v`
| Enable/disable FPS counter (on stdout) | `Ctrl`+`i` | Enable/disable FPS counter (on stdout) | `MOD`+`i`
_¹Double-click on black borders to remove them._ _¹Double-click on black borders to remove them._
_²Right-click turns the screen on if it was off, presses BACK otherwise._ _²Right-click turns the screen on if it was off, presses BACK otherwise._

View file

@ -186,7 +186,7 @@ if get_option('buildtype') == 'debug'
exe = executable(t[0], t[1], exe = executable(t[0], t[1],
include_directories: src_dir, include_directories: src_dir,
dependencies: dependencies, dependencies: dependencies,
c_args: ['-DSDL_MAIN_HANDLED']) c_args: ['-DSDL_MAIN_HANDLED', '-DSC_TEST'])
test(t[0], exe) test(t[0], exe)
endforeach endforeach
endif endif

View file

@ -149,6 +149,16 @@ Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each incre
.BI "\-s, \-\-serial " number .BI "\-s, \-\-serial " number
The device serial number. Mandatory only if several devices are connected to adb. The device serial number. Mandatory only if several devices are connected to adb.
.TP
.BI "\-\-shortcut\-mod " key[+...]][,...]
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 ','.
For example, to use either LCtrl+LAlt or LSuper for scrcpy shortcuts, pass "lctrl+lalt,lsuper".
Default is "lalt" (left-Alt).
.TP .TP
.B \-S, \-\-turn\-screen\-off .B \-S, \-\-turn\-screen\-off
Turn the device screen off immediately. Turn the device screen off immediately.
@ -207,52 +217,55 @@ Default is 0 (automatic).\n
.SH SHORTCUTS .SH SHORTCUTS
In the following list, MOD is the shortcut modifier. By default, it's (left)
Alt, but it can be configured by \-\-shortcut-mod.
.TP .TP
.B Ctrl+f .B MOD+f
Switch fullscreen mode Switch fullscreen mode
.TP .TP
.B Ctrl+Left .B MOD+Left
Rotate display left Rotate display left
.TP .TP
.B Ctrl+Right .B MOD+Right
Rotate display right Rotate display right
.TP .TP
.B Ctrl+g .B MOD+g
Resize window to 1:1 (pixel\-perfect) Resize window to 1:1 (pixel\-perfect)
.TP .TP
.B Ctrl+x, Double\-click on black borders .B MOD+x, Double\-click on black borders
Resize window to remove black borders Resize window to remove black borders
.TP .TP
.B Ctrl+h, Home, Middle\-click .B MOD+h, Home, Middle\-click
Click on HOME Click on HOME
.TP .TP
.B Ctrl+b, Ctrl+Backspace, Right\-click (when screen is on) .B MOD+b, MOD+Backspace, Right\-click (when screen is on)
Click on BACK Click on BACK
.TP .TP
.B Ctrl+s .B MOD+s
Click on APP_SWITCH Click on APP_SWITCH
.TP .TP
.B Ctrl+m .B MOD+m
Click on MENU Click on MENU
.TP .TP
.B Ctrl+Up .B MOD+Up
Click on VOLUME_UP Click on VOLUME_UP
.TP .TP
.B Ctrl+Down .B MOD+Down
Click on VOLUME_DOWN Click on VOLUME_DOWN
.TP .TP
.B Ctrl+p .B MOD+p
Click on POWER (turn screen on/off) Click on POWER (turn screen on/off)
.TP .TP
@ -260,39 +273,39 @@ Click on POWER (turn screen on/off)
Turn screen on Turn screen on
.TP .TP
.B Ctrl+o .B MOD+o
Turn device screen off (keep mirroring) Turn device screen off (keep mirroring)
.TP .TP
.B Ctrl+Shift+o .B MOD+Shift+o
Turn device screen on Turn device screen on
.TP .TP
.B Ctrl+r .B MOD+r
Rotate device screen Rotate device screen
.TP .TP
.B Ctrl+n .B MOD+n
Expand notification panel Expand notification panel
.TP .TP
.B Ctrl+Shift+n .B MOD+Shift+n
Collapse notification panel Collapse notification panel
.TP .TP
.B Ctrl+c .B MOD+c
Copy device clipboard to computer Copy device clipboard to computer
.TP .TP
.B Ctrl+v .B MOD+v
Paste computer clipboard to device Paste computer clipboard to device
.TP .TP
.B Ctrl+Shift+v .B MOD+Shift+v
Copy computer clipboard to device (and paste if the device runs Android >= 7) Copy computer clipboard to device (and paste if the device runs Android >= 7)
.TP .TP
.B Ctrl+i .B MOD+i
Enable/disable FPS counter (print frames/second in logs) Enable/disable FPS counter (print frames/second in logs)
.TP .TP

View file

@ -138,6 +138,19 @@ scrcpy_print_usage(const char *arg0) {
" The device serial number. Mandatory only if several devices\n" " The device serial number. Mandatory only if several devices\n"
" are connected to adb.\n" " are connected to adb.\n"
"\n" "\n"
" --shortcut-mod key[+...]][,...]\n"
" Specify the modifiers to use for scrcpy shortcuts.\n"
" Possible keys are \"lctrl\", \"rctrl\", \"lalt\", \"ralt\",\n"
" \"lsuper\" and \"rsuper\".\n"
"\n"
" A shortcut can consist in several keys, separated by '+'.\n"
" Several shortcuts can be specified, separated by ','.\n"
"\n"
" For example, to use either LCtrl+LAlt or LSuper for scrcpy\n"
" shortcuts, pass \"lctrl+lalt,lsuper\".\n"
"\n"
" Default is \"lalt\" (left-Alt).\n"
"\n"
" -S, --turn-screen-off\n" " -S, --turn-screen-off\n"
" Turn the device screen off immediately.\n" " Turn the device screen off immediately.\n"
"\n" "\n"
@ -185,75 +198,78 @@ scrcpy_print_usage(const char *arg0) {
"\n" "\n"
"Shortcuts:\n" "Shortcuts:\n"
"\n" "\n"
" Ctrl+f\n" " In the following list, MOD is the shortcut modifier. By default,\n"
" it's (left) Alt, but it can be configured by --shortcut-mod.\n"
"\n"
" MOD+f\n"
" Switch fullscreen mode\n" " Switch fullscreen mode\n"
"\n" "\n"
" Ctrl+Left\n" " MOD+Left\n"
" Rotate display left\n" " Rotate display left\n"
"\n" "\n"
" Ctrl+Right\n" " MOD+Right\n"
" Rotate display right\n" " Rotate display right\n"
"\n" "\n"
" Ctrl+g\n" " MOD+g\n"
" Resize window to 1:1 (pixel-perfect)\n" " Resize window to 1:1 (pixel-perfect)\n"
"\n" "\n"
" Ctrl+x\n" " MOD+x\n"
" Double-click on black borders\n" " Double-click on black borders\n"
" Resize window to remove black borders\n" " Resize window to remove black borders\n"
"\n" "\n"
" Ctrl+h\n" " MOD+h\n"
" Middle-click\n" " Middle-click\n"
" Click on HOME\n" " Click on HOME\n"
"\n" "\n"
" Ctrl+b\n" " MOD+b\n"
" Ctrl+Backspace\n" " MOD+Backspace\n"
" Right-click (when screen is on)\n" " Right-click (when screen is on)\n"
" Click on BACK\n" " Click on BACK\n"
"\n" "\n"
" Ctrl+s\n" " MOD+s\n"
" Click on APP_SWITCH\n" " Click on APP_SWITCH\n"
"\n" "\n"
" Ctrl+m\n" " MOD+m\n"
" Click on MENU\n" " Click on MENU\n"
"\n" "\n"
" Ctrl+Up\n" " MOD+Up\n"
" Click on VOLUME_UP\n" " Click on VOLUME_UP\n"
"\n" "\n"
" Ctrl+Down\n" " MOD+Down\n"
" Click on VOLUME_DOWN\n" " Click on VOLUME_DOWN\n"
"\n" "\n"
" Ctrl+p\n" " MOD+p\n"
" Click on POWER (turn screen on/off)\n" " Click on POWER (turn screen on/off)\n"
"\n" "\n"
" Right-click (when screen is off)\n" " Right-click (when screen is off)\n"
" Power on\n" " Power on\n"
"\n" "\n"
" Ctrl+o\n" " MOD+o\n"
" Turn device screen off (keep mirroring)\n" " Turn device screen off (keep mirroring)\n"
"\n" "\n"
" Ctrl+Shift+o\n" " MOD+Shift+o\n"
" Turn device screen on\n" " Turn device screen on\n"
"\n" "\n"
" Ctrl+r\n" " MOD+r\n"
" Rotate device screen\n" " Rotate device screen\n"
"\n" "\n"
" Ctrl+n\n" " MOD+n\n"
" Expand notification panel\n" " Expand notification panel\n"
"\n" "\n"
" Ctrl+Shift+n\n" " MOD+Shift+n\n"
" Collapse notification panel\n" " Collapse notification panel\n"
"\n" "\n"
" Ctrl+c\n" " MOD+c\n"
" Copy device clipboard to computer\n" " Copy device clipboard to computer\n"
"\n" "\n"
" Ctrl+v\n" " MOD+v\n"
" Paste computer clipboard to device\n" " Paste computer clipboard to device\n"
"\n" "\n"
" Ctrl+Shift+v\n" " MOD+Shift+v\n"
" Copy computer clipboard to device (and paste if the device\n" " Copy computer clipboard to device (and paste if the device\n"
" runs Android >= 7)\n" " runs Android >= 7)\n"
"\n" "\n"
" Ctrl+i\n" " MOD+i\n"
" Enable/disable FPS counter (print frames/second in logs)\n" " Enable/disable FPS counter (print frames/second in logs)\n"
"\n" "\n"
" Drag & drop APK file\n" " Drag & drop APK file\n"
@ -475,6 +491,101 @@ parse_log_level(const char *s, enum sc_log_level *log_level) {
return false; return false;
} }
// item is a list of mod keys separated by '+' (e.g. "lctrl+lalt")
// returns a bitwise-or of SC_MOD_* constants (or 0 on error)
static unsigned
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_MOD_LCTRL;
} else if (STREQ("rctrl", item, key_len)) {
mod |= SC_MOD_RCTRL;
} else if (STREQ("lalt", item, key_len)) {
mod |= SC_MOD_LALT;
} else if (STREQ("ralt", item, key_len)) {
mod |= SC_MOD_RALT;
} else if (STREQ("lsuper", item, key_len)) {
mod |= SC_MOD_LSUPER;
} else if (STREQ("rsuper", item, key_len)) {
mod |= SC_MOD_RSUPER;
} else {
LOGW("Unknown modifier key: %.*s", (int) key_len, item);
return 0;
}
#undef STREQ
if (!has_plus) {
break;
}
item = plus + 1;
assert(len >= key_len + 1);
len -= key_len + 1;
}
return mod;
}
static bool
parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) {
unsigned count = 0;
unsigned current = 0;
// LCtrl+LAlt or RCtrl or LCtrl+RSuper: "lctrl+lalt,rctrl,lctrl+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);
if (!mod) {
LOGE("Invalid modifier keys: %.*s", (int) limit, s);
return false;
}
mods->data[current++] = mod;
++count;
if (!comma) {
break;
}
s = comma + 1;
}
mods->count = count;
return true;
}
#ifdef SC_TEST
// expose the function to unit-tests
bool
sc_parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) {
return parse_shortcut_mods(s, mods);
}
#endif
static bool static bool
parse_record_format(const char *optarg, enum sc_record_format *format) { parse_record_format(const char *optarg, enum sc_record_format *format) {
if (!strcmp(optarg, "mp4")) { if (!strcmp(optarg, "mp4")) {
@ -526,6 +637,7 @@ guess_record_format(const char *filename) {
#define OPT_CODEC_OPTIONS 1018 #define OPT_CODEC_OPTIONS 1018
#define OPT_FORCE_ADB_FORWARD 1019 #define OPT_FORCE_ADB_FORWARD 1019
#define OPT_DISABLE_SCREENSAVER 1020 #define OPT_DISABLE_SCREENSAVER 1020
#define OPT_SHORTCUT_MOD 1021
bool bool
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
@ -558,6 +670,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
OPT_RENDER_EXPIRED_FRAMES}, OPT_RENDER_EXPIRED_FRAMES},
{"rotation", required_argument, NULL, OPT_ROTATION}, {"rotation", required_argument, NULL, OPT_ROTATION},
{"serial", required_argument, NULL, 's'}, {"serial", required_argument, NULL, 's'},
{"shortcut-mod", required_argument, NULL, OPT_SHORTCUT_MOD},
{"show-touches", no_argument, NULL, 't'}, {"show-touches", no_argument, NULL, 't'},
{"stay-awake", no_argument, NULL, 'w'}, {"stay-awake", no_argument, NULL, 'w'},
{"turn-screen-off", no_argument, NULL, 'S'}, {"turn-screen-off", no_argument, NULL, 'S'},
@ -721,6 +834,11 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
case OPT_DISABLE_SCREENSAVER: case OPT_DISABLE_SCREENSAVER:
opts->disable_screensaver = true; opts->disable_screensaver = true;
break; break;
case OPT_SHORTCUT_MOD:
if (!parse_shortcut_mods(optarg, &opts->shortcut_mods)) {
return false;
}
break;
default: default:
// getopt prints the error message on stderr // getopt prints the error message on stderr
return false; return false;

View file

@ -18,4 +18,9 @@ scrcpy_print_usage(const char *arg0);
bool bool
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]); 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);
#endif
#endif #endif

View file

@ -1,6 +1,7 @@
#include "input_manager.h" #include "input_manager.h"
#include <assert.h> #include <assert.h>
#include <SDL2/SDL_keycode.h>
#include "config.h" #include "config.h"
#include "event_converter.h" #include "event_converter.h"
@ -10,6 +11,64 @@
static const int ACTION_DOWN = 1; static const int ACTION_DOWN = 1;
static const int ACTION_UP = 1 << 1; static const int ACTION_UP = 1 << 1;
#define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI)
static inline uint16_t
to_sdl_mod(unsigned mod) {
uint16_t sdl_mod = 0;
if (mod & SC_MOD_LCTRL) {
sdl_mod |= KMOD_LCTRL;
}
if (mod & SC_MOD_RCTRL) {
sdl_mod |= KMOD_RCTRL;
}
if (mod & SC_MOD_LALT) {
sdl_mod |= KMOD_LALT;
}
if (mod & SC_MOD_RALT) {
sdl_mod |= KMOD_RALT;
}
if (mod & SC_MOD_LSUPER) {
sdl_mod |= KMOD_LGUI;
}
if (mod & SC_MOD_RSUPER) {
sdl_mod |= KMOD_RGUI;
}
return sdl_mod;
}
static bool
is_shortcut_mod(struct 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;
}
}
return false;
}
void
input_manager_init(struct input_manager *im, bool prefer_text,
const struct sc_shortcut_mods *shortcut_mods)
{
im->prefer_text = prefer_text;
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;
}
static void static void
send_keycode(struct controller *controller, enum android_keycode keycode, send_keycode(struct controller *controller, enum android_keycode keycode,
int actions, const char *name) { int actions, const char *name) {
@ -258,22 +317,13 @@ input_manager_process_key(struct input_manager *im,
const SDL_KeyboardEvent *event, const SDL_KeyboardEvent *event,
bool control) { bool control) {
// control: indicates the state of the command-line option --no-control // control: indicates the state of the command-line option --no-control
// ctrl: the Ctrl key
bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL); bool smod = is_shortcut_mod(im, event->keysym.mod);
bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT);
bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI);
if (alt || meta) {
// no shortcuts involve Alt or Meta, and they must not be forwarded to
// the device
return;
}
struct controller *controller = im->controller; struct controller *controller = im->controller;
// capture all Ctrl events // The shortcut modifier is pressed
if (ctrl) { if (smod) {
SDL_Keycode keycode = event->keysym.sym; SDL_Keycode keycode = event->keysym.sym;
bool down = event->type == SDL_KEYDOWN; bool down = event->type == SDL_KEYDOWN;
int action = down ? ACTION_DOWN : ACTION_UP; int action = down ? ACTION_DOWN : ACTION_UP;
@ -281,33 +331,33 @@ input_manager_process_key(struct input_manager *im,
bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT); bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
switch (keycode) { switch (keycode) {
case SDLK_h: case SDLK_h:
if (control && ctrl && !shift && !repeat) { if (control && !shift && !repeat) {
action_home(controller, action); action_home(controller, action);
} }
return; return;
case SDLK_b: // fall-through case SDLK_b: // fall-through
case SDLK_BACKSPACE: case SDLK_BACKSPACE:
if (control && ctrl && !shift && !repeat) { if (control && !shift && !repeat) {
action_back(controller, action); action_back(controller, action);
} }
return; return;
case SDLK_s: case SDLK_s:
if (control && ctrl && !shift && !repeat) { if (control && !shift && !repeat) {
action_app_switch(controller, action); action_app_switch(controller, action);
} }
return; return;
case SDLK_m: case SDLK_m:
if (control && ctrl && !shift && !repeat) { if (control && !shift && !repeat) {
action_menu(controller, action); action_menu(controller, action);
} }
return; return;
case SDLK_p: case SDLK_p:
if (control && ctrl && !shift && !repeat) { if (control && !shift && !repeat) {
action_power(controller, action); action_power(controller, action);
} }
return; return;
case SDLK_o: case SDLK_o:
if (control && ctrl && !repeat && down) { if (control && !repeat && down) {
enum screen_power_mode mode = shift enum screen_power_mode mode = shift
? SCREEN_POWER_MODE_NORMAL ? SCREEN_POWER_MODE_NORMAL
: SCREEN_POWER_MODE_OFF; : SCREEN_POWER_MODE_OFF;
@ -315,34 +365,34 @@ input_manager_process_key(struct input_manager *im,
} }
return; return;
case SDLK_DOWN: case SDLK_DOWN:
if (control && ctrl && !shift) { if (control && !shift) {
// forward repeated events // forward repeated events
action_volume_down(controller, action); action_volume_down(controller, action);
} }
return; return;
case SDLK_UP: case SDLK_UP:
if (control && ctrl && !shift) { if (control && !shift) {
// forward repeated events // forward repeated events
action_volume_up(controller, action); action_volume_up(controller, action);
} }
return; return;
case SDLK_LEFT: case SDLK_LEFT:
if (ctrl && !shift && !repeat && down) { if (!shift && !repeat && down) {
rotate_client_left(im->screen); rotate_client_left(im->screen);
} }
return; return;
case SDLK_RIGHT: case SDLK_RIGHT:
if (ctrl && !shift && !repeat && down) { if (!shift && !repeat && down) {
rotate_client_right(im->screen); rotate_client_right(im->screen);
} }
return; return;
case SDLK_c: case SDLK_c:
if (control && ctrl && !shift && !repeat && down) { if (control && !shift && !repeat && down) {
request_device_clipboard(controller); request_device_clipboard(controller);
} }
return; return;
case SDLK_v: case SDLK_v:
if (control && ctrl && !repeat && down) { if (control && !repeat && down) {
if (shift) { if (shift) {
// store the text in the device clipboard and paste // store the text in the device clipboard and paste
set_device_clipboard(controller, true); set_device_clipboard(controller, true);
@ -353,29 +403,29 @@ input_manager_process_key(struct input_manager *im,
} }
return; return;
case SDLK_f: case SDLK_f:
if (ctrl && !shift && !repeat && down) { if (!shift && !repeat && down) {
screen_switch_fullscreen(im->screen); screen_switch_fullscreen(im->screen);
} }
return; return;
case SDLK_x: case SDLK_x:
if (ctrl && !shift && !repeat && down) { if (!shift && !repeat && down) {
screen_resize_to_fit(im->screen); screen_resize_to_fit(im->screen);
} }
return; return;
case SDLK_g: case SDLK_g:
if (ctrl && !shift && !repeat && down) { if (!shift && !repeat && down) {
screen_resize_to_pixel_perfect(im->screen); screen_resize_to_pixel_perfect(im->screen);
} }
return; return;
case SDLK_i: case SDLK_i:
if (ctrl && !shift && !repeat && down) { if (!shift && !repeat && down) {
struct fps_counter *fps_counter = struct fps_counter *fps_counter =
im->video_buffer->fps_counter; im->video_buffer->fps_counter;
switch_fps_counter_state(fps_counter); switch_fps_counter_state(fps_counter);
} }
return; return;
case SDLK_n: case SDLK_n:
if (control && ctrl && !repeat && down) { if (control && !repeat && down) {
if (shift) { if (shift) {
collapse_notification_panel(controller); collapse_notification_panel(controller);
} else { } else {
@ -384,7 +434,7 @@ input_manager_process_key(struct input_manager *im,
} }
return; return;
case SDLK_r: case SDLK_r:
if (control && ctrl && !shift && !repeat && down) { if (control && !shift && !repeat && down) {
rotate_device(controller); rotate_device(controller);
} }
return; return;

View file

@ -3,12 +3,15 @@
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL.h>
#include "config.h" #include "config.h"
#include "common.h" #include "common.h"
#include "controller.h" #include "controller.h"
#include "fps_counter.h" #include "fps_counter.h"
#include "video_buffer.h" #include "scrcpy.h"
#include "screen.h" #include "screen.h"
#include "video_buffer.h"
struct input_manager { struct input_manager {
struct controller *controller; struct controller *controller;
@ -20,8 +23,17 @@ struct input_manager {
unsigned repeat; unsigned repeat;
bool prefer_text; bool prefer_text;
struct {
unsigned data[SC_MAX_SHORTCUT_MODS];
unsigned count;
} sdl_shortcut_mods;
}; };
void
input_manager_init(struct input_manager *im, bool prefer_text,
const struct sc_shortcut_mods *shortcut_mods);
void void
input_manager_process_text_input(struct input_manager *im, input_manager_process_text_input(struct input_manager *im,
const SDL_TextInputEvent *event); const SDL_TextInputEvent *event);

View file

@ -49,7 +49,13 @@ static struct input_manager input_manager = {
.video_buffer = &video_buffer, .video_buffer = &video_buffer,
.screen = &screen, .screen = &screen,
.repeat = 0, .repeat = 0,
.prefer_text = false, // initialized later
// initialized later
.prefer_text = false,
.sdl_shortcut_mods = {
.data = {0},
.count = 0,
},
}; };
#ifdef _WIN32 #ifdef _WIN32
@ -437,7 +443,8 @@ scrcpy(const struct scrcpy_options *options) {
} }
} }
input_manager.prefer_text = options->prefer_text; input_manager_init(&input_manager, options->prefer_text,
&options->shortcut_mods);
ret = event_loop(options->display, options->control); ret = event_loop(options->display, options->control);
LOGD("quit..."); LOGD("quit...");

View file

@ -20,6 +20,22 @@ enum sc_record_format {
SC_RECORD_FORMAT_MKV, SC_RECORD_FORMAT_MKV,
}; };
#define SC_MAX_SHORTCUT_MODS 8
enum sc_shortcut_mod {
SC_MOD_LCTRL = 1 << 0,
SC_MOD_RCTRL = 1 << 1,
SC_MOD_LALT = 1 << 2,
SC_MOD_RALT = 1 << 3,
SC_MOD_LSUPER = 1 << 4,
SC_MOD_RSUPER = 1 << 5,
};
struct sc_shortcut_mods {
unsigned data[SC_MAX_SHORTCUT_MODS];
unsigned count;
};
struct sc_port_range { struct sc_port_range {
uint16_t first; uint16_t first;
uint16_t last; uint16_t last;
@ -38,6 +54,7 @@ struct scrcpy_options {
enum sc_log_level log_level; enum sc_log_level log_level;
enum sc_record_format record_format; enum sc_record_format record_format;
struct sc_port_range port_range; struct sc_port_range port_range;
struct sc_shortcut_mods shortcut_mods;
uint16_t max_size; uint16_t max_size;
uint32_t bit_rate; uint32_t bit_rate;
uint16_t max_fps; uint16_t max_fps;
@ -77,6 +94,10 @@ struct scrcpy_options {
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \ .first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \
.last = DEFAULT_LOCAL_PORT_RANGE_LAST, \ .last = DEFAULT_LOCAL_PORT_RANGE_LAST, \
}, \ }, \
.shortcut_mods = { \
.data = {SC_MOD_LALT}, \
.count = 1, \
}, \
.max_size = DEFAULT_MAX_SIZE, \ .max_size = DEFAULT_MAX_SIZE, \
.bit_rate = DEFAULT_BIT_RATE, \ .bit_rate = DEFAULT_BIT_RATE, \
.max_fps = 0, \ .max_fps = 0, \

View file

@ -3,6 +3,7 @@
#include "cli.h" #include "cli.h"
#include "common.h" #include "common.h"
#include "scrcpy.h"
static void test_flag_version(void) { static void test_flag_version(void) {
struct scrcpy_cli_args args = { struct scrcpy_cli_args args = {
@ -122,6 +123,43 @@ static void test_options2(void) {
assert(opts->record_format == SC_RECORD_FORMAT_MP4); assert(opts->record_format == SC_RECORD_FORMAT_MP4);
} }
static void test_parse_shortcut_mods(void) {
struct sc_shortcut_mods mods;
bool ok;
ok = sc_parse_shortcut_mods("lctrl", &mods);
assert(ok);
assert(mods.count == 1);
assert(mods.data[0] == SC_MOD_LCTRL);
ok = sc_parse_shortcut_mods("lctrl+lalt", &mods);
assert(ok);
assert(mods.count == 1);
assert(mods.data[0] == (SC_MOD_LCTRL | SC_MOD_LALT));
ok = sc_parse_shortcut_mods("rctrl,lalt", &mods);
assert(ok);
assert(mods.count == 2);
assert(mods.data[0] == SC_MOD_RCTRL);
assert(mods.data[1] == SC_MOD_LALT);
ok = sc_parse_shortcut_mods("lsuper,rsuper+lalt,lctrl+rctrl+ralt", &mods);
assert(ok);
assert(mods.count == 3);
assert(mods.data[0] == SC_MOD_LSUPER);
assert(mods.data[1] == (SC_MOD_RSUPER | SC_MOD_LALT));
assert(mods.data[2] == (SC_MOD_LCTRL | SC_MOD_RCTRL | SC_MOD_RALT));
ok = sc_parse_shortcut_mods("", &mods);
assert(!ok);
ok = sc_parse_shortcut_mods("lctrl+", &mods);
assert(!ok);
ok = sc_parse_shortcut_mods("lctrl,", &mods);
assert(!ok);
}
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
(void) argc; (void) argc;
(void) argv; (void) argv;
@ -130,5 +168,6 @@ int main(int argc, char *argv[]) {
test_flag_help(); test_flag_help();
test_options(); test_options();
test_options2(); test_options2();
test_parse_shortcut_mods();
return 0; return 0;
}; };