Compare commits

...

144 Commits

Author SHA1 Message Date
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
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
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
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
a846c01883 Fix link in README 2021-09-11 21:36:21 +02:00
107 changed files with 6031 additions and 2242 deletions

View File

@ -270,10 +270,10 @@ install` must be run as root)._
#### Option 2: Use prebuilt server #### Option 2: Use prebuilt server
- [`scrcpy-server-v1.19`][direct-scrcpy-server] - [`scrcpy-server-v1.20`][direct-scrcpy-server]
_(SHA-256: 876f9322182e6aac6a58db1334f4225855ef3a17eaebc80aab6601d9d1ecb867)_ _(SHA-256: b20aee4951f99b060c4a44000ba94de973f9604758ef62beb253b371aad3df34)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.19/scrcpy-server-v1.19 [direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.20/scrcpy-server-v1.20
Download the prebuilt server somewhere, and specify its path during the Meson Download the prebuilt server somewhere, and specify its path during the Meson
configuration: configuration:

View File

@ -140,6 +140,24 @@ Potresti anche dover configurare il [comportamento di ridimensionamento][scaling
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723 [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 ### Crash del compositore KWin

23
FAQ.md
View File

@ -222,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
@ -262,6 +283,6 @@ to add some arguments.
This FAQ is available in other languages: This FAQ is available in other languages:
- [Italiano (Italiano, `it`) - v1.17](FAQ.it.md) - [Italiano (Italiano, `it`) - v1.19](FAQ.it.md)
- [한국어 (Korean, `ko`) - v1.11](FAQ.ko.md) - [한국어 (Korean, `ko`) - v1.11](FAQ.ko.md)
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.18](FAQ.zh-Hans.md) - [简体中文 (Simplified Chinese, `zh-Hans`) - v1.18](FAQ.zh-Hans.md)

View File

@ -1,6 +1,6 @@
_Apri il [README](README.md) originale e sempre aggiornato._ _Apri il [README](README.md) originale e sempre aggiornato._
# scrcpy (v1.17) # 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_. 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_. Funziona su _GNU/Linux_, _Windows_ e _macOS_.
@ -205,10 +205,11 @@ Se anche `--max-size` è specificata, il ridimensionamento è applicato dopo il
Per bloccare l'orientamento della trasmissione: Per bloccare l'orientamento della trasmissione:
```bash ```bash
scrcpy --lock-video-orientation 0 # orientamento naturale scrcpy --lock-video-orientation # orientamento iniziale (corrente)
scrcpy --lock-video-orientation 1 # 90° antiorario scrcpy --lock-video-orientation=0 # orientamento naturale
scrcpy --lock-video-orientation 2 # 180° scrcpy --lock-video-orientation=1 # 90° antiorario
scrcpy --lock-video-orientation 3 # 90° orario scrcpy --lock-video-orientation=2 # 180°
scrcpy --lock-video-orientation=3 # 90° orario
``` ```
Questo influisce sull'orientamento della registrazione. Questo influisce sull'orientamento della registrazione.
@ -231,7 +232,9 @@ Per elencare i codificatori disponibili puoi immettere un nome di codificatore n
scrcpy --encoder _ scrcpy --encoder _
``` ```
### Registrazione ### Cattura
#### Registrazione
È possibile registrare lo schermo durante la trasmissione: È possibile registrare lo schermo durante la trasmissione:
@ -253,6 +256,75 @@ I "fotogrammi saltati" sono registrati nonostante non siano mostrati in tempo re
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation [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 ### Connessione
#### Wireless #### Wireless
@ -479,15 +551,6 @@ scrcpy --turn-screen-off --stay-awake
scrcpy -Sw scrcpy -Sw
``` ```
#### Renderizzare i fotogrammi scaduti
Per minimizzare la latenza _scrcpy_ renderizza sempre l'ultimo fotogramma decodificato disponibile in maniera predefinita e tralascia quelli precedenti.
Per forzare la renderizzazione di tutti i fotogrammi (a costo di una possibile latenza superiore), utilizzare:
```bash
scrcpy --render-expired-frames
```
#### Mostrare i tocchi #### Mostrare i tocchi
@ -607,14 +670,14 @@ Non c'è alcuna risposta visiva, un log è stampato nella console.
#### Trasferimento di file verso il dispositivo #### Trasferimento di file verso il dispositivo
Per trasferire un file in `/sdcard/` del dispositivo trascina e rilascia un file (non APK) nella finestra di _scrcpy_. 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. Non c'è alcuna risposta visiva, un log è stampato nella console.
La cartella di destinazione può essere cambiata all'avvio: La cartella di destinazione può essere cambiata all'avvio:
```bash ```bash
scrcpy --push-target=/sdcard/Download/ scrcpy --push-target=/sdcard/Movies/
``` ```
@ -653,10 +716,10 @@ _<kbd>[Super]</kbd> è il pulsante <kbd>Windows</kbd> o <kbd>Cmd</kbd>._
| Rotazione schermo a sinistra | <kbd>MOD</kbd>+<kbd>←</kbd> _(sinistra)_ | Rotazione schermo a sinistra | <kbd>MOD</kbd>+<kbd>←</kbd> _(sinistra)_
| Rotazione schermo a destra | <kbd>MOD</kbd>+<kbd>→</kbd> _(destra)_ | Rotazione schermo a destra | <kbd>MOD</kbd>+<kbd>→</kbd> _(destra)_
| Ridimensiona finestra a 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd> | 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¹_ | 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 `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Click centrale_
| Premi il tasto `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Click destro²_ | Premi il tasto `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Click destro²_
| Premi il tasto `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> | 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 `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_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(su)_
| Premi il tasto `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(giù)_ | Premi il tasto `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(giù)_
@ -665,18 +728,26 @@ _<kbd>[Super]</kbd> è il pulsante <kbd>Windows</kbd> o <kbd>Cmd</kbd>._
| Spegni lo schermo del dispositivo (continua a trasmettere) | <kbd>MOD</kbd>+<kbd>o</kbd> | 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> | 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> | Ruota lo schermo del dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd>
| Espandi il pannello delle notifiche | <kbd>MOD</kbd>+<kbd>n</kbd> | Espandi il pannello delle notifiche | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5° click³_
| Chiudi il pannello delle notifiche | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd> | Espandi il pannello delle impostazioni | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Doppio 5° click³_
| Copia negli appunti³ | <kbd>MOD</kbd>+<kbd>c</kbd> | Chiudi pannelli | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| Taglia negli appunti³ | <kbd>MOD</kbd>+<kbd>x</kbd> | Copia negli appunti | <kbd>MOD</kbd>+<kbd>c</kbd>
| Sincronizza gli appunti e incolla³ | <kbd>MOD</kbd>+<kbd>v</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> | 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> | Abilita/Disabilita il contatore FPS (su stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| Pizzica per zoomare | <kbd>Ctrl</kbd>+_click e trascina_ | Pizzica per zoomare | <kbd>Ctrl</kbd>+_click e trascina_
_¹Doppio click sui bordi neri per rimuoverli._ _¹Doppio click sui bordi neri per rimuoverli._
_²Il tasto destro accende lo schermo se era spento, preme BACK in caso contrario._ _²Il tasto destro accende lo schermo se era spento, preme BACK in caso contrario._
Solo in Android >= 7._ 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. Tutte le scorciatoie <kbd>Ctrl</kbd>+_tasto_ sono inoltrate al dispositivo, così sono gestite dall'applicazione attiva.

View File

@ -1,6 +1,6 @@
_Only the original [README](README.md) is guaranteed to be up-to-date._ _Only the original [README](README.md) is guaranteed to be up-to-date._
# scrcpy (v1.17) # scrcpy (v1.19)
このアプリケーションはUSB(もしくは[TCP/IP経由][article-tcpip])で接続されたAndroidデバイスの表示と制御を提供します。このアプリケーションは _root_ でのアクセスを必要としません。このアプリケーションは _GNU/Linux__Windows_ そして _macOS_ 上で動作します。 このアプリケーションはUSB(もしくは[TCP/IP経由][article-tcpip])で接続されたAndroidデバイスの表示と制御を提供します。このアプリケーションは _root_ でのアクセスを必要としません。このアプリケーションは _GNU/Linux__Windows_ そして _macOS_ 上で動作します。
@ -103,18 +103,21 @@ scoop install adb # まだ入手していない場合
brew install scrcpy brew install scrcpy
``` ```
`PATH`から`adb`へのアクセスが必要です。もしまだ持っていない場合: `PATH`からアクセス可能な`adb`が必要です。もし持っていない場合はインストールしてください。
```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
``` ```
また、[アプリケーションをビルド][BUILD]することも可能です。 `adb`は[MacPorts]からでもインストールできます。
```bash
sudo port install scrcpy
```
[MacPorts]: https://www.macports.org/
また、[アプリケーションをビルド][BUILD]することも可能です。
## 実行 ## 実行
@ -184,10 +187,11 @@ scrcpy --crop 1224:1440:0:0 # オフセット位置(0,0)で1224x1440
ミラーリングの向きをロックするには: ミラーリングの向きをロックするには:
```bash ```bash
scrcpy --lock-video-orientation 0 # 自然な向き scrcpy --lock-video-orientation # 現在の向き
scrcpy --lock-video-orientation 1 # 90°反時計回り scrcpy --lock-video-orientation=0 # 自然な向き
scrcpy --lock-video-orientation 2 # 180° scrcpy --lock-video-orientation=1 # 90°反時計回り
scrcpy --lock-video-orientation 3 # 90°時計回り scrcpy --lock-video-orientation=2 # 180°
scrcpy --lock-video-orientation=3 # 90°時計回り
``` ```
この設定は録画の向きに影響します。 この設定は録画の向きに影響します。
@ -210,7 +214,9 @@ scrcpy --encoder OMX.qcom.video.encoder.avc
scrcpy --encoder _ scrcpy --encoder _
``` ```
### 録画 ### キャプチャ
#### 録画
ミラーリング中に画面の録画をすることが可能です: ミラーリング中に画面の録画をすることが可能です:
@ -233,6 +239,77 @@ scrcpy -Nr file.mkv
[パケット遅延のバリエーション]: https://en.wikipedia.org/wiki/Packet_delay_variation [パケット遅延のバリエーション]: 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
```
### 接続 ### 接続
@ -457,16 +534,6 @@ scrcpy -Sw
``` ```
#### 期限切れフレームをレンダリングする
初期状態では、待ち時間を最小限にするために、_scrcpy_ は最後にデコードされたフレームをレンダリングし、前のフレームを削除します。
全フレームのレンダリングを強制するには(待ち時間が長くなる可能性があります):
```bash
scrcpy --render-expired-frames
```
#### タッチを表示 #### タッチを表示
プレゼンテーションの場合(物理デバイス上で)物理的なタッチを表示すると便利な場合があります。 プレゼンテーションの場合(物理デバイス上で)物理的なタッチを表示すると便利な場合があります。
@ -586,14 +653,14 @@ APKをインストールするには、(`.apk`で終わる)APKファイルを _s
#### デバイスにファイルを送る #### デバイスにファイルを送る
デバイスの`/sdcard/`ディレクトリにファイルを送るには、(APKではない)ファイルを _scrcpy_ の画面にドラッグ&ドロップします。 デバイスの`/sdcard/Download`ディレクトリにファイルを送るには、(APKではない)ファイルを _scrcpy_ の画面にドラッグ&ドロップします。
見た目のフィードバックはありません。コンソールにログが出力されます。 見た目のフィードバックはありません。コンソールにログが出力されます。
転送先ディレクトリを起動時に変更することができます: 転送先ディレクトリを起動時に変更することができます:
```bash ```bash
scrcpy --push-target /sdcard/foo/bar/ scrcpy --push-target=/sdcard/Movies/
``` ```
@ -634,7 +701,7 @@ _<kbd>[Super]</kbd>は通常<kbd>Windows</kbd>もしくは<kbd>Cmd</kbd>キー
| ウィンドウサイズを変更して黒い境界線を削除 | <kbd>MOD</kbd>+<kbd>w</kbd> \| _ダブルクリック¹_ | ウィンドウサイズを変更して黒い境界線を削除 | <kbd>MOD</kbd>+<kbd>w</kbd> \| _ダブルクリック¹_
| `HOME`をクリック | <kbd>MOD</kbd>+<kbd>h</kbd> \| _真ん中クリック_ | `HOME`をクリック | <kbd>MOD</kbd>+<kbd>h</kbd> \| _真ん中クリック_
| `BACK`をクリック | <kbd>MOD</kbd>+<kbd>b</kbd> \| _右クリック²_ | `BACK`をクリック | <kbd>MOD</kbd>+<kbd>b</kbd> \| _右クリック²_
| `APP_SWITCH`をクリック | <kbd>MOD</kbd>+<kbd>s</kbd> | `APP_SWITCH`をクリック | <kbd>MOD</kbd>+<kbd>s</kbd> \| _4クリック³_
| `MENU` (画面のアンロック)をクリック | <kbd>MOD</kbd>+<kbd>m</kbd> | `MENU` (画面のアンロック)をクリック | <kbd>MOD</kbd>+<kbd>m</kbd>
| `VOLUME_UP`をクリック | <kbd>MOD</kbd>+<kbd>↑</kbd> _(上)_ | `VOLUME_UP`をクリック | <kbd>MOD</kbd>+<kbd>↑</kbd> _(上)_
| `VOLUME_DOWN`をクリック | <kbd>MOD</kbd>+<kbd>↓</kbd> _(下)_ | `VOLUME_DOWN`をクリック | <kbd>MOD</kbd>+<kbd>↓</kbd> _(下)_
@ -643,7 +710,8 @@ _<kbd>[Super]</kbd>は通常<kbd>Windows</kbd>もしくは<kbd>Cmd</kbd>キー
| デバイス画面をオフにする(ミラーリングしたまま) | <kbd>MOD</kbd>+<kbd>o</kbd> | デバイス画面をオフにする(ミラーリングしたまま) | <kbd>MOD</kbd>+<kbd>o</kbd>
| デバイス画面をオンにする | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd> | デバイス画面をオンにする | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
| デバイス画面を回転する | <kbd>MOD</kbd>+<kbd>r</kbd> | デバイス画面を回転する | <kbd>MOD</kbd>+<kbd>r</kbd>
| 通知パネルを展開する | <kbd>MOD</kbd>+<kbd>n</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>Shift</kbd>+<kbd>n</kbd>
| クリップボードへのコピー³ | <kbd>MOD</kbd>+<kbd>c</kbd> | クリップボードへのコピー³ | <kbd>MOD</kbd>+<kbd>c</kbd>
| クリップボードへのカット³ | <kbd>MOD</kbd>+<kbd>x</kbd> | クリップボードへのカット³ | <kbd>MOD</kbd>+<kbd>x</kbd>
@ -654,11 +722,17 @@ _<kbd>[Super]</kbd>は通常<kbd>Windows</kbd>もしくは<kbd>Cmd</kbd>キー
_¹黒い境界線を削除するため、境界線上でダブルクリック_ _¹黒い境界線を削除するため、境界線上でダブルクリック_
_²もしスクリーンがオフの場合、右クリックでスクリーンをオンする。それ以外の場合はBackを押します._ _²もしスクリーンがオフの場合、右クリックでスクリーンをオンする。それ以外の場合はBackを押します._
Android 7以上のみ._ 4と5はマウスのボタンです、もしあなたのマウスにボタンがあれば使えます._
_⁴Android 7以上のみ._
キーを繰り返すショートカットはキーを離して2回目を押したら実行されます。例えば「設定パネルを展開する」を実行する場合は以下のように操作する。
1. <kbd>MOD</kbd> キーを押し、押したままにする.
2. その後に <kbd>n</kbd>キーを2回押す.
3. 最後に <kbd>MOD</kbd>キーを離す.
全ての<kbd>Ctrl</kbd>+_キー_ ショートカットはデバイスに転送されます、そのためアクティブなアプリケーションによって処理されます。 全ての<kbd>Ctrl</kbd>+_キー_ ショートカットはデバイスに転送されます、そのためアクティブなアプリケーションによって処理されます。
## カスタムパス ## カスタムパス
特定の _adb_ バイナリを使用する場合、そのパスを環境変数`ADB`で構成します: 特定の _adb_ バイナリを使用する場合、そのパスを環境変数`ADB`で構成します:

159
README.md
View File

@ -1,26 +1,37 @@
# scrcpy (v1.19) # scrcpy (v1.20)
![icon](data/icon.png) <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
@ -90,10 +101,10 @@ process][BUILD_simple]).
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.19.zip`][direct-win64] - [`scrcpy-win64-v1.20.zip`][direct-win64]
_(SHA-256: 383d6483f25ac0092d4bb9fef6c967351ecd50fc248e0c82932db97d6d32f11b)_ _(SHA-256: 548532b616288bcaeceff6881ad5e6f0928e5ae2b48c380385f03627401cfdba)_
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.19/scrcpy-win64-v1.19.zip [direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.20/scrcpy-win64-v1.20.zip
It is also available in [Chocolatey]: It is also available in [Chocolatey]:
@ -326,7 +337,9 @@ For example, you could capture the video within [OBS].
#### Buffering #### Buffering
It is possible to add buffering. This increases latency but reduces jitter (see It is possible to add buffering. This increases latency but reduces jitter (see
#2464). [#2464]).
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464
The option is available for display buffering: The option is available for display buffering:
@ -343,10 +356,38 @@ 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.
##### 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 the device TCP/IP mode is disabled (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
@ -399,21 +440,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
``` ```
@ -421,14 +507,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
``` ```
@ -582,6 +670,14 @@ scrcpy --turn-screen-off --stay-awake
scrcpy -Sw scrcpy -Sw
``` ```
#### Power off on close
To turn the device screen off when closing scrcpy:
```bash
scrcpy --power-off-on-close
```
#### Show touches #### Show touches
@ -662,6 +758,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_.
@ -678,7 +777,7 @@ a location inverted through the center of the screen.
By default, scrcpy uses Android key or text injection: it works everywhere, but By default, scrcpy uses Android key or text injection: it works everywhere, but
is limited to ASCII. is limited to ASCII.
On Linux, scrcpy can simulate a USB physical keyboard on Android to provide a 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 better input experience (using [USB HID over AOAv2][hid-aoav2]): the virtual
keyboard is disabled and it works for all characters and IME. keyboard is disabled and it works for all characters and IME.
@ -830,7 +929,7 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
| 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> \| _5th-click³_ | Expand notification panel | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5th-click³_
| Expand settings panel | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Double-5th-click³_ | Expand settings panel | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Double-5th-click³_
| Collapse panels | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd> | Collapse panels | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| Copy to clipboard⁴ | <kbd>MOD</kbd>+<kbd>c</kbd> | Copy to clipboard⁴ | <kbd>MOD</kbd>+<kbd>c</kbd>
| Cut to clipboard⁴ | <kbd>MOD</kbd>+<kbd>x</kbd> | Cut to clipboard⁴ | <kbd>MOD</kbd>+<kbd>x</kbd>
@ -838,6 +937,8 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
| 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._
@ -927,10 +1028,10 @@ 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.17](README.it.md) - [Italiano (Italiano, `it`) - v1.19](README.it.md)
- [日本語 (Japanese, `jp`) - v1.17](README.jp.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.17](README.pt-br.md) - [Português Brasileiro (Brazilian Portuguese, `pt-BR`) - v1.19](README.pt-br.md)
- [Español (Spanish, `sp`) - v1.17](README.sp.md) - [Español (Spanish, `sp`) - v1.17](README.sp.md)
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.17](README.zh-Hans.md) - [简体中文 (Simplified Chinese, `zh-Hans`) - v1.17](README.zh-Hans.md)
- [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md) - [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md)

View File

@ -1,6 +1,6 @@
_Apenas o [README](README.md) original é garantido estar atualizado._ _Apenas o [README](README.md) original é garantido estar atualizado._
# scrcpy (v1.17) # scrcpy (v1.19)
Esta aplicação fornece exibiçã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_.
@ -38,6 +38,18 @@ controlá-lo usando teclado e 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>
### 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 (_testing_ e _sid_ por enquanto) e Ubuntu (20.04): No Debian (_testing_ e _sid_ por enquanto) e Ubuntu (20.04):
@ -67,9 +79,7 @@ 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] (não se preocupe, não é tão Você também pode [compilar o app manualmente][BUILD] ([processo simplificado][BUILD_simple]).
difícil).
### Windows ### Windows
@ -113,13 +123,18 @@ brew install scrcpy
Você precisa do `adb`, acessível pelo 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
# 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
``` ```
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]. Você também pode [compilar o app manualmente][BUILD].
@ -195,10 +210,11 @@ Se `--max-size` também for especificado, o redimensionamento é aplicado após
Para travar a orientação do espelhamento: Para travar a orientação do espelhamento:
```bash ```bash
scrcpy --lock-video-orientation 0 # orientação natural scrcpy --lock-video-orientation # orientação inicial (Atual)
scrcpy --lock-video-orientation 1 # 90° sentido anti-horário scrcpy --lock-video-orientation=0 # orientação natural
scrcpy --lock-video-orientation 2 # 180° scrcpy --lock-video-orientation=1 # 90° sentido anti-horário
scrcpy --lock-video-orientation 3 # 90° sentido 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. Isso afeta a orientação de gravação.
@ -222,7 +238,9 @@ erro dará os encoders disponíveis:
scrcpy --encoder _ scrcpy --encoder _
``` ```
### Gravando ### Captura
#### Gravando
É possível gravar a tela enquanto ocorre o espelhamento: É possível gravar a tela enquanto ocorre o espelhamento:
@ -246,6 +264,79 @@ pacotes][packet delay variation] não impacta o arquivo gravado.
[packet delay variation]: 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
#### Sem fio #### Sem fio
@ -488,18 +579,6 @@ scrcpy -Sw
``` ```
#### Renderizar frames expirados
Por padrão, para minimizar a latência, _scrcpy_ sempre renderiza o último frame decodificado
disponível, e descarta o anterior.
Para forçar a renderização de todos os frames (com o custo de um possível aumento de
latência), use:
```bash
scrcpy --render-expired-frames
```
#### Mostrar toques #### Mostrar toques
Para apresentações, pode ser útil mostrar toques físicos (no dispositivo Para apresentações, pode ser útil mostrar toques físicos (no dispositivo
@ -647,7 +726,7 @@ Não existe feedback visual, um log é imprimido no console.
#### Enviar arquivo para dispositivo #### Enviar arquivo para dispositivo
Para enviar um arquivo para `/sdcard/` no dispositivo, arraste e solte um arquivo (não-APK) para a Para enviar um arquivo para `/sdcard/Download/` no dispositivo, arraste e solte um arquivo (não-APK) para a
janela do _scrcpy_. janela do _scrcpy_.
Não existe feedback visual, um log é imprimido no console. Não existe feedback visual, um log é imprimido no console.
@ -694,12 +773,12 @@ _<kbd>[Super]</kbd> é tipicamente a tecla <kbd>Windows</kbd> ou <kbd>Cmd</kbd>.
| Mudar modo de tela cheia | <kbd>MOD</kbd>+<kbd>f</kbd> | Mudar modo de tela cheia | <kbd>MOD</kbd>+<kbd>f</kbd>
| Rotacionar display para esquerda | <kbd>MOD</kbd>+<kbd>←</kbd> _(esquerda)_ | Rotacionar display para esquerda | <kbd>MOD</kbd>+<kbd>←</kbd> _(esquerda)_
| Rotacionar display para direita | <kbd>MOD</kbd>+<kbd>→</kbd> _(direita)_ | Rotacionar display para direita | <kbd>MOD</kbd>+<kbd>→</kbd> _(direita)_
| Redimensionar janela para 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd> | 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¹_ | 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 `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Clique-do-meio_
| Clicar em `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Clique-direito²_ | Clicar em `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Clique-direito²_
| Clicar em `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> | 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 `MENU` (desbloquear tela) | <kbd>MOD</kbd>+<kbd>m</kbd>
| Clicar em `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(cima)_ | Clicar em `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(cima)_
| Clicar em `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(baixo)_ | Clicar em `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(baixo)_
| Clicar em `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd> | Clicar em `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd>
@ -707,18 +786,27 @@ _<kbd>[Super]</kbd> é tipicamente a tecla <kbd>Windows</kbd> ou <kbd>Cmd</kbd>.
| Desligar tela do dispositivo (continuar espelhando) | <kbd>MOD</kbd>+<kbd>o</kbd> | 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> | Ligar tela do dispositivo | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
| Rotacionar tela do dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd> | Rotacionar tela do dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd>
| Expandir painel de notificação | <kbd>MOD</kbd>+<kbd>n</kbd> | Expandir painel de notificação | <kbd>MOD</kbd>+<kbd>n</kbd> \| _Clique-do-5.°³_
| Colapsar painel de notificação | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd> | Expandir painel de configurção | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Clique-duplo-do-5.°³_
| Copiar para área de transferência³ | <kbd>MOD</kbd>+<kbd>c</kbd> | Colapsar paineis | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| Recortar para área de transferência³ | <kbd>MOD</kbd>+<kbd>x</kbd> | Copiar para área de transferência | <kbd>MOD</kbd>+<kbd>c</kbd>
| Sincronizar áreas de transferência e colar³ | <kbd>MOD</kbd>+<kbd>v</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> | 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> | Ativar/desativar contador de FPS (em stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| Pinçar para dar zoom | <kbd>Ctrl</kbd>+_clicar-e-mover_ | Pinçar para dar zoom | <kbd>Ctrl</kbd>+_Clicar-e-mover_
_¹Clique-duplo em bordas pretas para removê-las._ _¹Clique-duplo-esquerdo na borda preta para remove-la._
_²Clique-direito liga a tela se ela estiver desligada, pressiona BACK caso contrário._ _²Clique-direito liga a tela caso esteja desligada, pressione BACK caso contrário._
Apenas em Android >= 7._ 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 Todos os atalhos <kbd>Ctrl</kbd>+_tecla_ são encaminhados para o dispositivo, para que eles sejam
tratados pela aplicação ativa. tratados pela aplicação ativa.
@ -729,7 +817,9 @@ tratados pela aplicação ativa.
Para usar um binário _adb_ específico, configure seu caminho na variável de ambiente Para usar um binário _adb_ específico, configure seu caminho na variável de ambiente
`ADB`: `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`.
@ -751,8 +841,6 @@ Um colega me desafiou a encontrar um nome tão impronunciável quanto [gnirehtet
Veja [BUILD]. Veja [BUILD].
[BUILD]: BUILD.md
## Problemas comuns ## Problemas comuns

View File

@ -1,6 +1,8 @@
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/clock.c',
'src/compat.c', 'src/compat.c',
@ -24,18 +26,42 @@ src = [
'src/server.c', 'src/server.c',
'src/stream.c', 'src/stream.c',
'src/video_buffer.c', 'src/video_buffer.c',
'src/util/acksync.c',
'src/util/file.c',
'src/util/intr.c',
'src/util/log.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/thread.c',
'src/util/tick.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' ] src += [
'src/sys/win/file.c',
'src/sys/win/process.c',
]
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 endif
v4l2_support = host_machine.system() == 'linux' v4l2_support = host_machine.system() == 'linux'
@ -51,10 +77,6 @@ if aoa_hid_support
] ]
endif endif
check_functions = [
'strdup'
]
cc = meson.get_compiler('c') cc = meson.get_compiler('c')
if not get_option('crossbuild_windows') if not get_option('crossbuild_windows')
@ -115,7 +137,11 @@ 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',
]
foreach f : check_functions foreach f : check_functions
if cc.has_function(f) if cc.has_function(f)
@ -124,6 +150,9 @@ foreach f : check_functions
endif endif
endforeach 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())
@ -176,8 +205,14 @@ install_data('../data/icon.png',
# 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',
@ -186,7 +221,10 @@ if get_option('buildtype') == 'debug'
'tests/test_cli.c', 'tests/test_cli.c',
'src/cli.c', 'src/cli.c',
'src/options.c', 'src/options.c',
'src/util/str_util.c', 'src/util/net.c',
'src/util/str.c',
'src/util/strbuf.c',
'src/util/term.c',
]], ]],
['test_clock', [ ['test_clock', [
'tests/test_clock.c', 'tests/test_clock.c',
@ -195,7 +233,8 @@ if get_option('buildtype') == 'debug'
['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',
@ -204,9 +243,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

@ -114,6 +114,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).
@ -136,6 +142,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.
@ -189,6 +199,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.
@ -199,6 +217,18 @@ 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
.BI "\-\-tunnel\-host " ip
Set the IP address of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward.
Default is localhost.
.TP
.BI "\-\-tunnel\-port " port
Set the TCP port of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward.
Default is 0 (not forced): the local port used for establishing the tunnel will be used.
.TP .TP
.BI "\-\-v4l2-sink " /dev/videoN .BI "\-\-v4l2-sink " /dev/videoN
Output to v4l2loopback device. Output to v4l2loopback device.
@ -364,6 +394,10 @@ 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

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,27 +83,28 @@ 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[]) {
#define MAX_COMMAND_STRING_LEN 1024 #define MAX_COMMAND_STRING_LEN 1024
char *buf = malloc(MAX_COMMAND_STRING_LEN); char *buf = malloc(MAX_COMMAND_STRING_LEN);
if (!buf) { if (!buf) {
LOGE("Failed to execute (could not allocate error message)"); LOG_OOM();
LOGE("Failed to execute");
return; return;
} }
switch (err) { switch (err) {
case PROCESS_ERROR_GENERIC: case SC_PROCESS_ERROR_GENERIC:
argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN); 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, MAX_COMMAND_STRING_LEN); 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;
} }
@ -108,19 +112,61 @@ show_adb_err_msg(enum process_result err, const char *const argv[]) {
free(buf); free(buf);
} }
process_t static bool
adb_execute_redirect(const char *serial, const char *const adb_cmd[], process_check_success_internal(sc_pid pid, const char *name, bool close,
size_t len, pipe_t *pipe_stdin, pipe_t *pipe_stdout, unsigned flags) {
pipe_t *pipe_stderr) { bool log_errors = !(flags & SC_ADB_NO_LOGERR);
int i;
process_t process;
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)); const char **argv = malloc((len + 4) * sizeof(*argv));
if (!argv) { if (!argv) {
return PROCESS_NONE; LOG_OOM();
return NULL;
} }
argv[0] = get_adb_command(); argv[0] = get_adb_command();
int i;
if (serial) { if (serial) {
argv[1] = "-s"; argv[1] = "-s";
argv[2] = serial; argv[2] = serial;
@ -131,149 +177,295 @@ adb_execute_redirect(const char *serial, const char *const adb_cmd[],
memcpy(&argv[i], adb_cmd, len * sizeof(const char *)); memcpy(&argv[i], adb_cmd, len * sizeof(const char *));
argv[len + i] = NULL; argv[len + i] = NULL;
enum process_result r = return argv;
process_execute_redirect(argv, &process, pipe_stdin, pipe_stdout, }
pipe_stderr);
if (r != PROCESS_SUCCESS) { static sc_pid
adb_execute_p(const char *serial, const char *const adb_cmd[], size_t len,
unsigned flags, sc_pipe *pout) {
const char **argv = adb_create_argv(serial, adb_cmd, len);
if (!argv) {
return SC_PROCESS_NONE;
}
unsigned process_flags = 0;
if (flags & SC_ADB_NO_STDOUT) {
process_flags |= SC_PROCESS_NO_STDOUT;
}
if (flags & SC_ADB_NO_STDERR) {
process_flags |= SC_PROCESS_NO_STDERR;
}
sc_pid pid;
enum sc_process_result r =
sc_process_execute_p(argv, &pid, 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); show_adb_err_msg(r, argv);
process = PROCESS_NONE; pid = SC_PROCESS_NONE;
} }
free(argv); free(argv);
return process; return pid;
} }
process_t sc_pid
adb_execute(const char *serial, const char *const adb_cmd[], size_t len) { adb_execute(const char *serial, const char *const adb_cmd[], size_t len,
return adb_execute_redirect(serial, adb_cmd, len, NULL, NULL, NULL); unsigned flags) {
return adb_execute_p(serial, adb_cmd, len, flags, NULL);
} }
process_t bool
adb_forward(const char *serial, uint16_t local_port, adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
const char *device_socket_name) { const char *device_socket_name, unsigned flags) {
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) {
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__
free((void *) remote); free((void *) remote);
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__
free((void *) local); free((void *) local);
#endif #endif
return proc; return process_check_success_intr(intr, pid, "adb install", flags);
} }
static ssize_t bool
adb_execute_for_output(const char *serial, const char *const adb_cmd[], adb_tcpip(struct sc_intr *intr, const char *serial, uint16_t port,
size_t adb_cmd_len, char *buf, size_t buf_len, unsigned flags) {
const char *name) { char port_string[5 + 1];
pipe_t pipe_stdout; sprintf(port_string, "%" PRIu16, port);
process_t proc = adb_execute_redirect(serial, adb_cmd, adb_cmd_len, NULL, const char *const adb_cmd[] = {"tcpip", port_string};
&pipe_stdout, NULL);
ssize_t r = read_pipe_all(pipe_stdout, buf, buf_len); sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags);
close_pipe(pipe_stdout); return process_check_success_intr(intr, pid, "adb tcpip", flags);
if (!process_check_success(proc, name, true)) {
return -1;
}
return r;
} }
static size_t bool
truncate_first_line(char *data, size_t len) { adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags) {
data[len - 1] = '\0'; const char *const adb_cmd[] = {"connect", ip_port};
char *eol = strpbrk(data, "\r\n");
if (eol) { sc_pipe pout;
*eol = '\0'; sc_pid pid = adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), flags, &pout);
len = eol - data; if (pid == SC_PROCESS_NONE) {
LOGE("Could not execute \"adb connect\"");
return false;
} }
return len;
// "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 * char *
adb_get_serialno(void) { adb_getprop(struct sc_intr *intr, const char *serial, const char *prop,
char buf[128]; unsigned flags) {
const char *const adb_cmd[] = {"shell", "getprop", prop};
const char *const adb_cmd[] = {"get-serialno"}; sc_pipe pout;
ssize_t r = adb_execute_for_output(NULL, adb_cmd, ARRAY_LEN(adb_cmd), sc_pid pid =
buf, sizeof(buf), "get-serialno"); adb_execute_p(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags, &pout);
if (r <= 0) { if (pid == SC_PROCESS_NONE) {
LOGE("Could not execute \"adb getprop\"");
return NULL; return NULL;
} }
truncate_first_line(buf, r); 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); 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,38 +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_execute_redirect(const char *serial, const char *const adb_cmd[],
size_t len, pipe_t *pipe_stdin, pipe_t *pipe_stdout,
pipe_t *pipe_stderr);
process_t sc_pid
adb_forward(const char *serial, uint16_t local_port, adb_execute(const char *serial, const char *const adb_cmd[], size_t len,
const char *device_socket_name); unsigned flags);
process_t bool
adb_forward_remove(const char *serial, uint16_t local_port); adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
const char *device_socket_name, unsigned flags);
process_t bool
adb_reverse(const char *serial, const char *device_socket_name, adb_forward_remove(struct sc_intr *intr, const char *serial,
uint16_t local_port); uint16_t local_port, unsigned flags);
process_t bool
adb_reverse_remove(const char *serial, const char *device_socket_name); adb_reverse(struct sc_intr *intr, const char *serial,
const char *device_socket_name, uint16_t local_port,
unsigned flags);
process_t bool
adb_push(const char *serial, const char *local, const char *remote); adb_reverse_remove(struct sc_intr *intr, const char *serial,
const char *device_socket_name, unsigned flags);
process_t bool
adb_install(const char *serial, const char *local); adb_push(struct sc_intr *intr, const char *serial, const char *local,
const char *remote, unsigned flags);
// Return the result of "adb get-serialno". 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 * char *
adb_get_serialno(void); 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

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,16 +1,17 @@
#ifndef COMPAT_H #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 <libavformat/version.h> #include <libavformat/version.h>
#include <SDL2/SDL_version.h> #include <SDL2/SDL_version.h>
#ifndef __WIN32
# define PRIu64_ PRIu64
#else
# define PRIu64_ "I64u" // Windows...
#endif
// In ffmpeg/doc/APIchanges: // In ffmpeg/doc/APIchanges:
// 2018-02-06 - 0694d87024 - lavf 58.9.100 - avformat.h // 2018-02-06 - 0694d87024 - lavf 58.9.100 - avformat.h
// Deprecate use of av_register_input_format(), av_register_output_format(), // Deprecate use of av_register_input_format(), av_register_output_format(),
@ -57,4 +58,12 @@
char *strdup(const char *s); char *strdup(const char *s);
#endif #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

@ -7,7 +7,7 @@
#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 * Map an enum value to a string based on an array, without crashing on an
@ -66,7 +66,7 @@ write_position(uint8_t *buf, const struct sc_position *position) {
// write length (2 bytes) + string (non nul-terminated) // write length (2 bytes) + string (non nul-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;
@ -118,11 +118,12 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
buf[1] = msg->inject_keycode.action; buf[1] = msg->inject_keycode.action;
return 2; 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;
@ -170,11 +171,6 @@ control_msg_log(const struct control_msg *msg) {
(long) msg->inject_touch_event.buttons); (long) msg->inject_touch_event.buttons);
} else { } else {
// numeric pointer id // numeric pointer id
#ifndef __WIN32
# define PRIu64_ PRIu64
#else
# define PRIu64_ "I64u" // Windows...
#endif
LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%" LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%"
PRIi32 " pressure=%g buttons=%06lx", PRIi32 " pressure=%g buttons=%06lx",
id, id,
@ -199,7 +195,8 @@ control_msg_log(const struct control_msg *msg) {
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action)); KEYEVENT_ACTION_LABEL(msg->inject_keycode.action));
break; break;
case CONTROL_MSG_TYPE_SET_CLIPBOARD: case CONTROL_MSG_TYPE_SET_CLIPBOARD:
LOG_CMSG("clipboard %s \"%s\"", LOG_CMSG("clipboard %" PRIu64_ " %s \"%s\"",
msg->set_clipboard.sequence,
msg->set_clipboard.paste ? "paste" : "copy", msg->set_clipboard.paste ? "paste" : "copy",
msg->set_clipboard.text); msg->set_clipboard.text);
break; break;

View File

@ -70,6 +70,7 @@ struct control_msg {
// screen may only be turned on on ACTION_DOWN // screen may only be turned on on ACTION_DOWN
} back_or_screen_on; } back_or_screen_on;
struct { struct {
uint64_t sequence;
char *text; // owned, to be freed by free() char *text; // owned, to be freed by free()
bool paste; bool paste;
} set_clipboard; } set_clipboard;

View File

@ -5,10 +5,11 @@
#include "util/log.h" #include "util/log.h"
bool bool
controller_init(struct controller *controller, sc_socket control_socket) { controller_init(struct controller *controller, sc_socket control_socket,
struct sc_acksync *acksync) {
cbuf_init(&controller->queue); cbuf_init(&controller->queue);
bool ok = receiver_init(&controller->receiver, control_socket); bool ok = receiver_init(&controller->receiver, control_socket, acksync);
if (!ok) { if (!ok) {
return false; return false;
} }

View File

@ -7,6 +7,7 @@
#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" #include "util/thread.h"
@ -24,7 +25,8 @@ struct controller {
}; };
bool bool
controller_init(struct controller *controller, sc_socket 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

@ -42,7 +42,7 @@ 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;
} }
@ -54,7 +54,7 @@ decoder_open(struct decoder *decoder, const AVCodec *codec) {
decoder->frame = av_frame_alloc(); decoder->frame = av_frame_alloc();
if (!decoder->frame) { if (!decoder->frame) {
LOGE("Could not create decoder frame"); LOG_OOM();
avcodec_close(decoder->codec_ctx); avcodec_close(decoder->codec_ctx);
avcodec_free_context(&decoder->codec_ctx); avcodec_free_context(&decoder->codec_ctx);
return false; return false;

View File

@ -1,5 +1,6 @@
#include "device_msg.h" #include "device_msg.h"
#include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -23,7 +24,7 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
} }
char *text = 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) {
@ -34,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

View File

@ -13,6 +13,7 @@
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 {
@ -21,6 +22,9 @@ struct device_msg {
struct { struct {
char *text; // owned, to be freed by free() char *text; // owned, to be freed by free()
} clipboard; } clipboard;
struct {
uint64_t sequence;
} ack_clipboard;
}; };
}; };

View File

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

View File

@ -5,6 +5,7 @@
#include "adb.h" #include "adb.h"
#include "util/log.h" #include "util/log.h"
#include "util/process_intr.h"
#define DEFAULT_PUSH_TARGET "/sdcard/Download/" #define DEFAULT_PUSH_TARGET "/sdcard/Download/"
@ -16,6 +17,7 @@ file_handler_request_destroy(struct file_handler_request *req) {
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);
@ -30,23 +32,26 @@ file_handler_init(struct file_handler *file_handler, const char *serial,
return false; return false;
} }
if (serial) { ok = sc_intr_init(&file_handler->intr);
file_handler->serial = 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);
sc_cond_destroy(&file_handler->event_cond); return false;
sc_mutex_destroy(&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;
@ -57,6 +62,7 @@ void
file_handler_destroy(struct file_handler *file_handler) { file_handler_destroy(struct file_handler *file_handler) {
sc_cond_destroy(&file_handler->event_cond); sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex); sc_mutex_destroy(&file_handler->mutex);
sc_intr_destroy(&file_handler->intr);
free(file_handler->serial); free(file_handler->serial);
struct file_handler_request req; struct file_handler_request req;
@ -65,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) {
@ -106,10 +102,16 @@ file_handler_request(struct file_handler *file_handler,
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 (;;) {
sc_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)) {
sc_cond_wait(&file_handler->event_cond, &file_handler->mutex); sc_cond_wait(&file_handler->event_cond, &file_handler->mutex);
} }
@ -122,43 +124,26 @@ run_file_handler(void *data) {
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;
process_t process;
if (req.action == ACTION_INSTALL_APK) {
LOGI("Installing %s...", req.file);
process = install_apk(file_handler->serial, req.file);
} else {
LOGI("Pushing %s...", req.file);
process = push_file(file_handler->serial, req.file,
file_handler->push_target);
}
file_handler->current_process = process;
sc_mutex_unlock(&file_handler->mutex); sc_mutex_unlock(&file_handler->mutex);
if (req.action == ACTION_INSTALL_APK) { if (req.action == ACTION_INSTALL_APK) {
if (process_check_success(process, "adb install", false)) { LOGI("Installing %s...", req.file);
bool ok = adb_install(intr, serial, req.file, 0);
if (ok) {
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);
} }
} }
sc_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;
sc_mutex_unlock(&file_handler->mutex);
file_handler_request_destroy(&req); file_handler_request_destroy(&req);
} }
return 0; return 0;
@ -183,11 +168,7 @@ file_handler_stop(struct file_handler *file_handler) {
sc_mutex_lock(&file_handler->mutex); sc_mutex_lock(&file_handler->mutex);
file_handler->stopped = true; file_handler->stopped = true;
sc_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)) {
LOGW("Could not terminate push/install process");
}
}
sc_mutex_unlock(&file_handler->mutex); sc_mutex_unlock(&file_handler->mutex);
} }

View File

@ -8,6 +8,7 @@
#include "adb.h" #include "adb.h"
#include "util/cbuf.h" #include "util/cbuf.h"
#include "util/thread.h" #include "util/thread.h"
#include "util/intr.h"
typedef enum { typedef enum {
ACTION_INSTALL_APK, ACTION_INSTALL_APK,
@ -29,8 +30,9 @@ struct file_handler {
sc_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

View File

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

View File

@ -160,6 +160,7 @@ static bool
sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) { sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) {
unsigned char *buffer = malloc(HID_KEYBOARD_EVENT_SIZE); unsigned char *buffer = malloc(HID_KEYBOARD_EVENT_SIZE);
if (!buffer) { if (!buffer) {
LOG_OOM();
return false; return false;
} }
@ -278,7 +279,8 @@ push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t sdl_mod) {
static void static void
sc_key_processor_process_key(struct sc_key_processor *kp, sc_key_processor_process_key(struct sc_key_processor *kp,
const SDL_KeyboardEvent *event) { const SDL_KeyboardEvent *event,
uint64_t ack_to_wait) {
if (event->repeat) { if (event->repeat) {
// In USB HID protocol, key repeat is handled by the host (Android), so // In USB HID protocol, key repeat is handled by the host (Android), so
// just ignore key repeat here. // just ignore key repeat here.
@ -298,16 +300,12 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
} }
} }
SDL_Keycode keycode = event->keysym.sym; if (ack_to_wait) {
bool down = event->type == SDL_KEYDOWN;
bool ctrl = event->keysym.mod & KMOD_CTRL;
bool shift = event->keysym.mod & KMOD_SHIFT;
if (ctrl && !shift && keycode == SDLK_v && down) {
// Ctrl+v is pressed, so clipboard synchronization has been // Ctrl+v is pressed, so clipboard synchronization has been
// requested. Wait a bit so that the clipboard is set before // requested. Wait until clipboard synchronization is acknowledged
// injecting Ctrl+v via HID, otherwise it would paste the old // by the server, otherwise it could paste the old clipboard
// clipboard content. // content.
hid_event.delay = SC_TICK_FROM_MS(2); hid_event.ack_to_wait = ack_to_wait;
} }
if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) { if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) {
@ -348,6 +346,10 @@ sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) {
.process_text = sc_key_processor_process_text, .process_text = sc_key_processor_process_text,
}; };
// 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; kb->key_processor.ops = &ops;
return true; return true;

View File

@ -8,9 +8,9 @@
#include "config.h" #include "config.h"
#include "compat.h" #include "compat.h"
#include "util/file.h"
#include "util/log.h" #include "util/log.h"
#include "util/process.h" #include "util/str.h"
#include "util/str_util.h"
#define SCRCPY_PORTABLE_ICON_FILENAME "icon.png" #define SCRCPY_PORTABLE_ICON_FILENAME "icon.png"
#define SCRCPY_DEFAULT_ICON_PATH \ #define SCRCPY_DEFAULT_ICON_PATH \
@ -26,12 +26,12 @@ get_icon_path(void) {
if (icon_path_env) { if (icon_path_env) {
// if the envvar is set, use it // if the envvar is set, use it
#ifdef __WINDOWS__ #ifdef __WINDOWS__
char *icon_path = utf8_from_wide_char(icon_path_env); char *icon_path = sc_str_from_wchars(icon_path_env);
#else #else
char *icon_path = strdup(icon_path_env); char *icon_path = strdup(icon_path_env);
#endif #endif
if (!icon_path) { if (!icon_path) {
LOGE("Could not allocate memory"); LOG_OOM();
return NULL; return NULL;
} }
LOGD("Using SCRCPY_ICON_PATH: %s", icon_path); LOGD("Using SCRCPY_ICON_PATH: %s", icon_path);
@ -42,11 +42,11 @@ get_icon_path(void) {
LOGD("Using icon: " SCRCPY_DEFAULT_ICON_PATH); LOGD("Using icon: " SCRCPY_DEFAULT_ICON_PATH);
char *icon_path = strdup(SCRCPY_DEFAULT_ICON_PATH); char *icon_path = strdup(SCRCPY_DEFAULT_ICON_PATH);
if (!icon_path) { if (!icon_path) {
LOGE("Could not allocate memory"); LOG_OOM();
return NULL; return NULL;
} }
#else #else
char *icon_path = get_local_file_path(SCRCPY_PORTABLE_ICON_FILENAME); char *icon_path = sc_file_get_local_path(SCRCPY_PORTABLE_ICON_FILENAME);
if (!icon_path) { if (!icon_path) {
LOGE("Could not get icon path"); LOGE("Could not get icon path");
return NULL; return NULL;
@ -63,7 +63,7 @@ decode_image(const char *path) {
AVFormatContext *ctx = avformat_alloc_context(); AVFormatContext *ctx = avformat_alloc_context();
if (!ctx) { if (!ctx) {
LOGE("Could not allocate image decoder context"); LOG_OOM();
return NULL; return NULL;
} }
@ -93,7 +93,7 @@ decode_image(const char *path) {
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec); AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) { if (!codec_ctx) {
LOGE("Could not allocate codec context"); LOG_OOM();
goto close_input; goto close_input;
} }
@ -109,13 +109,13 @@ decode_image(const char *path) {
AVFrame *frame = av_frame_alloc(); AVFrame *frame = av_frame_alloc();
if (!frame) { if (!frame) {
LOGE("Could not allocate frame"); LOG_OOM();
goto close_codec; goto close_codec;
} }
AVPacket *packet = av_packet_alloc(); AVPacket *packet = av_packet_alloc();
if (!packet) { if (!packet) {
LOGE("Could not allocate packet"); LOG_OOM();
av_frame_free(&frame); av_frame_free(&frame);
goto close_codec; goto close_codec;
} }
@ -158,6 +158,12 @@ free_ctx:
return result; 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 static SDL_PixelFormatEnum
to_sdl_pixel_format(enum AVPixelFormat fmt) { to_sdl_pixel_format(enum AVPixelFormat fmt) {
switch (fmt) { switch (fmt) {
@ -172,7 +178,9 @@ to_sdl_pixel_format(enum AVPixelFormat fmt) {
case AV_PIX_FMT_BGR565BE: return SDL_PIXELFORMAT_BGR565; case AV_PIX_FMT_BGR565BE: return SDL_PIXELFORMAT_BGR565;
case AV_PIX_FMT_BGR555BE: return SDL_PIXELFORMAT_BGR555; case AV_PIX_FMT_BGR555BE: return SDL_PIXELFORMAT_BGR555;
case AV_PIX_FMT_RGB444BE: return SDL_PIXELFORMAT_RGB444; 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; case AV_PIX_FMT_BGR444BE: return SDL_PIXELFORMAT_BGR444;
#endif
case AV_PIX_FMT_PAL8: return SDL_PIXELFORMAT_INDEX8; case AV_PIX_FMT_PAL8: return SDL_PIXELFORMAT_INDEX8;
default: return SDL_PIXELFORMAT_UNKNOWN; default: return SDL_PIXELFORMAT_UNKNOWN;
} }

View File

@ -66,6 +66,7 @@ input_manager_init(struct input_manager *im, struct controller *controller,
im->control = options->control; im->control = options->control;
im->forward_all_clicks = options->forward_all_clicks; im->forward_all_clicks = options->forward_all_clicks;
im->legacy_paste = options->legacy_paste; im->legacy_paste = options->legacy_paste;
im->clipboard_autosync = options->clipboard_autosync;
const struct sc_shortcut_mods *shortcut_mods = &options->shortcut_mods; const struct sc_shortcut_mods *shortcut_mods = &options->shortcut_mods;
assert(shortcut_mods->count); assert(shortcut_mods->count);
@ -82,6 +83,8 @@ input_manager_init(struct input_manager *im, struct controller *controller,
im->last_keycode = SDLK_UNKNOWN; im->last_keycode = SDLK_UNKNOWN;
im->last_mod = 0; im->last_mod = 0;
im->key_repeat = 0; im->key_repeat = 0;
im->next_sequence = 1; // 0 is reserved for SC_SEQUENCE_INVALID
} }
static void static void
@ -208,35 +211,35 @@ collapse_panels(struct controller *controller) {
} }
} }
static void static bool
set_device_clipboard(struct controller *controller, bool paste) { 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
SDL_free(text);
return;
} }
char *text_dup = strdup(text); char *text_dup = strdup(text);
SDL_free(text); SDL_free(text);
if (!text_dup) { if (!text_dup) {
LOGW("Could not strdup input text"); LOGW("Could not strdup input text");
return; 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.sequence = sequence;
msg.set_clipboard.text = text_dup; msg.set_clipboard.text = text_dup;
msg.set_clipboard.paste = paste; msg.set_clipboard.paste = paste;
if (!controller_push_msg(controller, &msg)) { if (!controller_push_msg(controller, &msg)) {
free(text_dup); free(text_dup);
LOGW("Could not request 'set device clipboard'"); LOGW("Could not request 'set device clipboard'");
return false;
} }
return true;
} }
static void static void
@ -462,8 +465,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;
@ -512,18 +517,36 @@ input_manager_process_key(struct input_manager *im,
return; return;
} }
if (ctrl && !shift && keycode == SDLK_v && down && !repeat) { uint64_t ack_to_wait = SC_SEQUENCE_INVALID;
bool is_ctrl_v = ctrl && !shift && keycode == SDLK_v && down && !repeat;
if (im->clipboard_autosync && is_ctrl_v) {
if (im->legacy_paste) { 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;
}
if (im->kp->async_paste) {
// The key processor must wait for this ack before injecting Ctrl+v
ack_to_wait = sequence;
// Increment only when the request succeeded
++im->next_sequence;
}
} }
im->kp->ops->process_key(im->kp, event); im->kp->ops->process_key(im->kp, event, ack_to_wait);
} }
static void static void

View File

@ -24,6 +24,7 @@ struct input_manager {
bool control; bool control;
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];
@ -38,6 +39,8 @@ struct input_manager {
unsigned key_repeat; unsigned key_repeat;
SDL_Keycode last_keycode; SDL_Keycode last_keycode;
uint16_t last_mod; uint16_t last_mod;
uint64_t next_sequence; // used for request acknowledgements
}; };
void void

View File

@ -188,7 +188,13 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
static void static void
sc_key_processor_process_key(struct sc_key_processor *kp, sc_key_processor_process_key(struct sc_key_processor *kp,
const SDL_KeyboardEvent *event) { const SDL_KeyboardEvent *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); struct sc_keyboard_inject *ki = DOWNCAST(kp);
if (event->repeat) { if (event->repeat) {
@ -250,5 +256,7 @@ sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
.process_text = sc_key_processor_process_text, .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; ki->key_processor.ops = &ops;
} }

View File

@ -47,6 +47,9 @@ 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,
@ -73,8 +76,6 @@ 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

View File

@ -19,6 +19,8 @@ const struct scrcpy_options scrcpy_options_default = {
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST, .first = DEFAULT_LOCAL_PORT_RANGE_FIRST,
.last = DEFAULT_LOCAL_PORT_RANGE_LAST, .last = DEFAULT_LOCAL_PORT_RANGE_LAST,
}, },
.tunnel_host = 0,
.tunnel_port = 0,
.shortcut_mods = { .shortcut_mods = {
.data = {SC_MOD_LALT, SC_MOD_LSUPER}, .data = {SC_MOD_LALT, SC_MOD_LSUPER},
.count = 2, .count = 2,
@ -51,4 +53,7 @@ const struct scrcpy_options scrcpy_options_default = {
.forward_all_clicks = false, .forward_all_clicks = false,
.legacy_paste = false, .legacy_paste = false,
.power_off_on_close = false, .power_off_on_close = false,
.clipboard_autosync = true,
.tcpip = false,
.tcpip_dst = NULL,
}; };

View File

@ -77,6 +77,8 @@ struct scrcpy_options {
enum sc_record_format record_format; enum sc_record_format record_format;
enum sc_keyboard_input_mode keyboard_input_mode; enum sc_keyboard_input_mode keyboard_input_mode;
struct sc_port_range port_range; struct sc_port_range port_range;
uint32_t tunnel_host;
uint16_t tunnel_port;
struct sc_shortcut_mods shortcut_mods; struct sc_shortcut_mods shortcut_mods;
uint16_t max_size; uint16_t max_size;
uint32_t bit_rate; uint32_t bit_rate;
@ -106,6 +108,9 @@ struct scrcpy_options {
bool forward_all_clicks; bool forward_all_clicks;
bool legacy_paste; bool legacy_paste;
bool power_off_on_close; bool power_off_on_close;
bool clipboard_autosync;
bool tcpip;
const char *tcpip_dst;
}; };
extern const struct scrcpy_options scrcpy_options_default; extern const struct scrcpy_options scrcpy_options_default;

View File

@ -7,12 +7,16 @@
#include "util/log.h" #include "util/log.h"
bool bool
receiver_init(struct receiver *receiver, sc_socket control_socket) { receiver_init(struct receiver *receiver, sc_socket control_socket,
struct sc_acksync *acksync) {
bool ok = sc_mutex_init(&receiver->mutex); bool ok = sc_mutex_init(&receiver->mutex);
if (!ok) { if (!ok) {
return false; return false;
} }
receiver->control_socket = control_socket; receiver->control_socket = control_socket;
receiver->acksync = acksync;
return true; return true;
} }
@ -22,7 +26,7 @@ receiver_destroy(struct receiver *receiver) {
} }
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;

View File

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

@ -6,7 +6,7 @@
#include <libavutil/time.h> #include <libavutil/time.h>
#include "util/log.h" #include "util/log.h"
#include "util/str_util.h" #include "util/str.h"
/** Downcast packet_sink to recorder */ /** Downcast packet_sink to recorder */
#define DOWNCAST(SINK) container_of(SINK, struct recorder, packet_sink) #define DOWNCAST(SINK) container_of(SINK, struct recorder, packet_sink)
@ -26,7 +26,7 @@ find_muxer(const char *name) {
oformat = av_oformat_next(oformat); oformat = av_oformat_next(oformat);
#endif #endif
// until null or containing the requested name // until null or containing the requested name
} while (oformat && !strlist_contains(oformat->name, ',', name)); } while (oformat && !sc_str_list_contains(oformat->name, ',', name));
return oformat; return oformat;
} }
@ -34,11 +34,13 @@ static struct record_packet *
record_packet_new(const AVPacket *packet) { record_packet_new(const AVPacket *packet) {
struct record_packet *rec = malloc(sizeof(*rec)); struct record_packet *rec = malloc(sizeof(*rec));
if (!rec) { if (!rec) {
LOG_OOM();
return NULL; return NULL;
} }
rec->packet = av_packet_alloc(); rec->packet = av_packet_alloc();
if (!rec->packet) { if (!rec->packet) {
LOG_OOM();
free(rec); free(rec);
return NULL; return NULL;
} }
@ -81,7 +83,7 @@ recorder_write_header(struct recorder *recorder, const AVPacket *packet) {
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t)); 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;
} }
@ -228,13 +230,11 @@ static bool
recorder_open(struct recorder *recorder, const AVCodec *input_codec) { recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
bool ok = sc_mutex_init(&recorder->mutex); bool ok = sc_mutex_init(&recorder->mutex);
if (!ok) { if (!ok) {
LOGC("Could not create mutex");
return false; return false;
} }
ok = sc_cond_init(&recorder->queue_cond); ok = sc_cond_init(&recorder->queue_cond);
if (!ok) { if (!ok) {
LOGC("Could not create cond");
goto error_mutex_destroy; goto error_mutex_destroy;
} }
@ -254,7 +254,7 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
recorder->ctx = avformat_alloc_context(); recorder->ctx = avformat_alloc_context();
if (!recorder->ctx) { if (!recorder->ctx) {
LOGE("Could not allocate output context"); LOG_OOM();
goto error_cond_destroy; goto error_cond_destroy;
} }
@ -338,7 +338,7 @@ recorder_push(struct recorder *recorder, const AVPacket *packet) {
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();
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);
return false; return false;
} }
@ -375,7 +375,7 @@ recorder_init(struct recorder *recorder,
struct sc_size declared_frame_size) { struct sc_size declared_frame_size) {
recorder->filename = strdup(filename); recorder->filename = strdup(filename);
if (!recorder->filename) { if (!recorder->filename) {
LOGE("Could not strdup filename"); LOG_OOM();
return false; return false;
} }

View File

@ -27,6 +27,7 @@
#include "screen.h" #include "screen.h"
#include "server.h" #include "server.h"
#include "stream.h" #include "stream.h"
#include "util/acksync.h"
#include "util/log.h" #include "util/log.h"
#include "util/net.h" #include "util/net.h"
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
@ -34,7 +35,7 @@
#endif #endif
struct scrcpy { struct scrcpy {
struct server server; struct sc_server server;
struct screen screen; struct screen screen;
struct stream stream; struct stream stream;
struct decoder decoder; struct decoder decoder;
@ -46,6 +47,8 @@ struct scrcpy {
struct file_handler file_handler; struct file_handler file_handler;
#ifdef HAVE_AOA_HID #ifdef HAVE_AOA_HID
struct sc_aoa aoa; struct sc_aoa aoa;
// sequence/ack helper to synchronize clipboard and Ctrl+v via HID
struct sc_acksync acksync;
#endif #endif
union { union {
struct sc_keyboard_inject keyboard_inject; struct sc_keyboard_inject keyboard_inject;
@ -217,6 +220,29 @@ event_loop(struct scrcpy *s, 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) {
@ -245,7 +271,7 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
size_t fmt_len = strlen(fmt); size_t fmt_len = strlen(fmt);
char *local_fmt = malloc(fmt_len + 10); char *local_fmt = malloc(fmt_len + 10);
if (!local_fmt) { if (!local_fmt) {
LOGC("Could not allocate string"); LOG_OOM();
return; return;
} }
memcpy(local_fmt, "[FFmpeg] ", 9); // do not write the final '\0' memcpy(local_fmt, "[FFmpeg] ", 9); // do not write the final '\0'
@ -262,6 +288,32 @@ stream_on_eos(struct stream *stream, void *userdata) {
PUSH_EVENT(EVENT_STREAM_STOPPED); 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(struct scrcpy_options *options) { scrcpy(struct scrcpy_options *options) {
static struct scrcpy scrcpy; static struct scrcpy scrcpy;
@ -275,10 +327,6 @@ scrcpy(struct scrcpy_options *options) {
atexit(SDL_Quit); atexit(SDL_Quit);
if (!server_init(&s->server)) {
return false;
}
bool ret = false; bool ret = false;
bool server_started = false; bool server_started = false;
@ -295,11 +343,15 @@ scrcpy(struct scrcpy_options *options) {
bool controller_started = false; bool controller_started = false;
bool screen_initialized = false; bool screen_initialized = false;
struct server_params params = { struct sc_acksync *acksync = NULL;
struct sc_server_params params = {
.serial = options->serial, .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,
@ -312,8 +364,21 @@ scrcpy(struct scrcpy_options *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, .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(&s->server, &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;
} }
@ -331,15 +396,19 @@ scrcpy(struct scrcpy_options *options) {
sdl_configure(options->display, options->disable_screensaver); sdl_configure(options->display, options->disable_screensaver);
char device_name[DEVICE_NAME_FIELD_LENGTH]; // Await for server without blocking Ctrl+C handling
struct sc_size frame_size; if (!await_for_server()) {
if (!server_connect_to(&s->server, device_name, &frame_size)) {
goto end; goto end;
} }
// It is necessarily initialized here, since the device is connected
struct sc_server_info *info = &s->server.info;
const char *serial = s->server.params.serial;
assert(serial);
if (options->display && options->control) { if (options->display && options->control) {
if (!file_handler_init(&s->file_handler, s->server.serial, if (!file_handler_init(&s->file_handler, serial,
options->push_target)) { options->push_target)) {
goto end; goto end;
} }
@ -361,7 +430,7 @@ scrcpy(struct scrcpy_options *options) {
if (!recorder_init(&s->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 = &s->recorder; rec = &s->recorder;
@ -384,7 +453,18 @@ scrcpy(struct scrcpy_options *options) {
} }
if (options->control) { if (options->control) {
if (!controller_init(&s->controller, s->server.control_socket)) { #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;
}
acksync = &s->acksync;
}
#endif
if (!controller_init(&s->controller, s->server.control_socket,
acksync)) {
goto end; goto end;
} }
controller_initialized = true; controller_initialized = true;
@ -407,11 +487,11 @@ scrcpy(struct scrcpy_options *options) {
if (options->display) { if (options->display) {
const char *window_title = const char *window_title =
options->window_title ? options->window_title : device_name; options->window_title ? options->window_title : info->device_name;
struct screen_params screen_params = { struct screen_params screen_params = {
.window_title = window_title, .window_title = window_title,
.frame_size = frame_size, .frame_size = info->frame_size,
.always_on_top = options->always_on_top, .always_on_top = options->always_on_top,
.window_x = options->window_x, .window_x = options->window_x,
.window_y = options->window_y, .window_y = options->window_y,
@ -434,8 +514,8 @@ scrcpy(struct scrcpy_options *options) {
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
if (options->v4l2_device) { if (options->v4l2_device) {
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device, frame_size, if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device,
options->v4l2_buffer)) { info->frame_size, options->v4l2_buffer)) {
goto end; goto end;
} }
@ -460,21 +540,7 @@ scrcpy(struct scrcpy_options *options) {
#ifdef HAVE_AOA_HID #ifdef HAVE_AOA_HID
bool aoa_hid_ok = false; bool aoa_hid_ok = false;
char *serialno = NULL; bool ok = sc_aoa_init(&s->aoa, serial, acksync);
const char *serial = options->serial;
if (!serial) {
serialno = adb_get_serialno();
if (!serialno) {
LOGE("Could not get device serial");
goto aoa_hid_end;
}
serial = serialno;
LOGI("Device serial: %s", serial);
}
bool ok = sc_aoa_init(&s->aoa, serial);
free(serialno);
if (!ok) { if (!ok) {
goto aoa_hid_end; goto aoa_hid_end;
} }
@ -539,6 +605,9 @@ end:
sc_hid_keyboard_destroy(&s->keyboard_hid); sc_hid_keyboard_destroy(&s->keyboard_hid);
sc_aoa_stop(&s->aoa); sc_aoa_stop(&s->aoa);
} }
if (acksync) {
sc_acksync_destroy(acksync);
}
#endif #endif
if (controller_started) { if (controller_started) {
controller_stop(&s->controller); controller_stop(&s->controller);
@ -552,7 +621,7 @@ end:
if (server_started) { if (server_started) {
// shutdown the sockets and kill the server // shutdown the sockets and kill the server
server_stop(&s->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
@ -597,7 +666,7 @@ end:
file_handler_destroy(&s->file_handler); file_handler_destroy(&s->file_handler);
} }
server_destroy(&s->server); sc_server_destroy(&s->server);
return ret; return ret;
} }

View File

@ -224,6 +224,45 @@ create_texture(struct screen *screen) {
return texture; return texture;
} }
// render the texture to the renderer
//
// Set the update_content_rect flag if the window or content size may have
// changed, so that the content rectangle is recomputed
static void
screen_render(struct screen *screen, bool update_content_rect) {
if (update_content_rect) {
screen_update_content_rect(screen);
}
SDL_RenderClear(screen->renderer);
if (screen->rotation == 0) {
SDL_RenderCopy(screen->renderer, screen->texture, NULL, &screen->rect);
} else {
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
// counterclockwise (to be consistent with --lock-video-orientation)
int cw_rotation = (4 - screen->rotation) % 4;
double angle = 90 * cw_rotation;
SDL_Rect *dstrect = NULL;
SDL_Rect rect;
if (screen->rotation & 1) {
rect.x = screen->rect.x + (screen->rect.w - screen->rect.h) / 2;
rect.y = screen->rect.y + (screen->rect.h - screen->rect.w) / 2;
rect.w = screen->rect.h;
rect.h = screen->rect.w;
dstrect = &rect;
} else {
assert(screen->rotation == 2);
dstrect = &screen->rect;
}
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
angle, NULL, 0);
}
SDL_RenderPresent(screen->renderer);
}
#if defined(__APPLE__) || defined(__WINDOWS__) #if defined(__APPLE__) || defined(__WINDOWS__)
# define CONTINUOUS_RESIZING_WORKAROUND # define CONTINUOUS_RESIZING_WORKAROUND
#endif #endif
@ -327,18 +366,15 @@ screen_init(struct screen *screen, const struct screen_params *params) {
bool ok = sc_video_buffer_init(&screen->vb, params->buffering_time, &cbs, bool ok = sc_video_buffer_init(&screen->vb, params->buffering_time, &cbs,
screen); screen);
if (!ok) { if (!ok) {
LOGE("Could not initialize video buffer");
return false; return false;
} }
ok = sc_video_buffer_start(&screen->vb); ok = sc_video_buffer_start(&screen->vb);
if (!ok) { if (!ok) {
LOGE("Could not start video_buffer");
goto error_destroy_video_buffer; goto error_destroy_video_buffer;
} }
if (!fps_counter_init(&screen->fps_counter)) { if (!fps_counter_init(&screen->fps_counter)) {
LOGE("Could not initialize FPS counter");
goto error_stop_and_join_video_buffer; goto error_stop_and_join_video_buffer;
} }
@ -439,7 +475,7 @@ screen_init(struct screen *screen, const struct screen_params *params) {
screen->frame = av_frame_alloc(); screen->frame = av_frame_alloc();
if (!screen->frame) { if (!screen->frame) {
LOGC("Could not create screen frame"); LOG_OOM();
goto error_destroy_texture; goto error_destroy_texture;
} }
@ -642,40 +678,6 @@ screen_update_frame(struct screen *screen) {
return true; return true;
} }
void
screen_render(struct screen *screen, bool update_content_rect) {
if (update_content_rect) {
screen_update_content_rect(screen);
}
SDL_RenderClear(screen->renderer);
if (screen->rotation == 0) {
SDL_RenderCopy(screen->renderer, screen->texture, NULL, &screen->rect);
} else {
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
// counterclockwise (to be consistent with --lock-video-orientation)
int cw_rotation = (4 - screen->rotation) % 4;
double angle = 90 * cw_rotation;
SDL_Rect *dstrect = NULL;
SDL_Rect rect;
if (screen->rotation & 1) {
rect.x = screen->rect.x + (screen->rect.w - screen->rect.h) / 2;
rect.y = screen->rect.y + (screen->rect.h - screen->rect.w) / 2;
rect.w = screen->rect.h;
rect.h = screen->rect.w;
dstrect = &rect;
} else {
assert(screen->rotation == 2);
dstrect = &screen->rect;
}
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
angle, NULL, 0);
}
SDL_RenderPresent(screen->renderer);
}
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;

View File

@ -93,13 +93,6 @@ screen_destroy(struct screen *screen);
void void
screen_hide_window(struct screen *screen); screen_hide_window(struct screen *screen);
// render the texture to the renderer
//
// Set the update_content_rect flag if the window or content size may have
// changed, so that the content rectangle is recomputed
void
screen_render(struct screen *screen, bool update_content_rect);
// switch the fullscreen mode // switch the fullscreen mode
void void
screen_switch_fullscreen(struct screen *screen); screen_switch_fullscreen(struct screen *screen);

File diff suppressed because it is too large Load Diff

View File

@ -8,36 +8,29 @@
#include <stdint.h> #include <stdint.h>
#include "adb.h" #include "adb.h"
#include "adb_tunnel.h"
#include "coords.h" #include "coords.h"
#include "options.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" #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];
sc_thread wait_server_thread; struct sc_size frame_size;
sc_mutex mutex;
sc_cond process_terminated_cond;
bool process_terminated;
sc_socket server_socket; // only used if !tunnel_forward
sc_socket video_socket;
sc_socket control_socket;
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; 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;
@ -48,29 +41,67 @@ struct server_params {
bool stay_awake; bool stay_awake;
bool force_adb_forward; bool force_adb_forward;
bool power_off_on_close; 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 struct server_params *params);
#define DEVICE_NAME_FIELD_LENGTH 64 sc_mutex mutex;
// block until the communication with the server is established sc_cond cond_stopped;
// device_name must point to a buffer of at least DEVICE_NAME_FIELD_LENGTH bytes 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, char *device_name, sc_server_init(struct sc_server *server, const struct sc_server_params *params,
struct sc_size *size); 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

@ -42,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;
} }
@ -111,18 +111,18 @@ stream_push_packet(struct stream *stream, AVPacket *packet) {
if (stream->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;
stream->pending = av_packet_alloc(); stream->pending = av_packet_alloc();
if (!stream->pending) { if (!stream->pending) {
LOGE("Could not allocate packet"); LOG_OOM();
return false; return false;
} }
if (av_new_packet(stream->pending, packet->size)) { if (av_new_packet(stream->pending, packet->size)) {
LOGE("Could not create packet"); LOG_OOM();
av_packet_free(&stream->pending); av_packet_free(&stream->pending);
return false; return false;
} }
@ -200,7 +200,7 @@ 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;
} }
@ -221,7 +221,7 @@ run_stream(void *data) {
AVPacket *packet = av_packet_alloc(); AVPacket *packet = av_packet_alloc();
if (!packet) { if (!packet) {
LOGE("Could not allocate packet"); LOG_OOM();
goto finally_close_parser; goto finally_close_parser;
} }

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

@ -3,56 +3,19 @@
#include <assert.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;
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)
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;
}
enum process_result
process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin,
int *pipe_stdout, int *pipe_stderr) {
int in[2]; int in[2];
int out[2]; int out[2];
int err[2]; int err[2];
@ -60,44 +23,44 @@ process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin,
if (pipe(internal) == -1) { if (pipe(internal) == -1) {
perror("pipe"); perror("pipe");
return PROCESS_ERROR_GENERIC; return SC_PROCESS_ERROR_GENERIC;
} }
if (pipe_stdin) { if (pin) {
if (pipe(in) == -1) { if (pipe(in) == -1) {
perror("pipe"); perror("pipe");
close(internal[0]); close(internal[0]);
close(internal[1]); close(internal[1]);
return PROCESS_ERROR_GENERIC; return SC_PROCESS_ERROR_GENERIC;
} }
} }
if (pipe_stdout) { if (pout) {
if (pipe(out) == -1) { if (pipe(out) == -1) {
perror("pipe"); perror("pipe");
// clean up // clean up
if (pipe_stdin) { if (pin) {
close(in[0]); close(in[0]);
close(in[1]); close(in[1]);
} }
close(internal[0]); close(internal[0]);
close(internal[1]); close(internal[1]);
return PROCESS_ERROR_GENERIC; return SC_PROCESS_ERROR_GENERIC;
} }
} }
if (pipe_stderr) { if (perr) {
if (pipe(err) == -1) { if (pipe(err) == -1) {
perror("pipe"); perror("pipe");
// clean up // clean up
if (pipe_stdout) { if (pout) {
close(out[0]); close(out[0]);
close(out[1]); close(out[1]);
} }
if (pipe_stdin) { if (pin) {
close(in[0]); close(in[0]);
close(in[1]); close(in[1]);
} }
close(internal[0]); close(internal[0]);
close(internal[1]); close(internal[1]);
return PROCESS_ERROR_GENERIC; return SC_PROCESS_ERROR_GENERIC;
} }
} }
@ -105,55 +68,65 @@ process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin,
if (*pid == -1) { if (*pid == -1) {
perror("fork"); perror("fork");
// clean up // clean up
if (pipe_stderr) { if (perr) {
close(err[0]); close(err[0]);
close(err[1]); close(err[1]);
} }
if (pipe_stdout) { if (pout) {
close(out[0]); close(out[0]);
close(out[1]); close(out[1]);
} }
if (pipe_stdin) { if (pin) {
close(in[0]); close(in[0]);
close(in[1]); close(in[1]);
} }
close(internal[0]); close(internal[0]);
close(internal[1]); close(internal[1]);
return PROCESS_ERROR_GENERIC; return SC_PROCESS_ERROR_GENERIC;
} }
if (*pid == 0) { if (*pid == 0) {
if (pipe_stdin) { if (pin) {
if (in[0] != STDIN_FILENO) { if (in[0] != STDIN_FILENO) {
dup2(in[0], STDIN_FILENO); dup2(in[0], STDIN_FILENO);
close(in[0]); close(in[0]);
} }
close(in[1]); close(in[1]);
} }
if (pipe_stdout) { // Do not close stdin in the child process, this makes adb fail on Linux
if (pout) {
if (out[1] != STDOUT_FILENO) { if (out[1] != STDOUT_FILENO) {
dup2(out[1], STDOUT_FILENO); dup2(out[1], STDOUT_FILENO);
close(out[1]); close(out[1]);
} }
close(out[0]); close(out[0]);
} else if (!inherit_stdout) {
// Close stdout in the child process
close(STDOUT_FILENO);
} }
if (pipe_stderr) {
if (perr) {
if (err[1] != STDERR_FILENO) { if (err[1] != STDERR_FILENO) {
dup2(err[1], STDERR_FILENO); dup2(err[1], STDERR_FILENO);
close(err[1]); close(err[1]);
} }
close(err[0]); close(err[0]);
} else if (!inherit_stderr) {
// Close stderr in the child process
close(STDERR_FILENO);
} }
close(internal[0]); close(internal[0]);
enum process_result err; enum sc_process_result err;
if (fcntl(internal[1], F_SETFD, FD_CLOEXEC) == 0) { if (fcntl(internal[1], F_SETFD, FD_CLOEXEC) == 0) {
execvp(argv[0], (char *const *) argv); execvp(argv[0], (char *const *) argv);
perror("exec"); perror("exec");
err = errno == ENOENT ? PROCESS_ERROR_MISSING_BINARY err = errno == ENOENT ? SC_PROCESS_ERROR_MISSING_BINARY
: PROCESS_ERROR_GENERIC; : SC_PROCESS_ERROR_GENERIC;
} else { } else {
perror("fcntl"); perror("fcntl");
err = PROCESS_ERROR_GENERIC; err = SC_PROCESS_ERROR_GENERIC;
} }
// send err to the parent // send err to the parent
if (write(internal[1], &err, sizeof(err)) == -1) { if (write(internal[1], &err, sizeof(err)) == -1) {
@ -168,38 +141,33 @@ process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin,
close(internal[1]); close(internal[1]);
enum process_result res = PROCESS_SUCCESS; enum sc_process_result res = SC_PROCESS_SUCCESS;
// wait for EOF or receive err from child // wait for EOF or receive err from child
if (read(internal[0], &res, sizeof(res)) == -1) { if (read(internal[0], &res, sizeof(res)) == -1) {
perror("read"); perror("read");
res = PROCESS_ERROR_GENERIC; res = SC_PROCESS_ERROR_GENERIC;
} }
close(internal[0]); close(internal[0]);
if (pipe_stdin) { if (pin) {
close(in[0]); close(in[0]);
*pipe_stdin = in[1]; *pin = in[1];
} }
if (pipe_stdout) { if (pout) {
*pipe_stdout = out[0]; *pout = out[0];
close(out[1]); close(out[1]);
} }
if (pipe_stderr) { if (perr) {
*pipe_stderr = err[0]; *perr = err[0];
close(err[1]); close(err[1]);
} }
return res; return res;
} }
enum process_result
process_execute(const char *const argv[], pid_t *pid) {
return process_execute_redirect(argv, pid, NULL, NULL, NULL);
}
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);
@ -208,8 +176,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) {
@ -220,7 +188,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;
} }
@ -228,48 +196,17 @@ 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 *
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
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);
} }
ssize_t ssize_t
read_pipe(int pipe, char *data, size_t len) { sc_pipe_read(int pipe, char *data, size_t len) {
return read(pipe, data, len); return read(pipe, data, len);
} }
void void
close_pipe(int pipe) { sc_pipe_close(int pipe) {
if (close(pipe)) { if (close(pipe)) {
perror("close pipe"); perror("close pipe");
} }

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,10 +1,11 @@
#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"
#define CMD_MAX_LEN 8192 #define CMD_MAX_LEN 8192
@ -14,19 +15,26 @@ build_cmd(char *cmd, size_t len, const char *const argv[]) {
// <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 false; return false;
} }
return true; return true;
} }
enum process_result enum sc_process_result
process_execute_redirect(const char *const argv[], HANDLE *handle, sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags,
HANDLE *pipe_stdin, HANDLE *pipe_stdout, HANDLE *pin, HANDLE *pout, HANDLE *perr) {
HANDLE *pipe_stderr) { bool inherit_stdout = !pout && !(flags & SC_PROCESS_NO_STDOUT);
enum process_result ret = PROCESS_ERROR_GENERIC; bool inherit_stderr = !perr && !(flags & SC_PROCESS_NO_STDERR);
// Add 1 per non-NULL pointer
unsigned handle_count = !!pin
+ (pout || inherit_stdout)
+ (perr || inherit_stderr);
enum sc_process_result ret = SC_PROCESS_ERROR_GENERIC;
SECURITY_ATTRIBUTES sa; SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.nLength = sizeof(SECURITY_ATTRIBUTES);
@ -36,130 +44,178 @@ process_execute_redirect(const char *const argv[], HANDLE *handle,
HANDLE stdin_read_handle; HANDLE stdin_read_handle;
HANDLE stdout_write_handle; HANDLE stdout_write_handle;
HANDLE stderr_write_handle; HANDLE stderr_write_handle;
if (pipe_stdin) { if (pin) {
if (!CreatePipe(&stdin_read_handle, pipe_stdin, &sa, 0)) { if (!CreatePipe(&stdin_read_handle, pin, &sa, 0)) {
perror("pipe"); perror("pipe");
return PROCESS_ERROR_GENERIC; return SC_PROCESS_ERROR_GENERIC;
} }
if (!SetHandleInformation(*pipe_stdin, HANDLE_FLAG_INHERIT, 0)) { if (!SetHandleInformation(*pin, HANDLE_FLAG_INHERIT, 0)) {
LOGE("SetHandleInformation stdin failed"); LOGE("SetHandleInformation stdin failed");
goto error_close_stdin; goto error_close_stdin;
} }
} }
if (pipe_stdout) { if (pout) {
if (!CreatePipe(pipe_stdout, &stdout_write_handle, &sa, 0)) { if (!CreatePipe(pout, &stdout_write_handle, &sa, 0)) {
perror("pipe"); perror("pipe");
goto error_close_stdin; goto error_close_stdin;
} }
if (!SetHandleInformation(*pipe_stdout, HANDLE_FLAG_INHERIT, 0)) { if (!SetHandleInformation(*pout, HANDLE_FLAG_INHERIT, 0)) {
LOGE("SetHandleInformation stdout failed"); LOGE("SetHandleInformation stdout failed");
goto error_close_stdout; goto error_close_stdout;
} }
} }
if (pipe_stderr) { if (perr) {
if (!CreatePipe(pipe_stderr, &stderr_write_handle, &sa, 0)) { if (!CreatePipe(perr, &stderr_write_handle, &sa, 0)) {
perror("pipe"); perror("pipe");
goto error_close_stdout; goto error_close_stdout;
} }
if (!SetHandleInformation(*pipe_stderr, HANDLE_FLAG_INHERIT, 0)) { if (!SetHandleInformation(*perr, HANDLE_FLAG_INHERIT, 0)) {
LOGE("SetHandleInformation stderr failed"); LOGE("SetHandleInformation stderr failed");
goto error_close_stderr; goto error_close_stderr;
} }
} }
STARTUPINFOW si; 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);
if (pipe_stdin || pipe_stdout || pipe_stderr) { HANDLE handles[3];
si.dwFlags = STARTF_USESTDHANDLES;
if (pipe_stdin) { LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL;
si.hStdInput = stdin_read_handle; if (handle_count) {
si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
unsigned i = 0;
if (pin) {
si.StartupInfo.hStdInput = stdin_read_handle;
handles[i++] = si.StartupInfo.hStdInput;
} }
if (pipe_stdout) { if (pout || inherit_stdout) {
si.hStdOutput = stdout_write_handle; si.StartupInfo.hStdOutput = pout ? stdout_write_handle
: GetStdHandle(STD_OUTPUT_HANDLE);
handles[i++] = si.StartupInfo.hStdOutput;
} }
if (pipe_stderr) { if (perr || inherit_stderr) {
si.hStdError = stderr_write_handle; si.StartupInfo.hStdError = perr ? stderr_write_handle
: GetStdHandle(STD_ERROR_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); char *cmd = malloc(CMD_MAX_LEN);
if (!cmd || !build_cmd(cmd, CMD_MAX_LEN, argv)) { if (!cmd || !build_cmd(cmd, CMD_MAX_LEN, argv)) {
*handle = NULL; LOG_OOM();
goto error_close_stderr; goto error_free_attribute_list;
} }
wchar_t *wide = utf8_to_wide_char(cmd); wchar_t *wide = sc_str_to_wchars(cmd);
free(cmd); free(cmd);
if (!wide) { if (!wide) {
LOGC("Could not allocate wide char string"); LOG_OOM();
goto error_close_stderr; goto error_free_attribute_list;
} }
if (!CreateProcessW(NULL, wide, NULL, NULL, TRUE, 0, NULL, NULL, &si, BOOL bInheritHandles = handle_count > 0;
&pi)) { // DETACHED_PROCESS to disable stdin, stdout and stderr
free(wide); DWORD dwCreationFlags = handle_count > 0 ? EXTENDED_STARTUPINFO_PRESENT
*handle = NULL; : DETACHED_PROCESS;
BOOL ok = CreateProcessW(NULL, wide, NULL, NULL, bInheritHandles,
dwCreationFlags, NULL, NULL, &si.StartupInfo, &pi);
free(wide);
if (!ok) {
if (GetLastError() == ERROR_FILE_NOT_FOUND) { if (GetLastError() == ERROR_FILE_NOT_FOUND) {
ret = PROCESS_ERROR_MISSING_BINARY; ret = SC_PROCESS_ERROR_MISSING_BINARY;
} }
goto error_close_stderr; goto error_free_attribute_list;
}
if (lpAttributeList) {
DeleteProcThreadAttributeList(lpAttributeList);
free(lpAttributeList);
} }
// These handles are used by the child process, close them for this process // These handles are used by the child process, close them for this process
if (pipe_stdin) { if (pin) {
CloseHandle(stdin_read_handle); CloseHandle(stdin_read_handle);
} }
if (pipe_stdout) { if (pout) {
CloseHandle(stdout_write_handle); CloseHandle(stdout_write_handle);
} }
if (pipe_stderr) { if (perr) {
CloseHandle(stderr_write_handle); CloseHandle(stderr_write_handle);
} }
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: error_close_stderr:
if (pipe_stderr) { if (perr) {
CloseHandle(*pipe_stderr); CloseHandle(*perr);
CloseHandle(stderr_write_handle); CloseHandle(stderr_write_handle);
} }
error_close_stdout: error_close_stdout:
if (pipe_stdout) { if (pout) {
CloseHandle(*pipe_stdout); CloseHandle(*pout);
CloseHandle(stdout_write_handle); CloseHandle(stdout_write_handle);
} }
error_close_stdin: error_close_stdin:
if (pipe_stdin) { if (pin) {
CloseHandle(*pipe_stdin); CloseHandle(*pin);
CloseHandle(stdin_read_handle); CloseHandle(stdin_read_handle);
} }
return ret; return ret;
} }
enum process_result
process_execute(const char *const argv[], HANDLE *handle) {
return process_execute_redirect(argv, handle, NULL, NULL, NULL);
}
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);
@ -168,48 +224,14 @@ 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 *
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 utf8_from_wide_char(buf);
}
bool
is_regular_file(const char *path) {
wchar_t *wide_path = utf8_to_wide_char(path);
if (!wide_path) {
LOGC("Could not allocate wide char string");
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);
}
ssize_t ssize_t
read_pipe(HANDLE pipe, char *data, size_t len) { sc_pipe_read(HANDLE pipe, char *data, size_t len) {
DWORD r; DWORD r;
if (!ReadFile(pipe, data, len, &r, NULL)) { if (!ReadFile(pipe, data, len, &r, NULL)) {
return -1; return -1;
@ -218,7 +240,7 @@ read_pipe(HANDLE pipe, char *data, size_t len) {
} }
void void
close_pipe(HANDLE pipe) { sc_pipe_close(HANDLE pipe) {
if (!CloseHandle(pipe)) { if (!CloseHandle(pipe)) {
LOGW("Cannot close pipe"); LOGW("Cannot close pipe");
} }

View File

@ -14,12 +14,31 @@
* Component able to process and inject keys should implement this trait. * Component able to process and inject keys should implement this trait.
*/ */
struct sc_key_processor { 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; const struct sc_key_processor_ops *ops;
}; };
struct sc_key_processor_ops { struct sc_key_processor_ops {
/**
* Process the 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.
*/
void void
(*process_key)(struct sc_key_processor *kp, const SDL_KeyboardEvent *event); (*process_key)(struct sc_key_processor *kp, const SDL_KeyboardEvent *event,
uint64_t ack_to_wait);
void void
(*process_text)(struct sc_key_processor *kp, (*process_text)(struct sc_key_processor *kp,

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

83
app/src/util/intr.c Normal file
View File

@ -0,0 +1,83 @@
#include "intr.h"
#include "util/log.h"
#include <assert.h>
bool
sc_intr_init(struct sc_intr *intr) {
bool ok = sc_mutex_init(&intr->mutex);
if (!ok) {
LOG_OOM();
return false;
}
intr->socket = SC_SOCKET_NONE;
intr->process = SC_PROCESS_NONE;
atomic_store_explicit(&intr->interrupted, false, memory_order_relaxed);
return true;
}
bool
sc_intr_set_socket(struct sc_intr *intr, sc_socket socket) {
assert(intr->process == SC_PROCESS_NONE);
sc_mutex_lock(&intr->mutex);
bool interrupted =
atomic_load_explicit(&intr->interrupted, memory_order_relaxed);
if (!interrupted) {
intr->socket = socket;
}
sc_mutex_unlock(&intr->mutex);
return !interrupted;
}
bool
sc_intr_set_process(struct sc_intr *intr, sc_pid pid) {
assert(intr->socket == SC_SOCKET_NONE);
sc_mutex_lock(&intr->mutex);
bool interrupted =
atomic_load_explicit(&intr->interrupted, memory_order_relaxed);
if (!interrupted) {
intr->process = pid;
}
sc_mutex_unlock(&intr->mutex);
return !interrupted;
}
void
sc_intr_interrupt(struct sc_intr *intr) {
sc_mutex_lock(&intr->mutex);
atomic_store_explicit(&intr->interrupted, true, memory_order_relaxed);
// No more than one component to interrupt
assert(intr->socket == SC_SOCKET_NONE ||
intr->process == SC_PROCESS_NONE);
if (intr->socket != SC_SOCKET_NONE) {
LOGD("Interrupting socket");
net_interrupt(intr->socket);
intr->socket = SC_SOCKET_NONE;
}
if (intr->process != SC_PROCESS_NONE) {
LOGD("Interrupting process");
sc_process_terminate(intr->process);
intr->process = SC_PROCESS_NONE;
}
sc_mutex_unlock(&intr->mutex);
}
void
sc_intr_destroy(struct sc_intr *intr) {
assert(intr->socket == SC_SOCKET_NONE);
assert(intr->process == SC_PROCESS_NONE);
sc_mutex_destroy(&intr->mutex);
}

78
app/src/util/intr.h Normal file
View File

@ -0,0 +1,78 @@
#ifndef SC_INTR_H
#define SC_INTR_H
#include "common.h"
#include <stdatomic.h>
#include <stdbool.h>
#include "net.h"
#include "process.h"
#include "thread.h"
/**
* Interruptor to wake up a blocking call from another thread
*
* It allows to register a socket or a process before a blocking call, and
* interrupt/close from another thread to wake up the blocking call.
*/
struct sc_intr {
sc_mutex mutex;
sc_socket socket;
sc_pid process;
// Written protected by the mutex to avoid race conditions against
// sc_intr_set_socket() and sc_intr_set_process(), but can be read
// (atomically) without mutex
atomic_bool interrupted;
};
/**
* Initialize an interruptor
*/
bool
sc_intr_init(struct sc_intr *intr);
/**
* Set a socket as the interruptible component
*
* Call with SC_SOCKET_NONE to unset.
*/
bool
sc_intr_set_socket(struct sc_intr *intr, sc_socket socket);
/**
* Set a process as the interruptible component
*
* Call with SC_PROCESS_NONE to unset.
*/
bool
sc_intr_set_process(struct sc_intr *intr, sc_pid socket);
/**
* Interrupt the current interruptible component
*
* Must be called from a different thread.
*/
void
sc_intr_interrupt(struct sc_intr *intr);
/**
* Read the interrupted state
*
* It is exposed as a static inline function because it just loads from an
* atomic.
*/
static inline bool
sc_intr_is_interrupted(struct sc_intr *intr) {
return atomic_load_explicit(&intr->interrupted, memory_order_relaxed);
}
/**
* Destroy the interruptor
*/
void
sc_intr_destroy(struct sc_intr *intr);
#endif

View File

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

View File

@ -1,25 +1,30 @@
#include "net.h" #include "net.h"
#include <assert.h> #include <assert.h>
#include <errno.h>
#include <stdio.h> #include <stdio.h>
#include <SDL2/SDL_platform.h> #include <SDL2/SDL_platform.h>
#include "log.h" #include "log.h"
#ifdef __WINDOWS__ #ifdef __WINDOWS__
# include <ws2tcpip.h>
typedef int socklen_t; typedef int socklen_t;
typedef SOCKET sc_raw_socket; typedef SOCKET sc_raw_socket;
# define SC_RAW_SOCKET_NONE INVALID_SOCKET
#else #else
# include <sys/types.h> # include <sys/types.h>
# include <sys/socket.h> # include <sys/socket.h>
# include <netinet/in.h> # include <netinet/in.h>
# include <arpa/inet.h> # include <arpa/inet.h>
# include <unistd.h> # include <unistd.h>
# include <fcntl.h>
# define SOCKET_ERROR -1 # define SOCKET_ERROR -1
typedef struct sockaddr_in SOCKADDR_IN; typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR; typedef struct sockaddr SOCKADDR;
typedef struct in_addr IN_ADDR; typedef struct in_addr IN_ADDR;
typedef int sc_raw_socket; typedef int sc_raw_socket;
# define SC_RAW_SOCKET_NONE -1
#endif #endif
bool bool
@ -46,13 +51,14 @@ static inline sc_socket
wrap(sc_raw_socket sock) { wrap(sc_raw_socket sock) {
#ifdef __WINDOWS__ #ifdef __WINDOWS__
if (sock == INVALID_SOCKET) { if (sock == INVALID_SOCKET) {
return SC_INVALID_SOCKET; return SC_SOCKET_NONE;
} }
struct sc_socket_windows *socket = malloc(sizeof(*socket)); struct sc_socket_windows *socket = malloc(sizeof(*socket));
if (!socket) { if (!socket) {
LOG_OOM();
closesocket(sock); closesocket(sock);
return SC_INVALID_SOCKET; return SC_SOCKET_NONE;
} }
socket->socket = sock; socket->socket = sock;
@ -67,7 +73,7 @@ wrap(sc_raw_socket sock) {
static inline sc_raw_socket static inline sc_raw_socket
unwrap(sc_socket socket) { unwrap(sc_socket socket) {
#ifdef __WINDOWS__ #ifdef __WINDOWS__
if (socket == SC_INVALID_SOCKET) { if (socket == SC_SOCKET_NONE) {
return INVALID_SOCKET; return INVALID_SOCKET;
} }
@ -77,6 +83,35 @@ unwrap(sc_socket socket) {
#endif #endif
} }
static inline bool
sc_raw_socket_close(sc_raw_socket raw_sock) {
#ifndef _WIN32
return !close(raw_sock);
#else
return !closesocket(raw_sock);
#endif
}
#ifndef HAVE_SOCK_CLOEXEC
// If SOCK_CLOEXEC does not exist, the flag must be set manually once the
// socket is created
static bool
set_cloexec_flag(sc_raw_socket raw_sock) {
#ifndef _WIN32
if (fcntl(raw_sock, F_SETFD, FD_CLOEXEC) == -1) {
perror("fcntl F_SETFD");
return false;
}
#else
if (!SetHandleInformation((HANDLE) raw_sock, HANDLE_FLAG_INHERIT, 0)) {
LOGE("SetHandleInformation socket failed");
return false;
}
#endif
return true;
}
#endif
static void static void
net_perror(const char *s) { net_perror(const char *s) {
#ifdef _WIN32 #ifdef _WIN32
@ -94,13 +129,27 @@ net_perror(const char *s) {
} }
sc_socket sc_socket
net_connect(uint32_t addr, uint16_t port) { net_socket(void) {
#ifdef HAVE_SOCK_CLOEXEC
sc_raw_socket raw_sock = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
#else
sc_raw_socket raw_sock = socket(AF_INET, SOCK_STREAM, 0); sc_raw_socket raw_sock = socket(AF_INET, SOCK_STREAM, 0);
sc_socket sock = wrap(raw_sock); if (raw_sock != SC_RAW_SOCKET_NONE && !set_cloexec_flag(raw_sock)) {
if (sock == SC_INVALID_SOCKET) { sc_raw_socket_close(raw_sock);
net_perror("socket"); return SC_SOCKET_NONE;
return SC_INVALID_SOCKET;
} }
#endif
sc_socket sock = wrap(raw_sock);
if (sock == SC_SOCKET_NONE) {
net_perror("socket");
}
return sock;
}
bool
net_connect(sc_socket socket, uint32_t addr, uint16_t port) {
sc_raw_socket raw_sock = unwrap(socket);
SOCKADDR_IN sin; SOCKADDR_IN sin;
sin.sin_family = AF_INET; sin.sin_family = AF_INET;
@ -109,21 +158,15 @@ net_connect(uint32_t addr, uint16_t port) {
if (connect(raw_sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) { if (connect(raw_sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
net_perror("connect"); net_perror("connect");
net_close(sock); return false;
return SC_INVALID_SOCKET;
} }
return sock; return true;
} }
sc_socket bool
net_listen(uint32_t addr, uint16_t port, int backlog) { net_listen(sc_socket socket, uint32_t addr, uint16_t port, int backlog) {
sc_raw_socket raw_sock = socket(AF_INET, SOCK_STREAM, 0); sc_raw_socket raw_sock = unwrap(socket);
sc_socket sock = wrap(raw_sock);
if (sock == SC_INVALID_SOCKET) {
net_perror("socket");
return SC_INVALID_SOCKET;
}
int reuse = 1; int reuse = 1;
if (setsockopt(raw_sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse, if (setsockopt(raw_sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse,
@ -138,17 +181,15 @@ net_listen(uint32_t addr, uint16_t port, int backlog) {
if (bind(raw_sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) { if (bind(raw_sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
net_perror("bind"); net_perror("bind");
net_close(sock); return false;
return SC_INVALID_SOCKET;
} }
if (listen(raw_sock, backlog) == SOCKET_ERROR) { if (listen(raw_sock, backlog) == SOCKET_ERROR) {
net_perror("listen"); net_perror("listen");
net_close(sock); return false;
return SC_INVALID_SOCKET;
} }
return sock; return true;
} }
sc_socket sc_socket
@ -157,8 +198,18 @@ net_accept(sc_socket server_socket) {
SOCKADDR_IN csin; SOCKADDR_IN csin;
socklen_t sinsize = sizeof(csin); socklen_t sinsize = sizeof(csin);
#ifdef HAVE_SOCK_CLOEXEC
sc_raw_socket raw_sock =
accept4(raw_server_socket, (SOCKADDR *) &csin, &sinsize, SOCK_CLOEXEC);
#else
sc_raw_socket raw_sock = sc_raw_socket raw_sock =
accept(raw_server_socket, (SOCKADDR *) &csin, &sinsize); accept(raw_server_socket, (SOCKADDR *) &csin, &sinsize);
if (raw_sock != SC_RAW_SOCKET_NONE && !set_cloexec_flag(raw_sock)) {
sc_raw_socket_close(raw_sock);
return SC_SOCKET_NONE;
}
#endif
return wrap(raw_sock); return wrap(raw_sock);
} }
@ -198,7 +249,7 @@ net_send_all(sc_socket socket, const void *buf, size_t len) {
bool bool
net_interrupt(sc_socket socket) { net_interrupt(sc_socket socket) {
assert(socket != SC_INVALID_SOCKET); assert(socket != SC_SOCKET_NONE);
sc_raw_socket raw_sock = unwrap(socket); sc_raw_socket raw_sock = unwrap(socket);
@ -212,7 +263,6 @@ net_interrupt(sc_socket socket) {
#endif #endif
} }
#include <errno.h>
bool bool
net_close(sc_socket socket) { net_close(sc_socket socket) {
sc_raw_socket raw_sock = unwrap(socket); sc_raw_socket raw_sock = unwrap(socket);
@ -228,3 +278,15 @@ net_close(sc_socket socket) {
return !close(raw_sock); return !close(raw_sock);
#endif #endif
} }
bool
net_parse_ipv4(const char *s, uint32_t *ipv4) {
struct in_addr addr;
if (!inet_pton(AF_INET, s, &addr)) {
LOGE("Invalid IPv4 address: %s", s);
return false;
}
*ipv4 = ntohl(addr.s_addr);
return true;
}

View File

@ -11,7 +11,7 @@
# include <winsock2.h> # include <winsock2.h>
# include <stdatomic.h> # include <stdatomic.h>
# define SC_INVALID_SOCKET NULL # define SC_SOCKET_NONE NULL
typedef struct sc_socket_windows { typedef struct sc_socket_windows {
SOCKET socket; SOCKET socket;
atomic_flag closed; atomic_flag closed;
@ -20,10 +20,13 @@
#else // not __WINDOWS__ #else // not __WINDOWS__
# include <sys/socket.h> # include <sys/socket.h>
# define SC_INVALID_SOCKET -1 # define SC_SOCKET_NONE -1
typedef int sc_socket; typedef int sc_socket;
#endif #endif
#define IPV4_LOCALHOST 0x7F000001
bool bool
net_init(void); net_init(void);
@ -31,10 +34,13 @@ void
net_cleanup(void); net_cleanup(void);
sc_socket sc_socket
net_connect(uint32_t addr, uint16_t port); net_socket(void);
sc_socket bool
net_listen(uint32_t addr, uint16_t port, int backlog); net_connect(sc_socket socket, uint32_t addr, uint16_t port);
bool
net_listen(sc_socket socket, uint32_t addr, uint16_t port, int backlog);
sc_socket sc_socket
net_accept(sc_socket server_socket); net_accept(sc_socket server_socket);
@ -62,4 +68,10 @@ net_interrupt(sc_socket socket);
bool bool
net_close(sc_socket socket); net_close(sc_socket socket);
/**
* Parse `ip` "xxx.xxx.xxx.xxx" to an IPv4 host representation
*/
bool
net_parse_ipv4(const char *ip, uint32_t *ipv4);
#endif #endif

97
app/src/util/net_intr.c Normal file
View File

@ -0,0 +1,97 @@
#include "net_intr.h"
bool
net_connect_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr,
uint16_t port) {
if (!sc_intr_set_socket(intr, socket)) {
// Already interrupted
return false;
}
bool ret = net_connect(socket, addr, port);
sc_intr_set_socket(intr, SC_SOCKET_NONE);
return ret;
}
bool
net_listen_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr,
uint16_t port, int backlog) {
if (!sc_intr_set_socket(intr, socket)) {
// Already interrupted
return false;
}
bool ret = net_listen(socket, addr, port, backlog);
sc_intr_set_socket(intr, SC_SOCKET_NONE);
return ret;
}
sc_socket
net_accept_intr(struct sc_intr *intr, sc_socket server_socket) {
if (!sc_intr_set_socket(intr, server_socket)) {
// Already interrupted
return SC_SOCKET_NONE;
}
sc_socket socket = net_accept(server_socket);
sc_intr_set_socket(intr, SC_SOCKET_NONE);
return socket;
}
ssize_t
net_recv_intr(struct sc_intr *intr, sc_socket socket, void *buf, size_t len) {
if (!sc_intr_set_socket(intr, socket)) {
// Already interrupted
return -1;
}
ssize_t r = net_recv(socket, buf, len);
sc_intr_set_socket(intr, SC_SOCKET_NONE);
return r;
}
ssize_t
net_recv_all_intr(struct sc_intr *intr, sc_socket socket, void *buf,
size_t len) {
if (!sc_intr_set_socket(intr, socket)) {
// Already interrupted
return -1;
}
ssize_t r = net_recv_all(socket, buf, len);
sc_intr_set_socket(intr, SC_SOCKET_NONE);
return r;
}
ssize_t
net_send_intr(struct sc_intr *intr, sc_socket socket, const void *buf,
size_t len) {
if (!sc_intr_set_socket(intr, socket)) {
// Already interrupted
return -1;
}
ssize_t w = net_send(socket, buf, len);
sc_intr_set_socket(intr, SC_SOCKET_NONE);
return w;
}
ssize_t
net_send_all_intr(struct sc_intr *intr, sc_socket socket, const void *buf,
size_t len) {
if (!sc_intr_set_socket(intr, socket)) {
// Already interrupted
return -1;
}
ssize_t w = net_send_all(socket, buf, len);
sc_intr_set_socket(intr, SC_SOCKET_NONE);
return w;
}

35
app/src/util/net_intr.h Normal file
View File

@ -0,0 +1,35 @@
#ifndef SC_NET_INTR_H
#define SC_NET_INTR_H
#include "common.h"
#include "intr.h"
#include "net.h"
bool
net_connect_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr,
uint16_t port);
bool
net_listen_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr,
uint16_t port, int backlog);
sc_socket
net_accept_intr(struct sc_intr *intr, sc_socket server_socket);
ssize_t
net_recv_intr(struct sc_intr *intr, sc_socket socket, void *buf, size_t len);
ssize_t
net_recv_all_intr(struct sc_intr *intr, sc_socket socket, void *buf,
size_t len);
ssize_t
net_send_intr(struct sc_intr *intr, sc_socket socket, const void *buf,
size_t len);
ssize_t
net_send_all_intr(struct sc_intr *intr, sc_socket socket, const void *buf,
size_t len);
#endif

View File

@ -1,72 +1,19 @@
#include "process.h" #include "process.h"
#include <assert.h>
#include <libgen.h> #include <libgen.h>
#include "log.h" #include "log.h"
bool enum sc_process_result
process_check_success(process_t proc, const char *name, bool close) { sc_process_execute(const char *const argv[], sc_pid *pid, unsigned flags) {
if (proc == PROCESS_NONE) { return sc_process_execute_p(argv, pid, flags, NULL, NULL, NULL);
LOGE("Could not execute \"%s\"", name);
return false;
}
exit_code_t exit_code = process_wait(proc, close);
if (exit_code) {
if (exit_code != NO_EXIT_CODE) {
LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code);
} else {
LOGE("\"%s\" exited unexpectedly", name);
}
return false;
}
return true;
}
char *
get_local_file_path(const char *name) {
char *executable_path = 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, PATH_SEPARATOR);
if (!p) {
LOGE("Unexpected executable path: \"%s\" (it should contain a '%c')",
executable_path, 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) {
LOGE("Could not alloc path");
free(executable_path);
return NULL;
}
memcpy(file_path, dir, dirlen);
file_path[dirlen] = PATH_SEPARATOR;
// namelen + 1 to copy the final '\0'
memcpy(&file_path[dirlen + 1], name, namelen + 1);
free(executable_path);
return file_path;
} }
ssize_t ssize_t
read_pipe_all(pipe_t pipe, char *data, size_t len) { sc_pipe_read_all(sc_pipe pipe, char *data, size_t len) {
size_t copied = 0; size_t copied = 0;
while (len > 0) { while (len > 0) {
ssize_t r = read_pipe(pipe, data, len); ssize_t r = sc_pipe_read(pipe, data, len);
if (r <= 0) { if (r <= 0) {
return copied ? (ssize_t) copied : r; return copied ? (ssize_t) copied : r;
} }
@ -76,3 +23,80 @@ read_pipe_all(pipe_t pipe, char *data, size_t len) {
} }
return copied; return copied;
} }
static int
run_observer(void *data) {
struct sc_process_observer *observer = data;
sc_process_wait(observer->pid, false); // ignore exit code
sc_mutex_lock(&observer->mutex);
observer->terminated = true;
sc_cond_signal(&observer->cond_terminated);
sc_mutex_unlock(&observer->mutex);
if (observer->listener) {
observer->listener->on_terminated(observer->listener_userdata);
}
return 0;
}
bool
sc_process_observer_init(struct sc_process_observer *observer, sc_pid pid,
const struct sc_process_listener *listener,
void *listener_userdata) {
// Either no listener, or on_terminated() is defined
assert(!listener || listener->on_terminated);
bool ok = sc_mutex_init(&observer->mutex);
if (!ok) {
return false;
}
ok = sc_cond_init(&observer->cond_terminated);
if (!ok) {
sc_mutex_destroy(&observer->mutex);
return false;
}
observer->pid = pid;
observer->listener = listener;
observer->listener_userdata = listener_userdata;
observer->terminated = false;
ok = sc_thread_create(&observer->thread, run_observer, "process_observer",
observer);
if (!ok) {
sc_cond_destroy(&observer->cond_terminated);
sc_mutex_destroy(&observer->mutex);
return false;
}
return true;
}
bool
sc_process_observer_timedwait(struct sc_process_observer *observer,
sc_tick deadline) {
sc_mutex_lock(&observer->mutex);
bool timed_out = false;
while (!observer->terminated && !timed_out) {
timed_out = !sc_cond_timedwait(&observer->cond_terminated,
&observer->mutex, deadline);
}
bool terminated = observer->terminated;
sc_mutex_unlock(&observer->mutex);
return terminated;
}
void
sc_process_observer_join(struct sc_process_observer *observer) {
sc_thread_join(&observer->thread, NULL);
}
void
sc_process_observer_destroy(struct sc_process_observer *observer) {
sc_cond_destroy(&observer->cond_terminated);
sc_mutex_destroy(&observer->mutex);
}

View File

@ -4,102 +4,177 @@
#include "common.h" #include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include "util/thread.h"
#ifdef _WIN32 #ifdef _WIN32
// not needed here, but winsock2.h must never be included AFTER windows.h // not needed here, but winsock2.h must never be included AFTER windows.h
# include <winsock2.h> # include <winsock2.h>
# include <windows.h> # include <windows.h>
# define PATH_SEPARATOR '\\' # define SC_PRIexitcode "lu"
# define PRIexitcode "lu"
// <https://stackoverflow.com/a/44383330/1987178> // <https://stackoverflow.com/a/44383330/1987178>
# define PRIsizet "Iu" # define SC_PRIsizet "Iu"
# define PROCESS_NONE NULL # define SC_PROCESS_NONE NULL
# define NO_EXIT_CODE -1u // max value as unsigned # define SC_EXIT_CODE_NONE -1u // max value as unsigned
typedef HANDLE process_t; typedef HANDLE sc_pid;
typedef DWORD exit_code_t; typedef DWORD sc_exit_code;
typedef HANDLE pipe_t; typedef HANDLE sc_pipe;
#else #else
# include <sys/types.h> # include <sys/types.h>
# define PATH_SEPARATOR '/' # define SC_PRIsizet "zu"
# define PRIsizet "zu" # define SC_PRIexitcode "d"
# define PRIexitcode "d" # define SC_PROCESS_NONE -1
# define PROCESS_NONE -1 # define SC_EXIT_CODE_NONE -1
# define NO_EXIT_CODE -1 typedef pid_t sc_pid;
typedef pid_t process_t; typedef int sc_exit_code;
typedef int exit_code_t; typedef int sc_pipe;
typedef int pipe_t;
#endif #endif
enum process_result { struct sc_process_listener {
PROCESS_SUCCESS, void (*on_terminated)(void *userdata);
PROCESS_ERROR_GENERIC,
PROCESS_ERROR_MISSING_BINARY,
}; };
// execute the command and write the result to the output parameter "process" /**
enum process_result * Tool to observe process termination
process_execute(const char *const argv[], process_t *process); *
* To keep things simple and multiplatform, it runs a separate thread to wait
* for process termination (without closing the process to avoid race
* conditions).
*
* It allows a caller to block until the process is terminated (with a
* timeout), and to be notified asynchronously from the observer thread.
*
* The process is not owned by the observer (the observer will never close it).
*/
struct sc_process_observer {
sc_pid pid;
enum process_result sc_mutex mutex;
process_execute_redirect(const char *const argv[], process_t *process, sc_cond cond_terminated;
pipe_t *pipe_stdin, pipe_t *pipe_stdout, bool terminated;
pipe_t *pipe_stderr);
sc_thread thread;
const struct sc_process_listener *listener;
void *listener_userdata;
};
enum sc_process_result {
SC_PROCESS_SUCCESS,
SC_PROCESS_ERROR_GENERIC,
SC_PROCESS_ERROR_MISSING_BINARY,
};
#define SC_PROCESS_NO_STDOUT (1 << 0)
#define SC_PROCESS_NO_STDERR (1 << 1)
/**
* Execute the command and write the process id to `pid`
*
* The `flags` argument is a bitwise OR of the following values:
* - SC_PROCESS_NO_STDOUT
* - SC_PROCESS_NO_STDERR
*
* It indicates if stdout and stderr must be inherited from the scrcpy process
* (i.e. if the process must output to the scrcpy console).
*/
enum sc_process_result
sc_process_execute(const char *const argv[], sc_pid *pid, unsigned flags);
/**
* Execute the command and write the process id to `pid`
*
* If not NULL, provide a pipe for stdin (`pin`), stdout (`pout`) and stderr
* (`perr`).
*
* The `flags` argument has the same semantics as in `sc_process_execute()`.
*/
enum sc_process_result
sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
sc_pipe *pin, sc_pipe *pout, sc_pipe *perr);
/**
* Kill the process
*/
bool bool
process_terminate(process_t pid); sc_process_terminate(sc_pid pid);
// kill the process /**
bool * Wait and close the process (similar to waitpid())
process_terminate(process_t pid); *
* The `close` flag indicates if the process must be _closed_ (reaped) (passing
* false is equivalent to enable WNOWAIT in waitid()).
*/
sc_exit_code
sc_process_wait(sc_pid pid, bool close);
// wait and close the process (like waitpid()) /**
// the "close" flag indicates if the process must be "closed" (reaped) * Close (reap) the process
// (passing false is equivalent to enable WNOWAIT in waitid()) *
exit_code_t * Semantically:
process_wait(process_t pid, bool close); * sc_process_wait(close) = sc_process_wait(noclose) + sc_process_close()
*/
// close the process
//
// Semantically, process_wait(close) = process_wait(noclose) + process_close
void void
process_close(process_t pid); sc_process_close(sc_pid pid);
// convenience function to wait for a successful process execution
// automatically log process errors with the provided process name
bool
process_check_success(process_t proc, const char *name, bool close);
#ifndef _WIN32
// only used to find package manager, not implemented for Windows
bool
search_executable(const char *file);
#endif
// return the absolute path of the executable (the scrcpy binary)
// may be NULL on error; to be freed by free()
char *
get_executable_path(void);
// Return the absolute path of a file in the same directory as he executable.
// May be NULL on error. To be freed by free().
char *
get_local_file_path(const char *name);
// returns true if the file exists and is not a directory
bool
is_regular_file(const char *path);
/**
* Read from the pipe
*
* Same semantic as read().
*/
ssize_t ssize_t
read_pipe(pipe_t pipe, char *data, size_t len); sc_pipe_read(sc_pipe pipe, char *data, size_t len);
/**
* Read exactly `len` chars from a pipe (unless EOF)
*/
ssize_t ssize_t
read_pipe_all(pipe_t pipe, char *data, size_t len); sc_pipe_read_all(sc_pipe pipe, char *data, size_t len);
/**
* Close the pipe
*/
void void
close_pipe(pipe_t pipe); sc_pipe_close(sc_pipe pipe);
/**
* Start observing process
*
* The listener is optional. If set, its callback will be called from the
* observer thread once the process is terminated.
*/
bool
sc_process_observer_init(struct sc_process_observer *observer, sc_pid pid,
const struct sc_process_listener *listener,
void *listener_userdata);
/**
* Wait for process termination until a deadline
*
* Return true if the process is already terminated. Return false if the
* process terminatation has not been detected yet (however, it may have
* terminated in the meantime).
*
* To wait without timeout/deadline, just use sc_process_wait() instead.
*/
bool
sc_process_observer_timedwait(struct sc_process_observer *observer,
sc_tick deadline);
/**
* Join the observer thread
*/
void
sc_process_observer_join(struct sc_process_observer *observer);
/**
* Destroy the observer
*
* This does not close the associated process.
*/
void
sc_process_observer_destroy(struct sc_process_observer *observer);
#endif #endif

View File

@ -0,0 +1,29 @@
#include "process_intr.h"
ssize_t
sc_pipe_read_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe, char *data,
size_t len) {
if (!sc_intr_set_process(intr, pid)) {
// Already interrupted
return false;
}
ssize_t ret = sc_pipe_read(pipe, data, len);
sc_intr_set_process(intr, SC_PROCESS_NONE);
return ret;
}
ssize_t
sc_pipe_read_all_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe,
char *data, size_t len) {
if (!sc_intr_set_process(intr, pid)) {
// Already interrupted
return false;
}
ssize_t ret = sc_pipe_read_all(pipe, data, len);
sc_intr_set_process(intr, SC_PROCESS_NONE);
return ret;
}

View File

@ -0,0 +1,17 @@
#ifndef SC_PROCESS_INTR_H
#define SC_PROCESS_INTR_H
#include "common.h"
#include "intr.h"
#include "process.h"
ssize_t
sc_pipe_read_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe, char *data,
size_t len);
ssize_t
sc_pipe_read_all_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe,
char *data, size_t len);
#endif

View File

@ -1,5 +1,6 @@
#include "str_util.h" #include "str.h"
#include <assert.h>
#include <errno.h> #include <errno.h>
#include <limits.h> #include <limits.h>
#include <stdlib.h> #include <stdlib.h>
@ -10,8 +11,11 @@
# include <tchar.h> # include <tchar.h>
#endif #endif
#include "log.h"
#include "strbuf.h"
size_t size_t
xstrncpy(char *dest, const char *src, size_t n) { sc_strncpy(char *dest, const char *src, size_t n) {
size_t i; size_t i;
for (i = 0; i < n - 1 && src[i] != '\0'; ++i) for (i = 0; i < n - 1 && src[i] != '\0'; ++i)
dest[i] = src[i]; dest[i] = src[i];
@ -21,7 +25,7 @@ xstrncpy(char *dest, const char *src, size_t n) {
} }
size_t size_t
xstrjoin(char *dst, const char *const tokens[], char sep, size_t n) { sc_str_join(char *dst, const char *const tokens[], char sep, size_t n) {
const char *const *remaining = tokens; const char *const *remaining = tokens;
const char *token = *remaining++; const char *token = *remaining++;
size_t i = 0; size_t i = 0;
@ -31,7 +35,7 @@ xstrjoin(char *dst, const char *const tokens[], char sep, size_t n) {
if (i == n) if (i == n)
goto truncated; goto truncated;
} }
size_t w = xstrncpy(dst + i, token, n - i); size_t w = sc_strncpy(dst + i, token, n - i);
if (w >= n - i) if (w >= n - i)
goto truncated; goto truncated;
i += w; i += w;
@ -45,10 +49,11 @@ truncated:
} }
char * char *
strquote(const char *src) { sc_str_quote(const char *src) {
size_t len = strlen(src); size_t len = strlen(src);
char *quoted = malloc(len + 3); char *quoted = malloc(len + 3);
if (!quoted) { if (!quoted) {
LOG_OOM();
return NULL; return NULL;
} }
memcpy(&quoted[1], src, len); memcpy(&quoted[1], src, len);
@ -59,7 +64,7 @@ strquote(const char *src) {
} }
bool bool
parse_integer(const char *s, long *out) { sc_str_parse_integer(const char *s, long *out) {
char *endptr; char *endptr;
if (*s == '\0') { if (*s == '\0') {
return false; return false;
@ -78,7 +83,8 @@ parse_integer(const char *s, long *out) {
} }
size_t size_t
parse_integers(const char *s, const char sep, size_t max_items, long *out) { sc_str_parse_integers(const char *s, const char sep, size_t max_items,
long *out) {
size_t count = 0; size_t count = 0;
char *endptr; char *endptr;
do { do {
@ -107,7 +113,7 @@ parse_integers(const char *s, const char sep, size_t max_items, long *out) {
} }
bool bool
parse_integer_with_suffix(const char *s, long *out) { sc_str_parse_integer_with_suffix(const char *s, long *out) {
char *endptr; char *endptr;
if (*s == '\0') { if (*s == '\0') {
return false; return false;
@ -141,7 +147,7 @@ parse_integer_with_suffix(const char *s, long *out) {
} }
bool bool
strlist_contains(const char *list, char sep, const char *s) { sc_str_list_contains(const char *list, char sep, const char *s) {
char *p; char *p;
do { do {
p = strchr(list, sep); p = strchr(list, sep);
@ -159,7 +165,7 @@ strlist_contains(const char *list, char sep, const char *s) {
} }
size_t size_t
utf8_truncation_index(const char *utf8, size_t max_len) { sc_str_utf8_truncation_index(const char *utf8, size_t max_len) {
size_t len = strlen(utf8); size_t len = strlen(utf8);
if (len <= max_len) { if (len <= max_len) {
return len; return len;
@ -177,7 +183,7 @@ utf8_truncation_index(const char *utf8, size_t max_len) {
#ifdef _WIN32 #ifdef _WIN32
wchar_t * wchar_t *
utf8_to_wide_char(const char *utf8) { sc_str_to_wchars(const char *utf8) {
int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0); int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
if (!len) { if (!len) {
return NULL; return NULL;
@ -185,6 +191,7 @@ utf8_to_wide_char(const char *utf8) {
wchar_t *wide = malloc(len * sizeof(wchar_t)); wchar_t *wide = malloc(len * sizeof(wchar_t));
if (!wide) { if (!wide) {
LOG_OOM();
return NULL; return NULL;
} }
@ -193,7 +200,7 @@ utf8_to_wide_char(const char *utf8) {
} }
char * char *
utf8_from_wide_char(const wchar_t *ws) { sc_str_from_wchars(const wchar_t *ws) {
int len = WideCharToMultiByte(CP_UTF8, 0, ws, -1, NULL, 0, NULL, NULL); int len = WideCharToMultiByte(CP_UTF8, 0, ws, -1, NULL, 0, NULL, NULL);
if (!len) { if (!len) {
return NULL; return NULL;
@ -201,6 +208,7 @@ utf8_from_wide_char(const wchar_t *ws) {
char *utf8 = malloc(len); char *utf8 = malloc(len);
if (!utf8) { if (!utf8) {
LOG_OOM();
return NULL; return NULL;
} }
@ -209,3 +217,127 @@ utf8_from_wide_char(const wchar_t *ws) {
} }
#endif #endif
char *
sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent) {
assert(indent < columns);
struct sc_strbuf buf;
// The output string should not be much longer than the input string (just
// a few '\n' added), so this initial capacity should hopefully almost
// always avoid internal realloc() in string buffer
size_t cap = strlen(input) * 3 / 2;
if (!sc_strbuf_init(&buf, cap)) {
return false;
}
#define APPEND(S,N) if (!sc_strbuf_append(&buf, S, N)) goto error
#define APPEND_CHAR(C) if (!sc_strbuf_append_char(&buf, C)) goto error
#define APPEND_N(C,N) if (!sc_strbuf_append_n(&buf, C, N)) goto error
#define APPEND_INDENT() if (indent) APPEND_N(' ', indent)
APPEND_INDENT();
// The last separator encountered, it must be inserted only conditionnaly,
// depending on the next token
char pending = 0;
// col tracks the current column in the current line
size_t col = indent;
while (*input) {
size_t sep_idx = strcspn(input, "\n ");
size_t new_col = col + sep_idx;
if (pending == ' ') {
// The pending space counts
++new_col;
}
bool wrap = new_col > columns;
char sep = input[sep_idx];
if (sep == ' ')
sep = ' ';
if (wrap) {
APPEND_CHAR('\n');
APPEND_INDENT();
col = indent;
} else if (pending) {
APPEND_CHAR(pending);
++col;
if (pending == '\n')
{
APPEND_INDENT();
col = indent;
}
}
if (sep_idx) {
APPEND(input, sep_idx);
col += sep_idx;
}
pending = sep;
input += sep_idx;
if (*input != '\0') {
// Skip the separator
++input;
}
}
if (pending)
APPEND_CHAR(pending);
return buf.s;
error:
free(buf.s);
return NULL;
}
size_t
sc_str_truncate(char *data, size_t len, const char *endchars) {
data[len - 1] = '\0';
size_t idx = strcspn(data, endchars);
data[idx] = '\0';
return idx;
}
ssize_t
sc_str_index_of_column(const char *s, unsigned col, const char *seps) {
size_t colidx = 0;
size_t idx = 0;
while (s[idx] != '\0' && colidx != col) {
size_t r = strcspn(&s[idx], seps);
idx += r;
if (s[idx] == '\0') {
// Not found
return -1;
}
size_t consecutive_seps = strspn(&s[idx], seps);
assert(consecutive_seps); // At least one
idx += consecutive_seps;
if (s[idx] != '\0') {
++colidx;
}
}
return col == colidx ? (ssize_t) idx : -1;
}
size_t
sc_str_remove_trailing_cr(char *s, size_t len) {
while (len) {
if (s[len - 1] != '\r') {
break;
}
s[--len] = '\0';
}
return len;
}

148
app/src/util/str.h Normal file
View File

@ -0,0 +1,148 @@
#ifndef SC_STR_H
#define SC_STR_H
#include "common.h"
#include <stdbool.h>
#include <stddef.h>
/**
* Like strncpy(), except:
* - it copies at most n-1 chars
* - the dest string is nul-terminated
* - it does not write useless bytes if strlen(src) < n
* - it returns the number of chars actually written (max n-1) if src has
* been copied completely, or n if src has been truncated
*/
size_t
sc_strncpy(char *dest, const char *src, size_t n);
/**
* Join tokens by separator `sep` into `dst`
*
* Return the number of chars actually written (max n-1) if no truncation
* occurred, or n if truncated.
*/
size_t
sc_str_join(char *dst, const char *const tokens[], char sep, size_t n);
/**
* Quote a string
*
* Return a new allocated string, surrounded with quotes (`"`).
*/
char *
sc_str_quote(const char *src);
/**
* Parse `s` as an integer into `out`
*
* Return true if the conversion succeeded, false otherwise.
*/
bool
sc_str_parse_integer(const char *s, long *out);
/**
* Parse `s` as integers separated by `sep` (for example `1234:2000`) into `out`
*
* Returns the number of integers on success, 0 on failure.
*/
size_t
sc_str_parse_integers(const char *s, const char sep, size_t max_items,
long *out);
/**
* Parse `s` as an integer into `out`
*
* Like `sc_str_parse_integer()`, but accept 'k'/'K' (x1000) and 'm'/'M'
* (x1000000) as suffixes.
*
* Return true if the conversion succeeded, false otherwise.
*/
bool
sc_str_parse_integer_with_suffix(const char *s, long *out);
/**
* Search `s` in the list separated by `sep`
*
* For example, sc_str_list_contains("a,bc,def", ',', "bc") returns true.
*/
bool
sc_str_list_contains(const char *list, char sep, const char *s);
/**
* Return the index to truncate a UTF-8 string at a valid position
*/
size_t
sc_str_utf8_truncation_index(const char *utf8, size_t max_len);
#ifdef _WIN32
/**
* Convert a UTF-8 string to a wchar_t string
*
* Return the new allocated string, to be freed by the caller.
*/
wchar_t *
sc_str_to_wchars(const char *utf8);
/**
* Convert a wchar_t string to a UTF-8 string
*
* Return the new allocated string, to be freed by the caller.
*/
char *
sc_str_from_wchars(const wchar_t *s);
#endif
/**
* Wrap input lines to fit in `columns` columns
*
* Break input lines at word boundaries (spaces) so that they fit in `columns`
* columns, left-indented by `indent` spaces.
*/
char *
sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent);
/**
* Truncate the data after any of the characters from `endchars`
*
* An '\0' is always written at the end of the data, even if no newline
* character is encountered.
*
* Return the size of the resulting line.
*/
size_t
sc_str_truncate(char *data, size_t len, const char *endchars);
/**
* Find the start of a column in a string
*
* A string may represent several columns, separated by some "spaces"
* (separators). This function aims to find the start of the column number
* `col`.
*
* For example, to find the 4th column (column number 3):
*
* // here
* // v
* const char *s = "abc def ghi jk";
* ssize_t index = sc_str_index_of_column(s, 3, " ");
* assert(index == 16); // points to "jk"
*
* Return -1 if no such column exists.
*/
ssize_t
sc_str_index_of_column(const char *s, unsigned col, const char *seps);
/**
* Remove all `\r` at the end of the line
*
* The line length is provided by `len` (this avoids a call to `strlen()` when
* the caller already knows the length).
*
* Return the new length.
*/
size_t
sc_str_remove_trailing_cr(char *s, size_t len);
#endif

View File

@ -1,65 +0,0 @@
#ifndef STRUTIL_H
#define STRUTIL_H
#include "common.h"
#include <stdbool.h>
#include <stddef.h>
// like strncpy, except:
// - it copies at most n-1 chars
// - the dest string is nul-terminated
// - it does not write useless bytes if strlen(src) < n
// - it returns the number of chars actually written (max n-1) if src has
// been copied completely, or n if src has been truncated
size_t
xstrncpy(char *dest, const char *src, size_t n);
// join tokens by sep into dst
// returns the number of chars actually written (max n-1) if no truncation
// occurred, or n if truncated
size_t
xstrjoin(char *dst, const char *const tokens[], char sep, size_t n);
// quote a string
// returns the new allocated string, to be freed by the caller
char *
strquote(const char *src);
// parse s as an integer into value
// returns true if the conversion succeeded, false otherwise
bool
parse_integer(const char *s, long *out);
// parse s as integers separated by sep (for example '1234:2000')
// returns the number of integers on success, 0 on failure
size_t
parse_integers(const char *s, const char sep, size_t max_items, long *out);
// parse s as an integer into value
// like parse_integer(), but accept 'k'/'K' (x1000) and 'm'/'M' (x1000000) as
// suffix
// returns true if the conversion succeeded, false otherwise
bool
parse_integer_with_suffix(const char *s, long *out);
// search s in the list separated by sep
// for example, strlist_contains("a,bc,def", ',', "bc") returns true
bool
strlist_contains(const char *list, char sep, const char *s);
// return the index to truncate a UTF-8 string at a valid position
size_t
utf8_truncation_index(const char *utf8, size_t max_len);
#ifdef _WIN32
// convert a UTF-8 string to a wchar_t string
// returns the new allocated string, to be freed by the caller
wchar_t *
utf8_to_wide_char(const char *utf8);
char *
utf8_from_wide_char(const wchar_t *s);
#endif
#endif

90
app/src/util/strbuf.c Normal file
View File

@ -0,0 +1,90 @@
#include "strbuf.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "log.h"
bool
sc_strbuf_init(struct sc_strbuf *buf, size_t init_cap) {
buf->s = malloc(init_cap + 1); // +1 for '\0'
if (!buf->s) {
LOG_OOM();
return false;
}
buf->len = 0;
buf->cap = init_cap;
return true;
}
static bool
sc_strbuf_reserve(struct sc_strbuf *buf, size_t len) {
if (buf->len + len > buf->cap) {
size_t new_cap = buf->cap * 3 / 2 + len;
char *s = realloc(buf->s, new_cap + 1); // +1 for '\0'
if (!s) {
// Leave the old buf->s
LOG_OOM();
return false;
}
buf->s = s;
buf->cap = new_cap;
}
return true;
}
bool
sc_strbuf_append(struct sc_strbuf *buf, const char *s, size_t len) {
assert(s);
assert(*s);
assert(strlen(s) >= len);
if (!sc_strbuf_reserve(buf, len)) {
return false;
}
memcpy(&buf->s[buf->len], s, len);
buf->len += len;
buf->s[buf->len] = '\0';
return true;
}
bool
sc_strbuf_append_char(struct sc_strbuf *buf, const char c) {
if (!sc_strbuf_reserve(buf, 1)) {
return false;
}
buf->s[buf->len] = c;
buf->len ++;
buf->s[buf->len] = '\0';
return true;
}
bool
sc_strbuf_append_n(struct sc_strbuf *buf, const char c, size_t n) {
if (!sc_strbuf_reserve(buf, n)) {
return false;
}
memset(&buf->s[buf->len], c, n);
buf->len += n;
buf->s[buf->len] = '\0';
return true;
}
void
sc_strbuf_shrink(struct sc_strbuf *buf) {
assert(buf->len <= buf->cap);
if (buf->len != buf->cap) {
char *s = realloc(buf->s, buf->len + 1); // +1 for '\0'
assert(s); // decreasing the size may not fail
buf->s = s;
buf->cap = buf->len;
}
}

73
app/src/util/strbuf.h Normal file
View File

@ -0,0 +1,73 @@
#ifndef SC_STRBUF_H
#define SC_STRBUF_H
#include "common.h"
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
struct sc_strbuf {
char *s;
size_t len;
size_t cap;
};
/**
* Initialize the string buffer
*
* `buf->s` must be manually freed by the caller.
*/
bool
sc_strbuf_init(struct sc_strbuf *buf, size_t init_cap);
/**
* Append a string
*
* Append `len` characters from `s` to the buffer.
*/
bool
sc_strbuf_append(struct sc_strbuf *buf, const char *s, size_t len);
/**
* Append a char
*
* Append a single character to the buffer.
*/
bool
sc_strbuf_append_char(struct sc_strbuf *buf, const char c);
/**
* Append a char `n` times
*
* Append the same characters `n` times to the buffer.
*/
bool
sc_strbuf_append_n(struct sc_strbuf *buf, const char c, size_t n);
/**
* Append a NUL-terminated string
*/
static inline bool
sc_strbuf_append_str(struct sc_strbuf *buf, const char *s) {
return sc_strbuf_append(buf, s, strlen(s));
}
/**
* Append a static string
*
* Append a string whose size is known at compile time (for
* example a string literal).
*/
#define sc_strbuf_append_staticstr(BUF, S) \
sc_strbuf_append(BUF, S, sizeof(S) - 1)
/**
* Shrink the buffer capacity to its current length
*
* This resizes `buf->s` to fit the content.
*/
void
sc_strbuf_shrink(struct sc_strbuf *buf);
#endif

51
app/src/util/term.c Normal file
View File

@ -0,0 +1,51 @@
#include "term.h"
#include <assert.h>
#ifdef _WIN32
# include <windows.h>
#else
# include <unistd.h>
# include <sys/ioctl.h>
#endif
bool
sc_term_get_size(unsigned *rows, unsigned *cols) {
#ifdef _WIN32
CONSOLE_SCREEN_BUFFER_INFO csbi;
bool ok =
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
if (!ok) {
return false;
}
if (rows) {
assert(csbi.srWindow.Bottom >= csbi.srWindow.Top);
*rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
}
if (cols) {
assert(csbi.srWindow.Right >= csbi.srWindow.Left);
*cols = csbi.srWindow.Right - csbi.srWindow.Left + 1;
}
return true;
#else
struct winsize ws;
int r = ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
if (r == -1) {
return false;
}
if (rows) {
*rows = ws.ws_row;
}
if (cols) {
*cols = ws.ws_col;
}
return true;
#endif
}

21
app/src/util/term.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef SC_TERM_H
#define SC_TERM_H
#include "common.h"
#include <stdbool.h>
/**
* Return the terminal dimensions
*
* Return false if the dimensions could not be retrieved.
*
* Otherwise, return true, and:
* - if `rows` is not NULL, then the number of rows is written to `*rows`.
* - if `columns` is not NULL, then the number of columns is written to
* `*columns`.
*/
bool
sc_term_get_size(unsigned *rows, unsigned *cols);
#endif

View File

@ -10,6 +10,7 @@ sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
void *userdata) { void *userdata) {
SDL_Thread *sdl_thread = SDL_CreateThread(fn, name, userdata); SDL_Thread *sdl_thread = SDL_CreateThread(fn, name, userdata);
if (!sdl_thread) { if (!sdl_thread) {
LOG_OOM();
return false; return false;
} }
@ -26,6 +27,7 @@ bool
sc_mutex_init(sc_mutex *mutex) { sc_mutex_init(sc_mutex *mutex) {
SDL_mutex *sdl_mutex = SDL_CreateMutex(); SDL_mutex *sdl_mutex = SDL_CreateMutex();
if (!sdl_mutex) { if (!sdl_mutex) {
LOG_OOM();
return false; return false;
} }
@ -94,6 +96,7 @@ bool
sc_cond_init(sc_cond *cond) { sc_cond_init(sc_cond *cond) {
SDL_cond *sdl_cond = SDL_CreateCond(); SDL_cond *sdl_cond = SDL_CreateCond();
if (!sdl_cond) { if (!sdl_cond) {
LOG_OOM();
return false; return false;
} }

View File

@ -1,6 +1,8 @@
#ifndef SC_TICK_H #ifndef SC_TICK_H
#define SC_TICK_H #define SC_TICK_H
#include "common.h"
#include <stdint.h> #include <stdint.h>
typedef int64_t sc_tick; typedef int64_t sc_tick;

View File

@ -1,7 +1,9 @@
#include "v4l2_sink.h" #include "v4l2_sink.h"
#include <string.h>
#include "util/log.h" #include "util/log.h"
#include "util/str_util.h" #include "util/str.h"
/** Downcast frame_sink to sc_v4l2_sink */ /** Downcast frame_sink to sc_v4l2_sink */
#define DOWNCAST(SINK) container_of(SINK, struct sc_v4l2_sink, frame_sink) #define DOWNCAST(SINK) container_of(SINK, struct sc_v4l2_sink, frame_sink)
@ -21,7 +23,7 @@ find_muxer(const char *name) {
oformat = av_oformat_next(oformat); oformat = av_oformat_next(oformat);
#endif #endif
// until null or containing the requested name // until null or containing the requested name
} while (oformat && !strlist_contains(oformat->name, ',', name)); } while (oformat && !sc_str_list_contains(oformat->name, ',', name));
return oformat; return oformat;
} }
@ -31,7 +33,7 @@ write_header(struct sc_v4l2_sink *vs, const AVPacket *packet) {
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;
} }
@ -161,25 +163,21 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
bool ok = sc_video_buffer_init(&vs->vb, vs->buffering_time, &cbs, vs); bool ok = sc_video_buffer_init(&vs->vb, vs->buffering_time, &cbs, vs);
if (!ok) { if (!ok) {
LOGE("Could not initialize video buffer");
return false; return false;
} }
ok = sc_video_buffer_start(&vs->vb); ok = sc_video_buffer_start(&vs->vb);
if (!ok) { if (!ok) {
LOGE("Could not start video buffer");
goto error_video_buffer_destroy; goto error_video_buffer_destroy;
} }
ok = sc_mutex_init(&vs->mutex); ok = sc_mutex_init(&vs->mutex);
if (!ok) { if (!ok) {
LOGC("Could not create mutex");
goto error_video_buffer_stop_and_join; goto error_video_buffer_stop_and_join;
} }
ok = sc_cond_init(&vs->cond); ok = sc_cond_init(&vs->cond);
if (!ok) { if (!ok) {
LOGC("Could not create cond");
goto error_mutex_destroy; goto error_mutex_destroy;
} }
@ -201,7 +199,7 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
vs->format_ctx = avformat_alloc_context(); vs->format_ctx = avformat_alloc_context();
if (!vs->format_ctx) { if (!vs->format_ctx) {
LOGE("Could not allocate v4l2 output context"); LOG_OOM();
return false; return false;
} }
@ -213,9 +211,8 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
#ifdef SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL #ifdef SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL
vs->format_ctx->url = strdup(vs->device_name); vs->format_ctx->url = strdup(vs->device_name);
if (!vs->format_ctx->url) { if (!vs->format_ctx->url) {
LOGE("Could not strdup v4l2 device name"); LOG_OOM();
goto error_avformat_free_context; goto error_avformat_free_context;
return false;
} }
#else #else
strncpy(vs->format_ctx->filename, vs->device_name, strncpy(vs->format_ctx->filename, vs->device_name,
@ -224,9 +221,8 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
AVStream *ostream = avformat_new_stream(vs->format_ctx, encoder); AVStream *ostream = avformat_new_stream(vs->format_ctx, encoder);
if (!ostream) { if (!ostream) {
LOGE("Could not allocate new v4l2 stream"); LOG_OOM();
goto error_avformat_free_context; goto error_avformat_free_context;
return false;
} }
ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
@ -244,7 +240,7 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
vs->encoder_ctx = avcodec_alloc_context3(encoder); vs->encoder_ctx = avcodec_alloc_context3(encoder);
if (!vs->encoder_ctx) { if (!vs->encoder_ctx) {
LOGC("Could not allocate codec context for v4l2"); LOG_OOM();
goto error_avio_close; goto error_avio_close;
} }
@ -261,13 +257,13 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
vs->frame = av_frame_alloc(); vs->frame = av_frame_alloc();
if (!vs->frame) { if (!vs->frame) {
LOGE("Could not create v4l2 frame"); LOG_OOM();
goto error_avcodec_close; goto error_avcodec_close;
} }
vs->packet = av_packet_alloc(); vs->packet = av_packet_alloc();
if (!vs->packet) { if (!vs->packet) {
LOGE("Could not allocate packet"); LOG_OOM();
goto error_av_frame_free; goto error_av_frame_free;
} }

View File

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

View File

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

View File

@ -78,7 +78,7 @@ static void test_serialize_inject_touch_event(void) {
.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, .type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
.inject_touch_event = { .inject_touch_event = {
.action = AMOTION_EVENT_ACTION_DOWN, .action = AMOTION_EVENT_ACTION_DOWN,
.pointer_id = 0x1234567887654321L, .pointer_id = UINT64_C(0x1234567887654321),
.position = { .position = {
.point = { .point = {
.x = 100, .x = 100,
@ -226,6 +226,7 @@ static void test_serialize_set_clipboard(void) {
struct control_msg msg = { struct control_msg msg = {
.type = CONTROL_MSG_TYPE_SET_CLIPBOARD, .type = CONTROL_MSG_TYPE_SET_CLIPBOARD,
.set_clipboard = { .set_clipboard = {
.sequence = UINT64_C(0x0102030405060708),
.paste = true, .paste = true,
.text = "hello, world!", .text = "hello, world!",
}, },
@ -233,10 +234,11 @@ static void test_serialize_set_clipboard(void) {
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); size_t size = control_msg_serialize(&msg, buf);
assert(size == 19); assert(size == 27);
const unsigned char expected[] = { const unsigned char expected[] = {
CONTROL_MSG_TYPE_SET_CLIPBOARD, CONTROL_MSG_TYPE_SET_CLIPBOARD,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // sequence
1, // paste 1, // paste
0x00, 0x00, 0x00, 0x0d, // text length 0x00, 0x00, 0x00, 0x0d, // text length
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text

View File

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

424
app/tests/test_str.c Normal file
View File

@ -0,0 +1,424 @@
#include "common.h"
#include <assert.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "util/str.h"
static void test_strncpy_simple(void) {
char s[] = "xxxxxxxxxx";
size_t w = sc_strncpy(s, "abcdef", sizeof(s));
// returns strlen of copied string
assert(w == 6);
// is nul-terminated
assert(s[6] == '\0');
// does not write useless bytes
assert(s[7] == 'x');
// copies the content as expected
assert(!strcmp("abcdef", s));
}
static void test_strncpy_just_fit(void) {
char s[] = "xxxxxx";
size_t w = sc_strncpy(s, "abcdef", sizeof(s));
// returns strlen of copied string
assert(w == 6);
// is nul-terminated
assert(s[6] == '\0');
// copies the content as expected
assert(!strcmp("abcdef", s));
}
static void test_strncpy_truncated(void) {
char s[] = "xxx";
size_t w = sc_strncpy(s, "abcdef", sizeof(s));
// returns 'n' (sizeof(s))
assert(w == 4);
// is nul-terminated
assert(s[3] == '\0');
// copies the content as expected
assert(!strncmp("abcdef", s, 3));
}
static void test_join_simple(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxxxxxxxxxx";
size_t w = sc_str_join(s, tokens, ' ', sizeof(s));
// returns strlen of concatenation
assert(w == 11);
// is nul-terminated
assert(s[11] == '\0');
// does not write useless bytes
assert(s[12] == 'x');
// copies the content as expected
assert(!strcmp("abc de fghi", s));
}
static void test_join_just_fit(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxxxxxxx";
size_t w = sc_str_join(s, tokens, ' ', sizeof(s));
// returns strlen of concatenation
assert(w == 11);
// is nul-terminated
assert(s[11] == '\0');
// copies the content as expected
assert(!strcmp("abc de fghi", s));
}
static void test_join_truncated_in_token(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxx";
size_t w = sc_str_join(s, tokens, ' ', sizeof(s));
// returns 'n' (sizeof(s))
assert(w == 6);
// is nul-terminated
assert(s[5] == '\0');
// copies the content as expected
assert(!strcmp("abc d", s));
}
static void test_join_truncated_before_sep(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxx";
size_t w = sc_str_join(s, tokens, ' ', sizeof(s));
// returns 'n' (sizeof(s))
assert(w == 7);
// is nul-terminated
assert(s[6] == '\0');
// copies the content as expected
assert(!strcmp("abc de", s));
}
static void test_join_truncated_after_sep(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxxx";
size_t w = sc_str_join(s, tokens, ' ', sizeof(s));
// returns 'n' (sizeof(s))
assert(w == 8);
// is nul-terminated
assert(s[7] == '\0');
// copies the content as expected
assert(!strcmp("abc de ", s));
}
static void test_quote(void) {
const char *s = "abcde";
char *out = sc_str_quote(s);
// add '"' at the beginning and the end
assert(!strcmp("\"abcde\"", out));
free(out);
}
static void test_utf8_truncate(void) {
const char *s = "aÉbÔc";
assert(strlen(s) == 7); // É and Ô are 2 bytes-wide
size_t count;
count = sc_str_utf8_truncation_index(s, 1);
assert(count == 1);
count = sc_str_utf8_truncation_index(s, 2);
assert(count == 1); // É is 2 bytes-wide
count = sc_str_utf8_truncation_index(s, 3);
assert(count == 3);
count = sc_str_utf8_truncation_index(s, 4);
assert(count == 4);
count = sc_str_utf8_truncation_index(s, 5);
assert(count == 4); // Ô is 2 bytes-wide
count = sc_str_utf8_truncation_index(s, 6);
assert(count == 6);
count = sc_str_utf8_truncation_index(s, 7);
assert(count == 7);
count = sc_str_utf8_truncation_index(s, 8);
assert(count == 7); // no more chars
}
static void test_parse_integer(void) {
long value;
bool ok = sc_str_parse_integer("1234", &value);
assert(ok);
assert(value == 1234);
ok = sc_str_parse_integer("-1234", &value);
assert(ok);
assert(value == -1234);
ok = sc_str_parse_integer("1234k", &value);
assert(!ok);
ok = sc_str_parse_integer("123456789876543212345678987654321", &value);
assert(!ok); // out-of-range
}
static void test_parse_integers(void) {
long values[5];
size_t count = sc_str_parse_integers("1234", ':', 5, values);
assert(count == 1);
assert(values[0] == 1234);
count = sc_str_parse_integers("1234:5678", ':', 5, values);
assert(count == 2);
assert(values[0] == 1234);
assert(values[1] == 5678);
count = sc_str_parse_integers("1234:5678", ':', 2, values);
assert(count == 2);
assert(values[0] == 1234);
assert(values[1] == 5678);
count = sc_str_parse_integers("1234:-5678", ':', 2, values);
assert(count == 2);
assert(values[0] == 1234);
assert(values[1] == -5678);
count = sc_str_parse_integers("1:2:3:4:5", ':', 5, values);
assert(count == 5);
assert(values[0] == 1);
assert(values[1] == 2);
assert(values[2] == 3);
assert(values[3] == 4);
assert(values[4] == 5);
count = sc_str_parse_integers("1234:5678", ':', 1, values);
assert(count == 0); // max_items == 1
count = sc_str_parse_integers("1:2:3:4:5", ':', 3, values);
assert(count == 0); // max_items == 3
count = sc_str_parse_integers(":1234", ':', 5, values);
assert(count == 0); // invalid
count = sc_str_parse_integers("1234:", ':', 5, values);
assert(count == 0); // invalid
count = sc_str_parse_integers("1234:", ':', 1, values);
assert(count == 0); // invalid, even when max_items == 1
count = sc_str_parse_integers("1234::5678", ':', 5, values);
assert(count == 0); // invalid
}
static void test_parse_integer_with_suffix(void) {
long value;
bool ok = sc_str_parse_integer_with_suffix("1234", &value);
assert(ok);
assert(value == 1234);
ok = sc_str_parse_integer_with_suffix("-1234", &value);
assert(ok);
assert(value == -1234);
ok = sc_str_parse_integer_with_suffix("1234k", &value);
assert(ok);
assert(value == 1234000);
ok = sc_str_parse_integer_with_suffix("1234m", &value);
assert(ok);
assert(value == 1234000000);
ok = sc_str_parse_integer_with_suffix("-1234k", &value);
assert(ok);
assert(value == -1234000);
ok = sc_str_parse_integer_with_suffix("-1234m", &value);
assert(ok);
assert(value == -1234000000);
ok = sc_str_parse_integer_with_suffix("123456789876543212345678987654321", &value);
assert(!ok); // out-of-range
char buf[32];
sprintf(buf, "%ldk", LONG_MAX / 2000);
ok = sc_str_parse_integer_with_suffix(buf, &value);
assert(ok);
assert(value == LONG_MAX / 2000 * 1000);
sprintf(buf, "%ldm", LONG_MAX / 2000);
ok = sc_str_parse_integer_with_suffix(buf, &value);
assert(!ok);
sprintf(buf, "%ldk", LONG_MIN / 2000);
ok = sc_str_parse_integer_with_suffix(buf, &value);
assert(ok);
assert(value == LONG_MIN / 2000 * 1000);
sprintf(buf, "%ldm", LONG_MIN / 2000);
ok = sc_str_parse_integer_with_suffix(buf, &value);
assert(!ok);
}
static void test_strlist_contains(void) {
assert(sc_str_list_contains("a,bc,def", ',', "bc"));
assert(!sc_str_list_contains("a,bc,def", ',', "b"));
assert(sc_str_list_contains("", ',', ""));
assert(sc_str_list_contains("abc,", ',', ""));
assert(sc_str_list_contains(",abc", ',', ""));
assert(sc_str_list_contains("abc,,def", ',', ""));
assert(!sc_str_list_contains("abc", ',', ""));
assert(sc_str_list_contains(",,|x", '|', ",,"));
assert(sc_str_list_contains("xyz", '\0', "xyz"));
}
static void test_wrap_lines(void) {
const char *s = "This is a text to test line wrapping. The lines must be "
"wrapped at a space or a line break.\n"
"\n"
"This rectangle must remains a rectangle because it is "
"drawn in lines having lengths lower than the specified "
"number of columns:\n"
" +----+\n"
" | |\n"
" +----+\n";
// |---- 1 1 2 2|
// |0 5 0 5 0 3| <-- 24 columns
const char *expected = " This is a text to\n"
" test line wrapping.\n"
" The lines must be\n"
" wrapped at a space\n"
" or a line break.\n"
" \n"
" This rectangle must\n"
" remains a rectangle\n"
" because it is drawn\n"
" in lines having\n"
" lengths lower than\n"
" the specified number\n"
" of columns:\n"
" +----+\n"
" | |\n"
" +----+\n";
char *formatted = sc_str_wrap_lines(s, 24, 4);
assert(formatted);
assert(!strcmp(formatted, expected));
free(formatted);
}
static void test_truncate(void) {
char s[] = "hello\nworld\n!";
size_t len = sc_str_truncate(s, sizeof(s), "\n");
assert(len == 5);
assert(!strcmp("hello", s));
char s2[] = "hello\r\nworkd\r\n!";
len = sc_str_truncate(s2, sizeof(s2), "\n\r");
assert(len == 5);
assert(!strcmp("hello", s));
char s3[] = "hello world\n!";
len = sc_str_truncate(s3, sizeof(s3), " \n\r");
assert(len == 5);
assert(!strcmp("hello", s3));
char s4[] = "hello ";
len = sc_str_truncate(s4, sizeof(s4), " \n\r");
assert(len == 5);
assert(!strcmp("hello", s4));
}
static void test_index_of_column(void) {
assert(sc_str_index_of_column("a bc d", 0, " ") == 0);
assert(sc_str_index_of_column("a bc d", 1, " ") == 2);
assert(sc_str_index_of_column("a bc d", 2, " ") == 6);
assert(sc_str_index_of_column("a bc d", 3, " ") == -1);
assert(sc_str_index_of_column("a ", 0, " ") == 0);
assert(sc_str_index_of_column("a ", 1, " ") == -1);
assert(sc_str_index_of_column("", 0, " ") == 0);
assert(sc_str_index_of_column("", 1, " ") == -1);
assert(sc_str_index_of_column("a \t \t bc \t d\t", 0, " \t") == 0);
assert(sc_str_index_of_column("a \t \t bc \t d\t", 1, " \t") == 8);
assert(sc_str_index_of_column("a \t \t bc \t d\t", 2, " \t") == 15);
assert(sc_str_index_of_column("a \t \t bc \t d\t", 3, " \t") == -1);
assert(sc_str_index_of_column(" a bc d", 1, " ") == 2);
}
static void test_remove_trailing_cr() {
char s[] = "abc\r";
sc_str_remove_trailing_cr(s, sizeof(s) - 1);
assert(!strcmp(s, "abc"));
char s2[] = "def\r\r\r\r";
sc_str_remove_trailing_cr(s2, sizeof(s2) - 1);
assert(!strcmp(s2, "def"));
char s3[] = "adb\rdef\r";
sc_str_remove_trailing_cr(s3, sizeof(s3) - 1);
assert(!strcmp(s3, "adb\rdef"));
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_strncpy_simple();
test_strncpy_just_fit();
test_strncpy_truncated();
test_join_simple();
test_join_just_fit();
test_join_truncated_in_token();
test_join_truncated_before_sep();
test_join_truncated_after_sep();
test_quote();
test_utf8_truncate();
test_parse_integer();
test_parse_integers();
test_parse_integer_with_suffix();
test_strlist_contains();
test_wrap_lines();
test_truncate();
test_index_of_column();
test_remove_trailing_cr();
return 0;
}

48
app/tests/test_strbuf.c Normal file
View File

@ -0,0 +1,48 @@
#include "common.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "util/strbuf.h"
static void test_strbuf_simple(void) {
struct sc_strbuf buf;
bool ok = sc_strbuf_init(&buf, 10);
assert(ok);
ok = sc_strbuf_append_staticstr(&buf, "Hello");
assert(ok);
ok = sc_strbuf_append_char(&buf, ' ');
assert(ok);
ok = sc_strbuf_append_staticstr(&buf, "world");
assert(ok);
ok = sc_strbuf_append_staticstr(&buf, "!\n");
assert(ok);
ok = sc_strbuf_append_staticstr(&buf, "This is a test");
assert(ok);
ok = sc_strbuf_append_n(&buf, '.', 3);
assert(ok);
assert(!strcmp(buf.s, "Hello world!\nThis is a test..."));
sc_strbuf_shrink(&buf);
assert(buf.len == buf.cap);
assert(!strcmp(buf.s, "Hello world!\nThis is a test..."));
free(buf.s);
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_strbuf_simple();
return 0;
}

View File

@ -1,321 +0,0 @@
#include "common.h"
#include <assert.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include "util/str_util.h"
static void test_xstrncpy_simple(void) {
char s[] = "xxxxxxxxxx";
size_t w = xstrncpy(s, "abcdef", sizeof(s));
// returns strlen of copied string
assert(w == 6);
// is nul-terminated
assert(s[6] == '\0');
// does not write useless bytes
assert(s[7] == 'x');
// copies the content as expected
assert(!strcmp("abcdef", s));
}
static void test_xstrncpy_just_fit(void) {
char s[] = "xxxxxx";
size_t w = xstrncpy(s, "abcdef", sizeof(s));
// returns strlen of copied string
assert(w == 6);
// is nul-terminated
assert(s[6] == '\0');
// copies the content as expected
assert(!strcmp("abcdef", s));
}
static void test_xstrncpy_truncated(void) {
char s[] = "xxx";
size_t w = xstrncpy(s, "abcdef", sizeof(s));
// returns 'n' (sizeof(s))
assert(w == 4);
// is nul-terminated
assert(s[3] == '\0');
// copies the content as expected
assert(!strncmp("abcdef", s, 3));
}
static void test_xstrjoin_simple(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxxxxxxxxxx";
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
// returns strlen of concatenation
assert(w == 11);
// is nul-terminated
assert(s[11] == '\0');
// does not write useless bytes
assert(s[12] == 'x');
// copies the content as expected
assert(!strcmp("abc de fghi", s));
}
static void test_xstrjoin_just_fit(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxxxxxxx";
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
// returns strlen of concatenation
assert(w == 11);
// is nul-terminated
assert(s[11] == '\0');
// copies the content as expected
assert(!strcmp("abc de fghi", s));
}
static void test_xstrjoin_truncated_in_token(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxx";
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
// returns 'n' (sizeof(s))
assert(w == 6);
// is nul-terminated
assert(s[5] == '\0');
// copies the content as expected
assert(!strcmp("abc d", s));
}
static void test_xstrjoin_truncated_before_sep(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxx";
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
// returns 'n' (sizeof(s))
assert(w == 7);
// is nul-terminated
assert(s[6] == '\0');
// copies the content as expected
assert(!strcmp("abc de", s));
}
static void test_xstrjoin_truncated_after_sep(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxxx";
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
// returns 'n' (sizeof(s))
assert(w == 8);
// is nul-terminated
assert(s[7] == '\0');
// copies the content as expected
assert(!strcmp("abc de ", s));
}
static void test_strquote(void) {
const char *s = "abcde";
char *out = strquote(s);
// add '"' at the beginning and the end
assert(!strcmp("\"abcde\"", out));
free(out);
}
static void test_utf8_truncate(void) {
const char *s = "aÉbÔc";
assert(strlen(s) == 7); // É and Ô are 2 bytes-wide
size_t count;
count = utf8_truncation_index(s, 1);
assert(count == 1);
count = utf8_truncation_index(s, 2);
assert(count == 1); // É is 2 bytes-wide
count = utf8_truncation_index(s, 3);
assert(count == 3);
count = utf8_truncation_index(s, 4);
assert(count == 4);
count = utf8_truncation_index(s, 5);
assert(count == 4); // Ô is 2 bytes-wide
count = utf8_truncation_index(s, 6);
assert(count == 6);
count = utf8_truncation_index(s, 7);
assert(count == 7);
count = utf8_truncation_index(s, 8);
assert(count == 7); // no more chars
}
static void test_parse_integer(void) {
long value;
bool ok = parse_integer("1234", &value);
assert(ok);
assert(value == 1234);
ok = parse_integer("-1234", &value);
assert(ok);
assert(value == -1234);
ok = parse_integer("1234k", &value);
assert(!ok);
ok = parse_integer("123456789876543212345678987654321", &value);
assert(!ok); // out-of-range
}
static void test_parse_integers(void) {
long values[5];
size_t count = parse_integers("1234", ':', 5, values);
assert(count == 1);
assert(values[0] == 1234);
count = parse_integers("1234:5678", ':', 5, values);
assert(count == 2);
assert(values[0] == 1234);
assert(values[1] == 5678);
count = parse_integers("1234:5678", ':', 2, values);
assert(count == 2);
assert(values[0] == 1234);
assert(values[1] == 5678);
count = parse_integers("1234:-5678", ':', 2, values);
assert(count == 2);
assert(values[0] == 1234);
assert(values[1] == -5678);
count = parse_integers("1:2:3:4:5", ':', 5, values);
assert(count == 5);
assert(values[0] == 1);
assert(values[1] == 2);
assert(values[2] == 3);
assert(values[3] == 4);
assert(values[4] == 5);
count = parse_integers("1234:5678", ':', 1, values);
assert(count == 0); // max_items == 1
count = parse_integers("1:2:3:4:5", ':', 3, values);
assert(count == 0); // max_items == 3
count = parse_integers(":1234", ':', 5, values);
assert(count == 0); // invalid
count = parse_integers("1234:", ':', 5, values);
assert(count == 0); // invalid
count = parse_integers("1234:", ':', 1, values);
assert(count == 0); // invalid, even when max_items == 1
count = parse_integers("1234::5678", ':', 5, values);
assert(count == 0); // invalid
}
static void test_parse_integer_with_suffix(void) {
long value;
bool ok = parse_integer_with_suffix("1234", &value);
assert(ok);
assert(value == 1234);
ok = parse_integer_with_suffix("-1234", &value);
assert(ok);
assert(value == -1234);
ok = parse_integer_with_suffix("1234k", &value);
assert(ok);
assert(value == 1234000);
ok = parse_integer_with_suffix("1234m", &value);
assert(ok);
assert(value == 1234000000);
ok = parse_integer_with_suffix("-1234k", &value);
assert(ok);
assert(value == -1234000);
ok = parse_integer_with_suffix("-1234m", &value);
assert(ok);
assert(value == -1234000000);
ok = parse_integer_with_suffix("123456789876543212345678987654321", &value);
assert(!ok); // out-of-range
char buf[32];
sprintf(buf, "%ldk", LONG_MAX / 2000);
ok = parse_integer_with_suffix(buf, &value);
assert(ok);
assert(value == LONG_MAX / 2000 * 1000);
sprintf(buf, "%ldm", LONG_MAX / 2000);
ok = parse_integer_with_suffix(buf, &value);
assert(!ok);
sprintf(buf, "%ldk", LONG_MIN / 2000);
ok = parse_integer_with_suffix(buf, &value);
assert(ok);
assert(value == LONG_MIN / 2000 * 1000);
sprintf(buf, "%ldm", LONG_MIN / 2000);
ok = parse_integer_with_suffix(buf, &value);
assert(!ok);
}
static void test_strlist_contains(void) {
assert(strlist_contains("a,bc,def", ',', "bc"));
assert(!strlist_contains("a,bc,def", ',', "b"));
assert(strlist_contains("", ',', ""));
assert(strlist_contains("abc,", ',', ""));
assert(strlist_contains(",abc", ',', ""));
assert(strlist_contains("abc,,def", ',', ""));
assert(!strlist_contains("abc", ',', ""));
assert(strlist_contains(",,|x", '|', ",,"));
assert(strlist_contains("xyz", '\0', "xyz"));
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_xstrncpy_simple();
test_xstrncpy_just_fit();
test_xstrncpy_truncated();
test_xstrjoin_simple();
test_xstrjoin_just_fit();
test_xstrjoin_truncated_in_token();
test_xstrjoin_truncated_before_sep();
test_xstrjoin_truncated_after_sep();
test_strquote();
test_utf8_truncate();
test_parse_integer();
test_parse_integers();
test_parse_integer_with_suffix();
test_strlist_contains();
return 0;
}

View File

@ -2,8 +2,8 @@
set -e set -e
BUILDDIR=build-auto BUILDDIR=build-auto
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.19/scrcpy-server-v1.19 PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.20/scrcpy-server-v1.20
PREBUILT_SERVER_SHA256=876f9322182e6aac6a58db1334f4225855ef3a17eaebc80aab6601d9d1ecb867 PREBUILT_SERVER_SHA256=b20aee4951f99b060c4a44000ba94de973f9604758ef62beb253b371aad3df34
echo "[scrcpy] Downloading prebuilt server..." echo "[scrcpy] Downloading prebuilt server..."
wget "$PREBUILT_SERVER_URL" -O scrcpy-server wget "$PREBUILT_SERVER_URL" -O scrcpy-server

View File

@ -1,5 +1,5 @@
project('scrcpy', 'c', project('scrcpy', 'c',
version: '1.19', version: '1.20',
meson_version: '>= 0.48', meson_version: '>= 0.48',
default_options: [ default_options: [
'c_std=c11', 'c_std=c11',

View File

@ -6,8 +6,8 @@ android {
applicationId "com.genymobile.scrcpy" applicationId "com.genymobile.scrcpy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 31 targetSdkVersion 31
versionCode 11900 versionCode 12000
versionName "1.19" versionName "1.20"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
@ -20,7 +20,7 @@ android {
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation 'junit:junit:4.13' testImplementation 'junit:junit:4.13.1'
} }
apply from: "$project.rootDir/config/android-checkstyle.gradle" apply from: "$project.rootDir/config/android-checkstyle.gradle"

View File

@ -12,15 +12,17 @@
set -e set -e
SCRCPY_DEBUG=false SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=1.19 SCRCPY_VERSION_NAME=1.20
PLATFORM=${ANDROID_PLATFORM:-30} PLATFORM_VERSION=31
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-30.0.0} PLATFORM=${ANDROID_PLATFORM:-$PLATFORM_VERSION}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-31.0.0}
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})" BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
CLASSES_DIR="$BUILD_DIR/classes" CLASSES_DIR="$BUILD_DIR/classes"
SERVER_DIR=$(dirname "$0") SERVER_DIR=$(dirname "$0")
SERVER_BINARY=scrcpy-server SERVER_BINARY=scrcpy-server
ANDROID_JAR="$ANDROID_HOME/platforms/android-$PLATFORM/android.jar"
echo "Platform: android-$PLATFORM" echo "Platform: android-$PLATFORM"
echo "Build-tools: $BUILD_TOOLS" echo "Build-tools: $BUILD_TOOLS"
@ -47,23 +49,40 @@ cd "$SERVER_DIR/src/main/aidl"
echo "Compiling java sources..." echo "Compiling java sources..."
cd ../java cd ../java
javac -bootclasspath "$ANDROID_HOME/platforms/android-$PLATFORM/android.jar" \ javac -bootclasspath "$ANDROID_JAR" -cp "$CLASSES_DIR" -d "$CLASSES_DIR" \
-cp "$CLASSES_DIR" -d "$CLASSES_DIR" -source 1.8 -target 1.8 \ -source 1.8 -target 1.8 \
com/genymobile/scrcpy/*.java \ com/genymobile/scrcpy/*.java \
com/genymobile/scrcpy/wrappers/*.java com/genymobile/scrcpy/wrappers/*.java
echo "Dexing..." echo "Dexing..."
cd "$CLASSES_DIR" cd "$CLASSES_DIR"
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \
--output "$BUILD_DIR/classes.dex" \
android/view/*.class \
android/content/*.class \
com/genymobile/scrcpy/*.class \
com/genymobile/scrcpy/wrappers/*.class
echo "Archiving..." if [[ $PLATFORM_VERSION -lt 31 ]]
cd "$BUILD_DIR" then
jar cvf "$SERVER_BINARY" classes.dex # use dx
rm -rf classes.dex classes "$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \
--output "$BUILD_DIR/classes.dex" \
android/view/*.class \
android/content/*.class \
com/genymobile/scrcpy/*.class \
com/genymobile/scrcpy/wrappers/*.class
echo "Archiving..."
cd "$BUILD_DIR"
jar cvf "$SERVER_BINARY" classes.dex
rm -rf classes.dex classes
else
# use d8
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/d8" --classpath "$ANDROID_JAR" \
--output "$BUILD_DIR/classes.zip" \
android/view/*.class \
android/content/*.class \
com/genymobile/scrcpy/*.class \
com/genymobile/scrcpy/wrappers/*.class
cd "$BUILD_DIR"
mv classes.zip "$SERVER_BINARY"
rm -rf classes
fi
echo "Server generated in $BUILD_DIR/$SERVER_BINARY" echo "Server generated in $BUILD_DIR/$SERVER_BINARY"

View File

@ -1,6 +1,5 @@
package com.genymobile.scrcpy; package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ContentProvider;
import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.os.Parcel; import android.os.Parcel;
@ -166,14 +165,21 @@ public final class CleanUp {
if (config.disableShowTouches || config.restoreStayOn != -1) { if (config.disableShowTouches || config.restoreStayOn != -1) {
ServiceManager serviceManager = new ServiceManager(); ServiceManager serviceManager = new ServiceManager();
try (ContentProvider settings = serviceManager.getActivityManager().createSettingsProvider()) { Settings settings = new Settings(serviceManager);
if (config.disableShowTouches) { if (config.disableShowTouches) {
Ln.i("Disabling \"show touches\""); Ln.i("Disabling \"show touches\"");
settings.putValue(ContentProvider.TABLE_SYSTEM, "show_touches", "0"); try {
settings.putValue(Settings.TABLE_SYSTEM, "show_touches", "0");
} catch (SettingsException e) {
Ln.e("Could not restore \"show_touches\"", e);
} }
if (config.restoreStayOn != -1) { }
Ln.i("Restoring \"stay awake\""); if (config.restoreStayOn != -1) {
settings.putValue(ContentProvider.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn)); Ln.i("Restoring \"stay awake\"");
try {
settings.putValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn));
} catch (SettingsException e) {
Ln.e("Could not restore \"stay_on_while_plugged_in\"", e);
} }
} }
} }

View File

@ -21,7 +21,7 @@ public class CodecOption {
} }
public static List<CodecOption> parse(String codecOptions) { public static List<CodecOption> parse(String codecOptions) {
if ("-".equals(codecOptions)) { if (codecOptions.isEmpty()) {
return null; return null;
} }

View File

@ -0,0 +1,33 @@
package com.genymobile.scrcpy;
import java.io.IOException;
import java.util.Arrays;
import java.util.Scanner;
public final class Command {
private Command() {
// not instantiable
}
public static void exec(String... cmd) throws IOException, InterruptedException {
Process process = Runtime.getRuntime().exec(cmd);
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IOException("Command " + Arrays.toString(cmd) + " returned with value " + exitCode);
}
}
public static String execReadLine(String... cmd) throws IOException, InterruptedException {
String result = null;
Process process = Runtime.getRuntime().exec(cmd);
Scanner scanner = new Scanner(process.getInputStream());
if (scanner.hasNextLine()) {
result = scanner.nextLine();
}
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IOException("Command " + Arrays.toString(cmd) + " returned with value " + exitCode);
}
return result;
}
}

View File

@ -18,6 +18,8 @@ public final class ControlMessage {
public static final int TYPE_SET_SCREEN_POWER_MODE = 10; public static final int TYPE_SET_SCREEN_POWER_MODE = 10;
public static final int TYPE_ROTATE_DEVICE = 11; public static final int TYPE_ROTATE_DEVICE = 11;
public static final long SEQUENCE_INVALID = 0;
private int type; private int type;
private String text; private String text;
private int metaState; // KeyEvent.META_* private int metaState; // KeyEvent.META_*
@ -31,6 +33,7 @@ public final class ControlMessage {
private int vScroll; private int vScroll;
private boolean paste; private boolean paste;
private int repeat; private int repeat;
private long sequence;
private ControlMessage() { private ControlMessage() {
} }
@ -79,9 +82,10 @@ public final class ControlMessage {
return msg; return msg;
} }
public static ControlMessage createSetClipboard(String text, boolean paste) { public static ControlMessage createSetClipboard(long sequence, String text, boolean paste) {
ControlMessage msg = new ControlMessage(); ControlMessage msg = new ControlMessage();
msg.type = TYPE_SET_CLIPBOARD; msg.type = TYPE_SET_CLIPBOARD;
msg.sequence = sequence;
msg.text = text; msg.text = text;
msg.paste = paste; msg.paste = paste;
return msg; return msg;
@ -154,4 +158,8 @@ public final class ControlMessage {
public int getRepeat() { public int getRepeat() {
return repeat; return repeat;
} }
public long getSequence() {
return sequence;
}
} }

View File

@ -13,11 +13,11 @@ public class ControlMessageReader {
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20; static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
static final int BACK_OR_SCREEN_ON_LENGTH = 1; static final int BACK_OR_SCREEN_ON_LENGTH = 1;
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1; static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 9;
private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 6; // type: 1 byte; paste flag: 1 byte; length: 4 bytes public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 14; // type: 1 byte; sequence: 8 bytes; paste flag: 1 byte; length: 4 bytes
public static final int INJECT_TEXT_MAX_LENGTH = 300; public static final int INJECT_TEXT_MAX_LENGTH = 300;
private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE]; private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE];
@ -166,12 +166,13 @@ public class ControlMessageReader {
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) { if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
return null; return null;
} }
long sequence = buffer.getLong();
boolean paste = buffer.get() != 0; boolean paste = buffer.get() != 0;
String text = parseString(); String text = parseString();
if (text == null) { if (text == null) {
return null; return null;
} }
return ControlMessage.createSetClipboard(text, paste); return ControlMessage.createSetClipboard(sequence, text, paste);
} }
private ControlMessage parseSetScreenPowerMode() { private ControlMessage parseSetScreenPowerMode() {

View File

@ -120,7 +120,12 @@ public class Controller {
} }
break; break;
case ControlMessage.TYPE_SET_CLIPBOARD: case ControlMessage.TYPE_SET_CLIPBOARD:
long sequence = msg.getSequence();
setClipboard(msg.getText(), msg.getPaste()); setClipboard(msg.getText(), msg.getPaste());
if (sequence != ControlMessage.SEQUENCE_INVALID) {
// Acknowledgement requested
sender.pushAckClipboard(sequence);
}
break; break;
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE: case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
if (device.supportsInputEvents()) { if (device.supportsInputEvents()) {

View File

@ -1,7 +1,6 @@
package com.genymobile.scrcpy; package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ClipboardManager; import com.genymobile.scrcpy.wrappers.ClipboardManager;
import com.genymobile.scrcpy.wrappers.ContentProvider;
import com.genymobile.scrcpy.wrappers.InputManager; import com.genymobile.scrcpy.wrappers.InputManager;
import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.ServiceManager;
import com.genymobile.scrcpy.wrappers.SurfaceControl; import com.genymobile.scrcpy.wrappers.SurfaceControl;
@ -29,6 +28,7 @@ public final class Device {
public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2; public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2;
private static final ServiceManager SERVICE_MANAGER = new ServiceManager(); private static final ServiceManager SERVICE_MANAGER = new ServiceManager();
private static final Settings SETTINGS = new Settings(SERVICE_MANAGER);
public interface RotationListener { public interface RotationListener {
void onRotationChanged(int rotation); void onRotationChanged(int rotation);
@ -65,7 +65,7 @@ public final class Device {
int displayInfoFlags = displayInfo.getFlags(); int displayInfoFlags = displayInfo.getFlags();
screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockedVideoOrientation()); screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockVideoOrientation());
layerStack = displayInfo.getLayerStack(); layerStack = displayInfo.getLayerStack();
SERVICE_MANAGER.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() { SERVICE_MANAGER.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() {
@ -82,8 +82,8 @@ public final class Device {
} }
}, displayId); }, displayId);
if (options.getControl()) { if (options.getControl() && options.getClipboardAutosync()) {
// If control is enabled, synchronize Android clipboard to the computer automatically // If control and autosync are enabled, synchronize Android clipboard to the computer automatically
ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager(); ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager();
if (clipboardManager != null) { if (clipboardManager != null) {
clipboardManager.addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() { clipboardManager.addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() {
@ -296,7 +296,7 @@ public final class Device {
} }
} }
public static ContentProvider createSettingsProvider() { public static Settings getSettings() {
return SERVICE_MANAGER.getActivityManager().createSettingsProvider(); return SETTINGS;
} }
} }

View File

@ -3,9 +3,13 @@ package com.genymobile.scrcpy;
public final class DeviceMessage { public final class DeviceMessage {
public static final int TYPE_CLIPBOARD = 0; public static final int TYPE_CLIPBOARD = 0;
public static final int TYPE_ACK_CLIPBOARD = 1;
public static final long SEQUENCE_INVALID = ControlMessage.SEQUENCE_INVALID;
private int type; private int type;
private String text; private String text;
private long sequence;
private DeviceMessage() { private DeviceMessage() {
} }
@ -17,6 +21,13 @@ public final class DeviceMessage {
return event; return event;
} }
public static DeviceMessage createAckClipboard(long sequence) {
DeviceMessage event = new DeviceMessage();
event.type = TYPE_ACK_CLIPBOARD;
event.sequence = sequence;
return event;
}
public int getType() { public int getType() {
return type; return type;
} }
@ -24,4 +35,8 @@ public final class DeviceMessage {
public String getText() { public String getText() {
return text; return text;
} }
public long getSequence() {
return sequence;
}
} }

View File

@ -8,6 +8,8 @@ public final class DeviceMessageSender {
private String clipboardText; private String clipboardText;
private long ack;
public DeviceMessageSender(DesktopConnection connection) { public DeviceMessageSender(DesktopConnection connection) {
this.connection = connection; this.connection = connection;
} }
@ -17,18 +19,34 @@ public final class DeviceMessageSender {
notify(); notify();
} }
public synchronized void pushAckClipboard(long sequence) {
ack = sequence;
notify();
}
public void loop() throws IOException, InterruptedException { public void loop() throws IOException, InterruptedException {
while (true) { while (true) {
String text; String text;
long sequence;
synchronized (this) { synchronized (this) {
while (clipboardText == null) { while (ack == DeviceMessage.SEQUENCE_INVALID && clipboardText == null) {
wait(); wait();
} }
text = clipboardText; text = clipboardText;
clipboardText = null; clipboardText = null;
sequence = ack;
ack = DeviceMessage.SEQUENCE_INVALID;
}
if (sequence != DeviceMessage.SEQUENCE_INVALID) {
DeviceMessage event = DeviceMessage.createAckClipboard(sequence);
connection.sendDeviceMessage(event);
}
if (text != null) {
DeviceMessage event = DeviceMessage.createClipboard(text);
connection.sendDeviceMessage(event);
} }
DeviceMessage event = DeviceMessage.createClipboard(text);
connection.sendDeviceMessage(event);
} }
} }
} }

View File

@ -15,7 +15,7 @@ public class DeviceMessageWriter {
public void writeTo(DeviceMessage msg, OutputStream output) throws IOException { public void writeTo(DeviceMessage msg, OutputStream output) throws IOException {
buffer.clear(); buffer.clear();
buffer.put((byte) DeviceMessage.TYPE_CLIPBOARD); buffer.put((byte) msg.getType());
switch (msg.getType()) { switch (msg.getType()) {
case DeviceMessage.TYPE_CLIPBOARD: case DeviceMessage.TYPE_CLIPBOARD:
String text = msg.getText(); String text = msg.getText();
@ -25,6 +25,10 @@ public class DeviceMessageWriter {
buffer.put(raw, 0, len); buffer.put(raw, 0, len);
output.write(rawBuffer, 0, buffer.position()); output.write(rawBuffer, 0, buffer.position());
break; break;
case DeviceMessage.TYPE_ACK_CLIPBOARD:
buffer.putLong(msg.getSequence());
output.write(rawBuffer, 0, buffer.position());
break;
default: default:
Ln.w("Unknown device message: " + msg.getType()); Ln.w("Unknown device message: " + msg.getType());
break; break;

View File

@ -57,13 +57,20 @@ public final class Ln {
} }
} }
public static void w(String message) { public static void w(String message, Throwable throwable) {
if (isEnabled(Level.WARN)) { if (isEnabled(Level.WARN)) {
Log.w(TAG, message); Log.w(TAG, message, throwable);
System.out.println(PREFIX + "WARN: " + message); System.out.println(PREFIX + "WARN: " + message);
if (throwable != null) {
throwable.printStackTrace();
}
} }
} }
public static void w(String message) {
w(message, null);
}
public static void e(String message, Throwable throwable) { public static void e(String message, Throwable throwable) {
if (isEnabled(Level.ERROR)) { if (isEnabled(Level.ERROR)) {
Log.e(TAG, message, throwable); Log.e(TAG, message, throwable);

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