Compare commits

...

145 Commits

Author SHA1 Message Date
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
826ddf1a6e Document HID keyboard events 2021-12-28 12:23:03 +01:00
6261bb0b5a Ignore display bounds on resize-to-fit
The "resize to fit" feature (MOD+w or double-click on black borders)
computed the "optimal size" using the same function computing the
initial window size on start.

However, on "resize to fit", only the black borders must be removed (the
content size must be preserved), so the display bounds must not be
considered.
2021-12-20 19:24:28 +01:00
2f091beeaa Simplify sc_size assignment
Assign the whole struct instead of each field separately.
2021-12-20 19:24:28 +01:00
720c3064df Upgrade SDL (2.0.18) for Windows
Include the latest version of SDL in Windows releases.
2021-12-20 19:16:28 +01:00
ad11c5babb Set DPI awareness for Windows
Add a windows manifest to set the DPI awareness by default:
<https://docs.microsoft.com/en-us/windows/win32/hidpi/setting-the-default-dpi-awareness-for-a-process>

Refs #40 <https://github.com/Genymobile/scrcpy/issues/40>
Fixes #2865 <https://github.com/Genymobile/scrcpy/issues/2865>
2021-12-20 19:13:02 +01:00
feb250a973 Fix typos reported by codespell 2021-12-15 18:27:45 +01:00
d049671908 Fix adb server hang
Since commit 0426708544, the server is run
in a dedicated thread. For SDL, many signals, including SIGINT and
SIGTERM, are masked for new threads. As a result, if the adb server is
not already running, adb commands invoked by scrcpy will start an adb
server that ignores those signals and cannot be terminated at system
shutdown.

Fixes #2873 <https://github.com/Genymobile/scrcpy/issues/2873>
PR #2870 <https://github.com/Genymobile/scrcpy/pull/2870>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-12-11 19:09:11 +01:00
0685c491cd Improve crossbuild configuration
Use meson native features to detect crossbuild, and remove the
user-provided option crossbuild_windows.
2021-12-10 19:50:58 +01:00
892cfe943e Add script to bump version
The version must now be bumped at 4 different places. Add a script to
bump automatically:

    ./bump_version 1.23.4
2021-12-10 19:50:17 +01:00
29570ee819 Add metadata to scrcpy.exe for Windows
Refs <https://stackoverflow.com/a/708382/1987178>
2021-12-09 23:38:13 +01:00
cfcbc2ac21 Add icon to scrcpy.exe
The icon will be associated to scrcpy.exe in the Windows explorer.

The .ico was created using imagemagick:

    convert icon.png icon.ico

It is included as a binary for simplicity.

Refs #2815 <https://github.com/Genymobile/scrcpy/issues/2815>
2021-12-09 23:38:13 +01:00
2cb4e04209 Update copyright date to 2021 in manpage
December, it's time to update to 2021!
2021-12-09 23:37:40 +01:00
878ffffc36 Update environment variables section in manpage
Use the same content as the section printed with --help.
2021-12-09 21:47:05 +01:00
f0361fc8b3 Add environment variables in help
Print the list of environment variables used by scrcpy in --help.
2021-12-09 21:45:39 +01:00
b5d4ec61fc Move newline generation in help
If we removed the shortcuts intro, we would not need the additional '\n'
of the section title, so it should be printed along with the shortcuts
intro.
2021-12-09 21:43:54 +01:00
3ada5c51bc Rename scrcpy threads
Prefix the name of threads by "scrcpy-". This improves readability in
the output of `top -H` for example.

Limit the thread names to 16 bytes, because it is limited on some
platforms.
2021-12-09 21:32:11 +01:00
09c55b0f93 Set "low delay" decoder flag
I don't really know the concrete benefits, but scrcpy definitely wants
low delay decoding.

Suggested-by: François Cartegnie <fcvlcdev@free.fr>
2021-12-08 23:53:54 +01:00
682a691173 Use timers with microsecond precision
SDL only provides millisecond precision. Use system timers to get a
better precision.
2021-12-08 23:45:45 +01:00
ddb9396743 Interrupt and close sockets on server stop
The sockets were never interrupted or closed by the client since recent
changes to run the server from a dedicated thread (see commit
0426708544).

As a side effect, the server could never terminate properly (it was
waiting on socket blocking calls), so it was always killed by the client
after the WATCHDOG_DELAY.

Interrupt the sockets on stop to give the servera chance to terminate
property, then close them.
2021-12-08 23:44:23 +01:00
cabcbc2b15 Do not create control socket if no control
If --no-control is enabled, then it is not necessary to create a second
communication socket between the client and the server.

This also facilitates the use of the server alone (without the client)
to receive only the raw video stream.
2021-12-08 23:41:38 +01:00
80fe12a95f Require libavcodec >= 57.37
In ffmpeg/doc/APIchanges:

> 2016-04-21 - 7fc329e - lavc 57.37.100 - avcodec.h
>   Add a new audio/video encoding and decoding API with decoupled input
>   and output -- avcodec_send_packet(), avcodec_receive_frame(),
>   avcodec_send_frame() and avcodec_receive_packet().

Refs de9b79ec2d

Refs #2862 <https://github.com/Genymobile/scrcpy/issues/2862>
2021-12-07 00:04:35 +01:00
099c546580 Require libavformat >= 57.33
In ffmpeg/doc/APIchanges:

> 2016-04-11 - 6f69f7a / 9200514 - lavf 57.33.100 / 57.5.0 - avformat.h
>   Add AVStream.codecpar, deprecate AVStream.codec.

Refs 5d9e96dc4e

Refs #2862 <https://github.com/Genymobile/scrcpy/issues/2862>
2021-12-07 00:04:27 +01:00
dca2c5f94f Require SDL >= 2.0.5
Icon loading uses SDL_CreateRGBSurfaceWithFormatFrom(), available since
SDL 2.0.5 (in 2016).

Refs #2862 <https://github.com/Genymobile/scrcpy/issues/2862>
2021-12-06 23:49:55 +01:00
90cf956f57 Remove spurious ';' 2021-12-04 09:29:30 +01:00
36c8778d2d Add missing comma
Thank you clang:

    ../app/src/control_msg.c:45:5: warning: suspicious concatenation of
    string literals in an array initialization; did you mean to separate
    the elements with a comma? [-Wstring-concatenation]
        "hover-exit",
        ^
2021-12-04 09:25:58 +01:00
ae90ef22db Add a unit test for clipboard text length
This would have catched the possible memcpy() overflow fixed by the
previous commit.

Refs #2859 <https://github.com/Genymobile/scrcpy/pull/2859>
2021-12-04 09:22:35 +01:00
d80bc25eba Fix overflow in memcpy
In function ‘memcpy’,
    inlined from ‘control_msg_serialize.constprop’ at ../app/src/control_msg.c:77:5,
    inlined from ‘run_controller’ at ../app/src/controller.c:69:12:
        /usr/include/x86_64-linux-gnu/bits/string_fortified.h:34:10:
        warning: ‘__builtin___memcpy_chk’ writing 262138 bytes into a region
        of size 262130 overflows the destination [-Wstringop-overflow=]
   return __builtin___memcpy_chk (__dest, __src, __len, __bos0 (__dest));

Refs 901d837165
PR #2859 <https://github.com/Genymobile/scrcpy/pull/2859>

Signed-off-by: Yu-Chen Lin <npes87184@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-12-04 09:18:55 +01:00
daa06abd34 Fix comment in control message serialization
Refs 245999aec4

Signed-off-by: Yu-Chen Lin <npes87184@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-12-04 09:18:43 +01:00
94702a4309 Fix memset() size in tests
The memset() size was 1 byte too long. It was harmless because the last
'a' was overwritten by '\0`.
2021-12-04 09:12:20 +01:00
65fbec9643 Mention SCRCPY_ICON_PATH env var in manpage 2021-12-03 21:47:31 +01:00
a208400133 Remove useless intermediate variable
Now that PLATFORM is correctly used in the if-condition,
PLATFORM_VERSION is useless.

PR #2850 <https://github.com/Genymobile/scrcpy/pull/2850>
2021-12-03 21:47:00 +01:00
ab00210b37 Fix script to build without gradle
The PLATFORM variable is assigned either from $ANDROID_PLATFORM or gets
a default value (currently $PLATFORM_VERSION).

The check to use either dx (SDK < 31) or d8 (SDK >= 31) must be based on
the actual $PLATFORM, not the default $PLATFORM_VERSION.

Refs 52138fd921
Refs <57d30780dd/debian/patches/0002-Workaround-broken-script.patch>

PR #2850 <https://github.com/Genymobile/scrcpy/pull/2850>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-12-03 21:46:49 +01:00
64a04b8d4a Fix process execution on Windows 7
According to this bug report on Firefox:
<https://bugzilla.mozilla.org/show_bug.cgi?id=1460995>

> CreateProcess fails with ERROR_NO_SYSTEM_RESOURCES on Windows 7. It
> looks like the reason why is because PROC_THREAD_ATTRIBUTE_HANDLE_LIST
> doesn't like console handles.

To avoid the problem, do not pass console handles to
PROC_THREAD_ATTRIBUTE_HANDLE_LIST.

Refs #2783 <https://github.com/Genymobile/scrcpy/pull/2783>
Refs f801d8b312
Fixes #2838 <https://github.com/Genymobile/scrcpy/issues/2838>
PR #2840 <https://github.com/Genymobile/scrcpy/pull/2840>
2021-12-01 18:02:35 +01:00
86c91e183d Log CreateProcessW() error code on Windows
Refs #2838 <https://github.com/Genymobile/scrcpy/issues/2838>
2021-11-30 09:41:47 +01:00
cb8713eb1f Update links to v1.21 2021-11-29 22:24:06 +01:00
003e738106 Bump version to 1.21 2021-11-29 22:15:28 +01:00
30c79f2d25 Merge branch 'master' into dev 2021-11-29 22:08:43 +01:00
b25b674c45 Clarify TCP/IP mode in README 2021-11-29 22:03:58 +01:00
e2b3968c66 Always synchronize clipboard on explicit COPY/CUT
If --no-clipboard-autosync is enabled, the automatic clipboard
synchronization performed whenever the device clipboard changes is
disabled.

But on explicit COPY and CUT scrcpy shortcuts (MOD+c and MOD+x), the
clipboard should still be synchronized, so that it remains possible to
copy-paste from the device to the computer.

This is consistent with the behavior of MOD+v, which pastes the computer
clipboard to the device.

Refs #2228 <https://github.com/Genymobile/scrcpy/issues/2228>
Refs #2817 <https://github.com/Genymobile/scrcpy/pull/2817>
PR #2834 <https://github.com/Genymobile/scrcpy/pull/2834>
2021-11-29 22:00:55 +01:00
bfcb9d06c3 Expose sync mode for injecting events
Expose the inject input event mode so that it is possible to wait for
the events to be "finished". This will be necessary to read the
clipboard content only after the COPY or CUT key event is handled.

PR #2834 <https://github.com/Genymobile/scrcpy/pull/2834>
2021-11-29 22:00:21 +01:00
dc19ae334d Move acknowledgment handling
Handle all actions related to SET_CLIPBOARD from the dedicated method.

PR #2834 <https://github.com/Genymobile/scrcpy/pull/2834>
2021-11-29 21:58:30 +01:00
cbe73b0bc3 Fix set_clipboard message log
If paste is disabled on set_clipboard, then the PASTE key is not
injected, but COPY is unrelated.

PR #2834 <https://github.com/Genymobile/scrcpy/pull/2834>
2021-11-29 21:55:37 +01:00
bf97a46b0c Upgrade gradle build tools to 7.0.3 2021-11-29 21:55:12 +01:00
bd56d81f72 Add --raw-key-events
This option allows to inject all input keys as key events, and ignore
text events.

Fixes #2816 <https://github.com/Genymobile/scrcpy/issues/2816>
PR #2831 <https://github.com/Genymobile/scrcpy/pull/2831>
2021-11-29 21:15:41 +01:00
5e918ac0c3 Use enum for key injection mode
There was only two key injection modes:
 - the default one
 - the mode with --prefer-text enabled

To prepare the addition of another mode (--raw-key-events), use an enum
instead of a bool.

PR #2831 <https://github.com/Genymobile/scrcpy/pull/2831>
2021-11-29 21:15:32 +01:00
0c0f62e4ab Use static maps to convert input events
This improves readability (hopefully).

PR #2831 <https://github.com/Genymobile/scrcpy/pull/2831>
2021-11-29 21:15:07 +01:00
c96505200a Fix code style in keyboard_inject 2021-11-29 21:01:57 +01:00
82a053015d Improve HID keyboard documentation
Explain how to configure the keyboard layout.
2021-11-29 21:01:15 +01:00
1a6caeb18c Document --tcpip in README
PR #2827 <https://github.com/Genymobile/scrcpy/pull/2827>
2021-11-29 20:39:03 +01:00
19858e6aeb Add --tcpip feature
Expose an option to automatically configure and reconnect the device
over TCP/IP, to simplify wireless connection without using adb
explicitly.

There are two variants:
 - If a destination address is provided, then scrcpy connects to this
   address before starting. The device must listen on the given TCP port
   (default is 5555).
 - If no destination address is provided, then scrcpy attempts to find
   the IP address of the current device (typically connected over USB),
   enables TCP/IP mode, then connects to this address before starting.

PR #2827 <https://github.com/Genymobile/scrcpy/pull/2827>
2021-11-29 20:39:03 +01:00
3b310f8317 Extract interruptible sleep for server
This improves readability, and makes the function reusable.

PR #2827 <https://github.com/Genymobile/scrcpy/pull/2827>
2021-11-29 20:39:03 +01:00
800ba33ff4 Add function to read an adb property
This will allow to read the property "service.adb.tcp.port" to know if
the TCP/IP mode is enabled on the device, and which listening port is
used.

PR #2827 <https://github.com/Genymobile/scrcpy/pull/2827>
2021-11-29 20:39:03 +01:00
8543d842ea Add function to switch device to TCP/IP mode
Expose a function to execute "adb tcpip <port>".

PR #2827 <https://github.com/Genymobile/scrcpy/pull/2827>
2021-11-29 20:39:03 +01:00
f609b406c9 Add function to find the device IP address
Parse the result of "adb shell ip route" to find the device IP address.

PR #2827 <https://github.com/Genymobile/scrcpy/pull/2827>
2021-11-29 20:39:03 +01:00
b7e631791c Add util function to remove trailing '\r'
Depending on the platform and adb versions, the lines output by adb
could end with "\r\r\n". This util function helps to remove all trailing
'\r'.

PR #2827 <https://github.com/Genymobile/scrcpy/pull/2827>
2021-11-29 20:39:03 +01:00
b52f87a892 Add util function to locate a column in a string
This will help to parse the result of "adb shell ip route" to find the
device IP address.

PR #2827 <https://github.com/Genymobile/scrcpy/pull/2827>
2021-11-29 20:39:03 +01:00
3bf6fd2894 Workaround "adb connect" error detection
"adb connect" always returns successfully (with exit code 0), even in
case of failure.

As a workaround, capture its output and check if it starts with
"connected".

PR #2827 <https://github.com/Genymobile/scrcpy/pull/2827>
2021-11-29 20:39:03 +01:00
bfce22414f Add adb connect and disconnect
PR #2827 <https://github.com/Genymobile/scrcpy/pull/2827>
2021-11-29 20:39:03 +01:00
e6e6f865a0 Add adb flag to disable execution error logs
In addition to disable stdout and stderr of the child process, add a
flag to disable the error log printed by scrcpy if the command failed.

This will we useful for commands which are expected to fail in some
cases (like "adb disconnect" if the device is not connected).

PR #2827 <https://github.com/Genymobile/scrcpy/pull/2827>
2021-11-29 20:39:03 +01:00
e3d4aa8c5d Use flags for adb commands
Explicitly indicate, for each adb call, if stdout and stderr must be
inherited.

PR #2827 <https://github.com/Genymobile/scrcpy/pull/2827>
2021-11-29 20:39:03 +01:00
f801d8b312 Expose flags for process execution
Let the caller decide if stdout and stderr must be inherited on process
creation, i.e. if stdout and stderr of the child process should be
printed in the scrcpy console.

This allows to get output and errors for specific adb commands depending
on the context.

PR #2827 <https://github.com/Genymobile/scrcpy/pull/2827>
2021-11-29 20:39:03 +01:00
01ab503c22 Remove obsolete precision in README
Scrcpy is available in Debian stable packages, and several Ubuntu
versions.
2021-11-29 00:36:09 +01:00
b9b8b6aab8 Simplify Windows process inheritance configuration
Merge if-blocks together.
2021-11-26 09:41:11 +01:00
3e54773c48 Remove intermediate static functions from adb.c
They can easily be inlined into the public functions.
2021-11-26 09:41:11 +01:00
b90c89766b Set CLOEXEC flag on sockets
If SOCK_CLOEXEC exists, then set the flag on socket creation.

Otherwise, use fcntl() (or SetHandleInformation() on Windows) to set the
flag afterwards.

This avoids the sockets to be inherited in child processes.

Refs #2783 <https://github.com/Genymobile/scrcpy/pull/2783>
2021-11-26 09:40:39 +01:00
904f0ae61e Check process success locally for adb commands
Remove sc_process_check_success() from the process API, it is too
specific.
2021-11-25 22:22:34 +01:00
680d2cc940 Extract command argv building
This simplifies adb_execute_p().
2021-11-25 22:22:34 +01:00
8ed3328055 Use unsigned for connection attempts count
There is no reason to use an explicit uint32_t.
2021-11-25 22:22:34 +01:00
d31725f077 Reorder cli sanity checks
Check unexpected additional arguments before other sanity checks.
2021-11-25 22:22:34 +01:00
b0eb1a55d6 Fix adb get-serialno error handling
If pipe read fails, return.
2021-11-25 22:22:34 +01:00
3653fb6b15 Add OutOfMemory log helper
Add a special LOG_OOM() function to log all OutOfMemory errors (i.e.
allocations returning NULL).
2021-11-24 22:06:49 +01:00
92a458e846 Remove unreachable return statements 2021-11-24 21:48:57 +01:00
73e0311d14 Add missing return on file_handler failure
Mistake introduced by 84334cf7db.
2021-11-24 21:46:02 +01:00
007f616302 Add missing includes
Include these headers explicitly instead of relying on transitivity.
2021-11-24 21:44:29 +01:00
4c47598865 Make lockVideoOrientation option name uniform
On the server, the option was named lockedVideoOrientation.
2021-11-24 21:33:58 +01:00
7b29f5fd2d Do not pass default values to the server
By default, only pass the version and the bit rate (the default bitrate
is a client compilation option).
2021-11-24 21:31:12 +01:00
04e5537f8c Pass server parameters as key=value pairs
The options values to configure the server were identified by their
command-line argument index. Now that there are a lot of arguments, many
of them being booleans, it became unreadable and error-prone.

Identify the arguments by a key string instead, and make them optional.

This will also simplify running the server manually for debugging.
2021-11-24 21:23:20 +01:00
2eb881c5f1 Allocate and format server command args
This simplifies formatting.
2021-11-24 21:10:18 +01:00
2c3099e2de Parse codec options early
For consistency with other options, parse the codec options on the
server before storing them in the Options instance.
2021-11-24 21:06:02 +01:00
5434ea543c Remove local "serial" variable
In execute_server(), the serial is used only once. Moreover, it can be
retrieved from the `params` argument directly.
2021-11-24 21:01:25 +01:00
d5f6697f3a Add (v)asprintf compatibility functions
In case they are not available on the platform.
2021-11-24 19:55:00 +01:00
d6c0054545 Move check_functions in meson script
Move the variable to the place it is actually used.
2021-11-24 19:38:33 +01:00
6f487a2892 Add missing includes in compat implementation
These includes are necessary for the strdup() compat implementation.
2021-11-24 19:37:33 +01:00
dc0ac01e00 Define common feature test macros for all systems
_POSIX_C_SOURCE, _XOPEN_SOURCE and _GNU_SOURCE are also used on Windows.

Fix regression introduced by ba547e3895.
2021-11-24 19:36:33 +01:00
6abff46c9f Add option to disable 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.

This new option --no-clipboard-autosync disables this automatic
synchronization.

Fixes #2228 <https://github.com/Genymobile/scrcpy/issues/2228>
PR #2817 <https://github.com/Genymobile/scrcpy/pull/2817>
2021-11-24 09:44:39 +01:00
5d17bcf1bc Wait SET_CLIPBOARD ack before Ctrl+v via HID
To allow seamless copy-paste, on Ctrl+v, a SET_CLIPBOARD request is
performed before injecting Ctrl+v.

But when HID keyboard is enabled, the Ctrl+v injection is not sent on
the same channel as the clipboard request, so they are not serialized,
and may occur in any order. If Ctrl+v happens to be injected before the
new clipboard content is set, then the old content is pasted instead,
which is incorrect.

To minimize the probability of occurrence of the wrong order, a delay of
2 milliseconds was added before injecting Ctrl+v. Then 5ms. But even
with 5ms, the wrong behavior sometimes happens.

To handle it properly, add an acknowledgement mechanism, so that Ctrl+v
is injected over AOA only after the SET_CLIPBOARD request has been
performed and acknowledged by the server.

Refs e4163321f0
Refs 45b0f8123a

PR #2814 <https://github.com/Genymobile/scrcpy/pull/2814>
2021-11-24 09:41:21 +01:00
2d5525eac1 Move PRIu64 Windows workaround to compat.h
So that we can use it from several files.

PR #2814 <https://github.com/Genymobile/scrcpy/pull/2814>
2021-11-23 21:15:05 +01:00
41abe021e2 Make the device acknowledge device clipboard
If the client provided a sequence number on SET_CLIPBOARD request, make
the device send back an acknowledgement once the clipboard is set.

PR #2814 <https://github.com/Genymobile/scrcpy/pull/2814>
2021-11-23 21:15:05 +01:00
2a0730ee9b Add device clipboard set acknowledgement
Add a device message type so that the device could send acknowledgements
for SET_CLIPBOARD requests.

PR #2814 <https://github.com/Genymobile/scrcpy/pull/2814>
2021-11-23 21:15:05 +01:00
901d837165 Add sequence number to set_clipboard request
This will allow the client to request an acknowledgement.

PR #2814 <https://github.com/Genymobile/scrcpy/pull/2814>
2021-11-23 21:15:05 +01:00
aba1fc03c3 Add acksync helper to wait for acks
This will allow to send requests with sequence numbers to the server
and wait for acknowledgements.

PR #2814 <https://github.com/Genymobile/scrcpy/pull/2814>
2021-11-23 21:15:05 +01:00
5b3856c3b6 Explicitly indicate when device clipboard is set
Pass the information that device clipboard has been set to the key
processor. This avoids the keyprocessor to "guess", and paves the way to
implement a proper acknowledgement mechanism.

PR #2814 <https://github.com/Genymobile/scrcpy/pull/2814>
2021-11-23 21:15:05 +01:00
854de9659a Do not inject Ctrl+v if clipboard sync failed
This prevents to paste the current Android clipboard, which would be
unexpected.

PR #2814 <https://github.com/Genymobile/scrcpy/pull/2814>
2021-11-23 21:15:05 +01:00
ea8028332c Synchronize computer-to-device empty clipboard
Set the device clipboard to empty string if necessary. Otherwise, the
current device clipboard will be pasted on Ctrl+v.

PR #2814 <https://github.com/Genymobile/scrcpy/pull/2814>
2021-11-23 21:14:49 +01:00
0427a981e5 Use UINT64_C macro for uint64_t constant in tests
A long constant might not be sufficient.
2021-11-21 22:57:33 +01:00
44721ed982 Document remote ADB server in README
Refs #2807 <https://github.com/Genymobile/scrcpy/pull/2807>
2021-11-21 22:37:52 +01:00
68eaee5bb0 Force adb forward if tunnel host/port is provided
Tunnel host and port are only meaningful in "adb forward" mode.

They indicate where the client must connect to communicate with the
server. In "adb reverse" mode, the client _listens_, it does not
_connect_.

Refs #2807 <https://github.com/Genymobile/scrcpy/pull/2807>
2021-11-21 22:37:52 +01:00
7bdbde7363 Add options to configure tunnel host and port
In "adb forward" mode, by default, scrcpy connects to localhost:PORT,
where PORT is the local port passed to "adb forward". This assumes that
the tunnel is established on the local host with a local adb server
(which is the common case).

For advanced usage, add --tunnel-host and --tunnel-port to force the
connection to a different destination.

Fixes #2801 <https://github.com/Genymobile/scrcpy/issues/2801>
PR #2807 <https://github.com/Genymobile/scrcpy/pull/2807>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-11-21 22:37:52 +01:00
52e5181c84 Add function to parse IPv4 addresses
PR #2807 <https://github.com/Genymobile/scrcpy/pull/2807>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-11-21 22:37:37 +01:00
ba547e3895 Configure feature test macros in meson
Refs #2807 <https://github.com/Genymobile/scrcpy/pull/2807>

Co-authored-by: RipleyTom <RipleyTom@users.noreply.github.com>
2021-11-21 00:20:11 +01:00
226f3b2c91 Add missing include config.h 2021-11-21 00:13:25 +01:00
57fb08e443 Update Simplified Chinese README to 1.20
PR #2786 <https://github.com/Genymobile/scrcpy/pull/2786>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-11-17 19:14:32 +01:00
02ae0db6cd Fix wrong package to install for Ubuntu/Debian
Without this package, meson fails:

    Run-time dependency libusb-1.0 found: NO (tried pkgconfig and cmake)
    app/meson.build:88:8: ERROR: Dependency "libusb-1.0" not found, tried pkgconfig and cmake

PR #2790 <https://github.com/Genymobile/scrcpy/pull/2790>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-11-15 22:42:08 +01:00
121 changed files with 4707 additions and 1475 deletions

View File

@ -15,7 +15,7 @@ First, you need to install the required packages:
sudo apt install ffmpeg libsdl2-2.0-0 adb wget \
gcc git pkg-config meson ninja-build libsdl2-dev \
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \
libusb-1.0-0 libusb-dev
libusb-1.0-0 libusb-1.0-0-dev
```
Then clone the repo and execute the installation script
@ -94,7 +94,7 @@ sudo apt install ffmpeg libsdl2-2.0-0 adb libusb-1.0-0
# client build dependencies
sudo apt install gcc git pkg-config meson ninja-build libsdl2-dev \
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \
libusb-dev
libusb-1.0-0-dev
# server build dependencies
sudo apt install openjdk-11-jdk
@ -270,10 +270,10 @@ install` must be run as root)._
#### Option 2: Use prebuilt server
- [`scrcpy-server-v1.20`][direct-scrcpy-server]
_(SHA-256: b20aee4951f99b060c4a44000ba94de973f9604758ef62beb253b371aad3df34)_
- [`scrcpy-server-v1.21`][direct-scrcpy-server]
_(SHA-256: dbcccab523ee26796e55ea33652649e4b7af498edae9aa75e4d4d7869c0ab848)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.20/scrcpy-server-v1.20
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.21/scrcpy-server-v1.21
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:

View File

@ -188,7 +188,7 @@
identification within third-party archives.
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");
you may not use this file except in compliance with the License.

View File

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

137
README.md
View File

@ -1,11 +1,11 @@
# scrcpy (v1.20)
# scrcpy (v1.21)
<img src="data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
[Read in another language](#translations)
This application provides display and control of Android devices connected via
USB (or [over TCP/IP](#wireless)). It does not require any _root_ access.
USB (or [over TCP/IP](#tcpip-wireless)). It does not require any _root_ access.
It works on _GNU/Linux_, _Windows_ and _macOS_.
![screenshot](assets/screenshot-debian-600.jpg)
@ -31,6 +31,8 @@ Its features include:
- device screen [as a webcam (V4L2)](#v4l2loopback) (Linux-only)
- [physical keyboard simulation (HID)](#physical-keyboard-simulation-hid)
(Linux-only)
- [physical mouse simulation (HID)](#physical-mouse-simulation-hid)
(Linux-only)
- and more…
## Requirements
@ -65,7 +67,7 @@ Build from sources: [BUILD] ([simplified process][BUILD_simple])
### Linux
On Debian (_testing_ and _sid_ for now) and Ubuntu (20.04):
On Debian and Ubuntu:
```
apt install scrcpy
@ -101,10 +103,10 @@ process][BUILD_simple]).
For Windows, for simplicity, a prebuilt archive with all the dependencies
(including `adb`) is available:
- [`scrcpy-win64-v1.20.zip`][direct-win64]
_(SHA-256: 548532b616288bcaeceff6881ad5e6f0928e5ae2b48c380385f03627401cfdba)_
- [`scrcpy-win64-v1.21.zip`][direct-win64]
_(SHA-256: fdab0c1421353b592a9bbcebd6e252675eadccca65cca8105686feaa9c1ded53)_
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.20/scrcpy-win64-v1.20.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.21/scrcpy-win64-v1.21.zip
It is also available in [Chocolatey]:
@ -356,10 +358,39 @@ scrcpy --v4l2-buffer=500 # add 500 ms buffering for v4l2 sink
### Connection
#### Wireless
#### TCP/IP (wireless)
_Scrcpy_ uses `adb` to communicate with the device, and `adb` can [connect] to a
device over TCP/IP:
device over TCP/IP. The device must be connected on the same network as the
computer.
##### Automatic
An option `--tcpip` allows to configure the connection automatically. There are
two variants.
If the device (accessible at 192.168.1.1 in this example) already listens on a
port (typically 5555) for incoming adb connections, then run:
```bash
scrcpy --tcpip=192.168.1.1 # default port is 5555
scrcpy --tcpip=192.168.1.1:5555
```
If adb TCP/IP mode is disabled on the device (or if you don't know the IP
address), connect the device over USB, then run:
```bash
scrcpy --tcpip # without arguments
```
It will automatically find the device IP address, enable TCP/IP mode, then
connect to the device before starting.
##### Manual
Alternatively, it is possible to enable the TCP/IP connection manually using
`adb`:
1. Connect the device to the same Wi-Fi as your computer.
2. Get your device IP address, in Settings → About phone → Status, or by
@ -412,12 +443,47 @@ autoadb scrcpy -s '{}'
[AutoAdb]: https://github.com/rom1v/autoadb
#### SSH tunnel
#### Tunnels
To connect to a remote device, it is possible to connect a local `adb` client to
a remote `adb` server (provided they use the same version of the _adb_
protocol).
##### Remote ADB server
To connect to a remote ADB server, make the server listen on all interfaces:
```bash
adb kill-server
adb -a nodaemon server start
# keep this open
```
**Warning: all communications between clients and ADB server are unencrypted.**
Suppose that this server is accessible at 192.168.1.2. Then, from another
terminal, run scrcpy:
```bash
export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037
scrcpy --tunnel-host=192.168.1.2
```
By default, scrcpy uses the local port used for `adb forward` tunnel
establishment (typically `27183`, see `--port`). It is also possible to force a
different tunnel port (it may be useful in more complex situations, when more
redirections are involved):
```
scrcpy --tunnel-port=1234
```
##### SSH tunnel
To communicate with a remote ADB server securely, it is preferable to use a SSH
tunnel.
First, make sure the ADB server is running on the remote computer:
```bash
@ -695,6 +761,9 @@ of <kbd>Ctrl</kbd>+<kbd>v</kbd> and <kbd>MOD</kbd>+<kbd>v</kbd> so that they
also inject the computer clipboard text as a sequence of key events (the same
way as <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
To disable automatic clipboard synchronization, use
`--no-clipboard-autosync`.
#### Pinch-to-zoom
To simulate "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-and-move_.
@ -737,8 +806,46 @@ of the host key mapping. Therefore, if your keyboard layout does not match, it
must be configured on the Android device, in Settings → System → Languages and
input → [Physical keyboard].
This settings page can be started directly:
```bash
adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS
```
However, the option is only available when the HID keyboard is enabled (or when
a physical keyboard is connected).
[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
@ -758,7 +865,13 @@ scrcpy --prefer-text
(but this will break keyboard behavior in games)
This option has no effect on HID keyboard (all key events are sent as
On the contrary, you could force to always inject raw key events:
```bash
scrcpy --raw-key-events
```
These options have no effect on HID keyboard (all key events are sent as
scancodes in this mode).
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
@ -935,7 +1048,7 @@ Read the [developers page].
## Licence
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");
you may not use this file except in compliance with the License.
@ -967,7 +1080,7 @@ This README is available in other languages:
- [한국어 (Korean, `ko`) - v1.11](README.ko.md)
- [Português Brasileiro (Brazilian Portuguese, `pt-BR`) - v1.19](README.pt-br.md)
- [Español (Spanish, `sp`) - v1.17](README.sp.md)
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.17](README.zh-Hans.md)
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.20](README.zh-Hans.md)
- [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md)
- [Turkish (Turkish, `tr`) - v1.18](README.tr.md)

View File

@ -857,7 +857,7 @@ Leia a [página dos desenvolvedores][developers page].
## Licença
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");
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
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");
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
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");
you may not use this file except in compliance with the License.

View File

@ -2,27 +2,41 @@ _Only the original [README](README.md) is guaranteed to be up-to-date._
只有原版的[README](README.md)会保持最新。
本文根据[ed130e05]进行翻译。
Current version is based on [65b023a]
[ed130e05]: https://github.com/Genymobile/scrcpy/blob/ed130e05d55615d6014d93f15cfcb92ad62b01d8/README.md
本文根据[65b023a]进行翻译。
# scrcpy (v1.17)
[65b023a]: https://github.com/Genymobile/scrcpy/blob/65b023ac6d586593193fd5290f65e25603b68e02/README.md
# scrcpy (v1.20)
<img src="data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
本应用程序可以显示并控制通过 USB (或 [TCP/IP][article-tcpip]) 连接的安卓设备,且不需要任何 _root_ 权限。本程序支持 _GNU/Linux_, _Windows__macOS_
![screenshot](assets/screenshot-debian-600.jpg)
专注于:
本应用专注于:
- **轻量** (原生,仅显示设备屏幕)
- **性能** (30~60fps)
- **质量** (分辨率可达 1920×1080 或更高)
- **低延迟** ([35~70ms][lowlatency])
- **快速启动** (最快 1 秒内即可显示第一帧)
- **无侵入性** (不会在设备上遗留任何程序)
- **轻量** 原生,仅显示设备屏幕
- **性能** 30~120fps,取决于设备
- **质量** 分辨率可达 1920×1080 或更高
- **低延迟** [35~70ms][lowlatency]
- **快速启动** 最快 1 秒内即可显示第一帧
- **无侵入性** 不会在设备上遗留任何程序
- **用户利益** 无需帐号,无广告,无需联网
- **自由** 自由和开源软件
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
功能:
- [屏幕录制](#屏幕录制)
- 镜像时[关闭设备屏幕](#关闭设备屏幕)
- 双向[复制粘贴](#复制粘贴)
- [可配置显示质量](#采集设置)
- 以设备屏幕[作为摄像头(V4L2)](#v4l2loopback) (仅限 Linux)
- [模拟物理键盘 (HID)](#物理键盘模拟-hid) (仅限 Linux)
- 更多 ……
## 系统要求
@ -41,6 +55,17 @@ _Only the original [README](README.md) is guaranteed to be up-to-date._
<a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a>
### 概要
- Linux: `apt install scrcpy`
- Windows: [下载][direct-win64]
- macOS: `brew install scrcpy`
从源代码编译: [构建][BUILD] ([简化过程][BUILD_simple])
[BUILD]: BUILD.md
[BUILD_simple]: BUILD.md#simple
### Linux
在 Debian (目前仅支持 _testing__sid_ 分支) 和Ubuntu (20.04) 上:
@ -70,13 +95,12 @@ apt install scrcpy
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
您也可以[自行构建][BUILD] (不必担心,这并不困难)。
您也可以[自行构建][BUILD] ([简化过程][BUILD_simple])。
### Windows
在 Windows 上,简便起见,我们提供包含了所有依赖 (包括 `adb`) 的预编译包。
在 Windows 上,简便起见,我们提供包含了所有依赖 (包括 `adb`) 的预编译包。
- [README](README.md#windows)
@ -114,13 +138,17 @@ brew install scrcpy
你还需要在 `PATH` 内有 `adb`。如果还没有:
```bash
# Homebrew >= 2.6.0
brew install --cask android-platform-tools
# Homebrew < 2.6.0
brew cask install android-platform-tools
brew install android-platform-tools
```
或者通过 [MacPorts],该方法同时设置好 adb
```bash
sudo port install scrcpy
```
[MacPorts]: https://www.macports.org/
您也可以[自行构建][BUILD]。
@ -140,7 +168,7 @@ scrcpy --help
## 功能介绍
### 捕获设置
### 采集设置
#### 降低分辨率
@ -158,7 +186,7 @@ scrcpy -m 1024 # 简写
#### 修改码率
默认码率是 8Mbps。改变视频码率 (例如改为 2Mbps)
默认码率是 8 Mbps。改变视频码率 (例如改为 2 Mbps)
```bash
scrcpy --bit-rate 2M
@ -167,7 +195,7 @@ scrcpy -b 2M # 简写
#### 限制帧率
要限制捕获的帧率:
要限制采集的帧率:
```bash
scrcpy --max-fps 15
@ -194,10 +222,11 @@ scrcpy --crop 1224:1440:0:0 # 以 (0,0) 为原点的 1224x1440 像素
要锁定镜像画面的方向:
```bash
scrcpy --lock-video-orientation 0 # 自然方向
scrcpy --lock-video-orientation 1 # 逆时针旋转 90°
scrcpy --lock-video-orientation 2 # 18
scrcpy --lock-video-orientation 3 # 顺时针旋转 9
scrcpy --lock-video-orientation # 初始(目前)方向
scrcpy --lock-video-orientation=0 # 自然方向
scrcpy --lock-video-orientation=1 # 逆时针旋转 9
scrcpy --lock-video-orientation=2 # 18
scrcpy --lock-video-orientation=3 # 顺时针旋转 90°
```
只影响录制的方向。
@ -219,7 +248,9 @@ scrcpy --encoder OMX.qcom.video.encoder.avc
scrcpy --encoder _
```
### 屏幕录制
### 采集
#### 屏幕录制
可以在镜像的同时录制视频:
@ -241,6 +272,75 @@ scrcpy -Nr file.mkv
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
#### v4l2loopback
在 Linux 上,可以将视频流发送至 v4l2 回环 (loopback) 设备,因此可以使用任何 v4l2 工具像摄像头一样打开安卓设备。
需安装 `v4l2loopback` 模块:
```bash
sudo apt install v4l2loopback-dkms
```
创建一个 v4l2 设备:
```bash
sudo modprobe v4l2loopback
```
这样会在 `/dev/videoN` 创建一个新的视频设备,其中 `N` 是整数。 ([更多选项](https://github.com/umlaeute/v4l2loopback#options) 可以用来创建多个设备或者特定 ID 的设备)。
列出已启用的设备:
```bash
# 需要 v4l-utils 包
v4l2-ctl --list-devices
# 简单但或许足够
ls /dev/video*
```
使用一个 v4l2 漏开启 scrcpy
```bash
scrcpy --v4l2-sink=/dev/videoN
scrcpy --v4l2-sink=/dev/videoN --no-display # 禁用窗口镜像
scrcpy --v4l2-sink=/dev/videoN -N # 简写
```
(将 `N` 替换为设备 ID使用 `ls /dev/video*` 命令查看)
启用之后,可以使用 v4l2 工具打开视频流:
```bash
ffplay -i /dev/videoN
vlc v4l2:///dev/videoN # VLC 可能存在一些缓冲延迟
```
例如,可以在 [OBS] 中采集视频。
[OBS]: https://obsproject.com/
#### 缓冲
可以加入缓冲,会增加延迟,但可以减少抖动 (见 [#2464])。
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464
对于显示缓冲:
```bash
scrcpy --display-buffer=50 # 为显示增加 50 毫秒的缓冲
```
对于 V4L2 漏:
```bash
scrcpy --v4l2-buffer=500 # 为 v4l2 漏增加 500 毫秒的缓冲
```
### 连接
#### 无线
@ -249,16 +349,17 @@ _Scrcpy_ 使用 `adb` 与设备通信,并且 `adb` 支持通过 TCP/IP [连接
1. 将设备和电脑连接至同一 Wi-Fi。
2. 打开 设置 → 关于手机 → 状态信息,获取设备的 IP 地址,也可以执行以下的命令:
```bash
adb shell ip route | awk '{print $9}'
```
3. 启用设备的网络 adb 功能 `adb tcpip 5555`。
3. 启用设备的网络 adb 功能 `adb tcpip 5555`。
4. 断开设备的 USB 连接。
5. 连接到您的设备:`adb connect DEVICE_IP:5555` _(将 `DEVICE_IP` 替换为设备 IP)_.
5. 连接到您的设备:`adb connect DEVICE_IP:5555` _(将 `DEVICE_IP` 替换为设备 IP)_
6. 正常运行 `scrcpy`。
可能需要降低码率和分辨率:
可能降低码率和分辨率会更好一些
```bash
scrcpy --bit-rate 2M --max-size 800
@ -327,7 +428,7 @@ scrcpy --force-adb-forward
```
类似无线网络连接,可能需要降低画面质量:
类似地,对于无线连接,可能需要降低画面质量:
```
scrcpy -b2M -m800 --max-fps 15
@ -353,7 +454,7 @@ scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
#### 无边框
关闭边框:
禁用窗口边框:
```bash
scrcpy --window-borderless
@ -369,7 +470,7 @@ scrcpy --always-on-top
#### 全屏
您可以通过如下命令直接全屏启动scrcpy
您可以通过如下命令直接全屏启动 scrcpy
```bash
scrcpy --fullscreen
@ -394,7 +495,7 @@ scrcpy --rotation 1
也可以使用 <kbd>MOD</kbd>+<kbd>←</kbd> _(左箭头)_ 和 <kbd>MOD</kbd>+<kbd>→</kbd> _(右箭头)_ 随时更改。
需要注意的是, _scrcpy_ 有三个不同的方向:
需要注意的是, _scrcpy_ 有三类旋转方向:
- <kbd>MOD</kbd>+<kbd>r</kbd> 请求设备在竖屏和横屏之间切换 (如果前台应用程序不支持请求的朝向,可能会拒绝该请求)。
- [`--lock-video-orientation`](#锁定屏幕方向) 改变镜像的朝向 (设备传输到电脑的画面的朝向)。这会影响录制。
- `--rotation` (或 <kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>) 只旋转窗口的内容。这只影响显示,不影响录制。
@ -404,7 +505,7 @@ scrcpy --rotation 1
#### 只读
禁用电脑对设备的控制 (如键盘输入、鼠标事件和文件拖放)
禁用电脑对设备的控制 (任何可与设备交互的方式:如键盘输入、鼠标事件和文件拖放)
```bash
scrcpy --no-control
@ -430,14 +531,14 @@ adb shell dumpsys display # 在输出中搜索 “mDisplayId=”
#### 保持常亮
阻止设备在连接时休眠:
阻止设备在连接时一段时间后休眠:
```bash
scrcpy --stay-awake
scrcpy -w
```
程序关闭时会恢复设备原来的设置。
scrcpy 关闭时会恢复设备原来的设置。
#### 关闭设备屏幕
@ -451,7 +552,7 @@ scrcpy -S
或者在任何时候按 <kbd>MOD</kbd>+<kbd>o</kbd>。
要重新打开屏幕,按下 <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>.
要重新打开屏幕,按下 <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
在Android上`电源` 按钮始终能把屏幕打开。为了方便,对于在 _scrcpy_ 中发出的 `电源` 事件 (通过鼠标右键或 <kbd>MOD</kbd>+<kbd>p</kbd>),会 (尽最大的努力) 在短暂的延迟后将屏幕关闭。设备上的 `电源` 按钮仍然能打开设备屏幕。
@ -462,20 +563,17 @@ scrcpy --turn-screen-off --stay-awake
scrcpy -Sw
```
#### 退出时息屏
#### 渲染过期帧
默认状态下,为了降低延迟, _scrcpy_ 永远渲染解码成功的最近一帧,并跳过前面任意帧。
强制渲染所有帧 (可能导致延迟变高)
scrcpy 退出时关闭设备屏幕:
```bash
scrcpy --render-expired-frames
scrcpy --power-off-on-close
```
#### 显示触摸
在演示时,可能会需要显示物理触摸点 (在物理设备上的触摸点)
在演示时,可能会需要显示 (在物理设备上的) 物理触摸点。
Android 在 _开发者选项_ 中提供了这项功能。
@ -538,10 +636,32 @@ scrcpy --disable-screensaver
更准确的说,在按住鼠标左键时按住 <kbd>Ctrl</kbd>。直到松开鼠标左键,所有鼠标移动将以屏幕中心为原点,缩放或旋转内容 (如果应用支持)。
实际上_scrcpy_ 会在屏幕中心对称的位置上生成由“虚拟手指”发出的额外触摸事件。
实际上_scrcpy_ 会在关于屏幕中心对称的位置上“虚拟手指”发出触摸事件。
#### 物理键盘模拟 (HID)
#### 文字注入偏好
默认情况下scrcpy 使用安卓按键或文本注入这在任何情况都可以使用但仅限于ASCII字符。
在 Linux 上scrcpy 可以模拟为 Android 上的物理 USB 键盘,以提供更好地输入体验 (使用 [USB HID over AOAv2][hid-aoav2]):禁用虚拟键盘,并适用于任何字符和输入法。
[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support
不过,这种方法仅支持 USB 连接以及 Linux平台。
启用 HID 模式:
```bash
scrcpy --hid-keyboard
scrcpy -K # 简写
```
如果失败了 (如设备未通过 USB 连接),则自动回退至默认模式 (终端中会输出日志)。这即允许通过 USB 和 TCP/IP 连接时使用相同的命令行参数。
在这种模式下,原始按键事件 (扫描码) 被发送给设备,而与宿主机按键映射无关。因此,若键盘布局不匹配,需要在 Android 设备上进行配置,具体为 设置 → 系统 → 语言和输入法 → [实体键盘]。
[Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915
#### 文本注入偏好
打字的时候,系统会产生两种[事件][textevents]
- _按键事件_ ,代表一个按键被按下或松开。
@ -557,13 +677,15 @@ scrcpy --prefer-text
(这会导致键盘在游戏中工作不正常)
该选项不影响 HID 键盘 (该模式下,所有按键都发送为扫描码)。
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
#### 按键重复
默认状态下,按住一个按键不放会生成多个重复按键事件。在某些游戏中这可能会导致性能问题。
默认状态下,按住一个按键不放会生成多个重复按键事件。在某些游戏中这通常没有实际用途,且可能会导致性能问题。
避免转发重复按键事件:
@ -571,10 +693,11 @@ scrcpy --prefer-text
scrcpy --no-key-repeat
```
该选项不影响 HID 键盘 (该模式下,按键重复由 Android 直接管理)。
#### 右键和中键
默认状态下,右键会触发返回键 (或电源键),中键会触发 HOME 键。要禁用这些快捷键并把所有点击转发到设备:
默认状态下,右键会触发返回键 (或电源键开启),中键会触发 HOME 键。要禁用这些快捷键并把所有点击转发到设备:
```bash
scrcpy --forward-all-clicks
@ -587,27 +710,27 @@ scrcpy --forward-all-clicks
将 APK 文件 (文件名以 `.apk` 结尾) 拖放到 _scrcpy_ 窗口来安装。
该操作在屏幕上不会出现任何变化,而会在控制台输出一条日志。
不会有视觉反馈,终端会输出一条日志。
#### 将文件推送至设备
要推送文件到设备的 `/sdcard/`,将 (非 APK) 文件拖放至 _scrcpy_ 窗口。
要推送文件到设备的 `/sdcard/Download/`,将 (非 APK) 文件拖放至 _scrcpy_ 窗口。
该操作没有可见的响应,只会在控制台输出日志。
不会有视觉反馈,终端会输出一条日志。
在启动时可以修改目标目录:
```bash
scrcpy --push-target /sdcard/foo/bar/
scrcpy --push-target=/sdcard/Movies/
```
### 音频转发
_Scrcpy_ 不支持音频。请使用 [sndcpy].
_Scrcpy_ 不支持音频。请使用 [sndcpy]
外请阅读 [issue #14]。
[issue #14]。
[sndcpy]: https://github.com/rom1v/sndcpy
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14
@ -632,36 +755,46 @@ _<kbd>[Super]</kbd> 键通常是指 <kbd>Windows</kbd> 或 <kbd>Cmd</kbd> 键。
[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button)
| 操作 | 快捷键 |
| --------------------------------- | :------------------------------------------- |
| 全屏 | <kbd>MOD</kbd>+<kbd>f</kbd> |
| 向左旋转屏幕 | <kbd>MOD</kbd>+<kbd>←</kbd> _(左箭头)_ |
| 向右旋转屏幕 | <kbd>MOD</kbd>+<kbd>→</kbd> _(右箭头)_ |
| 将窗口大小重置为1:1 (匹配像素) | <kbd>MOD</kbd>+<kbd>g</kbd> |
| 将窗口大小重置为消除黑边 | <kbd>MOD</kbd>+<kbd>w</kbd> \| _双击¹_ |
| 点按 `主屏幕` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _鼠标中键_ |
| 点按 `返回` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _鼠标右键²_ |
| 点按 `切换应用` | <kbd>MOD</kbd>+<kbd>s</kbd> |
| 点按 `菜单` (解锁屏幕) | <kbd>MOD</kbd>+<kbd>m</kbd> |
| 点按 `音量+` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(上箭头)_ |
| 点按 `音量-` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(下箭头)_ |
| 点按 `电源` | <kbd>MOD</kbd>+<kbd>p</kbd> |
| 打开屏幕 | _鼠标右键²_ |
| 关闭设备屏幕 (但继续在电脑上显示) | <kbd>MOD</kbd>+<kbd>o</kbd> |
| 打开设备屏幕 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd> |
| 旋转设备屏幕 | <kbd>MOD</kbd>+<kbd>r</kbd> |
| 展开通知面板 | <kbd>MOD</kbd>+<kbd>n</kbd> |
| 收起通知面板 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd> |
| 复制到剪贴板³ | <kbd>MOD</kbd>+<kbd>c</kbd> |
| 剪切到剪贴板³ | <kbd>MOD</kbd>+<kbd>x</kbd> |
| 同步剪贴板并粘贴³ | <kbd>MOD</kbd>+<kbd>v</kbd> |
| 注入电脑剪贴板文本 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> |
| 打开/关闭FPS显示 (在 stdout) | <kbd>MOD</kbd>+<kbd>i</kbd> |
| 捏拉缩放 | <kbd>Ctrl</kbd>+_按住并移动鼠标_ |
| 操作 | 快捷键
| --------------------------------- | :-------------------------------------------
| 全屏 | <kbd>MOD</kbd>+<kbd>f</kbd>
| 向左旋转屏幕 | <kbd>MOD</kbd>+<kbd>←</kbd> _(左箭头)_
| 向右旋转屏幕 | <kbd>MOD</kbd>+<kbd>→</kbd> _(右箭头)_
| 将窗口大小重置为1:1 (匹配像素) | <kbd>MOD</kbd>+<kbd>g</kbd>
| 将窗口大小重置为消除黑边 | <kbd>MOD</kbd>+<kbd>w</kbd> \| _双击左键¹_
| 点按 `主屏幕` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _中键_
| 点按 `返回` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _右键²_
| 点按 `切换应用` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _第4键³_
| 点按 `菜单` (解锁屏幕) | <kbd>MOD</kbd>+<kbd>m</kbd>
| 点按 `音量+` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(上箭头)_
| 点按 `音量-` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(下箭头)_
| 点按 `电源` | <kbd>MOD</kbd>+<kbd>p</kbd>
| 打开屏幕 | _鼠标右键²_
| 关闭设备屏幕 (但继续在电脑上显示) | <kbd>MOD</kbd>+<kbd>o</kbd>
| 打开设备屏幕 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
| 旋转设备屏幕 | <kbd>MOD</kbd>+<kbd>r</kbd>
| 展开通知面板 | <kbd>MOD</kbd>+<kbd>n</kbd> \| _第5键³_
| 展开设置面板 | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _双击第5键³_
| 收起通知面板 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| 复制到剪贴板 | <kbd>MOD</kbd>+<kbd>c</kbd>
| 剪切到剪贴板⁴ | <kbd>MOD</kbd>+<kbd>x</kbd>
| 同步剪贴板并粘贴⁴ | <kbd>MOD</kbd>+<kbd>v</kbd>
| 注入电脑剪贴板文本 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| 打开/关闭FPS显示 (至标准输出) | <kbd>MOD</kbd>+<kbd>i</kbd>
| 捏拉缩放 | <kbd>Ctrl</kbd>+_按住并移动鼠标_
| 拖放 APK 文件 | 从电脑安装 APK 文件
| 拖放非 APK 文件 | [将文件推送至设备](#push-file-to-device)
_¹双击黑边可以去除黑边_
_¹双击黑边可以去除黑边。_
_²点击鼠标右键将在屏幕熄灭时点亮屏幕其余情况则视为按下返回键 。_
需要安卓版本 Android >= 7。_
鼠标的第4键和第5键。_
_⁴需要安卓版本 Android >= 7。_
有重复按键的快捷键通过松开再按下一个按键来进行,如“展开设置面板”:
1. 按下 <kbd>MOD</kbd> 不放。
2. 双击 <kbd>n</kbd>。
3. 松开 <kbd>MOD</kbd>。
所有的 <kbd>Ctrl</kbd>+_按键_ 的快捷键都会被转发到设备,所以会由当前应用程序进行处理。
@ -670,18 +803,20 @@ _³需要安卓版本 Android >= 7。_
要使用指定的 _adb_ 二进制文件,可以设置环境变量 `ADB`
ADB=/path/to/adb scrcpy
```bash
ADB=/path/to/adb scrcpy
```
要覆盖 `scrcpy-server` 的路径,可以设置 `SCRCPY_SERVER_PATH`。
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
要覆盖图标,可以设置其路径至 `SCRCPY_ICON_PATH`。
## 为什么叫 _scrcpy_
一个同事让我找出一个和 [gnirehtet] 一样难以发音的名字。
[`strcpy`] 复制一个 **str**ing `scrcpy` 复制一个 **scr**een。
[`strcpy`] 复制一个 **str**ing (字符串) `scrcpy` 复制一个 **scr**een (屏幕)
[gnirehtet]: https://github.com/Genymobile/gnirehtet
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
@ -689,14 +824,12 @@ _³需要安卓版本 Android >= 7。_
## 如何构建?
请查看[BUILD]。
[BUILD]: BUILD.md
请查看 [BUILD]。
## 常见问题
请查看[FAQ](FAQ.md)。
请查看 [FAQ](FAQ.md)。
## 开发者
@ -709,7 +842,7 @@ _³需要安卓版本 Android >= 7。_
## 许可协议
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");
you may not use this file except in compliance with the License.

View File

@ -679,7 +679,7 @@ _³只支援 Android 7+。_
## Licence
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");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,7 @@
src = [
'src/main.c',
'src/adb.c',
'src/adb_parser.c',
'src/adb_tunnel.c',
'src/cli.c',
'src/clock.c',
@ -25,7 +26,9 @@ src = [
'src/server.c',
'src/stream.c',
'src/video_buffer.c',
'src/util/acksync.c',
'src/util/file.c',
'src/util/intmap.c',
'src/util/intr.c',
'src/util/log.c',
'src/util/net.c',
@ -39,16 +42,29 @@ src = [
'src/util/tick.c',
]
conf = configuration_data()
conf.set('_POSIX_C_SOURCE', '200809L')
conf.set('_XOPEN_SOURCE', '700')
conf.set('_GNU_SOURCE', true)
if host_machine.system() == 'windows'
windows = import('windows')
src += [
'src/sys/win/file.c',
'src/sys/win/process.c',
windows.compile_resources('scrcpy-windows.rc'),
]
conf.set('_WIN32_WINNT', '0x0600')
conf.set('WINVER', '0x0600')
else
src += [
'src/sys/unix/file.c',
'src/sys/unix/process.c',
]
if host_machine.system() == 'darwin'
conf.set('_DARWIN_C_SOURCE', true)
endif
endif
v4l2_support = host_machine.system() == 'linux'
@ -61,23 +77,22 @@ if aoa_hid_support
src += [
'src/aoa_hid.c',
'src/hid_keyboard.c',
'src/hid_mouse.c',
]
endif
check_functions = [
'strdup'
]
cc = meson.get_compiler('c')
if not get_option('crossbuild_windows')
crossbuild_windows = meson.is_cross_build() and host_machine.system() == 'windows'
if not crossbuild_windows
# native build
dependencies = [
dependency('libavformat'),
dependency('libavcodec'),
dependency('libavformat', version: '>= 57.33'),
dependency('libavcodec', version: '>= 57.37'),
dependency('libavutil'),
dependency('sdl2'),
dependency('sdl2', version: '>= 2.0.5'),
]
if v4l2_support
@ -128,7 +143,11 @@ if host_machine.system() == 'windows'
dependencies += cc.find_library('ws2_32')
endif
conf = configuration_data()
check_functions = [
'strdup',
'asprintf',
'vasprintf',
]
foreach f : check_functions
if cc.has_function(f)
@ -137,6 +156,9 @@ foreach f : check_functions
endif
endforeach
conf.set('HAVE_SOCK_CLOEXEC', host_machine.system() != 'windows' and
cc.has_header_symbol('sys/socket.h', 'SOCK_CLOEXEC'))
# the version, updated on release
conf.set_quoted('SCRCPY_VERSION', meson.project_version())
@ -189,8 +211,14 @@ install_data('../data/icon.png',
# do not build tests in release (assertions would not be executed at all)
if get_option('buildtype') == 'debug'
tests = [
['test_adb_parser', [
'tests/test_adb_parser.c',
'src/adb_parser.c',
'src/util/str.c',
'src/util/strbuf.c',
]],
['test_buffer_util', [
'tests/test_buffer_util.c'
'tests/test_buffer_util.c',
]],
['test_cbuf', [
'tests/test_cbuf.c',
@ -199,6 +227,7 @@ if get_option('buildtype') == 'debug'
'tests/test_cli.c',
'src/cli.c',
'src/options.c',
'src/util/net.c',
'src/util/str.c',
'src/util/strbuf.c',
'src/util/term.c',

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:application>
<asmv3:windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>

23
app/scrcpy-windows.rc Normal file
View File

@ -0,0 +1,23 @@
#include <winuser.h>
0 ICON "../data/icon.ico"
1 RT_MANIFEST "scrcpy-windows.manifest"
2 VERSIONINFO
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904E4"
BEGIN
VALUE "FileDescription", "Display and control your Android device"
VALUE "InternalName", "scrcpy"
VALUE "LegalCopyright", "Romain Vimont, Genymobile"
VALUE "OriginalFilename", "scrcpy.exe"
VALUE "ProductName", "scrcpy"
VALUE "ProductVersion", "1.21"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1252
END
END

View File

@ -90,6 +90,14 @@ This provides a better experience for IME users, and allows to generate non-ASCI
It may only work over USB, and is currently only supported on Linux.
The keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly:
adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS
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
.B \-\-legacy\-paste
Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v).
@ -114,6 +122,24 @@ Limit both the width and height of the video to \fIvalue\fR. The other dimension
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
.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.
This option disables this automatic synchronization.
.TP
.B \-n, \-\-no\-control
Disable device control (mirror the device in read\-only).
@ -153,6 +179,10 @@ Set the target directory for pushing files to the device by drag & drop. It is p
Default is "/sdcard/Download/".
.TP
.B \-\-raw\-key\-events
Inject key events for all input keys, and ignore text events.
.TP
.BI "\-r, \-\-record " file
Record screen to
@ -193,6 +223,14 @@ For example, to use either LCtrl+LAlt or LSuper for scrcpy shortcuts, pass "lctr
Default is "lalt,lsuper" (left-Alt or left-Super).
.TP
.BI "\-\-tcpip[=ip[:port]]
Configure and reconnect the device over TCP/IP.
If a destination address is provided, then scrcpy connects to this address before starting. The device must listen on the given TCP port (default is 5555).
If no destination address is provided, then scrcpy attempts to find the IP address of the current device (typically connected over USB), enables TCP/IP mode, then connects to this address before starting.
.TP
.B \-S, \-\-turn\-screen\-off
Turn the device screen off immediately.
@ -203,6 +241,18 @@ Enable "show touches" on start, restore the initial value on exit.
It only shows physical touches (not clicks from scrcpy).
.TP
.BI "\-\-tunnel\-host " ip
Set the IP address of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward.
Default is localhost.
.TP
.BI "\-\-tunnel\-port " port
Set the TCP port of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward.
Default is 0 (not forced): the local port used for establishing the tunnel will be used.
.TP
.BI "\-\-v4l2-sink " /dev/videoN
Output to v4l2loopback device.
@ -377,11 +427,15 @@ Push file to device (see \fB\-\-push\-target\fR)
.TP
.B ADB
Specify the path to adb.
Path to adb.
.TP
.B SCRCPY_ICON_PATH
Path to the program icon.
.TP
.B SCRCPY_SERVER_PATH
Specify the path to server binary.
Path to the server binary.
.SH AUTHORS
@ -406,7 +460,7 @@ Copyright \(co 2018 Genymobile
Genymobile
.UE
Copyright \(co 2018\-2020
Copyright \(co 2018\-2022
.MT rom@rom1v.com
Romain Vimont
.ME

View File

@ -5,6 +5,7 @@
#include <stdlib.h>
#include <string.h>
#include "adb_parser.h"
#include "util/file.h"
#include "util/log.h"
#include "util/process_intr.h"
@ -86,7 +87,8 @@ show_adb_err_msg(enum sc_process_result err, const char *const argv[]) {
#define MAX_COMMAND_STRING_LEN 1024
char *buf = malloc(MAX_COMMAND_STRING_LEN);
if (!buf) {
LOGE("Failed to execute (could not allocate error message)");
LOG_OOM();
LOGE("Failed to execute");
return;
}
@ -110,18 +112,61 @@ show_adb_err_msg(enum sc_process_result err, const char *const argv[]) {
free(buf);
}
static sc_pid
adb_execute_p(const char *serial, const char *const adb_cmd[],
size_t len, sc_pipe *pout) {
int i;
sc_pid pid;
static bool
process_check_success_internal(sc_pid pid, const char *name, bool close,
unsigned flags) {
bool log_errors = !(flags & SC_ADB_NO_LOGERR);
if (pid == SC_PROCESS_NONE) {
if (log_errors) {
LOGE("Could not execute \"%s\"", name);
}
return false;
}
sc_exit_code exit_code = sc_process_wait(pid, close);
if (exit_code) {
if (log_errors) {
if (exit_code != SC_EXIT_CODE_NONE) {
LOGE("\"%s\" returned with value %" SC_PRIexitcode, name,
exit_code);
} else {
LOGE("\"%s\" exited unexpectedly", name);
}
}
return false;
}
return true;
}
static bool
process_check_success_intr(struct sc_intr *intr, sc_pid pid, const char *name,
unsigned flags) {
if (!sc_intr_set_process(intr, pid)) {
// Already interrupted
return false;
}
// Always pass close=false, interrupting would be racy otherwise
bool ret = process_check_success_internal(pid, name, false, flags);
sc_intr_set_process(intr, SC_PROCESS_NONE);
// Close separately
sc_process_close(pid);
return ret;
}
static const char **
adb_create_argv(const char *serial, const char *const adb_cmd[], size_t len) {
const char **argv = malloc((len + 4) * sizeof(*argv));
if (!argv) {
return SC_PROCESS_NONE;
LOG_OOM();
return NULL;
}
argv[0] = get_adb_command();
int i;
if (serial) {
argv[1] = "-s";
argv[2] = serial;
@ -132,9 +177,31 @@ adb_execute_p(const char *serial, const char *const adb_cmd[],
memcpy(&argv[i], adb_cmd, len * sizeof(const char *));
argv[len + i] = NULL;
return argv;
}
static sc_pid
adb_execute_p(const char *serial, const char *const adb_cmd[], size_t len,
unsigned flags, sc_pipe *pout) {
const char **argv = adb_create_argv(serial, adb_cmd, len);
if (!argv) {
return SC_PROCESS_NONE;
}
unsigned process_flags = 0;
if (flags & SC_ADB_NO_STDOUT) {
process_flags |= SC_PROCESS_NO_STDOUT;
}
if (flags & SC_ADB_NO_STDERR) {
process_flags |= SC_PROCESS_NO_STDERR;
}
sc_pid pid;
enum sc_process_result r =
sc_process_execute_p(argv, &pid, NULL, pout, NULL);
sc_process_execute_p(argv, &pid, process_flags, NULL, pout, NULL);
if (r != SC_PROCESS_SUCCESS) {
// If the execution itself failed (not the command exit code), log the
// error in all cases
show_adb_err_msg(r, argv);
pid = SC_PROCESS_NONE;
}
@ -144,50 +211,63 @@ adb_execute_p(const char *serial, const char *const adb_cmd[],
}
sc_pid
adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
return adb_execute_p(serial, adb_cmd, len, NULL);
adb_execute(const char *serial, const char *const adb_cmd[], size_t len,
unsigned flags) {
return adb_execute_p(serial, adb_cmd, len, flags, NULL);
}
static sc_pid
adb_exec_forward(const char *serial, uint16_t local_port,
const char *device_socket_name) {
bool
adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
const char *device_socket_name, unsigned flags) {
char local[4 + 5 + 1]; // tcp:PORT
char remote[108 + 14 + 1]; // localabstract:NAME
sprintf(local, "tcp:%" PRIu16, local_port);
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
const char *const adb_cmd[] = {"forward", local, remote};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags);
return process_check_success_intr(intr, pid, "adb forward", flags);
}
static sc_pid
adb_exec_forward_remove(const char *serial, uint16_t local_port) {
bool
adb_forward_remove(struct sc_intr *intr, const char *serial,
uint16_t local_port, unsigned flags) {
char local[4 + 5 + 1]; // tcp:PORT
sprintf(local, "tcp:%" PRIu16, local_port);
const char *const adb_cmd[] = {"forward", "--remove", local};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags);
return process_check_success_intr(intr, pid, "adb forward --remove", flags);
}
static sc_pid
adb_exec_reverse(const char *serial, const char *device_socket_name,
uint16_t local_port) {
bool
adb_reverse(struct sc_intr *intr, const char *serial,
const char *device_socket_name, uint16_t local_port,
unsigned flags) {
char local[4 + 5 + 1]; // tcp:PORT
char remote[108 + 14 + 1]; // localabstract:NAME
sprintf(local, "tcp:%" PRIu16, local_port);
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
const char *const adb_cmd[] = {"reverse", remote, local};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags);
return process_check_success_intr(intr, pid, "adb reverse", flags);
}
static sc_pid
adb_exec_reverse_remove(const char *serial, const char *device_socket_name) {
bool
adb_reverse_remove(struct sc_intr *intr, const char *serial,
const char *device_socket_name, unsigned flags) {
char remote[108 + 14 + 1]; // localabstract:NAME
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
const char *const adb_cmd[] = {"reverse", "--remove", remote};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags);
return process_check_success_intr(intr, pid, "adb reverse --remove", flags);
}
static sc_pid
adb_exec_push(const char *serial, const char *local, const char *remote) {
bool
adb_push(struct sc_intr *intr, const char *serial, const char *local,
const char *remote, unsigned flags) {
#ifdef __WINDOWS__
// Windows will parse the string, so the paths must be quoted
// (see sys/win/command.c)
@ -203,18 +283,19 @@ adb_exec_push(const char *serial, const char *local, const char *remote) {
#endif
const char *const adb_cmd[] = {"push", local, remote};
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags);
#ifdef __WINDOWS__
free((void *) remote);
free((void *) local);
#endif
return pid;
return process_check_success_intr(intr, pid, "adb push", flags);
}
static sc_pid
adb_exec_install(const char *serial, const char *local) {
bool
adb_install(struct sc_intr *intr, const char *serial, const char *local,
unsigned flags) {
#ifdef __WINDOWS__
// Windows will parse the string, so the local name must be quoted
// (see sys/win/command.c)
@ -225,68 +306,110 @@ adb_exec_install(const char *serial, const char *local) {
#endif
const char *const adb_cmd[] = {"install", "-r", local};
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags);
#ifdef __WINDOWS__
free((void *) local);
#endif
return pid;
}
static sc_pid
adb_exec_get_serialno(sc_pipe *pout) {
const char *const adb_cmd[] = {"get-serialno"};
return adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), pout);
return process_check_success_intr(intr, pid, "adb install", flags);
}
bool
adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
const char *device_socket_name) {
sc_pid pid = adb_exec_forward(serial, local_port, device_socket_name);
return sc_process_check_success_intr(intr, pid, "adb forward", true);
adb_tcpip(struct sc_intr *intr, const char *serial, uint16_t port,
unsigned flags) {
char port_string[5 + 1];
sprintf(port_string, "%" PRIu16, port);
const char *const adb_cmd[] = {"tcpip", port_string};
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags);
return process_check_success_intr(intr, pid, "adb tcpip", flags);
}
bool
adb_forward_remove(struct sc_intr *intr, const char *serial,
uint16_t local_port) {
sc_pid pid = adb_exec_forward_remove(serial, local_port);
return sc_process_check_success_intr(intr, pid, "adb forward --remove",
true);
adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags) {
const char *const adb_cmd[] = {"connect", ip_port};
sc_pipe pout;
sc_pid pid = adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), flags, &pout);
if (pid == SC_PROCESS_NONE) {
LOGE("Could not execute \"adb connect\"");
return false;
}
// "adb connect" always returns successfully (with exit code 0), even in
// case of failure. As a workaround, check if its output starts with
// "connected".
char buf[128];
ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf));
sc_pipe_close(pout);
bool ok = process_check_success_intr(intr, pid, "adb connect", flags);
if (!ok) {
return false;
}
if (r == -1) {
return false;
}
ok = !strncmp("connected", buf, sizeof("connected") - 1);
if (!ok && !(flags & SC_ADB_NO_STDERR)) {
// "adb connect" also prints errors to stdout. Since we capture it,
// re-print the error to stderr.
sc_str_truncate(buf, r, "\r\n");
fprintf(stderr, "%s\n", buf);
}
return ok;
}
bool
adb_reverse(struct sc_intr *intr, const char *serial,
const char *device_socket_name, uint16_t local_port) {
sc_pid pid = adb_exec_reverse(serial, device_socket_name, local_port);
return sc_process_check_success_intr(intr, pid, "adb reverse", true);
}
adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags) {
const char *const adb_cmd[] = {"disconnect", ip_port};
size_t len = ip_port ? ARRAY_LEN(adb_cmd)
: ARRAY_LEN(adb_cmd) - 1;
bool
adb_reverse_remove(struct sc_intr *intr, const char *serial,
const char *device_socket_name) {
sc_pid pid = adb_exec_reverse_remove(serial, device_socket_name);
return sc_process_check_success_intr(intr, pid, "adb reverse --remove",
true);
}
bool
adb_push(struct sc_intr *intr, const char *serial, const char *local,
const char *remote) {
sc_pid pid = adb_exec_push(serial, local, remote);
return sc_process_check_success_intr(intr, pid, "adb push", true);
}
bool
adb_install(struct sc_intr *intr, const char *serial, const char *local) {
sc_pid pid = adb_exec_install(serial, local);
return sc_process_check_success_intr(intr, pid, "adb install", true);
sc_pid pid = adb_execute(NULL, adb_cmd, len, flags);
return process_check_success_intr(intr, pid, "adb disconnect", flags);
}
char *
adb_get_serialno(struct sc_intr *intr) {
adb_getprop(struct sc_intr *intr, const char *serial, const char *prop,
unsigned flags) {
const char *const adb_cmd[] = {"shell", "getprop", prop};
sc_pipe pout;
sc_pid pid = adb_exec_get_serialno(&pout);
sc_pid pid =
adb_execute_p(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags, &pout);
if (pid == SC_PROCESS_NONE) {
LOGE("Could not execute \"adb getprop\"");
return NULL;
}
char buf[128];
ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf));
sc_pipe_close(pout);
bool ok = process_check_success_intr(intr, pid, "adb getprop", flags);
if (!ok) {
return NULL;
}
if (r == -1) {
return NULL;
}
sc_str_truncate(buf, r, " \r\n");
return strdup(buf);
}
char *
adb_get_serialno(struct sc_intr *intr, unsigned flags) {
const char *const adb_cmd[] = {"get-serialno"};
sc_pipe pout;
sc_pid pid = adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), flags, &pout);
if (pid == SC_PROCESS_NONE) {
LOGE("Could not execute \"adb get-serialno\"");
return NULL;
@ -296,13 +419,53 @@ adb_get_serialno(struct sc_intr *intr) {
ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf));
sc_pipe_close(pout);
bool ok =
sc_process_check_success_intr(intr, pid, "adb get-serialno", true);
bool ok = process_check_success_intr(intr, pid, "adb get-serialno", flags);
if (!ok) {
return NULL;
}
if (r == -1) {
return false;
}
sc_str_truncate(buf, r, " \r\n");
return strdup(buf);
}
char *
adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) {
const char *const cmd[] = {"shell", "ip", "route"};
sc_pipe pout;
sc_pid pid = adb_execute_p(serial, cmd, ARRAY_LEN(cmd), flags, &pout);
if (pid == SC_PROCESS_NONE) {
LOGD("Could not execute \"ip route\"");
return NULL;
}
// "adb shell ip route" output should contain only a few lines
char buf[1024];
ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf));
sc_pipe_close(pout);
bool ok = process_check_success_intr(intr, pid, "ip route", flags);
if (!ok) {
return NULL;
}
if (r == -1) {
return false;
}
assert((size_t) r <= sizeof(buf));
if (r == sizeof(buf) && buf[sizeof(buf) - 1] != '\0') {
// The implementation assumes that the output of "ip route" fits in the
// buffer in a single pass
LOGW("Result of \"ip route\" does not fit in 1Kb. "
"Please report an issue.\n");
return NULL;
}
return sc_adb_parse_device_ip_from_output(buf, r);
}

View File

@ -8,31 +8,71 @@
#include "util/intr.h"
#define SC_ADB_NO_STDOUT (1 << 0)
#define SC_ADB_NO_STDERR (1 << 1)
#define SC_ADB_NO_LOGERR (1 << 2)
#define SC_ADB_SILENT (SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR)
sc_pid
adb_execute(const char *serial, const char *const adb_cmd[], size_t len);
adb_execute(const char *serial, const char *const adb_cmd[], size_t len,
unsigned flags);
bool
adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
const char *device_socket_name);
const char *device_socket_name, unsigned flags);
bool
adb_forward_remove(struct sc_intr *intr, const char *serial,
uint16_t local_port);
uint16_t local_port, unsigned flags);
bool
adb_reverse(struct sc_intr *intr, const char *serial,
const char *device_socket_name, uint16_t local_port);
const char *device_socket_name, uint16_t local_port,
unsigned flags);
bool
adb_reverse_remove(struct sc_intr *intr, const char *serial,
const char *device_socket_name);
const char *device_socket_name, unsigned flags);
bool
adb_push(struct sc_intr *intr, const char *serial, const char *local,
const char *remote);
const char *remote, unsigned flags);
bool
adb_install(struct sc_intr *intr, const char *serial, const char *local);
adb_install(struct sc_intr *intr, const char *serial, const char *local,
unsigned flags);
/**
* Execute `adb tcpip <port>`
*/
bool
adb_tcpip(struct sc_intr *intr, const char *serial, uint16_t port,
unsigned flags);
/**
* Execute `adb connect <ip_port>`
*
* `ip_port` may not be NULL.
*/
bool
adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags);
/**
* Execute `adb disconnect [<ip_port>]`
*
* If `ip_port` is NULL, execute `adb disconnect`.
* Otherwise, execute `adb disconnect <ip_port>`.
*/
bool
adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags);
/**
* Execute `adb getprop <prop>`
*/
char *
adb_getprop(struct sc_intr *intr, const char *serial, const char *prop,
unsigned flags);
/**
* Execute `adb get-serialno`
@ -40,6 +80,15 @@ adb_install(struct sc_intr *intr, const char *serial, const char *local);
* Return the result, to be freed by the caller, or NULL on error.
*/
char *
adb_get_serialno(struct sc_intr *intr);
adb_get_serialno(struct sc_intr *intr, unsigned flags);
/**
* Attempt to retrieve the device IP
*
* Return the IP as a string of the form "xxx.xxx.xxx.xxx", to be freed by the
* caller, or NULL on error.
*/
char *
adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags);
#endif

65
app/src/adb_parser.c Normal file
View File

@ -0,0 +1,65 @@
#include "adb_parser.h"
#include <assert.h>
#include <string.h>
#include "util/log.h"
#include "util/str.h"
static char *
sc_adb_parse_device_ip_from_line(char *line, size_t len) {
// One line from "ip route" looks lile:
// "192.168.1.0/24 dev wlan0 proto kernel scope link src 192.168.1.x"
// Get the location of the device name (index of "wlan0" in the example)
ssize_t idx_dev_name = sc_str_index_of_column(line, 2, " ");
if (idx_dev_name == -1) {
return NULL;
}
// Get the location of the ip address (column 8, but column 6 if we start
// from column 2). Must be computed before truncating individual columns.
ssize_t idx_ip = sc_str_index_of_column(&line[idx_dev_name], 6, " ");
if (idx_ip == -1) {
return NULL;
}
// idx_ip is searched from &line[idx_dev_name]
idx_ip += idx_dev_name;
char *dev_name = &line[idx_dev_name];
sc_str_truncate(dev_name, len - idx_dev_name + 1, " \t");
char *ip = &line[idx_ip];
sc_str_truncate(ip, len - idx_ip + 1, " \t");
// Only consider lines where the device name starts with "wlan"
if (strncmp(dev_name, "wlan", sizeof("wlan") - 1)) {
LOGD("Device ip lookup: ignoring %s (%s)", ip, dev_name);
return NULL;
}
return strdup(ip);
}
char *
sc_adb_parse_device_ip_from_output(char *buf, size_t buf_len) {
size_t idx_line = 0;
while (idx_line < buf_len && buf[idx_line] != '\0') {
char *line = &buf[idx_line];
size_t len = sc_str_truncate(line, buf_len - idx_line, "\n");
// The same, but without any trailing '\r'
size_t line_len = sc_str_remove_trailing_cr(line, len);
char *ip = sc_adb_parse_device_ip_from_line(line, line_len);
if (ip) {
// Found
return ip;
}
// The next line starts after the '\n' (replaced by `\0`)
idx_line += len + 1;
}
return NULL;
}

14
app/src/adb_parser.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef SC_ADB_PARSER_H
#define SC_ADB_PARSER_H
#include "common.h"
#include "stddef.h"
/**
* Parse the ip from the output of `adb shell ip route`
*/
char *
sc_adb_parse_device_ip_from_output(char *buf, size_t buf_len);
#endif

View File

@ -20,7 +20,8 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
struct sc_port_range port_range) {
uint16_t port = port_range.first;
for (;;) {
if (!adb_reverse(intr, serial, SC_SOCKET_NAME, port)) {
if (!adb_reverse(intr, serial, SC_SOCKET_NAME, port,
SC_ADB_NO_STDOUT)) {
// the command itself failed, it will fail on any port
return false;
}
@ -51,7 +52,8 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
}
// failure, disable tunnel and try another port
if (!adb_reverse_remove(intr, serial, SC_SOCKET_NAME)) {
if (!adb_reverse_remove(intr, serial, SC_SOCKET_NAME,
SC_ADB_NO_STDOUT)) {
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
}
@ -81,7 +83,7 @@ enable_tunnel_forward_any_port(struct sc_adb_tunnel *tunnel,
uint16_t port = port_range.first;
for (;;) {
if (adb_forward(intr, serial, port, SC_SOCKET_NAME)) {
if (adb_forward(intr, serial, port, SC_SOCKET_NAME, SC_ADB_NO_STDOUT)) {
// success
tunnel->local_port = port;
tunnel->enabled = true;
@ -146,9 +148,11 @@ sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
bool ret;
if (tunnel->forward) {
ret = adb_forward_remove(intr, serial, tunnel->local_port);
ret = adb_forward_remove(intr, serial, tunnel->local_port,
SC_ADB_NO_STDOUT);
} else {
ret = adb_reverse_remove(intr, serial, SC_SOCKET_NAME);
ret = adb_reverse_remove(intr, serial, SC_SOCKET_NAME,
SC_ADB_NO_STDOUT);
assert(tunnel->server_socket != SC_SOCKET_NONE);
if (!net_close(tunnel->server_socket)) {

View File

@ -4,6 +4,7 @@
#include <stdio.h>
#include "aoa_hid.h"
#include "util/log.h"
// See <https://source.android.com/devices/accessories/aoa2#hid-support>.
#define ACCESSORY_REGISTER_HID 54
@ -20,6 +21,7 @@ sc_hid_event_log(const struct sc_hid_event *event) {
unsigned buffer_size = event->size * 3 + 1;
char *buffer = malloc(buffer_size);
if (!buffer) {
LOG_OOM();
return;
}
for (unsigned i = 0; i < event->size; ++i) {
@ -35,7 +37,7 @@ sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id,
hid_event->accessory_id = accessory_id;
hid_event->buffer = buffer;
hid_event->size = buffer_size;
hid_event->delay = 0;
hid_event->ack_to_wait = SC_SEQUENCE_INVALID;
}
void
@ -118,7 +120,10 @@ sc_aoa_open_usb_handle(libusb_device *device, libusb_device_handle **handle) {
}
bool
sc_aoa_init(struct sc_aoa *aoa, const char *serial) {
sc_aoa_init(struct sc_aoa *aoa, const char *serial,
struct sc_acksync *acksync) {
assert(acksync);
cbuf_init(&aoa->queue);
if (!sc_mutex_init(&aoa->mutex)) {
@ -155,6 +160,7 @@ sc_aoa_init(struct sc_aoa *aoa, const char *serial) {
}
aoa->stopped = false;
aoa->acksync = acksync;
return true;
}
@ -332,23 +338,28 @@ run_aoa_thread(void *data) {
assert(non_empty);
(void) non_empty;
assert(event.delay >= 0);
if (event.delay) {
// Wait during the specified delay before injecting the HID event
sc_tick deadline = sc_tick_now() + event.delay;
bool timed_out = false;
while (!aoa->stopped && !timed_out) {
timed_out = !sc_cond_timedwait(&aoa->event_cond, &aoa->mutex,
deadline);
}
if (aoa->stopped) {
sc_mutex_unlock(&aoa->mutex);
uint64_t ack_to_wait = event.ack_to_wait;
sc_mutex_unlock(&aoa->mutex);
if (ack_to_wait != SC_SEQUENCE_INVALID) {
LOGD("Waiting ack from server sequence=%" PRIu64_, ack_to_wait);
// Do not block the loop indefinitely if the ack never comes (it
// should never happen)
sc_tick deadline = sc_tick_now() + SC_TICK_FROM_MS(500);
enum sc_acksync_wait_result result =
sc_acksync_wait(aoa->acksync, ack_to_wait, deadline);
if (result == SC_ACKSYNC_WAIT_TIMEOUT) {
LOGW("Ack not received after 500ms, discarding HID event");
sc_hid_event_destroy(&event);
continue;
} else if (result == SC_ACKSYNC_WAIT_INTR) {
// stopped
sc_hid_event_destroy(&event);
break;
}
}
sc_mutex_unlock(&aoa->mutex);
bool ok = sc_aoa_send_hid_event(aoa, &event);
sc_hid_event_destroy(&event);
if (!ok) {
@ -362,7 +373,7 @@ bool
sc_aoa_start(struct sc_aoa *aoa) {
LOGD("Starting AOA thread");
bool ok = sc_thread_create(&aoa->thread, run_aoa_thread, "aoa_thread", aoa);
bool ok = sc_thread_create(&aoa->thread, run_aoa_thread, "scrcpy-aoa", aoa);
if (!ok) {
LOGC("Could not start AOA thread");
return false;
@ -377,6 +388,8 @@ sc_aoa_stop(struct sc_aoa *aoa) {
aoa->stopped = true;
sc_cond_signal(&aoa->event_cond);
sc_mutex_unlock(&aoa->mutex);
sc_acksync_interrupt(aoa->acksync);
}
void

View File

@ -6,6 +6,7 @@
#include <libusb-1.0/libusb.h>
#include "util/acksync.h"
#include "util/cbuf.h"
#include "util/thread.h"
#include "util/tick.h"
@ -14,7 +15,7 @@ struct sc_hid_event {
uint16_t accessory_id;
unsigned char *buffer;
uint16_t size;
sc_tick delay;
uint64_t ack_to_wait;
};
// Takes ownership of buffer
@ -36,10 +37,12 @@ struct sc_aoa {
sc_cond event_cond;
bool stopped;
struct sc_hid_event_queue queue;
struct sc_acksync *acksync;
};
bool
sc_aoa_init(struct sc_aoa *aoa, const char *serial);
sc_aoa_init(struct sc_aoa *aoa, const char *serial, struct sc_acksync *acksync);
void
sc_aoa_destroy(struct sc_aoa *aoa);

View File

@ -9,6 +9,7 @@
#include "options.h"
#include "util/log.h"
#include "util/net.h"
#include "util/str.h"
#include "util/strbuf.h"
#include "util/term.h"
@ -46,6 +47,11 @@
#define OPT_V4L2_SINK 1027
#define OPT_DISPLAY_BUFFER 1028
#define OPT_V4L2_BUFFER 1029
#define OPT_TUNNEL_HOST 1030
#define OPT_TUNNEL_PORT 1031
#define OPT_NO_CLIPBOARD_AUTOSYNC 1032
#define OPT_TCPIP 1033
#define OPT_RAW_KEY_EVENTS 1034
struct sc_option {
char shortopt;
@ -65,6 +71,11 @@ struct sc_shortcut {
const char *text;
};
struct sc_envvar {
const char *name;
const char *text;
};
struct sc_getopt_adapter {
char *optstring;
struct option *longopts;
@ -160,7 +171,15 @@ static const struct sc_option options[] = {
"generate non-ASCII characters, contrary to the default "
"injection method.\n"
"It may only work over USB, and is currently only supported "
"on Linux.",
"on Linux.\n"
"The keyboard layout must be configured (once and for all) on "
"the device, via Settings -> System -> Languages and input -> "
"Physical keyboard. This settings page can be started "
"directly: `adb shell am start -a "
"android.settings.HARD_KEYBOARD_SETTINGS`.\n"
"However, the option is only available when the HID keyboard "
"is enabled (or a physical keyboard is connected).\n"
"Also see --hid-mouse.",
},
{
.shortopt = 'h',
@ -196,6 +215,18 @@ static const struct sc_option options[] = {
.text = "Limit the frame rate of screen capture (officially supported "
"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',
.longopt = "max-size",
@ -205,6 +236,15 @@ static const struct sc_option options[] = {
"is preserved.\n"
"Default is 0 (unlimited).",
},
{
.longopt_id = OPT_NO_CLIPBOARD_AUTOSYNC,
.longopt = "no-clipboard-autosync",
.text = "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.\n"
"This option disables this automatic synchronization."
},
{
.shortopt = 'n',
.longopt = "no-control",
@ -213,11 +253,8 @@ static const struct sc_option options[] = {
{
.shortopt = 'N',
.longopt = "no-display",
.text = "Do not display device (only when screen recording "
#ifdef HAVE_V4L2
"or V4L2 sink "
#endif
"is enabled).",
.text = "Do not display device (only when screen recording or V4L2 "
"sink is enabled).",
},
{
.longopt_id = OPT_NO_KEY_REPEAT,
@ -261,6 +298,11 @@ static const struct sc_option options[] = {
"drag & drop. It is passed as is to \"adb push\".\n"
"Default is \"/sdcard/Download/\".",
},
{
.longopt_id = OPT_RAW_KEY_EVENTS,
.longopt = "raw-key-events",
.text = "Inject key events for all input keys, and ignore text events."
},
{
.shortopt = 'r',
.longopt = "record",
@ -330,14 +372,33 @@ static const struct sc_option options[] = {
"on exit.\n"
"It only shows physical touches (not clicks from scrcpy).",
},
#ifdef HAVE_V4L2
{
.longopt_id = OPT_TUNNEL_HOST,
.longopt = "tunnel-host",
.argdesc = "ip",
.text = "Set the IP address of the adb tunnel to reach the scrcpy "
"server. This option automatically enables "
"--force-adb-forward.\n"
"Default is localhost.",
},
{
.longopt_id = OPT_TUNNEL_PORT,
.longopt = "tunnel-port",
.argdesc = "port",
.text = "Set the TCP port of the adb tunnel to reach the scrcpy "
"server. This option automatically enables "
"--force-adb-forward.\n"
"Default is 0 (not forced): the local port used for "
"establishing the tunnel will be used.",
},
{
.longopt_id = OPT_V4L2_SINK,
.longopt = "v4l2-sink",
.argdesc = "/dev/videoN",
.text = "Output to v4l2loopback device.\n"
"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,
@ -347,9 +408,9 @@ static const struct sc_option options[] = {
"frames. This increases latency to compensate for jitter.\n"
"This option is similar to --display-buffer, but specific to "
"V4L2 sink.\n"
"Default is 0 (no buffering).",
"Default is 0 (no buffering).\n"
"This option is only available on Linux.",
},
#endif
{
.shortopt = 'V',
.longopt = "verbosity",
@ -372,6 +433,20 @@ static const struct sc_option options[] = {
.text = "Keep the device on while scrcpy is running, when the device "
"is plugged in.",
},
{
.longopt_id = OPT_TCPIP,
.longopt = "tcpip",
.argdesc = "ip[:port]",
.optional_arg = true,
.text = "Configure and reconnect the device over TCP/IP.\n"
"If a destination address is provided, then scrcpy connects to "
"this address before starting. The device must listen on the "
"given TCP port (default is 5555).\n"
"If no destination address is provided, then scrcpy attempts "
"to find the IP address of the current device (typically "
"connected over USB), enables TCP/IP mode, then connects to "
"this address before starting.",
},
{
.longopt_id = OPT_WINDOW_BORDERLESS,
.longopt = "window-borderless",
@ -525,6 +600,21 @@ static const struct sc_shortcut shortcuts[] = {
},
};
static const struct sc_envvar envvars[] = {
{
.name = "ADB",
.text = "Path to adb executable",
},
{
.name = "SCRCPY_ICON_PATH",
.text = "Path to the program icon",
},
{
.name = "SCRCPY_SERVER_PATH",
.text = "Path to the server binary",
}
};
static char *
sc_getopt_adapter_create_optstring(void) {
struct sc_strbuf buf;
@ -563,6 +653,7 @@ sc_getopt_adapter_create_longopts(void) {
struct option *longopts =
malloc((ARRAY_LEN(options) + 1) * sizeof(*longopts));
if (!longopts) {
LOG_OOM();
return NULL;
}
@ -617,7 +708,7 @@ sc_getopt_adapter_init(struct sc_getopt_adapter *adapter) {
}
return true;
};
}
static void
sc_getopt_adapter_destroy(struct sc_getopt_adapter *adapter) {
@ -715,7 +806,7 @@ print_shortcuts_intro(unsigned cols) {
return;
}
printf("%s\n", intro);
printf("\n%s\n", intro);
free(intro);
}
@ -743,6 +834,23 @@ print_shortcut(const struct sc_shortcut *shortcut, unsigned cols) {
free(text);
}
static void
print_envvar(const struct sc_envvar *envvar, unsigned cols) {
assert(cols > 8); // sc_str_wrap_lines() requires indent < columns
assert(envvar->name);
assert(envvar->text);
printf("\n %s\n", envvar->name);
char *text = sc_str_wrap_lines(envvar->text, cols, 8);
if (!text) {
printf("<ERROR>\n");
return;
}
printf("%s\n", text);
free(text);
}
void
scrcpy_print_usage(const char *arg0) {
#define SC_TERM_COLS_DEFAULT 80
@ -770,11 +878,17 @@ scrcpy_print_usage(const char *arg0) {
}
// Print shortcuts section
printf("\nShortcuts:\n\n");
printf("\nShortcuts:\n");
print_shortcuts_intro(cols);
for (size_t i = 0; i < ARRAY_LEN(shortcuts); ++i) {
print_shortcut(&shortcuts[i], cols);
}
// Print environment variables section
printf("\nEnvironment variables:\n");
for (size_t i = 0; i < ARRAY_LEN(envvars); ++i) {
print_envvar(&envvars[i], cols);
}
}
static bool
@ -1017,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")
// 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
parse_shortcut_mods_item(const char *item, size_t len) {
unsigned mod = 0;
@ -1035,17 +1149,17 @@ parse_shortcut_mods_item(const char *item, size_t len) {
((sizeof(literal)-1 == len) && !memcmp(literal, s, len))
if (STREQ("lctrl", item, key_len)) {
mod |= SC_MOD_LCTRL;
mod |= SC_SHORTCUT_MOD_LCTRL;
} else if (STREQ("rctrl", item, key_len)) {
mod |= SC_MOD_RCTRL;
mod |= SC_SHORTCUT_MOD_RCTRL;
} else if (STREQ("lalt", item, key_len)) {
mod |= SC_MOD_LALT;
mod |= SC_SHORTCUT_MOD_LALT;
} else if (STREQ("ralt", item, key_len)) {
mod |= SC_MOD_RALT;
mod |= SC_SHORTCUT_MOD_RALT;
} else if (STREQ("lsuper", item, key_len)) {
mod |= SC_MOD_LSUPER;
mod |= SC_SHORTCUT_MOD_LSUPER;
} else if (STREQ("rsuper", item, key_len)) {
mod |= SC_MOD_RSUPER;
mod |= SC_SHORTCUT_MOD_RSUPER;
} else {
LOGE("Unknown modifier key: %.*s "
"(must be one of: lctrl, rctrl, lalt, ralt, lsuper, rsuper)",
@ -1127,6 +1241,21 @@ parse_record_format(const char *optarg, enum sc_record_format *format) {
return false;
}
static bool
parse_ip(const char *optarg, uint32_t *ipv4) {
return net_parse_ipv4(optarg, ipv4);
}
static bool
parse_port(const char *optarg, uint16_t *port) {
long value;
if (!parse_integer_arg(optarg, &value, false, 0, 0xFFFF, "port")) {
return false;
}
*port = (uint16_t) value;
return true;
}
static enum sc_record_format
guess_record_format(const char *filename) {
size_t len = strlen(filename);
@ -1181,7 +1310,13 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
args->help = true;
break;
case 'K':
#ifdef HAVE_AOA_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;
case OPT_MAX_FPS:
if (!parse_max_fps(optarg, &opts->max_fps)) {
@ -1193,12 +1328,31 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false;
}
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:
if (!parse_lock_video_orientation(optarg,
&opts->lock_video_orientation)) {
return false;
}
break;
case OPT_TUNNEL_HOST:
if (!parse_ip(optarg, &opts->tunnel_host)) {
return false;
}
break;
case OPT_TUNNEL_PORT:
if (!parse_port(optarg, &opts->tunnel_port)) {
return false;
}
break;
case 'n':
opts->control = false;
break;
@ -1270,7 +1424,18 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
opts->push_target = optarg;
break;
case OPT_PREFER_TEXT:
opts->prefer_text = true;
if (opts->key_inject_mode != SC_KEY_INJECT_MODE_MIXED) {
LOGE("--prefer-text is incompatible with --raw-key-events");
return false;
}
opts->key_inject_mode = SC_KEY_INJECT_MODE_TEXT;
break;
case OPT_RAW_KEY_EVENTS:
if (opts->key_inject_mode != SC_KEY_INJECT_MODE_MIXED) {
LOGE("--prefer-text is incompatible with --raw-key-events");
return false;
}
opts->key_inject_mode = SC_KEY_INJECT_MODE_RAW;
break;
case OPT_ROTATION:
if (!parse_rotation(optarg, &opts->rotation)) {
@ -1317,22 +1482,51 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false;
}
break;
#ifdef HAVE_V4L2
case OPT_NO_CLIPBOARD_AUTOSYNC:
opts->clipboard_autosync = false;
break;
case OPT_TCPIP:
opts->tcpip = true;
opts->tcpip_dst = optarg;
break;
case OPT_V4L2_SINK:
#ifdef HAVE_V4L2
opts->v4l2_device = optarg;
#else
LOGE("V4L2 (--v4l2-sink) is only available on Linux.");
return false;
#endif
break;
case OPT_V4L2_BUFFER:
#ifdef HAVE_V4L2
if (!parse_buffering_time(optarg, &opts->v4l2_buffer)) {
return false;
}
break;
#else
LOGE("V4L2 (--v4l2-buffer) is only available on Linux.");
return false;
#endif
break;
default:
// getopt prints the error message on stderr
return false;
}
}
int index = optind;
if (index < argc) {
LOGE("Unexpected additional argument: %s", argv[index]);
return false;
}
// If a TCP/IP address is provided, then tcpip must be enabled
assert(opts->tcpip || !opts->tcpip_dst);
if (opts->serial && opts->tcpip_dst) {
LOGE("Incompatible options: -s/--serial and --tcpip with an argument");
return false;
}
#ifdef HAVE_V4L2
if (!opts->display && !opts->record_filename && !opts->v4l2_device) {
LOGE("-N/--no-display requires either screen recording (-r/--record)"
@ -1358,10 +1552,10 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
}
#endif
int index = optind;
if (index < argc) {
LOGE("Unexpected additional argument: %s", argv[index]);
return false;
if ((opts->tunnel_host || opts->tunnel_port) && !opts->force_adb_forward) {
LOGI("Tunnel host/port is set, "
"--force-adb-forward automatically enabled.");
opts->force_adb_forward = true;
}
if (opts->record_format && !opts->record_filename) {

View File

@ -7,6 +7,7 @@
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
#define MIN(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) \
((type *) (((char *) (ptr)) - offsetof(type, member)))

View File

@ -2,6 +2,12 @@
#include "config.h"
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#ifndef HAVE_STRDUP
char *strdup(const char *s) {
size_t size = strlen(s) + 1;
@ -12,3 +18,36 @@ char *strdup(const char *s) {
return dup;
}
#endif
#ifndef HAVE_ASPRINTF
int asprintf(char **strp, const char *fmt, ...) {
va_list va;
va_start(va, fmt);
int ret = vasprintf(strp, fmt, va);
va_end(va);
return ret;
}
#endif
#ifndef HAVE_VASPRINTF
int vasprintf(char **strp, const char *fmt, va_list ap) {
va_list va;
va_copy(va, ap);
int len = vsnprintf(NULL, 0, fmt, va);
va_end(va);
char *str = malloc(len + 1);
if (!str) {
return -1;
}
va_copy(va, ap);
int len2 = vsnprintf(str, len + 1, fmt, va);
(void) len2;
assert(len == len2);
va_end(va);
*strp = str;
return len;
}
#endif

View File

@ -1,16 +1,17 @@
#ifndef COMPAT_H
#define COMPAT_H
#define _POSIX_C_SOURCE 200809L
#define _XOPEN_SOURCE 700
#define _GNU_SOURCE
#ifdef __APPLE__
# define _DARWIN_C_SOURCE
#endif
#include "config.h"
#include <libavformat/version.h>
#include <SDL2/SDL_version.h>
#ifndef __WIN32
# define PRIu64_ PRIu64
#else
# define PRIu64_ "I64u" // Windows...
#endif
// In ffmpeg/doc/APIchanges:
// 2018-02-06 - 0694d87024 - lavf 58.9.100 - avformat.h
// Deprecate use of av_register_input_format(), av_register_output_format(),
@ -34,15 +35,6 @@
# define SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL
#endif
#if SDL_VERSION_ATLEAST(2, 0, 5)
// <https://wiki.libsdl.org/SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH>
# define SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH
// <https://wiki.libsdl.org/SDL_GetDisplayUsableBounds>
# define SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS
// <https://wiki.libsdl.org/SDL_WindowFlags>
# define SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
#endif
#if SDL_VERSION_ATLEAST(2, 0, 6)
// <https://github.com/libsdl-org/SDL/commit/d7a318de563125e5bb465b1000d6bc9576fbc6fc>
# define SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
@ -57,4 +49,12 @@
char *strdup(const char *s);
#endif
#ifndef HAVE_ASPRINTF
int asprintf(char **strp, const char *fmt, ...);
#endif
#ifndef HAVE_VASPRINTF
int vasprintf(char **strp, const char *fmt, va_list ap);
#endif
#endif

View File

@ -41,7 +41,7 @@ static const char *const android_motionevent_action_labels[] = {
"pointer-up",
"hover-move",
"scroll",
"hover-enter"
"hover-enter",
"hover-exit",
"btn-press",
"btn-release",
@ -55,6 +55,12 @@ static const char *const screen_power_mode_labels[] = {
"suspend",
};
static const char *const copy_key_labels[] = {
"none",
"copy",
"cut",
};
static void
write_position(uint8_t *buf, const struct sc_position *position) {
buffer_write32be(&buf[0], position->point.x);
@ -63,7 +69,7 @@ write_position(uint8_t *buf, const struct sc_position *position) {
buffer_write16be(&buf[10], position->screen_size.height);
}
// write length (2 bytes) + string (non nul-terminated)
// write length (4 bytes) + string (non null-terminated)
static size_t
write_string(const char *utf8, size_t max_len, unsigned char *buf) {
size_t len = sc_str_utf8_truncation_index(utf8, max_len);
@ -83,7 +89,7 @@ to_fixed_point_16(float f) {
}
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;
switch (msg->type) {
case CONTROL_MSG_TYPE_INJECT_KEYCODE:
@ -113,16 +119,21 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
(uint32_t) msg->inject_scroll_event.hscroll);
buffer_write32be(&buf[17],
(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:
buf[1] = msg->inject_keycode.action;
return 2;
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
buf[1] = msg->get_clipboard.copy_key;
return 2;
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
buf[1] = !!msg->set_clipboard.paste;
buffer_write64be(&buf[1], msg->set_clipboard.sequence);
buf[9] = !!msg->set_clipboard.paste;
size_t len = write_string(msg->set_clipboard.text,
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
&buf[2]);
return 2 + len;
&buf[10]);
return 10 + len;
}
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
buf[1] = msg->set_screen_power_mode.mode;
@ -130,7 +141,6 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
// no additional data
return 1;
@ -141,7 +151,7 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
}
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__)
switch (msg->type) {
case CONTROL_MSG_TYPE_INJECT_KEYCODE:
@ -170,11 +180,6 @@ control_msg_log(const struct control_msg *msg) {
(long) msg->inject_touch_event.buttons);
} else {
// numeric pointer id
#ifndef __WIN32
# define PRIu64_ PRIu64
#else
# define PRIu64_ "I64u" // Windows...
#endif
LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%"
PRIi32 " pressure=%g buttons=%06lx",
id,
@ -188,19 +193,25 @@ control_msg_log(const struct control_msg *msg) {
}
case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
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.y,
msg->inject_scroll_event.hscroll,
msg->inject_scroll_event.vscroll);
msg->inject_scroll_event.vscroll,
(long) msg->inject_scroll_event.buttons);
break;
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
LOG_CMSG("back-or-screen-on %s",
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action));
break;
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
LOG_CMSG("get clipboard copy_key=%s",
copy_key_labels[msg->get_clipboard.copy_key]);
break;
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
LOG_CMSG("clipboard %s \"%s\"",
msg->set_clipboard.paste ? "paste" : "copy",
LOG_CMSG("clipboard %" PRIu64_ " %s \"%s\"",
msg->set_clipboard.sequence,
msg->set_clipboard.paste ? "paste" : "nopaste",
msg->set_clipboard.text);
break;
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
@ -216,9 +227,6 @@ control_msg_log(const struct control_msg *msg) {
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
LOG_CMSG("collapse panels");
break;
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
LOG_CMSG("get clipboard");
break;
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
LOG_CMSG("rotate device");
break;
@ -229,7 +237,7 @@ control_msg_log(const struct control_msg *msg) {
}
void
control_msg_destroy(struct control_msg *msg) {
sc_control_msg_destroy(struct sc_control_msg *msg) {
switch (msg->type) {
case CONTROL_MSG_TYPE_INJECT_TEXT:
free(msg->inject_text.text);

View File

@ -14,13 +14,13 @@
#define CONTROL_MSG_MAX_SIZE (1 << 18) // 256k
#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
// type: 1 byte; paste flag: 1 byte; length: 4 bytes
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 6)
// type: 1 byte; sequence: 8 bytes; paste flag: 1 byte; length: 4 bytes
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 14)
#define POINTER_ID_MOUSE UINT64_C(-1)
#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_TEXT,
CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
@ -41,8 +41,14 @@ enum screen_power_mode {
SCREEN_POWER_MODE_NORMAL = 2,
};
struct control_msg {
enum control_msg_type type;
enum get_clipboard_copy_key {
GET_CLIPBOARD_COPY_KEY_NONE,
GET_CLIPBOARD_COPY_KEY_COPY,
GET_CLIPBOARD_COPY_KEY_CUT,
};
struct sc_control_msg {
enum sc_control_msg_type type;
union {
struct {
enum android_keyevent_action action;
@ -64,12 +70,17 @@ struct control_msg {
struct sc_position position;
int32_t hscroll;
int32_t vscroll;
enum android_motionevent_buttons buttons;
} inject_scroll_event;
struct {
enum android_keyevent_action action; // action for the BACK key
// screen may only be turned on on ACTION_DOWN
} back_or_screen_on;
struct {
enum get_clipboard_copy_key copy_key;
} get_clipboard;
struct {
uint64_t sequence;
char *text; // owned, to be freed by free()
bool paste;
} set_clipboard;
@ -82,12 +93,12 @@ struct control_msg {
// buf size must be at least CONTROL_MSG_MAX_SIZE
// return the number of bytes written
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
control_msg_log(const struct control_msg *msg);
sc_control_msg_log(const struct sc_control_msg *msg);
void
control_msg_destroy(struct control_msg *msg);
sc_control_msg_destroy(struct sc_control_msg *msg);
#endif

View File

@ -5,10 +5,11 @@
#include "util/log.h"
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) {
cbuf_init(&controller->queue);
bool ok = receiver_init(&controller->receiver, control_socket);
bool ok = receiver_init(&controller->receiver, control_socket, acksync);
if (!ok) {
return false;
}
@ -33,23 +34,23 @@ controller_init(struct controller *controller, sc_socket control_socket) {
}
void
controller_destroy(struct controller *controller) {
sc_controller_destroy(struct sc_controller *controller) {
sc_cond_destroy(&controller->msg_cond);
sc_mutex_destroy(&controller->mutex);
struct control_msg msg;
struct sc_control_msg msg;
while (cbuf_take(&controller->queue, &msg)) {
control_msg_destroy(&msg);
sc_control_msg_destroy(&msg);
}
receiver_destroy(&controller->receiver);
}
bool
controller_push_msg(struct controller *controller,
const struct control_msg *msg) {
sc_controller_push_msg(struct sc_controller *controller,
const struct sc_control_msg *msg) {
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
control_msg_log(msg);
sc_control_msg_log(msg);
}
sc_mutex_lock(&controller->mutex);
@ -63,9 +64,10 @@ controller_push_msg(struct controller *controller,
}
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];
size_t length = control_msg_serialize(msg, serialized_msg);
size_t length = sc_control_msg_serialize(msg, serialized_msg);
if (!length) {
return false;
}
@ -76,7 +78,7 @@ process_msg(struct controller *controller, const struct control_msg *msg) {
static int
run_controller(void *data) {
struct controller *controller = data;
struct sc_controller *controller = data;
for (;;) {
sc_mutex_lock(&controller->mutex);
@ -88,14 +90,14 @@ run_controller(void *data) {
sc_mutex_unlock(&controller->mutex);
break;
}
struct control_msg msg;
struct sc_control_msg msg;
bool non_empty = cbuf_take(&controller->queue, &msg);
assert(non_empty);
(void) non_empty;
sc_mutex_unlock(&controller->mutex);
bool ok = process_msg(controller, &msg);
control_msg_destroy(&msg);
sc_control_msg_destroy(&msg);
if (!ok) {
LOGD("Could not write msg to socket");
break;
@ -105,18 +107,18 @@ run_controller(void *data) {
}
bool
controller_start(struct controller *controller) {
sc_controller_start(struct sc_controller *controller) {
LOGD("Starting controller thread");
bool ok = sc_thread_create(&controller->thread, run_controller,
"controller", controller);
"scrcpy-ctl", controller);
if (!ok) {
LOGC("Could not start controller thread");
return false;
}
if (!receiver_start(&controller->receiver)) {
controller_stop(controller);
sc_controller_stop(controller);
sc_thread_join(&controller->thread, NULL);
return false;
}
@ -125,7 +127,7 @@ controller_start(struct controller *controller) {
}
void
controller_stop(struct controller *controller) {
sc_controller_stop(struct sc_controller *controller) {
sc_mutex_lock(&controller->mutex);
controller->stopped = true;
sc_cond_signal(&controller->msg_cond);
@ -133,7 +135,7 @@ controller_stop(struct controller *controller) {
}
void
controller_join(struct controller *controller) {
sc_controller_join(struct sc_controller *controller) {
sc_thread_join(&controller->thread, NULL);
receiver_join(&controller->receiver);
}

View File

@ -7,39 +7,41 @@
#include "control_msg.h"
#include "receiver.h"
#include "util/acksync.h"
#include "util/cbuf.h"
#include "util/net.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_thread thread;
sc_mutex mutex;
sc_cond msg_cond;
bool stopped;
struct control_msg_queue queue;
struct sc_control_msg_queue queue;
struct receiver receiver;
};
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);
void
controller_destroy(struct controller *controller);
sc_controller_destroy(struct sc_controller *controller);
bool
controller_start(struct controller *controller);
sc_controller_start(struct sc_controller *controller);
void
controller_stop(struct controller *controller);
sc_controller_stop(struct sc_controller *controller);
void
controller_join(struct controller *controller);
sc_controller_join(struct sc_controller *controller);
bool
controller_push_msg(struct controller *controller,
const struct control_msg *msg);
sc_controller_push_msg(struct sc_controller *controller,
const struct sc_control_msg *msg);
#endif

View File

@ -42,10 +42,12 @@ static bool
decoder_open(struct decoder *decoder, const AVCodec *codec) {
decoder->codec_ctx = avcodec_alloc_context3(codec);
if (!decoder->codec_ctx) {
LOGC("Could not allocate decoder context");
LOG_OOM();
return false;
}
decoder->codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
if (avcodec_open2(decoder->codec_ctx, codec, NULL) < 0) {
LOGE("Could not open codec");
avcodec_free_context(&decoder->codec_ctx);
@ -54,7 +56,7 @@ decoder_open(struct decoder *decoder, const AVCodec *codec) {
decoder->frame = av_frame_alloc();
if (!decoder->frame) {
LOGE("Could not create decoder frame");
LOG_OOM();
avcodec_close(decoder->codec_ctx);
avcodec_free_context(&decoder->codec_ctx);
return false;

View File

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

View File

@ -1,5 +1,6 @@
#include "device_msg.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
@ -23,7 +24,7 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
}
char *text = malloc(clipboard_len + 1);
if (!text) {
LOGW("Could not allocate text for clipboard");
LOG_OOM();
return -1;
}
if (clipboard_len) {
@ -34,6 +35,11 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
msg->clipboard.text = text;
return 5 + clipboard_len;
}
case DEVICE_MSG_TYPE_ACK_CLIPBOARD: {
uint64_t sequence = buffer_read64be(&buf[1]);
msg->ack_clipboard.sequence = sequence;
return 9;
}
default:
LOGW("Unknown device message type: %d", (int) msg->type);
return -1; // error, we cannot recover

View File

@ -13,6 +13,7 @@
enum device_msg_type {
DEVICE_MSG_TYPE_CLIPBOARD,
DEVICE_MSG_TYPE_ACK_CLIPBOARD,
};
struct device_msg {
@ -21,6 +22,9 @@ struct device_msg {
struct {
char *text; // owned, to be freed by free()
} clipboard;
struct {
uint64_t sequence;
} ack_clipboard;
};
};

View File

@ -34,14 +34,14 @@ file_handler_init(struct file_handler *file_handler, const char *serial,
ok = sc_intr_init(&file_handler->intr);
if (!ok) {
LOGE("Could not create intr");
sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex);
return false;
}
file_handler->serial = strdup(serial);
if (!file_handler->serial) {
LOGE("Could not strdup serial");
LOG_OOM();
sc_intr_destroy(&file_handler->intr);
sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex);
@ -128,7 +128,7 @@ run_file_handler(void *data) {
if (req.action == ACTION_INSTALL_APK) {
LOGI("Installing %s...", req.file);
bool ok = adb_install(intr, serial, req.file);
bool ok = adb_install(intr, serial, req.file, 0);
if (ok) {
LOGI("%s successfully installed", req.file);
} else {
@ -136,7 +136,7 @@ run_file_handler(void *data) {
}
} else {
LOGI("Pushing %s...", req.file);
bool ok = adb_push(intr, serial, req.file, push_target);
bool ok = adb_push(intr, serial, req.file, push_target, 0);
if (ok) {
LOGI("%s successfully pushed to %s", req.file, push_target);
} else {
@ -154,7 +154,7 @@ file_handler_start(struct file_handler *file_handler) {
LOGD("Starting file_handler thread");
bool ok = sc_thread_create(&file_handler->thread, run_file_handler,
"file_handler", file_handler);
"scrcpy-file", file_handler);
if (!ok) {
LOGC("Could not start file_handler thread");
return false;

View File

@ -108,7 +108,7 @@ fps_counter_start(struct fps_counter *counter) {
// same thread, no need to lock
if (!counter->thread_started) {
bool ok = sc_thread_create(&counter->thread, run_fps_counter,
"fps counter", counter);
"scrcpy-fps", counter);
if (!ok) {
LOGE("Could not start FPS counter thread");
return false;

View File

@ -10,11 +10,13 @@ bool
sc_frame_buffer_init(struct sc_frame_buffer *fb) {
fb->pending_frame = av_frame_alloc();
if (!fb->pending_frame) {
LOG_OOM();
return false;
}
fb->tmp_frame = av_frame_alloc();
if (!fb->tmp_frame) {
LOG_OOM();
av_frame_free(&fb->pending_frame);
return false;
}

View File

@ -1,8 +1,8 @@
#include "hid_keyboard.h"
#include <assert.h>
#include <SDL2/SDL_events.h>
#include "input_events.h"
#include "util/log.h"
/** Downcast key processor to hid_keyboard */
@ -126,31 +126,105 @@ static const unsigned char keyboard_report_desc[] = {
0xC0
};
/**
* A keyboard HID event is 8 bytes long:
*
* - byte 0: modifiers (1 flag per modifier key, 8 possible modifier keys)
* - byte 1: reserved (always 0)
* - bytes 2 to 7: pressed keys (6 at most)
*
* 7 6 5 4 3 2 1 0
* +---------------+
* byte 0: |. . . . . . . .| modifiers
* +---------------+
* ^ ^ ^ ^ ^ ^ ^ ^
* | | | | | | | `- left Ctrl
* | | | | | | `--- left Shift
* | | | | | `----- left Alt
* | | | | `------- left Gui
* | | | `--------- right Ctrl
* | | `----------- right Shift
* | `------------- right Alt
* `--------------- right Gui
*
* +---------------+
* byte 1: |0 0 0 0 0 0 0 0| reserved
* +---------------+
*
* +---------------+
* bytes 2 to 7: |. . . . . . . .| scancode of 1st key pressed
* +---------------+
* |. . . . . . . .| scancode of 2nd key pressed
* +---------------+
* |. . . . . . . .| scancode of 3rd key pressed
* +---------------+
* |. . . . . . . .| scancode of 4th key pressed
* +---------------+
* |. . . . . . . .| scancode of 5th key pressed
* +---------------+
* |. . . . . . . .| scancode of 6th key pressed
* +---------------+
*
* If there are less than 6 keys pressed, the last items are set to 0.
* For example, if A and W are pressed:
*
* +---------------+
* bytes 2 to 7: |0 0 0 0 0 1 0 0| A is pressed (scancode = 4)
* +---------------+
* |0 0 0 1 1 0 1 0| W is pressed (scancode = 26)
* +---------------+
* |0 0 0 0 0 0 0 0| ^
* +---------------+ | only 2 keys are pressed, the
* |0 0 0 0 0 0 0 0| | remaining items are set to 0
* +---------------+ |
* |0 0 0 0 0 0 0 0| |
* +---------------+ |
* |0 0 0 0 0 0 0 0| v
* +---------------+
*
* Pressing more than 6 keys is not supported. If this happens (typically,
* never in practice), report a "phantom state":
*
* +---------------+
* bytes 2 to 7: |0 0 0 0 0 0 0 1| ^
* +---------------+ |
* |0 0 0 0 0 0 0 1| | more than 6 keys pressed:
* +---------------+ | the list is filled with a special
* |0 0 0 0 0 0 0 1| | rollover error code (0x01)
* +---------------+ |
* |0 0 0 0 0 0 0 1| |
* +---------------+ |
* |0 0 0 0 0 0 0 1| |
* +---------------+ |
* |0 0 0 0 0 0 0 1| v
* +---------------+
*/
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;
if (mod & KMOD_LCTRL) {
if (mod & SC_MOD_LCTRL) {
modifiers |= HID_MODIFIER_LEFT_CONTROL;
}
if (mod & KMOD_LSHIFT) {
if (mod & SC_MOD_LSHIFT) {
modifiers |= HID_MODIFIER_LEFT_SHIFT;
}
if (mod & KMOD_LALT) {
if (mod & SC_MOD_LALT) {
modifiers |= HID_MODIFIER_LEFT_ALT;
}
if (mod & KMOD_LGUI) {
if (mod & SC_MOD_LGUI) {
modifiers |= HID_MODIFIER_LEFT_GUI;
}
if (mod & KMOD_RCTRL) {
if (mod & SC_MOD_RCTRL) {
modifiers |= HID_MODIFIER_RIGHT_CONTROL;
}
if (mod & KMOD_RSHIFT) {
if (mod & SC_MOD_RSHIFT) {
modifiers |= HID_MODIFIER_RIGHT_SHIFT;
}
if (mod & KMOD_RALT) {
if (mod & SC_MOD_RALT) {
modifiers |= HID_MODIFIER_RIGHT_ALT;
}
if (mod & KMOD_RGUI) {
if (mod & SC_MOD_RGUI) {
modifiers |= HID_MODIFIER_RIGHT_GUI;
}
return modifiers;
@ -160,6 +234,7 @@ static bool
sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) {
unsigned char *buffer = malloc(HID_KEYBOARD_EVENT_SIZE);
if (!buffer) {
LOG_OOM();
return false;
}
@ -173,15 +248,15 @@ sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) {
}
static inline bool
scancode_is_modifier(SDL_Scancode scancode) {
return scancode >= SDL_SCANCODE_LCTRL && scancode <= SDL_SCANCODE_RGUI;
scancode_is_modifier(enum sc_scancode scancode) {
return scancode >= SC_SCANCODE_LCTRL && scancode <= SC_SCANCODE_RGUI;
}
static bool
convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
struct sc_hid_event *hid_event,
const SDL_KeyboardEvent *event) {
SDL_Scancode scancode = event->keysym.scancode;
const struct sc_key_event *event) {
enum sc_scancode scancode = event->scancode;
assert(scancode >= 0);
// SDL also generates events when only modifiers are pressed, we cannot
@ -197,11 +272,11 @@ convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
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) {
// 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,
kb->keys[scancode] ? "true" : "false");
}
@ -216,7 +291,7 @@ convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
// USB HID protocol says that if keys exceeds report count, a
// phantom state should be reported
if (keys_pressed_count >= HID_KEYBOARD_MAX_KEYS) {
// Pantom state:
// Phantom state:
// - Modifiers
// - Reserved
// - ErrorRollOver * HID_MAX_KEYS
@ -231,17 +306,17 @@ convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
end:
LOGV("hid keyboard: key %-4s scancode=%02x (%u) mod=%02x",
event->type == SDL_KEYDOWN ? "down" : "up", event->keysym.scancode,
event->keysym.scancode, modifiers);
event->action == SC_ACTION_DOWN ? "down" : "up", event->scancode,
event->scancode, modifiers);
return true;
}
static bool
push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t sdl_mod) {
bool capslock = sdl_mod & KMOD_CAPS;
bool numlock = sdl_mod & KMOD_NUM;
push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t mods_state) {
bool capslock = mods_state & SC_MOD_CAPS;
bool numlock = mods_state & SC_MOD_NUM;
if (!capslock && !numlock) {
// Nothing to do
return true;
@ -253,8 +328,6 @@ push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t sdl_mod) {
return false;
}
#define SC_SCANCODE_CAPSLOCK SDL_SCANCODE_CAPSLOCK
#define SC_SCANCODE_NUMLOCK SDL_SCANCODE_NUMLOCKCLEAR
unsigned i = 0;
if (capslock) {
hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK;
@ -278,7 +351,8 @@ push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t sdl_mod) {
static void
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) {
if (event->repeat) {
// In USB HID protocol, key repeat is handled by the host (Android), so
// just ignore key repeat here.
@ -293,21 +367,17 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
if (!kb->mod_lock_synchronized) {
// Inject CAPSLOCK and/or NUMLOCK if necessary to synchronize
// 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;
}
}
SDL_Keycode keycode = event->keysym.sym;
bool down = event->type == SDL_KEYDOWN;
bool ctrl = event->keysym.mod & KMOD_CTRL;
bool shift = event->keysym.mod & KMOD_SHIFT;
if (ctrl && !shift && keycode == SDLK_v && down) {
if (ack_to_wait) {
// Ctrl+v is pressed, so clipboard synchronization has been
// requested. Wait a bit so that the clipboard is set before
// injecting Ctrl+v via HID, otherwise it would paste the old
// clipboard content.
hid_event.delay = SC_TICK_FROM_MS(5);
// requested. Wait until clipboard synchronization is acknowledged
// by the server, otherwise it could paste the old clipboard
// content.
hid_event.ack_to_wait = ack_to_wait;
}
if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) {
@ -317,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
sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) {
kb->aoa = aoa;
@ -345,9 +406,15 @@ sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) {
static const struct sc_key_processor_ops ops = {
.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
// events are sent over AOA, so it must wait for clipboard synchronization
// to be acknowledged by the device before injecting Ctrl+v.
kb->key_processor.async_paste = true;
kb->key_processor.ops = &ops;
return true;

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 <stdbool.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/pixdesc.h>
#include <libavutil/pixfmt.h>
@ -31,7 +32,7 @@ get_icon_path(void) {
char *icon_path = strdup(icon_path_env);
#endif
if (!icon_path) {
LOGE("Could not allocate memory");
LOG_OOM();
return NULL;
}
LOGD("Using SCRCPY_ICON_PATH: %s", icon_path);
@ -42,7 +43,7 @@ get_icon_path(void) {
LOGD("Using icon: " SCRCPY_DEFAULT_ICON_PATH);
char *icon_path = strdup(SCRCPY_DEFAULT_ICON_PATH);
if (!icon_path) {
LOGE("Could not allocate memory");
LOG_OOM();
return NULL;
}
#else
@ -63,7 +64,7 @@ decode_image(const char *path) {
AVFormatContext *ctx = avformat_alloc_context();
if (!ctx) {
LOGE("Could not allocate image decoder context");
LOG_OOM();
return NULL;
}
@ -85,7 +86,7 @@ decode_image(const char *path) {
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) {
LOGE("Could not find image decoder");
goto close_input;
@ -93,7 +94,7 @@ decode_image(const char *path) {
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
LOGE("Could not allocate codec context");
LOG_OOM();
goto close_input;
}
@ -109,13 +110,13 @@ decode_image(const char *path) {
AVFrame *frame = av_frame_alloc();
if (!frame) {
LOGE("Could not allocate frame");
LOG_OOM();
goto close_codec;
}
AVPacket *packet = av_packet_alloc();
if (!packet) {
LOGE("Could not allocate packet");
LOG_OOM();
av_frame_free(&frame);
goto close_codec;
}

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 <SDL2/SDL_keycode.h>
#include "input_events.h"
#include "screen.h"
#include "util/log.h"
static const int ACTION_DOWN = 1;
static const int ACTION_UP = 1 << 1;
static inline uint16_t
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)
static inline uint16_t
to_sdl_mod(unsigned mod) {
to_sdl_mod(unsigned shortcut_mod) {
uint16_t sdl_mod = 0;
if (mod & SC_MOD_LCTRL) {
if (shortcut_mod & SC_SHORTCUT_MOD_LCTRL) {
sdl_mod |= KMOD_LCTRL;
}
if (mod & SC_MOD_RCTRL) {
if (shortcut_mod & SC_SHORTCUT_MOD_RCTRL) {
sdl_mod |= KMOD_RCTRL;
}
if (mod & SC_MOD_LALT) {
if (shortcut_mod & SC_SHORTCUT_MOD_LALT) {
sdl_mod |= KMOD_LALT;
}
if (mod & SC_MOD_RALT) {
if (shortcut_mod & SC_SHORTCUT_MOD_RALT) {
sdl_mod |= KMOD_RALT;
}
if (mod & SC_MOD_LSUPER) {
if (shortcut_mod & SC_SHORTCUT_MOD_LSUPER) {
sdl_mod |= KMOD_LGUI;
}
if (mod & SC_MOD_RSUPER) {
if (shortcut_mod & SC_SHORTCUT_MOD_RSUPER) {
sdl_mod |= KMOD_RGUI;
}
return sdl_mod;
}
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
sdl_mod &= SC_SDL_SHORTCUT_MODS_MASK;
@ -51,23 +122,22 @@ is_shortcut_mod(struct input_manager *im, uint16_t sdl_mod) {
}
void
input_manager_init(struct input_manager *im, struct controller *controller,
struct screen *screen, struct sc_key_processor *kp,
struct sc_mouse_processor *mp,
const struct scrcpy_options *options) {
assert(!options->control || (kp && kp->ops));
assert(!options->control || (mp && mp->ops));
sc_input_manager_init(struct sc_input_manager *im,
const struct sc_input_manager_params *params) {
assert(!params->control || (params->kp && params->kp->ops));
assert(!params->control || (params->mp && params->mp->ops));
im->controller = controller;
im->screen = screen;
im->kp = kp;
im->mp = mp;
im->controller = params->controller;
im->screen = params->screen;
im->kp = params->kp;
im->mp = params->mp;
im->control = options->control;
im->forward_all_clicks = options->forward_all_clicks;
im->legacy_paste = options->legacy_paste;
im->control = params->control;
im->forward_all_clicks = params->forward_all_clicks;
im->legacy_paste = params->legacy_paste;
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 < SC_MAX_SHORTCUT_MODS);
for (unsigned i = 0; i < shortcut_mods->count; ++i) {
@ -82,171 +152,163 @@ input_manager_init(struct input_manager *im, struct controller *controller,
im->last_keycode = SDLK_UNKNOWN;
im->last_mod = 0;
im->key_repeat = 0;
im->next_sequence = 1; // 0 is reserved for SC_SEQUENCE_INVALID
}
static void
send_keycode(struct controller *controller, enum android_keycode keycode,
int actions, const char *name) {
send_keycode(struct sc_controller *controller, enum android_keycode keycode,
enum sc_action action, const char *name) {
// send DOWN event
struct control_msg msg;
struct sc_control_msg msg;
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.metastate = 0;
msg.inject_keycode.repeat = 0;
if (actions & ACTION_DOWN) {
msg.inject_keycode.action = AKEY_EVENT_ACTION_DOWN;
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);
}
if (!sc_controller_push_msg(controller, &msg)) {
LOGW("Could not request 'inject %s'", name);
}
}
static inline void
action_home(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_HOME, actions, "HOME");
action_home(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_HOME, action, "HOME");
}
static inline void
action_back(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_BACK, actions, "BACK");
action_back(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_BACK, action, "BACK");
}
static inline void
action_app_switch(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_APP_SWITCH, actions, "APP_SWITCH");
action_app_switch(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_APP_SWITCH, action, "APP_SWITCH");
}
static inline void
action_power(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_POWER, actions, "POWER");
action_power(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_POWER, action, "POWER");
}
static inline void
action_volume_up(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_VOLUME_UP, actions, "VOLUME_UP");
action_volume_up(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_VOLUME_UP, action, "VOLUME_UP");
}
static inline void
action_volume_down(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_VOLUME_DOWN, actions, "VOLUME_DOWN");
action_volume_down(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_VOLUME_DOWN, action, "VOLUME_DOWN");
}
static inline void
action_menu(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_MENU, actions, "MENU");
}
static inline void
action_copy(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_COPY, actions, "COPY");
}
static inline void
action_cut(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_CUT, actions, "CUT");
action_menu(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_MENU, action, "MENU");
}
// turn the screen on if it was off, press BACK otherwise
// If the screen is off, it is turned on only on ACTION_DOWN
static void
press_back_or_turn_screen_on(struct controller *controller, int actions) {
struct control_msg msg;
press_back_or_turn_screen_on(struct sc_controller *controller,
enum sc_action action) {
struct sc_control_msg msg;
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) {
msg.back_or_screen_on.action = AKEY_EVENT_ACTION_DOWN;
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'");
}
if (!sc_controller_push_msg(controller, &msg)) {
LOGW("Could not request 'press back or turn screen on'");
}
}
static void
expand_notification_panel(struct controller *controller) {
struct control_msg msg;
expand_notification_panel(struct sc_controller *controller) {
struct sc_control_msg msg;
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'");
}
}
static void
expand_settings_panel(struct controller *controller) {
struct control_msg msg;
expand_settings_panel(struct sc_controller *controller) {
struct sc_control_msg msg;
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'");
}
}
static void
collapse_panels(struct controller *controller) {
struct control_msg msg;
collapse_panels(struct sc_controller *controller) {
struct sc_control_msg msg;
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'");
}
}
static void
set_device_clipboard(struct controller *controller, bool paste) {
static bool
get_device_clipboard(struct sc_controller *controller,
enum get_clipboard_copy_key copy_key) {
struct sc_control_msg msg;
msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD;
msg.get_clipboard.copy_key = copy_key;
if (!sc_controller_push_msg(controller, &msg)) {
LOGW("Could not request 'get device clipboard'");
return false;
}
return true;
}
static bool
set_device_clipboard(struct sc_controller *controller, bool paste,
uint64_t sequence) {
char *text = SDL_GetClipboardText();
if (!text) {
LOGW("Could not get clipboard text: %s", SDL_GetError());
return;
}
if (!*text) {
// empty text
SDL_free(text);
return;
return false;
}
char *text_dup = strdup(text);
SDL_free(text);
if (!text_dup) {
LOGW("Could not strdup input text");
return;
return false;
}
struct control_msg msg;
struct sc_control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD;
msg.set_clipboard.sequence = sequence;
msg.set_clipboard.text = text_dup;
msg.set_clipboard.paste = paste;
if (!controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(controller, &msg)) {
free(text_dup);
LOGW("Could not request 'set device clipboard'");
return false;
}
return true;
}
static void
set_screen_power_mode(struct controller *controller,
set_screen_power_mode(struct sc_controller *controller,
enum screen_power_mode mode) {
struct control_msg msg;
struct sc_control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_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'");
}
}
@ -268,7 +330,7 @@ switch_fps_counter_state(struct fps_counter *fps_counter) {
}
static void
clipboard_paste(struct controller *controller) {
clipboard_paste(struct sc_controller *controller) {
char *text = SDL_GetClipboardText();
if (!text) {
LOGW("Could not get clipboard text: %s", SDL_GetError());
@ -287,55 +349,64 @@ clipboard_paste(struct controller *controller) {
return;
}
struct control_msg msg;
struct sc_control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = text_dup;
if (!controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(controller, &msg)) {
free(text_dup);
LOGW("Could not request 'paste clipboard'");
}
}
static void
rotate_device(struct controller *controller) {
struct control_msg msg;
rotate_device(struct sc_controller *controller) {
struct sc_control_msg msg;
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");
}
}
static void
rotate_client_left(struct screen *screen) {
rotate_client_left(struct sc_screen *screen) {
unsigned new_rotation = (screen->rotation + 1) % 4;
screen_set_rotation(screen, new_rotation);
sc_screen_set_rotation(screen, new_rotation);
}
static void
rotate_client_right(struct screen *screen) {
rotate_client_right(struct sc_screen *screen) {
unsigned new_rotation = (screen->rotation + 3) % 4;
screen_set_rotation(screen, new_rotation);
sc_screen_set_rotation(screen, new_rotation);
}
static void
input_manager_process_text_input(struct input_manager *im,
const SDL_TextInputEvent *event) {
sc_input_manager_process_text_input(struct sc_input_manager *im,
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())) {
// A shortcut must never generate text events
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
simulate_virtual_finger(struct input_manager *im,
simulate_virtual_finger(struct sc_input_manager *im,
enum android_motionevent_action action,
struct sc_point point) {
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.inject_touch_event.action = action;
msg.inject_touch_event.position.screen_size = im->screen->frame_size;
@ -344,7 +415,7 @@ simulate_virtual_finger(struct input_manager *im,
msg.inject_touch_event.pressure = up ? 0.0f : 1.0f;
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'");
return false;
}
@ -360,12 +431,12 @@ inverse_point(struct sc_point point, struct sc_size size) {
}
static void
input_manager_process_key(struct input_manager *im,
const SDL_KeyboardEvent *event) {
sc_input_manager_process_key(struct sc_input_manager *im,
const SDL_KeyboardEvent *event) {
// control: indicates the state of the command-line option --no-control
bool control = im->control;
struct controller *controller = im->controller;
struct sc_controller *controller = im->controller;
SDL_Keycode keycode = event->keysym.sym;
uint16_t mod = event->keysym.mod;
@ -388,7 +459,7 @@ input_manager_process_key(struct input_manager *im,
// The shortcut modifier is pressed
if (smod) {
int action = down ? ACTION_DOWN : ACTION_UP;
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
switch (keycode) {
case SDLK_h:
if (control && !shift && !repeat) {
@ -447,13 +518,15 @@ input_manager_process_key(struct input_manager *im,
}
return;
case SDLK_c:
if (control && !shift && !repeat) {
action_copy(controller, action);
if (control && !shift && !repeat && down) {
get_device_clipboard(controller,
GET_CLIPBOARD_COPY_KEY_COPY);
}
return;
case SDLK_x:
if (control && !shift && !repeat) {
action_cut(controller, action);
if (control && !shift && !repeat && down) {
get_device_clipboard(controller,
GET_CLIPBOARD_COPY_KEY_CUT);
}
return;
case SDLK_v:
@ -462,24 +535,26 @@ input_manager_process_key(struct input_manager *im,
// inject the text as input events
clipboard_paste(controller);
} else {
// store the text in the device clipboard and paste
set_device_clipboard(controller, true);
// store the text in the device clipboard and paste,
// without requesting an acknowledgment
set_device_clipboard(controller, true,
SC_SEQUENCE_INVALID);
}
}
return;
case SDLK_f:
if (!shift && !repeat && down) {
screen_switch_fullscreen(im->screen);
sc_screen_switch_fullscreen(im->screen);
}
return;
case SDLK_w:
if (!shift && !repeat && down) {
screen_resize_to_fit(im->screen);
sc_screen_resize_to_fit(im->screen);
}
return;
case SDLK_g:
if (!shift && !repeat && down) {
screen_resize_to_pixel_perfect(im->screen);
sc_screen_resize_to_pixel_perfect(im->screen);
}
return;
case SDLK_i:
@ -512,56 +587,119 @@ input_manager_process_key(struct input_manager *im,
return;
}
if (ctrl && !shift && keycode == SDLK_v && down && !repeat) {
uint64_t ack_to_wait = SC_SEQUENCE_INVALID;
bool is_ctrl_v = ctrl && !shift && keycode == SDLK_v && down && !repeat;
if (im->clipboard_autosync && is_ctrl_v) {
if (im->legacy_paste) {
// inject the text as input events
clipboard_paste(controller);
return;
}
// Request an acknowledgement only if necessary
uint64_t sequence = im->kp->async_paste ? im->next_sequence
: SC_SEQUENCE_INVALID;
// Synchronize the computer clipboard to the device clipboard before
// sending Ctrl+v, to allow seamless copy-paste.
set_device_clipboard(controller, false);
bool ok = set_device_clipboard(controller, false, sequence);
if (!ok) {
LOGW("Clipboard could not be synchronized, Ctrl+v not injected");
return;
}
if (im->kp->async_paste) {
// The key processor must wait for this ack before injecting Ctrl+v
ack_to_wait = sequence;
// Increment only when the request succeeded
++im->next_sequence;
}
}
im->kp->ops->process_key(im->kp, event);
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
input_manager_process_mouse_motion(struct input_manager *im,
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;
}
sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
const SDL_MouseMotionEvent *event) {
if (event->which == SDL_TOUCH_MOUSEID) {
// simulated from touch events, so it's a duplicate
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) {
assert(!im->mp->relative_mode); // assert one more time
struct sc_point mouse =
screen_convert_window_to_frame_coords(im->screen, event->x,
event->y);
sc_screen_convert_window_to_frame_coords(im->screen, event->x,
event->y);
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size);
simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger);
}
}
static void
input_manager_process_touch(struct input_manager *im,
const SDL_TouchFingerEvent *event) {
im->mp->ops->process_touch(im->mp, event);
sc_input_manager_process_touch(struct sc_input_manager *im,
const SDL_TouchFingerEvent *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
input_manager_process_mouse_button(struct input_manager *im,
const SDL_MouseButtonEvent *event) {
sc_input_manager_process_mouse_button(struct sc_input_manager *im,
const SDL_MouseButtonEvent *event) {
bool control = im->control;
if (event->which == SDL_TOUCH_MOUSEID) {
@ -571,7 +709,7 @@ input_manager_process_mouse_button(struct input_manager *im,
bool down = event->type == SDL_MOUSEBUTTONDOWN;
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) {
action_app_switch(im->controller, action);
@ -598,13 +736,13 @@ input_manager_process_mouse_button(struct input_manager *im,
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
int32_t x = event->x;
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;
bool outside = x < r->x || x >= r->x + r->w
|| y < r->y || y >= r->y + r->h;
if (outside) {
if (down) {
screen_resize_to_fit(im->screen);
sc_screen_resize_to_fit(im->screen);
}
return;
}
@ -616,7 +754,30 @@ input_manager_process_mouse_button(struct input_manager *im,
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.
//
@ -628,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
// screen.
#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL))
if ((down && !im->vfinger_down && CTRL_PRESSED)
|| (!down && im->vfinger_down)) {
if (event->button == SDL_BUTTON_LEFT &&
((down && !im->vfinger_down && CTRL_PRESSED) ||
(!down && im->vfinger_down))) {
struct sc_point mouse =
screen_convert_window_to_frame_coords(im->screen, event->x,
event->y);
sc_screen_convert_window_to_frame_coords(im->screen, event->x,
event->y);
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size);
enum android_motionevent_action action = down
? AMOTION_EVENT_ACTION_DOWN
@ -645,48 +807,70 @@ input_manager_process_mouse_button(struct input_manager *im,
}
static void
input_manager_process_mouse_wheel(struct input_manager *im,
const SDL_MouseWheelEvent *event) {
im->mp->ops->process_mouse_wheel(im->mp, event);
sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
const SDL_MouseWheelEvent *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
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) {
case SDL_TEXTINPUT:
if (!im->control) {
return true;
}
input_manager_process_text_input(im, &event->text);
sc_input_manager_process_text_input(im, &event->text);
return true;
case SDL_KEYDOWN:
case SDL_KEYUP:
// some key events do not interact with the device, so process the
// event even if control is disabled
input_manager_process_key(im, &event->key);
sc_input_manager_process_key(im, &event->key);
return true;
case SDL_MOUSEMOTION:
if (!im->control) {
break;
}
input_manager_process_mouse_motion(im, &event->motion);
sc_input_manager_process_mouse_motion(im, &event->motion);
return true;
case SDL_MOUSEWHEEL:
if (!im->control) {
break;
}
input_manager_process_mouse_wheel(im, &event->wheel);
sc_input_manager_process_mouse_wheel(im, &event->wheel);
return true;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
// some mouse events do not interact with the device, so process
// 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;
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
input_manager_process_touch(im, &event->tfinger);
sc_input_manager_process_touch(im, &event->tfinger);
return true;
}

View File

@ -10,13 +10,12 @@
#include "controller.h"
#include "fps_counter.h"
#include "options.h"
#include "screen.h"
#include "trait/key_processor.h"
#include "trait/mouse_processor.h"
struct input_manager {
struct controller *controller;
struct screen *screen;
struct sc_input_manager {
struct sc_controller *controller;
struct sc_screen *screen;
struct sc_key_processor *kp;
struct sc_mouse_processor *mp;
@ -24,6 +23,7 @@ struct input_manager {
bool control;
bool forward_all_clicks;
bool legacy_paste;
bool clipboard_autosync;
struct {
unsigned data[SC_MAX_SHORTCUT_MODS];
@ -38,15 +38,28 @@ struct input_manager {
unsigned key_repeat;
SDL_Keycode last_keycode;
uint16_t last_mod;
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
input_manager_init(struct input_manager *im, struct controller *controller,
struct screen *screen, struct sc_key_processor *kp,
struct sc_mouse_processor *mp,
const struct scrcpy_options *options);
sc_input_manager_init(struct sc_input_manager *im,
const struct sc_input_manager_params *params);
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

View File

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

View File

@ -12,19 +12,20 @@
struct sc_keyboard_inject {
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
// number of repetitions. This variable keeps track of the count.
unsigned repeat;
bool prefer_text;
enum sc_key_inject_mode key_inject_mode;
bool forward_key_repeat;
};
void
sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
struct controller *controller,
const struct scrcpy_options *options);
struct sc_controller *controller,
enum sc_key_inject_mode key_inject_mode,
bool forward_key_repeat);
#endif

View File

@ -1,11 +1,12 @@
#include "mouse_inject.h"
#include <assert.h>
#include <SDL2/SDL_events.h>
#include "android/input.h"
#include "control_msg.h"
#include "controller.h"
#include "input_events.h"
#include "util/intmap.h"
#include "util/log.h"
/** Downcast mouse processor to sc_mouse_inject */
@ -14,198 +15,147 @@
static enum android_motionevent_buttons
convert_mouse_buttons(uint32_t state) {
enum android_motionevent_buttons buttons = 0;
if (state & SDL_BUTTON_LMASK) {
if (state & SC_MOUSE_BUTTON_LEFT) {
buttons |= AMOTION_EVENT_BUTTON_PRIMARY;
}
if (state & SDL_BUTTON_RMASK) {
if (state & SC_MOUSE_BUTTON_RIGHT) {
buttons |= AMOTION_EVENT_BUTTON_SECONDARY;
}
if (state & SDL_BUTTON_MMASK) {
if (state & SC_MOUSE_BUTTON_MIDDLE) {
buttons |= AMOTION_EVENT_BUTTON_TERTIARY;
}
if (state & SDL_BUTTON_X1MASK) {
if (state & SC_MOUSE_BUTTON_X1) {
buttons |= AMOTION_EVENT_BUTTON_BACK;
}
if (state & SDL_BUTTON_X2MASK) {
if (state & SC_MOUSE_BUTTON_X2) {
buttons |= AMOTION_EVENT_BUTTON_FORWARD;
}
return buttons;
}
#define MAP(FROM, TO) case FROM: *to = TO; return true
#define FAIL default: return false
static bool
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
switch (from) {
MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN);
MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP);
FAIL;
static enum android_motionevent_action
convert_mouse_action(enum sc_action action) {
if (action == SC_ACTION_DOWN) {
return AMOTION_EVENT_ACTION_DOWN;
}
assert(action == SC_ACTION_UP);
return AMOTION_EVENT_ACTION_UP;
}
static bool
convert_touch_action(SDL_EventType from, enum android_motionevent_action *to) {
switch (from) {
MAP(SDL_FINGERMOTION, AMOTION_EVENT_ACTION_MOVE);
MAP(SDL_FINGERDOWN, AMOTION_EVENT_ACTION_DOWN);
MAP(SDL_FINGERUP, AMOTION_EVENT_ACTION_UP);
FAIL;
static enum android_motionevent_action
convert_touch_action(enum sc_touch_action action) {
switch (action) {
case SC_TOUCH_ACTION_MOVE:
return AMOTION_EVENT_ACTION_MOVE;
case SC_TOUCH_ACTION_DOWN:
return AMOTION_EVENT_ACTION_DOWN;
default:
assert(action == SC_TOUCH_ACTION_UP);
return AMOTION_EVENT_ACTION_UP;
}
}
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
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
const SDL_MouseMotionEvent *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct control_msg msg;
if (!convert_mouse_motion(event, mi->screen, &msg)) {
const struct sc_mouse_motion_event *event) {
if (!event->buttons_state) {
// Do not send motion events when no click is pressed
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'");
}
}
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
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 control_msg msg;
if (convert_touch(event, mi->screen, &msg)) {
if (!controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject touch event'");
}
}
}
struct sc_control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
.inject_touch_event = {
.action = convert_touch_action(event->action),
.pointer_id = event->pointer_id,
.position = event->position,
.pressure = event->pressure,
.buttons = 0,
},
};
static void
sc_mouse_processor_process_mouse_button(struct sc_mouse_processor *mp,
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'");
}
if (!sc_controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject touch event'");
}
}
void
sc_mouse_inject_init(struct sc_mouse_inject *mi, struct controller *controller,
struct screen *screen) {
sc_mouse_inject_init(struct sc_mouse_inject *mi,
struct sc_controller *controller) {
mi->controller = controller;
mi->screen = screen;
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,
.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.relative_mode = false;
}

View File

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

View File

@ -19,8 +19,10 @@ const struct scrcpy_options scrcpy_options_default = {
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST,
.last = DEFAULT_LOCAL_PORT_RANGE_LAST,
},
.tunnel_host = 0,
.tunnel_port = 0,
.shortcut_mods = {
.data = {SC_MOD_LALT, SC_MOD_LSUPER},
.data = {SC_SHORTCUT_MOD_LALT, SC_SHORTCUT_MOD_LSUPER},
.count = 2,
},
.max_size = 0,
@ -41,7 +43,7 @@ const struct scrcpy_options scrcpy_options_default = {
.control = true,
.display = true,
.turn_screen_off = false,
.prefer_text = false,
.key_inject_mode = SC_KEY_INJECT_MODE_MIXED,
.window_borderless = false,
.mipmaps = true,
.stay_awake = false,
@ -51,4 +53,7 @@ const struct scrcpy_options scrcpy_options_default = {
.forward_all_clicks = false,
.legacy_paste = false,
.power_off_on_close = false,
.clipboard_autosync = true,
.tcpip = false,
.tcpip_dst = NULL,
};

View File

@ -38,15 +38,34 @@ enum sc_keyboard_input_mode {
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 {
// Inject special keys, letters and space as key events.
// Inject numbers and punctuation as text events.
// This is the default mode.
SC_KEY_INJECT_MODE_MIXED,
// Inject special keys as key events.
// Inject letters and space, numbers and punctuation as text events.
SC_KEY_INJECT_MODE_TEXT,
// Inject everything as key events.
SC_KEY_INJECT_MODE_RAW,
};
#define SC_MAX_SHORTCUT_MODS 8
enum sc_shortcut_mod {
SC_MOD_LCTRL = 1 << 0,
SC_MOD_RCTRL = 1 << 1,
SC_MOD_LALT = 1 << 2,
SC_MOD_RALT = 1 << 3,
SC_MOD_LSUPER = 1 << 4,
SC_MOD_RSUPER = 1 << 5,
SC_SHORTCUT_MOD_LCTRL = 1 << 0,
SC_SHORTCUT_MOD_RCTRL = 1 << 1,
SC_SHORTCUT_MOD_LALT = 1 << 2,
SC_SHORTCUT_MOD_RALT = 1 << 3,
SC_SHORTCUT_MOD_LSUPER = 1 << 4,
SC_SHORTCUT_MOD_RSUPER = 1 << 5,
};
struct sc_shortcut_mods {
@ -76,7 +95,10 @@ struct scrcpy_options {
enum sc_log_level log_level;
enum sc_record_format record_format;
enum sc_keyboard_input_mode keyboard_input_mode;
enum sc_mouse_input_mode mouse_input_mode;
struct sc_port_range port_range;
uint32_t tunnel_host;
uint16_t tunnel_port;
struct sc_shortcut_mods shortcut_mods;
uint16_t max_size;
uint32_t bit_rate;
@ -96,7 +118,7 @@ struct scrcpy_options {
bool control;
bool display;
bool turn_screen_off;
bool prefer_text;
enum sc_key_inject_mode key_inject_mode;
bool window_borderless;
bool mipmaps;
bool stay_awake;
@ -106,6 +128,9 @@ struct scrcpy_options {
bool forward_all_clicks;
bool legacy_paste;
bool power_off_on_close;
bool clipboard_autosync;
bool tcpip;
const char *tcpip_dst;
};
extern const struct scrcpy_options scrcpy_options_default;

View File

@ -7,12 +7,16 @@
#include "util/log.h"
bool
receiver_init(struct receiver *receiver, sc_socket control_socket) {
receiver_init(struct receiver *receiver, sc_socket control_socket,
struct sc_acksync *acksync) {
bool ok = sc_mutex_init(&receiver->mutex);
if (!ok) {
return false;
}
receiver->control_socket = control_socket;
receiver->acksync = acksync;
return true;
}
@ -22,7 +26,7 @@ receiver_destroy(struct receiver *receiver) {
}
static void
process_msg(struct device_msg *msg) {
process_msg(struct receiver *receiver, struct device_msg *msg) {
switch (msg->type) {
case DEVICE_MSG_TYPE_CLIPBOARD: {
char *current = SDL_GetClipboardText();
@ -37,11 +41,17 @@ process_msg(struct device_msg *msg) {
SDL_SetClipboardText(msg->clipboard.text);
break;
}
case DEVICE_MSG_TYPE_ACK_CLIPBOARD:
assert(receiver->acksync);
LOGD("Ack device clipboard sequence=%" PRIu64_,
msg->ack_clipboard.sequence);
sc_acksync_ack(receiver->acksync, msg->ack_clipboard.sequence);
break;
}
}
static ssize_t
process_msgs(const unsigned char *buf, size_t len) {
process_msgs(struct receiver *receiver, const unsigned char *buf, size_t len) {
size_t head = 0;
for (;;) {
struct device_msg msg;
@ -53,7 +63,7 @@ process_msgs(const unsigned char *buf, size_t len) {
return head;
}
process_msg(&msg);
process_msg(receiver, &msg);
device_msg_destroy(&msg);
head += r;
@ -81,7 +91,7 @@ run_receiver(void *data) {
}
head += r;
ssize_t consumed = process_msgs(buf, head);
ssize_t consumed = process_msgs(receiver, buf, head);
if (consumed == -1) {
// an error occurred
break;
@ -101,8 +111,8 @@ bool
receiver_start(struct receiver *receiver) {
LOGD("Starting receiver thread");
bool ok = sc_thread_create(&receiver->thread, run_receiver, "receiver",
receiver);
bool ok = sc_thread_create(&receiver->thread, run_receiver,
"scrcpy-receiver", receiver);
if (!ok) {
LOGC("Could not start receiver thread");
return false;

View File

@ -5,6 +5,7 @@
#include <stdbool.h>
#include "util/acksync.h"
#include "util/net.h"
#include "util/thread.h"
@ -14,10 +15,13 @@ struct receiver {
sc_socket control_socket;
sc_thread thread;
sc_mutex mutex;
struct sc_acksync *acksync;
};
bool
receiver_init(struct receiver *receiver, sc_socket control_socket);
receiver_init(struct receiver *receiver, sc_socket control_socket,
struct sc_acksync *acksync);
void
receiver_destroy(struct receiver *receiver);

View File

@ -34,11 +34,13 @@ static struct record_packet *
record_packet_new(const AVPacket *packet) {
struct record_packet *rec = malloc(sizeof(*rec));
if (!rec) {
LOG_OOM();
return NULL;
}
rec->packet = av_packet_alloc();
if (!rec->packet) {
LOG_OOM();
free(rec);
return NULL;
}
@ -81,7 +83,7 @@ recorder_write_header(struct recorder *recorder, const AVPacket *packet) {
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t));
if (!extradata) {
LOGC("Could not allocate extradata");
LOG_OOM();
return false;
}
@ -228,13 +230,11 @@ static bool
recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
bool ok = sc_mutex_init(&recorder->mutex);
if (!ok) {
LOGC("Could not create mutex");
return false;
}
ok = sc_cond_init(&recorder->queue_cond);
if (!ok) {
LOGC("Could not create cond");
goto error_mutex_destroy;
}
@ -254,7 +254,7 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
recorder->ctx = avformat_alloc_context();
if (!recorder->ctx) {
LOGE("Could not allocate output context");
LOG_OOM();
goto error_cond_destroy;
}
@ -287,8 +287,8 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
}
LOGD("Starting recorder thread");
ok = sc_thread_create(&recorder->thread, run_recorder, "recorder",
recorder);
ok = sc_thread_create(&recorder->thread, run_recorder, "scrcpy-recorder",
recorder);
if (!ok) {
LOGC("Could not start recorder thread");
goto error_avio_close;
@ -338,7 +338,7 @@ recorder_push(struct recorder *recorder, const AVPacket *packet) {
struct record_packet *rec = record_packet_new(packet);
if (!rec) {
LOGC("Could not allocate record packet");
LOG_OOM();
sc_mutex_unlock(&recorder->mutex);
return false;
}
@ -375,7 +375,7 @@ recorder_init(struct recorder *recorder,
struct sc_size declared_frame_size) {
recorder->filename = strdup(filename);
if (!recorder->filename) {
LOGE("Could not strdup filename");
LOG_OOM();
return false;
}

View File

@ -17,9 +17,9 @@
#include "decoder.h"
#include "events.h"
#include "file_handler.h"
#include "input_manager.h"
#ifdef HAVE_AOA_HID
# include "hid_keyboard.h"
# include "hid_mouse.h"
#endif
#include "keyboard_inject.h"
#include "mouse_inject.h"
@ -27,6 +27,7 @@
#include "screen.h"
#include "server.h"
#include "stream.h"
#include "util/acksync.h"
#include "util/log.h"
#include "util/net.h"
#ifdef HAVE_V4L2
@ -35,17 +36,19 @@
struct scrcpy {
struct sc_server server;
struct screen screen;
struct sc_screen screen;
struct stream stream;
struct decoder decoder;
struct recorder recorder;
#ifdef HAVE_V4L2
struct sc_v4l2_sink v4l2_sink;
#endif
struct controller controller;
struct sc_controller controller;
struct file_handler file_handler;
#ifdef HAVE_AOA_HID
struct sc_aoa aoa;
// sequence/ack helper to synchronize clipboard and Ctrl+v via HID
struct sc_acksync acksync;
#endif
union {
struct sc_keyboard_inject keyboard_inject;
@ -53,8 +56,12 @@ struct scrcpy {
struct sc_hid_keyboard keyboard_hid;
#endif
};
struct sc_mouse_inject mouse_inject;
struct input_manager input_manager;
union {
struct sc_mouse_inject mouse_inject;
#ifdef HAVE_AOA_HID
struct sc_hid_mouse mouse_hid;
#endif
};
};
static inline void
@ -91,12 +98,10 @@ sdl_set_hints(const char *render_driver) {
LOGW("Could not enable linear filtering");
}
#ifdef SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH
// Handle a click to gain focus as any other click
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
LOGW("Could not enable mouse focus clickthrough");
}
#endif
#ifdef SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
// Disable synthetic mouse events from touch events
@ -187,12 +192,7 @@ handle_event(struct scrcpy *s, const struct scrcpy_options *options,
}
}
bool consumed = screen_handle_event(&s->screen, event);
if (consumed) {
goto end;
}
consumed = input_manager_handle_event(&s->input_manager, event);
bool consumed = sc_screen_handle_event(&s->screen, event);
(void) consumed;
end:
@ -268,7 +268,7 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
size_t fmt_len = strlen(fmt);
char *local_fmt = malloc(fmt_len + 10);
if (!local_fmt) {
LOGC("Could not allocate string");
LOG_OOM();
return;
}
memcpy(local_fmt, "[FFmpeg] ", 9); // do not write the final '\0'
@ -335,16 +335,22 @@ scrcpy(struct scrcpy_options *options) {
bool stream_started = false;
#ifdef HAVE_AOA_HID
bool aoa_hid_initialized = false;
bool hid_keyboard_initialized = false;
bool hid_mouse_initialized = false;
#endif
bool controller_initialized = false;
bool controller_started = false;
bool screen_initialized = false;
struct sc_acksync *acksync = NULL;
struct sc_server_params params = {
.serial = options->serial,
.log_level = options->log_level,
.crop = options->crop,
.port_range = options->port_range,
.tunnel_host = options->tunnel_host,
.tunnel_port = options->tunnel_port,
.max_size = options->max_size,
.bit_rate = options->bit_rate,
.max_fps = options->max_fps,
@ -357,6 +363,9 @@ scrcpy(struct scrcpy_options *options) {
.encoder_name = options->encoder_name,
.force_adb_forward = options->force_adb_forward,
.power_off_on_close = options->power_off_on_close,
.clipboard_autosync = options->clipboard_autosync,
.tcpip = options->tcpip,
.tcpip_dst = options->tcpip_dst,
};
static const struct sc_server_callbacks cbs = {
@ -442,33 +451,137 @@ scrcpy(struct scrcpy_options *options) {
stream_add_sink(&s->stream, &rec->packet_sink);
}
struct sc_key_processor *kp = NULL;
struct sc_mouse_processor *mp = NULL;
if (options->control) {
if (!controller_init(&s->controller, s->server.control_socket)) {
#ifdef HAVE_AOA_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);
if (!ok) {
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;
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
// 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;
}
controller_initialized = true;
if (!controller_start(&s->controller)) {
if (!sc_controller_start(&s->controller)) {
goto end;
}
controller_started = true;
if (options->turn_screen_off) {
struct control_msg msg;
struct sc_control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
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'");
}
}
}
if (options->display) {
const char *window_title =
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,
.frame_size = info->frame_size,
.always_on_top = options->always_on_top,
@ -483,7 +596,7 @@ scrcpy(struct scrcpy_options *options) {
.buffering_time = options->display_buffer,
};
if (!screen_init(&s->screen, &screen_params)) {
if (!sc_screen_init(&s->screen, &screen_params)) {
goto end;
}
screen_initialized = true;
@ -511,88 +624,35 @@ scrcpy(struct scrcpy_options *options) {
}
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);
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);
LOGD("quit...");
// Close the window immediately on closing, because screen_destroy() may
// 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:
// The stream is not stopped explicitly, because it will stop by itself on
// end-of-stream
#ifdef HAVE_AOA_HID
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);
}
if (acksync) {
sc_acksync_destroy(acksync);
}
#endif
if (controller_started) {
controller_stop(&s->controller);
sc_controller_stop(&s->controller);
}
if (file_handler_initialized) {
file_handler_stop(&s->file_handler);
}
if (screen_initialized) {
screen_interrupt(&s->screen);
sc_screen_interrupt(&s->screen);
}
if (server_started) {
@ -622,15 +682,15 @@ end:
// Destroy the screen only after the stream is guaranteed to be finished,
// because otherwise the screen could receive new frames after destruction
if (screen_initialized) {
screen_join(&s->screen);
screen_destroy(&s->screen);
sc_screen_join(&s->screen);
sc_screen_destroy(&s->screen);
}
if (controller_started) {
controller_join(&s->controller);
sc_controller_join(&s->controller);
}
if (controller_initialized) {
controller_destroy(&s->controller);
sc_controller_destroy(&s->controller);
}
if (recorder_initialized) {

View File

@ -12,7 +12,7 @@
#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
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
static struct sc_size
get_window_size(const struct screen *screen) {
get_window_size(const struct sc_screen *screen) {
int width;
int height;
SDL_GetWindowSize(screen->window, &width, &height);
@ -41,7 +41,7 @@ get_window_size(const struct screen *screen) {
}
static struct sc_point
get_window_position(const struct screen *screen) {
get_window_position(const struct sc_screen *screen) {
int x;
int 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
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->maximized);
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
@ -64,12 +64,7 @@ set_window_size(struct screen *screen, struct sc_size new_size) {
static bool
get_preferred_display_bounds(struct sc_size *bounds) {
SDL_Rect rect;
#ifdef SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS
# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayUsableBounds((i), (r))
#else
# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayBounds((i), (r))
#endif
if (GET_DISPLAY_BOUNDS(0, &rect)) {
if (SDL_GetDisplayUsableBounds(0, &rect)) {
LOGW("Could not get display usable bounds: %s", SDL_GetError());
return false;
}
@ -95,7 +90,8 @@ is_optimal_size(struct sc_size current_size, struct sc_size content_size) {
// - it keeps the aspect ratio
// - it scales down to make it fit in the display_size
static struct sc_size
get_optimal_size(struct sc_size current_size, struct sc_size content_size) {
get_optimal_size(struct sc_size current_size, struct sc_size content_size,
bool within_display_bounds) {
if (content_size.width == 0 || content_size.height == 0) {
// avoid division by 0
return current_size;
@ -104,10 +100,10 @@ get_optimal_size(struct sc_size current_size, struct sc_size content_size) {
struct sc_size window_size;
struct sc_size display_size;
if (!get_preferred_display_bounds(&display_size)) {
// could not get display bounds, do not constraint the size
window_size.width = current_size.width;
window_size.height = current_size.height;
if (!within_display_bounds ||
!get_preferred_display_bounds(&display_size)) {
// do not constraint the size
window_size = current_size;
} else {
window_size.width = MIN(current_size.width, display_size.width);
window_size.height = MIN(current_size.height, display_size.height);
@ -140,7 +136,7 @@ get_initial_optimal_size(struct sc_size content_size, uint16_t req_width,
uint16_t req_height) {
struct sc_size window_size;
if (!req_width && !req_height) {
window_size = get_optimal_size(content_size, content_size);
window_size = get_optimal_size(content_size, content_size, true);
} else {
if (req_width) {
window_size.width = req_width;
@ -160,8 +156,19 @@ get_initial_optimal_size(struct sc_size content_size, uint16_t req_width,
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
screen_update_content_rect(struct screen *screen) {
sc_screen_update_content_rect(struct sc_screen *screen) {
int dw;
int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
@ -198,7 +205,7 @@ screen_update_content_rect(struct screen *screen) {
}
static inline SDL_Texture *
create_texture(struct screen *screen) {
create_texture(struct sc_screen *screen) {
SDL_Renderer *renderer = screen->renderer;
struct sc_size size = screen->frame_size;
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
@ -229,9 +236,9 @@ create_texture(struct screen *screen) {
// Set the update_content_rect flag if the window or content size may have
// changed, so that the content rectangle is recomputed
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) {
screen_update_content_rect(screen);
sc_screen_update_content_rect(screen);
}
SDL_RenderClear(screen->renderer);
@ -275,20 +282,20 @@ screen_render(struct screen *screen, bool update_content_rect) {
// <https://stackoverflow.com/a/40693139/1987178>
static int
event_watcher(void *data, SDL_Event *event) {
struct screen *screen = data;
struct sc_screen *screen = data;
if (event->type == SDL_WINDOWEVENT
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
// In practice, it seems to always be called from the same thread in
// that specific case. Anyway, it's just a workaround.
screen_render(screen, true);
sc_screen_render(screen, true);
}
return 0;
}
#endif
static bool
screen_frame_sink_open(struct sc_frame_sink *sink) {
struct screen *screen = DOWNCAST(sink);
sc_screen_frame_sink_open(struct sc_frame_sink *sink) {
struct sc_screen *screen = DOWNCAST(sink);
(void) screen;
#ifndef NDEBUG
screen->open = true;
@ -299,8 +306,8 @@ screen_frame_sink_open(struct sc_frame_sink *sink) {
}
static void
screen_frame_sink_close(struct sc_frame_sink *sink) {
struct screen *screen = DOWNCAST(sink);
sc_screen_frame_sink_close(struct sc_frame_sink *sink) {
struct sc_screen *screen = DOWNCAST(sink);
(void) screen;
#ifndef NDEBUG
screen->open = false;
@ -310,8 +317,8 @@ screen_frame_sink_close(struct sc_frame_sink *sink) {
}
static bool
screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
struct screen *screen = DOWNCAST(sink);
sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
struct sc_screen *screen = DOWNCAST(sink);
return sc_video_buffer_push(&screen->vb, frame);
}
@ -319,7 +326,7 @@ static void
sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
void *userdata) {
(void) vb;
struct screen *screen = userdata;
struct sc_screen *screen = userdata;
// event_failed implies previous_skipped (the previous frame may not have
// been consumed if the event was not sent)
@ -352,12 +359,15 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
}
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->has_frame = false;
screen->fullscreen = false;
screen->maximized = false;
screen->event_failed = false;
screen->mouse_captured = false;
screen->mouse_capture_key_pressed = 0;
static const struct sc_video_buffer_callbacks cbs = {
.on_new_frame = sc_video_buffer_on_new_frame,
@ -366,18 +376,15 @@ screen_init(struct screen *screen, const struct screen_params *params) {
bool ok = sc_video_buffer_init(&screen->vb, params->buffering_time, &cbs,
screen);
if (!ok) {
LOGE("Could not initialize video buffer");
return false;
}
ok = sc_video_buffer_start(&screen->vb);
if (!ok) {
LOGE("Could not start video_buffer");
goto error_destroy_video_buffer;
}
if (!fps_counter_init(&screen->fps_counter)) {
LOGE("Could not initialize FPS counter");
goto error_stop_and_join_video_buffer;
}
@ -397,12 +404,7 @@ screen_init(struct screen *screen, const struct screen_params *params) {
| SDL_WINDOW_RESIZABLE
| SDL_WINDOW_ALLOW_HIGHDPI;
if (params->always_on_top) {
#ifdef SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
#else
LOGW("The 'always on top' flag is not available "
"(compile with SDL >= 2.0.5 to enable it)");
#endif
}
if (params->window_borderless) {
window_flags |= SDL_WINDOW_BORDERLESS;
@ -478,19 +480,33 @@ screen_init(struct screen *screen, const struct screen_params *params) {
screen->frame = av_frame_alloc();
if (!screen->frame) {
LOGC("Could not create screen frame");
LOG_OOM();
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
// HiDPI issues with some SDL renderers when several displays having
// different HiDPI scaling are connected
SDL_SetWindowSize(screen->window, window_size.width, window_size.height);
screen_update_content_rect(screen);
sc_screen_update_content_rect(screen);
if (params->fullscreen) {
screen_switch_fullscreen(screen);
sc_screen_switch_fullscreen(screen);
}
#ifdef CONTINUOUS_RESIZING_WORKAROUND
@ -498,9 +514,9 @@ screen_init(struct screen *screen, const struct screen_params *params) {
#endif
static const struct sc_frame_sink_ops ops = {
.open = screen_frame_sink_open,
.close = screen_frame_sink_close,
.push = screen_frame_sink_push,
.open = sc_screen_frame_sink_open,
.close = sc_screen_frame_sink_close,
.push = sc_screen_frame_sink_push,
};
screen->frame_sink.ops = &ops;
@ -529,29 +545,29 @@ error_destroy_video_buffer:
}
static void
screen_show_window(struct screen *screen) {
sc_screen_show_window(struct sc_screen *screen) {
SDL_ShowWindow(screen->window);
}
void
screen_hide_window(struct screen *screen) {
sc_screen_hide_window(struct sc_screen *screen) {
SDL_HideWindow(screen->window);
}
void
screen_interrupt(struct screen *screen) {
sc_screen_interrupt(struct sc_screen *screen) {
sc_video_buffer_stop(&screen->vb);
fps_counter_interrupt(&screen->fps_counter);
}
void
screen_join(struct screen *screen) {
sc_screen_join(struct sc_screen *screen) {
sc_video_buffer_join(&screen->vb);
fps_counter_join(&screen->fps_counter);
}
void
screen_destroy(struct screen *screen) {
sc_screen_destroy(struct sc_screen *screen) {
#ifndef NDEBUG
assert(!screen->open);
#endif
@ -564,7 +580,7 @@ screen_destroy(struct screen *screen) {
}
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 window_size = get_window_size(screen);
struct sc_size target_size = {
@ -573,12 +589,12 @@ resize_for_content(struct screen *screen, struct sc_size old_content_size,
.height = (uint32_t) window_size.height * new_content_size.height
/ old_content_size.height,
};
target_size = get_optimal_size(target_size, new_content_size);
target_size = get_optimal_size(target_size, new_content_size, true);
set_window_size(screen, target_size);
}
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) {
resize_for_content(screen, screen->content_size, new_content_size);
} else if (!screen->resize_pending) {
@ -592,7 +608,7 @@ set_content_size(struct screen *screen, struct sc_size new_content_size) {
}
static void
apply_pending_resize(struct screen *screen) {
apply_pending_resize(struct sc_screen *screen) {
assert(!screen->fullscreen);
assert(!screen->maximized);
if (screen->resize_pending) {
@ -603,7 +619,7 @@ apply_pending_resize(struct screen *screen) {
}
void
screen_set_rotation(struct screen *screen, unsigned rotation) {
sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation) {
assert(rotation < 4);
if (rotation == screen->rotation) {
return;
@ -617,12 +633,12 @@ screen_set_rotation(struct screen *screen, unsigned rotation) {
screen->rotation = 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
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
|| screen->frame_size.height != new_frame_size.height) {
// frame dimension changed, destroy texture
@ -634,7 +650,7 @@ prepare_for_frame(struct screen *screen, struct sc_size new_frame_size) {
get_rotated_size(new_frame_size, screen->rotation);
set_content_size(screen, new_content_size);
screen_update_content_rect(screen);
sc_screen_update_content_rect(screen);
LOGI("New texture: %" PRIu16 "x%" PRIu16,
screen->frame_size.width, screen->frame_size.height);
@ -650,7 +666,7 @@ prepare_for_frame(struct screen *screen, struct sc_size new_frame_size) {
// write the frame into the texture
static void
update_texture(struct screen *screen, const AVFrame *frame) {
update_texture(struct sc_screen *screen, const AVFrame *frame) {
SDL_UpdateYUVTexture(screen->texture, NULL,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
@ -664,7 +680,7 @@ update_texture(struct screen *screen, const AVFrame *frame) {
}
static bool
screen_update_frame(struct screen *screen) {
sc_screen_update_frame(struct sc_screen *screen) {
av_frame_unref(screen->frame);
sc_video_buffer_consume(&screen->vb, screen->frame);
AVFrame *frame = screen->frame;
@ -677,12 +693,12 @@ screen_update_frame(struct screen *screen) {
}
update_texture(screen, frame);
screen_render(screen, false);
sc_screen_render(screen, false);
return true;
}
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;
if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
@ -695,11 +711,11 @@ screen_switch_fullscreen(struct screen *screen) {
}
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
screen_render(screen, true);
sc_screen_render(screen, true);
}
void
screen_resize_to_fit(struct screen *screen) {
sc_screen_resize_to_fit(struct sc_screen *screen) {
if (screen->fullscreen || screen->maximized) {
return;
}
@ -708,7 +724,7 @@ screen_resize_to_fit(struct screen *screen) {
struct sc_size window_size = get_window_size(screen);
struct sc_size optimal_size =
get_optimal_size(window_size, screen->content_size);
get_optimal_size(window_size, screen->content_size, false);
// Center the window related to the device screen
assert(optimal_size.width <= window_size.width);
@ -723,7 +739,7 @@ screen_resize_to_fit(struct screen *screen) {
}
void
screen_resize_to_pixel_perfect(struct screen *screen) {
sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
if (screen->fullscreen) {
return;
}
@ -739,16 +755,21 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
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
screen_handle_event(struct screen *screen, SDL_Event *event) {
sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
switch (event->type) {
case EVENT_NEW_FRAME:
if (!screen->has_frame) {
screen->has_frame = true;
// 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) {
LOGW("Frame update failed\n");
}
@ -760,10 +781,10 @@ screen_handle_event(struct screen *screen, SDL_Event *event) {
}
switch (event->window.event) {
case SDL_WINDOWEVENT_EXPOSED:
screen_render(screen, true);
sc_screen_render(screen, true);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_render(screen, true);
sc_screen_render(screen, true);
break;
case SDL_WINDOWEVENT_MAXIMIZED:
screen->maximized = true;
@ -779,18 +800,78 @@ screen_handle_event(struct screen *screen, SDL_Event *event) {
}
screen->maximized = false;
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;
}
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
screen_convert_drawable_to_frame_coords(struct screen *screen,
int32_t x, int32_t y) {
sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
int32_t x, int32_t y) {
unsigned rotation = screen->rotation;
assert(rotation < 4);
@ -826,14 +907,14 @@ screen_convert_drawable_to_frame_coords(struct screen *screen,
}
struct sc_point
screen_convert_window_to_frame_coords(struct screen *screen,
int32_t x, int32_t y) {
screen_hidpi_scale_coords(screen, &x, &y);
return screen_convert_drawable_to_frame_coords(screen, x, y);
sc_screen_convert_window_to_frame_coords(struct sc_screen *screen,
int32_t x, int32_t y) {
sc_screen_hidpi_scale_coords(screen, &x, &y);
return sc_screen_convert_drawable_to_frame_coords(screen, x, y);
}
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
int ww, wh, dw, dh;
SDL_GetWindowSize(screen->window, &ww, &wh);

View File

@ -7,19 +7,24 @@
#include <SDL2/SDL.h>
#include <libavformat/avformat.h>
#include "controller.h"
#include "coords.h"
#include "fps_counter.h"
#include "input_manager.h"
#include "opengl.h"
#include "trait/key_processor.h"
#include "trait/frame_sink.h"
#include "trait/mouse_processor.h"
#include "video_buffer.h"
struct screen {
struct sc_screen {
struct sc_frame_sink frame_sink; // frame sink trait
#ifndef NDEBUG
bool open; // track the open/close state to assert correct behavior
#endif
struct sc_input_manager im;
struct sc_video_buffer vb;
struct fps_counter fps_counter;
@ -46,10 +51,25 @@ struct screen {
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;
};
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;
struct sc_size frame_size;
bool always_on_top;
@ -71,65 +91,65 @@ struct screen_params {
// initialize screen, create window, renderer and texture (window is hidden)
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
// must be called before screen_join()
void
screen_interrupt(struct screen *screen);
sc_screen_interrupt(struct sc_screen *screen);
// join any inner thread
void
screen_join(struct screen *screen);
sc_screen_join(struct sc_screen *screen);
// destroy window, renderer and texture (if any)
void
screen_destroy(struct screen *screen);
sc_screen_destroy(struct sc_screen *screen);
// hide the window
//
// It is used to hide the window immediately on closing without waiting for
// screen_destroy()
void
screen_hide_window(struct screen *screen);
sc_screen_hide_window(struct sc_screen *screen);
// switch the fullscreen mode
void
screen_switch_fullscreen(struct screen *screen);
sc_screen_switch_fullscreen(struct sc_screen *screen);
// resize window to optimal size (remove black borders)
void
screen_resize_to_fit(struct screen *screen);
sc_screen_resize_to_fit(struct sc_screen *screen);
// resize window to 1:1 (pixel-perfect)
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)
void
screen_set_rotation(struct screen *screen, unsigned rotation);
sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation);
// react to SDL events
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
// x and y are expressed in pixels
struct sc_point
screen_convert_window_to_frame_coords(struct screen *screen,
int32_t x, int32_t y);
sc_screen_convert_window_to_frame_coords(struct sc_screen *screen,
int32_t x, int32_t y);
// convert point from drawable coordinates to frame coordinates
// x and y are expressed in pixels
struct sc_point
screen_convert_drawable_to_frame_coords(struct screen *screen,
int32_t x, int32_t y);
sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
int32_t x, int32_t y);
// Convert coordinates from window to drawable.
// Events are expressed in window coordinates, but content is expressed in
// drawable coordinates. They are the same if HiDPI scaling is 1, but differ
// otherwise.
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

View File

@ -34,7 +34,7 @@ get_server_path(void) {
char *server_path = strdup(server_path_env);
#endif
if (!server_path) {
LOGE("Could not allocate memory");
LOG_OOM();
return NULL;
}
LOGD("Using SCRCPY_SERVER_PATH: %s", server_path);
@ -45,7 +45,7 @@ get_server_path(void) {
LOGD("Using server: " SC_SERVER_PATH_DEFAULT);
char *server_path = strdup(SC_SERVER_PATH_DEFAULT);
if (!server_path) {
LOGE("Could not allocate memory");
LOG_OOM();
return NULL;
}
#else
@ -69,6 +69,7 @@ sc_server_params_destroy(struct sc_server_params *params) {
free((char *) params->crop);
free((char *) params->codec_options);
free((char *) params->encoder_name);
free((char *) params->tcpip_dst);
}
static bool
@ -92,6 +93,7 @@ sc_server_params_copy(struct sc_server_params *dst,
COPY(crop);
COPY(codec_options);
COPY(encoder_name);
COPY(tcpip_dst);
#undef COPY
return true;
@ -112,7 +114,7 @@ push_server(struct sc_intr *intr, const char *serial) {
free(server_path);
return false;
}
bool ok = adb_push(intr, serial, server_path, SC_DEVICE_SERVER_PATH);
bool ok = adb_push(intr, serial, server_path, SC_DEVICE_SERVER_PATH, 0);
free(server_path);
return ok;
}
@ -136,28 +138,34 @@ log_level_to_server_string(enum sc_log_level level) {
}
}
static bool
sc_server_sleep(struct sc_server *server, sc_tick deadline) {
sc_mutex_lock(&server->mutex);
bool timed_out = false;
while (!server->stopped && !timed_out) {
timed_out = !sc_cond_timedwait(&server->cond_stopped,
&server->mutex, deadline);
}
bool stopped = server->stopped;
sc_mutex_unlock(&server->mutex);
return !stopped;
}
static sc_pid
execute_server(struct sc_server *server,
const struct sc_server_params *params) {
const char *serial = server->params.serial;
sc_pid pid = SC_PROCESS_NONE;
const char *cmd[128];
unsigned count = 0;
cmd[count++] = "shell";
cmd[count++] = "CLASSPATH=" SC_DEVICE_SERVER_PATH;
cmd[count++] = "app_process";
char max_size_string[6];
char bit_rate_string[11];
char max_fps_string[6];
char lock_video_orientation_string[5];
char display_id_string[11];
sprintf(max_size_string, "%"PRIu16, params->max_size);
sprintf(bit_rate_string, "%"PRIu32, params->bit_rate);
sprintf(max_fps_string, "%"PRIu16, params->max_fps);
sprintf(lock_video_orientation_string, "%"PRIi8,
params->lock_video_orientation);
sprintf(display_id_string, "%"PRIu32, params->display_id);
const char *const cmd[] = {
"shell",
"CLASSPATH=" SC_DEVICE_SERVER_PATH,
"app_process",
#ifdef SERVER_DEBUGGER
# define SERVER_DEBUGGER_PORT "5005"
cmd[count++] =
# ifdef SERVER_DEBUGGER_METHOD_NEW
/* Android 9 and above */
"-XjdwpProvider:internal -XjdwpOptions:transport=dt_socket,suspend=y,"
@ -166,27 +174,70 @@ execute_server(struct sc_server *server,
/* Android 8 and below */
"-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address="
# endif
SERVER_DEBUGGER_PORT,
SERVER_DEBUGGER_PORT;
#endif
"/", // unused
"com.genymobile.scrcpy.Server",
SCRCPY_VERSION,
log_level_to_server_string(params->log_level),
max_size_string,
bit_rate_string,
max_fps_string,
lock_video_orientation_string,
server->tunnel.forward ? "true" : "false",
params->crop ? params->crop : "-",
"true", // always send frame meta (packet boundaries + timestamp)
params->control ? "true" : "false",
display_id_string,
params->show_touches ? "true" : "false",
params->stay_awake ? "true" : "false",
params->codec_options ? params->codec_options : "-",
params->encoder_name ? params->encoder_name : "-",
params->power_off_on_close ? "true" : "false",
};
cmd[count++] = "/"; // unused
cmd[count++] = "com.genymobile.scrcpy.Server";
cmd[count++] = SCRCPY_VERSION;
unsigned dyn_idx = count; // from there, the strings are allocated
#define ADD_PARAM(fmt, ...) { \
char *p = (char *) &cmd[count]; \
if (asprintf(&p, fmt, ## __VA_ARGS__) == -1) { \
goto end; \
} \
cmd[count++] = p; \
}
ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level));
ADD_PARAM("bit_rate=%" PRIu32, params->bit_rate);
if (params->max_size) {
ADD_PARAM("max_size=%" PRIu16, params->max_size);
}
if (params->max_fps) {
ADD_PARAM("max_fps=%" PRIu16, params->max_fps);
}
if (params->lock_video_orientation != SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
ADD_PARAM("lock_video_orientation=%" PRIi8,
params->lock_video_orientation);
}
if (server->tunnel.forward) {
ADD_PARAM("tunnel_forward=true");
}
if (params->crop) {
ADD_PARAM("crop=%s", params->crop);
}
if (!params->control) {
// By default, control is true
ADD_PARAM("control=false");
}
if (params->display_id) {
ADD_PARAM("display_id=%" PRIu32, params->display_id);
}
if (params->show_touches) {
ADD_PARAM("show_touches=true");
}
if (params->stay_awake) {
ADD_PARAM("stay_awake=true");
}
if (params->codec_options) {
ADD_PARAM("codec_options=%s", params->codec_options);
}
if (params->encoder_name) {
ADD_PARAM("encoder_name=%s", params->encoder_name);
}
if (params->power_off_on_close) {
ADD_PARAM("power_off_on_close=true");
}
if (!params->clipboard_autosync) {
// By default, clipboard_autosync is true
ADD_PARAM("clipboard_autosync=false");
}
#undef ADD_PARAM
#undef STRBOOL
#ifdef SERVER_DEBUGGER
LOGI("Server debugger waiting for a client on device port "
SERVER_DEBUGGER_PORT "...");
@ -198,12 +249,21 @@ execute_server(struct sc_server *server,
// Port: 5005
// Then click on "Debug"
#endif
return adb_execute(serial, cmd, ARRAY_LEN(cmd));
// Inherit both stdout and stderr (all server logs are printed to stdout)
pid = adb_execute(params->serial, cmd, count, 0);
end:
for (unsigned i = dyn_idx; i < count; ++i) {
free((char *) cmd[i]);
}
return pid;
}
static bool
connect_and_read_byte(struct sc_intr *intr, sc_socket socket, uint16_t port) {
bool ok = net_connect_intr(intr, socket, IPV4_LOCALHOST, port);
connect_and_read_byte(struct sc_intr *intr, sc_socket socket,
uint32_t tunnel_host, uint16_t tunnel_port) {
bool ok = net_connect_intr(intr, socket, tunnel_host, tunnel_port);
if (!ok) {
return false;
}
@ -220,13 +280,13 @@ connect_and_read_byte(struct sc_intr *intr, sc_socket socket, uint16_t port) {
}
static sc_socket
connect_to_server(struct sc_server *server, uint32_t attempts, sc_tick delay) {
uint16_t port = server->tunnel.local_port;
connect_to_server(struct sc_server *server, unsigned attempts, sc_tick delay,
uint32_t host, uint16_t port) {
do {
LOGD("Remaining connection attempts: %d", (int) attempts);
LOGD("Remaining connection attempts: %u", attempts);
sc_socket socket = net_socket();
if (socket != SC_SOCKET_NONE) {
bool ok = connect_and_read_byte(&server->intr, socket, port);
bool ok = connect_and_read_byte(&server->intr, socket, host, port);
if (ok) {
// it worked!
return socket;
@ -241,22 +301,14 @@ connect_to_server(struct sc_server *server, uint32_t attempts, sc_tick delay) {
}
if (attempts) {
sc_mutex_lock(&server->mutex);
sc_tick deadline = sc_tick_now() + delay;
bool timed_out = false;
while (!server->stopped && !timed_out) {
timed_out = !sc_cond_timedwait(&server->cond_stopped,
&server->mutex, deadline);
}
bool stopped = server->stopped;
sc_mutex_unlock(&server->mutex);
if (stopped) {
bool ok = sc_server_sleep(server, deadline);
if (!ok) {
LOGI("Connection attempt stopped");
break;
}
}
} while (--attempts > 0);
} while (--attempts);
return SC_SOCKET_NONE;
}
@ -265,20 +317,18 @@ sc_server_init(struct sc_server *server, const struct sc_server_params *params,
const struct sc_server_callbacks *cbs, void *cbs_userdata) {
bool ok = sc_server_params_copy(&server->params, params);
if (!ok) {
LOGE("Could not copy server params");
LOG_OOM();
return false;
}
ok = sc_mutex_init(&server->mutex);
if (!ok) {
LOGE("Could not create server mutex");
sc_server_params_destroy(&server->params);
return false;
}
ok = sc_cond_init(&server->cond_stopped);
if (!ok) {
LOGE("Could not create server cond_stopped");
sc_mutex_destroy(&server->mutex);
sc_server_params_destroy(&server->params);
return false;
@ -286,7 +336,6 @@ sc_server_init(struct sc_server *server, const struct sc_server_params *params,
ok = sc_intr_init(&server->intr);
if (!ok) {
LOGE("Could not create intr");
sc_cond_destroy(&server->cond_stopped);
sc_mutex_destroy(&server->mutex);
sc_server_params_destroy(&server->params);
@ -338,6 +387,7 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
assert(tunnel->enabled);
const char *serial = server->params.serial;
bool control = server->params.control;
sc_socket video_socket = SC_SOCKET_NONE;
sc_socket control_socket = SC_SOCKET_NONE;
@ -347,27 +397,44 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
goto fail;
}
control_socket = net_accept_intr(&server->intr, tunnel->server_socket);
if (control_socket == SC_SOCKET_NONE) {
goto fail;
if (control) {
control_socket =
net_accept_intr(&server->intr, tunnel->server_socket);
if (control_socket == SC_SOCKET_NONE) {
goto fail;
}
}
} else {
uint32_t attempts = 100;
uint32_t tunnel_host = server->params.tunnel_host;
if (!tunnel_host) {
tunnel_host = IPV4_LOCALHOST;
}
uint16_t tunnel_port = server->params.tunnel_port;
if (!tunnel_port) {
tunnel_port = tunnel->local_port;
}
unsigned attempts = 100;
sc_tick delay = SC_TICK_FROM_MS(100);
video_socket = connect_to_server(server, attempts, delay);
video_socket = connect_to_server(server, attempts, delay, tunnel_host,
tunnel_port);
if (video_socket == SC_SOCKET_NONE) {
goto fail;
}
// we know that the device is listening, we don't need several attempts
control_socket = net_socket();
if (control_socket == SC_SOCKET_NONE) {
goto fail;
}
bool ok = net_connect_intr(&server->intr, control_socket,
IPV4_LOCALHOST, tunnel->local_port);
if (!ok) {
goto fail;
if (control) {
// we know that the device is listening, we don't need several
// attempts
control_socket = net_socket();
if (control_socket == SC_SOCKET_NONE) {
goto fail;
}
bool ok = net_connect_intr(&server->intr, control_socket,
tunnel_host, tunnel_port);
if (!ok) {
goto fail;
}
}
}
@ -381,7 +448,7 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
}
assert(video_socket != SC_SOCKET_NONE);
assert(control_socket != SC_SOCKET_NONE);
assert(!control || control_socket != SC_SOCKET_NONE);
server->video_socket = video_socket;
server->control_socket = control_socket;
@ -430,28 +497,221 @@ sc_server_fill_serial(struct sc_server *server) {
// device/emulator" error)
if (!server->params.serial) {
// The serial is owned by sc_server_params, and will be freed on destroy
server->params.serial = adb_get_serialno(&server->intr);
server->params.serial = adb_get_serialno(&server->intr, 0);
if (!server->params.serial) {
LOGE("Could not get device serial");
return false;
}
LOGD("Device serial: %s", server->params.serial);
}
return true;
}
static bool
is_tcpip_mode_enabled(struct sc_server *server) {
struct sc_intr *intr = &server->intr;
const char *serial = server->params.serial;
char *current_port =
adb_getprop(intr, serial, "service.adb.tcp.port", SC_ADB_SILENT);
if (!current_port) {
return false;
}
// Is the device is listening on TCP on port 5555?
bool enabled = !strcmp("5555", current_port);
free(current_port);
return enabled;
}
static bool
wait_tcpip_mode_enabled(struct sc_server *server, unsigned attempts,
sc_tick delay) {
if (is_tcpip_mode_enabled(server)) {
LOGI("TCP/IP mode enabled");
return true;
}
// Only print this log if TCP/IP is not enabled
LOGI("Waiting for TCP/IP mode enabled...");
do {
sc_tick deadline = sc_tick_now() + delay;
if (!sc_server_sleep(server, deadline)) {
LOGI("TCP/IP mode waiting interrupted");
return false;
}
if (is_tcpip_mode_enabled(server)) {
LOGI("TCP/IP mode enabled");
return true;
}
} while (--attempts);
return false;
}
char *
append_port_5555(const char *ip) {
size_t len = strlen(ip);
// sizeof counts the final '\0'
char *ip_port = malloc(len + sizeof(":5555"));
if (!ip_port) {
LOG_OOM();
return NULL;
}
memcpy(ip_port, ip, len);
memcpy(ip_port + len, ":5555", sizeof(":5555"));
return ip_port;
}
static bool
sc_server_switch_to_tcpip(struct sc_server *server, char **out_ip_port) {
const char *serial = server->params.serial;
assert(serial);
struct sc_intr *intr = &server->intr;
char *ip = adb_get_device_ip(intr, serial, 0);
if (!ip) {
LOGE("Device IP not found");
return false;
}
char *ip_port = append_port_5555(ip);
free(ip);
if (!ip_port) {
return false;
}
bool tcp_mode = is_tcpip_mode_enabled(server);
if (!tcp_mode) {
bool ok = adb_tcpip(intr, serial, 5555, SC_ADB_NO_STDOUT);
if (!ok) {
LOGE("Could not restart adbd in TCP/IP mode");
goto error;
}
unsigned attempts = 40;
sc_tick delay = SC_TICK_FROM_MS(250);
ok = wait_tcpip_mode_enabled(server, attempts, delay);
if (!ok) {
goto error;
}
}
*out_ip_port = ip_port;
return true;
error:
free(ip_port);
return false;
}
static bool
sc_server_connect_to_tcpip(struct sc_server *server, const char *ip_port) {
struct sc_intr *intr = &server->intr;
// Error expected if not connected, do not report any error
adb_disconnect(intr, ip_port, SC_ADB_SILENT);
bool ok = adb_connect(intr, ip_port, 0);
if (!ok) {
LOGE("Could not connect to %s", ip_port);
return false;
}
// Override the serial, owned by the sc_server_params
free((void *) server->params.serial);
server->params.serial = strdup(ip_port);
if (!server->params.serial) {
LOG_OOM();
return false;
}
LOGI("Connected to %s", ip_port);
return true;
}
static bool
sc_server_configure_tcpip(struct sc_server *server) {
char *ip_port;
const struct sc_server_params *params = &server->params;
// If tcpip parameter is given, then it must connect to this address.
// Therefore, the device is unknown, so serial is meaningless at this point.
assert(!params->serial || !params->tcpip_dst);
if (params->tcpip_dst) {
// Append ":5555" if no port is present
bool contains_port = strchr(params->tcpip_dst, ':');
ip_port = contains_port ? strdup(params->tcpip_dst)
: append_port_5555(params->tcpip_dst);
if (!ip_port) {
LOG_OOM();
return false;
}
} else {
// The device IP address must be retrieved from the current
// connected device
if (!sc_server_fill_serial(server)) {
return false;
}
// The serial is either the real serial when connected via USB, or
// the IP:PORT when connected over TCP/IP. Only the latter contains
// a colon.
bool is_already_tcpip = strchr(params->serial, ':');
if (is_already_tcpip) {
// Nothing to do
LOGI("Device already connected via TCP/IP: %s", params->serial);
return true;
}
bool ok = sc_server_switch_to_tcpip(server, &ip_port);
if (!ok) {
return false;
}
}
// On success, this call changes params->serial
bool ok = sc_server_connect_to_tcpip(server, ip_port);
free(ip_port);
return ok;
}
static int
run_server(void *data) {
struct sc_server *server = data;
const struct sc_server_params *params = &server->params;
if (params->serial) {
LOGD("Device serial: %s", params->serial);
}
if (params->tcpip) {
// params->serial may be changed after this call
bool ok = sc_server_configure_tcpip(server);
if (!ok) {
goto error_connection_failed;
}
}
// It is ok to call this function even if the device serial has been
// changed by switching over TCP/IP
if (!sc_server_fill_serial(server)) {
goto error_connection_failed;
}
const struct sc_server_params *params = &server->params;
LOGD("Device serial: %s", params->serial);
bool ok = push_server(&server->intr, params->serial);
if (!ok) {
goto error_connection_failed;
@ -502,6 +762,17 @@ run_server(void *data) {
}
sc_mutex_unlock(&server->mutex);
// Interrupt sockets to wake up socket blocking calls on the server
assert(server->video_socket != SC_SOCKET_NONE);
net_interrupt(server->video_socket);
net_close(server->video_socket);
if (server->control_socket != SC_SOCKET_NONE) {
// There is no control_socket if --no-control is set
net_interrupt(server->control_socket);
net_close(server->control_socket);
}
// Give some delay for the server to terminate properly
#define WATCHDOG_DELAY SC_TICK_FROM_SEC(1)
sc_tick deadline = sc_tick_now() + WATCHDOG_DELAY;
@ -532,7 +803,8 @@ error_connection_failed:
bool
sc_server_start(struct sc_server *server) {
bool ok = sc_thread_create(&server->thread, run_server, "server", server);
bool ok =
sc_thread_create(&server->thread, run_server, "scrcpy-server", server);
if (!ok) {
LOGE("Could not create server thread");
return false;

View File

@ -29,6 +29,8 @@ struct sc_server_params {
const char *codec_options;
const char *encoder_name;
struct sc_port_range port_range;
uint32_t tunnel_host;
uint16_t tunnel_port;
uint16_t max_size;
uint32_t bit_rate;
uint16_t max_fps;
@ -39,6 +41,9 @@ struct sc_server_params {
bool stay_awake;
bool force_adb_forward;
bool power_off_on_close;
bool clipboard_autosync;
bool tcpip;
const char *tcpip_dst;
};
struct sc_server {

View File

@ -1,7 +1,6 @@
#include "stream.h"
#include <assert.h>
#include <libavformat/avformat.h>
#include <libavutil/time.h>
#include <unistd.h>
@ -42,7 +41,7 @@ stream_recv_packet(struct stream *stream, AVPacket *packet) {
assert(len);
if (av_new_packet(packet, len)) {
LOGE("Could not allocate packet");
LOG_OOM();
return false;
}
@ -111,18 +110,18 @@ stream_push_packet(struct stream *stream, AVPacket *packet) {
if (stream->pending) {
offset = stream->pending->size;
if (av_grow_packet(stream->pending, packet->size)) {
LOGE("Could not grow packet");
LOG_OOM();
return false;
}
} else {
offset = 0;
stream->pending = av_packet_alloc();
if (!stream->pending) {
LOGE("Could not allocate packet");
LOG_OOM();
return false;
}
if (av_new_packet(stream->pending, packet->size)) {
LOGE("Could not create packet");
LOG_OOM();
av_packet_free(&stream->pending);
return false;
}
@ -192,7 +191,7 @@ static int
run_stream(void *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) {
LOGE("H.264 decoder not found");
goto end;
@ -200,7 +199,7 @@ run_stream(void *data) {
stream->codec_ctx = avcodec_alloc_context3(codec);
if (!stream->codec_ctx) {
LOGC("Could not allocate codec context");
LOG_OOM();
goto end;
}
@ -221,7 +220,7 @@ run_stream(void *data) {
AVPacket *packet = av_packet_alloc();
if (!packet) {
LOGE("Could not allocate packet");
LOG_OOM();
goto finally_close_parser;
}
@ -284,7 +283,8 @@ bool
stream_start(struct stream *stream) {
LOGD("Starting stream thread");
bool ok = sc_thread_create(&stream->thread, run_stream, "stream", stream);
bool ok =
sc_thread_create(&stream->thread, run_stream, "scrcpy-stream", stream);
if (!ok) {
LOGC("Could not start stream thread");
return false;

View File

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

View File

@ -2,10 +2,13 @@
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "util/log.h"
bool
sc_file_executable_exists(const char *file) {
char *path = getenv("PATH");
@ -23,7 +26,10 @@ sc_file_executable_exists(const char *file) {
size_t dir_len = strlen(dir);
char *fullpath = malloc(dir_len + file_len + 2);
if (!fullpath)
{
LOG_OOM();
continue;
}
memcpy(fullpath, dir, dir_len);
fullpath[dir_len] = '/';
memcpy(fullpath + dir_len + 1, file, file_len + 1);

View File

@ -11,8 +11,11 @@
#include "util/log.h"
enum sc_process_result
sc_process_execute_p(const char *const argv[], sc_pid *pid,
sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
int *pin, int *pout, int *perr) {
bool inherit_stdout = !pout && !(flags & SC_PROCESS_NO_STDOUT);
bool inherit_stderr = !perr && !(flags & SC_PROCESS_NO_STDERR);
int in[2];
int out[2];
int err[2];
@ -90,22 +93,39 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid,
}
close(in[1]);
}
// Do not close stdin in the child process, this makes adb fail on Linux
if (pout) {
if (out[1] != STDOUT_FILENO) {
dup2(out[1], STDOUT_FILENO);
close(out[1]);
}
close(out[0]);
} else if (!inherit_stdout) {
// Close stdout in the child process
close(STDOUT_FILENO);
}
if (perr) {
if (err[1] != STDERR_FILENO) {
dup2(err[1], STDERR_FILENO);
close(err[1]);
}
close(err[0]);
} else if (!inherit_stderr) {
// Close stderr in the child process
close(STDERR_FILENO);
}
close(internal[0]);
enum sc_process_result err;
// Somehow SDL masks many signals - undo them for other processes
// https://github.com/libsdl-org/SDL/blob/release-2.0.18/src/thread/pthread/SDL_systhread.c#L167
sigset_t mask;
sigemptyset(&mask);
sigprocmask(SIG_SETMASK, &mask, NULL);
if (fcntl(internal[1], F_SETFD, FD_CLOEXEC) == 0) {
execvp(argv[0], (char *const *) argv);
perror("exec");

View File

@ -26,7 +26,7 @@ bool
sc_file_is_regular(const char *path) {
wchar_t *wide_path = sc_str_to_wchars(path);
if (!wide_path) {
LOGC("Could not allocate wide char string");
LOG_OOM();
return false;
}

View File

@ -1,6 +1,3 @@
// <https://devblogs.microsoft.com/oldnewthing/20111216-00/?p=8873>
#define _WIN32_WINNT 0x0600 // For extended process API
#include "util/process.h"
#include <processthreadsapi.h>
@ -27,12 +24,15 @@ build_cmd(char *cmd, size_t len, const char *const argv[]) {
}
enum sc_process_result
sc_process_execute_p(const char *const argv[], HANDLE *handle,
sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags,
HANDLE *pin, HANDLE *pout, HANDLE *perr) {
enum sc_process_result ret = SC_PROCESS_ERROR_GENERIC;
bool inherit_stdout = !pout && !(flags & SC_PROCESS_NO_STDOUT);
bool inherit_stderr = !perr && !(flags & SC_PROCESS_NO_STDERR);
// Add 1 per non-NULL pointer
unsigned handle_count = !!pin + !!pout + !!perr;
unsigned handle_count = !!pin || !!pout || !!perr;
enum sc_process_result ret = SC_PROCESS_ERROR_GENERIC;
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
@ -79,17 +79,30 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle,
si.StartupInfo.cb = sizeof(si);
HANDLE handles[3];
si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
if (inherit_stdout) {
si.StartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
}
if (inherit_stderr) {
si.StartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
}
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL;
if (handle_count) {
si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
unsigned i = 0;
if (pin) {
si.StartupInfo.hStdInput = stdin_read_handle;
handles[i++] = si.StartupInfo.hStdInput;
}
if (pout) {
assert(!inherit_stdout);
si.StartupInfo.hStdOutput = stdout_write_handle;
handles[i++] = si.StartupInfo.hStdOutput;
}
if (perr) {
assert(!inherit_stderr);
si.StartupInfo.hStdError = stderr_write_handle;
handles[i++] = si.StartupInfo.hStdError;
}
SIZE_T size;
@ -103,6 +116,7 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle,
lpAttributeList = malloc(size);
if (!lpAttributeList) {
LOG_OOM();
goto error_close_stderr;
}
@ -112,17 +126,6 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle,
goto error_close_stderr;
}
// Explicitly pass the HANDLEs that must be inherited
unsigned i = 0;
if (pin) {
handles[i++] = stdin_read_handle;
}
if (pout) {
handles[i++] = stdout_write_handle;
}
if (perr) {
handles[i++] = stderr_write_handle;
}
ok = UpdateProcThreadAttribute(lpAttributeList, 0,
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
handles, handle_count * sizeof(HANDLE),
@ -136,23 +139,33 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle,
char *cmd = malloc(CMD_MAX_LEN);
if (!cmd || !build_cmd(cmd, CMD_MAX_LEN, argv)) {
LOG_OOM();
goto error_free_attribute_list;
}
wchar_t *wide = sc_str_to_wchars(cmd);
free(cmd);
if (!wide) {
LOGC("Could not allocate wide char string");
LOG_OOM();
goto error_free_attribute_list;
}
BOOL bInheritHandles = handle_count > 0;
DWORD dwCreationFlags = handle_count > 0 ? EXTENDED_STARTUPINFO_PRESENT : 0;
BOOL bInheritHandles = handle_count > 0 || inherit_stdout || inherit_stderr;
DWORD dwCreationFlags = 0;
if (handle_count > 0) {
dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT;
}
if (!inherit_stdout && !inherit_stderr) {
// DETACHED_PROCESS to disable stdin, stdout and stderr
dwCreationFlags |= DETACHED_PROCESS;
}
BOOL ok = CreateProcessW(NULL, wide, NULL, NULL, bInheritHandles,
dwCreationFlags, NULL, NULL, &si.StartupInfo, &pi);
free(wide);
if (!ok) {
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
int err = GetLastError();
LOGE("CreateProcessW() error %d", err);
if (err == ERROR_FILE_NOT_FOUND) {
ret = SC_PROCESS_ERROR_MISSING_BINARY;
}
goto error_free_attribute_list;

View File

@ -6,7 +6,7 @@
#include <assert.h>
#include <stdbool.h>
#include <SDL2/SDL_events.h>
#include "input_events.h"
/**
* Key processor trait.
@ -14,16 +14,42 @@
* Component able to process and inject keys should implement this trait.
*/
struct sc_key_processor {
/**
* Set by the implementation to indicate that it must explicitly wait for
* the clipboard to be set on the device before injecting Ctrl+v to avoid
* race conditions. If it is set, the input_manager will pass a valid
* ack_to_wait to process_key() in case of clipboard synchronization
* resulting of the key event.
*/
bool async_paste;
const struct sc_key_processor_ops *ops;
};
struct sc_key_processor_ops {
void
(*process_key)(struct sc_key_processor *kp, const SDL_KeyboardEvent *event);
/**
* Process a keyboard event
*
* The `sequence` number (if different from `SC_SEQUENCE_INVALID`) indicates
* the acknowledgement number to wait for before injecting this event.
* This allows to ensure that the device clipboard is set before injecting
* Ctrl+v on the device.
*
* This function is mandatory.
*/
void
(*process_key)(struct sc_key_processor *kp,
const struct sc_key_event *event, uint64_t ack_to_wait);
/**
* Process an input text
*
* This function is optional.
*/
void
(*process_text)(struct sc_key_processor *kp,
const SDL_TextInputEvent *event);
const struct sc_text_event *event);
};
#endif

View File

@ -6,7 +6,7 @@
#include <assert.h>
#include <stdbool.h>
#include <SDL2/SDL_events.h>
#include "input_events.h"
/**
* Mouse processor trait.
@ -16,24 +16,51 @@
*/
struct sc_mouse_processor {
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 {
/**
* Process a mouse motion event
*
* This function is mandatory.
*/
void
(*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
(*process_touch)(struct sc_mouse_processor *mp,
const SDL_TouchFingerEvent *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);
const struct sc_touch_event *event);
};
#endif

76
app/src/util/acksync.c Normal file
View File

@ -0,0 +1,76 @@
#include "acksync.h"
#include <assert.h>
#include "util/log.h"
bool
sc_acksync_init(struct sc_acksync *as) {
bool ok = sc_mutex_init(&as->mutex);
if (!ok) {
return false;
}
ok = sc_cond_init(&as->cond);
if (!ok) {
sc_mutex_destroy(&as->mutex);
return false;
}
as->stopped = false;
as->ack = SC_SEQUENCE_INVALID;
return true;
}
void
sc_acksync_destroy(struct sc_acksync *as) {
sc_cond_destroy(&as->cond);
sc_mutex_destroy(&as->mutex);
}
void
sc_acksync_ack(struct sc_acksync *as, uint64_t sequence) {
sc_mutex_lock(&as->mutex);
// Acknowledgements must be monotonic
assert(sequence >= as->ack);
as->ack = sequence;
sc_cond_signal(&as->cond);
sc_mutex_unlock(&as->mutex);
}
enum sc_acksync_wait_result
sc_acksync_wait(struct sc_acksync *as, uint64_t ack, sc_tick deadline) {
sc_mutex_lock(&as->mutex);
bool timed_out = false;
while (!as->stopped && as->ack < ack && !timed_out) {
timed_out = !sc_cond_timedwait(&as->cond, &as->mutex, deadline);
}
enum sc_acksync_wait_result ret;
if (as->stopped) {
ret = SC_ACKSYNC_WAIT_INTR;
} else if (as->ack >= ack) {
ret = SC_ACKSYNC_WAIT_OK;
} else {
assert(timed_out);
ret = SC_ACKSYNC_WAIT_TIMEOUT;
}
sc_mutex_unlock(&as->mutex);
return ret;
}
/**
* Interrupt any `sc_acksync_wait()`
*/
void
sc_acksync_interrupt(struct sc_acksync *as) {
sc_mutex_lock(&as->mutex);
as->stopped = true;
sc_cond_signal(&as->cond);
sc_mutex_unlock(&as->mutex);
}

66
app/src/util/acksync.h Normal file
View File

@ -0,0 +1,66 @@
#ifndef SC_ACK_SYNC_H
#define SC_ACK_SYNC_H
#include "common.h"
#include "thread.h"
#define SC_SEQUENCE_INVALID 0
/**
* Helper to wait for acknowledgments
*
* In practice, it is used to wait for device clipboard acknowledgement from the
* server before injecting Ctrl+v via AOA HID, in order to avoid pasting the
* content of the old device clipboard (if Ctrl+v was injected before the
* clipboard content was actually set).
*/
struct sc_acksync {
sc_mutex mutex;
sc_cond cond;
bool stopped;
// Last acked value, initially SC_SEQUENCE_INVALID
uint64_t ack;
};
enum sc_acksync_wait_result {
// Acknowledgment received
SC_ACKSYNC_WAIT_OK,
// Timeout expired
SC_ACKSYNC_WAIT_TIMEOUT,
// Interrupted from another thread by sc_acksync_interrupt()
SC_ACKSYNC_WAIT_INTR,
};
bool
sc_acksync_init(struct sc_acksync *as);
void
sc_acksync_destroy(struct sc_acksync *as);
/**
* Acknowledge `sequence`
*
* The `sequence` must be greater than (or equal to) any previous acknowledged
* sequence.
*/
void
sc_acksync_ack(struct sc_acksync *as, uint64_t sequence);
/**
* Wait for acknowledgment of sequence `ack` (or higher)
*/
enum sc_acksync_wait_result
sc_acksync_wait(struct sc_acksync *as, uint64_t ack, sc_tick deadline);
/**
* Interrupt any `sc_acksync_wait()`
*/
void
sc_acksync_interrupt(struct sc_acksync *as);
#endif

View File

@ -31,7 +31,7 @@ sc_file_get_local_path(const char *name) {
size_t len = dirlen + namelen + 2; // +2: '/' and '\0'
char *file_path = malloc(len);
if (!file_path) {
LOGE("Could not alloc path");
LOG_OOM();
free(executable_path);
return NULL;
}

13
app/src/util/intmap.c Normal file
View File

@ -0,0 +1,13 @@
#include "intmap.h"
const struct sc_intmap_entry *
sc_intmap_find_entry(const struct sc_intmap_entry entries[], size_t len,
int32_t key) {
for (size_t i = 0; i < len; ++i) {
const struct sc_intmap_entry *entry = &entries[i];
if (entry->key == key) {
return entry;
}
}
return NULL;
}

24
app/src/util/intmap.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef SC_ARRAYMAP_H
#define SC_ARRAYMAP_H
#include "common.h"
#include <stdint.h>
struct sc_intmap_entry {
int32_t key;
int32_t value;
};
const struct sc_intmap_entry *
sc_intmap_find_entry(const struct sc_intmap_entry entries[], size_t len,
int32_t key);
/**
* MAP is expected to be a static array of sc_intmap_entry, so that
* ARRAY_LEN(MAP) can be computed statically.
*/
#define SC_INTMAP_FIND_ENTRY(MAP, KEY) \
sc_intmap_find_entry(MAP, ARRAY_LEN(MAP), KEY)
#endif

View File

@ -8,7 +8,7 @@ bool
sc_intr_init(struct sc_intr *intr) {
bool ok = sc_mutex_init(&intr->mutex);
if (!ok) {
LOGE("Could not init intr mutex");
LOG_OOM();
return false;
}

View File

@ -7,6 +7,9 @@
#include "options.h"
#define LOG_STR_IMPL_(x) # x
#define LOG_STR(x) LOG_STR_IMPL_(x)
#define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
@ -14,6 +17,9 @@
#define LOGE(...) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGC(...) SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOG_OOM() \
LOGC("OOM: %s:%d %s()", __FILE__, __LINE__, __func__)
void
sc_set_log_level(enum sc_log_level level);

View File

@ -1,25 +1,30 @@
#include "net.h"
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <SDL2/SDL_platform.h>
#include "log.h"
#ifdef __WINDOWS__
# include <ws2tcpip.h>
typedef int socklen_t;
typedef SOCKET sc_raw_socket;
# define SC_RAW_SOCKET_NONE INVALID_SOCKET
#else
# include <sys/types.h>
# include <sys/socket.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <unistd.h>
# include <fcntl.h>
# define SOCKET_ERROR -1
typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR;
typedef struct in_addr IN_ADDR;
typedef int sc_raw_socket;
# define SC_RAW_SOCKET_NONE -1
#endif
bool
@ -51,6 +56,7 @@ wrap(sc_raw_socket sock) {
struct sc_socket_windows *socket = malloc(sizeof(*socket));
if (!socket) {
LOG_OOM();
closesocket(sock);
return SC_SOCKET_NONE;
}
@ -77,6 +83,37 @@ unwrap(sc_socket socket) {
#endif
}
#ifndef HAVE_SOCK_CLOEXEC // avoid unused-function warning
static inline bool
sc_raw_socket_close(sc_raw_socket raw_sock) {
#ifndef _WIN32
return !close(raw_sock);
#else
return !closesocket(raw_sock);
#endif
}
#endif
#ifndef HAVE_SOCK_CLOEXEC
// If SOCK_CLOEXEC does not exist, the flag must be set manually once the
// socket is created
static bool
set_cloexec_flag(sc_raw_socket raw_sock) {
#ifndef _WIN32
if (fcntl(raw_sock, F_SETFD, FD_CLOEXEC) == -1) {
perror("fcntl F_SETFD");
return false;
}
#else
if (!SetHandleInformation((HANDLE) raw_sock, HANDLE_FLAG_INHERIT, 0)) {
LOGE("SetHandleInformation socket failed");
return false;
}
#endif
return true;
}
#endif
static void
net_perror(const char *s) {
#ifdef _WIN32
@ -95,7 +132,16 @@ net_perror(const char *s) {
sc_socket
net_socket(void) {
#ifdef HAVE_SOCK_CLOEXEC
sc_raw_socket raw_sock = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
#else
sc_raw_socket raw_sock = socket(AF_INET, SOCK_STREAM, 0);
if (raw_sock != SC_RAW_SOCKET_NONE && !set_cloexec_flag(raw_sock)) {
sc_raw_socket_close(raw_sock);
return SC_SOCKET_NONE;
}
#endif
sc_socket sock = wrap(raw_sock);
if (sock == SC_SOCKET_NONE) {
net_perror("socket");
@ -154,8 +200,18 @@ net_accept(sc_socket server_socket) {
SOCKADDR_IN csin;
socklen_t sinsize = sizeof(csin);
#ifdef HAVE_SOCK_CLOEXEC
sc_raw_socket raw_sock =
accept4(raw_server_socket, (SOCKADDR *) &csin, &sinsize, SOCK_CLOEXEC);
#else
sc_raw_socket raw_sock =
accept(raw_server_socket, (SOCKADDR *) &csin, &sinsize);
if (raw_sock != SC_RAW_SOCKET_NONE && !set_cloexec_flag(raw_sock)) {
sc_raw_socket_close(raw_sock);
return SC_SOCKET_NONE;
}
#endif
return wrap(raw_sock);
}
@ -209,7 +265,6 @@ net_interrupt(sc_socket socket) {
#endif
}
#include <errno.h>
bool
net_close(sc_socket socket) {
sc_raw_socket raw_sock = unwrap(socket);
@ -225,3 +280,15 @@ net_close(sc_socket socket) {
return !close(raw_sock);
#endif
}
bool
net_parse_ipv4(const char *s, uint32_t *ipv4) {
struct in_addr addr;
if (!inet_pton(AF_INET, s, &addr)) {
LOGE("Invalid IPv4 address: %s", s);
return false;
}
*ipv4 = ntohl(addr.s_addr);
return true;
}

View File

@ -68,4 +68,10 @@ net_interrupt(sc_socket socket);
bool
net_close(sc_socket socket);
/**
* Parse `ip` "xxx.xxx.xxx.xxx" to an IPv4 host representation
*/
bool
net_parse_ipv4(const char *ip, uint32_t *ipv4);
#endif

View File

@ -5,27 +5,8 @@
#include "log.h"
enum sc_process_result
sc_process_execute(const char *const argv[], sc_pid *pid) {
return sc_process_execute_p(argv, pid, NULL, NULL, NULL);
}
bool
sc_process_check_success(sc_pid pid, const char *name, bool close) {
if (pid == SC_PROCESS_NONE) {
LOGE("Could not execute \"%s\"", name);
return false;
}
sc_exit_code exit_code = sc_process_wait(pid, close);
if (exit_code) {
if (exit_code != SC_EXIT_CODE_NONE) {
LOGE("\"%s\" returned with value %" SC_PRIexitcode, name,
exit_code);
} else {
LOGE("\"%s\" exited unexpectedly", name);
}
return false;
}
return true;
sc_process_execute(const char *const argv[], sc_pid *pid, unsigned flags) {
return sc_process_execute_p(argv, pid, flags, NULL, NULL, NULL);
}
ssize_t
@ -83,7 +64,7 @@ sc_process_observer_init(struct sc_process_observer *observer, sc_pid pid,
observer->listener_userdata = listener_userdata;
observer->terminated = false;
ok = sc_thread_create(&observer->thread, run_observer, "process_observer",
ok = sc_thread_create(&observer->thread, run_observer, "scrcpy-proc",
observer);
if (!ok) {
sc_cond_destroy(&observer->cond_terminated);

View File

@ -67,20 +67,32 @@ enum sc_process_result {
SC_PROCESS_ERROR_MISSING_BINARY,
};
#define SC_PROCESS_NO_STDOUT (1 << 0)
#define SC_PROCESS_NO_STDERR (1 << 1)
/**
* Execute the command and write the process id to `pid`
*
* The `flags` argument is a bitwise OR of the following values:
* - SC_PROCESS_NO_STDOUT
* - SC_PROCESS_NO_STDERR
*
* It indicates if stdout and stderr must be inherited from the scrcpy process
* (i.e. if the process must output to the scrcpy console).
*/
enum sc_process_result
sc_process_execute(const char *const argv[], sc_pid *pid);
sc_process_execute(const char *const argv[], sc_pid *pid, unsigned flags);
/**
* Execute the command and write the process id to `pid`
*
* If not NULL, provide a pipe for stdin (`pin`), stdout (`pout`) and stderr
* (`perr`).
*
* The `flags` argument has the same semantics as in `sc_process_execute()`.
*/
enum sc_process_result
sc_process_execute_p(const char *const argv[], sc_pid *pid,
sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
sc_pipe *pin, sc_pipe *pout, sc_pipe *perr);
/**
@ -107,14 +119,6 @@ sc_process_wait(sc_pid pid, bool close);
void
sc_process_close(sc_pid pid);
/**
* Convenience function to wait for a successful process execution
*
* Automatically log process errors with the provided process name.
*/
bool
sc_process_check_success(sc_pid pid, const char *name, bool close);
/**
* Read from the pipe
*

View File

@ -1,26 +1,5 @@
#include "process_intr.h"
bool
sc_process_check_success_intr(struct sc_intr *intr, sc_pid pid,
const char *name, bool close) {
if (!sc_intr_set_process(intr, pid)) {
// Already interrupted
return false;
}
// Always pass close=false, interrupting would be racy otherwise
bool ret = sc_process_check_success(pid, name, false);
sc_intr_set_process(intr, SC_PROCESS_NONE);
if (close) {
// Close separately
sc_process_close(pid);
}
return ret;
}
ssize_t
sc_pipe_read_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe, char *data,
size_t len) {

View File

@ -6,10 +6,6 @@
#include "intr.h"
#include "process.h"
bool
sc_process_check_success_intr(struct sc_intr *intr, sc_pid pid,
const char *name, bool close);
ssize_t
sc_pipe_read_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe, char *data,
size_t len);

View File

@ -5,13 +5,15 @@
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include "util/strbuf.h"
#ifdef _WIN32
# include <windows.h>
# include <tchar.h>
#endif
#include "log.h"
#include "strbuf.h"
size_t
sc_strncpy(char *dest, const char *src, size_t n) {
size_t i;
@ -51,6 +53,7 @@ sc_str_quote(const char *src) {
size_t len = strlen(src);
char *quoted = malloc(len + 3);
if (!quoted) {
LOG_OOM();
return NULL;
}
memcpy(&quoted[1], src, len);
@ -188,6 +191,7 @@ sc_str_to_wchars(const char *utf8) {
wchar_t *wide = malloc(len * sizeof(wchar_t));
if (!wide) {
LOG_OOM();
return NULL;
}
@ -204,6 +208,7 @@ sc_str_from_wchars(const wchar_t *ws) {
char *utf8 = malloc(len);
if (!utf8) {
LOG_OOM();
return NULL;
}
@ -235,7 +240,7 @@ sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent) {
APPEND_INDENT();
// The last separator encountered, it must be inserted only conditionnaly,
// The last separator encountered, it must be inserted only conditionally,
// depending on the next token
char pending = 0;
@ -299,3 +304,40 @@ sc_str_truncate(char *data, size_t len, const char *endchars) {
data[idx] = '\0';
return idx;
}
ssize_t
sc_str_index_of_column(const char *s, unsigned col, const char *seps) {
size_t colidx = 0;
size_t idx = 0;
while (s[idx] != '\0' && colidx != col) {
size_t r = strcspn(&s[idx], seps);
idx += r;
if (s[idx] == '\0') {
// Not found
return -1;
}
size_t consecutive_seps = strspn(&s[idx], seps);
assert(consecutive_seps); // At least one
idx += consecutive_seps;
if (s[idx] != '\0') {
++colidx;
}
}
return col == colidx ? (ssize_t) idx : -1;
}
size_t
sc_str_remove_trailing_cr(char *s, size_t len) {
while (len) {
if (s[len - 1] != '\r') {
break;
}
s[--len] = '\0';
}
return len;
}

View File

@ -114,4 +114,35 @@ sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent);
size_t
sc_str_truncate(char *data, size_t len, const char *endchars);
/**
* Find the start of a column in a string
*
* A string may represent several columns, separated by some "spaces"
* (separators). This function aims to find the start of the column number
* `col`.
*
* For example, to find the 4th column (column number 3):
*
* // here
* // v
* const char *s = "abc def ghi jk";
* ssize_t index = sc_str_index_of_column(s, 3, " ");
* assert(index == 16); // points to "jk"
*
* Return -1 if no such column exists.
*/
ssize_t
sc_str_index_of_column(const char *s, unsigned col, const char *seps);
/**
* Remove all `\r` at the end of the line
*
* The line length is provided by `len` (this avoids a call to `strlen()` when
* the caller already knows the length).
*
* Return the new length.
*/
size_t
sc_str_remove_trailing_cr(char *s, size_t len);
#endif

View File

@ -1,15 +1,17 @@
#include "strbuf.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "log.h"
bool
sc_strbuf_init(struct sc_strbuf *buf, size_t init_cap) {
buf->s = malloc(init_cap + 1); // +1 for '\0'
if (!buf->s) {
LOG_OOM();
return false;
}
@ -25,6 +27,7 @@ sc_strbuf_reserve(struct sc_strbuf *buf, size_t len) {
char *s = realloc(buf->s, new_cap + 1); // +1 for '\0'
if (!s) {
// Leave the old buf->s
LOG_OOM();
return false;
}
buf->s = s;

View File

@ -8,8 +8,13 @@
bool
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
void *userdata) {
// The thread name length is limited on some systems. Never use a name
// longer than 16 bytes (including the final '\0')
assert(strlen(name) <= 15);
SDL_Thread *sdl_thread = SDL_CreateThread(fn, name, userdata);
if (!sdl_thread) {
LOG_OOM();
return false;
}
@ -26,6 +31,7 @@ bool
sc_mutex_init(sc_mutex *mutex) {
SDL_mutex *sdl_mutex = SDL_CreateMutex();
if (!sdl_mutex) {
LOG_OOM();
return false;
}
@ -94,6 +100,7 @@ bool
sc_cond_init(sc_cond *cond) {
SDL_cond *sdl_cond = SDL_CreateCond();
if (!sdl_cond) {
LOG_OOM();
return false;
}

View File

@ -1,16 +1,55 @@
#include "tick.h"
#include <SDL2/SDL_timer.h>
#include <assert.h>
#include <time.h>
#ifdef _WIN32
# include <windows.h>
#endif
sc_tick
sc_tick_now(void) {
// SDL_GetTicks() resolution is in milliseconds, but sc_tick are expressed
// in microseconds to store PTS without precision loss.
//
// As an alternative, SDL_GetPerformanceCounter() and
// SDL_GetPerformanceFrequency() could be used, but:
// - the conversions (avoiding overflow) are expansive, since the
// frequency is not known at compile time;
// - in practice, we don't need more precision for now.
return (sc_tick) SDL_GetTicks() * 1000;
#ifndef _WIN32
// Maximum sc_tick precision (microsecond)
struct timespec ts;
int ret = clock_gettime(CLOCK_MONOTONIC, &ts);
if (ret) {
abort();
}
return SC_TICK_FROM_SEC(ts.tv_sec) + SC_TICK_FROM_NS(ts.tv_nsec);
#else
LARGE_INTEGER c;
// On systems that run Windows XP or later, the function will always
// succeed and will thus never return zero.
// <https://docs.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancecounter>
// <https://docs.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancefrequency>
BOOL ok = QueryPerformanceCounter(&c);
assert(ok);
(void) ok;
LONGLONG counter = c.QuadPart;
static LONGLONG frequency;
if (!frequency) {
// Initialize on first call
LARGE_INTEGER f;
ok = QueryPerformanceFrequency(&f);
assert(ok);
frequency = f.QuadPart;
assert(frequency);
}
if (frequency % SC_TICK_FREQ == 0) {
// Expected case (typically frequency = 10000000, i.e. 100ns precision)
sc_tick div = frequency / SC_TICK_FREQ;
return SC_TICK_FROM_US(counter / div);
}
// Split the division to avoid overflow
sc_tick secs = SC_TICK_FROM_SEC(counter / frequency);
sc_tick subsec = SC_TICK_FREQ * (counter % frequency) / frequency;
return secs + subsec;
#endif
}

View File

@ -10,9 +10,11 @@ typedef int64_t sc_tick;
#define SC_TICK_FREQ 1000000 // microsecond
// To be adapted if SC_TICK_FREQ changes
#define SC_TICK_TO_NS(tick) ((tick) * 1000)
#define SC_TICK_TO_US(tick) (tick)
#define SC_TICK_TO_MS(tick) ((tick) / 1000)
#define SC_TICK_TO_SEC(tick) ((tick) / 1000000)
#define SC_TICK_FROM_NS(ns) ((ns) / 1000)
#define SC_TICK_FROM_US(us) (us)
#define SC_TICK_FROM_MS(ms) ((ms) * 1000)
#define SC_TICK_FROM_SEC(sec) ((sec) * 1000000)

View File

@ -1,5 +1,7 @@
#include "v4l2_sink.h"
#include <string.h>
#include "util/log.h"
#include "util/str.h"
@ -31,7 +33,7 @@ write_header(struct sc_v4l2_sink *vs, const AVPacket *packet) {
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t));
if (!extradata) {
LOGC("Could not allocate extradata");
LOG_OOM();
return false;
}
@ -161,25 +163,21 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
bool ok = sc_video_buffer_init(&vs->vb, vs->buffering_time, &cbs, vs);
if (!ok) {
LOGE("Could not initialize video buffer");
return false;
}
ok = sc_video_buffer_start(&vs->vb);
if (!ok) {
LOGE("Could not start video buffer");
goto error_video_buffer_destroy;
}
ok = sc_mutex_init(&vs->mutex);
if (!ok) {
LOGC("Could not create mutex");
goto error_video_buffer_stop_and_join;
}
ok = sc_cond_init(&vs->cond);
if (!ok) {
LOGC("Could not create cond");
goto error_mutex_destroy;
}
@ -201,7 +199,7 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
vs->format_ctx = avformat_alloc_context();
if (!vs->format_ctx) {
LOGE("Could not allocate v4l2 output context");
LOG_OOM();
return false;
}
@ -213,9 +211,8 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
#ifdef SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL
vs->format_ctx->url = strdup(vs->device_name);
if (!vs->format_ctx->url) {
LOGE("Could not strdup v4l2 device name");
LOG_OOM();
goto error_avformat_free_context;
return false;
}
#else
strncpy(vs->format_ctx->filename, vs->device_name,
@ -224,9 +221,8 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
AVStream *ostream = avformat_new_stream(vs->format_ctx, encoder);
if (!ostream) {
LOGE("Could not allocate new v4l2 stream");
LOG_OOM();
goto error_avformat_free_context;
return false;
}
ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
@ -244,7 +240,7 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
vs->encoder_ctx = avcodec_alloc_context3(encoder);
if (!vs->encoder_ctx) {
LOGC("Could not allocate codec context for v4l2");
LOG_OOM();
goto error_avio_close;
}
@ -261,13 +257,13 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
vs->frame = av_frame_alloc();
if (!vs->frame) {
LOGE("Could not create v4l2 frame");
LOG_OOM();
goto error_avcodec_close;
}
vs->packet = av_packet_alloc();
if (!vs->packet) {
LOGE("Could not allocate packet");
LOG_OOM();
goto error_av_frame_free;
}
@ -276,7 +272,7 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
vs->stopped = false;
LOGD("Starting v4l2 thread");
ok = sc_thread_create(&vs->thread, run_v4l2_sink, "v4l2", vs);
ok = sc_thread_create(&vs->thread, run_v4l2_sink, "scrcpy-v4l2", vs);
if (!ok) {
LOGC("Could not start v4l2 thread");
goto error_av_packet_free;

View File

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

View File

@ -14,11 +14,13 @@ static struct sc_video_buffer_frame *
sc_video_buffer_frame_new(const AVFrame *frame) {
struct sc_video_buffer_frame *vb_frame = malloc(sizeof(*vb_frame));
if (!vb_frame) {
LOG_OOM();
return NULL;
}
vb_frame->frame = av_frame_alloc();
if (!vb_frame->frame) {
LOG_OOM();
free(vb_frame);
return NULL;
}
@ -132,14 +134,12 @@ sc_video_buffer_init(struct sc_video_buffer *vb, sc_tick buffering_time,
if (buffering_time) {
ok = sc_mutex_init(&vb->b.mutex);
if (!ok) {
LOGC("Could not create mutex");
sc_frame_buffer_destroy(&vb->fb);
return false;
}
ok = sc_cond_init(&vb->b.queue_cond);
if (!ok) {
LOGC("Could not create cond");
sc_mutex_destroy(&vb->b.mutex);
sc_frame_buffer_destroy(&vb->fb);
return false;
@ -147,7 +147,6 @@ sc_video_buffer_init(struct sc_video_buffer *vb, sc_tick buffering_time,
ok = sc_cond_init(&vb->b.wait_cond);
if (!ok) {
LOGC("Could not create wait cond");
sc_cond_destroy(&vb->b.queue_cond);
sc_mutex_destroy(&vb->b.mutex);
sc_frame_buffer_destroy(&vb->fb);
@ -171,7 +170,7 @@ bool
sc_video_buffer_start(struct sc_video_buffer *vb) {
if (vb->buffering_time) {
bool ok =
sc_thread_create(&vb->b.thread, run_buffering, "buffering", vb);
sc_thread_create(&vb->b.thread, run_buffering, "scrcpy-vbuf", vb);
if (!ok) {
LOGE("Could not start buffering thread");
return false;
@ -234,7 +233,7 @@ sc_video_buffer_push(struct sc_video_buffer *vb, const AVFrame *frame) {
struct sc_video_buffer_frame *vb_frame = sc_video_buffer_frame_new(frame);
if (!vb_frame) {
sc_mutex_unlock(&vb->b.mutex);
LOGE("Could not allocate frame");
LOG_OOM();
return false;
}

View File

@ -0,0 +1,88 @@
#include "common.h"
#include <assert.h>
#include "adb_parser.h"
static void test_get_ip_single_line() {
char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src "
"192.168.12.34\r\r\n";
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
assert(ip);
assert(!strcmp(ip, "192.168.12.34"));
free(ip);
}
static void test_get_ip_single_line_without_eol() {
char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src "
"192.168.12.34";
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
assert(ip);
assert(!strcmp(ip, "192.168.12.34"));
free(ip);
}
static void test_get_ip_single_line_with_trailing_space() {
char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src "
"192.168.12.34 \n";
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
assert(ip);
assert(!strcmp(ip, "192.168.12.34"));
free(ip);
}
static void test_get_ip_multiline_first_ok() {
char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src "
"192.168.1.2\r\n"
"10.0.0.0/24 dev rmnet proto kernel scope link src "
"10.0.0.2\r\n";
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
assert(ip);
assert(!strcmp(ip, "192.168.1.2"));
free(ip);
}
static void test_get_ip_multiline_second_ok() {
char ip_route[] = "10.0.0.0/24 dev rmnet proto kernel scope link src "
"10.0.0.3\r\n"
"192.168.1.0/24 dev wlan0 proto kernel scope link src "
"192.168.1.3\r\n";
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
assert(ip);
assert(!strcmp(ip, "192.168.1.3"));
free(ip);
}
static void test_get_ip_no_wlan() {
char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src "
"192.168.12.34\r\r\n";
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
assert(!ip);
}
static void test_get_ip_truncated() {
char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src "
"\n";
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
assert(!ip);
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_get_ip_single_line();
test_get_ip_single_line_without_eol();
test_get_ip_single_line_with_trailing_space();
test_get_ip_multiline_first_ok();
test_get_ip_multiline_second_ok();
test_get_ip_no_wlan();
test_get_ip_truncated();
}

View File

@ -89,7 +89,7 @@ static void test_options(void) {
assert(!strcmp(opts->serial, "0123456789abcdef"));
assert(opts->show_touches);
assert(opts->turn_screen_off);
assert(opts->prefer_text);
assert(opts->key_inject_mode == SC_KEY_INJECT_MODE_TEXT);
assert(!strcmp(opts->window_title, "my device"));
assert(opts->window_x == 100);
assert(opts->window_y == -1);
@ -129,25 +129,26 @@ static void test_parse_shortcut_mods(void) {
ok = sc_parse_shortcut_mods("lctrl", &mods);
assert(ok);
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);
assert(ok);
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);
assert(ok);
assert(mods.count == 2);
assert(mods.data[0] == SC_MOD_RCTRL);
assert(mods.data[1] == SC_MOD_LALT);
assert(mods.data[0] == SC_SHORTCUT_MOD_RCTRL);
assert(mods.data[1] == SC_SHORTCUT_MOD_LALT);
ok = sc_parse_shortcut_mods("lsuper,rsuper+lalt,lctrl+rctrl+ralt", &mods);
assert(ok);
assert(mods.count == 3);
assert(mods.data[0] == SC_MOD_LSUPER);
assert(mods.data[1] == (SC_MOD_RSUPER | SC_MOD_LALT));
assert(mods.data[2] == (SC_MOD_LCTRL | SC_MOD_RCTRL | SC_MOD_RALT));
assert(mods.data[0] == SC_SHORTCUT_MOD_LSUPER);
assert(mods.data[1] == (SC_SHORTCUT_MOD_RSUPER | SC_SHORTCUT_MOD_LALT));
assert(mods.data[2] == (SC_SHORTCUT_MOD_LCTRL | SC_SHORTCUT_MOD_RCTRL |
SC_SHORTCUT_MOD_RALT));
ok = sc_parse_shortcut_mods("", &mods);
assert(!ok);
@ -169,4 +170,4 @@ int main(int argc, char *argv[]) {
test_options2();
test_parse_shortcut_mods();
return 0;
};
}

View File

@ -6,7 +6,7 @@
#include "control_msg.h"
static void test_serialize_inject_keycode(void) {
struct control_msg msg = {
struct sc_control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_KEYCODE,
.inject_keycode = {
.action = AKEY_EVENT_ACTION_UP,
@ -17,7 +17,7 @@ static void test_serialize_inject_keycode(void) {
};
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);
const unsigned char expected[] = {
@ -31,7 +31,7 @@ static void test_serialize_inject_keycode(void) {
}
static void test_serialize_inject_text(void) {
struct control_msg msg = {
struct sc_control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_TEXT,
.inject_text = {
.text = "hello, world!",
@ -39,7 +39,7 @@ static void test_serialize_inject_text(void) {
};
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);
const unsigned char expected[] = {
@ -51,15 +51,15 @@ static void test_serialize_inject_text(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;
char text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH + 1];
memset(text, 'a', sizeof(text));
memset(text, 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH] = '\0';
msg.inject_text.text = text;
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);
unsigned char expected[5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH];
@ -74,11 +74,11 @@ static void test_serialize_inject_text_long(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,
.inject_touch_event = {
.action = AMOTION_EVENT_ACTION_DOWN,
.pointer_id = 0x1234567887654321L,
.pointer_id = UINT64_C(0x1234567887654321),
.position = {
.point = {
.x = 100,
@ -95,7 +95,7 @@ static void test_serialize_inject_touch_event(void) {
};
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);
const unsigned char expected[] = {
@ -111,7 +111,7 @@ static void test_serialize_inject_touch_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,
.inject_scroll_event = {
.position = {
@ -126,12 +126,13 @@ static void test_serialize_inject_scroll_event(void) {
},
.hscroll = 1,
.vscroll = -1,
.buttons = 1,
},
};
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 21);
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 25);
const unsigned char expected[] = {
CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
@ -139,12 +140,13 @@ static void test_serialize_inject_scroll_event(void) {
0x04, 0x38, 0x07, 0x80, // 1080 1920
0x00, 0x00, 0x00, 0x01, // 1
0xFF, 0xFF, 0xFF, 0xFF, // -1
0x00, 0x00, 0x00, 0x01, // 1
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
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,
.back_or_screen_on = {
.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];
size_t size = control_msg_serialize(&msg, buf);
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 2);
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) {
struct control_msg msg = {
struct sc_control_msg msg = {
.type = CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
};
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);
const unsigned char expected[] = {
@ -178,12 +180,12 @@ static void test_serialize_expand_notification_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,
};
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);
const unsigned char expected[] = {
@ -193,12 +195,12 @@ static void test_serialize_expand_settings_panel(void) {
}
static void test_serialize_collapse_panels(void) {
struct control_msg msg = {
struct sc_control_msg msg = {
.type = CONTROL_MSG_TYPE_COLLAPSE_PANELS,
};
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);
const unsigned char expected[] = {
@ -208,35 +210,41 @@ static void test_serialize_collapse_panels(void) {
}
static void test_serialize_get_clipboard(void) {
struct control_msg msg = {
struct sc_control_msg msg = {
.type = CONTROL_MSG_TYPE_GET_CLIPBOARD,
.get_clipboard = {
.copy_key = GET_CLIPBOARD_COPY_KEY_COPY,
},
};
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 1);
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 2);
const unsigned char expected[] = {
CONTROL_MSG_TYPE_GET_CLIPBOARD,
GET_CLIPBOARD_COPY_KEY_COPY,
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_set_clipboard(void) {
struct control_msg msg = {
struct sc_control_msg msg = {
.type = CONTROL_MSG_TYPE_SET_CLIPBOARD,
.set_clipboard = {
.sequence = UINT64_C(0x0102030405060708),
.paste = true,
.text = "hello, world!",
},
};
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 19);
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 27);
const unsigned char expected[] = {
CONTROL_MSG_TYPE_SET_CLIPBOARD,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // sequence
1, // paste
0x00, 0x00, 0x00, 0x0d, // text length
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
@ -244,8 +252,42 @@ static void test_serialize_set_clipboard(void) {
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_set_clipboard_long(void) {
struct sc_control_msg msg = {
.type = CONTROL_MSG_TYPE_SET_CLIPBOARD,
.set_clipboard = {
.sequence = UINT64_C(0x0102030405060708),
.paste = true,
.text = NULL,
},
};
char text[CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH + 1];
memset(text, 'a', CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH);
text[CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH] = '\0';
msg.set_clipboard.text = text;
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == CONTROL_MSG_MAX_SIZE);
unsigned char expected[CONTROL_MSG_MAX_SIZE] = {
CONTROL_MSG_TYPE_SET_CLIPBOARD,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // sequence
1, // paste
// text length
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH >> 24,
(CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH >> 16) & 0xff,
(CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH >> 8) & 0xff,
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH & 0xff,
};
memset(expected + 14, 'a', CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH);
assert(!memcmp(buf, expected, sizeof(expected)));
}
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,
.set_screen_power_mode = {
.mode = SCREEN_POWER_MODE_NORMAL,
@ -253,7 +295,7 @@ static void test_serialize_set_screen_power_mode(void) {
};
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);
const unsigned char expected[] = {
@ -264,12 +306,12 @@ static void test_serialize_set_screen_power_mode(void) {
}
static void test_serialize_rotate_device(void) {
struct control_msg msg = {
struct sc_control_msg msg = {
.type = CONTROL_MSG_TYPE_ROTATE_DEVICE,
};
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);
const unsigned char expected[] = {
@ -293,6 +335,7 @@ int main(int argc, char *argv[]) {
test_serialize_collapse_panels();
test_serialize_get_clipboard();
test_serialize_set_clipboard();
test_serialize_set_clipboard_long();
test_serialize_set_screen_power_mode();
test_serialize_rotate_device();
return 0;

View File

@ -47,11 +47,26 @@ static void test_deserialize_clipboard_big(void) {
device_msg_destroy(&msg);
}
static void test_deserialize_ack_set_clipboard(void) {
const unsigned char input[] = {
DEVICE_MSG_TYPE_ACK_CLIPBOARD,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // sequence
};
struct device_msg msg;
ssize_t r = device_msg_deserialize(input, sizeof(input), &msg);
assert(r == 9);
assert(msg.type == DEVICE_MSG_TYPE_ACK_CLIPBOARD);
assert(msg.ack_clipboard.sequence == UINT64_C(0x0102030405060708));
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_deserialize_clipboard();
test_deserialize_clipboard_big();
test_deserialize_ack_set_clipboard();
return 0;
}

View File

@ -3,6 +3,7 @@
#include <assert.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "util/str.h"
@ -363,6 +364,40 @@ static void test_truncate(void) {
assert(!strcmp("hello", s4));
}
static void test_index_of_column(void) {
assert(sc_str_index_of_column("a bc d", 0, " ") == 0);
assert(sc_str_index_of_column("a bc d", 1, " ") == 2);
assert(sc_str_index_of_column("a bc d", 2, " ") == 6);
assert(sc_str_index_of_column("a bc d", 3, " ") == -1);
assert(sc_str_index_of_column("a ", 0, " ") == 0);
assert(sc_str_index_of_column("a ", 1, " ") == -1);
assert(sc_str_index_of_column("", 0, " ") == 0);
assert(sc_str_index_of_column("", 1, " ") == -1);
assert(sc_str_index_of_column("a \t \t bc \t d\t", 0, " \t") == 0);
assert(sc_str_index_of_column("a \t \t bc \t d\t", 1, " \t") == 8);
assert(sc_str_index_of_column("a \t \t bc \t d\t", 2, " \t") == 15);
assert(sc_str_index_of_column("a \t \t bc \t d\t", 3, " \t") == -1);
assert(sc_str_index_of_column(" a bc d", 1, " ") == 2);
}
static void test_remove_trailing_cr() {
char s[] = "abc\r";
sc_str_remove_trailing_cr(s, sizeof(s) - 1);
assert(!strcmp(s, "abc"));
char s2[] = "def\r\r\r\r";
sc_str_remove_trailing_cr(s2, sizeof(s2) - 1);
assert(!strcmp(s2, "def"));
char s3[] = "adb\rdef\r";
sc_str_remove_trailing_cr(s3, sizeof(s3) - 1);
assert(!strcmp(s3, "adb\rdef"));
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
@ -383,5 +418,7 @@ int main(int argc, char *argv[]) {
test_strlist_contains();
test_wrap_lines();
test_truncate();
test_index_of_column();
test_remove_trailing_cr();
return 0;
}

View File

@ -2,6 +2,7 @@
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "util/strbuf.h"

View File

@ -7,7 +7,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.2'
classpath 'com.android.tools.build:gradle:7.0.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

34
bump_version Executable file
View File

@ -0,0 +1,34 @@
#!/usr/bin/env bash
#
# This script bump scrcpy version by editing all the necessary files.
#
# Usage:
#
# ./bump_version 1.23.4
#
# Then check the diff manually to confirm that everything is ok.
set -e
if [[ $# != 1 ]]
then
echo "Syntax: $0 <version>" >&2
exit 1
fi
VERSION="$1"
a=( ${VERSION//./ } )
MAJOR="${a[0]:-0}"
MINOR="${a[1]:-0}"
PATCH="${a[2]:-0}"
# If VERSION is 1.23.4, then VERSION_CODE is 12304
VERSION_CODE="$(( $MAJOR * 10000 + $MINOR * 100 + "$PATCH" ))"
echo "$VERSION: major=$MAJOR minor=$MINOR patch=$PATCH [versionCode=$VERSION_CODE]"
sed -i "s/^\(\s*version: \)'[^']*'/\1'$VERSION'/" meson.build
sed -i "s/^\(\s*versionCode \).*/\1$VERSION_CODE/;s/^\(\s*versionName \).*/\1\"$VERSION\"/" server/build.gradle
sed -i "s/^\(SCRCPY_VERSION_NAME=\).*/\1$VERSION/" server/build_without_gradle.sh
sed -i "s/^\(\s*VALUE \"ProductVersion\", \)\"[^\"]*\"/\1\"$VERSION\"/" app/scrcpy-windows.rc
echo done

View File

@ -7,6 +7,7 @@ cpp = 'i686-w64-mingw32-g++'
ar = 'i686-w64-mingw32-ar'
strip = 'i686-w64-mingw32-strip'
pkgconfig = 'i686-w64-mingw32-pkg-config'
windres = 'i686-w64-mingw32-windres'
[host_machine]
system = 'windows'
@ -17,4 +18,4 @@ endian = 'little'
[properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win32-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win32-dev'
prebuilt_sdl2 = 'SDL2-2.0.16/i686-w64-mingw32'
prebuilt_sdl2 = 'SDL2-2.0.18/i686-w64-mingw32'

View File

@ -7,6 +7,7 @@ cpp = 'x86_64-w64-mingw32-g++'
ar = 'x86_64-w64-mingw32-ar'
strip = 'x86_64-w64-mingw32-strip'
pkgconfig = 'x86_64-w64-mingw32-pkg-config'
windres = 'x86_64-w64-mingw32-windres'
[host_machine]
system = 'windows'
@ -17,4 +18,4 @@ endian = 'little'
[properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win64-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win64-dev'
prebuilt_sdl2 = 'SDL2-2.0.16/x86_64-w64-mingw32'
prebuilt_sdl2 = 'SDL2-2.0.18/x86_64-w64-mingw32'

BIN
data/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Some files were not shown because too many files have changed in this diff Show More