Compare commits

...

80 Commits

Author SHA1 Message Date
bd1deffa70 Use current adb port (if any) for --tcpip
If the current adb port is not 5555 (typically 0 because it is not in
TCP/IP mode), --tcpip automatically executes (among other commands):

    adb tcpip 5555

In case adb was already listening on another port, this command forced
to listen on 5555, and the connection should still succeed.

But this reconfiguration might be inconvenient for the user. If adb is
already in TCP/IP mode, use the current enabled port without
reconfiguration.

Fixes #3591 <https://github.com/Genymobile/scrcpy/issues/3591>
2022-12-02 19:09:53 +01:00
6469b55861 Fix CommandParserTest code style
Make checkstyle happy.
2022-11-24 09:27:10 +01:00
597703b62e Fix DisplayInfo parsing for Android Q
The DisplayInfo dump format has slightly changed in AOSP:
<1039ea50f3>

PR #3573 <https://github.com/Genymobile/scrcpy/pull/3573>
Ref #3416 <https://github.com/Genymobile/scrcpy/pull/3416>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-11-12 18:15:45 +01:00
48bb6f2ea8 Support wchar_t in argv for Windows
PR #3547 <https://github.com/Genymobile/scrcpy/pull/3547>
Fixes #2932 <https://github.com/Genymobile/scrcpy/issues/2932>

Signed-off-by: Yu-Chen Lin <npes87184@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-10-23 23:45:00 +02:00
d71587e39b Avoid string concatenation in crossfiles
This feature is not supported on older meson versions:

    ERROR: Malformed value in cross file variable prebuilt_libusb.

Refs <https://github.com/mesonbuild/meson/issues/3878>
PR #3546 <https://github.com/Genymobile/scrcpy/pull/3546>

Signed-off-by: Yu-Chen Lin <npes87184@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-10-23 12:31:44 +02:00
b62424a98a Build log.c for test_cli
On Windows, sc_log_windows_error() is called from net.c, so log.c must
also be compiled.

Fixes #3542 <https://github.com/Genymobile/scrcpy/issues/3542>
2022-10-19 15:17:43 +02:00
ffc7b91693 Add missing include <string.h> for strlen() 2022-10-19 15:14:56 +02:00
cb46e4a64a Add missing include <string.h> for memmove() 2022-10-19 15:13:55 +02:00
16e2c1ce26 Add -s auto-completion for zsh
Fixes #3522 <https://github.com/Genymobile/scrcpy/pull/3522>
PR #3523 <https://github.com/Genymobile/scrcpy/pull/3523>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-10-12 13:24:43 +02:00
1bfbadef96 Add -s auto-completion for bash
Fixes #3522 <https://github.com/Genymobile/scrcpy/pull/3522>
PR #3523 <https://github.com/Genymobile/scrcpy/pull/3523>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-10-12 13:24:43 +02:00
40644994e8 Make ServiceManager and Settings methods static
There were exactly one instance of ServiceManager and Settings, stored
in Device.

Since a Device instance is not created by the CleanUp executable, it was
not straightforward to call wrapper methods on cleanup.

Remove this artificial restriction and expose them publicly via static
methods (this is equivalent to expose a singleton, but less verbose).
2022-10-02 17:57:35 +02:00
7505f7117e Fix typo in logs 2022-09-27 14:12:37 +02:00
949b64dff2 Add fallback to get DisplayInfo
PR #3416 <https://github.com/Genymobile/scrcpy/pull/3416>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-09-25 16:22:51 +02:00
00e9e69c2a Use /dev/null instead of closing fds
Some adb commands do not like when stdin, stdout or stderr are closed
(they hang forever). Open /dev/null for each.
2022-09-25 15:42:33 +02:00
4a5cdcd390 Extract $BUILD_TOOLS_DIR
In the script to build without gradle, the build-tools full path is used
at several places. Use a separate variable for readability.
2022-09-25 14:26:07 +02:00
e5e210506f Add scrcpy-console.desktop
Add a launcher which opens a terminal, and keep it open in case of
errors (so that the user has time to read error messages).

The behavior is the same as scrcpy-console.bat on Windows.

PR #3351 <https://github.com/Genymobile/scrcpy/pull/3351>
2022-09-09 19:06:29 +02:00
a2a22f497f Use shell environment to execute launcher
Make Exec= compatible with $PATH configured in .bashrc or .zshrc…

PR #3351 <https://github.com/Genymobile/scrcpy/pull/3351>
Refs #296 <https://github.com/Genymobile/scrcpy/pull/296#discussion_r224987002>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-09-09 19:06:29 +02:00
51a1762cbd Add desktop entry file for Linux app launchers
Refs <https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html>

PR #3351 <https://github.com/Genymobile/scrcpy/pull/3351>
Replaces PR #296 <https://github.com/Genymobile/scrcpy/pull/296>
Fixes #295 <https://github.com/Genymobile/scrcpy/issues/295>
Fixes #748 <https://github.com/Genymobile/scrcpy/issues/748>
Fixes #1636 <https://github.com/Genymobile/scrcpy/issues/1636>

Co-authored-by: Chih-Hsuan Yen <yan12125@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-09-09 19:06:29 +02:00
c1ec1d1023 Replace hardcoded 'share/' by datadir variable
Meson defines a variable for the data directory.

PR #3351 <https://github.com/Genymobile/scrcpy/pull/3351>
2022-09-09 19:06:29 +02:00
0a0a446ea6 Upgrade Android SDK to 33 2022-09-02 14:42:37 +02:00
fccfc43b9e Upgrade gradle build tools to 7.2.2
Plugin version 7.2.2.
Gradle version 7.3.3.

Refs: <https://developer.android.com/studio/releases/gradle-plugin#updating-gradle>
2022-09-02 14:40:16 +02:00
121bb71dfe Move from jcenter() to mavenCentral()
Refs <https://developer.android.com/studio/build/jcenter-migration>
Refs <https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/>
2022-09-02 14:31:15 +02:00
57056d078d Use precise scrolling values
Since SDL 2.0.18, the amount scrolled horizontally or vertically is
exposed as a float (between 0 and 1). Forward a precise value to the
Android device when possible.

Refs <https://wiki.libsdl.org/SDL_MouseWheelEvent>
Fixes #3363 <https://github.com/Genymobile/scrcpy/issues/3363>
PR #3369 <https://github.com/Genymobile/scrcpy/pull/3369>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-08-28 15:23:08 +02:00
1f138aef41 Add conversion from float to fixed-point i16
To encode float values between -1 and 1.

PR #3369 <https://github.com/Genymobile/scrcpy/pull/3369>
2022-08-28 15:23:08 +02:00
1ab6c19486 Add unit test for float encoding
PR #3369 <https://github.com/Genymobile/scrcpy/pull/3369>
2022-08-28 15:23:08 +02:00
fd3483c837 Extract conversion from float to u16 fixed-point
PR #3369 <https://github.com/Genymobile/scrcpy/pull/3369>
2022-08-28 15:23:08 +02:00
041cdf6cf5 Rename buffer_util.h to binary.h
It will allow to expose more binary util functions not related to
buffers.

PR #3369 <https://github.com/Genymobile/scrcpy/pull/3369>
2022-08-28 15:23:08 +02:00
136ab8c199 Add unit test for float decoding
PR #3369 <https://github.com/Genymobile/scrcpy/pull/3369>
2022-08-28 15:23:08 +02:00
3848ce86f1 Extract conversion from u16 fixed-point to float
PR #3369 <https://github.com/Genymobile/scrcpy/pull/3369>
2022-08-28 15:23:08 +02:00
5b8e9aa0e9 Move toUnsigned() to a Binary util class
PR #3369 <https://github.com/Genymobile/scrcpy/pull/3369>
2022-08-28 15:23:08 +02:00
3a66b5fd01 Remove deprecated meson.source_root()
This method is deprecated since Meson 0.56.0:
<https://mesonbuild.com/Release-notes-for-0-56-0.html#mesonbuild_root-and-mesonsource_root-are-deprecated>

We could replace it with meson.project_source_root(), but this would
make Meson 0.56 or above mandatory. Since the path in always computed
from the server/ directory, just add '..' to reference the root project
directory.

Refs c456e38264
2022-08-28 15:16:31 +02:00
9c1722f428 Use DisplayManagerGlobal instance
Use the client instance to communicate with the DisplayManager server.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-08-19 18:03:38 +02:00
d19606eb0c Rename net_listen() parameter
For consistency with net_accept(), which necessarily uses a server
socket, name the net_listen() parameter "server_socket".
2022-08-17 16:40:45 +02:00
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
93 changed files with 1559 additions and 656 deletions

View File

@ -46,7 +46,7 @@ sudo ninja -Cbuild-auto uninstall
### `master` ### `master`
The `master` branch concerns the latest release, and is the home page of the The `master` branch concerns the latest release, and is the home page of the
project on Github. project on GitHub.
### `dev` ### `dev`
@ -272,10 +272,10 @@ install` must be run as root)._
#### Option 2: Use prebuilt server #### Option 2: Use prebuilt server
- [`scrcpy-server-v1.23`][direct-scrcpy-server] - [`scrcpy-server-v1.24`][direct-scrcpy-server]
_(SHA-256: 2a913fd47478c0b306fca507cb0beb625e49a19ff9fc7ab904e36ef5b9fe7e68)_ <sub>SHA-256: `ae74a81ea79c0dc7250e586627c278c0a9a8c5de46c9fb5c38c167fb1a36f056`</sub>
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.23/scrcpy-server-v1.23 [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 Download the prebuilt server somewhere, and specify its path during the Meson
configuration: configuration:
@ -305,13 +305,16 @@ After a successful build, you can install _scrcpy_ on the system:
sudo ninja -Cx install # without sudo on Windows sudo ninja -Cx install # without sudo on Windows
``` ```
This installs three files: This installs several files:
- `/usr/local/bin/scrcpy` - `/usr/local/bin/scrcpy` (main app)
- `/usr/local/share/scrcpy/scrcpy-server` - `/usr/local/share/scrcpy/scrcpy-server` (server to push to the device)
- `/usr/local/share/man/man1/scrcpy.1` - `/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 ### Uninstall

View File

@ -375,7 +375,7 @@ Dies finden automatisch das Gerät und aktiviert den TCP/IP-Modus. Anschließend
Alternativ kann die TCP/IP-Verbindung auch manuell per `adb` aktiviert werden: Alternativ kann die TCP/IP-Verbindung auch manuell per `adb` aktiviert werden:
1. Gerät mit demselben Wifi wie den Computer verbinden. 1. Gerät mit demselben Wi-Fi wie den Computer verbinden.
2. IP-Adresse des Gerätes herausfinden, entweder über Einstellungen → Über das Telefon → Status, oder indem dieser Befehl ausgeführt wird: 2. IP-Adresse des Gerätes herausfinden, entweder über Einstellungen → Über das Telefon → Status, oder indem dieser Befehl ausgeführt wird:
```bash ```bash

View File

@ -218,7 +218,7 @@ variation] does not impact the recorded file.
#### Wireless #### 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. 1. Hubungkan perangkat ke Wi-Fi yang sama dengan komputer Anda.
2. Dapatkan alamat IP perangkat Anda (dalam Pengaturan → Tentang ponsel → Status). 2. Dapatkan alamat IP perangkat Anda (dalam Pengaturan → Tentang ponsel → Status).
@ -281,7 +281,7 @@ Dari terminal lain:
scrcpy 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 ```bash
adb kill-server # matikan server adb lokal di 5037 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>. 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 ```bash
# gunakan RCtrl untuk jalan pintas # 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_. Funziona su _GNU/Linux_, _Windows_ e _macOS_.
![screenshot](assets/screenshot-debian-600.jpg) ![screenshot](assets/screenshot-debian-600.jpg)
Si concentra su: Si concentra su:
- **leggerezza** (nativo, mostra solo lo schermo del dispositivo) - **leggerezza**: nativo, mostra solo lo schermo del dispositivo
- **prestazioni** (30~60fps) - **prestazioni**: 30~120fps, in funzione del dispositivo
- **qualità** (1920×1080 o superiore) - **qualità**: 1920×1080 o superiore
- **bassa latenza** ([35~70ms][lowlatency]) - **bassa latenza**: [35~70ms][lowlatency]
- **tempo di avvio basso** (~ 1secondo per visualizzare la prima immagine) - **tempo di avvio basso**: ~ 1secondo per visualizzare la prima immagine
- **non invadenza** (nulla viene lasciato installato sul dispositivo) - **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 [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 ## Requisiti
@ -49,12 +68,18 @@ Compila dai sorgenti: [BUILD] (in inglese) ([procedimento semplificato][BUILD_si
### Linux ### Linux
Su Debian (_testing_ e _sid_ per ora) e Ubuntu (20.04): Su Debian e Ubuntu:
``` ```
apt install scrcpy apt install scrcpy
``` ```
Su Arch Linux:
```
pacman -S scrcpy
```
È disponibile anche un pacchetto [Snap]: [`scrcpy`][snap-link]. È disponibile anche un pacchetto [Snap]: [`scrcpy`][snap-link].
[snap-link]: https://snapstats.org/snaps/scrcpy [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]: https://fedoraproject.org/wiki/Category:Copr
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ [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]. Per Gentoo, è disponibile una [Ebuild]: [`scrcpy/`][ebuild-link].
@ -142,7 +163,7 @@ Collega un dispositivo Android ed esegui:
scrcpy scrcpy
``` ```
Scrcpy accetta argomenti da riga di comando, essi sono listati con: Scrcpy accetta argomenti da riga di comando, elencati con:
```bash ```bash
scrcpy --help scrcpy --help
@ -186,6 +207,14 @@ scrcpy --max-fps 15
Questo è supportato ufficialmente a partire da Android 10, ma potrebbe funzionare in versioni precedenti. 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 #### Ritaglio
Lo schermo del dispositivo può essere ritagliato per visualizzare solo parte di esso. 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 #### 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: 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: e per il V4L2 sink:
```bash ```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 ### 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. Un'opzione `--tcpip` permette di configurare automaticamente la connessione. Ci sono due varianti.
2. Trova l'indirizzo IP del tuo dispositivo in Impostazioni → Informazioni sul telefono → Stato, oppure eseguendo questo comando:
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 ```bash
adb shell ip route | awk '{print $9}' adb shell ip route | awk '{print $9}'
``` ```
3. Abilita adb via TCP/IP sul tuo dispositivo: `adb tcpip 5555`. 4. Abilita adb via TCP/IP sul tuo dispositivo: `adb tcpip 5555`.
4. Scollega il tuo dispositivo. 5. Scollega il tuo dispositivo.
5. Connetti il tuo dispositivo: `adb connect IP_DISPOSITVO:5555` _(rimpiazza `IP_DISPOSITIVO`)_. 6. Connettiti al tuo dispositivo: `adb connect DEVICE_IP:5555` _(sostituisci `DEVICE_IP`
6. Esegui `scrcpy` come al solito. 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 ```bash
scrcpy --bit-rate 2M --max-size 800 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 [connect]: https://developer.android.com/studio/command-line/adb.html#wireless
#### Multi dispositivo #### 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 ```bash
scrcpy --serial 0123456789abcdef scrcpy --serial 0123456789abcdef
@ -370,6 +429,18 @@ scrcpy --serial 192.168.0.1:5555
scrcpy -s 192.168.0.1:5555 # versione breve 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. Puoi avviare più istanze di _scrcpy_ per diversi dispositivi.
@ -383,37 +454,77 @@ autoadb scrcpy -s '{}'
[AutoAdb]: https://github.com/rom1v/autoadb [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 ```bash
adb kill-server # termina il server adb locale su 5037 adb kill-server
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer adb -a nodaemon server start
# tieni questo aperto # 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 ```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 scrcpy
``` ```
Per evitare l'abilitazione dell'apertura porte remota potresti invece forzare una "forward connection" (notare il `-L` invece di `-R`) Per evitare l'abilitazione dell'apertura porte remota potresti invece forzare una "forward connection" (notare il `-L` invece di `-R`)
```bash ```bash
adb kill-server # termina il server adb locale su 5037 # local 5038 --> remote 5037
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer # local 27183 --> remote 27183
ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer
# tieni questo aperto # tieni questo aperto
``` ```
Da un altro terminale: Da un altro terminale, esegui scrcpy:
```bash ```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038
scrcpy --force-adb-forward scrcpy --force-adb-forward
``` ```
Come per le connessioni wireless potrebbe essere utile ridurre la qualità: Come per le connessioni wireless potrebbe essere utile ridurre la qualità:
``` ```
@ -551,6 +662,14 @@ scrcpy --turn-screen-off --stay-awake
scrcpy -Sw scrcpy -Sw
``` ```
#### Spegnimento alla chiusura
Per spegnere lo schermo del dispositivo quando si chiude scrcpy:
```bash
scrcpy --power-off-on-close
```
#### Mostrare i tocchi #### 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>x</kbd> taglia
- <kbd>Ctrl</kbd>+<kbd>v</kbd> incolla (dopo la sincronizzazione degli appunti da computer a dispositivo) - <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. 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): 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>c</kbd> invia `COPY`
- <kbd>MOD</kbd>+<kbd>x</kbd> inietta `CUT` - <kbd>MOD</kbd>+<kbd>x</kbd> invia `CUT`
- <kbd>MOD</kbd>+<kbd>v</kbd> inietta `PASTE` (dopo la sincronizzazione degli appunti da computer a dispositivo) - <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. **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) #### 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. 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: Ci sono due tipi di [eventi][textevents] generati quando si scrive testo:
- _eventi di pressione_, segnalano che tasto è stato premuto o rilasciato; - _eventi di pressione_, segnalano che tasto è stato premuto o rilasciato;
- _eventi di testo_, segnalano che del testo è stato inserito. - _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: 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) (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 [textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 [prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
#### Ripetizione di tasti #### 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: 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 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 #### 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 ```bash
scrcpy --forward-all-clicks scrcpy --forward-all-clicks
@ -705,7 +919,7 @@ scrcpy --shortcut-mod=rctrl
scrcpy --shortcut-mod=lctrl+lalt,lsuper 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 [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 --> <!-- 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 `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Click centrale_
| Premi il tasto `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Click destro²_ | Premi il tasto `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Click destro²_
| Premi il tasto `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _4° click³_ | Premi il tasto `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _4° click³_
| Premi il tasto `MENU` (sblocca lo schermo) | <kbd>MOD</kbd>+<kbd>m</kbd> | Premi il tasto `MENU` (sblocca lo schermo) | <kbd>MOD</kbd>+<kbd>m</kbd>
| Premi il tasto `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(su)_ | Premi il tasto `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(su)_
| Premi il tasto `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(giù)_ | Premi il tasto `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(giù)_
| Premi il tasto `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd> | 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 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³_ | 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> | Chiudi pannelli | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| Copia negli appunti | <kbd>MOD</kbd>+<kbd>c</kbd> | Copia negli appunti | <kbd>MOD</kbd>+<kbd>c</kbd>
| Taglia negli appunti | <kbd>MOD</kbd>+<kbd>x</kbd> | Taglia negli appunti | <kbd>MOD</kbd>+<kbd>x</kbd>
| Sincronizza gli appunti e incolla | <kbd>MOD</kbd>+<kbd>v</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> | 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> | Abilita/Disabilita il contatore FPS (su stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| Pizzica per zoomare | <kbd>Ctrl</kbd>+_click e trascina_ | Pizzica per zoomare | <kbd>Ctrl</kbd>+_click e trascina_
| 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._ _¹Doppio click sui bordi neri per rimuoverli._
_²Il tasto destro accende lo schermo se era spento, preme BACK in caso contrario._ _²Il tasto destro accende lo schermo se era spento, preme BACK in caso contrario._
_³4° e 5° pulsante del mouse, se il tuo mouse ne dispone._ _³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": 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-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ [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)

179
README.md
View File

@ -1,4 +1,4 @@
# scrcpy (v1.23) # scrcpy (v1.24)
<img src="app/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" />
@ -7,7 +7,7 @@ _pronounced "**scr**een **c**o**py**"_
[Read in another language](#translations) [Read in another language](#translations)
This application provides display and control of Android devices connected via 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_. It works on _GNU/Linux_, _Windows_ and _macOS_.
![screenshot](assets/screenshot-debian-600.jpg) ![screenshot](assets/screenshot-debian-600.jpg)
@ -19,7 +19,7 @@ It focuses on:
- **quality**: 1920×1080 or above - **quality**: 1920×1080 or above
- **low latency**: [35~70ms][lowlatency] - **low latency**: [35~70ms][lowlatency]
- **low startup time**: ~1 second to display the first image - **low startup time**: ~1 second to display the first image
- **non-intrusiveness**: nothing is left installed on the device - **non-intrusiveness**: nothing is left installed on the Android device
- **user benefits**: no account, no ads, no internet required - **user benefits**: no account, no ads, no internet required
- **freedom**: free and open source software - **freedom**: free and open source software
@ -27,10 +27,10 @@ It focuses on:
Its features include: Its features include:
- [recording](#recording) - [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 - [copy-paste](#copy-paste) in both directions
- [configurable quality](#capture-configuration) - [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) - [physical keyboard simulation (HID)](#physical-keyboard-simulation-hid)
- [physical mouse simulation (HID)](#physical-mouse-simulation-hid) - [physical mouse simulation (HID)](#physical-mouse-simulation-hid)
- [OTG mode](#otg) - [OTG mode](#otg)
@ -40,12 +40,12 @@ Its features include:
The Android device requires at least API 21 (Android 5.0). 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 [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 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 [control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
@ -97,19 +97,19 @@ For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild [Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy [ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
You could also [build the app manually][BUILD] ([simplified You can also [build the app manually][BUILD] ([simplified
process][BUILD_simple]). process][BUILD_simple]).
### Windows ### Windows
For Windows, for simplicity, a prebuilt archive with all the dependencies For Windows, a prebuilt archive with all the dependencies (including `adb`) is
(including `adb`) is available: available:
- [`scrcpy-win64-v1.23.zip`][direct-win64] - [`scrcpy-win64-v1.24.zip`][direct-win64]
_(SHA-256: d2f601b1d0157faf65153d8a093d827fd65aec5d5842d677ac86fb2b5b7704cc)_ <sub>SHA-256: `6ccb64cba0a3e75715e85a188daeb4f306a1985f8ce123eba92ba74fc9b27367`</sub>
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.23/scrcpy-win64-v1.23.zip [direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.24/scrcpy-win64-v1.24.zip
It is also available in [Chocolatey]: It is also available in [Chocolatey]:
@ -148,7 +148,7 @@ You need `adb`, accessible from your `PATH`. If you don't have it yet:
brew install android-platform-tools 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 ```bash
sudo port install scrcpy sudo port install scrcpy
@ -162,7 +162,7 @@ You can also [build the app manually][BUILD].
## Run ## Run
Plug an Android device, and execute: Plug an Android device into your computer, and execute:
```bash ```bash
scrcpy scrcpy
@ -180,7 +180,7 @@ scrcpy --help
#### Reduce size #### 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. increase performance.
To limit both the width and height to some value (e.g. 1024): To limit both the width and height to some value (e.g. 1024):
@ -190,8 +190,8 @@ scrcpy --max-size 1024
scrcpy -m 1024 # short version scrcpy -m 1024 # short version
``` ```
The other dimension is computed to that the device aspect ratio is preserved. The other dimension is computed so that the Android device aspect ratio is
That way, a device in 1920×1080 will be mirrored at 1024×576. preserved. That way, a device in 1920×1080 will be mirrored at 1024×576.
#### Change bit-rate #### Change bit-rate
@ -226,7 +226,7 @@ It may also be enabled or disabled at any time with <kbd>MOD</kbd>+<kbd>i</kbd>.
The device screen may be cropped to mirror only part of the screen. 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 ```bash
scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0) scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0)
@ -237,7 +237,6 @@ If `--max-size` is also specified, resizing is applied after cropping.
#### Lock video orientation #### Lock video orientation
To lock the orientation of the mirroring: To lock the orientation of the mirroring:
```bash ```bash
@ -262,7 +261,7 @@ crash. It is possible to select a different encoder:
scrcpy --encoder OMX.qcom.video.encoder.avc 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: error will give the available encoders:
```bash ```bash
@ -326,7 +325,7 @@ v4l2-ctl --list-devices
ls /dev/video* ls /dev/video*
``` ```
To start scrcpy using a v4l2 sink: To start `scrcpy` using a v4l2 sink:
```bash ```bash
scrcpy --v4l2-sink=/dev/videoN scrcpy --v4l2-sink=/dev/videoN
@ -334,7 +333,7 @@ scrcpy --v4l2-sink=/dev/videoN --no-display # disable mirroring window
scrcpy --v4l2-sink=/dev/videoN -N # short version 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: Once enabled, you can open your video stream with a v4l2-capable tool:
@ -350,7 +349,7 @@ For example, you could capture the video within [OBS].
#### Buffering #### Buffering
It is possible to add buffering. This increases latency but reduces jitter (see It is possible to add buffering. This increases latency, but reduces jitter (see
[#2464]). [#2464]).
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464 [#2464]: https://github.com/Genymobile/scrcpy/issues/2464
@ -382,22 +381,22 @@ An option `--tcpip` allows to configure the connection automatically. There are
two variants. two variants.
If the device (accessible at 192.168.1.1 in this example) already listens on a 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 ```bash
scrcpy --tcpip=192.168.1.1 # default port is 5555 scrcpy --tcpip=192.168.1.1 # default port is 5555
scrcpy --tcpip=192.168.1.1: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: address), connect the device over USB, then run:
```bash ```bash
scrcpy --tcpip # without arguments scrcpy --tcpip # without arguments
``` ```
It will automatically find the device IP address, enable TCP/IP mode, then It will automatically find the device IP address and adb port, enable TCP/IP
connect to the device before starting. mode if necessary, then connect to the device before starting.
##### Manual ##### Manual
@ -413,7 +412,7 @@ Alternatively, it is possible to enable the TCP/IP connection manually using
adb shell ip route | awk '{print $9}' adb shell ip route | awk '{print $9}'
``` ```
4. Enable adb over TCP/IP on your device: `adb tcpip 5555`. 4. Enable `adb` over TCP/IP on your device: `adb tcpip 5555`.
5. Unplug your device. 5. Unplug your device.
6. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP` 6. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP`
with the device IP address you found)_. with the device IP address you found)_.
@ -427,9 +426,9 @@ having to physically connect your device directly to your computer.
If the connection randomly drops, run your `scrcpy` command to reconnect. If it If the connection randomly drops, run your `scrcpy` command to reconnect. If it
says there are no devices/emulators found, try running `adb connect 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 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. none found, try running `adb disconnect`, and then run those two commands again.
It may be useful to decrease the bit-rate and the definition: It may be useful to decrease the bit-rate and the resolution:
```bash ```bash
scrcpy --bit-rate 2M --max-size 800 scrcpy --bit-rate 2M --max-size 800
@ -448,6 +447,9 @@ scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # short version 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: If the device is connected over TCP/IP:
```bash ```bash
@ -488,7 +490,7 @@ protocol).
##### Remote ADB server ##### 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 ```bash
adb kill-server adb kill-server
@ -496,17 +498,18 @@ adb -a nodaemon server start
# keep this open # 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 Suppose that this server is accessible at 192.168.1.2. Then, from another
terminal, run scrcpy: terminal, run `scrcpy`:
```bash ```bash
export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037 export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037
scrcpy --tunnel-host=192.168.1.2 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 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 different tunnel port (it may be useful in more complex situations, when more
redirections are involved): redirections are involved):
@ -518,16 +521,16 @@ scrcpy --tunnel-port=1234
##### SSH tunnel ##### SSH tunnel
To communicate with a remote ADB server securely, it is preferable to use a SSH To communicate with a remote _adb server_ securely, it is preferable to use an
tunnel. 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 ```bash
adb start-server adb start-server
``` ```
Then, establish a SSH tunnel: Then, establish an SSH tunnel:
```bash ```bash
# local 5038 --> remote 5037 # local 5038 --> remote 5037
@ -536,7 +539,7 @@ ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer
# keep this open # keep this open
``` ```
From another terminal, run scrcpy: From another terminal, run `scrcpy`:
```bash ```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038 export ADB_SERVER_SOCKET=tcp:localhost:5038
@ -553,7 +556,7 @@ ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer
# keep this open # keep this open
``` ```
From another terminal, run scrcpy: From another terminal, run `scrcpy`:
```bash ```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038 export ADB_SERVER_SOCKET=tcp:localhost:5038
@ -595,7 +598,7 @@ scrcpy --window-borderless
#### Always on top #### Always on top
To keep the scrcpy window always on top: To keep the _scrcpy_ window always on top:
```bash ```bash
scrcpy --always-on-top scrcpy --always-on-top
@ -620,7 +623,7 @@ The window may be rotated:
scrcpy --rotation 1 scrcpy --rotation 1
``` ```
Possibles values are: Possible values:
- `0`: no rotation - `0`: no rotation
- `1`: 90 degrees counterclockwise - `1`: 90 degrees counterclockwise
- `2`: 180 degrees - `2`: 180 degrees
@ -669,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 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 #### 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 ```bash
scrcpy --stay-awake scrcpy --stay-awake
scrcpy -w scrcpy -w
``` ```
The initial state is restored when scrcpy is closed. The initial state is restored when _scrcpy_ is closed.
#### Turn screen off #### Turn screen off
@ -699,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>. 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 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 `POWER` is sent via _scrcpy_ (via right-click or <kbd>MOD</kbd>+<kbd>p</kbd>),
will force to turn the screen off after a small delay (on a best effort basis). it will force to turn the screen off after a small delay (on a best effort
The physical `POWER` button will still cause the screen to be turned on. 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: It can also be useful to prevent the device from sleeping:
@ -712,12 +716,22 @@ scrcpy -Sw
#### Power off on close #### Power off on close
To turn the device screen off when closing scrcpy: To turn the device screen off when closing _scrcpy_:
```bash ```bash
scrcpy --power-off-on-close 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 #### Show touches
@ -734,12 +748,13 @@ scrcpy --show-touches
scrcpy -t 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 #### 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: To disable it:
@ -781,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 - <kbd>MOD</kbd>+<kbd>v</kbd> injects `PASTE` (after computer-to-device
clipboard synchronization) clipboard synchronization)
In addition, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> allows to inject the In addition, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> injects the computer
computer clipboard text as a sequence of key events. This is useful when the clipboard text as a sequence of key events. This is useful when the component
component does not accept text pasting (for example in _Termux_), but it can does not accept text pasting (for example in _Termux_), but it can break
break non-ASCII content. non-ASCII content.
**WARNING:** Pasting the computer clipboard to the device (either via **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 <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 into the Android clipboard. As a consequence, any Android application could read
its content. You should avoid to paste sensitive content (like passwords) that its content. You should avoid pasting sensitive content (like passwords) that
way. 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 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 of <kbd>Ctrl</kbd>+<kbd>v</kbd> and <kbd>MOD</kbd>+<kbd>v</kbd> so that they
also inject the computer clipboard text as a sequence of key events (the same also inject the computer clipboard text as a sequence of key events (the same
@ -805,29 +820,29 @@ To disable automatic clipboard synchronization, use
To simulate "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-and-move_. To simulate "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-and-move_.
More precisely, hold <kbd>Ctrl</kbd> while pressing the left-click button. Until More precisely, hold down <kbd>Ctrl</kbd> while pressing the left-click button.
the left-click button is released, all mouse movements scale and rotate the Until the left-click button is released, all mouse movements scale and rotate
content (if supported by the app) relative to the center of the screen. 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 Technically, _scrcpy_ generates additional touch events from a "virtual finger"
a location inverted through the center of the screen. at a location inverted through the center of the screen.
#### Physical keyboard simulation (HID) #### Physical keyboard simulation (HID)
By default, scrcpy uses Android key or text injection: it works everywhere, but By default, _scrcpy_ uses Android key or text injection: it works everywhere,
is limited to ASCII. but is limited to ASCII.
Alternatively, scrcpy can simulate a physical USB keyboard on Android to provide Alternatively, `scrcpy` can simulate a physical USB keyboard on Android to
a better input experience (using [USB HID over AOAv2][hid-aoav2]): the virtual provide a better input experience (using [USB HID over AOAv2][hid-aoav2]): the
keyboard is disabled and it works for all characters and IME. virtual keyboard is disabled and it works for all characters and IME.
[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support [hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support
However, it only works if the device is connected by USB. 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 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 is not possible to open a USB device if it is already open by another process
like the adb daemon). like the _adb daemon_).
To enable this mode: To enable this mode:
@ -838,7 +853,7 @@ scrcpy -K # short version
If it fails for some reason (for example because the device is not connected via 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 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. USB and TCP/IP.
In this mode, raw key events (scancodes) are sent to the device, independently In this mode, raw key events (scancodes) are sent to the device, independently
@ -862,7 +877,7 @@ a physical keyboard is connected).
Similarly to the physical keyboard simulation, it is possible to simulate a 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. 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 coordinates. By simulating a physical mouse, a mouse pointer appears on the
Android device, and relative mouse motion, clicks and scrolls are injected. Android device, and relative mouse motion, clicks and scrolls are injected.
@ -873,7 +888,7 @@ scrcpy --hid-mouse
scrcpy -M # short version 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]. buttons][forward_all_clicks].
[forward_all_clicks]: #right-click-and-middle-click [forward_all_clicks]: #right-click-and-middle-click
@ -892,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 (HID), as if the computer keyboard and mouse were plugged directly to the device
via an OTG cable. 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: To enable OTG mode:
@ -918,7 +933,7 @@ connected by USB.
#### Text injection preference #### 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; - _key events_, signaling that a key is pressed or released;
- _text events_, signaling that a text has been entered. - _text events_, signaling that a text has been entered.
@ -1062,7 +1077,7 @@ _³4th and 5th mouse buttons, if your mouse has them._
_⁴For react-native apps in development, `MENU` triggers development menu._ _⁴For react-native apps in development, `MENU` triggers development menu._
_⁵Only on Android >= 7._ _⁵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": second time. For example, to execute "Expand settings panel":
1. Press and keep pressing <kbd>MOD</kbd>. 1. Press and keep pressing <kbd>MOD</kbd>.
@ -1075,7 +1090,7 @@ handled by the active application.
## Custom paths ## 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`: `ADB`:
```bash ```bash
@ -1088,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`. 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]. A colleague challenged me to find a name as unpronounceable as [gnirehtet].
@ -1105,7 +1120,7 @@ See [BUILD].
## Common issues ## Common issues
See the [FAQ].md). See the [FAQ].
[FAQ]: FAQ.md [FAQ]: FAQ.md
@ -1148,7 +1163,7 @@ If you encounter a bug, please read the [FAQ] first, then open an [issue].
[issue]: https://github.com/Genymobile/scrcpy/issues [issue]: https://github.com/Genymobile/scrcpy/issues
For general questions or discussions, you could also use: For general questions or discussions, you can also use:
- Reddit: [`r/scrcpy`](https://www.reddit.com/r/scrcpy) - Reddit: [`r/scrcpy`](https://www.reddit.com/r/scrcpy)
- Twitter: [`@scrcpy_app`](https://twitter.com/scrcpy_app) - Twitter: [`@scrcpy_app`](https://twitter.com/scrcpy_app)
@ -1159,7 +1174,7 @@ This README is available in other languages:
- [Deutsch (German, `de`) - v1.22](README.de.md) - [Deutsch (German, `de`) - v1.22](README.de.md)
- [Indonesian (Indonesia, `id`) - v1.16](README.id.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) - [日本語 (Japanese, `jp`) - v1.19](README.jp.md)
- [한국어 (Korean, `ko`) - v1.11](README.ko.md) - [한국어 (Korean, `ko`) - v1.11](README.ko.md)
- [Português Brasileiro (Brazilian Portuguese, `pt-BR`) - v1.19](README.pt-br.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) #### 物理键盘模拟 (HID)

View File

@ -29,6 +29,7 @@ _scrcpy() {
-N --no-display -N --no-display
--no-key-repeat --no-key-repeat
--no-mipmaps --no-mipmaps
--no-power-on
--otg --otg
-p --port= -p --port=
--power-off-on-close --power-off-on-close
@ -92,6 +93,11 @@ _scrcpy() {
COMPREPLY=($(compgen -W 'verbose debug info warn error' -- "$cur")) COMPREPLY=($(compgen -W 'verbose debug info warn error' -- "$cur"))
return return
;; ;;
-s|--serial)
# Use 'adb devices' to list serial numbers
COMPREPLY=($(compgen -W "$("${ADB:-adb}" devices | awk '$2 == "device" {print $1}')" -- ${cur}))
return
;;
-b|--bitrate \ -b|--bitrate \
|--codec-options \ |--codec-options \
|--crop \ |--crop \
@ -102,7 +108,6 @@ _scrcpy() {
|-m|--max-size \ |-m|--max-size \
|-p|--port \ |-p|--port \
|--push-target \ |--push-target \
|-s|--serial \
|--tunnel-host \ |--tunnel-host \
|--tunnel-port \ |--tunnel-port \
|--v4l2-buffer \ |--v4l2-buffer \

View File

@ -0,0 +1,13 @@
[Desktop Entry]
Name=scrcpy (console)
GenericName=Android Remote Control
Comment=Display and control your Android device
# For some users, the PATH or ADB environment variables are set from the shell
# startup file, like .bashrc or .zshrc… Run an interactive shell to get
# environment correctly initialized.
Exec=/bin/bash --norc --noprofile -i -c '"$SHELL" -i -c scrcpy || read -p "Press any key to quit..."'
Icon=scrcpy
Terminal=true
Type=Application
Categories=Utility;RemoteAccess;
StartupNotify=false

13
app/data/scrcpy.desktop Normal file
View File

@ -0,0 +1,13 @@
[Desktop Entry]
Name=scrcpy
GenericName=Android Remote Control
Comment=Display and control your Android device
# For some users, the PATH or ADB environment variables are set from the shell
# startup file, like .bashrc or .zshrc… Run an interactive shell to get
# environment correctly initialized.
Exec=/bin/sh -c '"$SHELL" -i -c scrcpy'
Icon=scrcpy
Terminal=false
Type=Application
Categories=Utility;RemoteAccess;
StartupNotify=false

View File

@ -35,6 +35,7 @@ arguments=(
{-N,--no-display}'[Do not display device \(during screen recording or when V4L2 sink is enabled\)]' {-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-key-repeat[Do not forward repeated key events when a key is held down]'
'--no-mipmaps[Disable the generation of mipmaps]' '--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\)]' '--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]' {-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]' '--power-off-on-close[Turn the device screen off when closing scrcpy]'
@ -46,7 +47,7 @@ arguments=(
'--record-format=[Force recording format]:format:(mp4 mkv)' '--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)' '--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)' '--rotation=[Set the initial display rotation]:rotation values:(0 1 2 3)'
{-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]' {-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]:serial:($("${ADB-adb}" devices | awk '\''$2 == "device" {print $1}'\''))'
'--shortcut-mod=[\[key1,key2+key3,...\] Specify the modifiers to use for scrcpy shortcuts]:shortcut mod:(lctrl rctrl lalt ralt lsuper rsuper)' '--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]' {-S,--turn-screen-off}'[Turn the device screen off immediately]'
{-t,--show-touches}'[Show physical touches]' {-t,--show-touches}'[Show physical touches]'

View File

@ -143,12 +143,12 @@ else
prebuilt_libusb = meson.get_cross_property('prebuilt_libusb') prebuilt_libusb = meson.get_cross_property('prebuilt_libusb')
prebuilt_libusb_root = meson.get_cross_property('prebuilt_libusb_root') prebuilt_libusb_root = meson.get_cross_property('prebuilt_libusb_root')
libusb_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_libusb + '/dll' libusb_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_libusb
libusb_include_dir = 'prebuilt-deps/data/' + prebuilt_libusb_root + '/include' libusb_include_dir = 'prebuilt-deps/data/' + prebuilt_libusb_root + '/include'
libusb = declare_dependency( libusb = declare_dependency(
dependencies: [ dependencies: [
cc.find_library('libusb-1.0', dirs: libusb_bin_dir), cc.find_library('msys-usb-1.0', dirs: libusb_bin_dir),
], ],
include_directories: include_directories(libusb_include_dir) include_directories: include_directories(libusb_include_dir)
) )
@ -223,14 +223,26 @@ executable('scrcpy', src,
install: true, install: true,
c_args: []) c_args: [])
# <https://mesonbuild.com/Builtin-options.html#directories>
datadir = get_option('datadir') # by default 'share'
install_man('scrcpy.1') install_man('scrcpy.1')
install_data('data/icon.png', install_data('data/icon.png',
rename: 'scrcpy.png', rename: 'scrcpy.png',
install_dir: 'share/icons/hicolor/256x256/apps') install_dir: join_paths(datadir, 'icons/hicolor/256x256/apps'))
install_data('data/zsh-completion/_scrcpy', install_data('data/zsh-completion/_scrcpy',
install_dir: 'share/zsh/site-functions') install_dir: join_paths(datadir, 'zsh/site-functions'))
install_data('data/bash-completion/scrcpy', install_data('data/bash-completion/scrcpy',
install_dir: 'share/bash-completion/completions') install_dir: join_paths(datadir, 'bash-completion/completions'))
# Desktop entry file for application launchers
if host_machine.system() == 'linux'
# Install a launcher (ex: /usr/local/share/applications/scrcpy.desktop)
install_data('data/scrcpy.desktop',
install_dir: join_paths(datadir, 'applications'))
install_data('data/scrcpy-console.desktop',
install_dir: join_paths(datadir, 'applications'))
endif
### TESTS ### TESTS
@ -245,8 +257,8 @@ if get_option('buildtype') == 'debug'
'src/util/str.c', 'src/util/str.c',
'src/util/strbuf.c', 'src/util/strbuf.c',
]], ]],
['test_buffer_util', [ ['test_binary', [
'tests/test_buffer_util.c', 'tests/test_binary.c',
]], ]],
['test_cbuf', [ ['test_cbuf', [
'tests/test_cbuf.c', 'tests/test_cbuf.c',
@ -255,6 +267,7 @@ if get_option('buildtype') == 'debug'
'tests/test_cli.c', 'tests/test_cli.c',
'src/cli.c', 'src/cli.c',
'src/options.c', 'src/options.c',
'src/util/log.c',
'src/util/net.c', 'src/util/net.c',
'src/util/str.c', 'src/util/str.c',
'src/util/strbuf.c', 'src/util/strbuf.c',

View File

@ -6,10 +6,10 @@ cd "$DIR"
mkdir -p "$PREBUILT_DATA_DIR" mkdir -p "$PREBUILT_DATA_DIR"
cd "$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 FILENAME=platform-tools_r33.0.1-windows.zip
SHA256SUM=0f4b8fdd26af2c3733539d6eebb3c2ed499ea1d4bb1f4e0ecc2d6016961a6e24 SHA256SUM=c1f02d42ea24ef4ff2a405ae7370e764ef4546f9b3e4520f5571a00ed5012c42
if [[ -d "$DEP_DIR" ]] if [[ -d "$DEP_DIR" ]]
then then

View File

@ -6,10 +6,11 @@ cd "$DIR"
mkdir -p "$PREBUILT_DATA_DIR" mkdir -p "$PREBUILT_DATA_DIR"
cd "$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 FILENAME=ffmpeg-$VERSION-full_build-shared.7z
SHA256SUM=e5900f6cecd4c438d398bd2fc308736c10b857cd8dd61c11bcfb05bff5d1211a SHA256SUM=ded28435b6f04b74f5ef5a6a13761233bce9e8e9f8ecb0eabe936fd36a778b0c
if [[ -d "$DEP_DIR" ]] if [[ -d "$DEP_DIR" ]]
then then
@ -17,13 +18,13 @@ then
exit 0 exit 0
fi 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" "$FILENAME" "$SHA256SUM"
mkdir "$DEP_DIR" mkdir "$DEP_DIR"
cd "$DEP_DIR" cd "$DEP_DIR"
ZIP_PREFIX=ffmpeg-5.0-full_build-shared ZIP_PREFIX=ffmpeg-$VERSION-full_build-shared
7z x "../$FILENAME" \ 7z x "../$FILENAME" \
"$ZIP_PREFIX"/bin/avutil-57.dll \ "$ZIP_PREFIX"/bin/avutil-57.dll \
"$ZIP_PREFIX"/bin/avcodec-59.dll \ "$ZIP_PREFIX"/bin/avcodec-59.dll \

View File

@ -6,10 +6,10 @@ cd "$DIR"
mkdir -p "$PREBUILT_DATA_DIR" mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR"
DEP_DIR=libusb-1.0.25 DEP_DIR=libusb-1.0.26
FILENAME=libusb-1.0.25.7z FILENAME=libusb-1.0.26-binaries.7z
SHA256SUM=3d1c98416f454026034b2b5d67f8a294053898cb70a8b489874e75b136c6674d SHA256SUM=9c242696342dbde9cdc47239391f71833939bf9f7aa2bbb28cdaabe890465ec5
if [[ -d "$DEP_DIR" ]] if [[ -d "$DEP_DIR" ]]
then then
@ -17,12 +17,18 @@ then
exit 0 exit 0
fi fi
get_file "https://github.com/libusb/libusb/releases/download/v1.0.25/$FILENAME" "$FILENAME" "$SHA256SUM" get_file "https://github.com/libusb/libusb/releases/download/v1.0.26/$FILENAME" "$FILENAME" "$SHA256SUM"
mkdir "$DEP_DIR" mkdir "$DEP_DIR"
cd "$DEP_DIR" cd "$DEP_DIR"
# include/ is the same in all folders of the archive
7z x "../$FILENAME" \ 7z x "../$FILENAME" \
MinGW32/dll/libusb-1.0.dll \ libusb-1.0.26-binaries/libusb-MinGW-Win32/bin/msys-usb-1.0.dll \
MinGW64/dll/libusb-1.0.dll \ libusb-1.0.26-binaries/libusb-MinGW-x64/bin/msys-usb-1.0.dll \
include / 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" mkdir -p "$PREBUILT_DATA_DIR"
cd "$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 FILENAME=SDL2-devel-2.0.22-mingw.tar.gz
SHA256SUM=38094d82a857d6c62352e5c5cdec74948c5b4d25c59cbd298d6d233568976bd1 SHA256SUM=0e91e35973366aa1e6f81ee368924d9b4f93f9da4d2f2a89ec80b06eadcf23d1
if [[ -d "$DEP_DIR" ]] if [[ -d "$DEP_DIR" ]]
then then

View File

@ -13,7 +13,7 @@ BEGIN
VALUE "LegalCopyright", "Romain Vimont, Genymobile" VALUE "LegalCopyright", "Romain Vimont, Genymobile"
VALUE "OriginalFilename", "scrcpy.exe" VALUE "OriginalFilename", "scrcpy.exe"
VALUE "ProductName", "scrcpy" VALUE "ProductName", "scrcpy"
VALUE "ProductVersion", "1.23" VALUE "ProductVersion", "1.24"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View File

@ -180,6 +180,10 @@ Do not forward repeated key events when a key is held down.
.B \-\-no\-mipmaps .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. 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 .TP
.B \-\-otg .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. 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.
@ -271,7 +275,7 @@ Configure and reconnect the device over TCP/IP.
If a destination address is provided, then scrcpy connects to this address before starting. The device must listen on the given TCP port (default is 5555). If a destination address is provided, then scrcpy connects to this address before starting. The device must listen on the given TCP port (default is 5555).
If no destination address is provided, then scrcpy attempts to find the IP address of the current device (typically connected over USB), enables TCP/IP mode, then connects to this address before starting. If no destination address is provided, then scrcpy attempts to find the IP address and adb port of the current device (typically connected over USB), enables TCP/IP mode if necessary, then connects to this address before starting.
.TP .TP
.B \-S, \-\-turn\-screen\-off .B \-S, \-\-turn\-screen\-off
@ -477,6 +481,10 @@ Push file to device (see \fB\-\-push\-target\fR)
.B ADB .B ADB
Path to adb. Path to adb.
.TP
.B ANDROID_SERIAL
Device serial to use if no selector (-s, -d, -e or --tcpip=<addr>) is specified.
.TP .TP
.B SCRCPY_ICON_PATH .B SCRCPY_ICON_PATH
Path to the program icon. Path to the program icon.

View File

@ -401,6 +401,7 @@ sc_adb_list_devices(struct sc_intr *intr, unsigned flags,
#define BUFSIZE 65536 #define BUFSIZE 65536
char *buf = malloc(BUFSIZE); char *buf = malloc(BUFSIZE);
if (!buf) { if (!buf) {
LOG_OOM();
return false; return false;
} }
@ -473,9 +474,12 @@ sc_adb_accept_device(const struct sc_adb_device *device,
} }
return !strcmp(selector->serial, device->serial); return !strcmp(selector->serial, device->serial);
case SC_ADB_DEVICE_SELECT_USB: case SC_ADB_DEVICE_SELECT_USB:
return !sc_adb_is_serial_tcpip(device->serial); return sc_adb_device_get_type(device->serial) ==
SC_ADB_DEVICE_TYPE_USB;
case SC_ADB_DEVICE_SELECT_TCPIP: case SC_ADB_DEVICE_SELECT_TCPIP:
return sc_adb_is_serial_tcpip(device->serial); // Both emulators and TCP/IP devices are selected via -e
return sc_adb_device_get_type(device->serial) !=
SC_ADB_DEVICE_TYPE_USB;
default: default:
assert(!"Missing SC_ADB_DEVICE_SELECT_* handling"); assert(!"Missing SC_ADB_DEVICE_SELECT_* handling");
break; break;
@ -509,8 +513,10 @@ sc_adb_devices_log(enum sc_log_level level, struct sc_adb_device *devices,
for (size_t i = 0; i < count; ++i) { for (size_t i = 0; i < count; ++i) {
struct sc_adb_device *d = &devices[i]; struct sc_adb_device *d = &devices[i];
const char *selection = d->selected ? "-->" : " "; const char *selection = d->selected ? "-->" : " ";
const char *type = sc_adb_is_serial_tcpip(d->serial) ? "(tcpip)" bool is_usb =
: " (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", LOG(level, " %s %s %-20s %16s %s",
selection, type, d->serial, d->state, d->model ? d->model : ""); selection, type, d->serial, d->state, d->model ? d->model : "");
} }
@ -531,6 +537,8 @@ sc_adb_device_check_state(struct sc_adb_device *device,
LOGE("A popup should open on the device to request authorization."); LOGE("A popup should open on the device to request authorization.");
LOGE("Check the FAQ: " LOGE("Check the FAQ: "
"<https://github.com/Genymobile/scrcpy/blob/master/FAQ.md>"); "<https://github.com/Genymobile/scrcpy/blob/master/FAQ.md>");
} else {
LOGE("Device could not be connected (state=%s)", state);
} }
return false; return false;
@ -703,10 +711,5 @@ sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) {
// It is parsed as a NUL-terminated string // It is parsed as a NUL-terminated string
buf[r] = '\0'; buf[r] = '\0';
return sc_adb_parse_device_ip_from_output(buf); return sc_adb_parse_device_ip(buf);
}
bool
sc_adb_is_serial_tcpip(const char *serial) {
return strchr(serial, ':');
} }

View File

@ -114,13 +114,4 @@ sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop,
char * char *
sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags); sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags);
/**
* Indicate if the serial represents an IP address
*
* In practice, it just returns true if and only if it contains a ':', which is
* sufficient to distinguish an ip:port from a real USB serial.
*/
bool
sc_adb_is_serial_tcpip(const char *serial);
#endif #endif

View File

@ -1,6 +1,7 @@
#include "adb_device.h" #include "adb_device.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
void void
sc_adb_device_destroy(struct sc_adb_device *device) { sc_adb_device_destroy(struct sc_adb_device *device) {
@ -25,3 +26,18 @@ sc_adb_devices_destroy(struct sc_vec_adb_devices *devices) {
sc_vector_destroy(devices); 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;
}

View File

@ -15,6 +15,12 @@ struct sc_adb_device {
bool selected; 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); struct sc_vec_adb_devices SC_VECTOR(struct sc_adb_device);
void void
@ -35,4 +41,10 @@ sc_adb_device_move(struct sc_adb_device *dst, struct sc_adb_device *src);
void void
sc_adb_devices_destroy(struct sc_vec_adb_devices *devices); 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 #endif

View File

@ -199,7 +199,7 @@ sc_adb_parse_device_ip_from_line(char *line) {
} }
char * char *
sc_adb_parse_device_ip_from_output(char *str) { sc_adb_parse_device_ip(char *str) {
size_t idx_line = 0; size_t idx_line = 0;
while (str[idx_line] != '\0') { while (str[idx_line] != '\0') {
char *line = &str[idx_line]; char *line = &str[idx_line];

View File

@ -25,6 +25,6 @@ sc_adb_parse_devices(char *str, struct sc_vec_adb_devices *out_vec);
* Warning: this function modifies the buffer for optimization purposes. * Warning: this function modifies the buffer for optimization purposes.
*/ */
char * char *
sc_adb_parse_device_ip_from_output(char *str); sc_adb_parse_device_ip(char *str);
#endif #endif

View File

@ -56,6 +56,7 @@
#define OPT_OTG 1036 #define OPT_OTG 1036
#define OPT_NO_CLEANUP 1037 #define OPT_NO_CLEANUP 1037
#define OPT_PRINT_FPS 1038 #define OPT_PRINT_FPS 1038
#define OPT_NO_POWER_ON 1039
struct sc_option { struct sc_option {
char shortopt; char shortopt;
@ -80,6 +81,11 @@ struct sc_envvar {
const char *text; const char *text;
}; };
struct sc_exit_status {
unsigned value;
const char *text;
};
struct sc_getopt_adapter { struct sc_getopt_adapter {
char *optstring; char *optstring;
struct option *longopts; struct option *longopts;
@ -297,6 +303,11 @@ static const struct sc_option options[] = {
"mipmaps are automatically generated to improve downscaling " "mipmaps are automatically generated to improve downscaling "
"quality. This option disables the generation of mipmaps.", "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_id = OPT_OTG,
.longopt = "otg", .longopt = "otg",
@ -572,7 +583,7 @@ static const struct sc_shortcut shortcuts[] = {
.text = "Click on BACK", .text = "Click on BACK",
}, },
{ {
.shortcuts = { "MOD+s" }, .shortcuts = { "MOD+s", "4th-click" },
.text = "Click on APP_SWITCH", .text = "Click on APP_SWITCH",
}, },
{ {
@ -608,7 +619,7 @@ static const struct sc_shortcut shortcuts[] = {
.text = "Rotate device screen", .text = "Rotate device screen",
}, },
{ {
.shortcuts = { "MOD+n" }, .shortcuts = { "MOD+n", "5th-click" },
.text = "Expand notification panel", .text = "Expand notification panel",
}, },
{ {
@ -655,6 +666,11 @@ static const struct sc_envvar envvars[] = {
.name = "ADB", .name = "ADB",
.text = "Path to adb executable", .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", .name = "SCRCPY_ICON_PATH",
.text = "Path to the program icon", .text = "Path to the program icon",
@ -662,7 +678,22 @@ static const struct sc_envvar envvars[] = {
{ {
.name = "SCRCPY_SERVER_PATH", .name = "SCRCPY_SERVER_PATH",
.text = "Path to the server binary", .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 * static char *
@ -901,6 +932,25 @@ print_envvar(const struct sc_envvar *envvar, unsigned cols) {
free(text); 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 void
scrcpy_print_usage(const char *arg0) { scrcpy_print_usage(const char *arg0) {
#define SC_TERM_COLS_DEFAULT 80 #define SC_TERM_COLS_DEFAULT 80
@ -939,6 +989,11 @@ scrcpy_print_usage(const char *arg0) {
for (size_t i = 0; i < ARRAY_LEN(envvars); ++i) { for (size_t i = 0; i < ARRAY_LEN(envvars); ++i) {
print_envvar(&envvars[i], cols); 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 static bool
@ -1549,6 +1604,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
case OPT_NO_CLEANUP: case OPT_NO_CLEANUP:
opts->cleanup = false; opts->cleanup = false;
break; break;
case OPT_NO_POWER_ON:
opts->power_on = false;
break;
case OPT_PRINT_FPS: case OPT_PRINT_FPS:
opts->start_fps_counter = true; opts->start_fps_counter = true;
break; break;

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); sc_clock_estimate(clock, &clock->slope, &clock->offset);
#ifndef SC_CLOCK_NDEBUG #ifndef SC_CLOCK_NDEBUG
LOGD("Clock estimation: %g * pts + %" PRItick, LOGD("Clock estimation: %f * pts + %" PRItick,
clock->slope, clock->offset); clock->slope, clock->offset);
#endif #endif
} }

View File

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

View File

@ -1,5 +1,5 @@
#ifndef COMPAT_H #ifndef SC_COMPAT_H
#define COMPAT_H #define SC_COMPAT_H
#include "config.h" #include "config.h"

View File

@ -5,7 +5,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "util/buffer_util.h" #include "util/binary.h"
#include "util/log.h" #include "util/log.h"
#include "util/str.h" #include "util/str.h"
@ -37,7 +37,7 @@ static const char *const android_motionevent_action_labels[] = {
"move", "move",
"cancel", "cancel",
"outside", "outside",
"ponter-down", "pointer-down",
"pointer-up", "pointer-up",
"hover-move", "hover-move",
"scroll", "scroll",
@ -78,16 +78,6 @@ write_string(const char *utf8, size_t max_len, unsigned char *buf) {
return 4 + len; return 4 + len;
} }
static uint16_t
to_fixed_point_16(float f) {
assert(f >= 0.0f && f <= 1.0f);
uint32_t u = f * 0x1p16f; // 2^16
if (u >= 0xffff) {
u = 0xffff;
}
return (uint16_t) u;
}
size_t size_t
sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) { sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
buf[0] = msg->type; buf[0] = msg->type;
@ -109,18 +99,20 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
sc_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); write_position(&buf[10], &msg->inject_touch_event.position);
uint16_t pressure = uint16_t pressure =
to_fixed_point_16(msg->inject_touch_event.pressure); sc_float_to_u16fp(msg->inject_touch_event.pressure);
sc_write16be(&buf[22], pressure); sc_write16be(&buf[22], pressure);
sc_write32be(&buf[24], msg->inject_touch_event.buttons); sc_write32be(&buf[24], msg->inject_touch_event.buttons);
return 28; return 28;
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT: case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
write_position(&buf[1], &msg->inject_scroll_event.position); write_position(&buf[1], &msg->inject_scroll_event.position);
sc_write32be(&buf[13], int16_t hscroll =
(uint32_t) msg->inject_scroll_event.hscroll); sc_float_to_i16fp(msg->inject_scroll_event.hscroll);
sc_write32be(&buf[17], int16_t vscroll =
(uint32_t) msg->inject_scroll_event.vscroll); sc_float_to_i16fp(msg->inject_scroll_event.vscroll);
sc_write32be(&buf[21], msg->inject_scroll_event.buttons); sc_write16be(&buf[13], (uint16_t) hscroll);
return 25; sc_write16be(&buf[15], (uint16_t) vscroll);
sc_write32be(&buf[17], msg->inject_scroll_event.buttons);
return 21;
case SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON: case SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
buf[1] = msg->inject_keycode.action; buf[1] = msg->inject_keycode.action;
return 2; return 2;
@ -170,7 +162,7 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
if (id == POINTER_ID_MOUSE || id == POINTER_ID_VIRTUAL_FINGER) { if (id == POINTER_ID_MOUSE || id == POINTER_ID_VIRTUAL_FINGER) {
// string pointer id // string pointer id
LOG_CMSG("touch [id=%s] %-4s position=%" PRIi32 ",%" PRIi32 LOG_CMSG("touch [id=%s] %-4s position=%" PRIi32 ",%" PRIi32
" pressure=%g buttons=%06lx", " pressure=%f buttons=%06lx",
id == POINTER_ID_MOUSE ? "mouse" : "vfinger", id == POINTER_ID_MOUSE ? "mouse" : "vfinger",
MOTIONEVENT_ACTION_LABEL(action), MOTIONEVENT_ACTION_LABEL(action),
msg->inject_touch_event.position.point.x, msg->inject_touch_event.position.point.x,
@ -180,7 +172,7 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
} else { } else {
// numeric pointer id // numeric pointer id
LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%" LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%"
PRIi32 " pressure=%g buttons=%06lx", PRIi32 " pressure=%f buttons=%06lx",
id, id,
MOTIONEVENT_ACTION_LABEL(action), MOTIONEVENT_ACTION_LABEL(action),
msg->inject_touch_event.position.point.x, msg->inject_touch_event.position.point.x,
@ -191,8 +183,8 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
break; break;
} }
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT: case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%" PRIi32 LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%f"
" vscroll=%" PRIi32 " buttons=%06lx", " vscroll=%f buttons=%06lx",
msg->inject_scroll_event.position.point.x, msg->inject_scroll_event.position.point.x,
msg->inject_scroll_event.position.point.y, msg->inject_scroll_event.position.point.y,
msg->inject_scroll_event.hscroll, msg->inject_scroll_event.hscroll,

View File

@ -1,5 +1,5 @@
#ifndef CONTROLMSG_H #ifndef SC_CONTROLMSG_H
#define CONTROLMSG_H #define SC_CONTROLMSG_H
#include "common.h" #include "common.h"
@ -68,8 +68,8 @@ struct sc_control_msg {
} inject_touch_event; } inject_touch_event;
struct { struct {
struct sc_position position; struct sc_position position;
int32_t hscroll; float hscroll;
int32_t vscroll; float vscroll;
enum android_motionevent_buttons buttons; enum android_motionevent_buttons buttons;
} inject_scroll_event; } inject_scroll_event;
struct { struct {

View File

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

View File

@ -7,7 +7,7 @@
#include "decoder.h" #include "decoder.h"
#include "events.h" #include "events.h"
#include "recorder.h" #include "recorder.h"
#include "util/buffer_util.h" #include "util/binary.h"
#include "util/log.h" #include "util/log.h"
#define SC_PACKET_HEADER_SIZE 12 #define SC_PACKET_HEADER_SIZE 12
@ -37,8 +37,8 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
// CK...... ........ ........ ........ ........ ........ ........ ........ // CK...... ........ ........ ........ ........ ........ ........ ........
// ^^<-------------------------------------------------------------------> // ^^<------------------------------------------------------------------->
// || PTS // || PTS
// | `- config packet // | `- key frame
// `-- key frame // `-- config packet
uint8_t header[SC_PACKET_HEADER_SIZE]; uint8_t header[SC_PACKET_HEADER_SIZE];
ssize_t r = net_recv_all(demuxer->socket, header, SC_PACKET_HEADER_SIZE); ssize_t r = net_recv_all(demuxer->socket, header, SC_PACKET_HEADER_SIZE);

View File

@ -4,7 +4,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "util/buffer_util.h" #include "util/binary.h"
#include "util/log.h" #include "util/log.h"
ssize_t ssize_t

View File

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

View File

@ -1,5 +1,5 @@
#ifndef FPSCOUNTER_H #ifndef SC_FPSCOUNTER_H
#define FPSCOUNTER_H #define SC_FPSCOUNTER_H
#include "common.h" #include "common.h"

View File

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

View File

@ -358,8 +358,8 @@ struct sc_mouse_click_event {
struct sc_mouse_scroll_event { struct sc_mouse_scroll_event {
struct sc_position position; struct sc_position position;
int32_t hscroll; float hscroll;
int32_t vscroll; float vscroll;
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
}; };

View File

@ -747,8 +747,13 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
.point = sc_screen_convert_window_to_frame_coords(im->screen, .point = sc_screen_convert_window_to_frame_coords(im->screen,
mouse_x, mouse_y), mouse_x, mouse_y),
}, },
.hscroll = event->x, #if SDL_VERSION_ATLEAST(2, 0, 18)
.vscroll = event->y, .hscroll = CLAMP(event->preciseX, -1.0f, 1.0f),
.vscroll = CLAMP(event->preciseY, -1.0f, 1.0f),
#else
.hscroll = CLAMP(event->x, -1, 1),
.vscroll = CLAMP(event->y, -1, 1),
#endif
.buttons_state = .buttons_state =
sc_mouse_buttons_state_from_sdl(buttons, im->forward_all_clicks), sc_mouse_buttons_state_from_sdl(buttons, im->forward_all_clicks),
}; };

View File

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

View File

@ -4,6 +4,10 @@
#include <stdbool.h> #include <stdbool.h>
#include <unistd.h> #include <unistd.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#ifdef _WIN32
#include <windows.h>
#include "util/str.h"
#endif
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
# include <libavdevice/avdevice.h> # include <libavdevice/avdevice.h>
#endif #endif
@ -18,8 +22,8 @@
#include "version.h" #include "version.h"
int int
main(int argc, char *argv[]) { main_scrcpy(int argc, char *argv[]) {
#ifdef __WINDOWS__ #ifdef _WIN32
// disable buffering, we want logs immediately // disable buffering, we want logs immediately
// even line buffering (setvbuf() with mode _IOLBF) is not sufficient // even line buffering (setvbuf() with mode _IOLBF) is not sufficient
setbuf(stdout, NULL); setbuf(stdout, NULL);
@ -80,3 +84,52 @@ main(int argc, char *argv[]) {
return ret; return ret;
} }
int
main(int argc, char *argv[]) {
#ifndef _WIN32
return main_scrcpy(argc, argv);
#else
(void) argc;
(void) argv;
int wargc;
wchar_t **wargv = CommandLineToArgvW(GetCommandLineW(), &wargc);
if (!wargv) {
LOG_OOM();
return SCRCPY_EXIT_FAILURE;
}
char **argv_utf8 = malloc((wargc + 1) * sizeof(*argv_utf8));
if (!argv_utf8) {
LOG_OOM();
LocalFree(wargv);
return SCRCPY_EXIT_FAILURE;
}
argv_utf8[wargc] = NULL;
for (int i = 0; i < wargc; ++i) {
argv_utf8[i] = sc_str_from_wchars(wargv[i]);
if (!argv_utf8[i]) {
LOG_OOM();
for (int j = 0; j < i; ++j) {
free(argv_utf8[j]);
}
LocalFree(wargv);
free(argv_utf8);
return SCRCPY_EXIT_FAILURE;
}
}
LocalFree(wargv);
int ret = main_scrcpy(wargc, argv_utf8);
for (int i = 0; i < wargc; ++i) {
free(argv_utf8[i]);
}
free(argv_utf8);
return ret;
#endif
}

View File

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

View File

@ -64,4 +64,5 @@ const struct scrcpy_options scrcpy_options_default = {
.select_usb = false, .select_usb = false,
.cleanup = true, .cleanup = true,
.start_fps_counter = false, .start_fps_counter = false,
.power_on = true,
}; };

View File

@ -139,6 +139,7 @@ struct scrcpy_options {
bool select_tcpip; bool select_tcpip;
bool cleanup; bool cleanup;
bool start_fps_counter; bool start_fps_counter;
bool power_on;
}; };
extern const struct scrcpy_options scrcpy_options_default; extern const struct scrcpy_options scrcpy_options_default;

View File

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

View File

@ -324,6 +324,7 @@ scrcpy(struct scrcpy_options *options) {
.tcpip = options->tcpip, .tcpip = options->tcpip,
.tcpip_dst = options->tcpip_dst, .tcpip_dst = options->tcpip_dst,
.cleanup = options->cleanup, .cleanup = options->cleanup,
.power_on = options->power_on,
}; };
static const struct sc_server_callbacks cbs = { static const struct sc_server_callbacks cbs = {

View File

@ -19,6 +19,8 @@
#define SC_SERVER_PATH_DEFAULT PREFIX "/share/scrcpy/" SC_SERVER_FILENAME #define SC_SERVER_PATH_DEFAULT PREFIX "/share/scrcpy/" SC_SERVER_FILENAME
#define SC_DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar" #define SC_DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
#define SC_ADB_PORT_DEFAULT 5555
static char * static char *
get_server_path(void) { get_server_path(void) {
#ifdef __WINDOWS__ #ifdef __WINDOWS__
@ -248,6 +250,10 @@ execute_server(struct sc_server *server,
// By default, cleanup is true // By default, cleanup is true
ADD_PARAM("cleanup=false"); ADD_PARAM("cleanup=false");
} }
if (!params->power_on) {
// By default, power_on is true
ADD_PARAM("power_on=false");
}
#undef ADD_PARAM #undef ADD_PARAM
@ -509,27 +515,36 @@ sc_server_on_terminated(void *userdata) {
LOGD("Server terminated"); LOGD("Server terminated");
} }
static bool static uint16_t
is_tcpip_mode_enabled(struct sc_server *server, const char *serial) { get_adb_tcp_port(struct sc_server *server, const char *serial) {
struct sc_intr *intr = &server->intr; struct sc_intr *intr = &server->intr;
char *current_port = char *current_port =
sc_adb_getprop(intr, serial, "service.adb.tcp.port", SC_ADB_SILENT); sc_adb_getprop(intr, serial, "service.adb.tcp.port", SC_ADB_SILENT);
if (!current_port) { if (!current_port) {
return false; return 0;
} }
// Is the device is listening on TCP on port 5555? long value;
bool enabled = !strcmp("5555", current_port); bool ok = sc_str_parse_integer(current_port, &value);
free(current_port); free(current_port);
return enabled; if (!ok) {
return 0;
}
if (value < 0 || value > 0xFFFF) {
return 0;
}
return value;
} }
static bool static bool
wait_tcpip_mode_enabled(struct sc_server *server, const char *serial, wait_tcpip_mode_enabled(struct sc_server *server, const char *serial,
unsigned attempts, sc_tick delay) { uint16_t expected_port, unsigned attempts,
if (is_tcpip_mode_enabled(server, serial)) { sc_tick delay) {
LOGI("TCP/IP mode enabled"); uint16_t adb_port = get_adb_tcp_port(server, serial);
if (adb_port == expected_port) {
return true; return true;
} }
@ -543,28 +558,23 @@ wait_tcpip_mode_enabled(struct sc_server *server, const char *serial,
return false; return false;
} }
if (is_tcpip_mode_enabled(server, serial)) { adb_port = get_adb_tcp_port(server, serial);
LOGI("TCP/IP mode enabled"); if (adb_port == expected_port) {
return true; return true;
} }
} while (--attempts); } while (--attempts);
return false; return false;
} }
char * static char *
append_port_5555(const char *ip) { append_port(const char *ip, uint16_t port) {
size_t len = strlen(ip); char *ip_port;
int ret = asprintf(&ip_port, "%s:%" PRIu16, ip, port);
// sizeof counts the final '\0' if (ret == -1) {
char *ip_port = malloc(len + sizeof(":5555"));
if (!ip_port) {
LOG_OOM(); LOG_OOM();
return NULL; return NULL;
} }
memcpy(ip_port, ip, len);
memcpy(ip_port + len, ":5555", sizeof(":5555"));
return ip_port; return ip_port;
} }
@ -582,34 +592,36 @@ sc_server_switch_to_tcpip(struct sc_server *server, const char *serial) {
return NULL; return NULL;
} }
char *ip_port = append_port_5555(ip); uint16_t adb_port = get_adb_tcp_port(server, serial);
free(ip); if (adb_port) {
if (!ip_port) { LOGI("TCP/IP mode already enabled on port %" PRIu16, adb_port);
return NULL; } else {
} LOGI("Enabling TCP/IP mode on port " SC_STR(SC_ADB_PORT_DEFAULT) "...");
bool tcp_mode = is_tcpip_mode_enabled(server, serial); bool ok = sc_adb_tcpip(intr, serial, SC_ADB_PORT_DEFAULT,
SC_ADB_NO_STDOUT);
if (!tcp_mode) {
bool ok = sc_adb_tcpip(intr, serial, 5555, SC_ADB_NO_STDOUT);
if (!ok) { if (!ok) {
LOGE("Could not restart adbd in TCP/IP mode"); LOGE("Could not restart adbd in TCP/IP mode");
goto error; free(ip);
return NULL;
} }
unsigned attempts = 40; unsigned attempts = 40;
sc_tick delay = SC_TICK_FROM_MS(250); sc_tick delay = SC_TICK_FROM_MS(250);
ok = wait_tcpip_mode_enabled(server, serial, attempts, delay); ok = wait_tcpip_mode_enabled(server, serial, SC_ADB_PORT_DEFAULT,
attempts, delay);
if (!ok) { if (!ok) {
goto error; free(ip);
return NULL;
} }
adb_port = SC_ADB_PORT_DEFAULT;
LOGI("TCP/IP mode enabled on port " SC_STR(SC_ADB_PORT_DEFAULT));
} }
char *ip_port = append_port(ip, adb_port);
free(ip);
return ip_port; return ip_port;
error:
free(ip_port);
return NULL;
} }
static bool static bool
@ -636,7 +648,8 @@ sc_server_configure_tcpip_known_address(struct sc_server *server,
const char *addr) { const char *addr) {
// Append ":5555" if no port is present // Append ":5555" if no port is present
bool contains_port = strchr(addr, ':'); bool contains_port = strchr(addr, ':');
char *ip_port = contains_port ? strdup(addr) : append_port_5555(addr); char *ip_port = contains_port ? strdup(addr)
: append_port(addr, SC_ADB_PORT_DEFAULT);
if (!ip_port) { if (!ip_port) {
LOG_OOM(); LOG_OOM();
return false; return false;
@ -649,7 +662,8 @@ sc_server_configure_tcpip_known_address(struct sc_server *server,
static bool static bool
sc_server_configure_tcpip_unknown_address(struct sc_server *server, sc_server_configure_tcpip_unknown_address(struct sc_server *server,
const char *serial) { const char *serial) {
bool is_already_tcpip = sc_adb_is_serial_tcpip(serial); bool is_already_tcpip =
sc_adb_device_get_type(serial) == SC_ADB_DEVICE_TYPE_TCPIP;
if (is_already_tcpip) { if (is_already_tcpip) {
// Nothing to do // Nothing to do
LOGI("Device already connected via TCP/IP: %s", serial); LOGI("Device already connected via TCP/IP: %s", serial);
@ -707,7 +721,15 @@ run_server(void *data) {
} else if (params->select_tcpip) { } else if (params->select_tcpip) {
selector.type = SC_ADB_DEVICE_SELECT_TCPIP; selector.type = SC_ADB_DEVICE_SELECT_TCPIP;
} else { } else {
selector.type = SC_ADB_DEVICE_SELECT_ALL; // 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; struct sc_adb_device device;
ok = sc_adb_select_device(&server->intr, &selector, 0, &device); ok = sc_adb_select_device(&server->intr, &selector, 0, &device);

View File

@ -47,6 +47,7 @@ struct sc_server_params {
bool select_usb; bool select_usb;
bool select_tcpip; bool select_tcpip;
bool cleanup; bool cleanup;
bool power_on;
}; };
struct sc_server { 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

@ -92,8 +92,14 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
close(in[0]); close(in[0]);
} }
close(in[1]); close(in[1]);
} else {
int devnull = open("/dev/null", O_RDONLY | O_CREAT, 0666);
if (devnull != -1) {
dup2(devnull, STDIN_FILENO);
} else {
LOGE("Could not open /dev/null for stdin");
}
} }
// Do not close stdin in the child process, this makes adb fail on Linux
if (pout) { if (pout) {
if (out[1] != STDOUT_FILENO) { if (out[1] != STDOUT_FILENO) {
@ -102,8 +108,12 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
} }
close(out[0]); close(out[0]);
} else if (!inherit_stdout) { } else if (!inherit_stdout) {
// Close stdout in the child process int devnull = open("/dev/null", O_WRONLY | O_CREAT, 0666);
close(STDOUT_FILENO); if (devnull != -1) {
dup2(devnull, STDOUT_FILENO);
} else {
LOGE("Could not open /dev/null for stdout");
}
} }
if (perr) { if (perr) {
@ -113,8 +123,12 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
} }
close(err[0]); close(err[0]);
} else if (!inherit_stderr) { } else if (!inherit_stderr) {
// Close stderr in the child process int devnull = open("/dev/null", O_WRONLY | O_CREAT, 0666);
close(STDERR_FILENO); if (devnull != -1) {
dup2(devnull, STDERR_FILENO);
} else {
LOGE("Could not open /dev/null for stderr");
}
} }
close(internal[0]); close(internal[0]);

View File

@ -55,6 +55,10 @@ scrcpy_otg(struct scrcpy_options *options) {
const char *serial = options->serial; const char *serial = options->serial;
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) {
LOGW("Could not enable linear filtering");
}
// Minimal SDL initialization // Minimal SDL initialization
if (SDL_Init(SDL_INIT_EVENTS)) { if (SDL_Init(SDL_INIT_EVENTS)) {
LOGE("Could not initialize SDL: %s", SDL_GetError()); LOGE("Could not initialize SDL: %s", SDL_GetError());
@ -162,6 +166,8 @@ scrcpy_otg(struct scrcpy_options *options) {
.always_on_top = options->always_on_top, .always_on_top = options->always_on_top,
.window_x = options->window_x, .window_x = options->window_x,
.window_y = options->window_y, .window_y = options->window_y,
.window_width = options->window_width,
.window_height = options->window_height,
.window_borderless = options->window_borderless, .window_borderless = options->window_borderless,
}; };

View File

@ -69,10 +69,10 @@ sc_screen_otg_init(struct sc_screen_otg *screen,
? params->window_x : (int) SDL_WINDOWPOS_UNDEFINED; ? params->window_x : (int) SDL_WINDOWPOS_UNDEFINED;
int y = params->window_y != SC_WINDOW_POSITION_UNDEFINED int y = params->window_y != SC_WINDOW_POSITION_UNDEFINED
? params->window_y : (int) SDL_WINDOWPOS_UNDEFINED; ? params->window_y : (int) SDL_WINDOWPOS_UNDEFINED;
int width = 256; int width = params->window_width ? params->window_width : 256;
int height = 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) { if (params->always_on_top) {
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP; window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
} }
@ -97,6 +97,11 @@ sc_screen_otg_init(struct sc_screen_otg *screen,
if (icon) { if (icon) {
SDL_SetWindowIcon(screen->window, 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); screen->texture = SDL_CreateTextureFromSurface(screen->renderer, icon);
scrcpy_icon_destroy(icon); scrcpy_icon_destroy(icon);
if (!screen->texture) { if (!screen->texture) {

View File

@ -29,6 +29,8 @@ struct sc_screen_otg_params {
bool always_on_top; bool always_on_top;
int16_t window_x; // accepts SC_WINDOW_POSITION_UNDEFINED int16_t window_x; // accepts SC_WINDOW_POSITION_UNDEFINED
int16_t window_y; // 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; bool window_borderless;
}; };

View File

@ -23,6 +23,11 @@ read_string(libusb_device_handle *handle, uint8_t desc_index) {
// When non-negative, 'result' contains the number of bytes written // When non-negative, 'result' contains the number of bytes written
char *s = malloc(result + 1); char *s = malloc(result + 1);
if (!s) {
LOG_OOM();
return NULL;
}
memcpy(s, buffer, result); memcpy(s, buffer, result);
s[result] = '\0'; s[result] = '\0';
return s; return s;

View File

@ -1,8 +1,9 @@
#ifndef SC_BUFFER_UTIL_H #ifndef SC_BINARY_H
#define SC_BUFFER_UTIL_H #define SC_BINARY_H
#include "common.h" #include "common.h"
#include <assert.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
@ -43,4 +44,33 @@ sc_read64be(const uint8_t *buf) {
return ((uint64_t) msb << 32) | lsb; return ((uint64_t) msb << 32) | lsb;
} }
/**
* Convert a float between 0 and 1 to an unsigned 16-bit fixed-point value
*/
static inline uint16_t
sc_float_to_u16fp(float f) {
assert(f >= 0.0f && f <= 1.0f);
uint32_t u = f * 0x1p16f; // 2^16
if (u >= 0xffff) {
assert(u == 0x10000); // for f == 1.0f
u = 0xffff;
}
return (uint16_t) u;
}
/**
* Convert a float between -1 and 1 to a signed 16-bit fixed-point value
*/
static inline int16_t
sc_float_to_i16fp(float f) {
assert(f >= -1.0f && f <= 1.0f);
int32_t i = f * 0x1p15f; // 2^15
assert(i >= -0x8000);
if (i >= 0x7fff) {
assert(i == 0x8000); // for f == 1.0f
i = 0x7fff;
}
return (int16_t) i;
}
#endif #endif

View File

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

View File

@ -3,11 +3,10 @@
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <stdio.h> #include <stdio.h>
#include <SDL2/SDL_platform.h>
#include "log.h" #include "log.h"
#ifdef __WINDOWS__ #ifdef _WIN32
# include <ws2tcpip.h> # include <ws2tcpip.h>
typedef int socklen_t; typedef int socklen_t;
typedef SOCKET sc_raw_socket; typedef SOCKET sc_raw_socket;
@ -29,7 +28,7 @@
bool bool
net_init(void) { net_init(void) {
#ifdef __WINDOWS__ #ifdef _WIN32
WSADATA wsa; WSADATA wsa;
int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0; int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0;
if (res < 0) { if (res < 0) {
@ -42,14 +41,14 @@ net_init(void) {
void void
net_cleanup(void) { net_cleanup(void) {
#ifdef __WINDOWS__ #ifdef _WIN32
WSACleanup(); WSACleanup();
#endif #endif
} }
static inline sc_socket static inline sc_socket
wrap(sc_raw_socket sock) { wrap(sc_raw_socket sock) {
#ifdef __WINDOWS__ #ifdef _WIN32
if (sock == INVALID_SOCKET) { if (sock == INVALID_SOCKET) {
return SC_SOCKET_NONE; return SC_SOCKET_NONE;
} }
@ -72,7 +71,7 @@ wrap(sc_raw_socket sock) {
static inline sc_raw_socket static inline sc_raw_socket
unwrap(sc_socket socket) { unwrap(sc_socket socket) {
#ifdef __WINDOWS__ #ifdef _WIN32
if (socket == SC_SOCKET_NONE) { if (socket == SC_SOCKET_NONE) {
return INVALID_SOCKET; return INVALID_SOCKET;
} }
@ -160,8 +159,8 @@ net_connect(sc_socket socket, uint32_t addr, uint16_t port) {
} }
bool bool
net_listen(sc_socket socket, uint32_t addr, uint16_t port, int backlog) { net_listen(sc_socket server_socket, uint32_t addr, uint16_t port, int backlog) {
sc_raw_socket raw_sock = unwrap(socket); sc_raw_socket raw_sock = unwrap(server_socket);
int reuse = 1; int reuse = 1;
if (setsockopt(raw_sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse, if (setsockopt(raw_sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse,
@ -248,7 +247,7 @@ net_interrupt(sc_socket socket) {
sc_raw_socket raw_sock = unwrap(socket); sc_raw_socket raw_sock = unwrap(socket);
#ifdef __WINDOWS__ #ifdef _WIN32
if (!atomic_flag_test_and_set(&socket->closed)) { if (!atomic_flag_test_and_set(&socket->closed)) {
return !closesocket(raw_sock); return !closesocket(raw_sock);
} }
@ -262,7 +261,7 @@ bool
net_close(sc_socket socket) { net_close(sc_socket socket) {
sc_raw_socket raw_sock = unwrap(socket); sc_raw_socket raw_sock = unwrap(socket);
#ifdef __WINDOWS__ #ifdef _WIN32
bool ret = true; bool ret = true;
if (!atomic_flag_test_and_set(&socket->closed)) { if (!atomic_flag_test_and_set(&socket->closed)) {
ret = !closesocket(raw_sock); ret = !closesocket(raw_sock);

View File

@ -1,13 +1,12 @@
#ifndef NET_H #ifndef SC_NET_H
#define NET_H #define SC_NET_H
#include "common.h" #include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <SDL2/SDL_platform.h>
#ifdef __WINDOWS__ #ifdef _WIN32
# include <winsock2.h> # include <winsock2.h>
# include <stdatomic.h> # include <stdatomic.h>
@ -17,7 +16,7 @@
atomic_flag closed; atomic_flag closed;
} *sc_socket; } *sc_socket;
#else // not __WINDOWS__ #else // not _WIN32
# include <sys/socket.h> # include <sys/socket.h>
# define SC_SOCKET_NONE -1 # define SC_SOCKET_NONE -1
@ -40,7 +39,7 @@ bool
net_connect(sc_socket socket, uint32_t addr, uint16_t port); net_connect(sc_socket socket, uint32_t addr, uint16_t port);
bool bool
net_listen(sc_socket socket, uint32_t addr, uint16_t port, int backlog); net_listen(sc_socket server_socket, uint32_t addr, uint16_t port, int backlog);
sc_socket sc_socket
net_accept(sc_socket server_socket); net_accept(sc_socket server_socket);

View File

@ -15,14 +15,14 @@ net_connect_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr,
} }
bool bool
net_listen_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr, net_listen_intr(struct sc_intr *intr, sc_socket server_socket, uint32_t addr,
uint16_t port, int backlog) { uint16_t port, int backlog) {
if (!sc_intr_set_socket(intr, socket)) { if (!sc_intr_set_socket(intr, server_socket)) {
// Already interrupted // Already interrupted
return false; return false;
} }
bool ret = net_listen(socket, addr, port, backlog); bool ret = net_listen(server_socket, addr, port, backlog);
sc_intr_set_socket(intr, SC_SOCKET_NONE); sc_intr_set_socket(intr, SC_SOCKET_NONE);
return ret; return ret;

View File

@ -11,7 +11,7 @@ net_connect_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr,
uint16_t port); uint16_t port);
bool bool
net_listen_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr, net_listen_intr(struct sc_intr *intr, sc_socket server_socket, uint32_t addr,
uint16_t port, int backlog); uint16_t port, int backlog);
sc_socket sc_socket

View File

@ -6,6 +6,10 @@
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
/* Stringify a numeric value */
#define SC_STR(s) SC_XSTR(s)
#define SC_XSTR(s) #s
/** /**
* Like strncpy(), except: * Like strncpy(), except:
* - it copies at most n-1 chars * - it copies at most n-1 chars

View File

@ -1,6 +1,7 @@
#include "thread.h" #include "thread.h"
#include <assert.h> #include <assert.h>
#include <string.h>
#include <SDL2/SDL_thread.h> #include <SDL2/SDL_thread.h>
#include "log.h" #include "log.h"

View File

@ -6,6 +6,7 @@
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
// Adapted from vlc_vector: // Adapted from vlc_vector:
// <https://code.videolan.org/videolan/vlc/-/blob/0857947abaed9c89810cd96353aaa1b7e6ba3b0d/include/vlc_vector.h> // <https://code.videolan.org/videolan/vlc/-/blob/0857947abaed9c89810cd96353aaa1b7e6ba3b0d/include/vlc_vector.h>

View File

@ -5,7 +5,7 @@
#include "adb/adb_device.h" #include "adb/adb_device.h"
#include "adb/adb_parser.h" #include "adb/adb_parser.h"
static void test_adb_devices() { static void test_adb_devices(void) {
char output[] = char output[] =
"List of devices attached\n" "List of devices attached\n"
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel " "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
@ -31,7 +31,7 @@ static void test_adb_devices() {
sc_adb_devices_destroy(&vec); sc_adb_devices_destroy(&vec);
} }
static void test_adb_devices_cr() { static void test_adb_devices_cr(void) {
char output[] = char output[] =
"List of devices attached\r\n" "List of devices attached\r\n"
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel " "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
@ -57,7 +57,7 @@ static void test_adb_devices_cr() {
sc_adb_devices_destroy(&vec); sc_adb_devices_destroy(&vec);
} }
static void test_adb_devices_daemon_start() { static void test_adb_devices_daemon_start(void) {
char output[] = char output[] =
"* daemon not running; starting now at tcp:5037\n" "* daemon not running; starting now at tcp:5037\n"
"* daemon started successfully\n" "* daemon started successfully\n"
@ -78,7 +78,7 @@ static void test_adb_devices_daemon_start() {
sc_adb_devices_destroy(&vec); sc_adb_devices_destroy(&vec);
} }
static void test_adb_devices_daemon_start_mixed() { static void test_adb_devices_daemon_start_mixed(void) {
char output[] = char output[] =
"List of devices attached\n" "List of devices attached\n"
"adb server version (41) doesn't match this client (39); killing...\n" "adb server version (41) doesn't match this client (39); killing...\n"
@ -105,7 +105,7 @@ static void test_adb_devices_daemon_start_mixed() {
sc_adb_devices_destroy(&vec); sc_adb_devices_destroy(&vec);
} }
static void test_adb_devices_without_eol() { static void test_adb_devices_without_eol(void) {
char output[] = char output[] =
"List of devices attached\n" "List of devices attached\n"
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel " "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
@ -124,7 +124,7 @@ static void test_adb_devices_without_eol() {
sc_adb_devices_destroy(&vec); sc_adb_devices_destroy(&vec);
} }
static void test_adb_devices_without_header() { static void test_adb_devices_without_header(void) {
char output[] = char output[] =
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel " "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
"device:MyDevice transport_id:1\n"; "device:MyDevice transport_id:1\n";
@ -134,7 +134,7 @@ static void test_adb_devices_without_header() {
assert(!ok); assert(!ok);
} }
static void test_adb_devices_corrupted() { static void test_adb_devices_corrupted(void) {
char output[] = char output[] =
"List of devices attached\n" "List of devices attached\n"
"corrupted_garbage\n"; "corrupted_garbage\n";
@ -145,7 +145,7 @@ static void test_adb_devices_corrupted() {
assert(vec.size == 0); assert(vec.size == 0);
} }
static void test_adb_devices_spaces() { static void test_adb_devices_spaces(void) {
char output[] = char output[] =
"List of devices attached\n" "List of devices attached\n"
"0123456789abcdef unauthorized usb:1-4 transport_id:3\n"; "0123456789abcdef unauthorized usb:1-4 transport_id:3\n";
@ -163,81 +163,81 @@ static void test_adb_devices_spaces() {
sc_adb_devices_destroy(&vec); sc_adb_devices_destroy(&vec);
} }
static void test_get_ip_single_line() { static void test_get_ip_single_line(void) {
char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src "
"192.168.12.34\r\r\n"; "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(ip);
assert(!strcmp(ip, "192.168.12.34")); assert(!strcmp(ip, "192.168.12.34"));
free(ip); 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 " char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src "
"192.168.12.34"; "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(ip);
assert(!strcmp(ip, "192.168.12.34")); assert(!strcmp(ip, "192.168.12.34"));
free(ip); 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 " char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src "
"192.168.12.34 \n"; "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(ip);
assert(!strcmp(ip, "192.168.12.34")); assert(!strcmp(ip, "192.168.12.34"));
free(ip); 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 " char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src "
"192.168.1.2\r\n" "192.168.1.2\r\n"
"10.0.0.0/24 dev rmnet proto kernel scope link src " "10.0.0.0/24 dev rmnet proto kernel scope link src "
"10.0.0.2\r\n"; "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(ip);
assert(!strcmp(ip, "192.168.1.2")); assert(!strcmp(ip, "192.168.1.2"));
free(ip); 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 " char ip_route[] = "10.0.0.0/24 dev rmnet proto kernel scope link src "
"10.0.0.3\r\n" "10.0.0.3\r\n"
"192.168.1.0/24 dev wlan0 proto kernel scope link src " "192.168.1.0/24 dev wlan0 proto kernel scope link src "
"192.168.1.3\r\n"; "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(ip);
assert(!strcmp(ip, "192.168.1.3")); assert(!strcmp(ip, "192.168.1.3"));
free(ip); 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 " char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src "
"192.168.12.34\r\r\n"; "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(!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 " char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src "
"192.168.12.34"; "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(!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 " char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src "
"\n"; "\n";
char *ip = sc_adb_parse_device_ip_from_output(ip_route); char *ip = sc_adb_parse_device_ip(ip_route);
assert(!ip); assert(!ip);
} }
@ -262,4 +262,6 @@ int main(int argc, char *argv[]) {
test_get_ip_no_wlan(); test_get_ip_no_wlan();
test_get_ip_no_wlan_without_eol(); test_get_ip_no_wlan_without_eol();
test_get_ip_truncated(); test_get_ip_truncated();
return 0;
} }

114
app/tests/test_binary.c Normal file
View File

@ -0,0 +1,114 @@
#include "common.h"
#include <assert.h>
#include "util/binary.h"
static void test_write16be(void) {
uint16_t val = 0xABCD;
uint8_t buf[2];
sc_write16be(buf, val);
assert(buf[0] == 0xAB);
assert(buf[1] == 0xCD);
}
static void test_write32be(void) {
uint32_t val = 0xABCD1234;
uint8_t buf[4];
sc_write32be(buf, val);
assert(buf[0] == 0xAB);
assert(buf[1] == 0xCD);
assert(buf[2] == 0x12);
assert(buf[3] == 0x34);
}
static void test_write64be(void) {
uint64_t val = 0xABCD1234567890EF;
uint8_t buf[8];
sc_write64be(buf, val);
assert(buf[0] == 0xAB);
assert(buf[1] == 0xCD);
assert(buf[2] == 0x12);
assert(buf[3] == 0x34);
assert(buf[4] == 0x56);
assert(buf[5] == 0x78);
assert(buf[6] == 0x90);
assert(buf[7] == 0xEF);
}
static void test_read16be(void) {
uint8_t buf[2] = {0xAB, 0xCD};
uint16_t val = sc_read16be(buf);
assert(val == 0xABCD);
}
static void test_read32be(void) {
uint8_t buf[4] = {0xAB, 0xCD, 0x12, 0x34};
uint32_t val = sc_read32be(buf);
assert(val == 0xABCD1234);
}
static void test_read64be(void) {
uint8_t buf[8] = {0xAB, 0xCD, 0x12, 0x34,
0x56, 0x78, 0x90, 0xEF};
uint64_t val = sc_read64be(buf);
assert(val == 0xABCD1234567890EF);
}
static void test_float_to_u16fp(void) {
assert(sc_float_to_u16fp(0.0f) == 0);
assert(sc_float_to_u16fp(0.03125f) == 0x800);
assert(sc_float_to_u16fp(0.0625f) == 0x1000);
assert(sc_float_to_u16fp(0.125f) == 0x2000);
assert(sc_float_to_u16fp(0.25f) == 0x4000);
assert(sc_float_to_u16fp(0.5f) == 0x8000);
assert(sc_float_to_u16fp(0.75f) == 0xc000);
assert(sc_float_to_u16fp(1.0f) == 0xffff);
}
static void test_float_to_i16fp(void) {
assert(sc_float_to_i16fp(0.0f) == 0);
assert(sc_float_to_i16fp(0.03125f) == 0x400);
assert(sc_float_to_i16fp(0.0625f) == 0x800);
assert(sc_float_to_i16fp(0.125f) == 0x1000);
assert(sc_float_to_i16fp(0.25f) == 0x2000);
assert(sc_float_to_i16fp(0.5f) == 0x4000);
assert(sc_float_to_i16fp(0.75f) == 0x6000);
assert(sc_float_to_i16fp(1.0f) == 0x7fff);
assert(sc_float_to_i16fp(-0.03125f) == -0x400);
assert(sc_float_to_i16fp(-0.0625f) == -0x800);
assert(sc_float_to_i16fp(-0.125f) == -0x1000);
assert(sc_float_to_i16fp(-0.25f) == -0x2000);
assert(sc_float_to_i16fp(-0.5f) == -0x4000);
assert(sc_float_to_i16fp(-0.75f) == -0x6000);
assert(sc_float_to_i16fp(-1.0f) == -0x8000);
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_write16be();
test_write32be();
test_write64be();
test_read16be();
test_read32be();
test_read64be();
test_float_to_u16fp();
test_float_to_i16fp();
return 0;
}

View File

@ -1,81 +0,0 @@
#include "common.h"
#include <assert.h>
#include "util/buffer_util.h"
static void test_buffer_write16be(void) {
uint16_t val = 0xABCD;
uint8_t buf[2];
sc_write16be(buf, val);
assert(buf[0] == 0xAB);
assert(buf[1] == 0xCD);
}
static void test_buffer_write32be(void) {
uint32_t val = 0xABCD1234;
uint8_t buf[4];
sc_write32be(buf, val);
assert(buf[0] == 0xAB);
assert(buf[1] == 0xCD);
assert(buf[2] == 0x12);
assert(buf[3] == 0x34);
}
static void test_buffer_write64be(void) {
uint64_t val = 0xABCD1234567890EF;
uint8_t buf[8];
sc_write64be(buf, val);
assert(buf[0] == 0xAB);
assert(buf[1] == 0xCD);
assert(buf[2] == 0x12);
assert(buf[3] == 0x34);
assert(buf[4] == 0x56);
assert(buf[5] == 0x78);
assert(buf[6] == 0x90);
assert(buf[7] == 0xEF);
}
static void test_buffer_read16be(void) {
uint8_t buf[2] = {0xAB, 0xCD};
uint16_t val = sc_read16be(buf);
assert(val == 0xABCD);
}
static void test_buffer_read32be(void) {
uint8_t buf[4] = {0xAB, 0xCD, 0x12, 0x34};
uint32_t val = sc_read32be(buf);
assert(val == 0xABCD1234);
}
static void test_buffer_read64be(void) {
uint8_t buf[8] = {0xAB, 0xCD, 0x12, 0x34,
0x56, 0x78, 0x90, 0xEF};
uint64_t val = sc_read64be(buf);
assert(val == 0xABCD1234567890EF);
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_buffer_write16be();
test_buffer_write32be();
test_buffer_write64be();
test_buffer_read16be();
test_buffer_read32be();
test_buffer_read64be();
return 0;
}

View File

@ -132,14 +132,14 @@ static void test_serialize_inject_scroll_event(void) {
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf); size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 25); assert(size == 21);
const unsigned char expected[] = { const unsigned char expected[] = {
SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026
0x04, 0x38, 0x07, 0x80, // 1080 1920 0x04, 0x38, 0x07, 0x80, // 1080 1920
0x00, 0x00, 0x00, 0x01, // 1 0x7F, 0xFF, // 1 (float encoded as i16)
0xFF, 0xFF, 0xFF, 0xFF, // -1 0x80, 0x00, // -1 (float encoded as i16)
0x00, 0x00, 0x00, 0x01, // 1 0x00, 0x00, 0x00, 0x01, // 1
}; };
assert(!memcmp(buf, expected, sizeof(expected))); assert(!memcmp(buf, expected, sizeof(expected)));

View File

@ -4,10 +4,10 @@ buildscript {
repositories { repositories {
google() google()
jcenter() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.0.3' classpath 'com.android.tools.build:gradle:7.2.2'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
@ -17,7 +17,7 @@ buildscript {
allprojects { allprojects {
repositories { repositories {
google() google()
jcenter() mavenCentral()
} }
tasks.withType(JavaCompile) { tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint:deprecation" options.compilerArgs << "-Xlint:deprecation"

View File

@ -20,6 +20,6 @@ ffmpeg_avcodec = 'avcodec-58'
ffmpeg_avformat = 'avformat-58' ffmpeg_avformat = 'avformat-58'
ffmpeg_avutil = 'avutil-56' ffmpeg_avutil = 'avutil-56'
prebuilt_ffmpeg = 'ffmpeg-win32-4.3.1' 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.25' prebuilt_libusb_root = 'libusb-1.0.26'
prebuilt_libusb = prebuilt_libusb_root + '/MinGW32' prebuilt_libusb = 'libusb-1.0.26/MinGW-Win32'

View File

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

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@ -2,8 +2,8 @@
set -e set -e
BUILDDIR=build-auto BUILDDIR=build-auto
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.23/scrcpy-server-v1.23 PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.24/scrcpy-server-v1.24
PREBUILT_SERVER_SHA256=2a913fd47478c0b306fca507cb0beb625e49a19ff9fc7ab904e36ef5b9fe7e68 PREBUILT_SERVER_SHA256=ae74a81ea79c0dc7250e586627c278c0a9a8c5de46c9fb5c38c167fb1a36f056
echo "[scrcpy] Downloading prebuilt server..." echo "[scrcpy] Downloading prebuilt server..."
wget "$PREBUILT_SERVER_URL" -O scrcpy-server wget "$PREBUILT_SERVER_URL" -O scrcpy-server

View File

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

View File

@ -75,12 +75,10 @@ prepare-deps-win64:
@app/prebuilt-deps/prepare-libusb.sh @app/prebuilt-deps/prepare-libusb.sh
build-win32: prepare-deps-win32 build-win32: prepare-deps-win32
# -Dusb=false because of libusb-win32 build issue, cf #3011
[ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \ [ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \
meson "$(WIN32_BUILD_DIR)" \ meson "$(WIN32_BUILD_DIR)" \
--cross-file cross_win32.txt \ --cross-file cross_win32.txt \
--buildtype release --strip -Db_lto=true \ --buildtype release --strip -Db_lto=true \
-Dusb=false \
-Dcompile_server=false \ -Dcompile_server=false \
-Dportable=true ) -Dportable=true )
ninja -C "$(WIN32_BUILD_DIR)" ninja -C "$(WIN32_BUILD_DIR)"
@ -107,11 +105,11 @@ dist-win32: build-server build-win32
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/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/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/ffmpeg-win32-4.3.1/bin/swscale-5.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-31.0.3/adb.exe "$(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-31.0.3/AdbWinApi.dll "$(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-31.0.3/AdbWinUsbApi.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.20/i686-w64-mingw32/bin/SDL2.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.25/MinGW32/dll/libusb-1.0.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 dist-win64: build-server build-win64
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)" mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
@ -121,16 +119,16 @@ dist-win64: build-server build-win64
cp app/data/scrcpy-noconsole.vbs "$(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/icon.png "$(DIST)/$(WIN64_TARGET_DIR)"
cp app/data/open_a_terminal_here.bat "$(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/bin/avutil-57.dll "$(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/bin/avcodec-59.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/bin/avformat-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/bin/swresample-4.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/bin/swscale-6.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-31.0.3/adb.exe "$(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-31.0.3/AdbWinApi.dll "$(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-31.0.3/AdbWinUsbApi.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.20/x86_64-w64-mingw32/bin/SDL2.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.25/MinGW64/dll/libusb-1.0.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 zip-win32: dist-win32
cd "$(DIST)/$(WIN32_TARGET_DIR)"; \ cd "$(DIST)/$(WIN32_TARGET_DIR)"; \

2
run
View File

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

View File

@ -1,13 +1,13 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
compileSdkVersion 31 compileSdkVersion 33
defaultConfig { defaultConfig {
applicationId "com.genymobile.scrcpy" applicationId "com.genymobile.scrcpy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 31 targetSdkVersion 33
versionCode 12300 versionCode 12400
versionName "1.23" versionName "1.24"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
@ -19,7 +19,6 @@ android {
} }
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation 'junit:junit:4.13.1' testImplementation 'junit:junit:4.13.1'
} }

View File

@ -12,10 +12,11 @@
set -e set -e
SCRCPY_DEBUG=false SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=1.23 SCRCPY_VERSION_NAME=1.24
PLATFORM=${ANDROID_PLATFORM:-31} PLATFORM=${ANDROID_PLATFORM:-33}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-31.0.0} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-33.0.0}
BUILD_TOOLS_DIR="$ANDROID_HOME/build-tools/$BUILD_TOOLS"
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})" BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
CLASSES_DIR="$BUILD_DIR/classes" CLASSES_DIR="$BUILD_DIR/classes"
@ -41,9 +42,8 @@ EOF
echo "Generating java from aidl..." echo "Generating java from aidl..."
cd "$SERVER_DIR/src/main/aidl" cd "$SERVER_DIR/src/main/aidl"
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o"$CLASSES_DIR" \ "$BUILD_TOOLS_DIR/aidl" -o"$CLASSES_DIR" android/view/IRotationWatcher.aidl
android/view/IRotationWatcher.aidl "$BUILD_TOOLS_DIR/aidl" -o"$CLASSES_DIR" \
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o"$CLASSES_DIR" \
android/content/IOnPrimaryClipChangedListener.aidl android/content/IOnPrimaryClipChangedListener.aidl
echo "Compiling java sources..." echo "Compiling java sources..."
@ -59,8 +59,7 @@ cd "$CLASSES_DIR"
if [[ $PLATFORM -lt 31 ]] if [[ $PLATFORM -lt 31 ]]
then then
# use dx # use dx
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \ "$BUILD_TOOLS_DIR/dx" --dex --output "$BUILD_DIR/classes.dex" \
--output "$BUILD_DIR/classes.dex" \
android/view/*.class \ android/view/*.class \
android/content/*.class \ android/content/*.class \
com/genymobile/scrcpy/*.class \ com/genymobile/scrcpy/*.class \
@ -72,7 +71,7 @@ then
rm -rf classes.dex classes rm -rf classes.dex classes
else else
# use d8 # use d8
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/d8" --classpath "$ANDROID_JAR" \ "$BUILD_TOOLS_DIR/d8" --classpath "$ANDROID_JAR" \
--output "$BUILD_DIR/classes.zip" \ --output "$BUILD_DIR/classes.zip" \
android/view/*.class \ android/view/*.class \
android/content/*.class \ android/content/*.class \

View File

@ -13,8 +13,8 @@ if prebuilt_server == ''
install_dir: 'share/scrcpy') install_dir: 'share/scrcpy')
else else
if not prebuilt_server.startswith('/') if not prebuilt_server.startswith('/')
# relative path needs some trick # prebuilt server path is relative to the root scrcpy directory
prebuilt_server = meson.source_root() + '/' + prebuilt_server prebuilt_server = '../' + prebuilt_server
endif endif
custom_target('scrcpy-server-prebuilt', custom_target('scrcpy-server-prebuilt',
input: prebuilt_server, input: prebuilt_server,

View File

@ -0,0 +1,38 @@
package com.genymobile.scrcpy;
public final class Binary {
private Binary() {
// not instantiable
}
public static int toUnsigned(short value) {
return value & 0xffff;
}
public static int toUnsigned(byte value) {
return value & 0xff;
}
/**
* Convert unsigned 16-bit fixed-point to a float between 0 and 1
*
* @param value encoded value
* @return Float value between 0 and 1
*/
public static float u16FixedPointToFloat(short value) {
int unsignedShort = Binary.toUnsigned(value);
// 0x1p16f is 2^16 as float
return unsignedShort == 0xffff ? 1f : (unsignedShort / 0x1p16f);
}
/**
* Convert signed 16-bit fixed-point to a float between -1 and 1
*
* @param value encoded value
* @return Float value between -1 and 1
*/
public static float i16FixedPointToFloat(short value) {
// 0x1p15f is 2^15 as float
return value == 0x7fff ? 1f : (value / 0x1p15f);
}
}

View File

@ -1,7 +1,5 @@
package com.genymobile.scrcpy; package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.util.Base64; import android.util.Base64;
@ -164,12 +162,10 @@ public final class CleanUp {
Config config = Config.fromBase64(args[0]); Config config = Config.fromBase64(args[0]);
if (config.disableShowTouches || config.restoreStayOn != -1) { if (config.disableShowTouches || config.restoreStayOn != -1) {
ServiceManager serviceManager = new ServiceManager();
Settings settings = new Settings(serviceManager);
if (config.disableShowTouches) { if (config.disableShowTouches) {
Ln.i("Disabling \"show touches\""); Ln.i("Disabling \"show touches\"");
try { try {
settings.putValue(Settings.TABLE_SYSTEM, "show_touches", "0"); Settings.putValue(Settings.TABLE_SYSTEM, "show_touches", "0");
} catch (SettingsException e) { } catch (SettingsException e) {
Ln.e("Could not restore \"show_touches\"", e); Ln.e("Could not restore \"show_touches\"", e);
} }
@ -177,7 +173,7 @@ public final class CleanUp {
if (config.restoreStayOn != -1) { if (config.restoreStayOn != -1) {
Ln.i("Restoring \"stay awake\""); Ln.i("Restoring \"stay awake\"");
try { try {
settings.putValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn)); Settings.putValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn));
} catch (SettingsException e) { } catch (SettingsException e) {
Ln.e("Could not restore \"stay_on_while_plugged_in\"", e); Ln.e("Could not restore \"stay_on_while_plugged_in\"", e);
} }

View File

@ -30,4 +30,14 @@ public final class Command {
} }
return result; return result;
} }
public static String execReadOutput(String... cmd) throws IOException, InterruptedException {
Process process = Runtime.getRuntime().exec(cmd);
String output = IO.toString(process.getInputStream());
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IOException("Command " + Arrays.toString(cmd) + " returned with value " + exitCode);
}
return output;
}
} }

View File

@ -33,8 +33,8 @@ public final class ControlMessage {
private long pointerId; private long pointerId;
private float pressure; private float pressure;
private Position position; private Position position;
private int hScroll; private float hScroll;
private int vScroll; private float vScroll;
private int copyKey; private int copyKey;
private boolean paste; private boolean paste;
private int repeat; private int repeat;
@ -71,7 +71,7 @@ public final class ControlMessage {
return msg; return msg;
} }
public static ControlMessage createInjectScrollEvent(Position position, int hScroll, int vScroll, int buttons) { public static ControlMessage createInjectScrollEvent(Position position, float hScroll, float vScroll, int buttons) {
ControlMessage msg = new ControlMessage(); ControlMessage msg = new ControlMessage();
msg.type = TYPE_INJECT_SCROLL_EVENT; msg.type = TYPE_INJECT_SCROLL_EVENT;
msg.position = position; msg.position = position;
@ -156,11 +156,11 @@ public final class ControlMessage {
return position; return position;
} }
public int getHScroll() { public float getHScroll() {
return hScroll; return hScroll;
} }
public int getVScroll() { public float getVScroll() {
return vScroll; return vScroll;
} }

View File

@ -10,7 +10,7 @@ public class ControlMessageReader {
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13; static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13;
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27; static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 24; static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
static final int BACK_OR_SCREEN_ON_LENGTH = 1; static final int BACK_OR_SCREEN_ON_LENGTH = 1;
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
static final int GET_CLIPBOARD_LENGTH = 1; static final int GET_CLIPBOARD_LENGTH = 1;
@ -103,7 +103,7 @@ public class ControlMessageReader {
if (buffer.remaining() < INJECT_KEYCODE_PAYLOAD_LENGTH) { if (buffer.remaining() < INJECT_KEYCODE_PAYLOAD_LENGTH) {
return null; return null;
} }
int action = toUnsigned(buffer.get()); int action = Binary.toUnsigned(buffer.get());
int keycode = buffer.getInt(); int keycode = buffer.getInt();
int repeat = buffer.getInt(); int repeat = buffer.getInt();
int metaState = buffer.getInt(); int metaState = buffer.getInt();
@ -136,13 +136,10 @@ public class ControlMessageReader {
if (buffer.remaining() < INJECT_TOUCH_EVENT_PAYLOAD_LENGTH) { if (buffer.remaining() < INJECT_TOUCH_EVENT_PAYLOAD_LENGTH) {
return null; return null;
} }
int action = toUnsigned(buffer.get()); int action = Binary.toUnsigned(buffer.get());
long pointerId = buffer.getLong(); long pointerId = buffer.getLong();
Position position = readPosition(buffer); Position position = readPosition(buffer);
// 16 bits fixed-point float pressure = Binary.u16FixedPointToFloat(buffer.getShort());
int pressureInt = toUnsigned(buffer.getShort());
// convert it to a float between 0 and 1 (0x1p16f is 2^16 as float)
float pressure = pressureInt == 0xffff ? 1f : (pressureInt / 0x1p16f);
int buttons = buffer.getInt(); int buttons = buffer.getInt();
return ControlMessage.createInjectTouchEvent(action, pointerId, position, pressure, buttons); return ControlMessage.createInjectTouchEvent(action, pointerId, position, pressure, buttons);
} }
@ -152,8 +149,8 @@ public class ControlMessageReader {
return null; return null;
} }
Position position = readPosition(buffer); Position position = readPosition(buffer);
int hScroll = buffer.getInt(); float hScroll = Binary.i16FixedPointToFloat(buffer.getShort());
int vScroll = buffer.getInt(); float vScroll = Binary.i16FixedPointToFloat(buffer.getShort());
int buttons = buffer.getInt(); int buttons = buffer.getInt();
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll, buttons); return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll, buttons);
} }
@ -162,7 +159,7 @@ public class ControlMessageReader {
if (buffer.remaining() < BACK_OR_SCREEN_ON_LENGTH) { if (buffer.remaining() < BACK_OR_SCREEN_ON_LENGTH) {
return null; return null;
} }
int action = toUnsigned(buffer.get()); int action = Binary.toUnsigned(buffer.get());
return ControlMessage.createBackOrScreenOn(action); return ControlMessage.createBackOrScreenOn(action);
} }
@ -170,7 +167,7 @@ public class ControlMessageReader {
if (buffer.remaining() < GET_CLIPBOARD_LENGTH) { if (buffer.remaining() < GET_CLIPBOARD_LENGTH) {
return null; return null;
} }
int copyKey = toUnsigned(buffer.get()); int copyKey = Binary.toUnsigned(buffer.get());
return ControlMessage.createGetClipboard(copyKey); return ControlMessage.createGetClipboard(copyKey);
} }
@ -198,16 +195,8 @@ public class ControlMessageReader {
private static Position readPosition(ByteBuffer buffer) { private static Position readPosition(ByteBuffer buffer) {
int x = buffer.getInt(); int x = buffer.getInt();
int y = buffer.getInt(); int y = buffer.getInt();
int screenWidth = toUnsigned(buffer.getShort()); int screenWidth = Binary.toUnsigned(buffer.getShort());
int screenHeight = toUnsigned(buffer.getShort()); int screenHeight = Binary.toUnsigned(buffer.getShort());
return new Position(x, y, screenWidth, screenHeight); return new Position(x, y, screenWidth, screenHeight);
} }
private static int toUnsigned(short value) {
return value & 0xffff;
}
private static int toUnsigned(byte value) {
return value & 0xff;
}
} }

View File

@ -22,6 +22,7 @@ public class Controller {
private final DesktopConnection connection; private final DesktopConnection connection;
private final DeviceMessageSender sender; private final DeviceMessageSender sender;
private final boolean clipboardAutosync; private final boolean clipboardAutosync;
private final boolean powerOn;
private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
@ -32,10 +33,11 @@ public class Controller {
private boolean keepPowerModeOff; 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.device = device;
this.connection = connection; this.connection = connection;
this.clipboardAutosync = clipboardAutosync; this.clipboardAutosync = clipboardAutosync;
this.powerOn = powerOn;
initPointers(); initPointers();
sender = new DeviceMessageSender(connection); sender = new DeviceMessageSender(connection);
} }
@ -56,7 +58,7 @@ public class Controller {
public void control() throws IOException { public void control() throws IOException {
// on start, power on the device // on start, power on the device
if (!Device.isScreenOn()) { if (powerOn && !Device.isScreenOn()) {
device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC); device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC);
// dirty hack // dirty hack
@ -221,7 +223,7 @@ public class Controller {
return device.injectEvent(event, Device.INJECT_MODE_ASYNC); return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
} }
private boolean injectScroll(Position position, int hScroll, int vScroll, int buttons) { private boolean injectScroll(Position position, float hScroll, float vScroll, int buttons) {
long now = SystemClock.uptimeMillis(); long now = SystemClock.uptimeMillis();
Point point = device.getPhysicalPoint(position); Point point = device.getPhysicalPoint(position);
if (point == null) { if (point == null) {

View File

@ -31,9 +31,6 @@ public final class Device {
public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1; public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1;
public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2; public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2;
private static final ServiceManager SERVICE_MANAGER = new ServiceManager();
private static final Settings SETTINGS = new Settings(SERVICE_MANAGER);
public interface RotationListener { public interface RotationListener {
void onRotationChanged(int rotation); void onRotationChanged(int rotation);
} }
@ -66,9 +63,9 @@ public final class Device {
public Device(Options options) { public Device(Options options) {
displayId = options.getDisplayId(); displayId = options.getDisplayId();
DisplayInfo displayInfo = SERVICE_MANAGER.getDisplayManager().getDisplayInfo(displayId); DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId);
if (displayInfo == null) { if (displayInfo == null) {
int[] displayIds = SERVICE_MANAGER.getDisplayManager().getDisplayIds(); int[] displayIds = ServiceManager.getDisplayManager().getDisplayIds();
throw new InvalidDisplayIdException(displayId, displayIds); throw new InvalidDisplayIdException(displayId, displayIds);
} }
@ -82,7 +79,7 @@ public final class Device {
screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation); screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation);
layerStack = displayInfo.getLayerStack(); layerStack = displayInfo.getLayerStack();
SERVICE_MANAGER.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() { ServiceManager.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() {
@Override @Override
public void onRotationChanged(int rotation) { public void onRotationChanged(int rotation) {
synchronized (Device.this) { synchronized (Device.this) {
@ -98,7 +95,7 @@ public final class Device {
if (options.getControl() && options.getClipboardAutosync()) { if (options.getControl() && options.getClipboardAutosync()) {
// If control and autosync are enabled, synchronize Android clipboard to the computer automatically // If control and autosync are enabled, synchronize Android clipboard to the computer automatically
ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager(); ClipboardManager clipboardManager = ServiceManager.getClipboardManager();
if (clipboardManager != null) { if (clipboardManager != null) {
clipboardManager.addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() { clipboardManager.addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() {
@Override @Override
@ -192,7 +189,7 @@ public final class Device {
return false; return false;
} }
return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, injectMode); return ServiceManager.getInputManager().injectInputEvent(inputEvent, injectMode);
} }
public boolean injectEvent(InputEvent event, int injectMode) { public boolean injectEvent(InputEvent event, int injectMode) {
@ -220,7 +217,7 @@ public final class Device {
} }
public static boolean isScreenOn() { public static boolean isScreenOn() {
return SERVICE_MANAGER.getPowerManager().isScreenOn(); return ServiceManager.getPowerManager().isScreenOn();
} }
public synchronized void setRotationListener(RotationListener rotationListener) { public synchronized void setRotationListener(RotationListener rotationListener) {
@ -232,19 +229,19 @@ public final class Device {
} }
public static void expandNotificationPanel() { public static void expandNotificationPanel() {
SERVICE_MANAGER.getStatusBarManager().expandNotificationsPanel(); ServiceManager.getStatusBarManager().expandNotificationsPanel();
} }
public static void expandSettingsPanel() { public static void expandSettingsPanel() {
SERVICE_MANAGER.getStatusBarManager().expandSettingsPanel(); ServiceManager.getStatusBarManager().expandSettingsPanel();
} }
public static void collapsePanels() { public static void collapsePanels() {
SERVICE_MANAGER.getStatusBarManager().collapsePanels(); ServiceManager.getStatusBarManager().collapsePanels();
} }
public static String getClipboardText() { public static String getClipboardText() {
ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager(); ClipboardManager clipboardManager = ServiceManager.getClipboardManager();
if (clipboardManager == null) { if (clipboardManager == null) {
return null; return null;
} }
@ -256,7 +253,7 @@ public final class Device {
} }
public boolean setClipboardText(String text) { public boolean setClipboardText(String text) {
ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager(); ClipboardManager clipboardManager = ServiceManager.getClipboardManager();
if (clipboardManager == null) { if (clipboardManager == null) {
return false; return false;
} }
@ -299,7 +296,7 @@ public final class Device {
* Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled). * Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled).
*/ */
public static void rotateDevice() { public static void rotateDevice() {
WindowManager wm = SERVICE_MANAGER.getWindowManager(); WindowManager wm = ServiceManager.getWindowManager();
boolean accelerometerRotation = !wm.isRotationFrozen(); boolean accelerometerRotation = !wm.isRotationFrozen();
@ -315,8 +312,4 @@ public final class Device {
wm.thawRotation(); wm.thawRotation();
} }
} }
public static Settings getSettings() {
return SETTINGS;
}
} }

View File

@ -6,7 +6,9 @@ import android.system.OsConstants;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Scanner;
public final class IO { public final class IO {
private IO() { private IO() {
@ -37,4 +39,13 @@ public final class IO {
public static void writeFully(FileDescriptor fd, byte[] buffer, int offset, int len) throws IOException { public static void writeFully(FileDescriptor fd, byte[] buffer, int offset, int len) throws IOException {
writeFully(fd, ByteBuffer.wrap(buffer, offset, len)); writeFully(fd, ByteBuffer.wrap(buffer, offset, len));
} }
public static String toString(InputStream inputStream) {
StringBuilder builder = new StringBuilder();
Scanner scanner = new Scanner(inputStream);
while (scanner.hasNextLine()) {
builder.append(scanner.nextLine()).append('\n');
}
return builder.toString();
}
} }

View File

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

View File

@ -20,10 +20,9 @@ public final class Server {
int restoreStayOn = -1; int restoreStayOn = -1;
boolean restoreNormalPowerMode = options.getControl(); // only restore power mode if control is enabled boolean restoreNormalPowerMode = options.getControl(); // only restore power mode if control is enabled
if (options.getShowTouches() || options.getStayAwake()) { if (options.getShowTouches() || options.getStayAwake()) {
Settings settings = Device.getSettings();
if (options.getShowTouches()) { if (options.getShowTouches()) {
try { try {
String oldValue = settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1"); String oldValue = Settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1");
// If "show touches" was disabled, it must be disabled back on clean up // If "show touches" was disabled, it must be disabled back on clean up
mustDisableShowTouchesOnCleanUp = !"1".equals(oldValue); mustDisableShowTouchesOnCleanUp = !"1".equals(oldValue);
} catch (SettingsException e) { } catch (SettingsException e) {
@ -34,7 +33,7 @@ public final class Server {
if (options.getStayAwake()) { if (options.getStayAwake()) {
int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS; int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS;
try { try {
String oldValue = settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn)); String oldValue = Settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn));
try { try {
restoreStayOn = Integer.parseInt(oldValue); restoreStayOn = Integer.parseInt(oldValue);
if (restoreStayOn == stayOn) { if (restoreStayOn == stayOn) {
@ -82,7 +81,7 @@ public final class Server {
Thread controllerThread = null; Thread controllerThread = null;
Thread deviceMessageSenderThread = null; Thread deviceMessageSenderThread = null;
if (control) { if (control) {
final Controller controller = new Controller(device, connection, options.getClipboardAutosync()); final Controller controller = new Controller(device, connection, options.getClipboardAutosync(), options.getPowerOn());
// asynchronous // asynchronous
controllerThread = startController(controller); controllerThread = startController(controller);
@ -248,6 +247,10 @@ public final class Server {
boolean cleanup = Boolean.parseBoolean(value); boolean cleanup = Boolean.parseBoolean(value);
options.setCleanup(cleanup); options.setCleanup(cleanup);
break; break;
case "power_on":
boolean powerOn = Boolean.parseBoolean(value);
options.setPowerOn(powerOn);
break;
case "send_device_meta": case "send_device_meta":
boolean sendDeviceMeta = Boolean.parseBoolean(value); boolean sendDeviceMeta = Boolean.parseBoolean(value);
options.setSendDeviceMeta(sendDeviceMeta); options.setSendDeviceMeta(sendDeviceMeta);

View File

@ -7,16 +7,14 @@ import android.os.Build;
import java.io.IOException; import java.io.IOException;
public class Settings { public final class Settings {
public static final String TABLE_SYSTEM = ContentProvider.TABLE_SYSTEM; public static final String TABLE_SYSTEM = ContentProvider.TABLE_SYSTEM;
public static final String TABLE_SECURE = ContentProvider.TABLE_SECURE; public static final String TABLE_SECURE = ContentProvider.TABLE_SECURE;
public static final String TABLE_GLOBAL = ContentProvider.TABLE_GLOBAL; public static final String TABLE_GLOBAL = ContentProvider.TABLE_GLOBAL;
private final ServiceManager serviceManager; private Settings() {
/* not instantiable */
public Settings(ServiceManager serviceManager) {
this.serviceManager = serviceManager;
} }
private static void execSettingsPut(String table, String key, String value) throws SettingsException { private static void execSettingsPut(String table, String key, String value) throws SettingsException {
@ -35,10 +33,10 @@ public class Settings {
} }
} }
public String getValue(String table, String key) throws SettingsException { public static String getValue(String table, String key) throws SettingsException {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
// on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788> // on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) { try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) {
return provider.getValue(table, key); return provider.getValue(table, key);
} catch (SettingsException e) { } catch (SettingsException e) {
Ln.w("Could not get settings value via ContentProvider, fallback to settings process", e); Ln.w("Could not get settings value via ContentProvider, fallback to settings process", e);
@ -48,10 +46,10 @@ public class Settings {
return execSettingsGet(table, key); return execSettingsGet(table, key);
} }
public void putValue(String table, String key, String value) throws SettingsException { public static void putValue(String table, String key, String value) throws SettingsException {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
// on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788> // on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) { try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) {
provider.putValue(table, key, value); provider.putValue(table, key, value);
} catch (SettingsException e) { } catch (SettingsException e) {
Ln.w("Could not put settings value via ContentProvider, fallback to settings process", e); Ln.w("Could not put settings value via ContentProvider, fallback to settings process", e);
@ -61,10 +59,10 @@ public class Settings {
execSettingsPut(table, key, value); execSettingsPut(table, key, value);
} }
public String getAndPutValue(String table, String key, String value) throws SettingsException { public static String getAndPutValue(String table, String key, String value) throws SettingsException {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
// on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788> // on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) { try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) {
String oldValue = provider.getValue(table, key); String oldValue = provider.getValue(table, key);
if (!value.equals(oldValue)) { if (!value.equals(oldValue)) {
provider.putValue(table, key, value); provider.putValue(table, key, value);

View File

@ -1,22 +1,78 @@
package com.genymobile.scrcpy.wrappers; package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.Command;
import com.genymobile.scrcpy.DisplayInfo; import com.genymobile.scrcpy.DisplayInfo;
import com.genymobile.scrcpy.Ln;
import com.genymobile.scrcpy.Size; import com.genymobile.scrcpy.Size;
import android.os.IInterface; import android.view.Display;
import java.lang.reflect.Field;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class DisplayManager { public final class DisplayManager {
private final IInterface manager; private final Object manager; // instance of hidden class android.hardware.display.DisplayManagerGlobal
public DisplayManager(IInterface manager) { public DisplayManager(Object manager) {
this.manager = manager; this.manager = manager;
} }
// public to call it from unit tests
public static DisplayInfo parseDisplayInfo(String dumpsysDisplayOutput, int displayId) {
Pattern regex = Pattern.compile(
"^ mOverrideDisplayInfo=DisplayInfo\\{\".*?, displayId " + displayId + ".*?(, FLAG_.*)?, real ([0-9]+) x ([0-9]+).*?, "
+ "rotation ([0-9]+).*?, layerStack ([0-9]+)",
Pattern.MULTILINE);
Matcher m = regex.matcher(dumpsysDisplayOutput);
if (!m.find()) {
return null;
}
int flags = parseDisplayFlags(m.group(1));
int width = Integer.parseInt(m.group(2));
int height = Integer.parseInt(m.group(3));
int rotation = Integer.parseInt(m.group(4));
int layerStack = Integer.parseInt(m.group(5));
return new DisplayInfo(displayId, new Size(width, height), rotation, layerStack, flags);
}
private static DisplayInfo getDisplayInfoFromDumpsysDisplay(int displayId) {
try {
String dumpsysDisplayOutput = Command.execReadOutput("dumpsys", "display");
return parseDisplayInfo(dumpsysDisplayOutput, displayId);
} catch (Exception e) {
Ln.e("Could not get display info from \"dumpsys display\" output", e);
return null;
}
}
private static int parseDisplayFlags(String text) {
Pattern regex = Pattern.compile("FLAG_[A-Z_]+");
if (text == null) {
return 0;
}
int flags = 0;
Matcher m = regex.matcher(text);
while (m.find()) {
String flagString = m.group();
try {
Field filed = Display.class.getDeclaredField(flagString);
flags |= filed.getInt(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
// Silently ignore, some flags reported by "dumpsys display" are @TestApi
}
}
return flags;
}
public DisplayInfo getDisplayInfo(int displayId) { public DisplayInfo getDisplayInfo(int displayId) {
try { try {
Object displayInfo = manager.getClass().getMethod("getDisplayInfo", int.class).invoke(manager, displayId); Object displayInfo = manager.getClass().getMethod("getDisplayInfo", int.class).invoke(manager, displayId);
if (displayInfo == null) { if (displayInfo == null) {
return null; // fallback when displayInfo is null
return getDisplayInfoFromDumpsysDisplay(displayId);
} }
Class<?> cls = displayInfo.getClass(); Class<?> cls = displayInfo.getClass();
// width and height already take the rotation into account // width and height already take the rotation into account

View File

@ -2,7 +2,6 @@ package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.Ln; import com.genymobile.scrcpy.Ln;
import android.os.IInterface;
import android.view.InputEvent; import android.view.InputEvent;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
@ -14,24 +13,18 @@ public final class InputManager {
public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT = 1; public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT = 1;
public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2; public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2;
private final IInterface manager; private final android.hardware.input.InputManager manager;
private Method injectInputEventMethod; private Method injectInputEventMethod;
private boolean alternativeInjectInputEventMethod;
private static Method setDisplayIdMethod; private static Method setDisplayIdMethod;
public InputManager(IInterface manager) { public InputManager(android.hardware.input.InputManager manager) {
this.manager = manager; this.manager = manager;
} }
private Method getInjectInputEventMethod() throws NoSuchMethodException { private Method getInjectInputEventMethod() throws NoSuchMethodException {
if (injectInputEventMethod == null) { if (injectInputEventMethod == null) {
try { injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class);
injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class);
} catch (NoSuchMethodException e) {
injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class, int.class);
alternativeInjectInputEventMethod = true;
}
} }
return injectInputEventMethod; return injectInputEventMethod;
} }
@ -39,10 +32,6 @@ public final class InputManager {
public boolean injectInputEvent(InputEvent inputEvent, int mode) { public boolean injectInputEvent(InputEvent inputEvent, int mode) {
try { try {
Method method = getInjectInputEventMethod(); Method method = getInjectInputEventMethod();
if (alternativeInjectInputEventMethod) {
// See <https://github.com/Genymobile/scrcpy/issues/2250>
return (boolean) method.invoke(manager, inputEvent, mode, 0);
}
return (boolean) method.invoke(manager, inputEvent, mode); return (boolean) method.invoke(manager, inputEvent, mode);
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
Ln.e("Could not invoke method", e); Ln.e("Could not invoke method", e);

View File

@ -4,6 +4,7 @@ import android.annotation.SuppressLint;
import android.os.IBinder; import android.os.IBinder;
import android.os.IInterface; import android.os.IInterface;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@SuppressLint("PrivateApi,DiscouragedPrivateApi") @SuppressLint("PrivateApi,DiscouragedPrivateApi")
@ -12,27 +13,30 @@ public final class ServiceManager {
public static final String PACKAGE_NAME = "com.android.shell"; public static final String PACKAGE_NAME = "com.android.shell";
public static final int USER_ID = 0; public static final int USER_ID = 0;
private final Method getServiceMethod; private static final Method GET_SERVICE_METHOD;
static {
private WindowManager windowManager;
private DisplayManager displayManager;
private InputManager inputManager;
private PowerManager powerManager;
private StatusBarManager statusBarManager;
private ClipboardManager clipboardManager;
private ActivityManager activityManager;
public ServiceManager() {
try { try {
getServiceMethod = Class.forName("android.os.ServiceManager").getDeclaredMethod("getService", String.class); GET_SERVICE_METHOD = Class.forName("android.os.ServiceManager").getDeclaredMethod("getService", String.class);
} catch (Exception e) { } catch (Exception e) {
throw new AssertionError(e); throw new AssertionError(e);
} }
} }
private IInterface getService(String service, String type) { private static WindowManager windowManager;
private static DisplayManager displayManager;
private static InputManager inputManager;
private static PowerManager powerManager;
private static StatusBarManager statusBarManager;
private static ClipboardManager clipboardManager;
private static ActivityManager activityManager;
private ServiceManager() {
/* not instantiable */
}
private static IInterface getService(String service, String type) {
try { try {
IBinder binder = (IBinder) getServiceMethod.invoke(null, service); IBinder binder = (IBinder) GET_SERVICE_METHOD.invoke(null, service);
Method asInterfaceMethod = Class.forName(type + "$Stub").getMethod("asInterface", IBinder.class); Method asInterfaceMethod = Class.forName(type + "$Stub").getMethod("asInterface", IBinder.class);
return (IInterface) asInterfaceMethod.invoke(null, binder); return (IInterface) asInterfaceMethod.invoke(null, binder);
} catch (Exception e) { } catch (Exception e) {
@ -40,42 +44,55 @@ public final class ServiceManager {
} }
} }
public WindowManager getWindowManager() { public static WindowManager getWindowManager() {
if (windowManager == null) { if (windowManager == null) {
windowManager = new WindowManager(getService("window", "android.view.IWindowManager")); windowManager = new WindowManager(getService("window", "android.view.IWindowManager"));
} }
return windowManager; return windowManager;
} }
public DisplayManager getDisplayManager() { public static DisplayManager getDisplayManager() {
if (displayManager == null) { if (displayManager == null) {
displayManager = new DisplayManager(getService("display", "android.hardware.display.IDisplayManager")); try {
Class<?> clazz = Class.forName("android.hardware.display.DisplayManagerGlobal");
Method getInstanceMethod = clazz.getDeclaredMethod("getInstance");
Object dmg = getInstanceMethod.invoke(null);
displayManager = new DisplayManager(dmg);
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new AssertionError(e);
}
} }
return displayManager; return displayManager;
} }
public InputManager getInputManager() { public static InputManager getInputManager() {
if (inputManager == null) { if (inputManager == null) {
inputManager = new InputManager(getService("input", "android.hardware.input.IInputManager")); try {
Method getInstanceMethod = android.hardware.input.InputManager.class.getDeclaredMethod("getInstance");
android.hardware.input.InputManager im = (android.hardware.input.InputManager) getInstanceMethod.invoke(null);
inputManager = new InputManager(im);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new AssertionError(e);
}
} }
return inputManager; return inputManager;
} }
public PowerManager getPowerManager() { public static PowerManager getPowerManager() {
if (powerManager == null) { if (powerManager == null) {
powerManager = new PowerManager(getService("power", "android.os.IPowerManager")); powerManager = new PowerManager(getService("power", "android.os.IPowerManager"));
} }
return powerManager; return powerManager;
} }
public StatusBarManager getStatusBarManager() { public static StatusBarManager getStatusBarManager() {
if (statusBarManager == null) { if (statusBarManager == null) {
statusBarManager = new StatusBarManager(getService("statusbar", "com.android.internal.statusbar.IStatusBarService")); statusBarManager = new StatusBarManager(getService("statusbar", "com.android.internal.statusbar.IStatusBarService"));
} }
return statusBarManager; return statusBarManager;
} }
public ClipboardManager getClipboardManager() { public static ClipboardManager getClipboardManager() {
if (clipboardManager == null) { if (clipboardManager == null) {
IInterface clipboard = getService("clipboard", "android.content.IClipboard"); IInterface clipboard = getService("clipboard", "android.content.IClipboard");
if (clipboard == null) { if (clipboard == null) {
@ -89,7 +106,7 @@ public final class ServiceManager {
return clipboardManager; return clipboardManager;
} }
public ActivityManager getActivityManager() { public static ActivityManager getActivityManager() {
if (activityManager == null) { if (activityManager == null) {
try { try {
// On old Android versions, the ActivityManager is not exposed via AIDL, // On old Android versions, the ActivityManager is not exposed via AIDL,

View File

@ -0,0 +1,42 @@
package com.genymobile.scrcpy;
import org.junit.Assert;
import org.junit.Test;
public class BinaryTest {
@Test
public void testU16FixedPointToFloat() {
final float delta = 0.0f; // on these values, there MUST be no rounding error
Assert.assertEquals(0.0f, Binary.u16FixedPointToFloat((short) 0), delta);
Assert.assertEquals(0.03125f, Binary.u16FixedPointToFloat((short) 0x800), delta);
Assert.assertEquals(0.0625f, Binary.u16FixedPointToFloat((short) 0x1000), delta);
Assert.assertEquals(0.125f, Binary.u16FixedPointToFloat((short) 0x2000), delta);
Assert.assertEquals(0.25f, Binary.u16FixedPointToFloat((short) 0x4000), delta);
Assert.assertEquals(0.5f, Binary.u16FixedPointToFloat((short) 0x8000), delta);
Assert.assertEquals(0.75f, Binary.u16FixedPointToFloat((short) 0xc000), delta);
Assert.assertEquals(1.0f, Binary.u16FixedPointToFloat((short) 0xffff), delta);
}
@Test
public void testI16FixedPointToFloat() {
final float delta = 0.0f; // on these values, there MUST be no rounding error
Assert.assertEquals(0.0f, Binary.i16FixedPointToFloat((short) 0), delta);
Assert.assertEquals(0.03125f, Binary.i16FixedPointToFloat((short) 0x400), delta);
Assert.assertEquals(0.0625f, Binary.i16FixedPointToFloat((short) 0x800), delta);
Assert.assertEquals(0.125f, Binary.i16FixedPointToFloat((short) 0x1000), delta);
Assert.assertEquals(0.25f, Binary.i16FixedPointToFloat((short) 0x2000), delta);
Assert.assertEquals(0.5f, Binary.i16FixedPointToFloat((short) 0x4000), delta);
Assert.assertEquals(0.75f, Binary.i16FixedPointToFloat((short) 0x6000), delta);
Assert.assertEquals(1.0f, Binary.i16FixedPointToFloat((short) 0x7fff), delta);
Assert.assertEquals(-0.03125f, Binary.i16FixedPointToFloat((short) -0x400), delta);
Assert.assertEquals(-0.0625f, Binary.i16FixedPointToFloat((short) -0x800), delta);
Assert.assertEquals(-0.125f, Binary.i16FixedPointToFloat((short) -0x1000), delta);
Assert.assertEquals(-0.25f, Binary.i16FixedPointToFloat((short) -0x2000), delta);
Assert.assertEquals(-0.5f, Binary.i16FixedPointToFloat((short) -0x4000), delta);
Assert.assertEquals(-0.75f, Binary.i16FixedPointToFloat((short) -0x6000), delta);
Assert.assertEquals(-1.0f, Binary.i16FixedPointToFloat((short) -0x8000), delta);
}
}

View File

@ -0,0 +1,242 @@
package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.DisplayManager;
import android.view.Display;
import org.junit.Assert;
import org.junit.Test;
public class CommandParserTest {
@Test
public void testParseDisplayInfoFromDumpsysDisplay() {
/* @formatter:off */
String partialOutput = "Logical Displays: size=1\n"
+ " Display 0:\n"
+ "mDisplayId=0\n"
+ " mLayerStack=0\n"
+ " mHasContent=true\n"
+ " mDesiredDisplayModeSpecs={baseModeId=2 primaryRefreshRateRange=[90 90] appRequestRefreshRateRange=[90 90]}\n"
+ " mRequestedColorMode=0\n"
+ " mDisplayOffset=(0, 0)\n"
+ " mDisplayScalingDisabled=false\n"
+ " mPrimaryDisplayDevice=Built-in Screen\n"
+ " mBaseDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_TRUSTED, "
+ "real 1440 x 3120, largest app 1440 x 3120, smallest app 1440 x 3120, appVsyncOff 2000000, presDeadline 11111111, mode 2, "
+ "defaultMode 1, modes [{id=1, width=1440, height=3120, fps=60.0}, {id=2, width=1440, height=3120, fps=90.0}, {id=3, width=1080, "
+ "height=2340, fps=90.0}, {id=4, width=1080, height=2340, fps=60.0}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[2, 3, 4], "
+ "mMaxLuminance=540.0, mMaxAverageLuminance=270.1, mMinLuminance=0.2}, minimalPostProcessingSupported false, rotation 0, state OFF, "
+ "type INTERNAL, uniqueId \"local:0\", app 1440 x 3120, density 600 (515.154 x 514.597) dpi, layerStack 0, colorMode 0, "
+ "supportedColorModes [0, 7, 9], address {port=129, model=0}, deviceProductInfo DeviceProductInfo{name=, manufacturerPnpId=QCM, "
+ "productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, relativeAddress=null}, removeMode 0}\n"
+ " mOverrideDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, "
+ "FLAG_TRUSTED, real 1440 x 3120, largest app 3120 x 2983, smallest app 1440 x 1303, appVsyncOff 2000000, presDeadline 11111111, "
+ "mode 2, defaultMode 1, modes [{id=1, width=1440, height=3120, fps=60.0}, {id=2, width=1440, height=3120, fps=90.0}, {id=3, "
+ "width=1080, height=2340, fps=90.0}, {id=4, width=1080, height=2340, fps=60.0}], hdrCapabilities "
+ "HdrCapabilities{mSupportedHdrTypes=[2, 3, 4], mMaxLuminance=540.0, mMaxAverageLuminance=270.1, mMinLuminance=0.2}, "
+ "minimalPostProcessingSupported false, rotation 0, state ON, type INTERNAL, uniqueId \"local:0\", app 1440 x 3120, density 600 "
+ "(515.154 x 514.597) dpi, layerStack 0, colorMode 0, supportedColorModes [0, 7, 9], address {port=129, model=0}, deviceProductInfo "
+ "DeviceProductInfo{name=, manufacturerPnpId=QCM, productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, "
+ "relativeAddress=null}, removeMode 0}\n"
+ " mRequestedMinimalPostProcessing=false\n";
DisplayInfo displayInfo = DisplayManager.parseDisplayInfo(partialOutput, 0);
Assert.assertNotNull(displayInfo);
Assert.assertEquals(0, displayInfo.getDisplayId());
Assert.assertEquals(0, displayInfo.getRotation());
Assert.assertEquals(0, displayInfo.getLayerStack());
// FLAG_TRUSTED does not exist in Display (@TestApi), so it won't be reported
Assert.assertEquals(Display.FLAG_SECURE | Display.FLAG_SUPPORTS_PROTECTED_BUFFERS, displayInfo.getFlags());
Assert.assertEquals(1440, displayInfo.getSize().getWidth());
Assert.assertEquals(3120, displayInfo.getSize().getHeight());
}
@Test
public void testParseDisplayInfoFromDumpsysDisplayWithRotation() {
/* @formatter:off */
String partialOutput = "Logical Displays: size=1\n"
+ " Display 0:\n"
+ "mDisplayId=0\n"
+ " mLayerStack=0\n"
+ " mHasContent=true\n"
+ " mDesiredDisplayModeSpecs={baseModeId=2 primaryRefreshRateRange=[90 90] appRequestRefreshRateRange=[90 90]}\n"
+ " mRequestedColorMode=0\n"
+ " mDisplayOffset=(0, 0)\n"
+ " mDisplayScalingDisabled=false\n"
+ " mPrimaryDisplayDevice=Built-in Screen\n"
+ " mBaseDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_TRUSTED, "
+ "real 1440 x 3120, largest app 1440 x 3120, smallest app 1440 x 3120, appVsyncOff 2000000, presDeadline 11111111, mode 2, "
+ "defaultMode 1, modes [{id=1, width=1440, height=3120, fps=60.0}, {id=2, width=1440, height=3120, fps=90.0}, {id=3, width=1080, "
+ "height=2340, fps=90.0}, {id=4, width=1080, height=2340, fps=60.0}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[2, 3, 4], "
+ "mMaxLuminance=540.0, mMaxAverageLuminance=270.1, mMinLuminance=0.2}, minimalPostProcessingSupported false, rotation 0, state ON, "
+ "type INTERNAL, uniqueId \"local:0\", app 1440 x 3120, density 600 (515.154 x 514.597) dpi, layerStack 0, colorMode 0, "
+ "supportedColorModes [0, 7, 9], address {port=129, model=0}, deviceProductInfo DeviceProductInfo{name=, manufacturerPnpId=QCM, "
+ "productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, relativeAddress=null}, removeMode 0}\n"
+ " mOverrideDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, "
+ "FLAG_TRUSTED, real 3120 x 1440, largest app 3120 x 2983, smallest app 1440 x 1303, appVsyncOff 2000000, presDeadline 11111111, "
+ "mode 2, defaultMode 1, modes [{id=1, width=1440, height=3120, fps=60.0}, {id=2, width=1440, height=3120, fps=90.0}, {id=3, "
+ "width=1080, height=2340, fps=90.0}, {id=4, width=1080, height=2340, fps=60.0}], hdrCapabilities "
+ "HdrCapabilities{mSupportedHdrTypes=[2, 3, 4], mMaxLuminance=540.0, mMaxAverageLuminance=270.1, mMinLuminance=0.2}, "
+ "minimalPostProcessingSupported false, rotation 3, state ON, type INTERNAL, uniqueId \"local:0\", app 3120 x 1440, density 600 "
+ "(515.154 x 514.597) dpi, layerStack 0, colorMode 0, supportedColorModes [0, 7, 9], address {port=129, model=0}, deviceProductInfo "
+ "DeviceProductInfo{name=, manufacturerPnpId=QCM, productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, "
+ "relativeAddress=null}, removeMode 0}\n"
+ " mRequestedMinimalPostProcessing=false";
DisplayInfo displayInfo = DisplayManager.parseDisplayInfo(partialOutput, 0);
Assert.assertNotNull(displayInfo);
Assert.assertEquals(0, displayInfo.getDisplayId());
Assert.assertEquals(3, displayInfo.getRotation());
Assert.assertEquals(0, displayInfo.getLayerStack());
// FLAG_TRUSTED does not exist in Display (@TestApi), so it won't be reported
Assert.assertEquals(Display.FLAG_SECURE | Display.FLAG_SUPPORTS_PROTECTED_BUFFERS, displayInfo.getFlags());
Assert.assertEquals(3120, displayInfo.getSize().getWidth());
Assert.assertEquals(1440, displayInfo.getSize().getHeight());
}
@Test
public void testParseDisplayInfoFromDumpsysDisplayAPI31() {
/* @formatter:off */
String partialOutput = "Logical Displays: size=1\n"
+ " Display 0:\n"
+ " mDisplayId=0\n"
+ " mPhase=1\n"
+ " mLayerStack=0\n"
+ " mHasContent=true\n"
+ " mDesiredDisplayModeSpecs={baseModeId=1 allowGroupSwitching=false primaryRefreshRateRange=[0 60] appRequestRefreshRateRange=[0 "
+ "Infinity]}\n"
+ " mRequestedColorMode=0\n"
+ " mDisplayOffset=(0, 0)\n"
+ " mDisplayScalingDisabled=false\n"
+ " mPrimaryDisplayDevice=Built-in Screen\n"
+ " mBaseDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0\", displayGroupId 0, FLAG_SECURE, "
+ "FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_TRUSTED, real 1080 x 2280, largest app 1080 x 2280, smallest app 1080 x 2280, appVsyncOff "
+ "1000000, presDeadline 16666666, mode 1, defaultMode 1, modes [{id=1, width=1080, height=2280, fps=60.000004, "
+ "alternativeRefreshRates=[]}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[], mMaxLuminance=500.0, "
+ "mMaxAverageLuminance=500.0, mMinLuminance=0.0}, userDisabledHdrTypes [], minimalPostProcessingSupported false, rotation 0, state "
+ "ON, type INTERNAL, uniqueId \"local:0\", app 1080 x 2280, density 440 (440.0 x 440.0) dpi, layerStack 0, colorMode 0, "
+ "supportedColorModes [0], address {port=0, model=0}, deviceProductInfo DeviceProductInfo{name=EMU_display_0, "
+ "manufacturerPnpId=GGL, productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, connectionToSinkType=0}, "
+ "removeMode 0, refreshRateOverride 0.0, brightnessMinimum 0.0, brightnessMaximum 1.0, brightnessDefault 0.39763778}\n"
+ " mOverrideDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0\", displayGroupId 0, FLAG_SECURE, "
+ "FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_TRUSTED, real 1080 x 2280, largest app 2148 x 2065, smallest app 1080 x 997, appVsyncOff "
+ "1000000, presDeadline 16666666, mode 1, defaultMode 1, modes [{id=1, width=1080, height=2280, fps=60.000004, "
+ "alternativeRefreshRates=[]}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[], mMaxLuminance=500.0, "
+ "mMaxAverageLuminance=500.0, mMinLuminance=0.0}, userDisabledHdrTypes [], minimalPostProcessingSupported false, rotation 0, state "
+ "ON, type INTERNAL, uniqueId \"local:0\", app 1080 x 2148, density 440 (440.0 x 440.0) dpi, layerStack 0, colorMode 0, "
+ "supportedColorModes [0], address {port=0, model=0}, deviceProductInfo DeviceProductInfo{name=EMU_display_0, "
+ "manufacturerPnpId=GGL, productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, connectionToSinkType=0}, "
+ "removeMode 0, refreshRateOverride 0.0, brightnessMinimum 0.0, brightnessMaximum 1.0, brightnessDefault 0.39763778}\n"
+ " mRequestedMinimalPostProcessing=false\n"
+ " mFrameRateOverrides=[]\n"
+ " mPendingFrameRateOverrideUids={}\n";
DisplayInfo displayInfo = DisplayManager.parseDisplayInfo(partialOutput, 0);
Assert.assertNotNull(displayInfo);
Assert.assertEquals(0, displayInfo.getDisplayId());
Assert.assertEquals(0, displayInfo.getRotation());
Assert.assertEquals(0, displayInfo.getLayerStack());
// FLAG_TRUSTED does not exist in Display (@TestApi), so it won't be reported
Assert.assertEquals(Display.FLAG_SECURE | Display.FLAG_SUPPORTS_PROTECTED_BUFFERS, displayInfo.getFlags());
Assert.assertEquals(1080, displayInfo.getSize().getWidth());
Assert.assertEquals(2280, displayInfo.getSize().getHeight());
}
@Test
public void testParseDisplayInfoFromDumpsysDisplayAPI31NoFlags() {
/* @formatter:off */
String partialOutput = "Logical Displays: size=1\n"
+ " Display 0:\n"
+ " mDisplayId=0\n"
+ " mPhase=1\n"
+ " mLayerStack=0\n"
+ " mHasContent=true\n"
+ " mDesiredDisplayModeSpecs={baseModeId=1 allowGroupSwitching=false primaryRefreshRateRange=[0 60] appRequestRefreshRateRange=[0 "
+ "Infinity]}\n"
+ " mRequestedColorMode=0\n"
+ " mDisplayOffset=(0, 0)\n"
+ " mDisplayScalingDisabled=false\n"
+ " mPrimaryDisplayDevice=Built-in Screen\n"
+ " mBaseDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0\", displayGroupId 0, "
+ "real 1080 x 2280, largest app 1080 x 2280, smallest app 1080 x 2280, appVsyncOff "
+ "1000000, presDeadline 16666666, mode 1, defaultMode 1, modes [{id=1, width=1080, height=2280, fps=60.000004, "
+ "alternativeRefreshRates=[]}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[], mMaxLuminance=500.0, "
+ "mMaxAverageLuminance=500.0, mMinLuminance=0.0}, userDisabledHdrTypes [], minimalPostProcessingSupported false, rotation 0, state "
+ "ON, type INTERNAL, uniqueId \"local:0\", app 1080 x 2280, density 440 (440.0 x 440.0) dpi, layerStack 0, colorMode 0, "
+ "supportedColorModes [0], address {port=0, model=0}, deviceProductInfo DeviceProductInfo{name=EMU_display_0, "
+ "manufacturerPnpId=GGL, productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, connectionToSinkType=0}, "
+ "removeMode 0, refreshRateOverride 0.0, brightnessMinimum 0.0, brightnessMaximum 1.0, brightnessDefault 0.39763778}\n"
+ " mOverrideDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0\", displayGroupId 0, "
+ "real 1080 x 2280, largest app 2148 x 2065, smallest app 1080 x 997, appVsyncOff "
+ "1000000, presDeadline 16666666, mode 1, defaultMode 1, modes [{id=1, width=1080, height=2280, fps=60.000004, "
+ "alternativeRefreshRates=[]}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[], mMaxLuminance=500.0, "
+ "mMaxAverageLuminance=500.0, mMinLuminance=0.0}, userDisabledHdrTypes [], minimalPostProcessingSupported false, rotation 0, state "
+ "ON, type INTERNAL, uniqueId \"local:0\", app 1080 x 2148, density 440 (440.0 x 440.0) dpi, layerStack 0, colorMode 0, "
+ "supportedColorModes [0], address {port=0, model=0}, deviceProductInfo DeviceProductInfo{name=EMU_display_0, "
+ "manufacturerPnpId=GGL, productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, connectionToSinkType=0}, "
+ "removeMode 0, refreshRateOverride 0.0, brightnessMinimum 0.0, brightnessMaximum 1.0, brightnessDefault 0.39763778}\n"
+ " mRequestedMinimalPostProcessing=false\n"
+ " mFrameRateOverrides=[]\n"
+ " mPendingFrameRateOverrideUids={}\n";
DisplayInfo displayInfo = DisplayManager.parseDisplayInfo(partialOutput, 0);
Assert.assertNotNull(displayInfo);
Assert.assertEquals(0, displayInfo.getDisplayId());
Assert.assertEquals(0, displayInfo.getRotation());
Assert.assertEquals(0, displayInfo.getLayerStack());
Assert.assertEquals(0, displayInfo.getFlags());
Assert.assertEquals(1080, displayInfo.getSize().getWidth());
Assert.assertEquals(2280, displayInfo.getSize().getHeight());
}
@Test
public void testParseDisplayInfoFromDumpsysDisplayAPI29WithNoFlags() {
/* @formatter:off */
String partialOutput = "Logical Displays: size=2\n"
+ " Display 0:\n"
+ " mDisplayId=0\n"
+ " mLayerStack=0\n"
+ " mHasContent=true\n"
+ " mAllowedDisplayModes=[1]\n"
+ " mRequestedColorMode=0\n"
+ " mDisplayOffset=(0, 0)\n"
+ " mDisplayScalingDisabled=false\n"
+ " mPrimaryDisplayDevice=Built-in Screen\n"
+ " mBaseDisplayInfo=DisplayInfo{\"Built-in Screen, displayId 0\", uniqueId \"local:0\", app 3664 x 1920, "
+ "real 3664 x 1920, largest app 3664 x 1920, smallest app 3664 x 1920, mode 61, defaultMode 61, modes ["
+ "{id=1, width=3664, height=1920, fps=60.000004}, {id=2, width=3664, height=1920, fps=61.000004}, "
+ "{id=61, width=3664, height=1920, fps=120.00001}], colorMode 0, supportedColorModes [0], "
+ "hdrCapabilities android.view.Display$HdrCapabilities@4a41fe79, rotation 0, density 290 (320.842 x 319.813) dpi, "
+ "layerStack 0, appVsyncOff 1000000, presDeadline 8333333, type BUILT_IN, address {port=129, model=0}, "
+ "state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, removeMode 0}\n"
+ " mOverrideDisplayInfo=DisplayInfo{\"Built-in Screen, displayId 0\", uniqueId \"local:0\", app 3664 x 1920, "
+ "real 3664 x 1920, largest app 3664 x 3620, smallest app 1920 x 1876, mode 61, defaultMode 61, modes ["
+ "{id=1, width=3664, height=1920, fps=60.000004}, {id=2, width=3664, height=1920, fps=61.000004}, "
+ "{id=61, width=3664, height=1920, fps=120.00001}], colorMode 0, supportedColorModes [0], "
+ "hdrCapabilities android.view.Display$HdrCapabilities@4a41fe79, rotation 0, density 290 (320.842 x 319.813) dpi, "
+ "layerStack 0, appVsyncOff 1000000, presDeadline 8333333, type BUILT_IN, address {port=129, model=0}, "
+ "state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, removeMode 0}\n"
+ " Display 31:\n"
+ " mDisplayId=31\n"
+ " mLayerStack=31\n"
+ " mHasContent=true\n"
+ " mAllowedDisplayModes=[92]\n"
+ " mRequestedColorMode=0\n"
+ " mDisplayOffset=(0, 0)\n"
+ " mDisplayScalingDisabled=false\n"
+ " mPrimaryDisplayDevice=PanelLayer-#main\n"
+ " mBaseDisplayInfo=DisplayInfo{\"PanelLayer-#main, displayId 31\", uniqueId "
+ "\"virtual:com.test.system,10040,PanelLayer-#main,0\", app 800 x 110, real 800 x 110, largest app 800 x 110, smallest app 800 x "
+ "110, mode 92, defaultMode 92, modes [{id=92, width=800, height=110, fps=60.0}], colorMode 0, supportedColorModes [0], "
+ "hdrCapabilities null, rotation 0, density 200 (200.0 x 200.0) dpi, layerStack 31, appVsyncOff 0, presDeadline 16666666, "
+ "type VIRTUAL, state ON, owner com.test.system (uid 10040), FLAG_PRIVATE, removeMode 1}\n"
+ " mOverrideDisplayInfo=DisplayInfo{\"PanelLayer-#main, displayId 31\", uniqueId "
+ "\"virtual:com.test.system,10040,PanelLayer-#main,0\", app 800 x 110, real 800 x 110, largest app 800 x 800, smallest app 110 x "
+ "110, mode 92, defaultMode 92, modes [{id=92, width=800, height=110, fps=60.0}], colorMode 0, supportedColorModes [0], "
+ "hdrCapabilities null, rotation 0, density 200 (200.0 x 200.0) dpi, layerStack 31, appVsyncOff 0, presDeadline 16666666, "
+ "type VIRTUAL, state OFF, owner com.test.system (uid 10040), FLAG_PRIVATE, removeMode 1}\n";
DisplayInfo displayInfo = DisplayManager.parseDisplayInfo(partialOutput, 31);
Assert.assertNotNull(displayInfo);
Assert.assertEquals(31, displayInfo.getDisplayId());
Assert.assertEquals(0, displayInfo.getRotation());
Assert.assertEquals(31, displayInfo.getLayerStack());
Assert.assertEquals(0, displayInfo.getFlags());
Assert.assertEquals(800, displayInfo.getSize().getWidth());
Assert.assertEquals(110, displayInfo.getSize().getHeight());
}
}

View File

@ -126,8 +126,8 @@ public class ControlMessageReaderTest {
dos.writeInt(1026); dos.writeInt(1026);
dos.writeShort(1080); dos.writeShort(1080);
dos.writeShort(1920); dos.writeShort(1920);
dos.writeInt(1); dos.writeShort(0); // 0.0f encoded as i16
dos.writeInt(-1); dos.writeShort(0x8000); // -1.0f encoded as i16
dos.writeInt(1); dos.writeInt(1);
byte[] packet = bos.toByteArray(); byte[] packet = bos.toByteArray();
@ -143,8 +143,8 @@ public class ControlMessageReaderTest {
Assert.assertEquals(1026, event.getPosition().getPoint().getY()); Assert.assertEquals(1026, event.getPosition().getPoint().getY());
Assert.assertEquals(1080, event.getPosition().getScreenSize().getWidth()); Assert.assertEquals(1080, event.getPosition().getScreenSize().getWidth());
Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight()); Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight());
Assert.assertEquals(1, event.getHScroll()); Assert.assertEquals(0f, event.getHScroll(), 0f);
Assert.assertEquals(-1, event.getVScroll()); Assert.assertEquals(-1f, event.getVScroll(), 0f);
Assert.assertEquals(1, event.getButtons()); Assert.assertEquals(1, event.getButtons());
} }