Compare commits

...

45 Commits

Author SHA1 Message Date
b3ff1f6b3b Upgrade FFmpeg (5.0) for Windows 64-bit
Use FFmpeg win64 binaries from gyan.dev (referenced from ffmpeg.org):

 - https://www.gyan.dev/ffmpeg/builds/
 - https://ffmpeg.org/download.html#build-windows

Keep the old FFmpeg prebuilt binaries (4.3.1) for win32 builds.

Fixes #1753 <https://github.com/Genymobile/scrcpy/issues/1753>
Refs #1838 <https://github.com/Genymobile/scrcpy/pull/1838>
Refs #2583 <https://github.com/Genymobile/scrcpy/pull/2583>
PR #2952 <https://github.com/Genymobile/scrcpy/pull/2952>

Co-authored-by: Yu-Chen Lin <npes87184@gmail.com>
Co-authored-by: nkh0472 <nkh0472@hotmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-01-18 10:13:41 +01:00
a2495c5ef1 Use symlink to simplify Windows ffmpeg dependency
The FFmpeg dependency is downloaded from two separate zipfiles.

Symlink include/ to expose everything from a single directory, to
simplify the meson script.

PR #2952 <https://github.com/Genymobile/scrcpy/pull/2952>
2022-01-18 10:13:38 +01:00
37c7827d46 Simplify ffmpeg dependencies Makefile
The fact that the current prebuilt FFmpeg is split into two separate
zipfiles is an implementation detail.

Use a single Makefile recipe for both files.

PR #2952 <https://github.com/Genymobile/scrcpy/pull/2952>
2022-01-18 10:13:18 +01:00
5e8fa56e7a Fix build with ffmpeg 5.0
PR #2948 <https://github.com/Genymobile/scrcpy/pull/2948>

Signed-off-by: Bernhard Rosenkränzer <bero@lindev.ch>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-01-16 17:37:28 +01:00
60bf133ac2 Add final modifier to ScreenEncoder fields
These fields are only set from the constructor.
2022-01-15 23:25:55 +01:00
1c71bd16be Use constant string for known booleans
Boolean options explicitly passed to the server are statically known.
2022-01-15 23:25:43 +01:00
afa4a1b728 Use sc_ prefix for control_msg 2022-01-14 22:17:30 +01:00
3a4d5c7f18 Use sc_ prefix for controller 2022-01-14 22:17:30 +01:00
5f7ddff8ae Use sc_ prefix for input_manager 2022-01-14 22:17:30 +01:00
2a0c2e5e99 Use sc_ prefix for screen 2022-01-14 22:17:30 +01:00
a6644e831b Fix code style
Limit to 80 chars.
2022-01-14 20:57:03 +01:00
75655194fb Do not pass scrcpy_options to keyboard inject
The components should be configurable independently of the global
scrcpy_options instance: their configuration could be provided
separately, like it is the case for example for some screen parameters.

For consistency, keyboard injection should not depend on scrcpy_options.
2022-01-14 20:55:44 +01:00
43aff4af73 Document HID mouse in README 2022-01-04 17:41:40 +01:00
cba84f6999 Add support for HID mouse 2022-01-04 17:41:40 +01:00
ed2e45ee29 Refactor AOA/HID keyboard initialization
This paves the way to add support for HID mouse initialization.
2022-01-04 17:41:40 +01:00
aee1b39790 Add CLAMP() macro 2022-01-04 17:41:40 +01:00
17d01b5bf7 Add UI/UX support for relative mouse mode
In relative mouse mode, the mouse pointer must be "captured" from the
computer.

Toggle (disable/enable) relative mouse mode using any of the hardcoded
capture keys:
 - left-Alt
 - left-Super
 - right-Super

These capture keys do not conflict with shortcuts, since a shortcut is
always a combination of the MOD key and some other key, while the
capture key triggers an action only if it is pressed and released alone.

The relative mouse mode is also automatically enabled on any click in
the window, and automatically disabled on focus lost (it is possible to
lose focus even without the mouse).
2022-01-04 17:41:40 +01:00
40fca82b60 Forward all motion events to mouse processors
The decision to not send motion events when no click is pressed is
specific to Android mouse injection. Other mouse processors (e.g. for
HID mouse) will need to receive all events.
2022-01-04 17:41:40 +01:00
643293752d Provide relative mouse motion vector in event
This will allow the mouse processor to handle relative motion easily.
2022-01-04 17:41:40 +01:00
b5855e5deb Add relative mode flag to mouse processors
The default mouse injection works in absolute mode: it forwards clicks
at a specific position on screen.

To support HID mouse, add a flag to indicate that the mouse processor
works in relative mode: it forwards mouse motion vectors, without any
absolute reference to the screen.
2022-01-04 17:41:40 +01:00
924375487e Pass buttons state in scroll events
A scroll event might be produced when a mouse button is pressed (for
example when scrolling while selecting a text). For consistency, pass
the actual buttons state (instead of 0).

In practice, it seems that this use case does not work properly with
Android event injection, but it will work with HID mouse.
2022-01-04 17:41:40 +01:00
7121a0dc53 Destroy acksync immediately on error
If AOA or HID keyboard may not be initialized for some reason, acksync
is useless.
2022-01-04 17:41:40 +01:00
f04812fc71 Remove duplicate boolean
The AOA initialization state is already tracked by aoa_hid_initialized.
2022-01-04 17:41:40 +01:00
5ce1ccde85 Reorder controller and HID initialization
This allows to merge two "#ifdef HAVE_AOA_HID" blocks to simplify.
2022-01-04 17:41:40 +01:00
6102a0b5bb Move input_manager into screen
The input_manager is strongly tied to the screen, it could not work
independently of the specific screen implementation.

To implement a user-friendly HID mouse behavior, some SDL events
will need to be handled both by the screen and by the input manager. For
example, a click must typically be handled by the input_manager so that
it is forwarded to the device, but in HID mouse mode, the first click
should be handled by the screen to capture the mouse (enable relative
mouse mode).

Make the input_manager a descendant of the screen, so that the screen
decides what to do on SDL events.

Concretely, replace this structure hierarchy:

     +- struct scrcpy
        +- struct input_manager
        +- struct screen

by this one:

     +- struct scrcpy
        +- struct screen
           +- struct input_manager
2022-01-04 17:41:35 +01:00
2b34e1224e Use separate struct for input manager params
This avoids to directly pass the options instance (which contains more
data than strictly necessary), and limit the number of parameters for
the init function.
2022-01-04 15:14:38 +01:00
a9d23400cd Remove unused enum value requiring SDL 2.0.18
Refs b8fed50639
Fixes #2924 <https://github.com/Genymobile/scrcpy/issues/2924>
2022-01-04 15:11:33 +01:00
cca3c953da Enable virtual finger only on left click
The pinch-to-zoom feature must only be enabled with Ctrl+left_click.
2022-01-02 00:00:33 +01:00
57f1655d4b Make some mouse processors ops optional
Do not force all mouse processors to implement scroll events or touch
events.
2022-01-01 23:34:56 +01:00
bc674721dc Make process_text() optional
Not all key processors support text injection (HID keyboard does not
support it).

Instead of providing a dummy op function, set it to NULL and check on
the caller side before calling it.
2022-01-01 23:31:01 +01:00
63e29b1782 Apply buttons mask if not --forward-all-clicks
If --forward-all-clicks is not set, then only left clicks are forwarded.
For consistency, also mask the buttons state in other events.
2022-01-01 23:31:01 +01:00
3c15cbdaf8 Reorder mouse processor ops
Group the mouse events callbacks before the touch event callback.
2022-01-01 23:31:01 +01:00
96e0e89740 Simplify mouse injection implementation
The static functions are now so simple that they become unnecessary: the
control message may be initialized directly instead.
2022-01-01 23:30:55 +01:00
a1f2f5fbd3 Make some event conversions infallible
When the implementation handles all possible input values, it may never
fail.
2022-01-01 23:28:45 +01:00
9460bdd87b Use scrcpy input events for mouse processors
Pass scrcpy input events instead of SDL input events to mouse
processors.

These events represent exactly what mouse processors need, abstracted
from any visual orientation and scaling applied on the SDL window.

This makes the mouse processors independent of the "screen" instance,
and the implementation source code independent of the SDL API.
2022-01-01 23:28:45 +01:00
b4b638e8fe Use scrcpy input events for key processors
Pass scrcpy input events instead of SDL input events to key processors.

This makes the source code of key processors independent of the SDL API.
2022-01-01 23:28:45 +01:00
e4396e34c2 Use common sc_action in input manager
Now that the scrcpy input events API exposes a sc_action enum, use the
same from the input manager.
2022-01-01 23:28:45 +01:00
b8fed50639 Add intermediate input events layer
This aims to make the key/mouse processors independent of the "screen",
by processing scrcpy-specific input events instead of SDL events.

In particular, these scrcpy events are not impacted by any UI window
scaling or rotation (contrary to SDL events).
2022-01-01 23:28:45 +01:00
d540c72e7c Rename SC_MOD_* to SC_SHORTCUT_MOD_*
This will avoid conflicts with new SC_MOD_* constants.
2022-01-01 23:28:45 +01:00
cd5891fee6 Remove actions bitset
The input manager exposed functions taking an "actions" parameter,
containing a bitmask-OR of ACTION_UP and ACTION_DOWN.

But they are never called with both actions simultaneously anymore, so
simplify.

Refs 964b6d2243
Refs d0739911a3
2022-01-01 23:28:45 +01:00
26ee7ce566 Expose V4L2 option on all platforms
This allows to report a meaningful error message if an unsupported
feature is used on an incompatible platform. This is consistent with the
behavior of -K/--hid-keyboard.
2022-01-01 23:28:45 +01:00
ba28d817fb Fail on unsupported HID option
If the feature is not supported on the platform, fail during command
line parsing instead of using a fallback.
2022-01-01 23:28:45 +01:00
37124e1452 Avoid unused function warning
If HAVE_SOCK_CLOEXEC is not defined, then sc_raw_socket_close() is never
used. Add an #ifndef block to remove the warning.
2022-01-01 23:27:34 +01:00
6b9f397733 Happy new year 2022! 2022-01-01 17:20:36 +01:00
1fbc590b26 Fix memory leaks in tests
Tests were failing when run with ASAN enabled.
2021-12-31 10:49:22 +01:00
57 changed files with 1906 additions and 860 deletions

View File

@ -188,7 +188,7 @@
identification within third-party archives. identification within third-party archives.
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont Copyright (C) 2018-2022 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -672,7 +672,7 @@ Baca [halaman pengembang].
## Lisensi ## Lisensi
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont Copyright (C) 2018-2022 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -790,7 +790,7 @@ Leggi la [pagina per sviluppatori].
## Licenza (in inglese) ## Licenza (in inglese)
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont Copyright (C) 2018-2022 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -776,7 +776,7 @@ _⁴Android 7以上のみ._
## ライセンス ## ライセンス
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont Copyright (C) 2018-2022 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -475,7 +475,7 @@ _²화면이 꺼진 상태에서 우클릭 시 다시 켜지며, 그 외의 상
## 라이선스 ## 라이선스
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont Copyright (C) 2018-2022 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -31,6 +31,8 @@ Its features include:
- device screen [as a webcam (V4L2)](#v4l2loopback) (Linux-only) - device screen [as a webcam (V4L2)](#v4l2loopback) (Linux-only)
- [physical keyboard simulation (HID)](#physical-keyboard-simulation-hid) - [physical keyboard simulation (HID)](#physical-keyboard-simulation-hid)
(Linux-only) (Linux-only)
- [physical mouse simulation (HID)](#physical-mouse-simulation-hid)
(Linux-only)
- and more… - and more…
## Requirements ## Requirements
@ -815,6 +817,35 @@ a physical keyboard is connected).
[Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915 [Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915
#### Physical mouse simulation (HID)
Similarly to the physical keyboard simulation, it is possible to simulate a
physical mouse. Likewise, it only works if the device is connected by USB, and
is currently only supported on Linux.
By default, scrcpy uses Android mouse events injection, using absolute
coordinates. By simulating a physical mouse, a mouse pointer appears on the
Android device, and relative mouse motion, clicks and scrolls are injected.
To enable this mode:
```bash
scrcpy --hid-mouse
scrcpy -M # short version
```
You could also add `--forward-all-clicks` to [forward all mouse
buttons][forward_all_clicks].
[forward_all_clicks]: #right-click-and-middle-click
When this mode is enabled, the computer mouse is "captured" (the mouse pointer
disappears from the computer and appears on the Android device instead).
Special capture keys, either <kbd>Alt</kbd> or <kbd>Super</kbd>, toggle
(disable or enable) the mouse capture. Use one of them to give the control of
the mouse back to the computer.
#### Text injection preference #### Text injection preference
@ -1017,7 +1048,7 @@ Read the [developers page].
## Licence ## Licence
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont Copyright (C) 2018-2022 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -857,7 +857,7 @@ Leia a [página dos desenvolvedores][developers page].
## Licença ## Licença
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont Copyright (C) 2018-2022 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -720,7 +720,7 @@ Lea la [hoja de desarrolladores (en inglés)](DEVELOP.md).
## Licencia ## Licencia
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont Copyright (C) 2018-2022 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -801,7 +801,7 @@ Bakınız [FAQ](FAQ.md).
## Lisans ## Lisans
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont Copyright (C) 2018-2022 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -842,7 +842,7 @@ ADB=/path/to/adb scrcpy
## 许可协议 ## 许可协议
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont Copyright (C) 2018-2022 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -679,7 +679,7 @@ _³只支援 Android 7+。_
## Licence ## Licence
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont Copyright (C) 2018-2022 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -77,6 +77,7 @@ if aoa_hid_support
src += [ src += [
'src/aoa_hid.c', 'src/aoa_hid.c',
'src/hid_keyboard.c', 'src/hid_keyboard.c',
'src/hid_mouse.c',
] ]
endif endif
@ -117,15 +118,20 @@ else
include_directories: include_directories(sdl2_include_dir) include_directories: include_directories(sdl2_include_dir)
) )
prebuilt_ffmpeg_shared = meson.get_cross_property('prebuilt_ffmpeg_shared') prebuilt_ffmpeg = meson.get_cross_property('prebuilt_ffmpeg')
prebuilt_ffmpeg_dev = meson.get_cross_property('prebuilt_ffmpeg_dev') ffmpeg_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_ffmpeg + '/bin'
ffmpeg_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_ffmpeg_shared + '/bin' ffmpeg_include_dir = '../prebuilt-deps/' + prebuilt_ffmpeg + '/include'
ffmpeg_include_dir = '../prebuilt-deps/' + prebuilt_ffmpeg_dev + '/include'
# ffmpeg versions are different for win32 and win64 builds
ffmpeg_avcodec = meson.get_cross_property('ffmpeg_avcodec')
ffmpeg_avformat = meson.get_cross_property('ffmpeg_avformat')
ffmpeg_avutil = meson.get_cross_property('ffmpeg_avutil')
ffmpeg = declare_dependency( ffmpeg = declare_dependency(
dependencies: [ dependencies: [
cc.find_library('avcodec-58', dirs: ffmpeg_bin_dir), cc.find_library(ffmpeg_avcodec, dirs: ffmpeg_bin_dir),
cc.find_library('avformat-58', dirs: ffmpeg_bin_dir), cc.find_library(ffmpeg_avformat, dirs: ffmpeg_bin_dir),
cc.find_library('avutil-56', dirs: ffmpeg_bin_dir), cc.find_library(ffmpeg_avutil, dirs: ffmpeg_bin_dir),
], ],
include_directories: include_directories(ffmpeg_include_dir) include_directories: include_directories(ffmpeg_include_dir)
) )

View File

@ -96,6 +96,8 @@ The keyboard layout must be configured (once and for all) on the device, via Set
However, the option is only available when the HID keyboard is enabled (or a physical keyboard is connected). However, the option is only available when the HID keyboard is enabled (or a physical keyboard is connected).
Also see \fB\-\-hid\-mouse\fR.
.TP .TP
.B \-\-legacy\-paste .B \-\-legacy\-paste
Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v). Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v).
@ -120,6 +122,18 @@ Limit both the width and height of the video to \fIvalue\fR. The other dimension
Default is 0 (unlimited). Default is 0 (unlimited).
.TP
.B \-M, \-\-hid\-mouse
Simulate a physical mouse by using HID over AOAv2.
In this mode, the computer mouse is captured to control the device directly (relative mouse mode).
LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse back to the computer.
It may only work over USB, and is currently only supported on Linux.
Also see \fB\-\-hid\-keyboard\fR.
.TP .TP
.B \-\-no\-clipboard\-autosync .B \-\-no\-clipboard\-autosync
By default, scrcpy automatically synchronizes the computer clipboard to the device clipboard before injecting Ctrl+v, and the device clipboard to the computer clipboard whenever it changes. By default, scrcpy automatically synchronizes the computer clipboard to the device clipboard before injecting Ctrl+v, and the device clipboard to the computer clipboard whenever it changes.
@ -446,7 +460,7 @@ Copyright \(co 2018 Genymobile
Genymobile Genymobile
.UE .UE
Copyright \(co 2018\-2021 Copyright \(co 2018\-2022
.MT rom@rom1v.com .MT rom@rom1v.com
Romain Vimont Romain Vimont
.ME .ME

View File

@ -343,8 +343,8 @@ run_aoa_thread(void *data) {
if (ack_to_wait != SC_SEQUENCE_INVALID) { if (ack_to_wait != SC_SEQUENCE_INVALID) {
LOGD("Waiting ack from server sequence=%" PRIu64_, ack_to_wait); LOGD("Waiting ack from server sequence=%" PRIu64_, ack_to_wait);
// Do not block the loop indefinitely if the ack never comes (it should // Do not block the loop indefinitely if the ack never comes (it
// never happen) // should never happen)
sc_tick deadline = sc_tick_now() + SC_TICK_FROM_MS(500); sc_tick deadline = sc_tick_now() + SC_TICK_FROM_MS(500);
enum sc_acksync_wait_result result = enum sc_acksync_wait_result result =
sc_acksync_wait(aoa->acksync, ack_to_wait, deadline); sc_acksync_wait(aoa->acksync, ack_to_wait, deadline);

View File

@ -178,7 +178,8 @@ static const struct sc_option options[] = {
"directly: `adb shell am start -a " "directly: `adb shell am start -a "
"android.settings.HARD_KEYBOARD_SETTINGS`.\n" "android.settings.HARD_KEYBOARD_SETTINGS`.\n"
"However, the option is only available when the HID keyboard " "However, the option is only available when the HID keyboard "
"is enabled (or a physical keyboard is connected).", "is enabled (or a physical keyboard is connected).\n"
"Also see --hid-mouse.",
}, },
{ {
.shortopt = 'h', .shortopt = 'h',
@ -214,6 +215,18 @@ static const struct sc_option options[] = {
.text = "Limit the frame rate of screen capture (officially supported " .text = "Limit the frame rate of screen capture (officially supported "
"since Android 10, but may work on earlier versions).", "since Android 10, but may work on earlier versions).",
}, },
{
.shortopt = 'M',
.longopt = "hid-mouse",
.text = "Simulate a physical mouse by using HID over AOAv2.\n"
"In this mode, the computer mouse is captured to control the "
"device directly (relative mouse mode).\n"
"LAlt, LSuper or RSuper toggle the capture mode, to give "
"control of the mouse back to the computer.\n"
"It may only work over USB, and is currently only supported "
"on Linux.\n"
"Also see --hid-keyboard.",
},
{ {
.shortopt = 'm', .shortopt = 'm',
.longopt = "max-size", .longopt = "max-size",
@ -240,11 +253,8 @@ static const struct sc_option options[] = {
{ {
.shortopt = 'N', .shortopt = 'N',
.longopt = "no-display", .longopt = "no-display",
.text = "Do not display device (only when screen recording " .text = "Do not display device (only when screen recording or V4L2 "
#ifdef HAVE_V4L2 "sink is enabled).",
"or V4L2 sink "
#endif
"is enabled).",
}, },
{ {
.longopt_id = OPT_NO_KEY_REPEAT, .longopt_id = OPT_NO_KEY_REPEAT,
@ -381,14 +391,14 @@ static const struct sc_option options[] = {
"Default is 0 (not forced): the local port used for " "Default is 0 (not forced): the local port used for "
"establishing the tunnel will be used.", "establishing the tunnel will be used.",
}, },
#ifdef HAVE_V4L2
{ {
.longopt_id = OPT_V4L2_SINK, .longopt_id = OPT_V4L2_SINK,
.longopt = "v4l2-sink", .longopt = "v4l2-sink",
.argdesc = "/dev/videoN", .argdesc = "/dev/videoN",
.text = "Output to v4l2loopback device.\n" .text = "Output to v4l2loopback device.\n"
"It requires to lock the video orientation (see " "It requires to lock the video orientation (see "
"--lock-video-orientation).", "--lock-video-orientation).\n"
"This feature is only available on Linux.",
}, },
{ {
.longopt_id = OPT_V4L2_BUFFER, .longopt_id = OPT_V4L2_BUFFER,
@ -398,9 +408,9 @@ static const struct sc_option options[] = {
"frames. This increases latency to compensate for jitter.\n" "frames. This increases latency to compensate for jitter.\n"
"This option is similar to --display-buffer, but specific to " "This option is similar to --display-buffer, but specific to "
"V4L2 sink.\n" "V4L2 sink.\n"
"Default is 0 (no buffering).", "Default is 0 (no buffering).\n"
"This option is only available on Linux.",
}, },
#endif
{ {
.shortopt = 'V', .shortopt = 'V',
.longopt = "verbosity", .longopt = "verbosity",
@ -1121,7 +1131,7 @@ parse_log_level(const char *s, enum sc_log_level *log_level) {
} }
// item is a list of mod keys separated by '+' (e.g. "lctrl+lalt") // 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) // returns a bitwise-or of SC_SHORTCUT_MOD_* constants (or 0 on error)
static unsigned static unsigned
parse_shortcut_mods_item(const char *item, size_t len) { parse_shortcut_mods_item(const char *item, size_t len) {
unsigned mod = 0; unsigned mod = 0;
@ -1139,17 +1149,17 @@ parse_shortcut_mods_item(const char *item, size_t len) {
((sizeof(literal)-1 == len) && !memcmp(literal, s, len)) ((sizeof(literal)-1 == len) && !memcmp(literal, s, len))
if (STREQ("lctrl", item, key_len)) { if (STREQ("lctrl", item, key_len)) {
mod |= SC_MOD_LCTRL; mod |= SC_SHORTCUT_MOD_LCTRL;
} else if (STREQ("rctrl", item, key_len)) { } else if (STREQ("rctrl", item, key_len)) {
mod |= SC_MOD_RCTRL; mod |= SC_SHORTCUT_MOD_RCTRL;
} else if (STREQ("lalt", item, key_len)) { } else if (STREQ("lalt", item, key_len)) {
mod |= SC_MOD_LALT; mod |= SC_SHORTCUT_MOD_LALT;
} else if (STREQ("ralt", item, key_len)) { } else if (STREQ("ralt", item, key_len)) {
mod |= SC_MOD_RALT; mod |= SC_SHORTCUT_MOD_RALT;
} else if (STREQ("lsuper", item, key_len)) { } else if (STREQ("lsuper", item, key_len)) {
mod |= SC_MOD_LSUPER; mod |= SC_SHORTCUT_MOD_LSUPER;
} else if (STREQ("rsuper", item, key_len)) { } else if (STREQ("rsuper", item, key_len)) {
mod |= SC_MOD_RSUPER; mod |= SC_SHORTCUT_MOD_RSUPER;
} else { } else {
LOGE("Unknown modifier key: %.*s " LOGE("Unknown modifier key: %.*s "
"(must be one of: lctrl, rctrl, lalt, ralt, lsuper, rsuper)", "(must be one of: lctrl, rctrl, lalt, ralt, lsuper, rsuper)",
@ -1300,7 +1310,13 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
args->help = true; args->help = true;
break; break;
case 'K': case 'K':
#ifdef HAVE_AOA_HID
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_HID; opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_HID;
#else
LOGE("HID over AOA (-K/--hid-keyboard) is not supported on "
"this platform. It is only available on Linux.");
return false;
#endif
break; break;
case OPT_MAX_FPS: case OPT_MAX_FPS:
if (!parse_max_fps(optarg, &opts->max_fps)) { if (!parse_max_fps(optarg, &opts->max_fps)) {
@ -1312,6 +1328,15 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false; return false;
} }
break; break;
case 'M':
#ifdef HAVE_AOA_HID
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_HID;
#else
LOGE("HID over AOA (-M/--hid-mouse) is not supported on this"
"platform. It is only available on Linux.");
return false;
#endif
break;
case OPT_LOCK_VIDEO_ORIENTATION: case OPT_LOCK_VIDEO_ORIENTATION:
if (!parse_lock_video_orientation(optarg, if (!parse_lock_video_orientation(optarg,
&opts->lock_video_orientation)) { &opts->lock_video_orientation)) {
@ -1464,16 +1489,24 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
opts->tcpip = true; opts->tcpip = true;
opts->tcpip_dst = optarg; opts->tcpip_dst = optarg;
break; break;
#ifdef HAVE_V4L2
case OPT_V4L2_SINK: case OPT_V4L2_SINK:
#ifdef HAVE_V4L2
opts->v4l2_device = optarg; opts->v4l2_device = optarg;
#else
LOGE("V4L2 (--v4l2-sink) is only available on Linux.");
return false;
#endif
break; break;
case OPT_V4L2_BUFFER: case OPT_V4L2_BUFFER:
#ifdef HAVE_V4L2
if (!parse_buffering_time(optarg, &opts->v4l2_buffer)) { if (!parse_buffering_time(optarg, &opts->v4l2_buffer)) {
return false; return false;
} }
break; #else
LOGE("V4L2 (--v4l2-buffer) is only available on Linux.");
return false;
#endif #endif
break;
default: default:
// getopt prints the error message on stderr // getopt prints the error message on stderr
return false; return false;

View File

@ -7,6 +7,7 @@
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
#define MIN(X,Y) (X) < (Y) ? (X) : (Y) #define MIN(X,Y) (X) < (Y) ? (X) : (Y)
#define MAX(X,Y) (X) > (Y) ? (X) : (Y) #define MAX(X,Y) (X) > (Y) ? (X) : (Y)
#define CLAMP(V,X,Y) MIN( MAX((V),(X)), (Y) )
#define container_of(ptr, type, member) \ #define container_of(ptr, type, member) \
((type *) (((char *) (ptr)) - offsetof(type, member))) ((type *) (((char *) (ptr)) - offsetof(type, member)))

View File

@ -89,7 +89,7 @@ to_fixed_point_16(float f) {
} }
size_t size_t
control_msg_serialize(const struct control_msg *msg, unsigned char *buf) { sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
buf[0] = msg->type; buf[0] = msg->type;
switch (msg->type) { switch (msg->type) {
case CONTROL_MSG_TYPE_INJECT_KEYCODE: case CONTROL_MSG_TYPE_INJECT_KEYCODE:
@ -119,7 +119,8 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
(uint32_t) msg->inject_scroll_event.hscroll); (uint32_t) msg->inject_scroll_event.hscroll);
buffer_write32be(&buf[17], buffer_write32be(&buf[17],
(uint32_t) msg->inject_scroll_event.vscroll); (uint32_t) msg->inject_scroll_event.vscroll);
return 21; buffer_write32be(&buf[21], msg->inject_scroll_event.buttons);
return 25;
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON: case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
buf[1] = msg->inject_keycode.action; buf[1] = msg->inject_keycode.action;
return 2; return 2;
@ -150,7 +151,7 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
} }
void void
control_msg_log(const struct control_msg *msg) { sc_control_msg_log(const struct sc_control_msg *msg) {
#define LOG_CMSG(fmt, ...) LOGV("input: " fmt, ## __VA_ARGS__) #define LOG_CMSG(fmt, ...) LOGV("input: " fmt, ## __VA_ARGS__)
switch (msg->type) { switch (msg->type) {
case CONTROL_MSG_TYPE_INJECT_KEYCODE: case CONTROL_MSG_TYPE_INJECT_KEYCODE:
@ -192,11 +193,12 @@ control_msg_log(const struct control_msg *msg) {
} }
case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT: case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%" PRIi32 LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%" PRIi32
" vscroll=%" PRIi32, " vscroll=%" PRIi32 " buttons=%06lx",
msg->inject_scroll_event.position.point.x, msg->inject_scroll_event.position.point.x,
msg->inject_scroll_event.position.point.y, msg->inject_scroll_event.position.point.y,
msg->inject_scroll_event.hscroll, msg->inject_scroll_event.hscroll,
msg->inject_scroll_event.vscroll); msg->inject_scroll_event.vscroll,
(long) msg->inject_scroll_event.buttons);
break; break;
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON: case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
LOG_CMSG("back-or-screen-on %s", LOG_CMSG("back-or-screen-on %s",
@ -235,7 +237,7 @@ control_msg_log(const struct control_msg *msg) {
} }
void void
control_msg_destroy(struct control_msg *msg) { sc_control_msg_destroy(struct sc_control_msg *msg) {
switch (msg->type) { switch (msg->type) {
case CONTROL_MSG_TYPE_INJECT_TEXT: case CONTROL_MSG_TYPE_INJECT_TEXT:
free(msg->inject_text.text); free(msg->inject_text.text);

View File

@ -20,7 +20,7 @@
#define POINTER_ID_MOUSE UINT64_C(-1) #define POINTER_ID_MOUSE UINT64_C(-1)
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2) #define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2)
enum control_msg_type { enum sc_control_msg_type {
CONTROL_MSG_TYPE_INJECT_KEYCODE, CONTROL_MSG_TYPE_INJECT_KEYCODE,
CONTROL_MSG_TYPE_INJECT_TEXT, CONTROL_MSG_TYPE_INJECT_TEXT,
CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
@ -47,8 +47,8 @@ enum get_clipboard_copy_key {
GET_CLIPBOARD_COPY_KEY_CUT, GET_CLIPBOARD_COPY_KEY_CUT,
}; };
struct control_msg { struct sc_control_msg {
enum control_msg_type type; enum sc_control_msg_type type;
union { union {
struct { struct {
enum android_keyevent_action action; enum android_keyevent_action action;
@ -70,6 +70,7 @@ struct control_msg {
struct sc_position position; struct sc_position position;
int32_t hscroll; int32_t hscroll;
int32_t vscroll; int32_t vscroll;
enum android_motionevent_buttons buttons;
} inject_scroll_event; } inject_scroll_event;
struct { struct {
enum android_keyevent_action action; // action for the BACK key enum android_keyevent_action action; // action for the BACK key
@ -92,12 +93,12 @@ struct control_msg {
// buf size must be at least CONTROL_MSG_MAX_SIZE // buf size must be at least CONTROL_MSG_MAX_SIZE
// return the number of bytes written // return the number of bytes written
size_t size_t
control_msg_serialize(const struct control_msg *msg, unsigned char *buf); sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf);
void void
control_msg_log(const struct control_msg *msg); sc_control_msg_log(const struct sc_control_msg *msg);
void void
control_msg_destroy(struct control_msg *msg); sc_control_msg_destroy(struct sc_control_msg *msg);
#endif #endif

View File

@ -5,8 +5,8 @@
#include "util/log.h" #include "util/log.h"
bool bool
controller_init(struct controller *controller, sc_socket control_socket, sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
struct sc_acksync *acksync) { struct sc_acksync *acksync) {
cbuf_init(&controller->queue); cbuf_init(&controller->queue);
bool ok = receiver_init(&controller->receiver, control_socket, acksync); bool ok = receiver_init(&controller->receiver, control_socket, acksync);
@ -34,23 +34,23 @@ controller_init(struct controller *controller, sc_socket control_socket,
} }
void void
controller_destroy(struct controller *controller) { sc_controller_destroy(struct sc_controller *controller) {
sc_cond_destroy(&controller->msg_cond); sc_cond_destroy(&controller->msg_cond);
sc_mutex_destroy(&controller->mutex); sc_mutex_destroy(&controller->mutex);
struct control_msg msg; struct sc_control_msg msg;
while (cbuf_take(&controller->queue, &msg)) { while (cbuf_take(&controller->queue, &msg)) {
control_msg_destroy(&msg); sc_control_msg_destroy(&msg);
} }
receiver_destroy(&controller->receiver); receiver_destroy(&controller->receiver);
} }
bool bool
controller_push_msg(struct controller *controller, sc_controller_push_msg(struct sc_controller *controller,
const struct control_msg *msg) { const struct sc_control_msg *msg) {
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) { if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
control_msg_log(msg); sc_control_msg_log(msg);
} }
sc_mutex_lock(&controller->mutex); sc_mutex_lock(&controller->mutex);
@ -64,9 +64,10 @@ controller_push_msg(struct controller *controller,
} }
static bool static bool
process_msg(struct controller *controller, const struct control_msg *msg) { process_msg(struct sc_controller *controller,
const struct sc_control_msg *msg) {
static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE]; static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE];
size_t length = control_msg_serialize(msg, serialized_msg); size_t length = sc_control_msg_serialize(msg, serialized_msg);
if (!length) { if (!length) {
return false; return false;
} }
@ -77,7 +78,7 @@ process_msg(struct controller *controller, const struct control_msg *msg) {
static int static int
run_controller(void *data) { run_controller(void *data) {
struct controller *controller = data; struct sc_controller *controller = data;
for (;;) { for (;;) {
sc_mutex_lock(&controller->mutex); sc_mutex_lock(&controller->mutex);
@ -89,14 +90,14 @@ run_controller(void *data) {
sc_mutex_unlock(&controller->mutex); sc_mutex_unlock(&controller->mutex);
break; break;
} }
struct control_msg msg; struct sc_control_msg msg;
bool non_empty = cbuf_take(&controller->queue, &msg); bool non_empty = cbuf_take(&controller->queue, &msg);
assert(non_empty); assert(non_empty);
(void) non_empty; (void) non_empty;
sc_mutex_unlock(&controller->mutex); sc_mutex_unlock(&controller->mutex);
bool ok = process_msg(controller, &msg); bool ok = process_msg(controller, &msg);
control_msg_destroy(&msg); sc_control_msg_destroy(&msg);
if (!ok) { if (!ok) {
LOGD("Could not write msg to socket"); LOGD("Could not write msg to socket");
break; break;
@ -106,7 +107,7 @@ run_controller(void *data) {
} }
bool bool
controller_start(struct controller *controller) { sc_controller_start(struct sc_controller *controller) {
LOGD("Starting controller thread"); LOGD("Starting controller thread");
bool ok = sc_thread_create(&controller->thread, run_controller, bool ok = sc_thread_create(&controller->thread, run_controller,
@ -117,7 +118,7 @@ controller_start(struct controller *controller) {
} }
if (!receiver_start(&controller->receiver)) { if (!receiver_start(&controller->receiver)) {
controller_stop(controller); sc_controller_stop(controller);
sc_thread_join(&controller->thread, NULL); sc_thread_join(&controller->thread, NULL);
return false; return false;
} }
@ -126,7 +127,7 @@ controller_start(struct controller *controller) {
} }
void void
controller_stop(struct controller *controller) { sc_controller_stop(struct sc_controller *controller) {
sc_mutex_lock(&controller->mutex); sc_mutex_lock(&controller->mutex);
controller->stopped = true; controller->stopped = true;
sc_cond_signal(&controller->msg_cond); sc_cond_signal(&controller->msg_cond);
@ -134,7 +135,7 @@ controller_stop(struct controller *controller) {
} }
void void
controller_join(struct controller *controller) { sc_controller_join(struct sc_controller *controller) {
sc_thread_join(&controller->thread, NULL); sc_thread_join(&controller->thread, NULL);
receiver_join(&controller->receiver); receiver_join(&controller->receiver);
} }

View File

@ -12,36 +12,36 @@
#include "util/net.h" #include "util/net.h"
#include "util/thread.h" #include "util/thread.h"
struct control_msg_queue CBUF(struct control_msg, 64); struct sc_control_msg_queue CBUF(struct sc_control_msg, 64);
struct controller { struct sc_controller {
sc_socket control_socket; sc_socket control_socket;
sc_thread thread; sc_thread thread;
sc_mutex mutex; sc_mutex mutex;
sc_cond msg_cond; sc_cond msg_cond;
bool stopped; bool stopped;
struct control_msg_queue queue; struct sc_control_msg_queue queue;
struct receiver receiver; struct receiver receiver;
}; };
bool bool
controller_init(struct controller *controller, sc_socket control_socket, sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
struct sc_acksync *acksync); struct sc_acksync *acksync);
void void
controller_destroy(struct controller *controller); sc_controller_destroy(struct sc_controller *controller);
bool bool
controller_start(struct controller *controller); sc_controller_start(struct sc_controller *controller);
void void
controller_stop(struct controller *controller); sc_controller_stop(struct sc_controller *controller);
void void
controller_join(struct controller *controller); sc_controller_join(struct sc_controller *controller);
bool bool
controller_push_msg(struct controller *controller, sc_controller_push_msg(struct sc_controller *controller,
const struct control_msg *msg); const struct sc_control_msg *msg);
#endif #endif

View File

@ -6,6 +6,7 @@
#include "trait/packet_sink.h" #include "trait/packet_sink.h"
#include <stdbool.h> #include <stdbool.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#define DECODER_MAX_SINKS 2 #define DECODER_MAX_SINKS 2

View File

@ -1,8 +1,8 @@
#include "hid_keyboard.h" #include "hid_keyboard.h"
#include <assert.h> #include <assert.h>
#include <SDL2/SDL_events.h>
#include "input_events.h"
#include "util/log.h" #include "util/log.h"
/** Downcast key processor to hid_keyboard */ /** Downcast key processor to hid_keyboard */
@ -201,30 +201,30 @@ static const unsigned char keyboard_report_desc[] = {
*/ */
static unsigned char static unsigned char
sdl_keymod_to_hid_modifiers(SDL_Keymod mod) { sdl_keymod_to_hid_modifiers(uint16_t mod) {
unsigned char modifiers = HID_MODIFIER_NONE; unsigned char modifiers = HID_MODIFIER_NONE;
if (mod & KMOD_LCTRL) { if (mod & SC_MOD_LCTRL) {
modifiers |= HID_MODIFIER_LEFT_CONTROL; modifiers |= HID_MODIFIER_LEFT_CONTROL;
} }
if (mod & KMOD_LSHIFT) { if (mod & SC_MOD_LSHIFT) {
modifiers |= HID_MODIFIER_LEFT_SHIFT; modifiers |= HID_MODIFIER_LEFT_SHIFT;
} }
if (mod & KMOD_LALT) { if (mod & SC_MOD_LALT) {
modifiers |= HID_MODIFIER_LEFT_ALT; modifiers |= HID_MODIFIER_LEFT_ALT;
} }
if (mod & KMOD_LGUI) { if (mod & SC_MOD_LGUI) {
modifiers |= HID_MODIFIER_LEFT_GUI; modifiers |= HID_MODIFIER_LEFT_GUI;
} }
if (mod & KMOD_RCTRL) { if (mod & SC_MOD_RCTRL) {
modifiers |= HID_MODIFIER_RIGHT_CONTROL; modifiers |= HID_MODIFIER_RIGHT_CONTROL;
} }
if (mod & KMOD_RSHIFT) { if (mod & SC_MOD_RSHIFT) {
modifiers |= HID_MODIFIER_RIGHT_SHIFT; modifiers |= HID_MODIFIER_RIGHT_SHIFT;
} }
if (mod & KMOD_RALT) { if (mod & SC_MOD_RALT) {
modifiers |= HID_MODIFIER_RIGHT_ALT; modifiers |= HID_MODIFIER_RIGHT_ALT;
} }
if (mod & KMOD_RGUI) { if (mod & SC_MOD_RGUI) {
modifiers |= HID_MODIFIER_RIGHT_GUI; modifiers |= HID_MODIFIER_RIGHT_GUI;
} }
return modifiers; return modifiers;
@ -248,15 +248,15 @@ sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) {
} }
static inline bool static inline bool
scancode_is_modifier(SDL_Scancode scancode) { scancode_is_modifier(enum sc_scancode scancode) {
return scancode >= SDL_SCANCODE_LCTRL && scancode <= SDL_SCANCODE_RGUI; return scancode >= SC_SCANCODE_LCTRL && scancode <= SC_SCANCODE_RGUI;
} }
static bool static bool
convert_hid_keyboard_event(struct sc_hid_keyboard *kb, convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
struct sc_hid_event *hid_event, struct sc_hid_event *hid_event,
const SDL_KeyboardEvent *event) { const struct sc_key_event *event) {
SDL_Scancode scancode = event->keysym.scancode; enum sc_scancode scancode = event->scancode;
assert(scancode >= 0); assert(scancode >= 0);
// SDL also generates events when only modifiers are pressed, we cannot // SDL also generates events when only modifiers are pressed, we cannot
@ -272,11 +272,11 @@ convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
return false; return false;
} }
unsigned char modifiers = sdl_keymod_to_hid_modifiers(event->keysym.mod); unsigned char modifiers = sdl_keymod_to_hid_modifiers(event->mods_state);
if (scancode < SC_HID_KEYBOARD_KEYS) { if (scancode < SC_HID_KEYBOARD_KEYS) {
// Pressed is true and released is false // Pressed is true and released is false
kb->keys[scancode] = (event->type == SDL_KEYDOWN); kb->keys[scancode] = (event->action == SC_ACTION_DOWN);
LOGV("keys[%02x] = %s", scancode, LOGV("keys[%02x] = %s", scancode,
kb->keys[scancode] ? "true" : "false"); kb->keys[scancode] ? "true" : "false");
} }
@ -306,17 +306,17 @@ convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
end: end:
LOGV("hid keyboard: key %-4s scancode=%02x (%u) mod=%02x", LOGV("hid keyboard: key %-4s scancode=%02x (%u) mod=%02x",
event->type == SDL_KEYDOWN ? "down" : "up", event->keysym.scancode, event->action == SC_ACTION_DOWN ? "down" : "up", event->scancode,
event->keysym.scancode, modifiers); event->scancode, modifiers);
return true; return true;
} }
static bool static bool
push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t sdl_mod) { push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t mods_state) {
bool capslock = sdl_mod & KMOD_CAPS; bool capslock = mods_state & SC_MOD_CAPS;
bool numlock = sdl_mod & KMOD_NUM; bool numlock = mods_state & SC_MOD_NUM;
if (!capslock && !numlock) { if (!capslock && !numlock) {
// Nothing to do // Nothing to do
return true; return true;
@ -328,8 +328,6 @@ push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t sdl_mod) {
return false; return false;
} }
#define SC_SCANCODE_CAPSLOCK SDL_SCANCODE_CAPSLOCK
#define SC_SCANCODE_NUMLOCK SDL_SCANCODE_NUMLOCKCLEAR
unsigned i = 0; unsigned i = 0;
if (capslock) { if (capslock) {
hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK; hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK;
@ -353,7 +351,7 @@ push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t sdl_mod) {
static void static void
sc_key_processor_process_key(struct sc_key_processor *kp, sc_key_processor_process_key(struct sc_key_processor *kp,
const SDL_KeyboardEvent *event, const struct sc_key_event *event,
uint64_t ack_to_wait) { uint64_t ack_to_wait) {
if (event->repeat) { if (event->repeat) {
// In USB HID protocol, key repeat is handled by the host (Android), so // In USB HID protocol, key repeat is handled by the host (Android), so
@ -369,7 +367,7 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
if (!kb->mod_lock_synchronized) { if (!kb->mod_lock_synchronized) {
// Inject CAPSLOCK and/or NUMLOCK if necessary to synchronize // Inject CAPSLOCK and/or NUMLOCK if necessary to synchronize
// keyboard state // keyboard state
if (push_mod_lock_state(kb, event->keysym.mod)) { if (push_mod_lock_state(kb, event->mods_state)) {
kb->mod_lock_synchronized = true; kb->mod_lock_synchronized = true;
} }
} }
@ -389,15 +387,6 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
} }
} }
static void
sc_key_processor_process_text(struct sc_key_processor *kp,
const SDL_TextInputEvent *event) {
(void) kp;
(void) event;
// Never forward text input via HID (all the keys are injected separately)
}
bool bool
sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) { sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) {
kb->aoa = aoa; kb->aoa = aoa;
@ -417,7 +406,9 @@ sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) {
static const struct sc_key_processor_ops ops = { static const struct sc_key_processor_ops ops = {
.process_key = sc_key_processor_process_key, .process_key = sc_key_processor_process_key,
.process_text = sc_key_processor_process_text, // Never forward text input via HID (all the keys are injected
// separately)
.process_text = NULL,
}; };
// Clipboard synchronization is requested over the control socket, while HID // Clipboard synchronization is requested over the control socket, while HID

267
app/src/hid_mouse.c Normal file
View File

@ -0,0 +1,267 @@
#include "hid_mouse.h"
#include <assert.h>
#include "input_events.h"
#include "util/log.h"
/** Downcast mouse processor to hid_mouse */
#define DOWNCAST(MP) container_of(MP, struct sc_hid_mouse, mouse_processor)
#define HID_MOUSE_ACCESSORY_ID 2
// 1 byte for buttons + padding, 1 byte for X position, 1 byte for Y position
#define HID_MOUSE_EVENT_SIZE 4
/**
* Mouse descriptor from the specification:
* <https://www.usb.org/sites/default/files/hid1_11.pdf>
*
* Appendix E (p71): §E.10 Report Descriptor (Mouse)
*
* The usage tags (like Wheel) are listed in "HID Usage Tables":
* <https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf>
* §4 Generic Desktop Page (0x01) (p26)
*/
static const unsigned char mouse_report_desc[] = {
// Usage Page (Generic Desktop)
0x05, 0x01,
// Usage (Mouse)
0x09, 0x02,
// Collection (Application)
0xA1, 0x01,
// Usage (Pointer)
0x09, 0x01,
// Collection (Physical)
0xA1, 0x00,
// Usage Page (Buttons)
0x05, 0x09,
// Usage Minimum (1)
0x19, 0x01,
// Usage Maximum (5)
0x29, 0x05,
// Logical Minimum (0)
0x15, 0x00,
// Logical Maximum (1)
0x25, 0x01,
// Report Count (5)
0x95, 0x05,
// Report Size (1)
0x75, 0x01,
// Input (Data, Variable, Absolute): 5 buttons bits
0x81, 0x02,
// Report Count (1)
0x95, 0x01,
// Report Size (3)
0x75, 0x03,
// Input (Constant): 3 bits padding
0x81, 0x01,
// Usage Page (Generic Desktop)
0x05, 0x01,
// Usage (X)
0x09, 0x30,
// Usage (Y)
0x09, 0x31,
// Usage (Wheel)
0x09, 0x38,
// Local Minimum (-127)
0x15, 0x81,
// Local Maximum (127)
0x25, 0x7F,
// Report Size (8)
0x75, 0x08,
// Report Count (3)
0x95, 0x03,
// Input (Data, Variable, Relative): 3 position bytes (X, Y, Wheel)
0x81, 0x06,
// End Collection
0xC0,
// End Collection
0xC0,
};
/**
* A mouse HID event is 3 bytes long:
*
* - byte 0: buttons state
* - byte 1: relative x motion (signed byte from -127 to 127)
* - byte 2: relative y motion (signed byte from -127 to 127)
*
* 7 6 5 4 3 2 1 0
* +---------------+
* byte 0: |0 0 0 . . . . .| buttons state
* +---------------+
* ^ ^ ^ ^ ^
* | | | | `- left button
* | | | `--- right button
* | | `----- middle button
* | `------- button 4
* `--------- button 5
*
* +---------------+
* byte 1: |. . . . . . . .| relative x motion
* +---------------+
* byte 2: |. . . . . . . .| relative y motion
* +---------------+
* byte 3: |. . . . . . . .| wheel motion (-1, 0 or 1)
* +---------------+
*
* As an example, here is the report for a motion of (x=5, y=-4) with left
* button pressed:
*
* +---------------+
* |0 0 0 0 0 0 0 1| left button pressed
* +---------------+
* |0 0 0 0 0 1 0 1| horizontal motion (x = 5)
* +---------------+
* |1 1 1 1 1 1 0 0| relative y motion (y = -4)
* +---------------+
* |0 0 0 0 0 0 0 0| wheel motion
* +---------------+
*/
static bool
sc_hid_mouse_event_init(struct sc_hid_event *hid_event) {
unsigned char *buffer = calloc(1, HID_MOUSE_EVENT_SIZE);
if (!buffer) {
LOG_OOM();
return false;
}
sc_hid_event_init(hid_event, HID_MOUSE_ACCESSORY_ID, buffer,
HID_MOUSE_EVENT_SIZE);
return true;
}
static unsigned char
buttons_state_to_hid_buttons(uint8_t buttons_state) {
unsigned char c = 0;
if (buttons_state & SC_MOUSE_BUTTON_LEFT) {
c |= 1 << 0;
}
if (buttons_state & SC_MOUSE_BUTTON_RIGHT) {
c |= 1 << 1;
}
if (buttons_state & SC_MOUSE_BUTTON_MIDDLE) {
c |= 1 << 2;
}
if (buttons_state & SC_MOUSE_BUTTON_X1) {
c |= 1 << 3;
}
if (buttons_state & SC_MOUSE_BUTTON_X2) {
c |= 1 << 4;
}
return c;
}
static void
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
const struct sc_mouse_motion_event *event) {
struct sc_hid_mouse *mouse = DOWNCAST(mp);
struct sc_hid_event hid_event;
if (!sc_hid_mouse_event_init(&hid_event)) {
return;
}
unsigned char *buffer = hid_event.buffer;
buffer[0] = buttons_state_to_hid_buttons(event->buttons_state);
buffer[1] = CLAMP(event->xrel, -127, 127);
buffer[2] = CLAMP(event->yrel, -127, 127);
buffer[3] = 0; // wheel coordinates only used for scrolling
if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) {
sc_hid_event_destroy(&hid_event);
LOGW("Could request HID event");
}
}
static void
sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
const struct sc_mouse_click_event *event) {
struct sc_hid_mouse *mouse = DOWNCAST(mp);
struct sc_hid_event hid_event;
if (!sc_hid_mouse_event_init(&hid_event)) {
return;
}
unsigned char *buffer = hid_event.buffer;
buffer[0] = buttons_state_to_hid_buttons(event->buttons_state);
buffer[1] = 0; // no x motion
buffer[2] = 0; // no y motion
buffer[3] = 0; // wheel coordinates only used for scrolling
if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) {
sc_hid_event_destroy(&hid_event);
LOGW("Could request HID event");
}
}
static void
sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
const struct sc_mouse_scroll_event *event) {
struct sc_hid_mouse *mouse = DOWNCAST(mp);
struct sc_hid_event hid_event;
if (!sc_hid_mouse_event_init(&hid_event)) {
return;
}
unsigned char *buffer = hid_event.buffer;
buffer[0] = 0; // buttons state irrelevant (and unknown)
buffer[1] = 0; // no x motion
buffer[2] = 0; // no y motion
// In practice, vscroll is always -1, 0 or 1, but in theory other values
// are possible
buffer[3] = CLAMP(event->vscroll, -127, 127);
// Horizontal scrolling ignored
if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) {
sc_hid_event_destroy(&hid_event);
LOGW("Could request HID event");
}
}
bool
sc_hid_mouse_init(struct sc_hid_mouse *mouse, struct sc_aoa *aoa) {
mouse->aoa = aoa;
bool ok = sc_aoa_setup_hid(aoa, HID_MOUSE_ACCESSORY_ID, mouse_report_desc,
ARRAY_LEN(mouse_report_desc));
if (!ok) {
LOGW("Register HID mouse failed");
return false;
}
static const struct sc_mouse_processor_ops ops = {
.process_mouse_motion = sc_mouse_processor_process_mouse_motion,
.process_mouse_click = sc_mouse_processor_process_mouse_click,
.process_mouse_scroll = sc_mouse_processor_process_mouse_scroll,
// Touch events not supported (coordinates are not relative)
.process_touch = NULL,
};
mouse->mouse_processor.ops = &ops;
mouse->mouse_processor.relative_mode = true;
return true;
}
void
sc_hid_mouse_destroy(struct sc_hid_mouse *mouse) {
bool ok = sc_aoa_unregister_hid(mouse->aoa, HID_MOUSE_ACCESSORY_ID);
if (!ok) {
LOGW("Could not unregister HID");
}
}

23
app/src/hid_mouse.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef HID_MOUSE_H
#define HID_MOUSE_H
#include "common.h"
#include <stdbool.h>
#include "aoa_hid.h"
#include "trait/mouse_processor.h"
struct sc_hid_mouse {
struct sc_mouse_processor mouse_processor; // mouse processor trait
struct sc_aoa *aoa;
};
bool
sc_hid_mouse_init(struct sc_hid_mouse *mouse, struct sc_aoa *aoa);
void
sc_hid_mouse_destroy(struct sc_hid_mouse *mouse);
#endif

View File

@ -2,6 +2,7 @@
#include <assert.h> #include <assert.h>
#include <stdbool.h> #include <stdbool.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <libavutil/pixdesc.h> #include <libavutil/pixdesc.h>
#include <libavutil/pixfmt.h> #include <libavutil/pixfmt.h>
@ -85,7 +86,7 @@ decode_image(const char *path) {
AVCodecParameters *params = ctx->streams[stream]->codecpar; AVCodecParameters *params = ctx->streams[stream]->codecpar;
AVCodec *codec = avcodec_find_decoder(params->codec_id); const AVCodec *codec = avcodec_find_decoder(params->codec_id);
if (!codec) { if (!codec) {
LOGE("Could not find image decoder"); LOGE("Could not find image decoder");
goto close_input; goto close_input;

380
app/src/input_events.h Normal file
View File

@ -0,0 +1,380 @@
#ifndef SC_INPUT_EVENTS_H
#define SC_INPUT_EVENTS_H
#include "common.h"
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <SDL2/SDL_events.h>
#include "coords.h"
/* The representation of input events in scrcpy is very close to the SDL API,
* for simplicity.
*
* This scrcpy input events API is designed to be consumed by input event
* processors (sc_key_processor and sc_mouse_processor, see app/src/trait/).
*
* One major semantic difference between SDL input events and scrcpy input
* events is their frame of reference (for mouse and touch events): SDL events
* coordinates are expressed in SDL window coordinates (the visible UI), while
* scrcpy events are expressed in device frame coordinates.
*
* In particular, the window may be visually scaled or rotated (with --rotation
* or MOD+Left/Right), but this does not impact scrcpy input events (contrary
* to SDL input events). This allows to abstract these display details from the
* input event processors (and to make them independent from the "screen").
*
* For many enums below, the values are purposely the same as the SDL
* constants (though not all SDL values are represented), so that the
* implementation to convert from the SDL version to the scrcpy version is
* straightforward.
*
* In practice, there are 3 levels of input events:
* 1. SDL input events (as received from SDL)
* 2. scrcpy input events (this API)
* 3. the key/mouse processors input events (Android API or HID events)
*
* An input event is first received (1), then (if accepted) converted to an
* scrcpy input event (2), then submitted to the relevant key/mouse processor,
* which (if accepted) is converted to an Android event (to be sent to the
* server) or to an HID event (to be sent over USB/AOA directly).
*/
enum sc_mod {
SC_MOD_LSHIFT = KMOD_LSHIFT,
SC_MOD_RSHIFT = KMOD_RSHIFT,
SC_MOD_LCTRL = KMOD_LCTRL,
SC_MOD_RCTRL = KMOD_RCTRL,
SC_MOD_LALT = KMOD_LALT,
SC_MOD_RALT = KMOD_RALT,
SC_MOD_LGUI = KMOD_LGUI,
SC_MOD_RGUI = KMOD_RGUI,
SC_MOD_NUM = KMOD_NUM,
SC_MOD_CAPS = KMOD_CAPS,
};
enum sc_action {
SC_ACTION_DOWN, // key or button pressed
SC_ACTION_UP, // key or button released
};
enum sc_keycode {
SC_KEYCODE_UNKNOWN = SDLK_UNKNOWN,
SC_KEYCODE_RETURN = SDLK_RETURN,
SC_KEYCODE_ESCAPE = SDLK_ESCAPE,
SC_KEYCODE_BACKSPACE = SDLK_BACKSPACE,
SC_KEYCODE_TAB = SDLK_TAB,
SC_KEYCODE_SPACE = SDLK_SPACE,
SC_KEYCODE_EXCLAIM = SDLK_EXCLAIM,
SC_KEYCODE_QUOTEDBL = SDLK_QUOTEDBL,
SC_KEYCODE_HASH = SDLK_HASH,
SC_KEYCODE_PERCENT = SDLK_PERCENT,
SC_KEYCODE_DOLLAR = SDLK_DOLLAR,
SC_KEYCODE_AMPERSAND = SDLK_AMPERSAND,
SC_KEYCODE_QUOTE = SDLK_QUOTE,
SC_KEYCODE_LEFTPAREN = SDLK_LEFTPAREN,
SC_KEYCODE_RIGHTPAREN = SDLK_RIGHTPAREN,
SC_KEYCODE_ASTERISK = SDLK_ASTERISK,
SC_KEYCODE_PLUS = SDLK_PLUS,
SC_KEYCODE_COMMA = SDLK_COMMA,
SC_KEYCODE_MINUS = SDLK_MINUS,
SC_KEYCODE_PERIOD = SDLK_PERIOD,
SC_KEYCODE_SLASH = SDLK_SLASH,
SC_KEYCODE_0 = SDLK_0,
SC_KEYCODE_1 = SDLK_1,
SC_KEYCODE_2 = SDLK_2,
SC_KEYCODE_3 = SDLK_3,
SC_KEYCODE_4 = SDLK_4,
SC_KEYCODE_5 = SDLK_5,
SC_KEYCODE_6 = SDLK_6,
SC_KEYCODE_7 = SDLK_7,
SC_KEYCODE_8 = SDLK_8,
SC_KEYCODE_9 = SDLK_9,
SC_KEYCODE_COLON = SDLK_COLON,
SC_KEYCODE_SEMICOLON = SDLK_SEMICOLON,
SC_KEYCODE_LESS = SDLK_LESS,
SC_KEYCODE_EQUALS = SDLK_EQUALS,
SC_KEYCODE_GREATER = SDLK_GREATER,
SC_KEYCODE_QUESTION = SDLK_QUESTION,
SC_KEYCODE_AT = SDLK_AT,
SC_KEYCODE_LEFTBRACKET = SDLK_LEFTBRACKET,
SC_KEYCODE_BACKSLASH = SDLK_BACKSLASH,
SC_KEYCODE_RIGHTBRACKET = SDLK_RIGHTBRACKET,
SC_KEYCODE_CARET = SDLK_CARET,
SC_KEYCODE_UNDERSCORE = SDLK_UNDERSCORE,
SC_KEYCODE_BACKQUOTE = SDLK_BACKQUOTE,
SC_KEYCODE_a = SDLK_a,
SC_KEYCODE_b = SDLK_b,
SC_KEYCODE_c = SDLK_c,
SC_KEYCODE_d = SDLK_d,
SC_KEYCODE_e = SDLK_e,
SC_KEYCODE_f = SDLK_f,
SC_KEYCODE_g = SDLK_g,
SC_KEYCODE_h = SDLK_h,
SC_KEYCODE_i = SDLK_i,
SC_KEYCODE_j = SDLK_j,
SC_KEYCODE_k = SDLK_k,
SC_KEYCODE_l = SDLK_l,
SC_KEYCODE_m = SDLK_m,
SC_KEYCODE_n = SDLK_n,
SC_KEYCODE_o = SDLK_o,
SC_KEYCODE_p = SDLK_p,
SC_KEYCODE_q = SDLK_q,
SC_KEYCODE_r = SDLK_r,
SC_KEYCODE_s = SDLK_s,
SC_KEYCODE_t = SDLK_t,
SC_KEYCODE_u = SDLK_u,
SC_KEYCODE_v = SDLK_v,
SC_KEYCODE_w = SDLK_w,
SC_KEYCODE_x = SDLK_x,
SC_KEYCODE_y = SDLK_y,
SC_KEYCODE_z = SDLK_z,
SC_KEYCODE_CAPSLOCK = SDLK_CAPSLOCK,
SC_KEYCODE_F1 = SDLK_F1,
SC_KEYCODE_F2 = SDLK_F2,
SC_KEYCODE_F3 = SDLK_F3,
SC_KEYCODE_F4 = SDLK_F4,
SC_KEYCODE_F5 = SDLK_F5,
SC_KEYCODE_F6 = SDLK_F6,
SC_KEYCODE_F7 = SDLK_F7,
SC_KEYCODE_F8 = SDLK_F8,
SC_KEYCODE_F9 = SDLK_F9,
SC_KEYCODE_F10 = SDLK_F10,
SC_KEYCODE_F11 = SDLK_F11,
SC_KEYCODE_F12 = SDLK_F12,
SC_KEYCODE_PRINTSCREEN = SDLK_PRINTSCREEN,
SC_KEYCODE_SCROLLLOCK = SDLK_SCROLLLOCK,
SC_KEYCODE_PAUSE = SDLK_PAUSE,
SC_KEYCODE_INSERT = SDLK_INSERT,
SC_KEYCODE_HOME = SDLK_HOME,
SC_KEYCODE_PAGEUP = SDLK_PAGEUP,
SC_KEYCODE_DELETE = SDLK_DELETE,
SC_KEYCODE_END = SDLK_END,
SC_KEYCODE_PAGEDOWN = SDLK_PAGEDOWN,
SC_KEYCODE_RIGHT = SDLK_RIGHT,
SC_KEYCODE_LEFT = SDLK_LEFT,
SC_KEYCODE_DOWN = SDLK_DOWN,
SC_KEYCODE_UP = SDLK_UP,
SC_KEYCODE_KP_DIVIDE = SDLK_KP_DIVIDE,
SC_KEYCODE_KP_MULTIPLY = SDLK_KP_MULTIPLY,
SC_KEYCODE_KP_MINUS = SDLK_KP_MINUS,
SC_KEYCODE_KP_PLUS = SDLK_KP_PLUS,
SC_KEYCODE_KP_ENTER = SDLK_KP_ENTER,
SC_KEYCODE_KP_1 = SDLK_KP_1,
SC_KEYCODE_KP_2 = SDLK_KP_2,
SC_KEYCODE_KP_3 = SDLK_KP_3,
SC_KEYCODE_KP_4 = SDLK_KP_4,
SC_KEYCODE_KP_5 = SDLK_KP_5,
SC_KEYCODE_KP_6 = SDLK_KP_6,
SC_KEYCODE_KP_7 = SDLK_KP_7,
SC_KEYCODE_KP_8 = SDLK_KP_8,
SC_KEYCODE_KP_9 = SDLK_KP_9,
SC_KEYCODE_KP_0 = SDLK_KP_0,
SC_KEYCODE_KP_PERIOD = SDLK_KP_PERIOD,
SC_KEYCODE_KP_EQUALS = SDLK_KP_EQUALS,
SC_KEYCODE_KP_LEFTPAREN = SDLK_KP_LEFTPAREN,
SC_KEYCODE_KP_RIGHTPAREN = SDLK_KP_RIGHTPAREN,
SC_KEYCODE_LCTRL = SDLK_LCTRL,
SC_KEYCODE_LSHIFT = SDLK_LSHIFT,
SC_KEYCODE_LALT = SDLK_LALT,
SC_KEYCODE_LGUI = SDLK_LGUI,
SC_KEYCODE_RCTRL = SDLK_RCTRL,
SC_KEYCODE_RSHIFT = SDLK_RSHIFT,
SC_KEYCODE_RALT = SDLK_RALT,
SC_KEYCODE_RGUI = SDLK_RGUI,
};
enum sc_scancode {
SC_SCANCODE_UNKNOWN = SDL_SCANCODE_UNKNOWN,
SC_SCANCODE_A = SDL_SCANCODE_A,
SC_SCANCODE_B = SDL_SCANCODE_B,
SC_SCANCODE_C = SDL_SCANCODE_C,
SC_SCANCODE_D = SDL_SCANCODE_D,
SC_SCANCODE_E = SDL_SCANCODE_E,
SC_SCANCODE_F = SDL_SCANCODE_F,
SC_SCANCODE_G = SDL_SCANCODE_G,
SC_SCANCODE_H = SDL_SCANCODE_H,
SC_SCANCODE_I = SDL_SCANCODE_I,
SC_SCANCODE_J = SDL_SCANCODE_J,
SC_SCANCODE_K = SDL_SCANCODE_K,
SC_SCANCODE_L = SDL_SCANCODE_L,
SC_SCANCODE_M = SDL_SCANCODE_M,
SC_SCANCODE_N = SDL_SCANCODE_N,
SC_SCANCODE_O = SDL_SCANCODE_O,
SC_SCANCODE_P = SDL_SCANCODE_P,
SC_SCANCODE_Q = SDL_SCANCODE_Q,
SC_SCANCODE_R = SDL_SCANCODE_R,
SC_SCANCODE_S = SDL_SCANCODE_S,
SC_SCANCODE_T = SDL_SCANCODE_T,
SC_SCANCODE_U = SDL_SCANCODE_U,
SC_SCANCODE_V = SDL_SCANCODE_V,
SC_SCANCODE_W = SDL_SCANCODE_W,
SC_SCANCODE_X = SDL_SCANCODE_X,
SC_SCANCODE_Y = SDL_SCANCODE_Y,
SC_SCANCODE_Z = SDL_SCANCODE_Z,
SC_SCANCODE_1 = SDL_SCANCODE_1,
SC_SCANCODE_2 = SDL_SCANCODE_2,
SC_SCANCODE_3 = SDL_SCANCODE_3,
SC_SCANCODE_4 = SDL_SCANCODE_4,
SC_SCANCODE_5 = SDL_SCANCODE_5,
SC_SCANCODE_6 = SDL_SCANCODE_6,
SC_SCANCODE_7 = SDL_SCANCODE_7,
SC_SCANCODE_8 = SDL_SCANCODE_8,
SC_SCANCODE_9 = SDL_SCANCODE_9,
SC_SCANCODE_0 = SDL_SCANCODE_0,
SC_SCANCODE_RETURN = SDL_SCANCODE_RETURN,
SC_SCANCODE_ESCAPE = SDL_SCANCODE_ESCAPE,
SC_SCANCODE_BACKSPACE = SDL_SCANCODE_BACKSPACE,
SC_SCANCODE_TAB = SDL_SCANCODE_TAB,
SC_SCANCODE_SPACE = SDL_SCANCODE_SPACE,
SC_SCANCODE_MINUS = SDL_SCANCODE_MINUS,
SC_SCANCODE_EQUALS = SDL_SCANCODE_EQUALS,
SC_SCANCODE_LEFTBRACKET = SDL_SCANCODE_LEFTBRACKET,
SC_SCANCODE_RIGHTBRACKET = SDL_SCANCODE_RIGHTBRACKET,
SC_SCANCODE_BACKSLASH = SDL_SCANCODE_BACKSLASH,
SC_SCANCODE_NONUSHASH = SDL_SCANCODE_NONUSHASH,
SC_SCANCODE_SEMICOLON = SDL_SCANCODE_SEMICOLON,
SC_SCANCODE_APOSTROPHE = SDL_SCANCODE_APOSTROPHE,
SC_SCANCODE_GRAVE = SDL_SCANCODE_GRAVE,
SC_SCANCODE_COMMA = SDL_SCANCODE_COMMA,
SC_SCANCODE_PERIOD = SDL_SCANCODE_PERIOD,
SC_SCANCODE_SLASH = SDL_SCANCODE_SLASH,
SC_SCANCODE_CAPSLOCK = SDL_SCANCODE_CAPSLOCK,
SC_SCANCODE_F1 = SDL_SCANCODE_F1,
SC_SCANCODE_F2 = SDL_SCANCODE_F2,
SC_SCANCODE_F3 = SDL_SCANCODE_F3,
SC_SCANCODE_F4 = SDL_SCANCODE_F4,
SC_SCANCODE_F5 = SDL_SCANCODE_F5,
SC_SCANCODE_F6 = SDL_SCANCODE_F6,
SC_SCANCODE_F7 = SDL_SCANCODE_F7,
SC_SCANCODE_F8 = SDL_SCANCODE_F8,
SC_SCANCODE_F9 = SDL_SCANCODE_F9,
SC_SCANCODE_F10 = SDL_SCANCODE_F10,
SC_SCANCODE_F11 = SDL_SCANCODE_F11,
SC_SCANCODE_F12 = SDL_SCANCODE_F12,
SC_SCANCODE_PRINTSCREEN = SDL_SCANCODE_PRINTSCREEN,
SC_SCANCODE_SCROLLLOCK = SDL_SCANCODE_SCROLLLOCK,
SC_SCANCODE_PAUSE = SDL_SCANCODE_PAUSE,
SC_SCANCODE_INSERT = SDL_SCANCODE_INSERT,
SC_SCANCODE_HOME = SDL_SCANCODE_HOME,
SC_SCANCODE_PAGEUP = SDL_SCANCODE_PAGEUP,
SC_SCANCODE_DELETE = SDL_SCANCODE_DELETE,
SC_SCANCODE_END = SDL_SCANCODE_END,
SC_SCANCODE_PAGEDOWN = SDL_SCANCODE_PAGEDOWN,
SC_SCANCODE_RIGHT = SDL_SCANCODE_RIGHT,
SC_SCANCODE_LEFT = SDL_SCANCODE_LEFT,
SC_SCANCODE_DOWN = SDL_SCANCODE_DOWN,
SC_SCANCODE_UP = SDL_SCANCODE_UP,
SC_SCANCODE_NUMLOCK = SDL_SCANCODE_NUMLOCKCLEAR,
SC_SCANCODE_KP_DIVIDE = SDL_SCANCODE_KP_DIVIDE,
SC_SCANCODE_KP_MULTIPLY = SDL_SCANCODE_KP_MULTIPLY,
SC_SCANCODE_KP_MINUS = SDL_SCANCODE_KP_MINUS,
SC_SCANCODE_KP_PLUS = SDL_SCANCODE_KP_PLUS,
SC_SCANCODE_KP_ENTER = SDL_SCANCODE_KP_ENTER,
SC_SCANCODE_KP_1 = SDL_SCANCODE_KP_1,
SC_SCANCODE_KP_2 = SDL_SCANCODE_KP_2,
SC_SCANCODE_KP_3 = SDL_SCANCODE_KP_3,
SC_SCANCODE_KP_4 = SDL_SCANCODE_KP_4,
SC_SCANCODE_KP_5 = SDL_SCANCODE_KP_5,
SC_SCANCODE_KP_6 = SDL_SCANCODE_KP_6,
SC_SCANCODE_KP_7 = SDL_SCANCODE_KP_7,
SC_SCANCODE_KP_8 = SDL_SCANCODE_KP_8,
SC_SCANCODE_KP_9 = SDL_SCANCODE_KP_9,
SC_SCANCODE_KP_0 = SDL_SCANCODE_KP_0,
SC_SCANCODE_KP_PERIOD = SDL_SCANCODE_KP_PERIOD,
SC_SCANCODE_LCTRL = SDL_SCANCODE_LCTRL,
SC_SCANCODE_LSHIFT = SDL_SCANCODE_LSHIFT,
SC_SCANCODE_LALT = SDL_SCANCODE_LALT,
SC_SCANCODE_LGUI = SDL_SCANCODE_LGUI,
SC_SCANCODE_RCTRL = SDL_SCANCODE_RCTRL,
SC_SCANCODE_RSHIFT = SDL_SCANCODE_RSHIFT,
SC_SCANCODE_RALT = SDL_SCANCODE_RALT,
SC_SCANCODE_RGUI = SDL_SCANCODE_RGUI,
};
// On purpose, only use the "mask" values (1, 2, 4, 8, 16) for a single button,
// to avoid unnecessary conversions (and confusion).
enum sc_mouse_button {
SC_MOUSE_BUTTON_UNKNOWN = 0,
SC_MOUSE_BUTTON_LEFT = SDL_BUTTON(SDL_BUTTON_LEFT),
SC_MOUSE_BUTTON_RIGHT = SDL_BUTTON(SDL_BUTTON_RIGHT),
SC_MOUSE_BUTTON_MIDDLE = SDL_BUTTON(SDL_BUTTON_MIDDLE),
SC_MOUSE_BUTTON_X1 = SDL_BUTTON(SDL_BUTTON_X1),
SC_MOUSE_BUTTON_X2 = SDL_BUTTON(SDL_BUTTON_X2),
};
static_assert(sizeof(enum sc_mod) >= sizeof(SDL_Keymod),
"SDL_Keymod must be convertible to sc_mod");
static_assert(sizeof(enum sc_keycode) >= sizeof(SDL_Keycode),
"SDL_Keycode must be convertible to sc_keycode");
static_assert(sizeof(enum sc_scancode) >= sizeof(SDL_Scancode),
"SDL_Scancode must be convertible to sc_scancode");
enum sc_touch_action {
SC_TOUCH_ACTION_MOVE,
SC_TOUCH_ACTION_DOWN,
SC_TOUCH_ACTION_UP,
};
struct sc_key_event {
enum sc_action action;
enum sc_keycode keycode;
enum sc_scancode scancode;
uint16_t mods_state; // bitwise-OR of sc_mod values
bool repeat;
};
struct sc_text_event {
const char *text; // not owned
};
struct sc_mouse_click_event {
struct sc_position position;
enum sc_action action;
enum sc_mouse_button button;
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
};
struct sc_mouse_scroll_event {
struct sc_position position;
int32_t hscroll;
int32_t vscroll;
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
};
struct sc_mouse_motion_event {
struct sc_position position;
int32_t xrel;
int32_t yrel;
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
};
struct sc_touch_event {
struct sc_position position;
enum sc_touch_action action;
uint64_t pointer_id;
float pressure;
};
#endif

View File

@ -3,39 +3,110 @@
#include <assert.h> #include <assert.h>
#include <SDL2/SDL_keycode.h> #include <SDL2/SDL_keycode.h>
#include "input_events.h"
#include "screen.h"
#include "util/log.h" #include "util/log.h"
static const int ACTION_DOWN = 1; static inline uint16_t
static const int ACTION_UP = 1 << 1; sc_mods_state_from_sdl(uint16_t mods_state) {
return mods_state;
}
static inline enum sc_keycode
sc_keycode_from_sdl(SDL_Keycode keycode) {
return (enum sc_keycode) keycode;
}
static inline enum sc_scancode
sc_scancode_from_sdl(SDL_Scancode scancode) {
return (enum sc_scancode) scancode;
}
static inline enum sc_action
sc_action_from_sdl_keyboard_type(uint32_t type) {
assert(type == SDL_KEYDOWN || type == SDL_KEYUP);
if (type == SDL_KEYDOWN) {
return SC_ACTION_DOWN;
}
return SC_ACTION_UP;
}
static inline enum sc_action
sc_action_from_sdl_mousebutton_type(uint32_t type) {
assert(type == SDL_MOUSEBUTTONDOWN || type == SDL_MOUSEBUTTONUP);
if (type == SDL_MOUSEBUTTONDOWN) {
return SC_ACTION_DOWN;
}
return SC_ACTION_UP;
}
static inline enum sc_touch_action
sc_touch_action_from_sdl(uint32_t type) {
assert(type == SDL_FINGERMOTION || type == SDL_FINGERDOWN ||
type == SDL_FINGERUP);
if (type == SDL_FINGERMOTION) {
return SC_TOUCH_ACTION_MOVE;
}
if (type == SDL_FINGERDOWN) {
return SC_TOUCH_ACTION_DOWN;
}
return SC_TOUCH_ACTION_UP;
}
static inline enum sc_mouse_button
sc_mouse_button_from_sdl(uint8_t button) {
if (button >= SDL_BUTTON_LEFT && button <= SDL_BUTTON_X2) {
// SC_MOUSE_BUTTON_* constants are initialized from SDL_BUTTON(index)
return SDL_BUTTON(button);
}
return SC_MOUSE_BUTTON_UNKNOWN;
}
static inline uint8_t
sc_mouse_buttons_state_from_sdl(uint32_t buttons_state,
bool forward_all_clicks) {
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;
}
return buttons_state & mask;
}
#define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI) #define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI)
static inline uint16_t static inline uint16_t
to_sdl_mod(unsigned mod) { to_sdl_mod(unsigned shortcut_mod) {
uint16_t sdl_mod = 0; uint16_t sdl_mod = 0;
if (mod & SC_MOD_LCTRL) { if (shortcut_mod & SC_SHORTCUT_MOD_LCTRL) {
sdl_mod |= KMOD_LCTRL; sdl_mod |= KMOD_LCTRL;
} }
if (mod & SC_MOD_RCTRL) { if (shortcut_mod & SC_SHORTCUT_MOD_RCTRL) {
sdl_mod |= KMOD_RCTRL; sdl_mod |= KMOD_RCTRL;
} }
if (mod & SC_MOD_LALT) { if (shortcut_mod & SC_SHORTCUT_MOD_LALT) {
sdl_mod |= KMOD_LALT; sdl_mod |= KMOD_LALT;
} }
if (mod & SC_MOD_RALT) { if (shortcut_mod & SC_SHORTCUT_MOD_RALT) {
sdl_mod |= KMOD_RALT; sdl_mod |= KMOD_RALT;
} }
if (mod & SC_MOD_LSUPER) { if (shortcut_mod & SC_SHORTCUT_MOD_LSUPER) {
sdl_mod |= KMOD_LGUI; sdl_mod |= KMOD_LGUI;
} }
if (mod & SC_MOD_RSUPER) { if (shortcut_mod & SC_SHORTCUT_MOD_RSUPER) {
sdl_mod |= KMOD_RGUI; sdl_mod |= KMOD_RGUI;
} }
return sdl_mod; return sdl_mod;
} }
static bool static bool
is_shortcut_mod(struct input_manager *im, uint16_t sdl_mod) { is_shortcut_mod(struct sc_input_manager *im, uint16_t sdl_mod) {
// keep only the relevant modifier keys // keep only the relevant modifier keys
sdl_mod &= SC_SDL_SHORTCUT_MODS_MASK; sdl_mod &= SC_SDL_SHORTCUT_MODS_MASK;
@ -51,24 +122,22 @@ is_shortcut_mod(struct input_manager *im, uint16_t sdl_mod) {
} }
void void
input_manager_init(struct input_manager *im, struct controller *controller, sc_input_manager_init(struct sc_input_manager *im,
struct screen *screen, struct sc_key_processor *kp, const struct sc_input_manager_params *params) {
struct sc_mouse_processor *mp, assert(!params->control || (params->kp && params->kp->ops));
const struct scrcpy_options *options) { assert(!params->control || (params->mp && params->mp->ops));
assert(!options->control || (kp && kp->ops));
assert(!options->control || (mp && mp->ops));
im->controller = controller; im->controller = params->controller;
im->screen = screen; im->screen = params->screen;
im->kp = kp; im->kp = params->kp;
im->mp = mp; im->mp = params->mp;
im->control = options->control; im->control = params->control;
im->forward_all_clicks = options->forward_all_clicks; im->forward_all_clicks = params->forward_all_clicks;
im->legacy_paste = options->legacy_paste; im->legacy_paste = params->legacy_paste;
im->clipboard_autosync = options->clipboard_autosync; im->clipboard_autosync = params->clipboard_autosync;
const struct sc_shortcut_mods *shortcut_mods = &options->shortcut_mods; const struct sc_shortcut_mods *shortcut_mods = params->shortcut_mods;
assert(shortcut_mods->count); assert(shortcut_mods->count);
assert(shortcut_mods->count < SC_MAX_SHORTCUT_MODS); assert(shortcut_mods->count < SC_MAX_SHORTCUT_MODS);
for (unsigned i = 0; i < shortcut_mods->count; ++i) { for (unsigned i = 0; i < shortcut_mods->count; ++i) {
@ -88,127 +157,112 @@ input_manager_init(struct input_manager *im, struct controller *controller,
} }
static void static void
send_keycode(struct controller *controller, enum android_keycode keycode, send_keycode(struct sc_controller *controller, enum android_keycode keycode,
int actions, const char *name) { enum sc_action action, const char *name) {
// send DOWN event // send DOWN event
struct control_msg msg; struct sc_control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_KEYCODE; msg.type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
msg.inject_keycode.action = action == SC_ACTION_DOWN
? AKEY_EVENT_ACTION_DOWN
: AKEY_EVENT_ACTION_UP;
msg.inject_keycode.keycode = keycode; msg.inject_keycode.keycode = keycode;
msg.inject_keycode.metastate = 0; msg.inject_keycode.metastate = 0;
msg.inject_keycode.repeat = 0; msg.inject_keycode.repeat = 0;
if (actions & ACTION_DOWN) { if (!sc_controller_push_msg(controller, &msg)) {
msg.inject_keycode.action = AKEY_EVENT_ACTION_DOWN; LOGW("Could not request 'inject %s'", name);
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'inject %s (DOWN)'", name);
return;
}
}
if (actions & ACTION_UP) {
msg.inject_keycode.action = AKEY_EVENT_ACTION_UP;
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'inject %s (UP)'", name);
}
} }
} }
static inline void static inline void
action_home(struct controller *controller, int actions) { action_home(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_HOME, actions, "HOME"); send_keycode(controller, AKEYCODE_HOME, action, "HOME");
} }
static inline void static inline void
action_back(struct controller *controller, int actions) { action_back(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_BACK, actions, "BACK"); send_keycode(controller, AKEYCODE_BACK, action, "BACK");
} }
static inline void static inline void
action_app_switch(struct controller *controller, int actions) { action_app_switch(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_APP_SWITCH, actions, "APP_SWITCH"); send_keycode(controller, AKEYCODE_APP_SWITCH, action, "APP_SWITCH");
} }
static inline void static inline void
action_power(struct controller *controller, int actions) { action_power(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_POWER, actions, "POWER"); send_keycode(controller, AKEYCODE_POWER, action, "POWER");
} }
static inline void static inline void
action_volume_up(struct controller *controller, int actions) { action_volume_up(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_VOLUME_UP, actions, "VOLUME_UP"); send_keycode(controller, AKEYCODE_VOLUME_UP, action, "VOLUME_UP");
} }
static inline void static inline void
action_volume_down(struct controller *controller, int actions) { action_volume_down(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_VOLUME_DOWN, actions, "VOLUME_DOWN"); send_keycode(controller, AKEYCODE_VOLUME_DOWN, action, "VOLUME_DOWN");
} }
static inline void static inline void
action_menu(struct controller *controller, int actions) { action_menu(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_MENU, actions, "MENU"); send_keycode(controller, AKEYCODE_MENU, action, "MENU");
} }
// turn the screen on if it was off, press BACK otherwise // turn the screen on if it was off, press BACK otherwise
// If the screen is off, it is turned on only on ACTION_DOWN // If the screen is off, it is turned on only on ACTION_DOWN
static void static void
press_back_or_turn_screen_on(struct controller *controller, int actions) { press_back_or_turn_screen_on(struct sc_controller *controller,
struct control_msg msg; enum sc_action action) {
struct sc_control_msg msg;
msg.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON; msg.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON;
msg.back_or_screen_on.action = action == SC_ACTION_DOWN
? AKEY_EVENT_ACTION_DOWN
: AKEY_EVENT_ACTION_UP;
if (actions & ACTION_DOWN) { if (!sc_controller_push_msg(controller, &msg)) {
msg.back_or_screen_on.action = AKEY_EVENT_ACTION_DOWN; LOGW("Could not request 'press back or turn screen on'");
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'press back or turn screen on'");
return;
}
}
if (actions & ACTION_UP) {
msg.back_or_screen_on.action = AKEY_EVENT_ACTION_UP;
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'press back or turn screen on'");
}
} }
} }
static void static void
expand_notification_panel(struct controller *controller) { expand_notification_panel(struct sc_controller *controller) {
struct control_msg msg; struct sc_control_msg msg;
msg.type = CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL; msg.type = CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL;
if (!controller_push_msg(controller, &msg)) { if (!sc_controller_push_msg(controller, &msg)) {
LOGW("Could not request 'expand notification panel'"); LOGW("Could not request 'expand notification panel'");
} }
} }
static void static void
expand_settings_panel(struct controller *controller) { expand_settings_panel(struct sc_controller *controller) {
struct control_msg msg; struct sc_control_msg msg;
msg.type = CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL; msg.type = CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL;
if (!controller_push_msg(controller, &msg)) { if (!sc_controller_push_msg(controller, &msg)) {
LOGW("Could not request 'expand settings panel'"); LOGW("Could not request 'expand settings panel'");
} }
} }
static void static void
collapse_panels(struct controller *controller) { collapse_panels(struct sc_controller *controller) {
struct control_msg msg; struct sc_control_msg msg;
msg.type = CONTROL_MSG_TYPE_COLLAPSE_PANELS; msg.type = CONTROL_MSG_TYPE_COLLAPSE_PANELS;
if (!controller_push_msg(controller, &msg)) { if (!sc_controller_push_msg(controller, &msg)) {
LOGW("Could not request 'collapse notification panel'"); LOGW("Could not request 'collapse notification panel'");
} }
} }
static bool static bool
get_device_clipboard(struct controller *controller, get_device_clipboard(struct sc_controller *controller,
enum get_clipboard_copy_key copy_key) { enum get_clipboard_copy_key copy_key) {
struct control_msg msg; struct sc_control_msg msg;
msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD; msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD;
msg.get_clipboard.copy_key = copy_key; msg.get_clipboard.copy_key = copy_key;
if (!controller_push_msg(controller, &msg)) { if (!sc_controller_push_msg(controller, &msg)) {
LOGW("Could not request 'get device clipboard'"); LOGW("Could not request 'get device clipboard'");
return false; return false;
} }
@ -217,7 +271,7 @@ get_device_clipboard(struct controller *controller,
} }
static bool static bool
set_device_clipboard(struct controller *controller, bool paste, set_device_clipboard(struct sc_controller *controller, bool paste,
uint64_t sequence) { uint64_t sequence) {
char *text = SDL_GetClipboardText(); char *text = SDL_GetClipboardText();
if (!text) { if (!text) {
@ -232,13 +286,13 @@ set_device_clipboard(struct controller *controller, bool paste,
return false; return false;
} }
struct control_msg msg; struct sc_control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD; msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD;
msg.set_clipboard.sequence = sequence; msg.set_clipboard.sequence = sequence;
msg.set_clipboard.text = text_dup; msg.set_clipboard.text = text_dup;
msg.set_clipboard.paste = paste; msg.set_clipboard.paste = paste;
if (!controller_push_msg(controller, &msg)) { if (!sc_controller_push_msg(controller, &msg)) {
free(text_dup); free(text_dup);
LOGW("Could not request 'set device clipboard'"); LOGW("Could not request 'set device clipboard'");
return false; return false;
@ -248,13 +302,13 @@ set_device_clipboard(struct controller *controller, bool paste,
} }
static void static void
set_screen_power_mode(struct controller *controller, set_screen_power_mode(struct sc_controller *controller,
enum screen_power_mode mode) { enum screen_power_mode mode) {
struct control_msg msg; struct sc_control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE; msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
msg.set_screen_power_mode.mode = mode; msg.set_screen_power_mode.mode = mode;
if (!controller_push_msg(controller, &msg)) { if (!sc_controller_push_msg(controller, &msg)) {
LOGW("Could not request 'set screen power mode'"); LOGW("Could not request 'set screen power mode'");
} }
} }
@ -276,7 +330,7 @@ switch_fps_counter_state(struct fps_counter *fps_counter) {
} }
static void static void
clipboard_paste(struct controller *controller) { clipboard_paste(struct sc_controller *controller) {
char *text = SDL_GetClipboardText(); char *text = SDL_GetClipboardText();
if (!text) { if (!text) {
LOGW("Could not get clipboard text: %s", SDL_GetError()); LOGW("Could not get clipboard text: %s", SDL_GetError());
@ -295,55 +349,64 @@ clipboard_paste(struct controller *controller) {
return; return;
} }
struct control_msg msg; struct sc_control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = text_dup; msg.inject_text.text = text_dup;
if (!controller_push_msg(controller, &msg)) { if (!sc_controller_push_msg(controller, &msg)) {
free(text_dup); free(text_dup);
LOGW("Could not request 'paste clipboard'"); LOGW("Could not request 'paste clipboard'");
} }
} }
static void static void
rotate_device(struct controller *controller) { rotate_device(struct sc_controller *controller) {
struct control_msg msg; struct sc_control_msg msg;
msg.type = CONTROL_MSG_TYPE_ROTATE_DEVICE; msg.type = CONTROL_MSG_TYPE_ROTATE_DEVICE;
if (!controller_push_msg(controller, &msg)) { if (!sc_controller_push_msg(controller, &msg)) {
LOGW("Could not request device rotation"); LOGW("Could not request device rotation");
} }
} }
static void static void
rotate_client_left(struct screen *screen) { rotate_client_left(struct sc_screen *screen) {
unsigned new_rotation = (screen->rotation + 1) % 4; unsigned new_rotation = (screen->rotation + 1) % 4;
screen_set_rotation(screen, new_rotation); sc_screen_set_rotation(screen, new_rotation);
} }
static void static void
rotate_client_right(struct screen *screen) { rotate_client_right(struct sc_screen *screen) {
unsigned new_rotation = (screen->rotation + 3) % 4; unsigned new_rotation = (screen->rotation + 3) % 4;
screen_set_rotation(screen, new_rotation); sc_screen_set_rotation(screen, new_rotation);
} }
static void static void
input_manager_process_text_input(struct input_manager *im, sc_input_manager_process_text_input(struct sc_input_manager *im,
const SDL_TextInputEvent *event) { const SDL_TextInputEvent *event) {
if (!im->kp->ops->process_text) {
// The key processor does not support text input
return;
}
if (is_shortcut_mod(im, SDL_GetModState())) { if (is_shortcut_mod(im, SDL_GetModState())) {
// A shortcut must never generate text events // A shortcut must never generate text events
return; return;
} }
im->kp->ops->process_text(im->kp, event); struct sc_text_event evt = {
.text = event->text,
};
im->kp->ops->process_text(im->kp, &evt);
} }
static bool static bool
simulate_virtual_finger(struct input_manager *im, simulate_virtual_finger(struct sc_input_manager *im,
enum android_motionevent_action action, enum android_motionevent_action action,
struct sc_point point) { struct sc_point point) {
bool up = action == AMOTION_EVENT_ACTION_UP; bool up = action == AMOTION_EVENT_ACTION_UP;
struct control_msg msg; struct sc_control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; msg.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
msg.inject_touch_event.action = action; msg.inject_touch_event.action = action;
msg.inject_touch_event.position.screen_size = im->screen->frame_size; msg.inject_touch_event.position.screen_size = im->screen->frame_size;
@ -352,7 +415,7 @@ simulate_virtual_finger(struct input_manager *im,
msg.inject_touch_event.pressure = up ? 0.0f : 1.0f; msg.inject_touch_event.pressure = up ? 0.0f : 1.0f;
msg.inject_touch_event.buttons = 0; msg.inject_touch_event.buttons = 0;
if (!controller_push_msg(im->controller, &msg)) { if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject virtual finger event'"); LOGW("Could not request 'inject virtual finger event'");
return false; return false;
} }
@ -368,12 +431,12 @@ inverse_point(struct sc_point point, struct sc_size size) {
} }
static void static void
input_manager_process_key(struct input_manager *im, sc_input_manager_process_key(struct sc_input_manager *im,
const SDL_KeyboardEvent *event) { const SDL_KeyboardEvent *event) {
// control: indicates the state of the command-line option --no-control // control: indicates the state of the command-line option --no-control
bool control = im->control; bool control = im->control;
struct controller *controller = im->controller; struct sc_controller *controller = im->controller;
SDL_Keycode keycode = event->keysym.sym; SDL_Keycode keycode = event->keysym.sym;
uint16_t mod = event->keysym.mod; uint16_t mod = event->keysym.mod;
@ -396,7 +459,7 @@ input_manager_process_key(struct input_manager *im,
// The shortcut modifier is pressed // The shortcut modifier is pressed
if (smod) { if (smod) {
int action = down ? ACTION_DOWN : ACTION_UP; enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
switch (keycode) { switch (keycode) {
case SDLK_h: case SDLK_h:
if (control && !shift && !repeat) { if (control && !shift && !repeat) {
@ -481,17 +544,17 @@ input_manager_process_key(struct input_manager *im,
return; return;
case SDLK_f: case SDLK_f:
if (!shift && !repeat && down) { if (!shift && !repeat && down) {
screen_switch_fullscreen(im->screen); sc_screen_switch_fullscreen(im->screen);
} }
return; return;
case SDLK_w: case SDLK_w:
if (!shift && !repeat && down) { if (!shift && !repeat && down) {
screen_resize_to_fit(im->screen); sc_screen_resize_to_fit(im->screen);
} }
return; return;
case SDLK_g: case SDLK_g:
if (!shift && !repeat && down) { if (!shift && !repeat && down) {
screen_resize_to_pixel_perfect(im->screen); sc_screen_resize_to_pixel_perfect(im->screen);
} }
return; return;
case SDLK_i: case SDLK_i:
@ -553,45 +616,90 @@ input_manager_process_key(struct input_manager *im,
} }
} }
im->kp->ops->process_key(im->kp, event, ack_to_wait); struct sc_key_event evt = {
.action = sc_action_from_sdl_keyboard_type(event->type),
.keycode = sc_keycode_from_sdl(event->keysym.sym),
.scancode = sc_scancode_from_sdl(event->keysym.scancode),
.repeat = event->repeat,
.mods_state = sc_mods_state_from_sdl(event->keysym.mod),
};
assert(im->kp->ops->process_key);
im->kp->ops->process_key(im->kp, &evt, ack_to_wait);
} }
static void static void
input_manager_process_mouse_motion(struct input_manager *im, sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
const SDL_MouseMotionEvent *event) { const SDL_MouseMotionEvent *event) {
uint32_t mask = SDL_BUTTON_LMASK;
if (im->forward_all_clicks) {
mask |= SDL_BUTTON_MMASK | SDL_BUTTON_RMASK;
}
if (!(event->state & mask)) {
// do not send motion events when no click is pressed
return;
}
if (event->which == SDL_TOUCH_MOUSEID) { if (event->which == SDL_TOUCH_MOUSEID) {
// simulated from touch events, so it's a duplicate // simulated from touch events, so it's a duplicate
return; return;
} }
im->mp->ops->process_mouse_motion(im->mp, event); struct sc_mouse_motion_event evt = {
.position = {
.screen_size = im->screen->frame_size,
.point = sc_screen_convert_window_to_frame_coords(im->screen,
event->x,
event->y),
},
.xrel = event->xrel,
.yrel = event->yrel,
.buttons_state =
sc_mouse_buttons_state_from_sdl(event->state,
im->forward_all_clicks),
};
assert(im->mp->ops->process_mouse_motion);
im->mp->ops->process_mouse_motion(im->mp, &evt);
// vfinger must never be used in relative mode
assert(!im->mp->relative_mode || !im->vfinger_down);
if (im->vfinger_down) { if (im->vfinger_down) {
assert(!im->mp->relative_mode); // assert one more time
struct sc_point mouse = struct sc_point mouse =
screen_convert_window_to_frame_coords(im->screen, event->x, sc_screen_convert_window_to_frame_coords(im->screen, event->x,
event->y); event->y);
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size); struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size);
simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger); simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger);
} }
} }
static void static void
input_manager_process_touch(struct input_manager *im, sc_input_manager_process_touch(struct sc_input_manager *im,
const SDL_TouchFingerEvent *event) { const SDL_TouchFingerEvent *event) {
im->mp->ops->process_touch(im->mp, event); if (!im->mp->ops->process_touch) {
// The mouse processor does not support touch events
return;
}
int dw;
int dh;
SDL_GL_GetDrawableSize(im->screen->window, &dw, &dh);
// SDL touch event coordinates are normalized in the range [0; 1]
int32_t x = event->x * dw;
int32_t y = event->y * dh;
struct sc_touch_event evt = {
.position = {
.screen_size = im->screen->frame_size,
.point =
sc_screen_convert_drawable_to_frame_coords(im->screen, x, y),
},
.action = sc_touch_action_from_sdl(event->type),
.pointer_id = event->fingerId,
.pressure = event->pressure,
};
im->mp->ops->process_touch(im->mp, &evt);
} }
static void static void
input_manager_process_mouse_button(struct input_manager *im, sc_input_manager_process_mouse_button(struct sc_input_manager *im,
const SDL_MouseButtonEvent *event) { const SDL_MouseButtonEvent *event) {
bool control = im->control; bool control = im->control;
if (event->which == SDL_TOUCH_MOUSEID) { if (event->which == SDL_TOUCH_MOUSEID) {
@ -601,7 +709,7 @@ input_manager_process_mouse_button(struct input_manager *im,
bool down = event->type == SDL_MOUSEBUTTONDOWN; bool down = event->type == SDL_MOUSEBUTTONDOWN;
if (!im->forward_all_clicks) { if (!im->forward_all_clicks) {
int action = down ? ACTION_DOWN : ACTION_UP; enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
if (control && event->button == SDL_BUTTON_X1) { if (control && event->button == SDL_BUTTON_X1) {
action_app_switch(im->controller, action); action_app_switch(im->controller, action);
@ -628,13 +736,13 @@ input_manager_process_mouse_button(struct input_manager *im,
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) { if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
int32_t x = event->x; int32_t x = event->x;
int32_t y = event->y; int32_t y = event->y;
screen_hidpi_scale_coords(im->screen, &x, &y); sc_screen_hidpi_scale_coords(im->screen, &x, &y);
SDL_Rect *r = &im->screen->rect; SDL_Rect *r = &im->screen->rect;
bool outside = x < r->x || x >= r->x + r->w bool outside = x < r->x || x >= r->x + r->w
|| y < r->y || y >= r->y + r->h; || y < r->y || y >= r->y + r->h;
if (outside) { if (outside) {
if (down) { if (down) {
screen_resize_to_fit(im->screen); sc_screen_resize_to_fit(im->screen);
} }
return; return;
} }
@ -646,7 +754,30 @@ input_manager_process_mouse_button(struct input_manager *im,
return; return;
} }
im->mp->ops->process_mouse_button(im->mp, event); uint32_t sdl_buttons_state = SDL_GetMouseState(NULL, NULL);
struct sc_mouse_click_event evt = {
.position = {
.screen_size = im->screen->frame_size,
.point = sc_screen_convert_window_to_frame_coords(im->screen,
event->x,
event->y),
},
.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,
im->forward_all_clicks),
};
assert(im->mp->ops->process_mouse_click);
im->mp->ops->process_mouse_click(im->mp, &evt);
if (im->mp->relative_mode) {
assert(!im->vfinger_down); // vfinger must not be used in relative mode
// No pinch-to-zoom simulation
return;
}
// Pinch-to-zoom simulation. // Pinch-to-zoom simulation.
// //
@ -658,11 +789,12 @@ input_manager_process_mouse_button(struct input_manager *im,
// In other words, the center of the rotation/scaling is the center of the // In other words, the center of the rotation/scaling is the center of the
// screen. // screen.
#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL)) #define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL))
if ((down && !im->vfinger_down && CTRL_PRESSED) if (event->button == SDL_BUTTON_LEFT &&
|| (!down && im->vfinger_down)) { ((down && !im->vfinger_down && CTRL_PRESSED) ||
(!down && im->vfinger_down))) {
struct sc_point mouse = struct sc_point mouse =
screen_convert_window_to_frame_coords(im->screen, event->x, sc_screen_convert_window_to_frame_coords(im->screen, event->x,
event->y); event->y);
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size); struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size);
enum android_motionevent_action action = down enum android_motionevent_action action = down
? AMOTION_EVENT_ACTION_DOWN ? AMOTION_EVENT_ACTION_DOWN
@ -675,48 +807,70 @@ input_manager_process_mouse_button(struct input_manager *im,
} }
static void static void
input_manager_process_mouse_wheel(struct input_manager *im, sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
const SDL_MouseWheelEvent *event) { const SDL_MouseWheelEvent *event) {
im->mp->ops->process_mouse_wheel(im->mp, event); if (!im->mp->ops->process_mouse_scroll) {
// The mouse processor does not support scroll events
return;
}
// mouse_x and mouse_y are expressed in pixels relative to the window
int mouse_x;
int mouse_y;
uint32_t buttons = SDL_GetMouseState(&mouse_x, &mouse_y);
struct sc_mouse_scroll_event evt = {
.position = {
.screen_size = im->screen->frame_size,
.point = sc_screen_convert_window_to_frame_coords(im->screen,
mouse_x, mouse_y),
},
.hscroll = event->x,
.vscroll = event->y,
.buttons_state =
sc_mouse_buttons_state_from_sdl(buttons, im->forward_all_clicks),
};
im->mp->ops->process_mouse_scroll(im->mp, &evt);
} }
bool bool
input_manager_handle_event(struct input_manager *im, SDL_Event *event) { sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event) {
switch (event->type) { switch (event->type) {
case SDL_TEXTINPUT: case SDL_TEXTINPUT:
if (!im->control) { if (!im->control) {
return true; return true;
} }
input_manager_process_text_input(im, &event->text); sc_input_manager_process_text_input(im, &event->text);
return true; return true;
case SDL_KEYDOWN: case SDL_KEYDOWN:
case SDL_KEYUP: case SDL_KEYUP:
// some key events do not interact with the device, so process the // some key events do not interact with the device, so process the
// event even if control is disabled // event even if control is disabled
input_manager_process_key(im, &event->key); sc_input_manager_process_key(im, &event->key);
return true; return true;
case SDL_MOUSEMOTION: case SDL_MOUSEMOTION:
if (!im->control) { if (!im->control) {
break; break;
} }
input_manager_process_mouse_motion(im, &event->motion); sc_input_manager_process_mouse_motion(im, &event->motion);
return true; return true;
case SDL_MOUSEWHEEL: case SDL_MOUSEWHEEL:
if (!im->control) { if (!im->control) {
break; break;
} }
input_manager_process_mouse_wheel(im, &event->wheel); sc_input_manager_process_mouse_wheel(im, &event->wheel);
return true; return true;
case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP: case SDL_MOUSEBUTTONUP:
// some mouse events do not interact with the device, so process // some mouse events do not interact with the device, so process
// the event even if control is disabled // the event even if control is disabled
input_manager_process_mouse_button(im, &event->button); sc_input_manager_process_mouse_button(im, &event->button);
return true; return true;
case SDL_FINGERMOTION: case SDL_FINGERMOTION:
case SDL_FINGERDOWN: case SDL_FINGERDOWN:
case SDL_FINGERUP: case SDL_FINGERUP:
input_manager_process_touch(im, &event->tfinger); sc_input_manager_process_touch(im, &event->tfinger);
return true; return true;
} }

View File

@ -10,13 +10,12 @@
#include "controller.h" #include "controller.h"
#include "fps_counter.h" #include "fps_counter.h"
#include "options.h" #include "options.h"
#include "screen.h"
#include "trait/key_processor.h" #include "trait/key_processor.h"
#include "trait/mouse_processor.h" #include "trait/mouse_processor.h"
struct input_manager { struct sc_input_manager {
struct controller *controller; struct sc_controller *controller;
struct screen *screen; struct sc_screen *screen;
struct sc_key_processor *kp; struct sc_key_processor *kp;
struct sc_mouse_processor *mp; struct sc_mouse_processor *mp;
@ -43,13 +42,24 @@ struct input_manager {
uint64_t next_sequence; // used for request acknowledgements uint64_t next_sequence; // used for request acknowledgements
}; };
struct sc_input_manager_params {
struct sc_controller *controller;
struct sc_screen *screen;
struct sc_key_processor *kp;
struct sc_mouse_processor *mp;
bool control;
bool forward_all_clicks;
bool legacy_paste;
bool clipboard_autosync;
const struct sc_shortcut_mods *shortcut_mods;
};
void void
input_manager_init(struct input_manager *im, struct controller *controller, sc_input_manager_init(struct sc_input_manager *im,
struct screen *screen, struct sc_key_processor *kp, const struct sc_input_manager_params *params);
struct sc_mouse_processor *mp,
const struct scrcpy_options *options);
bool bool
input_manager_handle_event(struct input_manager *im, SDL_Event *event); sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event);
#endif #endif

View File

@ -1,153 +1,146 @@
#include "keyboard_inject.h" #include "keyboard_inject.h"
#include <assert.h> #include <assert.h>
#include <SDL2/SDL_events.h>
#include "android/input.h" #include "android/input.h"
#include "control_msg.h" #include "control_msg.h"
#include "controller.h" #include "controller.h"
#include "input_events.h"
#include "util/intmap.h" #include "util/intmap.h"
#include "util/log.h" #include "util/log.h"
/** Downcast key processor to sc_keyboard_inject */ /** Downcast key processor to sc_keyboard_inject */
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_inject, key_processor) #define DOWNCAST(KP) container_of(KP, struct sc_keyboard_inject, key_processor)
static bool static enum android_keyevent_action
convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) { convert_keycode_action(enum sc_action action) {
static const struct sc_intmap_entry actions[] = { if (action == SC_ACTION_DOWN) {
{SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN}, return AKEY_EVENT_ACTION_DOWN;
{SDL_KEYUP, AKEY_EVENT_ACTION_UP},
};
const struct sc_intmap_entry *entry = SC_INTMAP_FIND_ENTRY(actions, from);
if (entry) {
*to = entry->value;
return true;
} }
assert(action == SC_ACTION_UP);
return false; return AKEY_EVENT_ACTION_UP;
} }
static bool static bool
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod, convert_keycode(enum sc_keycode from, enum android_keycode *to, uint16_t mod,
enum sc_key_inject_mode key_inject_mode) { enum sc_key_inject_mode key_inject_mode) {
// Navigation keys and ENTER. // Navigation keys and ENTER.
// Used in all modes. // Used in all modes.
static const struct sc_intmap_entry special_keys[] = { static const struct sc_intmap_entry special_keys[] = {
{SDLK_RETURN, AKEYCODE_ENTER}, {SC_KEYCODE_RETURN, AKEYCODE_ENTER},
{SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER}, {SC_KEYCODE_KP_ENTER, AKEYCODE_NUMPAD_ENTER},
{SDLK_ESCAPE, AKEYCODE_ESCAPE}, {SC_KEYCODE_ESCAPE, AKEYCODE_ESCAPE},
{SDLK_BACKSPACE, AKEYCODE_DEL}, {SC_KEYCODE_BACKSPACE, AKEYCODE_DEL},
{SDLK_TAB, AKEYCODE_TAB}, {SC_KEYCODE_TAB, AKEYCODE_TAB},
{SDLK_PAGEUP, AKEYCODE_PAGE_UP}, {SC_KEYCODE_PAGEUP, AKEYCODE_PAGE_UP},
{SDLK_DELETE, AKEYCODE_FORWARD_DEL}, {SC_KEYCODE_DELETE, AKEYCODE_FORWARD_DEL},
{SDLK_HOME, AKEYCODE_MOVE_HOME}, {SC_KEYCODE_HOME, AKEYCODE_MOVE_HOME},
{SDLK_END, AKEYCODE_MOVE_END}, {SC_KEYCODE_END, AKEYCODE_MOVE_END},
{SDLK_PAGEDOWN, AKEYCODE_PAGE_DOWN}, {SC_KEYCODE_PAGEDOWN, AKEYCODE_PAGE_DOWN},
{SDLK_RIGHT, AKEYCODE_DPAD_RIGHT}, {SC_KEYCODE_RIGHT, AKEYCODE_DPAD_RIGHT},
{SDLK_LEFT, AKEYCODE_DPAD_LEFT}, {SC_KEYCODE_LEFT, AKEYCODE_DPAD_LEFT},
{SDLK_DOWN, AKEYCODE_DPAD_DOWN}, {SC_KEYCODE_DOWN, AKEYCODE_DPAD_DOWN},
{SDLK_UP, AKEYCODE_DPAD_UP}, {SC_KEYCODE_UP, AKEYCODE_DPAD_UP},
{SDLK_LCTRL, AKEYCODE_CTRL_LEFT}, {SC_KEYCODE_LCTRL, AKEYCODE_CTRL_LEFT},
{SDLK_RCTRL, AKEYCODE_CTRL_RIGHT}, {SC_KEYCODE_RCTRL, AKEYCODE_CTRL_RIGHT},
{SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT}, {SC_KEYCODE_LSHIFT, AKEYCODE_SHIFT_LEFT},
{SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT}, {SC_KEYCODE_RSHIFT, AKEYCODE_SHIFT_RIGHT},
}; };
// Numpad navigation keys. // Numpad navigation keys.
// Used in all modes, when NumLock and Shift are disabled. // Used in all modes, when NumLock and Shift are disabled.
static const struct sc_intmap_entry kp_nav_keys[] = { static const struct sc_intmap_entry kp_nav_keys[] = {
{SDLK_KP_0, AKEYCODE_INSERT}, {SC_KEYCODE_KP_0, AKEYCODE_INSERT},
{SDLK_KP_1, AKEYCODE_MOVE_END}, {SC_KEYCODE_KP_1, AKEYCODE_MOVE_END},
{SDLK_KP_2, AKEYCODE_DPAD_DOWN}, {SC_KEYCODE_KP_2, AKEYCODE_DPAD_DOWN},
{SDLK_KP_3, AKEYCODE_PAGE_DOWN}, {SC_KEYCODE_KP_3, AKEYCODE_PAGE_DOWN},
{SDLK_KP_4, AKEYCODE_DPAD_LEFT}, {SC_KEYCODE_KP_4, AKEYCODE_DPAD_LEFT},
{SDLK_KP_6, AKEYCODE_DPAD_RIGHT}, {SC_KEYCODE_KP_6, AKEYCODE_DPAD_RIGHT},
{SDLK_KP_7, AKEYCODE_MOVE_HOME}, {SC_KEYCODE_KP_7, AKEYCODE_MOVE_HOME},
{SDLK_KP_8, AKEYCODE_DPAD_UP}, {SC_KEYCODE_KP_8, AKEYCODE_DPAD_UP},
{SDLK_KP_9, AKEYCODE_PAGE_UP}, {SC_KEYCODE_KP_9, AKEYCODE_PAGE_UP},
{SDLK_KP_PERIOD, AKEYCODE_FORWARD_DEL}, {SC_KEYCODE_KP_PERIOD, AKEYCODE_FORWARD_DEL},
}; };
// Letters and space. // Letters and space.
// Used in non-text mode. // Used in non-text mode.
static const struct sc_intmap_entry alphaspace_keys[] = { static const struct sc_intmap_entry alphaspace_keys[] = {
{SDLK_a, AKEYCODE_A}, {SC_KEYCODE_a, AKEYCODE_A},
{SDLK_b, AKEYCODE_B}, {SC_KEYCODE_b, AKEYCODE_B},
{SDLK_c, AKEYCODE_C}, {SC_KEYCODE_c, AKEYCODE_C},
{SDLK_d, AKEYCODE_D}, {SC_KEYCODE_d, AKEYCODE_D},
{SDLK_e, AKEYCODE_E}, {SC_KEYCODE_e, AKEYCODE_E},
{SDLK_f, AKEYCODE_F}, {SC_KEYCODE_f, AKEYCODE_F},
{SDLK_g, AKEYCODE_G}, {SC_KEYCODE_g, AKEYCODE_G},
{SDLK_h, AKEYCODE_H}, {SC_KEYCODE_h, AKEYCODE_H},
{SDLK_i, AKEYCODE_I}, {SC_KEYCODE_i, AKEYCODE_I},
{SDLK_j, AKEYCODE_J}, {SC_KEYCODE_j, AKEYCODE_J},
{SDLK_k, AKEYCODE_K}, {SC_KEYCODE_k, AKEYCODE_K},
{SDLK_l, AKEYCODE_L}, {SC_KEYCODE_l, AKEYCODE_L},
{SDLK_m, AKEYCODE_M}, {SC_KEYCODE_m, AKEYCODE_M},
{SDLK_n, AKEYCODE_N}, {SC_KEYCODE_n, AKEYCODE_N},
{SDLK_o, AKEYCODE_O}, {SC_KEYCODE_o, AKEYCODE_O},
{SDLK_p, AKEYCODE_P}, {SC_KEYCODE_p, AKEYCODE_P},
{SDLK_q, AKEYCODE_Q}, {SC_KEYCODE_q, AKEYCODE_Q},
{SDLK_r, AKEYCODE_R}, {SC_KEYCODE_r, AKEYCODE_R},
{SDLK_s, AKEYCODE_S}, {SC_KEYCODE_s, AKEYCODE_S},
{SDLK_t, AKEYCODE_T}, {SC_KEYCODE_t, AKEYCODE_T},
{SDLK_u, AKEYCODE_U}, {SC_KEYCODE_u, AKEYCODE_U},
{SDLK_v, AKEYCODE_V}, {SC_KEYCODE_v, AKEYCODE_V},
{SDLK_w, AKEYCODE_W}, {SC_KEYCODE_w, AKEYCODE_W},
{SDLK_x, AKEYCODE_X}, {SC_KEYCODE_x, AKEYCODE_X},
{SDLK_y, AKEYCODE_Y}, {SC_KEYCODE_y, AKEYCODE_Y},
{SDLK_z, AKEYCODE_Z}, {SC_KEYCODE_z, AKEYCODE_Z},
{SDLK_SPACE, AKEYCODE_SPACE}, {SC_KEYCODE_SPACE, AKEYCODE_SPACE},
}; };
// Numbers and punctuation keys. // Numbers and punctuation keys.
// Used in raw mode only. // Used in raw mode only.
static const struct sc_intmap_entry numbers_punct_keys[] = { static const struct sc_intmap_entry numbers_punct_keys[] = {
{SDLK_HASH, AKEYCODE_POUND}, {SC_KEYCODE_HASH, AKEYCODE_POUND},
{SDLK_PERCENT, AKEYCODE_PERIOD}, {SC_KEYCODE_PERCENT, AKEYCODE_PERIOD},
{SDLK_QUOTE, AKEYCODE_APOSTROPHE}, {SC_KEYCODE_QUOTE, AKEYCODE_APOSTROPHE},
{SDLK_ASTERISK, AKEYCODE_STAR}, {SC_KEYCODE_ASTERISK, AKEYCODE_STAR},
{SDLK_PLUS, AKEYCODE_PLUS}, {SC_KEYCODE_PLUS, AKEYCODE_PLUS},
{SDLK_COMMA, AKEYCODE_COMMA}, {SC_KEYCODE_COMMA, AKEYCODE_COMMA},
{SDLK_MINUS, AKEYCODE_MINUS}, {SC_KEYCODE_MINUS, AKEYCODE_MINUS},
{SDLK_PERIOD, AKEYCODE_PERIOD}, {SC_KEYCODE_PERIOD, AKEYCODE_PERIOD},
{SDLK_SLASH, AKEYCODE_SLASH}, {SC_KEYCODE_SLASH, AKEYCODE_SLASH},
{SDLK_0, AKEYCODE_0}, {SC_KEYCODE_0, AKEYCODE_0},
{SDLK_1, AKEYCODE_1}, {SC_KEYCODE_1, AKEYCODE_1},
{SDLK_2, AKEYCODE_2}, {SC_KEYCODE_2, AKEYCODE_2},
{SDLK_3, AKEYCODE_3}, {SC_KEYCODE_3, AKEYCODE_3},
{SDLK_4, AKEYCODE_4}, {SC_KEYCODE_4, AKEYCODE_4},
{SDLK_5, AKEYCODE_5}, {SC_KEYCODE_5, AKEYCODE_5},
{SDLK_6, AKEYCODE_6}, {SC_KEYCODE_6, AKEYCODE_6},
{SDLK_7, AKEYCODE_7}, {SC_KEYCODE_7, AKEYCODE_7},
{SDLK_8, AKEYCODE_8}, {SC_KEYCODE_8, AKEYCODE_8},
{SDLK_9, AKEYCODE_9}, {SC_KEYCODE_9, AKEYCODE_9},
{SDLK_SEMICOLON, AKEYCODE_SEMICOLON}, {SC_KEYCODE_SEMICOLON, AKEYCODE_SEMICOLON},
{SDLK_EQUALS, AKEYCODE_EQUALS}, {SC_KEYCODE_EQUALS, AKEYCODE_EQUALS},
{SDLK_AT, AKEYCODE_AT}, {SC_KEYCODE_AT, AKEYCODE_AT},
{SDLK_LEFTBRACKET, AKEYCODE_LEFT_BRACKET}, {SC_KEYCODE_LEFTBRACKET, AKEYCODE_LEFT_BRACKET},
{SDLK_BACKSLASH, AKEYCODE_BACKSLASH}, {SC_KEYCODE_BACKSLASH, AKEYCODE_BACKSLASH},
{SDLK_RIGHTBRACKET, AKEYCODE_RIGHT_BRACKET}, {SC_KEYCODE_RIGHTBRACKET, AKEYCODE_RIGHT_BRACKET},
{SDLK_BACKQUOTE, AKEYCODE_GRAVE}, {SC_KEYCODE_BACKQUOTE, AKEYCODE_GRAVE},
{SDLK_KP_1, AKEYCODE_NUMPAD_1}, {SC_KEYCODE_KP_1, AKEYCODE_NUMPAD_1},
{SDLK_KP_2, AKEYCODE_NUMPAD_2}, {SC_KEYCODE_KP_2, AKEYCODE_NUMPAD_2},
{SDLK_KP_3, AKEYCODE_NUMPAD_3}, {SC_KEYCODE_KP_3, AKEYCODE_NUMPAD_3},
{SDLK_KP_4, AKEYCODE_NUMPAD_4}, {SC_KEYCODE_KP_4, AKEYCODE_NUMPAD_4},
{SDLK_KP_5, AKEYCODE_NUMPAD_5}, {SC_KEYCODE_KP_5, AKEYCODE_NUMPAD_5},
{SDLK_KP_6, AKEYCODE_NUMPAD_6}, {SC_KEYCODE_KP_6, AKEYCODE_NUMPAD_6},
{SDLK_KP_7, AKEYCODE_NUMPAD_7}, {SC_KEYCODE_KP_7, AKEYCODE_NUMPAD_7},
{SDLK_KP_8, AKEYCODE_NUMPAD_8}, {SC_KEYCODE_KP_8, AKEYCODE_NUMPAD_8},
{SDLK_KP_9, AKEYCODE_NUMPAD_9}, {SC_KEYCODE_KP_9, AKEYCODE_NUMPAD_9},
{SDLK_KP_0, AKEYCODE_NUMPAD_0}, {SC_KEYCODE_KP_0, AKEYCODE_NUMPAD_0},
{SDLK_KP_DIVIDE, AKEYCODE_NUMPAD_DIVIDE}, {SC_KEYCODE_KP_DIVIDE, AKEYCODE_NUMPAD_DIVIDE},
{SDLK_KP_MULTIPLY, AKEYCODE_NUMPAD_MULTIPLY}, {SC_KEYCODE_KP_MULTIPLY, AKEYCODE_NUMPAD_MULTIPLY},
{SDLK_KP_MINUS, AKEYCODE_NUMPAD_SUBTRACT}, {SC_KEYCODE_KP_MINUS, AKEYCODE_NUMPAD_SUBTRACT},
{SDLK_KP_PLUS, AKEYCODE_NUMPAD_ADD}, {SC_KEYCODE_KP_PLUS, AKEYCODE_NUMPAD_ADD},
{SDLK_KP_PERIOD, AKEYCODE_NUMPAD_DOT}, {SC_KEYCODE_KP_PERIOD, AKEYCODE_NUMPAD_DOT},
{SDLK_KP_EQUALS, AKEYCODE_NUMPAD_EQUALS}, {SC_KEYCODE_KP_EQUALS, AKEYCODE_NUMPAD_EQUALS},
{SDLK_KP_LEFTPAREN, AKEYCODE_NUMPAD_LEFT_PAREN}, {SC_KEYCODE_KP_LEFTPAREN, AKEYCODE_NUMPAD_LEFT_PAREN},
{SDLK_KP_RIGHTPAREN, AKEYCODE_NUMPAD_RIGHT_PAREN}, {SC_KEYCODE_KP_RIGHTPAREN, AKEYCODE_NUMPAD_RIGHT_PAREN},
}; };
const struct sc_intmap_entry *entry = const struct sc_intmap_entry *entry =
@ -157,7 +150,7 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
return true; return true;
} }
if (!(mod & (KMOD_NUM | KMOD_SHIFT))) { if (!(mod & (SC_MOD_NUM | SC_MOD_LSHIFT | SC_MOD_RSHIFT))) {
// Handle Numpad events when Num Lock is disabled // Handle Numpad events when Num Lock is disabled
// If SHIFT is pressed, a text event will be sent instead // If SHIFT is pressed, a text event will be sent instead
entry = SC_INTMAP_FIND_ENTRY(kp_nav_keys, from); entry = SC_INTMAP_FIND_ENTRY(kp_nav_keys, from);
@ -167,12 +160,13 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
} }
} }
if (key_inject_mode == SC_KEY_INJECT_MODE_TEXT && !(mod & KMOD_CTRL)) { if (key_inject_mode == SC_KEY_INJECT_MODE_TEXT &&
!(mod & (SC_MOD_LCTRL | SC_MOD_RCTRL))) {
// do not forward alpha and space key events (unless Ctrl is pressed) // do not forward alpha and space key events (unless Ctrl is pressed)
return false; return false;
} }
if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) { if (mod & (SC_MOD_LALT | SC_MOD_RALT | SC_MOD_LGUI | SC_MOD_RGUI)) {
return false; return false;
} }
@ -214,70 +208,63 @@ autocomplete_metastate(enum android_metastate metastate) {
} }
static enum android_metastate static enum android_metastate
convert_meta_state(SDL_Keymod mod) { convert_meta_state(uint16_t mod) {
enum android_metastate metastate = 0; enum android_metastate metastate = 0;
if (mod & KMOD_LSHIFT) { if (mod & SC_MOD_LSHIFT) {
metastate |= AMETA_SHIFT_LEFT_ON; metastate |= AMETA_SHIFT_LEFT_ON;
} }
if (mod & KMOD_RSHIFT) { if (mod & SC_MOD_RSHIFT) {
metastate |= AMETA_SHIFT_RIGHT_ON; metastate |= AMETA_SHIFT_RIGHT_ON;
} }
if (mod & KMOD_LCTRL) { if (mod & SC_MOD_LCTRL) {
metastate |= AMETA_CTRL_LEFT_ON; metastate |= AMETA_CTRL_LEFT_ON;
} }
if (mod & KMOD_RCTRL) { if (mod & SC_MOD_RCTRL) {
metastate |= AMETA_CTRL_RIGHT_ON; metastate |= AMETA_CTRL_RIGHT_ON;
} }
if (mod & KMOD_LALT) { if (mod & SC_MOD_LALT) {
metastate |= AMETA_ALT_LEFT_ON; metastate |= AMETA_ALT_LEFT_ON;
} }
if (mod & KMOD_RALT) { if (mod & SC_MOD_RALT) {
metastate |= AMETA_ALT_RIGHT_ON; metastate |= AMETA_ALT_RIGHT_ON;
} }
if (mod & KMOD_LGUI) { // Windows key if (mod & SC_MOD_LGUI) { // Windows key
metastate |= AMETA_META_LEFT_ON; metastate |= AMETA_META_LEFT_ON;
} }
if (mod & KMOD_RGUI) { // Windows key if (mod & SC_MOD_RGUI) { // Windows key
metastate |= AMETA_META_RIGHT_ON; metastate |= AMETA_META_RIGHT_ON;
} }
if (mod & KMOD_NUM) { if (mod & SC_MOD_NUM) {
metastate |= AMETA_NUM_LOCK_ON; metastate |= AMETA_NUM_LOCK_ON;
} }
if (mod & KMOD_CAPS) { if (mod & SC_MOD_CAPS) {
metastate |= AMETA_CAPS_LOCK_ON; metastate |= AMETA_CAPS_LOCK_ON;
} }
if (mod & KMOD_MODE) { // Alt Gr
// no mapping?
}
// fill the dependent fields // fill the dependent fields
return autocomplete_metastate(metastate); return autocomplete_metastate(metastate);
} }
static bool static bool
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to, convert_input_key(const struct sc_key_event *event, struct sc_control_msg *msg,
enum sc_key_inject_mode key_inject_mode, uint32_t repeat) { enum sc_key_inject_mode key_inject_mode, uint32_t repeat) {
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE; msg->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) { if (!convert_keycode(event->keycode, &msg->inject_keycode.keycode,
event->mods_state, key_inject_mode)) {
return false; return false;
} }
uint16_t mod = from->keysym.mod; msg->inject_keycode.action = convert_keycode_action(event->action);
if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod, msg->inject_keycode.repeat = repeat;
key_inject_mode)) { msg->inject_keycode.metastate = convert_meta_state(event->mods_state);
return false;
}
to->inject_keycode.repeat = repeat;
to->inject_keycode.metastate = convert_meta_state(mod);
return true; return true;
} }
static void static void
sc_key_processor_process_key(struct sc_key_processor *kp, sc_key_processor_process_key(struct sc_key_processor *kp,
const SDL_KeyboardEvent *event, const struct sc_key_event *event,
uint64_t ack_to_wait) { uint64_t ack_to_wait) {
// The device clipboard synchronization and the key event messages are // The device clipboard synchronization and the key event messages are
// serialized, there is nothing special to do to ensure that the clipboard // serialized, there is nothing special to do to ensure that the clipboard
@ -295,9 +282,9 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
ki->repeat = 0; ki->repeat = 0;
} }
struct control_msg msg; struct sc_control_msg msg;
if (convert_input_key(event, &msg, ki->key_inject_mode, ki->repeat)) { if (convert_input_key(event, &msg, ki->key_inject_mode, ki->repeat)) {
if (!controller_push_msg(ki->controller, &msg)) { if (!sc_controller_push_msg(ki->controller, &msg)) {
LOGW("Could not request 'inject keycode'"); LOGW("Could not request 'inject keycode'");
} }
} }
@ -305,7 +292,7 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
static void static void
sc_key_processor_process_text(struct sc_key_processor *kp, sc_key_processor_process_text(struct sc_key_processor *kp,
const SDL_TextInputEvent *event) { const struct sc_text_event *event) {
struct sc_keyboard_inject *ki = DOWNCAST(kp); struct sc_keyboard_inject *ki = DOWNCAST(kp);
if (ki->key_inject_mode == SC_KEY_INJECT_MODE_RAW) { if (ki->key_inject_mode == SC_KEY_INJECT_MODE_RAW) {
@ -322,14 +309,14 @@ sc_key_processor_process_text(struct sc_key_processor *kp,
} }
} }
struct control_msg msg; struct sc_control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = strdup(event->text); msg.inject_text.text = strdup(event->text);
if (!msg.inject_text.text) { if (!msg.inject_text.text) {
LOGW("Could not strdup input text"); LOGW("Could not strdup input text");
return; return;
} }
if (!controller_push_msg(ki->controller, &msg)) { if (!sc_controller_push_msg(ki->controller, &msg)) {
free(msg.inject_text.text); free(msg.inject_text.text);
LOGW("Could not request 'inject text'"); LOGW("Could not request 'inject text'");
} }
@ -337,11 +324,12 @@ sc_key_processor_process_text(struct sc_key_processor *kp,
void void
sc_keyboard_inject_init(struct sc_keyboard_inject *ki, sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
struct controller *controller, struct sc_controller *controller,
const struct scrcpy_options *options) { enum sc_key_inject_mode key_inject_mode,
bool forward_key_repeat) {
ki->controller = controller; ki->controller = controller;
ki->key_inject_mode = options->key_inject_mode; ki->key_inject_mode = key_inject_mode;
ki->forward_key_repeat = options->forward_key_repeat; ki->forward_key_repeat = forward_key_repeat;
ki->repeat = 0; ki->repeat = 0;

View File

@ -12,7 +12,7 @@
struct sc_keyboard_inject { struct sc_keyboard_inject {
struct sc_key_processor key_processor; // key processor trait struct sc_key_processor key_processor; // key processor trait
struct controller *controller; struct sc_controller *controller;
// SDL reports repeated events as a boolean, but Android expects the actual // SDL reports repeated events as a boolean, but Android expects the actual
// number of repetitions. This variable keeps track of the count. // number of repetitions. This variable keeps track of the count.
@ -24,7 +24,8 @@ struct sc_keyboard_inject {
void void
sc_keyboard_inject_init(struct sc_keyboard_inject *ki, sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
struct controller *controller, struct sc_controller *controller,
const struct scrcpy_options *options); enum sc_key_inject_mode key_inject_mode,
bool forward_key_repeat);
#endif #endif

View File

@ -1,11 +1,11 @@
#include "mouse_inject.h" #include "mouse_inject.h"
#include <assert.h> #include <assert.h>
#include <SDL2/SDL_events.h>
#include "android/input.h" #include "android/input.h"
#include "control_msg.h" #include "control_msg.h"
#include "controller.h" #include "controller.h"
#include "input_events.h"
#include "util/intmap.h" #include "util/intmap.h"
#include "util/log.h" #include "util/log.h"
@ -15,210 +15,147 @@
static enum android_motionevent_buttons static enum android_motionevent_buttons
convert_mouse_buttons(uint32_t state) { convert_mouse_buttons(uint32_t state) {
enum android_motionevent_buttons buttons = 0; enum android_motionevent_buttons buttons = 0;
if (state & SDL_BUTTON_LMASK) { if (state & SC_MOUSE_BUTTON_LEFT) {
buttons |= AMOTION_EVENT_BUTTON_PRIMARY; buttons |= AMOTION_EVENT_BUTTON_PRIMARY;
} }
if (state & SDL_BUTTON_RMASK) { if (state & SC_MOUSE_BUTTON_RIGHT) {
buttons |= AMOTION_EVENT_BUTTON_SECONDARY; buttons |= AMOTION_EVENT_BUTTON_SECONDARY;
} }
if (state & SDL_BUTTON_MMASK) { if (state & SC_MOUSE_BUTTON_MIDDLE) {
buttons |= AMOTION_EVENT_BUTTON_TERTIARY; buttons |= AMOTION_EVENT_BUTTON_TERTIARY;
} }
if (state & SDL_BUTTON_X1MASK) { if (state & SC_MOUSE_BUTTON_X1) {
buttons |= AMOTION_EVENT_BUTTON_BACK; buttons |= AMOTION_EVENT_BUTTON_BACK;
} }
if (state & SDL_BUTTON_X2MASK) { if (state & SC_MOUSE_BUTTON_X2) {
buttons |= AMOTION_EVENT_BUTTON_FORWARD; buttons |= AMOTION_EVENT_BUTTON_FORWARD;
} }
return buttons; return buttons;
} }
static bool static enum android_motionevent_action
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) { convert_mouse_action(enum sc_action action) {
static const struct sc_intmap_entry actions[] = { if (action == SC_ACTION_DOWN) {
{SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN}, return AMOTION_EVENT_ACTION_DOWN;
{SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP},
};
const struct sc_intmap_entry *entry = SC_INTMAP_FIND_ENTRY(actions, from);
if (entry) {
*to = entry->value;
return true;
} }
assert(action == SC_ACTION_UP);
return false; return AMOTION_EVENT_ACTION_UP;
} }
static bool static enum android_motionevent_action
convert_touch_action(SDL_EventType from, enum android_motionevent_action *to) { convert_touch_action(enum sc_touch_action action) {
static const struct sc_intmap_entry actions[] = { switch (action) {
{SDL_FINGERMOTION, AMOTION_EVENT_ACTION_MOVE}, case SC_TOUCH_ACTION_MOVE:
{SDL_FINGERDOWN, AMOTION_EVENT_ACTION_DOWN}, return AMOTION_EVENT_ACTION_MOVE;
{SDL_FINGERUP, AMOTION_EVENT_ACTION_UP}, case SC_TOUCH_ACTION_DOWN:
}; return AMOTION_EVENT_ACTION_DOWN;
default:
const struct sc_intmap_entry *entry = SC_INTMAP_FIND_ENTRY(actions, from); assert(action == SC_TOUCH_ACTION_UP);
if (entry) { return AMOTION_EVENT_ACTION_UP;
*to = entry->value;
return true;
} }
return false;
}
static bool
convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
to->inject_touch_event.action = AMOTION_EVENT_ACTION_MOVE;
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = screen->frame_size;
to->inject_touch_event.position.point =
screen_convert_window_to_frame_coords(screen, from->x, from->y);
to->inject_touch_event.pressure = 1.f;
to->inject_touch_event.buttons = convert_mouse_buttons(from->state);
return true;
}
static bool
convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
if (!convert_touch_action(from->type, &to->inject_touch_event.action)) {
return false;
}
to->inject_touch_event.pointer_id = from->fingerId;
to->inject_touch_event.position.screen_size = screen->frame_size;
int dw;
int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
// SDL touch event coordinates are normalized in the range [0; 1]
int32_t x = from->x * dw;
int32_t y = from->y * dh;
to->inject_touch_event.position.point =
screen_convert_drawable_to_frame_coords(screen, x, y);
to->inject_touch_event.pressure = from->pressure;
to->inject_touch_event.buttons = 0;
return true;
}
static bool
convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
if (!convert_mouse_action(from->type, &to->inject_touch_event.action)) {
return false;
}
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = screen->frame_size;
to->inject_touch_event.position.point =
screen_convert_window_to_frame_coords(screen, from->x, from->y);
to->inject_touch_event.pressure =
from->type == SDL_MOUSEBUTTONDOWN ? 1.f : 0.f;
to->inject_touch_event.buttons =
convert_mouse_buttons(SDL_BUTTON(from->button));
return true;
}
static bool
convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
struct control_msg *to) {
// mouse_x and mouse_y are expressed in pixels relative to the window
int mouse_x;
int mouse_y;
SDL_GetMouseState(&mouse_x, &mouse_y);
struct sc_position position = {
.screen_size = screen->frame_size,
.point = screen_convert_window_to_frame_coords(screen,
mouse_x, mouse_y),
};
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;
to->inject_scroll_event.position = position;
to->inject_scroll_event.hscroll = from->x;
to->inject_scroll_event.vscroll = from->y;
return true;
} }
static void static void
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
const SDL_MouseMotionEvent *event) { const struct sc_mouse_motion_event *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp); if (!event->buttons_state) {
// Do not send motion events when no click is pressed
struct control_msg msg;
if (!convert_mouse_motion(event, mi->screen, &msg)) {
return; return;
} }
if (!controller_push_msg(mi->controller, &msg)) { struct sc_mouse_inject *mi = DOWNCAST(mp);
struct sc_control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
.inject_touch_event = {
.action = AMOTION_EVENT_ACTION_MOVE,
.pointer_id = POINTER_ID_MOUSE,
.position = event->position,
.pressure = 1.f,
.buttons = convert_mouse_buttons(event->buttons_state),
},
};
if (!sc_controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse motion event'"); LOGW("Could not request 'inject mouse motion event'");
} }
} }
static void
sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
const struct sc_mouse_click_event *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct sc_control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
.inject_touch_event = {
.action = convert_mouse_action(event->action),
.pointer_id = POINTER_ID_MOUSE,
.position = event->position,
.pressure = event->action == SC_ACTION_DOWN ? 1.f : 0.f,
.buttons = convert_mouse_buttons(event->buttons_state),
},
};
if (!sc_controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse click event'");
}
}
static void
sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
const struct sc_mouse_scroll_event *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct sc_control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
.inject_scroll_event = {
.position = event->position,
.hscroll = event->hscroll,
.vscroll = event->vscroll,
.buttons = convert_mouse_buttons(event->buttons_state),
},
};
if (!sc_controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse scroll event'");
}
}
static void static void
sc_mouse_processor_process_touch(struct sc_mouse_processor *mp, sc_mouse_processor_process_touch(struct sc_mouse_processor *mp,
const SDL_TouchFingerEvent *event) { const struct sc_touch_event *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp); struct sc_mouse_inject *mi = DOWNCAST(mp);
struct control_msg msg; struct sc_control_msg msg = {
if (convert_touch(event, mi->screen, &msg)) { .type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
if (!controller_push_msg(mi->controller, &msg)) { .inject_touch_event = {
LOGW("Could not request 'inject touch event'"); .action = convert_touch_action(event->action),
} .pointer_id = event->pointer_id,
} .position = event->position,
} .pressure = event->pressure,
.buttons = 0,
},
};
static void if (!sc_controller_push_msg(mi->controller, &msg)) {
sc_mouse_processor_process_mouse_button(struct sc_mouse_processor *mp, LOGW("Could not request 'inject touch event'");
const SDL_MouseButtonEvent *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct control_msg msg;
if (convert_mouse_button(event, mi->screen, &msg)) {
if (!controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse button event'");
}
}
}
static void
sc_mouse_processor_process_mouse_wheel(struct sc_mouse_processor *mp,
const SDL_MouseWheelEvent *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct control_msg msg;
if (convert_mouse_wheel(event, mi->screen, &msg)) {
if (!controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse wheel event'");
}
} }
} }
void void
sc_mouse_inject_init(struct sc_mouse_inject *mi, struct controller *controller, sc_mouse_inject_init(struct sc_mouse_inject *mi,
struct screen *screen) { struct sc_controller *controller) {
mi->controller = controller; mi->controller = controller;
mi->screen = screen;
static const struct sc_mouse_processor_ops ops = { static const struct sc_mouse_processor_ops ops = {
.process_mouse_motion = sc_mouse_processor_process_mouse_motion, .process_mouse_motion = sc_mouse_processor_process_mouse_motion,
.process_mouse_click = sc_mouse_processor_process_mouse_click,
.process_mouse_scroll = sc_mouse_processor_process_mouse_scroll,
.process_touch = sc_mouse_processor_process_touch, .process_touch = sc_mouse_processor_process_touch,
.process_mouse_button = sc_mouse_processor_process_mouse_button,
.process_mouse_wheel = sc_mouse_processor_process_mouse_wheel,
}; };
mi->mouse_processor.ops = &ops; mi->mouse_processor.ops = &ops;
mi->mouse_processor.relative_mode = false;
} }

View File

@ -12,12 +12,11 @@
struct sc_mouse_inject { struct sc_mouse_inject {
struct sc_mouse_processor mouse_processor; // mouse processor trait struct sc_mouse_processor mouse_processor; // mouse processor trait
struct controller *controller; struct sc_controller *controller;
struct screen *screen;
}; };
void void
sc_mouse_inject_init(struct sc_mouse_inject *mi, struct controller *controller, sc_mouse_inject_init(struct sc_mouse_inject *mi,
struct screen *screen); struct sc_controller *controller);
#endif #endif

View File

@ -22,7 +22,7 @@ const struct scrcpy_options scrcpy_options_default = {
.tunnel_host = 0, .tunnel_host = 0,
.tunnel_port = 0, .tunnel_port = 0,
.shortcut_mods = { .shortcut_mods = {
.data = {SC_MOD_LALT, SC_MOD_LSUPER}, .data = {SC_SHORTCUT_MOD_LALT, SC_SHORTCUT_MOD_LSUPER},
.count = 2, .count = 2,
}, },
.max_size = 0, .max_size = 0,

View File

@ -38,6 +38,11 @@ enum sc_keyboard_input_mode {
SC_KEYBOARD_INPUT_MODE_HID, SC_KEYBOARD_INPUT_MODE_HID,
}; };
enum sc_mouse_input_mode {
SC_MOUSE_INPUT_MODE_INJECT,
SC_MOUSE_INPUT_MODE_HID,
};
enum sc_key_inject_mode { enum sc_key_inject_mode {
// Inject special keys, letters and space as key events. // Inject special keys, letters and space as key events.
// Inject numbers and punctuation as text events. // Inject numbers and punctuation as text events.
@ -55,12 +60,12 @@ enum sc_key_inject_mode {
#define SC_MAX_SHORTCUT_MODS 8 #define SC_MAX_SHORTCUT_MODS 8
enum sc_shortcut_mod { enum sc_shortcut_mod {
SC_MOD_LCTRL = 1 << 0, SC_SHORTCUT_MOD_LCTRL = 1 << 0,
SC_MOD_RCTRL = 1 << 1, SC_SHORTCUT_MOD_RCTRL = 1 << 1,
SC_MOD_LALT = 1 << 2, SC_SHORTCUT_MOD_LALT = 1 << 2,
SC_MOD_RALT = 1 << 3, SC_SHORTCUT_MOD_RALT = 1 << 3,
SC_MOD_LSUPER = 1 << 4, SC_SHORTCUT_MOD_LSUPER = 1 << 4,
SC_MOD_RSUPER = 1 << 5, SC_SHORTCUT_MOD_RSUPER = 1 << 5,
}; };
struct sc_shortcut_mods { struct sc_shortcut_mods {
@ -90,6 +95,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;
enum sc_keyboard_input_mode keyboard_input_mode; enum sc_keyboard_input_mode keyboard_input_mode;
enum sc_mouse_input_mode mouse_input_mode;
struct sc_port_range port_range; struct sc_port_range port_range;
uint32_t tunnel_host; uint32_t tunnel_host;
uint16_t tunnel_port; uint16_t tunnel_port;

View File

@ -17,9 +17,9 @@
#include "decoder.h" #include "decoder.h"
#include "events.h" #include "events.h"
#include "file_handler.h" #include "file_handler.h"
#include "input_manager.h"
#ifdef HAVE_AOA_HID #ifdef HAVE_AOA_HID
# include "hid_keyboard.h" # include "hid_keyboard.h"
# include "hid_mouse.h"
#endif #endif
#include "keyboard_inject.h" #include "keyboard_inject.h"
#include "mouse_inject.h" #include "mouse_inject.h"
@ -36,14 +36,14 @@
struct scrcpy { struct scrcpy {
struct sc_server server; struct sc_server server;
struct screen screen; struct sc_screen screen;
struct stream stream; struct stream stream;
struct decoder decoder; struct decoder decoder;
struct recorder recorder; struct recorder recorder;
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
struct sc_v4l2_sink v4l2_sink; struct sc_v4l2_sink v4l2_sink;
#endif #endif
struct controller controller; struct sc_controller controller;
struct file_handler file_handler; struct file_handler file_handler;
#ifdef HAVE_AOA_HID #ifdef HAVE_AOA_HID
struct sc_aoa aoa; struct sc_aoa aoa;
@ -56,8 +56,12 @@ struct scrcpy {
struct sc_hid_keyboard keyboard_hid; struct sc_hid_keyboard keyboard_hid;
#endif #endif
}; };
struct sc_mouse_inject mouse_inject; union {
struct input_manager input_manager; struct sc_mouse_inject mouse_inject;
#ifdef HAVE_AOA_HID
struct sc_hid_mouse mouse_hid;
#endif
};
}; };
static inline void static inline void
@ -188,12 +192,7 @@ handle_event(struct scrcpy *s, const struct scrcpy_options *options,
} }
} }
bool consumed = screen_handle_event(&s->screen, event); bool consumed = sc_screen_handle_event(&s->screen, event);
if (consumed) {
goto end;
}
consumed = input_manager_handle_event(&s->input_manager, event);
(void) consumed; (void) consumed;
end: end:
@ -336,6 +335,8 @@ scrcpy(struct scrcpy_options *options) {
bool stream_started = false; bool stream_started = false;
#ifdef HAVE_AOA_HID #ifdef HAVE_AOA_HID
bool aoa_hid_initialized = false; bool aoa_hid_initialized = false;
bool hid_keyboard_initialized = false;
bool hid_mouse_initialized = false;
#endif #endif
bool controller_initialized = false; bool controller_initialized = false;
bool controller_started = false; bool controller_started = false;
@ -450,44 +451,137 @@ scrcpy(struct scrcpy_options *options) {
stream_add_sink(&s->stream, &rec->packet_sink); stream_add_sink(&s->stream, &rec->packet_sink);
} }
struct sc_key_processor *kp = NULL;
struct sc_mouse_processor *mp = NULL;
if (options->control) { if (options->control) {
#ifdef HAVE_AOA_HID #ifdef HAVE_AOA_HID
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID) { bool use_hid_keyboard =
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID;
bool use_hid_mouse =
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID;
if (use_hid_keyboard || use_hid_mouse) {
bool ok = sc_acksync_init(&s->acksync); bool ok = sc_acksync_init(&s->acksync);
if (!ok) { if (!ok) {
goto end; goto end;
} }
ok = sc_aoa_init(&s->aoa, serial, &s->acksync);
if (!ok) {
LOGE("Failed to enable HID over AOA");
sc_acksync_destroy(&s->acksync);
goto aoa_hid_end;
}
if (use_hid_keyboard) {
if (sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa)) {
hid_keyboard_initialized = true;
kp = &s->keyboard_hid.key_processor;
} else {
LOGE("Could not initialize HID keyboard");
}
}
if (use_hid_mouse) {
if (sc_hid_mouse_init(&s->mouse_hid, &s->aoa)) {
hid_mouse_initialized = true;
mp = &s->mouse_hid.mouse_processor;
} else {
LOGE("Could not initialized HID mouse");
}
}
bool need_aoa = hid_keyboard_initialized || hid_mouse_initialized;
if (!need_aoa || !sc_aoa_start(&s->aoa)) {
sc_acksync_destroy(&s->acksync);
sc_aoa_destroy(&s->aoa);
goto aoa_hid_end;
}
acksync = &s->acksync; acksync = &s->acksync;
aoa_hid_initialized = true;
aoa_hid_end:
if (!aoa_hid_initialized) {
if (hid_keyboard_initialized) {
sc_hid_keyboard_destroy(&s->keyboard_hid);
hid_keyboard_initialized = false;
}
if (hid_mouse_initialized) {
sc_hid_mouse_destroy(&s->mouse_hid);
hid_mouse_initialized = false;
}
}
if (use_hid_keyboard && !hid_keyboard_initialized) {
LOGE("Fallback to default keyboard injection method "
"(-K/--hid-keyboard ignored)");
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
}
if (use_hid_mouse && !hid_mouse_initialized) {
LOGE("Fallback to default mouse injection method "
"(-M/--hid-mouse ignored)");
options->mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT;
}
} }
#else
assert(options->keyboard_input_mode != SC_KEYBOARD_INPUT_MODE_HID);
assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_HID);
#endif #endif
if (!controller_init(&s->controller, s->server.control_socket,
acksync)) { // keyboard_input_mode may have been reset if HID mode failed
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_INJECT) {
sc_keyboard_inject_init(&s->keyboard_inject, &s->controller,
options->key_inject_mode,
options->forward_key_repeat);
kp = &s->keyboard_inject.key_processor;
}
// mouse_input_mode may have been reset if HID mode failed
if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_INJECT) {
sc_mouse_inject_init(&s->mouse_inject, &s->controller);
mp = &s->mouse_inject.mouse_processor;
}
if (!sc_controller_init(&s->controller, s->server.control_socket,
acksync)) {
goto end; goto end;
} }
controller_initialized = true; controller_initialized = true;
if (!controller_start(&s->controller)) { if (!sc_controller_start(&s->controller)) {
goto end; goto end;
} }
controller_started = true; controller_started = true;
if (options->turn_screen_off) { if (options->turn_screen_off) {
struct control_msg msg; struct sc_control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE; msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
msg.set_screen_power_mode.mode = SCREEN_POWER_MODE_OFF; msg.set_screen_power_mode.mode = SCREEN_POWER_MODE_OFF;
if (!controller_push_msg(&s->controller, &msg)) { if (!sc_controller_push_msg(&s->controller, &msg)) {
LOGW("Could not request 'set screen power mode'"); LOGW("Could not request 'set screen power mode'");
} }
} }
} }
if (options->display) { if (options->display) {
const char *window_title = const char *window_title =
options->window_title ? options->window_title : info->device_name; options->window_title ? options->window_title : info->device_name;
struct screen_params screen_params = { struct sc_screen_params screen_params = {
.controller = &s->controller,
.kp = kp,
.mp = mp,
.control = options->control,
.forward_all_clicks = options->forward_all_clicks,
.legacy_paste = options->legacy_paste,
.clipboard_autosync = options->clipboard_autosync,
.shortcut_mods = &options->shortcut_mods,
.window_title = window_title, .window_title = window_title,
.frame_size = info->frame_size, .frame_size = info->frame_size,
.always_on_top = options->always_on_top, .always_on_top = options->always_on_top,
@ -502,7 +596,7 @@ scrcpy(struct scrcpy_options *options) {
.buffering_time = options->display_buffer, .buffering_time = options->display_buffer,
}; };
if (!screen_init(&s->screen, &screen_params)) { if (!sc_screen_init(&s->screen, &screen_params)) {
goto end; goto end;
} }
screen_initialized = true; screen_initialized = true;
@ -530,77 +624,21 @@ scrcpy(struct scrcpy_options *options) {
} }
stream_started = true; stream_started = true;
struct sc_key_processor *kp = NULL;
struct sc_mouse_processor *mp = NULL;
if (options->control) {
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID) {
#ifdef HAVE_AOA_HID
bool aoa_hid_ok = false;
bool ok = sc_aoa_init(&s->aoa, serial, acksync);
if (!ok) {
goto aoa_hid_end;
}
if (!sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa)) {
sc_aoa_destroy(&s->aoa);
goto aoa_hid_end;
}
if (!sc_aoa_start(&s->aoa)) {
sc_hid_keyboard_destroy(&s->keyboard_hid);
sc_aoa_destroy(&s->aoa);
goto aoa_hid_end;
}
aoa_hid_ok = true;
kp = &s->keyboard_hid.key_processor;
aoa_hid_initialized = true;
aoa_hid_end:
if (!aoa_hid_ok) {
LOGE("Failed to enable HID over AOA, "
"fallback to default keyboard injection method "
"(-K/--hid-keyboard ignored)");
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
}
#else
LOGE("HID over AOA is not supported on this platform, "
"fallback to default keyboard injection method "
"(-K/--hid-keyboard ignored)");
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
#endif
}
// keyboard_input_mode may have been reset if HID mode failed
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_INJECT) {
sc_keyboard_inject_init(&s->keyboard_inject, &s->controller,
options);
kp = &s->keyboard_inject.key_processor;
}
sc_mouse_inject_init(&s->mouse_inject, &s->controller, &s->screen);
mp = &s->mouse_inject.mouse_processor;
}
input_manager_init(&s->input_manager, &s->controller, &s->screen, kp, mp,
options);
ret = event_loop(s, options); ret = event_loop(s, options);
LOGD("quit..."); LOGD("quit...");
// Close the window immediately on closing, because screen_destroy() may // Close the window immediately on closing, because screen_destroy() may
// only be called once the stream thread is joined (it may take time) // only be called once the stream thread is joined (it may take time)
screen_hide_window(&s->screen); sc_screen_hide_window(&s->screen);
end: end:
// The stream is not stopped explicitly, because it will stop by itself on // The stream is not stopped explicitly, because it will stop by itself on
// end-of-stream // end-of-stream
#ifdef HAVE_AOA_HID #ifdef HAVE_AOA_HID
if (aoa_hid_initialized) { if (aoa_hid_initialized) {
sc_hid_keyboard_destroy(&s->keyboard_hid); if (hid_keyboard_initialized) {
sc_hid_keyboard_destroy(&s->keyboard_hid);
}
sc_aoa_stop(&s->aoa); sc_aoa_stop(&s->aoa);
} }
if (acksync) { if (acksync) {
@ -608,13 +646,13 @@ end:
} }
#endif #endif
if (controller_started) { if (controller_started) {
controller_stop(&s->controller); sc_controller_stop(&s->controller);
} }
if (file_handler_initialized) { if (file_handler_initialized) {
file_handler_stop(&s->file_handler); file_handler_stop(&s->file_handler);
} }
if (screen_initialized) { if (screen_initialized) {
screen_interrupt(&s->screen); sc_screen_interrupt(&s->screen);
} }
if (server_started) { if (server_started) {
@ -644,15 +682,15 @@ end:
// Destroy the screen only after the stream is guaranteed to be finished, // Destroy the screen only after the stream is guaranteed to be finished,
// because otherwise the screen could receive new frames after destruction // because otherwise the screen could receive new frames after destruction
if (screen_initialized) { if (screen_initialized) {
screen_join(&s->screen); sc_screen_join(&s->screen);
screen_destroy(&s->screen); sc_screen_destroy(&s->screen);
} }
if (controller_started) { if (controller_started) {
controller_join(&s->controller); sc_controller_join(&s->controller);
} }
if (controller_initialized) { if (controller_initialized) {
controller_destroy(&s->controller); sc_controller_destroy(&s->controller);
} }
if (recorder_initialized) { if (recorder_initialized) {

View File

@ -12,7 +12,7 @@
#define DISPLAY_MARGINS 96 #define DISPLAY_MARGINS 96
#define DOWNCAST(SINK) container_of(SINK, struct screen, frame_sink) #define DOWNCAST(SINK) container_of(SINK, struct sc_screen, frame_sink)
static inline struct sc_size static inline struct sc_size
get_rotated_size(struct sc_size size, int rotation) { get_rotated_size(struct sc_size size, int rotation) {
@ -29,7 +29,7 @@ get_rotated_size(struct sc_size size, int rotation) {
// get the window size in a struct sc_size // get the window size in a struct sc_size
static struct sc_size static struct sc_size
get_window_size(const struct screen *screen) { get_window_size(const struct sc_screen *screen) {
int width; int width;
int height; int height;
SDL_GetWindowSize(screen->window, &width, &height); SDL_GetWindowSize(screen->window, &width, &height);
@ -41,7 +41,7 @@ get_window_size(const struct screen *screen) {
} }
static struct sc_point static struct sc_point
get_window_position(const struct screen *screen) { get_window_position(const struct sc_screen *screen) {
int x; int x;
int y; int y;
SDL_GetWindowPosition(screen->window, &x, &y); SDL_GetWindowPosition(screen->window, &x, &y);
@ -54,7 +54,7 @@ get_window_position(const struct screen *screen) {
// set the window size to be applied when fullscreen is disabled // set the window size to be applied when fullscreen is disabled
static void static void
set_window_size(struct screen *screen, struct sc_size new_size) { set_window_size(struct sc_screen *screen, struct sc_size new_size) {
assert(!screen->fullscreen); assert(!screen->fullscreen);
assert(!screen->maximized); assert(!screen->maximized);
SDL_SetWindowSize(screen->window, new_size.width, new_size.height); SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
@ -156,8 +156,19 @@ get_initial_optimal_size(struct sc_size content_size, uint16_t req_width,
return window_size; return window_size;
} }
static inline void
sc_screen_capture_mouse(struct sc_screen *screen, bool capture) {
if (SDL_SetRelativeMouseMode(capture)) {
LOGE("Could not set relative mouse mode to %s: %s",
capture ? "true" : "false", SDL_GetError());
return;
}
screen->mouse_captured = capture;
}
static void static void
screen_update_content_rect(struct screen *screen) { sc_screen_update_content_rect(struct sc_screen *screen) {
int dw; int dw;
int dh; int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh); SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
@ -194,7 +205,7 @@ screen_update_content_rect(struct screen *screen) {
} }
static inline SDL_Texture * static inline SDL_Texture *
create_texture(struct screen *screen) { create_texture(struct sc_screen *screen) {
SDL_Renderer *renderer = screen->renderer; SDL_Renderer *renderer = screen->renderer;
struct sc_size size = screen->frame_size; struct sc_size size = screen->frame_size;
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
@ -225,9 +236,9 @@ create_texture(struct screen *screen) {
// Set the update_content_rect flag if the window or content size may have // Set the update_content_rect flag if the window or content size may have
// changed, so that the content rectangle is recomputed // changed, so that the content rectangle is recomputed
static void static void
screen_render(struct screen *screen, bool update_content_rect) { sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
if (update_content_rect) { if (update_content_rect) {
screen_update_content_rect(screen); sc_screen_update_content_rect(screen);
} }
SDL_RenderClear(screen->renderer); SDL_RenderClear(screen->renderer);
@ -271,20 +282,20 @@ screen_render(struct screen *screen, bool update_content_rect) {
// <https://stackoverflow.com/a/40693139/1987178> // <https://stackoverflow.com/a/40693139/1987178>
static int static int
event_watcher(void *data, SDL_Event *event) { event_watcher(void *data, SDL_Event *event) {
struct screen *screen = data; struct sc_screen *screen = data;
if (event->type == SDL_WINDOWEVENT if (event->type == SDL_WINDOWEVENT
&& event->window.event == SDL_WINDOWEVENT_RESIZED) { && event->window.event == SDL_WINDOWEVENT_RESIZED) {
// In practice, it seems to always be called from the same thread in // In practice, it seems to always be called from the same thread in
// that specific case. Anyway, it's just a workaround. // that specific case. Anyway, it's just a workaround.
screen_render(screen, true); sc_screen_render(screen, true);
} }
return 0; return 0;
} }
#endif #endif
static bool static bool
screen_frame_sink_open(struct sc_frame_sink *sink) { sc_screen_frame_sink_open(struct sc_frame_sink *sink) {
struct screen *screen = DOWNCAST(sink); struct sc_screen *screen = DOWNCAST(sink);
(void) screen; (void) screen;
#ifndef NDEBUG #ifndef NDEBUG
screen->open = true; screen->open = true;
@ -295,8 +306,8 @@ screen_frame_sink_open(struct sc_frame_sink *sink) {
} }
static void static void
screen_frame_sink_close(struct sc_frame_sink *sink) { sc_screen_frame_sink_close(struct sc_frame_sink *sink) {
struct screen *screen = DOWNCAST(sink); struct sc_screen *screen = DOWNCAST(sink);
(void) screen; (void) screen;
#ifndef NDEBUG #ifndef NDEBUG
screen->open = false; screen->open = false;
@ -306,8 +317,8 @@ screen_frame_sink_close(struct sc_frame_sink *sink) {
} }
static bool static bool
screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) { sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
struct screen *screen = DOWNCAST(sink); struct sc_screen *screen = DOWNCAST(sink);
return sc_video_buffer_push(&screen->vb, frame); return sc_video_buffer_push(&screen->vb, frame);
} }
@ -315,7 +326,7 @@ static void
sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped, sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
void *userdata) { void *userdata) {
(void) vb; (void) vb;
struct screen *screen = userdata; struct sc_screen *screen = userdata;
// event_failed implies previous_skipped (the previous frame may not have // event_failed implies previous_skipped (the previous frame may not have
// been consumed if the event was not sent) // been consumed if the event was not sent)
@ -348,12 +359,15 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
} }
bool bool
screen_init(struct screen *screen, const struct screen_params *params) { sc_screen_init(struct sc_screen *screen,
const struct sc_screen_params *params) {
screen->resize_pending = false; screen->resize_pending = false;
screen->has_frame = false; screen->has_frame = false;
screen->fullscreen = false; screen->fullscreen = false;
screen->maximized = false; screen->maximized = false;
screen->event_failed = false; screen->event_failed = false;
screen->mouse_captured = false;
screen->mouse_capture_key_pressed = 0;
static const struct sc_video_buffer_callbacks cbs = { static const struct sc_video_buffer_callbacks cbs = {
.on_new_frame = sc_video_buffer_on_new_frame, .on_new_frame = sc_video_buffer_on_new_frame,
@ -470,15 +484,29 @@ screen_init(struct screen *screen, const struct screen_params *params) {
goto error_destroy_texture; goto error_destroy_texture;
} }
struct sc_input_manager_params im_params = {
.controller = params->controller,
.screen = screen,
.kp = params->kp,
.mp = params->mp,
.control = params->control,
.forward_all_clicks = params->forward_all_clicks,
.legacy_paste = params->legacy_paste,
.clipboard_autosync = params->clipboard_autosync,
.shortcut_mods = params->shortcut_mods,
};
sc_input_manager_init(&screen->im, &im_params);
// Reset the window size to trigger a SIZE_CHANGED event, to workaround // Reset the window size to trigger a SIZE_CHANGED event, to workaround
// HiDPI issues with some SDL renderers when several displays having // HiDPI issues with some SDL renderers when several displays having
// different HiDPI scaling are connected // different HiDPI scaling are connected
SDL_SetWindowSize(screen->window, window_size.width, window_size.height); SDL_SetWindowSize(screen->window, window_size.width, window_size.height);
screen_update_content_rect(screen); sc_screen_update_content_rect(screen);
if (params->fullscreen) { if (params->fullscreen) {
screen_switch_fullscreen(screen); sc_screen_switch_fullscreen(screen);
} }
#ifdef CONTINUOUS_RESIZING_WORKAROUND #ifdef CONTINUOUS_RESIZING_WORKAROUND
@ -486,9 +514,9 @@ screen_init(struct screen *screen, const struct screen_params *params) {
#endif #endif
static const struct sc_frame_sink_ops ops = { static const struct sc_frame_sink_ops ops = {
.open = screen_frame_sink_open, .open = sc_screen_frame_sink_open,
.close = screen_frame_sink_close, .close = sc_screen_frame_sink_close,
.push = screen_frame_sink_push, .push = sc_screen_frame_sink_push,
}; };
screen->frame_sink.ops = &ops; screen->frame_sink.ops = &ops;
@ -517,29 +545,29 @@ error_destroy_video_buffer:
} }
static void static void
screen_show_window(struct screen *screen) { sc_screen_show_window(struct sc_screen *screen) {
SDL_ShowWindow(screen->window); SDL_ShowWindow(screen->window);
} }
void void
screen_hide_window(struct screen *screen) { sc_screen_hide_window(struct sc_screen *screen) {
SDL_HideWindow(screen->window); SDL_HideWindow(screen->window);
} }
void void
screen_interrupt(struct screen *screen) { sc_screen_interrupt(struct sc_screen *screen) {
sc_video_buffer_stop(&screen->vb); sc_video_buffer_stop(&screen->vb);
fps_counter_interrupt(&screen->fps_counter); fps_counter_interrupt(&screen->fps_counter);
} }
void void
screen_join(struct screen *screen) { sc_screen_join(struct sc_screen *screen) {
sc_video_buffer_join(&screen->vb); sc_video_buffer_join(&screen->vb);
fps_counter_join(&screen->fps_counter); fps_counter_join(&screen->fps_counter);
} }
void void
screen_destroy(struct screen *screen) { sc_screen_destroy(struct sc_screen *screen) {
#ifndef NDEBUG #ifndef NDEBUG
assert(!screen->open); assert(!screen->open);
#endif #endif
@ -552,7 +580,7 @@ screen_destroy(struct screen *screen) {
} }
static void static void
resize_for_content(struct screen *screen, struct sc_size old_content_size, resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
struct sc_size new_content_size) { struct sc_size new_content_size) {
struct sc_size window_size = get_window_size(screen); struct sc_size window_size = get_window_size(screen);
struct sc_size target_size = { struct sc_size target_size = {
@ -566,7 +594,7 @@ resize_for_content(struct screen *screen, struct sc_size old_content_size,
} }
static void static void
set_content_size(struct screen *screen, struct sc_size new_content_size) { set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
if (!screen->fullscreen && !screen->maximized) { if (!screen->fullscreen && !screen->maximized) {
resize_for_content(screen, screen->content_size, new_content_size); resize_for_content(screen, screen->content_size, new_content_size);
} else if (!screen->resize_pending) { } else if (!screen->resize_pending) {
@ -580,7 +608,7 @@ set_content_size(struct screen *screen, struct sc_size new_content_size) {
} }
static void static void
apply_pending_resize(struct screen *screen) { apply_pending_resize(struct sc_screen *screen) {
assert(!screen->fullscreen); assert(!screen->fullscreen);
assert(!screen->maximized); assert(!screen->maximized);
if (screen->resize_pending) { if (screen->resize_pending) {
@ -591,7 +619,7 @@ apply_pending_resize(struct screen *screen) {
} }
void void
screen_set_rotation(struct screen *screen, unsigned rotation) { sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation) {
assert(rotation < 4); assert(rotation < 4);
if (rotation == screen->rotation) { if (rotation == screen->rotation) {
return; return;
@ -605,12 +633,12 @@ screen_set_rotation(struct screen *screen, unsigned rotation) {
screen->rotation = rotation; screen->rotation = rotation;
LOGI("Display rotation set to %u", rotation); LOGI("Display rotation set to %u", rotation);
screen_render(screen, true); sc_screen_render(screen, true);
} }
// recreate the texture and resize the window if the frame size has changed // recreate the texture and resize the window if the frame size has changed
static bool static bool
prepare_for_frame(struct screen *screen, struct sc_size new_frame_size) { prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
if (screen->frame_size.width != new_frame_size.width if (screen->frame_size.width != new_frame_size.width
|| screen->frame_size.height != new_frame_size.height) { || screen->frame_size.height != new_frame_size.height) {
// frame dimension changed, destroy texture // frame dimension changed, destroy texture
@ -622,7 +650,7 @@ prepare_for_frame(struct screen *screen, struct sc_size new_frame_size) {
get_rotated_size(new_frame_size, screen->rotation); get_rotated_size(new_frame_size, screen->rotation);
set_content_size(screen, new_content_size); set_content_size(screen, new_content_size);
screen_update_content_rect(screen); sc_screen_update_content_rect(screen);
LOGI("New texture: %" PRIu16 "x%" PRIu16, LOGI("New texture: %" PRIu16 "x%" PRIu16,
screen->frame_size.width, screen->frame_size.height); screen->frame_size.width, screen->frame_size.height);
@ -638,7 +666,7 @@ prepare_for_frame(struct screen *screen, struct sc_size new_frame_size) {
// write the frame into the texture // write the frame into the texture
static void static void
update_texture(struct screen *screen, const AVFrame *frame) { update_texture(struct sc_screen *screen, const AVFrame *frame) {
SDL_UpdateYUVTexture(screen->texture, NULL, SDL_UpdateYUVTexture(screen->texture, NULL,
frame->data[0], frame->linesize[0], frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1], frame->data[1], frame->linesize[1],
@ -652,7 +680,7 @@ update_texture(struct screen *screen, const AVFrame *frame) {
} }
static bool static bool
screen_update_frame(struct screen *screen) { sc_screen_update_frame(struct sc_screen *screen) {
av_frame_unref(screen->frame); av_frame_unref(screen->frame);
sc_video_buffer_consume(&screen->vb, screen->frame); sc_video_buffer_consume(&screen->vb, screen->frame);
AVFrame *frame = screen->frame; AVFrame *frame = screen->frame;
@ -665,12 +693,12 @@ screen_update_frame(struct screen *screen) {
} }
update_texture(screen, frame); update_texture(screen, frame);
screen_render(screen, false); sc_screen_render(screen, false);
return true; return true;
} }
void void
screen_switch_fullscreen(struct screen *screen) { sc_screen_switch_fullscreen(struct sc_screen *screen) {
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP; uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
if (SDL_SetWindowFullscreen(screen->window, new_mode)) { if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
LOGW("Could not switch fullscreen mode: %s", SDL_GetError()); LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
@ -683,11 +711,11 @@ screen_switch_fullscreen(struct screen *screen) {
} }
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed"); LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
screen_render(screen, true); sc_screen_render(screen, true);
} }
void void
screen_resize_to_fit(struct screen *screen) { sc_screen_resize_to_fit(struct sc_screen *screen) {
if (screen->fullscreen || screen->maximized) { if (screen->fullscreen || screen->maximized) {
return; return;
} }
@ -711,7 +739,7 @@ screen_resize_to_fit(struct screen *screen) {
} }
void void
screen_resize_to_pixel_perfect(struct screen *screen) { sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
if (screen->fullscreen) { if (screen->fullscreen) {
return; return;
} }
@ -727,16 +755,21 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
content_size.height); content_size.height);
} }
static inline bool
sc_screen_is_mouse_capture_key(SDL_Keycode key) {
return key == SDLK_LALT || key == SDLK_LGUI || key == SDLK_RGUI;
}
bool bool
screen_handle_event(struct screen *screen, SDL_Event *event) { sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
switch (event->type) { switch (event->type) {
case EVENT_NEW_FRAME: case EVENT_NEW_FRAME:
if (!screen->has_frame) { if (!screen->has_frame) {
screen->has_frame = true; screen->has_frame = true;
// this is the very first frame, show the window // this is the very first frame, show the window
screen_show_window(screen); sc_screen_show_window(screen);
} }
bool ok = screen_update_frame(screen); bool ok = sc_screen_update_frame(screen);
if (!ok) { if (!ok) {
LOGW("Frame update failed\n"); LOGW("Frame update failed\n");
} }
@ -748,10 +781,10 @@ screen_handle_event(struct screen *screen, SDL_Event *event) {
} }
switch (event->window.event) { switch (event->window.event) {
case SDL_WINDOWEVENT_EXPOSED: case SDL_WINDOWEVENT_EXPOSED:
screen_render(screen, true); sc_screen_render(screen, true);
break; break;
case SDL_WINDOWEVENT_SIZE_CHANGED: case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_render(screen, true); sc_screen_render(screen, true);
break; break;
case SDL_WINDOWEVENT_MAXIMIZED: case SDL_WINDOWEVENT_MAXIMIZED:
screen->maximized = true; screen->maximized = true;
@ -767,18 +800,78 @@ screen_handle_event(struct screen *screen, SDL_Event *event) {
} }
screen->maximized = false; screen->maximized = false;
apply_pending_resize(screen); apply_pending_resize(screen);
screen_render(screen, true); sc_screen_render(screen, true);
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
if (screen->im.mp->relative_mode) {
sc_screen_capture_mouse(screen, false);
}
break; break;
} }
return true; return true;
case SDL_KEYDOWN:
if (screen->im.mp->relative_mode) {
SDL_Keycode key = event->key.keysym.sym;
if (sc_screen_is_mouse_capture_key(key)) {
if (!screen->mouse_capture_key_pressed) {
screen->mouse_capture_key_pressed = key;
return true;
} else {
// Another mouse capture key has been pressed, cancel
// mouse (un)capture
screen->mouse_capture_key_pressed = 0;
// Do not return, the event must be forwarded to the
// input manager
}
}
}
break;
case SDL_KEYUP:
if (screen->im.mp->relative_mode) {
SDL_Keycode key = event->key.keysym.sym;
SDL_Keycode cap = screen->mouse_capture_key_pressed;
screen->mouse_capture_key_pressed = 0;
if (key == cap) {
// A mouse capture key has been pressed then released:
// toggle the capture mouse mode
sc_screen_capture_mouse(screen, !screen->mouse_captured);
return true;
}
// Do not return, the event must be forwarded to the input
// manager
}
break;
case SDL_MOUSEWHEEL:
case SDL_MOUSEMOTION:
case SDL_MOUSEBUTTONDOWN:
if (screen->im.mp->relative_mode && !screen->mouse_captured) {
// Do not forward to input manager, the mouse will be captured
// on SDL_MOUSEBUTTONUP
return true;
}
break;
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
if (screen->im.mp->relative_mode) {
// Touch events are not compatible with relative mode
// (coordinates are not relative)
return true;
}
break;
case SDL_MOUSEBUTTONUP:
if (screen->im.mp->relative_mode && !screen->mouse_captured) {
sc_screen_capture_mouse(screen, true);
return true;
}
} }
return false; return sc_input_manager_handle_event(&screen->im, event);
} }
struct sc_point struct sc_point
screen_convert_drawable_to_frame_coords(struct screen *screen, sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
int32_t x, int32_t y) { int32_t x, int32_t y) {
unsigned rotation = screen->rotation; unsigned rotation = screen->rotation;
assert(rotation < 4); assert(rotation < 4);
@ -814,14 +907,14 @@ screen_convert_drawable_to_frame_coords(struct screen *screen,
} }
struct sc_point struct sc_point
screen_convert_window_to_frame_coords(struct screen *screen, sc_screen_convert_window_to_frame_coords(struct sc_screen *screen,
int32_t x, int32_t y) { int32_t x, int32_t y) {
screen_hidpi_scale_coords(screen, &x, &y); sc_screen_hidpi_scale_coords(screen, &x, &y);
return screen_convert_drawable_to_frame_coords(screen, x, y); return sc_screen_convert_drawable_to_frame_coords(screen, x, y);
} }
void void
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y) { sc_screen_hidpi_scale_coords(struct sc_screen *screen, int32_t *x, int32_t *y) {
// take the HiDPI scaling (dw/ww and dh/wh) into account // take the HiDPI scaling (dw/ww and dh/wh) into account
int ww, wh, dw, dh; int ww, wh, dw, dh;
SDL_GetWindowSize(screen->window, &ww, &wh); SDL_GetWindowSize(screen->window, &ww, &wh);

View File

@ -7,19 +7,24 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include "controller.h"
#include "coords.h" #include "coords.h"
#include "fps_counter.h" #include "fps_counter.h"
#include "input_manager.h"
#include "opengl.h" #include "opengl.h"
#include "trait/key_processor.h"
#include "trait/frame_sink.h" #include "trait/frame_sink.h"
#include "trait/mouse_processor.h"
#include "video_buffer.h" #include "video_buffer.h"
struct screen { struct sc_screen {
struct sc_frame_sink frame_sink; // frame sink trait struct sc_frame_sink frame_sink; // frame sink trait
#ifndef NDEBUG #ifndef NDEBUG
bool open; // track the open/close state to assert correct behavior bool open; // track the open/close state to assert correct behavior
#endif #endif
struct sc_input_manager im;
struct sc_video_buffer vb; struct sc_video_buffer vb;
struct fps_counter fps_counter; struct fps_counter fps_counter;
@ -46,10 +51,25 @@ struct screen {
bool event_failed; // in case SDL_PushEvent() returned an error bool event_failed; // in case SDL_PushEvent() returned an error
bool mouse_captured; // only relevant in relative mouse mode
// To enable/disable mouse capture, a mouse capture key (LALT, LGUI or
// RGUI) must be pressed. This variable tracks the pressed capture key.
SDL_Keycode mouse_capture_key_pressed;
AVFrame *frame; AVFrame *frame;
}; };
struct screen_params { struct sc_screen_params {
struct sc_controller *controller;
struct sc_key_processor *kp;
struct sc_mouse_processor *mp;
bool control;
bool forward_all_clicks;
bool legacy_paste;
bool clipboard_autosync;
const struct sc_shortcut_mods *shortcut_mods;
const char *window_title; const char *window_title;
struct sc_size frame_size; struct sc_size frame_size;
bool always_on_top; bool always_on_top;
@ -71,65 +91,65 @@ struct screen_params {
// initialize screen, create window, renderer and texture (window is hidden) // initialize screen, create window, renderer and texture (window is hidden)
bool bool
screen_init(struct screen *screen, const struct screen_params *params); sc_screen_init(struct sc_screen *screen, const struct sc_screen_params *params);
// request to interrupt any inner thread // request to interrupt any inner thread
// must be called before screen_join() // must be called before screen_join()
void void
screen_interrupt(struct screen *screen); sc_screen_interrupt(struct sc_screen *screen);
// join any inner thread // join any inner thread
void void
screen_join(struct screen *screen); sc_screen_join(struct sc_screen *screen);
// destroy window, renderer and texture (if any) // destroy window, renderer and texture (if any)
void void
screen_destroy(struct screen *screen); sc_screen_destroy(struct sc_screen *screen);
// hide the window // hide the window
// //
// It is used to hide the window immediately on closing without waiting for // It is used to hide the window immediately on closing without waiting for
// screen_destroy() // screen_destroy()
void void
screen_hide_window(struct screen *screen); sc_screen_hide_window(struct sc_screen *screen);
// switch the fullscreen mode // switch the fullscreen mode
void void
screen_switch_fullscreen(struct screen *screen); sc_screen_switch_fullscreen(struct sc_screen *screen);
// resize window to optimal size (remove black borders) // resize window to optimal size (remove black borders)
void void
screen_resize_to_fit(struct screen *screen); sc_screen_resize_to_fit(struct sc_screen *screen);
// resize window to 1:1 (pixel-perfect) // resize window to 1:1 (pixel-perfect)
void void
screen_resize_to_pixel_perfect(struct screen *screen); sc_screen_resize_to_pixel_perfect(struct sc_screen *screen);
// set the display rotation (0, 1, 2 or 3, x90 degrees counterclockwise) // set the display rotation (0, 1, 2 or 3, x90 degrees counterclockwise)
void void
screen_set_rotation(struct screen *screen, unsigned rotation); sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation);
// react to SDL events // react to SDL events
bool bool
screen_handle_event(struct screen *screen, SDL_Event *event); sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event);
// convert point from window coordinates to frame coordinates // convert point from window coordinates to frame coordinates
// x and y are expressed in pixels // x and y are expressed in pixels
struct sc_point struct sc_point
screen_convert_window_to_frame_coords(struct screen *screen, sc_screen_convert_window_to_frame_coords(struct sc_screen *screen,
int32_t x, int32_t y); int32_t x, int32_t y);
// convert point from drawable coordinates to frame coordinates // convert point from drawable coordinates to frame coordinates
// x and y are expressed in pixels // x and y are expressed in pixels
struct sc_point struct sc_point
screen_convert_drawable_to_frame_coords(struct screen *screen, sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
int32_t x, int32_t y); int32_t x, int32_t y);
// Convert coordinates from window to drawable. // Convert coordinates from window to drawable.
// Events are expressed in window coordinates, but content is expressed in // Events are expressed in window coordinates, but content is expressed in
// drawable coordinates. They are the same if HiDPI scaling is 1, but differ // drawable coordinates. They are the same if HiDPI scaling is 1, but differ
// otherwise. // otherwise.
void void
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y); sc_screen_hidpi_scale_coords(struct sc_screen *screen, int32_t *x, int32_t *y);
#endif #endif

View File

@ -188,7 +188,6 @@ execute_server(struct sc_server *server,
} \ } \
cmd[count++] = p; \ cmd[count++] = p; \
} }
#define STRBOOL(v) (v ? "true" : "false")
ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level)); ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level));
ADD_PARAM("bit_rate=%" PRIu32, params->bit_rate); ADD_PARAM("bit_rate=%" PRIu32, params->bit_rate);
@ -204,23 +203,23 @@ execute_server(struct sc_server *server,
params->lock_video_orientation); params->lock_video_orientation);
} }
if (server->tunnel.forward) { if (server->tunnel.forward) {
ADD_PARAM("tunnel_forward=%s", STRBOOL(server->tunnel.forward)); ADD_PARAM("tunnel_forward=true");
} }
if (params->crop) { if (params->crop) {
ADD_PARAM("crop=%s", params->crop); ADD_PARAM("crop=%s", params->crop);
} }
if (!params->control) { if (!params->control) {
// By default, control is true // By default, control is true
ADD_PARAM("control=%s", STRBOOL(params->control)); ADD_PARAM("control=false");
} }
if (params->display_id) { if (params->display_id) {
ADD_PARAM("display_id=%" PRIu32, params->display_id); ADD_PARAM("display_id=%" PRIu32, params->display_id);
} }
if (params->show_touches) { if (params->show_touches) {
ADD_PARAM("show_touches=%s", STRBOOL(params->show_touches)); ADD_PARAM("show_touches=true");
} }
if (params->stay_awake) { if (params->stay_awake) {
ADD_PARAM("stay_awake=%s", STRBOOL(params->stay_awake)); ADD_PARAM("stay_awake=true");
} }
if (params->codec_options) { if (params->codec_options) {
ADD_PARAM("codec_options=%s", params->codec_options); ADD_PARAM("codec_options=%s", params->codec_options);
@ -229,11 +228,11 @@ execute_server(struct sc_server *server,
ADD_PARAM("encoder_name=%s", params->encoder_name); ADD_PARAM("encoder_name=%s", params->encoder_name);
} }
if (params->power_off_on_close) { if (params->power_off_on_close) {
ADD_PARAM("power_off_on_close=%s", STRBOOL(params->power_off_on_close)); ADD_PARAM("power_off_on_close=true");
} }
if (!params->clipboard_autosync) { if (!params->clipboard_autosync) {
// By default, clipboard_autosync is true // By default, clipboard_autosync is true
ADD_PARAM("clipboard_autosync=%s", STRBOOL(params->clipboard_autosync)); ADD_PARAM("clipboard_autosync=false");
} }
#undef ADD_PARAM #undef ADD_PARAM

View File

@ -1,7 +1,6 @@
#include "stream.h" #include "stream.h"
#include <assert.h> #include <assert.h>
#include <libavformat/avformat.h>
#include <libavutil/time.h> #include <libavutil/time.h>
#include <unistd.h> #include <unistd.h>
@ -192,7 +191,7 @@ static int
run_stream(void *data) { run_stream(void *data) {
struct stream *stream = data; struct stream *stream = data;
AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264); const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!codec) { if (!codec) {
LOGE("H.264 decoder not found"); LOGE("H.264 decoder not found");
goto end; goto end;

View File

@ -5,6 +5,7 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include "trait/packet_sink.h" #include "trait/packet_sink.h"

View File

@ -6,7 +6,7 @@
#include <assert.h> #include <assert.h>
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL_events.h> #include "input_events.h"
/** /**
* Key processor trait. * Key processor trait.
@ -29,20 +29,27 @@ struct sc_key_processor {
struct sc_key_processor_ops { struct sc_key_processor_ops {
/** /**
* Process the keyboard event * Process a keyboard event
* *
* The `sequence` number (if different from `SC_SEQUENCE_INVALID`) indicates * The `sequence` number (if different from `SC_SEQUENCE_INVALID`) indicates
* the acknowledgement number to wait for before injecting this event. * the acknowledgement number to wait for before injecting this event.
* This allows to ensure that the device clipboard is set before injecting * This allows to ensure that the device clipboard is set before injecting
* Ctrl+v on the device. * Ctrl+v on the device.
*
* This function is mandatory.
*/ */
void void
(*process_key)(struct sc_key_processor *kp, const SDL_KeyboardEvent *event, (*process_key)(struct sc_key_processor *kp,
uint64_t ack_to_wait); const struct sc_key_event *event, uint64_t ack_to_wait);
/**
* Process an input text
*
* This function is optional.
*/
void void
(*process_text)(struct sc_key_processor *kp, (*process_text)(struct sc_key_processor *kp,
const SDL_TextInputEvent *event); const struct sc_text_event *event);
}; };
#endif #endif

View File

@ -6,7 +6,7 @@
#include <assert.h> #include <assert.h>
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL_events.h> #include "input_events.h"
/** /**
* Mouse processor trait. * Mouse processor trait.
@ -16,24 +16,51 @@
*/ */
struct sc_mouse_processor { struct sc_mouse_processor {
const struct sc_mouse_processor_ops *ops; const struct sc_mouse_processor_ops *ops;
/**
* If set, the mouse processor works in relative mode (the absolute
* position is irrelevant). In particular, it indicates that the mouse
* pointer must be "captured" by the UI.
*/
bool relative_mode;
}; };
struct sc_mouse_processor_ops { struct sc_mouse_processor_ops {
/**
* Process a mouse motion event
*
* This function is mandatory.
*/
void void
(*process_mouse_motion)(struct sc_mouse_processor *mp, (*process_mouse_motion)(struct sc_mouse_processor *mp,
const SDL_MouseMotionEvent *event); const struct sc_mouse_motion_event *event);
/**
* Process a mouse click event
*
* This function is mandatory.
*/
void
(*process_mouse_click)(struct sc_mouse_processor *mp,
const struct sc_mouse_click_event *event);
/**
* Process a mouse scroll event
*
* This function is optional.
*/
void
(*process_mouse_scroll)(struct sc_mouse_processor *mp,
const struct sc_mouse_scroll_event *event);
/**
* Process a touch event
*
* This function is optional.
*/
void void
(*process_touch)(struct sc_mouse_processor *mp, (*process_touch)(struct sc_mouse_processor *mp,
const SDL_TouchFingerEvent *event); const struct sc_touch_event *event);
void
(*process_mouse_button)(struct sc_mouse_processor *mp,
const SDL_MouseButtonEvent *event);
void
(*process_mouse_wheel)(struct sc_mouse_processor *mp,
const SDL_MouseWheelEvent *event);
}; };
#endif #endif

View File

@ -83,6 +83,7 @@ unwrap(sc_socket socket) {
#endif #endif
} }
#ifndef HAVE_SOCK_CLOEXEC // avoid unused-function warning
static inline bool static inline bool
sc_raw_socket_close(sc_raw_socket raw_sock) { sc_raw_socket_close(sc_raw_socket raw_sock) {
#ifndef _WIN32 #ifndef _WIN32
@ -91,6 +92,7 @@ sc_raw_socket_close(sc_raw_socket raw_sock) {
return !closesocket(raw_sock); return !closesocket(raw_sock);
#endif #endif
} }
#endif
#ifndef HAVE_SOCK_CLOEXEC #ifndef HAVE_SOCK_CLOEXEC
// If SOCK_CLOEXEC does not exist, the flag must be set manually once the // If SOCK_CLOEXEC does not exist, the flag must be set manually once the

View File

@ -3,13 +3,14 @@
#include "common.h" #include "common.h"
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include "coords.h" #include "coords.h"
#include "trait/frame_sink.h" #include "trait/frame_sink.h"
#include "video_buffer.h" #include "video_buffer.h"
#include "util/tick.h" #include "util/tick.h"
#include <libavformat/avformat.h>
struct sc_v4l2_sink { struct sc_v4l2_sink {
struct sc_frame_sink frame_sink; // frame sink trait struct sc_frame_sink frame_sink; // frame sink trait

View File

@ -11,6 +11,7 @@ static void test_get_ip_single_line() {
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
assert(ip); assert(ip);
assert(!strcmp(ip, "192.168.12.34")); assert(!strcmp(ip, "192.168.12.34"));
free(ip);
} }
static void test_get_ip_single_line_without_eol() { static void test_get_ip_single_line_without_eol() {
@ -20,6 +21,7 @@ static void test_get_ip_single_line_without_eol() {
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
assert(ip); assert(ip);
assert(!strcmp(ip, "192.168.12.34")); assert(!strcmp(ip, "192.168.12.34"));
free(ip);
} }
static void test_get_ip_single_line_with_trailing_space() { static void test_get_ip_single_line_with_trailing_space() {
@ -29,6 +31,7 @@ static void test_get_ip_single_line_with_trailing_space() {
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
assert(ip); assert(ip);
assert(!strcmp(ip, "192.168.12.34")); assert(!strcmp(ip, "192.168.12.34"));
free(ip);
} }
static void test_get_ip_multiline_first_ok() { static void test_get_ip_multiline_first_ok() {
@ -40,6 +43,7 @@ static void test_get_ip_multiline_first_ok() {
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
assert(ip); assert(ip);
assert(!strcmp(ip, "192.168.1.2")); assert(!strcmp(ip, "192.168.1.2"));
free(ip);
} }
static void test_get_ip_multiline_second_ok() { static void test_get_ip_multiline_second_ok() {
@ -51,6 +55,7 @@ static void test_get_ip_multiline_second_ok() {
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
assert(ip); assert(ip);
assert(!strcmp(ip, "192.168.1.3")); assert(!strcmp(ip, "192.168.1.3"));
free(ip);
} }
static void test_get_ip_no_wlan() { static void test_get_ip_no_wlan() {

View File

@ -129,25 +129,26 @@ static void test_parse_shortcut_mods(void) {
ok = sc_parse_shortcut_mods("lctrl", &mods); ok = sc_parse_shortcut_mods("lctrl", &mods);
assert(ok); assert(ok);
assert(mods.count == 1); assert(mods.count == 1);
assert(mods.data[0] == SC_MOD_LCTRL); assert(mods.data[0] == SC_SHORTCUT_MOD_LCTRL);
ok = sc_parse_shortcut_mods("lctrl+lalt", &mods); ok = sc_parse_shortcut_mods("lctrl+lalt", &mods);
assert(ok); assert(ok);
assert(mods.count == 1); assert(mods.count == 1);
assert(mods.data[0] == (SC_MOD_LCTRL | SC_MOD_LALT)); assert(mods.data[0] == (SC_SHORTCUT_MOD_LCTRL | SC_SHORTCUT_MOD_LALT));
ok = sc_parse_shortcut_mods("rctrl,lalt", &mods); ok = sc_parse_shortcut_mods("rctrl,lalt", &mods);
assert(ok); assert(ok);
assert(mods.count == 2); assert(mods.count == 2);
assert(mods.data[0] == SC_MOD_RCTRL); assert(mods.data[0] == SC_SHORTCUT_MOD_RCTRL);
assert(mods.data[1] == SC_MOD_LALT); assert(mods.data[1] == SC_SHORTCUT_MOD_LALT);
ok = sc_parse_shortcut_mods("lsuper,rsuper+lalt,lctrl+rctrl+ralt", &mods); ok = sc_parse_shortcut_mods("lsuper,rsuper+lalt,lctrl+rctrl+ralt", &mods);
assert(ok); assert(ok);
assert(mods.count == 3); assert(mods.count == 3);
assert(mods.data[0] == SC_MOD_LSUPER); assert(mods.data[0] == SC_SHORTCUT_MOD_LSUPER);
assert(mods.data[1] == (SC_MOD_RSUPER | SC_MOD_LALT)); assert(mods.data[1] == (SC_SHORTCUT_MOD_RSUPER | SC_SHORTCUT_MOD_LALT));
assert(mods.data[2] == (SC_MOD_LCTRL | SC_MOD_RCTRL | SC_MOD_RALT)); assert(mods.data[2] == (SC_SHORTCUT_MOD_LCTRL | SC_SHORTCUT_MOD_RCTRL |
SC_SHORTCUT_MOD_RALT));
ok = sc_parse_shortcut_mods("", &mods); ok = sc_parse_shortcut_mods("", &mods);
assert(!ok); assert(!ok);

View File

@ -6,7 +6,7 @@
#include "control_msg.h" #include "control_msg.h"
static void test_serialize_inject_keycode(void) { static void test_serialize_inject_keycode(void) {
struct control_msg msg = { struct sc_control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_KEYCODE, .type = CONTROL_MSG_TYPE_INJECT_KEYCODE,
.inject_keycode = { .inject_keycode = {
.action = AKEY_EVENT_ACTION_UP, .action = AKEY_EVENT_ACTION_UP,
@ -17,7 +17,7 @@ static void test_serialize_inject_keycode(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 14); assert(size == 14);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -31,7 +31,7 @@ static void test_serialize_inject_keycode(void) {
} }
static void test_serialize_inject_text(void) { static void test_serialize_inject_text(void) {
struct control_msg msg = { struct sc_control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_TEXT, .type = CONTROL_MSG_TYPE_INJECT_TEXT,
.inject_text = { .inject_text = {
.text = "hello, world!", .text = "hello, world!",
@ -39,7 +39,7 @@ static void test_serialize_inject_text(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 18); assert(size == 18);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -51,7 +51,7 @@ static void test_serialize_inject_text(void) {
} }
static void test_serialize_inject_text_long(void) { static void test_serialize_inject_text_long(void) {
struct control_msg msg; struct sc_control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
char text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH + 1]; char text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH + 1];
memset(text, 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); memset(text, 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
@ -59,7 +59,7 @@ static void test_serialize_inject_text_long(void) {
msg.inject_text.text = text; msg.inject_text.text = text;
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); assert(size == 5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
unsigned char expected[5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH]; unsigned char expected[5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH];
@ -74,7 +74,7 @@ static void test_serialize_inject_text_long(void) {
} }
static void test_serialize_inject_touch_event(void) { static void test_serialize_inject_touch_event(void) {
struct control_msg msg = { struct sc_control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, .type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
.inject_touch_event = { .inject_touch_event = {
.action = AMOTION_EVENT_ACTION_DOWN, .action = AMOTION_EVENT_ACTION_DOWN,
@ -95,7 +95,7 @@ static void test_serialize_inject_touch_event(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 28); assert(size == 28);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -111,7 +111,7 @@ static void test_serialize_inject_touch_event(void) {
} }
static void test_serialize_inject_scroll_event(void) { static void test_serialize_inject_scroll_event(void) {
struct control_msg msg = { struct sc_control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, .type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
.inject_scroll_event = { .inject_scroll_event = {
.position = { .position = {
@ -126,12 +126,13 @@ static void test_serialize_inject_scroll_event(void) {
}, },
.hscroll = 1, .hscroll = 1,
.vscroll = -1, .vscroll = -1,
.buttons = 1,
}, },
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 21); assert(size == 25);
const unsigned char expected[] = { const unsigned char expected[] = {
CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
@ -139,12 +140,13 @@ static void test_serialize_inject_scroll_event(void) {
0x04, 0x38, 0x07, 0x80, // 1080 1920 0x04, 0x38, 0x07, 0x80, // 1080 1920
0x00, 0x00, 0x00, 0x01, // 1 0x00, 0x00, 0x00, 0x01, // 1
0xFF, 0xFF, 0xFF, 0xFF, // -1 0xFF, 0xFF, 0xFF, 0xFF, // -1
0x00, 0x00, 0x00, 0x01, // 1
}; };
assert(!memcmp(buf, expected, sizeof(expected))); assert(!memcmp(buf, expected, sizeof(expected)));
} }
static void test_serialize_back_or_screen_on(void) { static void test_serialize_back_or_screen_on(void) {
struct control_msg msg = { struct sc_control_msg msg = {
.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON, .type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
.back_or_screen_on = { .back_or_screen_on = {
.action = AKEY_EVENT_ACTION_UP, .action = AKEY_EVENT_ACTION_UP,
@ -152,7 +154,7 @@ static void test_serialize_back_or_screen_on(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 2); assert(size == 2);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -163,12 +165,12 @@ static void test_serialize_back_or_screen_on(void) {
} }
static void test_serialize_expand_notification_panel(void) { static void test_serialize_expand_notification_panel(void) {
struct control_msg msg = { struct sc_control_msg msg = {
.type = CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL, .type = CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 1); assert(size == 1);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -178,12 +180,12 @@ static void test_serialize_expand_notification_panel(void) {
} }
static void test_serialize_expand_settings_panel(void) { static void test_serialize_expand_settings_panel(void) {
struct control_msg msg = { struct sc_control_msg msg = {
.type = CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL, .type = CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 1); assert(size == 1);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -193,12 +195,12 @@ static void test_serialize_expand_settings_panel(void) {
} }
static void test_serialize_collapse_panels(void) { static void test_serialize_collapse_panels(void) {
struct control_msg msg = { struct sc_control_msg msg = {
.type = CONTROL_MSG_TYPE_COLLAPSE_PANELS, .type = CONTROL_MSG_TYPE_COLLAPSE_PANELS,
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 1); assert(size == 1);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -208,7 +210,7 @@ static void test_serialize_collapse_panels(void) {
} }
static void test_serialize_get_clipboard(void) { static void test_serialize_get_clipboard(void) {
struct control_msg msg = { struct sc_control_msg msg = {
.type = CONTROL_MSG_TYPE_GET_CLIPBOARD, .type = CONTROL_MSG_TYPE_GET_CLIPBOARD,
.get_clipboard = { .get_clipboard = {
.copy_key = GET_CLIPBOARD_COPY_KEY_COPY, .copy_key = GET_CLIPBOARD_COPY_KEY_COPY,
@ -216,7 +218,7 @@ static void test_serialize_get_clipboard(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 2); assert(size == 2);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -227,7 +229,7 @@ static void test_serialize_get_clipboard(void) {
} }
static void test_serialize_set_clipboard(void) { static void test_serialize_set_clipboard(void) {
struct control_msg msg = { struct sc_control_msg msg = {
.type = CONTROL_MSG_TYPE_SET_CLIPBOARD, .type = CONTROL_MSG_TYPE_SET_CLIPBOARD,
.set_clipboard = { .set_clipboard = {
.sequence = UINT64_C(0x0102030405060708), .sequence = UINT64_C(0x0102030405060708),
@ -237,7 +239,7 @@ static void test_serialize_set_clipboard(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 27); assert(size == 27);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -251,7 +253,7 @@ static void test_serialize_set_clipboard(void) {
} }
static void test_serialize_set_clipboard_long(void) { static void test_serialize_set_clipboard_long(void) {
struct control_msg msg = { struct sc_control_msg msg = {
.type = CONTROL_MSG_TYPE_SET_CLIPBOARD, .type = CONTROL_MSG_TYPE_SET_CLIPBOARD,
.set_clipboard = { .set_clipboard = {
.sequence = UINT64_C(0x0102030405060708), .sequence = UINT64_C(0x0102030405060708),
@ -266,7 +268,7 @@ static void test_serialize_set_clipboard_long(void) {
msg.set_clipboard.text = text; msg.set_clipboard.text = text;
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == CONTROL_MSG_MAX_SIZE); assert(size == CONTROL_MSG_MAX_SIZE);
unsigned char expected[CONTROL_MSG_MAX_SIZE] = { unsigned char expected[CONTROL_MSG_MAX_SIZE] = {
@ -285,7 +287,7 @@ static void test_serialize_set_clipboard_long(void) {
} }
static void test_serialize_set_screen_power_mode(void) { static void test_serialize_set_screen_power_mode(void) {
struct control_msg msg = { struct sc_control_msg msg = {
.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE, .type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
.set_screen_power_mode = { .set_screen_power_mode = {
.mode = SCREEN_POWER_MODE_NORMAL, .mode = SCREEN_POWER_MODE_NORMAL,
@ -293,7 +295,7 @@ static void test_serialize_set_screen_power_mode(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 2); assert(size == 2);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -304,12 +306,12 @@ static void test_serialize_set_screen_power_mode(void) {
} }
static void test_serialize_rotate_device(void) { static void test_serialize_rotate_device(void) {
struct control_msg msg = { struct sc_control_msg msg = {
.type = CONTROL_MSG_TYPE_ROTATE_DEVICE, .type = CONTROL_MSG_TYPE_ROTATE_DEVICE,
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 1); assert(size == 1);
const unsigned char expected[] = { const unsigned char expected[] = {

View File

@ -16,6 +16,8 @@ cpu = 'i686'
endian = 'little' endian = 'little'
[properties] [properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win32-shared' ffmpeg_avcodec = 'avcodec-58'
prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win32-dev' ffmpeg_avformat = 'avformat-58'
ffmpeg_avutil = 'avutil-56'
prebuilt_ffmpeg = 'ffmpeg-4.3.1-win32-shared'
prebuilt_sdl2 = 'SDL2-2.0.18/i686-w64-mingw32' prebuilt_sdl2 = 'SDL2-2.0.18/i686-w64-mingw32'

View File

@ -16,6 +16,8 @@ cpu = 'x86_64'
endian = 'little' endian = 'little'
[properties] [properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win64-shared' ffmpeg_avcodec = 'avcodec-59'
prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win64-dev' ffmpeg_avformat = 'avformat-59'
ffmpeg_avutil = 'avutil-57'
prebuilt_ffmpeg = 'ffmpeg-5.0-full_build-shared'
prebuilt_sdl2 = 'SDL2-2.0.18/x86_64-w64-mingw32' prebuilt_sdl2 = 'SDL2-2.0.18/x86_64-w64-mingw32'

View File

@ -1,33 +1,26 @@
.PHONY: prepare-win32 prepare-win64 \ .PHONY: prepare-win32 prepare-win64 \
prepare-ffmpeg-shared-win32 \ prepare-ffmpeg-win32 \
prepare-ffmpeg-dev-win32 \ prepare-ffmpeg-win64 \
prepare-ffmpeg-shared-win64 \
prepare-ffmpeg-dev-win64 \
prepare-sdl2 \ prepare-sdl2 \
prepare-adb prepare-adb
prepare-win32: prepare-sdl2 prepare-ffmpeg-shared-win32 prepare-ffmpeg-dev-win32 prepare-adb prepare-win32: prepare-sdl2 prepare-ffmpeg-win32 prepare-adb
prepare-win64: prepare-sdl2 prepare-ffmpeg-shared-win64 prepare-ffmpeg-dev-win64 prepare-adb prepare-win64: prepare-sdl2 prepare-ffmpeg-win64 prepare-adb
prepare-ffmpeg-shared-win32: # Use old FFmpeg version for win32, there are no new prebuilts
prepare-ffmpeg-win32:
@./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win32-shared.zip \ @./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win32-shared.zip \
357af9901a456f4dcbacd107e83a934d344c9cb07ddad8aaf80612eeab7d26d2 \ 357af9901a456f4dcbacd107e83a934d344c9cb07ddad8aaf80612eeab7d26d2 \
ffmpeg-4.3.1-win32-shared ffmpeg-4.3.1-win32-shared
prepare-ffmpeg-dev-win32:
@./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win32-dev.zip \ @./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win32-dev.zip \
230efb08e9bcf225bd474da29676c70e591fc94d8790a740ca801408fddcb78b \ 230efb08e9bcf225bd474da29676c70e591fc94d8790a740ca801408fddcb78b \
ffmpeg-4.3.1-win32-dev ffmpeg-4.3.1-win32-dev
ln -sf ../ffmpeg-4.3.1-win32-dev/include ffmpeg-4.3.1-win32-shared/
prepare-ffmpeg-shared-win64: prepare-ffmpeg-win64:
@./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win64-shared.zip \ @./prepare-dep https://github.com/GyanD/codexffmpeg/releases/download/5.0/ffmpeg-5.0-full_build-shared.7z \
dd29b7f92f48dead4dd940492c7509138c0f99db445076d0a597007298a79940 \ e5900f6cecd4c438d398bd2fc308736c10b857cd8dd61c11bcfb05bff5d1211a \
ffmpeg-4.3.1-win64-shared ffmpeg-5.0-full_build-shared
prepare-ffmpeg-dev-win64:
@./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win64-dev.zip \
2e8038242cf8e1bd095c2978f196ff0462b122cc6ef7e74626a6af15459d8b81 \
ffmpeg-4.3.1-win64-dev
prepare-sdl2: prepare-sdl2:
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.18-mingw.tar.gz \ @./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.18-mingw.tar.gz \

View File

@ -34,6 +34,9 @@ extract() {
elif [[ "$file" == *.tar.gz ]] elif [[ "$file" == *.tar.gz ]]
then then
tar xf "$file" tar xf "$file"
elif [[ "$file" == *.7z ]]
then
7z x "$file"
else else
echo "Unsupported file: $file" echo "Unsupported file: $file"
return 1 return 1

View File

@ -110,11 +110,11 @@ dist-win64: build-server build-win64
cp data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)" cp data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)"
cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)" cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)"
cp data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)" cp data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/avutil-57.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/avcodec-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/avformat-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/swscale-5.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/swscale-6.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"

View File

@ -71,12 +71,13 @@ public final class ControlMessage {
return msg; return msg;
} }
public static ControlMessage createInjectScrollEvent(Position position, int hScroll, int vScroll) { public static ControlMessage createInjectScrollEvent(Position position, int hScroll, int vScroll, int buttons) {
ControlMessage msg = new ControlMessage(); ControlMessage msg = new ControlMessage();
msg.type = TYPE_INJECT_SCROLL_EVENT; msg.type = TYPE_INJECT_SCROLL_EVENT;
msg.position = position; msg.position = position;
msg.hScroll = hScroll; msg.hScroll = hScroll;
msg.vScroll = vScroll; msg.vScroll = vScroll;
msg.buttons = buttons;
return msg; return msg;
} }

View File

@ -10,7 +10,7 @@ public class ControlMessageReader {
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13; static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13;
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27; static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20; static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 24;
static final int BACK_OR_SCREEN_ON_LENGTH = 1; static final int BACK_OR_SCREEN_ON_LENGTH = 1;
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
static final int GET_CLIPBOARD_LENGTH = 1; static final int GET_CLIPBOARD_LENGTH = 1;
@ -154,7 +154,8 @@ public class ControlMessageReader {
Position position = readPosition(buffer); Position position = readPosition(buffer);
int hScroll = buffer.getInt(); int hScroll = buffer.getInt();
int vScroll = buffer.getInt(); int vScroll = buffer.getInt();
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll); int buttons = buffer.getInt();
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll, buttons);
} }
private ControlMessage parseBackOrScreenOnEvent() { private ControlMessage parseBackOrScreenOnEvent() {

View File

@ -98,7 +98,7 @@ public class Controller {
break; break;
case ControlMessage.TYPE_INJECT_SCROLL_EVENT: case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
if (device.supportsInputEvents()) { if (device.supportsInputEvents()) {
injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll()); injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll(), msg.getButtons());
} }
break; break;
case ControlMessage.TYPE_BACK_OR_SCREEN_ON: case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
@ -221,7 +221,7 @@ public class Controller {
return device.injectEvent(event, Device.INJECT_MODE_ASYNC); return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
} }
private boolean injectScroll(Position position, int hScroll, int vScroll) { private boolean injectScroll(Position position, int hScroll, int vScroll, int buttons) {
long now = SystemClock.uptimeMillis(); long now = SystemClock.uptimeMillis();
Point point = device.getPhysicalPoint(position); Point point = device.getPhysicalPoint(position);
if (point == null) { if (point == null) {
@ -239,7 +239,7 @@ public class Controller {
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll); coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
MotionEvent event = MotionEvent MotionEvent event = MotionEvent
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEFAULT_DEVICE_ID, 0, .obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0,
InputDevice.SOURCE_MOUSE, 0); InputDevice.SOURCE_MOUSE, 0);
return device.injectEvent(event, Device.INJECT_MODE_ASYNC); return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
} }

View File

@ -30,11 +30,11 @@ public class ScreenEncoder implements Device.RotationListener {
private final AtomicBoolean rotationChanged = new AtomicBoolean(); private final AtomicBoolean rotationChanged = new AtomicBoolean();
private final ByteBuffer headerBuffer = ByteBuffer.allocate(12); private final ByteBuffer headerBuffer = ByteBuffer.allocate(12);
private String encoderName; private final String encoderName;
private List<CodecOption> codecOptions; private final List<CodecOption> codecOptions;
private int bitRate; private final int bitRate;
private int maxFps; private final int maxFps;
private boolean sendFrameMeta; private final boolean sendFrameMeta;
private long ptsOrigin; private long ptsOrigin;
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List<CodecOption> codecOptions, String encoderName) { public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List<CodecOption> codecOptions, String encoderName) {

View File

@ -128,6 +128,7 @@ public class ControlMessageReaderTest {
dos.writeShort(1920); dos.writeShort(1920);
dos.writeInt(1); dos.writeInt(1);
dos.writeInt(-1); dos.writeInt(-1);
dos.writeInt(1);
byte[] packet = bos.toByteArray(); byte[] packet = bos.toByteArray();
@ -144,6 +145,7 @@ public class ControlMessageReaderTest {
Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight()); Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight());
Assert.assertEquals(1, event.getHScroll()); Assert.assertEquals(1, event.getHScroll());
Assert.assertEquals(-1, event.getVScroll()); Assert.assertEquals(-1, event.getVScroll());
Assert.assertEquals(1, event.getButtons());
} }
@Test @Test