Compare commits

...

482 Commits

Author SHA1 Message Date
54d203dd1c relative-mouse-mode 2021-12-31 16:33:19 +01:00
1240e34fa8 hid_mouse 2021-12-31 16:33:19 +01:00
5caace5818 Add relative mouse motion in event
This will allow the mouse processor to handle relative motion easily.
2021-12-31 16:33:19 +01:00
94b3328c2d Add CLAMP() macro 2021-12-31 16:32:08 +01:00
2df0bbf6ab relative_mode 2021-12-31 16:32:08 +01:00
87d54b0ef6 embed input_manager into screen 2021-12-31 16:32:07 +01:00
540e2cd152 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.
2021-12-31 16:15:41 +01:00
923a892654 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.
2021-12-31 11:19:52 +01:00
850348ab60 Make some mouse processors ops optional
Do not force all mouse processors implementations to implement scroll
events or touch events.
2021-12-31 10:49:54 +01:00
1b8cbcbc65 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.
2021-12-31 10:49:54 +01:00
7a7ec97e9d 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.
2021-12-31 10:49:54 +01:00
9469db094a Reorder mouse processor ops
Group the mouse events callbacks before the touch event callback.
2021-12-31 10:49:54 +01:00
9ed983845a Simplify mouse injection implementation
The static functions are now so simple they become unnecessary: the
control message may be initialized directly instead.
2021-12-31 10:49:54 +01:00
196acfab77 Make some event conversions infallible
When the implementation handles all possible input values, it may never
fail.
2021-12-31 10:49:54 +01:00
a79f4ea901 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.
2021-12-31 10:49:54 +01:00
d2cdf2f507 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.
2021-12-31 10:49:54 +01:00
8aa92bdaa8 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.
2021-12-31 10:49:54 +01:00
21f94132d5 Add intermediate input events layer
This aims to make the key/mouse processors independent of the "screen",
instead of processing SDL events themselves.

In particular, these scrcpy events are not impacted by any UI window
scaling or rotation (contrary to SDL events).
2021-12-31 10:49:54 +01:00
6310b99411 Rename SC_MOD_* to SC_SHORTCUT_MOD_*
This will avoid conflicts with new SC_MOD_* constants.
2021-12-31 10:49:54 +01:00
cfd3b9534b 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
2021-12-31 10:49:54 +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
2d6a96776c Improve SSH tunnel documentation in README 2021-11-19 21:03:29 +01:00
6da6d905c2 Print scrcpy header first
Inconditionnally print the scrcpy version first, without using LOGI().

The log level is configured only after parsing the command line
parameters (it may be changed via -V), and command line parsing logs
should not appear before the scrcpy version.
2021-11-19 09:45:02 +01:00
b25404ee4b Print help to stdout
The output of -h/--help was printed on stderr, although it is not an
error.

Printing on stdout allows to pipe the result directly:

    scrcpy --help | less

Instead of (in bash):

    scrcpy --help |& less
2021-11-19 08:18:13 +01:00
4cfc1cd70a Assert that long options are correctly set 2021-11-19 08:06:23 +01:00
2fc80eae2d Simplify adb_tunnel
With the new adb functions, the static adb_tunnel functions become
unnecessary.
2021-11-19 07:25:03 +01:00
3fdbd994e0 Privatize low-level adb functions
Only expose the interruptible user-friendly API.
2021-11-19 07:25:03 +01:00
ce225f019a Use new user-friendly adb API
Replace the adb_exec_*() calls by the new adb functions to simplify.
2021-11-18 22:11:19 +01:00
b7559744a7 Expose new user-friendly adb functions
Expose interruptible adb functions which return the expected result
directly, without exposing the process (sc_pid) to the caller.
2021-11-18 22:08:15 +01:00
afb5a5e80f Rename adb functions to adb_exec_*
This paves the way to replace them by more user-friendly functions that
will call them internally.
2021-11-18 21:47:17 +01:00
84334cf7db Use sc_intr in file_handler
Replace manual interruption handling by the recent sc_intr mechanism.
2021-11-18 21:33:25 +01:00
0ba2686e1d Simplify file_handler
Call the target functions directly.
2021-11-18 19:46:47 +01:00
55648d4d64 Improve file_handler readability
Use local variables as short name aliases.
2021-11-18 19:46:40 +01:00
13fd693b50 Simplify adb_execute_p()
Only pass the stdout pipe as parameter, scrcpy never writes to stdin or
reads from stderr of an adb process.
2021-11-18 19:43:10 +01:00
0426ae885c Make "adb get-serialno" interruptible
All process executions must be interruptible, so that Ctrl+c reacts
immediately.
2021-11-18 19:41:41 +01:00
ea454e9cee Add interruptible function to read from pipe
This will avoid to block Ctrl+c if the process the pipe is read from
takes too much time.
2021-11-18 19:39:11 +01:00
cb65531533 Simplify sc_str_truncate()
Use strcspn() to get the prefix length directly.
2021-11-18 19:37:53 +01:00
9619ade706 Generalize string trunctation util function
Add an additional argument to let the client pass the possible end
chars.
2021-11-18 18:48:16 +01:00
f2781a8b6d Expose util function to truncate first line
Move the local implementation from adb functions to the string util
functions.
2021-11-18 18:48:16 +01:00
443cb14d6e Assume non-NULL serial in file_handler
The previous commit guarantees to always initialize the serial, so the
file_handle may assume it is never NULL.
2021-11-18 18:48:11 +01:00
b30c3a429f Always retrieve device serial
This allows to execute all adb commands with the specific -s parameter,
even if it is not provided by the user.

In practice, calling adb without -s works if there is exactly one device
connected. But some adb commands (for example "adb push" on drag & drop)
could be executed after another device is connected, so the actual
device serial must be known.
2021-11-18 18:32:15 +01:00
632bd5697b Add missing error handling
If "adb get-serialno" fails, attempting to read from the uninitialized
pipe is incorrect.
2021-11-18 18:32:15 +01:00
de50846905 Close process on check success
The interruptible version of the function to check process success
(sc_process_check_success_intr()) did not accept a close parameter to
avoid a race condition. But as the result, the processes were not closed
at all.

Add a close parameter, and close the process separately to avoid the
race condition.
2021-11-18 18:31:36 +01:00
ee93d2aac1 Configure init and cleanup asynchronously
Accessing the settings (like --show-touches) on start should not delay
screen mirroring.

PR #2802 <https://github.com/Genymobile/scrcpy/pull/2802>
2021-11-18 08:37:32 +01:00
c29a0bf675 Do not quit on cleanup configuration failure
Cleanup is used for some options like --show-touches to restore the
state on exit.

If the configuration fails, do not crash the whole process. Just log an
error.

PR #2802 <https://github.com/Genymobile/scrcpy/pull/2802>
2021-11-18 08:37:28 +01:00
411bb0d18e Move init and cleanup to a separate method
PR #2802 <https://github.com/Genymobile/scrcpy/pull/2802>
2021-11-18 08:37:26 +01:00
cc0902b13c Read/write settings via command on Android >= 12
Before Android 8, executing the "settings" command from a shell was
very slow (~1 second), because it spawned a new app_process to execute
Java code. Therefore, to access settings without performance issues,
scrcpy used private APIs to read from and write to settings.

However, since Android 12, this is not possible anymore, due to
permissions changes.

To make it work again, execute the "settings" command on Android 12 (or
on previous version if the other method failed). This method is faster
than before Android 8 (~100ms).

Fixes #2671 <https://github.com/Genymobile/scrcpy/issues/2671>
Fixes #2788 <https://github.com/Genymobile/scrcpy/issues/2788>
PR #2802 <https://github.com/Genymobile/scrcpy/pull/2802>
2021-11-18 08:37:22 +01:00
48b572c272 Add throwable parameter to Log.w()
When an exception occurs, we might want to log a warning instead of an
error.

PR #2802 <https://github.com/Genymobile/scrcpy/pull/2802>
2021-11-18 08:37:18 +01:00
94feae71f2 Report settings errors via Exceptions
Settings read/write errors were silently ignored. Report them via a
SettingsException so that the caller can handle them.

This allows to log a proper error message, and will also allow to
fallback to a different settings method in case of failure.

PR #2802 <https://github.com/Genymobile/scrcpy/pull/2802>
2021-11-18 08:37:02 +01:00
67170437f1 Wrap settings management into a Settings class
Until now, the code that needed to read/write the Android settings had
to explicitly open and close a ContentProvider.

Wrap these details into a Settings class.

This paves the way to provide an alternative implementation of settings
read/write for Android >= 12.

PR #2802 <https://github.com/Genymobile/scrcpy/pull/2802>
2021-11-18 08:36:48 +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
9cb14b5166 Inherit only specific handles on Windows
To be able to communicate with a child process via stdin, stdout and
stderr, the CreateProcess() parameter bInheritHandles must be set to
TRUE. But this causes *all* handles to be inherited, including sockets.

As a result, the server socket was inherited by the process running adb
to execute the server on the device, so it could not be closed properly,
causing other scrcpy instances to fail.

To fix the issue, use an extended API to explicitly set the HANDLEs to
inherit:
 - <https://stackoverflow.com/a/28185363/1987178>
 - <https://devblogs.microsoft.com/oldnewthing/20111216-00/?p=8873>

Fixes #2779 <https://github.com/Genymobile/scrcpy/issues/2779>
PR #2783 <https://github.com/Genymobile/scrcpy/pull/2783>
2021-11-15 10:13:30 +01:00
9cb8766220 Factorize resource release after CreateProcess()
Free the wide characters string in all cases before checking for errors.
2021-11-15 07:49:01 +01:00
fd4ec784e0 Remove useless assignments on error
Leave the output parameter untouched on error.
2021-11-14 22:53:49 +01:00
52cebe1597 Fix Windows sc_pipe function names
The implementation name was incorrect (it was harmless, because they are
not used on Windows).
2021-11-14 22:41:38 +01:00
6a27062f48 Stop connection attempts if interrupted
If the interruptor is interrupted, every network call will fail, but the
retry-on-error mechanism must also be stopped.
2021-11-14 15:40:59 +01:00
739ff9dce0 Fix compilation errors with old SDL versions
SDL_PixelFormatEnum has been introduced in SDL 2.0.10:
<cc6a8ac87e>

SDL_PIXELFORMAT_BGR444 has been introduced in SDL 2.0.12:
<a1c11854f2>

Fixes #2777 <https://github.com/Genymobile/scrcpy/issues/2777>
PR #2781 <https://github.com/Genymobile/scrcpy/pull/2781>

Reviewed-by: Yu-Chen Lin <npes87184@gmail.com>
2021-11-14 15:37:30 +01:00
65b023ac6d Update links to v1.20 2021-11-14 01:51:32 +01:00
a045e28df8 Bump version to 1.20 2021-11-14 01:25:24 +01:00
52138fd921 Update script to build without gradle to SDK 31
Build tools 31.x.x do not ship dx anymore. Use d8 instead.

Refs 8bf28e9f53
2021-11-14 01:25:24 +01:00
1bb0df5da1 Extract ANDROID_JAR path in build script
This will allow to reuse it.
2021-11-14 01:25:24 +01:00
562ec6e9b3 Merge branch 'master' into dev 2021-11-14 01:25:00 +01:00
1be5daf999 Fix word order in README 2021-11-14 01:23:06 +01:00
57387fa707 Mention crash on Android 12 on old scrcpy in FAQ 2021-11-14 01:23:06 +01:00
4e811a4a68 Adapt icon in README
Use SVG format, align to the right, and reduce its size.
2021-11-14 01:23:06 +01:00
99a4a48477 Replace "connected on" to "connected via"
I'm not sure "connected on USB" is correct.
2021-11-14 01:23:06 +01:00
1d97d77032 Improve scrcpy presentation in README 2021-11-14 01:23:06 +01:00
45b0f8123a Increase delay to inject HID on Ctrl+v
2 milliseconds turn out to be insufficient sometimes. It seems that 5
milliseconds is enough (and still not noticeable).
2021-11-14 01:23:06 +01:00
c1a34881d7 Use sc_ prefix for server 2021-11-14 01:23:05 +01:00
057c7a4df4 Move str_util to str
Simplify naming.
2021-11-14 01:22:22 +01:00
979ce64dc0 Improve string util API
Use prefixed names and improve documentation.
2021-11-14 01:22:22 +01:00
9a0bd545d5 Rename SC_INVALID_SOCKET to SC_SOCKET_NONE
For consistency with SC_PROCESS_NONE.
2021-11-14 01:22:22 +01:00
c4d008b96a Extract adb tunnel to a separate component
This simplifies the server code.
2021-11-14 01:22:22 +01:00
0d45c29d13 Move IPV4_LOCALHOST to net.h
This constant will be used from several files.
2021-11-14 01:22:22 +01:00
37c840a4c8 Interrupt on process terminated
Interrupt any blocking call on process terminated, like on
server_stop().

This allows to interrupt any blocking accept() with correct
synchronization without additional complexity.
2021-11-14 01:22:22 +01:00
f488cbd7e7 Make server interruptible
Use an interruptor to immediately wake up blocking calls on
server_stop().
2021-11-14 01:22:20 +01:00
40340509d9 Add interruptor utilities
Expose wrapper functions for interrupting blocking calls, for process
and sockets.
2021-11-13 22:54:18 +01:00
e0896142db Introduce interruptor tool
An interruptor instance will help to wake up a blocking call from
another thread (typically to terminate immediately on Ctrl+C).
2021-11-13 22:53:07 +01:00
0426708544 Run the server from a dedicated thread
Define server callbacks, start the server asynchronously and listen to
connection events to initialize scrcpy properly.

It will help to simplify the server code, and allows to run the UI event
loop while the server is connecting. In particular, this will allow to
receive SIGINT/Ctrl+C events during connection to interrupt immediately.
2021-11-13 10:19:52 +01:00
5b9c88693e Wait using a condition variable in server
Currently, server_stop() is called from the same thread as
server_connect_to(), so interruption may never happen.

This is a step to prepare executing the server from a dedicated thread.
2021-11-13 10:17:51 +01:00
a54dc8212f Reorder server functions
This will avoid forward declarations in future commits.
2021-11-13 10:16:35 +01:00
882e4cff5f Copy server params
This is a preliminary step necessary to move the server to a separate
thread.
2021-11-13 10:05:20 +01:00
9fa4d1cd4a Reorder server and server_params
This will allow to define a server_params field in server.
2021-11-13 10:05:20 +01:00
0bfa75a48b Split socket creation and connect/listen
This will allow to assign the socket to a variable before connecting or
listening, so that it can be interrupted from another thread.
2021-11-13 10:05:20 +01:00
ed19901db1 Set video and control sockets only on success
Store the video and control sockets only if server_connect_to() returns
successfully.
2021-11-13 10:05:01 +01:00
e69356c550 Initialize tunnel_enabled flag internally
Set the flag from the inner methods, to avoid bypassing the assignment
by mistake (on error for example).
2021-11-13 10:03:03 +01:00
03de9224fc Introduce process observer
Add a tool to easily observe process termination.

This allows to move this complexity out of the server code.
2021-11-13 10:02:23 +01:00
aa011832c1 Improve process API
Prefix symbols and constants names and improve documentation.
2021-11-12 22:44:37 +01:00
7e93abcf6d Factorize common impl of process_execute()
Both implementations are the same. Move them to the common process.c.
2021-11-12 22:44:37 +01:00
e80e6631e4 Remove duplicate function declaration
The function process_terminate() was declared twice.
2021-11-12 22:44:37 +01:00
fcc04f967b Improve file API
Prefix symbols and constants names and improve documentation.
2021-11-12 22:44:37 +01:00
d4c262301f Move functions from process to file
Move filesystem-related functions from process.[ch] to file.[ch].
2021-11-12 22:44:37 +01:00
be55e250ca Make screen_render() static
It is only used from screen.c.
2021-11-12 22:44:22 +01:00
c548f017bf Upgrade junit to 4.13.1 2021-11-12 22:44:18 +01:00
7a733328bc Adapt help to terminal size
If the error stream is a terminal, and we can retrieve the terminal
size, wrap the help content according to the terminal width.
2021-11-11 15:22:39 +01:00
38332f683c Add util function to get terminal size 2021-11-11 15:22:39 +01:00
3f51a2ab43 Generate getopt params from option structures
Use the option descriptions to generate the optstring and longopts
parameters for the getopt_long() command.

That way, the options are completely described in a single place.
2021-11-11 14:59:34 +01:00
74ab0a4df8 Structure shortcuts help 2021-11-11 14:59:34 +01:00
f59e9e3cb0 Structure command line options help
It will allow to correctly print the help for the current terminal size
(even if for now it is hardcoded to 80 columns).
2021-11-11 14:59:34 +01:00
9ec3406568 Add line wrapper
Add a tool to wrap lines at words boundaries (spaces) to fit in a
specified number of columns, left-indented by a specified number of
spaces.
2021-11-11 14:55:53 +01:00
6dba1922c1 Add string buffer util
This will help to build strings incrementally.
2021-11-11 14:55:52 +01:00
570a003c39 Remove deprecated -T option
The short option -T is deprecated since v1.11. Only the long version
(--always-on-top) remains.
2021-11-07 21:35:57 +01:00
b62df7ee91 Remove deprecated -c option
The short option -c is deprecated since v1.11. Only the long version
(--crop) remains.
2021-11-07 21:35:53 +01:00
30d40f4e78 Document --power-off-on-close
The option was not documented.

Refs #824 <https://github.com/Genymobile/scrcpy/pull/824>
2021-11-07 19:27:53 +01:00
f489f7fcad Mention drag & drop for non-APK files in help 2021-11-07 18:51:22 +01:00
d72c7076f7 Mention drag & drop APK in README
For consistency with --help and manpage.
2021-11-07 18:50:29 +01:00
fc64445555 Remove extra space in README 2021-11-07 18:50:24 +01:00
f65c3fde69 Fix typos in help 2021-11-07 18:50:24 +01:00
48fcfa96ab Add missing include "common.h" 2021-11-06 19:26:29 +01:00
676fa73d2c Wrap device name and size in a struct
As a benefit, this avoids to take care of the device name length on the
caller side.
2021-11-02 11:08:58 +01:00
790f04e91f Update README.jp.md to v1.19
PR #2740 <https://github.com/Genymobile/scrcpy/pull/2740>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-10-31 13:03:27 +01:00
58ea238fb2 Remove unnecessary variable
Test directly if a record filename is provided, without an intermediate
boolean variable.
2021-10-31 12:45:59 +01:00
13c4aa1a3b Disable synthetic mouse events from touch events
Touch events with id SDL_TOUCH_MOUSEID are ignored anyway, but it is
better not to generate them in the first place.
2021-10-31 12:45:59 +01:00
caf594c90e Split SDL initialization
Initialize SDL_INIT_EVENTS first (very quick) and SDL_INIT_VIDEO after
the server (quite long).
2021-10-31 12:45:59 +01:00
688477ff65 Move SDL initialization
Inline SDL initialization in scrcpy(). This will allow to split
minimal initialization and video initialization.
2021-10-31 12:45:59 +01:00
ac539e1312 Set SDL hints before initialization
In theory, some SDL hints should be initialized before calling
SDL_Init().
2021-10-31 12:45:59 +01:00
a57c7d3a2b Extract SDL hints
Set all SDL hints in a separate function.
2021-10-31 12:45:56 +01:00
d2d18466d4 Factorize SDL event push
Add a macro and inline function to call SDL_PushEvent() and log on error
(without actually handling the error, because there is nothing we could
do).
2021-10-30 22:36:56 +02:00
dae091e3ab Handle SDL_PushEvent() errors
Pushing an event to the main thread may fail. If this happens, log an
error, and try to recover when possible.
2021-10-30 20:25:20 +02:00
4c4381de4c Use sc_ prefix for size, position and point 2021-10-30 15:20:39 +02:00
db484d82db Upgrade gradle build tools to 7.0.2 2021-10-30 11:32:48 +02:00
0c9666b733 Upgrade Android checkstyle to 9.0.1
Adapt checkstyle.xml to match the latest version, and remove a line
break between imports which trigger a checkstyle volation.
2021-10-30 11:23:51 +02:00
8bf28e9f53 Upgrade Android SDK to 31 2021-10-30 11:23:51 +02:00
06131ef634 Fix typo in clock comments 2021-10-29 12:21:34 +02:00
34eb10ea0b Define v4l2 option field only if HAVE_V4L2 2021-10-27 18:43:47 +02:00
27fa23846d Define default options as const struct
This is more readable than a macro, and we could ifdef some fields.
2021-10-27 18:43:47 +02:00
e4d5c1ce36 Move scrcpy option structs to options.h
This will allow to define symbols in options.c without all the
dependencies of scrcpy.c.
2021-10-27 18:43:47 +02:00
ac23bec144 Expose socket interruption
On Linux, socket functions are unblocked by shutdown(), but on Windows
they are unblocked by closesocket().

Expose net_interrupt() and net_close() to abstract these differences:
 - net_interrupt() calls shutdown() on Linux and closesocket() on
   Windows (if not already called);
 - net_close() calls close() on Linux and closesocket() on Windows (if
   not already called).

This simplifies the server code, and prevents a data race on close
(reported by TSAN) on Linux (but does not fix it on Windows):

    WARNING: ThreadSanitizer: data race (pid=836124)
      Write of size 8 at 0x7ba0000000d0 by main thread:
        #0 close ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1690 (libtsan.so.0+0x359d8)
        #1 net_close ../app/src/util/net.c:211 (scrcpy+0x1c76b)
        #2 close_socket ../app/src/server.c:330 (scrcpy+0x19442)
        #3 server_stop ../app/src/server.c:522 (scrcpy+0x19e33)
        #4 scrcpy ../app/src/scrcpy.c:532 (scrcpy+0x156fc)
        #5 main ../app/src/main.c:92 (scrcpy+0x622a)

      Previous read of size 8 at 0x7ba0000000d0 by thread T6:
        #0 recv ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:6603 (libtsan.so.0+0x4f4a6)
        #1 net_recv ../app/src/util/net.c:167 (scrcpy+0x1c5a7)
        #2 run_receiver ../app/src/receiver.c:76 (scrcpy+0x12819)
        #3 <null> <null> (libSDL2-2.0.so.0+0x84f40)
2021-10-27 18:41:58 +02:00
e5ea13770b Add socket wrapper
This paves the way to store an additional "closed" flag on Windows
to interrupt and close properly.
2021-10-26 22:49:57 +02:00
3eac212af1 Use net_send() from net_send_all()
This will make net_send_all() continue to work even if net_send()
behavior is changed.
2021-10-26 22:49:45 +02:00
3adff37c2d Use sc_ prefix for sockets
Rename:
 - socket_t to sc_socket
 - INVALID_SOCKET to SC_INVALID_SOCKET
2021-10-26 22:49:45 +02:00
eb6afe7669 Move net_init() and net_cleanup() upwards
These two functions are global, define them at the top of the
implementation file. This is consistent with the header file.
2021-10-26 22:49:45 +02:00
5222f213f4 Update FAQ to mention HID keyboard 2021-10-26 21:30:04 +02:00
e4163321f0 Delay HID events on Ctrl+v
When Ctrl+v is pressed, a control is sent to the device to set the
device clipboard before injecting Ctrl+v.

With the InputManager method, it is guaranteed that the device
synchronization is executed before handling Ctrl+v, since the commands
are executed on the device in sequence.

However, HID are injected from the computer, so there is no such
guarantee. As a consequence, on Android, Ctrl+v triggers a paste with
the old clipboard content.

To workaround the issue, wait a bit (2 milliseconds) from the AOA
thread before injecting the event, to leave enough time for the
clipboard to be set before injecting Ctrl+v.
2021-10-26 21:30:04 +02:00
c96874b257 Synchronize HID keyboard state on first event
When an AOA HID keyboard is registered, CAPSLOCK and NUMLOCK are both
disabled, regardless of the state of the computer keyboard.

To synchronize the state, on first key event, inject CAPSLOCK and/or
NUMLOCK if necessary.
2021-10-26 21:30:04 +02:00
511356710d Retrieve device serial for AOA
The serial is necessary to find the correct Android device for AOA.

If it is not explicitly provided by the user via -s, then execute "adb
getserialno" to retrieve it.
2021-10-26 21:30:04 +02:00
d55015e4cf Expose function to get the device serial
Expose adb_get_serialno() to retrieve the device serial via the command
"adb getserialno".
2021-10-26 21:30:04 +02:00
0681480809 Add read_pipe_all()
Add a convenience function to read from a pipe until all requested data
has been read.
2021-10-26 21:30:04 +02:00
96b18dabaa Expose adb execution with redirection
Expose the redirection feature to the adb API.
2021-10-26 21:30:04 +02:00
eaf4afaad9 Add command execution with redirection
Expose command execution with pipes to stdin, stdout and stderr.

This will allow to read the result of adb commands.
2021-10-26 21:30:04 +02:00
207082977a Add support for USB HID keyboard over AOAv2
This provides a better input experience, by simulating a physical
keyboard. It converts SDL keyboard events to proper HID events, and send
them over AOAv2.

This is a rewriting and bugfix of the origin code from @amosbird:
<https://github.com/Genymobile/scrcpy/issues/279#issuecomment-453819354>

The feature is enabled the command line option -K or --hid-keyboard,
and is only available on Linux, over USB.

Refs <https://source.android.com/devices/accessories/aoa2#hid-support>
Refs <https://www.usb.org/sites/default/files/hid1_11.pdf>

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-10-26 21:30:04 +02:00
f7d1efdf1d Extract mouse processor trait
Mainly for consistency with the keyboard processor trait.

This could allow to provide alternative mouse processors.
2021-10-26 21:30:04 +02:00
bcf5a9750f Extract keyboard processor trait
This will allow to provide alternative key processors.
2021-10-26 21:30:04 +02:00
056d36ce4a Fix trait header guards 2021-10-26 21:30:04 +02:00
bea3197c3f Remove unused markdown link in README 2021-10-25 18:22:48 +02:00
d6568f64af Mention SCRCPY_ICON_PATH envvar in README 2021-10-25 18:22:17 +02:00
580f5d8bb9 Add scrcpy icon to README 2021-10-25 18:08:37 +02:00
1e340caf76 Remove legacy scrcpy icon
Remove the old icon in XPM format and the code to load it.
2021-10-25 18:08:37 +02:00
3d5b31e0cb Add icon source in SVG format
Scrcpy only uses the PNG format (because SDL only supports bitmap
icons), but keep the SVG source in the repo.
2021-10-25 18:08:37 +02:00
6004f0b6b0 Use a new scrcpy icon
Use the new icon designed by @varlesh:
<https://github.com/Genymobile/scrcpy/pull/1987#issuecomment-949684080>

Load it from a PNG file (SDL only supports bitmap icons).
2021-10-25 18:08:37 +02:00
12ed2f2402 Add support for palette icon formats
To support more icon formats.
2021-10-25 18:08:37 +02:00
0e4564da03 Add icon loader
Add helper to load icons from image files via FFmpeg.
2021-10-25 18:08:37 +02:00
156d958e77 Move common instruction out of ifdef
Both ifdef-branches return server_path.
2021-10-25 18:08:31 +02:00
7229e3cce0 Extract util function to build a local file path
Finding a local file in the scrcpy directory may be useful for files
other than scrcpy-server in the future.
2021-10-25 16:29:43 +02:00
a7e41b0f85 Fix code style 2021-10-21 18:36:34 +02:00
46d3e35c30 Fix "Could not find v4l2 muxer"
The AVOutputFormat name is a comma-separated list. In theory, possible
names for V4L2 are:

    - "video4linux2,v4l2"
    - "v4l2,video4linux2"
    - "v4l2"
    - "video4linux2"

To find the muxer in all cases, we must request exactly one muxer name
at a time.

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

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-10-21 16:26:48 +02:00
07d75eb336 Simplify net_send_all()
There is no need to declare the variable before the loop.
2021-10-17 16:21:37 +02:00
96e3963afc Update Readme.pt-br.md to v1.19
PR #2686 <https://github.com/Genymobile/scrcpy/pull/2686>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-10-08 21:46:56 +02:00
63b2b5ca4e Update italian translation to v1.19
PR #2650 <https://github.com/Genymobile/scrcpy/pull/2650>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-10-08 08:34:49 +02:00
8df42cec82 Fix workarounds for Meizu
Workarounds.fillAppInfo() is necessary for Meizu devices even before the
first call to internalStreamScreen(), but it is harmful on other
devices (#940).

Therefore, simplify the workaround, by calling fillAppInfo() only if
Build.BRAND equals "meizu" (case insensitive).

Fixes #240 <https://github.com/Genymobile/scrcpy/issues/240> (again)
Fixes #2656 <https://github.com/Genymobile/scrcpy/issues/2656>
2021-09-22 15:33:17 +02:00
31131039bb Add missing includes
Refs #2616 <https://github.com/Genymobile/scrcpy/issues/2616>
2021-09-20 18:27:37 +02:00
a846c01883 Fix link in README 2021-09-11 21:36:21 +02:00
fa100b814b Add support for expandNotificationsPanel() variant
Some custom vendor ROM added an int as a parameter.

Fixes #2551 <https://github.com/Genymobile/scrcpy/issues/2551>
2021-09-11 10:46:25 +02:00
069fe93f74 Update links to v1.19 2021-09-10 22:01:18 +02:00
228e2c15f4 Bump version to 1.19 2021-09-10 21:40:08 +02:00
9a8f06af65 Merge branch 'master' into dev 2021-09-10 20:26:17 +02:00
1d1c9f36f4 Retrieve correct error messages on Windows
For sockets functions, Windows does not store error codes in errno, so
perror() does not print any error. Use WSAGetLastError() instead.

Refs #2624 <https://github.com/Genymobile/scrcpy/issues/2624>
2021-09-09 23:03:35 +02:00
4d6dd9d281 Compute scrcpy directory manually
The function dirname() does not work correctly everywhere with non-ASCII
characters.

Fixes #2619 <https://github.com/Genymobile/scrcpy/issues/2619>
2021-09-09 12:51:18 +02:00
b5e98db635 Fix typo in manpage
PR #2606 <https://github.com/Genymobile/scrcpy/pull/2606>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-09-07 21:41:40 +02:00
116acc8d25 Use SOURCE_MOUSE for scroll events
This has no practical impact (AFAIK), but a scroll events should come
from a mouse.

Refs #2602 <https://github.com/Genymobile/scrcpy/issues/2602>
2021-08-28 23:14:56 +02:00
3a39bacb76 Upgrade SDL (2.0.16) for Windows
Include the latest version of SDL in Windows releases.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-08-28 14:20:17 +02:00
3fdc89ad42 Upgrade platform-tools (31.0.3) for Windows
Include the latest version of adb in Windows releases.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-08-28 14:12:00 +02:00
3761f56c28 Declare callbacks static
It was a typo, "static" was missing.
2021-08-26 12:26:44 +02:00
c96f5c70e9 Add Simplified Chinese translation of FAQ
PR #2568 <https://github.com/Genymobile/scrcpy/pull/2568>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-08-24 19:29:02 +02:00
d6aaa5bf9a Add a FAQ section for Wayland support
The video driver might need to be explicitly set to wayland.

Refs #2554 <https://github.com/Genymobile/scrcpy/issues/2554>
Refs #2559 <https://github.com/Genymobile/scrcpy/issues/2559>
2021-08-13 12:40:22 +02:00
4ab3e89c29 Add README file in Turkish
PR #2514 <https://github.com/Genymobile/scrcpy/pull/2514>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-08-11 15:42:31 +02:00
4bc78244b9 Fix OBS project ref URL
PR #2545 <https://github.com/Genymobile/scrcpy/pull/2545>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-08-11 15:29:17 +02:00
ea233d811d Fix typo in DEVELOP.md
PR #2540 <https://github.com/Genymobile/scrcpy/pull/2540>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-08-11 15:26:32 +02:00
f78608ab29 Fix type for assignment
The functions net_send_all() and net_recv_all() return ssize_t, not int.
2021-07-15 18:16:56 +02:00
6f03022646 Fix net_send_all()
On partial writes, the final result was the number of bytes written by
the last send() rather than the total.
2021-07-15 18:16:26 +02:00
daf90d33d5 Fix code style
Make the code fit into 80 columns.
2021-07-15 18:07:39 +02:00
0ae10f2b39 Improve slope estimation on start
The first frames are typically received and decoded with more delay than
the others, causing a wrong slope estimation on start.

To compensate, assume an initial slope of 1, then progressively use the
estimated slope.
2021-07-14 14:54:22 +02:00
4c4d02295c Add buffering debugging tools
Output buffering and clock logs by disabling a compilation flag.
2021-07-14 14:54:22 +02:00
2f03141e9f Add clock tests
The clock rolling sum is not trivial. Test it.
2021-07-14 14:54:22 +02:00
3397720330 Add buffering command line options
Add --display-buffer and --v4l2-buffer options to configure buffering
time.
2021-07-14 14:54:22 +02:00
79278961b9 Implement buffering
To minimize latency (at the cost of jitter), scrcpy always displays a
frame as soon as it available, without waiting.

However, when recording (--record), it still writes the captured
timestamps to the output file, so that the recorded file can be played
correctly without jitter.

Some real-time use cases might benefit from adding a small latency to
compensate for jitter too. For example, few tens of seconds of latency
for live-streaming are not important, but jitter is noticeable.

Therefore, implement a buffering mechanism (disabled by default) to add
a configurable latency delay.

PR #2417 <https://github.com/Genymobile/scrcpy/issues/2417>
2021-07-14 14:27:33 +02:00
408a301201 Notify new frames via callbacks
Currently, a frame is available to the consumer as soon as it is pushed
by the producer (which can detect if the previous frame is skipped).

Notify the new frames (and frame skipped) via callbacks instead.

This paves the way to add (optional) buffering, which will introduce a
delay between the time when the frame is produced and the time it is
available to be consumed.
2021-07-14 14:22:32 +02:00
4d8bcfc68a Extract current video_buffer to frame_buffer
The current video buffer only stores one pending frame.

In order to add a new buffering feature, move this part to a separate
"frame buffer". Keep the video_buffer, which currently delegates all its
calls to the frame_buffer.
2021-07-14 14:22:32 +02:00
336248df08 Rename video_buffer to sc_video_buffer
Add a scrcpy-specific prefix.
2021-07-14 14:22:32 +02:00
28bce48d47 Relax v4l2_sink lock constraints
To fix a data race, commit 5caeab5f6d
called video_buffer_push() and video_buffer_consume() under the
v4l2_sink lock.

Instead, use the previous_skipped indication (initialized with video
buffer locked) to lock only for protecting the has_frame flag.

This enables the possibility for the video_buffer to notify new frames
via callbacks without lock inversion issues.
2021-07-14 14:22:32 +02:00
32e692d5d2 Replace delay by deadline in timedwait()
The function sc_cond_timedwait() accepted a parameter representing the
max duration to wait, because it internally uses SDL_CondWaitTimeout().

Instead, accept a deadline, to be consistent with
pthread_cond_timedwait().
2021-07-14 14:22:32 +02:00
ec871dd3f5 Wrap tick API
This avoids to use the SDL timer API directly, and will allow to handle
generic ticks (possibly negative).
2021-07-14 14:22:32 +02:00
5524f378c8 Add missing error log
Log video buffer initialization failure in v4l2_sink.
2021-07-14 00:39:35 +02:00
4ed3aa3604 Move include fps_counter
The fps_counter is not used from video_buffer.
2021-07-14 00:35:10 +02:00
40cea1f677 Remove obsolete comment
Commit 2a94a2b119 removed video_buffer
callbacks, the comment is now meaningless.
2021-07-14 00:35:10 +02:00
099cba07f0 Rename queue to sc_queue
Add a scrcpy-specific prefix.
2021-07-14 00:35:10 +02:00
af8a21ed7c Fix manpage formatting 2021-07-06 18:33:04 +02:00
5938e862a1 Fix --lock-video-orientation syntax in help
Commit f76fe2c0d4 fixed README and tests.

Fix command line help and manpage.
2021-07-06 18:32:40 +02:00
e9096e3e34 Remove unnecessary calls to av_packet_unref()
av_packet_free() already calls av_packet_unref().
2021-07-04 12:19:52 +02:00
5caeab5f6d Fix v4l2 data race
The v4l2_sink implementation directly read the internal video_buffer
field "pending_frame_consumed", which is protected by the internal
video_buffer mutex. But this mutex was not locked, so reads were racy.

Lock using the v4l2_sink mutex in addition, and use a separate field to
avoid depending on the video_buffer internal data.
2021-06-26 16:03:52 +02:00
33fbdc86c7 Initialize fields before starting a thread
To avoid data races.

Reported by TSAN.
2021-06-26 16:03:34 +02:00
f33d37976c Fix assertion race condition in debug mode
Commit 21d206f360 added mutex assertions.

However, the "locker" variable to trace the locker thread id was read
and written by several threads without any protection, so it was racy.

Reported by TSAN.
2021-06-26 15:31:50 +02:00
7dca5078e7 Initialize controller even if there is no display
The options --no-display and --no-control are independent.

The controller was not initialized when no display was requested,
because it was assumed that no control could occur without display. But
that's not true (anymore): for example, it is possible to pass
--turn-screen-off.

Fixes #2426 <https://github.com/Genymobile/scrcpy/issues/2426>
2021-06-25 21:48:07 +02:00
ab12b6c981 Update scrcpy-server in install-release.sh
Make the install script download the new prebuilt server (v1.18).

Fixes #2409 <https://github.com/Genymobile/scrcpy/issues/2409>
2021-06-21 09:47:15 +02:00
376201a83c Update links to v1.18 in README and BUILD 2021-06-20 22:11:56 +02:00
60c4e886d4 Bump version to 1.18
Make the versionCode a decimal representation of the scrcpy version.

This will for example allow to correctly number the versionCode of
v1.17.1 after a v1.18 is released:
 - v1.18   -> 11800
 - v1.17.1 -> 11701
 - v1.18.1 -> 11801
2021-06-20 22:01:07 +02:00
ff7baad709 Upgrade platform-tools (31.0.2) for Windows
Include the latest version of adb in Windows releases.
2021-06-20 21:19:11 +02:00
e712f30ceb Merge branch 'master' into dev 2021-06-20 21:18:58 +02:00
a9d9cbf8b5 Replace VLA by dynamic allocation
And increase the command buffer size.

Refs #1358 <https://github.com/Genymobile/scrcpy/issues/1358#issuecomment-862989748>
PR #2405 <https://github.com/Genymobile/scrcpy/pull/2405>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-06-20 21:16:42 +02:00
fda32928c1 Rename cmd to argv
This is more explicit.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-06-20 21:16:42 +02:00
710e80aa0d Return build_cmd() success via a boolean
For consistency with other functions in the codebase.
2021-06-20 21:16:42 +02:00
77e96d745b Suggest --record-format instead of -F on error
The short option -F has been deprecated by
ff061b4f30. On error, suggest the long
option --record-format instead.
2021-06-20 21:16:03 +02:00
1c95043478 Attempt to log message only in verbose mode
If the log level is not verbose, there is no need to attempt to log
control messages at all.
2021-06-20 16:44:59 +02:00
488991116b Expose function to get the current log level
This will allow to avoid unnecessary processing for creating logs which
will be discarded anyway.
2021-06-20 16:04:18 +02:00
5c95d18beb Move log level conversion to log API 2021-06-20 16:04:18 +02:00
1039f9b531 Workaround PRIu64 on Windows
On Windows, PRIu64 is defined to "llu", which is not supported:

    error: unknown conversion type character 'l' in format
2021-06-20 16:04:18 +02:00
19ca02cd8f Log control messages in verbose mode
PR #2371 <https://github.com/Genymobile/scrcpy/pull/2371>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-06-20 16:03:52 +02:00
937fa704a6 Add --verbosity=verbose log level
PR #2371 <https://github.com/Genymobile/scrcpy/pull/2371>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-06-20 12:34:19 +02:00
7db0189f23 Forward mouse motion only on main clicks
Mouse motion events were forwarded as soon as any mouse button was
pressed.

Instead, only consider left-click (and also middle-click and right-click
if --forward-all-clicks is enabled).
2021-06-20 12:34:19 +02:00
8b90e1d3f4 Remove extra ';' in #define 2021-06-20 12:34:19 +02:00
df017160ed Replace strcpy() by memcpy()
It was safe to call strcpy() since the input length was checked, but
then it is more straightforward to call memcpy() directly.
2021-06-20 12:34:19 +02:00
b846d3a085 Adapt call() on ContentProvider for Android 12
Android 12 changed one of the call() overloads with a new parameter
AttributionSource. Adapt the wrapper.

Fixes #2402 <https://github.com/Genymobile/scrcpy/issues/2402>
2021-06-19 22:59:48 +02:00
a1f2094787 Push to /sdcard/Download/ by default
Change the default push target from /sdcard/ to /sdcard/Download/.

Pushing to the root of /sdcard/ is not very convenient, many apps do not
expose its content directly.

It can still be changed by --push-target.

PR #2384 <https://github.com/Genymobile/scrcpy/pull/2384>
2021-06-16 22:56:11 +02:00
9b89b7ab72 Center the window on resize-to-fit
When removing the black borders (by double-clicking on them, or by
pressing MOD+w), the window is resized to fit the device screen, but its
top-left position was left unchanged.

Instead, move the window so that the new window area is at the center of
the old window area.

Refs #2387 <https://github.com/Genymobile/scrcpy/issues/2387>
2021-06-14 21:24:51 +02:00
7343b233e4 Render screen on window restored
It should not be necessary, since screen_render() is called just after
on SDL_WINDOWEVENT_EXPOSED, but in practice the window content might not
be correctly displayed on restored if a rotation occurred while
minimized.

Note that calling screen_render() twice in a row on
SDL_WINDOWEVENT_EXPOSED also "fixes" the issue.
2021-06-14 09:36:08 +02:00
cd2894570d Allocate AVPacket for v4l2_sink
From FFmpeg/doc/APIchanges:

    2021-03-17 - f7db77bd87 - lavc 58.133.100 - codec.h
      Deprecated av_init_packet(). Once removed, sizeof(AVPacket) will
      no longer be a part of the public ABI.

Refs #2302 <https://github.com/Genymobile/scrcpy/issues/2302>
2021-06-14 09:07:49 +02:00
4af317d40d Allocate AVPacket for recorder
From FFmpeg/doc/APIchanges:

    2021-03-17 - f7db77bd87 - lavc 58.133.100 - codec.h
      Deprecated av_init_packet(). Once removed, sizeof(AVPacket) will
      no longer be a part of the public ABI.

Refs #2302 <https://github.com/Genymobile/scrcpy/issues/2302>
2021-06-14 09:07:49 +02:00
318b6a572e Allocate AVPacket for local stream packet
From FFmpeg/doc/APIchanges:

    2021-03-17 - f7db77bd87 - lavc 58.133.100 - codec.h
      Deprecated av_init_packet(). Once removed, sizeof(AVPacket) will
      no longer be a part of the public ABI.

Refs #2302 <https://github.com/Genymobile/scrcpy/issues/2302>
2021-06-14 09:07:49 +02:00
e8b053ad2f Allocate AVPacket for stream->pending
From FFmpeg/doc/APIchanges:

    2021-03-17 - f7db77bd87 - lavc 58.133.100 - codec.h
      Deprecated av_init_packet(). Once removed, sizeof(AVPacket) will
      no longer be a part of the public ABI.

Remove the has_pending boolean, which can be replaced by:

    stream->pending != NULL

Refs #2302 <https://github.com/Genymobile/scrcpy/issues/2302>
2021-06-14 09:07:49 +02:00
a5d71eee45 Clarify --no-display usage with v4l2 2021-06-14 08:16:05 +02:00
af228706f1 Fix compatibility with old FFmpeg
V4L2 sink used a "url" field format AVFormatContext which has been
introduced in lavf 58.7.100.

Fixes #2382 <https://github.com/Genymobile/scrcpy/issues/2382>

Refs <ea3672b7d6>
Refs <fa8308d3d4>
2021-06-13 19:20:57 +02:00
f76fe2c0d4 Fix --lock-video-orientation syntax
The argument for option --lock-video-orientation has been made optional
by 5af9d0ee0f.

With getopt_long(), contrary to mandatory arguments, optional arguments
must be given with a '=':

    --lock-video-orientation 2   # wrong, parse error
    --lock-video-orientation=2   # correct
2021-06-11 18:40:12 +02:00
16a63e0917 Use non-secure display for Android 12 preview
Android 12 preview identifies as Android 11, but its codename is "S".

Refs #2129 <https://github.com/Genymobile/scrcpy/issues/2129>
2021-06-11 10:02:52 +02:00
f7533e8896 Use non-secure display for Android >= 12
Since Android 12, secure displays could not be created with shell
permissions anymore.

Refs commit 1fdde490fd
Fixes #2129 <https://github.com/Genymobile/scrcpy/issues/2129>
2021-06-11 09:37:29 +02:00
969bfd4374 Serialize clean-up configuration
This avoids to pass each option as individual parameter and parse them
manually (it's still "manual" in the Parcelable implementation).

Refs #824 <https://github.com/Genymobile/scrcpy/pull/824#issuecomment-780319422>

Reviewed-by: Yu-Chen Lin <npes87184@gmail.com>
2021-06-11 09:33:29 +02:00
506f918fb7 Group components into struct scrcpy
This avoids to refer to many structs globally.
2021-05-28 21:29:14 +02:00
6fd7e89da1 Explicitly initialize decoder sink_count
The zero-initialization relied on the fact that the decoder instance is
static.
2021-05-28 21:23:18 +02:00
4c31911df2 Pass serial within struct server_params
This was the only option passed separately.
2021-05-28 21:21:54 +02:00
d3d955f67b Translate README.md into Spanish
PR #2318 <https://github.com/Genymobile/scrcpy/pull/2318>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-05-28 10:19:17 +02:00
8121b0b7e7 FAQ in Italian
PR #2316 <https://github.com/Genymobile/scrcpy/pull/2316>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-05-28 10:19:17 +02:00
2e9d520080 README in Italian
PR #2316 <https://github.com/Genymobile/scrcpy/pull/2316>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-05-28 10:18:49 +02:00
1b7c9b3e1c Reference translations from FAQ.md 2021-05-28 10:18:36 +02:00
9bed8cf3f4 Fix syntax highlighting in README 2021-05-28 10:06:26 +02:00
6adc97198b Provide device info directly on server connection
This avoids to retrieve them in a separate step.
2021-05-17 08:52:08 +02:00
6a2cd089a1 Initialize input manager dynamically
The input manager was partially initialized statically, but a call to
input_manager_init() was needed anyway, so initialize all the fields
from the "constructor".

This is consistent with the initialization of the other structs.
2021-05-17 08:48:51 +02:00
dcee7c0f7f Factorize screen_init() error management 2021-05-16 18:48:04 +02:00
e604e8a752 Move fps_counter to screen
The FPS counter specifically count frames from the screen video buffer,
so it is specific to the screen.
2021-05-16 18:47:16 +02:00
f19c455110 Fix leak on error
Destroy video buffer if screen window creation failed.
2021-05-16 18:46:47 +02:00
83116fc199 Notify end-of-stream via callback
Do not send a SDL event directly, to make stream independent of SDL.
2021-05-16 15:48:07 +02:00
72081a241b Fix visualization of comment in code block
PR #2315 <https://github.com/Genymobile/scrcpy/pull/2315>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-05-15 18:58:04 +02:00
LYK
42b3d1e66e Reformat README.ko.md
PR #2311 <https://github.com/Genymobile/scrcpy/pull/2311>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-05-15 18:56:45 +02:00
1e64f0f931 Use ARRAY_LEN() macro 2021-05-09 11:06:02 +02:00
8fb5715740 Add libavdevice-dev in BUILD instructions
On Linux, scrcpy now depends on libavdevice for v4l2.
2021-05-09 11:01:16 +02:00
f062dfd30b Merge branch 'master' into dev 2021-05-09 11:00:30 +02:00
644a5ef14a Add MacPorts documentation
PR #2299 <https://github.com/Genymobile/scrcpy/pull/2299>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-05-09 10:55:59 +02:00
1b9dcce23c Fix double-free on error
On error, server->serial was freed twice: immediately and in
server_destroy().

Refs #2292 <https://github.com/Genymobile/scrcpy/issues/2292#issuecomment-831424820>
2021-05-03 20:43:45 +02:00
233f8e6cc4 Rename keycode injection method
Make it explicit that it injects both "press" and "release" events.
2021-04-30 23:07:37 +02:00
9a7d351d67 Simplify non-static injectEvent() implementation
Just call the static version (having a displayId) from the non-static
version (using the displayId field).
2021-04-30 23:07:37 +02:00
d00ee640c0 Simplify Device.java
Remove useless intermediate method with a "mode" parameter (it's always
called with the same mode).

This also avoids the need for a specific injectEventOnDisplay() method,
since it does not conflict with another injectEvent() method anymore.
2021-04-30 23:07:29 +02:00
ae6ec7a194 Unref decoder AVFrame immediately
The frame can be unref immediately after it is pushed to the frame
sinks.

It was not really a memory leak because the frame was unref every time
by avcodec_receive_frame() (and freed on close), but a reference was
unnecessarily kept for too long.
2021-04-26 18:05:43 +02:00
84f17fdeab Fix v4l2 AVPacket memory leak on error
Unref v4l2 AVPacket even if writing failed.
2021-04-26 18:05:11 +02:00
1cde68a1fa Fix v4l2 AVFrame memory leak
Unref frame immediately once encoded.

Fixes #2279 <https://github.com/Genymobile/scrcpy/pull/2279>
2021-04-26 18:04:51 +02:00
45e7280148 Rename --v4l2_sink to --v4l2-sink
This was a rebase issue, the previous version in #2268 was correct.

Refs #2268 <https://github.com/Genymobile/scrcpy/pull/2268>
2021-04-26 17:59:35 +02:00
41a0383d7c Document v4l2 sink in README 2021-04-25 15:00:56 +02:00
d39161f753 Add support for v4l2loopback
It allows to send the video stream to /dev/videoN, so that it can be
captured (like a webcam) by any v4l2-capable tool.

PR #2232 <https://github.com/Genymobile/scrcpy/pull/2232>
PR #2233 <https://github.com/Genymobile/scrcpy/pull/2233>
PR #2268 <https://github.com/Genymobile/scrcpy/pull/2268>

Co-authored-by: Romain Vimont <rom@rom1v.com>
2021-04-25 14:59:10 +02:00
5af9d0ee0f Make --lock-video-orientation argument optional
If the option is set without argument, lock the initial device
orientation (as if the value "initial" was passed).
2021-04-25 14:55:54 +02:00
fd0dc6c0cd Add --lock-video-orientation=initial
Add a new mode to the --lock-video-orientation option, to lock the
initial orientation of the device.

This avoids to pass an explicit value (0, 1, 2 or 3) and think about
which is the right one.
2021-04-25 14:55:54 +02:00
151bc16644 Use strlist_contains() to find a muxer
The AVOutputFormat name is a string list: it contains names separated by
',' (possibly only one).
2021-04-25 14:55:19 +02:00
ffc00210e9 Add strlist_contains()
Add a function to know if a string list, using some separator, contains
a specific string.
2021-04-25 14:38:42 +02:00
243854a408 Fix recorder comment 2021-04-25 14:38:42 +02:00
8b90dc61b9 Handle EAGAIN on send_packet in decoder
EAGAIN was only handled on receive_frame.

In practice, it should not be necessary, since one packet always
contains one frame. But just in case.
2021-04-25 14:38:42 +02:00
2a5dfc1c17 Handle errors using gotos in recorder_open()
There are many initializations in recorder_open(). Handle RAII-like
deinitialization using gotos.
2021-04-25 14:38:42 +02:00
e3fccc5a5e Initialize recorder fields on open
Only initialize ops and parameters copy from recorder_init(). It was
inconsistent to initialize some fields from _init() and some others from
_open().
2021-04-25 14:38:42 +02:00
0541f1bff2 Hide the window immediately on close
The screen may not be destroyed immediately on close to avoid undefined
behavior, because it may still receive events from the decoder.

But the visual window must still be closed immediately.
2021-04-25 14:38:42 +02:00
0272e6dc77 Assert screen closed on destroy
The destruction order is important, but tricky, because the screen is
open/close by the decoder, but destroyed by scrcpy.c on the main thread.

Add assertions to guarantee that the screen is not destroyed before
being closed.
2021-04-25 14:38:42 +02:00
2a94a2b119 Remove video_buffer callbacks
Now that screen is both the owner and the listener of the video buffer,
execute the code directly without callbacks.
2021-04-25 14:38:42 +02:00
e91acdb0c4 Move video_buffer to screen
The video buffer is now an internal detail of the screen component.

Since the screen is plugged to the decoder via the frame sink trait, the
decoder does not access to the video buffer anymore.
2021-04-25 14:38:42 +02:00
6f5ad21f57 Make decoder push frames to sinks
Now that screen implements the packet sink trait, make decoder push
packets to the sinks without depending on the concrete sink types.
2021-04-25 14:38:42 +02:00
08b3086ffc Expose screen as frame sink
Make screen implement the frame sink trait.

This will allow the decoder to push frames without depending on the
concrete sink type.
2021-04-25 14:38:42 +02:00
deab7da761 Add frame sink trait
This trait will allow to abstract the concrete sink types from the frame
producer (decoder.c).
2021-04-25 14:38:42 +02:00
f7a1b67d66 Make stream push packets to sinks
Now that decoder and recorder implement the packet sink trait, make
stream push packets to the sinks without depending on the concrete sink
types.
2021-04-25 14:38:42 +02:00
cbed38799e Expose decoder as packet sink
Make decoder implement the packet sink trait.

This will allow the stream to push packets without depending on the
concrete sink type.
2021-04-25 14:38:42 +02:00
5beb7d6c02 Reorder decoder functions
This will make further commits more readable.
2021-04-25 14:38:42 +02:00
5980183a33 Expose recorder as packet sink
Make recorder implement the packet sink trait.

This will allow the stream to push packets without depending on the
concrete sink type.
2021-04-25 14:38:42 +02:00
fe8de893ca Privatize recorder threading
The fact that the recorder uses a separate thread is an internal detail,
so the functions _start(), _stop() and _join() should not be exposed.

Instead, start the thread on _open() and _stop()+_join() on close().

This paves the way to expose the recorder as a packet sink trait.
2021-04-25 14:38:42 +02:00
a974483c15 Reorder recorder functions
This will make further commits more readable.
2021-04-25 14:38:42 +02:00
1b072a24c4 Add packet sink trait
This trait will allow to abstract the concrete sink types from the
packet producer (stream.c).
2021-04-25 14:38:42 +02:00
08f1fd46c8 Add container_of() macro
This will allow to get the parent of an embedded struct.
2021-04-25 14:38:42 +02:00
2ddf760c09 Make video_buffer more generic
The video buffer took ownership of the producer frame (so that it could
swap frames quickly).

In order to support multiple sinks plugged to the decoder, the decoded
frame must not be consumed by the display video buffer.

Therefore, move the producer and consumer frames out of the video
buffer, and use FFmpeg AVFrame refcounting to share ownership while
avoiding copies.
2021-04-25 14:38:42 +02:00
5d9e96dc4e Remove compat with old FFmpeg codec params API
The new API has been introduced in 2016 in libavformat 57.xx, it's very
old.

This will avoid to maintain two code paths for codec parameters.
2021-04-25 14:38:42 +02:00
de9b79ec2d Remove compat with old FFmpeg decoding API
The new API has been introduced in 2016 in libavcodec 57.xx, it's very
old.

This will avoid to maintain two code paths for decoding.
2021-04-25 14:38:42 +02:00
55806e7d31 Remove option --render-expired-frames
This flag forced the decoder to wait for the previous frame to be
consumed by the display.

It was initially implemented as a compilation flag for testing, not
intended to be exposed at runtime. But to remove ifdefs and to allow
users to test this flag easily, it had finally been exposed by commit
ebccb9f6cc.

In practice, it turned out to be useless: it had no practical impact,
and it did not solve or mitigate any performance issues causing frame
skipping.

But that added some complexity to the codebase: it required an
additional condition variable, and made video buffer calls possibly
blocking, which in turn required code to interrupt it on exit.

To prepare support for multiple sinks plugged to the decoder (display
and v4l2 for example), the blocking call used for pacing the decoder
output becomes unacceptable, so just remove this useless "feature".
2021-04-25 14:38:42 +02:00
21b590b766 Write trailer from recorder thread
The recorder thread wrote the whole content except the trailer, which
was odd.
2021-04-25 14:38:42 +02:00
d7e6589677 Document 4th+5th + 2xn shortcuts
PR #2260 <https://github.com/Genymobile/scrcpy/pull/2260>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-04-25 14:36:48 +02:00
b4ee9f27ce Add mouse shortcut to expand settings panel
Double-click on extra mouse button to open the settings panel (a
single-click opens the notification panel).

This is consistent with the keyboard shortcut MOD+n+n.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-04-25 14:36:48 +02:00
6fa63cf6f8 Add keyboard shortcut to expand settings panel
PR #2260 <https://github.com/Genymobile/scrcpy/pull/2260>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-04-25 14:36:48 +02:00
50eecdab28 Add control message to expand settings panel
PR #2260 <https://github.com/Genymobile/scrcpy/pull/2260>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-04-25 14:36:48 +02:00
9576283907 Count repeated identical key events
This will allow shortcuts such as MOD+n+n to open the settings panel.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-04-25 14:36:48 +02:00
66c581851f Rename control message type to COLLAPSE_PANELS
The collapsing action collapses any panels.

By the way, the Android method is named collapsePanels().

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-04-25 14:36:48 +02:00
bb4614d558 Reverse boolean logic for readability
Refs #2260 <https://github.com/Genymobile/scrcpy/pull/2260#issuecomment-823508759>
2021-04-25 14:36:48 +02:00
aaf7875d92 Ensure get_server_path() retval is freeable
PR #2276 <https://github.com/Genymobile/scrcpy/pull/2276>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-04-22 22:12:23 +02:00
b9c3f65fd8 Provide actions for the extra mouse buttons
Bind APP_SWITCH to button 4 and expand notification panel on button 5.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-04-19 20:16:45 +02:00
d0739911a3 Forward DOWN and UP separately for right-click
The shortcut "back on screen on" is a bit special: the control is
requested by the client, but the actual event injection (POWER or BACK)
is determined on the device.

To properly inject DOWN and UP events for BACK, transmit the action as
a control parameter.

If the screen is off:
 - on DOWN, inject POWER (DOWN and UP) (wake up the device immediately)
 - on UP, do nothing
If the screen is on:
 - on DOWN, inject BACK DOWN
 - on UP, inject BACK UP

A corner case is when the screen turns off between the DOWN and UP
event. In that case, a BACK UP event will be injected, so it's harmless.

As a consequence of this change, the BACK button is now handled by
Android on mouse released. This is consistent with the keyboard shortcut
(Mod+b) behavior.

PR #2259 <https://github.com/Genymobile/scrcpy/pull/2259>
Refs #2258 <https://github.com/Genymobile/scrcpy/pull/2258>
2021-04-19 20:16:45 +02:00
498ad23e98 Fix typos
PR #2263 <https://github.com/Genymobile/scrcpy/pull/2263>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-04-18 14:36:31 +02:00
964b6d2243 Forward DOWN and UP separately for middle-click
As a consequence of this change, the HOME button is now handled by
Android on mouse released. This is consistent with the keyboard shortcut
(MOD+h) behavior.

PR #2259 <https://github.com/Genymobile/scrcpy/pull/2259>
Refs #2258 <https://github.com/Genymobile/scrcpy/pull/2258>
2021-04-17 18:04:24 +02:00
8cc057c8f1 Prevent forwarding only "mouse released" events
Some mouse clicks DOWN are captured for shortcuts, but the matching UP
events were still forwarded to the device.

Instead, capture both DOWN and UP for shortcuts, and do nothing on UP.

PR #2259 <https://github.com/Genymobile/scrcpy/pull/2259>
Refs #2258 <https://github.com/Genymobile/scrcpy/pull/2258>
2021-04-17 18:04:13 +02:00
edee69d637 Fix options alphabetical order
"verbosity" < "version"
2021-04-16 17:40:39 +02:00
8ef4c044fa Do not forward SDL_DROPFILE event
The event is handled by scrcpy.c, it is not necessary to send it to
screen or input_manager.
2021-04-13 22:37:08 +02:00
c23c38f99d Move resizing workaround to screen.c 2021-04-13 22:36:59 +02:00
65c4f487b3 Set initial fullscreen from screen.c 2021-04-13 22:15:05 +02:00
c6d7f5ee96 Make screen_show_window() static
It is only used from screen.c now.
2021-04-13 22:04:38 +02:00
28f6cbaea6 Destroy screen once stream is finished
The screen receives callbacks from the decoder, fed by the stream.

The decoder is run from the stream thread, so waiting for the end of
stream is sufficient to avoid possible use-after-destroy.
2021-04-11 14:42:09 +02:00
08fc6694e1 Do not destroy uninitialized screen
When --no-display was passed, screen_destroy() was called while
screen_init() was never called.

In practice, it did not crash because it just freed NULL pointers, but
it was still incorrect.
2021-04-11 13:07:44 +02:00
d0983db592 Make internal recorder function static 2021-04-10 18:48:52 +02:00
fb7870500a Remove unused field from input_manager 2021-04-10 18:48:52 +02:00
33006561c7 Remove useless forward declaration from stream.h 2021-04-10 18:48:52 +02:00
a09733d175 Remove useless includes from decoder.c 2021-04-10 18:48:44 +02:00
07a85b7c94 Fix typo in command-line help
Refs #2237 <https://github.com/Genymobile/scrcpy/issues/2237>
2021-04-07 15:12:33 +02:00
6231f683af Fix compilation error for old decoding API
Commits cb9c42bdcb and
441d3fb119 updated the code only for the
new decoding API.
2021-04-05 22:35:14 +02:00
9826c5c4a4 Remove HiDPI compilation flag
Always enable HiDPI support, there is no reason to expose a compilation
flag.
2021-04-04 15:00:13 +02:00
2812de8a9a Update brew java version to JDK 11
Refs f8524a2be7
Refs 7b51a0313e
2021-04-04 14:39:16 +02:00
d50c678a5f Update brew cask documentation
The command `brew cask` has been deprecated:
<https://github.com/Homebrew/discussions/discussions/340#discussioncomment-232364>

Should be `brew install` directly now.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-04-04 14:38:54 +02:00
b77932a5b7 Add instructions to uninstall 2021-04-01 09:47:15 +02:00
47d16a57ac Add simplified installation script
Add a script to download the server and build scrcpy using the
prebuilt server.
2021-03-29 09:33:39 +02:00
fc5de88eaa Clarify the order of operations in BUILD.md
PR #2223 <https://github.com/Genymobile/scrcpy/pull/2223>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-03-29 09:28:08 +02:00
fda293f9bb Fix BUILD.md line wrapping 2021-03-29 09:11:40 +02:00
38f392f08f Fix typo in BUILD.md 2021-03-29 08:39:02 +02:00
8414b688f0 Link release to main README in translations
This avoids to link an older version.
2021-03-28 21:36:00 +02:00
6a217b70f4 Add Japanese translation for README.md
PR #2195 <https://github.com/Genymobile/scrcpy/pull/2195>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-03-28 21:28:20 +02:00
3a4b10a38d Improve --push-target example in README
A common use case for the --push-target option is to pass the device
Download directory.
2021-03-28 12:25:08 +02:00
1fb7957525 Fix typo in README
Refs <https://github.com/Genymobile/scrcpy/pull/2195#discussion_r595664697>
2021-03-17 08:52:16 +01:00
19ad107f1f Add "Get the app" summary section
Give quick instructions to download/install scrcpy.
2021-03-16 21:43:52 +01:00
1d615a0d51 Support power off on close
PR #824 <https://github.com/Genymobile/scrcpy/pull/824>

Signed-off-by: Yu-Chen Lin <npes87184@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-03-16 21:12:35 +01:00
fb0bcaebc2 Export static method to power off screen in Device
PR #824 <https://github.com/Genymobile/scrcpy/pull/824>

Signed-off-by: Yu-Chen Lin <npes87184@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-03-16 21:12:29 +01:00
dd453ad041 Pass scrcpy-noconsole arguments through to scrcpy
PR #2052 <https://github.com/Genymobile/scrcpy/pull/2052>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-03-16 21:04:04 +01:00
0308ef43f2 Do not set buttons on touch events
BUTTON_PRIMARY must not be set for touch events:

> This button constant is not set in response to simple touches with a
> finger or stylus tip. The user must actually push a button.

<https://developer.android.com/reference/android/view/MotionEvent#BUTTON_PRIMARY>

Fixes #2169 <https://github.com/Genymobile/scrcpy/issues/2169>
2021-03-15 18:46:56 +01:00
40febf4a91 Use device id 0 for touch/mouse events
Virtual device is only for keyboard sources, not mouse or touchscreen
sources. Here is the value of InputDevice.getDevice(-1).toString():

    Input Device -1: Virtual
      Descriptor: ...
      Generation: 2
      Location: built-in
      Keyboard Type: alphabetic
      Has Vibrator: false
      Has mic: false
      Sources: 0x301 ( keyboard dpad )

InputDevice.getDeviceId() documentation says:

> An id of zero indicates that the event didn't come from a physical
> device and maps to the default keymap.

<https://developer.android.com/reference/android/view/InputEvent#getDeviceId()>

However, injecting events with a device id of 0 causes event.getDevice()
to be null on the client-side.

Commit 26529d377f used -1 as a workaround
to avoid a NPE on a specific Android TV device. But this is a bug in the
device system, which wrongly assumes that input device may not be null.

A similar issue was present in Flutter, but it is now fixed:
 - <https://github.com/flutter/flutter/issues/30665>
 - <https://github.com/flutter/engine/pull/7986>

On the other hand, using an id of -1 for touchscreen events (which is
invalid) causes issues for some apps:
<https://github.com/Genymobile/scrcpy/issues/2125#issuecomment-790535792>

Therefore, use a device id of 0.

An alternative could be to find an existing device matching the source,
like "adb shell input" does. See getInputDeviceId():
<https://android.googlesource.com/platform/frameworks/base.git/+/master/cmds/input/src/com/android/commands/input/Input.java>

But it seems better to indicate that the event didn't come from a
physical device, and it would not solve #962 anyway, because an Android
TV has no touchscreen.

Refs #962 <https://github.com/Genymobile/scrcpy/issues/962>
Fixes #2125 <https://github.com/Genymobile/scrcpy/issues/2125>
2021-03-14 18:30:56 +01:00
429fdef04f Fix encoder parameter suggestion
The option is --encoder, not --encoder-name.
2021-03-11 10:55:12 +01:00
d1789f082a meson: Do not use full path with mingw tools name
This helps to use mingw toolchains which are not in /usr/bin path.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-03-09 09:59:57 +01:00
eb7e1070cf Release frame data as soon as possible
During a frame swap, one of the two frames involved can be released.
2021-03-06 22:58:03 +01:00
386f017ba9 Factorize frame swap 2021-03-06 22:58:03 +01:00
cc48b24324 Simplify screen initialization
Use a single function to initialize the screen instance.
2021-03-06 22:58:03 +01:00
597c54f049 Group screen parameters into a struct
The function screen_init_rendering had too many parameters.
2021-03-06 22:58:03 +01:00
955da3b578 Remove screen static initializer
Most of the fields are initialized dynamically.
2021-03-06 22:58:03 +01:00
cb9c42bdcb Use a callback to notify frame skip
A skipped frame is detected when the producer offers a frame while the
current pending frame has not been consumed.

However, the producer (in practice the decoder) is not interested in the
fact that a frame has been skipped, only the consumer (the renderer) is.

Therefore, notify frame skip via a consumer callback. This allows to
manage the skipped and rendered frames count at the same place, and
remove fps_counter from decoder.
2021-03-06 22:58:03 +01:00
fb9f9848bd Use a callback to notify a new frame
Make the decoder independant of the SDL even mechanism, by making the
consumer register a callback on the video_buffer.
2021-03-06 22:58:03 +01:00
c50b958ee4 Initialize screen before starting the stream
As soon as the stream is started, the video buffer could notify a new
frame available.

In order to pass this event to the screen without race condition, the
screen must be initialized before the screen is started.
2021-03-06 22:58:03 +01:00
441d3fb119 Make video buffer more generic
Video buffer is a tool between a frame producer and a frame consumer.

For now, it is used between a decoder and a renderer, but in the future
another instance might be used to swscale decoded frames.
2021-03-06 22:58:03 +01:00
cb197ee3a2 Move fps counter out of video buffer
In order to make video buffer more generic, move out its specific
responsibility to count the fps between the decoder and the renderer.
2021-03-06 22:58:03 +01:00
dca11f6c51 Remove obsolete FAQ entry
Issue #15 had been fixed in v1.14 by
e40532a376.
2021-03-06 14:28:06 +01:00
08baaf4b57 Mention adb debugging in FAQ 2021-03-04 15:19:00 +01:00
218636dc10 Inject touch events with smallest detectable size
A value of 1 means the "largest detectable size", a value of 0 means the
"smallest detectable size".

https://developer.android.com/reference/android/view/MotionEvent.PointerCoords#size
https://developer.android.com/reference/android/view/MotionEvent#AXIS_SIZE

Fixes #2125 <https://github.com/Genymobile/scrcpy/issues/2125>
2021-03-03 18:22:54 +01:00
1863ca7ad1 Remove unnecessary escape characters in manpage
PR #2123 <https://github.com/Genymobile/scrcpy/pull/2123>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-02-27 17:53:40 +01:00
7b51a0313e Update another java version in BUILD.md
Commit f8524a2be7 updated one reference to
the openjdk package, but there was another one.
2021-02-27 00:27:58 +01:00
a2919b3ef2 Fix release instructions in BUILD.md
Makefile.CrossWindows have been renamed to release.mk, which is called
from release.sh.
2021-02-27 00:20:18 +01:00
b16b65a715 Simplify default values
It makes sense to extract default values for bitrate and port range
(which are arbitrary and might be changed in the future).

However, the default values for "max size" and "lock video orientation"
are naturally unlimited/unlocked, and will never be changed. Extracting
these options just added complexity for no benefit, so hardcode them.
2021-02-25 22:19:05 +01:00
a3aa5ac95e Insert numerical values statically in usage string 2021-02-25 22:19:05 +01:00
0207e3df33 Remove unused no_window field 2021-02-25 22:19:05 +01:00
9cd1a7380d Enable NDEBUG via Meson built-in option 2021-02-25 22:19:05 +01:00
24b637b972 Handle im-related events from input_manager.c 2021-02-25 22:19:05 +01:00
76a3d9805b Inline window events handling
Now that all screen-related events are handled from screen.c, there is
no need for a separate method for window events.
2021-02-25 22:19:05 +01:00
50b4a730e3 Handle screen-related events from screen.c 2021-02-25 22:19:05 +01:00
ea2369f568 Reference video buffer from screen
This paves the way to handle EVENT_NEW_FRAME from screen.c, by allowing
to call screen_update_frame() without an explicit video_buffer instance.
2021-02-25 22:19:05 +01:00
0538e9645b Improve error handling in screen initialization
After the struct screen is initialized, the window, the renderer and the
texture are necessarily valid, so there is no need to check in
screen_destroy().
2021-02-25 22:18:51 +01:00
626094ad13 Handle window events only once visible
This will avoid corner cases where we need to resize while no frame has
been received yet.
2021-02-17 09:54:03 +01:00
a566635c43 Log mipmaps error only if mipmaps are enabled 2021-02-17 09:54:03 +01:00
862948b132 Make use_opengl local
The flag is used only locally, there is no need to store it in the
screen structure.
2021-02-17 09:54:03 +01:00
c0c4ba7009 Add intermediate frame in video buffer
There were only two frames simultaneously:
 - one used by the decoder;
 - one used by the renderer.

When the decoder finished decoding a frame, it swapped it with the
rendering frame.

Adding a third frame provides several benefits:
 - the decoder do not have to wait for the renderer to release the
   mutex;
 - it simplifies the video_buffer API;
 - it makes the rendering frame valid until the next call to
   video_buffer_take_rendering_frame(), which will be useful for
   swscaling on window resize.
2021-02-17 09:54:03 +01:00
c53bd4d8b6 Assert non-recursive usage of mutexes 2021-02-17 09:54:03 +01:00
54f5c42d7b Add mutex assertions 2021-02-17 09:54:03 +01:00
21d206f360 Expose mutex assertions
Add a function to assert that the mutex is held (or not).
2021-02-17 09:54:03 +01:00
d2689fc168 Expose thread id 2021-02-17 09:54:03 +01:00
f6320c7e31 Wrap SDL thread functions into scrcpy-specific API
The goal is to expose a consistent API for system tools, and paves the
way to make the "core" independant of SDL in the future.
2021-02-17 09:54:03 +01:00
30e619d37f Replace SDL_strdup() by strdup()
The functions SDL_malloc(), SDL_free() and SDL_strdup() were used only
because strdup() was not available everywhere.

Now that it is available, use the native version of these functions.
2021-02-17 09:54:03 +01:00
c0dde0fade Provide strdup() compat
Make strdup() available on all platforms.
2021-02-17 09:53:25 +01:00
ace438e52a Remove unused port_range field
The port_range is used from "struct server_params", the copy in
"struct server" was unused.
2021-02-14 14:47:49 +01:00
8e83f3e8af Remove unused custom event 2021-02-14 14:44:05 +01:00
f8524a2be7 Update java version in BUILD.md
PR #2064 <https://github.com/Genymobile/scrcpy/pull/2064>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-01-27 21:50:17 +01:00
ce43fad645 Update README.zh-Hans to v1.17
PR #2029 <https://github.com/Genymobile/scrcpy/pull/2029>

Reviewed-by: Win7GM
Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-01-15 16:20:34 +01:00
af516e33ee Update README.pt-br to v1.17
PR #2034 <https://github.com/Genymobile/scrcpy/pull/2034>

Reviewed-by: latreta <yves_henry13@hotmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-01-15 10:17:11 +01:00
181 changed files with 18548 additions and 5115 deletions

143
BUILD.md
View File

@ -2,11 +2,44 @@
Here are the instructions to build _scrcpy_ (client and server). Here are the instructions to build _scrcpy_ (client and server).
You may want to build only the client: the server binary, which will be pushed
to the Android device, does not depend on your system and architecture. In that
case, use the [prebuilt server] (so you will not need Java or the Android SDK).
[prebuilt server]: #prebuilt-server ## Simple
If you just want to install the latest release from `master`, follow this
simplified process.
First, you need to install the required packages:
```bash
# for Debian/Ubuntu
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-1.0-0-dev
```
Then clone the repo and execute the installation script
([source](install_release.sh)):
```bash
git clone https://github.com/Genymobile/scrcpy
cd scrcpy
./install_release.sh
```
When a new release is out, update the repo and reinstall:
```bash
git pull
./install_release.sh
```
To uninstall:
```bash
sudo ninja -Cbuild-auto uninstall
```
## Branches ## Branches
@ -56,15 +89,15 @@ Install the required packages from your package manager.
```bash ```bash
# runtime dependencies # runtime dependencies
sudo apt install ffmpeg libsdl2-2.0-0 adb sudo apt install ffmpeg libsdl2-2.0-0 adb libusb-1.0-0
# client build dependencies # client build dependencies
sudo apt install gcc git pkg-config meson ninja-build \ sudo apt install gcc git pkg-config meson ninja-build libsdl2-dev \
libavcodec-dev libavformat-dev libavutil-dev \ libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \
libsdl2-dev libusb-1.0-0-dev
# server build dependencies # server build dependencies
sudo apt install openjdk-8-jdk sudo apt install openjdk-11-jdk
``` ```
On old versions (like Ubuntu 16.04), `meson` is too old. In that case, install On old versions (like Ubuntu 16.04), `meson` is too old. In that case, install
@ -83,7 +116,7 @@ pip3 install meson
sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
# client build dependencies # client build dependencies
sudo dnf install SDL2-devel ffms2-devel meson gcc make sudo dnf install SDL2-devel ffms2-devel libusb-devel meson gcc make
# server build dependencies # server build dependencies
sudo dnf install java-devel sudo dnf install java-devel
@ -106,13 +139,13 @@ sudo apt install mingw-w64 mingw-w64-tools
You also need the JDK to build the server: You also need the JDK to build the server:
```bash ```bash
sudo apt install openjdk-8-jdk sudo apt install openjdk-11-jdk
``` ```
Then generate the releases: Then generate the releases:
```bash ```bash
make -f Makefile.CrossWindows ./release.sh
``` ```
It will generate win32 and win64 releases into `dist/`. It will generate win32 and win64 releases into `dist/`.
@ -173,12 +206,12 @@ brew install pkg-config meson
``` ```
Additionally, if you want to build the server, install Java 8 from Caskroom, and Additionally, if you want to build the server, install Java 8 from Caskroom, and
make it avaliable from the `PATH`: make it available from the `PATH`:
```bash ```bash
brew tap homebrew/cask-versions brew tap homebrew/cask-versions
brew cask install adoptopenjdk/openjdk/adoptopenjdk8 brew install adoptopenjdk/openjdk/adoptopenjdk11
export JAVA_HOME="$(/usr/libexec/java_home --version 1.8)" export JAVA_HOME="$(/usr/libexec/java_home --version 1.11)"
export PATH="$JAVA_HOME/bin:$PATH" export PATH="$JAVA_HOME/bin:$PATH"
``` ```
@ -189,8 +222,27 @@ See [pierlon/scrcpy-docker](https://github.com/pierlon/scrcpy-docker).
## Common steps ## Common steps
If you want to build the server, install the [Android SDK] (_Android Studio_), **As a non-root user**, clone the project:
and set `ANDROID_SDK_ROOT` to its directory. For example:
```bash
git clone https://github.com/Genymobile/scrcpy
cd scrcpy
```
### Build
You may want to build only the client: the server binary, which will be pushed
to the Android device, does not depend on your system and architecture. In that
case, use the [prebuilt server] (so you will not need Java or the Android SDK).
[prebuilt server]: #option-2-use-prebuilt-server
#### Option 1: Build everything from sources
Install the [Android SDK] (_Android Studio_), and set `ANDROID_SDK_ROOT` to its
directory. For example:
[Android SDK]: https://developer.android.com/studio/index.html [Android SDK]: https://developer.android.com/studio/index.html
@ -203,20 +255,11 @@ export ANDROID_SDK_ROOT=~/Library/Android/sdk
set ANDROID_SDK_ROOT=%LOCALAPPDATA%\Android\sdk set ANDROID_SDK_ROOT=%LOCALAPPDATA%\Android\sdk
``` ```
If you don't want to build the server, use the [prebuilt server].
Clone the project:
```bash
git clone https://github.com/Genymobile/scrcpy
cd scrcpy
```
Then, build: Then, build:
```bash ```bash
meson x --buildtype release --strip -Db_lto=true meson x --buildtype release --strip -Db_lto=true
ninja -Cx ninja -Cx # DO NOT RUN AS ROOT
``` ```
_Note: `ninja` [must][ninja-user] be run as a non-root user (only `ninja _Note: `ninja` [must][ninja-user] be run as a non-root user (only `ninja
@ -225,9 +268,27 @@ install` must be run as root)._
[ninja-user]: https://github.com/Genymobile/scrcpy/commit/4c49b27e9f6be02b8e63b508b60535426bd0291a [ninja-user]: https://github.com/Genymobile/scrcpy/commit/4c49b27e9f6be02b8e63b508b60535426bd0291a
### Run #### Option 2: Use prebuilt server
To run without installing: - [`scrcpy-server-v1.21`][direct-scrcpy-server]
_(SHA-256: dbcccab523ee26796e55ea33652649e4b7af498edae9aa75e4d4d7869c0ab848)_
[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:
```bash
meson x --buildtype release --strip -Db_lto=true \
-Dprebuilt_server=/path/to/scrcpy-server
ninja -Cx # DO NOT RUN AS ROOT
```
The server only works with a matching client version (this server works with the
`master` branch).
### Run without installing:
```bash ```bash
./run x [options] ./run x [options]
@ -242,32 +303,16 @@ After a successful build, you can install _scrcpy_ on the system:
sudo ninja -Cx install # without sudo on Windows sudo ninja -Cx install # without sudo on Windows
``` ```
This installs two files: This installs three files:
- `/usr/local/bin/scrcpy` - `/usr/local/bin/scrcpy`
- `/usr/local/share/scrcpy/scrcpy-server` - `/usr/local/share/scrcpy/scrcpy-server`
- `/usr/local/share/man/man1/scrcpy.1`
Just remove them to "uninstall" the application.
You can then [run](README.md#run) _scrcpy_. You can then [run](README.md#run) _scrcpy_.
### Uninstall
## Prebuilt server
- [`scrcpy-server-v1.17`][direct-scrcpy-server]
_(SHA-256: 11b5ad2d1bc9b9730fb7254a78efd71a8ff46b1938ff468e47a21b653a1b6725_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.17/scrcpy-server-v1.17
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:
```bash ```bash
meson x --buildtype release --strip -Db_lto=true \ sudo ninja -Cx uninstall # without sudo on Windows
-Dprebuilt_server=/path/to/scrcpy-server
ninja -Cx
sudo ninja -Cx install
``` ```
The server only works with a matching client version (this server works with the
`master` branch).

View File

@ -76,7 +76,7 @@ The server uses 3 threads:
- the **main** thread, encoding and streaming the video to the client; - the **main** thread, encoding and streaming the video to the client;
- the **controller** thread, listening for _control messages_ (typically, - the **controller** thread, listening for _control messages_ (typically,
keyboard and mouse events) from the client; keyboard and mouse events) from the client;
- the **receiver** thread (managed by the controller), sending _device messges_ - the **receiver** thread (managed by the controller), sending _device messages_
to the clients (currently, it is only used to send the device clipboard to the clients (currently, it is only used to send the device clipboard
content). content).
@ -211,7 +211,7 @@ There are two [frames][video_buffer] simultaneously in memory:
- the **rendering** frame, rendered in a texture from the main thread. - the **rendering** frame, rendered in a texture from the main thread.
When a new decoded frame is available, the decoder _swaps_ the decoding and When a new decoded frame is available, the decoder _swaps_ the decoding and
rendering frame (with proper synchronization). Thus, it immediatly starts rendering frame (with proper synchronization). Thus, it immediately starts
to decode a new frame while the main thread renders the last one. to decode a new frame while the main thread renders the last one.
If a [recorder] is present (i.e. `--record` is enabled), then it muxes the raw If a [recorder] is present (i.e. `--record` is enabled), then it muxes the raw

235
FAQ.it.md Normal file
View File

@ -0,0 +1,235 @@
_Apri le [FAQ](FAQ.md) originali e sempre aggiornate._
# Domande Frequenti (FAQ)
Questi sono i problemi più comuni riportati e i loro stati.
## Problemi di `adb`
`scrcpy` esegue comandi `adb` per inizializzare la connessione con il dispositivo. Se `adb` fallisce, scrcpy non funzionerà.
In questo caso sarà stampato questo errore:
> ERROR: "adb push" returned with value 1
Questo solitamente non è un bug di _scrcpy_, ma un problema del tuo ambiente.
Per trovare la causa, esegui:
```bash
adb devices
```
### `adb` not found (`adb` non trovato)
È necessario che `adb` sia accessibile dal tuo `PATH`.
In Windows, la cartella corrente è nel tuo `PATH` e `adb.exe` è incluso nella release, perciò dovrebbe già essere pronto all'uso.
### Device unauthorized (Dispositivo non autorizzato)
Controlla [stackoverflow][device-unauthorized] (in inglese).
[device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized
### Device not detected (Dispositivo non rilevato)
> adb: error: failed to get feature set: no devices/emulators found
Controlla di aver abilitato correttamente il [debug con adb][enable-adb] (link in inglese).
Se il tuo dispositivo non è rilevato, potresti avere bisogno dei [driver][drivers] (link in inglese) (in Windows).
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
[drivers]: https://developer.android.com/studio/run/oem-usb.html
### Più dispositivi connessi
Se più dispositivi sono connessi, riscontrerai questo errore:
> adb: error: failed to get feature set: more than one device/emulator
l'identificatore del tuo dispositivo deve essere fornito:
```bash
scrcpy -s 01234567890abcdef
```
Notare che se il tuo dispositivo è connesso mediante TCP/IP, riscontrerai questo messaggio:
> adb: error: more than one device/emulator
> ERROR: "adb reverse" returned with value 1
> WARN: 'adb reverse' failed, fallback to 'adb forward'
Questo è un problema atteso (a causa di un bug di una vecchia versione di Android, vedi [#5] (link in inglese)), ma in quel caso scrcpy ripiega su un metodo differente, il quale dovrebbe funzionare.
[#5]: https://github.com/Genymobile/scrcpy/issues/5
### Conflitti tra versioni di adb
> adb server version (41) doesn't match this client (39); killing...
L'errore compare quando usi più versioni di `adb` simultaneamente. Devi trovare il programma che sta utilizzando una versione differente di `adb` e utilizzare la stessa versione dappertutto.
Puoi sovrascrivere i binari di `adb` nell'altro programma, oppure chiedere a _scrcpy_ di usare un binario specifico di `adb`, impostando la variabile d'ambiente `ADB`:
```bash
set ADB=/path/to/your/adb
scrcpy
```
### Device disconnected (Dispositivo disconnesso)
Se _scrcpy_ si interrompe con l'avviso "Device disconnected", allora la connessione `adb` è stata chiusa.
Prova con un altro cavo USB o inseriscilo in un'altra porta USB. Vedi [#281] (in inglese) e [#283] (in inglese).
[#281]: https://github.com/Genymobile/scrcpy/issues/281
[#283]: https://github.com/Genymobile/scrcpy/issues/283
## Problemi di controllo
### Mouse e tastiera non funzionano
Su alcuni dispositivi potresti dover abilitare un opzione che permette l'[input simulato][simulating input] (link in inglese). Nelle opzioni sviluppatore, abilita:
> **Debug USB (Impostazioni di sicurezza)**
> _Permetti la concessione dei permessi e la simulazione degli input mediante il debug USB_
<!--- Ho tradotto personalmente il testo sopra, non conosco esattamente il testo reale --->
[simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
### I caratteri speciali non funzionano
Iniettare del testo in input è [limitato ai caratteri ASCII][text-input] (link in inglese). Un trucco permette di iniettare dei [caratteri accentati][accented-characters] (link in inglese), ma questo è tutto. Vedi [#37] (link in inglese).
[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
[#37]: https://github.com/Genymobile/scrcpy/issues/37
## Problemi del client
### La qualità è bassa
Se la definizione della finestra del tuo client è minore di quella del tuo dispositivo, allora potresti avere una bassa qualità di visualizzazione, specialmente individuabile nei testi (vedi [#40] (link in inglese)).
[#40]: https://github.com/Genymobile/scrcpy/issues/40
Per migliorare la qualità di ridimensionamento (downscaling), il filtro trilineare è applicato automaticamente se il renderizzatore è OpenGL e se supporta la creazione di mipmap.
In Windows, potresti voler forzare OpenGL:
```
scrcpy --render-driver=opengl
```
Potresti anche dover configurare il [comportamento di ridimensionamento][scaling behavior] (link in inglese):
> `scrcpy.exe` > Propietà > Compatibilità > Modifica impostazioni DPI elevati > Esegui l'override del comportamento di ridimensionamento DPI elevati > Ridimensionamento eseguito per: _Applicazione_.
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
### Problema con Wayland
Per impostazione predefinita, SDL utilizza x11 su Linux. Il [video driver] può essere cambiato attraversio la variabile d'ambiente `SDL_VIDEODRIVER`:
[video driver]: https://wiki.libsdl.org/FAQUsingSDL#how_do_i_choose_a_specific_video_driver
```bash
export SDL_VIDEODRIVER=wayland
scrcpy
```
Su alcune distribuzioni (almeno Fedora), il pacchetto `libdecor` deve essere installato manualmente.
Vedi le issues [#2554] e [#2559].
[#2554]: https://github.com/Genymobile/scrcpy/issues/2554
[#2559]: https://github.com/Genymobile/scrcpy/issues/2559
### Crash del compositore KWin
In Plasma Desktop, il compositore è disabilitato mentre _scrcpy_ è in esecuzione.
Come soluzione alternativa, [disattiva la "composizione dei blocchi"][kwin] (link in inglese).
<!--- Non sono sicuro di aver tradotto correttamente la stringa di testo del pulsante --->
[kwin]: https://github.com/Genymobile/scrcpy/issues/114#issuecomment-378778613
## Crash
### Eccezione
Ci potrebbero essere molte ragioni. Una causa comune è che il codificatore hardware del tuo dispositivo non riesce a codificare alla definizione selezionata:
> ```
> ERROR: Exception on thread Thread[main,5,main]
> android.media.MediaCodec$CodecException: Error 0xfffffc0e
> ...
> Exit due to uncaughtException in main thread:
> ERROR: Could not open video stream
> INFO: Initial texture: 1080x2336
> ```
o
> ```
> ERROR: Exception on thread Thread[main,5,main]
> java.lang.IllegalStateException
> at android.media.MediaCodec.native_dequeueOutputBuffer(Native Method)
> ```
Prova con una definizione inferiore:
```
scrcpy -m 1920
scrcpy -m 1024
scrcpy -m 800
```
Potresti anche provare un altro [codificatore](README.it.md#codificatore).
## Linea di comando in Windows
Alcuni utenti Windows non sono familiari con la riga di comando. Qui è descritto come aprire un terminale ed eseguire `scrcpy` con gli argomenti:
1. Premi <kbd>Windows</kbd>+<kbd>r</kbd>, questo apre una finestra di dialogo.
2. Scrivi `cmd` e premi <kbd>Enter</kbd>, questo apre un terminale.
3. Vai nella tua cartella di _scrcpy_ scrivendo (adatta il percorso):
```bat
cd C:\Users\user\Downloads\scrcpy-win64-xxx
```
e premi <kbd>Enter</kbd>
4. Scrivi il tuo comando. Per esempio:
```bat
scrcpy --record file.mkv
```
Se pianifichi di utilizzare sempre gli stessi argomenti, crea un file `myscrcpy.bat` (abilita mostra [estensioni nomi file][show file extensions] per evitare di far confusione) contenente il tuo comando nella cartella di `scrcpy`. Per esempio:
```bat
scrcpy --prefer-text --turn-screen-off --stay-awake
```
Poi fai doppio click su quel file.
Potresti anche modificare (una copia di) `scrcpy-console.bat` o `scrcpy-noconsole.vbs` per aggiungere alcuni argomenti.
[show file extensions]: https://www.techpedia.it/14-windows/windows-10/171-visualizzare-le-estensioni-nomi-file-con-windows-10

75
FAQ.md
View File

@ -1,5 +1,7 @@
# Frequently Asked Questions # Frequently Asked Questions
[Read in another language](#translations)
Here are the common reported problems and their status. Here are the common reported problems and their status.
@ -39,8 +41,11 @@ Check [stackoverflow][device-unauthorized].
> adb: error: failed to get feature set: no devices/emulators found > adb: error: failed to get feature set: no devices/emulators found
Check that you correctly enabled [adb debugging][enable-adb].
If your device is not detected, you may need some [drivers] (on Windows). If your device is not detected, you may need some [drivers] (on Windows).
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
[drivers]: https://developer.android.com/studio/run/oem-usb.html [drivers]: https://developer.android.com/studio/run/oem-usb.html
@ -111,25 +116,19 @@ In developer options, enable:
[simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 [simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
### Mouse clicks at wrong location
On MacOS, with HiDPI support and multiple screens, input location are wrongly
scaled. See [#15].
[#15]: https://github.com/Genymobile/scrcpy/issues/15
Open _scrcpy_ directly on the monitor you use it.
### Special characters do not work ### Special characters do not work
Injecting text input is [limited to ASCII characters][text-input]. A trick The default text injection method is [limited to ASCII characters][text-input].
allows to also inject some [accented characters][accented-characters], but A trick allows to also inject some [accented characters][accented-characters],
that's all. See [#37]. but that's all. See [#37].
Since scrcpy v1.20 on Linux, it is possible to simulate a [physical
keyboard][hid] (HID).
[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode [text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters [accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
[#37]: https://github.com/Genymobile/scrcpy/issues/37 [#37]: https://github.com/Genymobile/scrcpy/issues/37
[hid]: README.md#physical-keyboard-simulation-hid
## Client issues ## Client issues
@ -158,6 +157,26 @@ You may also need to configure the [scaling behavior]:
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723 [scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
### Issue with Wayland
By default, SDL uses x11 on Linux. The [video driver] can be changed via the
`SDL_VIDEODRIVER` environment variable:
[video driver]: https://wiki.libsdl.org/FAQUsingSDL#how_do_i_choose_a_specific_video_driver
```bash
export SDL_VIDEODRIVER=wayland
scrcpy
```
On some distributions (at least Fedora), the package `libdecor` must be
installed manually.
See issues [#2554] and [#2559].
[#2554]: https://github.com/Genymobile/scrcpy/issues/2554
[#2559]: https://github.com/Genymobile/scrcpy/issues/2559
### KWin compositor crashes ### KWin compositor crashes
@ -203,6 +222,27 @@ scrcpy -m 800
You could also try another [encoder](README.md#encoder). You could also try another [encoder](README.md#encoder).
If you encounter this exception on Android 12, then just upgrade to scrcpy >=
1.18 (see [#2129]):
```
> ERROR: Exception on thread Thread[main,5,main]
java.lang.AssertionError: java.lang.reflect.InvocationTargetException
at com.genymobile.scrcpy.wrappers.SurfaceControl.setDisplaySurface(SurfaceControl.java:75)
...
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invoke(Native Method)
at com.genymobile.scrcpy.wrappers.SurfaceControl.setDisplaySurface(SurfaceControl.java:73)
... 7 more
Caused by: java.lang.IllegalArgumentException: displayToken must not be null
at android.view.SurfaceControl$Transaction.setDisplaySurface(SurfaceControl.java:3067)
at android.view.SurfaceControl.setDisplaySurface(SurfaceControl.java:2147)
... 9 more
```
[#2129]: https://github.com/Genymobile/scrcpy/issues/2129
## Command line on Windows ## Command line on Windows
Some Windows users are not familiar with the command line. Here is how to open a Some Windows users are not familiar with the command line. Here is how to open a
@ -237,3 +277,12 @@ You could also edit (a copy of) `scrcpy-console.bat` or `scrcpy-noconsole.vbs`
to add some arguments. to add some arguments.
[show file extensions]: https://www.howtogeek.com/205086/beginner-how-to-make-windows-show-file-extensions/ [show file extensions]: https://www.howtogeek.com/205086/beginner-how-to-make-windows-show-file-extensions/
## Translations
This FAQ is available in other languages:
- [Italiano (Italiano, `it`) - v1.19](FAQ.it.md)
- [한국어 (Korean, `ko`) - v1.11](FAQ.ko.md)
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.18](FAQ.zh-Hans.md)

240
FAQ.zh-Hans.md Normal file
View File

@ -0,0 +1,240 @@
只有原版的[FAQ](FAQ.md)会保持更新。
本文根据[d6aaa5]翻译。
[d6aaa5]:https://github.com/Genymobile/scrcpy/blob/d6aaa5bf9aa3710660c683b6e3e0ed971ee44af5/FAQ.md
# 常见问题
这里是一些常见的问题以及他们的状态。
## `adb` 相关问题
`scrcpy` 执行 `adb` 命令来初始化和设备之间的连接。如果`adb` 执行失败了, scrcpy 就无法工作。
在这种情况中,将会输出这个错误:
> ERROR: "adb push" returned with value 1
这通常不是 _scrcpy_ 的bug而是你的环境的问题。
要找出原因,请执行以下操作:
```bash
adb devices
```
### 找不到`adb`
你的`PATH`中需要能访问到`adb`
在Windows上当前目录会包含在`PATH`中,并且`adb.exe`也包含在发行版中,因此它应该是开箱即用(直接解压就可以)的。
### 设备未授权
参见这里 [stackoverflow][device-unauthorized].
[device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized
### 未检测到设备
> adb: error: failed to get feature set: no devices/emulators found
确认已经正确启用 [adb debugging][enable-adb].
如果你的设备没有被检测到,你可能需要一些[驱动][drivers] (在 Windows上).
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
[drivers]: https://developer.android.com/studio/run/oem-usb.html
### 已连接多个设备
如果连接了多个设备,您将遇到以下错误:
> adb: error: failed to get feature set: more than one device/emulator
必须提供要镜像的设备的标识符:
```bash
scrcpy -s 01234567890abcdef
```
注意,如果你的设备是通过 TCP/IP 连接的, 你将会收到以下消息:
> adb: error: more than one device/emulator
> ERROR: "adb reverse" returned with value 1
> WARN: 'adb reverse' failed, fallback to 'adb forward'
这是意料之中的 (由于旧版安卓的一个bug, 请参见 [#5])但是在这种情况下scrcpy会退回到另一种方法这种方法应该可以起作用。
[#5]: https://github.com/Genymobile/scrcpy/issues/5
### adb版本之间冲突
> adb server version (41) doesn't match this client (39); killing...
同时使用多个版本的`adb`时会发生此错误。你必须查找使用不同`adb`版本的程序,并在所有地方使用相同版本的`adb`
你可以覆盖另一个程序中的`adb`二进制文件,或者通过设置`ADB`环境变量来让 _scrcpy_ 使用特定的`adb`二进制文件。
```bash
set ADB=/path/to/your/adb
scrcpy
```
### 设备断开连接
如果 _scrcpy_ 在警告“设备连接断开”的情况下自动中止,那就意味着`adb`连接已经断开了。
请尝试使用另一条USB线或者电脑上的另一个USB接口。
请参看 [#281] 和 [#283]。
[#281]: https://github.com/Genymobile/scrcpy/issues/281
[#283]: https://github.com/Genymobile/scrcpy/issues/283
## 控制相关问题
### 鼠标和键盘不起作用
在某些设备上,您可能需要启用一个选项以允许 [模拟输入][simulating input]。
在开发者选项中,打开:
> **USB调试 (安全设置)**
> _允许通过USB调试修改权限或模拟点击_
[simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
### 特殊字符不起作用
可输入的文本[被限制为ASCII字符][text-input]。也可以用一些小技巧输入一些[带重音符号的字符][accented-characters],但是仅此而已。参见[#37]。
[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
[#37]: https://github.com/Genymobile/scrcpy/issues/37
## 客户端相关问题
### 效果很差
如果你的客户端窗口分辨率比你的设备屏幕小,则可能出现效果差的问题,尤其是在文本上(参见 [#40])。
[#40]: https://github.com/Genymobile/scrcpy/issues/40
为了提升降尺度的质量如果渲染器是OpenGL并且支持mip映射就会自动开启三线性过滤。
在Windows上你可能希望强制使用OpenGL
```
scrcpy --render-driver=opengl
```
你可能还需要配置[缩放行为][scaling behavior]
> `scrcpy.exe` > Properties > Compatibility > Change high DPI settings >
> Override high DPI scaling behavior > Scaling performed by: _Application_.
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
### Wayland相关的问题
在Linux上SDL默认使用x11。可以通过`SDL_VIDEODRIVER`环境变量来更改[视频驱动][video driver]
[video driver]: https://wiki.libsdl.org/FAQUsingSDL#how_do_i_choose_a_specific_video_driver
```bash
export SDL_VIDEODRIVER=wayland
scrcpy
```
在一些发行版上 (至少包括 Fedora) `libdecor` 包必须手动安装。
参见 [#2554] 和 [#2559]。
[#2554]: https://github.com/Genymobile/scrcpy/issues/2554
[#2559]: https://github.com/Genymobile/scrcpy/issues/2559
### KWin compositor 崩溃
在Plasma桌面中_scrcpy_ 运行时会禁用compositor。
一种解决方法是, [禁用 "Block compositing"][kwin].
[kwin]: https://github.com/Genymobile/scrcpy/issues/114#issuecomment-378778613
## 崩溃
### 异常
可能有很多原因。一个常见的原因是您的设备无法按给定清晰度进行编码:
> ```
> ERROR: Exception on thread Thread[main,5,main]
> android.media.MediaCodec$CodecException: Error 0xfffffc0e
> ...
> Exit due to uncaughtException in main thread:
> ERROR: Could not open video stream
> INFO: Initial texture: 1080x2336
> ```
或者
> ```
> ERROR: Exception on thread Thread[main,5,main]
> java.lang.IllegalStateException
> at android.media.MediaCodec.native_dequeueOutputBuffer(Native Method)
> ```
请尝试使用更低的清晰度:
```
scrcpy -m 1920
scrcpy -m 1024
scrcpy -m 800
```
你也可以尝试另一种 [编码器](README.md#encoder)。
## Windows命令行
一些Windows用户不熟悉命令行。以下是如何打开终端并带参数执行`scrcpy`
1. 按下 <kbd>Windows</kbd>+<kbd>r</kbd>,打开一个对话框。
2. 输入 `cmd` 并按 <kbd>Enter</kbd>,这样就打开了一个终端。
3. 通过输入以下命令,切换到你的 _scrcpy_ 所在的目录 (根据你的实际位置修改路径):
```bat
cd C:\Users\user\Downloads\scrcpy-win64-xxx
```
然后按 <kbd>Enter</kbd>
4. 输入你的命令。比如:
```bat
scrcpy --record file.mkv
```
如果你打算总是使用相同的参数,在`scrcpy`目录创建一个文件 `myscrcpy.bat`
(启用 [显示文件拓展名][show file extensions] 避免混淆),文件中包含你的命令。例如:
```bat
scrcpy --prefer-text --turn-screen-off --stay-awake
```
然后双击刚刚创建的文件。
你也可以编辑 `scrcpy-console.bat` 或者 `scrcpy-noconsole.vbs`(的副本)来添加参数。
[show file extensions]: https://www.howtogeek.com/205086/beginner-how-to-make-windows-show-file-extensions/

View File

@ -69,10 +69,7 @@ Anda juga bisa [membangun aplikasi secara manual][BUILD] (jangan khawatir, tidak
Untuk Windows, untuk kesederhanaan, arsip prebuilt dengan semua dependensi (termasuk `adb`) tersedia : Untuk Windows, untuk kesederhanaan, arsip prebuilt dengan semua dependensi (termasuk `adb`) tersedia :
- [`scrcpy-win64-v1.16.zip`][direct-win64] - [README](README.md#windows)
_(SHA-256: 3f30dc5db1a2f95c2b40a0f5de91ec1642d9f53799250a8c529bc882bc0918f0)_
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.16/scrcpy-win64-v1.16.zip
Ini juga tersedia di [Chocolatey]: Ini juga tersedia di [Chocolatey]:

813
README.it.md Normal file
View File

@ -0,0 +1,813 @@
_Apri il [README](README.md) originale e sempre aggiornato._
# scrcpy (v1.19)
Questa applicazione fornisce la visualizzazione e il controllo dei dispositivi Android collegati via USB (o [via TCP/IP][article-tcpip]). Non richiede alcun accesso _root_.
Funziona su _GNU/Linux_, _Windows_ e _macOS_.
![screenshot](assets/screenshot-debian-600.jpg)
Si concentra su:
- **leggerezza** (nativo, mostra solo lo schermo del dispositivo)
- **prestazioni** (30~60fps)
- **qualità** (1920×1080 o superiore)
- **bassa latenza** ([35~70ms][lowlatency])
- **tempo di avvio basso** (~ 1secondo per visualizzare la prima immagine)
- **non invadenza** (nulla viene lasciato installato sul dispositivo)
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
## Requisiti
Il dispositivo Android richiede almeno le API 21 (Android 5.0).
Assiucurati di aver [attivato il debug usb][enable-adb] sul(/i) tuo(i) dispositivo(/i).
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
In alcuni dispositivi, devi anche abilitare [un'opzione aggiuntiva][control] per controllarli con tastiera e mouse.
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
## Ottieni l'app
<a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a>
### Sommario
- Linux: `apt install scrcpy`
- Windows: [download](README.md#windows)
- macOS: `brew install scrcpy`
Compila dai sorgenti: [BUILD] (in inglese) ([procedimento semplificato][BUILD_simple] (in inglese))
[BUILD]: BUILD.md
[BUILD_simple]: BUILD.md#simple
### Linux
Su Debian (_testing_ e _sid_ per ora) e Ubuntu (20.04):
```
apt install scrcpy
```
È disponibile anche un pacchetto [Snap]: [`scrcpy`][snap-link].
[snap-link]: https://snapstats.org/snaps/scrcpy
[snap]: https://it.wikipedia.org/wiki/Snappy_(gestore_pacchetti)
Per Fedora, è disponibile un pacchetto [COPR]: [`scrcpy`][copr-link].
[COPR]: https://fedoraproject.org/wiki/Category:Copr
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
Per Arch Linux, è disponibile un pacchetto [AUR]: [`scrcpy`][aur-link].
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
Per Gentoo, è disponibile una [Ebuild]: [`scrcpy/`][ebuild-link].
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
Puoi anche [compilare l'app manualmente][BUILD] (in inglese) ([procedimento semplificato][BUILD_simple] (in inglese)).
### Windows
Per Windows, per semplicità è disponibile un archivio precompilato con tutte le dipendenze (incluso `adb`):
- [README](README.md#windows) (Link al README originale per l'ultima versione)
È anche disponibile in [Chocolatey]:
[Chocolatey]: https://chocolatey.org/
```bash
choco install scrcpy
choco install adb # se non lo hai già
```
E in [Scoop]:
```bash
scoop install scrcpy
scoop install adb # se non lo hai già
```
[Scoop]: https://scoop.sh
Puoi anche [compilare l'app manualmente][BUILD] (in inglese).
### macOS
L'applicazione è disponibile in [Homebrew]. Basta installarlo:
[Homebrew]: https://brew.sh/
```bash
brew install scrcpy
```
Serve che `adb` sia accessibile dal tuo `PATH`. Se non lo hai già:
```bash
brew install android-platform-tools
```
È anche disponibile in [MacPorts], che imposta adb per te:
```bash
sudo port install scrcpy
```
[MacPorts]: https://www.macports.org/
Puoi anche [compilare l'app manualmente][BUILD] (in inglese).
## Esecuzione
Collega un dispositivo Android ed esegui:
```bash
scrcpy
```
Scrcpy accetta argomenti da riga di comando, essi sono listati con:
```bash
scrcpy --help
```
## Funzionalità
### Configurazione di acquisizione
#### Riduci dimensione
Qualche volta è utile trasmettere un dispositvo Android ad una definizione inferiore per aumentare le prestazioni.
Per limitare sia larghezza che altezza ad un certo valore (ad es. 1024):
```bash
scrcpy --max-size 1024
scrcpy -m 1024 # versione breve
```
L'altra dimensione è calcolata in modo tale che il rapporto di forma del dispositivo sia preservato.
In questo esempio un dispositivo in 1920x1080 viene trasmesso a 1024x576.
#### Cambia bit-rate (velocità di trasmissione)
Il bit-rate predefinito è 8 Mbps. Per cambiare il bitrate video (ad es. a 2 Mbps):
```bash
scrcpy --bit-rate 2M
scrcpy -b 2M # versione breve
```
#### Limitare il frame rate (frequenza di fotogrammi)
Il frame rate di acquisizione può essere limitato:
```bash
scrcpy --max-fps 15
```
Questo è supportato ufficialmente a partire da Android 10, ma potrebbe funzionare in versioni precedenti.
#### Ritaglio
Lo schermo del dispositivo può essere ritagliato per visualizzare solo parte di esso.
Questo può essere utile, per esempio, per trasmettere solo un occhio dell'Oculus Go:
```bash
scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0)
```
Se anche `--max-size` è specificata, il ridimensionamento è applicato dopo il ritaglio.
#### Blocca orientamento del video
Per bloccare l'orientamento della trasmissione:
```bash
scrcpy --lock-video-orientation # orientamento iniziale (corrente)
scrcpy --lock-video-orientation=0 # orientamento naturale
scrcpy --lock-video-orientation=1 # 90° antiorario
scrcpy --lock-video-orientation=2 # 180°
scrcpy --lock-video-orientation=3 # 90° orario
```
Questo influisce sull'orientamento della registrazione.
La [finestra può anche essere ruotata](#rotazione) indipendentemente.
#### Codificatore
Alcuni dispositivi hanno più di un codificatore e alcuni di questi possono provocare problemi o crash. È possibile selezionare un encoder diverso:
```bash
scrcpy --encoder OMX.qcom.video.encoder.avc
```
Per elencare i codificatori disponibili puoi immettere un nome di codificatore non valido e l'errore mostrerà i codificatori disponibili:
```bash
scrcpy --encoder _
```
### Cattura
#### Registrazione
È possibile registrare lo schermo durante la trasmissione:
```bash
scrcpy --record file.mp4
scrcpy -r file.mkv
```
Per disabilitare la trasmissione durante la registrazione:
```bash
scrcpy --no-display --record file.mp4
scrcpy -Nr file.mkv
# interrompere la registrazione con Ctrl+C
```
I "fotogrammi saltati" sono registrati nonostante non siano mostrati in tempo reale (per motivi di prestazioni). I fotogrammi sono _datati_ sul dispositivo, così una [variazione di latenza dei pacchetti][packet delay variation] non impatta il file registrato.
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
#### v4l2loopback
Su Linux è possibile inviare il flusso video ad un dispositivo v4l2 loopback, cosicchè un dispositivo Android possa essere aperto come una webcam da qualsiasi strumento compatibile con v4l2.
Il modulo `v4l2loopback` deve essere installato:
```bash
sudo apt install v4l2loopback-dkms
```
Per creare un dispositvo v4l2:
```bash
sudo modprobe v4l2loopback
```
Questo creerà un nuovo dispositivo video in `/dev/videoN` dove `N` è un intero (più [opzioni](https://github.com/umlaeute/v4l2loopback#options) sono disponibili per crere più dispositivi o dispositivi con ID specifici).
Per elencare i dispositvi attivati:
```bash
# necessita del pacchetto v4l-utils
v4l2-ctl --list-devices
# semplice ma potrebbe essere sufficiente
ls /dev/video*
```
Per avviare scrcpy utilizzando un v4l2 sink:
```bash
scrcpy --v4l2-sink=/dev/videoN
scrcpy --v4l2-sink=/dev/videoN --no-display # disabilita la finestra di trasmissione
scrcpy --v4l2-sink=/dev/videoN -N # versione corta
```
(sostituisci `N` con l'ID del dispositivo, controlla con `ls /dev/video*`)
Una volta abilitato, puoi aprire il tuo flusso video con uno strumento compatibile con v4l2:
```bash
ffplay -i /dev/videoN
vlc v4l2:///dev/videoN # VLC potrebbe aggiungere del ritardo per il buffer
```
Per esempio potresti catturare il video in [OBS].
[OBS]: https://obsproject.com/
#### Buffering
È possibile aggiungere del buffer. Questo aumenta la latenza ma riduce il jitter (vedi [#2464]).
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464
L'opzione è disponibile per il buffer della visualizzazione:
```bash
scrcpy --display-buffer=50 # aggiungi 50 ms di buffer per la visualizzazione
```
e per il V4L2 sink:
```bash
scrcpy --v4l2-buffer=500 # aggiungi 50 ms di buffer per il v4l2 sink
```
### Connessione
#### Wireless
_Scrcpy_ usa `adb` per comunicare col dispositivo e `adb` può [connettersi][connect] al dispositivo mediante TCP/IP:
1. Connetti il dispositivo alla stessa rete Wi-Fi del tuo computer.
2. Trova l'indirizzo IP del tuo dispositivo in Impostazioni → Informazioni sul telefono → Stato, oppure eseguendo questo comando:
```bash
adb shell ip route | awk '{print $9}'
```
3. Abilita adb via TCP/IP sul tuo dispositivo: `adb tcpip 5555`.
4. Scollega il tuo dispositivo.
5. Connetti il tuo dispositivo: `adb connect IP_DISPOSITVO:5555` _(rimpiazza `IP_DISPOSITIVO`)_.
6. Esegui `scrcpy` come al solito.
Potrebbe essere utile diminuire il bit-rate e la definizione
```bash
scrcpy --bit-rate 2M --max-size 800
scrcpy -b2M -m800 # versione breve
```
[connect]: https://developer.android.com/studio/command-line/adb.html#wireless
#### Multi dispositivo
Se in `adb devices` sono listati più dispositivi, è necessario specificare il _seriale_:
```bash
scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # versione breve
```
Se il dispositivo è collegato mediante TCP/IP:
```bash
scrcpy --serial 192.168.0.1:5555
scrcpy -s 192.168.0.1:5555 # versione breve
```
Puoi avviare più istanze di _scrcpy_ per diversi dispositivi.
#### Avvio automativo alla connessione del dispositivo
Potresti usare [AutoAdb]:
```bash
autoadb scrcpy -s '{}'
```
[AutoAdb]: https://github.com/rom1v/autoadb
#### Tunnel SSH
Per connettersi a un dispositivo remoto è possibile collegare un client `adb` locale ad un server `adb` remoto (assunto che entrambi stiano usando la stessa versione del protocollo _adb_):
```bash
adb kill-server # termina il server adb locale su 5037
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
# tieni questo aperto
```
Da un altro terminale:
```bash
scrcpy
```
Per evitare l'abilitazione dell'apertura porte remota potresti invece forzare una "forward connection" (notare il `-L` invece di `-R`)
```bash
adb kill-server # termina il server adb locale su 5037
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
# tieni questo aperto
```
Da un altro terminale:
```bash
scrcpy --force-adb-forward
```
Come per le connessioni wireless potrebbe essere utile ridurre la qualità:
```
scrcpy -b2M -m800 --max-fps 15
```
### Configurazione della finestra
#### Titolo
Il titolo della finestra è il modello del dispositivo per impostazione predefinita. Esso può essere cambiato:
```bash
scrcpy --window-title 'My device'
```
#### Posizione e dimensione
La posizione e la dimensione iniziale della finestra può essere specificata:
```bash
scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
```
#### Senza bordi
Per disabilitare le decorazioni della finestra:
```bash
scrcpy --window-borderless
```
#### Sempre in primo piano
Per tenere scrcpy sempre in primo piano:
```bash
scrcpy --always-on-top
```
#### Schermo intero
L'app può essere avviata direttamente a schermo intero:
```bash
scrcpy --fullscreen
scrcpy -f # versione breve
```
Lo schermo intero può anche essere attivato/disattivato con <kbd>MOD</kbd>+<kbd>f</kbd>.
#### Rotazione
La finestra può essere ruotata:
```bash
scrcpy --rotation 1
```
I valori possibili sono:
- `0`: nessuna rotazione
- `1`: 90 gradi antiorari
- `2`: 180 gradi
- `3`: 90 gradi orari
La rotazione può anche essere cambiata dinamicamente con <kbd>MOD</kbd>+<kbd>←</kbd>
_(sinistra)_ e <kbd>MOD</kbd>+<kbd>→</kbd> _(destra)_.
Notare che _scrcpy_ gestisce 3 diversi tipi di rotazione:
- <kbd>MOD</kbd>+<kbd>r</kbd> richiede al dispositvo di cambiare tra orientamento verticale (portrait) e orizzontale (landscape) (l'app in uso potrebbe rifiutarsi se non supporta l'orientamento richiesto).
- [`--lock-video-orientation`](#blocca-orientamento-del-video) cambia l'orientamento della trasmissione (l'orientamento del video inviato dal dispositivo al computer). Questo influenza la registrazione.
- `--rotation` (o <kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>) ruota solo il contenuto della finestra. Questo influenza solo la visualizzazione, non la registrazione.
### Altre opzioni di trasmissione
#### "Sola lettura"
Per disabilitare i controlli (tutto ciò che può interagire col dispositivo: tasti di input, eventi del mouse, trascina e rilascia (drag&drop) file):
```bash
scrcpy --no-control
scrcpy -n
```
#### Schermo
Se sono disponibili più schermi, è possibile selezionare lo schermo da trasmettere:
```bash
scrcpy --display 1
```
La lista degli id schermo può essere ricavata da:
```bash
adb shell dumpsys display # cerca "mDisplayId=" nell'output
```
Lo schermo secondario potrebbe essere possibile controllarlo solo se il dispositivo esegue almeno Android 10 (in caso contrario è trasmesso in modalità sola lettura).
#### Mantenere sbloccato
Per evitare che il dispositivo si blocchi dopo un po' che il dispositivo è collegato:
```bash
scrcpy --stay-awake
scrcpy -w
```
Lo stato iniziale è ripristinato quando scrcpy viene chiuso.
#### Spegnere lo schermo
È possibile spegnere lo schermo del dispositivo durante la trasmissione con un'opzione da riga di comando:
```bash
scrcpy --turn-screen-off
scrcpy -S
```
Oppure premendo <kbd>MOD</kbd>+<kbd>o</kbd> in qualsiasi momento.
Per riaccenderlo premere <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>.
In Android il pulsante `POWER` (tasto di accensione) accende sempre lo schermo. Per comodità, se `POWER` è inviato via scrcpy (con click destro o con <kbd>MOD</kbd>+<kbd>p</kbd>), si forza il dispositivo a spegnere lo schermo dopo un piccolo ritardo (appena possibile).
Il pulsante fisico `POWER` continuerà ad accendere lo schermo normalmente.
Può anche essere utile evitare il blocco del dispositivo:
```bash
scrcpy --turn-screen-off --stay-awake
scrcpy -Sw
```
#### Mostrare i tocchi
Per le presentazioni può essere utile mostrare i tocchi fisici (sul dispositivo fisico).
Android fornisce questa funzionalità nelle _Opzioni sviluppatore_.
_Scrcpy_ fornisce un'opzione per abilitare questa funzionalità all'avvio e ripristinare il valore iniziale alla chiusura:
```bash
scrcpy --show-touches
scrcpy -t
```
Notare che mostra solo i tocchi _fisici_ (con le dita sul dispositivo).
#### Disabilitare il salvaschermo
In maniera predefinita scrcpy non previene l'attivazione del salvaschermo del computer.
Per disabilitarlo:
```bash
scrcpy --disable-screensaver
```
### Input di controlli
#### Rotazione dello schermo del dispostivo
Premere <kbd>MOD</kbd>+<kbd>r</kbd> per cambiare tra le modalità verticale (portrait) e orizzontale (landscape).
Notare che la rotazione avviene solo se l'applicazione in primo piano supporta l'orientamento richiesto.
#### Copia-incolla
Quando gli appunti di Android cambiano, essi vengono automaticamente sincronizzati con gli appunti del computer.
Qualsiasi scorciatoia <kbd>Ctrl</kbd> viene inoltrata al dispositivo. In particolare:
- <kbd>Ctrl</kbd>+<kbd>c</kbd> copia
- <kbd>Ctrl</kbd>+<kbd>x</kbd> taglia
- <kbd>Ctrl</kbd>+<kbd>v</kbd> incolla (dopo la sincronizzazione degli appunti da computer a dispositivo)
Questo solitamente funziona nella maniera più comune.
Il comportamento reale, però, dipende dall'applicazione attiva. Per esempio _Termux_ invia SIGINT con <kbd>Ctrl</kbd>+<kbd>c</kbd>, e _K-9 Mail_ compone un nuovo messaggio.
Per copiare, tagliare e incollare in questi casi (ma è solo supportato in Android >= 7):
- <kbd>MOD</kbd>+<kbd>c</kbd> inietta `COPY`
- <kbd>MOD</kbd>+<kbd>x</kbd> inietta `CUT`
- <kbd>MOD</kbd>+<kbd>v</kbd> inietta `PASTE` (dopo la sincronizzazione degli appunti da computer a dispositivo)
In aggiunta, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> permette l'iniezione del testo degli appunti del computer come una sequenza di eventi pressione dei tasti. Questo è utile quando il componente non accetta l'incollaggio di testo (per esempio in _Termux_), ma questo può rompere il contenuto non ASCII.
**AVVISO:** Incollare gli appunti del computer nel dispositivo (sia con <kbd>Ctrl</kbd>+<kbd>v</kbd> che con <kbd>MOD</kbd>+<kbd>v</kbd>) copia il contenuto negli appunti del dispositivo. Come conseguenza, qualsiasi applicazione Android potrebbe leggere il suo contenuto. Dovresti evitare di incollare contenuti sensibili (come password) in questa maniera.
Alcuni dispositivi non si comportano come aspettato quando si modificano gli appunti del dispositivo a livello di codice. L'opzione `--legacy-paste` è fornita per cambiare il comportamento di <kbd>Ctrl</kbd>+<kbd>v</kbd> and <kbd>MOD</kbd>+<kbd>v</kbd> in modo tale che anch'essi iniettino il testo gli appunti del computer come una sequenza di eventi pressione dei tasti (nella stessa maniera di <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
#### Pizzica per zoomare (pinch-to-zoom)
Per simulare il "pizzica per zoomare": <kbd>Ctrl</kbd>+_click e trascina_.
Più precisamente, tieni premuto <kbd>Ctrl</kbd> mentre premi il pulsante sinistro. Finchè il pulsante non sarà rilasciato, tutti i movimenti del mouse ridimensioneranno e ruoteranno il contenuto (se supportato dall'applicazione) relativamente al centro dello schermo.
Concretamente scrcpy genera degli eventi di tocco addizionali di un "dito virtuale" nella posizione simmetricamente opposta rispetto al centro dello schermo.
#### Preferenze di iniezione del testo
Ci sono due tipi di [eventi][textevents] generati quando si scrive testo:
- _eventi di pressione_, segnalano che tasto è stato premuto o rilasciato;
- _eventi di testo_, segnalano che del testo è stato inserito.
In maniera predefinita le lettere sono "iniettate" usando gli eventi di pressione, in maniera tale che la tastiera si comporti come aspettato nei giochi (come accade solitamente per i tasti WASD).
Questo, però, può [causare problemi][prefertext]. Se incontri un problema del genere, puoi evitarlo con:
```bash
scrcpy --prefer-text
```
(ma questo romperà il normale funzionamento della tastiera nei giochi)
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
#### Ripetizione di tasti
In maniera predefinita tenere premuto un tasto genera una ripetizione degli eventi di pressione di tale tasto. Questo può creare problemi di performance in alcuni giochi, dove questi eventi sono inutilizzati.
Per prevenire l'inoltro ripetuto degli eventi di pressione:
```bash
scrcpy --no-key-repeat
```
#### Click destro e click centrale
In maniera predefinita, click destro aziona BACK (indietro) e il click centrale aziona HOME. Per disabilitare queste scorciatoie e, invece, inviare i click al dispositivo:
```bash
scrcpy --forward-all-clicks
```
### Rilascio di file
#### Installare APK
Per installare un APK, trascina e rilascia un file APK (finisce con `.apk`) nella finestra di _scrcpy_.
Non c'è alcuna risposta visiva, un log è stampato nella console.
#### Trasferimento di file verso il dispositivo
Per trasferire un file in `/sdcard/Download` del dispositivo trascina e rilascia un file (non APK) nella finestra di _scrcpy_.
Non c'è alcuna risposta visiva, un log è stampato nella console.
La cartella di destinazione può essere cambiata all'avvio:
```bash
scrcpy --push-target=/sdcard/Movies/
```
### Inoltro dell'audio
L'audio non è inoltrato da _scrcpy_. Usa [sndcpy].
Vedi anche la [issue #14].
[sndcpy]: https://github.com/rom1v/sndcpy
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14
## Scociatoie
Nella lista seguente, <kbd>MOD</kbd> è il modificatore delle scorciatoie. In maniera predefinita è <kbd>Alt</kbd> (sinistro) o <kbd>Super</kbd> (sinistro).
Può essere cambiato usando `--shortcut-mod`. I tasti possibili sono `lctrl`, `rctrl`, `lalt`, `ralt`, `lsuper` and `rsuper` (`l` significa sinistro e `r` significa destro). Per esempio:
```bash
# usa ctrl destro per le scorciatoie
scrcpy --shortcut-mod=rctrl
# use sia "ctrl sinistro"+"alt sinistro" che "super sinistro" per le scorciatoie
scrcpy --shortcut-mod=lctrl+lalt,lsuper
```
_<kbd>[Super]</kbd> è il pulsante <kbd>Windows</kbd> o <kbd>Cmd</kbd>._
[Super]: https://it.wikipedia.org/wiki/Tasto_Windows
<!-- https://en.wikipedia.org/wiki/Super_key_(keyboard_button) è la pagina originale di Wikipedia inglese, l'ho sostituita con una simile in quello italiano -->
| Azione | Scorciatoia
| ------------------------------------------- |:-----------------------------
| Schermo intero | <kbd>MOD</kbd>+<kbd>f</kbd>
| Rotazione schermo a sinistra | <kbd>MOD</kbd>+<kbd>←</kbd> _(sinistra)_
| Rotazione schermo a destra | <kbd>MOD</kbd>+<kbd>→</kbd> _(destra)_
| Ridimensiona finestra a 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
| Ridimensiona la finestra per rimuovere i bordi neri | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Doppio click sinistro¹_
| Premi il tasto `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Click centrale_
| Premi il tasto `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Click destro²_
| Premi il tasto `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _4° click³_
| Premi il tasto `MENU` (sblocca lo schermo) | <kbd>MOD</kbd>+<kbd>m</kbd>
| Premi il tasto `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(su)_
| Premi il tasto `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(giù)_
| Premi il tasto `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd>
| Accendi | _Click destro²_
| Spegni lo schermo del dispositivo (continua a trasmettere) | <kbd>MOD</kbd>+<kbd>o</kbd>
| Accendi lo schermo del dispositivo | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
| Ruota lo schermo del dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd>
| Espandi il pannello delle notifiche | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5° click³_
| Espandi il pannello delle impostazioni | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Doppio 5° click³_
| Chiudi pannelli | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| Copia negli appunti⁴ | <kbd>MOD</kbd>+<kbd>c</kbd>
| Taglia negli appunti⁴ | <kbd>MOD</kbd>+<kbd>x</kbd>
| Sincronizza gli appunti e incolla⁴ | <kbd>MOD</kbd>+<kbd>v</kbd>
| Inietta il testo degli appunti del computer | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| Abilita/Disabilita il contatore FPS (su stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| Pizzica per zoomare | <kbd>Ctrl</kbd>+_click e trascina_
_¹Doppio click sui bordi neri per rimuoverli._
_²Il tasto destro accende lo schermo se era spento, preme BACK in caso contrario._
_³4° e 5° pulsante del mouse, se il tuo mouse ne dispone._
_⁴Solo in Android >= 7._
Le scorciatoie con pulsanti ripetuti sono eseguite rilasciando e premendo il pulsante una seconda volta. Per esempio, per eseguire "Espandi il pannello delle impostazioni":
1. Premi e tieni premuto <kbd>MOD</kbd>.
2. Poi premi due volte <kbd>n</kbd>.
3. Infine rilascia <kbd>MOD</kbd>.
Tutte le scorciatoie <kbd>Ctrl</kbd>+_tasto_ sono inoltrate al dispositivo, così sono gestite dall'applicazione attiva.
## Path personalizzati
Per utilizzare dei binari _adb_ specifici, configura il suo path nella variabile d'ambente `ADB`:
```bash
ADB=/percorso/per/adb scrcpy
```
Per sovrascrivere il percorso del file `scrcpy-server`, configura il percorso in `SCRCPY_SERVER_PATH`.
## Perchè _scrcpy_?
Un collega mi ha sfidato a trovare un nome tanto impronunciabile quanto [gnirehtet].
[`strcpy`] copia una **str**ing (stringa); `scrcpy` copia uno **scr**een (schermo).
[gnirehtet]: https://github.com/Genymobile/gnirehtet
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
## Come compilare?
Vedi [BUILD] (in inglese).
## Problemi comuni
Vedi le [FAQ](FAQ.it.md).
## Sviluppatori
Leggi la [pagina per sviluppatori].
[pagina per sviluppatori]: DEVELOP.md
## Licenza (in inglese)
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
## Articoli (in inglese)
- [Introducendo scrcpy][article-intro]
- [Scrcpy ora funziona wireless][article-tcpip]
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/

799
README.jp.md Normal file
View File

@ -0,0 +1,799 @@
_Only the original [README](README.md) is guaranteed to be up-to-date._
# scrcpy (v1.19)
このアプリケーションはUSB(もしくは[TCP/IP経由][article-tcpip])で接続されたAndroidデバイスの表示と制御を提供します。このアプリケーションは _root_ でのアクセスを必要としません。このアプリケーションは _GNU/Linux__Windows_ そして _macOS_ 上で動作します。
![screenshot](assets/screenshot-debian-600.jpg)
以下に焦点を当てています:
- **軽量** (ネイティブ、デバイス画面表示のみ)
- **パフォーマンス** (30~60fps)
- **クオリティ** (1920x1080以上)
- **低遅延** ([35~70ms][lowlatency])
- **短い起動時間** (初回画像を1秒以内に表示)
- **非侵入型** (デバイスに何もインストールされていない状態になる)
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
## 必要要件
AndroidデバイスはAPI21(Android 5.0)以上。
Androidデバイスで[adbデバッグが有効][enable-adb]であること。
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
一部のAndroidデバイスでは、キーボードとマウスを使用して制御する[追加オプション][control]を有効にする必要がある。
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
## アプリの取得
<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
Debian (_testing_ と _sid_) とUbuntu(20.04):
```
apt install scrcpy
```
[Snap]パッケージが利用可能: [`scrcpy`][snap-link]
[snap-link]: https://snapstats.org/snaps/scrcpy
[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager)
Fedora用[COPR]パッケージが利用可能: [`scrcpy`][copr-link]
[COPR]: https://fedoraproject.org/wiki/Category:Copr
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
Arch Linux用[AUR]パッケージが利用可能: [`scrcpy`][aur-link]
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
Gentoo用[Ebuild]が利用可能: [`scrcpy`][ebuild-link]
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
[自分でビルド][BUILD]も可能(心配しないでください、それほど難しくはありません。)
### Windows
Windowsでは簡単に、`adb`を含む)すべての依存関係を構築済みのアーカイブを利用可能です。
- [README](README.md#windows)
[Chocolatey]でも利用可能です:
[Chocolatey]: https://chocolatey.org/
```bash
choco install scrcpy
choco install adb # まだ入手していない場合
```
[Scoop]でも利用可能です:
```bash
scoop install scrcpy
scoop install adb # まだ入手していない場合
```
[Scoop]: https://scoop.sh
また、[アプリケーションをビルド][BUILD]することも可能です。
### macOS
アプリケーションは[Homebrew]で利用可能です。ただインストールするだけです。
[Homebrew]: https://brew.sh/
```bash
brew install scrcpy
```
`PATH`からアクセス可能な`adb`が必要です。もし持っていない場合はインストールしてください。
```bash
brew install android-platform-tools
```
`adb`は[MacPorts]からでもインストールできます。
```bash
sudo port install scrcpy
```
[MacPorts]: https://www.macports.org/
また、[アプリケーションをビルド][BUILD]することも可能です。
## 実行
Androidデバイスを接続し、実行:
```bash
scrcpy
```
次のコマンドでリストされるコマンドライン引数も受け付けます:
```bash
scrcpy --help
```
## 機能
### キャプチャ構成
#### サイズ削減
Androidデバイスを低解像度でミラーリングする場合、パフォーマンス向上に便利な場合があります。
幅と高さをある値(例1024)に制限するには:
```bash
scrcpy --max-size 1024
scrcpy -m 1024 # 短縮版
```
一方のサイズはデバイスのアスペクト比が維持されるように計算されます。この方法では、1920x1080のデバイスでは1024x576にミラーリングされます。
#### ビットレート変更
ビットレートの初期値は8Mbpsです。ビットレートを変更するには(例:2Mbpsに変更):
```bash
scrcpy --bit-rate 2M
scrcpy -b 2M # 短縮版
```
#### フレームレート制限
キャプチャするフレームレートを制限できます:
```bash
scrcpy --max-fps 15
```
この機能はAndroid 10からオフィシャルサポートとなっていますが、以前のバージョンでも動作する可能性があります。
#### トリミング
デバイスの画面は、画面の一部のみをミラーリングするようにトリミングできます。
これは、例えばOculus Goの片方の目をミラーリングする場合に便利です。:
```bash
scrcpy --crop 1224:1440:0:0 # オフセット位置(0,0)で1224x1440
```
もし`--max-size`も指定されている場合、トリミング後にサイズ変更が適用されます。
#### ビデオの向きをロックする
ミラーリングの向きをロックするには:
```bash
scrcpy --lock-video-orientation # 現在の向き
scrcpy --lock-video-orientation=0 # 自然な向き
scrcpy --lock-video-orientation=1 # 90°反時計回り
scrcpy --lock-video-orientation=2 # 180°
scrcpy --lock-video-orientation=3 # 90°時計回り
```
この設定は録画の向きに影響します。
[ウィンドウは独立して回転することもできます](#回転)。
#### エンコーダ
いくつかのデバイスでは一つ以上のエンコーダを持ちます。それらのいくつかは、問題やクラッシュを引き起こします。別のエンコーダを選択することが可能です:
```bash
scrcpy --encoder OMX.qcom.video.encoder.avc
```
利用可能なエンコーダをリストするために、無効なエンコーダ名を渡すことができます。エラー表示で利用可能なエンコーダを提供します。
```bash
scrcpy --encoder _
```
### キャプチャ
#### 録画
ミラーリング中に画面の録画をすることが可能です:
```bash
scrcpy --record file.mp4
scrcpy -r file.mkv
```
録画中にミラーリングを無効にするには:
```bash
scrcpy --no-display --record file.mp4
scrcpy -Nr file.mkv
# Ctrl+Cで録画を中断する
```
"スキップされたフレーム"は(パフォーマンス上の理由で)リアルタイムで表示されなくても録画されます。
フレームはデバイス上で _タイムスタンプされる_ ため [パケット遅延のバリエーション] は録画されたファイルに影響を与えません。
[パケット遅延のバリエーション]: https://en.wikipedia.org/wiki/Packet_delay_variation
#### v4l2loopback
Linuxでは、ビデオストリームをv4l2ループバックデバイスに送信することができます。
v4l2loopbackのデバイスにビデオストリームを送信することで、Androidデバイスをウェブカメラのようにv4l2対応ツールで開くこともできます。
`v4l2loopback` モジュールのインストールが必要です。
```bash
sudo apt install v4l2loopback-dkms
```
v4l2デバイスを作成する。
```bash
sudo modprobe v4l2loopback
```
これにより、新しいビデオデバイスが `/dev/videoN` に作成されます。(`N` は整数)
(複数のデバイスや特定のIDのデバイスを作成するために、より多くの[オプション](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/
#### Buffering
バッファリングを追加することも可能です。これによりレイテンシーは増加しますが、ジッターは減少します。(参照
[#2464])
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464
このオプションでディスプレイバッファリングを設定できます。
```bash
scrcpy --display-buffer=50 # ディスプレイに50msのバッファリングを追加する
```
V4L2の場合はこちらのオプションで設定できます。
```bash
scrcpy --v4l2-buffer=500 # add 500 ms buffering for v4l2 sink
```
### 接続
#### ワイヤレス
_Scrcpy_ はデバイスとの通信に`adb`を使用します。そして`adb`はTCP/IPを介しデバイスに[接続]することができます:
1. あなたのコンピュータと同じWi-Fiに接続します。
2. あなたのIPアドレスを取得します。設定 → 端末情報 → ステータス情報、もしくは、このコマンドを実行します:
```bash
adb shell ip route | awk '{print $9}'
```
3. あなたのデバイスでTCP/IPを介したadbを有効にします: `adb tcpip 5555`
4. あなたのデバイスの接続を外します。
5. あなたのデバイスに接続します:
`adb connect DEVICE_IP:5555` _(`DEVICE_IP`は置き換える)_
6. 通常通り`scrcpy`を実行します。
この方法はビットレートと解像度を減らすのにおそらく有用です:
```bash
scrcpy --bit-rate 2M --max-size 800
scrcpy -b2M -m800 # 短縮版
```
[接続]: https://developer.android.com/studio/command-line/adb.html#wireless
#### マルチデバイス
もし`adb devices`でいくつかのデバイスがリストされる場合、 _シリアルナンバー_ を指定する必要があります:
```bash
scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # 短縮版
```
デバイスがTCP/IPを介して接続されている場合:
```bash
scrcpy --serial 192.168.0.1:5555
scrcpy -s 192.168.0.1:5555 # 短縮版
```
複数のデバイスに対して、複数の _scrcpy_ インスタンスを開始することができます。
#### デバイス接続での自動起動
[AutoAdb]を使用可能です:
```bash
autoadb scrcpy -s '{}'
```
[AutoAdb]: https://github.com/rom1v/autoadb
#### SSHトンネル
リモートデバイスに接続するため、ローカル`adb`クライアントからリモート`adb`サーバーへ接続することが可能です(同じバージョンの _adb_ プロトコルを使用している場合):
```bash
adb kill-server # 5037ポートのローカルadbサーバーを終了する
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
# オープンしたままにする
```
他の端末から:
```bash
scrcpy
```
リモートポート転送の有効化を回避するためには、代わりに転送接続を強制することができます(`-R`の代わりに`-L`を使用することに注意):
```bash
adb kill-server # 5037ポートのローカルadbサーバーを終了する
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
# オープンしたままにする
```
他の端末から:
```bash
scrcpy --force-adb-forward
```
ワイヤレス接続と同様に、クオリティを下げると便利な場合があります:
```
scrcpy -b2M -m800 --max-fps 15
```
### ウィンドウ構成
#### タイトル
ウィンドウのタイトルはデバイスモデルが初期値です。これは変更できます:
```bash
scrcpy --window-title 'My device'
```
#### 位置とサイズ
ウィンドウの位置とサイズの初期値を指定できます:
```bash
scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
```
#### ボーダーレス
ウィンドウの装飾を無効化するには:
```bash
scrcpy --window-borderless
```
#### 常に画面のトップ
scrcpyの画面を常にトップにするには:
```bash
scrcpy --always-on-top
```
#### フルスクリーン
アプリケーションを直接フルスクリーンで開始できます:
```bash
scrcpy --fullscreen
scrcpy -f # 短縮版
```
フルスクリーンは、次のコマンドで動的に切り替えることができます <kbd>MOD</kbd>+<kbd>f</kbd>
#### 回転
ウィンドウは回転することができます:
```bash
scrcpy --rotation 1
```
設定可能な値:
- `0`: 回転なし
- `1`: 90° 反時計回り
- `2`: 180°
- `3`: 90° 時計回り
回転は次のコマンドで動的に変更することができます。 <kbd>MOD</kbd>+<kbd>←</kbd>_(左)_ 、 <kbd>MOD</kbd>+<kbd>→</kbd>_(右)_
_scrcpy_ は3つの回転を管理することに注意:
- <kbd>MOD</kbd>+<kbd>r</kbd>はデバイスに縦向きと横向きの切り替えを要求する(現在実行中のアプリで要求している向きをサポートしていない場合、拒否することがある)
- [`--lock-video-orientation`](#ビデオの向きをロックする)は、ミラーリングする向きを変更する(デバイスからPCへ送信される向き)。録画に影響します。
- `--rotation` (もしくは<kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>)は、ウィンドウのコンテンツのみを回転します。これは表示にのみに影響し、録画には影響しません。
### 他のミラーリングオプション
#### Read-only リードオンリー
制御を無効にするには(デバイスと対話する全てのもの:入力キー、マウスイベント、ファイルのドラッグ&ドロップ):
```bash
scrcpy --no-control
scrcpy -n
```
#### ディスプレイ
いくつか利用可能なディスプレイがある場合、ミラーリングするディスプレイを選択できます:
```bash
scrcpy --display 1
```
ディスプレイIDのリストは次の方法で取得できます:
```
adb shell dumpsys display # search "mDisplayId=" in the output
```
セカンダリディスプレイは、デバイスが少なくともAndroid 10の場合にコントロール可能です。(それ以外ではリードオンリーでミラーリングされます)
#### 起動状態にする
デバイス接続時、少し遅れてからデバイスのスリープを防ぐには:
```bash
scrcpy --stay-awake
scrcpy -w
```
scrcpyが閉じられた時、初期状態に復元されます。
#### 画面OFF
コマンドラインオプションを使用することで、ミラーリングの開始時にデバイスの画面をOFFにすることができます:
```bash
scrcpy --turn-screen-off
scrcpy -S
```
もしくは、<kbd>MOD</kbd>+<kbd>o</kbd>を押すことでいつでもできます。
元に戻すには、<kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>を押します。
Androidでは、`POWER`ボタンはいつでも画面を表示します。便宜上、`POWER`がscrcpyを介して(右クリックもしくは<kbd>MOD</kbd>+<kbd>p</kbd>を介して)送信される場合、(ベストエフォートベースで)少し遅れて、強制的に画面を非表示にします。ただし、物理的な`POWER`ボタンを押した場合は、画面は表示されます。
このオプションはデバイスがスリープしないようにすることにも役立ちます:
```bash
scrcpy --turn-screen-off --stay-awake
scrcpy -Sw
```
#### タッチを表示
プレゼンテーションの場合(物理デバイス上で)物理的なタッチを表示すると便利な場合があります。
Androidはこの機能を _開発者オプション_ で提供します。
_Scrcpy_ は開始時にこの機能を有効にし、終了時に初期値を復元するオプションを提供します:
```bash
scrcpy --show-touches
scrcpy -t
```
(デバイス上で指を使った) _物理的な_ タッチのみ表示されることに注意してください。
#### スクリーンセーバー無効
初期状態では、scrcpyはコンピュータ上でスクリーンセーバーが実行される事を妨げません。
これを無効にするには:
```bash
scrcpy --disable-screensaver
```
### 入力制御
#### デバイス画面の回転
<kbd>MOD</kbd>+<kbd>r</kbd>を押すことで、縦向きと横向きを切り替えます。
フォアグラウンドのアプリケーションが要求された向きをサポートしている場合のみ回転することに注意してください。
#### コピー-ペースト
Androidのクリップボードが変更される度に、コンピュータのクリップボードに自動的に同期されます。
<kbd>Ctrl</kbd>のショートカットは全てデバイスに転送されます。特に:
- <kbd>Ctrl</kbd>+<kbd>c</kbd> 通常はコピーします
- <kbd>Ctrl</kbd>+<kbd>x</kbd> 通常はカットします
- <kbd>Ctrl</kbd>+<kbd>v</kbd> 通常はペーストします(コンピュータとデバイスのクリップボードが同期された後)
通常は期待通りに動作します。
しかしながら、実際の動作はアクティブなアプリケーションに依存します。例えば、_Termux_ は代わりに<kbd>Ctrl</kbd>+<kbd>c</kbd>でSIGINTを送信します、そして、_K-9 Mail_ は新しいメッセージを作成します。
このようなケースでコピー、カットそしてペーストをするには(Android 7以上でのサポートのみですが):
- <kbd>MOD</kbd>+<kbd>c</kbd> `COPY`を挿入
- <kbd>MOD</kbd>+<kbd>x</kbd> `CUT`を挿入
- <kbd>MOD</kbd>+<kbd>v</kbd> `PASTE`を挿入(コンピュータとデバイスのクリップボードが同期された後)
加えて、<kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>はコンピュータのクリップボードテキストにキーイベントのシーケンスとして挿入することを許可します。これはコンポーネントがテキストのペーストを許可しない場合(例えば _Termux_)に有用ですが、非ASCIIコンテンツを壊す可能性があります。
**警告:** デバイスにコンピュータのクリップボードを(<kbd>Ctrl</kbd>+<kbd>v</kbd>または<kbd>MOD</kbd>+<kbd>v</kbd>を介して)ペーストすることは、デバイスのクリップボードにコンテンツをコピーします。結果としてどのAndoridアプリケーションもそのコンテンツを読み取ることができます。機密性の高いコンテンツ(例えばパスワードなど)をこの方法でペーストすることは避けてください。
プログラムでデバイスのクリップボードを設定した場合、一部のデバイスは期待どおりに動作しません。`--legacy-paste`オプションは、コンピュータのクリップボードテキストをキーイベントのシーケンスとして挿入するため(<kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>と同じ方法)、<kbd>Ctrl</kbd>+<kbd>v</kbd>と<kbd>MOD</kbd>+<kbd>v</kbd>の動作の変更を提供します。
#### ピンチしてズームする
"ピンチしてズームする"をシミュレートするには: <kbd>Ctrl</kbd>+_クリック&移動_
より正確にするには、左クリックボタンを押している間、<kbd>Ctrl</kbd>を押したままにします。左クリックボタンを離すまで、全てのマウスの動きは、(アプリでサポートされている場合)画面の中心を基準として、コンテンツを拡大縮小および回転します。
具体的には、scrcpyは画面の中央を反転した位置にある"バーチャルフィンガー"から追加のタッチイベントを生成します。
#### テキストインジェクション環境設定
テキストをタイプした時に生成される2種類の[イベント][textevents]があります:
- _key events_ はキーを押したときと離したことを通知します。
- _text events_ はテキストが入力されたことを通知します。
初期状態で、文字はキーイベントで挿入されるため、キーボードはゲームで期待通りに動作します(通常はWASDキー)。
しかし、これは[問題を引き起こす][prefertext]かもしれません。もしこのような問題が発生した場合は、この方法で回避できます:
```bash
scrcpy --prefer-text
```
(しかしこの方法はゲームのキーボードの動作を壊します)
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
#### キーの繰り返し
初期状態では、キーの押しっぱなしは繰り返しのキーイベントを生成します。これらのイベントが使われない場合でも、この方法は一部のゲームでパフォーマンスの問題を引き起す可能性があります。
繰り返しのキーイベントの転送を回避するためには:
```bash
scrcpy --no-key-repeat
```
#### 右クリックと真ん中クリック
初期状態では、右クリックはバックの動作(もしくはパワーオン)を起こし、真ん中クリックではホーム画面へ戻ります。このショートカットを無効にし、代わりにデバイスへクリックを転送するには:
```bash
scrcpy --forward-all-clicks
```
### ファイルのドロップ
#### APKのインストール
APKをインストールするには、(`.apk`で終わる)APKファイルを _scrcpy_ の画面にドラッグ&ドロップします。
見た目のフィードバックはありません。コンソールにログが出力されます。
#### デバイスにファイルを送る
デバイスの`/sdcard/Download`ディレクトリにファイルを送るには、(APKではない)ファイルを _scrcpy_ の画面にドラッグ&ドロップします。
見た目のフィードバックはありません。コンソールにログが出力されます。
転送先ディレクトリを起動時に変更することができます:
```bash
scrcpy --push-target=/sdcard/Movies/
```
### 音声転送
音声は _scrcpy_ では転送されません。[sndcpy]を使用します。
[issue #14]も参照ください。
[sndcpy]: https://github.com/rom1v/sndcpy
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14
## ショートカット
次のリストでは、<kbd>MOD</kbd>でショートカット変更します。初期状態では、(left)<kbd>Alt</kbd>または(left)<kbd>Super</kbd>です。
これは`--shortcut-mod`で変更することができます。可能なキーは`lctrl`、`rctrl`、`lalt`、 `ralt`、 `lsuper`そして`rsuper`です。例えば:
```bash
# RCtrlをショートカットとして使用します
scrcpy --shortcut-mod=rctrl
# ショートカットにLCtrl+LAltまたはLSuperのいずれかを使用します
scrcpy --shortcut-mod=lctrl+lalt,lsuper
```
_<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> \| _ダブルクリック¹_
| `HOME`をクリック | <kbd>MOD</kbd>+<kbd>h</kbd> \| _真ん中クリック_
| `BACK`をクリック | <kbd>MOD</kbd>+<kbd>b</kbd> \| _右クリック²_
| `APP_SWITCH`をクリック | <kbd>MOD</kbd>+<kbd>s</kbd> \| _4クリック³_
| `MENU` (画面のアンロック)をクリック | <kbd>MOD</kbd>+<kbd>m</kbd>
| `VOLUME_UP`をクリック | <kbd>MOD</kbd>+<kbd>↑</kbd> _(上)_
| `VOLUME_DOWN`をクリック | <kbd>MOD</kbd>+<kbd>↓</kbd> _(下)_
| `POWER`をクリック | <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>+_クリック&移動_
_¹黒い境界線を削除するため、境界線上でダブルクリック_
_²もしスクリーンがオフの場合、右クリックでスクリーンをオンする。それ以外の場合はBackを押します._
_³4と5はマウスのボタンです、もしあなたのマウスにボタンがあれば使えます._
_⁴Android 7以上のみ._
キーを繰り返すショートカットはキーを離して2回目を押したら実行されます。例えば「設定パネルを展開する」を実行する場合は以下のように操作する。
1. <kbd>MOD</kbd> キーを押し、押したままにする.
2. その後に <kbd>n</kbd>キーを2回押す.
3. 最後に <kbd>MOD</kbd>キーを離す.
全ての<kbd>Ctrl</kbd>+_キー_ ショートカットはデバイスに転送されます、そのためアクティブなアプリケーションによって処理されます。
## カスタムパス
特定の _adb_ バイナリを使用する場合、そのパスを環境変数`ADB`で構成します:
ADB=/path/to/adb scrcpy
`scrcpy-server`ファイルのパスを上書きするには、`SCRCPY_SERVER_PATH`でそのパスを構成します。
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
## なぜ _scrcpy_?
同僚が私に、[gnirehtet]のように発音できない名前を見つけるように要求しました。
[`strcpy`]は**str**ingをコピーします。`scrcpy`は**scr**eenをコピーします。
[gnirehtet]: https://github.com/Genymobile/gnirehtet
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
## ビルド方法は?
[BUILD]を参照してください。
[BUILD]: BUILD.md
## よくある質問
[FAQ](FAQ.md)を参照してください。
## 開発者
[開発者のページ]を読んでください。
[開発者のページ]: DEVELOP.md
## ライセンス
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
## 記事
- [Introducing scrcpy][article-intro]
- [Scrcpy now works wirelessly][article-tcpip]
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/

View File

@ -68,9 +68,7 @@ Gentoo에서 ,[Ebuild] 가 가능합니다 : [`scrcpy/`][ebuild-link].
윈도우 상에서, 간단하게 설치하기 위해 종속성이 있는 사전 구축된 아카이브가 제공됩니다 (`adb` 포함) : 윈도우 상에서, 간단하게 설치하기 위해 종속성이 있는 사전 구축된 아카이브가 제공됩니다 (`adb` 포함) :
해당 파일은 Readme원본 링크를 통해서 다운로드가 가능합니다. 해당 파일은 Readme원본 링크를 통해서 다운로드가 가능합니다.
- [`scrcpy-win`][direct-win] - [README](README.md#windows)
[direct-win]: https://github.com/Genymobile/scrcpy/blob/master/README.md#windows
[어플을 직접 설치][BUILD] 할 수도 있습니다. [어플을 직접 설치][BUILD] 할 수도 있습니다.
@ -114,7 +112,7 @@ scrcpy --help
### 캡쳐 환경 설정 ### 캡쳐 환경 설정
###사이즈 재정의 ### 사이즈 재정의
가끔씩 성능을 향상시키기위해 안드로이드 디바이스를 낮은 해상도에서 미러링하는 것이 유용할 때도 있습니다. 가끔씩 성능을 향상시키기위해 안드로이드 디바이스를 낮은 해상도에서 미러링하는 것이 유용할 때도 있습니다.
@ -138,7 +136,7 @@ scrcpy --bit-rate 2M
scrcpy -b 2M # 축약 버전 scrcpy -b 2M # 축약 버전
``` ```
###프레임 비율 제한 ### 프레임 비율 제한
안드로이드 버전 10이상의 디바이스에서는, 다음의 명령어로 캡쳐 화면의 프레임 비율을 제한할 수 있습니다: 안드로이드 버전 10이상의 디바이스에서는, 다음의 명령어로 캡쳐 화면의 프레임 비율을 제한할 수 있습니다:

375
README.md
View File

@ -1,24 +1,37 @@
# scrcpy (v1.17) # scrcpy (v1.21)
<img src="data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
[Read in another language](#translations) [Read in another language](#translations)
This application provides display and control of Android devices connected on This application provides display and control of Android devices connected via
USB (or [over TCP/IP][article-tcpip]). 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_. It works on _GNU/Linux_, _Windows_ and _macOS_.
![screenshot](assets/screenshot-debian-600.jpg) ![screenshot](assets/screenshot-debian-600.jpg)
It focuses on: It focuses on:
- **lightness** (native, displays only the device screen) - **lightness**: native, displays only the device screen
- **performance** (30~60fps) - **performance**: 30~120fps, depending on the device
- **quality** (1920×1080 or above) - **quality**: 1920×1080 or above
- **low latency** ([35~70ms][lowlatency]) - **low latency**: [35~70ms][lowlatency]
- **low startup time** (~1 second to display the first image) - **low startup time**: ~1 second to display the first image
- **non-intrusiveness** (nothing is left installed on the device) - **non-intrusiveness**: nothing is left installed on the device
- **user benefits**: no account, no ads, no internet required
- **freedom**: free and open source software
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 [lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
Its features include:
- [recording](#recording)
- mirroring with [device screen off](#turn-screen-off)
- [copy-paste](#copy-paste) in both directions
- [configurable quality](#capture-configuration)
- device screen [as a webcam (V4L2)](#v4l2loopback) (Linux-only)
- [physical keyboard simulation (HID)](#physical-keyboard-simulation-hid)
(Linux-only)
- and more…
## Requirements ## Requirements
@ -38,9 +51,21 @@ control it using keyboard and mouse.
<a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a> <a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a>
### Summary
- Linux: `apt install scrcpy`
- Windows: [download][direct-win64]
- macOS: `brew install scrcpy`
Build from sources: [BUILD] ([simplified process][BUILD_simple])
[BUILD]: BUILD.md
[BUILD_simple]: BUILD.md#simple
### Linux ### Linux
On Debian (_testing_ and _sid_ for now) and Ubuntu (20.04): On Debian and Ubuntu:
``` ```
apt install scrcpy apt install scrcpy
@ -67,9 +92,8 @@ For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild [Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy [ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
You could also [build the app manually][BUILD] (don't worry, it's not that You could also [build the app manually][BUILD] ([simplified
hard). process][BUILD_simple]).
### Windows ### Windows
@ -77,10 +101,10 @@ hard).
For Windows, for simplicity, a prebuilt archive with all the dependencies For Windows, for simplicity, a prebuilt archive with all the dependencies
(including `adb`) is available: (including `adb`) is available:
- [`scrcpy-win64-v1.17.zip`][direct-win64] - [`scrcpy-win64-v1.21.zip`][direct-win64]
_(SHA-256: 8b9e57993c707367ed10ebfe0e1ef563c7a29d9af4a355cd8b6a52a317c73eea)_ _(SHA-256: fdab0c1421353b592a9bbcebd6e252675eadccca65cca8105686feaa9c1ded53)_
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.17/scrcpy-win64-v1.17.zip [direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.21/scrcpy-win64-v1.21.zip
It is also available in [Chocolatey]: It is also available in [Chocolatey]:
@ -116,13 +140,18 @@ brew install scrcpy
You need `adb`, accessible from your `PATH`. If you don't have it yet: You need `adb`, accessible from your `PATH`. If you don't have it yet:
```bash ```bash
# Homebrew >= 2.6.0 brew install android-platform-tools
brew install --cask android-platform-tools
# Homebrew < 2.6.0
brew cask install android-platform-tools
``` ```
It's also available in [MacPorts], which sets up adb for you:
```bash
sudo port install scrcpy
```
[MacPorts]: https://www.macports.org/
You can also [build the app manually][BUILD]. You can also [build the app manually][BUILD].
@ -198,10 +227,11 @@ If `--max-size` is also specified, resizing is applied after cropping.
To lock the orientation of the mirroring: To lock the orientation of the mirroring:
```bash ```bash
scrcpy --lock-video-orientation 0 # natural orientation scrcpy --lock-video-orientation # initial (current) orientation
scrcpy --lock-video-orientation 1 # 90° counterclockwise scrcpy --lock-video-orientation=0 # natural orientation
scrcpy --lock-video-orientation 2 # 180° scrcpy --lock-video-orientation=1 # 90° counterclockwise
scrcpy --lock-video-orientation 3 # 90° clockwise scrcpy --lock-video-orientation=2 # 180°
scrcpy --lock-video-orientation=3 # 90° clockwise
``` ```
This affects recording orientation. This affects recording orientation.
@ -225,7 +255,9 @@ error will give the available encoders:
scrcpy --encoder _ scrcpy --encoder _
``` ```
### Recording ### Capture
#### Recording
It is possible to record the screen while mirroring: It is possible to record the screen while mirroring:
@ -249,12 +281,114 @@ variation] does not impact the recorded file.
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation [packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
#### v4l2loopback
On Linux, it is possible to send the video stream to a v4l2 loopback device, so
that the Android device can be opened like a webcam by any v4l2-capable tool.
The module `v4l2loopback` must be installed:
```bash
sudo apt install v4l2loopback-dkms
```
To create a v4l2 device:
```bash
sudo modprobe v4l2loopback
```
This will create a new video device in `/dev/videoN`, where `N` is an integer
(more [options](https://github.com/umlaeute/v4l2loopback#options) are available
to create several devices or devices with specific IDs).
To list the enabled devices:
```bash
# requires v4l-utils package
v4l2-ctl --list-devices
# simple but might be sufficient
ls /dev/video*
```
To start scrcpy using a v4l2 sink:
```bash
scrcpy --v4l2-sink=/dev/videoN
scrcpy --v4l2-sink=/dev/videoN --no-display # disable mirroring window
scrcpy --v4l2-sink=/dev/videoN -N # short version
```
(replace `N` by the device ID, check with `ls /dev/video*`)
Once enabled, you can open your video stream with a v4l2-capable tool:
```bash
ffplay -i /dev/videoN
vlc v4l2:///dev/videoN # VLC might add some buffering delay
```
For example, you could capture the video within [OBS].
[OBS]: https://obsproject.com/
#### Buffering
It is possible to add buffering. This increases latency but reduces jitter (see
[#2464]).
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464
The option is available for display buffering:
```bash
scrcpy --display-buffer=50 # add 50 ms buffering for display
```
and V4L2 sink:
```bash
scrcpy --v4l2-buffer=500 # add 500 ms buffering for v4l2 sink
```
### Connection ### Connection
#### Wireless #### TCP/IP (wireless)
_Scrcpy_ uses `adb` to communicate with the device, and `adb` can [connect] to a _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. 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 2. Get your device IP address, in Settings → About phone → Status, or by
@ -307,21 +441,66 @@ autoadb scrcpy -s '{}'
[AutoAdb]: https://github.com/rom1v/autoadb [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 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_ a remote `adb` server (provided they use the same version of the _adb_
protocol): protocol).
##### Remote ADB server
To connect to a remote ADB server, make the server listen on all interfaces:
```bash ```bash
adb kill-server # kill the local adb server on 5037 adb kill-server
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer adb -a nodaemon server start
# keep this open # keep this open
``` ```
From another terminal: **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 ```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
adb start-server
```
Then, establish a SSH tunnel:
```bash
# local 5038 --> remote 5037
# local 27183 <-- remote 27183
ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer
# keep this open
```
From another terminal, run scrcpy:
```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038
scrcpy scrcpy
``` ```
@ -329,14 +508,16 @@ To avoid enabling remote port forwarding, you could force a forward connection
instead (notice the `-L` instead of `-R`): instead (notice the `-L` instead of `-R`):
```bash ```bash
adb kill-server # kill the local adb server on 5037 # local 5038 --> remote 5037
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer # local 27183 --> remote 27183
ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer
# keep this open # keep this open
``` ```
From another terminal: From another terminal, run scrcpy:
```bash ```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038
scrcpy --force-adb-forward scrcpy --force-adb-forward
``` ```
@ -411,7 +592,7 @@ _(left)_ and <kbd>MOD</kbd>+<kbd>→</kbd> _(right)_.
Note that _scrcpy_ manages 3 different rotations: Note that _scrcpy_ manages 3 different rotations:
- <kbd>MOD</kbd>+<kbd>r</kbd> requests the device to switch between portrait - <kbd>MOD</kbd>+<kbd>r</kbd> requests the device to switch between portrait
and landscape (the current running app may refuse, if it does support the and landscape (the current running app may refuse, if it does not support the
requested orientation). requested orientation).
- [`--lock-video-orientation`](#lock-video-orientation) changes the mirroring - [`--lock-video-orientation`](#lock-video-orientation) changes the mirroring
orientation (the orientation of the video sent from the device to the orientation (the orientation of the video sent from the device to the
@ -444,7 +625,7 @@ scrcpy --display 1
The list of display ids can be retrieved by: The list of display ids can be retrieved by:
``` ```bash
adb shell dumpsys display # search "mDisplayId=" in the output adb shell dumpsys display # search "mDisplayId=" in the output
``` ```
@ -490,19 +671,15 @@ scrcpy --turn-screen-off --stay-awake
scrcpy -Sw scrcpy -Sw
``` ```
#### Power off on close
#### Render expired frames To turn the device screen off when closing scrcpy:
By default, to minimize latency, _scrcpy_ always renders the last decoded frame
available, and drops any previous one.
To force the rendering of all frames (at a cost of a possible increased
latency), use:
```bash ```bash
scrcpy --render-expired-frames scrcpy --power-off-on-close
``` ```
#### Show touches #### Show touches
For presentations, it may be useful to show physical touches (on the physical For presentations, it may be useful to show physical touches (on the physical
@ -582,6 +759,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 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>). way as <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
To disable automatic clipboard synchronization, use
`--no-clipboard-autosync`.
#### Pinch-to-zoom #### Pinch-to-zoom
To simulate "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-and-move_. To simulate "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-and-move_.
@ -593,6 +773,48 @@ content (if supported by the app) relative to the center of the screen.
Concretely, scrcpy generates additional touch events from a "virtual finger" at Concretely, scrcpy generates additional touch events from a "virtual finger" at
a location inverted through the center of the screen. a location inverted through the center of the screen.
#### Physical keyboard simulation (HID)
By default, scrcpy uses Android key or text injection: it works everywhere, but
is limited to ASCII.
On Linux, scrcpy can simulate a physical USB keyboard on Android to provide a
better input experience (using [USB HID over AOAv2][hid-aoav2]): the virtual
keyboard is disabled and it works for all characters and IME.
[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support
However, it only works if the device is connected by USB, and is currently only
supported on Linux.
To enable this mode:
```bash
scrcpy --hid-keyboard
scrcpy -K # short version
```
If it fails for some reason (for example because the device is not connected via
USB), it automatically fallbacks to the default mode (with a log in the
console). This allows to use the same command line options when connected over
USB and TCP/IP.
In this mode, raw key events (scancodes) are sent to the device, independently
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
#### Text injection preference #### Text injection preference
@ -612,6 +834,15 @@ scrcpy --prefer-text
(but this will break keyboard behavior in games) (but this will break keyboard behavior in games)
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 [textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 [prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
@ -627,6 +858,9 @@ To avoid forwarding repeated key events:
scrcpy --no-key-repeat scrcpy --no-key-repeat
``` ```
This option has no effect on HID keyboard (key repeat is handled by Android
directly in this mode).
#### Right-click and middle-click #### Right-click and middle-click
@ -650,15 +884,15 @@ There is no visual feedback, a log is printed to the console.
#### Push file to device #### Push file to device
To push a file to `/sdcard/` on the device, drag & drop a (non-APK) file to the To push a file to `/sdcard/Download/` on the device, drag & drop a (non-APK)
_scrcpy_ window. file to the _scrcpy_ window.
There is no visual feedback, a log is printed to the console. There is no visual feedback, a log is printed to the console.
The target directory can be changed on start: The target directory can be changed on start:
```bash ```bash
scrcpy --push-target /sdcard/foo/bar/ scrcpy --push-target=/sdcard/Movies/
``` ```
@ -698,10 +932,10 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
| Rotate display left | <kbd>MOD</kbd>+<kbd>←</kbd> _(left)_ | Rotate display left | <kbd>MOD</kbd>+<kbd>←</kbd> _(left)_
| Rotate display right | <kbd>MOD</kbd>+<kbd>→</kbd> _(right)_ | Rotate display right | <kbd>MOD</kbd>+<kbd>→</kbd> _(right)_
| Resize window to 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd> | Resize window to 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
| Resize window to remove black borders | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Double-click¹_ | Resize window to remove black borders | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Double-left-click¹_
| Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_ | Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_
| Click on `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Right-click²_ | Click on `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Right-click²_
| Click on `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> | Click on `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _4th-click³_
| Click on `MENU` (unlock screen) | <kbd>MOD</kbd>+<kbd>m</kbd> | Click on `MENU` (unlock screen) | <kbd>MOD</kbd>+<kbd>m</kbd>
| Click on `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(up)_ | Click on `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(up)_
| Click on `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(down)_ | Click on `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(down)_
@ -710,18 +944,29 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
| Turn device screen off (keep mirroring) | <kbd>MOD</kbd>+<kbd>o</kbd> | Turn device screen off (keep mirroring) | <kbd>MOD</kbd>+<kbd>o</kbd>
| Turn device screen on | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd> | Turn device screen on | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
| Rotate device screen | <kbd>MOD</kbd>+<kbd>r</kbd> | Rotate device screen | <kbd>MOD</kbd>+<kbd>r</kbd>
| Expand notification panel | <kbd>MOD</kbd>+<kbd>n</kbd> | Expand notification panel | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5th-click³_
| Collapse notification panel | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd> | Expand settings panel | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Double-5th-click³_
| Copy to clipboard³ | <kbd>MOD</kbd>+<kbd>c</kbd> | Collapse panels | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| Cut to clipboard³ | <kbd>MOD</kbd>+<kbd>x</kbd> | Copy to clipboard | <kbd>MOD</kbd>+<kbd>c</kbd>
| Synchronize clipboards and paste³ | <kbd>MOD</kbd>+<kbd>v</kbd> | Cut to clipboard⁴ | <kbd>MOD</kbd>+<kbd>x</kbd>
| Synchronize clipboards and paste⁴ | <kbd>MOD</kbd>+<kbd>v</kbd>
| Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> | Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd> | Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_ | Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_
| Drag & drop APK file | Install APK from computer
| Drag & drop non-APK file | [Push file to device](#push-file-to-device)
_¹Double-click on black borders to remove them._ _¹Double-click on black borders to remove them._
_²Right-click turns the screen on if it was off, presses BACK otherwise._ _²Right-click turns the screen on if it was off, presses BACK otherwise._
Only on Android >= 7._ 4th and 5th mouse buttons, if your mouse has them._
_⁴Only on Android >= 7._
Shortcuts with repeated keys are executted by releasing and pressing the key a
second time. For example, to execute "Expand settings panel":
1. Press and keep pressing <kbd>MOD</kbd>.
2. Then double-press <kbd>n</kbd>.
3. Finally, release <kbd>MOD</kbd>.
All <kbd>Ctrl</kbd>+_key_ shortcuts are forwarded to the device, so they are All <kbd>Ctrl</kbd>+_key_ shortcuts are forwarded to the device, so they are
handled by the active application. handled by the active application.
@ -732,12 +977,14 @@ handled by the active application.
To use a specific _adb_ binary, configure its path in the environment variable To use a specific _adb_ binary, configure its path in the environment variable
`ADB`: `ADB`:
ADB=/path/to/adb scrcpy ```bash
ADB=/path/to/adb scrcpy
```
To override the path of the `scrcpy-server` file, configure its path in To override the path of the `scrcpy-server` file, configure its path in
`SCRCPY_SERVER_PATH`. `SCRCPY_SERVER_PATH`.
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345 To override the icon, configure its path in `SCRCPY_ICON_PATH`.
## Why _scrcpy_? ## Why _scrcpy_?
@ -754,8 +1001,6 @@ A colleague challenged me to find a name as unpronounceable as [gnirehtet].
See [BUILD]. See [BUILD].
[BUILD]: BUILD.md
## Common issues ## Common issues
@ -799,9 +1044,13 @@ Read the [developers page].
This README is available in other languages: This README is available in other languages:
- [Indonesian (Indonesia, `id`) - v1.16](README.id.md) - [Indonesian (Indonesia, `id`) - v1.16](README.id.md)
- [Italiano (Italiano, `it`) - v1.19](README.it.md)
- [日本語 (Japanese, `jp`) - v1.19](README.jp.md)
- [한국어 (Korean, `ko`) - v1.11](README.ko.md) - [한국어 (Korean, `ko`) - v1.11](README.ko.md)
- [português brasileiro (Brazilian Portuguese, `pt-BR`) - v1.12.1](README.pt-br.md) - [Português Brasileiro (Brazilian Portuguese, `pt-BR`) - v1.19](README.pt-br.md)
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.16](README.zh-Hans.md) - [Español (Spanish, `sp`) - v1.17](README.sp.md)
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.20](README.zh-Hans.md)
- [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md) - [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md)
- [Turkish (Turkish, `tr`) - v1.18](README.tr.md)
Only this README file is guaranteed to be up-to-date. Only this README file is guaranteed to be up-to-date.

View File

@ -1,16 +1,16 @@
_Only the original [README](README.md) is guaranteed to be up-to-date._ _Apenas o [README](README.md) original é garantido estar atualizado._
# scrcpy (v1.12.1) # scrcpy (v1.19)
Esta aplicação fornece visualização e controle de dispositivos Android conectados via Esta aplicação fornece exibição e controle de dispositivos Android conectados via
USB (ou [via TCP/IP][article-tcpip]). Não requer nenhum acesso root. USB (ou [via TCP/IP][article-tcpip]). Não requer nenhum acesso _root_.
Funciona em _GNU/Linux_, _Windows_ e _macOS_. Funciona em _GNU/Linux_, _Windows_ e _macOS_.
![screenshot](assets/screenshot-debian-600.jpg) ![screenshot](assets/screenshot-debian-600.jpg)
Foco em: Foco em:
- **leveza** (Nativo, mostra apenas a tela do dispositivo) - **leveza** (nativo, mostra apenas a tela do dispositivo)
- **performance** (30~60fps) - **performance** (30~60fps)
- **qualidade** (1920×1080 ou acima) - **qualidade** (1920×1080 ou acima)
- **baixa latência** ([35~70ms][lowlatency]) - **baixa latência** ([35~70ms][lowlatency])
@ -22,36 +22,53 @@ Foco em:
## Requisitos ## Requisitos
O Dispositivo Android requer pelo menos a API 21 (Android 5.0). O dispositivo Android requer pelo menos a API 21 (Android 5.0).
Tenha certeza de ter [ativado a depuração adb][enable-adb] no(s) seu(s) dispositivo(s).
Tenha certeza de ter [ativado a depuração USB][enable-adb] no(s) seu(s) dispositivo(s).
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling [enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
Em alguns dispositivos, você também precisa ativar [uma opção adicional][control] para
Em alguns dispositivos, você também precisará ativar [uma opção adicional][control] para controlá-lo usando o teclado e mouse. controlá-lo usando teclado e mouse.
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 [control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
## Obtendo o app ## Obter o app
<a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a>
### Sumário
- Linux: `apt install scrcpy`
- Windows: [baixar][direct-win64]
- macOS: `brew install scrcpy`
Compilar pelos arquivos fontes: [BUILD] ([processo simplificado][BUILD_simple])
[BUILD]: BUILD.md
[BUILD_simple]: BUILD.md#simple
### Linux ### Linux
No Debian (_em testes_ e _sid_ por enquanto): No Debian (_testing_ e _sid_ por enquanto) e Ubuntu (20.04):
``` ```
apt install scrcpy apt install scrcpy
``` ```
O pacote [Snap] está disponível: [`scrcpy`][snap-link]. Um pacote [Snap] está disponível: [`scrcpy`][snap-link].
[snap-link]: https://snapstats.org/snaps/scrcpy [snap-link]: https://snapstats.org/snaps/scrcpy
[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager) [snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager)
Para Fedora, um pacote [COPR] está disponível: [`scrcpy`][copr-link].
[COPR]: https://fedoraproject.org/wiki/Category:Copr
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
Para Arch Linux, um pacote [AUR] está disponível: [`scrcpy`][aur-link]. Para Arch Linux, um pacote [AUR] está disponível: [`scrcpy`][aur-link].
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository [AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
@ -62,21 +79,17 @@ Para Gentoo, uma [Ebuild] está disponível: [`scrcpy/`][ebuild-link].
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild [Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy [ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
Você também pode [compilar o app manualmente][BUILD] ([processo simplificado][BUILD_simple]).
Você também pode [compilar a aplicação manualmente][BUILD] (não se preocupe, não é tão difícil).
### Windows ### Windows
Para Windows, para simplicidade, um arquivo pré-compilado com todas as dependências Para Windows, por simplicidade, um arquivo pré-compilado com todas as dependências
(incluindo `adb`) está disponível: (incluindo `adb`) está disponível:
- [`scrcpy-win64-v1.12.1.zip`][direct-win64] - [README](README.md#windows)
_(SHA-256: 57d34b6d16cfd9fe169bc37c4df58ebd256d05c1ea3febc63d9cb0a027ab47c9)_
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.12.1/scrcpy-win64-v1.12.1.zip Também está disponível em [Chocolatey]:
Também disponível em [Chocolatey]:
[Chocolatey]: https://chocolatey.org/ [Chocolatey]: https://chocolatey.org/
@ -94,12 +107,12 @@ scoop install adb # se você ainda não o tem
[Scoop]: https://scoop.sh [Scoop]: https://scoop.sh
Você também pode [compilar a aplicação manualmente][BUILD]. Você também pode [compilar o app manualmente][BUILD].
### macOS ### macOS
A aplicação está disponível em [Homebrew]. Apenas a instale: A aplicação está disponível em [Homebrew]. Apenas instale-a:
[Homebrew]: https://brew.sh/ [Homebrew]: https://brew.sh/
@ -107,18 +120,27 @@ A aplicação está disponível em [Homebrew]. Apenas a instale:
brew install scrcpy brew install scrcpy
``` ```
Você precisa do `adb`, acessível através do seu `PATH`. Se você ainda não o tem: Você precisa do `adb`, acessível pelo seu `PATH`. Se você ainda não o tem:
```bash ```bash
brew cask install android-platform-tools brew install android-platform-tools
``` ```
Você também pode [compilar a aplicação manualmente][BUILD]. Está também disponivel em [MacPorts], que prepara o adb para você:
```bash
sudo port install scrcpy
```
[MacPorts]: https://www.macports.org/
Você também pode [compilar o app manualmente][BUILD].
## Executar ## Executar
Plugue um dispositivo Android e execute: Conecte um dispositivo Android e execute:
```bash ```bash
scrcpy scrcpy
@ -134,53 +156,91 @@ scrcpy --help
### Configuração de captura ### Configuração de captura
#### Redução de tamanho #### Reduzir tamanho
Algumas vezes, é útil espelhar um dispositivo Android em uma resolução menor para Algumas vezes, é útil espelhar um dispositivo Android em uma resolução menor para
aumentar performance. aumentar a performance.
Para limitar ambos(largura e altura) para algum valor (ex: 1024): Para limitar ambos (largura e altura) para algum valor (ex: 1024):
```bash ```bash
scrcpy --max-size 1024 scrcpy --max-size 1024
scrcpy -m 1024 # versão reduzida scrcpy -m 1024 # versão curta
``` ```
A outra dimensão é calculada para que a proporção do dispositivo seja preservada. A outra dimensão é calculada para que a proporção do dispositivo seja preservada.
Dessa forma, um dispositivo em 1920x1080 será espelhado em 1024x576. Dessa forma, um dispositivo de 1920x1080 será espelhado em 1024x576.
#### Mudanças no bit-rate #### Mudar bit-rate
O Padrão de bit-rate é 8 mbps. Para mudar o bitrate do vídeo (ex: para 2 Mbps): O bit-rate padrão é 8 Mbps. Para mudar o bit-rate do vídeo (ex: para 2 Mbps):
```bash ```bash
scrcpy --bit-rate 2M scrcpy --bit-rate 2M
scrcpy -b 2M # versão reduzida scrcpy -b 2M # versão curta
``` ```
#### Limitar frame rates #### Limitar frame rate
Em dispositivos com Android >= 10, a captura de frame rate pode ser limitada: O frame rate de captura pode ser limitado:
```bash ```bash
scrcpy --max-fps 15 scrcpy --max-fps 15
``` ```
Isso é oficialmente suportado desde o Android 10, mas pode funcionar em versões anteriores.
#### Cortar #### Cortar
A tela do dispositivo pode ser cortada para espelhar apenas uma parte da tela. A tela do dispositivo pode ser cortada para espelhar apenas uma parte da tela.
Isso é útil por exemplo, ao espelhar apenas um olho do Oculus Go: Isso é útil por exemplo, para espelhar apenas um olho do Oculus Go:
```bash ```bash
scrcpy --crop 1224:1440:0:0 # 1224x1440 no deslocamento (0,0) scrcpy --crop 1224:1440:0:0 # 1224x1440 no deslocamento (0,0)
``` ```
Se `--max-size` também for especificado, redimensionar é aplicado após os cortes. Se `--max-size` também for especificado, o redimensionamento é aplicado após o corte.
### Gravando #### Travar orientação do vídeo
Para travar a orientação do espelhamento:
```bash
scrcpy --lock-video-orientation # orientação inicial (Atual)
scrcpy --lock-video-orientation=0 # orientação natural
scrcpy --lock-video-orientation=1 # 90° sentido anti-horário
scrcpy --lock-video-orientation=2 # 180°
scrcpy --lock-video-orientation=3 # 90° sentido horário
```
Isso afeta a orientação de gravação.
A [janela também pode ser rotacionada](#rotação) independentemente.
#### Encoder
Alguns dispositivos têm mais de um encoder, e alguns deles podem causar problemas ou
travar. É possível selecionar um encoder diferente:
```bash
scrcpy --encoder OMX.qcom.video.encoder.avc
```
Para listar os encoders disponíveis, você pode passar um nome de encoder inválido, o
erro dará os encoders disponíveis:
```bash
scrcpy --encoder _
```
### Captura
#### Gravando
É possível gravar a tela enquanto ocorre o espelhamento: É possível gravar a tela enquanto ocorre o espelhamento:
@ -194,65 +254,157 @@ Para desativar o espelhamento durante a gravação:
```bash ```bash
scrcpy --no-display --record file.mp4 scrcpy --no-display --record file.mp4
scrcpy -Nr file.mkv scrcpy -Nr file.mkv
# interrompe a gravação com Ctrl+C # interrompa a gravação com Ctrl+C
# Ctrl+C não encerrar propriamente no Windows, então desconecte o dispositivo
``` ```
"Frames pulados" são gravados, mesmo que não sejam mostrado em tempo real (por motivos de performance). "Frames pulados" são gravados, mesmo que não sejam exibidos em tempo real (por
Frames tem seu _horário_ _carimbado_ no dispositivo, então [Variação de atraso nos pacotes] não impacta na gravação do arquivo. motivos de performance). Frames têm seu _horário carimbado_ no dispositivo, então [variação de atraso nos
pacotes][packet delay variation] não impacta o arquivo gravado.
[Variação de atraso de pacote]: https://en.wikipedia.org/wiki/Packet_delay_variation [packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
#### v4l2loopback
Em Linux, é possível enviar a transmissão do video para um disposiivo v4l2 loopback, assim
o dispositivo Android pode ser aberto como uma webcam por qualquer ferramneta capaz de v4l2
The module `v4l2loopback` must be installed:
```bash
sudo apt install v4l2loopback-dkms
```
Para criar um dispositivo v4l2:
```bash
sudo modprobe v4l2loopback
```
Isso criara um novo dispositivo de vídeo em `/dev/videoN`, onde `N` é uma integer
(mais [opções](https://github.com/umlaeute/v4l2loopback#options) estão disponiveis
para criar varios dispositivos ou dispositivos com IDs específicas).
Para listar os dispositivos disponíveis:
```bash
# requer o pacote v4l-utils
v4l2-ctl --list-devices
# simples, mas pode ser suficiente
ls /dev/video*
```
Para iniciar o scrcpy usando o coletor v4l2 (sink):
```bash
scrcpy --v4l2-sink=/dev/videoN
scrcpy --v4l2-sink=/dev/videoN --no-display # desativa a janela espelhada
scrcpy --v4l2-sink=/dev/videoN -N # versão curta
```
(troque `N` pelo ID do dipositivo, verifique com `ls /dev/video*`)
Uma vez ativado, você pode abrir suas trasmissões de videos com uma ferramenta capaz de v4l2:
```bash
ffplay -i /dev/videoN
vlc v4l2:///dev/videoN # VLC pode adicionar um pouco de atraso de buffering
```
Por exemplo, você pode capturar o video dentro do [OBS].
[OBS]: https://obsproject.com/
#### Buffering
É possivel adicionar buffering. Isso aumenta a latência, mas reduz a tenção (jitter) (veja
[#2464]).
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464
A opção éta disponivel para buffering de exibição:
```bash
scrcpy --display-buffer=50 # adiciona 50 ms de buffering para a exibição
```
e coletor V4L2:
```bash
scrcpy --v4l2-buffer=500 # adiciona 500 ms de buffering para coletor V4L2
```
,
### Conexão ### Conexão
#### Wireless/Sem fio #### Sem fio
_Scrcpy_ usa `adb` para se comunicar com o dispositivo, e `adb` pode [conectar-se] à um dispositivo via TCP/IP: _Scrcpy_ usa `adb` para se comunicar com o dispositivo, e `adb` pode [conectar-se][connect] a um
dispositivo via TCP/IP:
1. Conecte o dispositivo no mesmo Wi-Fi do seu computador.
2. Pegue o endereço IP do seu dispositivo, em Configurações → Sobre o telefone → Status, ou
executando este comando:
```bash
adb shell ip route | awk '{print $9}'
```
1. Conecte o dispositivo a mesma rede Wi-Fi do seu computador.
2. Pegue o endereço de IP do seu dispositivo (Em Configurações → Sobre o Telefone → Status).
3. Ative o adb via TCP/IP no seu dispositivo: `adb tcpip 5555`. 3. Ative o adb via TCP/IP no seu dispositivo: `adb tcpip 5555`.
4. Desplugue seu dispositivo. 4. Desconecte seu dispositivo.
5. Conecte-se ao seu dispositivo: `adb connect DEVICE_IP:5555` _(substitua o `DEVICE_IP`)_. 5. Conecte-se ao seu dispositivo: `adb connect DEVICE_IP:5555` _(substitua `DEVICE_IP`)_.
6. Execute `scrcpy` como de costume. 6. Execute `scrcpy` como de costume.
Pode ser útil diminuir o bit-rate e a resolução: Pode ser útil diminuir o bit-rate e a resolução:
```bash ```bash
scrcpy --bit-rate 2M --max-size 800 scrcpy --bit-rate 2M --max-size 800
scrcpy -b2M -m800 # versão reduzida scrcpy -b2M -m800 # versão curta
``` ```
[conectar-se]: https://developer.android.com/studio/command-line/adb.html#wireless [connect]: https://developer.android.com/studio/command-line/adb.html#wireless
#### N-dispositivos #### Múltiplos dispositivos
Se alguns dispositivos estão listados em `adb devices`, você precisa especificar o _serial_: Se vários dispositivos são listados em `adb devices`, você deve especificar o _serial_:
```bash ```bash
scrcpy --serial 0123456789abcdef scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # versão reduzida scrcpy -s 0123456789abcdef # versão curta
``` ```
Se o dispositivo está conectado via TCP/IP: Se o dispositivo está conectado via TCP/IP:
```bash ```bash
scrcpy --serial 192.168.0.1:5555 scrcpy --serial 192.168.0.1:5555
scrcpy -s 192.168.0.1:5555 # versão reduzida scrcpy -s 192.168.0.1:5555 # versão curta
``` ```
Você pode iniciar algumas instâncias do _scrcpy_ para alguns dispositivos. Você pode iniciar várias instâncias do _scrcpy_ para vários dispositivos.
#### Conexão via SSH #### Iniciar automaticamente quando dispositivo é conectado
Para conectar-se à um dispositivo remoto, é possível se conectar um cliente local `adb` à um servidor `adb` remoto (contanto que eles usem a mesma versão do protocolo _adb_): Você pode usar [AutoAdb]:
```bash ```bash
adb kill-server # encerra o servidor local na 5037 autoadb scrcpy -s '{}'
```
[AutoAdb]: https://github.com/rom1v/autoadb
#### Túnel SSH
Para conectar-se a um dispositivo remoto, é possível conectar um cliente `adb` local a
um servidor `adb` remoto (contanto que eles usem a mesma versão do protocolo
_adb_):
```bash
adb kill-server # encerra o servidor adb local em 5037
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
# mantém isso aberto # mantenha isso aberto
``` ```
De outro terminal: De outro terminal:
@ -261,17 +413,33 @@ De outro terminal:
scrcpy scrcpy
``` ```
Igual para conexões sem fio, pode ser útil reduzir a qualidade: Para evitar ativar o encaminhamento de porta remota, você pode forçar uma conexão
de encaminhamento (note o `-L` em vez de `-R`):
```bash
adb kill-server # encerra o servidor adb local em 5037
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
# mantenha isso aberto
```
De outro terminal:
```bash
scrcpy --force-adb-forward
```
Igual a conexões sem fio, pode ser útil reduzir a qualidade:
``` ```
scrcpy -b2M -m800 --max-fps 15 scrcpy -b2M -m800 --max-fps 15
``` ```
### Configurações de Janela ### Configuração de janela
#### Título #### Título
Por padrão, o título da janela é o modelo do dispositivo. Isto pode ser mudado: Por padrão, o título da janela é o modelo do dispositivo. Isso pode ser mudado:
```bash ```bash
scrcpy --window-title 'Meu dispositivo' scrcpy --window-title 'Meu dispositivo'
@ -287,15 +455,15 @@ scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
#### Sem bordas #### Sem bordas
Para desativar decorações da janela: Para desativar decorações de janela:
```bash ```bash
scrcpy --window-borderless scrcpy --window-borderless
``` ```
#### Sempre visível #### Sempre no topo
Para manter a janela do scrcpy sempre visível: Para manter a janela do scrcpy sempre no topo:
```bash ```bash
scrcpy --always-on-top scrcpy --always-on-top
@ -307,53 +475,119 @@ A aplicação pode ser iniciada diretamente em tela cheia:
```bash ```bash
scrcpy --fullscreen scrcpy --fullscreen
scrcpy -f # versão reduzida scrcpy -f # versão curta
``` ```
Tela cheia pode ser alternada dinamicamente com `Ctrl`+`f`. Tela cheia pode ser alternada dinamicamente com <kbd>MOD</kbd>+<kbd>f</kbd>.
#### Rotação
A janela pode ser rotacionada:
```bash
scrcpy --rotation 1
```
Valores possíveis são:
- `0`: sem rotação
- `1`: 90 graus sentido anti-horário
- `2`: 180 graus
- `3`: 90 graus sentido horário
A rotação também pode ser mudada dinamicamente com <kbd>MOD</kbd>+<kbd>←</kbd>
_(esquerda)_ e <kbd>MOD</kbd>+<kbd>→</kbd> _(direita)_.
Note que _scrcpy_ controla 3 rotações diferentes:
- <kbd>MOD</kbd>+<kbd>r</kbd> requisita ao dispositivo para mudar entre retrato
e paisagem (a aplicação em execução pode se recusar, se ela não suporta a
orientação requisitada).
- [`--lock-video-orientation`](#travar-orientação-do-vídeo) muda a orientação de
espelhamento (a orientação do vídeo enviado pelo dispositivo para o
computador). Isso afeta a gravação.
- `--rotation` (ou <kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>)
rotaciona apenas o conteúdo da janela. Isso afeta apenas a exibição, não a
gravação.
### Outras opções de espelhamento ### Outras opções de espelhamento
#### Apenas leitura #### Apenas leitura
Para desativar controles (tudo que possa interagir com o dispositivo: teclas de entrada, eventos de mouse, arrastar e soltar arquivos): Para desativar controles (tudo que possa interagir com o dispositivo: teclas de entrada,
eventos de mouse, arrastar e soltar arquivos):
```bash ```bash
scrcpy --no-control scrcpy --no-control
scrcpy -n scrcpy -n
``` ```
#### Desligar a tela #### Display
É possível desligar a tela do dispositivo durante o início do espelhamento com uma opção de linha de comando: Se vários displays estão disponíveis, é possível selecionar o display para
espelhar:
```bash
scrcpy --display 1
```
A lista de IDs dos displays pode ser obtida por:
```
adb shell dumpsys display # busca "mDisplayId=" na saída
```
O display secundário pode apenas ser controlado se o dispositivo roda pelo menos Android
10 (caso contrário é espelhado como apenas leitura).
#### Permanecer ativo
Para evitar que o dispositivo seja suspenso após um delay quando o dispositivo é conectado:
```bash
scrcpy --stay-awake
scrcpy -w
```
O estado inicial é restaurado quando o scrcpy é fechado.
#### Desligar tela
É possível desligar a tela do dispositivo durante o início do espelhamento com uma
opção de linha de comando:
```bash ```bash
scrcpy --turn-screen-off scrcpy --turn-screen-off
scrcpy -S scrcpy -S
``` ```
Ou apertando `Ctrl`+`o` durante qualquer momento. Ou apertando <kbd>MOD</kbd>+<kbd>o</kbd> a qualquer momento.
Para ligar novamente, pressione `POWER` (ou `Ctrl`+`p`). Para ligar novamente, pressione <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>.
#### Frames expirados de renderização No Android, o botão de `POWER` sempre liga a tela. Por conveniência, se
`POWER` é enviado via scrcpy (via clique-direito ou <kbd>MOD</kbd>+<kbd>p</kbd>), ele
forçará a desligar a tela após um delay pequeno (numa base de melhor esforço).
O botão `POWER` físico ainda causará a tela ser ligada.
Por padrão, para minimizar a latência, _scrcpy_ sempre renderiza o último frame decodificado disponível e descarta o anterior. Também pode ser útil evitar que o dispositivo seja suspenso:
Para forçar a renderização de todos os frames ( com o custo de aumento de latência), use:
```bash ```bash
scrcpy --render-expired-frames scrcpy --turn-screen-off --stay-awake
scrcpy -Sw
``` ```
#### Mostrar toques #### Mostrar toques
Para apresentações, pode ser útil mostrar toques físicos(dispositivo físico). Para apresentações, pode ser útil mostrar toques físicos (no dispositivo
físico).
Android fornece esta funcionalidade nas _Opções do Desenvolvedor_. Android fornece esta funcionalidade nas _Opções do desenvolvedor_.
_Scrcpy_ fornece esta opção de ativar esta funcionalidade no início e desativar no encerramento: _Scrcpy_ fornece esta opção de ativar esta funcionalidade no início e restaurar o
valor inicial no encerramento:
```bash ```bash
scrcpy --show-touches scrcpy --show-touches
@ -363,59 +597,137 @@ scrcpy -t
Note que isto mostra apenas toques _físicos_ (com o dedo no dispositivo). Note que isto mostra apenas toques _físicos_ (com o dedo no dispositivo).
#### Desativar descanso de tela
Por padrão, scrcpy não evita que o descanso de tela rode no computador.
Para desativá-lo:
```bash
scrcpy --disable-screensaver
```
### Controle de entrada ### Controle de entrada
#### Rotacionar a tela do dispositivo #### Rotacionar a tela do dispositivo
Pressione `Ctrl`+`r` para mudar entre os modos Retrato e Paisagem. Pressione <kbd>MOD</kbd>+<kbd>r</kbd> para mudar entre os modos retrato e
paisagem.
Note que só será rotacionado se a aplicação em primeiro plano tiver suporte para o modo requisitado. Note que só será rotacionado se a aplicação em primeiro plano suportar a
orientação requisitada.
#### Copiar-Colar #### Copiar-colar
É possível sincronizar áreas de transferência entre computador e o dispositivo, Sempre que a área de transferência do Android muda, é automaticamente sincronizada com a
para ambas direções: área de transferência do computador.
- `Ctrl`+`c` copia a área de transferência do dispositivo para a área de trasferência do computador; Qualquer atalho com <kbd>Ctrl</kbd> é encaminhado para o dispositivo. Em particular:
- `Ctrl`+`Shift`+`v` copia a área de transferência do computador para a área de transferência do dispositivo; - <kbd>Ctrl</kbd>+<kbd>c</kbd> tipicamente copia
- `Ctrl`+`v` _cola_ a área de transferência do computador como uma sequência de eventos de texto (mas - <kbd>Ctrl</kbd>+<kbd>x</kbd> tipicamente recorta
quebra caracteres não-ASCII). - <kbd>Ctrl</kbd>+<kbd>v</kbd> tipicamente cola (após a sincronização de área de transferência
computador-para-dispositivo)
#### Preferências de injeção de texto Isso tipicamente funciona como esperado.
Existe dois tipos de [eventos][textevents] gerados ao digitar um texto: O comportamento de fato depende da aplicação ativa, no entanto. Por exemplo,
- _eventos de teclas_, sinalizando que a tecla foi pressionada ou solta; _Termux_ envia SIGINT com <kbd>Ctrl</kbd>+<kbd>c</kbd>, e _K-9 Mail_
compõe uma nova mensagem.
Para copiar, recortar e colar em tais casos (mas apenas suportado no Android >= 7):
- <kbd>MOD</kbd>+<kbd>c</kbd> injeta `COPY`
- <kbd>MOD</kbd>+<kbd>x</kbd> injeta `CUT`
- <kbd>MOD</kbd>+<kbd>v</kbd> injeta `PASTE` (após a sincronização de área de transferência
computador-para-dispositivo)
Em adição, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> permite injetar o
texto da área de transferência do computador como uma sequência de eventos de tecla. Isso é útil quando o
componente não aceita colar texto (por exemplo no _Termux_), mas pode
quebrar conteúdo não-ASCII.
**ADVERTÊNCIA:** Colar a área de transferência do computador para o dispositivo (tanto via
<kbd>Ctrl</kbd>+<kbd>v</kbd> quanto <kbd>MOD</kbd>+<kbd>v</kbd>) copia o conteúdo
para a área de transferência do dispositivo. Como consequência, qualquer aplicação Android pode ler
o seu conteúdo. Você deve evitar colar conteúdo sensível (como senhas) dessa
forma.
Alguns dispositivos não se comportam como esperado quando a área de transferência é definida
programaticamente. Uma opção `--legacy-paste` é fornecida para mudar o comportamento
de <kbd>Ctrl</kbd>+<kbd>v</kbd> e <kbd>MOD</kbd>+<kbd>v</kbd> para que eles
também injetem o texto da área de transferência do computador como uma sequência de eventos de tecla (da mesma
forma que <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
#### Pinçar para dar zoom
Para simular "pinçar para dar zoom": <kbd>Ctrl</kbd>+_clicar-e-mover_.
Mais precisamente, segure <kbd>Ctrl</kbd> enquanto pressiona o botão de clique-esquerdo. Até que
o botão de clique-esquerdo seja liberado, todos os movimentos do mouse ampliar e rotacionam o
conteúdo (se suportado pelo app) relativo ao centro da tela.
Concretamente, scrcpy gera eventos adicionais de toque de um "dedo virtual" em
uma posição invertida em relação ao centro da tela.
#### Preferência de injeção de texto
Existem dois tipos de [eventos][textevents] gerados ao digitar um texto:
- _eventos de tecla_, sinalizando que a tecla foi pressionada ou solta;
- _eventos de texto_, sinalizando que o texto foi inserido. - _eventos de texto_, sinalizando que o texto foi inserido.
Por padrão, letras são injetadas usando eventos de teclas, assim teclados comportam-se Por padrão, letras são injetadas usando eventos de tecla, assim o teclado comporta-se
como esperado em jogos (normalmente para tecladas WASD) como esperado em jogos (normalmente para teclas WASD).
Mas isto pode [causar problemas][prefertext]. Se você encontrar tal problema, Mas isso pode [causar problemas][prefertext]. Se você encontrar tal problema, você
pode evitá-lo usando: pode evitá-lo com:
```bash ```bash
scrcpy --prefer-text scrcpy --prefer-text
``` ```
(mas isto vai quebrar o comportamento do teclado em jogos) (mas isso vai quebrar o comportamento do teclado em jogos)
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input [textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 [prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
### Transferência de arquivo #### Repetir tecla
Por padrão, segurar uma tecla gera eventos de tecla repetidos. Isso pode causar
problemas de performance em alguns jogos, onde esses eventos são inúteis de qualquer forma.
Para evitar o encaminhamento eventos de tecla repetidos:
```bash
scrcpy --no-key-repeat
```
#### Clique-direito e clique-do-meio
Por padrão, clique-direito dispara BACK (ou POWER) e clique-do-meio dispara
HOME. Para desabilitar esses atalhos e encaminhar os cliques para o dispositivo:
```bash
scrcpy --forward-all-clicks
```
### Soltar arquivo
#### Instalar APK #### Instalar APK
Para instalar um APK, arraste e solte o arquivo APK(com extensão `.apk`) na janela _scrcpy_. Para instalar um APK, arraste e solte o arquivo APK (com extensão `.apk`) na janela
_scrcpy_.
Não existe feedback visual, um log é imprimido no console. Não existe feedback visual, um log é imprimido no console.
#### Enviar arquivo para o dispositivo #### Enviar arquivo para dispositivo
Para enviar um arquivo para o diretório `/sdcard/` no dispositivo, arraste e solte um arquivo não APK para a janela do Para enviar um arquivo para `/sdcard/Download/` no dispositivo, arraste e solte um arquivo (não-APK) para a
_scrcpy_. janela do _scrcpy_.
Não existe feedback visual, um log é imprimido no console. Não existe feedback visual, um log é imprimido no console.
@ -428,47 +740,86 @@ scrcpy --push-target /sdcard/foo/bar/
### Encaminhamento de áudio ### Encaminhamento de áudio
Áudio não é encaminhando pelo _scrcpy_. Use [USBaudio] (Apenas linux). Áudio não é encaminhado pelo _scrcpy_. Use [sndcpy].
Também veja [issue #14]. Também veja [issue #14].
[USBaudio]: https://github.com/rom1v/usbaudio [sndcpy]: https://github.com/rom1v/sndcpy
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14 [issue #14]: https://github.com/Genymobile/scrcpy/issues/14
## Atalhos ## Atalhos
| Ação | Atalho | Atalho (macOS) Na lista a seguir, <kbd>MOD</kbd> é o modificador de atalho. Por padrão, é
| ------------------------------------------------------------- |:------------------------------- |:----------------------------- <kbd>Alt</kbd> (esquerdo) ou <kbd>Super</kbd> (esquerdo).
| Alternar para modo de tela cheia | `Ctrl`+`f` | `Cmd`+`f`
| Redimensionar janela para pixel-perfect(Escala 1:1) | `Ctrl`+`g` | `Cmd`+`g`
| Redimensionar janela para tirar as bordas pretas | `Ctrl`+`x` \| _Clique-duplo¹_ | `Cmd`+`x` \| _Clique-duplo¹_
| Clicar em `HOME` | `Ctrl`+`h` \| _Clique-central_ | `Ctrl`+`h` \| _Clique-central_
| Clicar em `BACK` | `Ctrl`+`b` \| _Clique-direito²_ | `Cmd`+`b` \| _Clique-direito²_
| Clicar em `APP_SWITCH` | `Ctrl`+`s` | `Cmd`+`s`
| Clicar em `MENU` | `Ctrl`+`m` | `Ctrl`+`m`
| Clicar em `VOLUME_UP` | `Ctrl`+`↑` _(cima)_ | `Cmd`+`↑` _(cima)_
| Clicar em `VOLUME_DOWN` | `Ctrl`+`↓` _(baixo)_ | `Cmd`+`↓` _(baixo)_
| Clicar em `POWER` | `Ctrl`+`p` | `Cmd`+`p`
| Ligar | _Clique-direito²_ | _Clique-direito²_
| Desligar a tela do dispositivo | `Ctrl`+`o` | `Cmd`+`o`
| Rotacionar tela do dispositivo | `Ctrl`+`r` | `Cmd`+`r`
| Expandir painel de notificação | `Ctrl`+`n` | `Cmd`+`n`
| Esconder painel de notificação | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n`
| Copiar área de transferência do dispositivo para o computador | `Ctrl`+`c` | `Cmd`+`c`
| Colar área de transferência do computador para o dispositivo | `Ctrl`+`v` | `Cmd`+`v`
| Copiar área de transferência do computador para dispositivo | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v`
| Ativar/desativar contador de FPS(Frames por segundo) | `Ctrl`+`i` | `Cmd`+`i`
_¹Clique-duplo em bordas pretas para removê-las._ Ele pode ser mudado usando `--shortcut-mod`. Possíveis teclas são `lctrl`, `rctrl`,
_²Botão direito liga a tela se ela estiver desligada, clique BACK para o contrário._ `lalt`, `ralt`, `lsuper` e `rsuper`. Por exemplo:
```bash
# usar RCtrl para atalhos
scrcpy --shortcut-mod=rctrl
# usar tanto LCtrl+LAlt quanto LSuper para atalhos
scrcpy --shortcut-mod=lctrl+lalt,lsuper
```
_<kbd>[Super]</kbd> é tipicamente a tecla <kbd>Windows</kbd> ou <kbd>Cmd</kbd>._
[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button)
| Ação | Atalho
| ------------------------------------------- |:-----------------------------
| Mudar modo de tela cheia | <kbd>MOD</kbd>+<kbd>f</kbd>
| Rotacionar display para esquerda | <kbd>MOD</kbd>+<kbd>←</kbd> _(esquerda)_
| Rotacionar display para direita | <kbd>MOD</kbd>+<kbd>→</kbd> _(direita)_
| Redimensionar janela para 1:1 (pixel-perfeito) | <kbd>MOD</kbd>+<kbd>g</kbd>
| Redimensionar janela para remover bordas pretas | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Clique-duplo-esquerdo¹_
| Clicar em `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Clique-do-meio_
| Clicar em `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Clique-direito²_
| Clicar em `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _Clique-do-4.°³_
| Clicar em `MENU` (desbloquear tela) | <kbd>MOD</kbd>+<kbd>m</kbd>
| Clicar em `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(cima)_
| Clicar em `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(baixo)_
| Clicar em `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd>
| Ligar | _Clique-direito²_
| Desligar tela do dispositivo (continuar espelhando) | <kbd>MOD</kbd>+<kbd>o</kbd>
| Ligar tela do dispositivo | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
| Rotacionar tela do dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd>
| Expandir painel de notificação | <kbd>MOD</kbd>+<kbd>n</kbd> \| _Clique-do-5.°³_
| Expandir painel de configurção | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Clique-duplo-do-5.°³_
| Colapsar paineis | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| Copiar para área de transferência⁴ | <kbd>MOD</kbd>+<kbd>c</kbd>
| Recortar para área de transferência⁴ | <kbd>MOD</kbd>+<kbd>x</kbd>
| Sincronizar áreas de transferência e colar⁴ | <kbd>MOD</kbd>+<kbd>v</kbd>
| Injetar texto da área de transferência do computador | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| Ativar/desativar contador de FPS (em stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| Pinçar para dar zoom | <kbd>Ctrl</kbd>+_Clicar-e-mover_
_¹Clique-duplo-esquerdo na borda preta para remove-la._
_²Clique-direito liga a tela caso esteja desligada, pressione BACK caso contrário._
_³4.° and 5.° botões do mouse, caso o mouse possua._
_⁴Apenas em Android >= 7._
Atalhos com teclas reptidas são executados soltando e precionando a tecla
uma segunda vez. Por exemplo, para executar "Expandir painel de Configurção":
1. Mantenha pressionado <kbd>MOD</kbd>.
2. Depois click duas vezes <kbd>n</kbd>.
3. Finalmente, solte <kbd>MOD</kbd>.
Todos os atalhos <kbd>Ctrl</kbd>+_tecla_ são encaminhados para o dispositivo, para que eles sejam
tratados pela aplicação ativa.
## Caminhos personalizados ## Caminhos personalizados
Para usar um binário específico _adb_, configure seu caminho na variável de ambiente `ADB`: Para usar um binário _adb_ específico, configure seu caminho na variável de ambiente
`ADB`:
ADB=/caminho/para/adb scrcpy ```bash
ADB=/caminho/para/adb scrcpy
```
Para sobrepor o caminho do arquivo `scrcpy-server`, configure seu caminho em Para sobrepor o caminho do arquivo `scrcpy-server`, configure seu caminho em
`SCRCPY_SERVER_PATH`. `SCRCPY_SERVER_PATH`.
@ -478,7 +829,7 @@ Para sobrepor o caminho do arquivo `scrcpy-server`, configure seu caminho em
## Por quê _scrcpy_? ## Por quê _scrcpy_?
Um colega me desafiou a encontrar um nome impronunciável como [gnirehtet]. Um colega me desafiou a encontrar um nome tão impronunciável quanto [gnirehtet].
[`strcpy`] copia uma **str**ing; `scrcpy` copia uma **scr**een. [`strcpy`] copia uma **str**ing; `scrcpy` copia uma **scr**een.
@ -490,17 +841,15 @@ Um colega me desafiou a encontrar um nome impronunciável como [gnirehtet].
Veja [BUILD]. Veja [BUILD].
[BUILD]: BUILD.md
## Problemas comuns ## Problemas comuns
Veja [FAQ](FAQ.md). Veja o [FAQ](FAQ.md).
## Desenvolvedores ## Desenvolvedores
Leia a [developers page]. Leia a [página dos desenvolvedores][developers page].
[developers page]: DEVELOP.md [developers page]: DEVELOP.md

743
README.sp.md Normal file
View File

@ -0,0 +1,743 @@
Solo se garantiza que el archivo [README](README.md) original esté actualizado.
# scrcpy (v1.17)
Esta aplicación proporciona imagen y control de un dispositivo Android conectado
por USB (o [por TCP/IP][article-tcpip]). No requiere acceso _root_.
Compatible con _GNU/Linux_, _Windows_ y _macOS_.
![screenshot](assets/screenshot-debian-600.jpg)
Sus características principales son:
- **ligero** (nativo, solo muestra la imagen del dispositivo)
- **desempeño** (30~60fps)
- **calidad** (1920×1080 o superior)
- **baja latencia** ([35~70ms][lowlatency])
- **corto tiempo de inicio** (~1 segundo para mostrar la primera imagen)
- **no intrusivo** (no se deja nada instalado en el dispositivo)
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
## Requisitos
El dispositivo Android requiere como mínimo API 21 (Android 5.0).
Asegurate de [habilitar el adb debugging][enable-adb] en tu(s) dispositivo(s).
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
En algunos dispositivos, también necesitas habilitar [una opción adicional][control] para controlarlo con el teclado y ratón.
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
## Consigue la app
<a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a>
### Resumen
- Linux: `apt install scrcpy`
- Windows: [download](README.md#windows)
- macOS: `brew install scrcpy`
Construir desde la fuente: [BUILD] ([proceso simplificado][BUILD_simple])
[BUILD]: BUILD.md
[BUILD_simple]: BUILD.md#simple
### Linux
En Debian (_test_ y _sid_ por ahora) y Ubuntu (20.04):
```
apt install scrcpy
```
Hay un paquete [Snap]: [`scrcpy`][snap-link].
[snap-link]: https://snapstats.org/snaps/scrcpy
[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager)
Para Fedora, hay un paquete [COPR]: [`scrcpy`][copr-link].
[COPR]: https://fedoraproject.org/wiki/Category:Copr
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
Para Arch Linux, hay un paquete [AUR]: [`scrcpy`][aur-link].
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
Para Gentoo, hay un paquete [Ebuild]: [`scrcpy/`][ebuild-link].
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
También puedes [construir la aplicación manualmente][BUILD] ([proceso simplificado][BUILD_simple]).
### Windows
Para Windows, por simplicidad, hay un pre-compilado con todas las dependencias
(incluyendo `adb`):
- [README](README.md#windows)
También está disponible en [Chocolatey]:
[Chocolatey]: https://chocolatey.org/
```bash
choco install scrcpy
choco install adb # si aún no está instalado
```
Y en [Scoop]:
```bash
scoop install scrcpy
scoop install adb # si aún no está instalado
```
[Scoop]: https://scoop.sh
También puedes [construir la aplicación manualmente][BUILD].
### macOS
La aplicación está disponible en [Homebrew]. Solo instalala:
[Homebrew]: https://brew.sh/
```bash
brew install scrcpy
```
Necesitarás `adb`, accesible desde `PATH`. Si aún no lo tienes:
```bash
brew install android-platform-tools
```
También está disponible en [MacPorts], que configurará el adb automáticamente:
```bash
sudo port install scrcpy
```
[MacPorts]: https://www.macports.org/
También puedes [construir la aplicación manualmente][BUILD].
## Ejecutar
Enchufa el dispositivo Android, y ejecuta:
```bash
scrcpy
```
Acepta argumentos desde la línea de comandos, listados en:
```bash
scrcpy --help
```
## Características
### Capturar configuración
#### Reducir la definición
A veces es útil reducir la definición de la imagen del dispositivo Android para aumentar el desempeño.
Para limitar el ancho y la altura a un valor específico (ej. 1024):
```bash
scrcpy --max-size 1024
scrcpy -m 1024 # versión breve
```
La otra dimensión es calculada para conservar el aspect ratio del dispositivo.
De esta forma, un dispositivo en 1920×1080 será transmitido a 1024×576.
#### Cambiar el bit-rate
El bit-rate por defecto es 8 Mbps. Para cambiar el bit-rate del video (ej. a 2 Mbps):
```bash
scrcpy --bit-rate 2M
scrcpy -b 2M # versión breve
```
#### Limitar los fps
El fps puede ser limitado:
```bash
scrcpy --max-fps 15
```
Es oficialmente soportado desde Android 10, pero puede funcionar en versiones anteriores.
#### Recortar
La imagen del dispositivo puede ser recortada para transmitir solo una parte de la pantalla.
Por ejemplo, puede ser útil para transmitir la imagen de un solo ojo del Oculus Go:
```bash
scrcpy --crop 1224:1440:0:0 # 1224x1440 con coordenadas de origen en (0,0)
```
Si `--max-size` también está especificado, el cambio de tamaño es aplicado después de cortar.
#### Fijar la rotación del video
Para fijar la rotación de la transmisión:
```bash
scrcpy --lock-video-orientation 0 # orientación normal
scrcpy --lock-video-orientation 1 # 90° contrarreloj
scrcpy --lock-video-orientation 2 # 180°
scrcpy --lock-video-orientation 3 # 90° sentido de las agujas del reloj
```
Esto afecta la rotación de la grabación.
La [ventana también puede ser rotada](#rotación) independientemente.
#### Codificador
Algunos dispositivos pueden tener más de una rotación, y algunos pueden causar problemas o errores. Es posible seleccionar un codificador diferente:
```bash
scrcpy --encoder OMX.qcom.video.encoder.avc
```
Para listar los codificadores disponibles, puedes pasar un nombre de codificador inválido, el error te dará los codificadores disponibles:
```bash
scrcpy --encoder _
```
### Grabación
Es posible grabar la pantalla mientras se transmite:
```bash
scrcpy --record file.mp4
scrcpy -r file.mkv
```
Para grabar sin transmitir la pantalla:
```bash
scrcpy --no-display --record file.mp4
scrcpy -Nr file.mkv
# interrumpe la grabación con Ctrl+C
```
"Skipped frames" son grabados, incluso si no son mostrados en tiempo real (por razones de desempeño). Los frames tienen _marcas de tiempo_ en el dispositivo, por lo que el "[packet delay
variation]" no impacta el archivo grabado.
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
### Conexión
#### Inalámbrica
_Scrcpy_ usa `adb` para comunicarse con el dispositivo, y `adb` puede [conectarse] vía TCP/IP:
1. Conecta el dispositivo al mismo Wi-Fi que tu computadora.
2. Obtén la dirección IP del dispositivo, en Ajustes → Acerca del dispositivo → Estado, o ejecutando este comando:
```bash
adb shell ip route | awk '{print $9}'
```
3. Habilita adb vía TCP/IP en el dispositivo: `adb tcpip 5555`.
4. Desenchufa el dispositivo.
5. Conéctate a tu dispositivo: `adb connect IP_DEL_DISPOSITIVO:5555` _(reemplaza `IP_DEL_DISPOSITIVO`)_.
6. Ejecuta `scrcpy` con normalidad.
Podría resultar útil reducir el bit-rate y la definición:
```bash
scrcpy --bit-rate 2M --max-size 800
scrcpy -b2M -m800 # versión breve
```
[conectarse]: https://developer.android.com/studio/command-line/adb.html#wireless
#### Múltiples dispositivos
Si hay muchos dispositivos listados en `adb devices`, será necesario especificar el _número de serie_:
```bash
scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # versión breve
```
Si el dispositivo está conectado por TCP/IP:
```bash
scrcpy --serial 192.168.0.1:5555
scrcpy -s 192.168.0.1:5555 # versión breve
```
Puedes iniciar múltiples instancias de _scrcpy_ para múltiples dispositivos.
#### Autoiniciar al detectar dispositivo
Puedes utilizar [AutoAdb]:
```bash
autoadb scrcpy -s '{}'
```
[AutoAdb]: https://github.com/rom1v/autoadb
#### Túnel SSH
Para conectarse a un dispositivo remoto, es posible conectar un cliente local de `adb` a un servidor remoto `adb` (siempre y cuando utilicen la misma versión de protocolos _adb_):
```bash
adb kill-server # cierra el servidor local adb en 5037
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
# conserva este servidor abierto
```
Desde otra terminal:
```bash
scrcpy
```
Para evitar habilitar "remote port forwarding", puedes forzar una "forward connection" (nótese el argumento `-L` en vez de `-R`):
```bash
adb kill-server # cierra el servidor local adb en 5037
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
# conserva este servidor abierto
```
Desde otra terminal:
```bash
scrcpy --force-adb-forward
```
Al igual que las conexiones inalámbricas, puede resultar útil reducir la calidad:
```
scrcpy -b2M -m800 --max-fps 15
```
### Configuración de la ventana
#### Título
Por defecto, el título de la ventana es el modelo del dispositivo. Puede ser modificado:
```bash
scrcpy --window-title 'My device'
```
#### Posición y tamaño
La posición y tamaño inicial de la ventana puede ser especificado:
```bash
scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
```
#### Sin bordes
Para deshabilitar el diseño de la ventana:
```bash
scrcpy --window-borderless
```
#### Siempre adelante
Para mantener la ventana de scrcpy siempre adelante:
```bash
scrcpy --always-on-top
```
#### Pantalla completa
La aplicación puede ser iniciada en pantalla completa:
```bash
scrcpy --fullscreen
scrcpy -f # versión breve
```
Puede entrar y salir de la pantalla completa con la combinación <kbd>MOD</kbd>+<kbd>f</kbd>.
#### Rotación
Se puede rotar la ventana:
```bash
scrcpy --rotation 1
```
Los valores posibles son:
- `0`: sin rotación
- `1`: 90 grados contrarreloj
- `2`: 180 grados
- `3`: 90 grados en sentido de las agujas del reloj
La rotación también puede ser modificada con la combinación de teclas <kbd>MOD</kbd>+<kbd>←</kbd> _(izquierda)_ y <kbd>MOD</kbd>+<kbd>→</kbd> _(derecha)_.
Nótese que _scrcpy_ maneja 3 diferentes rotaciones:
- <kbd>MOD</kbd>+<kbd>r</kbd> solicita al dispositivo cambiar entre vertical y horizontal (la aplicación en uso puede rechazarlo si no soporta la orientación solicitada).
- [`--lock-video-orientation`](#fijar-la-rotación-del-video) cambia la rotación de la transmisión (la orientación del video enviado a la PC). Esto afecta a la grabación.
- `--rotation` (o <kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>) rota solo el contenido de la imagen. Esto solo afecta a la imagen mostrada, no a la grabación.
### Otras opciones menores
#### Solo lectura ("Read-only")
Para deshabilitar los controles (todo lo que interactúe con el dispositivo: eventos del teclado, eventos del mouse, arrastrar y soltar archivos):
```bash
scrcpy --no-control
scrcpy -n # versión breve
```
#### Pantalla
Si múltiples pantallas están disponibles, es posible elegir cual transmitir:
```bash
scrcpy --display 1
```
Los ids de las pantallas se pueden obtener con el siguiente comando:
```bash
adb shell dumpsys display # busque "mDisplayId=" en la respuesta
```
La segunda pantalla solo puede ser manejada si el dispositivo cuenta con Android 10 (en caso contrario será transmitida en el modo solo lectura).
#### Permanecer activo
Para evitar que el dispositivo descanse después de un tiempo mientras está conectado:
```bash
scrcpy --stay-awake
scrcpy -w # versión breve
```
La configuración original se restaura al cerrar scrcpy.
#### Apagar la pantalla
Es posible apagar la pantalla mientras se transmite al iniciar con el siguiente comando:
```bash
scrcpy --turn-screen-off
scrcpy -S # versión breve
```
O presionando <kbd>MOD</kbd>+<kbd>o</kbd> en cualquier momento.
Para volver a prenderla, presione <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>.
En Android, el botón de `POWER` siempre prende la pantalla. Por conveniencia, si `POWER` es enviado vía scrcpy (con click-derecho o <kbd>MOD</kbd>+<kbd>p</kbd>), esto forzará a apagar la pantalla con un poco de atraso (en la mejor de las situaciones). El botón físico `POWER` seguirá prendiendo la pantalla.
También puede resultar útil para evitar que el dispositivo entre en inactividad:
```bash
scrcpy --turn-screen-off --stay-awake
scrcpy -Sw # versión breve
```
#### Renderizar frames vencidos
Por defecto, para minimizar la latencia, _scrcpy_ siempre renderiza el último frame disponible decodificado, e ignora cualquier frame anterior.
Para forzar el renderizado de todos los frames (a costo de posible aumento de latencia), use:
```bash
scrcpy --render-expired-frames
```
#### Mostrar clicks
Para presentaciones, puede resultar útil mostrar los clicks físicos (en el dispositivo físicamente).
Android provee esta opción en _Opciones para desarrolladores_.
_Scrcpy_ provee una opción para habilitar esta función al iniciar la aplicación y restaurar el valor original al salir:
```bash
scrcpy --show-touches
scrcpy -t # versión breve
```
Nótese que solo muestra los clicks _físicos_ (con el dedo en el dispositivo).
#### Desactivar protector de pantalla
Por defecto, scrcpy no evita que el protector de pantalla se active en la computadora.
Para deshabilitarlo:
```bash
scrcpy --disable-screensaver
```
### Control
#### Rotar pantalla del dispositivo
Presione <kbd>MOD</kbd>+<kbd>r</kbd> para cambiar entre posición vertical y horizontal.
Nótese que solo rotará si la aplicación activa soporta la orientación solicitada.
#### Copiar y pegar
Cuando que el portapapeles de Android cambia, automáticamente se sincroniza al portapapeles de la computadora.
Cualquier shortcut con <kbd>Ctrl</kbd> es enviado al dispositivo. En particular:
- <kbd>Ctrl</kbd>+<kbd>c</kbd> normalmente copia
- <kbd>Ctrl</kbd>+<kbd>x</kbd> normalmente corta
- <kbd>Ctrl</kbd>+<kbd>v</kbd> normalmente pega (después de la sincronización de portapapeles entre la computadora y el dispositivo)
Esto normalmente funciona como es esperado.
Sin embargo, este comportamiento depende de la aplicación en uso. Por ejemplo, _Termux_ envía SIGINT con <kbd>Ctrl</kbd>+<kbd>c</kbd>, y _K-9 Mail_ crea un nuevo mensaje.
Para copiar, cortar y pegar, en tales casos (solo soportado en Android >= 7):
- <kbd>MOD</kbd>+<kbd>c</kbd> inyecta `COPY`
- <kbd>MOD</kbd>+<kbd>x</kbd> inyecta `CUT`
- <kbd>MOD</kbd>+<kbd>v</kbd> inyecta `PASTE` (después de la sincronización de portapapeles entre la computadora y el dispositivo)
Además, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> permite inyectar el texto en el portapapeles de la computadora como una secuencia de teclas. Esto es útil cuando el componente no acepta pegado de texto (por ejemplo en _Termux_), pero puede romper caracteres no pertenecientes a ASCII.
**AVISO:** Pegar de la computadora al dispositivo (tanto con <kbd>Ctrl</kbd>+<kbd>v</kbd> o <kbd>MOD</kbd>+<kbd>v</kbd>) copia el contenido al portapapeles del dispositivo. Como consecuencia, cualquier aplicación de Android puede leer su contenido. Debería evitar pegar contenido sensible (como contraseñas) de esta forma.
Algunos dispositivos no se comportan como es esperado al establecer el portapapeles programáticamente. La opción `--legacy-paste` está disponible para cambiar el comportamiento de <kbd>Ctrl</kbd>+<kbd>v</kbd> y <kbd>MOD</kbd>+<kbd>v</kbd> para que también inyecten el texto del portapapeles de la computadora como una secuencia de teclas (de la misma forma que <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
#### Pellizcar para zoom
Para simular "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-y-mover_.
Más precisamente, mantén <kbd>Ctrl</kbd> mientras presionas botón izquierdo. Hasta que no se suelte el botón, todos los movimientos del mouse cambiarán el tamaño y rotación del contenido (si es soportado por la app en uso) respecto al centro de la pantalla.
Concretamente, scrcpy genera clicks adicionales con un "dedo virtual" en la posición invertida respecto al centro de la pantalla.
#### Preferencias de inyección de texto
Existen dos tipos de [eventos][textevents] generados al escribir texto:
- _key events_, marcando si la tecla es presionada o soltada;
- _text events_, marcando si un texto fue introducido.
Por defecto, las letras son inyectadas usando _key events_, para que el teclado funcione como es esperado en juegos (típicamente las teclas WASD).
Pero esto puede [causar problemas][prefertext]. Si encuentras tales problemas, los puedes evitar con:
```bash
scrcpy --prefer-text
```
(Pero esto romperá el comportamiento del teclado en los juegos)
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
#### Repetir tecla
Por defecto, mantener una tecla presionada genera múltiples _key events_. Esto puede causar problemas de desempeño en algunos juegos, donde estos eventos no tienen sentido de todos modos.
Para evitar enviar _key events_ repetidos:
```bash
scrcpy --no-key-repeat
```
#### Botón derecho y botón del medio
Por defecto, botón derecho ejecuta RETROCEDER (o ENCENDIDO) y botón del medio INICIO. Para inhabilitar estos atajos y enviar los clicks al dispositivo:
```bash
scrcpy --forward-all-clicks
```
### Arrastrar y soltar archivos
#### Instalar APKs
Para instalar un APK, arrastre y suelte el archivo APK (terminado en `.apk`) a la ventana de _scrcpy_.
No hay respuesta visual, un mensaje se escribirá en la consola.
#### Enviar archivos al dispositivo
Para enviar un archivo a `/sdcard/` en el dispositivo, arrastre y suelte un archivo (no APK) a la ventana de _scrcpy_.
No hay respuesta visual, un mensaje se escribirá en la consola.
El directorio de destino puede ser modificado al iniciar:
```bash
scrcpy --push-target=/sdcard/Download/
```
### Envío de Audio
_Scrcpy_ no envía el audio. Use [sndcpy].
También lea [issue #14].
[sndcpy]: https://github.com/rom1v/sndcpy
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14
## Atajos
En la siguiente lista, <kbd>MOD</kbd> es el atajo modificador. Por defecto es <kbd>Alt</kbd> (izquierdo) o <kbd>Super</kbd> (izquierdo).
Se puede modificar usando `--shortcut-mod`. Las posibles teclas son `lctrl` (izquierdo), `rctrl` (derecho), `lalt` (izquierdo), `ralt` (derecho), `lsuper` (izquierdo) y `rsuper` (derecho). Por ejemplo:
```bash
# use RCtrl para los atajos
scrcpy --shortcut-mod=rctrl
# use tanto LCtrl+LAlt o LSuper para los atajos
scrcpy --shortcut-mod=lctrl+lalt,lsuper
```
_<kbd>[Super]</kbd> es generalmente la tecla <kbd>Windows</kbd> o <kbd>Cmd</kbd>._
[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button)
| Acción | Atajo
| ------------------------------------------- |:-----------------------------
| Alterne entre pantalla compelta | <kbd>MOD</kbd>+<kbd>f</kbd>
| Rotar pantalla hacia la izquierda | <kbd>MOD</kbd>+<kbd>←</kbd> _(izquierda)_
| Rotar pantalla hacia la derecha | <kbd>MOD</kbd>+<kbd>→</kbd> _(derecha)_
| Ajustar ventana a 1:1 ("pixel-perfect") | <kbd>MOD</kbd>+<kbd>g</kbd>
| Ajustar ventana para quitar los bordes negros| <kbd>MOD</kbd>+<kbd>w</kbd> \| _Doble click¹_
| Click en `INICIO` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Botón del medio_
| Click en `RETROCEDER` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Botón derecho²_
| Click en `CAMBIAR APLICACIÓN` | <kbd>MOD</kbd>+<kbd>s</kbd>
| Click en `MENÚ` (desbloquear pantalla) | <kbd>MOD</kbd>+<kbd>m</kbd>
| Click en `SUBIR VOLUMEN` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(arriba)_
| Click en `BAJAR VOLUME` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(abajo)_
| Click en `ENCENDIDO` | <kbd>MOD</kbd>+<kbd>p</kbd>
| Encendido | _Botón derecho²_
| Apagar pantalla (manteniendo la transmisión)| <kbd>MOD</kbd>+<kbd>o</kbd>
| Encender pantalla | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
| Rotar pantalla del dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd>
| Abrir panel de notificaciones | <kbd>MOD</kbd>+<kbd>n</kbd>
| Cerrar panel de notificaciones | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| Copiar al portapapeles³ | <kbd>MOD</kbd>+<kbd>c</kbd>
| Cortar al portapapeles³ | <kbd>MOD</kbd>+<kbd>x</kbd>
| Synchronizar portapapeles y pegar³ | <kbd>MOD</kbd>+<kbd>v</kbd>
| inyectar texto del portapapeles de la PC | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| Habilitar/Deshabilitar contador de FPS (en stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| Pellizcar para zoom | <kbd>Ctrl</kbd>+_click-y-mover_
_¹Doble click en los bordes negros para eliminarlos._
_²Botón derecho enciende la pantalla si estaba apagada, sino ejecuta RETROCEDER._
_³Solo en Android >= 7._
Todos los atajos <kbd>Ctrl</kbd>+_tecla_ son enviados al dispositivo para que sean manejados por la aplicación activa.
## Path personalizado
Para usar un binario de _adb_ en particular, configure el path `ADB` en las variables de entorno:
```bash
ADB=/path/to/adb scrcpy
```
Para sobreescribir el path del archivo `scrcpy-server`, configure el path en `SCRCPY_SERVER_PATH`.
## ¿Por qué _scrcpy_?
Un colega me retó a encontrar un nombre tan impronunciable como [gnirehtet].
[`strcpy`] copia un **str**ing; `scrcpy` copia un **scr**een.
[gnirehtet]: https://github.com/Genymobile/gnirehtet
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
## ¿Cómo construir (BUILD)?
Véase [BUILD] (en inglés).
## Problemas generales
Vea las [preguntas frecuentes (en inglés)](FAQ.md).
## Desarrolladores
Lea la [hoja de desarrolladores (en inglés)](DEVELOP.md).
## Licencia
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
## Artículos
- [Introducing scrcpy][article-intro] (en inglés)
- [Scrcpy now works wirelessly][article-tcpip] (en inglés)
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/

824
README.tr.md Normal file
View File

@ -0,0 +1,824 @@
# scrcpy (v1.18)
Bu uygulama Android cihazların USB (ya da [TCP/IP][article-tcpip]) üzerinden
görüntülenmesini ve kontrol edilmesini sağlar. _root_ erişimine ihtiyaç duymaz.
_GNU/Linux_, _Windows_ ve _macOS_ sistemlerinde çalışabilir.
![screenshot](assets/screenshot-debian-600.jpg)
Öne çıkan özellikler:
- **hafiflik** (doğal, sadece cihazın ekranını gösterir)
- **performans** (30~60fps)
- **kalite** (1920×1080 ya da üzeri)
- **düşük gecikme süresi** ([35~70ms][lowlatency])
- **düşük başlangıç süresi** (~1 saniye ilk kareyi gösterme süresi)
- **müdaheleci olmama** (cihazda kurulu yazılım kalmaz)
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
## Gereksinimler
Android cihaz en düşük API 21 (Android 5.0) olmalıdır.
[Adb hata ayıklamasının][enable-adb] cihazınızda aktif olduğundan emin olun.
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
Bazı cihazlarda klavye ve fare ile kontrol için [ilave bir seçenek][control] daha
etkinleştirmeniz gerekebilir.
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
## Uygulamayı indirin
<a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a>
### Özet
- Linux: `apt install scrcpy`
- Windows: [indir][direct-win64]
- macOS: `brew install scrcpy`
Kaynak kodu derle: [BUILD] ([basitleştirilmiş süreç][build_simple])
[build]: BUILD.md
[build_simple]: BUILD.md#simple
### Linux
Debian (şimdilik _testing_ ve _sid_) ve Ubuntu (20.04) için:
```
apt install scrcpy
```
[Snap] paketi: [`scrcpy`][snap-link].
[snap-link]: https://snapstats.org/snaps/scrcpy
[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager)
Fedora için, [COPR] paketi: [`scrcpy`][copr-link].
[copr]: https://fedoraproject.org/wiki/Category:Copr
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
Arch Linux için, [AUR] paketi: [`scrcpy`][aur-link].
[aur]: https://wiki.archlinux.org/index.php/Arch_User_Repository
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
Gentoo için, [Ebuild] mevcut: [`scrcpy/`][ebuild-link].
[ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
Ayrıca [uygulamayı el ile de derleyebilirsiniz][build] ([basitleştirilmiş süreç][build_simple]).
### Windows
Windows için (`adb` dahil) tüm gereksinimleri ile derlenmiş bir arşiv mevcut:
- [README](README.md#windows)
[Chocolatey] ile kurulum:
[chocolatey]: https://chocolatey.org/
```bash
choco install scrcpy
choco install adb # if you don't have it yet
```
[Scoop] ile kurulum:
```bash
scoop install scrcpy
scoop install adb # if you don't have it yet
```
[scoop]: https://scoop.sh
Ayrıca [uygulamayı el ile de derleyebilirsiniz][build].
### macOS
Uygulama [Homebrew] içerisinde mevcut. Sadece kurun:
[homebrew]: https://brew.sh/
```bash
brew install scrcpy
```
`adb`, `PATH` içerisinden erişilebilir olmalıdır. Eğer değilse:
```bash
brew install android-platform-tools
```
[MacPorts] kullanılarak adb ve uygulamanın birlikte kurulumu yapılabilir:
```bash
sudo port install scrcpy
```
[macports]: https://www.macports.org/
Ayrıca [uygulamayı el ile de derleyebilirsiniz][build].
## Çalıştırma
Android cihazınızı bağlayın ve aşağıdaki komutu çalıştırın:
```bash
scrcpy
```
Komut satırı argümanları aşağıdaki komut ile listelenebilir:
```bash
scrcpy --help
```
## Özellikler
### Ekran yakalama ayarları
#### Boyut azaltma
Bazen, Android cihaz ekranını daha düşük seviyede göstermek performansı artırabilir.
Hem genişliği hem de yüksekliği bir değere sabitlemek için (ör. 1024):
```bash
scrcpy --max-size 1024
scrcpy -m 1024 # kısa versiyon
```
Diğer boyut en-boy oranı korunacak şekilde hesaplanır.
Bu şekilde ekran boyutu 1920x1080 olan bir cihaz 1024x576 olarak görünür.
#### Bit-oranı değiştirme
Varsayılan bit-oranı 8 Mbps'dir. Değiştirmek için (ör. 2 Mbps):
```bash
scrcpy --bit-rate 2M
scrcpy -b 2M # kısa versiyon
```
#### Çerçeve oranı sınırlama
Ekran yakalama için maksimum çerçeve oranı için sınır koyulabilir:
```bash
scrcpy --max-fps 15
```
Bu özellik Android 10 ve sonrası sürümlerde resmi olarak desteklenmektedir,
ancak daha önceki sürümlerde çalışmayabilir.
#### Kesme
Cihaz ekranının sadece bir kısmı görünecek şekilde kesilebilir.
Bu özellik Oculus Go'nun bir gözünü yakalamak gibi durumlarda kullanışlı olur:
```bash
scrcpy --crop 1224:1440:0:0 # (0,0) noktasından 1224x1440
```
Eğer `--max-size` belirtilmişse yeniden boyutlandırma kesme işleminden sonra yapılır.
#### Video yönünü kilitleme
Videonun yönünü kilitlemek için:
```bash
scrcpy --lock-video-orientation # başlangıç yönü
scrcpy --lock-video-orientation=0 # doğal yön
scrcpy --lock-video-orientation=1 # 90° saatin tersi yönü
scrcpy --lock-video-orientation=2 # 180°
scrcpy --lock-video-orientation=3 # 90° saat yönü
```
Bu özellik kaydetme yönünü de etkiler.
[Pencere ayrı olarak döndürülmüş](#rotation) olabilir.
#### Kodlayıcı
Bazı cihazlar birden fazla kodlayıcıya sahiptir, ve bunların bazıları programın
kapanmasına sebep olabilir. Bu durumda farklı bir kodlayıcı seçilebilir:
```bash
scrcpy --encoder OMX.qcom.video.encoder.avc
```
Mevcut kodlayıcıları listelemek için geçerli olmayan bir kodlayıcı ismi girebilirsiniz,
hata mesajı mevcut kodlayıcıları listeleyecektir:
```bash
scrcpy --encoder _
```
### Yakalama
#### Kaydetme
Ekran yakalama sırasında kaydedilebilir:
```bash
scrcpy --record file.mp4
scrcpy -r file.mkv
```
Yakalama olmadan kayıt için:
```bash
scrcpy --no-display --record file.mp4
scrcpy -Nr file.mkv
# Ctrl+C ile kayıt kesilebilir
```
"Atlanan kareler" gerçek zamanlı olarak gösterilmese (performans sebeplerinden ötürü) dahi kaydedilir.
Kareler cihazda _zamandamgası_ ile saklanır, bu sayede [paket gecikme varyasyonu]
kayıt edilen dosyayı etkilemez.
[paket gecikme varyasyonu]: https://en.wikipedia.org/wiki/Packet_delay_variation
#### v4l2loopback
Linux'ta video akışı bir v4l2 loopback cihazına gönderilebilir. Bu sayede Android
cihaz bir web kamerası gibi davranabilir.
Bu işlem için `v4l2loopback` modülü kurulu olmalıdır:
```bash
sudo apt install v4l2loopback-dkms
```
v4l2 cihazı oluşturmak için:
```bash
sudo modprobe v4l2loopback
```
Bu komut `/dev/videoN` adresinde `N` yerine bir tamsayı koyarak yeni bir video
cihazı oluşturacaktır.
(birden fazla cihaz oluşturmak veya spesifik ID'ye sahip cihazlar için
diğer [seçenekleri](https://github.com/umlaeute/v4l2loopback#options) inceleyebilirsiniz.)
Aktif cihazları listelemek için:
```bash
# v4l-utils paketi ile
v4l2-ctl --list-devices
# daha basit ama yeterli olabilecek şekilde
ls /dev/video*
```
v4l2 kullanarak scrpy kullanmaya başlamak için:
```bash
scrcpy --v4l2-sink=/dev/videoN
scrcpy --v4l2-sink=/dev/videoN --no-display # ayna penceresini kapatarak
scrcpy --v4l2-sink=/dev/videoN -N # kısa versiyon
```
(`N` harfini oluşturulan cihaz ID numarası ile değiştirin. `ls /dev/video*` cihaz ID'lerini görebilirsiniz.)
Aktifleştirildikten sonra video akışını herhangi bir v4l2 özellikli araçla açabilirsiniz:
```bash
ffplay -i /dev/videoN
vlc v4l2:///dev/videoN # VLC kullanırken yükleme gecikmesi olabilir
```
Örneğin, [OBS] ile video akışını kullanabilirsiniz.
[obs]: https://obsproject.com/
### Bağlantı
#### Kablosuz
_Scrcpy_ cihazla iletişim kurmak için `adb`'yi kullanır, Ve `adb`
bir cihaza TCP/IP kullanarak [bağlanabilir].
1. Cihazınızı bilgisayarınızla aynı Wi-Fi ağına bağlayın.
2. Cihazınızın IP adresini bulun. Ayarlar → Telefon Hakkında → Durum sekmesinden veya
aşağıdaki komutu çalıştırarak öğrenebilirsiniz:
```bash
adb shell ip route | awk '{print $9}'
```
3. Cihazınızda TCP/IP üzerinden adb kullanımını etkinleştirin: `adb tcpip 5555`.
4. Cihazınızı bilgisayarınızdan sökün.
5. Cihazınıza bağlanın: `adb connect DEVICE_IP:5555` _(`DEVICE_IP` değerini değiştirin)_.
6. `scrcpy` komutunu normal olarak çalıştırın.
Bit-oranını ve büyüklüğü azaltmak yararlı olabilir:
```bash
scrcpy --bit-rate 2M --max-size 800
scrcpy -b2M -m800 # kısa version
```
[bağlanabilir]: https://developer.android.com/studio/command-line/adb.html#wireless
#### Birden fazla cihaz
Eğer `adb devices` komutu birden fazla cihaz listeliyorsa _serial_ değerini belirtmeniz gerekir:
```bash
scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # kısa versiyon
```
Eğer cihaz TCP/IP üzerinden bağlanmışsa:
```bash
scrcpy --serial 192.168.0.1:5555
scrcpy -s 192.168.0.1:5555 # kısa version
```
Birden fazla cihaz için birden fazla _scrcpy_ uygulaması çalıştırabilirsiniz.
#### Cihaz bağlantısı ile otomatik başlatma
[AutoAdb] ile yapılabilir:
```bash
autoadb scrcpy -s '{}'
```
[autoadb]: https://github.com/rom1v/autoadb
#### SSH Tünel
Uzaktaki bir cihaza erişmek için lokal `adb` istemcisi, uzaktaki bir `adb` sunucusuna
(aynı _adb_ sürümünü kullanmak şartı ile) bağlanabilir :
```bash
adb kill-server # 5037 portunda çalışan lokal adb sunucusunu kapat
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
# bunu açık tutun
```
Başka bir terminalde:
```bash
scrcpy
```
Uzaktan port yönlendirme ileri yönlü bağlantı kullanabilirsiniz
(`-R` yerine `-L` olduğuna dikkat edin):
```bash
adb kill-server # 5037 portunda çalışan lokal adb sunucusunu kapat
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
# bunu açık tutun
```
Başka bir terminalde:
```bash
scrcpy --force-adb-forward
```
Kablosuz bağlantı gibi burada da kalite düşürmek faydalı olabilir:
```
scrcpy -b2M -m800 --max-fps 15
```
### Pencere ayarları
#### İsim
Cihaz modeli varsayılan pencere ismidir. Değiştirmek için:
```bash
scrcpy --window-title 'Benim cihazım'
```
#### Konum ve
Pencerenin başlangıç konumu ve boyutu belirtilebilir:
```bash
scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
```
#### Kenarlıklar
Pencere dekorasyonunu kapatmak için:
```bash
scrcpy --window-borderless
```
#### Her zaman üstte
Scrcpy penceresini her zaman üstte tutmak için:
```bash
scrcpy --always-on-top
```
#### Tam ekran
Uygulamayı tam ekran başlatmak için:
```bash
scrcpy --fullscreen
scrcpy -f # kısa versiyon
```
Tam ekran <kbd>MOD</kbd>+<kbd>f</kbd> ile dinamik olarak değiştirilebilir.
#### Döndürme
Pencere döndürülebilir:
```bash
scrcpy --rotation 1
```
Seçilebilecek değerler:
- `0`: döndürme yok
- `1`: 90 derece saat yönünün tersi
- `2`: 180 derece
- `3`: 90 derece saat yönü
Döndürme <kbd>MOD</kbd>+<kbd>←</kbd>_(sol)_ ve
<kbd>MOD</kbd>+<kbd>→</kbd> _(sağ)_ ile dinamik olarak değiştirilebilir.
_scrcpy_'de 3 farklı döndürme olduğuna dikkat edin:
- <kbd>MOD</kbd>+<kbd>r</kbd> cihazın yatay veya dikey modda çalışmasını sağlar.
(çalışan uygulama istenilen oryantasyonda çalışmayı desteklemiyorsa döndürme
işlemini reddedebilir.)
- [`--lock-video-orientation`](#lock-video-orientation) görüntü yakalama oryantasyonunu
(cihazdan bilgisayara gelen video akışının oryantasyonu) değiştirir. Bu kayıt işlemini
etkiler.
- `--rotation` (or <kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>)
pencere içeriğini dönderir. Bu sadece canlı görüntüyü etkiler, kayıt işlemini etkilemez.
### Diğer ekran yakalama seçenekleri
#### Yazma korumalı
Kontrolleri devre dışı bırakmak için (cihazla etkileşime geçebilecek her şey: klavye ve
fare girdileri, dosya sürükleyip bırakma):
```bash
scrcpy --no-control
scrcpy -n
```
#### Ekran
Eğer cihazın birden fazla ekranı varsa hangi ekranın kullanılacağını seçebilirsiniz:
```bash
scrcpy --display 1
```
Kullanılabilecek ekranları listelemek için:
```bash
adb shell dumpsys display # çıktı içerisinde "mDisplayId=" terimini arayın
```
İkinci ekran ancak cihaz Android sürümü 10 veya üzeri olmalıdır (değilse yazma korumalı
olarak görüntülenir).
#### Uyanık kalma
Cihazın uyku moduna girmesini engellemek için:
```bash
scrcpy --stay-awake
scrcpy -w
```
scrcpy kapandığında cihaz başlangıç durumuna geri döner.
#### Ekranı kapatma
Ekran yakalama sırasında cihazın ekranı kapatılabilir:
```bash
scrcpy --turn-screen-off
scrcpy -S
```
Ya da <kbd>MOD</kbd>+<kbd>o</kbd> kısayolunu kullanabilirsiniz.
Tekrar açmak için ise <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd> tuşlarına basın.
Android'de, `GÜÇ` tuşu her zaman ekranı açar. Eğer `GÜÇ` sinyali scrcpy ile
gönderilsiyse (sağ tık veya <kbd>MOD</kbd>+<kbd>p</kbd>), ekran kısa bir gecikme
ile kapanacaktır. Fiziksel `GÜÇ` tuşuna basmak hala ekranın açılmasına sebep olacaktır.
Bu cihazın uykuya geçmesini engellemek için kullanılabilir:
```bash
scrcpy --turn-screen-off --stay-awake
scrcpy -Sw
```
#### Dokunuşları gösterme
Sunumlar sırasında fiziksel dokunuşları (fiziksel cihazdaki) göstermek
faydalı olabilir.
Android'de bu özellik _Geliştici seçenekleri_ içerisinde bulunur.
_Scrcpy_ bu özelliği çalışırken etkinleştirebilir ve kapanırken eski
haline geri getirebilir:
```bash
scrcpy --show-touches
scrcpy -t
```
Bu opsiyon sadece _fiziksel_ dokunuşları (cihaz ekranındaki) gösterir.
#### Ekran koruyucuyu devre dışı bırakma
Scrcpy varsayılan ayarlarında ekran koruyucuyu devre dışı bırakmaz.
Bırakmak için:
```bash
scrcpy --disable-screensaver
```
### Girdi kontrolü
#### Cihaz ekranını dönderme
<kbd>MOD</kbd>+<kbd>r</kbd> tuşları ile yatay ve dikey modlar arasında
geçiş yapabilirsiniz.
Bu kısayol ancak çalışan uygulama desteklediği takdirde ekranı döndürecektir.
#### Kopyala yapıştır
Ne zaman Android cihazdaki pano değişse bilgisayardaki pano otomatik olarak
senkronize edilir.
Tüm <kbd>Ctrl</kbd> kısayolları cihaza iletilir:
- <kbd>Ctrl</kbd>+<kbd>c</kbd> genelde kopyalar
- <kbd>Ctrl</kbd>+<kbd>x</kbd> genelde keser
- <kbd>Ctrl</kbd>+<kbd>v</kbd> genelde yapıştırır (bilgisayar ve cihaz arasındaki
pano senkronizasyonundan sonra)
Bu kısayollar genelde beklediğiniz gibi çalışır.
Ancak kısayolun gerçekten yaptığı eylemi açık olan uygulama belirler.
Örneğin, _Termux_ <kbd>Ctrl</kbd>+<kbd>c</kbd> ile kopyalama yerine
SIGINT sinyali gönderir, _K-9 Mail_ ise yeni mesaj oluşturur.
Bu tip durumlarda kopyalama, kesme ve yapıştırma için (Android versiyon 7 ve
üstü):
- <kbd>MOD</kbd>+<kbd>c</kbd> `KOPYALA`
- <kbd>MOD</kbd>+<kbd>x</kbd> `KES`
- <kbd>MOD</kbd>+<kbd>v</kbd> `YAPIŞTIR` (bilgisayar ve cihaz arasındaki
pano senkronizasyonundan sonra)
Bunlara ek olarak, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> tuşları
bilgisayar pano içeriğini tuş basma eylemleri şeklinde gönderir. Bu metin
yapıştırmayı desteklemeyen (_Termux_ gibi) uygulamar için kullanışlıdır,
ancak ASCII olmayan içerikleri bozabilir.
**UYARI:** Bilgisayar pano içeriğini cihaza yapıştırmak
(<kbd>Ctrl</kbd>+<kbd>v</kbd> ya da <kbd>MOD</kbd>+<kbd>v</kbd> tuşları ile)
içeriği cihaz panosuna kopyalar. Sonuç olarak, herhangi bir Android uygulaması
içeriğe erişebilir. Hassas içerikler (parolalar gibi) için bu özelliği kullanmaktan
kaçının.
Bazı cihazlar pano değişikleri konusunda beklenilen şekilde çalışmayabilir.
Bu durumlarda `--legacy-paste` argümanı kullanılabilir. Bu sayede
<kbd>Ctrl</kbd>+<kbd>v</kbd> ve <kbd>MOD</kbd>+<kbd>v</kbd> tuşları da
pano içeriğini tuş basma eylemleri şeklinde gönderir
(<kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> ile aynı şekilde).
#### İki parmak ile yakınlaştırma
"İki parmak ile yakınlaştırma" için: <kbd>Ctrl</kbd>+_tıkla-ve-sürükle_.
Daha açıklayıcı şekilde, <kbd>Ctrl</kbd> tuşuna sol-tık ile birlikte basılı
tutun. Sol-tık serbest bırakılıncaya kadar yapılan tüm fare hareketleri
ekran içeriğini ekranın merkezini baz alarak dönderir, büyütür veya küçültür
(eğer uygulama destekliyorsa).
Scrcpy ekranın merkezinde bir "sanal parmak" varmış gibi davranır.
#### Metin gönderme tercihi
Metin girilirken ili çeşit [eylem][textevents] gerçekleştirilir:
- _tuş eylemleri_, bir tuşa basıldığı sinyalini verir;
- _metin eylemleri_, bir metin girildiği sinyalini verir.
Varsayılan olarak, harfler tuş eylemleri kullanılarak gönderilir. Bu sayede
klavye oyunlarda beklenilene uygun olarak çalışır (Genelde WASD tuşları).
Ancak bu [bazı problemlere][prefertext] yol açabilir. Eğer bu problemler ile
karşılaşırsanız metin eylemlerini tercih edebilirsiniz:
```bash
scrcpy --prefer-text
```
(Ama bu oyunlardaki klavye davranışlarını bozacaktır)
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
#### Tuş tekrarı
Varsayılan olarak, bir tuşa basılı tutmak tuş eylemini tekrarlar. Bu durum
bazı oyunlarda problemlere yol açabilir.
Tuş eylemlerinin tekrarını kapatmak için:
```bash
scrcpy --no-key-repeat
```
#### Sağ-tık ve Orta-tık
Varsayılan olarak, sağ-tık GERİ (ya da GÜÇ açma) eylemlerini, orta-tık ise
ANA EKRAN eylemini tetikler. Bu kısayolları devre dışı bırakmak için:
```bash
scrcpy --forward-all-clicks
```
### Dosya bırakma
#### APK kurulumu
APK kurmak için, bilgisayarınızdaki APK dosyasını (`.apk` ile biten) _scrcpy_
penceresine sürükleyip bırakın.
Bu eylem görsel bir geri dönüt oluşturmaz, konsola log yazılır.
#### Dosyayı cihaza gönderme
Bir dosyayı cihazdaki `/sdcard/Download/` dizinine atmak için, (APK olmayan)
bir dosyayı _scrcpy_ penceresine sürükleyip bırakın.
Bu eylem görsel bir geri dönüt oluşturmaz, konsola log yazılır.
Hedef dizin uygulama başlatılırken değiştirilebilir:
```bash
scrcpy --push-target=/sdcard/Movies/
```
### Ses iletimi
_Scrcpy_ ses iletimi yapmaz. Yerine [sndcpy] kullanabilirsiniz.
Ayrıca bakınız [issue #14].
[sndcpy]: https://github.com/rom1v/sndcpy
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14
## Kısayollar
Aşağıdaki listede, <kbd>MOD</kbd> kısayol tamamlayıcısıdır. Varsayılan olarak
(sol) <kbd>Alt</kbd> veya (sol) <kbd>Super</kbd> tuşudur.
Bu tuş `--shortcut-mod` argümanı kullanılarak `lctrl`, `rctrl`,
`lalt`, `ralt`, `lsuper` ve `rsuper` tuşlarından biri ile değiştirilebilir.
Örneğin:
```bash
# Sağ Ctrl kullanmak için
scrcpy --shortcut-mod=rctrl
# Sol Ctrl, Sol Alt veya Sol Super tuşlarından birini kullanmak için
scrcpy --shortcut-mod=lctrl+lalt,lsuper
```
_<kbd>[Super]</kbd> tuşu genelde <kbd>Windows</kbd> veya <kbd>Cmd</kbd> tuşudur._
[super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button)
| Action | Shortcut |
| ------------------------------------------------ | :-------------------------------------------------------- |
| Tam ekran modunu değiştirme | <kbd>MOD</kbd>+<kbd>f</kbd> |
| Ekranı sola çevirme | <kbd>MOD</kbd>+<kbd>←</kbd> _(sol)_ |
| Ekranı sağa çevirme | <kbd>MOD</kbd>+<kbd>→</kbd> _(sağ)_ |
| Pencereyi 1:1 oranına çevirme (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd> |
| Penceredeki siyah kenarlıkları kaldırma | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Çift-sol-tık¹_ |
| `ANA EKRAN` tuşu | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Orta-tık_ |
| `GERİ` tuşu | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Sağ-tık²_ |
| `UYGULAMA_DEĞİŞTİR` tuşu | <kbd>MOD</kbd>+<kbd>s</kbd> \| _4.tık³_ |
| `MENÜ` tuşu (ekran kilidini açma) | <kbd>MOD</kbd>+<kbd>m</kbd> |
| `SES_AÇ` tuşu | <kbd>MOD</kbd>+<kbd>↑</kbd> _(yukarı)_ |
| `SES_KIS` tuşu | <kbd>MOD</kbd>+<kbd>↓</kbd> _(aşağı)_ |
| `GÜÇ` tuşu | <kbd>MOD</kbd>+<kbd>p</kbd> |
| Gücü açma | _Sağ-tık²_ |
| Cihaz ekranını kapatma (ekran yakalama durmadan) | <kbd>MOD</kbd>+<kbd>o</kbd> |
| Cihaz ekranını açma | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd> |
| Cihaz ekranını dönderme | <kbd>MOD</kbd>+<kbd>r</kbd> |
| Bildirim panelini genişletme | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5.tık³_ |
| Ayarlar panelini genişletme | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Çift-5.tık³_ |
| Panelleri kapatma | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd> |
| Panoya kopyalama⁴ | <kbd>MOD</kbd>+<kbd>c</kbd> |
| Panoya kesme⁴ | <kbd>MOD</kbd>+<kbd>x</kbd> |
| Panoları senkronize ederek yapıştırma⁴ | <kbd>MOD</kbd>+<kbd>v</kbd> |
| Bilgisayar panosundaki metini girme | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> |
| FPS sayacını açma/kapatma (terminalde) | <kbd>MOD</kbd>+<kbd>i</kbd> |
| İki parmakla yakınlaştırma | <kbd>Ctrl</kbd>+_tıkla-ve-sürükle_ |
_¹Siyah kenarlıkları silmek için üzerine çift tıklayın._
_²Sağ-tık ekran kapalıysa açar, değilse GERİ sinyali gönderir._
_³4. ve 5. fare tuşları (eğer varsa)._
_⁴Sadece Android 7 ve üzeri versiyonlarda._
Tekrarlı tuşu olan kısayollar tuş bırakılıp tekrar basılarak tekrar çalıştırılır.
Örneğin, "Ayarlar panelini genişletmek" için:
1. <kbd>MOD</kbd> tuşuna basın ve basılı tutun.
2. <kbd>n</kbd> tuşuna iki defa basın.
3. <kbd>MOD</kbd> tuşuna basmayı bırakın.
Tüm <kbd>Ctrl</kbd>+_tuş_ kısayolları cihaza gönderilir. Bu sayede istenilen komut
uygulama tarafından çalıştırılır.
## Özel dizinler
Varsayılandan farklı bir _adb_ programı çalıştırmak için `ADB` ortam değişkenini
ayarlayın:
```bash
ADB=/path/to/adb scrcpy
```
`scrcpy-server` programının dizinini değiştirmek için `SCRCPY_SERVER_PATH`
değişkenini ayarlayın.
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
## Neden _scrcpy_?
Bir meslektaşım [gnirehtet] gibi söylenmesi zor bir isim bulmam için bana meydan okudu.
[`strcpy`] **str**ing kopyalıyor; `scrcpy` **scr**een kopyalıyor.
[gnirehtet]: https://github.com/Genymobile/gnirehtet
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
## Nasıl derlenir?
Bakınız [BUILD].
## Yaygın problemler
Bakınız [FAQ](FAQ.md).
## Geliştiriciler
[Geliştiriciler sayfası]nı okuyun.
[geliştiriciler sayfası]: DEVELOP.md
## Lisans
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
## Makaleler
- [Introducing scrcpy][article-intro]
- [Scrcpy now works wirelessly][article-tcpip]
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/

File diff suppressed because it is too large Load Diff

View File

@ -80,10 +80,7 @@ apt install scrcpy
為了保持簡單Windows 用戶可以下載一個包含所有必需軟體 (包含 `adb`) 的壓縮包: 為了保持簡單Windows 用戶可以下載一個包含所有必需軟體 (包含 `adb`) 的壓縮包:
- [`scrcpy-win64-v1.15.zip`][direct-win64] - [README](README.md#windows)
_(SHA-256: dd514bb591e63ef4cd52a53c30f1153a28f59722d64690eb07bd017849edcba2)_
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.15/scrcpy-win64-v1.15.zip
[Chocolatey] 上也可以下載: [Chocolatey] 上也可以下載:

View File

@ -1,50 +1,109 @@
src = [ src = [
'src/main.c', 'src/main.c',
'src/adb.c', 'src/adb.c',
'src/adb_parser.c',
'src/adb_tunnel.c',
'src/cli.c', 'src/cli.c',
'src/clock.c',
'src/compat.c',
'src/control_msg.c', 'src/control_msg.c',
'src/controller.c', 'src/controller.c',
'src/decoder.c', 'src/decoder.c',
'src/device.c',
'src/device_msg.c', 'src/device_msg.c',
'src/event_converter.c', 'src/icon.c',
'src/file_handler.c', 'src/file_handler.c',
'src/fps_counter.c', 'src/fps_counter.c',
'src/frame_buffer.c',
'src/input_manager.c', 'src/input_manager.c',
'src/keyboard_inject.c',
'src/mouse_inject.c',
'src/opengl.c', 'src/opengl.c',
'src/options.c',
'src/receiver.c', 'src/receiver.c',
'src/recorder.c', 'src/recorder.c',
'src/scrcpy.c', 'src/scrcpy.c',
'src/screen.c', 'src/screen.c',
'src/server.c', 'src/server.c',
'src/stream.c', 'src/stream.c',
'src/tiny_xpm.c',
'src/video_buffer.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', 'src/util/net.c',
'src/util/net_intr.c',
'src/util/process.c', 'src/util/process.c',
'src/util/str_util.c' 'src/util/process_intr.c',
'src/util/strbuf.c',
'src/util/str.c',
'src/util/term.c',
'src/util/thread.c',
'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' if host_machine.system() == 'windows'
src += [ 'src/sys/win/process.c' ] 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 else
src += [ 'src/sys/unix/process.c' ] 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'
if v4l2_support
src += [ 'src/v4l2_sink.c' ]
endif
aoa_hid_support = host_machine.system() == 'linux'
if aoa_hid_support
src += [
'src/aoa_hid.c',
'src/hid_keyboard.c',
'src/hid_mouse.c',
]
endif endif
cc = meson.get_compiler('c') 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 # native build
dependencies = [ dependencies = [
dependency('libavformat'), dependency('libavformat', version: '>= 57.33'),
dependency('libavcodec'), dependency('libavcodec', version: '>= 57.37'),
dependency('libavutil'), dependency('libavutil'),
dependency('sdl2'), dependency('sdl2', version: '>= 2.0.5'),
] ]
else if v4l2_support
dependencies += dependency('libavdevice')
endif
if aoa_hid_support
dependencies += dependency('libusb-1.0')
endif
else
# cross-compile mingw32 build (from Linux to Windows) # cross-compile mingw32 build (from Linux to Windows)
prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2') prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2')
sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/bin' sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/bin'
@ -84,10 +143,21 @@ if host_machine.system() == 'windows'
dependencies += cc.find_library('ws2_32') dependencies += cc.find_library('ws2_32')
endif endif
conf = configuration_data() check_functions = [
'strdup',
'asprintf',
'vasprintf',
]
# expose the build type foreach f : check_functions
conf.set('NDEBUG', get_option('buildtype') != 'debug') if cc.has_function(f)
define = 'HAVE_' + f.underscorify().to_upper()
conf.set(define, true)
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 # the version, updated on release
conf.set_quoted('SCRCPY_VERSION', meson.project_version()) conf.set_quoted('SCRCPY_VERSION', meson.project_version())
@ -104,29 +174,22 @@ conf.set('PORTABLE', get_option('portable'))
conf.set('DEFAULT_LOCAL_PORT_RANGE_FIRST', '27183') conf.set('DEFAULT_LOCAL_PORT_RANGE_FIRST', '27183')
conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199') conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199')
# the default max video size for both dimensions, in pixels
# overridden by option --max-size
conf.set('DEFAULT_MAX_SIZE', '0') # 0: unlimited
# the default video orientation
# natural device orientation is 0 and each increment adds 90 degrees
# counterclockwise
# overridden by option --lock-video-orientation
conf.set('DEFAULT_LOCK_VIDEO_ORIENTATION', '-1') # -1: unlocked
# the default video bitrate, in bits/second # the default video bitrate, in bits/second
# overridden by option --bit-rate # overridden by option --bit-rate
conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps
# enable High DPI support
conf.set('HIDPI_SUPPORT', get_option('hidpi_support'))
# run a server debugger and wait for a client to be attached # run a server debugger and wait for a client to be attached
conf.set('SERVER_DEBUGGER', get_option('server_debugger')) conf.set('SERVER_DEBUGGER', get_option('server_debugger'))
# select the debugger method ('old' for Android < 9, 'new' for Android >= 9) # select the debugger method ('old' for Android < 9, 'new' for Android >= 9)
conf.set('SERVER_DEBUGGER_METHOD_NEW', get_option('server_debugger_method') == 'new') conf.set('SERVER_DEBUGGER_METHOD_NEW', get_option('server_debugger_method') == 'new')
# enable V4L2 support (linux only)
conf.set('HAVE_V4L2', v4l2_support)
# enable HID over AOA support (linux only)
conf.set('HAVE_AOA_HID', aoa_hid_support)
configure_file(configuration: conf, output: 'config.h') configure_file(configuration: conf, output: 'config.h')
src_dir = include_directories('src') src_dir = include_directories('src')
@ -138,6 +201,9 @@ executable('scrcpy', src,
c_args: []) c_args: [])
install_man('scrcpy.1') install_man('scrcpy.1')
install_data('../data/icon.png',
rename: 'scrcpy.png',
install_dir: 'share/icons/hicolor/256x256/apps')
### TESTS ### TESTS
@ -145,8 +211,14 @@ install_man('scrcpy.1')
# do not build tests in release (assertions would not be executed at all) # do not build tests in release (assertions would not be executed at all)
if get_option('buildtype') == 'debug' if get_option('buildtype') == 'debug'
tests = [ tests = [
['test_adb_parser', [
'tests/test_adb_parser.c',
'src/adb_parser.c',
'src/util/str.c',
'src/util/strbuf.c',
]],
['test_buffer_util', [ ['test_buffer_util', [
'tests/test_buffer_util.c' 'tests/test_buffer_util.c',
]], ]],
['test_cbuf', [ ['test_cbuf', [
'tests/test_cbuf.c', 'tests/test_cbuf.c',
@ -154,12 +226,21 @@ if get_option('buildtype') == 'debug'
['test_cli', [ ['test_cli', [
'tests/test_cli.c', 'tests/test_cli.c',
'src/cli.c', 'src/cli.c',
'src/util/str_util.c', 'src/options.c',
'src/util/net.c',
'src/util/str.c',
'src/util/strbuf.c',
'src/util/term.c',
]],
['test_clock', [
'tests/test_clock.c',
'src/clock.c',
]], ]],
['test_control_msg_serialize', [ ['test_control_msg_serialize', [
'tests/test_control_msg_serialize.c', 'tests/test_control_msg_serialize.c',
'src/control_msg.c', 'src/control_msg.c',
'src/util/str_util.c', 'src/util/str.c',
'src/util/strbuf.c',
]], ]],
['test_device_msg_deserialize', [ ['test_device_msg_deserialize', [
'tests/test_device_msg_deserialize.c', 'tests/test_device_msg_deserialize.c',
@ -168,9 +249,14 @@ if get_option('buildtype') == 'debug'
['test_queue', [ ['test_queue', [
'tests/test_queue.c', 'tests/test_queue.c',
]], ]],
['test_strutil', [ ['test_strbuf', [
'tests/test_strutil.c', 'tests/test_strbuf.c',
'src/util/str_util.c', 'src/util/strbuf.c',
]],
['test_str', [
'tests/test_str.c',
'src/util/str.c',
'src/util/strbuf.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

@ -56,6 +56,12 @@ The list of possible display ids can be listed by "adb shell dumpsys display"
Default is 0. Default is 0.
.TP
.BI "\-\-display\-buffer ms
Add a buffering delay (in milliseconds) before displaying. This increases latency to compensate for jitter.
Default is 0 (no buffering).
.TP .TP
.BI "\-\-encoder " name .BI "\-\-encoder " name
Use a specific MediaCodec encoder (must be a H.264 encoder). Use a specific MediaCodec encoder (must be a H.264 encoder).
@ -76,6 +82,20 @@ Start in fullscreen.
.B \-h, \-\-help .B \-h, \-\-help
Print this help. Print this help.
.TP
.B \-K, \-\-hid\-keyboard
Simulate a physical keyboard by using HID over AOAv2.
This provides a better experience for IME users, and allows to generate non-ASCII characters, contrary to the default injection method.
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).
.TP .TP
.B \-\-legacy\-paste .B \-\-legacy\-paste
Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v). Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v).
@ -83,10 +103,12 @@ Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+S
This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically. This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically.
.TP .TP
.BI "\-\-lock\-video\-orientation " value .BI "\-\-lock\-video\-orientation[=value]
Lock video orientation to \fIvalue\fR. Possible values are -1 (unlocked), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees otation counterclockwise. Lock video orientation to \fIvalue\fR. Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees rotation counterclockwise.
Default is -1 (unlocked). Default is "unlocked".
Passing the option without argument is equivalent to passing "initial".
.TP .TP
.BI "\-\-max\-fps " value .BI "\-\-max\-fps " value
@ -98,6 +120,12 @@ Limit both the width and height of the video to \fIvalue\fR. The other dimension
Default is 0 (unlimited). Default is 0 (unlimited).
.TP
.B \-\-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 .TP
.B \-n, \-\-no\-control .B \-n, \-\-no\-control
Disable device control (mirror the device in read\-only). Disable device control (mirror the device in read\-only).
@ -120,6 +148,10 @@ Set the TCP port (range) used by the client to listen.
Default is 27183:27199. Default is 27183:27199.
.TP
.B \-\-power\-off\-on\-close
Turn the device screen off when closing scrcpy.
.TP .TP
.B \-\-prefer\-text .B \-\-prefer\-text
Inject alpha characters and space as text events instead of key events. Inject alpha characters and space as text events instead of key events.
@ -131,7 +163,11 @@ but breaks the expected behavior of alpha keys in games (typically WASD).
.BI "\-\-push\-target " path .BI "\-\-push\-target " path
Set the target directory for pushing files to the device by drag & drop. It is passed as\-is to "adb push". Set the target directory for pushing files to the device by drag & drop. It is passed as\-is to "adb push".
Default is "/sdcard/". Default is "/sdcard/Download/".
.TP
.B \-\-raw\-key\-events
Inject key events for all input keys, and ignore text events.
.TP .TP
.BI "\-r, \-\-record " file .BI "\-r, \-\-record " file
@ -155,10 +191,6 @@ Supported names are currently "direct3d", "opengl", "opengles2", "opengles", "me
.UR https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER .UR https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER
.UE .UE
.TP
.B \-\-render\-expired\-frames
By default, to minimize latency, scrcpy always renders the last available decoded frame, and drops any previous ones. This flag forces to render all frames, at a cost of a possible increased latency.
.TP .TP
.BI "\-\-rotation " value .BI "\-\-rotation " value
Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each increment adds a 90 degrees rotation counterclockwise. Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each increment adds a 90 degrees rotation counterclockwise.
@ -177,6 +209,14 @@ For example, to use either LCtrl+LAlt or LSuper for scrcpy shortcuts, pass "lctr
Default is "lalt,lsuper" (left-Alt or left-Super). 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 .TP
.B \-S, \-\-turn\-screen\-off .B \-S, \-\-turn\-screen\-off
Turn the device screen off immediately. Turn the device screen off immediately.
@ -188,15 +228,41 @@ Enable "show touches" on start, restore the initial value on exit.
It only shows physical touches (not clicks from scrcpy). It only shows physical touches (not clicks from scrcpy).
.TP .TP
.B \-v, \-\-version .BI "\-\-tunnel\-host " ip
Print the version of scrcpy. 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.
It requires to lock the video orientation (see \fB\-\-lock\-video\-orientation\fR).
.TP
.BI "\-\-v4l2-buffer " ms
Add a buffering delay (in milliseconds) before pushing frames. This increases latency to compensate for jitter.
This option is similar to \fB\-\-display\-buffer\fR, but specific to V4L2 sink.
Default is 0 (no buffering).
.TP .TP
.BI "\-V, \-\-verbosity " value .BI "\-V, \-\-verbosity " value
Set the log level ("debug", "info", "warn" or "error"). Set the log level ("verbose", "debug", "info", "warn" or "error").
Default is "info" for release builds, "debug" for debug builds. Default is "info" for release builds, "debug" for debug builds.
.TP
.B \-v, \-\-version
Print the version of scrcpy.
.TP .TP
.B \-w, \-\-stay-awake .B \-w, \-\-stay-awake
Keep the device on while scrcpy is running, when the device is plugged in. Keep the device on while scrcpy is running, when the device is plugged in.
@ -213,30 +279,30 @@ Set a custom window title.
.BI "\-\-window\-x " value .BI "\-\-window\-x " value
Set the initial window horizontal position. Set the initial window horizontal position.
Default is "auto".\n Default is "auto".
.TP .TP
.BI "\-\-window\-y " value .BI "\-\-window\-y " value
Set the initial window vertical position. Set the initial window vertical position.
Default is "auto".\n Default is "auto".
.TP .TP
.BI "\-\-window\-width " value .BI "\-\-window\-width " value
Set the initial window width. Set the initial window width.
Default is 0 (automatic).\n Default is 0 (automatic).
.TP .TP
.BI "\-\-window\-height " value .BI "\-\-window\-height " value
Set the initial window height. Set the initial window height.
Default is 0 (automatic).\n Default is 0 (automatic).
.SH SHORTCUTS .SH SHORTCUTS
In the following list, MOD is the shortcut modifier. By default, it's (left) In the following list, MOD is the shortcut modifier. By default, it's (left)
Alt or (left) Super, but it can be configured by \-\-shortcut-mod (see above). Alt or (left) Super, but it can be configured by \fB\-\-shortcut\-mod\fR (see above).
.TP .TP
.B MOD+f .B MOD+f
@ -338,16 +404,24 @@ Pinch-to-zoom from the center of the screen
.B Drag & drop APK file .B Drag & drop APK file
Install APK from computer Install APK from computer
.TP
.B Drag & drop non-APK file
Push file to device (see \fB\-\-push\-target\fR)
.SH Environment variables .SH Environment variables
.TP .TP
.B ADB .B ADB
Specify the path to adb. Path to adb.
.TP
.B SCRCPY_ICON_PATH
Path to the program icon.
.TP .TP
.B SCRCPY_SERVER_PATH .B SCRCPY_SERVER_PATH
Specify the path to server binary. Path to the server binary.
.SH AUTHORS .SH AUTHORS
@ -372,7 +446,7 @@ Copyright \(co 2018 Genymobile
Genymobile Genymobile
.UE .UE
Copyright \(co 2018\-2020 Copyright \(co 2018\-2021
.MT rom@rom1v.com .MT rom@rom1v.com
Romain Vimont Romain Vimont
.ME .ME

View File

@ -5,8 +5,11 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "adb_parser.h"
#include "util/file.h"
#include "util/log.h" #include "util/log.h"
#include "util/str_util.h" #include "util/process_intr.h"
#include "util/str.h"
static const char *adb_command; static const char *adb_command;
@ -68,7 +71,7 @@ show_adb_installation_msg() {
{"pacman", "pacman -S android-tools"}, {"pacman", "pacman -S android-tools"},
}; };
for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) { for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) {
if (search_executable(pkg_managers[i].binary)) { if (sc_file_executable_exists(pkg_managers[i].binary)) {
LOGI("You may install 'adb' by \"%s\"", pkg_managers[i].command); LOGI("You may install 'adb' by \"%s\"", pkg_managers[i].command);
return; return;
} }
@ -80,132 +83,389 @@ show_adb_installation_msg() {
} }
static void static void
show_adb_err_msg(enum process_result err, const char *const argv[]) { show_adb_err_msg(enum sc_process_result err, const char *const argv[]) {
char buf[512]; #define MAX_COMMAND_STRING_LEN 1024
char *buf = malloc(MAX_COMMAND_STRING_LEN);
if (!buf) {
LOG_OOM();
LOGE("Failed to execute");
return;
}
switch (err) { switch (err) {
case PROCESS_ERROR_GENERIC: case SC_PROCESS_ERROR_GENERIC:
argv_to_string(argv, buf, sizeof(buf)); argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN);
LOGE("Failed to execute: %s", buf); LOGE("Failed to execute: %s", buf);
break; break;
case PROCESS_ERROR_MISSING_BINARY: case SC_PROCESS_ERROR_MISSING_BINARY:
argv_to_string(argv, buf, sizeof(buf)); argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN);
LOGE("Command not found: %s", buf); LOGE("Command not found: %s", buf);
LOGE("(make 'adb' accessible from your PATH or define its full" LOGE("(make 'adb' accessible from your PATH or define its full"
"path in the ADB environment variable)"); "path in the ADB environment variable)");
show_adb_installation_msg(); show_adb_installation_msg();
break; break;
case PROCESS_SUCCESS: case SC_PROCESS_SUCCESS:
// do nothing // do nothing
break; break;
} }
free(buf);
} }
process_t static bool
adb_execute(const char *serial, const char *const adb_cmd[], size_t len) { process_check_success_internal(sc_pid pid, const char *name, bool close,
const char *cmd[len + 4]; 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) {
LOG_OOM();
return NULL;
}
argv[0] = get_adb_command();
int i; int i;
process_t process;
cmd[0] = get_adb_command();
if (serial) { if (serial) {
cmd[1] = "-s"; argv[1] = "-s";
cmd[2] = serial; argv[2] = serial;
i = 3; i = 3;
} else { } else {
i = 1; i = 1;
} }
memcpy(&cmd[i], adb_cmd, len * sizeof(const char *)); memcpy(&argv[i], adb_cmd, len * sizeof(const char *));
cmd[len + i] = NULL; argv[len + i] = NULL;
enum process_result r = process_execute(cmd, &process); return argv;
if (r != PROCESS_SUCCESS) {
show_adb_err_msg(r, cmd);
return PROCESS_NONE;
}
return process;
} }
process_t static sc_pid
adb_forward(const char *serial, uint16_t local_port, adb_execute_p(const char *serial, const char *const adb_cmd[], size_t len,
const char *device_socket_name) { 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, 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;
}
free(argv);
return pid;
}
sc_pid
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);
}
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 local[4 + 5 + 1]; // tcp:PORT
char remote[108 + 14 + 1]; // localabstract:NAME char remote[108 + 14 + 1]; // localabstract:NAME
sprintf(local, "tcp:%" PRIu16, local_port); sprintf(local, "tcp:%" PRIu16, local_port);
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
const char *const adb_cmd[] = {"forward", local, remote}; 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);
} }
process_t bool
adb_forward_remove(const char *serial, uint16_t local_port) { adb_forward_remove(struct sc_intr *intr, const char *serial,
uint16_t local_port, unsigned flags) {
char local[4 + 5 + 1]; // tcp:PORT char local[4 + 5 + 1]; // tcp:PORT
sprintf(local, "tcp:%" PRIu16, local_port); sprintf(local, "tcp:%" PRIu16, local_port);
const char *const adb_cmd[] = {"forward", "--remove", local}; 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);
} }
process_t bool
adb_reverse(const char *serial, const char *device_socket_name, adb_reverse(struct sc_intr *intr, const char *serial,
uint16_t local_port) { const char *device_socket_name, uint16_t local_port,
unsigned flags) {
char local[4 + 5 + 1]; // tcp:PORT char local[4 + 5 + 1]; // tcp:PORT
char remote[108 + 14 + 1]; // localabstract:NAME char remote[108 + 14 + 1]; // localabstract:NAME
sprintf(local, "tcp:%" PRIu16, local_port); sprintf(local, "tcp:%" PRIu16, local_port);
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
const char *const adb_cmd[] = {"reverse", remote, local}; 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);
} }
process_t bool
adb_reverse_remove(const char *serial, const char *device_socket_name) { adb_reverse_remove(struct sc_intr *intr, const char *serial,
const char *device_socket_name, unsigned flags) {
char remote[108 + 14 + 1]; // localabstract:NAME char remote[108 + 14 + 1]; // localabstract:NAME
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
const char *const adb_cmd[] = {"reverse", "--remove", remote}; 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);
} }
process_t bool
adb_push(const char *serial, const char *local, const char *remote) { adb_push(struct sc_intr *intr, const char *serial, const char *local,
const char *remote, unsigned flags) {
#ifdef __WINDOWS__ #ifdef __WINDOWS__
// Windows will parse the string, so the paths must be quoted // Windows will parse the string, so the paths must be quoted
// (see sys/win/command.c) // (see sys/win/command.c)
local = strquote(local); local = sc_str_quote(local);
if (!local) { if (!local) {
return PROCESS_NONE; return SC_PROCESS_NONE;
} }
remote = strquote(remote); remote = sc_str_quote(remote);
if (!remote) { if (!remote) {
SDL_free((void *) local); free((void *) local);
return PROCESS_NONE; return SC_PROCESS_NONE;
} }
#endif #endif
const char *const adb_cmd[] = {"push", local, remote}; const char *const adb_cmd[] = {"push", local, remote};
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags);
#ifdef __WINDOWS__ #ifdef __WINDOWS__
SDL_free((void *) remote); free((void *) remote);
SDL_free((void *) local); free((void *) local);
#endif #endif
return proc; return process_check_success_intr(intr, pid, "adb push", flags);
} }
process_t bool
adb_install(const char *serial, const char *local) { adb_install(struct sc_intr *intr, const char *serial, const char *local,
unsigned flags) {
#ifdef __WINDOWS__ #ifdef __WINDOWS__
// Windows will parse the string, so the local name must be quoted // Windows will parse the string, so the local name must be quoted
// (see sys/win/command.c) // (see sys/win/command.c)
local = strquote(local); local = sc_str_quote(local);
if (!local) { if (!local) {
return PROCESS_NONE; return SC_PROCESS_NONE;
} }
#endif #endif
const char *const adb_cmd[] = {"install", "-r", local}; const char *const adb_cmd[] = {"install", "-r", local};
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags);
#ifdef __WINDOWS__ #ifdef __WINDOWS__
SDL_free((void *) local); free((void *) local);
#endif #endif
return proc; return process_check_success_intr(intr, pid, "adb install", flags);
}
bool
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_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_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;
sc_pid pid = adb_execute(NULL, adb_cmd, len, flags);
return process_check_success_intr(intr, pid, "adb disconnect", flags);
}
char *
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_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;
}
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 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

@ -6,29 +6,89 @@
#include <stdbool.h> #include <stdbool.h>
#include <inttypes.h> #include <inttypes.h>
#include "util/process.h" #include "util/intr.h"
process_t #define SC_ADB_NO_STDOUT (1 << 0)
adb_execute(const char *serial, const char *const adb_cmd[], size_t len); #define SC_ADB_NO_STDERR (1 << 1)
#define SC_ADB_NO_LOGERR (1 << 2)
process_t #define SC_ADB_SILENT (SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR)
adb_forward(const char *serial, uint16_t local_port,
const char *device_socket_name);
process_t sc_pid
adb_forward_remove(const char *serial, uint16_t local_port); adb_execute(const char *serial, const char *const adb_cmd[], size_t len,
unsigned flags);
process_t bool
adb_reverse(const char *serial, const char *device_socket_name, adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
uint16_t local_port); const char *device_socket_name, unsigned flags);
process_t bool
adb_reverse_remove(const char *serial, const char *device_socket_name); adb_forward_remove(struct sc_intr *intr, const char *serial,
uint16_t local_port, unsigned flags);
process_t bool
adb_push(const char *serial, const char *local, const char *remote); adb_reverse(struct sc_intr *intr, const char *serial,
const char *device_socket_name, uint16_t local_port,
unsigned flags);
process_t bool
adb_install(const char *serial, const char *local); adb_reverse_remove(struct sc_intr *intr, const char *serial,
const char *device_socket_name, unsigned flags);
bool
adb_push(struct sc_intr *intr, const char *serial, const char *local,
const char *remote, unsigned flags);
bool
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`
*
* Return the result, to be freed by the caller, or NULL on error.
*/
char *
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 #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

169
app/src/adb_tunnel.c Normal file
View File

@ -0,0 +1,169 @@
#include "adb_tunnel.h"
#include <assert.h>
#include "adb.h"
#include "util/log.h"
#include "util/net_intr.h"
#include "util/process_intr.h"
#define SC_SOCKET_NAME "scrcpy"
static bool
listen_on_port(struct sc_intr *intr, sc_socket socket, uint16_t port) {
return net_listen_intr(intr, socket, IPV4_LOCALHOST, port, 1);
}
static bool
enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
struct sc_intr *intr, const char *serial,
struct sc_port_range port_range) {
uint16_t port = port_range.first;
for (;;) {
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;
}
// At the application level, the device part is "the server" because it
// serves video stream and control. However, at the network level, the
// client listens and the server connects to the client. That way, the
// client can listen before starting the server app, so there is no
// need to try to connect until the server socket is listening on the
// device.
sc_socket server_socket = net_socket();
if (server_socket != SC_SOCKET_NONE) {
bool ok = listen_on_port(intr, server_socket, port);
if (ok) {
// success
tunnel->server_socket = server_socket;
tunnel->local_port = port;
tunnel->enabled = true;
return true;
}
net_close(server_socket);
}
if (sc_intr_is_interrupted(intr)) {
// Stop immediately
return false;
}
// failure, disable tunnel and try another port
if (!adb_reverse_remove(intr, serial, SC_SOCKET_NAME,
SC_ADB_NO_STDOUT)) {
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
}
// check before incrementing to avoid overflow on port 65535
if (port < port_range.last) {
LOGW("Could not listen on port %" PRIu16", retrying on %" PRIu16,
port, (uint16_t) (port + 1));
port++;
continue;
}
if (port_range.first == port_range.last) {
LOGE("Could not listen on port %" PRIu16, port_range.first);
} else {
LOGE("Could not listen on any port in range %" PRIu16 ":%" PRIu16,
port_range.first, port_range.last);
}
return false;
}
}
static bool
enable_tunnel_forward_any_port(struct sc_adb_tunnel *tunnel,
struct sc_intr *intr, const char *serial,
struct sc_port_range port_range) {
tunnel->forward = true;
uint16_t port = port_range.first;
for (;;) {
if (adb_forward(intr, serial, port, SC_SOCKET_NAME, SC_ADB_NO_STDOUT)) {
// success
tunnel->local_port = port;
tunnel->enabled = true;
return true;
}
if (sc_intr_is_interrupted(intr)) {
// Stop immediately
return false;
}
if (port < port_range.last) {
LOGW("Could not forward port %" PRIu16", retrying on %" PRIu16,
port, (uint16_t) (port + 1));
port++;
continue;
}
if (port_range.first == port_range.last) {
LOGE("Could not forward port %" PRIu16, port_range.first);
} else {
LOGE("Could not forward any port in range %" PRIu16 ":%" PRIu16,
port_range.first, port_range.last);
}
return false;
}
}
void
sc_adb_tunnel_init(struct sc_adb_tunnel *tunnel) {
tunnel->enabled = false;
tunnel->forward = false;
tunnel->server_socket = SC_SOCKET_NONE;
tunnel->local_port = 0;
}
bool
sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
const char *serial, struct sc_port_range port_range,
bool force_adb_forward) {
assert(!tunnel->enabled);
if (!force_adb_forward) {
// Attempt to use "adb reverse"
if (enable_tunnel_reverse_any_port(tunnel, intr, serial, port_range)) {
return true;
}
// if "adb reverse" does not work (e.g. over "adb connect"), it
// fallbacks to "adb forward", so the app socket is the client
LOGW("'adb reverse' failed, fallback to 'adb forward'");
}
return enable_tunnel_forward_any_port(tunnel, intr, serial, port_range);
}
bool
sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
const char *serial) {
assert(tunnel->enabled);
bool ret;
if (tunnel->forward) {
ret = adb_forward_remove(intr, serial, tunnel->local_port,
SC_ADB_NO_STDOUT);
} else {
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)) {
LOGW("Could not close server socket");
}
// server_socket is never used anymore
}
// Consider tunnel disabled even if the command failed
tunnel->enabled = false;
return ret;
}

47
app/src/adb_tunnel.h Normal file
View File

@ -0,0 +1,47 @@
#ifndef SC_ADB_TUNNEL_H
#define SC_ADB_TUNNEL_H
#include "common.h"
#include <stdbool.h>
#include <stdint.h>
#include "options.h"
#include "util/intr.h"
#include "util/net.h"
struct sc_adb_tunnel {
bool enabled;
bool forward; // use "adb forward" instead of "adb reverse"
sc_socket server_socket; // only used if !forward
uint16_t local_port;
};
/**
* Initialize the adb tunnel struct to default values
*/
void
sc_adb_tunnel_init(struct sc_adb_tunnel *tunnel);
/**
* Open a tunnel
*
* Blocking calls may be interrupted asynchronously via `intr`.
*
* If `force_adb_forward` is not set, then attempts to set up an "adb reverse"
* tunnel first. Only if it fails (typical on old Android version connected via
* TCP/IP), use "adb forward".
*/
bool
sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
const char *serial, struct sc_port_range port_range,
bool force_adb_forward);
/**
* Close the tunnel
*/
bool
sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
const char *serial);
#endif

View File

@ -21,7 +21,7 @@
#define _ANDROID_INPUT_H #define _ANDROID_INPUT_H
/** /**
* Meta key / modifer state. * Meta key / modifier state.
*/ */
enum android_metastate { enum android_metastate {
/** No meta keys are pressed. */ /** No meta keys are pressed. */

398
app/src/aoa_hid.c Normal file
View File

@ -0,0 +1,398 @@
#include "util/log.h"
#include <assert.h>
#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
#define ACCESSORY_SET_HID_REPORT_DESC 56
#define ACCESSORY_SEND_HID_EVENT 57
#define ACCESSORY_UNREGISTER_HID 55
#define DEFAULT_TIMEOUT 1000
static void
sc_hid_event_log(const struct sc_hid_event *event) {
// HID Event: [00] FF FF FF FF...
assert(event->size);
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) {
snprintf(buffer + i * 3, 4, " %02x", event->buffer[i]);
}
LOGV("HID Event: [%d]%s", event->accessory_id, buffer);
free(buffer);
}
void
sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id,
unsigned char *buffer, uint16_t buffer_size) {
hid_event->accessory_id = accessory_id;
hid_event->buffer = buffer;
hid_event->size = buffer_size;
hid_event->ack_to_wait = SC_SEQUENCE_INVALID;
}
void
sc_hid_event_destroy(struct sc_hid_event *hid_event) {
free(hid_event->buffer);
}
static inline void
log_libusb_error(enum libusb_error errcode) {
LOGW("libusb error: %s", libusb_strerror(errcode));
}
static bool
accept_device(libusb_device *device, const char *serial) {
// do not log any USB error in this function, it is expected that many USB
// devices available on the computer have permission restrictions
struct libusb_device_descriptor desc;
libusb_get_device_descriptor(device, &desc);
if (!desc.iSerialNumber) {
return false;
}
libusb_device_handle *handle;
int result = libusb_open(device, &handle);
if (result < 0) {
return false;
}
char buffer[128];
result = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber,
(unsigned char *) buffer,
sizeof(buffer));
libusb_close(handle);
if (result < 0) {
return false;
}
buffer[sizeof(buffer) - 1] = '\0'; // just in case
// accept the device if its serial matches
return !strcmp(buffer, serial);
}
static libusb_device *
sc_aoa_find_usb_device(const char *serial) {
if (!serial) {
return NULL;
}
libusb_device **list;
libusb_device *result = NULL;
ssize_t count = libusb_get_device_list(NULL, &list);
if (count < 0) {
log_libusb_error((enum libusb_error) count);
return NULL;
}
for (size_t i = 0; i < (size_t) count; ++i) {
libusb_device *device = list[i];
if (accept_device(device, serial)) {
result = libusb_ref_device(device);
break;
}
}
libusb_free_device_list(list, 1);
return result;
}
static int
sc_aoa_open_usb_handle(libusb_device *device, libusb_device_handle **handle) {
int result = libusb_open(device, handle);
if (result < 0) {
log_libusb_error((enum libusb_error) result);
return result;
}
return 0;
}
bool
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)) {
return false;
}
if (!sc_cond_init(&aoa->event_cond)) {
sc_mutex_destroy(&aoa->mutex);
return false;
}
if (libusb_init(&aoa->usb_context) != LIBUSB_SUCCESS) {
sc_cond_destroy(&aoa->event_cond);
sc_mutex_destroy(&aoa->mutex);
return false;
}
aoa->usb_device = sc_aoa_find_usb_device(serial);
if (!aoa->usb_device) {
LOGW("USB device of serial %s not found", serial);
libusb_exit(aoa->usb_context);
sc_mutex_destroy(&aoa->mutex);
sc_cond_destroy(&aoa->event_cond);
return false;
}
if (sc_aoa_open_usb_handle(aoa->usb_device, &aoa->usb_handle) < 0) {
LOGW("Open USB handle failed");
libusb_unref_device(aoa->usb_device);
libusb_exit(aoa->usb_context);
sc_cond_destroy(&aoa->event_cond);
sc_mutex_destroy(&aoa->mutex);
return false;
}
aoa->stopped = false;
aoa->acksync = acksync;
return true;
}
void
sc_aoa_destroy(struct sc_aoa *aoa) {
// Destroy remaining events
struct sc_hid_event event;
while (cbuf_take(&aoa->queue, &event)) {
sc_hid_event_destroy(&event);
}
libusb_close(aoa->usb_handle);
libusb_unref_device(aoa->usb_device);
libusb_exit(aoa->usb_context);
sc_cond_destroy(&aoa->event_cond);
sc_mutex_destroy(&aoa->mutex);
}
static bool
sc_aoa_register_hid(struct sc_aoa *aoa, uint16_t accessory_id,
uint16_t report_desc_size) {
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
uint8_t request = ACCESSORY_REGISTER_HID;
// <https://source.android.com/devices/accessories/aoa2.html#hid-support>
// value (arg0): accessory assigned ID for the HID device
// index (arg1): total length of the HID report descriptor
uint16_t value = accessory_id;
uint16_t index = report_desc_size;
unsigned char *buffer = NULL;
uint16_t length = 0;
int result = libusb_control_transfer(aoa->usb_handle, request_type, request,
value, index, buffer, length,
DEFAULT_TIMEOUT);
if (result < 0) {
log_libusb_error((enum libusb_error) result);
return false;
}
return true;
}
static bool
sc_aoa_set_hid_report_desc(struct sc_aoa *aoa, uint16_t accessory_id,
const unsigned char *report_desc,
uint16_t report_desc_size) {
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
uint8_t request = ACCESSORY_SET_HID_REPORT_DESC;
/**
* If the HID descriptor is longer than the endpoint zero max packet size,
* the descriptor will be sent in multiple ACCESSORY_SET_HID_REPORT_DESC
* commands. The data for the descriptor must be sent sequentially
* if multiple packets are needed.
* <https://source.android.com/devices/accessories/aoa2.html#hid-support>
*
* libusb handles packet abstraction internally, so we don't need to care
* about bMaxPacketSize0 here.
*
* See <https://libusb.sourceforge.io/api-1.0/libusb_packetoverflow.html>
*/
// value (arg0): accessory assigned ID for the HID device
// index (arg1): offset of data (buffer) in descriptor
uint16_t value = accessory_id;
uint16_t index = 0;
// libusb_control_transfer expects a pointer to non-const
unsigned char *buffer = (unsigned char *) report_desc;
uint16_t length = report_desc_size;
int result = libusb_control_transfer(aoa->usb_handle, request_type, request,
value, index, buffer, length,
DEFAULT_TIMEOUT);
if (result < 0) {
log_libusb_error((enum libusb_error) result);
return false;
}
return true;
}
bool
sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id,
const unsigned char *report_desc, uint16_t report_desc_size) {
bool ok = sc_aoa_register_hid(aoa, accessory_id, report_desc_size);
if (!ok) {
return false;
}
ok = sc_aoa_set_hid_report_desc(aoa, accessory_id, report_desc,
report_desc_size);
if (!ok) {
if (!sc_aoa_unregister_hid(aoa, accessory_id)) {
LOGW("Could not unregister HID");
}
return false;
}
return true;
}
static bool
sc_aoa_send_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) {
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
uint8_t request = ACCESSORY_SEND_HID_EVENT;
// <https://source.android.com/devices/accessories/aoa2.html#hid-support>
// value (arg0): accessory assigned ID for the HID device
// index (arg1): 0 (unused)
uint16_t value = event->accessory_id;
uint16_t index = 0;
unsigned char *buffer = event->buffer;
uint16_t length = event->size;
int result = libusb_control_transfer(aoa->usb_handle, request_type, request,
value, index, buffer, length,
DEFAULT_TIMEOUT);
if (result < 0) {
log_libusb_error((enum libusb_error) result);
return false;
}
return true;
}
bool
sc_aoa_unregister_hid(struct sc_aoa *aoa, const uint16_t accessory_id) {
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
uint8_t request = ACCESSORY_UNREGISTER_HID;
// <https://source.android.com/devices/accessories/aoa2.html#hid-support>
// value (arg0): accessory assigned ID for the HID device
// index (arg1): 0
uint16_t value = accessory_id;
uint16_t index = 0;
unsigned char *buffer = NULL;
uint16_t length = 0;
int result = libusb_control_transfer(aoa->usb_handle, request_type, request,
value, index, buffer, length,
DEFAULT_TIMEOUT);
if (result < 0) {
log_libusb_error((enum libusb_error) result);
return false;
}
return true;
}
bool
sc_aoa_push_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) {
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
sc_hid_event_log(event);
}
sc_mutex_lock(&aoa->mutex);
bool was_empty = cbuf_is_empty(&aoa->queue);
bool res = cbuf_push(&aoa->queue, *event);
if (was_empty) {
sc_cond_signal(&aoa->event_cond);
}
sc_mutex_unlock(&aoa->mutex);
return res;
}
static int
run_aoa_thread(void *data) {
struct sc_aoa *aoa = data;
for (;;) {
sc_mutex_lock(&aoa->mutex);
while (!aoa->stopped && cbuf_is_empty(&aoa->queue)) {
sc_cond_wait(&aoa->event_cond, &aoa->mutex);
}
if (aoa->stopped) {
// Stop immediately, do not process further events
sc_mutex_unlock(&aoa->mutex);
break;
}
struct sc_hid_event event;
bool non_empty = cbuf_take(&aoa->queue, &event);
assert(non_empty);
(void) non_empty;
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;
}
}
bool ok = sc_aoa_send_hid_event(aoa, &event);
sc_hid_event_destroy(&event);
if (!ok) {
LOGW("Could not send HID event to USB device");
}
}
return 0;
}
bool
sc_aoa_start(struct sc_aoa *aoa) {
LOGD("Starting AOA thread");
bool ok = sc_thread_create(&aoa->thread, run_aoa_thread, "scrcpy-aoa", aoa);
if (!ok) {
LOGC("Could not start AOA thread");
return false;
}
return true;
}
void
sc_aoa_stop(struct sc_aoa *aoa) {
sc_mutex_lock(&aoa->mutex);
aoa->stopped = true;
sc_cond_signal(&aoa->event_cond);
sc_mutex_unlock(&aoa->mutex);
sc_acksync_interrupt(aoa->acksync);
}
void
sc_aoa_join(struct sc_aoa *aoa) {
sc_thread_join(&aoa->thread, NULL);
}

69
app/src/aoa_hid.h Normal file
View File

@ -0,0 +1,69 @@
#ifndef SC_AOA_HID_H
#define SC_AOA_HID_H
#include <stdint.h>
#include <stdbool.h>
#include <libusb-1.0/libusb.h>
#include "util/acksync.h"
#include "util/cbuf.h"
#include "util/thread.h"
#include "util/tick.h"
struct sc_hid_event {
uint16_t accessory_id;
unsigned char *buffer;
uint16_t size;
uint64_t ack_to_wait;
};
// Takes ownership of buffer
void
sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id,
unsigned char *buffer, uint16_t buffer_size);
void
sc_hid_event_destroy(struct sc_hid_event *hid_event);
struct sc_hid_event_queue CBUF(struct sc_hid_event, 64);
struct sc_aoa {
libusb_context *usb_context;
libusb_device *usb_device;
libusb_device_handle *usb_handle;
sc_thread thread;
sc_mutex mutex;
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, struct sc_acksync *acksync);
void
sc_aoa_destroy(struct sc_aoa *aoa);
bool
sc_aoa_start(struct sc_aoa *aoa);
void
sc_aoa_stop(struct sc_aoa *aoa);
void
sc_aoa_join(struct sc_aoa *aoa);
bool
sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id,
const unsigned char *report_desc, uint16_t report_desc_size);
bool
sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id);
bool
sc_aoa_push_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event);
#endif

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
#include <stdbool.h> #include <stdbool.h>
#include "scrcpy.h" #include "options.h"
struct scrcpy_cli_args { struct scrcpy_cli_args {
struct scrcpy_options opts; struct scrcpy_options opts;

111
app/src/clock.c Normal file
View File

@ -0,0 +1,111 @@
#include "clock.h"
#include "util/log.h"
#define SC_CLOCK_NDEBUG // comment to debug
void
sc_clock_init(struct sc_clock *clock) {
clock->count = 0;
clock->head = 0;
clock->left_sum.system = 0;
clock->left_sum.stream = 0;
clock->right_sum.system = 0;
clock->right_sum.stream = 0;
}
// Estimate the affine function f(stream) = slope * stream + offset
static void
sc_clock_estimate(struct sc_clock *clock,
double *out_slope, sc_tick *out_offset) {
assert(clock->count > 1); // two points are necessary
struct sc_clock_point left_avg = {
.system = clock->left_sum.system / (clock->count / 2),
.stream = clock->left_sum.stream / (clock->count / 2),
};
struct sc_clock_point right_avg = {
.system = clock->right_sum.system / ((clock->count + 1) / 2),
.stream = clock->right_sum.stream / ((clock->count + 1) / 2),
};
double slope = (double) (right_avg.system - left_avg.system)
/ (right_avg.stream - left_avg.stream);
if (clock->count < SC_CLOCK_RANGE) {
/* The first frames are typically received and decoded with more delay
* than the others, causing a wrong slope estimation on start. To
* compensate, assume an initial slope of 1, then progressively use the
* estimated slope. */
slope = (clock->count * slope + (SC_CLOCK_RANGE - clock->count))
/ SC_CLOCK_RANGE;
}
struct sc_clock_point global_avg = {
.system = (clock->left_sum.system + clock->right_sum.system)
/ clock->count,
.stream = (clock->left_sum.stream + clock->right_sum.stream)
/ clock->count,
};
sc_tick offset = global_avg.system - (sc_tick) (global_avg.stream * slope);
*out_slope = slope;
*out_offset = offset;
}
void
sc_clock_update(struct sc_clock *clock, sc_tick system, sc_tick stream) {
struct sc_clock_point *point = &clock->points[clock->head];
if (clock->count == SC_CLOCK_RANGE || clock->count & 1) {
// One point passes from the right sum to the left sum
unsigned mid;
if (clock->count == SC_CLOCK_RANGE) {
mid = (clock->head + SC_CLOCK_RANGE / 2) % SC_CLOCK_RANGE;
} else {
// Only for the first frames
mid = clock->count / 2;
}
struct sc_clock_point *mid_point = &clock->points[mid];
clock->left_sum.system += mid_point->system;
clock->left_sum.stream += mid_point->stream;
clock->right_sum.system -= mid_point->system;
clock->right_sum.stream -= mid_point->stream;
}
if (clock->count == SC_CLOCK_RANGE) {
// The current point overwrites the previous value in the circular
// array, update the left sum accordingly
clock->left_sum.system -= point->system;
clock->left_sum.stream -= point->stream;
} else {
++clock->count;
}
point->system = system;
point->stream = stream;
clock->right_sum.system += system;
clock->right_sum.stream += stream;
clock->head = (clock->head + 1) % SC_CLOCK_RANGE;
if (clock->count > 1) {
// Update estimation
sc_clock_estimate(clock, &clock->slope, &clock->offset);
#ifndef SC_CLOCK_NDEBUG
LOGD("Clock estimation: %g * pts + %" PRItick,
clock->slope, clock->offset);
#endif
}
}
sc_tick
sc_clock_to_system_time(struct sc_clock *clock, sc_tick stream) {
assert(clock->count > 1); // sc_clock_update() must have been called
return (sc_tick) (stream * clock->slope) + clock->offset;
}

70
app/src/clock.h Normal file
View File

@ -0,0 +1,70 @@
#ifndef SC_CLOCK_H
#define SC_CLOCK_H
#include "common.h"
#include <assert.h>
#include "util/tick.h"
#define SC_CLOCK_RANGE 32
static_assert(!(SC_CLOCK_RANGE & 1), "SC_CLOCK_RANGE must be even");
struct sc_clock_point {
sc_tick system;
sc_tick stream;
};
/**
* The clock aims to estimate the affine relation between the stream (device)
* time and the system time:
*
* f(stream) = slope * stream + offset
*
* To that end, it stores the SC_CLOCK_RANGE last clock points (the timestamps
* of a frame expressed both in stream time and system time) in a circular
* array.
*
* To estimate the slope, it splits the last SC_CLOCK_RANGE points into two
* sets of SC_CLOCK_RANGE/2 points, and computes their centroid ("average
* point"). The slope of the estimated affine function is that of the line
* passing through these two points.
*
* To estimate the offset, it computes the centroid of all the SC_CLOCK_RANGE
* points. The resulting affine function passes by this centroid.
*
* With a circular array, the rolling sums (and average) are quick to compute.
* In practice, the estimation is stable and the evolution is smooth.
*/
struct sc_clock {
// Circular array
struct sc_clock_point points[SC_CLOCK_RANGE];
// Number of points in the array (count <= SC_CLOCK_RANGE)
unsigned count;
// Index of the next point to write
unsigned head;
// Sum of the first count/2 points
struct sc_clock_point left_sum;
// Sum of the last (count+1)/2 points
struct sc_clock_point right_sum;
// Estimated slope and offset
// (computed on sc_clock_update(), used by sc_clock_to_system_time())
double slope;
sc_tick offset;
};
void
sc_clock_init(struct sc_clock *clock);
void
sc_clock_update(struct sc_clock *clock, sc_tick system, sc_tick stream);
sc_tick
sc_clock_to_system_time(struct sc_clock *clock, sc_tick stream);
#endif

View File

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

53
app/src/compat.c Normal file
View File

@ -0,0 +1,53 @@
#include "compat.h"
#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;
char *dup = malloc(size);
if (dup) {
memcpy(dup, s, size);
}
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,25 +1,15 @@
#ifndef COMPAT_H #ifndef COMPAT_H
#define COMPAT_H #define COMPAT_H
#define _POSIX_C_SOURCE 200809L #include "config.h"
#define _XOPEN_SOURCE 700
#define _GNU_SOURCE
#ifdef __APPLE__
# define _DARWIN_C_SOURCE
#endif
#include <libavcodec/version.h>
#include <libavformat/version.h> #include <libavformat/version.h>
#include <SDL2/SDL_version.h> #include <SDL2/SDL_version.h>
// In ffmpeg/doc/APIchanges: #ifndef __WIN32
// 2016-04-11 - 6f69f7a / 9200514 - lavf 57.33.100 / 57.5.0 - avformat.h # define PRIu64_ PRIu64
// Add AVStream.codecpar, deprecate AVStream.codec. #else
#if (LIBAVFORMAT_VERSION_MICRO >= 100 /* FFmpeg */ && \ # define PRIu64_ "I64u" // Windows...
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 33, 100)) \
|| (LIBAVFORMAT_VERSION_MICRO < 100 && /* Libav */ \
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 5, 0))
# define SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
#endif #endif
// In ffmpeg/doc/APIchanges: // In ffmpeg/doc/APIchanges:
@ -33,22 +23,21 @@
# define SCRCPY_LAVF_REQUIRES_REGISTER_ALL # define SCRCPY_LAVF_REQUIRES_REGISTER_ALL
#endif #endif
// In ffmpeg/doc/APIchanges: // In ffmpeg/doc/APIchanges:
// 2016-04-21 - 7fc329e - lavc 57.37.100 - avcodec.h // 2018-01-28 - ea3672b7d6 - lavf 58.7.100 - avformat.h
// Add a new audio/video encoding and decoding API with decoupled input // Deprecate AVFormatContext filename field which had limited length, use the
// and output -- avcodec_send_packet(), avcodec_receive_frame(), // new dynamically allocated url field instead.
// avcodec_send_frame() and avcodec_receive_packet(). //
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 100) // 2018-01-28 - ea3672b7d6 - lavf 58.7.100 - avformat.h
# define SCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API // Add url field to AVFormatContext and add ff_format_set_url helper function.
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 7, 100)
# define SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL
#endif #endif
#if SDL_VERSION_ATLEAST(2, 0, 5) #if SDL_VERSION_ATLEAST(2, 0, 6)
// <https://wiki.libsdl.org/SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH> // <https://github.com/libsdl-org/SDL/commit/d7a318de563125e5bb465b1000d6bc9576fbc6fc>
# define SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH # define SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
// <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 #endif
#if SDL_VERSION_ATLEAST(2, 0, 8) #if SDL_VERSION_ATLEAST(2, 0, 8)
@ -56,4 +45,16 @@
# define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR # define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
#endif #endif
#ifndef HAVE_STRDUP
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 #endif

View File

@ -1,24 +1,78 @@
#include "control_msg.h" #include "control_msg.h"
#include <assert.h> #include <assert.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include "util/buffer_util.h" #include "util/buffer_util.h"
#include "util/log.h" #include "util/log.h"
#include "util/str_util.h" #include "util/str.h"
/**
* Map an enum value to a string based on an array, without crashing on an
* out-of-bounds index.
*/
#define ENUM_TO_LABEL(labels, value) \
((size_t) (value) < ARRAY_LEN(labels) ? labels[value] : "???")
#define KEYEVENT_ACTION_LABEL(value) \
ENUM_TO_LABEL(android_keyevent_action_labels, value)
#define MOTIONEVENT_ACTION_LABEL(value) \
ENUM_TO_LABEL(android_motionevent_action_labels, value)
#define SCREEN_POWER_MODE_LABEL(value) \
ENUM_TO_LABEL(screen_power_mode_labels, value)
static const char *const android_keyevent_action_labels[] = {
"down",
"up",
"multi",
};
static const char *const android_motionevent_action_labels[] = {
"down",
"up",
"move",
"cancel",
"outside",
"ponter-down",
"pointer-up",
"hover-move",
"scroll",
"hover-enter",
"hover-exit",
"btn-press",
"btn-release",
};
static const char *const screen_power_mode_labels[] = {
"off",
"doze",
"normal",
"doze-suspend",
"suspend",
};
static const char *const copy_key_labels[] = {
"none",
"copy",
"cut",
};
static void static void
write_position(uint8_t *buf, const struct position *position) { write_position(uint8_t *buf, const struct sc_position *position) {
buffer_write32be(&buf[0], position->point.x); buffer_write32be(&buf[0], position->point.x);
buffer_write32be(&buf[4], position->point.y); buffer_write32be(&buf[4], position->point.y);
buffer_write16be(&buf[8], position->screen_size.width); buffer_write16be(&buf[8], position->screen_size.width);
buffer_write16be(&buf[10], position->screen_size.height); 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 static size_t
write_string(const char *utf8, size_t max_len, unsigned char *buf) { write_string(const char *utf8, size_t max_len, unsigned char *buf) {
size_t len = utf8_truncation_index(utf8, max_len); size_t len = sc_str_utf8_truncation_index(utf8, max_len);
buffer_write32be(buf, len); buffer_write32be(buf, len);
memcpy(&buf[4], utf8, len); memcpy(&buf[4], utf8, len);
return 4 + len; return 4 + len;
@ -65,21 +119,28 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
(uint32_t) msg->inject_scroll_event.hscroll); (uint32_t) msg->inject_scroll_event.hscroll);
buffer_write32be(&buf[17], buffer_write32be(&buf[17],
(uint32_t) msg->inject_scroll_event.vscroll); (uint32_t) msg->inject_scroll_event.vscroll);
return 21; buffer_write32be(&buf[21], msg->inject_scroll_event.buttons);
return 25;
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
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: { 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, size_t len = write_string(msg->set_clipboard.text,
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH, CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
&buf[2]); &buf[10]);
return 2 + len; return 10 + len;
} }
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE: case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
buf[1] = msg->set_screen_power_mode.mode; buf[1] = msg->set_screen_power_mode.mode;
return 2; return 2;
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL: case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
case CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL: case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
case CONTROL_MSG_TYPE_GET_CLIPBOARD: case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
case CONTROL_MSG_TYPE_ROTATE_DEVICE: case CONTROL_MSG_TYPE_ROTATE_DEVICE:
// no additional data // no additional data
return 1; return 1;
@ -89,14 +150,100 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
} }
} }
void
control_msg_log(const struct control_msg *msg) {
#define LOG_CMSG(fmt, ...) LOGV("input: " fmt, ## __VA_ARGS__)
switch (msg->type) {
case CONTROL_MSG_TYPE_INJECT_KEYCODE:
LOG_CMSG("key %-4s code=%d repeat=%" PRIu32 " meta=%06lx",
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action),
(int) msg->inject_keycode.keycode,
msg->inject_keycode.repeat,
(long) msg->inject_keycode.metastate);
break;
case CONTROL_MSG_TYPE_INJECT_TEXT:
LOG_CMSG("text \"%s\"", msg->inject_text.text);
break;
case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT: {
int action = msg->inject_touch_event.action
& AMOTION_EVENT_ACTION_MASK;
uint64_t id = msg->inject_touch_event.pointer_id;
if (id == POINTER_ID_MOUSE || id == POINTER_ID_VIRTUAL_FINGER) {
// string pointer id
LOG_CMSG("touch [id=%s] %-4s position=%" PRIi32 ",%" PRIi32
" pressure=%g buttons=%06lx",
id == POINTER_ID_MOUSE ? "mouse" : "vfinger",
MOTIONEVENT_ACTION_LABEL(action),
msg->inject_touch_event.position.point.x,
msg->inject_touch_event.position.point.y,
msg->inject_touch_event.pressure,
(long) msg->inject_touch_event.buttons);
} else {
// numeric pointer id
LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%"
PRIi32 " pressure=%g buttons=%06lx",
id,
MOTIONEVENT_ACTION_LABEL(action),
msg->inject_touch_event.position.point.x,
msg->inject_touch_event.position.point.y,
msg->inject_touch_event.pressure,
(long) msg->inject_touch_event.buttons);
}
break;
}
case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%" 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,
(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 %" 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:
LOG_CMSG("power mode %s",
SCREEN_POWER_MODE_LABEL(msg->set_screen_power_mode.mode));
break;
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
LOG_CMSG("expand notification panel");
break;
case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
LOG_CMSG("expand settings panel");
break;
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
LOG_CMSG("collapse panels");
break;
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
LOG_CMSG("rotate device");
break;
default:
LOG_CMSG("unknown type: %u", (unsigned) msg->type);
break;
}
}
void void
control_msg_destroy(struct control_msg *msg) { control_msg_destroy(struct control_msg *msg) {
switch (msg->type) { switch (msg->type) {
case CONTROL_MSG_TYPE_INJECT_TEXT: case CONTROL_MSG_TYPE_INJECT_TEXT:
SDL_free(msg->inject_text.text); free(msg->inject_text.text);
break; break;
case CONTROL_MSG_TYPE_SET_CLIPBOARD: case CONTROL_MSG_TYPE_SET_CLIPBOARD:
SDL_free(msg->set_clipboard.text); free(msg->set_clipboard.text);
break; break;
default: default:
// do nothing // do nothing

View File

@ -14,11 +14,11 @@
#define CONTROL_MSG_MAX_SIZE (1 << 18) // 256k #define CONTROL_MSG_MAX_SIZE (1 << 18) // 256k
#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300 #define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
// type: 1 byte; paste flag: 1 byte; length: 4 bytes // type: 1 byte; sequence: 8 bytes; paste flag: 1 byte; length: 4 bytes
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 6) #define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 14)
#define POINTER_ID_MOUSE UINT64_C(-1); #define POINTER_ID_MOUSE UINT64_C(-1)
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2); #define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2)
enum control_msg_type { enum control_msg_type {
CONTROL_MSG_TYPE_INJECT_KEYCODE, CONTROL_MSG_TYPE_INJECT_KEYCODE,
@ -27,7 +27,8 @@ enum control_msg_type {
CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON, CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL, CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL, CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
CONTROL_MSG_TYPE_COLLAPSE_PANELS,
CONTROL_MSG_TYPE_GET_CLIPBOARD, CONTROL_MSG_TYPE_GET_CLIPBOARD,
CONTROL_MSG_TYPE_SET_CLIPBOARD, CONTROL_MSG_TYPE_SET_CLIPBOARD,
CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE, CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
@ -40,6 +41,12 @@ enum screen_power_mode {
SCREEN_POWER_MODE_NORMAL = 2, SCREEN_POWER_MODE_NORMAL = 2,
}; };
enum get_clipboard_copy_key {
GET_CLIPBOARD_COPY_KEY_NONE,
GET_CLIPBOARD_COPY_KEY_COPY,
GET_CLIPBOARD_COPY_KEY_CUT,
};
struct control_msg { struct control_msg {
enum control_msg_type type; enum control_msg_type type;
union { union {
@ -50,22 +57,31 @@ struct control_msg {
enum android_metastate metastate; enum android_metastate metastate;
} inject_keycode; } inject_keycode;
struct { struct {
char *text; // owned, to be freed by SDL_free() char *text; // owned, to be freed by free()
} inject_text; } inject_text;
struct { struct {
enum android_motionevent_action action; enum android_motionevent_action action;
enum android_motionevent_buttons buttons; enum android_motionevent_buttons buttons;
uint64_t pointer_id; uint64_t pointer_id;
struct position position; struct sc_position position;
float pressure; float pressure;
} inject_touch_event; } inject_touch_event;
struct { struct {
struct position position; struct sc_position position;
int32_t hscroll; int32_t hscroll;
int32_t vscroll; int32_t vscroll;
enum android_motionevent_buttons buttons;
} inject_scroll_event; } inject_scroll_event;
struct { struct {
char *text; // owned, to be freed by SDL_free() 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; bool paste;
} set_clipboard; } set_clipboard;
struct { struct {
@ -79,6 +95,9 @@ struct control_msg {
size_t size_t
control_msg_serialize(const struct control_msg *msg, unsigned char *buf); control_msg_serialize(const struct control_msg *msg, unsigned char *buf);
void
control_msg_log(const struct control_msg *msg);
void void
control_msg_destroy(struct control_msg *msg); control_msg_destroy(struct control_msg *msg);

View File

@ -2,25 +2,28 @@
#include <assert.h> #include <assert.h>
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
bool bool
controller_init(struct controller *controller, socket_t control_socket) { controller_init(struct controller *controller, sc_socket control_socket,
struct sc_acksync *acksync) {
cbuf_init(&controller->queue); cbuf_init(&controller->queue);
if (!receiver_init(&controller->receiver, control_socket)) { bool ok = receiver_init(&controller->receiver, control_socket, acksync);
if (!ok) {
return false; return false;
} }
if (!(controller->mutex = SDL_CreateMutex())) { ok = sc_mutex_init(&controller->mutex);
if (!ok) {
receiver_destroy(&controller->receiver); receiver_destroy(&controller->receiver);
return false; return false;
} }
if (!(controller->msg_cond = SDL_CreateCond())) { ok = sc_cond_init(&controller->msg_cond);
if (!ok) {
receiver_destroy(&controller->receiver); receiver_destroy(&controller->receiver);
SDL_DestroyMutex(controller->mutex); sc_mutex_destroy(&controller->mutex);
return false; return false;
} }
@ -32,8 +35,8 @@ controller_init(struct controller *controller, socket_t control_socket) {
void void
controller_destroy(struct controller *controller) { controller_destroy(struct controller *controller) {
SDL_DestroyCond(controller->msg_cond); sc_cond_destroy(&controller->msg_cond);
SDL_DestroyMutex(controller->mutex); sc_mutex_destroy(&controller->mutex);
struct control_msg msg; struct control_msg msg;
while (cbuf_take(&controller->queue, &msg)) { while (cbuf_take(&controller->queue, &msg)) {
@ -45,26 +48,30 @@ controller_destroy(struct controller *controller) {
bool bool
controller_push_msg(struct controller *controller, controller_push_msg(struct controller *controller,
const struct control_msg *msg) { const struct control_msg *msg) {
mutex_lock(controller->mutex); if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
control_msg_log(msg);
}
sc_mutex_lock(&controller->mutex);
bool was_empty = cbuf_is_empty(&controller->queue); bool was_empty = cbuf_is_empty(&controller->queue);
bool res = cbuf_push(&controller->queue, *msg); bool res = cbuf_push(&controller->queue, *msg);
if (was_empty) { if (was_empty) {
cond_signal(controller->msg_cond); sc_cond_signal(&controller->msg_cond);
} }
mutex_unlock(controller->mutex); sc_mutex_unlock(&controller->mutex);
return res; return res;
} }
static bool static bool
process_msg(struct controller *controller, process_msg(struct controller *controller, const struct control_msg *msg) {
const struct control_msg *msg) {
static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE]; static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE];
size_t length = control_msg_serialize(msg, serialized_msg); size_t length = control_msg_serialize(msg, serialized_msg);
if (!length) { if (!length) {
return false; return false;
} }
int w = net_send_all(controller->control_socket, serialized_msg, length); ssize_t w =
net_send_all(controller->control_socket, serialized_msg, length);
return (size_t) w == length; return (size_t) w == length;
} }
@ -73,20 +80,20 @@ run_controller(void *data) {
struct controller *controller = data; struct controller *controller = data;
for (;;) { for (;;) {
mutex_lock(controller->mutex); sc_mutex_lock(&controller->mutex);
while (!controller->stopped && cbuf_is_empty(&controller->queue)) { while (!controller->stopped && cbuf_is_empty(&controller->queue)) {
cond_wait(controller->msg_cond, controller->mutex); sc_cond_wait(&controller->msg_cond, &controller->mutex);
} }
if (controller->stopped) { if (controller->stopped) {
// stop immediately, do not process further msgs // stop immediately, do not process further msgs
mutex_unlock(controller->mutex); sc_mutex_unlock(&controller->mutex);
break; break;
} }
struct control_msg msg; struct control_msg msg;
bool non_empty = cbuf_take(&controller->queue, &msg); bool non_empty = cbuf_take(&controller->queue, &msg);
assert(non_empty); assert(non_empty);
(void) non_empty; (void) non_empty;
mutex_unlock(controller->mutex); sc_mutex_unlock(&controller->mutex);
bool ok = process_msg(controller, &msg); bool ok = process_msg(controller, &msg);
control_msg_destroy(&msg); control_msg_destroy(&msg);
@ -102,16 +109,16 @@ bool
controller_start(struct controller *controller) { controller_start(struct controller *controller) {
LOGD("Starting controller thread"); LOGD("Starting controller thread");
controller->thread = SDL_CreateThread(run_controller, "controller", bool ok = sc_thread_create(&controller->thread, run_controller,
controller); "scrcpy-ctl", controller);
if (!controller->thread) { if (!ok) {
LOGC("Could not start controller thread"); LOGC("Could not start controller thread");
return false; return false;
} }
if (!receiver_start(&controller->receiver)) { if (!receiver_start(&controller->receiver)) {
controller_stop(controller); controller_stop(controller);
SDL_WaitThread(controller->thread, NULL); sc_thread_join(&controller->thread, NULL);
return false; return false;
} }
@ -120,14 +127,14 @@ controller_start(struct controller *controller) {
void void
controller_stop(struct controller *controller) { controller_stop(struct controller *controller) {
mutex_lock(controller->mutex); sc_mutex_lock(&controller->mutex);
controller->stopped = true; controller->stopped = true;
cond_signal(controller->msg_cond); sc_cond_signal(&controller->msg_cond);
mutex_unlock(controller->mutex); sc_mutex_unlock(&controller->mutex);
} }
void void
controller_join(struct controller *controller) { controller_join(struct controller *controller) {
SDL_WaitThread(controller->thread, NULL); sc_thread_join(&controller->thread, NULL);
receiver_join(&controller->receiver); receiver_join(&controller->receiver);
} }

View File

@ -4,28 +4,29 @@
#include "common.h" #include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "control_msg.h" #include "control_msg.h"
#include "receiver.h" #include "receiver.h"
#include "util/acksync.h"
#include "util/cbuf.h" #include "util/cbuf.h"
#include "util/net.h" #include "util/net.h"
#include "util/thread.h"
struct control_msg_queue CBUF(struct control_msg, 64); struct control_msg_queue CBUF(struct control_msg, 64);
struct controller { struct controller {
socket_t control_socket; sc_socket control_socket;
SDL_Thread *thread; sc_thread thread;
SDL_mutex *mutex; sc_mutex mutex;
SDL_cond *msg_cond; sc_cond msg_cond;
bool stopped; bool stopped;
struct control_msg_queue queue; struct control_msg_queue queue;
struct receiver receiver; struct receiver receiver;
}; };
bool bool
controller_init(struct controller *controller, socket_t control_socket); controller_init(struct controller *controller, sc_socket control_socket,
struct sc_acksync *acksync);
void void
controller_destroy(struct controller *controller); controller_destroy(struct controller *controller);

View File

@ -3,22 +3,22 @@
#include <stdint.h> #include <stdint.h>
struct size { struct sc_size {
uint16_t width; uint16_t width;
uint16_t height; uint16_t height;
}; };
struct point { struct sc_point {
int32_t x; int32_t x;
int32_t y; int32_t y;
}; };
struct position { struct sc_position {
// The video screen size may be different from the real device screen size, // The video screen size may be different from the real device screen size,
// so store to which size the absolute position apply, to scale it // so store to which size the absolute position apply, to scale it
// accordingly. // accordingly.
struct size screen_size; struct sc_size screen_size;
struct point point; struct sc_point point;
}; };
#endif #endif

View File

@ -1,99 +1,163 @@
#include "decoder.h" #include "decoder.h"
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <libavutil/time.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include <unistd.h>
#include "events.h" #include "events.h"
#include "recorder.h"
#include "video_buffer.h" #include "video_buffer.h"
#include "util/buffer_util.h" #include "trait/frame_sink.h"
#include "util/log.h" #include "util/log.h"
// set the decoded frame as ready for rendering, and notify /** Downcast packet_sink to decoder */
#define DOWNCAST(SINK) container_of(SINK, struct decoder, packet_sink)
static void static void
push_frame(struct decoder *decoder) { decoder_close_first_sinks(struct decoder *decoder, unsigned count) {
bool previous_frame_skipped; while (count) {
video_buffer_offer_decoded_frame(decoder->video_buffer, struct sc_frame_sink *sink = decoder->sinks[--count];
&previous_frame_skipped); sink->ops->close(sink);
if (previous_frame_skipped) {
// the previous EVENT_NEW_FRAME will consume this frame
return;
} }
static SDL_Event new_frame_event = {
.type = EVENT_NEW_FRAME,
};
SDL_PushEvent(&new_frame_event);
} }
void static inline void
decoder_init(struct decoder *decoder, struct video_buffer *vb) { decoder_close_sinks(struct decoder *decoder) {
decoder->video_buffer = vb; decoder_close_first_sinks(decoder, decoder->sink_count);
} }
bool static bool
decoder_open_sinks(struct decoder *decoder) {
for (unsigned i = 0; i < decoder->sink_count; ++i) {
struct sc_frame_sink *sink = decoder->sinks[i];
if (!sink->ops->open(sink)) {
LOGE("Could not open frame sink %d", i);
decoder_close_first_sinks(decoder, i);
return false;
}
}
return true;
}
static bool
decoder_open(struct decoder *decoder, const AVCodec *codec) { decoder_open(struct decoder *decoder, const AVCodec *codec) {
decoder->codec_ctx = avcodec_alloc_context3(codec); decoder->codec_ctx = avcodec_alloc_context3(codec);
if (!decoder->codec_ctx) { if (!decoder->codec_ctx) {
LOGC("Could not allocate decoder context"); LOG_OOM();
return false; return false;
} }
decoder->codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
if (avcodec_open2(decoder->codec_ctx, codec, NULL) < 0) { if (avcodec_open2(decoder->codec_ctx, codec, NULL) < 0) {
LOGE("Could not open codec"); LOGE("Could not open codec");
avcodec_free_context(&decoder->codec_ctx); avcodec_free_context(&decoder->codec_ctx);
return false; return false;
} }
decoder->frame = av_frame_alloc();
if (!decoder->frame) {
LOG_OOM();
avcodec_close(decoder->codec_ctx);
avcodec_free_context(&decoder->codec_ctx);
return false;
}
if (!decoder_open_sinks(decoder)) {
LOGE("Could not open decoder sinks");
av_frame_free(&decoder->frame);
avcodec_close(decoder->codec_ctx);
avcodec_free_context(&decoder->codec_ctx);
return false;
}
return true; return true;
} }
void static void
decoder_close(struct decoder *decoder) { decoder_close(struct decoder *decoder) {
decoder_close_sinks(decoder);
av_frame_free(&decoder->frame);
avcodec_close(decoder->codec_ctx); avcodec_close(decoder->codec_ctx);
avcodec_free_context(&decoder->codec_ctx); avcodec_free_context(&decoder->codec_ctx);
} }
bool static bool
push_frame_to_sinks(struct decoder *decoder, const AVFrame *frame) {
for (unsigned i = 0; i < decoder->sink_count; ++i) {
struct sc_frame_sink *sink = decoder->sinks[i];
if (!sink->ops->push(sink, frame)) {
LOGE("Could not send frame to sink %d", i);
return false;
}
}
return true;
}
static bool
decoder_push(struct decoder *decoder, const AVPacket *packet) { decoder_push(struct decoder *decoder, const AVPacket *packet) {
// the new decoding/encoding API has been introduced by: bool is_config = packet->pts == AV_NOPTS_VALUE;
// <http://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=7fc329e2dd6226dfecaa4a1d7adf353bf2773726> if (is_config) {
#ifdef SCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API // nothing to do
int ret; return true;
if ((ret = avcodec_send_packet(decoder->codec_ctx, packet)) < 0) { }
int ret = avcodec_send_packet(decoder->codec_ctx, packet);
if (ret < 0 && ret != AVERROR(EAGAIN)) {
LOGE("Could not send video packet: %d", ret); LOGE("Could not send video packet: %d", ret);
return false; return false;
} }
ret = avcodec_receive_frame(decoder->codec_ctx, ret = avcodec_receive_frame(decoder->codec_ctx, decoder->frame);
decoder->video_buffer->decoding_frame);
if (!ret) { if (!ret) {
// a frame was received // a frame was received
push_frame(decoder); bool ok = push_frame_to_sinks(decoder, decoder->frame);
// A frame lost should not make the whole pipeline fail. The error, if
// any, is already logged.
(void) ok;
av_frame_unref(decoder->frame);
} else if (ret != AVERROR(EAGAIN)) { } else if (ret != AVERROR(EAGAIN)) {
LOGE("Could not receive video frame: %d", ret); LOGE("Could not receive video frame: %d", ret);
return false; return false;
} }
#else
int got_picture;
int len = avcodec_decode_video2(decoder->codec_ctx,
decoder->video_buffer->decoding_frame,
&got_picture,
packet);
if (len < 0) {
LOGE("Could not decode video packet: %d", len);
return false;
}
if (got_picture) {
push_frame(decoder);
}
#endif
return true; return true;
} }
void static bool
decoder_interrupt(struct decoder *decoder) { decoder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) {
video_buffer_interrupt(decoder->video_buffer); struct decoder *decoder = DOWNCAST(sink);
return decoder_open(decoder, codec);
}
static void
decoder_packet_sink_close(struct sc_packet_sink *sink) {
struct decoder *decoder = DOWNCAST(sink);
decoder_close(decoder);
}
static bool
decoder_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) {
struct decoder *decoder = DOWNCAST(sink);
return decoder_push(decoder, packet);
}
void
decoder_init(struct decoder *decoder) {
decoder->sink_count = 0;
static const struct sc_packet_sink_ops ops = {
.open = decoder_packet_sink_open,
.close = decoder_packet_sink_close,
.push = decoder_packet_sink_push,
};
decoder->packet_sink.ops = &ops;
}
void
decoder_add_sink(struct decoder *decoder, struct sc_frame_sink *sink) {
assert(decoder->sink_count < DECODER_MAX_SINKS);
assert(sink);
assert(sink->ops);
decoder->sinks[decoder->sink_count++] = sink;
} }

View File

@ -3,29 +3,27 @@
#include "common.h" #include "common.h"
#include "trait/packet_sink.h"
#include <stdbool.h> #include <stdbool.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
struct video_buffer; #define DECODER_MAX_SINKS 2
struct decoder { struct decoder {
struct video_buffer *video_buffer; struct sc_packet_sink packet_sink; // packet sink trait
struct sc_frame_sink *sinks[DECODER_MAX_SINKS];
unsigned sink_count;
AVCodecContext *codec_ctx; AVCodecContext *codec_ctx;
AVFrame *frame;
}; };
void void
decoder_init(struct decoder *decoder, struct video_buffer *vb); decoder_init(struct decoder *decoder);
bool
decoder_open(struct decoder *decoder, const AVCodec *codec);
void void
decoder_close(struct decoder *decoder); decoder_add_sink(struct decoder *decoder, struct sc_frame_sink *sink);
bool
decoder_push(struct decoder *decoder, const AVPacket *packet);
void
decoder_interrupt(struct decoder *decoder);
#endif #endif

View File

@ -1,23 +0,0 @@
#include "device.h"
#include "util/log.h"
bool
device_read_info(socket_t device_socket, char *device_name, struct size *size) {
unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4];
int r = net_recv_all(device_socket, buf, sizeof(buf));
if (r < DEVICE_NAME_FIELD_LENGTH + 4) {
LOGE("Could not retrieve device information");
return false;
}
// in case the client sends garbage
buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0';
// strcpy is safe here, since name contains at least
// DEVICE_NAME_FIELD_LENGTH bytes and strlen(buf) < DEVICE_NAME_FIELD_LENGTH
strcpy(device_name, (char *) buf);
size->width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8)
| buf[DEVICE_NAME_FIELD_LENGTH + 1];
size->height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8)
| buf[DEVICE_NAME_FIELD_LENGTH + 3];
return true;
}

View File

@ -1,17 +0,0 @@
#ifndef DEVICE_H
#define DEVICE_H
#include "common.h"
#include <stdbool.h>
#include "coords.h"
#include "util/net.h"
#define DEVICE_NAME_FIELD_LENGTH 64
// name must be at least DEVICE_NAME_FIELD_LENGTH bytes
bool
device_read_info(socket_t device_socket, char *device_name, struct size *size);
#endif

View File

@ -1,5 +1,7 @@
#include "device_msg.h" #include "device_msg.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include "util/buffer_util.h" #include "util/buffer_util.h"
@ -20,9 +22,9 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
if (clipboard_len > len - 5) { if (clipboard_len > len - 5) {
return 0; // not available return 0; // not available
} }
char *text = SDL_malloc(clipboard_len + 1); char *text = malloc(clipboard_len + 1);
if (!text) { if (!text) {
LOGW("Could not allocate text for clipboard"); LOG_OOM();
return -1; return -1;
} }
if (clipboard_len) { if (clipboard_len) {
@ -33,6 +35,11 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
msg->clipboard.text = text; msg->clipboard.text = text;
return 5 + clipboard_len; 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: default:
LOGW("Unknown device message type: %d", (int) msg->type); LOGW("Unknown device message type: %d", (int) msg->type);
return -1; // error, we cannot recover return -1; // error, we cannot recover
@ -42,6 +49,6 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
void void
device_msg_destroy(struct device_msg *msg) { device_msg_destroy(struct device_msg *msg) {
if (msg->type == DEVICE_MSG_TYPE_CLIPBOARD) { if (msg->type == DEVICE_MSG_TYPE_CLIPBOARD) {
SDL_free(msg->clipboard.text); free(msg->clipboard.text);
} }
} }

View File

@ -13,14 +13,18 @@
enum device_msg_type { enum device_msg_type {
DEVICE_MSG_TYPE_CLIPBOARD, DEVICE_MSG_TYPE_CLIPBOARD,
DEVICE_MSG_TYPE_ACK_CLIPBOARD,
}; };
struct device_msg { struct device_msg {
enum device_msg_type type; enum device_msg_type type;
union { union {
struct { struct {
char *text; // owned, to be freed by SDL_free() char *text; // owned, to be freed by free()
} clipboard; } clipboard;
struct {
uint64_t sequence;
} ack_clipboard;
}; };
}; };

View File

@ -1,195 +0,0 @@
#include "event_converter.h"
#define MAP(FROM, TO) case FROM: *to = TO; return true
#define FAIL default: return false
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_metastate
autocomplete_metastate(enum android_metastate metastate) {
// fill dependant flags
if (metastate & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) {
metastate |= AMETA_SHIFT_ON;
}
if (metastate & (AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) {
metastate |= AMETA_CTRL_ON;
}
if (metastate & (AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) {
metastate |= AMETA_ALT_ON;
}
if (metastate & (AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) {
metastate |= AMETA_META_ON;
}
return metastate;
}
enum android_metastate
convert_meta_state(SDL_Keymod mod) {
enum android_metastate metastate = 0;
if (mod & KMOD_LSHIFT) {
metastate |= AMETA_SHIFT_LEFT_ON;
}
if (mod & KMOD_RSHIFT) {
metastate |= AMETA_SHIFT_RIGHT_ON;
}
if (mod & KMOD_LCTRL) {
metastate |= AMETA_CTRL_LEFT_ON;
}
if (mod & KMOD_RCTRL) {
metastate |= AMETA_CTRL_RIGHT_ON;
}
if (mod & KMOD_LALT) {
metastate |= AMETA_ALT_LEFT_ON;
}
if (mod & KMOD_RALT) {
metastate |= AMETA_ALT_RIGHT_ON;
}
if (mod & KMOD_LGUI) { // Windows key
metastate |= AMETA_META_LEFT_ON;
}
if (mod & KMOD_RGUI) { // Windows key
metastate |= AMETA_META_RIGHT_ON;
}
if (mod & KMOD_NUM) {
metastate |= AMETA_NUM_LOCK_ON;
}
if (mod & KMOD_CAPS) {
metastate |= AMETA_CAPS_LOCK_ON;
}
if (mod & KMOD_MODE) { // Alt Gr
// no mapping?
}
// fill the dependent fields
return autocomplete_metastate(metastate);
}
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);
}
if (!(mod & (KMOD_NUM | KMOD_SHIFT))) {
// 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);
}
}
if (prefer_text && !(mod & KMOD_CTRL)) {
// do not forward alpha and space key events (unless Ctrl is pressed)
return false;
}
if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_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;
}
}
enum android_motionevent_buttons
convert_mouse_buttons(uint32_t state) {
enum android_motionevent_buttons buttons = 0;
if (state & SDL_BUTTON_LMASK) {
buttons |= AMOTION_EVENT_BUTTON_PRIMARY;
}
if (state & SDL_BUTTON_RMASK) {
buttons |= AMOTION_EVENT_BUTTON_SECONDARY;
}
if (state & SDL_BUTTON_MMASK) {
buttons |= AMOTION_EVENT_BUTTON_TERTIARY;
}
if (state & SDL_BUTTON_X1MASK) {
buttons |= AMOTION_EVENT_BUTTON_BACK;
}
if (state & SDL_BUTTON_X2MASK) {
buttons |= AMOTION_EVENT_BUTTON_FORWARD;
}
return buttons;
}
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;
}
}
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;
}
}

View File

@ -1,30 +0,0 @@
#ifndef CONVERT_H
#define CONVERT_H
#include "common.h"
#include <stdbool.h>
#include <SDL2/SDL_events.h>
#include "control_msg.h"
bool
convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to);
enum android_metastate
convert_meta_state(SDL_Keymod mod);
bool
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
bool prefer_text);
enum android_motionevent_buttons
convert_mouse_buttons(uint32_t state);
bool
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to);
bool
convert_touch_action(SDL_EventType from, enum android_motionevent_action *to);
#endif

View File

@ -1,3 +1,4 @@
#define EVENT_NEW_SESSION SDL_USEREVENT #define EVENT_NEW_FRAME SDL_USEREVENT
#define EVENT_NEW_FRAME (SDL_USEREVENT + 1) #define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1)
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 2) #define EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2)
#define EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3)

View File

@ -4,48 +4,54 @@
#include <string.h> #include <string.h>
#include "adb.h" #include "adb.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
#include "util/process_intr.h"
#define DEFAULT_PUSH_TARGET "/sdcard/" #define DEFAULT_PUSH_TARGET "/sdcard/Download/"
static void static void
file_handler_request_destroy(struct file_handler_request *req) { file_handler_request_destroy(struct file_handler_request *req) {
SDL_free(req->file); free(req->file);
} }
bool bool
file_handler_init(struct file_handler *file_handler, const char *serial, file_handler_init(struct file_handler *file_handler, const char *serial,
const char *push_target) { const char *push_target) {
assert(serial);
cbuf_init(&file_handler->queue); cbuf_init(&file_handler->queue);
if (!(file_handler->mutex = SDL_CreateMutex())) { bool ok = sc_mutex_init(&file_handler->mutex);
if (!ok) {
return false; return false;
} }
if (!(file_handler->event_cond = SDL_CreateCond())) { ok = sc_cond_init(&file_handler->event_cond);
SDL_DestroyMutex(file_handler->mutex); if (!ok) {
sc_mutex_destroy(&file_handler->mutex);
return false; return false;
} }
if (serial) { ok = sc_intr_init(&file_handler->intr);
file_handler->serial = SDL_strdup(serial); if (!ok) {
if (!file_handler->serial) { sc_cond_destroy(&file_handler->event_cond);
LOGW("Could not strdup serial"); sc_mutex_destroy(&file_handler->mutex);
SDL_DestroyCond(file_handler->event_cond); return false;
SDL_DestroyMutex(file_handler->mutex); }
return false;
} file_handler->serial = strdup(serial);
} else { if (!file_handler->serial) {
file_handler->serial = NULL; LOG_OOM();
sc_intr_destroy(&file_handler->intr);
sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex);
return false;
} }
// lazy initialization // lazy initialization
file_handler->initialized = false; file_handler->initialized = false;
file_handler->stopped = false; file_handler->stopped = false;
file_handler->current_process = PROCESS_NONE;
file_handler->push_target = push_target ? push_target : DEFAULT_PUSH_TARGET; file_handler->push_target = push_target ? push_target : DEFAULT_PUSH_TARGET;
@ -54,9 +60,10 @@ file_handler_init(struct file_handler *file_handler, const char *serial,
void void
file_handler_destroy(struct file_handler *file_handler) { file_handler_destroy(struct file_handler *file_handler) {
SDL_DestroyCond(file_handler->event_cond); sc_cond_destroy(&file_handler->event_cond);
SDL_DestroyMutex(file_handler->mutex); sc_mutex_destroy(&file_handler->mutex);
SDL_free(file_handler->serial); sc_intr_destroy(&file_handler->intr);
free(file_handler->serial);
struct file_handler_request req; struct file_handler_request req;
while (cbuf_take(&file_handler->queue, &req)) { while (cbuf_take(&file_handler->queue, &req)) {
@ -64,16 +71,6 @@ file_handler_destroy(struct file_handler *file_handler) {
} }
} }
static process_t
install_apk(const char *serial, const char *file) {
return adb_install(serial, file);
}
static process_t
push_file(const char *serial, const char *file, const char *push_target) {
return adb_push(serial, file, push_target);
}
bool bool
file_handler_request(struct file_handler *file_handler, file_handler_request(struct file_handler *file_handler,
file_handler_action_t action, char *file) { file_handler_action_t action, char *file) {
@ -92,72 +89,61 @@ file_handler_request(struct file_handler *file_handler,
.file = file, .file = file,
}; };
mutex_lock(file_handler->mutex); sc_mutex_lock(&file_handler->mutex);
bool was_empty = cbuf_is_empty(&file_handler->queue); bool was_empty = cbuf_is_empty(&file_handler->queue);
bool res = cbuf_push(&file_handler->queue, req); bool res = cbuf_push(&file_handler->queue, req);
if (was_empty) { if (was_empty) {
cond_signal(file_handler->event_cond); sc_cond_signal(&file_handler->event_cond);
} }
mutex_unlock(file_handler->mutex); sc_mutex_unlock(&file_handler->mutex);
return res; return res;
} }
static int static int
run_file_handler(void *data) { run_file_handler(void *data) {
struct file_handler *file_handler = data; struct file_handler *file_handler = data;
struct sc_intr *intr = &file_handler->intr;
const char *serial = file_handler->serial;
assert(serial);
const char *push_target = file_handler->push_target;
assert(push_target);
for (;;) { for (;;) {
mutex_lock(file_handler->mutex); sc_mutex_lock(&file_handler->mutex);
file_handler->current_process = PROCESS_NONE;
while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) { while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) {
cond_wait(file_handler->event_cond, file_handler->mutex); sc_cond_wait(&file_handler->event_cond, &file_handler->mutex);
} }
if (file_handler->stopped) { if (file_handler->stopped) {
// stop immediately, do not process further events // stop immediately, do not process further events
mutex_unlock(file_handler->mutex); sc_mutex_unlock(&file_handler->mutex);
break; break;
} }
struct file_handler_request req; struct file_handler_request req;
bool non_empty = cbuf_take(&file_handler->queue, &req); bool non_empty = cbuf_take(&file_handler->queue, &req);
assert(non_empty); assert(non_empty);
(void) non_empty; (void) non_empty;
sc_mutex_unlock(&file_handler->mutex);
process_t process;
if (req.action == ACTION_INSTALL_APK) { if (req.action == ACTION_INSTALL_APK) {
LOGI("Installing %s...", req.file); LOGI("Installing %s...", req.file);
process = install_apk(file_handler->serial, req.file); bool ok = adb_install(intr, serial, req.file, 0);
} else { if (ok) {
LOGI("Pushing %s...", req.file);
process = push_file(file_handler->serial, req.file,
file_handler->push_target);
}
file_handler->current_process = process;
mutex_unlock(file_handler->mutex);
if (req.action == ACTION_INSTALL_APK) {
if (process_check_success(process, "adb install", false)) {
LOGI("%s successfully installed", req.file); LOGI("%s successfully installed", req.file);
} else { } else {
LOGE("Failed to install %s", req.file); LOGE("Failed to install %s", req.file);
} }
} else { } else {
if (process_check_success(process, "adb push", false)) { LOGI("Pushing %s...", req.file);
LOGI("%s successfully pushed to %s", req.file, bool ok = adb_push(intr, serial, req.file, push_target, 0);
file_handler->push_target); if (ok) {
LOGI("%s successfully pushed to %s", req.file, push_target);
} else { } else {
LOGE("Failed to push %s to %s", req.file, LOGE("Failed to push %s to %s", req.file, push_target);
file_handler->push_target);
} }
} }
mutex_lock(file_handler->mutex);
// Close the process (it is necessary already terminated)
// Execute this call with mutex locked to avoid race conditions with
// file_handler_stop()
process_close(file_handler->current_process);
file_handler->current_process = PROCESS_NONE;
mutex_unlock(file_handler->mutex);
file_handler_request_destroy(&req); file_handler_request_destroy(&req);
} }
return 0; return 0;
@ -167,9 +153,9 @@ bool
file_handler_start(struct file_handler *file_handler) { file_handler_start(struct file_handler *file_handler) {
LOGD("Starting file_handler thread"); LOGD("Starting file_handler thread");
file_handler->thread = SDL_CreateThread(run_file_handler, "file_handler", bool ok = sc_thread_create(&file_handler->thread, run_file_handler,
file_handler); "scrcpy-file", file_handler);
if (!file_handler->thread) { if (!ok) {
LOGC("Could not start file_handler thread"); LOGC("Could not start file_handler thread");
return false; return false;
} }
@ -179,18 +165,14 @@ file_handler_start(struct file_handler *file_handler) {
void void
file_handler_stop(struct file_handler *file_handler) { file_handler_stop(struct file_handler *file_handler) {
mutex_lock(file_handler->mutex); sc_mutex_lock(&file_handler->mutex);
file_handler->stopped = true; file_handler->stopped = true;
cond_signal(file_handler->event_cond); sc_cond_signal(&file_handler->event_cond);
if (file_handler->current_process != PROCESS_NONE) { sc_intr_interrupt(&file_handler->intr);
if (!process_terminate(file_handler->current_process)) { sc_mutex_unlock(&file_handler->mutex);
LOGW("Could not terminate push/install process");
}
}
mutex_unlock(file_handler->mutex);
} }
void void
file_handler_join(struct file_handler *file_handler) { file_handler_join(struct file_handler *file_handler) {
SDL_WaitThread(file_handler->thread, NULL); sc_thread_join(&file_handler->thread, NULL);
} }

View File

@ -4,11 +4,11 @@
#include "common.h" #include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "adb.h" #include "adb.h"
#include "util/cbuf.h" #include "util/cbuf.h"
#include "util/thread.h"
#include "util/intr.h"
typedef enum { typedef enum {
ACTION_INSTALL_APK, ACTION_INSTALL_APK,
@ -25,13 +25,14 @@ struct file_handler_request_queue CBUF(struct file_handler_request, 16);
struct file_handler { struct file_handler {
char *serial; char *serial;
const char *push_target; const char *push_target;
SDL_Thread *thread; sc_thread thread;
SDL_mutex *mutex; sc_mutex mutex;
SDL_cond *event_cond; sc_cond event_cond;
bool stopped; bool stopped;
bool initialized; bool initialized;
process_t current_process;
struct file_handler_request_queue queue; struct file_handler_request_queue queue;
struct sc_intr intr;
}; };
bool bool
@ -50,7 +51,7 @@ file_handler_stop(struct file_handler *file_handler);
void void
file_handler_join(struct file_handler *file_handler); file_handler_join(struct file_handler *file_handler);
// take ownership of file, and will SDL_free() it // take ownership of file, and will free() it
bool bool
file_handler_request(struct file_handler *file_handler, file_handler_request(struct file_handler *file_handler,
file_handler_action_t action, file_handler_action_t action,

View File

@ -1,27 +1,25 @@
#include "fps_counter.h" #include "fps_counter.h"
#include <assert.h> #include <assert.h>
#include <SDL2/SDL_timer.h>
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
#define FPS_COUNTER_INTERVAL_MS 1000 #define FPS_COUNTER_INTERVAL SC_TICK_FROM_SEC(1)
bool bool
fps_counter_init(struct fps_counter *counter) { fps_counter_init(struct fps_counter *counter) {
counter->mutex = SDL_CreateMutex(); bool ok = sc_mutex_init(&counter->mutex);
if (!counter->mutex) { if (!ok) {
return false; return false;
} }
counter->state_cond = SDL_CreateCond(); ok = sc_cond_init(&counter->state_cond);
if (!counter->state_cond) { if (!ok) {
SDL_DestroyMutex(counter->mutex); sc_mutex_destroy(&counter->mutex);
return false; return false;
} }
counter->thread = NULL; counter->thread_started = false;
atomic_init(&counter->started, 0); atomic_init(&counter->started, 0);
// no need to initialize the other fields, they are unused until started // no need to initialize the other fields, they are unused until started
@ -30,8 +28,8 @@ fps_counter_init(struct fps_counter *counter) {
void void
fps_counter_destroy(struct fps_counter *counter) { fps_counter_destroy(struct fps_counter *counter) {
SDL_DestroyCond(counter->state_cond); sc_cond_destroy(&counter->state_cond);
SDL_DestroyMutex(counter->mutex); sc_mutex_destroy(&counter->mutex);
} }
static inline bool static inline bool
@ -48,7 +46,7 @@ set_started(struct fps_counter *counter, bool started) {
static void static void
display_fps(struct fps_counter *counter) { display_fps(struct fps_counter *counter) {
unsigned rendered_per_second = unsigned rendered_per_second =
counter->nr_rendered * 1000 / FPS_COUNTER_INTERVAL_MS; counter->nr_rendered * SC_TICK_FREQ / FPS_COUNTER_INTERVAL;
if (counter->nr_skipped) { if (counter->nr_skipped) {
LOGI("%u fps (+%u frames skipped)", rendered_per_second, LOGI("%u fps (+%u frames skipped)", rendered_per_second,
counter->nr_skipped); counter->nr_skipped);
@ -69,53 +67,54 @@ check_interval_expired(struct fps_counter *counter, uint32_t now) {
counter->nr_skipped = 0; counter->nr_skipped = 0;
// add a multiple of the interval // add a multiple of the interval
uint32_t elapsed_slices = uint32_t elapsed_slices =
(now - counter->next_timestamp) / FPS_COUNTER_INTERVAL_MS + 1; (now - counter->next_timestamp) / FPS_COUNTER_INTERVAL + 1;
counter->next_timestamp += FPS_COUNTER_INTERVAL_MS * elapsed_slices; counter->next_timestamp += FPS_COUNTER_INTERVAL * elapsed_slices;
} }
static int static int
run_fps_counter(void *data) { run_fps_counter(void *data) {
struct fps_counter *counter = data; struct fps_counter *counter = data;
mutex_lock(counter->mutex); sc_mutex_lock(&counter->mutex);
while (!counter->interrupted) { while (!counter->interrupted) {
while (!counter->interrupted && !is_started(counter)) { while (!counter->interrupted && !is_started(counter)) {
cond_wait(counter->state_cond, counter->mutex); sc_cond_wait(&counter->state_cond, &counter->mutex);
} }
while (!counter->interrupted && is_started(counter)) { while (!counter->interrupted && is_started(counter)) {
uint32_t now = SDL_GetTicks(); sc_tick now = sc_tick_now();
check_interval_expired(counter, now); check_interval_expired(counter, now);
assert(counter->next_timestamp > now);
uint32_t remaining = counter->next_timestamp - now;
// ignore the reason (timeout or signaled), we just loop anyway // ignore the reason (timeout or signaled), we just loop anyway
cond_wait_timeout(counter->state_cond, counter->mutex, remaining); sc_cond_timedwait(&counter->state_cond, &counter->mutex,
counter->next_timestamp);
} }
} }
mutex_unlock(counter->mutex); sc_mutex_unlock(&counter->mutex);
return 0; return 0;
} }
bool bool
fps_counter_start(struct fps_counter *counter) { fps_counter_start(struct fps_counter *counter) {
mutex_lock(counter->mutex); sc_mutex_lock(&counter->mutex);
counter->next_timestamp = SDL_GetTicks() + FPS_COUNTER_INTERVAL_MS; counter->next_timestamp = sc_tick_now() + FPS_COUNTER_INTERVAL;
counter->nr_rendered = 0; counter->nr_rendered = 0;
counter->nr_skipped = 0; counter->nr_skipped = 0;
mutex_unlock(counter->mutex); sc_mutex_unlock(&counter->mutex);
set_started(counter, true); set_started(counter, true);
cond_signal(counter->state_cond); sc_cond_signal(&counter->state_cond);
// counter->thread is always accessed from the same thread, no need to lock // counter->thread_started and counter->thread are always accessed from the
if (!counter->thread) { // same thread, no need to lock
counter->thread = if (!counter->thread_started) {
SDL_CreateThread(run_fps_counter, "fps counter", counter); bool ok = sc_thread_create(&counter->thread, run_fps_counter,
if (!counter->thread) { "scrcpy-fps", counter);
if (!ok) {
LOGE("Could not start FPS counter thread"); LOGE("Could not start FPS counter thread");
return false; return false;
} }
counter->thread_started = true;
} }
return true; return true;
@ -124,7 +123,7 @@ fps_counter_start(struct fps_counter *counter) {
void void
fps_counter_stop(struct fps_counter *counter) { fps_counter_stop(struct fps_counter *counter) {
set_started(counter, false); set_started(counter, false);
cond_signal(counter->state_cond); sc_cond_signal(&counter->state_cond);
} }
bool bool
@ -134,21 +133,25 @@ fps_counter_is_started(struct fps_counter *counter) {
void void
fps_counter_interrupt(struct fps_counter *counter) { fps_counter_interrupt(struct fps_counter *counter) {
if (!counter->thread) { if (!counter->thread_started) {
return; return;
} }
mutex_lock(counter->mutex); sc_mutex_lock(&counter->mutex);
counter->interrupted = true; counter->interrupted = true;
mutex_unlock(counter->mutex); sc_mutex_unlock(&counter->mutex);
// wake up blocking wait // wake up blocking wait
cond_signal(counter->state_cond); sc_cond_signal(&counter->state_cond);
} }
void void
fps_counter_join(struct fps_counter *counter) { fps_counter_join(struct fps_counter *counter) {
if (counter->thread) { if (counter->thread_started) {
SDL_WaitThread(counter->thread, NULL); // interrupted must be set by the thread calling join(), so no need to
// lock for the assertion
assert(counter->interrupted);
sc_thread_join(&counter->thread, NULL);
} }
} }
@ -158,11 +161,11 @@ fps_counter_add_rendered_frame(struct fps_counter *counter) {
return; return;
} }
mutex_lock(counter->mutex); sc_mutex_lock(&counter->mutex);
uint32_t now = SDL_GetTicks(); sc_tick now = sc_tick_now();
check_interval_expired(counter, now); check_interval_expired(counter, now);
++counter->nr_rendered; ++counter->nr_rendered;
mutex_unlock(counter->mutex); sc_mutex_unlock(&counter->mutex);
} }
void void
@ -171,9 +174,9 @@ fps_counter_add_skipped_frame(struct fps_counter *counter) {
return; return;
} }
mutex_lock(counter->mutex); sc_mutex_lock(&counter->mutex);
uint32_t now = SDL_GetTicks(); sc_tick now = sc_tick_now();
check_interval_expired(counter, now); check_interval_expired(counter, now);
++counter->nr_skipped; ++counter->nr_skipped;
mutex_unlock(counter->mutex); sc_mutex_unlock(&counter->mutex);
} }

View File

@ -6,13 +6,15 @@
#include <stdatomic.h> #include <stdatomic.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h> #include "util/thread.h"
struct fps_counter { struct fps_counter {
SDL_Thread *thread; sc_thread thread;
SDL_mutex *mutex; sc_mutex mutex;
SDL_cond *state_cond; sc_cond state_cond;
bool thread_started;
// atomic so that we can check without locking the mutex // atomic so that we can check without locking the mutex
// if the FPS counter is disabled, we don't want to lock unnecessarily // if the FPS counter is disabled, we don't want to lock unnecessarily
@ -22,7 +24,7 @@ struct fps_counter {
bool interrupted; bool interrupted;
unsigned nr_rendered; unsigned nr_rendered;
unsigned nr_skipped; unsigned nr_skipped;
uint32_t next_timestamp; sc_tick next_timestamp;
}; };
bool bool

90
app/src/frame_buffer.c Normal file
View File

@ -0,0 +1,90 @@
#include "frame_buffer.h"
#include <assert.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>
#include "util/log.h"
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;
}
bool ok = sc_mutex_init(&fb->mutex);
if (!ok) {
av_frame_free(&fb->pending_frame);
av_frame_free(&fb->tmp_frame);
return false;
}
// there is initially no frame, so consider it has already been consumed
fb->pending_frame_consumed = true;
return true;
}
void
sc_frame_buffer_destroy(struct sc_frame_buffer *fb) {
sc_mutex_destroy(&fb->mutex);
av_frame_free(&fb->pending_frame);
av_frame_free(&fb->tmp_frame);
}
static inline void
swap_frames(AVFrame **lhs, AVFrame **rhs) {
AVFrame *tmp = *lhs;
*lhs = *rhs;
*rhs = tmp;
}
bool
sc_frame_buffer_push(struct sc_frame_buffer *fb, const AVFrame *frame,
bool *previous_frame_skipped) {
sc_mutex_lock(&fb->mutex);
// Use a temporary frame to preserve pending_frame in case of error.
// tmp_frame is an empty frame, no need to call av_frame_unref() beforehand.
int r = av_frame_ref(fb->tmp_frame, frame);
if (r) {
LOGE("Could not ref frame: %d", r);
return false;
}
// Now that av_frame_ref() succeeded, we can replace the previous
// pending_frame
swap_frames(&fb->pending_frame, &fb->tmp_frame);
av_frame_unref(fb->tmp_frame);
if (previous_frame_skipped) {
*previous_frame_skipped = !fb->pending_frame_consumed;
}
fb->pending_frame_consumed = false;
sc_mutex_unlock(&fb->mutex);
return true;
}
void
sc_frame_buffer_consume(struct sc_frame_buffer *fb, AVFrame *dst) {
sc_mutex_lock(&fb->mutex);
assert(!fb->pending_frame_consumed);
fb->pending_frame_consumed = true;
av_frame_move_ref(dst, fb->pending_frame);
// av_frame_move_ref() resets its source frame, so no need to call
// av_frame_unref()
sc_mutex_unlock(&fb->mutex);
}

44
app/src/frame_buffer.h Normal file
View File

@ -0,0 +1,44 @@
#ifndef SC_FRAME_BUFFER_H
#define SC_FRAME_BUFFER_H
#include "common.h"
#include <stdbool.h>
#include "util/thread.h"
// forward declarations
typedef struct AVFrame AVFrame;
/**
* A frame buffer holds 1 pending frame, which is the last frame received from
* the producer (typically, the decoder).
*
* If a pending frame has not been consumed when the producer pushes a new
* frame, then it is lost. The intent is to always provide access to the very
* last frame to minimize latency.
*/
struct sc_frame_buffer {
AVFrame *pending_frame;
AVFrame *tmp_frame; // To preserve the pending frame on error
sc_mutex mutex;
bool pending_frame_consumed;
};
bool
sc_frame_buffer_init(struct sc_frame_buffer *fb);
void
sc_frame_buffer_destroy(struct sc_frame_buffer *fb);
bool
sc_frame_buffer_push(struct sc_frame_buffer *fb, const AVFrame *frame,
bool *skipped);
void
sc_frame_buffer_consume(struct sc_frame_buffer *fb, AVFrame *dst);
#endif

430
app/src/hid_keyboard.c Normal file
View File

@ -0,0 +1,430 @@
#include "hid_keyboard.h"
#include <assert.h>
#include "input_events.h"
#include "util/log.h"
/** Downcast key processor to hid_keyboard */
#define DOWNCAST(KP) container_of(KP, struct sc_hid_keyboard, key_processor)
#define HID_KEYBOARD_ACCESSORY_ID 1
#define HID_MODIFIER_NONE 0x00
#define HID_MODIFIER_LEFT_CONTROL (1 << 0)
#define HID_MODIFIER_LEFT_SHIFT (1 << 1)
#define HID_MODIFIER_LEFT_ALT (1 << 2)
#define HID_MODIFIER_LEFT_GUI (1 << 3)
#define HID_MODIFIER_RIGHT_CONTROL (1 << 4)
#define HID_MODIFIER_RIGHT_SHIFT (1 << 5)
#define HID_MODIFIER_RIGHT_ALT (1 << 6)
#define HID_MODIFIER_RIGHT_GUI (1 << 7)
#define HID_KEYBOARD_INDEX_MODIFIER 0
#define HID_KEYBOARD_INDEX_KEYS 2
// USB HID protocol says 6 keys in an event is the requirement for BIOS
// keyboard support, though OS could support more keys via modifying the report
// desc. 6 should be enough for scrcpy.
#define HID_KEYBOARD_MAX_KEYS 6
#define HID_KEYBOARD_EVENT_SIZE (2 + HID_KEYBOARD_MAX_KEYS)
#define HID_RESERVED 0x00
#define HID_ERROR_ROLL_OVER 0x01
/**
* For HID over AOAv2, only report descriptor is needed.
*
* The specification is available here:
* <https://www.usb.org/sites/default/files/hid1_11.pdf>
*
* In particular, read:
* - 6.2.2 Report Descriptor
* - Appendix B.1 Protocol 1 (Keyboard)
* - Appendix C: Keyboard Implementation
*
* Normally a basic HID keyboard uses 8 bytes:
* Modifier Reserved Key Key Key Key Key Key
*
* You can dump your device's report descriptor with:
*
* sudo usbhid-dump -m vid:pid -e descriptor
*
* (change vid:pid' to your device's vendor ID and product ID).
*/
static const unsigned char keyboard_report_desc[] = {
// Usage Page (Generic Desktop)
0x05, 0x01,
// Usage (Keyboard)
0x09, 0x06,
// Collection (Application)
0xA1, 0x01,
// Usage Page (Key Codes)
0x05, 0x07,
// Usage Minimum (224)
0x19, 0xE0,
// Usage Maximum (231)
0x29, 0xE7,
// Logical Minimum (0)
0x15, 0x00,
// Logical Maximum (1)
0x25, 0x01,
// Report Size (1)
0x75, 0x01,
// Report Count (8)
0x95, 0x08,
// Input (Data, Variable, Absolute): Modifier byte
0x81, 0x02,
// Report Size (8)
0x75, 0x08,
// Report Count (1)
0x95, 0x01,
// Input (Constant): Reserved byte
0x81, 0x01,
// Usage Page (LEDs)
0x05, 0x08,
// Usage Minimum (1)
0x19, 0x01,
// Usage Maximum (5)
0x29, 0x05,
// Report Size (1)
0x75, 0x01,
// Report Count (5)
0x95, 0x05,
// Output (Data, Variable, Absolute): LED report
0x91, 0x02,
// Report Size (3)
0x75, 0x03,
// Report Count (1)
0x95, 0x01,
// Output (Constant): LED report padding
0x91, 0x01,
// Usage Page (Key Codes)
0x05, 0x07,
// Usage Minimum (0)
0x19, 0x00,
// Usage Maximum (101)
0x29, SC_HID_KEYBOARD_KEYS - 1,
// Logical Minimum (0)
0x15, 0x00,
// Logical Maximum(101)
0x25, SC_HID_KEYBOARD_KEYS - 1,
// Report Size (8)
0x75, 0x08,
// Report Count (6)
0x95, HID_KEYBOARD_MAX_KEYS,
// Input (Data, Array): Keys
0x81, 0x00,
// End Collection
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(uint16_t mod) {
unsigned char modifiers = HID_MODIFIER_NONE;
if (mod & SC_MOD_LCTRL) {
modifiers |= HID_MODIFIER_LEFT_CONTROL;
}
if (mod & SC_MOD_LSHIFT) {
modifiers |= HID_MODIFIER_LEFT_SHIFT;
}
if (mod & SC_MOD_LALT) {
modifiers |= HID_MODIFIER_LEFT_ALT;
}
if (mod & SC_MOD_LGUI) {
modifiers |= HID_MODIFIER_LEFT_GUI;
}
if (mod & SC_MOD_RCTRL) {
modifiers |= HID_MODIFIER_RIGHT_CONTROL;
}
if (mod & SC_MOD_RSHIFT) {
modifiers |= HID_MODIFIER_RIGHT_SHIFT;
}
if (mod & SC_MOD_RALT) {
modifiers |= HID_MODIFIER_RIGHT_ALT;
}
if (mod & SC_MOD_RGUI) {
modifiers |= HID_MODIFIER_RIGHT_GUI;
}
return modifiers;
}
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;
}
buffer[HID_KEYBOARD_INDEX_MODIFIER] = HID_MODIFIER_NONE;
buffer[1] = HID_RESERVED;
memset(&buffer[HID_KEYBOARD_INDEX_KEYS], 0, HID_KEYBOARD_MAX_KEYS);
sc_hid_event_init(hid_event, HID_KEYBOARD_ACCESSORY_ID, buffer,
HID_KEYBOARD_EVENT_SIZE);
return true;
}
static inline bool
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 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
// ignore them totally, for example press 'a' first then press 'Control',
// if we ignore 'Control' event, only 'a' is sent.
if (scancode >= SC_HID_KEYBOARD_KEYS && !scancode_is_modifier(scancode)) {
// Scancode to ignore
return false;
}
if (!sc_hid_keyboard_event_init(hid_event)) {
LOGW("Could not initialize HID keyboard event");
return false;
}
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->action == SC_ACTION_DOWN);
LOGV("keys[%02x] = %s", scancode,
kb->keys[scancode] ? "true" : "false");
}
hid_event->buffer[HID_KEYBOARD_INDEX_MODIFIER] = modifiers;
unsigned char *keys_buffer = &hid_event->buffer[HID_KEYBOARD_INDEX_KEYS];
// Re-calculate pressed keys every time
int keys_pressed_count = 0;
for (int i = 0; i < SC_HID_KEYBOARD_KEYS; ++i) {
if (kb->keys[i]) {
// USB HID protocol says that if keys exceeds report count, a
// phantom state should be reported
if (keys_pressed_count >= HID_KEYBOARD_MAX_KEYS) {
// Phantom state:
// - Modifiers
// - Reserved
// - ErrorRollOver * HID_MAX_KEYS
memset(keys_buffer, HID_ERROR_ROLL_OVER, HID_KEYBOARD_MAX_KEYS);
goto end;
}
keys_buffer[keys_pressed_count] = i;
++keys_pressed_count;
}
}
end:
LOGV("hid keyboard: key %-4s scancode=%02x (%u) mod=%02x",
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 mods_state) {
bool capslock = mods_state & SC_MOD_CAPS;
bool numlock = mods_state & SC_MOD_NUM;
if (!capslock && !numlock) {
// Nothing to do
return true;
}
struct sc_hid_event hid_event;
if (!sc_hid_keyboard_event_init(&hid_event)) {
LOGW("Could not initialize HID keyboard event");
return false;
}
unsigned i = 0;
if (capslock) {
hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK;
++i;
}
if (numlock) {
hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_NUMLOCK;
++i;
}
if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) {
sc_hid_event_destroy(&hid_event);
LOGW("Could request HID event");
return false;
}
LOGD("HID keyboard state synchronized");
return true;
}
static void
sc_key_processor_process_key(struct sc_key_processor *kp,
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.
return;
}
struct sc_hid_keyboard *kb = DOWNCAST(kp);
struct sc_hid_event hid_event;
// Not all keys are supported, just ignore unsupported keys
if (convert_hid_keyboard_event(kb, &hid_event, event)) {
if (!kb->mod_lock_synchronized) {
// Inject CAPSLOCK and/or NUMLOCK if necessary to synchronize
// keyboard state
if (push_mod_lock_state(kb, event->mods_state)) {
kb->mod_lock_synchronized = true;
}
}
if (ack_to_wait) {
// Ctrl+v is pressed, so clipboard synchronization has been
// 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)) {
sc_hid_event_destroy(&hid_event);
LOGW("Could request HID event");
}
}
}
bool
sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) {
kb->aoa = aoa;
bool ok = sc_aoa_setup_hid(aoa, HID_KEYBOARD_ACCESSORY_ID,
keyboard_report_desc,
ARRAY_LEN(keyboard_report_desc));
if (!ok) {
LOGW("Register HID keyboard failed");
return false;
}
// Reset all states
memset(kb->keys, false, SC_HID_KEYBOARD_KEYS);
kb->mod_lock_synchronized = false;
static const struct sc_key_processor_ops ops = {
.process_key = sc_key_processor_process_key,
// 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;
}
void
sc_hid_keyboard_destroy(struct sc_hid_keyboard *kb) {
// Unregister HID keyboard so the soft keyboard shows again on Android
bool ok = sc_aoa_unregister_hid(kb->aoa, HID_KEYBOARD_ACCESSORY_ID);
if (!ok) {
LOGW("Could not unregister HID keyboard");
}
}

44
app/src/hid_keyboard.h Normal file
View File

@ -0,0 +1,44 @@
#ifndef SC_HID_KEYBOARD_H
#define SC_HID_KEYBOARD_H
#include "common.h"
#include <stdbool.h>
#include "aoa_hid.h"
#include "trait/key_processor.h"
// See "SDL2/SDL_scancode.h".
// Maybe SDL_Keycode is used by most people, but SDL_Scancode is taken from USB
// HID protocol.
// 0x65 is Application, typically AT-101 Keyboard ends here.
#define SC_HID_KEYBOARD_KEYS 0x66
/**
* HID keyboard events are sequence-based, every time keyboard state changes
* it sends an array of currently pressed keys, the host is responsible for
* compare events and determine which key becomes pressed and which key becomes
* released. In order to convert SDL_KeyboardEvent to HID events, we first use
* an array of keys to save each keys' state. And when a SDL_KeyboardEvent was
* emitted, we updated our state, and then we use a loop to generate HID
* events. The sequence of array elements is unimportant and when too much keys
* pressed at the same time (more than report count), we should generate
* phantom state. Don't forget that modifiers should be updated too, even for
* phantom state.
*/
struct sc_hid_keyboard {
struct sc_key_processor key_processor; // key processor trait
struct sc_aoa *aoa;
bool keys[SC_HID_KEYBOARD_KEYS];
bool mod_lock_synchronized;
};
bool
sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa);
void
sc_hid_keyboard_destroy(struct sc_hid_keyboard *kb);
#endif

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

@ -0,0 +1,265 @@
#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;
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

290
app/src/icon.c Normal file
View File

@ -0,0 +1,290 @@
#include "icon.h"
#include <assert.h>
#include <stdbool.h>
#include <libavformat/avformat.h>
#include <libavutil/pixdesc.h>
#include <libavutil/pixfmt.h>
#include "config.h"
#include "compat.h"
#include "util/file.h"
#include "util/log.h"
#include "util/str.h"
#define SCRCPY_PORTABLE_ICON_FILENAME "icon.png"
#define SCRCPY_DEFAULT_ICON_PATH \
PREFIX "/share/icons/hicolor/256x256/apps/scrcpy.png"
static char *
get_icon_path(void) {
#ifdef __WINDOWS__
const wchar_t *icon_path_env = _wgetenv(L"SCRCPY_ICON_PATH");
#else
const char *icon_path_env = getenv("SCRCPY_ICON_PATH");
#endif
if (icon_path_env) {
// if the envvar is set, use it
#ifdef __WINDOWS__
char *icon_path = sc_str_from_wchars(icon_path_env);
#else
char *icon_path = strdup(icon_path_env);
#endif
if (!icon_path) {
LOG_OOM();
return NULL;
}
LOGD("Using SCRCPY_ICON_PATH: %s", icon_path);
return icon_path;
}
#ifndef PORTABLE
LOGD("Using icon: " SCRCPY_DEFAULT_ICON_PATH);
char *icon_path = strdup(SCRCPY_DEFAULT_ICON_PATH);
if (!icon_path) {
LOG_OOM();
return NULL;
}
#else
char *icon_path = sc_file_get_local_path(SCRCPY_PORTABLE_ICON_FILENAME);
if (!icon_path) {
LOGE("Could not get icon path");
return NULL;
}
LOGD("Using icon (portable): %s", icon_path);
#endif
return icon_path;
}
static AVFrame *
decode_image(const char *path) {
AVFrame *result = NULL;
AVFormatContext *ctx = avformat_alloc_context();
if (!ctx) {
LOG_OOM();
return NULL;
}
if (avformat_open_input(&ctx, path, NULL, NULL) < 0) {
LOGE("Could not open image codec: %s", path);
goto free_ctx;
}
if (avformat_find_stream_info(ctx, NULL) < 0) {
LOGE("Could not find image stream info");
goto close_input;
}
int stream = av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (stream < 0 ) {
LOGE("Could not find best image stream");
goto close_input;
}
AVCodecParameters *params = ctx->streams[stream]->codecpar;
AVCodec *codec = avcodec_find_decoder(params->codec_id);
if (!codec) {
LOGE("Could not find image decoder");
goto close_input;
}
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
LOG_OOM();
goto close_input;
}
if (avcodec_parameters_to_context(codec_ctx, params) < 0) {
LOGE("Could not fill codec context");
goto free_codec_ctx;
}
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
LOGE("Could not open image codec");
goto free_codec_ctx;
}
AVFrame *frame = av_frame_alloc();
if (!frame) {
LOG_OOM();
goto close_codec;
}
AVPacket *packet = av_packet_alloc();
if (!packet) {
LOG_OOM();
av_frame_free(&frame);
goto close_codec;
}
if (av_read_frame(ctx, packet) < 0) {
LOGE("Could not read frame");
av_packet_free(&packet);
av_frame_free(&frame);
goto close_codec;
}
int ret;
if ((ret = avcodec_send_packet(codec_ctx, packet)) < 0) {
LOGE("Could not send icon packet: %d", ret);
av_packet_free(&packet);
av_frame_free(&frame);
goto close_codec;
}
if ((ret = avcodec_receive_frame(codec_ctx, frame)) != 0) {
LOGE("Could not receive icon frame: %d", ret);
av_packet_free(&packet);
av_frame_free(&frame);
goto close_codec;
}
av_packet_free(&packet);
result = frame;
close_codec:
avcodec_close(codec_ctx);
free_codec_ctx:
avcodec_free_context(&codec_ctx);
close_input:
avformat_close_input(&ctx);
free_ctx:
avformat_free_context(ctx);
return result;
}
#if !SDL_VERSION_ATLEAST(2, 0, 10)
// SDL_PixelFormatEnum has been introduced in SDL 2.0.10. Use int for older SDL
// versions.
typedef int SDL_PixelFormatEnum;
#endif
static SDL_PixelFormatEnum
to_sdl_pixel_format(enum AVPixelFormat fmt) {
switch (fmt) {
case AV_PIX_FMT_RGB24: return SDL_PIXELFORMAT_RGB24;
case AV_PIX_FMT_BGR24: return SDL_PIXELFORMAT_BGR24;
case AV_PIX_FMT_ARGB: return SDL_PIXELFORMAT_ARGB32;
case AV_PIX_FMT_RGBA: return SDL_PIXELFORMAT_RGBA32;
case AV_PIX_FMT_ABGR: return SDL_PIXELFORMAT_ABGR32;
case AV_PIX_FMT_BGRA: return SDL_PIXELFORMAT_BGRA32;
case AV_PIX_FMT_RGB565BE: return SDL_PIXELFORMAT_RGB565;
case AV_PIX_FMT_RGB555BE: return SDL_PIXELFORMAT_RGB555;
case AV_PIX_FMT_BGR565BE: return SDL_PIXELFORMAT_BGR565;
case AV_PIX_FMT_BGR555BE: return SDL_PIXELFORMAT_BGR555;
case AV_PIX_FMT_RGB444BE: return SDL_PIXELFORMAT_RGB444;
#if SDL_VERSION_ATLEAST(2, 0, 12)
case AV_PIX_FMT_BGR444BE: return SDL_PIXELFORMAT_BGR444;
#endif
case AV_PIX_FMT_PAL8: return SDL_PIXELFORMAT_INDEX8;
default: return SDL_PIXELFORMAT_UNKNOWN;
}
}
static SDL_Surface *
load_from_path(const char *path) {
AVFrame *frame = decode_image(path);
if (!frame) {
return NULL;
}
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format);
if (!desc) {
LOGE("Could not get icon format descriptor");
goto error;
}
bool is_packed = !(desc->flags & AV_PIX_FMT_FLAG_PLANAR);
if (!is_packed) {
LOGE("Could not load non-packed icon");
goto error;
}
SDL_PixelFormatEnum format = to_sdl_pixel_format(frame->format);
if (format == SDL_PIXELFORMAT_UNKNOWN) {
LOGE("Unsupported icon pixel format: %s (%d)", desc->name,
frame->format);
goto error;
}
int bits_per_pixel = av_get_bits_per_pixel(desc);
SDL_Surface *surface =
SDL_CreateRGBSurfaceWithFormatFrom(frame->data[0],
frame->width, frame->height,
bits_per_pixel,
frame->linesize[0],
format);
if (!surface) {
LOGE("Could not create icon surface");
goto error;
}
if (frame->format == AV_PIX_FMT_PAL8) {
// Initialize the SDL palette
uint8_t *data = frame->data[1];
SDL_Color colors[256];
for (int i = 0; i < 256; ++i) {
SDL_Color *color = &colors[i];
// The palette is transported in AVFrame.data[1], is 1024 bytes
// long (256 4-byte entries) and is formatted the same as in
// AV_PIX_FMT_RGB32 described above (i.e., it is also
// endian-specific).
// <https://ffmpeg.org/doxygen/4.1/pixfmt_8h.html#a9a8e335cf3be472042bc9f0cf80cd4c5>
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
color->a = data[i * 4];
color->r = data[i * 4 + 1];
color->g = data[i * 4 + 2];
color->b = data[i * 4 + 3];
#else
color->a = data[i * 4 + 3];
color->r = data[i * 4 + 2];
color->g = data[i * 4 + 1];
color->b = data[i * 4];
#endif
}
SDL_Palette *palette = surface->format->palette;
assert(palette);
int ret = SDL_SetPaletteColors(palette, colors, 0, 256);
if (ret) {
LOGE("Could not set palette colors");
SDL_FreeSurface(surface);
goto error;
}
}
surface->userdata = frame; // frame owns the data
return surface;
error:
av_frame_free(&frame);
return NULL;
}
SDL_Surface *
scrcpy_icon_load() {
char *icon_path = get_icon_path();
if (!icon_path) {
return NULL;
}
SDL_Surface *icon = load_from_path(icon_path);
free(icon_path);
return icon;
}
void
scrcpy_icon_destroy(SDL_Surface *icon) {
AVFrame *frame = icon->userdata;
assert(frame);
av_frame_free(&frame);
SDL_FreeSurface(icon);
}

16
app/src/icon.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef ICON_H
#define ICON_H
#include "common.h"
#include <stdbool.h>
#include <SDL2/SDL.h>
#include <libavformat/avformat.h>
SDL_Surface *
scrcpy_icon_load(void);
void
scrcpy_icon_destroy(SDL_Surface *icon);
#endif

View File

@ -1,53 +0,0 @@
/* XPM */
static char * icon_xpm[] = {
"48 48 2 1",
" c None",
". c #96C13E",
" .. .. ",
" ... ... ",
" ... ...... ... ",
" ................ ",
" .............. ",
" ................ ",
" .................. ",
" .................... ",
" ..... ........ ..... ",
" ..... ........ ..... ",
" ...................... ",
" ........................ ",
" ........................ ",
" ........................ ",
" ",
" ",
" .... ........................ .... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" .... ........................ .... ",
" ........................ ",
" ...................... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" .... .... "};

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

@ -0,0 +1,381 @@
#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,
SC_MOD_SCROLL = KMOD_SCROLL,
};
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,34 +3,103 @@
#include <assert.h> #include <assert.h>
#include <SDL2/SDL_keycode.h> #include <SDL2/SDL_keycode.h>
#include "event_converter.h" #include "input_events.h"
#include "util/lock.h" #include "screen.h"
#include "util/log.h" #include "util/log.h"
static const int ACTION_DOWN = 1; static inline uint16_t
static const int ACTION_UP = 1 << 1; sc_mods_state_from_sdl(uint16_t mods_state) {
return mods_state;
}
static inline enum sc_keycode
sc_keycode_from_sdl(SDL_Keycode keycode) {
return (enum sc_keycode) keycode;
}
static inline enum sc_scancode
sc_scancode_from_sdl(SDL_Scancode scancode) {
return (enum sc_scancode) scancode;
}
static inline enum sc_action
sc_action_from_sdl_keyboard_type(uint32_t type) {
assert(type == SDL_KEYDOWN || type == SDL_KEYUP);
if (type == SDL_KEYDOWN) {
return SC_ACTION_DOWN;
}
return SC_ACTION_UP;
}
static inline enum sc_action
sc_action_from_sdl_mousebutton_type(uint32_t type) {
assert(type == SDL_MOUSEBUTTONDOWN || type == SDL_MOUSEBUTTONUP);
if (type == SDL_MOUSEBUTTONDOWN) {
return SC_ACTION_DOWN;
}
return SC_ACTION_UP;
}
static inline enum sc_touch_action
sc_touch_action_from_sdl(uint32_t type) {
assert(type == SDL_FINGERMOTION || type == SDL_FINGERDOWN ||
type == SDL_FINGERUP);
if (type == SDL_FINGERMOTION) {
return SC_TOUCH_ACTION_MOVE;
}
if (type == SDL_FINGERDOWN) {
return SC_TOUCH_ACTION_DOWN;
}
return SC_TOUCH_ACTION_UP;
}
static inline enum sc_mouse_button
sc_mouse_button_from_sdl(uint8_t button) {
if (button >= SDL_BUTTON_LEFT && button <= SDL_BUTTON_X2) {
// SC_MOUSE_BUTTON_* constants are initialized from SDL_BUTTON(index)
return SDL_BUTTON(button);
}
return SC_MOUSE_BUTTON_UNKNOWN;
}
static inline uint8_t
sc_mouse_buttons_state_from_sdl(uint32_t buttons_state,
bool forward_all_clicks) {
assert(buttons_state < 0x100); // fits in uint8_t
uint8_t mask = SC_MOUSE_BUTTON_LEFT;
if (forward_all_clicks) {
mask |= SC_MOUSE_BUTTON_RIGHT
| SC_MOUSE_BUTTON_MIDDLE
| SC_MOUSE_BUTTON_X1
| SC_MOUSE_BUTTON_X2;
}
return buttons_state & mask;
}
#define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI) #define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI)
static inline uint16_t static inline uint16_t
to_sdl_mod(unsigned mod) { to_sdl_mod(unsigned shortcut_mod) {
uint16_t sdl_mod = 0; uint16_t sdl_mod = 0;
if (mod & SC_MOD_LCTRL) { if (shortcut_mod & SC_SHORTCUT_MOD_LCTRL) {
sdl_mod |= KMOD_LCTRL; sdl_mod |= KMOD_LCTRL;
} }
if (mod & SC_MOD_RCTRL) { if (shortcut_mod & SC_SHORTCUT_MOD_RCTRL) {
sdl_mod |= KMOD_RCTRL; sdl_mod |= KMOD_RCTRL;
} }
if (mod & SC_MOD_LALT) { if (shortcut_mod & SC_SHORTCUT_MOD_LALT) {
sdl_mod |= KMOD_LALT; sdl_mod |= KMOD_LALT;
} }
if (mod & SC_MOD_RALT) { if (shortcut_mod & SC_SHORTCUT_MOD_RALT) {
sdl_mod |= KMOD_RALT; sdl_mod |= KMOD_RALT;
} }
if (mod & SC_MOD_LSUPER) { if (shortcut_mod & SC_SHORTCUT_MOD_LSUPER) {
sdl_mod |= KMOD_LGUI; sdl_mod |= KMOD_LGUI;
} }
if (mod & SC_MOD_RSUPER) { if (shortcut_mod & SC_SHORTCUT_MOD_RSUPER) {
sdl_mod |= KMOD_RGUI; sdl_mod |= KMOD_RGUI;
} }
return sdl_mod; return sdl_mod;
@ -54,15 +123,21 @@ is_shortcut_mod(struct input_manager *im, uint16_t sdl_mod) {
void void
input_manager_init(struct input_manager *im, input_manager_init(struct input_manager *im,
const struct scrcpy_options *options) const struct input_manager_params *params) {
{ assert(!params->control || (params->kp && params->kp->ops));
im->control = options->control; assert(!params->control || (params->mp && params->mp->ops));
im->forward_key_repeat = options->forward_key_repeat;
im->prefer_text = options->prefer_text;
im->forward_all_clicks = options->forward_all_clicks;
im->legacy_paste = options->legacy_paste;
const struct sc_shortcut_mods *shortcut_mods = &options->shortcut_mods; im->controller = params->controller;
im->screen = params->screen;
im->kp = params->kp;
im->mp = params->mp;
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 = params->shortcut_mods;
assert(shortcut_mods->count); assert(shortcut_mods->count);
assert(shortcut_mods->count < SC_MAX_SHORTCUT_MODS); assert(shortcut_mods->count < SC_MAX_SHORTCUT_MODS);
for (unsigned i = 0; i < shortcut_mods->count; ++i) { for (unsigned i = 0; i < shortcut_mods->count; ++i) {
@ -73,87 +148,82 @@ input_manager_init(struct input_manager *im,
im->sdl_shortcut_mods.count = shortcut_mods->count; im->sdl_shortcut_mods.count = shortcut_mods->count;
im->vfinger_down = false; im->vfinger_down = false;
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 static void
send_keycode(struct controller *controller, enum android_keycode keycode, send_keycode(struct controller *controller, enum android_keycode keycode,
int actions, const char *name) { enum sc_action action, const char *name) {
// send DOWN event // send DOWN event
struct control_msg msg; struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_KEYCODE; msg.type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
msg.inject_keycode.action = action == SC_ACTION_DOWN
? AKEY_EVENT_ACTION_DOWN
: AKEY_EVENT_ACTION_UP;
msg.inject_keycode.keycode = keycode; msg.inject_keycode.keycode = keycode;
msg.inject_keycode.metastate = 0; msg.inject_keycode.metastate = 0;
msg.inject_keycode.repeat = 0; msg.inject_keycode.repeat = 0;
if (actions & ACTION_DOWN) { if (!controller_push_msg(controller, &msg)) {
msg.inject_keycode.action = AKEY_EVENT_ACTION_DOWN; LOGW("Could not request 'inject %s'", name);
if (!controller_push_msg(controller, &msg)) { return;
LOGW("Could not request 'inject %s (DOWN)'", name);
return;
}
}
if (actions & ACTION_UP) {
msg.inject_keycode.action = AKEY_EVENT_ACTION_UP;
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'inject %s (UP)'", name);
}
} }
} }
static inline void static inline void
action_home(struct controller *controller, int actions) { action_home(struct controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_HOME, actions, "HOME"); send_keycode(controller, AKEYCODE_HOME, action, "HOME");
} }
static inline void static inline void
action_back(struct controller *controller, int actions) { action_back(struct controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_BACK, actions, "BACK"); send_keycode(controller, AKEYCODE_BACK, action, "BACK");
} }
static inline void static inline void
action_app_switch(struct controller *controller, int actions) { action_app_switch(struct controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_APP_SWITCH, actions, "APP_SWITCH"); send_keycode(controller, AKEYCODE_APP_SWITCH, action, "APP_SWITCH");
} }
static inline void static inline void
action_power(struct controller *controller, int actions) { action_power(struct controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_POWER, actions, "POWER"); send_keycode(controller, AKEYCODE_POWER, action, "POWER");
} }
static inline void static inline void
action_volume_up(struct controller *controller, int actions) { action_volume_up(struct controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_VOLUME_UP, actions, "VOLUME_UP"); send_keycode(controller, AKEYCODE_VOLUME_UP, action, "VOLUME_UP");
} }
static inline void static inline void
action_volume_down(struct controller *controller, int actions) { action_volume_down(struct controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_VOLUME_DOWN, actions, "VOLUME_DOWN"); send_keycode(controller, AKEYCODE_VOLUME_DOWN, action, "VOLUME_DOWN");
} }
static inline void static inline void
action_menu(struct controller *controller, int actions) { action_menu(struct controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_MENU, actions, "MENU"); send_keycode(controller, AKEYCODE_MENU, action, "MENU");
}
static inline void
action_copy(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_COPY, actions, "COPY");
}
static inline void
action_cut(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_CUT, actions, "CUT");
} }
// turn the screen on if it was off, press BACK otherwise // turn the screen on if it was off, press BACK otherwise
// If the screen is off, it is turned on only on ACTION_DOWN
static void static void
press_back_or_turn_screen_on(struct controller *controller) { press_back_or_turn_screen_on(struct controller *controller,
enum sc_action action) {
struct control_msg msg; struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON; msg.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON;
msg.back_or_screen_on.action = action == SC_ACTION_DOWN
? AKEY_EVENT_ACTION_DOWN
: AKEY_EVENT_ACTION_UP;
if (!controller_push_msg(controller, &msg)) { if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'press back or turn screen on'"); LOGW("Could not request 'press back or turn screen on'");
return;
} }
} }
@ -168,37 +238,69 @@ expand_notification_panel(struct controller *controller) {
} }
static void static void
collapse_notification_panel(struct controller *controller) { expand_settings_panel(struct controller *controller) {
struct control_msg msg; struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL; msg.type = CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL;
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'expand settings panel'");
}
}
static void
collapse_panels(struct controller *controller) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_COLLAPSE_PANELS;
if (!controller_push_msg(controller, &msg)) { if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'collapse notification panel'"); LOGW("Could not request 'collapse notification panel'");
} }
} }
static void static bool
set_device_clipboard(struct controller *controller, bool paste) { get_device_clipboard(struct controller *controller,
enum get_clipboard_copy_key copy_key) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD;
msg.get_clipboard.copy_key = copy_key;
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'get device clipboard'");
return false;
}
return true;
}
static bool
set_device_clipboard(struct controller *controller, bool paste,
uint64_t sequence) {
char *text = SDL_GetClipboardText(); char *text = SDL_GetClipboardText();
if (!text) { if (!text) {
LOGW("Could not get clipboard text: %s", SDL_GetError()); LOGW("Could not get clipboard text: %s", SDL_GetError());
return; return false;
} }
if (!*text) {
// empty text char *text_dup = strdup(text);
SDL_free(text); SDL_free(text);
return; if (!text_dup) {
LOGW("Could not strdup input text");
return false;
} }
struct control_msg msg; struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD; msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD;
msg.set_clipboard.text = text; msg.set_clipboard.sequence = sequence;
msg.set_clipboard.text = text_dup;
msg.set_clipboard.paste = paste; msg.set_clipboard.paste = paste;
if (!controller_push_msg(controller, &msg)) { if (!controller_push_msg(controller, &msg)) {
SDL_free(text); free(text_dup);
LOGW("Could not request 'set device clipboard'"); LOGW("Could not request 'set device clipboard'");
return false;
} }
return true;
} }
static void static void
@ -242,11 +344,18 @@ clipboard_paste(struct controller *controller) {
return; return;
} }
char *text_dup = strdup(text);
SDL_free(text);
if (!text_dup) {
LOGW("Could not strdup input text");
return;
}
struct control_msg msg; struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = text; msg.inject_text.text = text_dup;
if (!controller_push_msg(controller, &msg)) { if (!controller_push_msg(controller, &msg)) {
SDL_free(text); free(text_dup);
LOGW("Could not request 'paste clipboard'"); LOGW("Could not request 'paste clipboard'");
} }
} }
@ -273,39 +382,30 @@ rotate_client_right(struct screen *screen) {
screen_set_rotation(screen, new_rotation); screen_set_rotation(screen, new_rotation);
} }
void static void
input_manager_process_text_input(struct input_manager *im, input_manager_process_text_input(struct input_manager *im,
const SDL_TextInputEvent *event) { const SDL_TextInputEvent *event) {
if (!im->kp->ops->process_text) {
// The key processor does not support text input
return;
}
if (is_shortcut_mod(im, SDL_GetModState())) { if (is_shortcut_mod(im, SDL_GetModState())) {
// A shortcut must never generate text events // A shortcut must never generate text events
return; return;
} }
if (!im->prefer_text) {
char c = event->text[0];
if (isalpha(c) || c == ' ') {
assert(event->text[1] == '\0');
// letters and space are handled as raw key event
return;
}
}
struct control_msg msg; struct sc_text_event evt = {
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; .text = event->text,
msg.inject_text.text = SDL_strdup(event->text); };
if (!msg.inject_text.text) {
LOGW("Could not strdup input text"); im->kp->ops->process_text(im->kp, &evt);
return;
}
if (!controller_push_msg(im->controller, &msg)) {
SDL_free(msg.inject_text.text);
LOGW("Could not request 'inject text'");
}
} }
static bool static bool
simulate_virtual_finger(struct input_manager *im, simulate_virtual_finger(struct input_manager *im,
enum android_motionevent_action action, enum android_motionevent_action action,
struct point point) { struct sc_point point) {
bool up = action == AMOTION_EVENT_ACTION_UP; bool up = action == AMOTION_EVENT_ACTION_UP;
struct control_msg msg; struct control_msg msg;
@ -325,53 +425,43 @@ simulate_virtual_finger(struct input_manager *im,
return true; return true;
} }
static struct point static struct sc_point
inverse_point(struct point point, struct size size) { inverse_point(struct sc_point point, struct sc_size size) {
point.x = size.width - point.x; point.x = size.width - point.x;
point.y = size.height - point.y; point.y = size.height - point.y;
return point; return point;
} }
static bool static void
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
bool prefer_text, uint32_t repeat) {
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) {
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);
return true;
}
void
input_manager_process_key(struct input_manager *im, input_manager_process_key(struct input_manager *im,
const SDL_KeyboardEvent *event) { const SDL_KeyboardEvent *event) {
// control: indicates the state of the command-line option --no-control // control: indicates the state of the command-line option --no-control
bool control = im->control; bool control = im->control;
bool smod = is_shortcut_mod(im, event->keysym.mod);
struct controller *controller = im->controller; struct controller *controller = im->controller;
SDL_Keycode keycode = event->keysym.sym; SDL_Keycode keycode = event->keysym.sym;
uint16_t mod = event->keysym.mod;
bool down = event->type == SDL_KEYDOWN; bool down = event->type == SDL_KEYDOWN;
bool ctrl = event->keysym.mod & KMOD_CTRL; bool ctrl = event->keysym.mod & KMOD_CTRL;
bool shift = event->keysym.mod & KMOD_SHIFT; bool shift = event->keysym.mod & KMOD_SHIFT;
bool repeat = event->repeat; bool repeat = event->repeat;
bool smod = is_shortcut_mod(im, mod);
if (down && !repeat) {
if (keycode == im->last_keycode && mod == im->last_mod) {
++im->key_repeat;
} else {
im->key_repeat = 0;
im->last_keycode = keycode;
im->last_mod = mod;
}
}
// The shortcut modifier is pressed // The shortcut modifier is pressed
if (smod) { if (smod) {
int action = down ? ACTION_DOWN : ACTION_UP; enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
switch (keycode) { switch (keycode) {
case SDLK_h: case SDLK_h:
if (control && !shift && !repeat) { if (control && !shift && !repeat) {
@ -430,13 +520,15 @@ input_manager_process_key(struct input_manager *im,
} }
return; return;
case SDLK_c: case SDLK_c:
if (control && !shift && !repeat) { if (control && !shift && !repeat && down) {
action_copy(controller, action); get_device_clipboard(controller,
GET_CLIPBOARD_COPY_KEY_COPY);
} }
return; return;
case SDLK_x: case SDLK_x:
if (control && !shift && !repeat) { if (control && !shift && !repeat && down) {
action_cut(controller, action); get_device_clipboard(controller,
GET_CLIPBOARD_COPY_KEY_CUT);
} }
return; return;
case SDLK_v: case SDLK_v:
@ -445,8 +537,10 @@ input_manager_process_key(struct input_manager *im,
// inject the text as input events // inject the text as input events
clipboard_paste(controller); clipboard_paste(controller);
} else { } else {
// store the text in the device clipboard and paste // store the text in the device clipboard and paste,
set_device_clipboard(controller, true); // without requesting an acknowledgment
set_device_clipboard(controller, true,
SC_SEQUENCE_INVALID);
} }
} }
return; return;
@ -467,17 +561,17 @@ input_manager_process_key(struct input_manager *im,
return; return;
case SDLK_i: case SDLK_i:
if (!shift && !repeat && down) { if (!shift && !repeat && down) {
struct fps_counter *fps_counter = switch_fps_counter_state(&im->screen->fps_counter);
im->video_buffer->fps_counter;
switch_fps_counter_state(fps_counter);
} }
return; return;
case SDLK_n: case SDLK_n:
if (control && !repeat && down) { if (control && !repeat && down) {
if (shift) { if (shift) {
collapse_notification_panel(controller); collapse_panels(controller);
} else { } else if (im->key_repeat == 0) {
expand_notification_panel(controller); expand_notification_panel(controller);
} else {
expand_settings_panel(controller);
} }
} }
return; return;
@ -495,136 +589,119 @@ input_manager_process_key(struct input_manager *im,
return; return;
} }
if (event->repeat) { uint64_t ack_to_wait = SC_SEQUENCE_INVALID;
if (!im->forward_key_repeat) { bool is_ctrl_v = ctrl && !shift && keycode == SDLK_v && down && !repeat;
return; if (im->clipboard_autosync && is_ctrl_v) {
}
++im->repeat;
} else {
im->repeat = 0;
}
if (ctrl && !shift && keycode == SDLK_v && down && !repeat) {
if (im->legacy_paste) { if (im->legacy_paste) {
// inject the text as input events // inject the text as input events
clipboard_paste(controller); clipboard_paste(controller);
return; 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 // Synchronize the computer clipboard to the device clipboard before
// sending Ctrl+v, to allow seamless copy-paste. // 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;
}
struct control_msg msg; if (im->kp->async_paste) {
if (convert_input_key(event, &msg, im->prefer_text, im->repeat)) { // The key processor must wait for this ack before injecting Ctrl+v
if (!controller_push_msg(controller, &msg)) { ack_to_wait = sequence;
LOGW("Could not request 'inject keycode'"); // Increment only when the request succeeded
++im->next_sequence;
} }
} }
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 bool static void
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;
}
void
input_manager_process_mouse_motion(struct input_manager *im, input_manager_process_mouse_motion(struct input_manager *im,
const SDL_MouseMotionEvent *event) { const SDL_MouseMotionEvent *event) {
if (!event->state) {
// do not send motion events when no button is pressed uint32_t mask = SDL_BUTTON_LMASK;
return; if (im->forward_all_clicks) {
mask |= SDL_BUTTON_MMASK | SDL_BUTTON_RMASK;
} }
//if (!(event->state & mask)) {
// // do not send motion events when no click is pressed
// return;
//}
if (event->which == SDL_TOUCH_MOUSEID) { if (event->which == SDL_TOUCH_MOUSEID) {
// simulated from touch events, so it's a duplicate // simulated from touch events, so it's a duplicate
return; return;
} }
struct control_msg msg;
if (!convert_mouse_motion(event, im->screen, &msg)) {
return;
}
if (!controller_push_msg(im->controller, &msg)) { struct sc_mouse_motion_event evt = {
LOGW("Could not request 'inject mouse motion event'"); .position = {
} .screen_size = im->screen->frame_size,
.point = 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);
if (im->vfinger_down) { if (im->vfinger_down) {
struct point mouse = msg.inject_touch_event.position.point; struct sc_point mouse =
struct point vfinger = inverse_point(mouse, im->screen->frame_size); 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); simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger);
} }
} }
static bool static void
convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen, input_manager_process_touch(struct input_manager *im,
struct control_msg *to) { const SDL_TouchFingerEvent *event) {
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; if (!im->mp->ops->process_touch) {
// The mouse processor does not support touch events
if (!convert_touch_action(from->type, &to->inject_touch_event.action)) { return;
return false;
} }
to->inject_touch_event.pointer_id = from->fingerId;
to->inject_touch_event.position.screen_size = screen->frame_size;
int dw; int dw;
int dh; int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh); SDL_GL_GetDrawableSize(im->screen->window, &dw, &dh);
// SDL touch event coordinates are normalized in the range [0; 1] // SDL touch event coordinates are normalized in the range [0; 1]
int32_t x = from->x * dw; int32_t x = event->x * dw;
int32_t y = from->y * dh; int32_t y = event->y * dh;
to->inject_touch_event.position.point =
screen_convert_drawable_to_frame_coords(screen, x, y);
to->inject_touch_event.pressure = from->pressure; struct sc_touch_event evt = {
to->inject_touch_event.buttons = 0; .position = {
return true; .screen_size = im->screen->frame_size,
.point = 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);
} }
void static void
input_manager_process_touch(struct input_manager *im,
const SDL_TouchFingerEvent *event) {
struct control_msg msg;
if (convert_touch(event, im->screen, &msg)) {
if (!controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject touch event'");
}
}
}
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;
}
void
input_manager_process_mouse_button(struct input_manager *im, input_manager_process_mouse_button(struct input_manager *im,
const SDL_MouseButtonEvent *event) { const SDL_MouseButtonEvent *event) {
bool control = im->control; bool control = im->control;
@ -635,13 +712,27 @@ input_manager_process_mouse_button(struct input_manager *im,
} }
bool down = event->type == SDL_MOUSEBUTTONDOWN; bool down = event->type == SDL_MOUSEBUTTONDOWN;
if (!im->forward_all_clicks && down) { if (!im->forward_all_clicks) {
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
if (control && event->button == SDL_BUTTON_X1) {
action_app_switch(im->controller, action);
return;
}
if (control && event->button == SDL_BUTTON_X2 && down) {
if (event->clicks < 2) {
expand_notification_panel(im->controller);
} else {
expand_settings_panel(im->controller);
}
return;
}
if (control && event->button == SDL_BUTTON_RIGHT) { if (control && event->button == SDL_BUTTON_RIGHT) {
press_back_or_turn_screen_on(im->controller); press_back_or_turn_screen_on(im->controller, action);
return; return;
} }
if (control && event->button == SDL_BUTTON_MIDDLE) { if (control && event->button == SDL_BUTTON_MIDDLE) {
action_home(im->controller, ACTION_DOWN | ACTION_UP); action_home(im->controller, action);
return; return;
} }
@ -654,7 +745,9 @@ input_manager_process_mouse_button(struct input_manager *im,
bool outside = x < r->x || x >= r->x + r->w bool outside = x < r->x || x >= r->x + r->w
|| y < r->y || y >= r->y + r->h; || y < r->y || y >= r->y + r->h;
if (outside) { if (outside) {
screen_resize_to_fit(im->screen); if (down) {
screen_resize_to_fit(im->screen);
}
return; return;
} }
} }
@ -665,13 +758,27 @@ input_manager_process_mouse_button(struct input_manager *im,
return; return;
} }
struct control_msg msg; uint32_t sdl_buttons_state = SDL_GetMouseState(NULL, NULL);
if (!convert_mouse_button(event, im->screen, &msg)) {
return;
}
if (!controller_push_msg(im->controller, &msg)) { struct sc_mouse_click_event evt = {
LOGW("Could not request 'inject mouse button event'"); .position = {
.screen_size = im->screen->frame_size,
.point = 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; return;
} }
@ -687,8 +794,10 @@ input_manager_process_mouse_button(struct input_manager *im,
#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL)) #define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL))
if ((down && !im->vfinger_down && CTRL_PRESSED) if ((down && !im->vfinger_down && CTRL_PRESSED)
|| (!down && im->vfinger_down)) { || (!down && im->vfinger_down)) {
struct point mouse = msg.inject_touch_event.position.point; struct sc_point mouse =
struct point vfinger = inverse_point(mouse, im->screen->frame_size); 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 enum android_motionevent_action action = down
? AMOTION_EVENT_ACTION_DOWN ? AMOTION_EVENT_ACTION_DOWN
: AMOTION_EVENT_ACTION_UP; : AMOTION_EVENT_ACTION_UP;
@ -699,37 +808,73 @@ input_manager_process_mouse_button(struct input_manager *im,
} }
} }
static bool static void
convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen, input_manager_process_mouse_wheel(struct input_manager *im,
struct control_msg *to) { 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 // mouse_x and mouse_y are expressed in pixels relative to the window
int mouse_x; int mouse_x;
int mouse_y; int mouse_y;
SDL_GetMouseState(&mouse_x, &mouse_y); uint32_t buttons = SDL_GetMouseState(&mouse_x, &mouse_y);
struct position position = { struct sc_mouse_scroll_event evt = {
.screen_size = screen->frame_size, .position = {
.point = screen_convert_window_to_frame_coords(screen, .screen_size = im->screen->frame_size,
mouse_x, mouse_y), .point = 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),
}; };
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT; im->mp->ops->process_mouse_scroll(im->mp, &evt);
to->inject_scroll_event.position = position;
to->inject_scroll_event.hscroll = from->x;
to->inject_scroll_event.vscroll = from->y;
return true;
} }
void bool
input_manager_process_mouse_wheel(struct input_manager *im, input_manager_handle_event(struct input_manager *im, SDL_Event *event) {
const SDL_MouseWheelEvent *event) { switch (event->type) {
struct control_msg msg; case SDL_TEXTINPUT:
if (convert_mouse_wheel(event, im->screen, &msg)) { if (!im->control) {
if (!controller_push_msg(im->controller, &msg)) { return true;
LOGW("Could not request 'inject mouse wheel event'"); }
} 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);
return true;
case SDL_MOUSEMOTION:
if (!im->control) {
break;
}
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);
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);
return true;
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
input_manager_process_touch(im, &event->tfinger);
return true;
} }
return false;
} }

View File

@ -9,24 +9,23 @@
#include "controller.h" #include "controller.h"
#include "fps_counter.h" #include "fps_counter.h"
#include "scrcpy.h" #include "options.h"
#include "screen.h" #include "trait/key_processor.h"
#include "video_buffer.h" #include "trait/mouse_processor.h"
struct screen; // forward declaration to avoid cycle
struct input_manager { struct input_manager {
struct controller *controller; struct controller *controller;
struct video_buffer *video_buffer;
struct screen *screen; struct screen *screen;
// SDL reports repeated events as a boolean, but Android expects the actual struct sc_key_processor *kp;
// number of repetitions. This variable keeps track of the count. struct sc_mouse_processor *mp;
unsigned repeat;
bool control; bool control;
bool forward_key_repeat;
bool prefer_text;
bool forward_all_clicks; bool forward_all_clicks;
bool legacy_paste; bool legacy_paste;
bool clipboard_autosync;
struct { struct {
unsigned data[SC_MAX_SHORTCUT_MODS]; unsigned data[SC_MAX_SHORTCUT_MODS];
@ -34,34 +33,35 @@ struct input_manager {
} sdl_shortcut_mods; } sdl_shortcut_mods;
bool vfinger_down; bool vfinger_down;
// Tracks the number of identical consecutive shortcut key down events.
// Not to be confused with event->repeat, which counts the number of
// system-generated repeated key presses.
unsigned key_repeat;
SDL_Keycode last_keycode;
uint16_t last_mod;
uint64_t next_sequence; // used for request acknowledgements
};
struct input_manager_params {
struct controller *controller;
struct screen *screen;
struct sc_key_processor *kp;
struct sc_mouse_processor *mp;
bool control;
bool forward_all_clicks;
bool legacy_paste;
bool clipboard_autosync;
const struct sc_shortcut_mods *shortcut_mods;
}; };
void void
input_manager_init(struct input_manager *im, input_manager_init(struct input_manager *im,
const struct scrcpy_options *options); const struct input_manager_params *params);
void bool
input_manager_process_text_input(struct input_manager *im, input_manager_handle_event(struct input_manager *im, SDL_Event *event);
const SDL_TextInputEvent *event);
void
input_manager_process_key(struct input_manager *im,
const SDL_KeyboardEvent *event);
void
input_manager_process_mouse_motion(struct input_manager *im,
const SDL_MouseMotionEvent *event);
void
input_manager_process_touch(struct input_manager *im,
const SDL_TouchFingerEvent *event);
void
input_manager_process_mouse_button(struct input_manager *im,
const SDL_MouseButtonEvent *event);
void
input_manager_process_mouse_wheel(struct input_manager *im,
const SDL_MouseWheelEvent *event);
#endif #endif

343
app/src/keyboard_inject.c Normal file
View File

@ -0,0 +1,343 @@
#include "keyboard_inject.h"
#include <assert.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)
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(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 & (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
entry = SC_INTMAP_FIND_ENTRY(kp_nav_keys, from);
if (entry) {
*to = entry->value;
return true;
}
}
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 & (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
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
autocomplete_metastate(enum android_metastate metastate) {
// fill dependent flags
if (metastate & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) {
metastate |= AMETA_SHIFT_ON;
}
if (metastate & (AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) {
metastate |= AMETA_CTRL_ON;
}
if (metastate & (AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) {
metastate |= AMETA_ALT_ON;
}
if (metastate & (AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) {
metastate |= AMETA_META_ON;
}
return metastate;
}
static enum android_metastate
convert_meta_state(uint16_t mod) {
enum android_metastate metastate = 0;
if (mod & SC_MOD_LSHIFT) {
metastate |= AMETA_SHIFT_LEFT_ON;
}
if (mod & SC_MOD_RSHIFT) {
metastate |= AMETA_SHIFT_RIGHT_ON;
}
if (mod & SC_MOD_LCTRL) {
metastate |= AMETA_CTRL_LEFT_ON;
}
if (mod & SC_MOD_RCTRL) {
metastate |= AMETA_CTRL_RIGHT_ON;
}
if (mod & SC_MOD_LALT) {
metastate |= AMETA_ALT_LEFT_ON;
}
if (mod & SC_MOD_RALT) {
metastate |= AMETA_ALT_RIGHT_ON;
}
if (mod & SC_MOD_LGUI) { // Windows key
metastate |= AMETA_META_LEFT_ON;
}
if (mod & SC_MOD_RGUI) { // Windows key
metastate |= AMETA_META_RIGHT_ON;
}
if (mod & SC_MOD_NUM) {
metastate |= AMETA_NUM_LOCK_ON;
}
if (mod & SC_MOD_CAPS) {
metastate |= AMETA_CAPS_LOCK_ON;
}
// fill the dependent fields
return autocomplete_metastate(metastate);
}
static bool
convert_input_key(const struct sc_key_event *event, struct control_msg *msg,
enum sc_key_inject_mode key_inject_mode, uint32_t repeat) {
msg->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
if (!convert_keycode(event->keycode, &msg->inject_keycode.keycode,
event->mods_state, key_inject_mode)) {
return false;
}
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 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) {
if (!ki->forward_key_repeat) {
return;
}
++ki->repeat;
} else {
ki->repeat = 0;
}
struct control_msg msg;
if (convert_input_key(event, &msg, ki->key_inject_mode, ki->repeat)) {
if (!controller_push_msg(ki->controller, &msg)) {
LOGW("Could not request 'inject keycode'");
}
}
}
static void
sc_key_processor_process_text(struct sc_key_processor *kp,
const struct sc_text_event *event) {
struct sc_keyboard_inject *ki = DOWNCAST(kp);
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 events
return;
}
}
struct 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)) {
free(msg.inject_text.text);
LOGW("Could not request 'inject text'");
}
}
void
sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
struct controller *controller,
const struct scrcpy_options *options) {
ki->controller = controller;
ki->key_inject_mode = options->key_inject_mode;
ki->forward_key_repeat = options->forward_key_repeat;
ki->repeat = 0;
static const struct sc_key_processor_ops ops = {
.process_key = sc_key_processor_process_key,
.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;
}

30
app/src/keyboard_inject.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef SC_KEYBOARD_INJECT_H
#define SC_KEYBOARD_INJECT_H
#include "common.h"
#include <stdbool.h>
#include "controller.h"
#include "options.h"
#include "trait/key_processor.h"
struct sc_keyboard_inject {
struct sc_key_processor key_processor; // key processor trait
struct 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;
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);
#endif

View File

@ -1,15 +1,18 @@
#include "scrcpy.h"
#include "common.h" #include "common.h"
#include <assert.h> #include <assert.h>
#include <stdbool.h> #include <stdbool.h>
#include <unistd.h> #include <unistd.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#ifdef HAVE_V4L2
# include <libavdevice/avdevice.h>
#endif
#define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem #define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "cli.h" #include "cli.h"
#include "options.h"
#include "scrcpy.h"
#include "util/log.h" #include "util/log.h"
static void static void
@ -28,26 +31,13 @@ print_version(void) {
fprintf(stderr, " - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR, fprintf(stderr, " - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR,
LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MINOR,
LIBAVUTIL_VERSION_MICRO); LIBAVUTIL_VERSION_MICRO);
#ifdef HAVE_V4L2
fprintf(stderr, " - libavdevice %d.%d.%d\n", LIBAVDEVICE_VERSION_MAJOR,
LIBAVDEVICE_VERSION_MINOR,
LIBAVDEVICE_VERSION_MICRO);
#endif
} }
static SDL_LogPriority
convert_log_level_to_sdl(enum sc_log_level level) {
switch (level) {
case SC_LOG_LEVEL_DEBUG:
return SDL_LOG_PRIORITY_DEBUG;
case SC_LOG_LEVEL_INFO:
return SDL_LOG_PRIORITY_INFO;
case SC_LOG_LEVEL_WARN:
return SDL_LOG_PRIORITY_WARN;
case SC_LOG_LEVEL_ERROR:
return SDL_LOG_PRIORITY_ERROR;
default:
assert(!"unexpected log level");
return SDL_LOG_PRIORITY_INFO;
}
}
int int
main(int argc, char *argv[]) { main(int argc, char *argv[]) {
#ifdef __WINDOWS__ #ifdef __WINDOWS__
@ -57,8 +47,11 @@ main(int argc, char *argv[]) {
setbuf(stderr, NULL); setbuf(stderr, NULL);
#endif #endif
printf("scrcpy " SCRCPY_VERSION
" <https://github.com/Genymobile/scrcpy>\n");
struct scrcpy_cli_args args = { struct scrcpy_cli_args args = {
.opts = SCRCPY_OPTIONS_DEFAULT, .opts = scrcpy_options_default,
.help = false, .help = false,
.version = false, .version = false,
}; };
@ -71,8 +64,7 @@ main(int argc, char *argv[]) {
return 1; return 1;
} }
SDL_LogPriority sdl_log = convert_log_level_to_sdl(args.opts.log_level); sc_set_log_level(args.opts.log_level);
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log);
if (args.help) { if (args.help) {
scrcpy_print_usage(argv[0]); scrcpy_print_usage(argv[0]);
@ -84,12 +76,16 @@ main(int argc, char *argv[]) {
return 0; return 0;
} }
LOGI("scrcpy " SCRCPY_VERSION " <https://github.com/Genymobile/scrcpy>");
#ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL #ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL
av_register_all(); av_register_all();
#endif #endif
#ifdef HAVE_V4L2
if (args.opts.v4l2_device) {
avdevice_register_all();
}
#endif
if (avformat_network_init()) { if (avformat_network_init()) {
return 1; return 1;
} }

156
app/src/mouse_inject.c Normal file
View File

@ -0,0 +1,156 @@
#include "mouse_inject.h"
#include <assert.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 */
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_inject, mouse_processor)
static enum android_motionevent_buttons
convert_mouse_buttons(uint32_t state) {
enum android_motionevent_buttons buttons = 0;
if (state & SC_MOUSE_BUTTON_LEFT) {
buttons |= AMOTION_EVENT_BUTTON_PRIMARY;
}
if (state & SC_MOUSE_BUTTON_RIGHT) {
buttons |= AMOTION_EVENT_BUTTON_SECONDARY;
}
if (state & SC_MOUSE_BUTTON_MIDDLE) {
buttons |= AMOTION_EVENT_BUTTON_TERTIARY;
}
if (state & SC_MOUSE_BUTTON_X1) {
buttons |= AMOTION_EVENT_BUTTON_BACK;
}
if (state & SC_MOUSE_BUTTON_X2) {
buttons |= AMOTION_EVENT_BUTTON_FORWARD;
}
return buttons;
}
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 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 void
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
const struct sc_mouse_motion_event *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct 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 (!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 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 (!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 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 (!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 struct sc_touch_event *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct 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,
},
};
if (!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) {
mi->controller = controller;
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,
};
mi->mouse_processor.ops = &ops;
mi->mouse_processor.relative_mode = false;
}

21
app/src/mouse_inject.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef SC_MOUSE_INJECT_H
#define SC_MOUSE_INJECT_H
#include "common.h"
#include <stdbool.h>
#include "controller.h"
#include "screen.h"
#include "trait/mouse_processor.h"
struct sc_mouse_inject {
struct sc_mouse_processor mouse_processor; // mouse processor trait
struct controller *controller;
};
void
sc_mouse_inject_init(struct sc_mouse_inject *mi, struct controller *controller);
#endif

59
app/src/options.c Normal file
View File

@ -0,0 +1,59 @@
#include "options.h"
const struct scrcpy_options scrcpy_options_default = {
.serial = NULL,
.crop = NULL,
.record_filename = NULL,
.window_title = NULL,
.push_target = NULL,
.render_driver = NULL,
.codec_options = NULL,
.encoder_name = NULL,
#ifdef HAVE_V4L2
.v4l2_device = NULL,
#endif
.log_level = SC_LOG_LEVEL_INFO,
.record_format = SC_RECORD_FORMAT_AUTO,
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT,
.port_range = {
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST,
.last = DEFAULT_LOCAL_PORT_RANGE_LAST,
},
.tunnel_host = 0,
.tunnel_port = 0,
.shortcut_mods = {
.data = {SC_SHORTCUT_MOD_LALT, SC_SHORTCUT_MOD_LSUPER},
.count = 2,
},
.max_size = 0,
.bit_rate = DEFAULT_BIT_RATE,
.max_fps = 0,
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED,
.rotation = 0,
.window_x = SC_WINDOW_POSITION_UNDEFINED,
.window_y = SC_WINDOW_POSITION_UNDEFINED,
.window_width = 0,
.window_height = 0,
.display_id = 0,
.display_buffer = 0,
.v4l2_buffer = 0,
.show_touches = false,
.fullscreen = false,
.always_on_top = false,
.control = true,
.display = true,
.turn_screen_off = false,
.key_inject_mode = SC_KEY_INJECT_MODE_MIXED,
.window_borderless = false,
.mipmaps = true,
.stay_awake = false,
.force_adb_forward = false,
.disable_screensaver = false,
.forward_key_repeat = true,
.forward_all_clicks = false,
.legacy_paste = false,
.power_off_on_close = false,
.clipboard_autosync = true,
.tcpip = false,
.tcpip_dst = NULL,
};

132
app/src/options.h Normal file
View File

@ -0,0 +1,132 @@
#ifndef SCRCPY_OPTIONS_H
#define SCRCPY_OPTIONS_H
#include "common.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "util/tick.h"
enum sc_log_level {
SC_LOG_LEVEL_VERBOSE,
SC_LOG_LEVEL_DEBUG,
SC_LOG_LEVEL_INFO,
SC_LOG_LEVEL_WARN,
SC_LOG_LEVEL_ERROR,
};
enum sc_record_format {
SC_RECORD_FORMAT_AUTO,
SC_RECORD_FORMAT_MP4,
SC_RECORD_FORMAT_MKV,
};
enum sc_lock_video_orientation {
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
// lock the current orientation when scrcpy starts
SC_LOCK_VIDEO_ORIENTATION_INITIAL = -2,
SC_LOCK_VIDEO_ORIENTATION_0 = 0,
SC_LOCK_VIDEO_ORIENTATION_1,
SC_LOCK_VIDEO_ORIENTATION_2,
SC_LOCK_VIDEO_ORIENTATION_3,
};
enum sc_keyboard_input_mode {
SC_KEYBOARD_INPUT_MODE_INJECT,
SC_KEYBOARD_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_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 {
unsigned data[SC_MAX_SHORTCUT_MODS];
unsigned count;
};
struct sc_port_range {
uint16_t first;
uint16_t last;
};
#define SC_WINDOW_POSITION_UNDEFINED (-0x8000)
struct scrcpy_options {
const char *serial;
const char *crop;
const char *record_filename;
const char *window_title;
const char *push_target;
const char *render_driver;
const char *codec_options;
const char *encoder_name;
#ifdef HAVE_V4L2
const char *v4l2_device;
#endif
enum sc_log_level log_level;
enum sc_record_format record_format;
enum sc_keyboard_input_mode keyboard_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;
uint16_t max_fps;
enum sc_lock_video_orientation lock_video_orientation;
uint8_t rotation;
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
uint16_t window_width;
uint16_t window_height;
uint32_t display_id;
sc_tick display_buffer;
sc_tick v4l2_buffer;
bool show_touches;
bool fullscreen;
bool always_on_top;
bool control;
bool display;
bool turn_screen_off;
enum sc_key_inject_mode key_inject_mode;
bool window_borderless;
bool mipmaps;
bool stay_awake;
bool force_adb_forward;
bool disable_screensaver;
bool forward_key_repeat;
bool forward_all_clicks;
bool legacy_paste;
bool power_off_on_close;
bool clipboard_autosync;
bool tcpip;
const char *tcpip_dst;
};
extern const struct scrcpy_options scrcpy_options_default;
#endif

View File

@ -4,25 +4,29 @@
#include <SDL2/SDL_clipboard.h> #include <SDL2/SDL_clipboard.h>
#include "device_msg.h" #include "device_msg.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
bool bool
receiver_init(struct receiver *receiver, socket_t control_socket) { receiver_init(struct receiver *receiver, sc_socket control_socket,
if (!(receiver->mutex = SDL_CreateMutex())) { struct sc_acksync *acksync) {
bool ok = sc_mutex_init(&receiver->mutex);
if (!ok) {
return false; return false;
} }
receiver->control_socket = control_socket; receiver->control_socket = control_socket;
receiver->acksync = acksync;
return true; return true;
} }
void void
receiver_destroy(struct receiver *receiver) { receiver_destroy(struct receiver *receiver) {
SDL_DestroyMutex(receiver->mutex); sc_mutex_destroy(&receiver->mutex);
} }
static void static void
process_msg(struct device_msg *msg) { process_msg(struct receiver *receiver, struct device_msg *msg) {
switch (msg->type) { switch (msg->type) {
case DEVICE_MSG_TYPE_CLIPBOARD: { case DEVICE_MSG_TYPE_CLIPBOARD: {
char *current = SDL_GetClipboardText(); char *current = SDL_GetClipboardText();
@ -37,11 +41,17 @@ process_msg(struct device_msg *msg) {
SDL_SetClipboardText(msg->clipboard.text); SDL_SetClipboardText(msg->clipboard.text);
break; 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 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; size_t head = 0;
for (;;) { for (;;) {
struct device_msg msg; struct device_msg msg;
@ -53,7 +63,7 @@ process_msgs(const unsigned char *buf, size_t len) {
return head; return head;
} }
process_msg(&msg); process_msg(receiver, &msg);
device_msg_destroy(&msg); device_msg_destroy(&msg);
head += r; head += r;
@ -81,7 +91,7 @@ run_receiver(void *data) {
} }
head += r; head += r;
ssize_t consumed = process_msgs(buf, head); ssize_t consumed = process_msgs(receiver, buf, head);
if (consumed == -1) { if (consumed == -1) {
// an error occurred // an error occurred
break; break;
@ -101,8 +111,9 @@ bool
receiver_start(struct receiver *receiver) { receiver_start(struct receiver *receiver) {
LOGD("Starting receiver thread"); LOGD("Starting receiver thread");
receiver->thread = SDL_CreateThread(run_receiver, "receiver", receiver); bool ok = sc_thread_create(&receiver->thread, run_receiver,
if (!receiver->thread) { "scrcpy-receiver", receiver);
if (!ok) {
LOGC("Could not start receiver thread"); LOGC("Could not start receiver thread");
return false; return false;
} }
@ -112,5 +123,5 @@ receiver_start(struct receiver *receiver) {
void void
receiver_join(struct receiver *receiver) { receiver_join(struct receiver *receiver) {
SDL_WaitThread(receiver->thread, NULL); sc_thread_join(&receiver->thread, NULL);
} }

View File

@ -4,21 +4,24 @@
#include "common.h" #include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "util/acksync.h"
#include "util/net.h" #include "util/net.h"
#include "util/thread.h"
// receive events from the device // receive events from the device
// managed by the controller // managed by the controller
struct receiver { struct receiver {
socket_t control_socket; sc_socket control_socket;
SDL_Thread *thread; sc_thread thread;
SDL_mutex *mutex; sc_mutex mutex;
struct sc_acksync *acksync;
}; };
bool bool
receiver_init(struct receiver *receiver, socket_t control_socket); receiver_init(struct receiver *receiver, sc_socket control_socket,
struct sc_acksync *acksync);
void void
receiver_destroy(struct receiver *receiver); receiver_destroy(struct receiver *receiver);

View File

@ -1,10 +1,15 @@
#include "recorder.h" #include "recorder.h"
#include <assert.h> #include <assert.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/time.h> #include <libavutil/time.h>
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
#include "util/str.h"
/** Downcast packet_sink to recorder */
#define DOWNCAST(SINK) container_of(SINK, struct recorder, packet_sink)
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
@ -20,24 +25,29 @@ find_muxer(const char *name) {
#else #else
oformat = av_oformat_next(oformat); oformat = av_oformat_next(oformat);
#endif #endif
// until null or with name "mp4" // until null or containing the requested name
} while (oformat && strcmp(oformat->name, name)); } while (oformat && !sc_str_list_contains(oformat->name, ',', name));
return oformat; return oformat;
} }
static struct record_packet * static struct record_packet *
record_packet_new(const AVPacket *packet) { record_packet_new(const AVPacket *packet) {
struct record_packet *rec = SDL_malloc(sizeof(*rec)); struct record_packet *rec = malloc(sizeof(*rec));
if (!rec) { if (!rec) {
LOG_OOM();
return NULL; return NULL;
} }
// av_packet_ref() does not initialize all fields in old FFmpeg versions rec->packet = av_packet_alloc();
// See <https://github.com/Genymobile/scrcpy/issues/707> if (!rec->packet) {
av_init_packet(&rec->packet); LOG_OOM();
free(rec);
return NULL;
}
if (av_packet_ref(&rec->packet, packet)) { if (av_packet_ref(rec->packet, packet)) {
SDL_free(rec); av_packet_free(&rec->packet);
free(rec);
return NULL; return NULL;
} }
return rec; return rec;
@ -45,63 +55,19 @@ record_packet_new(const AVPacket *packet) {
static void static void
record_packet_delete(struct record_packet *rec) { record_packet_delete(struct record_packet *rec) {
av_packet_unref(&rec->packet); av_packet_free(&rec->packet);
SDL_free(rec); free(rec);
} }
static void static void
recorder_queue_clear(struct recorder_queue *queue) { recorder_queue_clear(struct recorder_queue *queue) {
while (!queue_is_empty(queue)) { while (!sc_queue_is_empty(queue)) {
struct record_packet *rec; struct record_packet *rec;
queue_take(queue, next, &rec); sc_queue_take(queue, next, &rec);
record_packet_delete(rec); record_packet_delete(rec);
} }
} }
bool
recorder_init(struct recorder *recorder,
const char *filename,
enum sc_record_format format,
struct size declared_frame_size) {
recorder->filename = SDL_strdup(filename);
if (!recorder->filename) {
LOGE("Could not strdup filename");
return false;
}
recorder->mutex = SDL_CreateMutex();
if (!recorder->mutex) {
LOGC("Could not create mutex");
SDL_free(recorder->filename);
return false;
}
recorder->queue_cond = SDL_CreateCond();
if (!recorder->queue_cond) {
LOGC("Could not create cond");
SDL_DestroyMutex(recorder->mutex);
SDL_free(recorder->filename);
return false;
}
queue_init(&recorder->queue);
recorder->stopped = false;
recorder->failed = false;
recorder->format = format;
recorder->declared_frame_size = declared_frame_size;
recorder->header_written = false;
recorder->previous = NULL;
return true;
}
void
recorder_destroy(struct recorder *recorder) {
SDL_DestroyCond(recorder->queue_cond);
SDL_DestroyMutex(recorder->mutex);
SDL_free(recorder->filename);
}
static const char * static const char *
recorder_get_format_name(enum sc_record_format format) { recorder_get_format_name(enum sc_record_format format) {
switch (format) { switch (format) {
@ -111,108 +77,21 @@ recorder_get_format_name(enum sc_record_format format) {
} }
} }
bool
recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
const char *format_name = recorder_get_format_name(recorder->format);
assert(format_name);
const AVOutputFormat *format = find_muxer(format_name);
if (!format) {
LOGE("Could not find muxer");
return false;
}
recorder->ctx = avformat_alloc_context();
if (!recorder->ctx) {
LOGE("Could not allocate output context");
return false;
}
// contrary to the deprecated API (av_oformat_next()), av_muxer_iterate()
// returns (on purpose) a pointer-to-const, but AVFormatContext.oformat
// still expects a pointer-to-non-const (it has not be updated accordingly)
// <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd>
recorder->ctx->oformat = (AVOutputFormat *) format;
av_dict_set(&recorder->ctx->metadata, "comment",
"Recorded by scrcpy " SCRCPY_VERSION, 0);
AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec);
if (!ostream) {
avformat_free_context(recorder->ctx);
return false;
}
#ifdef SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
ostream->codecpar->codec_id = input_codec->id;
ostream->codecpar->format = AV_PIX_FMT_YUV420P;
ostream->codecpar->width = recorder->declared_frame_size.width;
ostream->codecpar->height = recorder->declared_frame_size.height;
#else
ostream->codec->codec_type = AVMEDIA_TYPE_VIDEO;
ostream->codec->codec_id = input_codec->id;
ostream->codec->pix_fmt = AV_PIX_FMT_YUV420P;
ostream->codec->width = recorder->declared_frame_size.width;
ostream->codec->height = recorder->declared_frame_size.height;
#endif
int ret = avio_open(&recorder->ctx->pb, recorder->filename,
AVIO_FLAG_WRITE);
if (ret < 0) {
LOGE("Failed to open output file: %s", recorder->filename);
// ostream will be cleaned up during context cleaning
avformat_free_context(recorder->ctx);
return false;
}
LOGI("Recording started to %s file: %s", format_name, recorder->filename);
return true;
}
void
recorder_close(struct recorder *recorder) {
if (recorder->header_written) {
int ret = av_write_trailer(recorder->ctx);
if (ret < 0) {
LOGE("Failed to write trailer to %s", recorder->filename);
recorder->failed = true;
}
} else {
// the recorded file is empty
recorder->failed = true;
}
avio_close(recorder->ctx->pb);
avformat_free_context(recorder->ctx);
if (recorder->failed) {
LOGE("Recording failed to %s", recorder->filename);
} else {
const char *format_name = recorder_get_format_name(recorder->format);
LOGI("Recording complete to %s file: %s", format_name, recorder->filename);
}
}
static bool static bool
recorder_write_header(struct recorder *recorder, const AVPacket *packet) { recorder_write_header(struct recorder *recorder, const AVPacket *packet) {
AVStream *ostream = recorder->ctx->streams[0]; AVStream *ostream = recorder->ctx->streams[0];
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t)); uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t));
if (!extradata) { if (!extradata) {
LOGC("Could not allocate extradata"); LOG_OOM();
return false; return false;
} }
// copy the first packet to the extra data // copy the first packet to the extra data
memcpy(extradata, packet->data, packet->size); memcpy(extradata, packet->data, packet->size);
#ifdef SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
ostream->codecpar->extradata = extradata; ostream->codecpar->extradata = extradata;
ostream->codecpar->extradata_size = packet->size; ostream->codecpar->extradata_size = packet->size;
#else
ostream->codec->extradata = extradata;
ostream->codec->extradata_size = packet->size;
#endif
int ret = avformat_write_header(recorder->ctx, NULL); int ret = avformat_write_header(recorder->ctx, NULL);
if (ret < 0) { if (ret < 0) {
@ -229,7 +108,7 @@ recorder_rescale_packet(struct recorder *recorder, AVPacket *packet) {
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base); av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
} }
bool static bool
recorder_write(struct recorder *recorder, AVPacket *packet) { recorder_write(struct recorder *recorder, AVPacket *packet) {
if (!recorder->header_written) { if (!recorder->header_written) {
if (packet->pts != AV_NOPTS_VALUE) { if (packet->pts != AV_NOPTS_VALUE) {
@ -258,22 +137,22 @@ run_recorder(void *data) {
struct recorder *recorder = data; struct recorder *recorder = data;
for (;;) { for (;;) {
mutex_lock(recorder->mutex); sc_mutex_lock(&recorder->mutex);
while (!recorder->stopped && queue_is_empty(&recorder->queue)) { while (!recorder->stopped && sc_queue_is_empty(&recorder->queue)) {
cond_wait(recorder->queue_cond, recorder->mutex); sc_cond_wait(&recorder->queue_cond, &recorder->mutex);
} }
// if stopped is set, continue to process the remaining events (to // if stopped is set, continue to process the remaining events (to
// finish the recording) before actually stopping // finish the recording) before actually stopping
if (recorder->stopped && queue_is_empty(&recorder->queue)) { if (recorder->stopped && sc_queue_is_empty(&recorder->queue)) {
mutex_unlock(recorder->mutex); sc_mutex_unlock(&recorder->mutex);
struct record_packet *last = recorder->previous; struct record_packet *last = recorder->previous;
if (last) { if (last) {
// assign an arbitrary duration to the last packet // assign an arbitrary duration to the last packet
last->packet.duration = 100000; last->packet->duration = 100000;
bool ok = recorder_write(recorder, &last->packet); bool ok = recorder_write(recorder, last->packet);
if (!ok) { if (!ok) {
// failing to write the last frame is not very serious, no // failing to write the last frame is not very serious, no
// future frame may depend on it, so the resulting file // future frame may depend on it, so the resulting file
@ -286,9 +165,9 @@ run_recorder(void *data) {
} }
struct record_packet *rec; struct record_packet *rec;
queue_take(&recorder->queue, next, &rec); sc_queue_take(&recorder->queue, next, &rec);
mutex_unlock(recorder->mutex); sc_mutex_unlock(&recorder->mutex);
// recorder->previous is only written from this thread, no need to lock // recorder->previous is only written from this thread, no need to lock
struct record_packet *previous = recorder->previous; struct record_packet *previous = recorder->previous;
@ -300,25 +179,46 @@ run_recorder(void *data) {
} }
// config packets have no PTS, we must ignore them // config packets have no PTS, we must ignore them
if (rec->packet.pts != AV_NOPTS_VALUE if (rec->packet->pts != AV_NOPTS_VALUE
&& previous->packet.pts != AV_NOPTS_VALUE) { && previous->packet->pts != AV_NOPTS_VALUE) {
// we now know the duration of the previous packet // we now know the duration of the previous packet
previous->packet.duration = rec->packet.pts - previous->packet.pts; previous->packet->duration =
rec->packet->pts - previous->packet->pts;
} }
bool ok = recorder_write(recorder, &previous->packet); bool ok = recorder_write(recorder, previous->packet);
record_packet_delete(previous); record_packet_delete(previous);
if (!ok) { if (!ok) {
LOGE("Could not record packet"); LOGE("Could not record packet");
mutex_lock(recorder->mutex); sc_mutex_lock(&recorder->mutex);
recorder->failed = true; recorder->failed = true;
// discard pending packets // discard pending packets
recorder_queue_clear(&recorder->queue); recorder_queue_clear(&recorder->queue);
mutex_unlock(recorder->mutex); sc_mutex_unlock(&recorder->mutex);
break; break;
} }
}
if (!recorder->failed) {
if (recorder->header_written) {
int ret = av_write_trailer(recorder->ctx);
if (ret < 0) {
LOGE("Failed to write trailer to %s", recorder->filename);
recorder->failed = true;
}
} else {
// the recorded file is empty
recorder->failed = true;
}
}
if (recorder->failed) {
LOGE("Recording failed to %s", recorder->filename);
} else {
const char *format_name = recorder_get_format_name(recorder->format);
LOGI("Recording complete to %s file: %s", format_name,
recorder->filename);
} }
LOGD("Recorder thread ended"); LOGD("Recorder thread ended");
@ -326,53 +226,174 @@ run_recorder(void *data) {
return 0; return 0;
} }
bool static bool
recorder_start(struct recorder *recorder) { recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
LOGD("Starting recorder thread"); bool ok = sc_mutex_init(&recorder->mutex);
if (!ok) {
recorder->thread = SDL_CreateThread(run_recorder, "recorder", recorder);
if (!recorder->thread) {
LOGC("Could not start recorder thread");
return false; return false;
} }
ok = sc_cond_init(&recorder->queue_cond);
if (!ok) {
goto error_mutex_destroy;
}
sc_queue_init(&recorder->queue);
recorder->stopped = false;
recorder->failed = false;
recorder->header_written = false;
recorder->previous = NULL;
const char *format_name = recorder_get_format_name(recorder->format);
assert(format_name);
const AVOutputFormat *format = find_muxer(format_name);
if (!format) {
LOGE("Could not find muxer");
goto error_cond_destroy;
}
recorder->ctx = avformat_alloc_context();
if (!recorder->ctx) {
LOG_OOM();
goto error_cond_destroy;
}
// contrary to the deprecated API (av_oformat_next()), av_muxer_iterate()
// returns (on purpose) a pointer-to-const, but AVFormatContext.oformat
// still expects a pointer-to-non-const (it has not be updated accordingly)
// <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd>
recorder->ctx->oformat = (AVOutputFormat *) format;
av_dict_set(&recorder->ctx->metadata, "comment",
"Recorded by scrcpy " SCRCPY_VERSION, 0);
AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec);
if (!ostream) {
goto error_avformat_free_context;
}
ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
ostream->codecpar->codec_id = input_codec->id;
ostream->codecpar->format = AV_PIX_FMT_YUV420P;
ostream->codecpar->width = recorder->declared_frame_size.width;
ostream->codecpar->height = recorder->declared_frame_size.height;
int ret = avio_open(&recorder->ctx->pb, recorder->filename,
AVIO_FLAG_WRITE);
if (ret < 0) {
LOGE("Failed to open output file: %s", recorder->filename);
// ostream will be cleaned up during context cleaning
goto error_avformat_free_context;
}
LOGD("Starting recorder thread");
ok = sc_thread_create(&recorder->thread, run_recorder, "scrcpy-recorder",
recorder);
if (!ok) {
LOGC("Could not start recorder thread");
goto error_avio_close;
}
LOGI("Recording started to %s file: %s", format_name, recorder->filename);
return true; return true;
error_avio_close:
avio_close(recorder->ctx->pb);
error_avformat_free_context:
avformat_free_context(recorder->ctx);
error_cond_destroy:
sc_cond_destroy(&recorder->queue_cond);
error_mutex_destroy:
sc_mutex_destroy(&recorder->mutex);
return false;
} }
void static void
recorder_stop(struct recorder *recorder) { recorder_close(struct recorder *recorder) {
mutex_lock(recorder->mutex); sc_mutex_lock(&recorder->mutex);
recorder->stopped = true; recorder->stopped = true;
cond_signal(recorder->queue_cond); sc_cond_signal(&recorder->queue_cond);
mutex_unlock(recorder->mutex); sc_mutex_unlock(&recorder->mutex);
sc_thread_join(&recorder->thread, NULL);
avio_close(recorder->ctx->pb);
avformat_free_context(recorder->ctx);
sc_cond_destroy(&recorder->queue_cond);
sc_mutex_destroy(&recorder->mutex);
} }
void static bool
recorder_join(struct recorder *recorder) {
SDL_WaitThread(recorder->thread, NULL);
}
bool
recorder_push(struct recorder *recorder, const AVPacket *packet) { recorder_push(struct recorder *recorder, const AVPacket *packet) {
mutex_lock(recorder->mutex); sc_mutex_lock(&recorder->mutex);
assert(!recorder->stopped); assert(!recorder->stopped);
if (recorder->failed) { if (recorder->failed) {
// reject any new packet (this will stop the stream) // reject any new packet (this will stop the stream)
mutex_unlock(recorder->mutex); sc_mutex_unlock(&recorder->mutex);
return false; return false;
} }
struct record_packet *rec = record_packet_new(packet); struct record_packet *rec = record_packet_new(packet);
if (!rec) { if (!rec) {
LOGC("Could not allocate record packet"); LOG_OOM();
mutex_unlock(recorder->mutex); sc_mutex_unlock(&recorder->mutex);
return false; return false;
} }
queue_push(&recorder->queue, next, rec); sc_queue_push(&recorder->queue, next, rec);
cond_signal(recorder->queue_cond); sc_cond_signal(&recorder->queue_cond);
mutex_unlock(recorder->mutex); sc_mutex_unlock(&recorder->mutex);
return true; return true;
} }
static bool
recorder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) {
struct recorder *recorder = DOWNCAST(sink);
return recorder_open(recorder, codec);
}
static void
recorder_packet_sink_close(struct sc_packet_sink *sink) {
struct recorder *recorder = DOWNCAST(sink);
recorder_close(recorder);
}
static bool
recorder_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) {
struct recorder *recorder = DOWNCAST(sink);
return recorder_push(recorder, packet);
}
bool
recorder_init(struct recorder *recorder,
const char *filename,
enum sc_record_format format,
struct sc_size declared_frame_size) {
recorder->filename = strdup(filename);
if (!recorder->filename) {
LOG_OOM();
return false;
}
recorder->format = format;
recorder->declared_frame_size = declared_frame_size;
static const struct sc_packet_sink_ops ops = {
.open = recorder_packet_sink_open,
.close = recorder_packet_sink_close,
.push = recorder_packet_sink_push,
};
recorder->packet_sink.ops = &ops;
return true;
}
void
recorder_destroy(struct recorder *recorder) {
free(recorder->filename);
}

View File

@ -5,31 +5,33 @@
#include <stdbool.h> #include <stdbool.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "coords.h" #include "coords.h"
#include "scrcpy.h" #include "options.h"
#include "trait/packet_sink.h"
#include "util/queue.h" #include "util/queue.h"
#include "util/thread.h"
struct record_packet { struct record_packet {
AVPacket packet; AVPacket *packet;
struct record_packet *next; struct record_packet *next;
}; };
struct recorder_queue QUEUE(struct record_packet); struct recorder_queue SC_QUEUE(struct record_packet);
struct recorder { struct recorder {
struct sc_packet_sink packet_sink; // packet sink trait
char *filename; char *filename;
enum sc_record_format format; enum sc_record_format format;
AVFormatContext *ctx; AVFormatContext *ctx;
struct size declared_frame_size; struct sc_size declared_frame_size;
bool header_written; bool header_written;
SDL_Thread *thread; sc_thread thread;
SDL_mutex *mutex; sc_mutex mutex;
SDL_cond *queue_cond; sc_cond queue_cond;
bool stopped; // set on recorder_stop() by the stream reader bool stopped; // set on recorder_close()
bool failed; // set on packet write failure bool failed; // set on packet write failure
struct recorder_queue queue; struct recorder_queue queue;
@ -42,27 +44,9 @@ struct recorder {
bool bool
recorder_init(struct recorder *recorder, const char *filename, recorder_init(struct recorder *recorder, const char *filename,
enum sc_record_format format, struct size declared_frame_size); enum sc_record_format format, struct sc_size declared_frame_size);
void void
recorder_destroy(struct recorder *recorder); recorder_destroy(struct recorder *recorder);
bool
recorder_open(struct recorder *recorder, const AVCodec *input_codec);
void
recorder_close(struct recorder *recorder);
bool
recorder_start(struct recorder *recorder);
void
recorder_stop(struct recorder *recorder);
void
recorder_join(struct recorder *recorder);
bool
recorder_push(struct recorder *recorder, const AVPacket *packet);
#endif #endif

View File

@ -15,80 +15,79 @@
#include "controller.h" #include "controller.h"
#include "decoder.h" #include "decoder.h"
#include "device.h"
#include "events.h" #include "events.h"
#include "file_handler.h" #include "file_handler.h"
#include "fps_counter.h" #ifdef HAVE_AOA_HID
#include "input_manager.h" # include "hid_keyboard.h"
# include "hid_mouse.h"
#endif
#include "keyboard_inject.h"
#include "mouse_inject.h"
#include "recorder.h" #include "recorder.h"
#include "screen.h" #include "screen.h"
#include "server.h" #include "server.h"
#include "stream.h" #include "stream.h"
#include "tiny_xpm.h" #include "util/acksync.h"
#include "video_buffer.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
#include "util/net.h" #include "util/net.h"
#ifdef HAVE_V4L2
# include "v4l2_sink.h"
#endif
static struct server server; struct scrcpy {
static struct screen screen = SCREEN_INITIALIZER; struct sc_server server;
static struct fps_counter fps_counter; struct screen screen;
static struct video_buffer video_buffer; struct stream stream;
static struct stream stream; struct decoder decoder;
static struct decoder decoder; struct recorder recorder;
static struct recorder recorder; #ifdef HAVE_V4L2
static struct controller controller; struct sc_v4l2_sink v4l2_sink;
static struct file_handler file_handler; #endif
struct controller controller;
static struct input_manager input_manager = { struct file_handler file_handler;
.controller = &controller, #ifdef HAVE_AOA_HID
.video_buffer = &video_buffer, struct sc_aoa aoa;
.screen = &screen, // sequence/ack helper to synchronize clipboard and Ctrl+v via HID
.repeat = 0, struct sc_acksync acksync;
#endif
// initialized later union {
.prefer_text = false, struct sc_keyboard_inject keyboard_inject;
.sdl_shortcut_mods = { #ifdef HAVE_AOA_HID
.data = {0}, struct sc_hid_keyboard keyboard_hid;
.count = 0, #endif
}, };
union {
struct sc_mouse_inject mouse_inject;
#ifdef HAVE_AOA_HID
struct sc_hid_mouse mouse_hid;
#endif
};
}; };
static inline void
push_event(uint32_t type, const char *name) {
SDL_Event event;
event.type = type;
int ret = SDL_PushEvent(&event);
if (ret < 0) {
LOGE("Could not post %s event: %s", name, SDL_GetError());
// What could we do?
}
}
#define PUSH_EVENT(TYPE) push_event(TYPE, # TYPE)
#ifdef _WIN32 #ifdef _WIN32
BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) { BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
if (ctrl_type == CTRL_C_EVENT) { if (ctrl_type == CTRL_C_EVENT) {
SDL_Event event; PUSH_EVENT(SDL_QUIT);
event.type = SDL_QUIT;
SDL_PushEvent(&event);
return TRUE; return TRUE;
} }
return FALSE; return FALSE;
} }
#endif // _WIN32 #endif // _WIN32
// init SDL and set appropriate hints static void
static bool sdl_set_hints(const char *render_driver) {
sdl_init_and_configure(bool display, const char *render_driver,
bool disable_screensaver) {
uint32_t flags = display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS;
if (SDL_Init(flags)) {
LOGC("Could not initialize SDL: %s", SDL_GetError());
return false;
}
atexit(SDL_Quit);
#ifdef _WIN32
// Clean up properly on Ctrl+C on Windows
bool ok = SetConsoleCtrlHandler(windows_ctrl_handler, TRUE);
if (!ok) {
LOGW("Could not set Ctrl+C handler");
}
#endif // _WIN32
if (!display) {
return true;
}
if (render_driver && !SDL_SetHint(SDL_HINT_RENDER_DRIVER, render_driver)) { if (render_driver && !SDL_SetHint(SDL_HINT_RENDER_DRIVER, render_driver)) {
LOGW("Could not set render driver"); LOGW("Could not set render driver");
@ -99,11 +98,18 @@ sdl_init_and_configure(bool display, const char *render_driver,
LOGW("Could not enable linear filtering"); LOGW("Could not enable linear filtering");
} }
#ifdef SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH
// Handle a click to gain focus as any other click // Handle a click to gain focus as any other click
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) { if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
LOGW("Could not enable mouse focus clickthrough"); LOGW("Could not enable mouse focus clickthrough");
} }
#ifdef SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
// Disable synthetic mouse events from touch events
// Touch events with id SDL_TOUCH_MOUSEID are ignored anyway, but it is
// better not to generate them in the first place.
if (!SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0")) {
LOGW("Could not disable synthetic mouse events");
}
#endif #endif
#ifdef SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR #ifdef SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
@ -117,6 +123,21 @@ sdl_init_and_configure(bool display, const char *render_driver,
if (!SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0")) { if (!SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0")) {
LOGW("Could not disable minimize on focus loss"); LOGW("Could not disable minimize on focus loss");
} }
}
static void
sdl_configure(bool display, bool disable_screensaver) {
#ifdef _WIN32
// Clean up properly on Ctrl+C on Windows
bool ok = SetConsoleCtrlHandler(windows_ctrl_handler, TRUE);
if (!ok) {
LOGW("Could not set Ctrl+C handler");
}
#endif // _WIN32
if (!display) {
return;
}
if (disable_screensaver) { if (disable_screensaver) {
LOGD("Screensaver disabled"); LOGD("Screensaver disabled");
@ -125,34 +146,8 @@ sdl_init_and_configure(bool display, const char *render_driver,
LOGD("Screensaver enabled"); LOGD("Screensaver enabled");
SDL_EnableScreenSaver(); SDL_EnableScreenSaver();
} }
return true;
} }
#if defined(__APPLE__) || defined(__WINDOWS__)
# define CONTINUOUS_RESIZING_WORKAROUND
#endif
#ifdef CONTINUOUS_RESIZING_WORKAROUND
// On Windows and MacOS, resizing blocks the event loop, so resizing events are
// not triggered. As a workaround, handle them in an event handler.
//
// <https://bugzilla.libsdl.org/show_bug.cgi?id=2077>
// <https://stackoverflow.com/a/40693139/1987178>
static int
event_watcher(void *data, SDL_Event *event) {
(void) 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);
}
return 0;
}
#endif
static bool static bool
is_apk(const char *file) { is_apk(const char *file) {
const char *ext = strrchr(file, '.'); const char *ext = strrchr(file, '.');
@ -166,7 +161,8 @@ enum event_result {
}; };
static enum event_result static enum event_result
handle_event(SDL_Event *event, const struct scrcpy_options *options) { handle_event(struct scrcpy *s, const struct scrcpy_options *options,
SDL_Event *event) {
switch (event->type) { switch (event->type) {
case EVENT_STREAM_STOPPED: case EVENT_STREAM_STOPPED:
LOGD("Video stream stopped"); LOGD("Video stream stopped");
@ -174,81 +170,40 @@ handle_event(SDL_Event *event, const struct scrcpy_options *options) {
case SDL_QUIT: case SDL_QUIT:
LOGD("User requested to quit"); LOGD("User requested to quit");
return EVENT_RESULT_STOPPED_BY_USER; return EVENT_RESULT_STOPPED_BY_USER;
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);
}
if (!screen_update_frame(&screen, &video_buffer)) {
return EVENT_RESULT_CONTINUE;
}
break;
case SDL_WINDOWEVENT:
screen_handle_window_event(&screen, &event->window);
break;
case SDL_TEXTINPUT:
if (!options->control) {
break;
}
input_manager_process_text_input(&input_manager, &event->text);
break;
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(&input_manager, &event->key);
break;
case SDL_MOUSEMOTION:
if (!options->control) {
break;
}
input_manager_process_mouse_motion(&input_manager, &event->motion);
break;
case SDL_MOUSEWHEEL:
if (!options->control) {
break;
}
input_manager_process_mouse_wheel(&input_manager, &event->wheel);
break;
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(&input_manager, &event->button);
break;
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
input_manager_process_touch(&input_manager, &event->tfinger);
break;
case SDL_DROPFILE: { case SDL_DROPFILE: {
if (!options->control) { if (!options->control) {
break; break;
} }
char *file = strdup(event->drop.file);
SDL_free(event->drop.file);
if (!file) {
LOGW("Could not strdup drop filename\n");
break;
}
file_handler_action_t action; file_handler_action_t action;
if (is_apk(event->drop.file)) { if (is_apk(file)) {
action = ACTION_INSTALL_APK; action = ACTION_INSTALL_APK;
} else { } else {
action = ACTION_PUSH_FILE; action = ACTION_PUSH_FILE;
} }
file_handler_request(&file_handler, action, event->drop.file); file_handler_request(&s->file_handler, action, file);
break; goto end;
} }
} }
bool consumed = screen_handle_event(&s->screen, event);
(void) consumed;
end:
return EVENT_RESULT_CONTINUE; return EVENT_RESULT_CONTINUE;
} }
static bool static bool
event_loop(const struct scrcpy_options *options) { event_loop(struct scrcpy *s, const struct scrcpy_options *options) {
#ifdef CONTINUOUS_RESIZING_WORKAROUND
if (options->display) {
SDL_AddEventWatch(event_watcher, NULL);
}
#endif
SDL_Event event; SDL_Event event;
while (SDL_WaitEvent(&event)) { while (SDL_WaitEvent(&event)) {
enum event_result result = handle_event(&event, options); enum event_result result = handle_event(s, options, &event);
switch (result) { switch (result) {
case EVENT_RESULT_STOPPED_BY_USER: case EVENT_RESULT_STOPPED_BY_USER:
return true; return true;
@ -262,6 +217,29 @@ event_loop(const struct scrcpy_options *options) {
return false; return false;
} }
static bool
await_for_server(void) {
SDL_Event event;
while (SDL_WaitEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
LOGD("User requested to quit");
return false;
case EVENT_SERVER_CONNECTION_FAILED:
LOGE("Server connection failed");
return false;
case EVENT_SERVER_CONNECTED:
LOGD("Server connected");
return true;
default:
break;
}
}
LOGE("SDL_WaitEvent() error: %s", SDL_GetError());
return false;
}
static SDL_LogPriority static SDL_LogPriority
sdl_priority_from_av_level(int level) { sdl_priority_from_av_level(int level) {
switch (level) { switch (level) {
@ -286,40 +264,91 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
if (priority == 0) { if (priority == 0) {
return; return;
} }
char *local_fmt = SDL_malloc(strlen(fmt) + 10);
size_t fmt_len = strlen(fmt);
char *local_fmt = malloc(fmt_len + 10);
if (!local_fmt) { if (!local_fmt) {
LOGC("Could not allocate string"); LOG_OOM();
return; return;
} }
// strcpy is safe here, the destination is large enough memcpy(local_fmt, "[FFmpeg] ", 9); // do not write the final '\0'
strcpy(local_fmt, "[FFmpeg] "); memcpy(local_fmt + 9, fmt, fmt_len + 1); // include '\0'
strcpy(local_fmt + 9, fmt);
SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl); SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl);
SDL_free(local_fmt); free(local_fmt);
}
static void
stream_on_eos(struct stream *stream, void *userdata) {
(void) stream;
(void) userdata;
PUSH_EVENT(EVENT_STREAM_STOPPED);
}
static void
sc_server_on_connection_failed(struct sc_server *server, void *userdata) {
(void) server;
(void) userdata;
PUSH_EVENT(EVENT_SERVER_CONNECTION_FAILED);
}
static void
sc_server_on_connected(struct sc_server *server, void *userdata) {
(void) server;
(void) userdata;
PUSH_EVENT(EVENT_SERVER_CONNECTED);
}
static void
sc_server_on_disconnected(struct sc_server *server, void *userdata) {
(void) server;
(void) userdata;
LOGD("Server disconnected");
// Do nothing, the disconnection will be handled by the "stream stopped"
// event
} }
bool bool
scrcpy(const struct scrcpy_options *options) { scrcpy(struct scrcpy_options *options) {
if (!server_init(&server)) { static struct scrcpy scrcpy;
struct scrcpy *s = &scrcpy;
// Minimal SDL initialization
if (SDL_Init(SDL_INIT_EVENTS)) {
LOGC("Could not initialize SDL: %s", SDL_GetError());
return false; return false;
} }
atexit(SDL_Quit);
bool ret = false; bool ret = false;
bool server_started = false; bool server_started = false;
bool fps_counter_initialized = false;
bool video_buffer_initialized = false;
bool file_handler_initialized = false; bool file_handler_initialized = false;
bool recorder_initialized = false; bool recorder_initialized = false;
#ifdef HAVE_V4L2
bool v4l2_sink_initialized = false;
#endif
bool stream_started = false; bool stream_started = false;
#ifdef HAVE_AOA_HID
bool aoa_hid_initialized = false;
#endif
bool controller_initialized = false; bool controller_initialized = false;
bool controller_started = false; bool controller_started = false;
bool screen_initialized = false;
bool record = !!options->record_filename; struct sc_acksync *acksync = NULL;
struct server_params params = {
struct sc_server_params params = {
.serial = options->serial,
.log_level = options->log_level, .log_level = options->log_level,
.crop = options->crop, .crop = options->crop,
.port_range = options->port_range, .port_range = options->port_range,
.tunnel_host = options->tunnel_host,
.tunnel_port = options->tunnel_port,
.max_size = options->max_size, .max_size = options->max_size,
.bit_rate = options->bit_rate, .bit_rate = options->bit_rate,
.max_fps = options->max_fps, .max_fps = options->max_fps,
@ -331,179 +360,314 @@ scrcpy(const struct scrcpy_options *options) {
.codec_options = options->codec_options, .codec_options = options->codec_options,
.encoder_name = options->encoder_name, .encoder_name = options->encoder_name,
.force_adb_forward = options->force_adb_forward, .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,
}; };
if (!server_start(&server, options->serial, &params)) {
static const struct sc_server_callbacks cbs = {
.on_connection_failed = sc_server_on_connection_failed,
.on_connected = sc_server_on_connected,
.on_disconnected = sc_server_on_disconnected,
};
if (!sc_server_init(&s->server, &params, &cbs, NULL)) {
return false;
}
if (!sc_server_start(&s->server)) {
goto end; goto end;
} }
server_started = true; server_started = true;
if (!sdl_init_and_configure(options->display, options->render_driver, if (options->display) {
options->disable_screensaver)) { sdl_set_hints(options->render_driver);
}
// Initialize SDL video in addition if display is enabled
if (options->display && SDL_Init(SDL_INIT_VIDEO)) {
LOGC("Could not initialize SDL: %s", SDL_GetError());
goto end; goto end;
} }
if (!server_connect_to(&server)) { sdl_configure(options->display, options->disable_screensaver);
// Await for server without blocking Ctrl+C handling
if (!await_for_server()) {
goto end; goto end;
} }
char device_name[DEVICE_NAME_FIELD_LENGTH]; // It is necessarily initialized here, since the device is connected
struct size frame_size; struct sc_server_info *info = &s->server.info;
// screenrecord does not send frames when the screen content does not const char *serial = s->server.params.serial;
// change therefore, we transmit the screen size before the video stream, assert(serial);
// to be able to init the window immediately
if (!device_read_info(server.video_socket, device_name, &frame_size)) { if (options->display && options->control) {
goto end; if (!file_handler_init(&s->file_handler, serial,
options->push_target)) {
goto end;
}
file_handler_initialized = true;
} }
struct decoder *dec = NULL; struct decoder *dec = NULL;
if (options->display) { bool needs_decoder = options->display;
if (!fps_counter_init(&fps_counter)) { #ifdef HAVE_V4L2
goto end; needs_decoder |= !!options->v4l2_device;
} #endif
fps_counter_initialized = true; if (needs_decoder) {
decoder_init(&s->decoder);
if (!video_buffer_init(&video_buffer, &fps_counter, dec = &s->decoder;
options->render_expired_frames)) {
goto end;
}
video_buffer_initialized = true;
if (options->control) {
if (!file_handler_init(&file_handler, server.serial,
options->push_target)) {
goto end;
}
file_handler_initialized = true;
}
decoder_init(&decoder, &video_buffer);
dec = &decoder;
} }
struct recorder *rec = NULL; struct recorder *rec = NULL;
if (record) { if (options->record_filename) {
if (!recorder_init(&recorder, if (!recorder_init(&s->recorder,
options->record_filename, options->record_filename,
options->record_format, options->record_format,
frame_size)) { info->frame_size)) {
goto end; goto end;
} }
rec = &recorder; rec = &s->recorder;
recorder_initialized = true; recorder_initialized = true;
} }
av_log_set_callback(av_log_callback); av_log_set_callback(av_log_callback);
stream_init(&stream, server.video_socket, dec, rec); static const struct stream_callbacks stream_cbs = {
.on_eos = stream_on_eos,
};
stream_init(&s->stream, s->server.video_socket, &stream_cbs, NULL);
// now we consumed the header values, the socket receives the video stream if (dec) {
// start the stream stream_add_sink(&s->stream, &dec->packet_sink);
if (!stream_start(&stream)) {
goto end;
} }
stream_started = true;
if (options->display) { if (rec) {
if (options->control) { stream_add_sink(&s->stream, &rec->packet_sink);
if (!controller_init(&controller, server.control_socket)) { }
struct sc_key_processor *kp = NULL;
struct sc_mouse_processor *mp = NULL;
if (options->control) {
#ifdef HAVE_AOA_HID
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID) {
bool ok = sc_acksync_init(&s->acksync);
if (!ok) {
goto end; goto end;
} }
controller_initialized = true;
if (!controller_start(&controller)) { acksync = &s->acksync;
goto end;
}
controller_started = true;
} }
#endif
const char *window_title = if (!controller_init(&s->controller, s->server.control_socket,
options->window_title ? options->window_title : device_name; acksync)) {
if (!screen_init_rendering(&screen, window_title, frame_size,
options->always_on_top, options->window_x,
options->window_y, options->window_width,
options->window_height,
options->window_borderless,
options->rotation, options->mipmaps)) {
goto end; goto end;
} }
controller_initialized = true;
if (!controller_start(&s->controller)) {
goto end;
}
controller_started = true;
if (options->turn_screen_off) { if (options->turn_screen_off) {
struct control_msg msg; struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE; msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
msg.set_screen_power_mode.mode = SCREEN_POWER_MODE_OFF; msg.set_screen_power_mode.mode = SCREEN_POWER_MODE_OFF;
if (!controller_push_msg(&controller, &msg)) { if (!controller_push_msg(&s->controller, &msg)) {
LOGW("Could not request 'set screen power mode'"); LOGW("Could not request 'set screen power mode'");
} }
} }
if (options->fullscreen) { if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID) {
screen_switch_fullscreen(&screen); #ifdef HAVE_AOA_HID
bool aoa_hid_ok = false;
bool ok = sc_aoa_init(&s->aoa, serial, acksync);
if (!ok) {
goto aoa_hid_end;
}
if (!sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa)) {
sc_aoa_destroy(&s->aoa);
goto aoa_hid_end;
}
if (!sc_aoa_start(&s->aoa)) {
sc_hid_keyboard_destroy(&s->keyboard_hid);
sc_aoa_destroy(&s->aoa);
goto aoa_hid_end;
}
aoa_hid_ok = true;
kp = &s->keyboard_hid.key_processor;
aoa_hid_initialized = true;
aoa_hid_end:
if (!aoa_hid_ok) {
LOGE("Failed to enable HID over AOA, "
"fallback to default keyboard injection method "
"(-K/--hid-keyboard ignored)");
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
}
#else
LOGE("HID over AOA is not supported on this platform, "
"fallback to default keyboard injection method "
"(-K/--hid-keyboard ignored)");
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
#endif
} }
// keyboard_input_mode may have been reset if HID mode failed
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_INJECT) {
sc_keyboard_inject_init(&s->keyboard_inject, &s->controller,
options);
kp = &s->keyboard_inject.key_processor;
}
//sc_mouse_inject_init(&s->mouse_inject, &s->controller);
sc_hid_mouse_init(&s->mouse_hid, &s->aoa);
mp = &s->mouse_hid.mouse_processor;
} }
input_manager_init(&input_manager, options); if (options->display) {
const char *window_title =
options->window_title ? options->window_title : info->device_name;
ret = event_loop(options); struct 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,
.window_x = options->window_x,
.window_y = options->window_y,
.window_width = options->window_width,
.window_height = options->window_height,
.window_borderless = options->window_borderless,
.rotation = options->rotation,
.mipmaps = options->mipmaps,
.fullscreen = options->fullscreen,
.buffering_time = options->display_buffer,
};
if (!screen_init(&s->screen, &screen_params)) {
goto end;
}
screen_initialized = true;
decoder_add_sink(&s->decoder, &s->screen.frame_sink);
}
#ifdef HAVE_V4L2
if (options->v4l2_device) {
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device,
info->frame_size, options->v4l2_buffer)) {
goto end;
}
decoder_add_sink(&s->decoder, &s->v4l2_sink.frame_sink);
v4l2_sink_initialized = true;
}
#endif
// now we consumed the header values, the socket receives the video stream
// start the stream
if (!stream_start(&s->stream)) {
goto end;
}
stream_started = true;
ret = event_loop(s, options);
LOGD("quit..."); LOGD("quit...");
screen_destroy(&screen); // 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);
end: end:
// stop stream and controller so that they don't continue once their socket // The stream is not stopped explicitly, because it will stop by itself on
// is shutdown // end-of-stream
if (stream_started) { #ifdef HAVE_AOA_HID
stream_stop(&stream); if (aoa_hid_initialized) {
sc_hid_keyboard_destroy(&s->keyboard_hid);
sc_aoa_stop(&s->aoa);
} }
if (acksync) {
sc_acksync_destroy(acksync);
}
#endif
if (controller_started) { if (controller_started) {
controller_stop(&controller); controller_stop(&s->controller);
} }
if (file_handler_initialized) { if (file_handler_initialized) {
file_handler_stop(&file_handler); file_handler_stop(&s->file_handler);
} }
if (fps_counter_initialized) { if (screen_initialized) {
fps_counter_interrupt(&fps_counter); screen_interrupt(&s->screen);
} }
if (server_started) { if (server_started) {
// shutdown the sockets and kill the server // shutdown the sockets and kill the server
server_stop(&server); sc_server_stop(&s->server);
} }
// now that the sockets are shutdown, the stream and controller are // now that the sockets are shutdown, the stream and controller are
// interrupted, we can join them // interrupted, we can join them
if (stream_started) { if (stream_started) {
stream_join(&stream); stream_join(&s->stream);
} }
#ifdef HAVE_V4L2
if (v4l2_sink_initialized) {
sc_v4l2_sink_destroy(&s->v4l2_sink);
}
#endif
#ifdef HAVE_AOA_HID
if (aoa_hid_initialized) {
sc_aoa_join(&s->aoa);
sc_aoa_destroy(&s->aoa);
}
#endif
// 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);
}
if (controller_started) { if (controller_started) {
controller_join(&controller); controller_join(&s->controller);
} }
if (controller_initialized) { if (controller_initialized) {
controller_destroy(&controller); controller_destroy(&s->controller);
} }
if (recorder_initialized) { if (recorder_initialized) {
recorder_destroy(&recorder); recorder_destroy(&s->recorder);
} }
if (file_handler_initialized) { if (file_handler_initialized) {
file_handler_join(&file_handler); file_handler_join(&s->file_handler);
file_handler_destroy(&file_handler); file_handler_destroy(&s->file_handler);
} }
if (video_buffer_initialized) { sc_server_destroy(&s->server);
video_buffer_destroy(&video_buffer);
}
if (fps_counter_initialized) {
fps_counter_join(&fps_counter);
fps_counter_destroy(&fps_counter);
}
server_destroy(&server);
return ret; return ret;
} }

View File

@ -4,134 +4,9 @@
#include "common.h" #include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include "options.h"
#include <stdint.h>
enum sc_log_level {
SC_LOG_LEVEL_DEBUG,
SC_LOG_LEVEL_INFO,
SC_LOG_LEVEL_WARN,
SC_LOG_LEVEL_ERROR,
};
enum sc_record_format {
SC_RECORD_FORMAT_AUTO,
SC_RECORD_FORMAT_MP4,
SC_RECORD_FORMAT_MKV,
};
#define SC_MAX_SHORTCUT_MODS 8
enum sc_shortcut_mod {
SC_MOD_LCTRL = 1 << 0,
SC_MOD_RCTRL = 1 << 1,
SC_MOD_LALT = 1 << 2,
SC_MOD_RALT = 1 << 3,
SC_MOD_LSUPER = 1 << 4,
SC_MOD_RSUPER = 1 << 5,
};
struct sc_shortcut_mods {
unsigned data[SC_MAX_SHORTCUT_MODS];
unsigned count;
};
struct sc_port_range {
uint16_t first;
uint16_t last;
};
#define SC_WINDOW_POSITION_UNDEFINED (-0x8000)
struct scrcpy_options {
const char *serial;
const char *crop;
const char *record_filename;
const char *window_title;
const char *push_target;
const char *render_driver;
const char *codec_options;
const char *encoder_name;
enum sc_log_level log_level;
enum sc_record_format record_format;
struct sc_port_range port_range;
struct sc_shortcut_mods shortcut_mods;
uint16_t max_size;
uint32_t bit_rate;
uint16_t max_fps;
int8_t lock_video_orientation;
uint8_t rotation;
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
uint16_t window_width;
uint16_t window_height;
uint32_t display_id;
bool show_touches;
bool fullscreen;
bool always_on_top;
bool control;
bool display;
bool turn_screen_off;
bool render_expired_frames;
bool prefer_text;
bool window_borderless;
bool mipmaps;
bool stay_awake;
bool force_adb_forward;
bool disable_screensaver;
bool forward_key_repeat;
bool forward_all_clicks;
bool legacy_paste;
};
#define SCRCPY_OPTIONS_DEFAULT { \
.serial = NULL, \
.crop = NULL, \
.record_filename = NULL, \
.window_title = NULL, \
.push_target = NULL, \
.render_driver = NULL, \
.codec_options = NULL, \
.encoder_name = NULL, \
.log_level = SC_LOG_LEVEL_INFO, \
.record_format = SC_RECORD_FORMAT_AUTO, \
.port_range = { \
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \
.last = DEFAULT_LOCAL_PORT_RANGE_LAST, \
}, \
.shortcut_mods = { \
.data = {SC_MOD_LALT, SC_MOD_LSUPER}, \
.count = 2, \
}, \
.max_size = DEFAULT_MAX_SIZE, \
.bit_rate = DEFAULT_BIT_RATE, \
.max_fps = 0, \
.lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \
.rotation = 0, \
.window_x = SC_WINDOW_POSITION_UNDEFINED, \
.window_y = SC_WINDOW_POSITION_UNDEFINED, \
.window_width = 0, \
.window_height = 0, \
.display_id = 0, \
.show_touches = false, \
.fullscreen = false, \
.always_on_top = false, \
.control = true, \
.display = true, \
.turn_screen_off = false, \
.render_expired_frames = false, \
.prefer_text = false, \
.window_borderless = false, \
.mipmaps = true, \
.stay_awake = false, \
.force_adb_forward = false, \
.disable_screensaver = false, \
.forward_key_repeat = true, \
.forward_all_clicks = false, \
.legacy_paste = false, \
}
bool bool
scrcpy(const struct scrcpy_options *options); scrcpy(struct scrcpy_options *options);
#endif #endif

View File

@ -4,18 +4,19 @@
#include <string.h> #include <string.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "icon.xpm" #include "events.h"
#include "scrcpy.h" #include "icon.h"
#include "tiny_xpm.h" #include "options.h"
#include "video_buffer.h" #include "video_buffer.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
#define DISPLAY_MARGINS 96 #define DISPLAY_MARGINS 96
static inline struct size #define DOWNCAST(SINK) container_of(SINK, struct screen, frame_sink)
get_rotated_size(struct size size, int rotation) {
struct size rotated_size; static inline struct sc_size
get_rotated_size(struct sc_size size, int rotation) {
struct sc_size rotated_size;
if (rotation & 1) { if (rotation & 1) {
rotated_size.width = size.height; rotated_size.width = size.height;
rotated_size.height = size.width; rotated_size.height = size.width;
@ -26,22 +27,34 @@ get_rotated_size(struct size size, int rotation) {
return rotated_size; return rotated_size;
} }
// get the window size in a struct size // get the window size in a struct sc_size
static struct size static struct sc_size
get_window_size(const struct screen *screen) { get_window_size(const struct screen *screen) {
int width; int width;
int height; int height;
SDL_GetWindowSize(screen->window, &width, &height); SDL_GetWindowSize(screen->window, &width, &height);
struct size size; struct sc_size size;
size.width = width; size.width = width;
size.height = height; size.height = height;
return size; return size;
} }
static struct sc_point
get_window_position(const struct screen *screen) {
int x;
int y;
SDL_GetWindowPosition(screen->window, &x, &y);
struct sc_point point;
point.x = x;
point.y = y;
return point;
}
// set the window size to be applied when fullscreen is disabled // set the window size to be applied when fullscreen is disabled
static void static void
set_window_size(struct screen *screen, struct size new_size) { set_window_size(struct screen *screen, struct sc_size new_size) {
assert(!screen->fullscreen); assert(!screen->fullscreen);
assert(!screen->maximized); assert(!screen->maximized);
SDL_SetWindowSize(screen->window, new_size.width, new_size.height); SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
@ -49,14 +62,9 @@ set_window_size(struct screen *screen, struct size new_size) {
// get the preferred display bounds (i.e. the screen bounds with some margins) // get the preferred display bounds (i.e. the screen bounds with some margins)
static bool static bool
get_preferred_display_bounds(struct size *bounds) { get_preferred_display_bounds(struct sc_size *bounds) {
SDL_Rect rect; SDL_Rect rect;
#ifdef SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS if (SDL_GetDisplayUsableBounds(0, &rect)) {
# 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)) {
LOGW("Could not get display usable bounds: %s", SDL_GetError()); LOGW("Could not get display usable bounds: %s", SDL_GetError());
return false; return false;
} }
@ -67,7 +75,7 @@ get_preferred_display_bounds(struct size *bounds) {
} }
static bool static bool
is_optimal_size(struct size current_size, struct size content_size) { is_optimal_size(struct sc_size current_size, struct sc_size content_size) {
// The size is optimal if we can recompute one dimension of the current // The size is optimal if we can recompute one dimension of the current
// size from the other // size from the other
return current_size.height == current_size.width * content_size.height return current_size.height == current_size.width * content_size.height
@ -81,20 +89,21 @@ is_optimal_size(struct size current_size, struct size content_size) {
// crops the black borders) // crops the black borders)
// - it keeps the aspect ratio // - it keeps the aspect ratio
// - it scales down to make it fit in the display_size // - it scales down to make it fit in the display_size
static struct size static struct sc_size
get_optimal_size(struct size current_size, struct 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) { if (content_size.width == 0 || content_size.height == 0) {
// avoid division by 0 // avoid division by 0
return current_size; return current_size;
} }
struct size window_size; struct sc_size window_size;
struct size display_size; struct sc_size display_size;
if (!get_preferred_display_bounds(&display_size)) { if (!within_display_bounds ||
// could not get display bounds, do not constraint the size !get_preferred_display_bounds(&display_size)) {
window_size.width = current_size.width; // do not constraint the size
window_size.height = current_size.height; window_size = current_size;
} else { } else {
window_size.width = MIN(current_size.width, display_size.width); window_size.width = MIN(current_size.width, display_size.width);
window_size.height = MIN(current_size.height, display_size.height); window_size.height = MIN(current_size.height, display_size.height);
@ -120,21 +129,14 @@ get_optimal_size(struct size current_size, struct size content_size) {
return window_size; return window_size;
} }
// same as get_optimal_size(), but read the current size from the window
static inline struct size
get_optimal_window_size(const struct screen *screen, struct size content_size) {
struct size window_size = get_window_size(screen);
return get_optimal_size(window_size, content_size);
}
// initially, there is no current size, so use the frame size as current size // initially, there is no current size, so use the frame size as current size
// req_width and req_height, if not 0, are the sizes requested by the user // req_width and req_height, if not 0, are the sizes requested by the user
static inline struct size static inline struct sc_size
get_initial_optimal_size(struct size content_size, uint16_t req_width, get_initial_optimal_size(struct sc_size content_size, uint16_t req_width,
uint16_t req_height) { uint16_t req_height) {
struct size window_size; struct sc_size window_size;
if (!req_width && !req_height) { 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 { } else {
if (req_width) { if (req_width) {
window_size.width = req_width; window_size.width = req_width;
@ -160,9 +162,9 @@ screen_update_content_rect(struct screen *screen) {
int dh; int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh); SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
struct size content_size = screen->content_size; struct sc_size content_size = screen->content_size;
// The drawable size is the window size * the HiDPI scale // The drawable size is the window size * the HiDPI scale
struct size drawable_size = {dw, dh}; struct sc_size drawable_size = {dw, dh};
SDL_Rect *rect = &screen->rect; SDL_Rect *rect = &screen->rect;
@ -191,15 +193,10 @@ screen_update_content_rect(struct screen *screen) {
} }
} }
void
screen_init(struct screen *screen) {
*screen = (struct screen) SCREEN_INITIALIZER;
}
static inline SDL_Texture * static inline SDL_Texture *
create_texture(struct screen *screen) { create_texture(struct screen *screen) {
SDL_Renderer *renderer = screen->renderer; SDL_Renderer *renderer = screen->renderer;
struct size size = screen->frame_size; struct sc_size size = screen->frame_size;
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
SDL_TEXTUREACCESS_STREAMING, SDL_TEXTUREACCESS_STREAMING,
size.width, size.height); size.width, size.height);
@ -223,252 +220,11 @@ create_texture(struct screen *screen) {
return texture; return texture;
} }
bool // render the texture to the renderer
screen_init_rendering(struct screen *screen, const char *window_title, //
struct size frame_size, bool always_on_top, // Set the update_content_rect flag if the window or content size may have
int16_t window_x, int16_t window_y, uint16_t window_width, // changed, so that the content rectangle is recomputed
uint16_t window_height, bool window_borderless,
uint8_t rotation, bool mipmaps) {
screen->frame_size = frame_size;
screen->rotation = rotation;
if (rotation) {
LOGI("Initial display rotation set to %u", rotation);
}
struct size content_size = get_rotated_size(frame_size, screen->rotation);
screen->content_size = content_size;
struct size window_size =
get_initial_optimal_size(content_size, window_width, window_height);
uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE;
#ifdef HIDPI_SUPPORT
window_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
#endif
if (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 (window_borderless) {
window_flags |= SDL_WINDOW_BORDERLESS;
}
int x = window_x != SC_WINDOW_POSITION_UNDEFINED
? window_x : (int) SDL_WINDOWPOS_UNDEFINED;
int y = window_y != SC_WINDOW_POSITION_UNDEFINED
? window_y : (int) SDL_WINDOWPOS_UNDEFINED;
screen->window = SDL_CreateWindow(window_title, x, y,
window_size.width, window_size.height,
window_flags);
if (!screen->window) {
LOGC("Could not create window: %s", SDL_GetError());
return false;
}
screen->renderer = SDL_CreateRenderer(screen->window, -1,
SDL_RENDERER_ACCELERATED);
if (!screen->renderer) {
LOGC("Could not create renderer: %s", SDL_GetError());
screen_destroy(screen);
return false;
}
SDL_RendererInfo renderer_info;
int r = SDL_GetRendererInfo(screen->renderer, &renderer_info);
const char *renderer_name = r ? NULL : renderer_info.name;
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
// starts with "opengl"
screen->use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
if (screen->use_opengl) {
struct sc_opengl *gl = &screen->gl;
sc_opengl_init(gl);
LOGI("OpenGL version: %s", gl->version);
if (mipmaps) {
bool supports_mipmaps =
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
2, 0 /* OpenGL ES 2.0+ */);
if (supports_mipmaps) {
LOGI("Trilinear filtering enabled");
screen->mipmaps = true;
} else {
LOGW("Trilinear filtering disabled "
"(OpenGL 3.0+ or ES 2.0+ required)");
}
} else {
LOGI("Trilinear filtering disabled");
}
} else {
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
}
SDL_Surface *icon = read_xpm(icon_xpm);
if (icon) {
SDL_SetWindowIcon(screen->window, icon);
SDL_FreeSurface(icon);
} else {
LOGW("Could not load icon");
}
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width,
frame_size.height);
screen->texture = create_texture(screen);
if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError());
screen_destroy(screen);
return false;
}
// 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);
return true;
}
void
screen_show_window(struct screen *screen) {
SDL_ShowWindow(screen->window);
}
void
screen_destroy(struct screen *screen) {
if (screen->texture) {
SDL_DestroyTexture(screen->texture);
}
if (screen->renderer) {
SDL_DestroyRenderer(screen->renderer);
}
if (screen->window) {
SDL_DestroyWindow(screen->window);
}
}
static void static void
resize_for_content(struct screen *screen, struct size old_content_size,
struct size new_content_size) {
struct size window_size = get_window_size(screen);
struct size target_size = {
.width = (uint32_t) window_size.width * new_content_size.width
/ old_content_size.width,
.height = (uint32_t) window_size.height * new_content_size.height
/ old_content_size.height,
};
target_size = get_optimal_size(target_size, new_content_size);
set_window_size(screen, target_size);
}
static void
set_content_size(struct screen *screen, struct size new_content_size) {
if (!screen->fullscreen && !screen->maximized) {
resize_for_content(screen, screen->content_size, new_content_size);
} else if (!screen->resize_pending) {
// Store the windowed size to be able to compute the optimal size once
// fullscreen and maximized are disabled
screen->windowed_content_size = screen->content_size;
screen->resize_pending = true;
}
screen->content_size = new_content_size;
}
static void
apply_pending_resize(struct screen *screen) {
assert(!screen->fullscreen);
assert(!screen->maximized);
if (screen->resize_pending) {
resize_for_content(screen, screen->windowed_content_size,
screen->content_size);
screen->resize_pending = false;
}
}
void
screen_set_rotation(struct screen *screen, unsigned rotation) {
assert(rotation < 4);
if (rotation == screen->rotation) {
return;
}
struct size new_content_size =
get_rotated_size(screen->frame_size, rotation);
set_content_size(screen, new_content_size);
screen->rotation = rotation;
LOGI("Display rotation set to %u", rotation);
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 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
SDL_DestroyTexture(screen->texture);
screen->frame_size = new_frame_size;
struct size new_content_size =
get_rotated_size(new_frame_size, screen->rotation);
set_content_size(screen, new_content_size);
screen_update_content_rect(screen);
LOGI("New texture: %" PRIu16 "x%" PRIu16,
screen->frame_size.width, screen->frame_size.height);
screen->texture = create_texture(screen);
if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError());
return false;
}
}
return true;
}
// write the frame into the texture
static void
update_texture(struct screen *screen, const AVFrame *frame) {
SDL_UpdateYUVTexture(screen->texture, NULL,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
if (screen->mipmaps) {
assert(screen->use_opengl);
SDL_GL_BindTexture(screen->texture, NULL, NULL);
screen->gl.GenerateMipmap(GL_TEXTURE_2D);
SDL_GL_UnbindTexture(screen->texture);
}
}
bool
screen_update_frame(struct screen *screen, struct video_buffer *vb) {
mutex_lock(vb->mutex);
const AVFrame *frame = video_buffer_consume_rendered_frame(vb);
struct size new_frame_size = {frame->width, frame->height};
if (!prepare_for_frame(screen, new_frame_size)) {
mutex_unlock(vb->mutex);
return false;
}
update_texture(screen, frame);
mutex_unlock(vb->mutex);
screen_render(screen, false);
return true;
}
void
screen_render(struct screen *screen, bool update_content_rect) { screen_render(struct screen *screen, bool update_content_rect) {
if (update_content_rect) { if (update_content_rect) {
screen_update_content_rect(screen); screen_update_content_rect(screen);
@ -502,6 +258,435 @@ screen_render(struct screen *screen, bool update_content_rect) {
SDL_RenderPresent(screen->renderer); SDL_RenderPresent(screen->renderer);
} }
#if defined(__APPLE__) || defined(__WINDOWS__)
# define CONTINUOUS_RESIZING_WORKAROUND
#endif
#ifdef CONTINUOUS_RESIZING_WORKAROUND
// On Windows and MacOS, resizing blocks the event loop, so resizing events are
// not triggered. As a workaround, handle them in an event handler.
//
// <https://bugzilla.libsdl.org/show_bug.cgi?id=2077>
// <https://stackoverflow.com/a/40693139/1987178>
static int
event_watcher(void *data, SDL_Event *event) {
struct 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);
}
return 0;
}
#endif
static bool
screen_frame_sink_open(struct sc_frame_sink *sink) {
struct screen *screen = DOWNCAST(sink);
(void) screen;
#ifndef NDEBUG
screen->open = true;
#endif
// nothing to do, the screen is already open on the main thread
return true;
}
static void
screen_frame_sink_close(struct sc_frame_sink *sink) {
struct screen *screen = DOWNCAST(sink);
(void) screen;
#ifndef NDEBUG
screen->open = false;
#endif
// nothing to do, the screen lifecycle is not managed by the frame producer
}
static bool
screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
struct screen *screen = DOWNCAST(sink);
return sc_video_buffer_push(&screen->vb, frame);
}
static void
sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
void *userdata) {
(void) vb;
struct screen *screen = userdata;
// event_failed implies previous_skipped (the previous frame may not have
// been consumed if the event was not sent)
assert(!screen->event_failed || previous_skipped);
bool need_new_event;
if (previous_skipped) {
fps_counter_add_skipped_frame(&screen->fps_counter);
// The EVENT_NEW_FRAME triggered for the previous frame will consume
// this new frame instead, unless the previous event failed
need_new_event = screen->event_failed;
} else {
need_new_event = true;
}
if (need_new_event) {
static SDL_Event new_frame_event = {
.type = EVENT_NEW_FRAME,
};
// Post the event on the UI thread
int ret = SDL_PushEvent(&new_frame_event);
if (ret < 0) {
LOGW("Could not post new frame event: %s", SDL_GetError());
screen->event_failed = true;
} else {
screen->event_failed = false;
}
}
}
bool
screen_init(struct screen *screen, const struct screen_params *params) {
screen->resize_pending = false;
screen->has_frame = false;
screen->fullscreen = false;
screen->maximized = false;
screen->event_failed = false;
static const struct sc_video_buffer_callbacks cbs = {
.on_new_frame = sc_video_buffer_on_new_frame,
};
bool ok = sc_video_buffer_init(&screen->vb, params->buffering_time, &cbs,
screen);
if (!ok) {
return false;
}
ok = sc_video_buffer_start(&screen->vb);
if (!ok) {
goto error_destroy_video_buffer;
}
if (!fps_counter_init(&screen->fps_counter)) {
goto error_stop_and_join_video_buffer;
}
screen->frame_size = params->frame_size;
screen->rotation = params->rotation;
if (screen->rotation) {
LOGI("Initial display rotation set to %u", screen->rotation);
}
struct sc_size content_size =
get_rotated_size(screen->frame_size, screen->rotation);
screen->content_size = content_size;
struct sc_size window_size =
get_initial_optimal_size(content_size,params->window_width,
params->window_height);
uint32_t window_flags = SDL_WINDOW_HIDDEN
| SDL_WINDOW_RESIZABLE
| SDL_WINDOW_ALLOW_HIGHDPI;
if (params->always_on_top) {
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
}
if (params->window_borderless) {
window_flags |= SDL_WINDOW_BORDERLESS;
}
int x = params->window_x != SC_WINDOW_POSITION_UNDEFINED
? params->window_x : (int) SDL_WINDOWPOS_UNDEFINED;
int y = params->window_y != SC_WINDOW_POSITION_UNDEFINED
? params->window_y : (int) SDL_WINDOWPOS_UNDEFINED;
screen->window = SDL_CreateWindow(params->window_title, x, y,
window_size.width, window_size.height,
window_flags);
if (!screen->window) {
LOGC("Could not create window: %s", SDL_GetError());
goto error_destroy_fps_counter;
}
screen->renderer = SDL_CreateRenderer(screen->window, -1,
SDL_RENDERER_ACCELERATED);
if (!screen->renderer) {
LOGC("Could not create renderer: %s", SDL_GetError());
goto error_destroy_window;
}
SDL_RendererInfo renderer_info;
int r = SDL_GetRendererInfo(screen->renderer, &renderer_info);
const char *renderer_name = r ? NULL : renderer_info.name;
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
screen->mipmaps = false;
// starts with "opengl"
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
if (use_opengl) {
struct sc_opengl *gl = &screen->gl;
sc_opengl_init(gl);
LOGI("OpenGL version: %s", gl->version);
if (params->mipmaps) {
bool supports_mipmaps =
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
2, 0 /* OpenGL ES 2.0+ */);
if (supports_mipmaps) {
LOGI("Trilinear filtering enabled");
screen->mipmaps = true;
} else {
LOGW("Trilinear filtering disabled "
"(OpenGL 3.0+ or ES 2.0+ required)");
}
} else {
LOGI("Trilinear filtering disabled");
}
} else if (params->mipmaps) {
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
}
SDL_Surface *icon = scrcpy_icon_load();
if (icon) {
SDL_SetWindowIcon(screen->window, icon);
scrcpy_icon_destroy(icon);
} else {
LOGW("Could not load icon");
}
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, params->frame_size.width,
params->frame_size.height);
screen->texture = create_texture(screen);
if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError());
goto error_destroy_renderer;
}
screen->frame = av_frame_alloc();
if (!screen->frame) {
LOG_OOM();
goto error_destroy_texture;
}
struct 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,
};
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);
if (params->fullscreen) {
screen_switch_fullscreen(screen);
}
#ifdef CONTINUOUS_RESIZING_WORKAROUND
SDL_AddEventWatch(event_watcher, screen);
#endif
if (SDL_SetRelativeMouseMode(true)) {
LOGE("Could not set relative mouse mode: %s", SDL_GetError());
}
static const struct sc_frame_sink_ops ops = {
.open = screen_frame_sink_open,
.close = screen_frame_sink_close,
.push = screen_frame_sink_push,
};
screen->frame_sink.ops = &ops;
#ifndef NDEBUG
screen->open = false;
#endif
return true;
error_destroy_texture:
SDL_DestroyTexture(screen->texture);
error_destroy_renderer:
SDL_DestroyRenderer(screen->renderer);
error_destroy_window:
SDL_DestroyWindow(screen->window);
error_destroy_fps_counter:
fps_counter_destroy(&screen->fps_counter);
error_stop_and_join_video_buffer:
sc_video_buffer_stop(&screen->vb);
sc_video_buffer_join(&screen->vb);
error_destroy_video_buffer:
sc_video_buffer_destroy(&screen->vb);
return false;
}
static void
screen_show_window(struct screen *screen) {
SDL_ShowWindow(screen->window);
}
void
screen_hide_window(struct screen *screen) {
SDL_HideWindow(screen->window);
}
void
screen_interrupt(struct screen *screen) {
sc_video_buffer_stop(&screen->vb);
fps_counter_interrupt(&screen->fps_counter);
}
void
screen_join(struct screen *screen) {
sc_video_buffer_join(&screen->vb);
fps_counter_join(&screen->fps_counter);
}
void
screen_destroy(struct screen *screen) {
#ifndef NDEBUG
assert(!screen->open);
#endif
av_frame_free(&screen->frame);
SDL_DestroyTexture(screen->texture);
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
fps_counter_destroy(&screen->fps_counter);
sc_video_buffer_destroy(&screen->vb);
}
static void
resize_for_content(struct 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 = {
.width = (uint32_t) window_size.width * new_content_size.width
/ old_content_size.width,
.height = (uint32_t) window_size.height * new_content_size.height
/ old_content_size.height,
};
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) {
if (!screen->fullscreen && !screen->maximized) {
resize_for_content(screen, screen->content_size, new_content_size);
} else if (!screen->resize_pending) {
// Store the windowed size to be able to compute the optimal size once
// fullscreen and maximized are disabled
screen->windowed_content_size = screen->content_size;
screen->resize_pending = true;
}
screen->content_size = new_content_size;
}
static void
apply_pending_resize(struct screen *screen) {
assert(!screen->fullscreen);
assert(!screen->maximized);
if (screen->resize_pending) {
resize_for_content(screen, screen->windowed_content_size,
screen->content_size);
screen->resize_pending = false;
}
}
void
screen_set_rotation(struct screen *screen, unsigned rotation) {
assert(rotation < 4);
if (rotation == screen->rotation) {
return;
}
struct sc_size new_content_size =
get_rotated_size(screen->frame_size, rotation);
set_content_size(screen, new_content_size);
screen->rotation = rotation;
LOGI("Display rotation set to %u", rotation);
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) {
if (screen->frame_size.width != new_frame_size.width
|| screen->frame_size.height != new_frame_size.height) {
// frame dimension changed, destroy texture
SDL_DestroyTexture(screen->texture);
screen->frame_size = new_frame_size;
struct sc_size new_content_size =
get_rotated_size(new_frame_size, screen->rotation);
set_content_size(screen, new_content_size);
screen_update_content_rect(screen);
LOGI("New texture: %" PRIu16 "x%" PRIu16,
screen->frame_size.width, screen->frame_size.height);
screen->texture = create_texture(screen);
if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError());
return false;
}
}
return true;
}
// write the frame into the texture
static void
update_texture(struct screen *screen, const AVFrame *frame) {
SDL_UpdateYUVTexture(screen->texture, NULL,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
if (screen->mipmaps) {
SDL_GL_BindTexture(screen->texture, NULL, NULL);
screen->gl.GenerateMipmap(GL_TEXTURE_2D);
SDL_GL_UnbindTexture(screen->texture);
}
}
static bool
screen_update_frame(struct screen *screen) {
av_frame_unref(screen->frame);
sc_video_buffer_consume(&screen->vb, screen->frame);
AVFrame *frame = screen->frame;
fps_counter_add_rendered_frame(&screen->fps_counter);
struct sc_size new_frame_size = {frame->width, frame->height};
if (!prepare_for_frame(screen, new_frame_size)) {
return false;
}
update_texture(screen, frame);
screen_render(screen, false);
return true;
}
void void
screen_switch_fullscreen(struct screen *screen) { screen_switch_fullscreen(struct screen *screen) {
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP; uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
@ -525,9 +710,20 @@ screen_resize_to_fit(struct screen *screen) {
return; return;
} }
struct size optimal_size = struct sc_point point = get_window_position(screen);
get_optimal_window_size(screen, screen->content_size); struct sc_size window_size = get_window_size(screen);
struct sc_size optimal_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);
assert(optimal_size.height <= window_size.height);
uint32_t new_x = point.x + (window_size.width - optimal_size.width) / 2;
uint32_t new_y = point.y + (window_size.height - optimal_size.height) / 2;
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height); SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
SDL_SetWindowPosition(screen->window, new_x, new_y);
LOGD("Resized to optimal size: %ux%u", optimal_size.width, LOGD("Resized to optimal size: %ux%u", optimal_size.width,
optimal_size.height); optimal_size.height);
} }
@ -543,40 +739,62 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
screen->maximized = false; screen->maximized = false;
} }
struct size content_size = screen->content_size; struct sc_size content_size = screen->content_size;
SDL_SetWindowSize(screen->window, content_size.width, content_size.height); SDL_SetWindowSize(screen->window, content_size.width, content_size.height);
LOGD("Resized to pixel-perfect: %ux%u", content_size.width, LOGD("Resized to pixel-perfect: %ux%u", content_size.width,
content_size.height); content_size.height);
} }
void bool
screen_handle_window_event(struct screen *screen, screen_handle_event(struct screen *screen, SDL_Event *event) {
const SDL_WindowEvent *event) { switch (event->type) {
switch (event->event) { case EVENT_NEW_FRAME:
case SDL_WINDOWEVENT_EXPOSED: if (!screen->has_frame) {
screen_render(screen, true); screen->has_frame = true;
break; // this is the very first frame, show the window
case SDL_WINDOWEVENT_SIZE_CHANGED: screen_show_window(screen);
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_MAXIMIZED:
screen->maximized = true;
break;
case SDL_WINDOWEVENT_RESTORED:
if (screen->fullscreen) {
// On Windows, in maximized+fullscreen, disabling fullscreen
// mode unexpectedly triggers the "restored" then "maximized"
// events, leaving the window in a weird state (maximized
// according to the events, but not maximized visually).
break;
} }
screen->maximized = false; bool ok = screen_update_frame(screen);
apply_pending_resize(screen); if (!ok) {
break; LOGW("Frame update failed\n");
}
return true;
case SDL_WINDOWEVENT:
if (!screen->has_frame) {
// Do nothing
return true;
}
switch (event->window.event) {
case SDL_WINDOWEVENT_EXPOSED:
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_MAXIMIZED:
screen->maximized = true;
break;
case SDL_WINDOWEVENT_RESTORED:
if (screen->fullscreen) {
// On Windows, in maximized+fullscreen, disabling
// fullscreen mode unexpectedly triggers the "restored"
// then "maximized" events, leaving the window in a
// weird state (maximized according to the events, but
// not maximized visually).
break;
}
screen->maximized = false;
apply_pending_resize(screen);
screen_render(screen, true);
break;
}
return true;
} }
return input_manager_handle_event(&screen->im, event);
} }
struct point struct sc_point
screen_convert_drawable_to_frame_coords(struct screen *screen, screen_convert_drawable_to_frame_coords(struct screen *screen,
int32_t x, int32_t y) { int32_t x, int32_t y) {
unsigned rotation = screen->rotation; unsigned rotation = screen->rotation;
@ -590,7 +808,7 @@ screen_convert_drawable_to_frame_coords(struct screen *screen,
y = (int64_t) (y - screen->rect.y) * h / screen->rect.h; y = (int64_t) (y - screen->rect.y) * h / screen->rect.h;
// rotate // rotate
struct point result; struct sc_point result;
switch (rotation) { switch (rotation) {
case 0: case 0:
result.x = x; result.x = x;
@ -613,7 +831,7 @@ screen_convert_drawable_to_frame_coords(struct screen *screen,
return result; return result;
} }
struct point struct sc_point
screen_convert_window_to_frame_coords(struct screen *screen, screen_convert_window_to_frame_coords(struct screen *screen,
int32_t x, int32_t y) { int32_t x, int32_t y) {
screen_hidpi_scale_coords(screen, &x, &y); screen_hidpi_scale_coords(screen, &x, &y);

View File

@ -7,24 +7,38 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include "controller.h"
#include "coords.h" #include "coords.h"
#include "fps_counter.h"
#include "input_manager.h"
#include "opengl.h" #include "opengl.h"
#include "trait/key_processor.h"
struct video_buffer; #include "trait/frame_sink.h"
#include "trait/mouse_processor.h"
#include "video_buffer.h"
struct screen { struct 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 input_manager im;
struct sc_video_buffer vb;
struct fps_counter fps_counter;
SDL_Window *window; SDL_Window *window;
SDL_Renderer *renderer; SDL_Renderer *renderer;
SDL_Texture *texture; SDL_Texture *texture;
bool use_opengl;
struct sc_opengl gl; struct sc_opengl gl;
struct size frame_size; struct sc_size frame_size;
struct size content_size; // rotated frame_size struct sc_size content_size; // rotated frame_size
bool resize_pending; // resize requested while fullscreen or maximized bool resize_pending; // resize requested while fullscreen or maximized
// The content size the last time the window was not maximized or // The content size the last time the window was not maximized or
// fullscreen (meaningful only when resize_pending is true) // fullscreen (meaningful only when resize_pending is true)
struct size windowed_content_size; struct sc_size windowed_content_size;
// client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise) // client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise)
unsigned rotation; unsigned rotation;
@ -33,74 +47,66 @@ struct screen {
bool has_frame; bool has_frame;
bool fullscreen; bool fullscreen;
bool maximized; bool maximized;
bool no_window;
bool mipmaps; bool mipmaps;
bool event_failed; // in case SDL_PushEvent() returned an error
AVFrame *frame;
}; };
#define SCREEN_INITIALIZER { \ struct screen_params {
.window = NULL, \ struct controller *controller;
.renderer = NULL, \ struct sc_key_processor *kp;
.texture = NULL, \ struct sc_mouse_processor *mp;
.use_opengl = false, \
.gl = {0}, \
.frame_size = { \
.width = 0, \
.height = 0, \
}, \
.content_size = { \
.width = 0, \
.height = 0, \
}, \
.resize_pending = false, \
.windowed_content_size = { \
.width = 0, \
.height = 0, \
}, \
.rotation = 0, \
.rect = { \
.x = 0, \
.y = 0, \
.w = 0, \
.h = 0, \
}, \
.has_frame = false, \
.fullscreen = false, \
.maximized = false, \
.no_window = false, \
.mipmaps = false, \
}
// initialize default values bool control;
void bool forward_all_clicks;
screen_init(struct screen *screen); 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;
int16_t window_x;
int16_t window_y;
uint16_t window_width; // accepts SC_WINDOW_POSITION_UNDEFINED
uint16_t window_height; // accepts SC_WINDOW_POSITION_UNDEFINED
bool window_borderless;
uint8_t rotation;
bool mipmaps;
bool fullscreen;
sc_tick buffering_time;
};
// initialize screen, create window, renderer and texture (window is hidden) // initialize screen, create window, renderer and texture (window is hidden)
// window_x and window_y accept SC_WINDOW_POSITION_UNDEFINED
bool bool
screen_init_rendering(struct screen *screen, const char *window_title, screen_init(struct screen *screen, const struct screen_params *params);
struct size frame_size, bool always_on_top,
int16_t window_x, int16_t window_y, uint16_t window_width,
uint16_t window_height, bool window_borderless,
uint8_t rotation, bool mipmaps);
// show the window // request to interrupt any inner thread
// must be called before screen_join()
void void
screen_show_window(struct screen *screen); screen_interrupt(struct screen *screen);
// join any inner thread
void
screen_join(struct screen *screen);
// destroy window, renderer and texture (if any) // destroy window, renderer and texture (if any)
void void
screen_destroy(struct screen *screen); screen_destroy(struct screen *screen);
// resize if necessary and write the rendered frame into the texture // hide the window
bool
screen_update_frame(struct screen *screen, struct video_buffer *vb);
// render the texture to the renderer
// //
// Set the update_content_rect flag if the window or content size may have // It is used to hide the window immediately on closing without waiting for
// changed, so that the content rectangle is recomputed // screen_destroy()
void void
screen_render(struct screen *screen, bool update_content_rect); screen_hide_window(struct screen *screen);
// switch the fullscreen mode // switch the fullscreen mode
void void
@ -118,19 +124,19 @@ screen_resize_to_pixel_perfect(struct screen *screen);
void void
screen_set_rotation(struct screen *screen, unsigned rotation); screen_set_rotation(struct screen *screen, unsigned rotation);
// react to window events // react to SDL events
void bool
screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event); screen_handle_event(struct screen *screen, SDL_Event *event);
// convert point from window coordinates to frame coordinates // convert point from window coordinates to frame coordinates
// x and y are expressed in pixels // x and y are expressed in pixels
struct point struct sc_point
screen_convert_window_to_frame_coords(struct screen *screen, screen_convert_window_to_frame_coords(struct screen *screen,
int32_t x, int32_t y); int32_t x, int32_t y);
// convert point from drawable coordinates to frame coordinates // convert point from drawable coordinates to frame coordinates
// x and y are expressed in pixels // x and y are expressed in pixels
struct point struct sc_point
screen_convert_drawable_to_frame_coords(struct screen *screen, screen_convert_drawable_to_frame_coords(struct screen *screen,
int32_t x, int32_t y); int32_t x, int32_t y);

File diff suppressed because it is too large Load Diff

View File

@ -6,38 +6,31 @@
#include <stdatomic.h> #include <stdatomic.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <SDL2/SDL_thread.h>
#include "adb.h" #include "adb.h"
#include "scrcpy.h" #include "adb_tunnel.h"
#include "coords.h"
#include "options.h"
#include "util/intr.h"
#include "util/log.h" #include "util/log.h"
#include "util/net.h" #include "util/net.h"
#include "util/thread.h"
struct server { #define SC_DEVICE_NAME_FIELD_LENGTH 64
char *serial; struct sc_server_info {
process_t process; char device_name[SC_DEVICE_NAME_FIELD_LENGTH];
SDL_Thread *wait_server_thread; struct sc_size frame_size;
atomic_flag server_socket_closed;
SDL_mutex *mutex;
SDL_cond *process_terminated_cond;
bool process_terminated;
socket_t server_socket; // only used if !tunnel_forward
socket_t video_socket;
socket_t control_socket;
struct sc_port_range port_range;
uint16_t local_port; // selected from port_range
bool tunnel_enabled;
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
}; };
struct server_params { struct sc_server_params {
const char *serial;
enum sc_log_level log_level; enum sc_log_level log_level;
const char *crop; const char *crop;
const char *codec_options; const char *codec_options;
const char *encoder_name; const char *encoder_name;
struct sc_port_range port_range; struct sc_port_range port_range;
uint32_t tunnel_host;
uint16_t tunnel_port;
uint16_t max_size; uint16_t max_size;
uint32_t bit_rate; uint32_t bit_rate;
uint16_t max_fps; uint16_t max_fps;
@ -47,27 +40,68 @@ struct server_params {
bool show_touches; bool show_touches;
bool stay_awake; bool stay_awake;
bool force_adb_forward; bool force_adb_forward;
bool power_off_on_close;
bool clipboard_autosync;
bool tcpip;
const char *tcpip_dst;
}; };
// init default values struct sc_server {
bool // The internal allocated strings are copies owned by the server
server_init(struct server *server); struct sc_server_params params;
// push, enable tunnel et start the server sc_thread thread;
bool struct sc_server_info info; // initialized once connected
server_start(struct server *server, const char *serial,
const struct server_params *params);
// block until the communication with the server is established sc_mutex mutex;
sc_cond cond_stopped;
bool stopped;
struct sc_intr intr;
struct sc_adb_tunnel tunnel;
sc_socket video_socket;
sc_socket control_socket;
const struct sc_server_callbacks *cbs;
void *cbs_userdata;
};
struct sc_server_callbacks {
/**
* Called when the server failed to connect
*
* If it is called, then on_connected() and on_disconnected() will never be
* called.
*/
void (*on_connection_failed)(struct sc_server *server, void *userdata);
/**
* Called on server connection
*/
void (*on_connected)(struct sc_server *server, void *userdata);
/**
* Called on server disconnection (after it has been connected)
*/
void (*on_disconnected)(struct sc_server *server, void *userdata);
};
// init the server with the given params
bool bool
server_connect_to(struct server *server); sc_server_init(struct sc_server *server, const struct sc_server_params *params,
const struct sc_server_callbacks *cbs, void *cbs_userdata);
// start the server asynchronously
bool
sc_server_start(struct sc_server *server);
// disconnect and kill the server process // disconnect and kill the server process
void void
server_stop(struct server *server); sc_server_stop(struct sc_server *server);
// close and release sockets // close and release sockets
void void
server_destroy(struct server *server); sc_server_destroy(struct sc_server *server);
#endif #endif

View File

@ -3,9 +3,6 @@
#include <assert.h> #include <assert.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <libavutil/time.h> #include <libavutil/time.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include <unistd.h> #include <unistd.h>
#include "decoder.h" #include "decoder.h"
@ -45,7 +42,7 @@ stream_recv_packet(struct stream *stream, AVPacket *packet) {
assert(len); assert(len);
if (av_new_packet(packet, len)) { if (av_new_packet(packet, len)) {
LOGE("Could not allocate packet"); LOG_OOM();
return false; return false;
} }
@ -60,33 +57,12 @@ stream_recv_packet(struct stream *stream, AVPacket *packet) {
return true; return true;
} }
static void
notify_stopped(void) {
SDL_Event stop_event;
stop_event.type = EVENT_STREAM_STOPPED;
SDL_PushEvent(&stop_event);
}
static bool static bool
process_config_packet(struct stream *stream, AVPacket *packet) { push_packet_to_sinks(struct stream *stream, const AVPacket *packet) {
if (stream->recorder && !recorder_push(stream->recorder, packet)) { for (unsigned i = 0; i < stream->sink_count; ++i) {
LOGE("Could not send config packet to recorder"); struct sc_packet_sink *sink = stream->sinks[i];
return false; if (!sink->ops->push(sink, packet)) {
} LOGE("Could not send config packet to sink %d", i);
return true;
}
static bool
process_frame(struct stream *stream, AVPacket *packet) {
if (stream->decoder && !decoder_push(stream->decoder, packet)) {
return false;
}
if (stream->recorder) {
packet->dts = packet->pts;
if (!recorder_push(stream->recorder, packet)) {
LOGE("Could not send packet to recorder");
return false; return false;
} }
} }
@ -113,9 +89,11 @@ stream_parse(struct stream *stream, AVPacket *packet) {
packet->flags |= AV_PKT_FLAG_KEY; packet->flags |= AV_PKT_FLAG_KEY;
} }
bool ok = process_frame(stream, packet); packet->dts = packet->pts;
bool ok = push_packet_to_sinks(stream, packet);
if (!ok) { if (!ok) {
LOGE("Could not process frame"); LOGE("Could not process packet");
return false; return false;
} }
@ -126,39 +104,44 @@ static bool
stream_push_packet(struct stream *stream, AVPacket *packet) { stream_push_packet(struct stream *stream, AVPacket *packet) {
bool is_config = packet->pts == AV_NOPTS_VALUE; bool is_config = packet->pts == AV_NOPTS_VALUE;
// A config packet must not be decoded immetiately (it contains no // A config packet must not be decoded immediately (it contains no
// frame); instead, it must be concatenated with the future data packet. // frame); instead, it must be concatenated with the future data packet.
if (stream->has_pending || is_config) { if (stream->pending || is_config) {
size_t offset; size_t offset;
if (stream->has_pending) { if (stream->pending) {
offset = stream->pending.size; offset = stream->pending->size;
if (av_grow_packet(&stream->pending, packet->size)) { if (av_grow_packet(stream->pending, packet->size)) {
LOGE("Could not grow packet"); LOG_OOM();
return false; return false;
} }
} else { } else {
offset = 0; offset = 0;
if (av_new_packet(&stream->pending, packet->size)) { stream->pending = av_packet_alloc();
LOGE("Could not create packet"); if (!stream->pending) {
LOG_OOM();
return false;
}
if (av_new_packet(stream->pending, packet->size)) {
LOG_OOM();
av_packet_free(&stream->pending);
return false; return false;
} }
stream->has_pending = true;
} }
memcpy(stream->pending.data + offset, packet->data, packet->size); memcpy(stream->pending->data + offset, packet->data, packet->size);
if (!is_config) { if (!is_config) {
// prepare the concat packet to send to the decoder // prepare the concat packet to send to the decoder
stream->pending.pts = packet->pts; stream->pending->pts = packet->pts;
stream->pending.dts = packet->dts; stream->pending->dts = packet->dts;
stream->pending.flags = packet->flags; stream->pending->flags = packet->flags;
packet = &stream->pending; packet = stream->pending;
} }
} }
if (is_config) { if (is_config) {
// config packet // config packet
bool ok = process_config_packet(stream, packet); bool ok = push_packet_to_sinks(stream, packet);
if (!ok) { if (!ok) {
return false; return false;
} }
@ -166,10 +149,9 @@ stream_push_packet(struct stream *stream, AVPacket *packet) {
// data packet // data packet
bool ok = stream_parse(stream, packet); bool ok = stream_parse(stream, packet);
if (stream->has_pending) { if (stream->pending) {
// the pending packet must be discarded (consumed or error) // the pending packet must be discarded (consumed or error)
stream->has_pending = false; av_packet_free(&stream->pending);
av_packet_unref(&stream->pending);
} }
if (!ok) { if (!ok) {
@ -179,6 +161,33 @@ stream_push_packet(struct stream *stream, AVPacket *packet) {
return true; return true;
} }
static void
stream_close_first_sinks(struct stream *stream, unsigned count) {
while (count) {
struct sc_packet_sink *sink = stream->sinks[--count];
sink->ops->close(sink);
}
}
static inline void
stream_close_sinks(struct stream *stream) {
stream_close_first_sinks(stream, stream->sink_count);
}
static bool
stream_open_sinks(struct stream *stream, const AVCodec *codec) {
for (unsigned i = 0; i < stream->sink_count; ++i) {
struct sc_packet_sink *sink = stream->sinks[i];
if (!sink->ops->open(sink, codec)) {
LOGE("Could not open packet sink %d", i);
stream_close_first_sinks(stream, i);
return false;
}
}
return true;
}
static int static int
run_stream(void *data) { run_stream(void *data) {
struct stream *stream = data; struct stream *stream = data;
@ -191,47 +200,40 @@ run_stream(void *data) {
stream->codec_ctx = avcodec_alloc_context3(codec); stream->codec_ctx = avcodec_alloc_context3(codec);
if (!stream->codec_ctx) { if (!stream->codec_ctx) {
LOGC("Could not allocate codec context"); LOG_OOM();
goto end; goto end;
} }
if (stream->decoder && !decoder_open(stream->decoder, codec)) { if (!stream_open_sinks(stream, codec)) {
LOGE("Could not open decoder"); LOGE("Could not open stream sinks");
goto finally_free_codec_ctx; goto finally_free_codec_ctx;
} }
if (stream->recorder) {
if (!recorder_open(stream->recorder, codec)) {
LOGE("Could not open recorder");
goto finally_close_decoder;
}
if (!recorder_start(stream->recorder)) {
LOGE("Could not start recorder");
goto finally_close_recorder;
}
}
stream->parser = av_parser_init(AV_CODEC_ID_H264); stream->parser = av_parser_init(AV_CODEC_ID_H264);
if (!stream->parser) { if (!stream->parser) {
LOGE("Could not initialize parser"); LOGE("Could not initialize parser");
goto finally_stop_and_join_recorder; goto finally_close_sinks;
} }
// We must only pass complete frames to av_parser_parse2()! // We must only pass complete frames to av_parser_parse2()!
// It's more complicated, but this allows to reduce the latency by 1 frame! // It's more complicated, but this allows to reduce the latency by 1 frame!
stream->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES; stream->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
AVPacket *packet = av_packet_alloc();
if (!packet) {
LOG_OOM();
goto finally_close_parser;
}
for (;;) { for (;;) {
AVPacket packet; bool ok = stream_recv_packet(stream, packet);
bool ok = stream_recv_packet(stream, &packet);
if (!ok) { if (!ok) {
// end of stream // end of stream
break; break;
} }
ok = stream_push_packet(stream, &packet); ok = stream_push_packet(stream, packet);
av_packet_unref(&packet); av_packet_unref(packet);
if (!ok) { if (!ok) {
// cannot process packet (error already logged) // cannot process packet (error already logged)
break; break;
@ -240,61 +242,58 @@ run_stream(void *data) {
LOGD("End of frames"); LOGD("End of frames");
if (stream->has_pending) { if (stream->pending) {
av_packet_unref(&stream->pending); av_packet_free(&stream->pending);
} }
av_packet_free(&packet);
finally_close_parser:
av_parser_close(stream->parser); av_parser_close(stream->parser);
finally_stop_and_join_recorder: finally_close_sinks:
if (stream->recorder) { stream_close_sinks(stream);
recorder_stop(stream->recorder);
LOGI("Finishing recording...");
recorder_join(stream->recorder);
}
finally_close_recorder:
if (stream->recorder) {
recorder_close(stream->recorder);
}
finally_close_decoder:
if (stream->decoder) {
decoder_close(stream->decoder);
}
finally_free_codec_ctx: finally_free_codec_ctx:
avcodec_free_context(&stream->codec_ctx); avcodec_free_context(&stream->codec_ctx);
end: end:
notify_stopped(); stream->cbs->on_eos(stream, stream->cbs_userdata);
return 0; return 0;
} }
void void
stream_init(struct stream *stream, socket_t socket, stream_init(struct stream *stream, sc_socket socket,
struct decoder *decoder, struct recorder *recorder) { const struct stream_callbacks *cbs, void *cbs_userdata) {
stream->socket = socket; stream->socket = socket;
stream->decoder = decoder, stream->pending = NULL;
stream->recorder = recorder; stream->sink_count = 0;
stream->has_pending = false;
assert(cbs && cbs->on_eos);
stream->cbs = cbs;
stream->cbs_userdata = cbs_userdata;
}
void
stream_add_sink(struct stream *stream, struct sc_packet_sink *sink) {
assert(stream->sink_count < STREAM_MAX_SINKS);
assert(sink);
assert(sink->ops);
stream->sinks[stream->sink_count++] = sink;
} }
bool bool
stream_start(struct stream *stream) { stream_start(struct stream *stream) {
LOGD("Starting stream thread"); LOGD("Starting stream thread");
stream->thread = SDL_CreateThread(run_stream, "stream", stream); bool ok =
if (!stream->thread) { sc_thread_create(&stream->thread, run_stream, "scrcpy-stream", stream);
if (!ok) {
LOGC("Could not start stream thread"); LOGC("Could not start stream thread");
return false; return false;
} }
return true; return true;
} }
void
stream_stop(struct stream *stream) {
if (stream->decoder) {
decoder_interrupt(stream->decoder);
}
}
void void
stream_join(struct stream *stream) { stream_join(struct stream *stream) {
SDL_WaitThread(stream->thread, NULL); sc_thread_join(&stream->thread, NULL);
} }

View File

@ -6,36 +6,44 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <SDL2/SDL_atomic.h>
#include <SDL2/SDL_thread.h>
#include "trait/packet_sink.h"
#include "util/net.h" #include "util/net.h"
#include "util/thread.h"
struct video_buffer; #define STREAM_MAX_SINKS 2
struct stream { struct stream {
socket_t socket; sc_socket socket;
SDL_Thread *thread; sc_thread thread;
struct decoder *decoder;
struct recorder *recorder; struct sc_packet_sink *sinks[STREAM_MAX_SINKS];
unsigned sink_count;
AVCodecContext *codec_ctx; AVCodecContext *codec_ctx;
AVCodecParserContext *parser; AVCodecParserContext *parser;
// successive packets may need to be concatenated, until a non-config // successive packets may need to be concatenated, until a non-config
// packet is available // packet is available
bool has_pending; AVPacket *pending;
AVPacket pending;
const struct stream_callbacks *cbs;
void *cbs_userdata;
};
struct stream_callbacks {
void (*on_eos)(struct stream *stream, void *userdata);
}; };
void void
stream_init(struct stream *stream, socket_t socket, stream_init(struct stream *stream, sc_socket socket,
struct decoder *decoder, struct recorder *recorder); const struct stream_callbacks *cbs, void *cbs_userdata);
void
stream_add_sink(struct stream *stream, struct sc_packet_sink *sink);
bool bool
stream_start(struct stream *stream); stream_start(struct stream *stream);
void
stream_stop(struct stream *stream);
void void
stream_join(struct stream *stream); stream_join(struct stream *stream);

81
app/src/sys/unix/file.c Normal file
View File

@ -0,0 +1,81 @@
#include "util/file.h"
#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");
if (!path)
return false;
path = strdup(path);
if (!path)
return false;
bool ret = false;
size_t file_len = strlen(file);
char *saveptr;
for (char *dir = strtok_r(path, ":", &saveptr); dir;
dir = strtok_r(NULL, ":", &saveptr)) {
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);
struct stat sb;
bool fullpath_executable = stat(fullpath, &sb) == 0 &&
sb.st_mode & S_IXUSR;
free(fullpath);
if (fullpath_executable) {
ret = true;
break;
}
}
free(path);
return ret;
}
char *
sc_file_get_executable_path(void) {
// <https://stackoverflow.com/a/1024937/1987178>
#ifdef __linux__
char buf[PATH_MAX + 1]; // +1 for the null byte
ssize_t len = readlink("/proc/self/exe", buf, PATH_MAX);
if (len == -1) {
perror("readlink");
return NULL;
}
buf[len] = '\0';
return strdup(buf);
#else
// in practice, we only need this feature for portable builds, only used on
// Windows, so we don't care implementing it for every platform
// (it's useful to have a working version on Linux for debugging though)
return NULL;
#endif
}
bool
sc_file_is_regular(const char *path) {
struct stat path_stat;
if (stat(path, &path_stat)) {
perror("stat");
return false;
}
return S_ISREG(path_stat.st_mode);
}

View File

@ -1,118 +1,180 @@
#include "util/process.h" #include "util/process.h"
#include <assert.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <limits.h>
#include <signal.h> #include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
#include "util/log.h" #include "util/log.h"
bool enum sc_process_result
search_executable(const char *file) { sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
char *path = getenv("PATH"); int *pin, int *pout, int *perr) {
if (!path) bool inherit_stdout = !pout && !(flags & SC_PROCESS_NO_STDOUT);
return false; bool inherit_stderr = !perr && !(flags & SC_PROCESS_NO_STDERR);
path = strdup(path);
if (!path)
return false;
bool ret = false; int in[2];
size_t file_len = strlen(file); int out[2];
char *saveptr; int err[2];
for (char *dir = strtok_r(path, ":", &saveptr); dir; int internal[2]; // communication between parent and children
dir = strtok_r(NULL, ":", &saveptr)) {
size_t dir_len = strlen(dir);
char *fullpath = malloc(dir_len + file_len + 2);
if (!fullpath)
continue;
memcpy(fullpath, dir, dir_len);
fullpath[dir_len] = '/';
memcpy(fullpath + dir_len + 1, file, file_len + 1);
struct stat sb; if (pipe(internal) == -1) {
bool fullpath_executable = stat(fullpath, &sb) == 0 && perror("pipe");
sb.st_mode & S_IXUSR; return SC_PROCESS_ERROR_GENERIC;
free(fullpath); }
if (fullpath_executable) { if (pin) {
ret = true; if (pipe(in) == -1) {
break; perror("pipe");
close(internal[0]);
close(internal[1]);
return SC_PROCESS_ERROR_GENERIC;
} }
} }
if (pout) {
free(path); if (pipe(out) == -1) {
return ret; perror("pipe");
} // clean up
if (pin) {
enum process_result close(in[0]);
process_execute(const char *const argv[], pid_t *pid) { close(in[1]);
int fd[2]; }
close(internal[0]);
if (pipe(fd) == -1) { close(internal[1]);
perror("pipe"); return SC_PROCESS_ERROR_GENERIC;
return PROCESS_ERROR_GENERIC; }
}
if (perr) {
if (pipe(err) == -1) {
perror("pipe");
// clean up
if (pout) {
close(out[0]);
close(out[1]);
}
if (pin) {
close(in[0]);
close(in[1]);
}
close(internal[0]);
close(internal[1]);
return SC_PROCESS_ERROR_GENERIC;
}
} }
enum process_result ret = PROCESS_SUCCESS;
*pid = fork(); *pid = fork();
if (*pid == -1) { if (*pid == -1) {
perror("fork"); perror("fork");
ret = PROCESS_ERROR_GENERIC; // clean up
goto end; if (perr) {
close(err[0]);
close(err[1]);
}
if (pout) {
close(out[0]);
close(out[1]);
}
if (pin) {
close(in[0]);
close(in[1]);
}
close(internal[0]);
close(internal[1]);
return SC_PROCESS_ERROR_GENERIC;
} }
if (*pid > 0) { if (*pid == 0) {
// parent close write side if (pin) {
close(fd[1]); if (in[0] != STDIN_FILENO) {
fd[1] = -1; dup2(in[0], STDIN_FILENO);
// wait for EOF or receive errno from child close(in[0]);
if (read(fd[0], &ret, sizeof(ret)) == -1) {
perror("read");
ret = PROCESS_ERROR_GENERIC;
goto end;
}
} else if (*pid == 0) {
// child close read side
close(fd[0]);
if (fcntl(fd[1], F_SETFD, FD_CLOEXEC) == 0) {
execvp(argv[0], (char *const *)argv);
if (errno == ENOENT) {
ret = PROCESS_ERROR_MISSING_BINARY;
} else {
ret = PROCESS_ERROR_GENERIC;
} }
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"); perror("exec");
err = errno == ENOENT ? SC_PROCESS_ERROR_MISSING_BINARY
: SC_PROCESS_ERROR_GENERIC;
} else { } else {
perror("fcntl"); perror("fcntl");
ret = PROCESS_ERROR_GENERIC; err = SC_PROCESS_ERROR_GENERIC;
} }
// send ret to the parent // send err to the parent
if (write(fd[1], &ret, sizeof(ret)) == -1) { if (write(internal[1], &err, sizeof(err)) == -1) {
perror("write"); perror("write");
} }
// close write side before exiting close(internal[1]);
close(fd[1]);
_exit(1); _exit(1);
} }
end: // parent
if (fd[0] != -1) { assert(*pid > 0);
close(fd[0]);
close(internal[1]);
enum sc_process_result res = SC_PROCESS_SUCCESS;
// wait for EOF or receive err from child
if (read(internal[0], &res, sizeof(res)) == -1) {
perror("read");
res = SC_PROCESS_ERROR_GENERIC;
} }
if (fd[1] != -1) {
close(fd[1]); close(internal[0]);
if (pin) {
close(in[0]);
*pin = in[1];
} }
return ret; if (pout) {
*pout = out[0];
close(out[1]);
}
if (perr) {
*perr = err[0];
close(err[1]);
}
return res;
} }
bool bool
process_terminate(pid_t pid) { sc_process_terminate(pid_t pid) {
if (pid <= 0) { if (pid <= 0) {
LOGC("Requested to kill %d, this is an error. Please report the bug.\n", LOGC("Requested to kill %d, this is an error. Please report the bug.\n",
(int) pid); (int) pid);
@ -121,8 +183,8 @@ process_terminate(pid_t pid) {
return kill(pid, SIGKILL) != -1; return kill(pid, SIGKILL) != -1;
} }
exit_code_t sc_exit_code
process_wait(pid_t pid, bool close) { sc_process_wait(pid_t pid, bool close) {
int code; int code;
int options = WEXITED; int options = WEXITED;
if (!close) { if (!close) {
@ -133,7 +195,7 @@ process_wait(pid_t pid, bool close) {
int r = waitid(P_PID, pid, &info, options); int r = waitid(P_PID, pid, &info, options);
if (r == -1 || info.si_code != CLD_EXITED) { if (r == -1 || info.si_code != CLD_EXITED) {
// could not wait, or exited unexpectedly, probably by a signal // could not wait, or exited unexpectedly, probably by a signal
code = NO_EXIT_CODE; code = SC_EXIT_CODE_NONE;
} else { } else {
code = info.si_status; code = info.si_status;
} }
@ -141,37 +203,18 @@ process_wait(pid_t pid, bool close) {
} }
void void
process_close(pid_t pid) { sc_process_close(pid_t pid) {
process_wait(pid, true); // ignore exit code sc_process_wait(pid, true); // ignore exit code
} }
char * ssize_t
get_executable_path(void) { sc_pipe_read(int pipe, char *data, size_t len) {
// <https://stackoverflow.com/a/1024937/1987178> return read(pipe, data, len);
#ifdef __linux__ }
char buf[PATH_MAX + 1]; // +1 for the null byte
ssize_t len = readlink("/proc/self/exe", buf, PATH_MAX); void
if (len == -1) { sc_pipe_close(int pipe) {
perror("readlink"); if (close(pipe)) {
return NULL; perror("close pipe");
} }
buf[len] = '\0';
return SDL_strdup(buf);
#else
// in practice, we only need this feature for portable builds, only used on
// Windows, so we don't care implementing it for every platform
// (it's useful to have a working version on Linux for debugging though)
return NULL;
#endif
}
bool
is_regular_file(const char *path) {
struct stat path_stat;
if (stat(path, &path_stat)) {
perror("stat");
return false;
}
return S_ISREG(path_stat.st_mode);
} }

43
app/src/sys/win/file.c Normal file
View File

@ -0,0 +1,43 @@
#include "util/file.h"
#include <windows.h>
#include <sys/stat.h>
#include "util/log.h"
#include "util/str.h"
char *
sc_file_get_executable_path(void) {
HMODULE hModule = GetModuleHandleW(NULL);
if (!hModule) {
return NULL;
}
WCHAR buf[MAX_PATH + 1]; // +1 for the null byte
int len = GetModuleFileNameW(hModule, buf, MAX_PATH);
if (!len) {
return NULL;
}
buf[len] = '\0';
return sc_str_from_wchars(buf);
}
bool
sc_file_is_regular(const char *path) {
wchar_t *wide_path = sc_str_to_wchars(path);
if (!wide_path) {
LOG_OOM();
return false;
}
struct _stat path_stat;
int r = _wstat(wide_path, &path_stat);
free(wide_path);
if (r) {
perror("stat");
return false;
}
return S_ISREG(path_stat.st_mode);
}

View File

@ -1,71 +1,232 @@
#include "util/process.h" #include "util/process.h"
#include <processthreadsapi.h>
#include <assert.h> #include <assert.h>
#include <sys/stat.h>
#include "util/log.h" #include "util/log.h"
#include "util/str_util.h" #include "util/str.h"
static int #define CMD_MAX_LEN 8192
static bool
build_cmd(char *cmd, size_t len, const char *const argv[]) { build_cmd(char *cmd, size_t len, const char *const argv[]) {
// Windows command-line parsing is WTF: // Windows command-line parsing is WTF:
// <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS> // <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS>
// only make it work for this very specific program // only make it work for this very specific program
// (don't handle escaping nor quotes) // (don't handle escaping nor quotes)
size_t ret = xstrjoin(cmd, argv, ' ', len); size_t ret = sc_str_join(cmd, argv, ' ', len);
if (ret >= len) { if (ret >= len) {
LOGE("Command too long (%" PRIsizet " chars)", len - 1); LOGE("Command too long (%" SC_PRIsizet " chars)", len - 1);
return -1; return false;
} }
return 0; return true;
} }
enum process_result enum sc_process_result
process_execute(const char *const argv[], HANDLE *handle) { sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags,
STARTUPINFOW si; HANDLE *pin, HANDLE *pout, HANDLE *perr) {
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;
enum sc_process_result ret = SC_PROCESS_ERROR_GENERIC;
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
HANDLE stdin_read_handle;
HANDLE stdout_write_handle;
HANDLE stderr_write_handle;
if (pin) {
if (!CreatePipe(&stdin_read_handle, pin, &sa, 0)) {
perror("pipe");
return SC_PROCESS_ERROR_GENERIC;
}
if (!SetHandleInformation(*pin, HANDLE_FLAG_INHERIT, 0)) {
LOGE("SetHandleInformation stdin failed");
goto error_close_stdin;
}
}
if (pout) {
if (!CreatePipe(pout, &stdout_write_handle, &sa, 0)) {
perror("pipe");
goto error_close_stdin;
}
if (!SetHandleInformation(*pout, HANDLE_FLAG_INHERIT, 0)) {
LOGE("SetHandleInformation stdout failed");
goto error_close_stdout;
}
}
if (perr) {
if (!CreatePipe(perr, &stderr_write_handle, &sa, 0)) {
perror("pipe");
goto error_close_stdout;
}
if (!SetHandleInformation(*perr, HANDLE_FLAG_INHERIT, 0)) {
LOGE("SetHandleInformation stderr failed");
goto error_close_stderr;
}
}
STARTUPINFOEXW si;
PROCESS_INFORMATION pi; PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si)); memset(&si, 0, sizeof(si));
si.cb = sizeof(si); si.StartupInfo.cb = sizeof(si);
HANDLE handles[3];
char cmd[256]; si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
if (build_cmd(cmd, sizeof(cmd), argv)) { if (inherit_stdout) {
*handle = NULL; si.StartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
return PROCESS_ERROR_GENERIC; }
if (inherit_stderr) {
si.StartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
} }
wchar_t *wide = utf8_to_wide_char(cmd); LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL;
if (!wide) { if (handle_count) {
LOGC("Could not allocate wide char string"); unsigned i = 0;
return PROCESS_ERROR_GENERIC; if (pin) {
} si.StartupInfo.hStdInput = stdin_read_handle;
handles[i++] = si.StartupInfo.hStdInput;
if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, 0, NULL, NULL, &si,
&pi)) {
SDL_free(wide);
*handle = NULL;
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
return PROCESS_ERROR_MISSING_BINARY;
} }
return PROCESS_ERROR_GENERIC; 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;
// Call it once to know the required buffer size
BOOL ok =
InitializeProcThreadAttributeList(NULL, 1, 0, &size)
|| GetLastError() == ERROR_INSUFFICIENT_BUFFER;
if (!ok) {
goto error_close_stderr;
}
lpAttributeList = malloc(size);
if (!lpAttributeList) {
LOG_OOM();
goto error_close_stderr;
}
ok = InitializeProcThreadAttributeList(lpAttributeList, 1, 0, &size);
if (!ok) {
free(lpAttributeList);
goto error_close_stderr;
}
ok = UpdateProcThreadAttribute(lpAttributeList, 0,
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
handles, handle_count * sizeof(HANDLE),
NULL, NULL);
if (!ok) {
goto error_free_attribute_list;
}
si.lpAttributeList = lpAttributeList;
}
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) {
LOG_OOM();
goto error_free_attribute_list;
}
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) {
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;
}
if (lpAttributeList) {
DeleteProcThreadAttributeList(lpAttributeList);
free(lpAttributeList);
}
// These handles are used by the child process, close them for this process
if (pin) {
CloseHandle(stdin_read_handle);
}
if (pout) {
CloseHandle(stdout_write_handle);
}
if (perr) {
CloseHandle(stderr_write_handle);
} }
SDL_free(wide);
*handle = pi.hProcess; *handle = pi.hProcess;
return PROCESS_SUCCESS;
return SC_PROCESS_SUCCESS;
error_free_attribute_list:
if (lpAttributeList) {
DeleteProcThreadAttributeList(lpAttributeList);
free(lpAttributeList);
}
error_close_stderr:
if (perr) {
CloseHandle(*perr);
CloseHandle(stderr_write_handle);
}
error_close_stdout:
if (pout) {
CloseHandle(*pout);
CloseHandle(stdout_write_handle);
}
error_close_stdin:
if (pin) {
CloseHandle(*pin);
CloseHandle(stdin_read_handle);
}
return ret;
} }
bool bool
process_terminate(HANDLE handle) { sc_process_terminate(HANDLE handle) {
return TerminateProcess(handle, 1); return TerminateProcess(handle, 1);
} }
exit_code_t sc_exit_code
process_wait(HANDLE handle, bool close) { sc_process_wait(HANDLE handle, bool close) {
DWORD code; DWORD code;
if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0 if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0
|| !GetExitCodeProcess(handle, &code)) { || !GetExitCodeProcess(handle, &code)) {
// could not wait or retrieve the exit code // could not wait or retrieve the exit code
code = NO_EXIT_CODE; // max value, it's unsigned code = SC_EXIT_CODE_NONE;
} }
if (close) { if (close) {
CloseHandle(handle); CloseHandle(handle);
@ -74,42 +235,24 @@ process_wait(HANDLE handle, bool close) {
} }
void void
process_close(HANDLE handle) { sc_process_close(HANDLE handle) {
bool closed = CloseHandle(handle); bool closed = CloseHandle(handle);
assert(closed); assert(closed);
(void) closed; (void) closed;
} }
char * ssize_t
get_executable_path(void) { sc_pipe_read(HANDLE pipe, char *data, size_t len) {
HMODULE hModule = GetModuleHandleW(NULL); DWORD r;
if (!hModule) { if (!ReadFile(pipe, data, len, &r, NULL)) {
return NULL; return -1;
} }
WCHAR buf[MAX_PATH + 1]; // +1 for the null byte return r;
int len = GetModuleFileNameW(hModule, buf, MAX_PATH);
if (!len) {
return NULL;
}
buf[len] = '\0';
return utf8_from_wide_char(buf);
} }
bool void
is_regular_file(const char *path) { sc_pipe_close(HANDLE pipe) {
wchar_t *wide_path = utf8_to_wide_char(path); if (!CloseHandle(pipe)) {
if (!wide_path) { LOGW("Cannot close pipe");
LOGC("Could not allocate wide char string");
return false;
} }
struct _stat path_stat;
int r = _wstat(wide_path, &path_stat);
SDL_free(wide_path);
if (r) {
perror("stat");
return false;
}
return S_ISREG(path_stat.st_mode);
} }

View File

@ -1,119 +0,0 @@
#include "tiny_xpm.h"
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "util/log.h"
struct index {
char c;
uint32_t color;
};
static bool
find_color(struct index *index, int len, char c, uint32_t *color) {
// there are typically very few color, so it's ok to iterate over the array
for (int i = 0; i < len; ++i) {
if (index[i].c == c) {
*color = index[i].color;
return true;
}
}
*color = 0;
return false;
}
// We encounter some problems with SDL2_image on MSYS2 (Windows),
// so here is our own XPM parsing not to depend on SDL_image.
//
// We do not hardcode the binary image to keep some flexibility to replace the
// icon easily (just by replacing icon.xpm).
//
// Parameter is not "const char *" because XPM formats are generally stored in a
// (non-const) "char *"
SDL_Surface *
read_xpm(char *xpm[]) {
#ifndef NDEBUG
// patch the XPM to change the icon color in debug mode
xpm[2] = ". c #CC00CC";
#endif
char *endptr;
// *** No error handling, assume the XPM source is valid ***
// (it's in our source repo)
// Assertions are only checked in debug
int width = strtol(xpm[0], &endptr, 10);
int height = strtol(endptr + 1, &endptr, 10);
int colors = strtol(endptr + 1, &endptr, 10);
int chars = strtol(endptr + 1, &endptr, 10);
// sanity checks
assert(0 <= width && width < 256);
assert(0 <= height && height < 256);
assert(0 <= colors && colors < 256);
assert(chars == 1); // this implementation does not support more
(void) chars;
// init index
struct index index[colors];
for (int i = 0; i < colors; ++i) {
const char *line = xpm[1+i];
index[i].c = line[0];
assert(line[1] == '\t');
assert(line[2] == 'c');
assert(line[3] == ' ');
if (line[4] == '#') {
index[i].color = 0xff000000 | strtol(&line[5], &endptr, 0x10);
assert(*endptr == '\0');
} else {
assert(!strcmp("None", &line[4]));
index[i].color = 0;
}
}
// parse image
uint32_t *pixels = SDL_malloc(4 * width * height);
if (!pixels) {
LOGE("Could not allocate icon memory");
return NULL;
}
for (int y = 0; y < height; ++y) {
const char *line = xpm[1 + colors + y];
for (int x = 0; x < width; ++x) {
char c = line[x];
uint32_t color;
bool color_found = find_color(index, colors, c, &color);
assert(color_found);
(void) color_found;
pixels[y * width + x] = color;
}
}
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
uint32_t amask = 0x000000ff;
uint32_t rmask = 0x0000ff00;
uint32_t gmask = 0x00ff0000;
uint32_t bmask = 0xff000000;
#else // little endian, like x86
uint32_t amask = 0xff000000;
uint32_t rmask = 0x00ff0000;
uint32_t gmask = 0x0000ff00;
uint32_t bmask = 0x000000ff;
#endif
SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(pixels,
width, height,
32, 4 * width,
rmask, gmask, bmask, amask);
if (!surface) {
LOGE("Could not create icon surface");
return NULL;
}
// make the surface own the raw pixels
surface->flags &= ~SDL_PREALLOC;
return surface;
}

View File

@ -1,11 +0,0 @@
#ifndef TINYXPM_H
#define TINYXPM_H
#include "common.h"
#include <SDL2/SDL.h>
SDL_Surface *
read_xpm(char *xpm[]);
#endif

View File

@ -0,0 +1,26 @@
#ifndef SC_FRAME_SINK_H
#define SC_FRAME_SINK_H
#include "common.h"
#include <assert.h>
#include <stdbool.h>
typedef struct AVFrame AVFrame;
/**
* Frame sink trait.
*
* Component able to receive AVFrames should implement this trait.
*/
struct sc_frame_sink {
const struct sc_frame_sink_ops *ops;
};
struct sc_frame_sink_ops {
bool (*open)(struct sc_frame_sink *sink);
void (*close)(struct sc_frame_sink *sink);
bool (*push)(struct sc_frame_sink *sink, const AVFrame *frame);
};
#endif

View File

@ -0,0 +1,55 @@
#ifndef SC_KEY_PROCESSOR_H
#define SC_KEY_PROCESSOR_H
#include "common.h"
#include <assert.h>
#include <stdbool.h>
#include "input_events.h"
/**
* Key processor trait.
*
* 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 {
/**
* 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 struct sc_text_event *event);
};
#endif

View File

@ -0,0 +1,66 @@
#ifndef SC_MOUSE_PROCESSOR_H
#define SC_MOUSE_PROCESSOR_H
#include "common.h"
#include <assert.h>
#include <stdbool.h>
#include "input_events.h"
/**
* Mouse processor trait.
*
* Component able to process and inject mouse events should implement this
* trait.
*/
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 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 struct sc_touch_event *event);
};
#endif

View File

@ -0,0 +1,27 @@
#ifndef SC_PACKET_SINK_H
#define SC_PACKET_SINK_H
#include "common.h"
#include <assert.h>
#include <stdbool.h>
typedef struct AVCodec AVCodec;
typedef struct AVPacket AVPacket;
/**
* Packet sink trait.
*
* Component able to receive AVPackets should implement this trait.
*/
struct sc_packet_sink {
const struct sc_packet_sink_ops *ops;
};
struct sc_packet_sink_ops {
bool (*open)(struct sc_packet_sink *sink, const AVCodec *codec);
void (*close)(struct sc_packet_sink *sink);
bool (*push)(struct sc_packet_sink *sink, const AVPacket *packet);
};
#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

48
app/src/util/file.c Normal file
View File

@ -0,0 +1,48 @@
#include "file.h"
#include <stdlib.h>
#include <string.h>
#include "util/log.h"
char *
sc_file_get_local_path(const char *name) {
char *executable_path = sc_file_get_executable_path();
if (!executable_path) {
return NULL;
}
// dirname() does not work correctly everywhere, so get the parent
// directory manually.
// See <https://github.com/Genymobile/scrcpy/issues/2619>
char *p = strrchr(executable_path, SC_PATH_SEPARATOR);
if (!p) {
LOGE("Unexpected executable path: \"%s\" (it should contain a '%c')",
executable_path, SC_PATH_SEPARATOR);
free(executable_path);
return NULL;
}
*p = '\0'; // modify executable_path in place
char *dir = executable_path;
size_t dirlen = strlen(dir);
size_t namelen = strlen(name);
size_t len = dirlen + namelen + 2; // +2: '/' and '\0'
char *file_path = malloc(len);
if (!file_path) {
LOG_OOM();
free(executable_path);
return NULL;
}
memcpy(file_path, dir, dirlen);
file_path[dirlen] = SC_PATH_SEPARATOR;
// namelen + 1 to copy the final '\0'
memcpy(&file_path[dirlen + 1], name, namelen + 1);
free(executable_path);
return file_path;
}

49
app/src/util/file.h Normal file
View File

@ -0,0 +1,49 @@
#ifndef SC_FILE_H
#define SC_FILE_H
#include "common.h"
#include <stdbool.h>
#ifdef _WIN32
# define SC_PATH_SEPARATOR '\\'
#else
# define SC_PATH_SEPARATOR '/'
#endif
#ifndef _WIN32
/**
* Indicate if an executable exists using $PATH
*
* In practice, it is only used to know if a package manager is available on
* the system. It is only implemented on Linux.
*/
bool
sc_file_executable_exists(const char *file);
#endif
/**
* Return the absolute path of the executable (the scrcpy binary)
*
* The result must be freed by the caller using free(). It may return NULL on
* error.
*/
char *
sc_file_get_executable_path(void);
/**
* Return the absolute path of a file in the same directory as the executable
*
* The result must be freed by the caller using free(). It may return NULL on
* error.
*/
char *
sc_file_get_local_path(const char *name);
/**
* Indicate if the file exists and is not a directory
*/
bool
sc_file_is_regular(const char *path);
#endif

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

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