Compare commits
15 Commits
android13
...
shortcut.1
Author | SHA1 | Date | |
---|---|---|---|
9d10426207 | |||
0a98b4d4a6 | |||
ae2b26fc47 | |||
405f5f2704 | |||
9ce0a475bf | |||
0987cd324e | |||
ecbbf5ff1c | |||
25b80a85f1 | |||
98d63fb24a | |||
59d30cb094 | |||
ff0711fbff | |||
7bfff9704e | |||
060d91e4ce | |||
a90b08ceb3 | |||
cfa2844121 |
119
README.md
119
README.md
@ -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,24 +494,42 @@ 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.
|
||||||
|
|
||||||
#### Copy-paste
|
#### Copy-paste
|
||||||
|
|
||||||
It is possible to synchronize clipboards between the computer and the device, in
|
Any time the Android clipboard changes, it is automatically synchronized to the
|
||||||
both directions:
|
computer clipboard.
|
||||||
|
|
||||||
- `Ctrl`+`c` copies the device clipboard to the computer clipboard;
|
Any `Ctrl` shortcut is forwarded to the device. In particular:
|
||||||
- `Ctrl`+`Shift`+`v` copies the computer clipboard to the device clipboard (and
|
- `Ctrl`+`c` typically copies
|
||||||
pastes if the device runs Android >= 7);
|
- `Ctrl`+`x` typically cuts
|
||||||
- `Ctrl`+`v` _pastes_ the computer clipboard as a sequence of text events (but
|
- `Ctrl`+`v` typically pastes (after computer-to-device clipboard
|
||||||
breaks non-ASCII characters).
|
synchronization)
|
||||||
|
|
||||||
Moreover, any time the Android clipboard changes, it is automatically
|
This typically works as you expect.
|
||||||
synchronized to the computer clipboard.
|
|
||||||
|
The actual behavior depends on the active application though. For example,
|
||||||
|
_Termux_ sends SIGINT on `Ctrl`+`c` instead, and _K-9 Mail_ composes a new
|
||||||
|
message.
|
||||||
|
|
||||||
|
To copy, cut and paste in all cases (but only supported on Android >= 7):
|
||||||
|
- `MOD`+`c` injects `COPY`
|
||||||
|
- `MOD`+`x` injects `CUT`
|
||||||
|
- `MOD`+`v` injects `PASTE` (after computer-to-device clipboard
|
||||||
|
synchronization)
|
||||||
|
|
||||||
|
In addition, `MOD`+`Shift`+`v` allows to inject the computer clipboard text as a
|
||||||
|
sequence of key events. This is useful when the component does not accept text
|
||||||
|
pasting (for example in _Termux_), but it can break non-ASCII content.
|
||||||
|
|
||||||
|
*WARNING:* Pasting the computer clipboard to the device (either via `Ctrl`+`v`
|
||||||
|
or `MOD`+`v`) copies the content in the device clipboard. As a consequence, any
|
||||||
|
Android application could read its content. You should avoid to paste sensitive
|
||||||
|
content (like passwords) that way.
|
||||||
|
|
||||||
#### Text injection preference
|
#### Text injection preference
|
||||||
|
|
||||||
@ -571,33 +589,52 @@ Also see [issue #14].
|
|||||||
|
|
||||||
## Shortcuts
|
## Shortcuts
|
||||||
|
|
||||||
| Action | Shortcut | Shortcut (macOS)
|
In the following list, `MOD` is the shortcut modifier. By default, it's (left)
|
||||||
| ------------------------------------------- |:----------------------------- |:-----------------------------
|
`Alt` or (left) `Cmd`.
|
||||||
| Switch fullscreen mode | `Ctrl`+`f` | `Cmd`+`f`
|
|
||||||
| Rotate display left | `Ctrl`+`←` _(left)_ | `Cmd`+`←` _(left)_
|
It can be changed using `--shortcut-mod`. Possible keys are `lctrl`, `rctrl`,
|
||||||
| Rotate display right | `Ctrl`+`→` _(right)_ | `Cmd`+`→` _(right)_
|
`lalt`, `ralt`, `lcmd` and `rcmd`. For example:
|
||||||
| Resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` | `Cmd`+`g`
|
|
||||||
| Resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ | `Cmd`+`x` \| _Double-click¹_
|
```bash
|
||||||
| Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ | `Ctrl`+`h` \| _Middle-click_
|
# use RCtrl for shortcuts
|
||||||
| Click on `BACK` | `Ctrl`+`b` \| _Right-click²_ | `Cmd`+`b` \| _Right-click²_
|
scrcpy --shortcut-mod=rctrl
|
||||||
| Click on `APP_SWITCH` | `Ctrl`+`s` | `Cmd`+`s`
|
|
||||||
| Click on `MENU` | `Ctrl`+`m` | `Ctrl`+`m`
|
# use either LCtrl+LAlt or LCmd for shortcuts
|
||||||
| Click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ | `Cmd`+`↑` _(up)_
|
scrcpy --shortcut-mod=lctrl+lalt,lcmd
|
||||||
| Click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ | `Cmd`+`↓` _(down)_
|
```
|
||||||
| Click on `POWER` | `Ctrl`+`p` | `Cmd`+`p`
|
|
||||||
| Power on | _Right-click²_ | _Right-click²_
|
| Action | Shortcut
|
||||||
| Turn device screen off (keep mirroring) | `Ctrl`+`o` | `Cmd`+`o`
|
| ------------------------------------------- |:-----------------------------
|
||||||
| Turn device screen on | `Ctrl`+`Shift`+`o` | `Cmd`+`Shift`+`o`
|
| Switch fullscreen mode | `MOD`+`f`
|
||||||
| Rotate device screen | `Ctrl`+`r` | `Cmd`+`r`
|
| Rotate display left | `MOD`+`←` _(left)_
|
||||||
| Expand notification panel | `Ctrl`+`n` | `Cmd`+`n`
|
| Rotate display right | `MOD`+`→` _(right)_
|
||||||
| Collapse notification panel | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n`
|
| Resize window to 1:1 (pixel-perfect) | `MOD`+`g`
|
||||||
| Copy device clipboard to computer | `Ctrl`+`c` | `Cmd`+`c`
|
| Resize window to remove black borders | `MOD`+`w` \| _Double-click¹_
|
||||||
| Paste computer clipboard to device | `Ctrl`+`v` | `Cmd`+`v`
|
| Click on `HOME` | `MOD`+`h` \| _Middle-click_
|
||||||
| Copy computer clipboard to device and paste | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v`
|
| Click on `BACK` | `MOD`+`b` \| _Right-click²_
|
||||||
| Enable/disable FPS counter (on stdout) | `Ctrl`+`i` | `Cmd`+`i`
|
| Click on `APP_SWITCH` | `MOD`+`s`
|
||||||
|
| Click on `MENU` | `MOD`+`m`
|
||||||
|
| Click on `VOLUME_UP` | `MOD`+`↑` _(up)_
|
||||||
|
| Click on `VOLUME_DOWN` | `MOD`+`↓` _(down)_
|
||||||
|
| Click on `POWER` | `MOD`+`p`
|
||||||
|
| Power on | _Right-click²_
|
||||||
|
| Turn device screen off (keep mirroring) | `MOD`+`o`
|
||||||
|
| Turn device screen on | `MOD`+`Shift`+`o`
|
||||||
|
| Rotate device screen | `MOD`+`r`
|
||||||
|
| Expand notification panel | `MOD`+`n`
|
||||||
|
| Collapse notification panel | `MOD`+`Shift`+`n`
|
||||||
|
| Copy to clipboard³ | `MOD`+`c`
|
||||||
|
| Cut to clipboard³ | `MOD`+`x`
|
||||||
|
| Synchronize clipboards and paste³ | `MOD`+`v`
|
||||||
|
| Inject computer clipboard text | `MOD`+`Shift`+`v`
|
||||||
|
| 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._
|
||||||
|
_³Only on Android >= 7._
|
||||||
|
|
||||||
|
All `Ctrl`+_key_ shortcuts are forwarded to the device, so they are handled by
|
||||||
|
the active application.
|
||||||
|
|
||||||
|
|
||||||
## Custom paths
|
## Custom paths
|
||||||
|
@ -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
|
||||||
|
65
app/scrcpy.1
65
app/scrcpy.1
@ -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", "lcmd" and "rcmd".
|
||||||
|
|
||||||
|
A shortcut can consist in several keys, separated by '+'. Several shortcuts can be specified, separated by ','.
|
||||||
|
|
||||||
|
For example, to use either LCtrl+LAlt or LCmd for scrcpy shortcuts, pass "lctrl+lalt,lcmd".
|
||||||
|
|
||||||
|
Default is "lalt,lcmd" (left-Alt or left-Cmd).
|
||||||
|
|
||||||
.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 or (left) Cmd, 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+w, 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,43 @@ 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 to clipboard (inject COPY keycode, Android >= 7 only)
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Ctrl+v
|
.B Mod+x
|
||||||
Paste computer clipboard to device
|
Cut to clipboard (inject CUT keycode, Android >= 7 only)
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Ctrl+Shift+v
|
.B MOD+v
|
||||||
Copy computer clipboard to device (and paste if the device runs Android >= 7)
|
Copy computer clipboard to device, then paste (inject PASTE keycode, Android >= 7 only)
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Ctrl+i
|
.B MOD+Shift+v
|
||||||
|
Inject computer clipboard text as a sequence of key events
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B MOD+i
|
||||||
Enable/disable FPS counter (print frames/second in logs)
|
Enable/disable FPS counter (print frames/second in logs)
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
|
179
app/src/cli.c
179
app/src/cli.c
@ -13,11 +13,6 @@
|
|||||||
|
|
||||||
void
|
void
|
||||||
scrcpy_print_usage(const char *arg0) {
|
scrcpy_print_usage(const char *arg0) {
|
||||||
#ifdef __APPLE__
|
|
||||||
# define CTRL_OR_CMD "Cmd"
|
|
||||||
#else
|
|
||||||
# define CTRL_OR_CMD "Ctrl"
|
|
||||||
#endif
|
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"Usage: %s [options]\n"
|
"Usage: %s [options]\n"
|
||||||
"\n"
|
"\n"
|
||||||
@ -143,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. Possible\n"
|
||||||
|
" keys are \"lctrl\", \"rctrl\", \"lalt\", \"ralt\", \"lcmd\"\n"
|
||||||
|
" and \"rcmd\".\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 LCmd for scrcpy\n"
|
||||||
|
" shortcuts, pass \"lctrl+lalt,lcmd\".\n"
|
||||||
|
"\n"
|
||||||
|
" Default is \"lalt,lcmd\" (left-Alt or left-Cmd).\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"
|
||||||
@ -190,75 +198,82 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
"\n"
|
"\n"
|
||||||
"Shortcuts:\n"
|
"Shortcuts:\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+f\n"
|
" In the following list, MOD is the shortcut modifier. By default,\n"
|
||||||
|
" it's (left) Alt or (left) Cmd, but it can be configured by\n"
|
||||||
|
" --shortcut-mod.\n"
|
||||||
|
"\n"
|
||||||
|
" MOD+f\n"
|
||||||
" Switch fullscreen mode\n"
|
" Switch fullscreen mode\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+Left\n"
|
" MOD+Left\n"
|
||||||
" Rotate display left\n"
|
" Rotate display left\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+Right\n"
|
" MOD+Right\n"
|
||||||
" Rotate display right\n"
|
" Rotate display right\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+g\n"
|
" MOD+g\n"
|
||||||
" Resize window to 1:1 (pixel-perfect)\n"
|
" Resize window to 1:1 (pixel-perfect)\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+x\n"
|
" MOD+w\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_OR_CMD "+b\n"
|
" MOD+b\n"
|
||||||
" " CTRL_OR_CMD "+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_OR_CMD "+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_OR_CMD "+Up\n"
|
" MOD+Up\n"
|
||||||
" Click on VOLUME_UP\n"
|
" Click on VOLUME_UP\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+Down\n"
|
" MOD+Down\n"
|
||||||
" Click on VOLUME_DOWN\n"
|
" Click on VOLUME_DOWN\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+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_OR_CMD "+o\n"
|
" MOD+o\n"
|
||||||
" Turn device screen off (keep mirroring)\n"
|
" Turn device screen off (keep mirroring)\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+Shift+o\n"
|
" MOD+Shift+o\n"
|
||||||
" Turn device screen on\n"
|
" Turn device screen on\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+r\n"
|
" MOD+r\n"
|
||||||
" Rotate device screen\n"
|
" Rotate device screen\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+n\n"
|
" MOD+n\n"
|
||||||
" Expand notification panel\n"
|
" Expand notification panel\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+Shift+n\n"
|
" MOD+Shift+n\n"
|
||||||
" Collapse notification panel\n"
|
" Collapse notification panel\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+c\n"
|
" MOD+c\n"
|
||||||
" Copy device clipboard to computer\n"
|
" Copy to clipboard (inject COPY keycode, Android >= 7 only)\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+v\n"
|
" MOD+x\n"
|
||||||
" Paste computer clipboard to device\n"
|
" Cut to clipboard (inject CUT keycode, Android >= 7 only)\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+Shift+v\n"
|
" MOD+v\n"
|
||||||
" Copy computer clipboard to device (and paste if the device\n"
|
" Copy computer clipboard to device, then paste (inject PASTE\n"
|
||||||
" runs Android >= 7)\n"
|
" keycode, Android >= 7 only)\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+i\n"
|
" MOD+Shift+v\n"
|
||||||
|
" Inject computer clipboard text as a sequence of key events\n"
|
||||||
|
"\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"
|
||||||
@ -480,6 +495,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("lcmd", item, key_len)) {
|
||||||
|
mod |= SC_MOD_LCMD;
|
||||||
|
} else if (STREQ("rcmd", item, key_len)) {
|
||||||
|
mod |= SC_MOD_RCMD;
|
||||||
|
} 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+RCmd: "lctrl+lalt,rctrl,lctrl+rcmd"
|
||||||
|
|
||||||
|
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")) {
|
||||||
@ -531,6 +641,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[]) {
|
||||||
@ -563,6 +674,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'},
|
||||||
@ -726,6 +838,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;
|
||||||
|
@ -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
|
||||||
|
@ -92,6 +92,10 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
|||||||
MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT);
|
MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT);
|
||||||
MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN);
|
MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN);
|
||||||
MAP(SDLK_UP, AKEYCODE_DPAD_UP);
|
MAP(SDLK_UP, AKEYCODE_DPAD_UP);
|
||||||
|
MAP(SDLK_LCTRL, AKEYCODE_CTRL_LEFT);
|
||||||
|
MAP(SDLK_RCTRL, AKEYCODE_CTRL_RIGHT);
|
||||||
|
MAP(SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT);
|
||||||
|
MAP(SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(mod & (KMOD_NUM | KMOD_SHIFT))) {
|
if (!(mod & (KMOD_NUM | KMOD_SHIFT))) {
|
||||||
@ -111,8 +115,8 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prefer_text) {
|
if (prefer_text && !(mod & KMOD_CTRL)) {
|
||||||
// do not forward alpha and space key events
|
// do not forward alpha and space key events (unless Ctrl is pressed)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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_LCMD) {
|
||||||
|
sdl_mod |= KMOD_LGUI;
|
||||||
|
}
|
||||||
|
if (mod & SC_MOD_RCMD) {
|
||||||
|
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) {
|
||||||
@ -70,6 +129,16 @@ action_menu(struct controller *controller, int actions) {
|
|||||||
send_keycode(controller, AKEYCODE_MENU, actions, "MENU");
|
send_keycode(controller, AKEYCODE_MENU, actions, "MENU");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
action_copy(struct controller *controller, int actions) {
|
||||||
|
send_keycode(controller, AKEYCODE_COPY, actions, "COPY");
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
action_cut(struct controller *controller, int actions) {
|
||||||
|
send_keycode(controller, AKEYCODE_CUT, actions, "CUT");
|
||||||
|
}
|
||||||
|
|
||||||
// turn the screen on if it was off, press BACK otherwise
|
// turn the screen on if it was off, press BACK otherwise
|
||||||
static void
|
static void
|
||||||
press_back_or_turn_screen_on(struct controller *controller) {
|
press_back_or_turn_screen_on(struct controller *controller) {
|
||||||
@ -101,16 +170,6 @@ collapse_notification_panel(struct controller *controller) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
request_device_clipboard(struct controller *controller) {
|
|
||||||
struct control_msg msg;
|
|
||||||
msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD;
|
|
||||||
|
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
|
||||||
LOGW("Could not request device clipboard");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
set_device_clipboard(struct controller *controller, bool paste) {
|
set_device_clipboard(struct controller *controller, bool paste) {
|
||||||
char *text = SDL_GetClipboardText();
|
char *text = SDL_GetClipboardText();
|
||||||
@ -210,6 +269,10 @@ rotate_client_right(struct screen *screen) {
|
|||||||
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) {
|
||||||
|
if (is_shortcut_mod(im, SDL_GetModState())) {
|
||||||
|
// A shortcut must never generate text events
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!im->prefer_text) {
|
if (!im->prefer_text) {
|
||||||
char c = event->text[0];
|
char c = event->text[0];
|
||||||
if (isalpha(c) || c == ' ') {
|
if (isalpha(c) || c == ' ') {
|
||||||
@ -258,71 +321,49 @@ 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);
|
|
||||||
|
|
||||||
// use Cmd on macOS, Ctrl on other platforms
|
|
||||||
#ifdef __APPLE__
|
|
||||||
bool cmd = !ctrl && meta;
|
|
||||||
#else
|
|
||||||
if (meta) {
|
|
||||||
// no shortcuts involve Meta on platforms other than macOS, and it must
|
|
||||||
// not be forwarded to the device
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
bool cmd = ctrl; // && !meta, already guaranteed
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (alt) {
|
|
||||||
// no shortcuts involve Alt, and it must not be forwarded to the device
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct controller *controller = im->controller;
|
struct controller *controller = im->controller;
|
||||||
|
|
||||||
// capture all Ctrl events
|
SDL_Keycode keycode = event->keysym.sym;
|
||||||
if (ctrl || cmd) {
|
bool down = event->type == SDL_KEYDOWN;
|
||||||
SDL_Keycode keycode = event->keysym.sym;
|
bool ctrl = event->keysym.mod & KMOD_CTRL;
|
||||||
bool down = event->type == SDL_KEYDOWN;
|
bool shift = event->keysym.mod & KMOD_SHIFT;
|
||||||
|
bool repeat = event->repeat;
|
||||||
|
|
||||||
|
// The shortcut modifier is pressed
|
||||||
|
if (smod) {
|
||||||
int action = down ? ACTION_DOWN : ACTION_UP;
|
int action = down ? ACTION_DOWN : ACTION_UP;
|
||||||
bool repeat = event->repeat;
|
|
||||||
bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
|
|
||||||
switch (keycode) {
|
switch (keycode) {
|
||||||
case SDLK_h:
|
case SDLK_h:
|
||||||
// Ctrl+h on all platform, since Cmd+h is already captured by
|
if (control && !shift && !repeat) {
|
||||||
// the system on macOS to hide the window
|
|
||||||
if (control && ctrl && !meta && !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 && cmd && !shift && !repeat) {
|
if (control && !shift && !repeat) {
|
||||||
action_back(controller, action);
|
action_back(controller, action);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_s:
|
case SDLK_s:
|
||||||
if (control && cmd && !shift && !repeat) {
|
if (control && !shift && !repeat) {
|
||||||
action_app_switch(controller, action);
|
action_app_switch(controller, action);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_m:
|
case SDLK_m:
|
||||||
// Ctrl+m on all platform, since Cmd+m is already captured by
|
if (control && !shift && !repeat) {
|
||||||
// the system on macOS to minimize the window
|
|
||||||
if (control && ctrl && !meta && !shift && !repeat) {
|
|
||||||
action_menu(controller, action);
|
action_menu(controller, action);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_p:
|
case SDLK_p:
|
||||||
if (control && cmd && !shift && !repeat) {
|
if (control && !shift && !repeat) {
|
||||||
action_power(controller, action);
|
action_power(controller, action);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_o:
|
case SDLK_o:
|
||||||
if (control && cmd && !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;
|
||||||
@ -330,67 +371,72 @@ input_manager_process_key(struct input_manager *im,
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_DOWN:
|
case SDLK_DOWN:
|
||||||
if (control && cmd && !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 && cmd && !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 (cmd && !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 (cmd && !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 && cmd && !shift && !repeat && down) {
|
if (control && !shift && !repeat) {
|
||||||
request_device_clipboard(controller);
|
action_copy(controller, action);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case SDLK_x:
|
||||||
|
if (control && !shift && !repeat) {
|
||||||
|
action_cut(controller, action);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_v:
|
case SDLK_v:
|
||||||
if (control && cmd && !repeat && down) {
|
if (control && !repeat && down) {
|
||||||
if (shift) {
|
if (shift) {
|
||||||
// store the text in the device clipboard and paste
|
|
||||||
set_device_clipboard(controller, true);
|
|
||||||
} else {
|
|
||||||
// inject the text as input events
|
// inject the text as input events
|
||||||
clipboard_paste(controller);
|
clipboard_paste(controller);
|
||||||
|
} else {
|
||||||
|
// store the text in the device clipboard and paste
|
||||||
|
set_device_clipboard(controller, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_f:
|
case SDLK_f:
|
||||||
if (cmd && !shift && !repeat && down) {
|
if (!shift && !repeat && down) {
|
||||||
screen_switch_fullscreen(im->screen);
|
screen_switch_fullscreen(im->screen);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_x:
|
case SDLK_w:
|
||||||
if (cmd && !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 (cmd && !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 (cmd && !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 && cmd && !repeat && down) {
|
if (control && !repeat && down) {
|
||||||
if (shift) {
|
if (shift) {
|
||||||
collapse_notification_panel(controller);
|
collapse_notification_panel(controller);
|
||||||
} else {
|
} else {
|
||||||
@ -399,7 +445,7 @@ input_manager_process_key(struct input_manager *im,
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_r:
|
case SDLK_r:
|
||||||
if (control && cmd && !shift && !repeat && down) {
|
if (control && !shift && !repeat && down) {
|
||||||
rotate_device(controller);
|
rotate_device(controller);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -418,6 +464,12 @@ input_manager_process_key(struct input_manager *im,
|
|||||||
im->repeat = 0;
|
im->repeat = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ctrl && !shift && keycode == SDLK_v && down && !repeat) {
|
||||||
|
// Synchronize the computer clipboard to the device clipboard before
|
||||||
|
// sending Ctrl+v, to allow seamless copy-paste.
|
||||||
|
set_device_clipboard(controller, false);
|
||||||
|
}
|
||||||
|
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
if (convert_input_key(event, &msg, im->prefer_text, im->repeat)) {
|
if (convert_input_key(event, &msg, im->prefer_text, im->repeat)) {
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
|
@ -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);
|
||||||
|
@ -25,10 +25,19 @@ receiver_destroy(struct receiver *receiver) {
|
|||||||
static void
|
static void
|
||||||
process_msg(struct device_msg *msg) {
|
process_msg(struct device_msg *msg) {
|
||||||
switch (msg->type) {
|
switch (msg->type) {
|
||||||
case DEVICE_MSG_TYPE_CLIPBOARD:
|
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
||||||
|
char *current = SDL_GetClipboardText();
|
||||||
|
bool same = current && !strcmp(current, msg->clipboard.text);
|
||||||
|
SDL_free(current);
|
||||||
|
if (same) {
|
||||||
|
LOGD("Computer clipboard unchanged");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
LOGI("Device clipboard copied");
|
LOGI("Device clipboard copied");
|
||||||
SDL_SetClipboardText(msg->clipboard.text);
|
SDL_SetClipboardText(msg->clipboard.text);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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...");
|
||||||
|
@ -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_LCMD = 1 << 4,
|
||||||
|
SC_MOD_RCMD = 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, SC_MOD_LCMD}, \
|
||||||
|
.count = 2, \
|
||||||
|
}, \
|
||||||
.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, \
|
||||||
|
@ -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("lcmd,rcmd+lalt,lctrl+rctrl+ralt", &mods);
|
||||||
|
assert(ok);
|
||||||
|
assert(mods.count == 3);
|
||||||
|
assert(mods.data[0] == SC_MOD_LCMD);
|
||||||
|
assert(mods.data[1] == (SC_MOD_RCMD | 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;
|
||||||
};
|
};
|
||||||
|
@ -221,6 +221,16 @@ public final class Device {
|
|||||||
if (clipboardManager == null) {
|
if (clipboardManager == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String currentClipboard = getClipboardText();
|
||||||
|
if (currentClipboard == null || currentClipboard.equals(text)) {
|
||||||
|
// The clipboard already contains the requested text.
|
||||||
|
// Since pasting text from the computer involves setting the device clipboard, it could be set twice on a copy-paste. This would cause
|
||||||
|
// the clipboard listeners to be notified twice, and that would flood the Android keyboard clipboard history. To workaround this
|
||||||
|
// problem, do not explicitly set the clipboard text if it already contains the expected content.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
isSettingClipboard.set(true);
|
isSettingClipboard.set(true);
|
||||||
boolean ok = clipboardManager.setText(text);
|
boolean ok = clipboardManager.setText(text);
|
||||||
isSettingClipboard.set(false);
|
isSettingClipboard.set(false);
|
||||||
|
Reference in New Issue
Block a user