Compare commits

...

141 Commits

Author SHA1 Message Date
d23b3e88a4 Replace '%g' by '%f' as printf format
For some reason, '%g' does not work correctly with MinGW.

Refs #3369 <https://github.com/Genymobile/scrcpy/pull/3369>
PR #3399 <https://github.com/Genymobile/scrcpy/pull/3399>
2022-08-03 23:25:09 +02:00
a47848f304 Detect Windows using _WIN32 in network util
For consistency, always use _WIN32 instead of a mix of __WINDOWS__ and
_WIN32.
2022-07-27 14:54:27 +02:00
db8c1ce8e1 Fix protocol documentation in comments
Flags were in the correct order in the schema, but their description
were reversed.
2022-07-20 11:41:04 +02:00
4aeb78ece2 Add missing allocation failure check 2022-07-19 12:17:02 +02:00
396e4bd925 Add missing LOG_OOM() on malloc failure 2022-07-19 12:15:06 +02:00
7f2f5950f2 Remove useless dependencies reference
There is no libs/ directory with local jar files.
2022-06-20 21:23:05 +02:00
af4b7855e1 Remove unused stream.h
The file was not removed by 7dec225ceb.
2022-06-09 15:02:42 +02:00
b1d8c72780 Rename function to simplify
For consistency with sc_adb_parse_device(), do not include "from_output"
in the function name.
2022-06-09 15:02:42 +02:00
55e65fa270 Add missing return 0 in tests 2022-06-09 15:02:42 +02:00
69fb5f6ee1 Fix function declarations
Add missing void in function parameters list.
2022-06-09 15:02:42 +02:00
faf4535487 Reduce SHA-256 size in README and BUILD
This avoids breaking the page layout on GitHub.
2022-04-28 21:34:56 +02:00
3a99e129e6 Update links to v1.24 2022-04-28 21:03:03 +02:00
ef13d394fd Bump version to 1.24 2022-04-28 20:46:28 +02:00
0049893e10 Merge branch 'master' into dev 2022-04-28 20:46:19 +02:00
2f038c834a Revert "Make OTG window resizable"
On Windows and macOS, resizing blocks the event loop. Handling it
properly would require the same workaround as done in screen.c.

This reverts commit 436b368f9d.
2022-04-28 20:45:46 +02:00
76b3fcf986 Fix inverted check
SDL_RenderSetLogicalSize() returns 0 on success.

Refs fc8942aa03
2022-04-28 20:45:46 +02:00
05d84084ef Fix release script for platform-tools 33.0.1
These paths were not updated by commit
b8d78743f7.
2022-04-28 20:45:38 +02:00
471a360099 Use quotes for commands in documentation 2022-04-28 19:50:35 +02:00
349dcd8e7b Update installed files list in BUILD documentation 2022-04-28 19:49:34 +02:00
f9e3275d4e Upgrade FFmpeg (5.0.1) for Windows 64-bit
Use the latest version of FFmpeg in Windows 64-bit releases.
2022-04-28 19:32:14 +02:00
91706ae3d0 Upgrade SDL (2.0.22) for Windows
Include the latest version of SDL in Windows releases.
2022-04-28 19:23:59 +02:00
854a56e588 Enable linear filtering in OTG mode
This improves the icon quality with non-standard window size.

PR #3219 <https://github.com/Genymobile/scrcpy/pull/3219>
2022-04-28 19:18:16 +02:00
436b368f9d Make OTG window resizable
PR #3219 <https://github.com/Genymobile/scrcpy/pull/3219>
2022-04-28 19:18:12 +02:00
fc8942aa03 Apply requested window size in OTG mode
Fixes #3099 <https://github.com/Genymobile/scrcpy/issues/3099>
PR #3219 <https://github.com/Genymobile/scrcpy/pull/3219>
2022-04-28 19:18:09 +02:00
c6d9711109 Create OTG window with HIGHDPI flag
This will avoid poor quality with HiDPI displays.

PR #3219 <https://github.com/Genymobile/scrcpy/pull/3219>
2022-04-28 19:17:49 +02:00
0fca2ad830 Add option to not power on on start
By default, on start, the device is powered on. To prevent this
behavior, add a new option --no-power-on.

Fixes #3148 <https://github.com/Genymobile/scrcpy/issues/3148>
PR #3210 <https://github.com/Genymobile/scrcpy/pull/3210>
2022-04-28 19:12:52 +02:00
326897a0d4 Add missing mouse shortcuts in --help
Document 4th-click and 5th-click shortcuts.

Fixes #3122 <https://github.com/Genymobile/scrcpy/issues/3122>
2022-04-25 18:35:06 +02:00
a90dfb46bc Fix GitHub case in BUILD
Replace "Github" with "GitHub".

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-04-25 18:25:59 +02:00
85512b1467 Fix typo in German README
Replace "Wifi" with "Wi-Fi", as in English and other translations.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-04-25 18:25:59 +02:00
c05981587f Fix typos in Indonesian README
Fix `code` blocks.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-04-25 18:25:47 +02:00
4db97531e8 Upgrade libusb (1.0.26) for Windows
Upgrade and enable libusb support for Windows 32-bit builds.

Refs #3011 <https://github.com/Genymobile/scrcpy/pull/3011>
Fixes #3204 <https://github.com/Genymobile/scrcpy/issues/3204>
PR #3206 <https://github.com/Genymobile/scrcpy/pull/3206>
2022-04-22 13:34:58 +02:00
b8d78743f7 Upgrade platform-tools (33.0.1) for Windows
PR #3206 <https://github.com/Genymobile/scrcpy/pull/3206>
2022-04-22 13:34:11 +02:00
6a4a4a283d Remove obsolete alternative injection method
The previous commit replaced the IInterface instance (the "input"
service) by the InputManager instance (retrieved by
InputManager.getInstance()).

Both define an "injectInputEvent" method, but the alternate version
(probably) does not concern the InputManager.

This reverts commit b7a06278fe.

PR #3190 <https://github.com/Genymobile/scrcpy/pull/3190>
2022-04-22 09:52:41 +02:00
7d8b72d4a6 Adapt event injection to Android 13
Using the "input" service results in a permission error in Android 13.

Use the InputManager instance (retrieved by InputManager.getInstance())
instead.

Fixes #3186 <https://github.com/Genymobile/scrcpy/issues/3186>
PR #3190 <https://github.com/Genymobile/scrcpy/pull/3190>
2022-04-22 09:52:41 +02:00
3c8ebf9abd Improve README
Improve clarity, grammar, consistency, punctuation, and formatting.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-04-16 10:03:52 +02:00
53b1e16f42 Fix typos/grammar issues in README
PR #3174 <https://github.com/Genymobile/scrcpy/pull/3174>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-04-14 10:18:45 +02:00
fc65c6cc4b Update README.it.md to v1.23
PR #3151 <https://github.com/Genymobile/scrcpy/pull/3151>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-04-14 10:11:03 +02:00
fa5b2a29e9 Add missing SC_ prefix to header guards 2022-04-12 23:59:01 +02:00
0c94887075 Add missing include
Refs c3d45c8397
2022-04-12 23:51:05 +02:00
7b7cfc3a3e Fix reference to FAQ in README
PR #3065 <https://github.com/Genymobile/scrcpy/pull/3065>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-04-10 13:57:44 +02:00
88543cb545 Fix icon path in ./run
The data/ directory has been moved to app/data/.

Refs 36c75e15b8
2022-03-30 14:00:05 +02:00
aaf3869a54 Fix OpenGL ES prefix skip 2022-03-30 12:01:01 +02:00
2edc73e4b8 Improve README.zh-Hans.md
Fix misleading zoom instruction, which gave opposite action steps.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-03-25 08:38:58 +01:00
c3d45c8397 Consider emulators as TCP/IP devices
Emulators were wrongly considered as USB devices, so they were selected
using -d instead of -e (which was inconsistent with the adb behavior).

Refs #3005 <https://github.com/Genymobile/scrcpy/issues/3005>
Refs #3137 <https://github.com/Genymobile/scrcpy/issues/3137>
2022-03-22 21:08:08 +01:00
e56f2ac7a9 Log an error on unexpected device state
Refs #3129 <https://github.com/Genymobile/scrcpy/issues/3129>
2022-03-20 14:56:52 +01:00
4ce7af42c6 Use $ANDROID_SERIAL if no selector is specified
Like adb, read the ANDROID_SERIAL environment variable to select a
device by serial if no explicit selection (-s, -d, -e or --tcpip=<addr>)
is provided via the command line.

Fixes #3111 <https://github.com/Genymobile/scrcpy/issues/3111>
PR #3113 <https://github.com/Genymobile/scrcpy/pull/3113>
2022-03-15 08:32:34 +01:00
b1dbc30072 Document exit status in --help
Refs #3085 <https://github.com/Genymobile/scrcpy/pull/3085>
2022-03-10 09:13:21 +01:00
b3f5dfe1de Add specific exit code for device disconnection
Modify the return logic such that exit code 1 is used when the initial
connection fails, but if a session is established, and then the device
disconnects, exit code 2 is emitted.

Fixes #3083 <https://github.com/Genymobile/scrcpy/issues/3083>
PR #3085 <https://github.com/Genymobile/scrcpy/pull/3085>
Signed-off-by: martin f. krafft <madduck@madduck.net>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-03-06 22:19:46 +01:00
1f4c801f3c Report server connection state
We must distinguish 3 cases for await_for_server():
 - an error occurred
 - no error occurred, the device is connected
 - no error occurred, the device is not connected (user requested to
   quit)

For this purpose, use an additional output parameter to indicate if the
device is connected (only set when no error occurs).

Refs #3085 <https://github.com/Genymobile/scrcpy/pull/3085>
2022-03-06 22:16:13 +01:00
8d91cda4f6 Improve HID event push error message
On HID event push failure, add the event type in the error message.
2022-02-24 23:28:20 +01:00
59656fe649 Fix typo in error message 2022-02-24 23:26:12 +01:00
e4bb2b8728 Add libusb error log
Log libusb_get_string_descriptor_ascii() errors.

Refs #3050 <https://github.com/Genymobile/scrcpy/issues/3050>
2022-02-24 23:25:02 +01:00
adbe7908c6 Fix icon path in README
The data/ directory was moved to app/data/.

Refs 36c75e15b8
2022-02-23 01:21:18 +01:00
49434da36e Update links to v1.23 2022-02-22 23:48:00 +01:00
7deccef1c2 Bump version to 1.23 2022-02-22 21:01:55 +01:00
977735f916 Merge branch 'master' into dev 2022-02-22 21:01:43 +01:00
71ef5cc0a9 Add missing include for vector
Include stdlib.h for realloc().
2022-02-22 21:00:43 +01:00
4ab4548769 Add contact links to the README
Add Reddit and Twitter links (and an additional link to the GitHub
issues).
2022-02-22 19:36:22 +01:00
3ce6f8ca91 Add Bash completion script
Fixes #2930 <https://github.com/Genymobile/scrcpy/issues/2930>
Refs #3012 <https://github.com/Genymobile/scrcpy/pull/3012>
2022-02-22 19:22:12 +01:00
26953784d9 Add ZSH completion script
PR #3012 <https://github.com/Genymobile/scrcpy/pull/3012>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-02-22 19:15:27 +01:00
2716385887 Move "Device unauthorized" in FAQ
Put "Device not detected" first.
2022-02-22 19:12:02 +01:00
1f951f1a3a Update FAQ to match the latest version 2022-02-22 19:08:15 +01:00
6a9b2f2c36 Remove spurious empty line 2022-02-22 18:31:37 +01:00
ff8a69d8ec Mention adb wireless option for Android 11+
Add a paragraph about toggling an option to bypass having to physically
connect the device to the user's computer.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-02-22 18:27:25 +01:00
1693797277 Make step more explicit in wireless section
PR #1303 <https://github.com/Genymobile/scrcpy/pull/1303>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-02-22 18:27:25 +01:00
8610e9a454 Add troubleshooting in wireless section
Add a small troubleshooting section since wireless might add some
complexity, and to lessen incoming relevant issue posts.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-02-22 18:27:25 +01:00
528275d501 Improve phrasing in wireless section
PR #1303 <https://github.com/Genymobile/scrcpy/pull/1303>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-02-22 18:27:25 +01:00
78b18b7cee Renumber steps in wireless section
The actual numbers are ignored by markdown, but start at 1 for
consistency.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-02-22 18:27:25 +01:00
90816291d4 Add an explicit first step in wireless section
Mention that the device must be plugged via USB before configuring
TCP/IP connections.

It wasn't obvious that the device should be first plugged before running
scrcpy wirelessly, especially to those who aren't very familiar with
adb.

Note from committer: add this new step with index 0 to make the diff
readable, the next commit will renumber all the steps.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-02-22 18:27:25 +01:00
3e0df6ad05 Update HID/OTG features in README
HID/OTG features are not limited to Linux anymore.

Refs 82a99f69ec
2022-02-22 18:27:13 +01:00
79ed83ab68 Reorder --tcpip option in cli
To keep options in alphabetic order.
2022-02-22 18:10:30 +01:00
71b41d846f Also retry on IllegalArgumentException
MediaCodec.configure() may throw an IllegalArgumentException if it does
not support the requested size. Also retry on this exception.

Fixes #2993 <https://github.com/Genymobile/scrcpy/issues/2993>
Refs #2947 <https://github.com/Genymobile/scrcpy/pull/2947>
Refs #2990 <https://github.com/Genymobile/scrcpy/pull/2990>
PR #3043 <https://github.com/Genymobile/scrcpy/pull/3043>
2022-02-22 10:27:11 +01:00
e2e76c5d48 Increase adb devices -l max output size
For simplicity, the parsing of `adb devices -l` output is performed in a
single pass on the whole output.

This output was limited to 4096 bytes. Since there are about 100 chars
per device line, this limited the number of connected devices to ~40.

Increase to 65536 bytes to avoid a limitation in practice.

PR #3035 <https://github.com/Genymobile/scrcpy/pull/3035>
2022-02-21 00:03:45 +01:00
4b8cb042c4 Use vector for listing ADB devices
This avoids the hardcoded maximum number of ADB devices detected (16).

Refs #3029 <https://github.com/Genymobile/scrcpy/pull/3029>
PR #3035 <https://github.com/Genymobile/scrcpy/pull/3035>

Co-authored-by: Daniel Ansorregui <d.ansorregui@samsung.com>
2022-02-20 23:59:35 +01:00
1790e88278 Use vector for listing USB devices
This avoids the hardcoded maximum number of USB devices detected (16).

Refs #3029 <https://github.com/Genymobile/scrcpy/pull/3029>
PR #3035 <https://github.com/Genymobile/scrcpy/pull/3035>
2022-02-20 23:59:35 +01:00
c070723bc8 Add sc_vector
Adapt vlc_vector [1], that I initially wrote while implementing the VLC
playlist [2].

Change the implementation to use "statement expressions" [3], which are
forbidden in VLC because "non-standard", but:
 - they are supported by gcc and clang;
 - they are already used in the scrcpy codebase;
 - they avoid implementation hacks (VLC_VECTOR_FAILFLAG_);
 - they allow a better API (sc_vector_index_of() may return the result
   without an output parameter).

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

[1]: 0857947aba/include/vlc_vector.h
[2]: https://blog.rom1v.com/2019/05/a-new-core-playlist-for-vlc-4
[3]: https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html
2022-02-20 23:59:35 +01:00
36c75e15b8 Move data/ to app/
The files in data/ are specific to the client app (not the server).

This also avoids to reference the parent directory (../) from
app/meson.build.

Refs 8d583d36e2
2022-02-20 17:56:50 +01:00
d9bc5082ab Disable USB features for win32
Currently, there is an issue with the libusb prebuilt dll.

Refs libusb/#1049 <https://github.com/libusb/libusb/issues/1049>
PR #3011 <https://github.com/Genymobile/scrcpy/pull/3011>
2022-02-20 17:39:52 +01:00
73a5311ac6 Forbid HID input without OTG on Windows
On Windows, if the adb daemon is running, opening the USB device will
necessarily fail, so HID input is not possible.

Refs #2773 <https://github.com/Genymobile/scrcpy/issues/2773>
PR #3011 <https://github.com/Genymobile/scrcpy/pull/3011>
2022-02-20 17:39:42 +01:00
25296ae167 Kill adb daemon in OTG mode on Windows
On Windows, it is not possible to open a USB device from several
process, so HID events may only work if no adb daemon is running.

PR #3011 <https://github.com/Genymobile/scrcpy/pull/3011>
2022-02-20 17:39:39 +01:00
3bb24b3926 Make intr optional for adb commands
All adb commands are executed with an "interruptor", so that they can be
interrupted on Ctrl+C.

Make this interruptor optional, so that we could call "adb kill-server"
in OTG mode. This command always returns almost immediately anyway.

Ideally, we should make all blocking calls interruptible (including
libusb calls, by using the asynchronous API), but it's a lot of work,
and in practice it works well enough.

PR #3011 <https://github.com/Genymobile/scrcpy/pull/3011>
2022-02-20 17:39:36 +01:00
6ee75c0cff Remove obsolete text in error message
The HID/OTG features are now available on all platforms.

PR #3011 <https://github.com/Genymobile/scrcpy/pull/3011>
2022-02-20 17:39:30 +01:00
6b65cd405a Build for Windows with libusb support
Fixes #2773 <https://github.com/Genymobile/scrcpy/issues/2773>
PR #3011 <https://github.com/Genymobile/scrcpy/pull/3011>
2022-02-20 17:39:12 +01:00
ff3cb31cb4 Fix libusb callback for Windows
Add LIBUSB_CALL so that the callback has the correct signature on
Windows (including __attribute__((stdcall))).

PR #3011 <https://github.com/Genymobile/scrcpy/pull/3011>
2022-02-20 17:39:09 +01:00
06243e7c3c Avoid PRIx16 printf format on Windows
Convert uint16_t to unsigned to avoid using PRIx16, which may not exist
on Windows.

PR #3011 <https://github.com/Genymobile/scrcpy/pull/3011>
2022-02-20 17:39:04 +01:00
b9b2879789 Remove USB hotplug callback error log
If it fails, the error is already logged by sc_usb_register_callback().

PR #3011 <https://github.com/Genymobile/scrcpy/pull/3011>
2022-02-20 17:38:51 +01:00
be1936bb85 Report USB device disconnection when detected
USB device disconnection is detected via a hotplug callback when it is
supported.

In addition, report disconnection on libusb calls returning
LIBUSB_ERROR_NO_DEVICE or LIBUSB_ERROR_NOT_FOUND. This allows to detect
disconnection after a libusb call when hotplug is not available.

PR #3011 <https://github.com/Genymobile/scrcpy/pull/3011>
2022-02-20 17:38:14 +01:00
3ee3f8dc02 Work around mouse capture SDL bug on macOS
On macOS, SDL relative mouse mode does not work correctly when the
cursor is outside the window.

As a workaround, move the cursor inside the window before setting the
relative mouse mode.

Refs SDL/#5340 <https://github.com/libsdl-org/SDL/issues/5340>
PR #3031 <https://github.com/Genymobile/scrcpy/pull/3031>
2022-02-20 17:28:02 +01:00
9db42341e4 Pass screen instance to mouse capture functions
Using the screen instance or not in these functions is an implementation
detail. Further changes will require the screen instance.

Refs 7848a387c8
PR #3031 <https://github.com/Genymobile/scrcpy/pull/3031>
2022-02-20 17:27:53 +01:00
82a99f69ec Remove "linux-only" mentions for HID/OTG features
HID/OTG features are not limited to Linux anymore.

PR #3031 <https://github.com/Genymobile/scrcpy/pull/3031>
2022-02-20 17:27:43 +01:00
33202491e1 Build on macOS with libusb support
Fixes #2774 <https://github.com/Genymobile/scrcpy/issues/2774>
PR #3031 <https://github.com/Genymobile/scrcpy/pull/3031>
2022-02-20 17:27:22 +01:00
b4fd882ece Fix typo 2022-02-20 17:21:25 +01:00
c4ab65eb79 Remove useless '\n' in log 2022-02-18 21:18:36 +01:00
6edf50d447 Remove fprintf() in tests
It was committed by mistake.
2022-02-18 19:34:54 +01:00
4b018be789 Add --print-fps to enable FPS counter on start
The FPS counter could be enabled/disabled via MOD+i.

Add a command line option to enable it on start. This is consistent with
other features like --turn-screen-off or --fullscreen.

Fixes #468 <https://github.com/Genymobile/scrcpy/issues/468>
PR #3030 <https://github.com/Genymobile/scrcpy/pull/3030>
2022-02-18 18:16:50 +01:00
fa93c8a91b Move FPS counter start/stop logs
This will allow to print the same logs for every start/stop call.

PR #3030 <https://github.com/Genymobile/scrcpy/pull/3030>
2022-02-18 18:16:12 +01:00
0e22032710 Update FAQ about Windows scaling behavior
Recommend to update to v1.22 before suggesting manual configuration.

Fixes #3028 <https://github.com/Genymobile/scrcpy/issues/3028>
PR #3032 <https://github.com/Genymobile/scrcpy/pull/3032>
2022-02-18 18:13:35 +01:00
03705b828b Use sc_prefix for fps counter 2022-02-17 19:55:24 +01:00
7a138c6929 Fix links in German README
There were three links that weren't displayed correctly due to incorrect
references:
 - the Windows release link to `README.md#windows`
 - 2 links that expected a German reference but got an English reference

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-02-16 20:44:09 +01:00
85edba20e7 Enforce deadline reached on timeout
The value of sc_tick_now() has microsecond precision, but
sc_cond_timedwait() has only millisecond precision.

To guarantee that sc_tick_now() >= deadline when sc_cond_timedwait()
returns due to timeout, round up to the next millisecond.

This avoids to call a non-blocking sc_cond_timedwait() in a loop for no
reason until a target deadline during up to 1 millisecond.

Refs 682a691173
2022-02-16 18:29:30 +01:00
2a872c3865 Fix fps_counter tick type
The type uint32_t is not sufficient to store the result of
sc_tick_now().

As a consequence, the FPS counter entered a live loop and caused a lock
starvation (deadlock in practice).

Refs ec871dd3f5
Refs 682a691173
2022-02-16 18:12:57 +01:00
b58b566fa5 Add German translation of README.md
PR #3023 <https://github.com/Genymobile/scrcpy/pull/3023>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-02-15 21:52:08 +01:00
ccbe370cc5 Add --no-cleanup option
It might be useful not to cleanup on exit, for example to leave the
screen turned off, or keep the server binary on the device (via the
server option "cleanup=false").

Fixes #1764 <https://github.com/Genymobile/scrcpy/issues/1764>
PR #3020 <https://github.com/Genymobile/scrcpy/pull/3020>
2022-02-15 19:25:57 +01:00
bb991f829c Fix order of options
In alphabetic order, "no-clipboard-autosync" is before
"no-downsize-on-error".
2022-02-13 17:26:34 +01:00
ca9e1a0514 Add compilation flag for USB features
This allows to disable HID/OTG features on Linux to build without
libusb.
2022-02-12 14:15:07 +01:00
cc27771dd1 Add compilation flag for V4L2 feature
This allows to disable V4L2 support on Linux to build without
libavdevice.
2022-02-12 14:15:07 +01:00
d0ab8c0e7b Fix double adb tunnel closing
On error, close the adb tunnel only if it has not already been closed
beforehand.
2022-02-12 14:15:02 +01:00
5c62f3419d Rename buffer util functions with sc_ prefix 2022-02-12 09:12:46 +01:00
044acc2259 Rename HEADER_SIZE to SC_PACKET_HEADER_SIZE
Prefix the constant for consistency.
2022-02-11 21:34:58 +01:00
6fc388f369 Remove unused BUFSIZE 2022-02-11 21:34:12 +01:00
1c02b58412 Remove sc_demuxer_parse()
Now that the key frame flag is known, parsing the packet is useless.
2022-02-11 21:33:09 +01:00
67068e4e3d Pass key frame flag from the device
MediaCodec indicates when a packet is a key frame. Transmit it to the
client.
2022-02-11 21:32:55 +01:00
e3c2398aa2 Store packet flags in PTS most significant bits
A special PTS value was used to encode a config packet.

To prepare for adding more flags, use the most significant bits of the
PTS field to store flags.
2022-02-11 21:32:11 +01:00
29c163959c Indent ifdef for clarity
Make it explicit that the ifdef is an inner block.
2022-02-10 09:24:19 +01:00
4a95c08d56 Improve error message for unsupported usb hotplug 2022-02-10 08:54:43 +01:00
7848a387c8 Do not duplicate relative mouse mode state
The relative mouse mode is tracked by SDL, and accessible via
SDL_GetRelativeMouseMode().

This is more robust in case SDL changes the relative mouse mode on its
own.
2022-02-10 08:50:18 +01:00
43ae418752 Fix USB device leak on connection error
If sc_usb_connect() failed, then the sc_usb_device was never destroyed.

The assignment was mistakenly removed by commit
61969aeb80.
2022-02-10 08:47:39 +01:00
8d583d36e2 Move prebuilt-deps/ to app/
The prebuilt dependencies are specific to the client app (not the
server).

This also avoids to reference the parent directory (../) from
app/meson.build.
2022-02-09 23:18:34 +01:00
8498a2e8a6 Reorder release.mk recipes
Group prepare-deps for win32 and win64.
2022-02-09 23:01:49 +01:00
c00a31f1b0 Pass --buildtype=release as a single meson arg
For consistency with the other arguments
2022-02-09 18:16:34 +01:00
f86df817f9 Print libusb version on --version 2022-02-09 10:15:19 +01:00
9a546ef1af Print both compiled and linked versions of libs
On --version, print both the version scrcpy had been compiled against,
and the version linked at runtime.
2022-02-09 10:15:19 +01:00
9477594f80 Move version handling to a separate file
This will avoid to include all dependencies headers from main.c.
2022-02-09 10:15:07 +01:00
29828aa330 Log device opening errors during listing
Without this log, the user would have no way to know that a USB device
is rejected because it could not be opened (typically due to
insufficient permissions).
2022-02-09 10:04:09 +01:00
dc5276b0e1 Mention --select-usb and --select-tcpip in README
PR #3005 <https://github.com/Genymobile/scrcpy/pull/3005>
2022-02-09 10:00:25 +01:00
582161607e Add option to select USB or TCP/IP devices
If several devices are connected (as listed by `adb devices`), it was
necessary to provide the explicit serial via -s/--serial.

If only one device is connected via USB (respectively, via TCP/IP), it
might be convenient to select it automatically. For this purpose, two
new options are introduced:
 - -d/--select-usb: select the single device connected over USB
 - -e/--select-tcpip: select the single device connected over TCP/IP

PR #3005 <https://github.com/Genymobile/scrcpy/pull/3005>
2022-02-09 10:00:25 +01:00
146f65d7b2 Introduce adb device selector
Currently, a device is selected either from a specific serial, or if it
is the only one connected.

In order to support selecting the only device connected via USB or via
TCP/IP separately, introduce a new selection structure.

PR #3005 <https://github.com/Genymobile/scrcpy/pull/3005>
2022-02-09 10:00:25 +01:00
5ed13ef477 Execute adb start-server
This does nothing if the adb daemon is already started, but allows to
print any output/errors to the console.

Otherwise, the daemon starting would occur during `adb devices`, which
does not output to the console because the result is parsed.

PR #3005 <https://github.com/Genymobile/scrcpy/pull/3005>
2022-02-09 10:00:14 +01:00
9c545e8c29 Remove sc_adb_get_serialno()
The device serial is now retrieved from `adb devices -l`, `adb
get-serialno` is not called anymore.

PR #3005 <https://github.com/Genymobile/scrcpy/pull/3005>
2022-02-09 09:56:26 +01:00
0a619dc9ef Allow selecting a device from IP without port
Since the previous commit, if a serial is given via -s/--serial (either
a real USB serial or an IP:port), a device is selected if its serial
matches exactly.

In addition, if the user pass an IP without a port, then select any
device with this IP, regardless of the port (so that "192.168.1.1"
matches any "192.168.1.1:port"). This is also the default behavior of
adb.

PR #3005 <https://github.com/Genymobile/scrcpy/pull/3005>
2022-02-09 09:56:26 +01:00
4692d13179 Expose simple API to select a single adb device
Select an adb device from the output of `adb device -l`.

PR #3005 <https://github.com/Genymobile/scrcpy/pull/3005>
2022-02-09 09:56:26 +01:00
02d46b2262 Expose function to test if a serial is TCP/IP
In practice, it just tests if the serial contains a ':', which is
sufficient to distinguish ip:port from a real USB serial.

PR #3005 <https://github.com/Genymobile/scrcpy/pull/3005>
2022-02-09 09:56:25 +01:00
4389de1c23 Add adb devices parser
Add a parser of `adb device -l` output, to extract a list of devices
with their serial, state and model.

PR #3005 <https://github.com/Genymobile/scrcpy/pull/3005>
2022-02-09 09:56:25 +01:00
85ff70fc95 Refactor device configuration
Depending on the parameters passed to scrcpy, either the initial device
serial is necessary or not. Reorganize to simplify the logic.

PR #3005 <https://github.com/Genymobile/scrcpy/pull/3005>
2022-02-09 09:55:25 +01:00
700503df6c List and select USB devices separately
List all USB devices in a first step, then select the matching one(s).

This allows to report a user-friendly log message containing the list of
devices, with the matching one(s) highlighted.

PR #3005 <https://github.com/Genymobile/scrcpy/pull/3005>
2022-02-09 09:55:25 +01:00
61969aeb80 Expose simple API to select a single USB device
The caller just wants a single device. Handle all cases and error
messages internally.

PR #3005 <https://github.com/Genymobile/scrcpy/pull/3005>
2022-02-09 09:55:25 +01:00
b88c4aa75e Add move-function for sc_usb_device
Add a function to "move" a sc_usb_device into another instance.

This will avoid unnecessary copies.

PR #3005 <https://github.com/Genymobile/scrcpy/pull/3005>
2022-02-09 09:55:23 +01:00
8c50342fb2 Move SC_PRIsizet to compat.h
Define the printf format macro for size_t in compat.h so that it can be
used from anywhere.

PR #3005 <https://github.com/Genymobile/scrcpy/pull/3005>
2022-02-09 09:53:19 +01:00
0eadf95a3e Rename function to destroy a list of USB devices
Rename from "usb_device_" to "usb_devices_".

PR #3005 <https://github.com/Genymobile/scrcpy/pull/3005>
2022-02-09 09:53:19 +01:00
6df2205cf3 Add generic LOG() macro with level parameter
One log macro was provided for each log level (LOGV(), LOGD(), LOGI(),
LOGW(), LOGE()).

Add a generic macro LOG(LEVEL, ...) accepting a log level as parameter,
so that it is possible to write logging wrappers.

PR #3005 <https://github.com/Genymobile/scrcpy/pull/3005>
2022-02-09 09:53:11 +01:00
61b6324ee9 Remove LOGC()
It is not clear when to use LOGC() rather than LOGE(). Always use
LOGE().

Moreover, enum sc_log_level has no "critical" log level.
2022-02-09 09:52:15 +01:00
103 changed files with 4585 additions and 896 deletions

View File

@ -46,7 +46,7 @@ sudo ninja -Cbuild-auto uninstall
### `master`
The `master` branch concerns the latest release, and is the home page of the
project on Github.
project on GitHub.
### `dev`
@ -161,7 +161,8 @@ install the required packages:
```bash
# runtime dependencies
pacman -S mingw-w64-x86_64-SDL2 \
mingw-w64-x86_64-ffmpeg
mingw-w64-x86_64-ffmpeg \
mingw-w64-x86_64-libusb
# client build dependencies
pacman -S mingw-w64-x86_64-make \
@ -175,7 +176,8 @@ For a 32 bits version, replace `x86_64` by `i686`:
```bash
# runtime dependencies
pacman -S mingw-w64-i686-SDL2 \
mingw-w64-i686-ffmpeg
mingw-w64-i686-ffmpeg \
mingw-w64-i686-libusb
# client build dependencies
pacman -S mingw-w64-i686-make \
@ -199,7 +201,7 @@ Install the packages with [Homebrew]:
```bash
# runtime dependencies
brew install sdl2 ffmpeg
brew install sdl2 ffmpeg libusb
# client build dependencies
brew install pkg-config meson
@ -258,7 +260,7 @@ set ANDROID_SDK_ROOT=%LOCALAPPDATA%\Android\sdk
Then, build:
```bash
meson x --buildtype release --strip -Db_lto=true
meson x --buildtype=release --strip -Db_lto=true
ninja -Cx # DO NOT RUN AS ROOT
```
@ -270,16 +272,16 @@ install` must be run as root)._
#### Option 2: Use prebuilt server
- [`scrcpy-server-v1.22`][direct-scrcpy-server]
_(SHA-256: c05d273eec7533c0e106282e0254cf04e7f5e8f0c2920ca39448865fab2a419b)_
- [`scrcpy-server-v1.24`][direct-scrcpy-server]
<sub>SHA-256: `ae74a81ea79c0dc7250e586627c278c0a9a8c5de46c9fb5c38c167fb1a36f056`</sub>
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.22/scrcpy-server-v1.22
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.24/scrcpy-server-v1.24
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:
```bash
meson x --buildtype release --strip -Db_lto=true \
meson x --buildtype=release --strip -Db_lto=true \
-Dprebuilt_server=/path/to/scrcpy-server
ninja -Cx # DO NOT RUN AS ROOT
```
@ -303,13 +305,16 @@ After a successful build, you can install _scrcpy_ on the system:
sudo ninja -Cx install # without sudo on Windows
```
This installs three files:
This installs several files:
- `/usr/local/bin/scrcpy`
- `/usr/local/share/scrcpy/scrcpy-server`
- `/usr/local/share/man/man1/scrcpy.1`
- `/usr/local/bin/scrcpy` (main app)
- `/usr/local/share/scrcpy/scrcpy-server` (server to push to the device)
- `/usr/local/share/man/man1/scrcpy.1` (manpage)
- `/usr/local/share/icons/hicolor/256x256/apps/icon.png` (app icon)
- `/usr/local/share/zsh/site-functions/_scrcpy` (zsh completion)
- `/usr/local/share/bash-completion/completions/scrcpy` (bash completion)
You can then [run](README.md#run) _scrcpy_.
You can then [run](README.md#run) `scrcpy`.
### Uninstall

88
FAQ.md
View File

@ -4,23 +4,16 @@
Here are the common reported problems and their status.
If you encounter any error, the first step is to upgrade to the latest version.
## `adb` issues
`scrcpy` execute `adb` commands to initialize the connection with the device. If
`adb` fails, then scrcpy will not work.
In that case, it will print this error:
> ERROR: "adb get-serialno" returned with value 1
This is typically not a bug in _scrcpy_, but a problem in your environment.
To find out the cause, execute:
```bash
adb devices
```
### `adb` not found
@ -30,13 +23,30 @@ On Windows, the current directory is in your `PATH`, and `adb.exe` is included
in the release, so it should work out-of-the-box.
### Device not detected
> ERROR: Could not find any ADB device
Check that you correctly enabled [adb debugging][enable-adb].
Your device must be detected by `adb`:
```
adb devices
```
If your device is not detected, you may need some [drivers] (on Windows). There is a separate [USB driver for Google devices][google-usb-driver].
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
[drivers]: https://developer.android.com/studio/run/oem-usb.html
[google-usb-driver]: https://developer.android.com/studio/run/win-usb
### Device unauthorized
> error: device unauthorized.
> This adb server's $ADB_VENDOR_KEYS is not set
> Try 'adb kill-server' if that seems wrong.
> Otherwise check for a confirmation dialog on your device.
> ERROR: Device is unauthorized:
> ERROR: --> (usb) 0123456789abcdef unauthorized
> ERROR: A popup should open on the device to request authorization.
When connecting, a popup should open on the device. You must authorize USB
debugging.
@ -46,29 +56,27 @@ If it does not open, check [stackoverflow][device-unauthorized].
[device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized
### Device not detected
> error: no devices/emulators found
Check that you correctly enabled [adb debugging][enable-adb].
If your device is not detected, you may need some [drivers] (on Windows). There is a separate [USB driver for Google devices][google-usb-driver].
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
[drivers]: https://developer.android.com/studio/run/oem-usb.html
[google-usb-driver]: https://developer.android.com/studio/run/win-usb
### Several devices connected
If several devices are connected, you will encounter this error:
> error: more than one device/emulator
ERROR: Multiple (2) ADB devices:
ERROR: --> (usb) 0123456789abcdef device Nexus_5
ERROR: --> (tcpip) 192.168.1.5:5555 device GM1913
ERROR: Select a device via -s (--serial), -d (--select-usb) or -e (--select-tcpip)
the identifier of the device you want to mirror must be provided:
In that case, you can either provide the identifier of the device you want to
mirror:
```bash
scrcpy -s 01234567890abcdef
scrcpy -s 0123456789abcdef
```
Or request the single USB (or TCP/IP) device:
```bash
scrcpy -d # USB device
scrcpy -e # TCP/IP device
```
Note that if your device is connected over TCP/IP, you might get this message:
@ -150,22 +158,24 @@ screen, then you might get poor quality, especially visible on text (see [#40]).
[#40]: https://github.com/Genymobile/scrcpy/issues/40
To improve downscaling quality, trilinear filtering is enabled automatically
if the renderer is OpenGL and if it supports mipmapping.
This problem should be fixed in scrcpy v1.22: **update to the latest version**.
On Windows, you might want to force OpenGL:
```
scrcpy --render-driver=opengl
```
You may also need to configure the [scaling behavior]:
On older versions, you must configure the [scaling behavior]:
> `scrcpy.exe` > Properties > Compatibility > Change high DPI settings >
> Override high DPI scaling behavior > Scaling performed by: _Application_.
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
Also, to improve downscaling quality, trilinear filtering is enabled
automatically if the renderer is OpenGL and if it supports mipmapping.
On Windows, you might want to force OpenGL to enable mipmapping:
```
scrcpy --render-driver=opengl
```
### Issue with Wayland

1016
README.de.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -218,7 +218,7 @@ variation] does not impact the recorded file.
#### Wireless
_Scrcpy_ menggunakan `adb` untuk berkomunikasi dengan perangkat, dan` adb` dapat [terhubung] ke perangkat melalui TCP / IP:
_Scrcpy_ menggunakan `adb` untuk berkomunikasi dengan perangkat, dan `adb` dapat [terhubung] ke perangkat melalui TCP / IP:
1. Hubungkan perangkat ke Wi-Fi yang sama dengan komputer Anda.
2. Dapatkan alamat IP perangkat Anda (dalam Pengaturan → Tentang ponsel → Status).
@ -281,7 +281,7 @@ Dari terminal lain:
scrcpy
```
Untuk menghindari mengaktifkan penerusan port jarak jauh, Anda dapat memaksa sambungan maju sebagai gantinya (perhatikan `-L`, bukan` -R`):
Untuk menghindari mengaktifkan penerusan port jarak jauh, Anda dapat memaksa sambungan maju sebagai gantinya (perhatikan `-L`, bukan `-R`):
```bash
adb kill-server # matikan server adb lokal di 5037
@ -579,7 +579,7 @@ Lihat juga [Masalah #14].
Dalam daftar berikut, <kbd>MOD</kbd> adalah pengubah pintasan. Secara default, ini (kiri) <kbd>Alt</kbd> atau (kiri) <kbd>Super</kbd>.
Ini dapat diubah menggunakan `--shortcut-mod`. Kunci yang memungkinkan adalah `lctrl`,`rctrl`, `lalt`,` ralt`, `lsuper` dan` rsuper`. Sebagai contoh:
Ini dapat diubah menggunakan `--shortcut-mod`. Kunci yang memungkinkan adalah `lctrl`, `rctrl`, `lalt`, `ralt`, `lsuper` dan `rsuper`. Sebagai contoh:
```bash
# gunakan RCtrl untuk jalan pintas

View File

@ -1,23 +1,42 @@
_Apri il [README](README.md) originale e sempre aggiornato._
_Apri il [README](README.md) originale (in inglese) e sempre aggiornato._
# scrcpy (v1.19)
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
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_.
# scrcpy (v1.23)
_si pronuncia "**scr**een **c**o**py**"_
[Leggi in altre lingue](#traduzioni)
Questa applicazione fornisce la visualizzazione e il controllo di dispositivi Android collegati via USB (o [via TCP/IP](#tcpip-wireless)). Non richiede alcun accesso _root_.
Funziona su _GNU/Linux_, _Windows_ e _macOS_.
![screenshot](assets/screenshot-debian-600.jpg)
Si concentra su:
- **leggerezza** (nativo, mostra solo lo schermo del dispositivo)
- **prestazioni** (30~60fps)
- **qualità** (1920×1080 o superiore)
- **bassa latenza** ([35~70ms][lowlatency])
- **tempo di avvio basso** (~ 1secondo per visualizzare la prima immagine)
- **non invadenza** (nulla viene lasciato installato sul dispositivo)
- **leggerezza**: nativo, mostra solo lo schermo del dispositivo
- **prestazioni**: 30~120fps, in funzione del dispositivo
- **qualità**: 1920×1080 o superiore
- **bassa latenza**: [35~70ms][lowlatency]
- **tempo di avvio basso**: ~ 1secondo per visualizzare la prima immagine
- **non invadenza**: nulla rimane installato sul dispositivo
- **vantaggi per l'utente**: nessun account, nessuna pubblicità, non è richiesta alcuna connessione a internet
- **libertà**: software libero e a codice aperto (_free and open source_)
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
Le sue caratteristiche includono:
- [registrazione](#registrazione)
- mirroring con [schermo del dispositivo spento](#spegnere-lo-schermo)
- [copia-incolla](#copia-incolla) in entrambe le direzioni
- [qualità configurabile](#configurazione-di-acquisizione)
- schermo del dispositivo [come webcam (V4L2)](#v4l2loopback) (solo per Linux)
- [simulazione della tastiera fisica (HID)](#simulazione-della-tastiera-fisica-HID)
- [simulazione mouse fisico (HID)](#simulazione-del-mouse-fisico-HID)
- [modalità OTG](#otg)
- e altro ancora...
## Requisiti
@ -49,12 +68,18 @@ Compila dai sorgenti: [BUILD] (in inglese) ([procedimento semplificato][BUILD_si
### Linux
Su Debian (_testing_ e _sid_ per ora) e Ubuntu (20.04):
Su Debian e Ubuntu:
```
apt install scrcpy
```
Su Arch Linux:
```
pacman -S scrcpy
```
È disponibile anche un pacchetto [Snap]: [`scrcpy`][snap-link].
[snap-link]: https://snapstats.org/snaps/scrcpy
@ -66,10 +91,6 @@ Per Fedora, è disponibile un pacchetto [COPR]: [`scrcpy`][copr-link].
[COPR]: https://fedoraproject.org/wiki/Category:Copr
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
Per Arch Linux, è disponibile un pacchetto [AUR]: [`scrcpy`][aur-link].
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
Per Gentoo, è disponibile una [Ebuild]: [`scrcpy/`][ebuild-link].
@ -142,7 +163,7 @@ Collega un dispositivo Android ed esegui:
scrcpy
```
Scrcpy accetta argomenti da riga di comando, essi sono listati con:
Scrcpy accetta argomenti da riga di comando, elencati con:
```bash
scrcpy --help
@ -186,6 +207,14 @@ scrcpy --max-fps 15
Questo è supportato ufficialmente a partire da Android 10, ma potrebbe funzionare in versioni precedenti.
L'attuale frame rate di acquisizione può essere stampato sulla console:
```
scrcpy --print-fps
```
Può anche essere abilitato o disabilitato in qualsiasi momento con <kbd>MOD</kbd>+<kbd>i</kbd>.
#### Ritaglio
Lo schermo del dispositivo può essere ritagliato per visualizzare solo parte di esso.
@ -258,7 +287,7 @@ I "fotogrammi saltati" sono registrati nonostante non siano mostrati in tempo re
#### 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.
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:
@ -321,42 +350,72 @@ 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
scrcpy --v4l2-buffer=500 # aggiungi 500 ms di buffer per il v4l2 sink
```
### Connessione
#### Wireless
#### TCP/IP (wireless)
_Scrcpy_ usa `adb` per comunicare col dispositivo e `adb` può [connettersi][connect] a un dispositivo mediante TCP/IP. Il dispositivo deve essere collegato alla stessa rete del computer.
_Scrcpy_ usa `adb` per comunicare col dispositivo e `adb` può [connettersi][connect] al dispositivo mediante TCP/IP:
##### Automatico
1. Connetti il dispositivo alla stessa rete Wi-Fi del tuo computer.
2. Trova l'indirizzo IP del tuo dispositivo in Impostazioni → Informazioni sul telefono → Stato, oppure eseguendo questo comando:
Un'opzione `--tcpip` permette di configurare automaticamente la connessione. Ci sono due varianti.
Se il dispositivo (accessibile a 192.168.1.1 in questo esempio) ascolta già su una porta (tipicamente 5555) per le connessioni adb in entrata, allora esegui:
```bash
scrcpy --tcpip=192.168.1.1 # la porta predefinita è 5555
scrcpy --tcpip=192.168.1.1:5555
```
Se la modalità TCP/IP di adb è disabilitata sul dispositivo (o se non si conosce l'indirizzo IP indirizzo), collegare il dispositivo tramite USB, quindi eseguire:
```bash
scrcpy --tcpip # senza argomenti
```
Il comando troverà automaticamente l'indirizzo IP del dispositivo, abiliterà la modalità TCP/IP, quindi connettersi al dispositivo prima di iniziare.
##### Manuale
In alternativa, è possibile abilitare la connessione TCP/IP manualmente usando `adb`:
1. Inserisci il dispositivo in una porta USB del tuo computer.
2. Connetti il dispositivo alla stessa rete Wi-Fi del tuo computer.
3. Ottieni l'indirizzo IP del tuo dispositivo, in Impostazioni → Informazioni sul telefono → Stato, o
eseguendo questo comando:
```bash
adb shell ip route | awk '{print $9}'
```
3. Abilita adb via TCP/IP sul tuo dispositivo: `adb tcpip 5555`.
4. Scollega il tuo dispositivo.
5. Connetti il tuo dispositivo: `adb connect IP_DISPOSITVO:5555` _(rimpiazza `IP_DISPOSITIVO`)_.
6. Esegui `scrcpy` come al solito.
4. Abilita adb via TCP/IP sul tuo dispositivo: `adb tcpip 5555`.
5. Scollega il tuo dispositivo.
6. Connettiti al tuo dispositivo: `adb connect DEVICE_IP:5555` _(sostituisci `DEVICE_IP`
con l'indirizzo IP del dispositivo che hai trovato)_.
7. Esegui `scrcpy` come al solito.
Potrebbe essere utile diminuire il bit-rate e la definizione
Da Android 11, una [opzione di debug wireless][adb-wireless] permette di evitare di dover collegare fisicamente il dispositivo direttamente al computer.
[adb-wireless]: https://developer.android.com/studio/command-line/adb#connect-to-a-device-over-wi-fi-android-11+
Se la connessione cade casualmente, esegui il comando `scrcpy` per riconnetterti. Se il comando dice che non ci sono dispositivi/emulatori trovati, prova ad eseguire `adb connect DEVICE_IP:5555` di nuovo, e poi `scrcpy` come al solito. Se dice ancora che non ne ha trovato nessuno, prova ad eseguire `adb disconnect` e poi esegui di nuovo questi due comandi.
Potrebbe essere utile diminuire il bit-rate e la definizione:
```bash
scrcpy --bit-rate 2M --max-size 800
scrcpy -b2M -m800 # versione breve
scrcpy -b2M -m800 # versione breve
```
[connect]: https://developer.android.com/studio/command-line/adb.html#wireless
#### Multi dispositivo
Se in `adb devices` sono listati più dispositivi, è necessario specificare il _seriale_:
Se in `adb devices` sono elencati più dispositivi, è necessario specificare il _seriale_:
```bash
scrcpy --serial 0123456789abcdef
@ -370,6 +429,18 @@ scrcpy --serial 192.168.0.1:5555
scrcpy -s 192.168.0.1:5555 # versione breve
```
Se solo un dispositivo è collegato via USB o TCP/IP, è possibile selezionarlo automaticamente:
```bash
# Select the only device connected via USB
scrcpy -d # like adb -d
scrcpy --select-usb # long version
# Select the only device connected via TCP/IP
scrcpy -e # like adb -e
scrcpy --select-tcpip # long version
```
Puoi avviare più istanze di _scrcpy_ per diversi dispositivi.
@ -383,37 +454,77 @@ autoadb scrcpy -s '{}'
[AutoAdb]: https://github.com/rom1v/autoadb
#### Tunnel SSH
#### Tunnels
Per connettersi a un dispositivo remoto è possibile collegare un client `adb` locale ad un server `adb` remoto (assunto che entrambi stiano usando la stessa versione del protocollo _adb_):
Per connettersi a un dispositivo remoto, è possibile collegare un client `adb` locale a un server remoto `adb` (purché usino la stessa versione del protocollo _adb_). ).
##### Server ADB remoto
Per connettersi a un server ADB remoto, fate ascoltare il server su tutte le interfacce:
```bash
adb kill-server # termina il server adb locale su 5037
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
# tieni questo aperto
adb kill-server
adb -a nodaemon server start
# tienilo aperto
```
Da un altro terminale:
**Attenzione: tutte le comunicazioni tra i client e il server ADB non sono criptate.**
Supponi che questo server sia accessibile a 192.168.1.2. Poi, da un altro terminale, esegui scrcpy:
```bash
export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037
scrcpy --tunnel-host=192.168.1.2
```
Per impostazione predefinita, scrcpy utilizza la porta locale utilizzata per il tunnel `adb forward` (tipicamente `27183`, vedi `--port`). È anche possibile forzare una diversa porta del tunnel (può essere utile in situazioni più complesse, quando sono coinvolti più reindirizzamenti):
```
scrcpy --tunnel-port=1234
```
##### SSH tunnel
Per comunicare con un server ADB remoto in modo sicuro, è preferibile utilizzare un tunnel SSH.
Per prima cosa, assicurati che il server ADB sia in esecuzione sul computer remoto:
```bash
adb start-server
```
Poi, crea un tunnel SSH:
```bash
# local 5038 --> remote 5037
# local 27183 <-- remote 27183
ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer
# keep this open
```
Da un altro terminale, esegui scrcpy:
```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038
scrcpy
```
Per evitare l'abilitazione dell'apertura porte remota potresti invece forzare una "forward connection" (notare il `-L` invece di `-R`)
```bash
adb kill-server # termina il server adb locale su 5037
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
# local 5038 --> remote 5037
# local 27183 --> remote 27183
ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer
# tieni questo aperto
```
Da un altro terminale:
Da un altro terminale, esegui scrcpy:
```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038
scrcpy --force-adb-forward
```
Come per le connessioni wireless potrebbe essere utile ridurre la qualità:
```
@ -551,6 +662,14 @@ scrcpy --turn-screen-off --stay-awake
scrcpy -Sw
```
#### Spegnimento alla chiusura
Per spegnere lo schermo del dispositivo quando si chiude scrcpy:
```bash
scrcpy --power-off-on-close
```
#### Mostrare i tocchi
@ -596,20 +715,22 @@ Qualsiasi scorciatoia <kbd>Ctrl</kbd> viene inoltrata al dispositivo. In partico
- <kbd>Ctrl</kbd>+<kbd>x</kbd> taglia
- <kbd>Ctrl</kbd>+<kbd>v</kbd> incolla (dopo la sincronizzazione degli appunti da computer a dispositivo)
Questo solitamente funziona nella maniera più comune.
Questo solitamente funziona come ci si aspetta.
Il comportamento reale, però, dipende dall'applicazione attiva. Per esempio _Termux_ invia SIGINT con <kbd>Ctrl</kbd>+<kbd>c</kbd>, e _K-9 Mail_ compone un nuovo messaggio.
Per copiare, tagliare e incollare in questi casi (ma è solo supportato in Android >= 7):
- <kbd>MOD</kbd>+<kbd>c</kbd> inietta `COPY`
- <kbd>MOD</kbd>+<kbd>x</kbd> inietta `CUT`
- <kbd>MOD</kbd>+<kbd>v</kbd> inietta `PASTE` (dopo la sincronizzazione degli appunti da computer a dispositivo)
- <kbd>MOD</kbd>+<kbd>c</kbd> invia `COPY`
- <kbd>MOD</kbd>+<kbd>x</kbd> invia `CUT`
- <kbd>MOD</kbd>+<kbd>v</kbd> invia `PASTE` (dopo la sincronizzazione degli appunti da computer a dispositivo)
In aggiunta, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> permette l'iniezione del testo degli appunti del computer come una sequenza di eventi pressione dei tasti. Questo è utile quando il componente non accetta l'incollaggio di testo (per esempio in _Termux_), ma questo può rompere il contenuto non ASCII.
In aggiunta, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> permette l'invio del testo degli appunti del computer come una sequenza di eventi pressione dei tasti. Questo è utile quando il componente non accetta l'incollaggio di testo (per esempio in _Termux_), ma questo può compromettere il contenuto non ASCII.
**AVVISO:** Incollare gli appunti del computer nel dispositivo (sia con <kbd>Ctrl</kbd>+<kbd>v</kbd> che con <kbd>MOD</kbd>+<kbd>v</kbd>) copia il contenuto negli appunti del dispositivo. Come conseguenza, qualsiasi applicazione Android potrebbe leggere il suo contenuto. Dovresti evitare di incollare contenuti sensibili (come password) in questa maniera.
Alcuni dispositivi non si comportano come aspettato quando si modificano gli appunti del dispositivo a livello di codice. L'opzione `--legacy-paste` è fornita per cambiare il comportamento di <kbd>Ctrl</kbd>+<kbd>v</kbd> and <kbd>MOD</kbd>+<kbd>v</kbd> in modo tale che anch'essi iniettino il testo gli appunti del computer come una sequenza di eventi pressione dei tasti (nella stessa maniera di <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
Alcuni dispositivi non si comportano come aspettato quando si modificano gli appunti del dispositivo a livello di codice. L'opzione `--legacy-paste` è fornita per cambiare il comportamento di <kbd>Ctrl</kbd>+<kbd>v</kbd> and <kbd>MOD</kbd>+<kbd>v</kbd> in modo tale che anch'essi inviino il testo degli appunti del computer come una sequenza di eventi di pressione dei tasti (nella stessa maniera di <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
Per disabilitare la sincronizzazione automatica degli appunti, usa `--no-clipboard-autosync`.
#### Pizzica per zoomare (pinch-to-zoom)
@ -617,16 +738,98 @@ Per simulare il "pizzica per zoomare": <kbd>Ctrl</kbd>+_click e trascina_.
Più precisamente, tieni premuto <kbd>Ctrl</kbd> mentre premi il pulsante sinistro. Finchè il pulsante non sarà rilasciato, tutti i movimenti del mouse ridimensioneranno e ruoteranno il contenuto (se supportato dall'applicazione) relativamente al centro dello schermo.
Concretamente scrcpy genera degli eventi di tocco addizionali di un "dito virtuale" nella posizione simmetricamente opposta rispetto al centro dello schermo.
Concretamente, scrcpy genera degli eventi di tocco addizionali di un "dito virtuale" nella posizione simmetricamente opposta rispetto al centro dello schermo.
#### Simulazione della tastiera fisica (HID)
Per impostazione predefinita, scrcpy utilizza l'invio dei tasti o del testo di Android: funziona ovunque, ma è limitato all'ASCII.
In alternativa scrcpy può simulare una tastiera fisica USB su Android per fornire una migliore esperienza di input (utilizzando [USB HID over AOAv2][hid-aoav2]): la tastiera virtuale è disabilitata e funziona per tutti i caratteri e IME.
[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support
Tuttavia, funziona solo se il dispositivo è collegato via USB.
Nota: su Windows, può funzionare solo in [odalità OTG](#otg), non durante il mirroring (non è possibile aprire un dispositivo USB se è già aperto da un altro processo come il daemon adb).
Per abilitare questa modalità:
```bash
scrcpy --hid-keyboard
scrcpy -K # versione breve
```
Se fallisce per qualche motivo (per esempio perché il dispositivo non è connesso via USB), ritorna automaticamente alla modalità predefinita (con un log nella console). Questo permette di usare le stesse opzioni della linea di comando quando si è connessi via USB e TCP/IP.
In questa modalità, gli eventi i pressione originali (scancodes) sono inviati al dispositivo, indipendentemente dalla mappatura dei tasti dell'host. Pertanto, se il layout della tua tastiera non corrisponde, deve essere configurato sul dispositivo Android, in Impostazioni → Sistema → Lingue e input → [Tastiera fisica] (in inglese).
Questa pagina di impostazioni può essere avviata direttamente:
```bash
adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS
```
Tuttavia, l'opzione è disponibile solo quando la tastiera HID è abilitata (o quando una tastiera fisica è collegata).
[Tastiera fisica]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915
#### Simulazione del mouse fisico (HID)
In modo simile alla simulazione della tastiera fisica, è possibile simulare un mouse fisico. Allo stesso modo funziona solo se il dispositivo è connesso via USB.
Per impostazione predefinita, scrcpy utilizza l'invio degli eventi del mouse di Android, utilizzando coordinate assolute. Simulando un mouse fisico, un puntatore del mouse appare sul dispositivo Android e vengono inviati i movimenti relativi del mouse, i click e gli scorrimenti.
Per abilitare questa modalità:
```bash
scrcpy --hid-mouse
scrcpy -M # versione breve
```
Si potrebbe anche aggiungere `--forward-all-clicks` a [inoltra tutti i pulsanti del mouse][forward_all_clicks].
[forward_all_clicks]: #click-destro-e-click-centrale
#### Preferenze di iniezione del testo
Quando questa modalità è attivata, il mouse del computer viene "catturato" (il puntatore del mouse scompare dal computer e appare invece sul dispositivo Android).
I tasti speciali di cattura, <kbd>Alt</kbd> o <kbd>Super</kbd>, commutano (disabilitano o abilitano) la cattura del mouse. Usa uno di essi per ridare il controllo del mouse al computer.
#### OTG
È possibile eseguire _scrcpy_ con la sola simulazione della tastiera fisica e del mouse (HID), come se la tastiera e il mouse del computer fossero collegati direttamente al dispositivo tramite un cavo OTG.
In questa modalità, _adb_ (debug USB) non è necessario e il mirroring è disabilitato.
Per attivare la modallità OTG:
```bash
scrcpy --otg
# Passa la seriale se sono disponibili diversi dispositivi USB
scrcpy --otg -s 0123456789abcdef
```
È possibile abilitare solo la tastiera HID o il mouse HID:
```bash
scrcpy --otg --hid-keyboard # solo la tastiera
scrcpy --otg --hid-mouse # solo mouse
scrcpy --otg --hid-keyboard --hid-mouse # tastiera e mouse
# per comodità, abilita entrambi per default
scrcpy --otg # tastiera e mouse
```
Come `--hid-keyboard` e `--hid-mouse`, funziona solo se il dispositivo è collegato via USB.
#### Preferenze di invio del testo
Ci sono due tipi di [eventi][textevents] generati quando si scrive testo:
- _eventi di pressione_, segnalano che tasto è stato premuto o rilasciato;
- _eventi di testo_, segnalano che del testo è stato inserito.
In maniera predefinita le lettere sono "iniettate" usando gli eventi di pressione, in maniera tale che la tastiera si comporti come aspettato nei giochi (come accade solitamente per i tasti WASD).
In maniera predefinita le lettere sono inviate usando gli eventi di pressione, in maniera tale che la tastiera si comporti come aspettato nei giochi (come accade solitamente per i tasti WASD).
Questo, però, può [causare problemi][prefertext]. Se incontri un problema del genere, puoi evitarlo con:
@ -636,13 +839,21 @@ scrcpy --prefer-text
(ma questo romperà il normale funzionamento della tastiera nei giochi)
Al contrario, si potrebbe forzare per inviare sempre eventi di pressione grezzi:
```bash
scrcpy --raw-key-events
```
Queste opzioni non hanno effetto sulla tastiera HID (tutti gli eventi di pressione sono inviati come scancodes in questa modalità).
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
#### Ripetizione di tasti
In maniera predefinita tenere premuto un tasto genera una ripetizione degli eventi di pressione di tale tasto. Questo può creare problemi di performance in alcuni giochi, dove questi eventi sono inutilizzati.
In maniera predefinita, tenere premuto un tasto genera una ripetizione degli eventi di pressione di tale tasto. Questo può creare problemi di performance in alcuni giochi, dove questi eventi sono inutilizzati.
Per prevenire l'inoltro ripetuto degli eventi di pressione:
@ -650,9 +861,12 @@ Per prevenire l'inoltro ripetuto degli eventi di pressione:
scrcpy --no-key-repeat
```
Questa opzione non ha effetto sulla tastiera HID (la ripetizione dei tasti è gestita da Android direttamente in questa modalità).
#### Click destro e click centrale
In maniera predefinita, click destro aziona BACK (indietro) e il click centrale aziona HOME. Per disabilitare queste scorciatoie e, invece, inviare i click al dispositivo:
In maniera predefinita, click destro aziona BACK (indietro) o POWER on (accensione) e il click centrale aziona HOME. Per disabilitare queste scorciatoie e, invece, inviare i click al dispositivo:
```bash
scrcpy --forward-all-clicks
@ -705,7 +919,7 @@ scrcpy --shortcut-mod=rctrl
scrcpy --shortcut-mod=lctrl+lalt,lsuper
```
_<kbd>[Super]</kbd> è il pulsante <kbd>Windows</kbd> o <kbd>Cmd</kbd>._
_<kbd>[Super]</kbd> è solitamente il pulsante <kbd>Windows</kbd> o <kbd>Cmd</kbd>._
[Super]: https://it.wikipedia.org/wiki/Tasto_Windows
<!-- https://en.wikipedia.org/wiki/Super_key_(keyboard_button) è la pagina originale di Wikipedia inglese, l'ho sostituita con una simile in quello italiano -->
@ -720,7 +934,7 @@ _<kbd>[Super]</kbd> è il pulsante <kbd>Windows</kbd> o <kbd>Cmd</kbd>._
| Premi il tasto `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Click centrale_
| Premi il tasto `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Click destro²_
| Premi il tasto `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _4° click³_
| Premi il tasto `MENU` (sblocca lo schermo) | <kbd>MOD</kbd>+<kbd>m</kbd>
| Premi il tasto `MENU` (sblocca lo schermo) | <kbd>MOD</kbd>+<kbd>m</kbd>
| Premi il tasto `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(su)_
| Premi il tasto `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(giù)_
| Premi il tasto `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd>
@ -731,17 +945,20 @@ _<kbd>[Super]</kbd> è il pulsante <kbd>Windows</kbd> o <kbd>Cmd</kbd>._
| Espandi il pannello delle notifiche | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5° click³_
| Espandi il pannello delle impostazioni | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Doppio 5° click³_
| Chiudi pannelli | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| Copia negli appunti | <kbd>MOD</kbd>+<kbd>c</kbd>
| Taglia negli appunti | <kbd>MOD</kbd>+<kbd>x</kbd>
| Sincronizza gli appunti e incolla | <kbd>MOD</kbd>+<kbd>v</kbd>
| Inietta il testo degli appunti del computer | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| Copia negli appunti | <kbd>MOD</kbd>+<kbd>c</kbd>
| Taglia negli appunti | <kbd>MOD</kbd>+<kbd>x</kbd>
| Sincronizza gli appunti e incolla | <kbd>MOD</kbd>+<kbd>v</kbd>
| Invia il testo degli appunti del computer | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| Abilita/Disabilita il contatore FPS (su stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| Pizzica per zoomare | <kbd>Ctrl</kbd>+_click e trascina_
| Trascina file APK | Installa APK dal computer
| Trascina file non-APK | [Trasferisci file verso il dispositivo](#push-file-to-device)
_¹Doppio click sui bordi neri per rimuoverli._
_²Il tasto destro accende lo schermo se era spento, preme BACK in caso contrario._
_³4° e 5° pulsante del mouse, se il tuo mouse ne dispone._
_⁴Solo in Android >= 7._
_⁴Per le app native react in sviluppo, `MENU` attiva il menu di sviluppo._
_⁵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":
@ -811,3 +1028,14 @@ Leggi la [pagina per sviluppatori].
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/
## Contatti
Se incontri un bug, per favore leggi prima le [FAQ](FAQ.it.md), poi apri una [issue].
[issue]: https://github.com/Genymobile/scrcpy/issues
Per domande generali o discussioni, puoi anche usare:
- Reddit: [`r/scrcpy`](https://www.reddit.com/r/scrcpy)
- Twitter: [`@scrcpy_app`](https://twitter.com/scrcpy_app)

245
README.md
View File

@ -1,13 +1,13 @@
# scrcpy (v1.22)
# scrcpy (v1.24)
<img src="data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
_pronounced "**scr**een **c**o**py**"_
[Read in another language](#translations)
This application provides display and control of Android devices connected via
USB (or [over TCP/IP](#tcpip-wireless)). It does not require any _root_ access.
USB or [over TCP/IP](#tcpip-wireless). It does not require any _root_ access.
It works on _GNU/Linux_, _Windows_ and _macOS_.
![screenshot](assets/screenshot-debian-600.jpg)
@ -19,7 +19,7 @@ It focuses on:
- **quality**: 1920×1080 or above
- **low latency**: [35~70ms][lowlatency]
- **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 Android device
- **user benefits**: no account, no ads, no internet required
- **freedom**: free and open source software
@ -27,27 +27,25 @@ It focuses on:
Its features include:
- [recording](#recording)
- mirroring with [device screen off](#turn-screen-off)
- mirroring with [Android 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)
- Android device [as a webcam (V4L2)](#v4l2loopback) (Linux-only)
- [physical keyboard simulation (HID)](#physical-keyboard-simulation-hid)
(Linux-only)
- [physical mouse simulation (HID)](#physical-mouse-simulation-hid)
(Linux-only)
- [OTG mode](#otg) (Linux-only)
- [OTG mode](#otg)
- and more…
## Requirements
The Android device requires at least API 21 (Android 5.0).
Make sure you [enabled adb debugging][enable-adb] on your device(s).
Make sure you [enable adb debugging][enable-adb] on your device(s).
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
On some devices, you also need to enable [an additional option][control] to
control it using keyboard and mouse.
control it using a keyboard and mouse.
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
@ -99,19 +97,19 @@ For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
You could also [build the app manually][BUILD] ([simplified
You can also [build the app manually][BUILD] ([simplified
process][BUILD_simple]).
### Windows
For Windows, for simplicity, a prebuilt archive with all the dependencies
(including `adb`) is available:
For Windows, a prebuilt archive with all the dependencies (including `adb`) is
available:
- [`scrcpy-win64-v1.22.zip`][direct-win64]
_(SHA-256: ce4d9b8cc761e29862c4a72d8ad6f538bdd1f1831d15fd1f36633cd3b403db82)_
- [`scrcpy-win64-v1.24.zip`][direct-win64]
<sub>SHA-256: `6ccb64cba0a3e75715e85a188daeb4f306a1985f8ce123eba92ba74fc9b27367`</sub>
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.22/scrcpy-win64-v1.22.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.24/scrcpy-win64-v1.24.zip
It is also available in [Chocolatey]:
@ -150,7 +148,7 @@ You need `adb`, accessible from your `PATH`. If you don't have it yet:
brew install android-platform-tools
```
It's also available in [MacPorts], which sets up adb for you:
It's also available in [MacPorts], which sets up `adb` for you:
```bash
sudo port install scrcpy
@ -164,7 +162,7 @@ You can also [build the app manually][BUILD].
## Run
Plug an Android device, and execute:
Plug an Android device into your computer, and execute:
```bash
scrcpy
@ -182,7 +180,7 @@ scrcpy --help
#### Reduce size
Sometimes, it is useful to mirror an Android device at a lower definition to
Sometimes, it is useful to mirror an Android device at a lower resolution to
increase performance.
To limit both the width and height to some value (e.g. 1024):
@ -192,8 +190,8 @@ scrcpy --max-size 1024
scrcpy -m 1024 # short version
```
The other dimension is computed to that the device aspect ratio is preserved.
That way, a device in 1920×1080 will be mirrored at 1024×576.
The other dimension is computed so that the Android device aspect ratio is
preserved. That way, a device in 1920×1080 will be mirrored at 1024×576.
#### Change bit-rate
@ -215,11 +213,20 @@ scrcpy --max-fps 15
This is officially supported since Android 10, but may work on earlier versions.
The actual capture framerate may be printed to the console:
```
scrcpy --print-fps
```
It may also be enabled or disabled at any time with <kbd>MOD</kbd>+<kbd>i</kbd>.
#### Crop
The device screen may be cropped to mirror only part of the screen.
This is useful for example to mirror only one eye of the Oculus Go:
This is useful, for example, to mirror only one eye of the Oculus Go:
```bash
scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0)
@ -230,7 +237,6 @@ If `--max-size` is also specified, resizing is applied after cropping.
#### Lock video orientation
To lock the orientation of the mirroring:
```bash
@ -255,7 +261,7 @@ crash. It is possible to select a different encoder:
scrcpy --encoder OMX.qcom.video.encoder.avc
```
To list the available encoders, you could pass an invalid encoder name, the
To list the available encoders, you can pass an invalid encoder name; the
error will give the available encoders:
```bash
@ -319,7 +325,7 @@ v4l2-ctl --list-devices
ls /dev/video*
```
To start scrcpy using a v4l2 sink:
To start `scrcpy` using a v4l2 sink:
```bash
scrcpy --v4l2-sink=/dev/videoN
@ -327,7 +333,7 @@ scrcpy --v4l2-sink=/dev/videoN --no-display # disable mirroring window
scrcpy --v4l2-sink=/dev/videoN -N # short version
```
(replace `N` by the device ID, check with `ls /dev/video*`)
(replace `N` with the device ID, check with `ls /dev/video*`)
Once enabled, you can open your video stream with a v4l2-capable tool:
@ -343,7 +349,7 @@ For example, you could capture the video within [OBS].
#### 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]: https://github.com/Genymobile/scrcpy/issues/2464
@ -375,14 +381,14 @@ 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:
port (typically 5555) for incoming _adb_ connections, then run:
```bash
scrcpy --tcpip=192.168.1.1 # default port is 5555
scrcpy --tcpip=192.168.1.1:5555
```
If adb TCP/IP mode is disabled on the device (or if you don't know the IP
If _adb_ TCP/IP mode is disabled on the device (or if you don't know the IP
address), connect the device over USB, then run:
```bash
@ -397,20 +403,32 @@ connect to the device before starting.
Alternatively, it is possible to enable the TCP/IP connection manually using
`adb`:
1. Connect the device to the same Wi-Fi as your computer.
2. Get your device IP address, in Settings → About phone → Status, or by
1. Plug the device into a USB port on your computer.
2. Connect the device to the same Wi-Fi network as your computer.
3. Get your device IP address, in Settings → About phone → Status, or by
executing this command:
```bash
adb shell ip route | awk '{print $9}'
```
3. Enable adb over TCP/IP on your device: `adb tcpip 5555`.
4. Unplug your device.
5. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP`)_.
6. Run `scrcpy` as usual.
4. Enable `adb` over TCP/IP on your device: `adb tcpip 5555`.
5. Unplug your device.
6. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP`
with the device IP address you found)_.
7. Run `scrcpy` as usual.
It may be useful to decrease the bit-rate and the definition:
Since Android 11, a [Wireless debugging option][adb-wireless] allows to bypass
having to physically connect your device directly to your computer.
[adb-wireless]: https://developer.android.com/studio/command-line/adb#connect-to-a-device-over-wi-fi-android-11+
If the connection randomly drops, run your `scrcpy` command to reconnect. If it
says there are no devices/emulators found, try running `adb connect
DEVICE_IP:5555` again, and then `scrcpy` as usual. If it still says there are
none found, try running `adb disconnect`, and then run those two commands again.
It may be useful to decrease the bit-rate and the resolution:
```bash
scrcpy --bit-rate 2M --max-size 800
@ -422,13 +440,16 @@ scrcpy -b2M -m800 # short version
#### Multi-devices
If several devices are listed in `adb devices`, you must specify the _serial_:
If several devices are listed in `adb devices`, you can specify the _serial_:
```bash
scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # short version
```
The serial may also be provided via the environment variable `ANDROID_SERIAL`
(also used by `adb`).
If the device is connected over TCP/IP:
```bash
@ -436,6 +457,19 @@ scrcpy --serial 192.168.0.1:5555
scrcpy -s 192.168.0.1:5555 # short version
```
If only one device is connected via either USB or TCP/IP, it is possible to
select it automatically:
```bash
# Select the only device connected via USB
scrcpy -d # like adb -d
scrcpy --select-usb # long version
# Select the only device connected via TCP/IP
scrcpy -e # like adb -e
scrcpy --select-tcpip # long version
```
You can start several instances of _scrcpy_ for several devices.
#### Autostart on device connection
@ -456,7 +490,7 @@ protocol).
##### Remote ADB server
To connect to a remote ADB server, make the server listen on all interfaces:
To connect to a remote _adb server_, make the server listen on all interfaces:
```bash
adb kill-server
@ -464,17 +498,18 @@ adb -a nodaemon server start
# keep this open
```
**Warning: all communications between clients and ADB server are unencrypted.**
**Warning: all communications between clients and the _adb server_ are
unencrypted.**
Suppose that this server is accessible at 192.168.1.2. Then, from another
terminal, run scrcpy:
terminal, run `scrcpy`:
```bash
export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037
scrcpy --tunnel-host=192.168.1.2
```
By default, scrcpy uses the local port used for `adb forward` tunnel
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):
@ -486,16 +521,16 @@ scrcpy --tunnel-port=1234
##### SSH tunnel
To communicate with a remote ADB server securely, it is preferable to use a SSH
tunnel.
To communicate with a remote _adb server_ securely, it is preferable to use an
SSH tunnel.
First, make sure the ADB server is running on the remote computer:
First, make sure the _adb server_ is running on the remote computer:
```bash
adb start-server
```
Then, establish a SSH tunnel:
Then, establish an SSH tunnel:
```bash
# local 5038 --> remote 5037
@ -504,7 +539,7 @@ ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer
# keep this open
```
From another terminal, run scrcpy:
From another terminal, run `scrcpy`:
```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038
@ -521,7 +556,7 @@ ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer
# keep this open
```
From another terminal, run scrcpy:
From another terminal, run `scrcpy`:
```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038
@ -563,7 +598,7 @@ scrcpy --window-borderless
#### Always on top
To keep the scrcpy window always on top:
To keep the _scrcpy_ window always on top:
```bash
scrcpy --always-on-top
@ -588,7 +623,7 @@ The window may be rotated:
scrcpy --rotation 1
```
Possibles values are:
Possible values:
- `0`: no rotation
- `1`: 90 degrees counterclockwise
- `2`: 180 degrees
@ -637,19 +672,19 @@ adb shell dumpsys display # search "mDisplayId=" in the output
```
The secondary display may only be controlled if the device runs at least Android
10 (otherwise it is mirrored in read-only).
10 (otherwise it is mirrored as read-only).
#### Stay awake
To prevent the device to sleep after some delay when the device is plugged in:
To prevent the device from sleeping after a delay when the device is plugged in:
```bash
scrcpy --stay-awake
scrcpy -w
```
The initial state is restored when scrcpy is closed.
The initial state is restored when _scrcpy_ is closed.
#### Turn screen off
@ -667,9 +702,10 @@ Or by pressing <kbd>MOD</kbd>+<kbd>o</kbd> at any time.
To turn it back on, press <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>.
On Android, the `POWER` button always turns the screen on. For convenience, if
`POWER` is sent via scrcpy (via right-click or <kbd>MOD</kbd>+<kbd>p</kbd>), it
will force to turn the screen off after a small delay (on a best effort basis).
The physical `POWER` button will still cause the screen to be turned on.
`POWER` is sent via _scrcpy_ (via right-click or <kbd>MOD</kbd>+<kbd>p</kbd>),
it will force to turn the screen off after a small delay (on a best effort
basis). The physical `POWER` button will still cause the screen to be turned
on.
It can also be useful to prevent the device from sleeping:
@ -680,12 +716,22 @@ scrcpy -Sw
#### Power off on close
To turn the device screen off when closing scrcpy:
To turn the device screen off when closing _scrcpy_:
```bash
scrcpy --power-off-on-close
```
#### Power on on start
By default, on start, the device is powered on.
To prevent this behavior:
```bash
scrcpy --no-power-on
```
#### Show touches
@ -702,12 +748,13 @@ scrcpy --show-touches
scrcpy -t
```
Note that it only shows _physical_ touches (with the finger on the device).
Note that it only shows _physical_ touches (by a finger on the device).
#### Disable screensaver
By default, scrcpy does not prevent the screensaver to run on the computer.
By default, _scrcpy_ does not prevent the screensaver from running on the
computer.
To disable it:
@ -749,18 +796,18 @@ To copy, cut and paste in such cases (but only supported on Android >= 7):
- <kbd>MOD</kbd>+<kbd>v</kbd> injects `PASTE` (after computer-to-device
clipboard synchronization)
In addition, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> allows to inject the
computer clipboard text as a sequence of key events. This is useful when the
component does not accept text pasting (for example in _Termux_), but it can
break non-ASCII content.
In addition, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> injects the computer
clipboard text as a sequence of key events. This is useful when the component
does not accept text pasting (for example in _Termux_), but it can break
non-ASCII content.
**WARNING:** Pasting the computer clipboard to the device (either via
<kbd>Ctrl</kbd>+<kbd>v</kbd> or <kbd>MOD</kbd>+<kbd>v</kbd>) copies the content
into the device clipboard. As a consequence, any Android application could read
its content. You should avoid to paste sensitive content (like passwords) that
into the Android clipboard. As a consequence, any Android application could read
its content. You should avoid pasting sensitive content (like passwords) that
way.
Some devices do not behave as expected when setting the device clipboard
Some Android devices do not behave as expected when setting the device clipboard
programmatically. An option `--legacy-paste` is provided to change the behavior
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
@ -773,26 +820,29 @@ To disable automatic clipboard synchronization, use
To simulate "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-and-move_.
More precisely, hold <kbd>Ctrl</kbd> while pressing the left-click button. Until
the left-click button is released, all mouse movements scale and rotate the
content (if supported by the app) relative to the center of the screen.
More precisely, hold down <kbd>Ctrl</kbd> while pressing the left-click button.
Until the left-click button is released, all mouse movements scale and rotate
the content (if supported by the app) relative to the center of the screen.
Concretely, scrcpy generates additional touch events from a "virtual finger" at
a location inverted through the center of the screen.
Technically, _scrcpy_ generates additional touch events from a "virtual finger"
at a location inverted through the center of the screen.
#### Physical keyboard simulation (HID)
By default, scrcpy uses Android key or text injection: it works everywhere, but
is limited to ASCII.
By default, _scrcpy_ uses Android key or text injection: it works everywhere,
but is limited to ASCII.
On Linux, scrcpy can simulate a physical USB keyboard on Android to provide a
better input experience (using [USB HID over AOAv2][hid-aoav2]): the virtual
keyboard is disabled and it works for all characters and IME.
Alternatively, `scrcpy` can simulate a physical USB keyboard on Android to
provide a better input experience (using [USB HID over AOAv2][hid-aoav2]): the
virtual keyboard is disabled and it works for all characters and IME.
[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support
However, it only works if the device is connected by USB, and is currently only
supported on Linux.
However, it only works if the device is connected via USB.
Note: On Windows, it may only work in [OTG mode](#otg), not while mirroring (it
is not possible to open a USB device if it is already open by another process
like the _adb daemon_).
To enable this mode:
@ -803,7 +853,7 @@ scrcpy -K # short version
If it fails for some reason (for example because the device is not connected via
USB), it automatically fallbacks to the default mode (with a log in the
console). This allows to use the same command line options when connected over
console). This allows using the same command line options when connected over
USB and TCP/IP.
In this mode, raw key events (scancodes) are sent to the device, independently
@ -825,10 +875,9 @@ a physical keyboard is connected).
#### Physical mouse simulation (HID)
Similarly to the physical keyboard simulation, it is possible to simulate a
physical mouse. Likewise, it only works if the device is connected by USB, and
is currently only supported on Linux.
physical mouse. Likewise, it only works if the device is connected by USB.
By default, scrcpy uses Android mouse events injection, using absolute
By default, _scrcpy_ uses Android mouse events injection with absolute
coordinates. By simulating a physical mouse, a mouse pointer appears on the
Android device, and relative mouse motion, clicks and scrolls are injected.
@ -839,7 +888,7 @@ scrcpy --hid-mouse
scrcpy -M # short version
```
You could also add `--forward-all-clicks` to [forward all mouse
You can also add `--forward-all-clicks` to [forward all mouse
buttons][forward_all_clicks].
[forward_all_clicks]: #right-click-and-middle-click
@ -858,7 +907,7 @@ It is possible to run _scrcpy_ with only physical keyboard and mouse simulation
(HID), as if the computer keyboard and mouse were plugged directly to the device
via an OTG cable.
In this mode, _adb_ (USB debugging) is not necessary, and mirroring is disabled.
In this mode, `adb` (USB debugging) is not necessary, and mirroring is disabled.
To enable OTG mode:
@ -879,12 +928,12 @@ scrcpy --otg # keyboard and mouse
```
Like `--hid-keyboard` and `--hid-mouse`, it only works if the device is
connected by USB, and is currently only supported on Linux.
connected by USB.
#### Text injection preference
There are two kinds of [events][textevents] generated when typing text:
Two kinds of [events][textevents] are generated when typing text:
- _key events_, signaling that a key is pressed or released;
- _text events_, signaling that a text has been entered.
@ -1028,7 +1077,7 @@ _³4th and 5th mouse buttons, if your mouse has them._
_⁴For react-native apps in development, `MENU` triggers development menu._
_⁵Only on Android >= 7._
Shortcuts with repeated keys are executted by releasing and pressing the key a
Shortcuts with repeated keys are executed by releasing and pressing the key a
second time. For example, to execute "Expand settings panel":
1. Press and keep pressing <kbd>MOD</kbd>.
@ -1041,7 +1090,7 @@ handled by the active application.
## Custom paths
To use a specific _adb_ binary, configure its path in the environment variable
To use a specific `adb` binary, configure its path in the environment variable
`ADB`:
```bash
@ -1054,7 +1103,7 @@ To override the path of the `scrcpy-server` file, configure its path in
To override the icon, configure its path in `SCRCPY_ICON_PATH`.
## Why _scrcpy_?
## Why the name _scrcpy_?
A colleague challenged me to find a name as unpronounceable as [gnirehtet].
@ -1071,7 +1120,9 @@ See [BUILD].
## Common issues
See the [FAQ](FAQ.md).
See the [FAQ].
[FAQ]: FAQ.md
## Developers
@ -1106,12 +1157,24 @@ Read the [developers page].
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/
## Contact
If you encounter a bug, please read the [FAQ] first, then open an [issue].
[issue]: https://github.com/Genymobile/scrcpy/issues
For general questions or discussions, you can also use:
- Reddit: [`r/scrcpy`](https://www.reddit.com/r/scrcpy)
- Twitter: [`@scrcpy_app`](https://twitter.com/scrcpy_app)
## Translations
This README is available in other languages:
- [Deutsch (German, `de`) - v1.22](README.de.md)
- [Indonesian (Indonesia, `id`) - v1.16](README.id.md)
- [Italiano (Italiano, `it`) - v1.19](README.it.md)
- [Italiano (Italiano, `it`) - v1.23](README.it.md)
- [日本語 (Japanese, `jp`) - v1.19](README.jp.md)
- [한국어 (Korean, `ko`) - v1.11](README.ko.md)
- [Português Brasileiro (Brazilian Portuguese, `pt-BR`) - v1.19](README.pt-br.md)

View File

@ -704,11 +704,11 @@ scrcpy --disable-screensaver
#### 双指缩放
模拟“双指缩放”:<kbd>Ctrl</kbd>+_按住并移动鼠标_。
模拟“双指缩放”:<kbd>Ctrl</kbd>+_按下并拖动鼠标_。
更准确的说,在按住鼠标左键时按住 <kbd>Ctrl</kbd>。直到松开鼠标左键,所有鼠标移动将以屏幕中心为原点,缩放或旋转内容 (如果应用支持)。
按住 <kbd>Ctrl</kbd> 时按下鼠标左键,直到松开鼠标左键前,移动鼠标会使屏幕内容相对于屏幕中心进行缩放或旋转 (如果应用支持)。
实际上_scrcpy_ 会在关于屏幕中心对称的位置上用“虚拟手指”发出触摸事件。
具体来说_scrcpy_ 会在鼠标位置,以及鼠标以屏幕中心镜像的位置分别生成触摸事件。
#### 物理键盘模拟 (HID)

View File

@ -0,0 +1,122 @@
_scrcpy() {
local cur prev words cword
local opts="
--always-on-top
-b --bit-rate=
--codec-options=
--crop=
-d --select-usb
--disable-screensaver
--display=
--display-buffer=
-e --select-tcpip
--encoder=
--force-adb-forward
--forward-all-clicks
-f --fullscreen
-K --hid-keyboard
-h --help
--legacy-paste
--lock-video-orientation
--lock-video-orientation=
--max-fps=
-M --hid-mouse
-m --max-size=
--no-cleanup
--no-clipboard-on-error
--no-downsize-on-error
-n --no-control
-N --no-display
--no-key-repeat
--no-mipmaps
--no-power-on
--otg
-p --port=
--power-off-on-close
--prefer-text
--print-fps
--push-target=
--raw-key-events
-r --record=
--record-format=
--render-driver=
--rotation=
-s --serial=
--shortcut-mod=
-S --turn-screen-off
-t --show-touches
--tcpip
--tcpip=
--tunnel-host=
--tunnel-port=
--v4l2-buffer=
--v4l2-sink=
-V --verbosity=
-v --version
-w --stay-awake
--window-borderless
--window-title=
--window-x=
--window-y=
--window-width=
--window-height="
_init_completion -s || return
case "$prev" in
--lock-video-orientation)
COMPREPLY=($(compgen -W 'unlocked initial 0 1 2 3' -- "$cur"))
return
;;
-r|--record)
COMPREPLY=($(compgen -f -- "$cur"))
return
;;
--record-format)
COMPREPLY=($(compgen -W 'mkv mp4' -- "$cur"))
return
;;
--render-driver)
COMPREPLY=($(compgen -W 'direct3d opengl opengles2 opengles metal software' -- "$cur"))
return
;;
--rotation)
COMPREPLY=($(compgen -W '0 1 2 3' -- "$cur"))
return
;;
--shortcut-mod)
# Only auto-complete a single key
COMPREPLY=($(compgen -W 'lctrl rctrl lalt ralt lsuper rsuper' -- "$cur"))
return
;;
-V|--verbosity)
COMPREPLY=($(compgen -W 'verbose debug info warn error' -- "$cur"))
return
;;
-b|--bitrate \
|--codec-options \
|--crop \
|--display \
|--display-buffer \
|--encoder \
|--max-fps \
|-m|--max-size \
|-p|--port \
|--push-target \
|-s|--serial \
|--tunnel-host \
|--tunnel-port \
|--v4l2-buffer \
|--v4l2-sink \
|--tcpip \
|--window-*)
# Option accepting an argument, but nothing to auto-complete
return
;;
esac
COMPREPLY=($(compgen -W "$opts" -- "$cur"))
[[ $COMPREPLY == *= ]] && compopt -o nospace
}
complete -F _scrcpy scrcpy

View File

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -0,0 +1,70 @@
#compdef -N scrcpy -N scrcpy.exe
#
# name: scrcpy
# auth: hltdev [hltdev8642@gmail.com]
# desc: completion file for scrcpy (all OSes)
#
local arguments
arguments=(
'--always-on-top[Make scrcpy window always on top \(above other windows\)]'
{-b,--bit-rate=}'[Encode the video at the given bit-rate]'
'--codec-options=[Set a list of comma-separated key\:type=value options for the device encoder]'
'--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]'
{-d,--select-usb}'[Use USB device]'
'--disable-screensaver[Disable screensaver while scrcpy is running]'
'--display=[Specify the display id to mirror]'
'--display-buffer=[Add a buffering delay \(in milliseconds\) before displaying]'
{-e,--select-tcpip}'[Use TCP/IP device]'
'--encoder=[Use a specific MediaCodec encoder \(must be a H.264 encoder\)]'
'--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]'
'--forward-all-clicks[Forward clicks to device]'
{-f,--fullscreen}'[Start in fullscreen]'
{-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]'
{-h,--help}'[Print the help]'
'--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]'
'--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 1 2 3)'
'--max-fps=[Limit the frame rate of screen capture]'
{-M,--hid-mouse}'[Simulate a physical mouse by using HID over AOAv2]'
{-m,--max-size=}'[Limit both the width and height of the video to value]'
'--no-cleanup[Disable device cleanup actions on exit]'
'--no-clipboard-autosync[Disable automatic clipboard synchronization]'
'--no-downsize-on-error[Disable lowering definition on MediaCodec error]'
{-n,--no-control}'[Disable device control \(mirror the device in read only\)]'
{-N,--no-display}'[Do not display device \(during screen recording or when V4L2 sink is enabled\)]'
'--no-key-repeat[Do not forward repeated key events when a key is held down]'
'--no-mipmaps[Disable the generation of mipmaps]'
'--no-power-on[Do not power on the device on start]'
'--otg[Run in OTG mode \(simulating physical keyboard and mouse\)]'
{-p,--port=}'[\[port\[\:port\]\] Set the TCP port \(range\) used by the client to listen]'
'--power-off-on-close[Turn the device screen off when closing scrcpy]'
'--prefer-text[Inject alpha characters and space as text events instead of key events]'
'--print-fps[Start FPS counter, to print frame logs to the console]'
'--push-target=[Set the target directory for pushing files to the device by drag and drop]'
'--raw-key-events[Inject key events for all input keys, and ignore text events]'
{-r,--record=}'[Record screen to file]:record file:_files'
'--record-format=[Force recording format]:format:(mp4 mkv)'
'--render-driver=[Request SDL to use the given render driver]:driver name:(direct3d opengl opengles2 opengles metal software)'
'--rotation=[Set the initial display rotation]:rotation values:(0 1 2 3)'
{-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]'
'--shortcut-mod=[\[key1,key2+key3,...\] Specify the modifiers to use for scrcpy shortcuts]:shortcut mod:(lctrl rctrl lalt ralt lsuper rsuper)'
{-S,--turn-screen-off}'[Turn the device screen off immediately]'
{-t,--show-touches}'[Show physical touches]'
'--tcpip[\(optional \[ip\:port\]\) Configure and connect the device over TCP/IP]'
'--tunnel-host=[Set the IP address of the adb tunnel to reach the scrcpy server]'
'--tunnel-port=[Set the TCP port of the adb tunnel to reach the scrcpy server]'
'--v4l2-buffer=[Add a buffering delay \(in milliseconds\) before pushing frames]'
'--v4l2-sink=[\[\/dev\/videoN\] Output to v4l2loopback device]'
{-V,--verbosity=}'[Set the log level]:verbosity:(verbose debug info warn error)'
{-v,--version}'[Print the version of scrcpy]'
{-w,--stay-awake}'[Keep the device on while scrcpy is running, when the device is plugged in]'
'--window-borderless[Disable window decorations \(display borderless window\)]'
'--window-title=[Set a custom window title]'
'--window-x=[Set the initial window horizontal position]'
'--window-y=[Set the initial window vertical position]'
'--window-width=[Set the initial window width]'
'--window-height=[Set the initial window height]'
)
_arguments -s $arguments

View File

@ -1,6 +1,7 @@
src = [
'src/main.c',
'src/adb/adb.c',
'src/adb/adb_device.c',
'src/adb/adb_parser.c',
'src/adb/adb_tunnel.c',
'src/cli.c',
@ -25,6 +26,7 @@ src = [
'src/scrcpy.c',
'src/screen.c',
'src/server.c',
'src/version.c',
'src/video_buffer.c',
'src/util/acksync.c',
'src/util/file.c',
@ -67,12 +69,12 @@ else
endif
endif
v4l2_support = host_machine.system() == 'linux'
v4l2_support = get_option('v4l2') and host_machine.system() == 'linux'
if v4l2_support
src += [ 'src/v4l2_sink.c' ]
endif
usb_support = host_machine.system() == 'linux'
usb_support = get_option('usb')
if usb_support
src += [
'src/usb/aoa_hid.c',
@ -109,9 +111,9 @@ if not crossbuild_windows
else
# cross-compile mingw32 build (from Linux to Windows)
prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2')
sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/data/' + prebuilt_sdl2 + '/bin'
sdl2_lib_dir = meson.current_source_dir() + '/../prebuilt-deps/data/' + prebuilt_sdl2 + '/lib'
sdl2_include_dir = '../prebuilt-deps/data/' + prebuilt_sdl2 + '/include'
sdl2_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_sdl2 + '/bin'
sdl2_lib_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_sdl2 + '/lib'
sdl2_include_dir = 'prebuilt-deps/data/' + prebuilt_sdl2 + '/include'
sdl2 = declare_dependency(
dependencies: [
@ -122,8 +124,8 @@ else
)
prebuilt_ffmpeg = meson.get_cross_property('prebuilt_ffmpeg')
ffmpeg_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/data/' + prebuilt_ffmpeg + '/bin'
ffmpeg_include_dir = '../prebuilt-deps/data/' + prebuilt_ffmpeg + '/include'
ffmpeg_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_ffmpeg + '/bin'
ffmpeg_include_dir = 'prebuilt-deps/data/' + prebuilt_ffmpeg + '/include'
# ffmpeg versions are different for win32 and win64 builds
ffmpeg_avcodec = meson.get_cross_property('ffmpeg_avcodec')
@ -139,9 +141,22 @@ else
include_directories: include_directories(ffmpeg_include_dir)
)
prebuilt_libusb = meson.get_cross_property('prebuilt_libusb')
prebuilt_libusb_root = meson.get_cross_property('prebuilt_libusb_root')
libusb_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_libusb
libusb_include_dir = 'prebuilt-deps/data/' + prebuilt_libusb_root + '/include'
libusb = declare_dependency(
dependencies: [
cc.find_library('msys-usb-1.0', dirs: libusb_bin_dir),
],
include_directories: include_directories(libusb_include_dir)
)
dependencies = [
ffmpeg,
sdl2,
libusb,
cc.find_library('mingw32')
]
@ -209,9 +224,13 @@ executable('scrcpy', src,
c_args: [])
install_man('scrcpy.1')
install_data('../data/icon.png',
install_data('data/icon.png',
rename: 'scrcpy.png',
install_dir: 'share/icons/hicolor/256x256/apps')
install_data('data/zsh-completion/_scrcpy',
install_dir: 'share/zsh/site-functions')
install_data('data/bash-completion/scrcpy',
install_dir: 'share/bash-completion/completions')
### TESTS
@ -221,6 +240,7 @@ if get_option('buildtype') == 'debug'
tests = [
['test_adb_parser', [
'tests/test_adb_parser.c',
'src/adb/adb_device.c',
'src/adb/adb_parser.c',
'src/util/str.c',
'src/util/strbuf.c',
@ -266,6 +286,9 @@ if get_option('buildtype') == 'debug'
'src/util/str.c',
'src/util/strbuf.c',
]],
['test_vector', [
'tests/test_vector.c',
]],
]
foreach t : tests

View File

@ -6,10 +6,10 @@ cd "$DIR"
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
DEP_DIR=platform-tools-31.0.3
DEP_DIR=platform-tools-33.0.1
FILENAME=platform-tools_r31.0.3-windows.zip
SHA256SUM=0f4b8fdd26af2c3733539d6eebb3c2ed499ea1d4bb1f4e0ecc2d6016961a6e24
FILENAME=platform-tools_r33.0.1-windows.zip
SHA256SUM=c1f02d42ea24ef4ff2a405ae7370e764ef4546f9b3e4520f5571a00ed5012c42
if [[ -d "$DEP_DIR" ]]
then

View File

@ -6,10 +6,11 @@ cd "$DIR"
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
DEP_DIR=ffmpeg-win64-5.0
VERSION=5.0.1
DEP_DIR=ffmpeg-win64-$VERSION
FILENAME=ffmpeg-5.0-full_build-shared.7z
SHA256SUM=e5900f6cecd4c438d398bd2fc308736c10b857cd8dd61c11bcfb05bff5d1211a
FILENAME=ffmpeg-$VERSION-full_build-shared.7z
SHA256SUM=ded28435b6f04b74f5ef5a6a13761233bce9e8e9f8ecb0eabe936fd36a778b0c
if [[ -d "$DEP_DIR" ]]
then
@ -17,13 +18,13 @@ then
exit 0
fi
get_file "https://github.com/GyanD/codexffmpeg/releases/download/5.0/$FILENAME" \
get_file "https://github.com/GyanD/codexffmpeg/releases/download/$VERSION/$FILENAME" \
"$FILENAME" "$SHA256SUM"
mkdir "$DEP_DIR"
cd "$DEP_DIR"
ZIP_PREFIX=ffmpeg-5.0-full_build-shared
ZIP_PREFIX=ffmpeg-$VERSION-full_build-shared
7z x "../$FILENAME" \
"$ZIP_PREFIX"/bin/avutil-57.dll \
"$ZIP_PREFIX"/bin/avcodec-59.dll \

View File

@ -0,0 +1,34 @@
#!/usr/bin/env bash
set -e
DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DIR"
. common
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
DEP_DIR=libusb-1.0.26
FILENAME=libusb-1.0.26-binaries.7z
SHA256SUM=9c242696342dbde9cdc47239391f71833939bf9f7aa2bbb28cdaabe890465ec5
if [[ -d "$DEP_DIR" ]]
then
echo "$DEP_DIR" found
exit 0
fi
get_file "https://github.com/libusb/libusb/releases/download/v1.0.26/$FILENAME" "$FILENAME" "$SHA256SUM"
mkdir "$DEP_DIR"
cd "$DEP_DIR"
# include/ is the same in all folders of the archive
7z x "../$FILENAME" \
libusb-1.0.26-binaries/libusb-MinGW-Win32/bin/msys-usb-1.0.dll \
libusb-1.0.26-binaries/libusb-MinGW-x64/bin/msys-usb-1.0.dll \
libusb-1.0.26-binaries/libusb-MinGW-x64/include/
mv libusb-1.0.26-binaries/libusb-MinGW-Win32/bin MinGW-Win32
mv libusb-1.0.26-binaries/libusb-MinGW-x64/bin MinGW-x64
mv libusb-1.0.26-binaries/libusb-MinGW-x64/include .
rm -rf libusb-1.0.26-binaries

View File

@ -6,10 +6,10 @@ cd "$DIR"
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
DEP_DIR=SDL2-2.0.20
DEP_DIR=SDL2-2.0.22
FILENAME=SDL2-devel-2.0.20-mingw.tar.gz
SHA256SUM=38094d82a857d6c62352e5c5cdec74948c5b4d25c59cbd298d6d233568976bd1
FILENAME=SDL2-devel-2.0.22-mingw.tar.gz
SHA256SUM=0e91e35973366aa1e6f81ee368924d9b4f93f9da4d2f2a89ec80b06eadcf23d1
if [[ -d "$DEP_DIR" ]]
then

View File

@ -1,6 +1,6 @@
#include <winuser.h>
0 ICON "../data/icon.ico"
0 ICON "data/icon.ico"
1 RT_MANIFEST "scrcpy-windows.manifest"
2 VERSIONINFO
BEGIN
@ -13,7 +13,7 @@ BEGIN
VALUE "LegalCopyright", "Romain Vimont, Genymobile"
VALUE "OriginalFilename", "scrcpy.exe"
VALUE "ProductName", "scrcpy"
VALUE "ProductVersion", "1.22"
VALUE "ProductVersion", "1.24"
END
END
BLOCK "VarFileInfo"

View File

@ -43,6 +43,12 @@ The values are expressed in the device natural orientation (typically, portrait
.B \-\-max\-size
value is computed on the cropped size.
.TP
.B \-d, \-\-select\-usb
Use USB device (if there is exactly one, like adb -d).
Also see \fB\-e\fR (\fB\-\-select\-tcpip\fR).
.TP
.BI "\-\-disable-screensaver"
Disable screensaver while scrcpy is running.
@ -62,6 +68,12 @@ Add a buffering delay (in milliseconds) before displaying. This increases latenc
Default is 0 (no buffering).
.TP
.B \-e, \-\-select\-tcpip
Use TCP/IP device (if there is exactly one, like adb -e).
Also see \fB\-d\fR (\fB\-\-select\-usb\fR).
.TP
.BI "\-\-encoder " name
Use a specific MediaCodec encoder (must be a H.264 encoder).
@ -88,7 +100,7 @@ Simulate a physical keyboard by using HID over AOAv2.
This provides a better experience for IME users, and allows to generate non-ASCII characters, contrary to the default injection method.
It may only work over USB, and is currently only supported on Linux.
It may only work over USB.
The keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly:
@ -130,10 +142,16 @@ In this mode, the computer mouse is captured to control the device directly (rel
LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse back to the computer.
It may only work over USB, and is currently only supported on Linux.
It may only work over USB.
Also see \fB\-\-hid\-keyboard\fR.
.TP
.B \-\-no\-cleanup
By default, scrcpy removes the server binary from the device and restores the device state (show touches, stay awake and power mode) on exit.
This option disables this cleanup.
.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.
@ -162,6 +180,10 @@ Do not forward repeated key events when a key is held down.
.B \-\-no\-mipmaps
If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps.
.TP
.B \-\-no\-power\-on
Do not power on the device on start.
.TP
.B \-\-otg
Run in OTG mode: simulate physical keyboard and mouse, as if the computer keyboard and mouse were plugged directly to the device via an OTG cable.
@ -172,7 +194,7 @@ LAlt, LSuper or RSuper toggle the mouse capture mode, to give control of the mou
If any of \fB\-\-hid\-keyboard\fR or \fB\-\-hid\-mouse\fR is set, only enable keyboard or mouse respectively, otherwise enable both.
It may only work over USB, and is currently only supported on Linux.
It may only work over USB.
See \fB\-\-hid\-keyboard\fR and \fB\-\-hid\-mouse\fR.
@ -193,6 +215,10 @@ Inject alpha characters and space as text events instead of key events.
This avoids issues when combining multiple keys to enter special characters,
but breaks the expected behavior of alpha keys in games (typically WASD).
.TP
.B "\-\-print\-fps
Start FPS counter, to print framerate logs to the console. It can be started or stopped at any time with MOD+i.
.TP
.BI "\-\-push\-target " path
Set the target directory for pushing files to the device by drag & drop. It is passed as\-is to "adb push".
@ -333,6 +359,12 @@ Set the initial window height.
Default is 0 (automatic).
.SH EXIT STATUS
.B scrcpy
will exit with code 0 on normal program termination. If an initial
connection cannot be established, the exit code 1 will be returned. If the
device disconnects while a session is active, exit code 2 will be returned.
.SH SHORTCUTS
In the following list, MOD is the shortcut modifier. By default, it's (left)
@ -449,6 +481,10 @@ Push file to device (see \fB\-\-push\-target\fR)
.B ADB
Path to adb.
.TP
.B ANDROID_SERIAL
Device serial to use if no selector (-s, -d, -e or --tcpip=<addr>) is specified.
.TP
.B SCRCPY_ICON_PATH
Path to the program icon.

View File

@ -5,6 +5,7 @@
#include <stdlib.h>
#include <string.h>
#include "adb_device.h"
#include "adb_parser.h"
#include "util/file.h"
#include "util/log.h"
@ -150,7 +151,7 @@ process_check_success_internal(sc_pid pid, const char *name, bool close,
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)) {
if (intr && !sc_intr_set_process(intr, pid)) {
// Already interrupted
return false;
}
@ -158,7 +159,9 @@ process_check_success_intr(struct sc_intr *intr, sc_pid pid, const char *name,
// 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);
if (intr) {
sc_intr_set_process(intr, SC_PROCESS_NONE);
}
// Close separately
sc_process_close(pid);
@ -194,6 +197,22 @@ sc_adb_execute(const char *const argv[], unsigned flags) {
return sc_adb_execute_p(argv, flags, NULL);
}
bool
sc_adb_start_server(struct sc_intr *intr, unsigned flags) {
const char *const argv[] = SC_ADB_COMMAND("start-server");
sc_pid pid = sc_adb_execute(argv, flags);
return process_check_success_intr(intr, pid, "adb start-server", flags);
}
bool
sc_adb_kill_server(struct sc_intr *intr, unsigned flags) {
const char *const argv[] = SC_ADB_COMMAND("kill-server");
sc_pid pid = sc_adb_execute(argv, flags);
return process_check_success_intr(intr, pid, "adb kill-server", flags);
}
bool
sc_adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
const char *device_socket_name, unsigned flags) {
@ -374,6 +393,250 @@ sc_adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags) {
return process_check_success_intr(intr, pid, "adb disconnect", flags);
}
static bool
sc_adb_list_devices(struct sc_intr *intr, unsigned flags,
struct sc_vec_adb_devices *out_vec) {
const char *const argv[] = SC_ADB_COMMAND("devices", "-l");
#define BUFSIZE 65536
char *buf = malloc(BUFSIZE);
if (!buf) {
LOG_OOM();
return false;
}
sc_pipe pout;
sc_pid pid = sc_adb_execute_p(argv, flags, &pout);
if (pid == SC_PROCESS_NONE) {
LOGE("Could not execute \"adb devices -l\"");
free(buf);
return false;
}
ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, BUFSIZE - 1);
sc_pipe_close(pout);
bool ok = process_check_success_intr(intr, pid, "adb devices -l", flags);
if (!ok) {
free(buf);
return false;
}
if (r == -1) {
free(buf);
return false;
}
assert((size_t) r < BUFSIZE);
if (r == BUFSIZE - 1) {
// The implementation assumes that the output of "adb devices -l" fits
// in the buffer in a single pass
LOGW("Result of \"adb devices -l\" does not fit in 64Kb. "
"Please report an issue.");
return false;
}
// It is parsed as a NUL-terminated string
buf[r] = '\0';
// List all devices to the output list directly
ok = sc_adb_parse_devices(buf, out_vec);
free(buf);
return ok;
}
static bool
sc_adb_accept_device(const struct sc_adb_device *device,
const struct sc_adb_device_selector *selector) {
switch (selector->type) {
case SC_ADB_DEVICE_SELECT_ALL:
return true;
case SC_ADB_DEVICE_SELECT_SERIAL:
assert(selector->serial);
char *device_serial_colon = strchr(device->serial, ':');
if (device_serial_colon) {
// The device serial is an IP:port...
char *serial_colon = strchr(selector->serial, ':');
if (!serial_colon) {
// But the requested serial has no ':', so only consider
// the IP part of the device serial. This allows to use
// "192.168.1.1" to match any "192.168.1.1:port".
size_t serial_len = strlen(selector->serial);
size_t device_ip_len = device_serial_colon - device->serial;
if (serial_len != device_ip_len) {
// They are not equal, they don't even have the same
// length
return false;
}
return !strncmp(selector->serial, device->serial,
device_ip_len);
}
}
return !strcmp(selector->serial, device->serial);
case SC_ADB_DEVICE_SELECT_USB:
return sc_adb_device_get_type(device->serial) ==
SC_ADB_DEVICE_TYPE_USB;
case SC_ADB_DEVICE_SELECT_TCPIP:
// Both emulators and TCP/IP devices are selected via -e
return sc_adb_device_get_type(device->serial) !=
SC_ADB_DEVICE_TYPE_USB;
default:
assert(!"Missing SC_ADB_DEVICE_SELECT_* handling");
break;
}
return false;
}
static size_t
sc_adb_devices_select(struct sc_adb_device *devices, size_t len,
const struct sc_adb_device_selector *selector,
size_t *idx_out) {
size_t count = 0;
for (size_t i = 0; i < len; ++i) {
struct sc_adb_device *device = &devices[i];
device->selected = sc_adb_accept_device(device, selector);
if (device->selected) {
if (idx_out && !count) {
*idx_out = i;
}
++count;
}
}
return count;
}
static void
sc_adb_devices_log(enum sc_log_level level, struct sc_adb_device *devices,
size_t count) {
for (size_t i = 0; i < count; ++i) {
struct sc_adb_device *d = &devices[i];
const char *selection = d->selected ? "-->" : " ";
bool is_usb =
sc_adb_device_get_type(d->serial) == SC_ADB_DEVICE_TYPE_USB;
const char *type = is_usb ? " (usb)"
: "(tcpip)";
LOG(level, " %s %s %-20s %16s %s",
selection, type, d->serial, d->state, d->model ? d->model : "");
}
}
static bool
sc_adb_device_check_state(struct sc_adb_device *device,
struct sc_adb_device *devices, size_t count) {
const char *state = device->state;
if (!strcmp("device", state)) {
return true;
}
if (!strcmp("unauthorized", state)) {
LOGE("Device is unauthorized:");
sc_adb_devices_log(SC_LOG_LEVEL_ERROR, devices, count);
LOGE("A popup should open on the device to request authorization.");
LOGE("Check the FAQ: "
"<https://github.com/Genymobile/scrcpy/blob/master/FAQ.md>");
} else {
LOGE("Device could not be connected (state=%s)", state);
}
return false;
}
bool
sc_adb_select_device(struct sc_intr *intr,
const struct sc_adb_device_selector *selector,
unsigned flags, struct sc_adb_device *out_device) {
struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER;
bool ok = sc_adb_list_devices(intr, flags, &vec);
if (!ok) {
LOGE("Could not list ADB devices");
return false;
}
if (vec.size == 0) {
LOGE("Could not find any ADB device");
return false;
}
size_t sel_idx; // index of the single matching device if sel_count == 1
size_t sel_count =
sc_adb_devices_select(vec.data, vec.size, selector, &sel_idx);
if (sel_count == 0) {
// if count > 0 && sel_count == 0, then necessarily a selection is
// requested
assert(selector->type != SC_ADB_DEVICE_SELECT_ALL);
switch (selector->type) {
case SC_ADB_DEVICE_SELECT_SERIAL:
assert(selector->serial);
LOGE("Could not find ADB device %s:", selector->serial);
break;
case SC_ADB_DEVICE_SELECT_USB:
LOGE("Could not find any ADB device over USB:");
break;
case SC_ADB_DEVICE_SELECT_TCPIP:
LOGE("Could not find any ADB device over TCP/IP:");
break;
default:
assert(!"Unexpected selector type");
break;
}
sc_adb_devices_log(SC_LOG_LEVEL_ERROR, vec.data, vec.size);
sc_adb_devices_destroy(&vec);
return false;
}
if (sel_count > 1) {
switch (selector->type) {
case SC_ADB_DEVICE_SELECT_ALL:
LOGE("Multiple (%" SC_PRIsizet ") ADB devices:", sel_count);
break;
case SC_ADB_DEVICE_SELECT_SERIAL:
assert(selector->serial);
LOGE("Multiple (%" SC_PRIsizet ") ADB devices with serial %s:",
sel_count, selector->serial);
break;
case SC_ADB_DEVICE_SELECT_USB:
LOGE("Multiple (%" SC_PRIsizet ") ADB devices over USB:",
sel_count);
break;
case SC_ADB_DEVICE_SELECT_TCPIP:
LOGE("Multiple (%" SC_PRIsizet ") ADB devices over TCP/IP:",
sel_count);
break;
default:
assert(!"Unexpected selector type");
break;
}
sc_adb_devices_log(SC_LOG_LEVEL_ERROR, vec.data, vec.size);
LOGE("Select a device via -s (--serial), -d (--select-usb) or -e "
"(--select-tcpip)");
sc_adb_devices_destroy(&vec);
return false;
}
assert(sel_count == 1); // sel_idx is valid only if sel_count == 1
struct sc_adb_device *device = &vec.data[sel_idx];
ok = sc_adb_device_check_state(device, vec.data, vec.size);
if (!ok) {
sc_adb_devices_destroy(&vec);
return false;
}
LOGD("ADB device found:");
sc_adb_devices_log(SC_LOG_LEVEL_DEBUG, vec.data, vec.size);
// Move devics into out_device (do not destroy device)
sc_adb_device_move(out_device, device);
sc_adb_devices_destroy(&vec);
return true;
}
char *
sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop,
unsigned flags) {
@ -409,38 +672,6 @@ sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop,
return strdup(buf);
}
char *
sc_adb_get_serialno(struct sc_intr *intr, unsigned flags) {
const char *const argv[] = SC_ADB_COMMAND("get-serialno");
sc_pipe pout;
sc_pid pid = sc_adb_execute_p(argv, 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) - 1);
sc_pipe_close(pout);
bool ok = process_check_success_intr(intr, pid, "adb get-serialno", flags);
if (!ok) {
return NULL;
}
if (r == -1) {
return NULL;
}
assert((size_t) r < sizeof(buf));
buf[r] = '\0';
size_t len = strcspn(buf, " \r\n");
buf[len] = '\0';
return strdup(buf);
}
char *
sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) {
assert(serial);
@ -473,12 +704,12 @@ sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) {
// 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");
"Please report an issue.");
return NULL;
}
// It is parsed as a NUL-terminated string
buf[r] = '\0';
return sc_adb_parse_device_ip_from_output(buf);
return sc_adb_parse_device_ip(buf);
}

View File

@ -6,6 +6,7 @@
#include <stdbool.h>
#include <inttypes.h>
#include "adb_device.h"
#include "util/intr.h"
#define SC_ADB_NO_STDOUT (1 << 0)
@ -17,9 +18,27 @@
const char *
sc_adb_get_executable(void);
enum sc_adb_device_selector_type {
SC_ADB_DEVICE_SELECT_ALL,
SC_ADB_DEVICE_SELECT_SERIAL,
SC_ADB_DEVICE_SELECT_USB,
SC_ADB_DEVICE_SELECT_TCPIP,
};
struct sc_adb_device_selector {
enum sc_adb_device_selector_type type;
const char *serial;
};
sc_pid
sc_adb_execute(const char *const argv[], unsigned flags);
bool
sc_adb_start_server(struct sc_intr *intr, unsigned flags);
bool
sc_adb_kill_server(struct sc_intr *intr, unsigned flags);
bool
sc_adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
const char *device_socket_name, unsigned flags);
@ -69,6 +88,16 @@ sc_adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags);
bool
sc_adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags);
/**
* Execute `adb devices` and parse the result to select a device
*
* Return true if a single matching device is found, and write it to out_device.
*/
bool
sc_adb_select_device(struct sc_intr *intr,
const struct sc_adb_device_selector *selector,
unsigned flags, struct sc_adb_device *out_device);
/**
* Execute `adb getprop <prop>`
*/
@ -76,14 +105,6 @@ char *
sc_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 *
sc_adb_get_serialno(struct sc_intr *intr, unsigned flags);
/**
* Attempt to retrieve the device IP
*

43
app/src/adb/adb_device.c Normal file
View File

@ -0,0 +1,43 @@
#include "adb_device.h"
#include <stdlib.h>
#include <string.h>
void
sc_adb_device_destroy(struct sc_adb_device *device) {
free(device->serial);
free(device->state);
free(device->model);
}
void
sc_adb_device_move(struct sc_adb_device *dst, struct sc_adb_device *src) {
*dst = *src;
src->serial = NULL;
src->state = NULL;
src->model = NULL;
}
void
sc_adb_devices_destroy(struct sc_vec_adb_devices *devices) {
for (size_t i = 0; i < devices->size; ++i) {
sc_adb_device_destroy(&devices->data[i]);
}
sc_vector_destroy(devices);
}
enum sc_adb_device_type
sc_adb_device_get_type(const char *serial) {
// Starts with "emulator-"
if (!strncmp(serial, "emulator-", sizeof("emulator-") - 1)) {
return SC_ADB_DEVICE_TYPE_EMULATOR;
}
// If the serial contains a ':', then it is a TCP/IP device (it is
// sufficient to distinguish an ip:port from a real USB serial)
if (strchr(serial, ':')) {
return SC_ADB_DEVICE_TYPE_TCPIP;
}
return SC_ADB_DEVICE_TYPE_USB;
}

50
app/src/adb/adb_device.h Normal file
View File

@ -0,0 +1,50 @@
#ifndef SC_ADB_DEVICE_H
#define SC_ADB_DEVICE_H
#include "common.h"
#include <stdbool.h>
#include <stddef.h>
#include "util/vector.h"
struct sc_adb_device {
char *serial;
char *state;
char *model;
bool selected;
};
enum sc_adb_device_type {
SC_ADB_DEVICE_TYPE_USB,
SC_ADB_DEVICE_TYPE_TCPIP,
SC_ADB_DEVICE_TYPE_EMULATOR,
};
struct sc_vec_adb_devices SC_VECTOR(struct sc_adb_device);
void
sc_adb_device_destroy(struct sc_adb_device *device);
/**
* Move src to dst
*
* After this call, the content of src is undefined, except that
* sc_adb_device_destroy() can be called.
*
* This is useful to take a device from a list that will be destroyed, without
* making unnecessary copies.
*/
void
sc_adb_device_move(struct sc_adb_device *dst, struct sc_adb_device *src);
void
sc_adb_devices_destroy(struct sc_vec_adb_devices *devices);
/**
* Deduce the device type from the serial
*/
enum sc_adb_device_type
sc_adb_device_get_type(const char *serial);
#endif

View File

@ -1,11 +1,166 @@
#include "adb_parser.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "util/log.h"
#include "util/str.h"
bool
sc_adb_parse_device(char *line, struct sc_adb_device *device) {
// One device line looks like:
// "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
// "device:MyDevice transport_id:1"
if (line[0] == '*') {
// Garbage lines printed by adb daemon while starting start with a '*'
return false;
}
if (!strncmp("adb server", line, sizeof("adb server") - 1)) {
// Ignore lines starting with "adb server":
// adb server version (41) doesn't match this client (39); killing...
return false;
}
char *s = line; // cursor in the line
// After the serial:
// - "adb devices" writes a single '\t'
// - "adb devices -l" writes multiple spaces
// For flexibility, accept both.
size_t serial_len = strcspn(s, " \t");
if (!serial_len) {
// empty serial
return false;
}
bool eol = s[serial_len] == '\0';
if (eol) {
// serial alone is unexpected
return false;
}
s[serial_len] = '\0';
char *serial = s;
s += serial_len + 1;
// After the serial, there might be several spaces
s += strspn(s, " \t"); // consume all separators
size_t state_len = strcspn(s, " ");
if (!state_len) {
// empty state
return false;
}
eol = s[state_len] == '\0';
s[state_len] = '\0';
char *state = s;
char *model = NULL;
if (!eol) {
s += state_len + 1;
// Iterate over all properties "key:value key:value ..."
for (;;) {
size_t token_len = strcspn(s, " ");
if (!token_len) {
break;
}
eol = s[token_len] == '\0';
s[token_len] = '\0';
char *token = s;
if (!strncmp("model:", token, sizeof("model:") - 1)) {
model = &token[sizeof("model:") - 1];
// We only need the model
break;
}
if (eol) {
break;
} else {
s+= token_len + 1;
}
}
}
device->serial = strdup(serial);
if (!device->serial) {
return false;
}
device->state = strdup(state);
if (!device->state) {
free(device->serial);
return false;
}
if (model) {
device->model = strdup(model);
if (!device->model) {
LOG_OOM();
// model is optional, do not fail
}
} else {
device->model = NULL;
}
device->selected = false;
return true;
}
bool
sc_adb_parse_devices(char *str, struct sc_vec_adb_devices *out_vec) {
#define HEADER "List of devices attached"
#define HEADER_LEN (sizeof(HEADER) - 1)
bool header_found = false;
size_t idx_line = 0;
while (str[idx_line] != '\0') {
char *line = &str[idx_line];
size_t len = strcspn(line, "\n");
// The next line starts after the '\n' (replaced by `\0`)
idx_line += len;
if (str[idx_line] != '\0') {
// The next line starts after the '\n'
++idx_line;
}
if (!header_found) {
if (!strncmp(line, HEADER, HEADER_LEN)) {
header_found = true;
}
// Skip everything until the header, there might be garbage lines
// related to daemon starting before
continue;
}
// The line, but without any trailing '\r'
size_t line_len = sc_str_remove_trailing_cr(line, len);
line[line_len] = '\0';
struct sc_adb_device device;
bool ok = sc_adb_parse_device(line, &device);
if (!ok) {
continue;
}
ok = sc_vector_push(out_vec, device);
if (!ok) {
LOG_OOM();
LOGE("Could not push adb_device to vector");
sc_adb_device_destroy(&device);
// continue anyway
continue;
}
}
assert(header_found || out_vec->size == 0);
return header_found;
}
static char *
sc_adb_parse_device_ip_from_line(char *line) {
// One line from "ip route" looks like:
@ -44,7 +199,7 @@ sc_adb_parse_device_ip_from_line(char *line) {
}
char *
sc_adb_parse_device_ip_from_output(char *str) {
sc_adb_parse_device_ip(char *str) {
size_t idx_line = 0;
while (str[idx_line] != '\0') {
char *line = &str[idx_line];
@ -64,7 +219,7 @@ sc_adb_parse_device_ip_from_output(char *str) {
if (str[idx_line] != '\0') {
// The next line starts after the '\n'
idx_line += 1;
++idx_line;
}
}

View File

@ -5,6 +5,18 @@
#include <stddef.h>
#include "adb_device.h"
/**
* Parse the available devices from the output of `adb devices`
*
* The parameter must be a NUL-terminated string.
*
* Warning: this function modifies the buffer for optimization purposes.
*/
bool
sc_adb_parse_devices(char *str, struct sc_vec_adb_devices *out_vec);
/**
* Parse the ip from the output of `adb shell ip route`
*
@ -13,6 +25,6 @@
* Warning: this function modifies the buffer for optimization purposes.
*/
char *
sc_adb_parse_device_ip_from_output(char *str);
sc_adb_parse_device_ip(char *str);
#endif

View File

@ -54,6 +54,9 @@
#define OPT_RAW_KEY_EVENTS 1034
#define OPT_NO_DOWNSIZE_ON_ERROR 1035
#define OPT_OTG 1036
#define OPT_NO_CLEANUP 1037
#define OPT_PRINT_FPS 1038
#define OPT_NO_POWER_ON 1039
struct sc_option {
char shortopt;
@ -78,6 +81,11 @@ struct sc_envvar {
const char *text;
};
struct sc_exit_status {
unsigned value;
const char *text;
};
struct sc_getopt_adapter {
char *optstring;
struct option *longopts;
@ -116,7 +124,13 @@ static const struct sc_option options[] = {
.text = "Crop the device screen on the server.\n"
"The values are expressed in the device natural orientation "
"(typically, portrait for a phone, landscape for a tablet). "
"Any --max-size value is cmoputed on the cropped size.",
"Any --max-size value is computed on the cropped size.",
},
{
.shortopt = 'd',
.longopt = "select-usb",
.text = "Use USB device (if there is exactly one, like adb -d).\n"
"Also see -e (--select-tcpip).",
},
{
.longopt_id = OPT_DISABLE_SCREENSAVER,
@ -141,6 +155,12 @@ static const struct sc_option options[] = {
"This increases latency to compensate for jitter.\n"
"Default is 0 (no buffering).",
},
{
.shortopt = 'e',
.longopt = "select-tcpip",
.text = "Use TCP/IP device (if there is exactly one, like adb -e).\n"
"Also see -d (--select-usb).",
},
{
.longopt_id = OPT_ENCODER_NAME,
.longopt = "encoder",
@ -172,8 +192,7 @@ static const struct sc_option options[] = {
"It provides a better experience for IME users, and allows to "
"generate non-ASCII characters, contrary to the default "
"injection method.\n"
"It may only work over USB, and is currently only supported "
"on Linux.\n"
"It may only work over USB.\n"
"The keyboard layout must be configured (once and for all) on "
"the device, via Settings -> System -> Languages and input -> "
"Physical keyboard. This settings page can be started "
@ -225,8 +244,7 @@ static const struct sc_option options[] = {
"device directly (relative mouse mode).\n"
"LAlt, LSuper or RSuper toggle the capture mode, to give "
"control of the mouse back to the computer.\n"
"It may only work over USB, and is currently only supported "
"on Linux.\n"
"It may only work over USB.\n"
"Also see --hid-keyboard.",
},
{
@ -239,11 +257,12 @@ static const struct sc_option options[] = {
"Default is 0 (unlimited).",
},
{
.longopt_id = OPT_NO_DOWNSIZE_ON_ERROR,
.longopt = "no-downsize-on-error",
.text = "By default, on MediaCodec error, scrcpy automatically tries "
"again with a lower definition.\n"
"This option disables this behavior.",
.longopt_id = OPT_NO_CLEANUP,
.longopt = "no-cleanup",
.text = "By default, scrcpy removes the server binary from the device "
"and restores the device state (show touches, stay awake and "
"power mode) on exit.\n"
"This option disables this cleanup."
},
{
.longopt_id = OPT_NO_CLIPBOARD_AUTOSYNC,
@ -254,6 +273,13 @@ static const struct sc_option options[] = {
"it changes.\n"
"This option disables this automatic synchronization."
},
{
.longopt_id = OPT_NO_DOWNSIZE_ON_ERROR,
.longopt = "no-downsize-on-error",
.text = "By default, on MediaCodec error, scrcpy automatically tries "
"again with a lower definition.\n"
"This option disables this behavior.",
},
{
.shortopt = 'n',
.longopt = "no-control",
@ -277,6 +303,11 @@ static const struct sc_option options[] = {
"mipmaps are automatically generated to improve downscaling "
"quality. This option disables the generation of mipmaps.",
},
{
.longopt_id = OPT_NO_POWER_ON,
.longopt = "no-power-on",
.text = "Do not power on the device on start.",
},
{
.longopt_id = OPT_OTG,
.longopt = "otg",
@ -289,8 +320,7 @@ static const struct sc_option options[] = {
"control of the mouse back to the computer.\n"
"If any of --hid-keyboard or --hid-mouse is set, only enable "
"keyboard or mouse respectively, otherwise enable both.\n"
"It may only work over USB, and is currently only supported "
"on Linux.\n"
"It may only work over USB.\n"
"See --hid-keyboard and --hid-mouse.",
},
{
@ -315,6 +345,12 @@ static const struct sc_option options[] = {
"special character, but breaks the expected behavior of alpha "
"keys in games (typically WASD).",
},
{
.longopt_id = OPT_PRINT_FPS,
.longopt = "print-fps",
.text = "Start FPS counter, to print framerate logs to the console. "
"It can be started or stopped at any time with MOD+i.",
},
{
.longopt_id = OPT_PUSH_TARGET,
.longopt = "push-target",
@ -397,6 +433,20 @@ static const struct sc_option options[] = {
"on exit.\n"
"It only shows physical touches (not clicks from scrcpy).",
},
{
.longopt_id = OPT_TCPIP,
.longopt = "tcpip",
.argdesc = "ip[:port]",
.optional_arg = true,
.text = "Configure and reconnect the device over TCP/IP.\n"
"If a destination address is provided, then scrcpy connects to "
"this address before starting. The device must listen on the "
"given TCP port (default is 5555).\n"
"If no destination address is provided, then scrcpy attempts "
"to find the IP address of the current device (typically "
"connected over USB), enables TCP/IP mode, then connects to "
"this address before starting.",
},
{
.longopt_id = OPT_TUNNEL_HOST,
.longopt = "tunnel-host",
@ -458,20 +508,6 @@ static const struct sc_option options[] = {
.text = "Keep the device on while scrcpy is running, when the device "
"is plugged in.",
},
{
.longopt_id = OPT_TCPIP,
.longopt = "tcpip",
.argdesc = "ip[:port]",
.optional_arg = true,
.text = "Configure and reconnect the device over TCP/IP.\n"
"If a destination address is provided, then scrcpy connects to "
"this address before starting. The device must listen on the "
"given TCP port (default is 5555).\n"
"If no destination address is provided, then scrcpy attempts "
"to find the IP address of the current device (typically "
"connected over USB), enables TCP/IP mode, then connects to "
"this address before starting.",
},
{
.longopt_id = OPT_WINDOW_BORDERLESS,
.longopt = "window-borderless",
@ -547,7 +583,7 @@ static const struct sc_shortcut shortcuts[] = {
.text = "Click on BACK",
},
{
.shortcuts = { "MOD+s" },
.shortcuts = { "MOD+s", "4th-click" },
.text = "Click on APP_SWITCH",
},
{
@ -583,7 +619,7 @@ static const struct sc_shortcut shortcuts[] = {
.text = "Rotate device screen",
},
{
.shortcuts = { "MOD+n" },
.shortcuts = { "MOD+n", "5th-click" },
.text = "Expand notification panel",
},
{
@ -630,6 +666,11 @@ static const struct sc_envvar envvars[] = {
.name = "ADB",
.text = "Path to adb executable",
},
{
.name = "ANDROID_SERIAL",
.text = "Device serial to use if no selector (-s, -d, -e or "
"--tcpip=<addr>) is specified",
},
{
.name = "SCRCPY_ICON_PATH",
.text = "Path to the program icon",
@ -637,7 +678,22 @@ static const struct sc_envvar envvars[] = {
{
.name = "SCRCPY_SERVER_PATH",
.text = "Path to the server binary",
}
},
};
static const struct sc_exit_status exit_statuses[] = {
{
.value = 0,
.text = "Normal program termination",
},
{
.value = 1,
.text = "Start failure",
},
{
.value = 2,
.text = "Device disconnected while running",
},
};
static char *
@ -876,6 +932,25 @@ print_envvar(const struct sc_envvar *envvar, unsigned cols) {
free(text);
}
static void
print_exit_status(const struct sc_exit_status *status, unsigned cols) {
assert(cols > 8); // sc_str_wrap_lines() requires indent < columns
assert(status->text);
// The text starts at 9: 4 ident spaces, 3 chars for numeric value, 2 spaces
char *text = sc_str_wrap_lines(status->text, cols, 9);
if (!text) {
printf("<ERROR>\n");
return;
}
assert(strlen(text) >= 9); // Contains at least the initial identation
// text + 9 to remove the initial indentation
printf(" %3d %s\n", status->value, text + 9);
free(text);
}
void
scrcpy_print_usage(const char *arg0) {
#define SC_TERM_COLS_DEFAULT 80
@ -914,6 +989,11 @@ scrcpy_print_usage(const char *arg0) {
for (size_t i = 0; i < ARRAY_LEN(envvars); ++i) {
print_envvar(&envvars[i], cols);
}
printf("\nExit status:\n\n");
for (size_t i = 0; i < ARRAY_LEN(exit_statuses); ++i) {
print_exit_status(&exit_statuses[i], cols);
}
}
static bool
@ -1320,6 +1400,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false;
}
break;
case 'd':
opts->select_usb = true;
break;
case 'e':
opts->select_tcpip = true;
break;
case 'f':
opts->fullscreen = true;
break;
@ -1339,8 +1425,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_HID;
break;
#else
LOGE("HID over AOA (-K/--hid-keyboard) is not supported on "
"this platform. It is only available on Linux.");
LOGE("HID over AOA (-K/--hid-keyboard) is disabled.");
return false;
#endif
case OPT_MAX_FPS:
@ -1358,8 +1443,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_HID;
break;
#else
LOGE("HID over AOA (-M/--hid-mouse) is not supported on this"
"platform. It is only available on Linux.");
LOGE("HID over AOA (-M/--hid-mouse) is disabled.");
return false;
#endif
case OPT_LOCK_VIDEO_ORIENTATION:
@ -1517,13 +1601,21 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
case OPT_NO_DOWNSIZE_ON_ERROR:
opts->downsize_on_error = false;
break;
case OPT_NO_CLEANUP:
opts->cleanup = false;
break;
case OPT_NO_POWER_ON:
opts->power_on = false;
break;
case OPT_PRINT_FPS:
opts->start_fps_counter = true;
break;
case OPT_OTG:
#ifdef HAVE_USB
opts->otg = true;
break;
#else
LOGE("OTG mode (--otg) is not supported on this platform. It "
"is only available on Linux.");
LOGE("OTG mode (--otg) is disabled.");
return false;
#endif
case OPT_V4L2_SINK:
@ -1531,7 +1623,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
opts->v4l2_device = optarg;
break;
#else
LOGE("V4L2 (--v4l2-sink) is only available on Linux.");
LOGE("V4L2 (--v4l2-sink) is disabled (or unsupported on this "
"platform).");
return false;
#endif
case OPT_V4L2_BUFFER:
@ -1559,8 +1652,16 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
// If a TCP/IP address is provided, then tcpip must be enabled
assert(opts->tcpip || !opts->tcpip_dst);
if (opts->serial && opts->tcpip_dst) {
LOGE("Incompatible options: -s/--serial and --tcpip with an argument");
unsigned selectors = !!opts->serial
+ !!opts->tcpip_dst
+ opts->select_tcpip
+ opts->select_usb;
if (selectors > 1) {
LOGE("At most one device selector option may be passed, among:\n"
" --serial (-s)\n"
" --select-usb (-d)\n"
" --select-tcpip (-e)\n"
" --tcpip=<addr> (with an argument)");
return false;
}
@ -1637,6 +1738,18 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
}
#ifdef HAVE_USB
# ifdef _WIN32
if (!opts->otg && (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID
|| opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID)) {
LOGE("On Windows, it is not possible to open a USB device already open "
"by another process (like adb).");
LOGE("Therefore, -K/--hid-keyboard and -M/--hid-mouse may only work in "
"OTG mode (--otg).");
return false;
}
# endif
if (opts->otg) {
// OTG mode is compatible with only very few options.
// Only report obvious errors.
@ -1664,12 +1777,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
LOGE("OTG mode: could not select display");
return false;
}
#ifdef HAVE_V4L2
# ifdef HAVE_V4L2
if (opts->v4l2_device) {
LOGE("OTG mode: could not sink to V4L2 device");
return false;
}
#endif
# endif
}
#endif

View File

@ -98,7 +98,7 @@ sc_clock_update(struct sc_clock *clock, sc_tick system, sc_tick stream) {
sc_clock_estimate(clock, &clock->slope, &clock->offset);
#ifndef SC_CLOCK_NDEBUG
LOGD("Clock estimation: %g * pts + %" PRItick,
LOGD("Clock estimation: %f * pts + %" PRItick,
clock->slope, clock->offset);
#endif
}

View File

@ -1,5 +1,5 @@
#ifndef COMMON_H
#define COMMON_H
#ifndef SC_COMMON_H
#define SC_COMMON_H
#include "config.h"
#include "compat.h"

View File

@ -1,5 +1,5 @@
#ifndef COMPAT_H
#define COMPAT_H
#ifndef SC_COMPAT_H
#define SC_COMPAT_H
#include "config.h"
@ -8,8 +8,10 @@
#ifndef __WIN32
# define PRIu64_ PRIu64
# define SC_PRIsizet "zu"
#else
# define PRIu64_ "I64u" // Windows...
# define SC_PRIsizet "Iu"
#endif
// In ffmpeg/doc/APIchanges:

View File

@ -63,17 +63,17 @@ static const char *const copy_key_labels[] = {
static void
write_position(uint8_t *buf, const struct sc_position *position) {
buffer_write32be(&buf[0], position->point.x);
buffer_write32be(&buf[4], position->point.y);
buffer_write16be(&buf[8], position->screen_size.width);
buffer_write16be(&buf[10], position->screen_size.height);
sc_write32be(&buf[0], position->point.x);
sc_write32be(&buf[4], position->point.y);
sc_write16be(&buf[8], position->screen_size.width);
sc_write16be(&buf[10], position->screen_size.height);
}
// write length (4 bytes) + string (non null-terminated)
static size_t
write_string(const char *utf8, size_t max_len, unsigned char *buf) {
size_t len = sc_str_utf8_truncation_index(utf8, max_len);
buffer_write32be(buf, len);
sc_write32be(buf, len);
memcpy(&buf[4], utf8, len);
return 4 + len;
}
@ -94,9 +94,9 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
switch (msg->type) {
case SC_CONTROL_MSG_TYPE_INJECT_KEYCODE:
buf[1] = msg->inject_keycode.action;
buffer_write32be(&buf[2], msg->inject_keycode.keycode);
buffer_write32be(&buf[6], msg->inject_keycode.repeat);
buffer_write32be(&buf[10], msg->inject_keycode.metastate);
sc_write32be(&buf[2], msg->inject_keycode.keycode);
sc_write32be(&buf[6], msg->inject_keycode.repeat);
sc_write32be(&buf[10], msg->inject_keycode.metastate);
return 14;
case SC_CONTROL_MSG_TYPE_INJECT_TEXT: {
size_t len =
@ -106,20 +106,20 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
}
case SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
buf[1] = msg->inject_touch_event.action;
buffer_write64be(&buf[2], msg->inject_touch_event.pointer_id);
sc_write64be(&buf[2], msg->inject_touch_event.pointer_id);
write_position(&buf[10], &msg->inject_touch_event.position);
uint16_t pressure =
to_fixed_point_16(msg->inject_touch_event.pressure);
buffer_write16be(&buf[22], pressure);
buffer_write32be(&buf[24], msg->inject_touch_event.buttons);
sc_write16be(&buf[22], pressure);
sc_write32be(&buf[24], msg->inject_touch_event.buttons);
return 28;
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
write_position(&buf[1], &msg->inject_scroll_event.position);
buffer_write32be(&buf[13],
sc_write32be(&buf[13],
(uint32_t) msg->inject_scroll_event.hscroll);
buffer_write32be(&buf[17],
sc_write32be(&buf[17],
(uint32_t) msg->inject_scroll_event.vscroll);
buffer_write32be(&buf[21], msg->inject_scroll_event.buttons);
sc_write32be(&buf[21], msg->inject_scroll_event.buttons);
return 25;
case SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
buf[1] = msg->inject_keycode.action;
@ -128,7 +128,7 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
buf[1] = msg->get_clipboard.copy_key;
return 2;
case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD:
buffer_write64be(&buf[1], msg->set_clipboard.sequence);
sc_write64be(&buf[1], msg->set_clipboard.sequence);
buf[9] = !!msg->set_clipboard.paste;
size_t len = write_string(msg->set_clipboard.text,
SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
@ -170,7 +170,7 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
if (id == POINTER_ID_MOUSE || id == POINTER_ID_VIRTUAL_FINGER) {
// string pointer id
LOG_CMSG("touch [id=%s] %-4s position=%" PRIi32 ",%" PRIi32
" pressure=%g buttons=%06lx",
" pressure=%f buttons=%06lx",
id == POINTER_ID_MOUSE ? "mouse" : "vfinger",
MOTIONEVENT_ACTION_LABEL(action),
msg->inject_touch_event.position.point.x,
@ -180,7 +180,7 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
} else {
// numeric pointer id
LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%"
PRIi32 " pressure=%g buttons=%06lx",
PRIi32 " pressure=%f buttons=%06lx",
id,
MOTIONEVENT_ACTION_LABEL(action),
msg->inject_touch_event.position.point.x,

View File

@ -1,5 +1,5 @@
#ifndef CONTROLMSG_H
#define CONTROLMSG_H
#ifndef SC_CONTROLMSG_H
#define SC_CONTROLMSG_H
#include "common.h"

View File

@ -113,7 +113,7 @@ sc_controller_start(struct sc_controller *controller) {
bool ok = sc_thread_create(&controller->thread, run_controller,
"scrcpy-ctl", controller);
if (!ok) {
LOGC("Could not start controller thread");
LOGE("Could not start controller thread");
return false;
}

View File

@ -1,5 +1,5 @@
#ifndef CONTROLLER_H
#define CONTROLLER_H
#ifndef SC_CONTROLLER_H
#define SC_CONTROLLER_H
#include "common.h"

View File

@ -10,10 +10,12 @@
#include "util/buffer_util.h"
#include "util/log.h"
#define BUFSIZE 0x10000
#define SC_PACKET_HEADER_SIZE 12
#define HEADER_SIZE 12
#define NO_PTS UINT64_C(-1)
#define SC_PACKET_FLAG_CONFIG (UINT64_C(1) << 63)
#define SC_PACKET_FLAG_KEY_FRAME (UINT64_C(1) << 62)
#define SC_PACKET_PTS_MASK (SC_PACKET_FLAG_KEY_FRAME - 1)
static bool
sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
@ -28,16 +30,24 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
// size
//
// It is followed by <packet_size> bytes containing the packet/frame.
//
// The most significant bits of the PTS are used for packet flags:
//
// byte 7 byte 6 byte 5 byte 4 byte 3 byte 2 byte 1 byte 0
// CK...... ........ ........ ........ ........ ........ ........ ........
// ^^<------------------------------------------------------------------->
// || PTS
// | `- key frame
// `-- config packet
uint8_t header[HEADER_SIZE];
ssize_t r = net_recv_all(demuxer->socket, header, HEADER_SIZE);
if (r < HEADER_SIZE) {
uint8_t header[SC_PACKET_HEADER_SIZE];
ssize_t r = net_recv_all(demuxer->socket, header, SC_PACKET_HEADER_SIZE);
if (r < SC_PACKET_HEADER_SIZE) {
return false;
}
uint64_t pts = buffer_read64be(header);
uint32_t len = buffer_read32be(&header[8]);
assert(pts == NO_PTS || (pts & 0x8000000000000000) == 0);
uint64_t pts_flags = sc_read64be(header);
uint32_t len = sc_read32be(&header[8]);
assert(len);
if (av_new_packet(packet, len)) {
@ -51,8 +61,17 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
return false;
}
packet->pts = pts != NO_PTS ? (int64_t) pts : AV_NOPTS_VALUE;
if (pts_flags & SC_PACKET_FLAG_CONFIG) {
packet->pts = AV_NOPTS_VALUE;
} else {
packet->pts = pts_flags & SC_PACKET_PTS_MASK;
}
if (pts_flags & SC_PACKET_FLAG_KEY_FRAME) {
packet->flags |= AV_PKT_FLAG_KEY;
}
packet->dts = packet->pts;
return true;
}
@ -69,28 +88,6 @@ push_packet_to_sinks(struct sc_demuxer *demuxer, const AVPacket *packet) {
return true;
}
static void
sc_demuxer_parse(struct sc_demuxer *demuxer, AVPacket *packet) {
uint8_t *in_data = packet->data;
int in_len = packet->size;
uint8_t *out_data = NULL;
int out_len = 0;
int r = av_parser_parse2(demuxer->parser, demuxer->codec_ctx,
&out_data, &out_len, in_data, in_len,
AV_NOPTS_VALUE, AV_NOPTS_VALUE, -1);
// PARSER_FLAG_COMPLETE_FRAMES is set
assert(r == in_len);
(void) r;
assert(out_len == in_len);
if (demuxer->parser->key_frame == 1) {
packet->flags |= AV_PKT_FLAG_KEY;
}
packet->dts = packet->pts;
}
static bool
sc_demuxer_push_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
bool is_config = packet->pts == AV_NOPTS_VALUE;
@ -130,11 +127,6 @@ sc_demuxer_push_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
}
}
if (!is_config) {
// data packet
sc_demuxer_parse(demuxer, packet);
}
bool ok = push_packet_to_sinks(demuxer, packet);
if (!is_config && demuxer->pending) {
@ -276,7 +268,7 @@ sc_demuxer_start(struct sc_demuxer *demuxer) {
bool ok = sc_thread_create(&demuxer->thread, run_demuxer, "scrcpy-demuxer",
demuxer);
if (!ok) {
LOGC("Could not start demuxer thread");
LOGE("Could not start demuxer thread");
return false;
}
return true;

View File

@ -18,7 +18,7 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
msg->type = buf[0];
switch (msg->type) {
case DEVICE_MSG_TYPE_CLIPBOARD: {
size_t clipboard_len = buffer_read32be(&buf[1]);
size_t clipboard_len = sc_read32be(&buf[1]);
if (clipboard_len > len - 5) {
return 0; // not available
}
@ -36,7 +36,7 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
return 5 + clipboard_len;
}
case DEVICE_MSG_TYPE_ACK_CLIPBOARD: {
uint64_t sequence = buffer_read64be(&buf[1]);
uint64_t sequence = sc_read64be(&buf[1]);
msg->ack_clipboard.sequence = sequence;
return 9;
}

View File

@ -1,5 +1,5 @@
#ifndef DEVICEMSG_H
#define DEVICEMSG_H
#ifndef SC_DEVICEMSG_H
#define SC_DEVICEMSG_H
#include "common.h"

View File

@ -156,7 +156,7 @@ sc_file_pusher_start(struct sc_file_pusher *fp) {
bool ok = sc_thread_create(&fp->thread, run_file_pusher, "scrcpy-file", fp);
if (!ok) {
LOGC("Could not start file_pusher thread");
LOGE("Could not start file_pusher thread");
return false;
}

View File

@ -4,10 +4,10 @@
#include "util/log.h"
#define FPS_COUNTER_INTERVAL SC_TICK_FROM_SEC(1)
#define SC_FPS_COUNTER_INTERVAL SC_TICK_FROM_SEC(1)
bool
fps_counter_init(struct fps_counter *counter) {
sc_fps_counter_init(struct sc_fps_counter *counter) {
bool ok = sc_mutex_init(&counter->mutex);
if (!ok) {
return false;
@ -27,26 +27,26 @@ fps_counter_init(struct fps_counter *counter) {
}
void
fps_counter_destroy(struct fps_counter *counter) {
sc_fps_counter_destroy(struct sc_fps_counter *counter) {
sc_cond_destroy(&counter->state_cond);
sc_mutex_destroy(&counter->mutex);
}
static inline bool
is_started(struct fps_counter *counter) {
is_started(struct sc_fps_counter *counter) {
return atomic_load_explicit(&counter->started, memory_order_acquire);
}
static inline void
set_started(struct fps_counter *counter, bool started) {
set_started(struct sc_fps_counter *counter, bool started) {
atomic_store_explicit(&counter->started, started, memory_order_release);
}
// must be called with mutex locked
static void
display_fps(struct fps_counter *counter) {
display_fps(struct sc_fps_counter *counter) {
unsigned rendered_per_second =
counter->nr_rendered * SC_TICK_FREQ / FPS_COUNTER_INTERVAL;
counter->nr_rendered * SC_TICK_FREQ / SC_FPS_COUNTER_INTERVAL;
if (counter->nr_skipped) {
LOGI("%u fps (+%u frames skipped)", rendered_per_second,
counter->nr_skipped);
@ -57,7 +57,7 @@ display_fps(struct fps_counter *counter) {
// must be called with mutex locked
static void
check_interval_expired(struct fps_counter *counter, uint32_t now) {
check_interval_expired(struct sc_fps_counter *counter, sc_tick now) {
if (now < counter->next_timestamp) {
return;
}
@ -67,13 +67,13 @@ check_interval_expired(struct fps_counter *counter, uint32_t now) {
counter->nr_skipped = 0;
// add a multiple of the interval
uint32_t elapsed_slices =
(now - counter->next_timestamp) / FPS_COUNTER_INTERVAL + 1;
counter->next_timestamp += FPS_COUNTER_INTERVAL * elapsed_slices;
(now - counter->next_timestamp) / SC_FPS_COUNTER_INTERVAL + 1;
counter->next_timestamp += SC_FPS_COUNTER_INTERVAL * elapsed_slices;
}
static int
run_fps_counter(void *data) {
struct fps_counter *counter = data;
struct sc_fps_counter *counter = data;
sc_mutex_lock(&counter->mutex);
while (!counter->interrupted) {
@ -94,9 +94,9 @@ run_fps_counter(void *data) {
}
bool
fps_counter_start(struct fps_counter *counter) {
sc_fps_counter_start(struct sc_fps_counter *counter) {
sc_mutex_lock(&counter->mutex);
counter->next_timestamp = sc_tick_now() + FPS_COUNTER_INTERVAL;
counter->next_timestamp = sc_tick_now() + SC_FPS_COUNTER_INTERVAL;
counter->nr_rendered = 0;
counter->nr_skipped = 0;
sc_mutex_unlock(&counter->mutex);
@ -117,22 +117,24 @@ fps_counter_start(struct fps_counter *counter) {
counter->thread_started = true;
}
LOGI("FPS counter started");
return true;
}
void
fps_counter_stop(struct fps_counter *counter) {
sc_fps_counter_stop(struct sc_fps_counter *counter) {
set_started(counter, false);
sc_cond_signal(&counter->state_cond);
LOGI("FPS counter stopped");
}
bool
fps_counter_is_started(struct fps_counter *counter) {
sc_fps_counter_is_started(struct sc_fps_counter *counter) {
return is_started(counter);
}
void
fps_counter_interrupt(struct fps_counter *counter) {
sc_fps_counter_interrupt(struct sc_fps_counter *counter) {
if (!counter->thread_started) {
return;
}
@ -145,7 +147,7 @@ fps_counter_interrupt(struct fps_counter *counter) {
}
void
fps_counter_join(struct fps_counter *counter) {
sc_fps_counter_join(struct sc_fps_counter *counter) {
if (counter->thread_started) {
// interrupted must be set by the thread calling join(), so no need to
// lock for the assertion
@ -156,7 +158,7 @@ fps_counter_join(struct fps_counter *counter) {
}
void
fps_counter_add_rendered_frame(struct fps_counter *counter) {
sc_fps_counter_add_rendered_frame(struct sc_fps_counter *counter) {
if (!is_started(counter)) {
return;
}
@ -169,7 +171,7 @@ fps_counter_add_rendered_frame(struct fps_counter *counter) {
}
void
fps_counter_add_skipped_frame(struct fps_counter *counter) {
sc_fps_counter_add_skipped_frame(struct sc_fps_counter *counter) {
if (!is_started(counter)) {
return;
}

View File

@ -1,5 +1,5 @@
#ifndef FPSCOUNTER_H
#define FPSCOUNTER_H
#ifndef SC_FPSCOUNTER_H
#define SC_FPSCOUNTER_H
#include "common.h"
@ -9,7 +9,7 @@
#include "util/thread.h"
struct fps_counter {
struct sc_fps_counter {
sc_thread thread;
sc_mutex mutex;
sc_cond state_cond;
@ -28,32 +28,32 @@ struct fps_counter {
};
bool
fps_counter_init(struct fps_counter *counter);
sc_fps_counter_init(struct sc_fps_counter *counter);
void
fps_counter_destroy(struct fps_counter *counter);
sc_fps_counter_destroy(struct sc_fps_counter *counter);
bool
fps_counter_start(struct fps_counter *counter);
sc_fps_counter_start(struct sc_fps_counter *counter);
void
fps_counter_stop(struct fps_counter *counter);
sc_fps_counter_stop(struct sc_fps_counter *counter);
bool
fps_counter_is_started(struct fps_counter *counter);
sc_fps_counter_is_started(struct sc_fps_counter *counter);
// request to stop the thread (on quit)
// must be called before fps_counter_join()
// must be called before sc_fps_counter_join()
void
fps_counter_interrupt(struct fps_counter *counter);
sc_fps_counter_interrupt(struct sc_fps_counter *counter);
void
fps_counter_join(struct fps_counter *counter);
sc_fps_counter_join(struct sc_fps_counter *counter);
void
fps_counter_add_rendered_frame(struct fps_counter *counter);
sc_fps_counter_add_rendered_frame(struct sc_fps_counter *counter);
void
fps_counter_add_skipped_frame(struct fps_counter *counter);
sc_fps_counter_add_skipped_frame(struct sc_fps_counter *counter);
#endif

View File

@ -1,5 +1,5 @@
#ifndef ICON_H
#define ICON_H
#ifndef SC_ICON_H
#define SC_ICON_H
#include "common.h"

View File

@ -242,18 +242,14 @@ set_screen_power_mode(struct sc_controller *controller,
}
static void
switch_fps_counter_state(struct fps_counter *fps_counter) {
switch_fps_counter_state(struct sc_fps_counter *fps_counter) {
// the started state can only be written from the current thread, so there
// is no ToCToU issue
if (fps_counter_is_started(fps_counter)) {
fps_counter_stop(fps_counter);
LOGI("FPS counter stopped");
if (sc_fps_counter_is_started(fps_counter)) {
sc_fps_counter_stop(fps_counter);
} else {
if (fps_counter_start(fps_counter)) {
LOGI("FPS counter started");
} else {
LOGE("FPS counter starting failed");
}
sc_fps_counter_start(fps_counter);
// Any error is already logged
}
}

View File

@ -1,5 +1,5 @@
#ifndef INPUTMANAGER_H
#define INPUTMANAGER_H
#ifndef SC_INPUTMANAGER_H
#define SC_INPUTMANAGER_H
#include "common.h"

View File

@ -15,27 +15,7 @@
#include "scrcpy.h"
#include "usb/scrcpy_otg.h"
#include "util/log.h"
static void
print_version(void) {
printf("\ndependencies:\n");
printf(" - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION,
SDL_PATCHLEVEL);
printf(" - libavcodec %d.%d.%d\n", LIBAVCODEC_VERSION_MAJOR,
LIBAVCODEC_VERSION_MINOR,
LIBAVCODEC_VERSION_MICRO);
printf(" - libavformat %d.%d.%d\n", LIBAVFORMAT_VERSION_MAJOR,
LIBAVFORMAT_VERSION_MINOR,
LIBAVFORMAT_VERSION_MICRO);
printf(" - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR,
LIBAVUTIL_VERSION_MINOR,
LIBAVUTIL_VERSION_MICRO);
#ifdef HAVE_V4L2
printf(" - libavdevice %d.%d.%d\n", LIBAVDEVICE_VERSION_MAJOR,
LIBAVDEVICE_VERSION_MINOR,
LIBAVDEVICE_VERSION_MICRO);
#endif
}
#include "version.h"
int
main(int argc, char *argv[]) {
@ -60,19 +40,19 @@ main(int argc, char *argv[]) {
#endif
if (!scrcpy_parse_args(&args, argc, argv)) {
return 1;
return SCRCPY_EXIT_FAILURE;
}
sc_set_log_level(args.opts.log_level);
if (args.help) {
scrcpy_print_usage(argv[0]);
return 0;
return SCRCPY_EXIT_SUCCESS;
}
if (args.version) {
print_version();
return 0;
scrcpy_print_version();
return SCRCPY_EXIT_SUCCESS;
}
#ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL
@ -86,17 +66,17 @@ main(int argc, char *argv[]) {
#endif
if (avformat_network_init()) {
return 1;
return SCRCPY_EXIT_FAILURE;
}
#ifdef HAVE_USB
bool ok = args.opts.otg ? scrcpy_otg(&args.opts)
: scrcpy(&args.opts);
enum scrcpy_exit_code ret = args.opts.otg ? scrcpy_otg(&args.opts)
: scrcpy(&args.opts);
#else
bool ok = scrcpy(&args.opts);
enum scrcpy_exit_code ret = scrcpy(&args.opts);
#endif
avformat_network_deinit(); // ignore failure
return ok ? 0 : 1;
return ret;
}

View File

@ -28,7 +28,7 @@ sc_opengl_init(struct sc_opengl *gl) {
sizeof(OPENGL_ES_PREFIX) - 1);
if (gl->is_opengles) {
/* skip the prefix */
version += sizeof(PREFIX) - 1;
version += sizeof(OPENGL_ES_PREFIX) - 1;
}
int r = sscanf(version, "%d.%d", &gl->version_major, &gl->version_minor);

View File

@ -60,4 +60,9 @@ const struct scrcpy_options scrcpy_options_default = {
.downsize_on_error = true,
.tcpip = false,
.tcpip_dst = NULL,
.select_tcpip = false,
.select_usb = false,
.cleanup = true,
.start_fps_counter = false,
.power_on = true,
};

View File

@ -135,6 +135,11 @@ struct scrcpy_options {
bool downsize_on_error;
bool tcpip;
const char *tcpip_dst;
bool select_usb;
bool select_tcpip;
bool cleanup;
bool start_fps_counter;
bool power_on;
};
extern const struct scrcpy_options scrcpy_options_default;

View File

@ -114,7 +114,7 @@ receiver_start(struct receiver *receiver) {
bool ok = sc_thread_create(&receiver->thread, run_receiver,
"scrcpy-receiver", receiver);
if (!ok) {
LOGC("Could not start receiver thread");
LOGE("Could not start receiver thread");
return false;
}

View File

@ -1,5 +1,5 @@
#ifndef RECEIVER_H
#define RECEIVER_H
#ifndef SC_RECEIVER_H
#define SC_RECEIVER_H
#include "common.h"

View File

@ -290,7 +290,7 @@ sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) {
ok = sc_thread_create(&recorder->thread, run_recorder, "scrcpy-recorder",
recorder);
if (!ok) {
LOGC("Could not start recorder thread");
LOGE("Could not start recorder thread");
goto error_avio_close;
}

View File

@ -149,38 +149,41 @@ sdl_configure(bool display, bool disable_screensaver) {
}
}
static bool
static enum scrcpy_exit_code
event_loop(struct scrcpy *s) {
SDL_Event event;
while (SDL_WaitEvent(&event)) {
switch (event.type) {
case EVENT_STREAM_STOPPED:
LOGW("Device disconnected");
return false;
return SCRCPY_EXIT_DISCONNECTED;
case SDL_QUIT:
LOGD("User requested to quit");
return true;
return SCRCPY_EXIT_SUCCESS;
default:
sc_screen_handle_event(&s->screen, &event);
break;
}
}
return false;
return SCRCPY_EXIT_FAILURE;
}
// Return true on success, false on error
static bool
await_for_server(void) {
await_for_server(bool *connected) {
SDL_Event event;
while (SDL_WaitEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
LOGD("User requested to quit");
return false;
*connected = false;
return true;
case EVENT_SERVER_CONNECTION_FAILED:
LOGE("Server connection failed");
return false;
case EVENT_SERVER_CONNECTED:
LOGD("Server connected");
*connected = true;
return true;
default:
break;
@ -262,20 +265,20 @@ sc_server_on_disconnected(struct sc_server *server, void *userdata) {
// event
}
bool
enum scrcpy_exit_code
scrcpy(struct scrcpy_options *options) {
static struct scrcpy scrcpy;
struct scrcpy *s = &scrcpy;
// Minimal SDL initialization
if (SDL_Init(SDL_INIT_EVENTS)) {
LOGC("Could not initialize SDL: %s", SDL_GetError());
return false;
LOGE("Could not initialize SDL: %s", SDL_GetError());
return SCRCPY_EXIT_FAILURE;
}
atexit(SDL_Quit);
bool ret = false;
enum scrcpy_exit_code ret = SCRCPY_EXIT_FAILURE;
bool server_started = false;
bool file_pusher_initialized = false;
@ -297,6 +300,8 @@ scrcpy(struct scrcpy_options *options) {
struct sc_server_params params = {
.req_serial = options->serial,
.select_usb = options->select_usb,
.select_tcpip = options->select_tcpip,
.log_level = options->log_level,
.crop = options->crop,
.port_range = options->port_range,
@ -318,6 +323,8 @@ scrcpy(struct scrcpy_options *options) {
.downsize_on_error = options->downsize_on_error,
.tcpip = options->tcpip,
.tcpip_dst = options->tcpip_dst,
.cleanup = options->cleanup,
.power_on = options->power_on,
};
static const struct sc_server_callbacks cbs = {
@ -326,7 +333,7 @@ scrcpy(struct scrcpy_options *options) {
.on_disconnected = sc_server_on_disconnected,
};
if (!sc_server_init(&s->server, &params, &cbs, NULL)) {
return false;
return SCRCPY_EXIT_FAILURE;
}
if (!sc_server_start(&s->server)) {
@ -341,14 +348,21 @@ scrcpy(struct scrcpy_options *options) {
// Initialize SDL video in addition if display is enabled
if (options->display && SDL_Init(SDL_INIT_VIDEO)) {
LOGC("Could not initialize SDL: %s", SDL_GetError());
LOGE("Could not initialize SDL: %s", SDL_GetError());
goto end;
}
sdl_configure(options->display, options->disable_screensaver);
// Await for server without blocking Ctrl+C handling
if (!await_for_server()) {
bool connected;
if (!await_for_server(&connected)) {
goto end;
}
if (!connected) {
// This is not an error, user requested to quit
ret = SCRCPY_EXIT_SUCCESS;
goto end;
}
@ -430,32 +444,19 @@ scrcpy(struct scrcpy_options *options) {
}
assert(serial);
struct sc_usb_device usb_devices[16];
ssize_t count = sc_usb_find_devices(&s->usb, serial, usb_devices,
ARRAY_LEN(usb_devices));
if (count <= 0) {
LOGE("Could not find USB device %s", serial);
struct sc_usb_device usb_device;
ok = sc_usb_select_device(&s->usb, serial, &usb_device);
if (!ok) {
sc_usb_destroy(&s->usb);
sc_acksync_destroy(&s->acksync);
goto aoa_hid_end;
}
if (count > 1) {
LOGE("Multiple (%d) devices with serial %s", (int) count, serial);
sc_usb_device_destroy_all(usb_devices, count);
sc_usb_destroy(&s->usb);
sc_acksync_destroy(&s->acksync);
goto aoa_hid_end;
}
struct sc_usb_device *usb_device = &usb_devices[0];
LOGI("USB device: %s (%04" PRIx16 ":%04" PRIx16 ") %s %s",
usb_device->serial, usb_device->vid, usb_device->pid,
usb_device->manufacturer, usb_device->product);
usb_device.serial, usb_device.vid, usb_device.pid,
usb_device.manufacturer, usb_device.product);
ok = sc_usb_connect(&s->usb, usb_device->device, NULL, NULL);
sc_usb_device_destroy(usb_device);
ok = sc_usb_connect(&s->usb, usb_device.device, NULL, NULL);
sc_usb_device_destroy(&usb_device);
if (!ok) {
LOGE("Failed to connect to USB device %s", serial);
sc_usb_destroy(&s->usb);
@ -598,6 +599,7 @@ aoa_hid_end:
.rotation = options->rotation,
.mipmaps = options->mipmaps,
.fullscreen = options->fullscreen,
.start_fps_counter = options->start_fps_counter,
.buffering_time = options->display_buffer,
};

View File

@ -6,7 +6,18 @@
#include <stdbool.h>
#include "options.h"
bool
enum scrcpy_exit_code {
// Normal program termination
SCRCPY_EXIT_SUCCESS,
// No connection could be established
SCRCPY_EXIT_FAILURE,
// Device was disconnected while running
SCRCPY_EXIT_DISCONNECTED,
};
enum scrcpy_exit_code
scrcpy(struct scrcpy_options *options);
#endif

View File

@ -163,14 +163,44 @@ sc_screen_is_relative_mode(struct sc_screen *screen) {
}
static void
sc_screen_capture_mouse(struct sc_screen *screen, bool capture) {
sc_screen_set_mouse_capture(struct sc_screen *screen, bool capture) {
#ifdef __APPLE__
// Workaround for SDL bug on macOS:
// <https://github.com/libsdl-org/SDL/issues/5340>
if (capture) {
int mouse_x, mouse_y;
SDL_GetGlobalMouseState(&mouse_x, &mouse_y);
int x, y, w, h;
SDL_GetWindowPosition(screen->window, &x, &y);
SDL_GetWindowSize(screen->window, &w, &h);
bool outside_window = mouse_x < x || mouse_x >= x + w
|| mouse_y < y || mouse_y >= y + h;
if (outside_window) {
SDL_WarpMouseInWindow(screen->window, w / 2, h / 2);
}
}
#else
(void) screen;
#endif
if (SDL_SetRelativeMouseMode(capture)) {
LOGE("Could not set relative mouse mode to %s: %s",
capture ? "true" : "false", SDL_GetError());
return;
}
}
screen->mouse_captured = capture;
static inline bool
sc_screen_get_mouse_capture(struct sc_screen *screen) {
(void) screen;
return SDL_GetRelativeMouseMode();
}
static inline void
sc_screen_toggle_mouse_capture(struct sc_screen *screen) {
(void) screen;
bool new_value = !sc_screen_get_mouse_capture(screen);
sc_screen_set_mouse_capture(screen, new_value);
}
static void
@ -340,7 +370,7 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
bool need_new_event;
if (previous_skipped) {
fps_counter_add_skipped_frame(&screen->fps_counter);
sc_fps_counter_add_skipped_frame(&screen->fps_counter);
// The EVENT_NEW_FRAME triggered for the previous frame will consume
// this new frame instead, unless the previous event failed
need_new_event = screen->event_failed;
@ -372,7 +402,6 @@ sc_screen_init(struct sc_screen *screen,
screen->fullscreen = false;
screen->maximized = false;
screen->event_failed = false;
screen->mouse_captured = false;
screen->mouse_capture_key_pressed = 0;
screen->req.x = params->window_x;
@ -380,6 +409,7 @@ sc_screen_init(struct sc_screen *screen,
screen->req.width = params->window_width;
screen->req.height = params->window_height;
screen->req.fullscreen = params->fullscreen;
screen->req.start_fps_counter = params->start_fps_counter;
static const struct sc_video_buffer_callbacks cbs = {
.on_new_frame = sc_video_buffer_on_new_frame,
@ -396,7 +426,7 @@ sc_screen_init(struct sc_screen *screen,
goto error_destroy_video_buffer;
}
if (!fps_counter_init(&screen->fps_counter)) {
if (!sc_fps_counter_init(&screen->fps_counter)) {
goto error_stop_and_join_video_buffer;
}
@ -423,14 +453,14 @@ sc_screen_init(struct sc_screen *screen,
screen->window =
SDL_CreateWindow(params->window_title, 0, 0, 0, 0, window_flags);
if (!screen->window) {
LOGC("Could not create window: %s", SDL_GetError());
LOGE("Could not create window: %s", SDL_GetError());
goto error_destroy_fps_counter;
}
screen->renderer = SDL_CreateRenderer(screen->window, -1,
SDL_RENDERER_ACCELERATED);
if (!screen->renderer) {
LOGC("Could not create renderer: %s", SDL_GetError());
LOGE("Could not create renderer: %s", SDL_GetError());
goto error_destroy_window;
}
@ -479,7 +509,7 @@ sc_screen_init(struct sc_screen *screen,
params->frame_size.height);
screen->texture = create_texture(screen);
if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError());
LOGE("Could not create texture: %s", SDL_GetError());
goto error_destroy_renderer;
}
@ -528,7 +558,7 @@ error_destroy_renderer:
error_destroy_window:
SDL_DestroyWindow(screen->window);
error_destroy_fps_counter:
fps_counter_destroy(&screen->fps_counter);
sc_fps_counter_destroy(&screen->fps_counter);
error_stop_and_join_video_buffer:
sc_video_buffer_stop(&screen->vb);
sc_video_buffer_join(&screen->vb);
@ -556,6 +586,10 @@ sc_screen_show_initial_window(struct sc_screen *screen) {
sc_screen_switch_fullscreen(screen);
}
if (screen->req.start_fps_counter) {
sc_fps_counter_start(&screen->fps_counter);
}
SDL_ShowWindow(screen->window);
}
@ -567,13 +601,13 @@ sc_screen_hide_window(struct sc_screen *screen) {
void
sc_screen_interrupt(struct sc_screen *screen) {
sc_video_buffer_stop(&screen->vb);
fps_counter_interrupt(&screen->fps_counter);
sc_fps_counter_interrupt(&screen->fps_counter);
}
void
sc_screen_join(struct sc_screen *screen) {
sc_video_buffer_join(&screen->vb);
fps_counter_join(&screen->fps_counter);
sc_fps_counter_join(&screen->fps_counter);
}
void
@ -585,7 +619,7 @@ sc_screen_destroy(struct sc_screen *screen) {
SDL_DestroyTexture(screen->texture);
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
fps_counter_destroy(&screen->fps_counter);
sc_fps_counter_destroy(&screen->fps_counter);
sc_video_buffer_destroy(&screen->vb);
}
@ -666,7 +700,7 @@ prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
screen->frame_size.width, screen->frame_size.height);
screen->texture = create_texture(screen);
if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError());
LOGE("Could not create texture: %s", SDL_GetError());
return false;
}
}
@ -695,7 +729,7 @@ sc_screen_update_frame(struct sc_screen *screen) {
sc_video_buffer_consume(&screen->vb, screen->frame);
AVFrame *frame = screen->frame;
fps_counter_add_rendered_frame(&screen->fps_counter);
sc_fps_counter_add_rendered_frame(&screen->fps_counter);
struct sc_size new_frame_size = {frame->width, frame->height};
if (!prepare_for_frame(screen, new_frame_size)) {
@ -710,7 +744,7 @@ sc_screen_update_frame(struct sc_screen *screen) {
if (sc_screen_is_relative_mode(screen)) {
// Capture mouse on start
sc_screen_capture_mouse(screen, true);
sc_screen_set_mouse_capture(screen, true);
}
}
@ -823,7 +857,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
if (relative_mode) {
sc_screen_capture_mouse(screen, false);
sc_screen_set_mouse_capture(screen, false);
}
break;
}
@ -853,8 +887,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
if (key == cap) {
// A mouse capture key has been pressed then released:
// toggle the capture mouse mode
sc_screen_capture_mouse(screen,
!screen->mouse_captured);
sc_screen_toggle_mouse_capture(screen);
}
// Mouse capture keys are never forwarded to the device
return;
@ -864,7 +897,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
case SDL_MOUSEWHEEL:
case SDL_MOUSEMOTION:
case SDL_MOUSEBUTTONDOWN:
if (relative_mode && !screen->mouse_captured) {
if (relative_mode && !sc_screen_get_mouse_capture(screen)) {
// Do not forward to input manager, the mouse will be captured
// on SDL_MOUSEBUTTONUP
return;
@ -880,8 +913,8 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
}
break;
case SDL_MOUSEBUTTONUP:
if (relative_mode && !screen->mouse_captured) {
sc_screen_capture_mouse(screen, true);
if (relative_mode && !sc_screen_get_mouse_capture(screen)) {
sc_screen_set_mouse_capture(screen, true);
return;
}
break;

View File

@ -26,7 +26,7 @@ struct sc_screen {
struct sc_input_manager im;
struct sc_video_buffer vb;
struct fps_counter fps_counter;
struct sc_fps_counter fps_counter;
// The initial requested window properties
struct {
@ -35,6 +35,7 @@ struct sc_screen {
uint16_t width;
uint16_t height;
bool fullscreen;
bool start_fps_counter;
} req;
SDL_Window *window;
@ -60,7 +61,6 @@ struct sc_screen {
bool event_failed; // in case SDL_PushEvent() returned an error
bool mouse_captured; // only relevant in relative mouse mode
// To enable/disable mouse capture, a mouse capture key (LALT, LGUI or
// RGUI) must be pressed. This variable tracks the pressed capture key.
SDL_Keycode mouse_capture_key_pressed;
@ -94,6 +94,7 @@ struct sc_screen_params {
bool mipmaps;
bool fullscreen;
bool start_fps_counter;
sc_tick buffering_time;
};

View File

@ -244,6 +244,14 @@ execute_server(struct sc_server *server,
// By default, downsize_on_error is true
ADD_PARAM("downsize_on_error=false");
}
if (!params->cleanup) {
// By default, cleanup is true
ADD_PARAM("cleanup=false");
}
if (!params->power_on) {
// By default, power_on is true
ADD_PARAM("power_on=false");
}
#undef ADD_PARAM
@ -482,8 +490,10 @@ fail:
}
}
// Always leave this function with tunnel disabled
sc_adb_tunnel_close(tunnel, &server->intr, serial);
if (tunnel->enabled) {
// Always leave this function with tunnel disabled
sc_adb_tunnel_close(tunnel, &server->intr, serial);
}
return false;
}
@ -503,22 +513,6 @@ sc_server_on_terminated(void *userdata) {
LOGD("Server terminated");
}
static char *
sc_server_read_serial(struct sc_server *server) {
char *serial;
if (server->params.req_serial) {
// The serial is already known
serial = strdup(server->params.req_serial);
if (!serial) {
LOG_OOM();
}
} else {
serial = sc_adb_get_serialno(&server->intr, 0);
}
return serial;
}
static bool
is_tcpip_mode_enabled(struct sc_server *server, const char *serial) {
struct sc_intr *intr = &server->intr;
@ -641,51 +635,35 @@ sc_server_connect_to_tcpip(struct sc_server *server, const char *ip_port) {
return true;
}
static bool
sc_server_configure_tcpip_known_address(struct sc_server *server,
const char *addr) {
// Append ":5555" if no port is present
bool contains_port = strchr(addr, ':');
char *ip_port = contains_port ? strdup(addr) : append_port_5555(addr);
if (!ip_port) {
LOG_OOM();
return false;
}
server->serial = ip_port;
return sc_server_connect_to_tcpip(server, ip_port);
}
static bool
sc_server_configure_tcpip(struct sc_server *server) {
char *ip_port;
sc_server_configure_tcpip_unknown_address(struct sc_server *server,
const char *serial) {
bool is_already_tcpip =
sc_adb_device_get_type(serial) == SC_ADB_DEVICE_TYPE_TCPIP;
if (is_already_tcpip) {
// Nothing to do
LOGI("Device already connected via TCP/IP: %s", serial);
return true;
}
const struct sc_server_params *params = &server->params;
// If tcpip parameter is given, then it must connect to this address.
// Therefore, the device is unknown, so serial is meaningless at this point.
assert(!params->req_serial || !params->tcpip_dst);
if (params->tcpip_dst) {
// Append ":5555" if no port is present
bool contains_port = strchr(params->tcpip_dst, ':');
ip_port = contains_port ? strdup(params->tcpip_dst)
: append_port_5555(params->tcpip_dst);
if (!ip_port) {
LOG_OOM();
return false;
}
} else {
// The device IP address must be retrieved from the current
// connected device
char *serial = sc_server_read_serial(server);
if (!serial) {
LOGE("Could not get device serial");
return false;
}
// The serial is either the real serial when connected via USB, or
// the IP:PORT when connected over TCP/IP. Only the latter contains
// a colon.
bool is_already_tcpip = strchr(serial, ':');
if (is_already_tcpip) {
// Nothing to do
LOGI("Device already connected via TCP/IP: %s", serial);
free(serial);
return true;
}
ip_port = sc_server_switch_to_tcpip(server, serial);
free(serial);
if (!ip_port) {
return false;
}
char *ip_port = sc_server_switch_to_tcpip(server, serial);
if (!ip_port) {
return false;
}
server->serial = ip_port;
@ -698,16 +676,77 @@ run_server(void *data) {
const struct sc_server_params *params = &server->params;
if (params->tcpip) {
bool ok = sc_server_configure_tcpip(server);
// Execute "adb start-server" before "adb devices" so that daemon starting
// output/errors is correctly printed in the console ("adb devices" output
// is parsed, so it is not output)
bool ok = sc_adb_start_server(&server->intr, 0);
if (!ok) {
LOGE("Could not start adb daemon");
goto error_connection_failed;
}
// params->tcpip_dst implies params->tcpip
assert(!params->tcpip_dst || params->tcpip);
// If tcpip_dst parameter is given, then it must connect to this address.
// Therefore, the device is unknown, so serial is meaningless at this point.
assert(!params->req_serial || !params->tcpip_dst);
// A device must be selected via a serial in all cases except when --tcpip=
// is called with a parameter (in that case, the device may initially not
// exist, and scrcpy will execute "adb connect").
bool need_initial_serial = !params->tcpip_dst;
if (need_initial_serial) {
// At most one of the 3 following parameters may be set
assert(!!params->req_serial
+ params->select_usb
+ params->select_tcpip <= 1);
struct sc_adb_device_selector selector;
if (params->req_serial) {
selector.type = SC_ADB_DEVICE_SELECT_SERIAL;
selector.serial = params->req_serial;
} else if (params->select_usb) {
selector.type = SC_ADB_DEVICE_SELECT_USB;
} else if (params->select_tcpip) {
selector.type = SC_ADB_DEVICE_SELECT_TCPIP;
} else {
// No explicit selection, check $ANDROID_SERIAL
const char *env_serial = getenv("ANDROID_SERIAL");
if (env_serial) {
LOGI("Using ANDROID_SERIAL: %s", env_serial);
selector.type = SC_ADB_DEVICE_SELECT_SERIAL;
selector.serial = env_serial;
} else {
selector.type = SC_ADB_DEVICE_SELECT_ALL;
}
}
struct sc_adb_device device;
ok = sc_adb_select_device(&server->intr, &selector, 0, &device);
if (!ok) {
goto error_connection_failed;
}
assert(server->serial);
if (params->tcpip) {
assert(!params->tcpip_dst);
ok = sc_server_configure_tcpip_unknown_address(server,
device.serial);
sc_adb_device_destroy(&device);
if (!ok) {
goto error_connection_failed;
}
assert(server->serial);
} else {
// "move" the device.serial without copy
server->serial = device.serial;
// the serial must not be freed by the destructor
device.serial = NULL;
sc_adb_device_destroy(&device);
}
} else {
server->serial = sc_server_read_serial(server);
if (!server->serial) {
LOGD("Could not get device serial");
ok = sc_server_configure_tcpip_known_address(server, params->tcpip_dst);
if (!ok) {
goto error_connection_failed;
}
}
@ -716,7 +755,7 @@ run_server(void *data) {
assert(serial);
LOGD("Device serial: %s", serial);
bool ok = push_server(&server->intr, serial);
ok = push_server(&server->intr, serial);
if (!ok) {
goto error_connection_failed;
}

View File

@ -44,6 +44,10 @@ struct sc_server_params {
bool downsize_on_error;
bool tcpip;
const char *tcpip_dst;
bool select_usb;
bool select_tcpip;
bool cleanup;
bool power_on;
};
struct sc_server {

View File

@ -1,51 +0,0 @@
#ifndef STREAM_H
#define STREAM_H
#include "common.h"
#include <stdbool.h>
#include <stdint.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include "trait/packet_sink.h"
#include "util/net.h"
#include "util/thread.h"
#define STREAM_MAX_SINKS 2
struct stream {
sc_socket socket;
sc_thread thread;
struct sc_packet_sink *sinks[STREAM_MAX_SINKS];
unsigned sink_count;
AVCodecContext *codec_ctx;
AVCodecParserContext *parser;
// successive packets may need to be concatenated, until a non-config
// packet is available
AVPacket *pending;
const struct stream_callbacks *cbs;
void *cbs_userdata;
};
struct stream_callbacks {
void (*on_eos)(struct stream *stream, void *userdata);
};
void
stream_init(struct stream *stream, sc_socket socket,
const struct stream_callbacks *cbs, void *cbs_userdata);
void
stream_add_sink(struct stream *stream, struct sc_packet_sink *sink);
bool
stream_start(struct stream *stream);
void
stream_join(struct stream *stream);
#endif

View File

@ -176,7 +176,7 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
bool
sc_process_terminate(pid_t pid) {
if (pid <= 0) {
LOGC("Requested to kill %d, this is an error. Please report the bug.\n",
LOGE("Requested to kill %d, this is an error. Please report the bug.\n",
(int) pid);
abort();
}

View File

@ -95,6 +95,7 @@ sc_aoa_register_hid(struct sc_aoa *aoa, uint16_t accessory_id,
DEFAULT_TIMEOUT);
if (result < 0) {
LOGE("REGISTER_HID: libusb error: %s", libusb_strerror(result));
sc_usb_check_disconnected(aoa->usb, result);
return false;
}
@ -131,6 +132,7 @@ sc_aoa_set_hid_report_desc(struct sc_aoa *aoa, uint16_t accessory_id,
DEFAULT_TIMEOUT);
if (result < 0) {
LOGE("SET_HID_REPORT_DESC: libusb error: %s", libusb_strerror(result));
sc_usb_check_disconnected(aoa->usb, result);
return false;
}
@ -173,6 +175,7 @@ sc_aoa_send_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) {
DEFAULT_TIMEOUT);
if (result < 0) {
LOGE("SEND_HID_EVENT: libusb error: %s", libusb_strerror(result));
sc_usb_check_disconnected(aoa->usb, result);
return false;
}
@ -195,6 +198,7 @@ sc_aoa_unregister_hid(struct sc_aoa *aoa, const uint16_t accessory_id) {
DEFAULT_TIMEOUT);
if (result < 0) {
LOGE("UNREGISTER_HID: libusb error: %s", libusb_strerror(result));
sc_usb_check_disconnected(aoa->usb, result);
return false;
}
@ -278,7 +282,7 @@ sc_aoa_start(struct sc_aoa *aoa) {
bool ok = sc_thread_create(&aoa->thread, run_aoa_thread, "scrcpy-aoa", aoa);
if (!ok) {
LOGC("Could not start AOA thread");
LOGE("Could not start AOA thread");
return false;
}

View File

@ -340,7 +340,7 @@ push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t mods_state) {
if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) {
sc_hid_event_destroy(&hid_event);
LOGW("Could request HID event");
LOGW("Could not request HID event (mod lock state)");
return false;
}
@ -382,7 +382,7 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) {
sc_hid_event_destroy(&hid_event);
LOGW("Could request HID event");
LOGW("Could not request HID event (key)");
}
}
}

View File

@ -181,7 +181,7 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) {
sc_hid_event_destroy(&hid_event);
LOGW("Could request HID event");
LOGW("Could not request HID event (mouse motion)");
}
}
@ -203,7 +203,7 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) {
sc_hid_event_destroy(&hid_event);
LOGW("Could request HID event");
LOGW("Could not request HID event (mouse click)");
}
}
@ -228,7 +228,7 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) {
sc_hid_event_destroy(&hid_event);
LOGW("Could request HID event");
LOGW("Could not request HID event (mouse scroll)");
}
}

View File

@ -2,6 +2,7 @@
#include <SDL2/SDL.h>
#include "adb/adb.h"
#include "events.h"
#include "screen_otg.h"
#include "util/log.h"
@ -28,35 +29,39 @@ sc_usb_on_disconnected(struct sc_usb *usb, void *userdata) {
}
}
static bool
static enum scrcpy_exit_code
event_loop(struct scrcpy_otg *s) {
SDL_Event event;
while (SDL_WaitEvent(&event)) {
switch (event.type) {
case EVENT_USB_DEVICE_DISCONNECTED:
LOGW("Device disconnected");
return false;
return SCRCPY_EXIT_DISCONNECTED;
case SDL_QUIT:
LOGD("User requested to quit");
return true;
return SCRCPY_EXIT_SUCCESS;
default:
sc_screen_otg_handle_event(&s->screen_otg, &event);
break;
}
}
return false;
return SCRCPY_EXIT_FAILURE;
}
bool
enum scrcpy_exit_code
scrcpy_otg(struct scrcpy_options *options) {
static struct scrcpy_otg scrcpy_otg;
struct scrcpy_otg *s = &scrcpy_otg;
const char *serial = options->serial;
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) {
LOGW("Could not enable linear filtering");
}
// Minimal SDL initialization
if (SDL_Init(SDL_INIT_EVENTS)) {
LOGC("Could not initialize SDL: %s", SDL_GetError());
LOGE("Could not initialize SDL: %s", SDL_GetError());
return false;
}
@ -66,7 +71,7 @@ scrcpy_otg(struct scrcpy_options *options) {
LOGW("Could not enable mouse focus clickthrough");
}
bool ret = false;
enum scrcpy_exit_code ret = SCRCPY_EXIT_FAILURE;
struct sc_hid_keyboard *keyboard = NULL;
struct sc_hid_mouse *mouse = NULL;
@ -75,58 +80,36 @@ scrcpy_otg(struct scrcpy_options *options) {
bool aoa_started = false;
bool aoa_initialized = false;
#ifdef _WIN32
// On Windows, only one process could open a USB device
// <https://github.com/Genymobile/scrcpy/issues/2773>
LOGI("Killing adb daemon (if any)...");
unsigned flags = SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR;
// uninterruptible (intr == NULL), but in practice it's very quick
sc_adb_kill_server(NULL, flags);
#endif
static const struct sc_usb_callbacks cbs = {
.on_disconnected = sc_usb_on_disconnected,
};
bool ok = sc_usb_init(&s->usb);
if (!ok) {
return false;
return SCRCPY_EXIT_FAILURE;
}
struct sc_usb_device usb_devices[16];
ssize_t count = sc_usb_find_devices(&s->usb, serial, usb_devices,
ARRAY_LEN(usb_devices));
if (count < 0) {
LOGE("Could not list USB devices");
struct sc_usb_device usb_device;
ok = sc_usb_select_device(&s->usb, serial, &usb_device);
if (!ok) {
goto end;
}
if (count == 0) {
if (serial) {
LOGE("Could not find USB device %s", serial);
} else {
LOGE("Could not find any USB device");
}
goto end;
}
if (count > 1) {
if (serial) {
LOGE("Multiple (%d) USB devices with serial %s:", (int) count,
serial);
} else {
LOGE("Multiple (%d) USB devices:", (int) count);
}
for (size_t i = 0; i < (size_t) count; ++i) {
struct sc_usb_device *d = &usb_devices[i];
LOGE(" %-18s (%04" PRIx16 ":%04" PRIx16 ") %s %s",
d->serial, d->vid, d->pid, d->manufacturer, d->product);
}
if (!serial) {
LOGE("Specify the device via -s or --serial");
}
sc_usb_device_destroy_all(usb_devices, count);
goto end;
}
usb_device_initialized = true;
struct sc_usb_device *usb_device = &usb_devices[0];
LOGI("USB device: %s (%04x:%04x) %s %s", usb_device.serial,
(unsigned) usb_device.vid, (unsigned) usb_device.pid,
usb_device.manufacturer, usb_device.product);
LOGI("USB device: %s (%04" PRIx16 ":%04" PRIx16 ") %s %s",
usb_device->serial, usb_device->vid, usb_device->pid,
usb_device->manufacturer, usb_device->product);
ok = sc_usb_connect(&s->usb, usb_device->device, &cbs, NULL);
ok = sc_usb_connect(&s->usb, usb_device.device, &cbs, NULL);
if (!ok) {
goto end;
}
@ -173,7 +156,7 @@ scrcpy_otg(struct scrcpy_options *options) {
const char *window_title = options->window_title;
if (!window_title) {
window_title = usb_device->product ? usb_device->product : "scrcpy";
window_title = usb_device.product ? usb_device.product : "scrcpy";
}
struct sc_screen_otg_params params = {
@ -183,6 +166,8 @@ scrcpy_otg(struct scrcpy_options *options) {
.always_on_top = options->always_on_top,
.window_x = options->window_x,
.window_y = options->window_y,
.window_width = options->window_width,
.window_height = options->window_height,
.window_borderless = options->window_borderless,
};
@ -192,7 +177,7 @@ scrcpy_otg(struct scrcpy_options *options) {
}
// usb_device not needed anymore
sc_usb_device_destroy(usb_device);
sc_usb_device_destroy(&usb_device);
usb_device_initialized = false;
ret = event_loop(s);
@ -223,7 +208,7 @@ end:
}
if (usb_device_initialized) {
sc_usb_device_destroy(usb_device);
sc_usb_device_destroy(&usb_device);
}
sc_usb_destroy(&s->usb);

View File

@ -3,10 +3,10 @@
#include "common.h"
#include <stdbool.h>
#include "options.h"
#include "scrcpy.h"
bool
enum scrcpy_exit_code
scrcpy_otg(struct scrcpy_options *options);
#endif

View File

@ -5,15 +5,44 @@
#include "util/log.h"
static void
sc_screen_otg_capture_mouse(struct sc_screen_otg *screen, bool capture) {
assert(screen->mouse);
sc_screen_otg_set_mouse_capture(struct sc_screen_otg *screen, bool capture) {
#ifdef __APPLE__
// Workaround for SDL bug on macOS:
// <https://github.com/libsdl-org/SDL/issues/5340>
if (capture) {
int mouse_x, mouse_y;
SDL_GetGlobalMouseState(&mouse_x, &mouse_y);
int x, y, w, h;
SDL_GetWindowPosition(screen->window, &x, &y);
SDL_GetWindowSize(screen->window, &w, &h);
bool outside_window = mouse_x < x || mouse_x >= x + w
|| mouse_y < y || mouse_y >= y + h;
if (outside_window) {
SDL_WarpMouseInWindow(screen->window, w / 2, h / 2);
}
}
#else
(void) screen;
#endif
if (SDL_SetRelativeMouseMode(capture)) {
LOGE("Could not set relative mouse mode to %s: %s",
capture ? "true" : "false", SDL_GetError());
return;
}
}
screen->mouse_captured = capture;
static inline bool
sc_screen_otg_get_mouse_capture(struct sc_screen_otg *screen) {
(void) screen;
return SDL_GetRelativeMouseMode();
}
static inline void
sc_screen_otg_toggle_mouse_capture(struct sc_screen_otg *screen) {
(void) screen;
bool new_value = !sc_screen_otg_get_mouse_capture(screen);
sc_screen_otg_set_mouse_capture(screen, new_value);
}
static void
@ -31,7 +60,6 @@ sc_screen_otg_init(struct sc_screen_otg *screen,
screen->keyboard = params->keyboard;
screen->mouse = params->mouse;
screen->mouse_captured = false;
screen->mouse_capture_key_pressed = 0;
const char *title = params->window_title;
@ -41,10 +69,10 @@ sc_screen_otg_init(struct sc_screen_otg *screen,
? params->window_x : (int) SDL_WINDOWPOS_UNDEFINED;
int y = params->window_y != SC_WINDOW_POSITION_UNDEFINED
? params->window_y : (int) SDL_WINDOWPOS_UNDEFINED;
int width = 256;
int height = 256;
int width = params->window_width ? params->window_width : 256;
int height = params->window_height ? params->window_height : 256;
uint32_t window_flags = 0;
uint32_t window_flags = SDL_WINDOW_ALLOW_HIGHDPI;
if (params->always_on_top) {
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
}
@ -69,6 +97,11 @@ sc_screen_otg_init(struct sc_screen_otg *screen,
if (icon) {
SDL_SetWindowIcon(screen->window, icon);
if (SDL_RenderSetLogicalSize(screen->renderer, icon->w, icon->h)) {
LOGW("Could not set renderer logical size: %s", SDL_GetError());
// don't fail
}
screen->texture = SDL_CreateTextureFromSurface(screen->renderer, icon);
scrcpy_icon_destroy(icon);
if (!screen->texture) {
@ -81,7 +114,7 @@ sc_screen_otg_init(struct sc_screen_otg *screen,
if (screen->mouse) {
// Capture mouse on start
sc_screen_otg_capture_mouse(screen, true);
sc_screen_otg_set_mouse_capture(screen, true);
}
return true;
@ -193,7 +226,7 @@ sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) {
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
if (screen->mouse) {
sc_screen_otg_capture_mouse(screen, false);
sc_screen_otg_set_mouse_capture(screen, false);
}
break;
}
@ -227,8 +260,7 @@ sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) {
if (key == cap) {
// A mouse capture key has been pressed then released:
// toggle the capture mouse mode
sc_screen_otg_capture_mouse(screen,
!screen->mouse_captured);
sc_screen_otg_toggle_mouse_capture(screen);
}
// Mouse capture keys are never forwarded to the device
return;
@ -240,26 +272,26 @@ sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) {
}
break;
case SDL_MOUSEMOTION:
if (screen->mouse && screen->mouse_captured) {
if (screen->mouse && sc_screen_otg_get_mouse_capture(screen)) {
sc_screen_otg_process_mouse_motion(screen, &event->motion);
}
break;
case SDL_MOUSEBUTTONDOWN:
if (screen->mouse && screen->mouse_captured) {
if (screen->mouse && sc_screen_otg_get_mouse_capture(screen)) {
sc_screen_otg_process_mouse_button(screen, &event->button);
}
break;
case SDL_MOUSEBUTTONUP:
if (screen->mouse) {
if (screen->mouse_captured) {
if (sc_screen_otg_get_mouse_capture(screen)) {
sc_screen_otg_process_mouse_button(screen, &event->button);
} else {
sc_screen_otg_capture_mouse(screen, true);
sc_screen_otg_set_mouse_capture(screen, true);
}
}
break;
case SDL_MOUSEWHEEL:
if (screen->mouse && screen->mouse_captured) {
if (screen->mouse && sc_screen_otg_get_mouse_capture(screen)) {
sc_screen_otg_process_mouse_wheel(screen, &event->wheel);
}
break;

View File

@ -18,7 +18,6 @@ struct sc_screen_otg {
SDL_Texture *texture;
// See equivalent mechanism in screen.h
bool mouse_captured;
SDL_Keycode mouse_capture_key_pressed;
};
@ -30,6 +29,8 @@ struct sc_screen_otg_params {
bool always_on_top;
int16_t window_x; // accepts SC_WINDOW_POSITION_UNDEFINED
int16_t window_y; // accepts SC_WINDOW_POSITION_UNDEFINED
uint16_t window_width;
uint16_t window_height;
bool window_borderless;
};

View File

@ -3,6 +3,9 @@
#include <assert.h>
#include "util/log.h"
#include "util/vector.h"
struct sc_vec_usb_devices SC_VECTOR(struct sc_usb_device);
static char *
read_string(libusb_device_handle *handle, uint8_t desc_index) {
@ -12,6 +15,7 @@ read_string(libusb_device_handle *handle, uint8_t desc_index) {
(unsigned char *) buffer,
sizeof(buffer));
if (result < 0) {
LOGD("Read string: libusb error: %s", libusb_strerror(result));
return NULL;
}
@ -19,14 +23,18 @@ read_string(libusb_device_handle *handle, uint8_t desc_index) {
// When non-negative, 'result' contains the number of bytes written
char *s = malloc(result + 1);
if (!s) {
LOG_OOM();
return NULL;
}
memcpy(s, buffer, result);
s[result] = '\0';
return s;
}
static bool
accept_device(libusb_device *device, const char *serial,
struct sc_usb_device *out) {
sc_usb_read_device(libusb_device *device, struct sc_usb_device *out) {
// Do not log any USB error in this function, it is expected that many USB
// devices available on the computer have permission restrictions
@ -39,6 +47,11 @@ accept_device(libusb_device *device, const char *serial,
libusb_device_handle *handle;
result = libusb_open(device, &handle);
if (result < 0) {
// Log at debug level because it is expected that some non-Android USB
// devices present on the computer require special permissions
LOGD("Open USB device %04x:%04x: libusb error: %s",
(unsigned) desc.idVendor, (unsigned) desc.idProduct,
libusb_strerror(result));
return false;
}
@ -48,22 +61,13 @@ accept_device(libusb_device *device, const char *serial,
return false;
}
if (serial) {
// Filter by serial
bool matches = !strcmp(serial, device_serial);
if (!matches) {
free(device_serial);
libusb_close(handle);
return false;
}
}
out->device = libusb_ref_device(device);
out->serial = device_serial;
out->vid = desc.idVendor;
out->pid = desc.idProduct;
out->manufacturer = read_string(handle, desc.iManufacturer);
out->product = read_string(handle, desc.iProduct);
out->selected = false;
libusb_close(handle);
@ -72,40 +76,150 @@ accept_device(libusb_device *device, const char *serial,
void
sc_usb_device_destroy(struct sc_usb_device *usb_device) {
libusb_unref_device(usb_device->device);
if (usb_device->device) {
libusb_unref_device(usb_device->device);
}
free(usb_device->serial);
free(usb_device->manufacturer);
free(usb_device->product);
}
void
sc_usb_device_destroy_all(struct sc_usb_device *usb_devices, size_t count) {
for (size_t i = 0; i < count; ++i) {
sc_usb_device_destroy(&usb_devices[i]);
}
sc_usb_device_move(struct sc_usb_device *dst, struct sc_usb_device *src) {
*dst = *src;
src->device = NULL;
src->serial = NULL;
src->manufacturer = NULL;
src->product = NULL;
}
ssize_t
sc_usb_find_devices(struct sc_usb *usb, const char *serial,
struct sc_usb_device *devices, size_t len) {
void
sc_usb_devices_destroy(struct sc_vec_usb_devices *usb_devices) {
for (size_t i = 0; i < usb_devices->size; ++i) {
sc_usb_device_destroy(&usb_devices->data[i]);
}
sc_vector_destroy(usb_devices);
}
static bool
sc_usb_list_devices(struct sc_usb *usb, struct sc_vec_usb_devices *out_vec) {
libusb_device **list;
ssize_t count = libusb_get_device_list(usb->context, &list);
if (count < 0) {
LOGE("List USB devices: libusb error: %s", libusb_strerror(count));
return -1;
return false;
}
size_t idx = 0;
for (size_t i = 0; i < (size_t) count && idx < len; ++i) {
for (size_t i = 0; i < (size_t) count; ++i) {
libusb_device *device = list[i];
if (accept_device(device, serial, &devices[idx])) {
++idx;
struct sc_usb_device usb_device;
if (sc_usb_read_device(device, &usb_device)) {
bool ok = sc_vector_push(out_vec, usb_device);
if (!ok) {
LOG_OOM();
LOGE("Could not push usb_device to vector");
sc_usb_device_destroy(&usb_device);
// continue anyway
}
}
}
libusb_free_device_list(list, 1);
return idx;
return true;
}
static bool
sc_usb_accept_device(const struct sc_usb_device *device, const char *serial) {
if (!serial) {
return true;
}
return !strcmp(serial, device->serial);
}
static size_t
sc_usb_devices_select(struct sc_usb_device *devices, size_t len,
const char *serial, size_t *idx_out) {
size_t count = 0;
for (size_t i = 0; i < len; ++i) {
struct sc_usb_device *device = &devices[i];
device->selected = sc_usb_accept_device(device, serial);
if (device->selected) {
if (idx_out && !count) {
*idx_out = i;
}
++count;
}
}
return count;
}
static void
sc_usb_devices_log(enum sc_log_level level, struct sc_usb_device *devices,
size_t count) {
for (size_t i = 0; i < count; ++i) {
struct sc_usb_device *d = &devices[i];
const char *selection = d->selected ? "-->" : " ";
// Convert uint16_t to unsigned because PRIx16 may not exist on Windows
LOG(level, " %s %-18s (%04x:%04x) %s %s",
selection, d->serial, (unsigned) d->vid, (unsigned) d->pid,
d->manufacturer, d->product);
}
}
bool
sc_usb_select_device(struct sc_usb *usb, const char *serial,
struct sc_usb_device *out_device) {
struct sc_vec_usb_devices vec = SC_VECTOR_INITIALIZER;
bool ok = sc_usb_list_devices(usb, &vec);
if (!ok) {
LOGE("Could not list USB devices");
return false;
}
if (vec.size == 0) {
LOGE("Could not find any USB device");
return false;
}
size_t sel_idx; // index of the single matching device if sel_count == 1
size_t sel_count =
sc_usb_devices_select(vec.data, vec.size, serial, &sel_idx);
if (sel_count == 0) {
// if count > 0 && sel_count == 0, then necessarily a serial is provided
assert(serial);
LOGE("Could not find USB device %s", serial);
sc_usb_devices_log(SC_LOG_LEVEL_ERROR, vec.data, vec.size);
sc_usb_devices_destroy(&vec);
return false;
}
if (sel_count > 1) {
if (serial) {
LOGE("Multiple (%" SC_PRIsizet ") USB devices with serial %s:",
sel_count, serial);
} else {
LOGE("Multiple (%" SC_PRIsizet ") USB devices:", sel_count);
}
sc_usb_devices_log(SC_LOG_LEVEL_ERROR, vec.data, vec.size);
LOGE("Select a device via -s (--serial)");
sc_usb_devices_destroy(&vec);
return false;
}
assert(sel_count == 1); // sel_idx is valid only if sel_count == 1
struct sc_usb_device *device = &vec.data[sel_idx];
LOGD("USB device found:");
sc_usb_devices_log(SC_LOG_LEVEL_DEBUG, vec.data, vec.size);
// Move device into out_device (do not destroy device)
sc_usb_device_move(out_device, device);
sc_usb_devices_destroy(&vec);
return true;
}
bool
@ -119,7 +233,25 @@ sc_usb_destroy(struct sc_usb *usb) {
libusb_exit(usb->context);
}
static int
static void
sc_usb_report_disconnected(struct sc_usb *usb) {
if (usb->cbs && !atomic_flag_test_and_set(&usb->disconnection_notified)) {
assert(usb->cbs && usb->cbs->on_disconnected);
usb->cbs->on_disconnected(usb, usb->cbs_userdata);
}
}
bool
sc_usb_check_disconnected(struct sc_usb *usb, int result) {
if (result == LIBUSB_ERROR_NO_DEVICE || result == LIBUSB_ERROR_NOT_FOUND) {
sc_usb_report_disconnected(usb);
return false;
}
return true;
}
static LIBUSB_CALL int
sc_usb_libusb_callback(libusb_context *ctx, libusb_device *device,
libusb_hotplug_event event, void *userdata) {
(void) ctx;
@ -135,8 +267,7 @@ sc_usb_libusb_callback(libusb_context *ctx, libusb_device *device,
return 0;
}
assert(usb->cbs && usb->cbs->on_disconnected);
usb->cbs->on_disconnected(usb, usb->cbs_userdata);
sc_usb_report_disconnected(usb);
// Do not automatically deregister the callback by returning 1. Instead,
// manually deregister to interrupt libusb_handle_events() from the libusb
@ -157,7 +288,8 @@ run_libusb_event_handler(void *data) {
static bool
sc_usb_register_callback(struct sc_usb *usb) {
if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) {
LOGW("libusb does not have hotplug capability");
LOGW("On this platform, libusb does not have hotplug capability; "
"device disconnection will not be detected properly");
return false;
}
@ -209,6 +341,7 @@ sc_usb_connect(struct sc_usb *usb, libusb_device *device,
if (cbs) {
atomic_init(&usb->stopped, false);
usb->disconnection_notified = (atomic_flag) ATOMIC_FLAG_INIT;
if (sc_usb_register_callback(usb)) {
// Create a thread to process libusb events, so that device
// disconnection could be detected immediately
@ -219,8 +352,6 @@ sc_usb_connect(struct sc_usb *usb, libusb_device *device,
LOGW("Libusb event thread handler could not be created, USB "
"device disconnection might not be detected immediately");
}
} else {
LOGW("Could not register USB device disconnection callback");
}
}

View File

@ -22,6 +22,7 @@ struct sc_usb {
sc_thread libusb_event_thread;
atomic_bool stopped; // only used if cbs != NULL
atomic_flag disconnection_notified;
};
struct sc_usb_callbacks {
@ -35,13 +36,26 @@ struct sc_usb_device {
char *product;
uint16_t vid;
uint16_t pid;
bool selected;
};
void
sc_usb_device_destroy(struct sc_usb_device *usb_device);
/**
* Move src to dst
*
* After this call, the content of src is undefined, except that
* sc_usb_device_destroy() can be called.
*
* This is useful to take a device from a list that will be destroyed, without
* making unnecessary copies.
*/
void
sc_usb_device_destroy_all(struct sc_usb_device *usb_devices, size_t count);
sc_usb_device_move(struct sc_usb_device *dst, struct sc_usb_device *src);
void
sc_usb_devices_destroy_all(struct sc_usb_device *usb_devices, size_t count);
bool
sc_usb_init(struct sc_usb *usb);
@ -49,9 +63,9 @@ sc_usb_init(struct sc_usb *usb);
void
sc_usb_destroy(struct sc_usb *usb);
ssize_t
sc_usb_find_devices(struct sc_usb *usb, const char *serial,
struct sc_usb_device *devices, size_t len);
bool
sc_usb_select_device(struct sc_usb *usb, const char *serial,
struct sc_usb_device *out_device);
bool
sc_usb_connect(struct sc_usb *usb, libusb_device *device,
@ -60,6 +74,11 @@ sc_usb_connect(struct sc_usb *usb, libusb_device *device,
void
sc_usb_disconnect(struct sc_usb *usb);
// A client should call this function with the return value of a libusb call
// to detect disconnection immediately
bool
sc_usb_check_disconnected(struct sc_usb *usb, int result);
void
sc_usb_stop(struct sc_usb *usb);

View File

@ -1,5 +1,5 @@
#ifndef BUFFER_UTIL_H
#define BUFFER_UTIL_H
#ifndef SC_BUFFER_UTIL_H
#define SC_BUFFER_UTIL_H
#include "common.h"
@ -7,13 +7,13 @@
#include <stdint.h>
static inline void
buffer_write16be(uint8_t *buf, uint16_t value) {
sc_write16be(uint8_t *buf, uint16_t value) {
buf[0] = value >> 8;
buf[1] = value;
}
static inline void
buffer_write32be(uint8_t *buf, uint32_t value) {
sc_write32be(uint8_t *buf, uint32_t value) {
buf[0] = value >> 24;
buf[1] = value >> 16;
buf[2] = value >> 8;
@ -21,25 +21,25 @@ buffer_write32be(uint8_t *buf, uint32_t value) {
}
static inline void
buffer_write64be(uint8_t *buf, uint64_t value) {
buffer_write32be(buf, value >> 32);
buffer_write32be(&buf[4], (uint32_t) value);
sc_write64be(uint8_t *buf, uint64_t value) {
sc_write32be(buf, value >> 32);
sc_write32be(&buf[4], (uint32_t) value);
}
static inline uint16_t
buffer_read16be(const uint8_t *buf) {
sc_read16be(const uint8_t *buf) {
return (buf[0] << 8) | buf[1];
}
static inline uint32_t
buffer_read32be(const uint8_t *buf) {
sc_read32be(const uint8_t *buf) {
return ((uint32_t) buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
}
static inline uint64_t
buffer_read64be(const uint8_t *buf) {
uint32_t msb = buffer_read32be(buf);
uint32_t lsb = buffer_read32be(&buf[4]);
sc_read64be(const uint8_t *buf) {
uint32_t msb = sc_read32be(buf);
uint32_t lsb = sc_read32be(&buf[4]);
return ((uint64_t) msb << 32) | lsb;
}

View File

@ -1,6 +1,6 @@
// generic circular buffer (bounded queue) implementation
#ifndef CBUF_H
#define CBUF_H
#ifndef SC_CBUF_H
#define SC_CBUF_H
#include "common.h"

View File

@ -55,6 +55,16 @@ sc_get_log_level(void) {
return log_level_sdl_to_sc(sdl_log);
}
void
sc_log(enum sc_log_level level, const char *fmt, ...) {
SDL_LogPriority sdl_level = log_level_sc_to_sdl(level);
va_list ap;
va_start(ap, fmt);
SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, sdl_level, fmt, ap);
va_end(ap);
}
#ifdef _WIN32
bool
sc_log_windows_error(const char *prefix, int error) {

View File

@ -15,10 +15,9 @@
#define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGW(...) SDL_LogWarn(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 LOG_OOM() \
LOGC("OOM: %s:%d %s()", __FILE__, __LINE__, __func__)
LOGE("OOM: %s:%d %s()", __FILE__, __LINE__, __func__)
void
sc_set_log_level(enum sc_log_level level);
@ -26,6 +25,10 @@ sc_set_log_level(enum sc_log_level level);
enum sc_log_level
sc_get_log_level(void);
void
sc_log(enum sc_log_level level, const char *fmt, ...);
#define LOG(LEVEL, ...) sc_log((LEVEL), __VA_ARGS__)
#ifdef _WIN32
// Log system error (typically returned by GetLastError() or similar)
bool

View File

@ -3,11 +3,10 @@
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <SDL2/SDL_platform.h>
#include "log.h"
#ifdef __WINDOWS__
#ifdef _WIN32
# include <ws2tcpip.h>
typedef int socklen_t;
typedef SOCKET sc_raw_socket;
@ -29,11 +28,11 @@
bool
net_init(void) {
#ifdef __WINDOWS__
#ifdef _WIN32
WSADATA wsa;
int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0;
if (res < 0) {
LOGC("WSAStartup failed with error %d", res);
LOGE("WSAStartup failed with error %d", res);
return false;
}
#endif
@ -42,14 +41,14 @@ net_init(void) {
void
net_cleanup(void) {
#ifdef __WINDOWS__
#ifdef _WIN32
WSACleanup();
#endif
}
static inline sc_socket
wrap(sc_raw_socket sock) {
#ifdef __WINDOWS__
#ifdef _WIN32
if (sock == INVALID_SOCKET) {
return SC_SOCKET_NONE;
}
@ -72,7 +71,7 @@ wrap(sc_raw_socket sock) {
static inline sc_raw_socket
unwrap(sc_socket socket) {
#ifdef __WINDOWS__
#ifdef _WIN32
if (socket == SC_SOCKET_NONE) {
return INVALID_SOCKET;
}
@ -248,7 +247,7 @@ net_interrupt(sc_socket socket) {
sc_raw_socket raw_sock = unwrap(socket);
#ifdef __WINDOWS__
#ifdef _WIN32
if (!atomic_flag_test_and_set(&socket->closed)) {
return !closesocket(raw_sock);
}
@ -262,7 +261,7 @@ bool
net_close(sc_socket socket) {
sc_raw_socket raw_sock = unwrap(socket);
#ifdef __WINDOWS__
#ifdef _WIN32
bool ret = true;
if (!atomic_flag_test_and_set(&socket->closed)) {
ret = !closesocket(raw_sock);

View File

@ -1,13 +1,12 @@
#ifndef NET_H
#define NET_H
#ifndef SC_NET_H
#define SC_NET_H
#include "common.h"
#include <stdbool.h>
#include <stdint.h>
#include <SDL2/SDL_platform.h>
#ifdef __WINDOWS__
#ifdef _WIN32
# include <winsock2.h>
# include <stdatomic.h>
@ -17,7 +16,7 @@
atomic_flag closed;
} *sc_socket;
#else // not __WINDOWS__
#else // not _WIN32
# include <sys/socket.h>
# define SC_SOCKET_NONE -1

View File

@ -12,8 +12,6 @@
# include <winsock2.h>
# include <windows.h>
# define SC_PRIexitcode "lu"
// <https://stackoverflow.com/a/44383330/1987178>
# define SC_PRIsizet "Iu"
# define SC_PROCESS_NONE NULL
# define SC_EXIT_CODE_NONE -1UL // max value as unsigned long
typedef HANDLE sc_pid;
@ -23,7 +21,6 @@
#else
# include <sys/types.h>
# define SC_PRIsizet "zu"
# define SC_PRIexitcode "d"
# define SC_PROCESS_NONE -1
# define SC_EXIT_CODE_NONE -1

View File

@ -3,27 +3,33 @@
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)) {
if (intr && !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);
if (intr) {
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)) {
if (intr && !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);
if (intr) {
sc_intr_set_process(intr, SC_PROCESS_NONE);
}
return ret;
}

View File

@ -54,7 +54,7 @@ sc_mutex_lock(sc_mutex *mutex) {
int r = SDL_LockMutex(mutex->mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not lock mutex: %s", SDL_GetError());
LOGE("Could not lock mutex: %s", SDL_GetError());
abort();
}
@ -74,7 +74,7 @@ sc_mutex_unlock(sc_mutex *mutex) {
int r = SDL_UnlockMutex(mutex->mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not lock mutex: %s", SDL_GetError());
LOGE("Could not lock mutex: %s", SDL_GetError());
abort();
}
#else
@ -118,7 +118,7 @@ sc_cond_wait(sc_cond *cond, sc_mutex *mutex) {
int r = SDL_CondWait(cond->cond, mutex->mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not wait on condition: %s", SDL_GetError());
LOGE("Could not wait on condition: %s", SDL_GetError());
abort();
}
@ -136,11 +136,13 @@ sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, sc_tick deadline) {
return false; // timeout
}
uint32_t ms = SC_TICK_TO_MS(deadline - now);
// Round up to the next millisecond to guarantee that the deadline is
// reached when returning due to timeout
uint32_t ms = SC_TICK_TO_MS(deadline - now + SC_TICK_FROM_MS(1) - 1);
int r = SDL_CondWaitTimeout(cond->cond, mutex->mutex, ms);
#ifndef NDEBUG
if (r < 0) {
LOGC("Could not wait on condition with timeout: %s", SDL_GetError());
LOGE("Could not wait on condition with timeout: %s", SDL_GetError());
abort();
}
@ -148,6 +150,8 @@ sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, sc_tick deadline) {
memory_order_relaxed);
#endif
assert(r == 0 || r == SDL_MUTEX_TIMEDOUT);
// The deadline is reached on timeout
assert(r != SDL_MUTEX_TIMEDOUT || sc_tick_now() >= deadline);
return r == 0;
}
@ -156,7 +160,7 @@ sc_cond_signal(sc_cond *cond) {
int r = SDL_CondSignal(cond->cond);
#ifndef NDEBUG
if (r) {
LOGC("Could not signal a condition: %s", SDL_GetError());
LOGE("Could not signal a condition: %s", SDL_GetError());
abort();
}
#else
@ -169,7 +173,7 @@ sc_cond_broadcast(sc_cond *cond) {
int r = SDL_CondBroadcast(cond->cond);
#ifndef NDEBUG
if (r) {
LOGC("Could not broadcast a condition: %s", SDL_GetError());
LOGE("Could not broadcast a condition: %s", SDL_GetError());
abort();
}
#else

540
app/src/util/vector.h Normal file
View File

@ -0,0 +1,540 @@
#ifndef SC_VECTOR_H
#define SC_VECTOR_H
#include "common.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
// Adapted from vlc_vector:
// <https://code.videolan.org/videolan/vlc/-/blob/0857947abaed9c89810cd96353aaa1b7e6ba3b0d/include/vlc_vector.h>
/**
* Vector struct body
*
* A vector is a dynamic array, managed by the sc_vector_* helpers.
*
* It is generic over the type of its items, so it is implemented as macros.
*
* To use a vector, a new type must be defined:
*
* struct vec_int SC_VECTOR(int);
*
* The struct may be anonymous:
*
* struct SC_VECTOR(const char *) names;
*
* Vector size is accessible via `vec.size`, and items are intended to be
* accessed directly, via `vec.data[i]`.
*
* Functions and macros having name ending with '_' are private.
*/
#define SC_VECTOR(type) \
{ \
size_t cap; \
size_t size; \
type *data; \
}
/**
* Static initializer for a vector
*/
#define SC_VECTOR_INITIALIZER { 0, 0, NULL }
/**
* Initialize an empty vector
*/
#define sc_vector_init(pv) \
({ \
(pv)->cap = 0; \
(pv)->size = 0; \
(pv)->data = NULL; \
})
/**
* Destroy a vector
*
* The vector may not be used anymore unless sc_vector_init() is called.
*/
#define sc_vector_destroy(pv) \
free((pv)->data)
/**
* Clear a vector
*
* Remove all items from the vector.
*/
#define sc_vector_clear(pv) \
({ \
sc_vector_destroy(pv); \
sc_vector_init(pv);\
})
/**
* The minimal allocation size, in number of items
*
* Private.
*/
#define SC_VECTOR_MINCAP_ ((size_t) 10)
static inline size_t
sc_vector_min_(size_t a, size_t b)
{
return a < b ? a : b;
}
static inline size_t
sc_vector_max_(size_t a, size_t b)
{
return a > b ? a : b;
}
static inline size_t
sc_vector_clamp_(size_t x, size_t min, size_t max)
{
return sc_vector_max_(min, sc_vector_min_(max, x));
}
/**
* Realloc data and update vector fields
*
* On reallocation success, update the vector capacity (*pcap) and size
* (*psize), and return the reallocated data.
*
* On reallocation failure, return NULL without any change.
*
* Private.
*
* \param ptr the current `data` field of the vector to realloc
* \param count the requested capacity, in number of items
* \param size the size of one item
* \param pcap a pointer to the `cap` field of the vector [IN/OUT]
* \param psize a pointer to the `size` field of the vector [IN/OUT]
* \return the new ptr on success, NULL on error
*/
static inline void *
sc_vector_reallocdata_(void *ptr, size_t count, size_t size,
size_t *restrict pcap, size_t *restrict psize)
{
void *p = realloc(ptr, count * size);
if (!p) {
return NULL;
}
*pcap = count;
*psize = sc_vector_min_(*psize, count);
return p;
}
#define sc_vector_realloc_(pv, newcap) \
({ \
void *p = sc_vector_reallocdata_((pv)->data, newcap, sizeof(*(pv)->data), \
&(pv)->cap, &(pv)->size); \
if (p) { \
(pv)->data = p; \
} \
(bool) p; \
});
#define sc_vector_resize_(pv, newcap) \
({ \
bool ok; \
if ((pv)->cap == (newcap)) { \
ok = true; \
} else if ((newcap) > 0) { \
ok = sc_vector_realloc_(pv, (newcap)); \
} else { \
sc_vector_clear(pv); \
ok = true; \
} \
ok; \
})
static inline size_t
sc_vector_growsize_(size_t value)
{
/* integer multiplication by 1.5 */
return value + (value >> 1);
}
/* SIZE_MAX/2 to fit in ssize_t, and so that cap*1.5 does not overflow. */
#define sc_vector_max_cap_(pv) (SIZE_MAX / 2 / sizeof(*(pv)->data))
/**
* Increase the capacity of the vector to at least `mincap`
*
* \param pv a pointer to the vector
* \param mincap (size_t) the requested capacity
* \retval true if no allocation failed
* \retval false on allocation failure (the vector is left untouched)
*/
#define sc_vector_reserve(pv, mincap) \
({ \
bool ok; \
/* avoid to allocate tiny arrays (< SC_VECTOR_MINCAP_) */ \
size_t mincap_ = sc_vector_max_(mincap, SC_VECTOR_MINCAP_); \
if (mincap_ <= (pv)->cap) { \
/* nothing to do */ \
ok = true; \
} else if (mincap_ <= sc_vector_max_cap_(pv)) { \
/* not too big */ \
size_t newsize = sc_vector_growsize_((pv)->cap); \
newsize = sc_vector_clamp_(newsize, mincap_, sc_vector_max_cap_(pv)); \
ok = sc_vector_realloc_(pv, newsize); \
} else { \
ok = false; \
} \
ok; \
})
#define sc_vector_shrink_to_fit(pv) \
/* decreasing the size may not fail */ \
(void) sc_vector_resize_(pv, (pv)->size)
/**
* Resize the vector down automatically
*
* Shrink only when necessary (in practice when cap > (size+5)*1.5)
*
* \param pv a pointer to the vector
*/
#define sc_vector_autoshrink(pv) \
({ \
bool must_shrink = \
/* do not shrink to tiny size */ \
(pv)->cap > SC_VECTOR_MINCAP_ && \
/* no need to shrink */ \
(pv)->cap >= sc_vector_growsize_((pv)->size + 5); \
if (must_shrink) { \
size_t newsize = sc_vector_max_((pv)->size + 5, SC_VECTOR_MINCAP_); \
sc_vector_resize_(pv, newsize); \
} \
})
#define sc_vector_check_same_ptr_type_(a, b) \
(void) ((a) == (b)) /* warn on type mismatch */
/**
* Push an item at the end of the vector
*
* The amortized complexity is O(1).
*
* \param pv a pointer to the vector
* \param item the item to append
* \retval true if no allocation failed
* \retval false on allocation failure (the vector is left untouched)
*/
#define sc_vector_push(pv, item) \
({ \
bool ok = sc_vector_reserve(pv, (pv)->size + 1); \
if (ok) { \
(pv)->data[(pv)->size++] = (item); \
} \
ok; \
})
/**
* Append `count` items at the end of the vector
*
* \param pv a pointer to the vector
* \param items the items array to append
* \param count the number of items in the array
* \retval true if no allocation failed
* \retval false on allocation failure (the vector is left untouched)
*/
#define sc_vector_push_all(pv, items, count) \
sc_vector_push_all_(pv, items, (size_t) count)
#define sc_vector_push_all_(pv, items, count) \
({ \
sc_vector_check_same_ptr_type_((pv)->data, items); \
bool ok = sc_vector_reserve(pv, (pv)->size + (count)); \
if (ok) { \
memcpy(&(pv)->data[(pv)->size], items, (count) * sizeof(*(pv)->data)); \
(pv)->size += count; \
} \
ok; \
})
/**
* Insert an hole of size `count` to the given index
*
* The items in range [index; size-1] will be moved. The items in the hole are
* left uninitialized.
*
* \param pv a pointer to the vector
* \param index the index where the hole is to be inserted
* \param count the number of items in the hole
* \retval true if no allocation failed
* \retval false on allocation failure (the vector is left untouched)
*/
#define sc_vector_insert_hole(pv, index, count) \
sc_vector_insert_hole_(pv, (size_t) index, (size_t) count);
#define sc_vector_insert_hole_(pv, index, count) \
({ \
bool ok = sc_vector_reserve(pv, (pv)->size + (count)); \
if (ok) { \
if ((index) < (pv)->size) { \
memmove(&(pv)->data[(index) + (count)], \
&(pv)->data[(index)], \
((pv)->size - (index)) * sizeof(*(pv)->data)); \
} \
(pv)->size += count; \
} \
ok; \
})
/**
* Insert an item at the given index
*
* The items in range [index; size-1] will be moved.
*
* \param pv a pointer to the vector
* \param index the index where the item is to be inserted
* \param item the item to append
* \retval true if no allocation failed
* \retval false on allocation failure (the vector is left untouched)
*/
#define sc_vector_insert(pv, index, item) \
sc_vector_insert_(pv, (size_t) index, (size_t) item);
#define sc_vector_insert_(pv, index, item) \
({ \
bool ok = sc_vector_insert_hole_(pv, index, 1); \
if (ok) { \
(pv)->data[index] = (item); \
} \
ok; \
})
/**
* Insert `count` items at the given index
*
* The items in range [index; size-1] will be moved.
*
* \param pv a pointer to the vector
* \param index the index where the items are to be inserted
* \param items the items array to append
* \param count the number of items in the array
* \retval true if no allocation failed
* \retval false on allocation failure (the vector is left untouched)
*/
#define sc_vector_insert_all(pv, index, items, count) \
sc_vector_insert_all_(pv, (size_t) index, items, (size_t) count)
#define sc_vector_insert_all_(pv, index, items, count) \
({ \
sc_vector_check_same_ptr_type_((pv)->data, items); \
bool ok = sc_vector_insert_hole_(pv, index, count); \
if (ok) { \
memcpy(&(pv)->data[index], items, count * sizeof(*(pv)->data)); \
} \
ok; \
})
/** Reverse a char array in place */
static inline void
sc_char_array_reverse(char *array, size_t len)
{
for (size_t i = 0; i < len / 2; ++i)
{
char c = array[i];
array[i] = array[len - i - 1];
array[len - i - 1] = c;
}
}
/**
* Right-rotate a (char) array in place
*
* For example, left-rotating a char array containing {1, 2, 3, 4, 5, 6} with
* distance 4 will result in {5, 6, 1, 2, 3, 4}.
*
* Private.
*/
static inline void
sc_char_array_rotate_left(char *array, size_t len, size_t distance)
{
sc_char_array_reverse(array, distance);
sc_char_array_reverse(&array[distance], len - distance);
sc_char_array_reverse(array, len);
}
/**
* Right-rotate a (char) array in place
*
* For example, left-rotating a char array containing {1, 2, 3, 4, 5, 6} with
* distance 2 will result in {5, 6, 1, 2, 3, 4}.
*
* Private.
*/
static inline void
sc_char_array_rotate_right(char *array, size_t len, size_t distance)
{
sc_char_array_rotate_left(array, len, len - distance);
}
/**
* Move items in a (char) array in place
*
* Move slice [index, count] to target.
*/
static inline void
sc_char_array_move(char *array, size_t idx, size_t count, size_t target)
{
if (idx < target) {
sc_char_array_rotate_left(&array[idx], target - idx + count, count);
} else {
sc_char_array_rotate_right(&array[target], idx - target + count, count);
}
}
/**
* Move a slice of items to a given target index
*
* The items in range [index; count] will be moved so that the *new* position
* of the first item is `target`.
*
* \param pv a pointer to the vector
* \param index the index of the first item to move
* \param count the number of items to move
* \param target the new index of the moved slice
*/
#define sc_vector_move_slice(pv, index, count, target) \
sc_vector_move_slice_(pv, (size_t) index, count, (size_t) target);
#define sc_vector_move_slice_(pv, index, count, target) \
({ \
sc_char_array_move((char *) (pv)->data, \
(index) * sizeof(*(pv)->data), \
(count) * sizeof(*(pv)->data), \
(target) * sizeof(*(pv)->data)); \
})
/**
* Move an item to a given target index
*
* The items will be moved so that its *new* position is `target`.
*
* \param pv a pointer to the vector
* \param index the index of the item to move
* \param target the new index of the moved item
*/
#define sc_vector_move(pv, index, target) \
sc_vector_move_slice(pv, index, 1, target)
/**
* Remove a slice of items, without shrinking the array
*
* If you have no good reason to use the _noshrink() version, use
* sc_vector_remove_slice() instead.
*
* The items in range [index+count; size-1] will be moved.
*
* \param pv a pointer to the vector
* \param index the index of the first item to remove
* \param count the number of items to remove
*/
#define sc_vector_remove_slice_noshrink(pv, index, count) \
sc_vector_remove_slice_noshrink_(pv, (size_t) index, (size_t) count)
#define sc_vector_remove_slice_noshrink_(pv, index, count) \
({ \
if ((index) + (count) < (pv)->size) { \
memmove(&(pv)->data[index], \
&(pv)->data[(index) + (count)], \
((pv)->size - (index) - (count)) * sizeof(*(pv)->data)); \
} \
(pv)->size -= count; \
})
/**
* Remove a slice of items
*
* The items in range [index+count; size-1] will be moved.
*
* \param pv a pointer to the vector
* \param index the index of the first item to remove
* \param count the number of items to remove
*/
#define sc_vector_remove_slice(pv, index, count) \
({ \
sc_vector_remove_slice_noshrink(pv, index, count); \
sc_vector_autoshrink(pv); \
})
/**
* Remove an item, without shrinking the array
*
* If you have no good reason to use the _noshrink() version, use
* sc_vector_remove() instead.
*
* The items in range [index+1; size-1] will be moved.
*
* \param pv a pointer to the vector
* \param index the index of item to remove
*/
#define sc_vector_remove_noshrink(pv, index) \
sc_vector_remove_slice_noshrink(pv, index, 1)
/**
* Remove an item
*
* The items in range [index+1; size-1] will be moved.
*
* \param pv a pointer to the vector
* \param index the index of item to remove
*/
#define sc_vector_remove(pv, index) \
({ \
sc_vector_remove_noshrink(pv, index); \
sc_vector_autoshrink(pv); \
})
/**
* Remove an item
*
* The removed item is replaced by the last item of the vector.
*
* This does not preserve ordering, but is O(1). This is useful when the order
* of items is not meaningful.
*
* \param pv a pointer to the vector
* \param index the index of item to remove
*/
#define sc_vector_swap_remove(pv, index) \
sc_vector_swap_remove_(pv, (size_t) index);
#define sc_vector_swap_remove_(pv, index) \
({ \
(pv)->data[index] = (pv)->data[(pv)->size-1]; \
(pv)->size--; \
});
/**
* Return the index of an item
*
* Iterate over all items to find a given item.
*
* Use only for vectors of primitive types or pointers.
*
* Return the index, or -1 if not found.
*
* \param pv a pointer to the vector
* \param item the item to find (compared with ==)
*/
#define sc_vector_index_of(pv, item) \
({ \
ssize_t idx = -1; \
for (size_t i = 0; i < (pv)->size; ++i) { \
if ((pv)->data[i] == (item)) { \
idx = (ssize_t) i; \
break; \
} \
} \
idx; \
})
#endif

View File

@ -274,7 +274,7 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
LOGD("Starting v4l2 thread");
ok = sc_thread_create(&vs->thread, run_v4l2_sink, "scrcpy-v4l2", vs);
if (!ok) {
LOGC("Could not start v4l2 thread");
LOGE("Could not start v4l2 thread");
goto error_av_packet_free;
}

67
app/src/version.c Normal file
View File

@ -0,0 +1,67 @@
#include "version.h"
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#ifdef HAVE_V4L2
# include <libavdevice/avdevice.h>
#endif
#ifdef HAVE_USB
# include <libusb-1.0/libusb.h>
#endif
void
scrcpy_print_version(void) {
printf("\nDependencies (compiled / linked):\n");
SDL_version sdl;
SDL_GetVersion(&sdl);
printf(" - SDL: %u.%u.%u / %u.%u.%u\n",
SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL,
(unsigned) sdl.major, (unsigned) sdl.minor, (unsigned) sdl.patch);
unsigned avcodec = avcodec_version();
printf(" - libavcodec: %u.%u.%u / %u.%u.%u\n",
LIBAVCODEC_VERSION_MAJOR,
LIBAVCODEC_VERSION_MINOR,
LIBAVCODEC_VERSION_MICRO,
AV_VERSION_MAJOR(avcodec),
AV_VERSION_MINOR(avcodec),
AV_VERSION_MICRO(avcodec));
unsigned avformat = avformat_version();
printf(" - libavformat: %u.%u.%u / %u.%u.%u\n",
LIBAVFORMAT_VERSION_MAJOR,
LIBAVFORMAT_VERSION_MINOR,
LIBAVFORMAT_VERSION_MICRO,
AV_VERSION_MAJOR(avformat),
AV_VERSION_MINOR(avformat),
AV_VERSION_MICRO(avformat));
unsigned avutil = avutil_version();
printf(" - libavutil: %u.%u.%u / %u.%u.%u\n",
LIBAVUTIL_VERSION_MAJOR,
LIBAVUTIL_VERSION_MINOR,
LIBAVUTIL_VERSION_MICRO,
AV_VERSION_MAJOR(avutil),
AV_VERSION_MINOR(avutil),
AV_VERSION_MICRO(avutil));
#ifdef HAVE_V4L2
unsigned avdevice = avdevice_version();
printf(" - libavdevice: %u.%u.%u / %u.%u.%u\n",
LIBAVDEVICE_VERSION_MAJOR,
LIBAVDEVICE_VERSION_MINOR,
LIBAVDEVICE_VERSION_MICRO,
AV_VERSION_MAJOR(avdevice),
AV_VERSION_MINOR(avdevice),
AV_VERSION_MICRO(avdevice));
#endif
#ifdef HAVE_USB
const struct libusb_version *usb = libusb_get_version();
// The compiled version may not be known
printf(" - libusb: - / %u.%u.%u\n",
(unsigned) usb->major, (unsigned) usb->minor, (unsigned) usb->micro);
#endif
}

9
app/src/version.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef SC_VERSION_H
#define SC_VERSION_H
#include "common.h"
void
scrcpy_print_version(void);
#endif

View File

@ -2,83 +2,242 @@
#include <assert.h>
#include "adb/adb_device.h"
#include "adb/adb_parser.h"
static void test_get_ip_single_line() {
static void test_adb_devices(void) {
char output[] =
"List of devices attached\n"
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
"device:MyDevice transport_id:1\n"
"192.168.1.1:5555 device product:MyWifiProduct model:MyWifiModel "
"device:MyWifiDevice trandport_id:2\n";
struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER;
bool ok = sc_adb_parse_devices(output, &vec);
assert(ok);
assert(vec.size == 2);
struct sc_adb_device *device = &vec.data[0];
assert(!strcmp("0123456789abcdef", device->serial));
assert(!strcmp("device", device->state));
assert(!strcmp("MyModel", device->model));
device = &vec.data[1];
assert(!strcmp("192.168.1.1:5555", device->serial));
assert(!strcmp("device", device->state));
assert(!strcmp("MyWifiModel", device->model));
sc_adb_devices_destroy(&vec);
}
static void test_adb_devices_cr(void) {
char output[] =
"List of devices attached\r\n"
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
"device:MyDevice transport_id:1\r\n"
"192.168.1.1:5555 device product:MyWifiProduct model:MyWifiModel "
"device:MyWifiDevice trandport_id:2\r\n";
struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER;
bool ok = sc_adb_parse_devices(output, &vec);
assert(ok);
assert(vec.size == 2);
struct sc_adb_device *device = &vec.data[0];
assert(!strcmp("0123456789abcdef", device->serial));
assert(!strcmp("device", device->state));
assert(!strcmp("MyModel", device->model));
device = &vec.data[1];
assert(!strcmp("192.168.1.1:5555", device->serial));
assert(!strcmp("device", device->state));
assert(!strcmp("MyWifiModel", device->model));
sc_adb_devices_destroy(&vec);
}
static void test_adb_devices_daemon_start(void) {
char output[] =
"* daemon not running; starting now at tcp:5037\n"
"* daemon started successfully\n"
"List of devices attached\n"
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
"device:MyDevice transport_id:1\n";
struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER;
bool ok = sc_adb_parse_devices(output, &vec);
assert(ok);
assert(vec.size == 1);
struct sc_adb_device *device = &vec.data[0];
assert(!strcmp("0123456789abcdef", device->serial));
assert(!strcmp("device", device->state));
assert(!strcmp("MyModel", device->model));
sc_adb_devices_destroy(&vec);
}
static void test_adb_devices_daemon_start_mixed(void) {
char output[] =
"List of devices attached\n"
"adb server version (41) doesn't match this client (39); killing...\n"
"* daemon started successfully *\n"
"0123456789abcdef unauthorized usb:1-1\n"
"87654321 device usb:2-1 product:MyProduct model:MyModel "
"device:MyDevice\n";
struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER;
bool ok = sc_adb_parse_devices(output, &vec);
assert(ok);
assert(vec.size == 2);
struct sc_adb_device *device = &vec.data[0];
assert(!strcmp("0123456789abcdef", device->serial));
assert(!strcmp("unauthorized", device->state));
assert(!device->model);
device = &vec.data[1];
assert(!strcmp("87654321", device->serial));
assert(!strcmp("device", device->state));
assert(!strcmp("MyModel", device->model));
sc_adb_devices_destroy(&vec);
}
static void test_adb_devices_without_eol(void) {
char output[] =
"List of devices attached\n"
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
"device:MyDevice transport_id:1";
struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER;
bool ok = sc_adb_parse_devices(output, &vec);
assert(ok);
assert(vec.size == 1);
struct sc_adb_device *device = &vec.data[0];
assert(!strcmp("0123456789abcdef", device->serial));
assert(!strcmp("device", device->state));
assert(!strcmp("MyModel", device->model));
sc_adb_devices_destroy(&vec);
}
static void test_adb_devices_without_header(void) {
char output[] =
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
"device:MyDevice transport_id:1\n";
struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER;
bool ok = sc_adb_parse_devices(output, &vec);
assert(!ok);
}
static void test_adb_devices_corrupted(void) {
char output[] =
"List of devices attached\n"
"corrupted_garbage\n";
struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER;
bool ok = sc_adb_parse_devices(output, &vec);
assert(ok);
assert(vec.size == 0);
}
static void test_adb_devices_spaces(void) {
char output[] =
"List of devices attached\n"
"0123456789abcdef unauthorized usb:1-4 transport_id:3\n";
struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER;
bool ok = sc_adb_parse_devices(output, &vec);
assert(ok);
assert(vec.size == 1);
struct sc_adb_device *device = &vec.data[0];
assert(!strcmp("0123456789abcdef", device->serial));
assert(!strcmp("unauthorized", device->state));
assert(!device->model);
sc_adb_devices_destroy(&vec);
}
static void test_get_ip_single_line(void) {
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);
char *ip = sc_adb_parse_device_ip(ip_route);
assert(ip);
assert(!strcmp(ip, "192.168.12.34"));
free(ip);
}
static void test_get_ip_single_line_without_eol() {
static void test_get_ip_single_line_without_eol(void) {
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);
char *ip = sc_adb_parse_device_ip(ip_route);
assert(ip);
assert(!strcmp(ip, "192.168.12.34"));
free(ip);
}
static void test_get_ip_single_line_with_trailing_space() {
static void test_get_ip_single_line_with_trailing_space(void) {
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);
char *ip = sc_adb_parse_device_ip(ip_route);
assert(ip);
assert(!strcmp(ip, "192.168.12.34"));
free(ip);
}
static void test_get_ip_multiline_first_ok() {
static void test_get_ip_multiline_first_ok(void) {
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);
char *ip = sc_adb_parse_device_ip(ip_route);
assert(ip);
assert(!strcmp(ip, "192.168.1.2"));
free(ip);
}
static void test_get_ip_multiline_second_ok() {
static void test_get_ip_multiline_second_ok(void) {
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);
char *ip = sc_adb_parse_device_ip(ip_route);
assert(ip);
assert(!strcmp(ip, "192.168.1.3"));
free(ip);
}
static void test_get_ip_no_wlan() {
static void test_get_ip_no_wlan(void) {
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);
char *ip = sc_adb_parse_device_ip(ip_route);
assert(!ip);
}
static void test_get_ip_no_wlan_without_eol() {
static void test_get_ip_no_wlan_without_eol(void) {
char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src "
"192.168.12.34";
char *ip = sc_adb_parse_device_ip_from_output(ip_route);
char *ip = sc_adb_parse_device_ip(ip_route);
assert(!ip);
}
static void test_get_ip_truncated() {
static void test_get_ip_truncated(void) {
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);
char *ip = sc_adb_parse_device_ip(ip_route);
assert(!ip);
}
@ -86,6 +245,15 @@ int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_adb_devices();
test_adb_devices_cr();
test_adb_devices_daemon_start();
test_adb_devices_daemon_start_mixed();
test_adb_devices_without_eol();
test_adb_devices_without_header();
test_adb_devices_corrupted();
test_adb_devices_spaces();
test_get_ip_single_line();
test_get_ip_single_line_without_eol();
test_get_ip_single_line_with_trailing_space();
@ -94,4 +262,6 @@ int main(int argc, char *argv[]) {
test_get_ip_no_wlan();
test_get_ip_no_wlan_without_eol();
test_get_ip_truncated();
return 0;
}

View File

@ -8,7 +8,7 @@ static void test_buffer_write16be(void) {
uint16_t val = 0xABCD;
uint8_t buf[2];
buffer_write16be(buf, val);
sc_write16be(buf, val);
assert(buf[0] == 0xAB);
assert(buf[1] == 0xCD);
@ -18,7 +18,7 @@ static void test_buffer_write32be(void) {
uint32_t val = 0xABCD1234;
uint8_t buf[4];
buffer_write32be(buf, val);
sc_write32be(buf, val);
assert(buf[0] == 0xAB);
assert(buf[1] == 0xCD);
@ -30,7 +30,7 @@ static void test_buffer_write64be(void) {
uint64_t val = 0xABCD1234567890EF;
uint8_t buf[8];
buffer_write64be(buf, val);
sc_write64be(buf, val);
assert(buf[0] == 0xAB);
assert(buf[1] == 0xCD);
@ -45,7 +45,7 @@ static void test_buffer_write64be(void) {
static void test_buffer_read16be(void) {
uint8_t buf[2] = {0xAB, 0xCD};
uint16_t val = buffer_read16be(buf);
uint16_t val = sc_read16be(buf);
assert(val == 0xABCD);
}
@ -53,7 +53,7 @@ static void test_buffer_read16be(void) {
static void test_buffer_read32be(void) {
uint8_t buf[4] = {0xAB, 0xCD, 0x12, 0x34};
uint32_t val = buffer_read32be(buf);
uint32_t val = sc_read32be(buf);
assert(val == 0xABCD1234);
}
@ -62,7 +62,7 @@ static void test_buffer_read64be(void) {
uint8_t buf[8] = {0xAB, 0xCD, 0x12, 0x34,
0x56, 0x78, 0x90, 0xEF};
uint64_t val = buffer_read64be(buf);
uint64_t val = sc_read64be(buf);
assert(val == 0xABCD1234567890EF);
}

421
app/tests/test_vector.c Normal file
View File

@ -0,0 +1,421 @@
#include "common.h"
#include <assert.h>
#include "util/vector.h"
static void test_vector_insert_remove(void) {
struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER;
bool ok;
ok = sc_vector_push(&vec, 42);
assert(ok);
assert(vec.data[0] == 42);
assert(vec.size == 1);
ok = sc_vector_push(&vec, 37);
assert(ok);
assert(vec.size == 2);
assert(vec.data[0] == 42);
assert(vec.data[1] == 37);
ok = sc_vector_insert(&vec, 1, 100);
assert(ok);
assert(vec.size == 3);
assert(vec.data[0] == 42);
assert(vec.data[1] == 100);
assert(vec.data[2] == 37);
ok = sc_vector_push(&vec, 77);
assert(ok);
assert(vec.size == 4);
assert(vec.data[0] == 42);
assert(vec.data[1] == 100);
assert(vec.data[2] == 37);
assert(vec.data[3] == 77);
sc_vector_remove(&vec, 1);
assert(vec.size == 3);
assert(vec.data[0] == 42);
assert(vec.data[1] == 37);
assert(vec.data[2] == 77);
sc_vector_clear(&vec);
assert(vec.size == 0);
sc_vector_destroy(&vec);
}
static void test_vector_push_array(void) {
struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER;
bool ok;
ok = sc_vector_push(&vec, 3); assert(ok);
ok = sc_vector_push(&vec, 14); assert(ok);
ok = sc_vector_push(&vec, 15); assert(ok);
ok = sc_vector_push(&vec, 92); assert(ok);
ok = sc_vector_push(&vec, 65); assert(ok);
assert(vec.size == 5);
int items[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
ok = sc_vector_push_all(&vec, items, 8);
assert(ok);
assert(vec.size == 13);
assert(vec.data[0] == 3);
assert(vec.data[1] == 14);
assert(vec.data[2] == 15);
assert(vec.data[3] == 92);
assert(vec.data[4] == 65);
assert(vec.data[5] == 1);
assert(vec.data[6] == 2);
assert(vec.data[7] == 3);
assert(vec.data[8] == 4);
assert(vec.data[9] == 5);
assert(vec.data[10] == 6);
assert(vec.data[11] == 7);
assert(vec.data[12] == 8);
sc_vector_destroy(&vec);
}
static void test_vector_insert_array(void) {
struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER;
bool ok;
ok = sc_vector_push(&vec, 3); assert(ok);
ok = sc_vector_push(&vec, 14); assert(ok);
ok = sc_vector_push(&vec, 15); assert(ok);
ok = sc_vector_push(&vec, 92); assert(ok);
ok = sc_vector_push(&vec, 65); assert(ok);
assert(vec.size == 5);
int items[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
ok = sc_vector_insert_all(&vec, 3, items, 8);
assert(ok);
assert(vec.size == 13);
assert(vec.data[0] == 3);
assert(vec.data[1] == 14);
assert(vec.data[2] == 15);
assert(vec.data[3] == 1);
assert(vec.data[4] == 2);
assert(vec.data[5] == 3);
assert(vec.data[6] == 4);
assert(vec.data[7] == 5);
assert(vec.data[8] == 6);
assert(vec.data[9] == 7);
assert(vec.data[10] == 8);
assert(vec.data[11] == 92);
assert(vec.data[12] == 65);
sc_vector_destroy(&vec);
}
static void test_vector_remove_slice(void) {
struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER;
bool ok;
for (int i = 0; i < 100; ++i)
{
ok = sc_vector_push(&vec, i);
assert(ok);
}
assert(vec.size == 100);
sc_vector_remove_slice(&vec, 32, 60);
assert(vec.size == 40);
assert(vec.data[31] == 31);
assert(vec.data[32] == 92);
sc_vector_destroy(&vec);
}
static void test_vector_swap_remove(void) {
struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER;
bool ok;
ok = sc_vector_push(&vec, 3); assert(ok);
ok = sc_vector_push(&vec, 14); assert(ok);
ok = sc_vector_push(&vec, 15); assert(ok);
ok = sc_vector_push(&vec, 92); assert(ok);
ok = sc_vector_push(&vec, 65); assert(ok);
assert(vec.size == 5);
sc_vector_swap_remove(&vec, 1);
assert(vec.size == 4);
assert(vec.data[0] == 3);
assert(vec.data[1] == 65);
assert(vec.data[2] == 15);
assert(vec.data[3] == 92);
sc_vector_destroy(&vec);
}
static void test_vector_index_of(void) {
struct SC_VECTOR(int) vec;
sc_vector_init(&vec);
bool ok;
for (int i = 0; i < 10; ++i)
{
ok = sc_vector_push(&vec, i);
assert(ok);
}
ssize_t idx;
idx = sc_vector_index_of(&vec, 0);
assert(idx == 0);
idx = sc_vector_index_of(&vec, 1);
assert(idx == 1);
idx = sc_vector_index_of(&vec, 4);
assert(idx == 4);
idx = sc_vector_index_of(&vec, 9);
assert(idx == 9);
idx = sc_vector_index_of(&vec, 12);
assert(idx == -1);
sc_vector_destroy(&vec);
}
static void test_vector_grow() {
struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER;
bool ok;
for (int i = 0; i < 50; ++i)
{
ok = sc_vector_push(&vec, i); /* append */
assert(ok);
}
assert(vec.cap >= 50);
assert(vec.size == 50);
for (int i = 0; i < 25; ++i)
{
ok = sc_vector_insert(&vec, 20, i); /* insert in the middle */
assert(ok);
}
assert(vec.cap >= 75);
assert(vec.size == 75);
for (int i = 0; i < 25; ++i)
{
ok = sc_vector_insert(&vec, 0, i); /* prepend */
assert(ok);
}
assert(vec.cap >= 100);
assert(vec.size == 100);
for (int i = 0; i < 50; ++i)
sc_vector_remove(&vec, 20); /* remove from the middle */
assert(vec.cap >= 50);
assert(vec.size == 50);
for (int i = 0; i < 25; ++i)
sc_vector_remove(&vec, 0); /* remove from the head */
assert(vec.cap >= 25);
assert(vec.size == 25);
for (int i = 24; i >=0; --i)
sc_vector_remove(&vec, i); /* remove from the tail */
assert(vec.size == 0);
sc_vector_destroy(&vec);
}
static void test_vector_exp_growth(void) {
struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER;
size_t oldcap = vec.cap;
int realloc_count = 0;
bool ok;
for (int i = 0; i < 10000; ++i)
{
ok = sc_vector_push(&vec, i);
assert(ok);
if (vec.cap != oldcap)
{
realloc_count++;
oldcap = vec.cap;
}
}
/* Test speciically for an expected growth factor of 1.5. In practice, the
* result is even lower (19) due to the first alloc of size 10 */
assert(realloc_count <= 23); /* ln(10000) / ln(1.5) ~= 23 */
realloc_count = 0;
for (int i = 9999; i >= 0; --i)
{
sc_vector_remove(&vec, i);
if (vec.cap != oldcap)
{
realloc_count++;
oldcap = vec.cap;
}
}
assert(realloc_count <= 23); /* same expectations for removals */
assert(realloc_count > 0); /* sc_vector_remove() must autoshrink */
sc_vector_destroy(&vec);
}
static void test_vector_reserve(void) {
struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER;
bool ok;
ok = sc_vector_reserve(&vec, 800);
assert(ok);
assert(vec.cap >= 800);
assert(vec.size == 0);
size_t initial_cap = vec.cap;
for (int i = 0; i < 800; ++i)
{
ok = sc_vector_push(&vec, i);
assert(ok);
assert(vec.cap == initial_cap); /* no realloc */
}
sc_vector_destroy(&vec);
}
static void test_vector_shrink_to_fit(void) {
struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER;
bool ok;
ok = sc_vector_reserve(&vec, 800);
assert(ok);
for (int i = 0; i < 250; ++i)
{
ok = sc_vector_push(&vec, i);
assert(ok);
}
assert(vec.cap >= 800);
assert(vec.size == 250);
sc_vector_shrink_to_fit(&vec);
assert(vec.cap == 250);
assert(vec.size == 250);
sc_vector_destroy(&vec);
}
static void test_vector_move(void) {
struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER;
for (int i = 0; i < 7; ++i)
{
bool ok = sc_vector_push(&vec, i);
assert(ok);
}
/* move item at 1 so that its new position is 4 */
sc_vector_move(&vec, 1, 4);
assert(vec.size == 7);
assert(vec.data[0] == 0);
assert(vec.data[1] == 2);
assert(vec.data[2] == 3);
assert(vec.data[3] == 4);
assert(vec.data[4] == 1);
assert(vec.data[5] == 5);
assert(vec.data[6] == 6);
sc_vector_destroy(&vec);
}
static void test_vector_move_slice_forward(void) {
struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER;
for (int i = 0; i < 10; ++i)
{
bool ok = sc_vector_push(&vec, i);
assert(ok);
}
/* move slice {2, 3, 4, 5} so that its new position is 5 */
sc_vector_move_slice(&vec, 2, 4, 5);
assert(vec.size == 10);
assert(vec.data[0] == 0);
assert(vec.data[1] == 1);
assert(vec.data[2] == 6);
assert(vec.data[3] == 7);
assert(vec.data[4] == 8);
assert(vec.data[5] == 2);
assert(vec.data[6] == 3);
assert(vec.data[7] == 4);
assert(vec.data[8] == 5);
assert(vec.data[9] == 9);
sc_vector_destroy(&vec);
}
static void test_vector_move_slice_backward(void) {
struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER;
for (int i = 0; i < 10; ++i)
{
bool ok = sc_vector_push(&vec, i);
assert(ok);
}
/* move slice {5, 6, 7} so that its new position is 2 */
sc_vector_move_slice(&vec, 5, 3, 2);
assert(vec.size == 10);
assert(vec.data[0] == 0);
assert(vec.data[1] == 1);
assert(vec.data[2] == 5);
assert(vec.data[3] == 6);
assert(vec.data[4] == 7);
assert(vec.data[5] == 2);
assert(vec.data[6] == 3);
assert(vec.data[7] == 4);
assert(vec.data[8] == 8);
assert(vec.data[9] == 9);
sc_vector_destroy(&vec);
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_vector_insert_remove();
test_vector_push_array();
test_vector_insert_array();
test_vector_remove_slice();
test_vector_swap_remove();
test_vector_move();
test_vector_move_slice_forward();
test_vector_move_slice_backward();
test_vector_index_of();
test_vector_grow();
test_vector_exp_growth();
test_vector_reserve();
test_vector_shrink_to_fit();
return 0;
}

View File

@ -20,4 +20,6 @@ ffmpeg_avcodec = 'avcodec-58'
ffmpeg_avformat = 'avformat-58'
ffmpeg_avutil = 'avutil-56'
prebuilt_ffmpeg = 'ffmpeg-win32-4.3.1'
prebuilt_sdl2 = 'SDL2-2.0.20/i686-w64-mingw32'
prebuilt_sdl2 = 'SDL2-2.0.22/i686-w64-mingw32'
prebuilt_libusb_root = 'libusb-1.0.26'
prebuilt_libusb = prebuilt_libusb_root + '/MinGW-Win32'

View File

@ -19,5 +19,7 @@ endian = 'little'
ffmpeg_avcodec = 'avcodec-59'
ffmpeg_avformat = 'avformat-59'
ffmpeg_avutil = 'avutil-57'
prebuilt_ffmpeg = 'ffmpeg-win64-5.0'
prebuilt_sdl2 = 'SDL2-2.0.20/x86_64-w64-mingw32'
prebuilt_ffmpeg = 'ffmpeg-win64-5.0.1'
prebuilt_sdl2 = 'SDL2-2.0.22/x86_64-w64-mingw32'
prebuilt_libusb_root = 'libusb-1.0.26'
prebuilt_libusb = prebuilt_libusb_root + '/MinGW-x64'

View File

@ -2,8 +2,8 @@
set -e
BUILDDIR=build-auto
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.22/scrcpy-server-v1.22
PREBUILT_SERVER_SHA256=c05d273eec7533c0e106282e0254cf04e7f5e8f0c2920ca39448865fab2a419b
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.24/scrcpy-server-v1.24
PREBUILT_SERVER_SHA256=ae74a81ea79c0dc7250e586627c278c0a9a8c5de46c9fb5c38c167fb1a36f056
echo "[scrcpy] Downloading prebuilt server..."
wget "$PREBUILT_SERVER_URL" -O scrcpy-server
@ -12,7 +12,7 @@ echo "$PREBUILT_SERVER_SHA256 scrcpy-server" | sha256sum --check
echo "[scrcpy] Building client..."
rm -rf "$BUILDDIR"
meson "$BUILDDIR" --buildtype release --strip -Db_lto=true \
meson "$BUILDDIR" --buildtype=release --strip -Db_lto=true \
-Dprebuilt_server=scrcpy-server
cd "$BUILDDIR"
ninja

View File

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

View File

@ -4,3 +4,5 @@ option('prebuilt_server', type: 'string', description: 'Path of the prebuilt ser
option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable')
option('server_debugger', type: 'boolean', value: false, description: 'Run a server debugger and wait for a client to be attached')
option('server_debugger_method', type: 'combo', choices: ['old', 'new'], value: 'new', description: 'Select the debugger method (Android < 9: "old", Android >= 9: "new")')
option('v4l2', type: 'boolean', value: true, description: 'Enable V4L2 feature when supported')
option('usb', type: 'boolean', value: true, description: 'Enable HID/OTG features when supported')

View File

@ -63,9 +63,16 @@ build-server:
ninja -C "$(SERVER_BUILD_DIR)"
prepare-deps-win32:
@prebuilt-deps/prepare-adb.sh
@prebuilt-deps/prepare-sdl.sh
@prebuilt-deps/prepare-ffmpeg-win32.sh
@app/prebuilt-deps/prepare-adb.sh
@app/prebuilt-deps/prepare-sdl.sh
@app/prebuilt-deps/prepare-ffmpeg-win32.sh
@app/prebuilt-deps/prepare-libusb.sh
prepare-deps-win64:
@app/prebuilt-deps/prepare-adb.sh
@app/prebuilt-deps/prepare-sdl.sh
@app/prebuilt-deps/prepare-ffmpeg-win64.sh
@app/prebuilt-deps/prepare-libusb.sh
build-win32: prepare-deps-win32
[ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \
@ -76,11 +83,6 @@ build-win32: prepare-deps-win32
-Dportable=true )
ninja -C "$(WIN32_BUILD_DIR)"
prepare-deps-win64:
@prebuilt-deps/prepare-adb.sh
@prebuilt-deps/prepare-sdl.sh
@prebuilt-deps/prepare-ffmpeg-win64.sh
build-win64: prepare-deps-win64
[ -d "$(WIN64_BUILD_DIR)" ] || ( mkdir "$(WIN64_BUILD_DIR)" && \
meson "$(WIN64_BUILD_DIR)" \
@ -94,37 +96,39 @@ dist-win32: build-server build-win32
mkdir -p "$(DIST)/$(WIN32_TARGET_DIR)"
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN32_TARGET_DIR)/"
cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp data/scrcpy-console.bat "$(DIST)/$(WIN32_TARGET_DIR)"
cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)"
cp data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)"
cp data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)"
cp prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/swscale-5.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/data/platform-tools-31.0.3/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/data/SDL2-2.0.20/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/data/scrcpy-console.bat "$(DIST)/$(WIN32_TARGET_DIR)"
cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)"
cp app/data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)"
cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)"
cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/swscale-5.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-33.0.1/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-33.0.1/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-33.0.1/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/SDL2-2.0.22/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/libusb-1.0.26/MinGW-Win32/msys-usb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
dist-win64: build-server build-win64
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN64_TARGET_DIR)/"
cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)"
cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)"
cp data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)"
cp data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)"
cp prebuilt-deps/data/ffmpeg-win64-5.0/bin/avutil-57.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/data/ffmpeg-win64-5.0/bin/avcodec-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/data/ffmpeg-win64-5.0/bin/avformat-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/data/ffmpeg-win64-5.0/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/data/ffmpeg-win64-5.0/bin/swscale-6.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/data/platform-tools-31.0.3/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/data/SDL2-2.0.20/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)"
cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)"
cp app/data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)"
cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)"
cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/avutil-57.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/avcodec-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/avformat-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/swscale-6.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-33.0.1/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-33.0.1/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-33.0.1/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/SDL2-2.0.22/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/libusb-1.0.26/MinGW-x64/msys-usb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
zip-win32: dist-win32
cd "$(DIST)/$(WIN32_TARGET_DIR)"; \

2
run
View File

@ -20,6 +20,6 @@ then
exit 1
fi
SCRCPY_ICON_PATH="data/icon.png" \
SCRCPY_ICON_PATH="app/data/icon.png" \
SCRCPY_SERVER_PATH="$BUILDDIR/server/scrcpy-server" \
"$BUILDDIR/app/scrcpy" "$@"

View File

@ -6,8 +6,8 @@ android {
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
targetSdkVersion 31
versionCode 12200
versionName "1.22"
versionCode 12400
versionName "1.24"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
@ -19,7 +19,6 @@ android {
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation 'junit:junit:4.13.1'
}

View File

@ -12,7 +12,7 @@
set -e
SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=1.22
SCRCPY_VERSION_NAME=1.24
PLATFORM=${ANDROID_PLATFORM:-31}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-31.0.0}

View File

@ -22,6 +22,7 @@ public class Controller {
private final DesktopConnection connection;
private final DeviceMessageSender sender;
private final boolean clipboardAutosync;
private final boolean powerOn;
private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
@ -32,10 +33,11 @@ public class Controller {
private boolean keepPowerModeOff;
public Controller(Device device, DesktopConnection connection, boolean clipboardAutosync) {
public Controller(Device device, DesktopConnection connection, boolean clipboardAutosync, boolean powerOn) {
this.device = device;
this.connection = connection;
this.clipboardAutosync = clipboardAutosync;
this.powerOn = powerOn;
initPointers();
sender = new DeviceMessageSender(connection);
}
@ -56,7 +58,7 @@ public class Controller {
public void control() throws IOException {
// on start, power on the device
if (!Device.isScreenOn()) {
if (powerOn && !Device.isScreenOn()) {
device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC);
// dirty hack

View File

@ -21,6 +21,8 @@ public class Options {
private boolean powerOffScreenOnClose;
private boolean clipboardAutosync = true;
private boolean downsizeOnError = true;
private boolean cleanup = true;
private boolean powerOn = true;
// Options not used by the scrcpy client, but useful to use scrcpy-server directly
private boolean sendDeviceMeta = true; // send device name and size
@ -155,6 +157,22 @@ public class Options {
this.downsizeOnError = downsizeOnError;
}
public boolean getCleanup() {
return cleanup;
}
public void setCleanup(boolean cleanup) {
this.cleanup = cleanup;
}
public boolean getPowerOn() {
return powerOn;
}
public void setPowerOn(boolean powerOn) {
this.powerOn = powerOn;
}
public boolean getSendDeviceMeta() {
return sendDeviceMeta;
}

View File

@ -28,7 +28,8 @@ public class ScreenEncoder implements Device.RotationListener {
// Keep the values in descending order
private static final int[] MAX_SIZE_FALLBACK = {2560, 1920, 1600, 1280, 1024, 800};
private static final int NO_PTS = -1;
private static final long PACKET_FLAG_CONFIG = 1L << 63;
private static final long PACKET_FLAG_KEY_FRAME = 1L << 62;
private final AtomicBoolean rotationChanged = new AtomicBoolean();
private final ByteBuffer headerBuffer = ByteBuffer.allocate(12);
@ -101,7 +102,7 @@ public class ScreenEncoder implements Device.RotationListener {
alive = encode(codec, fd);
// do not call stop() on exception, it would trigger an IllegalStateException
codec.stop();
} catch (IllegalStateException e) {
} catch (IllegalStateException | IllegalArgumentException e) {
Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage());
if (!downsizeOnError || firstFrameSent) {
// Fail immediately
@ -183,12 +184,15 @@ public class ScreenEncoder implements Device.RotationListener {
long pts;
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
pts = NO_PTS; // non-media data packet
pts = PACKET_FLAG_CONFIG; // non-media data packet
} else {
if (ptsOrigin == 0) {
ptsOrigin = bufferInfo.presentationTimeUs;
}
pts = bufferInfo.presentationTimeUs - ptsOrigin;
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) {
pts |= PACKET_FLAG_KEY_FRAME;
}
}
headerBuffer.putLong(pts);

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