Compare commits

...

650 Commits

Author SHA1 Message Date
Romain Vimont c27ab46efb Remove suggestion to install from winget
It does not work.

Refs #4027 <https://github.com/Genymobile/scrcpy/issues/4027>
Refs #4389 <https://github.com/Genymobile/scrcpy/issues/4389>
Refs #4956 <https://github.com/Genymobile/scrcpy/issues/4956>
1 week ago
Romain Vimont b5849db32f Document missing package to build for Windows
To build ffmpeg, libz is necessary.

Refs #4955 <https://github.com/Genymobile/scrcpy/issues/4955>
1 week ago
Romain Vimont 206809a99a Fix typo in documentation 2 months ago
Romain Vimont 79968a0ae6 Reorder documentation
Present the --tcpip option without arguments first.
3 months ago
Romain Vimont 7f23ff3f2c Add videos for pinch-to-zoom and tilt
A video is worth a thousand words.
3 months ago
Romain Vimont cc7719079a Italicize coordinates letters in documentation 3 months ago
Romain Vimont 0c94b75eef Update links to 2.4 3 months ago
Romain Vimont af57309074 Bump version to 2.4 3 months ago
Romain Vimont a720c946a6 Merge branch 'master' into release 3 months ago
Romain Vimont 8d87b91f69 Build dependencies from sources
The project has 3 build dependencies:
 - SDL
 - FFmpeg
 - libusb

For Windows, the release script downloaded pre-built build dependencies
(either from upstream, or from the scrcpy-deps repository).

Instead, download the source releases and build locally. This offers
more flexibility.

The official adb release is still downloaded and included as is in the
release archive (it is not a build dependency).

Also upgrade FFmpeg to 6.1.1 and libusb to 1.0.27.

PR #4713 <https://github.com/Genymobile/scrcpy/pull/4713>
3 months ago
inson1 125b1103e1 Happy new year 2024!
PR #4716 <https://github.com/Genymobile/scrcpy/pull/4716>

Signed-off-by: Romain Vimont <rom@rom1v.com>
3 months ago
Romain Vimont 36189b90ea Remove spurious line 3 months ago
Romain Vimont 4dca08cfe3 Set SDL hints before creating any thread
To avoid race conditions in SDL (reported by TSAN).
3 months ago
Romain Vimont fd0f432e87 Detect missing initializations
Write invalid data in memory to easily detect missing initializations in
debug mode.
3 months ago
Romain Vimont cdf09805c0 Add missing initialization 3 months ago
Romain Vimont bf069bd37b Document usage examples
This exposes several common options on the front page.
3 months ago
Romain Vimont b9d244b4c9 Document UHID
Rework the documentation to present the keyboard and mouse input modes.

PR #4473 <https://github.com/Genymobile/scrcpy/pull/4473>
3 months ago
Romain Vimont dd479ed176 Check options specific to SDK keyboard
Fail if an option specific to --keyboard=sdk is passed with another
keyboard input mode.

PR #4473 <https://github.com/Genymobile/scrcpy/pull/4473>
3 months ago
Romain Vimont 5f12132c47 Do not fallback keyboard mode if AOA fails
Initially, if AOA initialization failed, default injection method was
used, in order to use the same command/shortcut when the device is
connected via USB or via TCP/IP, without changing the arguments.

Now that there are 3 keyboard modes, it seems unexpected to switch to
another specific mode if AOA fails (and it is inconsistent). If the user
explicitly requests AOA, then use AOA or fail.

Refs #2632 comment <https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-945190859>
PR #4473 <https://github.com/Genymobile/scrcpy/pull/4473>
3 months ago
Romain Vimont 1c5ad0e813 Reassign -K and -M to UHID keyboard and mouse
The options were deprecated, but for convenience, reassign them to
aliases for --keyboard=uhid and --mouse=uhid respectively.

Their long version (--hid-keyboard and --hid-mouse) remain deprecated.

PR #4473 <https://github.com/Genymobile/scrcpy/pull/4473>
3 months ago
Romain Vimont 6a103c809f Add UHID mouse support
Use the following command:

    scrcpy --mouse=uhid

PR #4473 <https://github.com/Genymobile/scrcpy/pull/4473>
3 months ago
Romain Vimont 151a6225d4 Add shortcut to open keyboard settings
The keyboard settings can be opened by:

    adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS

Add a shortcut (MOD+k) for convenience if the current keyboard is HID.

PR #4473 <https://github.com/Genymobile/scrcpy/pull/4473>
3 months ago
Romain Vimont 54dede3630 Fix startActivity() for supporting API < 30
Call the older startActivityAsUser() instead of
startActivityAsUserWithFeature() so that it also works on older Android
versions.

Fixes #4704 <https://github.com/Genymobile/scrcpy/issues/4704>
PR #4473 <https://github.com/Genymobile/scrcpy/pull/4473>
3 months ago
Romain Vimont f557188dc8 Create UhidManager only on first use
There is no need to create a UhidManager instance (with its thread) if
no UHID is used.

PR #4473 <https://github.com/Genymobile/scrcpy/pull/4473>
3 months ago
Simon Chan 87da68ee0d Handle UHID output
Use UHID output reports to synchronize CapsLock and VerrNum states.

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

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
3 months ago
Romain Vimont 021c5d371a Refactor DeviceMessageSender
Refactor DeviceMessage as a queue of message. This will allow to add
other message types.

PR #4473 <https://github.com/Genymobile/scrcpy/pull/4473>
3 months ago
Simon Chan 840680f546 Add UHID keyboard support
Use the following command:

    scrcpy --keyboard=uhid

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

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
3 months ago
Romain Vimont 4d5b67cc80 Log controller handling errors
On close, the controller is expected to throw an IOException because the
socket is closed, so the exception was ignored.

However, message handling actions may also throw IOException, and they
must not be silently ignored.

PR #4473 <https://github.com/Genymobile/scrcpy/pull/4473>
3 months ago
Romain Vimont 604e59ac7b Initialize controller before keyboards
The UHID keyboard initializer will need the controller.

PR #4473 <https://github.com/Genymobile/scrcpy/pull/4473>
3 months ago
Romain Vimont 4d2c2514fc Initialize controller in two steps
There is a dependency cycle in the initialization order:
 - keyboard depends on controller
 - controller depends on acksync
 - acksync depends on keyboard initialization

To break this cycle, bind the async instance to the controller in a
second step.

PR #4473 <https://github.com/Genymobile/scrcpy/pull/4473>
3 months ago
Romain Vimont 107f7a83ab Extract binary to hex string conversion
PR #4473 <https://github.com/Genymobile/scrcpy/pull/4473>
3 months ago
Romain Vimont 2e7f6a6fc4 Rename default keyboard implementation to "sdk"
Rename {keyboard,mouse}_inject to {keyboard,mouse}_sdk.

All implementations "inject" key events and mouse events, what differs
is the mechanism. For these implementations, the Android SDK API is used
to inject events.

Note that the input mode enum variants were already renamed
(SC_KEYBOARD_INPUT_MODE_SDK and SC_MOUSE_INPUT_MODE_SDK).

PR #4473 <https://github.com/Genymobile/scrcpy/pull/4473>
3 months ago
Romain Vimont d95276467b Extract mouse HID handling
Split the mouse implementation using AOA and the code handling HID
events, so that HID events can be reused for another protocol (UHID).

PR #4473 <https://github.com/Genymobile/scrcpy/pull/4473>
3 months ago
Romain Vimont 91485e2863 Extract keyboard HID handling
Split the keyboard implementation using AOA and the code handling HID
events, so that HID events can be reused for another protocol (UHID).

PR #4473 <https://github.com/Genymobile/scrcpy/pull/4473>
3 months ago
Romain Vimont f2d6203156 Extract HID events struct
An event contained several fields:
 - the accessory id
 - the HID event data
 - a field ack_to_wait specific to the AOA implementation.

Extract the HID event part to prepare the factorization of HID event
creation.

PR #4473 <https://github.com/Genymobile/scrcpy/pull/4473>
3 months ago
Romain Vimont 2d32557fde Embed HID event data
In the implementation, an HID event is at most 8 bytes. Embed the data
in the HID event structure to avoid allocations and simplify the code.

PR #4473 <https://github.com/Genymobile/scrcpy/pull/4473>
3 months ago
Romain Vimont ae303b8d07 Rename hid event "buffer" to "data"
This fields contains the HID event data (there is no "bufferization").

PR #4473 <https://github.com/Genymobile/scrcpy/pull/4473>
3 months ago
Romain Vimont 29ce03e337 Rename "buffer" to "data"
The variable name is intended to match the parameter name of
libusb_control_transfer().

PR #4473 <https://github.com/Genymobile/scrcpy/pull/4473>
3 months ago
Romain Vimont 48adae1728 Fix HID mouse documentation
The size of a mouse HID event is 4 bytes.

PR #4473 <https://github.com/Genymobile/scrcpy/pull/4473>
3 months ago
Simon Chan ea98d49bae Introduce --keyboard and --mouse
Until now, there was two modes for keyboard and mouse:
 - event injection using the Android system API (default)
 - HID/AOA over USB

For this reason, the options were exposed as simple flags:
 - -K or --hid-keyboard to enable physical keyboard simulation (AOA)
 - -M or --hid-mouse to enable physical mouse simulation (AOA)

Replace them by explicit --keyboard and --mouse options, with 3 possible
values:
 - disabled
 - sdk (default)
 - aoa

This will allow to add a new mode (uhid).

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

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
3 months ago
Romain Vimont 35add3daee Accept disabled keyboard or mouse
The input manager assumed that if a controller was present, then both a
key processor and a mouse processor were present.

Remove this assumption, to support disabling keyboard and mouse
separately. This prepares the introduction of new command line options
--keyboard and --mouse.

PR #4473 <https://github.com/Genymobile/scrcpy/pull/4473>
3 months ago
Romain Vimont c0a1aee8ce Always pass input manager instance
Some functions in input_manager.c only have access to a sub-object (for
example the controller). For consistency, always pass the whole
input manager instance.

This will allow to add assertions when keyboard and mouse could be
disabled separately.

PR #4473 <https://github.com/Genymobile/scrcpy/pull/4473>
3 months ago
Romain Vimont a976417572 Fix typo in error message 3 months ago
Romain Vimont ffa238b9d3 Remove duplicate lines in libusb script 3 months ago
Romain Vimont f6459dd742 Fix FAQ link
Refs ad05a01800
3 months ago
Romain Vimont 295102a6d9 Check device messages assumptions at runtime
Do not assume the server behaves correctly (scrcpy should not require
the device to be trusted).
3 months ago
eiyooooo d894e270a7 Add rotation support for non-default display
Use new methods introduced by this commit:
<90c9005e68%5E%21/>

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
3 months ago
Romain Vimont 746eaea556 Add missing clipboard workaround for IQOO device
The first part of the workaround fixed getPrimaryClip(). This part fixes
setPrimaryClip().

Fixes #4703 <https://github.com/Genymobile/scrcpy/issues/4703>
Refs 5ce8672ebc
Refs #4492 <https://github.com/Genymobile/scrcpy/issues/4492>
3 months ago
Romain Vimont 78a7e4f293 Use sc_ prefix for device sender 4 months ago
Romain Vimont 9858eff856 Fix device message deserialization checks
If any message is incomplete, the deserialization method must return
immediately.
4 months ago
Romain Vimont 9e22f3bf1c Replace unsigned char by uint8_t for buffers
For consistency.
4 months ago
Romain Vimont 25f1e703b7 Extract ControlChannel class
This prevents many components from depending on the whole
DesktopConnection.
4 months ago
Romain Vimont a7cf4daf3b Avoid negative average buffering
The assumption that underflow and overbuffering are caused by jitter
(and that the delay between the producer and consumer will be caught up)
does not always hold.

For example, if the consumer does not consume at the expected rate (the
SDL callback is not called often enough, which is an audio output
issue), many samples will be dropped due to overbuffering, decreasing
the average buffering indefinitely.

Prevent the average buffering to become negative to limit the
consequences of an unexpected behavior.

PR #4572 <https://github.com/Genymobile/scrcpy/pull/4572>
4 months ago
Romain Vimont c12fdf900f Minimize buffer underflow on starting
If playback starts too early, insert silence until the buffer is filled
up to at least target_buffering before playing.

PR #4572 <https://github.com/Genymobile/scrcpy/pull/4572>
4 months ago
Romain Vimont 4502126e3b Use early return to avoid additional indentation
PR #4572 <https://github.com/Genymobile/scrcpy/pull/4572>
4 months ago
Romain Vimont dfa3f97a87 Fix audio player comment
PR #4572 <https://github.com/Genymobile/scrcpy/pull/4572>
4 months ago
Romain Vimont edac4b8a9a Increase buffering level smoothness
The buffering level does not change continuously: it increases abruptly
when a packet is received, and decreases abruptly when an audio block is
consumed.

To estimate the buffering level, a rolling average is used.

To make the buffering more stable, increase the smoothness of this
rolling average. This decreases the risk of enabling audio compensation
due to an estimation error.

PR #4572 <https://github.com/Genymobile/scrcpy/pull/4572>
4 months ago
Romain Vimont 44abed5c68 Improve audio compensation thresholds
Use different thresholds for enabling and disabling compensation.

Concretely, enable compensation if the difference between the average
and the target buffering levels exceeds 4 ms (instead of 1 ms). This
avoids unnecessary compensation due to small noise in buffering level
estimation.

But keep a smaller threshold (1 ms) for disabling compensation, so that
the buffering level is restored closer to the target value. This avoids
to keep the actual level close to the compensation threshold.

PR #4572 <https://github.com/Genymobile/scrcpy/pull/4572>
4 months ago
Romain Vimont cfa4f7e2f2 Replace locks by atomics in audio player
The audio output thread only reads samples from the buffer, and most of
the time, the audio receiver thread only writes samples to the buffer.
In these cases, using atomics avoids lock contention.

There are still corner cases where the audio receiver thread needs to
"read" samples (and drop them), so lock only in these cases.

PR #4572 <https://github.com/Genymobile/scrcpy/pull/4572>
4 months ago
Romain Vimont d47ecef1b5 Limit buffering time value
This avoids unreasonable values which could lead to integer overflow.

PR #4572 <https://github.com/Genymobile/scrcpy/pull/4572>
4 months ago
Romain Vimont 9efa162949 Configure clean up actions dynamically
Some actions may be performed when scrcpy exits, currently:
 - disable "show touches"
 - restore "stay on while plugged in"
 - power off screen
 - restore "power mode" (to disable "turn screen off")

They are performed from a separate process so that they can be executed
even when scrcpy-server is killed (e.g. if the device is unplugged).

The clean up actions to perform were configured when scrcpy started.
Given that there is no method to read the current "power mode" in
Android, and that "turn screen off" can be applied at any time using an
scrcpy shortcut, there was no way to determine if "power mode" had to be
restored on exit. Therefore, it was always restored to "normal", even
when not necessary.

However, setting the "power mode" is quite fragile on some devices, and
may cause some issues, so it is preferable to call it only when
necessary (when "turn screen off" has actually been called).

For that purpose, make the scrcpy-server main process and the clean up
process communicate the actions to perform over a pipe (stdin/stdout),
so that they can be changed dynamically. In particular, when the power
mode is changed at runtime, notify the clean up process.

Refs 1beec99f82
Refs #4456 <https://github.com/Genymobile/scrcpy/issues/4456>
Refs #4624 <https://github.com/Genymobile/scrcpy/issues/4624>
PR #4649 <https://github.com/Genymobile/scrcpy/pull/4649>
4 months ago
wuderek be3f949aa5 Adapt to display API changes
The method SurfaceControl.createDisplay() has been removed in AOSP.

Use DisplayManager to create a VirtualDisplay object instead.

Fixes #4646 <https://github.com/Genymobile/scrcpy/issues/4646>
Fixes #4656 <https://github.com/Genymobile/scrcpy/issues/4656>
PR #4657 <https://github.com/Genymobile/scrcpy/pull/4657>

Signed-off-by: Romain Vimont <rom@rom1v.com>
4 months ago
Romain Vimont f7b4a18b43 Catch generic ReflectiveOperationException
This exception is a super-type of:
 - ClassNotFoundException
 - IllegalAccessException
 - InstantiationException
 - InvocationTargetException
 - NoSuchFieldException
 - NoSuchMethodException

Use it to simplify.
4 months ago
Romain Vimont 05b5deacad Move service managers creation
Create the service managers from each manager wrapper class rather than
from their getter in ServiceManager.

The way a wrapper retrieve the underlying service is an implementation
detail, and it must be consistent with the way it accesses it, so it is
better to write the creation in the wrapper.
4 months ago
Romain Vimont d25cbc55f2 Remove unused field 4 months ago
Romain Vimont 3333e67452 Fix memory leak on error
Fixes #4636 <https://github.com/Genymobile/scrcpy/issues/4636>
4 months ago
Romain Vimont 7c53a29d72 Remove useless run script
This script was outdated and redundant with ./run.
4 months ago
Romain Vimont 5187f7254e Add another clipboard workaround for IQOO device
Fixes #4589 <https://github.com/Genymobile/scrcpy/issues/4589>
Refs 5ce8672ebc
Refs #4492 <https://github.com/Genymobile/scrcpy/issues/4492>
5 months ago
Romain Vimont 2ad93d1fc0 Fix scrcpy_otg() return value on error
The function now returns an enum scrcpy_exit_code, not a bool.
5 months ago
Romain Vimont d067a11478 Do not power on if no video
Power on the device on start only if video capture is enabled.

Note that it only impacts display mirroring, since control is completely
disabled if video source is camera.

Refs 110b3a16f6
5 months ago
Romain Vimont cd4056d0f3 Fix include formatting 5 months ago
Romain Vimont 6a58891e13 Use current time as initial timestamp on error
If the initial timestamp could not be retrieved, use the current time as
returned by System.nanoTime(). In practice, it is the same time base as
AudioRecord timestamps.

Fixes #4536 <https://github.com/Genymobile/scrcpy/issues/4536>
6 months ago
Romain Vimont ec41896c85 Fix integer overflow for audio packet duration
The result is assigned to a long (64-bit signed integer), but the
intermediate multiplication was stored in an int (32-bit signed
integer).

This value is only used as a fallback when no timestamp could be
retrieved, that's why it did not cause too much harm so far.

Fixes #4536 <https://github.com/Genymobile/scrcpy/issues/4536>
6 months ago
Romain Vimont 4cd61b5a90 Fix checkstyle violation
Reported by checkstyle:

> [ant:checkstyle] [INFO]
> scrcpy/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java:48:
> Line is longer than 150 characters (found 167). [LineLength]
6 months ago
Till Rathmann d2ed4510a7 Simulate tilt multitouch event by pressing Shift
PR #4529 <https://github.com/Genymobile/scrcpy/pull/4529>

Signed-off-by: Romain Vimont <rom@rom1v.com>
6 months ago
Harsh Shandilya 604dfd7c6b Fix incorrect compgen usage
PR #4532 <https://github.com/Genymobile/scrcpy/pull/4532>

Signed-off-by: Romain Vimont <rom@rom1v.com>
6 months ago
Harsh Shandilya af69689ec1 Fix bash completion syntax
PR #4532 <https://github.com/Genymobile/scrcpy/pull/4532>

Signed-off-by: Romain Vimont <rom@rom1v.com>
6 months ago
Romain Vimont cbce42336d Fix manpage syntax
The '-' character must be escaped.

Fixes #4528 <https://github.com/Genymobile/scrcpy/issues/4528>
6 months ago
Romain Vimont 5a6b8310ca Add note about official website 6 months ago
Romain Vimont c9a4d2b38f Use up-to-date values on display fold change
When a display is folded or unfolded, the maxSize may have been updated
since the option was passed, and deviceSize must be updated.

Refs #4469 <https://github.com/Genymobile/scrcpy/pull/4469>
6 months ago
Romain Vimont 1beec99f82 Explicitly exit cleanup process
This avoids an internal crash reported in `adb logcat`.

Refs #4456 <https://github.com/Genymobile/scrcpy/pull/4456#issuecomment-1837427802>
6 months ago
Romain Vimont 5ce8672ebc Add clipboard workaround for IQOO device
Fixes #4492 <https://github.com/Genymobile/scrcpy/issues/4492>
6 months ago
Romain Vimont 3001f8a2d5 Adapt AudioRecord workaround to Android 14
Android 14 added a new int parameter "halInputFlags" to an internal
method:
<f6135d75db>

Fixes #4492 <https://github.com/Genymobile/scrcpy/issues/4492>
6 months ago
Romain Vimont c6ff78f414 Update links to v2.3.1 6 months ago
Romain Vimont 40f2560d98 Bump version to 2.3.1 6 months ago
Romain Vimont 26aa28c998 Merge branch 'master' into release 6 months ago
Romain Vimont ef79fcbbd2 Fix AV1 demuxing
For AV1, the config packet must not be merged with the next non-config
packet.

This fixes the following error when passing --video-codec=av1:

> INFO: [FFmpeg] libdav1d 1.3.0
> ERROR: [FFmpeg] Unknown OBU type 0 of size 29393
> ERROR: [FFmpeg] Error parsing OBU data
> ERROR: Decoder 'video': could not send video packet: -1094995529

PR #4487 <https://github.com/Genymobile/scrcpy/pull/4487>
6 months ago
Romain Vimont 9497f39fb4 Do not fail if SDL_INIT_VIDEO fails without video
The SDL video subsystem may be initialized so that clipboard
synchronization works even without video playback.

But if the video subsystem initialization fails (e.g. because no video
device is available), consider it as an error only if video playback is
enabled.

Refs 5e59ed3135
Fixes #4477 <https://github.com/Genymobile/scrcpy/issues/4477>
6 months ago
Romain Vimont bf056b1fee Do not initialize SDL video when not necessary
The SDL video subsystem is required for video playback and clipboard
synchronization.

If neither is used, it is not necessary to initialize it.

Refs 5e59ed3135
Refs 110b3a16f6
Refs #4418 <https://github.com/Genymobile/scrcpy/issues/4418>
Refs #4477 <https://github.com/Genymobile/scrcpy/issues/4477>
6 months ago
Johannes Neyer bd9292931e Mention exclusive_caps mode in v4l2 documentation
PR #4435 <https://github.com/Genymobile/scrcpy/pull/4435>

Signed-off-by: Romain Vimont <rom@rom1v.com>
6 months ago
Romain Vimont 140a49b8be Add workaround for Samsung devices issues
On some Samsung devices, DisplayManagerGlobal.getDisplayInfoLocked()
calls ActivityThread.currentActivityThread().getConfiguration(), which
requires a non-null ConfigurationController.

Fixes <https://github.com/Genymobile/scrcpy/issues/4467>
6 months ago
Romain Vimont 4135c411af Fix compilation error
Fix the following warning/error:

    ../app/src/cli.c:2158:17: warning: a label can only be part of a
    statement and a declaration is not a statement [-Wpedantic]

With some compilers, this is an error rather than a pedantic warning.

Refs <https://github.com/Genymobile/scrcpy/issues/2256#issuecomment-1467008307>
6 months ago
Romain Vimont 5e061636f6 Update links to v2.3 6 months ago
Romain Vimont 5f3fb843f5 Bump version to 2.3
The previous version bump to 2.2 was incorrect, it was updated by:

    ./bump_version v2.2

instead of:

    ./bump_version 2.2

Correctly bump to version 2.3.

Refs #4433 <https://github.com/Genymobile/scrcpy/issues/4433#issuecomment-1816830875>
7 months ago
Romain Vimont ce8126f322 Merge branch 'master' into release 7 months ago
Romain Vimont d037b02cc2 Fix scrcpy-console.desktop
The argument passed to scrcpy was not applied, the full command must be
passed as a single argument.

PR #4448 <https://github.com/Genymobile/scrcpy/pull/4448>
7 months ago
Kid 89761213c3 Do not quote $SHELL in .desktop files
This does not work properly on some desktop environments (KDE), and
$SHELL is unlikely to require quoting.

Fixes #4367 <https://github.com/Genymobile/scrcpy/issues/4367>
PR #4448 <https://github.com/Genymobile/scrcpy/pull/4448>

Signed-off-by: Romain Vimont <rom@rom1v.com>
7 months ago
Kid 8db4e78b34 Fix Linux desktop files
There were too many backslashes in the Exec line.

Fixes #4367 <https://github.com/Genymobile/scrcpy/issues/4367>
PR #4448 <https://github.com/Genymobile/scrcpy/pull/4448>

Signed-off-by: Romain Vimont <rom@rom1v.com>
7 months ago
Romain Vimont 5d4b8a7e6d Fix turn screen off on Android 14
On Android 14, the methods to access the display have been moved to
DisplayControl, which is not in the core framework. Use a specific
ClassLoader to access this class and its native dependencies.

Fixes #3927 <https://github.com/Genymobile/scrcpy/issues/3927>
Refs #3927 comment <https://github.com/Genymobile/scrcpy/issues/3927#issuecomment-1790031953>
Refs #4446 comment <https://github.com/Genymobile/scrcpy/pull/4446#issuecomment-1824660915>
PR #4456 <https://github.com/Genymobile/scrcpy/pull/4456>

Co-authored-by: Simon Chan <1330321+yume-chan@users.noreply.github.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
7 months ago
Romain Vimont eed06b141a Upgrade sdl (2.28.5) for Windows
Include the latest version of SDL in Windows releases.
7 months ago
Romain Vimont 825d7f72c0 Extract $VERSION for dependency scripts
This will allow to update the version only once in these files.
7 months ago
Romain Vimont 2370298b61 Download SDL prebuilt binaries from github
The server is faster than libsdl.org.
7 months ago
Romain Vimont 67f356f881 Improve crossbuild
Install all the prebuilt dependencies for Windows to a specific folder,
and use meson command line options to specify their location.

This removes crossbuild-specific code from the meson scripts and will
simplify dependency upgrades.

PR #4460 <https://github.com/Genymobile/scrcpy/pull/4460>
7 months ago
Romain Vimont c573bd2a33 Fix java code style 7 months ago
sam80180 acb2988837 Do not hardcode server path on the device
The path can be retrieved from the classpath.

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

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
7 months ago
Romain Vimont 85a94dd4b5 Fix meson deprecated 'pkgconfig' to 'pkg-config'
When running ./release.sh:

> DEPRECATION: "pkgconfig" entry is deprecated and should be replaced by
> "pkg-config"
7 months ago
Romain Vimont 94031dfe97 Update documentation about video orientation
PR #4441 <https://github.com/Genymobile/scrcpy/pull/4441>
7 months ago
Romain Vimont b43a9e8e7a Add --orientation
Add a shortcut to set both the display and record orientations.

PR #4441 <https://github.com/Genymobile/scrcpy/pull/4441>
7 months ago
Romain Vimont a9d6cb5837 Add --record-orientation
Add an option to store the orientation to apply in a recorded file.

Only rotations are supported (not flips).

PR #4441 <https://github.com/Genymobile/scrcpy/pull/4441>
7 months ago
Romain Vimont 2f92686930 Pass --lock-video-orientation argument in degrees
For consistency with the new --display-orientation option, express the
--lock-video-orientation in degrees clockwise:

 * --lock-video-orientation=0 -> --lock-video-orientation=0
 * --lock-video-orientation=3 -> --lock-video-orientation=90
 * --lock-video-orientation=2 -> --lock-video-orientation=180
 * --lock-video-orientation=1 -> --lock-video-orientation=270

PR #4441 <https://github.com/Genymobile/scrcpy/pull/4441>
7 months ago
Romain Vimont bb88b60227 Add --display-orientation
Deprecate the option --rotation and introduce a new option
--display-orientation with the 8 possible orientations (0, 90, 180, 270,
flip0, flip90, flip180 and flip270).

New shortcuts MOD+Shift+(arrow) dynamically change the display
(horizontal or vertical) flip.

Fixes #1380 <https://github.com/Genymobile/scrcpy/issues/1380>
Fixes #3819 <https://github.com/Genymobile/scrcpy/issues/3819>
PR #4441 <https://github.com/Genymobile/scrcpy/pull/4441>
7 months ago
Romain Vimont 25e33566f5 Mention turning off audio in camera documentation 7 months ago
Romain Vimont 9df92ebe37 Fix manpage style syntax 7 months ago
Romain Vimont 0801cf0627 Fix options alphabetical order
Renaming --display to --display-id broke the alphabetical order.

Refs 23e116064d
7 months ago
Romain Vimont 4658c0e5d2 Update record format error message
Recording now supports formats other than mp4 and mkv.

Refs e637feba51
7 months ago
Romain Vimont 45a073a333 Do not create Device instance for camera
The device instance manages the display and the injection of input
events. It is not necessary for camera capture.
7 months ago
Romain Vimont 7e3b935932 Recreate the display on rotation
On Android 14 (Pixel 8), a device rotation while the camera app was
running resulted in an incorrect capture.

Destroying and recreating the display fixes the issue.
7 months ago
Romain Vimont abcb100597 Upgrade Android SDK to 34 7 months ago
Romain Vimont e8801cc3c0 Upgrade AGP (8.1.3) and Gradle to 8.4
Android Gradle Plugin 8.1.3.
Gradle 8.4.

From now on, Java 17 is required.
7 months ago
Romain Vimont 86808e8114 Upgrade Android checkstyle to 10.12.5
Upgrade to the latest version.
7 months ago
Romain Vimont 15a3bad4ab Log PTS fixing at debug level
Audio PTS are retrieved by AudioRecord.getTimestamp(), so they do not
necessarily exactly match the number of samples (this allows to take
drift and lag into account).

As a consequence, two consecutive timestamps in microseconds may
sometimes end up within the same millisecond, causing the warning. This
is particularly true for the Matroska muxer which uses a timebase of
1/1000 (1 ms precision).

Since this is "expected", lower the log level from warning to debug.
7 months ago
Romain Vimont 200488111e Add support for RAW audio (WAV) recording
RAW audio forwarding was supported but not for recording.

Add support for recording a raw audio stream to a `.wav` file (and
`.mkv`).
7 months ago
Romain Vimont 1713422c13 Upgrade FFmpeg build to 6.1-scrcpy-2
Use a build with WAV muxer.
7 months ago
Romain Vimont 4b4f045e19 Fix audio PTS by the duration of 1 sample
If the difference of PTS between two consecutive blocks of audio is less
than 1 sample, then it will be considered as non-increasing by FFmpeg
muxers having a time_base of 1/sample_rate.

Increase the PTS by 1 sample instead.
7 months ago
Romain Vimont a402eac7f2 Compute PTS of intermediate blocks
If several reads are performed for a single captured audio block (e.g.
if the read size is smaller than the captured block), then the provided
timestamp was the same for all packets.

Recompute the timestamp for each of them.
7 months ago
Romain Vimont 3bb6b0cb9f Read audio by blocks of 1024 samples
In practice, the system captures audio samples by blocks of 1024
samples.

Remplace the hardcoded value of 5 milliseconds (240 samples), and let
AudioRecord fill the input buffer provided by MediaCodec (or by
AudioRawRecorder), with a maximum size of 1024 samples (just in case).
7 months ago
Romain Vimont 258eaaae2a Increase default audio buffer for FLAC
FLAC is not low latency: the default encoder produces blocks of 4096
samples, which represent ~85.333ms.

Increase the audio buffer by default so that audio playback works.
7 months ago
megapro17 4857c5dd59 Add support for FLAC audio codec
PR #4410 <#https://github.com/Genymobile/scrcpy/pull/4410>

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
7 months ago
Romain Vimont f23be823fd Upgrade FFmpeg build to 6.1-scrcpy
Upgrade to FFmpeg 6.1, and with FLAC support enabled.
7 months ago
Romain Vimont 783719c72e Fix OPUS packet in an endian-independent way
Reading the header id as an int assumed that the current endianness was
little endian. Read to a byte array to remove this assumption.
7 months ago
Romain Vimont 80defdd8aa Suppress private APIs lints to Workarounds class
The whole class need them (including the static block).
7 months ago
Romain Vimont e637feba51 Update muxers documentation
Recording now supports formats other than mp4 and mkv.
7 months ago
Romain Vimont 5e59ed3135 Always initialize SDL with the video subsystem
Clipboard synchronization requires SDL_INIT_VIDEO, so always initialize
the video subsystem, even if --no-video or --no-video-playback is
passed.

Refs caf594c90e
Fixes #4418 <https://github.com/Genymobile/scrcpy/issues/4418>
7 months ago
Romain Vimont 4eb33054cd Do not log EPIPE on close for raw audio
Handle EPIPE the same way in AudioRawRecorder as in AudioEncoder.

This prevents useless errors on close.
7 months ago
Romain Vimont 420d3a40dd Fix error handling in raw audio recorder
It is incorret to ever call:

    streamer.writeDisableStream(...);

after:

    streamer.writeAudioHeader();

Move the try-catch block so that it can never happen.
7 months ago
Romain Vimont 9d5f53caa7 Stop capture on any RAW audio error
The server was stopped only if an IOException occurred during RAW audio
capture, but it did not catch RuntimeExceptions.
7 months ago
Romain Vimont 3c45625324 Log recording RAW audio codec as error
It is not possible to record with a RAW audio codec, so the log before
exiting should be an error rather than a warning.
7 months ago
Romain Vimont 11d738321f Recover on invalid camera FPS ranges
Some devices may provide invalid ranges, causing an
IllegalArgumentException "lower must be less than or equal to upper".

Catch the exception to list the cameras anyway.

Refs #4403 <https://github.com/Genymobile/scrcpy/issues/4403>
7 months ago
Romain Vimont ccaa832f48 Simplify --list-cameras output
Remove --video-source=camera from the output of --list-cameras (this is
implicit).
7 months ago
Romain Vimont 4e4ddc499f Return the FakeContext as application context
This avoids getApplicationContext() to return null and cause
NullPointerException.

Fixes #4392 <https://github.com/Genymobile/scrcpy/issues/4392#issuecomment-1792806080>
7 months ago
Romain Vimont 8d76b3e06d Fill application context for camera
Using the camera fails on some devices without a proper application
context.

Fixes #4392 <https://github.com/Genymobile/scrcpy/issues/4392>
7 months ago
Romain Vimont 85a0b935c9 Always assign a system context as base context
FakeContext used ActivityThread.getSystemContext() as base context only
in some cases, because it caused problems on some devices:
 - warnings on Xiaomi devices [1], which are now fixed by
   b8c5853aa6
 - issues related to Looper [2], which are solved by just calling
   Looper.prepare*()

Therefore, we can now always assign a base context, which simplifies and
helps to solve camera issues on some devices (#4392).

[1] <https://github.com/Genymobile/scrcpy/issues/4015#issuecomment-1595382142>
[2] <https://github.com/Genymobile/scrcpy/issues/3805#issuecomment-1596148031>

Fixes #4392 <https://github.com/Genymobile/scrcpy/issues/4392>
7 months ago
Romain Vimont 8c3e2bae7b Simplify Application instantiation
The constructor is public.
7 months ago
Romain Vimont 446ea818a4 Update links to v2.2 7 months ago
Romain Vimont c3c7bf7af3 Bump version to v2.2 7 months ago
Romain Vimont 5000368c2f Merge branch 'master' into release 7 months ago
Romain Vimont 855ae4adb1 Upgrade SDL (2.28.4) for Windows
Include the latest version of SDL in Windows releases.
7 months ago
Romain Vimont a8db3ec9e2 Upgrade platform-tools (34.0.5) for Windows
Include the latest version of adb in Windows releases.
7 months ago
Romain Vimont ff579990c2 Shutdown connection before joining threads
Interrupting async processors may require to shutdown the connection to
wake up blocking calls.

Therefore, shutdown the connection first, then join the threads, then
close the connection.

Refs commit 9c08eb79cb
7 months ago
Romain Vimont b8c5853aa6 Disable default stdout/stderr
Some devices (mostly Xiaomi) print internal errors using
e.printStackTrace(), flooding the console with irrelevant errors.

Disable system streams used via System.out and System.err streams, to
print only the logs from scrcpy.

Refs #994 <https://github.com/Genymobile/scrcpy/issues/994>
Refs #4213 <https://github.com/Genymobile/scrcpy/pull/4213>
7 months ago
Romain Vimont c64d150202 Improve manpage formatting 7 months ago
Romain Vimont 8350a61926 Simplify URLs in manpage
The .UR-formatted URLs are not always rendered correctly. Use simple
brackets instead.
7 months ago
Romain Vimont 5580803406 Always print device model and version
Print the log before checking for --list-* options so that it is
printed in all cases.
7 months ago
Romain Vimont 9bfc749803 Add camera documentation
PR #4213 <https://github.com/Genymobile/scrcpy/pull/4213>
7 months ago
Andrew Gunnerson 6af4bd601f Add support for high frame rate camera capture
Add --camera-high-speed to enable high frame rate camera capture. If
the option is enabled, then --camera-fps is mandatory.

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

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
7 months ago
Andrew Gunnerson 4722bff423 Add --camera-fps
Add a new option for specifying the camera frame rate.

By default, Android's default frame rate (30 fps) is used.

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

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
7 months ago
Romain Vimont 928f8b8eb3 Do not arbitrary limit --max-fps to 1000
Limit to the variable type size, for consistency.

PR #4213 <https://github.com/Genymobile/scrcpy/pull/4213>
7 months ago
Simon Chan 9fc5835485 Fail-fast camera mirroring on Android 11 and older
PR #4213 <https://github.com/Genymobile/scrcpy/pull/4213>

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
7 months ago
Romain Vimont dd36d6135f Support camera size selection using -m/--camera-ar
In addition to --camera-size to specify an explicit size, make it
possible to select the camera size automatically, respecting the maximum
size (already used for display mirroring) and an aspect ratio.

For example, "scrcpy --video-source=camera" followed by:
 - (no additional arguments)
    : mirrors at the maximum size, any a-r
 - -m1920
    : only consider valid sizes having both dimensions not above 1920
 - --camera-ar=4:3
    : only consider valid sizes having an aspect ratio of 4:3 (+/- 10%)
 - -m2048 --camera-ar=1.6
    : only consider valid sizes having both dimensions not above 2048
      and an aspect ratio of 1.6 (+/- 10%)

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

Co-authored-by: Simon Chan <1330321+yume-chan@users.noreply.github.com>
7 months ago
Simon Chan faebb7d70a Add --camera-facing
Add an option to select the camera by its lens facing (front, back or
external).

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

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
7 months ago
Romain Vimont 7f8d079c8c Make camera id optional
If no camera id is provided, use the first camera available.

PR #4213 <https://github.com/Genymobile/scrcpy/pull/4213>
7 months ago
Romain Vimont 64930e71b9 Handle camera disconnection
Stop mirroring on camera disconnection.

PR #4213 <https://github.com/Genymobile/scrcpy/pull/4213>
7 months ago
Romain Vimont d544e577c0 Automatically select audio source
If --audio-source is not specified, select the default value
according to the video source:
 - for display mirroring, use device audio by default;
 - for camera mirroring, use microphone by default.

PR #4213 <https://github.com/Genymobile/scrcpy/pull/4213>
7 months ago
Simon Chan bfeecc0131 Add camera mirroring
Add --video-source=camera, and related options:
 - --camera-id=<id>: select the camera by its id (see --list-cameras);
 - --camera-size=<width>x<height>: select the capture size.

Fixed #241 <https://github.com/Genymobile/scrcpy/issues/241>
PR #4213 <https://github.com/Genymobile/scrcpy/pull/4213>

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
7 months ago
Romain Vimont f032262cd7 Add --list-camera-sizes
Add an option to list the device camera declared sizes.

PR #4213 <https://github.com/Genymobile/scrcpy/pull/4213>
7 months ago
Simon Chan cd63896d63 Add --list-cameras
Add an option to list the device cameras.

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

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
7 months ago
Romain Vimont f085765e04 Factorize --list- options handling
This will limit code duplication as more list options will be added.

PR #4213 <https://github.com/Genymobile/scrcpy/pull/4213>
7 months ago
Simon Chan a2fb1b40f6 Extract SurfaceCapture from ScreenEncoder
Extract an interface SurfaceCapture from ScreenEncoder, representing a
video source which can be rendered to a Surface for encoding.

Split ScreenEncoder into:
 - ScreenCapture, implementing SurfaceCapture to capture the device
   screen,
 - SurfaceEncoder, to encode any SurfaceCapture.

This separation prepares the introduction of another SurfaceCapture
implementation to capture the camera instead of the device screen.

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

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
7 months ago
Romain Vimont 41ccb5883e Force server exit at the end of main()
By default, the Java process exits when all non-daemon threads are
terminated.

The Android SDK might start some non-daemon threads internally,
preventing the scrcpy server to exit in some cases.

So force the process to exit explicitly.

PR #4213 <https://github.com/Genymobile/scrcpy/pull/4213>
7 months ago
Romain Vimont 23e116064d Rename --display to --display-id
The option is named "display id" everywhere.

This will be consistent with --camera-id (there will be many camera
options, so an option --camera would be confusing).

PR #4213 <https://github.com/Genymobile/scrcpy/pull/4213>
7 months ago
Romain Vimont 3432029a3d Make separator configurable for parsing integers
The separator was hardcoded to ':'. This will allow to reuse the
function to parse sizes as WIDTHxHEIGHT.

PR #4213 <https://github.com/Genymobile/scrcpy/pull/4213>
7 months ago
Romain Vimont 7a2b756f1e Fix incorrect comment about AV1 constant
MediaFormat.MIMETYPE_VIDEO_AV1 has been added in API 29, not 21.
7 months ago
Romain Vimont b7ad652a75 Move empty string test for crop option parsing
For consistency with other options.
8 months ago
Avinash Sonawane 76a99a7fcd Replace raw number by its name
PR #4373 <https://github.com/Genymobile/scrcpy/pull/4373>

Signed-off-by: Romain Vimont <rom@rom1v.com>
8 months ago
Avinash Sonawane 68b55ef2fe Replace sprintf() with safer snprintf()
PR #4373 <https://github.com/Genymobile/scrcpy/pull/4373>

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
8 months ago
Avinash Sonawane bc8913e12b Use `char *` for pointer arithmetic
PR #4374 <https://github.com/Genymobile/scrcpy/pull/4374>

Signed-off-by: Romain Vimont <rom@rom1v.com>
8 months ago
Romain Vimont 3c2013de10 Enable missing-prototypes warning
Warn if a global function is defined without a previous prototype
declaration. It is not enabled by default at warning_level=2.
8 months ago
Avinash Sonawane 8cef8bac94 Declare local functions as static
PR #4374 <https://github.com/Genymobile/scrcpy/pull/4374>

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
8 months ago
Avinash Sonawane 0bbe8a7007 Wrap macros in do-while(0)
To fix the warnings of stray `;`.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
8 months ago
Romain Vimont 9fdb882509 Fix --pause-on-exit parsing
The function incorrectly returned `false` instead of a valid (and
expected) enum value.
8 months ago
Romain Vimont 8e7b041f35 Add missing `void`s for empty parameter list 8 months ago
Avinash Sonawane 9ade389069 Make sc_usb_devices_destroy() static
It is only called from the implementation file.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
8 months ago
Avinash Sonawane 90ba885547 Remove redundant `;`
PR #4371 <https://github.com/Genymobile/scrcpy/pull/4371>

Signed-off-by: Romain Vimont <rom@rom1v.com>
8 months ago
Avinash Sonawane 7adf98e9d4 Use `void` for empty function parameter list
PR #4371 <https://github.com/Genymobile/scrcpy/pull/4371>

Signed-off-by: Romain Vimont <rom@rom1v.com>
8 months ago
AmirSina Mashayekh 90ca46ee41 Add scrcpy-server to .gitignore
The script install_release.sh downloads a file named scrcpy-server to
the repo root directory. Add it to .gitignore so that it is ignored.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
8 months ago
Romain Vimont 1c864a88eb Use --pause-on-exit from launchers
The terminal opened by scrcpy-console (.bat or .desktop) must not close
if scrcpy terminates with an error, so that error messages can be read.

Refs #3817 <https://github.com/Genymobile/scrcpy/pull/3817>
Refs #3822 <https://github.com/Genymobile/scrcpy/pull/3822>
PR #4130 <https://github.com/Genymobile/scrcpy/pull/4130>
8 months ago
Romain Vimont 1650b7c058 Add --pause-on-exit
Add an option to make scrcpy pause on exit.

Three behaviors are possible:
 - always pause on exit:
    --pause-on-exit
    --pause-on-exit=true
 - never pause on exit:
    (no option)
    --pause-on-exit=false
 - pause when scrcpy returns with an error (a non-zero exit code):
    --pause-on-exit=if-error

This is useful to prevent the terminal window from automatically
closing, so that error messages can be read.

Refs #3817 <https://github.com/Genymobile/scrcpy/pull/3817>
Refs #3822 <https://github.com/Genymobile/scrcpy/pull/3822>
PR #4130 <https://github.com/Genymobile/scrcpy/pull/4130>
8 months ago
Romain Vimont a7c3c9a54c Make fillBaseContext() method private
This is consistent with fillAppInfo() and fillAppContext(), which are
also private.
10 months ago
Romain Vimont 111d02fca4 Add missing 'final' in Java classes
For consistency.
10 months ago
Romain Vimont 36670dda40 Fix warning typo
A parenthesis was missing.
10 months ago
Romain Vimont 0983f0a194 Report device disconnection on audio EOS
If --no-video was set, then device disconnection was not reported. To
avoid the problem, report device disconnection also on audio
end-of-stream (EOS).

If both video and audio are enabled, then a device disconnection event
will be sent twice, but only the first one will be handled (since it
makes scrcpy exit).

Fixes #4207 <https://github.com/Genymobile/scrcpy/issues/4207>
10 months ago
Romain Vimont 110b3a16f6 Do not disable controls without video playback
Some control messages can still be used even when video playback is
disabled (i.e. there is no window), for example to turn the screen off.

This reverts commit 92483fe11b
(semantically).

Fixes #4175 <https://github.com/Genymobile/scrcpy/issues/4175>
11 months ago
Aritz T 1ee46970e3 Fix TCP/IP link in README
Refs 328ed3650d
PR #4173 <https://github.com/Genymobile/scrcpy/pull/4173>

Signed-off-by: Romain Vimont <rom@rom1v.com>
11 months ago
Romain Vimont fcdf847dd3 Add missing syntax highlighting in audio doc 11 months ago
Romain Vimont ad05a01800 Add Encoder section
This will allow to reference the encoder section directly in issues.
11 months ago
Romain Vimont 328ed3650d Extract device connection to a separate doc page
Create a new "Connection" documentation page.
11 months ago
Romain Vimont c14668b177 Move display section to video documentation 11 months ago
Romain Vimont 637f48f360 Update links to v2.1.1 11 months ago
Romain Vimont d391fc3b69 Bump version to 2.1.1 11 months ago
Romain Vimont 75ad925423 Merge branch 'master' into release 11 months ago
Romain Vimont 7e936fa879 Fix meizu deadlock
Some devices (Meizu) assume that the video encoding thread has a
Looper. By moving video encoding to a separate thread, commit
feab87053a broke this assumption.

Call Looper.prepare() from this thread to fix the problem.

Fixes #4143 <https://github.com/Genymobile/scrcpy/issues/4143>
11 months ago
Romain Vimont 01d785d9a3 Increase attempts to start AudioRecord
Making the shell app foreground (specific for Android 11) may take more
than 300ms on some devices, so increase the number of attempts from 3 to
5 (separated by 100ms).

Fixes #4147 <https://github.com/Genymobile/scrcpy/issues/4147>
Refs #3796 <https://github.com/Genymobile/scrcpy/issues/3796>
Refs 02f4ff7534
11 months ago
Romain Vimont fe6e9acb36 Log device selection at INFO level
The selected device should be logged by default.
11 months ago
Cédric Skwar 625934fb1b Fix fedora package in build instructions
In Fedora, the package is libusb1-devel.

Fixes #4131 <https://github.com/Genymobile/scrcpy/issues/4131>
PR #4132 <https://github.com/Genymobile/scrcpy/pull/4132>

Signed-off-by: Romain Vimont <rom@rom1v.com>
11 months ago
Romain Vimont 85b55b3c4e Fix possible division by zero
On sway (a window manager), SDL_WINDOWEVENT_EXPOSED and
SDL_WINDOWEVENT_SIZE_CHANGED might not be called before a mouse event is
triggered. As a consequence, the "content rectangle" might not be
initialized when the mouse event is processed, causing a division by
zero.

To avoid the problem, initialize the content rect immediately when the
window is shown.

Fixes #4115 <https://github.com/Genymobile/scrcpy/issues/4115>
11 months ago
Romain Vimont 7b7076ef85 Add direct links to donations 12 months ago
Romain Vimont 808bd14e30 Ignore fold change events for other display ids
Scrcpy mirrors a specific display id, it must ignore events for other
display ids.

Fixes #4120 <https://github.com/Genymobile/scrcpy/issues/4120>
12 months ago
Romain Vimont 0049b3ce07 Remove superfluous log
This line was committed by error in commit
a52053421a.
12 months ago
Romain Vimont 5764f47fee Update links to v2.1 12 months ago
Romain Vimont 2dab1f7024 Bump version to 2.1 12 months ago
Romain Vimont 744312ec64 Merge branch 'master' into release 12 months ago
Romain Vimont b9315620e2 Fix adb forward initialization
In forward mode, the dummy byte must be written immediately after the
first accept(), otherwise the client will wait indefinitely, causing a
deadlock (or a timeout).

Regression introduced by 8c650e53cd.
12 months ago
Romain Vimont ea59d525bd Fix code style
The code should fit in 80 columns.
12 months ago
Romain Vimont 0ffcfa0f5c Accept failure in rotation or fold registration
Do not make scrcpy fail if rotation or display fold listeners could not
be registered.
12 months ago
Romain Vimont c0f3c080b6 Register DisplayFoldListener only for Android 10+
This listener does not exist on Android < 10, and it makes scrcpy fail.
12 months ago
Romain Vimont d046678f85 Upgrade platform-tools (34.0.3) for Windows
Include the latest version of adb in Windows releases.
12 months ago
Romain Vimont fae3fbc934 Update developer documentation 12 months ago
Romain Vimont 5061b7e02c Fix build without gradle
Add missing class generation from IDisplayFoldListener.aidl.

Refs 24999d0d32
12 months ago
Romain Vimont 09009c2aa7 Upgrade SDL (2.28.0) for Windows
Include the latest version of SDL in Windows releases.

Fixes #3825 <https://github.com/Genymobile/scrcpy/issues/3825>
Refs libsdl/#7478 <https://github.com/libsdl-org/SDL/issues/7478>
12 months ago
Romain Vimont fb21bbf763 Add workarounds for Honor devices
Audio did not work on Honor devices.

To make it work, a system context must be set as a base context of
FakeContext (so that a PackageManager is available), and a current
Application and ActivityThread must be set.

These workarounds must not be applied for all devices, because they
might cause other issues.

Fixes #4015 <https://github.com/Genymobile/scrcpy/issues/4015>
Refs #3085 <https://github.com/Genymobile/scrcpy/issues/3805>

Co-authored-by: Simon Chan <1330321+yume-chan@users.noreply.github.com>
12 months ago
Romain Vimont 0f1afff7a6 Move workarounds execution
Expose a single public static method in the Workarounds class to apply
all necessary workarounds.
12 months ago
Romain Vimont 48a00fb481 Log device BRAND
The BRAND value is not always the same as the MANUFACTURER value.
12 months ago
Romain Vimont 3b7e2ca9c8 Fix lint warning
Suppress lint "DiscouragedPrivateApi" in Workarounds.java.
12 months ago
wuderek 5bd7514871 Add InputManagerGlobal for Android 14 beta 3
Parts of the InputManager class have been moved to a new
InputManagerGlobal class in Android 14 preview.

Fixes #4074 <https://github.com/Genymobile/scrcpy/issues/4074>
PR #4075 <https://github.com/Genymobile/scrcpy/pull/4075>

Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Romain Vimont d3c2955fb9 Add --time-limit
Add an option to stop scrcpy automatically after a given delay.

PR #4052 <https://github.com/Genymobile/scrcpy/pull/4052>
Fixes #3752 <https://github.com/Genymobile/scrcpy/issues/3752>
1 year ago
Romain Vimont 5042f8de93 Improve recording documentation 1 year ago
Romain Vimont 7536f95d1c Rename raw_video_stream to raw_stream
This server-specific option impacts both the video and audio streams.
1 year ago
Romain Vimont 6832e8d629 Remove spurious empty line 1 year ago
Romain Vimont 28313631e5 Reformat Java code
Fix code style.
1 year ago
Romain Vimont fdbc9397a7 Name Java threads
Give a user-friendly name to Java threads created by the server.
1 year ago
Romain Vimont 4ad7479425 Add missing shortcut in documentation
MOD+Backspace also triggers BACK.
1 year ago
Romain Vimont a3cdf1a6b8 Add option to kill adb on close
Killing adb on close by default would be incorrect, since it would break
any other usage of adb in parallel.

It could be easily done manually by calling "adb kill-server" once
scrcpy terminates, but add an option --kill-adb-on-close for
convenience.

Fixes #205 <https://github.com/Genymobile/scrcpy/issues/205>
Fixes #2580 <https://github.com/Genymobile/scrcpy/issues/2580>
Fixes #4049 <https://github.com/Genymobile/scrcpy/issues/4049>
1 year ago
Romain Vimont b16d4d1835 Fix adb server vs adb daemon confusion
The adb daemon runs on the device, the adb server runs as a background
process on the computer.
1 year ago
Romain Vimont b8d43866d2 Fix options alphabetical order
Commit fc52b24503 missed this one.
1 year ago
Romain Vimont 2d79aeb117 Simplify command in documentation
If --no-video is passed, --no-playback is equivalent to
--no-audio-playback.
1 year ago
Romain Vimont 888a5aae7d Fix typo in recording documentation
The option is --record, not --record-file.
1 year ago
Romain Vimont 323ea2f1d9 Fix PTS when not monotonically increasing
Some decoders fail to guarantee that PTS is strictly monotonically
increasing. Fix the (rescaled) PTS when it does not respect this
constraint.

Fixes #4054 <https://github.com/Genymobile/scrcpy/issues/4054>
1 year ago
Romain Vimont 9ca554ca41 Extract stream-specific structure in recorder
For now, it only contains the stream index, but more fields will be
added.
1 year ago
Romain Vimont 9d3c656414 Fix recorder waiting when stream disabled
In the recorder, if the video or audio stream is disabled, do not wait
for its initialization (it will never happen) to process the header.

In that case (scrcpy --no-audio --record=file.mp4), this caused the
whole content to be buffered in memory, and written only on exit.
1 year ago
Romain Vimont 379caf8551 Use a single condvar in recorder
The sc_cond_wait() in sc_recorder_process_header() needs to be notified
of changes to video_init/audio_init (protected by stream_cond) and
video_queue/audio_queue (protected by queue_cond).

Use only one condition variable to simplify.
1 year ago
Romain Vimont 2aec7b4c9d Mention how to interrupt scrcpy without video
There is no window to close if video playback is disabled.
1 year ago
Romain Vimont fc52b24503 Reorder options in alphabetical order
Fix the options order, using the short option as key first (if any) in
all cases for consistency.
1 year ago
Romain Vimont ff5ffc892f Add option to select audio source
Pass --audio-source=mic to capture the microphone instead of the device
audio output.
1 year ago
Romain Vimont 360f2fea1e Extract AudioCapture creation
This will allow to pass capture options without code duplication.
1 year ago
Adonis Najimi 24999d0d32 Reset video capture on folding event
Handle folding event the same way as rotation events.

Fixes #3960 <https://github.com/Genymobile/scrcpy/issues/3960>
PR #3979 <https://github.com/Genymobile/scrcpy/pull/3979>

Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Romain Vimont 8e2c0d6407 Rename rotationChanged to resetCapture
The flag is used to reset the capture (restart the encoding) on rotation
change. It will also be used for other events (on folding change), so
rename it.

PR #3979 <https://github.com/Genymobile/scrcpy/pull/3979>
1 year ago
Romain Vimont 9a2abba098 Update demuxer comment
The comment was outdated:
 - the "meta" header is now always present (not only when recording is
   enabled);
 - it is not only used for the video stream, but also for the audio
   stream.
1 year ago
shuax b2d860382f Fix stream offset on audio buffer underflow
The `read` variable is in number of samples, while the offset must be in
bytes.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Romain Vimont 4c4a03ebe1 Reorder options to maintain alphabetical order 1 year ago
Yan 798dfd240e Turn device screen off after set up
Sometimes it can take quite a while for everything to get set up and
the screen to appear.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Romain Vimont c4caa6b81d Document --no-{video,audio}-playback
PR #4033 <https://github.com/Genymobile/scrcpy/pull/4033>
1 year ago
Romain Vimont 1efbfe1175 Add separate video and audio playback options
Add --no-video-playback and --no-audio-playback. The option
--no-playback is now an alias for both.

PR #4033 <https://github.com/Genymobile/scrcpy/pull/4033>
1 year ago
Romain Vimont 751c09f47a Simplify V4L2/USB ifdefs
Define local variables whose value depends on ifdefs, to avoid
cluttering all conditions with ifdefs.
1 year ago
Romain Vimont 6ad46d70b8 Define v4l2_buffer only if HAVE_V4L2
If V4L2 support is disabled, there is no v4l2 buffer option.
1 year ago
Romain Vimont f46758d1c5 Fix V4L2 error message when disabled
For consistency, use the same error message for --v4l2-sink and
--v4l2-buffer.
1 year ago
Romain Vimont e71f5358b3 Reorder command line options checks
Perform checks that impact the options first.
1 year ago
Romain Vimont a2c8910006 Rename --no-mirror to --no-playback
This option impacts video and audio _playback_. For example, if we use
V4L2, the device is still "mirrored" (via V4L2), even if playback is
disabled. Therefore, "playback" is more approriate than "mirror".

The initial option --no-display option was renamed to --no-mirror by
commit 6928acdeac, but this has never been
released, so it is ok to rename it one more time.

Refs #3978 <https://github.com/Genymobile/scrcpy/pull/3978#issuecomment-1549420103>
PR #4033 <https://github.com/Genymobile/scrcpy/pull/4033>
1 year ago
Simon Chan cab354102d Create AudioRecord by reflection as a fallback
Some devices (Vivo phones) fail to create an AudioRecord from an
AudioRecord.Builder (which throws a NullPointerException).

In that case, create an AudioRecord instance directly by reflection.

The AOSP version of AudioRecord constructor code can be found at:
 - Android 11 (R):
   <https://cs.android.com/android/platform/superproject/+/android-11.0.0_r1:frameworks/base/media/java/android/media/AudioRecord.java;l=335;drc=64ed2ec38a511bbbd048985fe413268335e072f8>
 - Android 12 (S):
   <https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/media/java/android/media/AudioRecord.java;l=388;drc=2eebf929650e0d320a21f0d13677a27d7ab278e9>
 - Android 13 (T, functionally identical to Android 12):
   <https://cs.android.com/android/platform/superproject/+/android-13.0.0_r1:frameworks/base/media/java/android/media/AudioRecord.java;l=382;drc=ed242da52f975a1dd18671afb346b18853d729f2>
 - Android 14 (U): Not released, but expected to change

PR #3862 <https://github.com/Genymobile/scrcpy/pull/3862>
Fixes #3805 <https://github.com/Genymobile/scrcpy/issues/3805>

Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Romain Vimont 597d2ccc01 Rename FORMAT to ENCODING
The AudioFormat contains several properties. This specific value is
named "encoding".
1 year ago
Romain Vimont 38900d7730 Extract audio source to a static constant
For consistency with the other parameters.
1 year ago
Romain Vimont e926bf1fe8 Delay window resize when minimized
On some window managers (e.g. on Windows), performing a resize while the
window is minimized does nothing (the restored window keeps its old
size).

Therefore, like for maximized and fullscreen states, wait for the window
to be restored to apply a resize.

Refs #3947 <https://github.com/Genymobile/scrcpy/issues/3947>
1 year ago
Romain Vimont 6298ef095f Accept texture failures
When the scrcpy window is minimized on Windows with D3D9, texture
creation and update fail.

In that case, do not terminate scrcpy. Instead, store the pending size
or frame to update, to attempt again during the next update or
rendering.

Fixes #3947 <https://github.com/Genymobile/scrcpy/issues/3947>
1 year ago
Marek Madejski 958f22490b Document installation via winget on Windows
PR #4005 <https://github.com/Genymobile/scrcpy/pull/4005>
Refs #1444 <https://github.com/Genymobile/scrcpy/issues/1444>
Refs #3932 <https://github.com/Genymobile/scrcpy/issues/3932>

Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Romain Vimont 7d33798b40 Upgrade FFmpeg build to 6.0-scrcpy-4
Use FFmpeg DLLs which do not depend on zlib1.dll.
1 year ago
Romain Vimont d500550212 Update audio recording documentation
Document how to record audio-only to .opus and .aac files.

PR #3978 <https://github.com/Genymobile/scrcpy/pull/3978>
1 year ago
Romain Vimont a166eee909 Upgrade FFmpeg build to 6.0-scrcpy-3
Use a build which includes the opus muxer, to support recording to .opus
files.

Refs <https://github.com/rom1v/scrcpy-deps/commits/6.0-scrcpy-3>
PR #3978 <https://github.com/Genymobile/scrcpy/pull/3978>
1 year ago
Romain Vimont b11b363e8e Add recording to aac file
It is just an alias for mp4.

PR #3978 <https://github.com/Genymobile/scrcpy/pull/3978>
1 year ago
Romain Vimont 7321db6f28 Add recording to opus file
Use the FFmpeg opus muxer to record an opus file.

PR #3978 <https://github.com/Genymobile/scrcpy/pull/3978>
1 year ago
Romain Vimont d6bcde565f Accept .m4a and .mka
These are just aliases for mp4 and mkv when there is no video stream.

PR #3978 <https://github.com/Genymobile/scrcpy/pull/3978>
1 year ago
Romain Vimont 98f4f4e68a Refactor command line checks
Several checks are performed when opts->record_filename is not NULL.
Group them in a single block.

PR #3978 <https://github.com/Genymobile/scrcpy/pull/3978>
1 year ago
Romain Vimont be86e14e05 Factorize record format parsing
Convert either the filename extension or the explicit record format
to a sc_record_format using the same function.

PR #3978 <https://github.com/Genymobile/scrcpy/pull/3978>
1 year ago
Romain Vimont 8c650e53cd Add --no-video
Similar to --no-audio, add --no-video to play audio only.

Fixes #3842 <https://github.com/Genymobile/scrcpy/issues/3842>
PR #3978 <https://github.com/Genymobile/scrcpy/pull/3978>
1 year ago
Romain Vimont e89e772c7c Remove unnecessary 'else'
Some server parameters may depend on one another. For example,
audio_bit_rate is meaningless if audio is false.

But it is inconsistent to disable some parameters based on these
dependencies checks, but not others. Handling all dependencies between
parameters would add too much complexity for no benefit.

So just pass individual parameters independently.

PR #3978 <https://github.com/Genymobile/scrcpy/pull/3978>
1 year ago
Romain Vimont feab87053a Convert screen encoder to async processor
Contrary to the other tasks (controller and audio capture/encoding), the
screen encoder was executed synchronously. As a consequence,
scrcpy-server could not terminate until the screen encoder returned.

Convert it to an async processor. This allows to terminate on controller
error, and this paves the way to disable video mirroring.

PR #3978 <https://github.com/Genymobile/scrcpy/pull/3978>
1 year ago
Romain Vimont 751a3653a0 Add missing @Override annotations
PR #3978 <https://github.com/Genymobile/scrcpy/pull/3978>
1 year ago
Romain Vimont 9c08eb79cb Close connection at the end of finally-block
The async processors use the socket file descriptors from the
connection. Therefore, the connection must not be closed before all
async processor threads are joined.

PR #3978 <https://github.com/Genymobile/scrcpy/pull/3978>
1 year ago
Romain Vimont 92483fe11b Disable controls on --no-mirror
If mirroring is disabled, control must also be disabled.

PR #3978 <https://github.com/Genymobile/scrcpy/pull/3978>
1 year ago
Romain Vimont 6928acdeac Rename --no-display to --no-mirror
The option impacts both video and audio playback, so "no display" is not
an appropriate name.

PR #3978 <https://github.com/Genymobile/scrcpy/pull/3978>
1 year ago
parknich081 cb20bcb16f Clarify API versions that support Audio Forwarding
Reword the supported API versions for audio forwarding sentence to
clarify that it supports API >= 30

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Romain Vimont 0f3af2d20b Fix build for FFmpeg < 3.3
The constant AV_CODEC_ID_AV1 was introduced in FFmpeg 3.3. Add an ifdef
to support older versions.

Fixes #3939 <https://github.com/Genymobile/scrcpy/issues/3939>
1 year ago
Yan c083a7cc90 Force OpenGL Core Profile context on macOS
By default, SDL creates an OpenGL 2.1 context on macOS for an OpenGL
renderer. As a consequence, mipmapping is not supported.

Force to use a core profile context, to get a higher version.

Before:

    INFO: Renderer: opengl
    INFO: OpenGL version: 2.1 NVIDIA-14.0.32 355.11.11.10.10.143
    WARN: Trilinear filtering disabled (OpenGL 3.0+ or ES 2.0+ required)

After:

    INFO: Renderer: opengl
    DEBUG: Creating OpenGL Core Profile context
    INFO: OpenGL version: 4.1 NVIDIA-14.0.32 355.11.11.10.10.143
    INFO: Trilinear filtering enabled

when running with:

    scrcpy --verbosity=debug --render-driver=opengl

Note: Since SDL_CreateRenderer() causes a fallback to OpenGL 2.1, the
profile and version attributes have to be set and the context created
_after_.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Romain Vimont 9eb6591913 Add missing --no-audio option in manpage 1 year ago
Romain Vimont 9cfea347d0 Remove Options setters
Now that options parsing is performed from the Options class, setters
are not necessary anymore.
1 year ago
Romain Vimont ce064fb5e0 Move options parsing to Options class 1 year ago
Romain Vimont afcdfc7fd7 Fix checkstyle violation
Checkstyle reported this error:

    [ant:checkstyle] [ERROR] AudioCapture.java:89:145: '+' should be on
    a new line. [OperatorWrap]
1 year ago
Romain Vimont 051b74c883 Extract sc_display from sc_screen
Move the display code to a separate component.
1 year ago
Romain Vimont 2e532afd2b Pass const pointers to events
SDL_Events are only read.
1 year ago
Simon Chan fdf465851c Add Android version check in raw audio recorder
Do not attempt to capture audio below Android 11, this may cause a
segfault on the device.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Romain Vimont 669e9a8d1e Fix "ip route" parsing
If a line did not end with '\r', then the final `\n' was replaced by
'\0' for parsing the current line. This `\0` was then mistakenly
considered as the end of the whole "ip route" output, so the remaining
lines were not parsed, causing "scrcpy --tcpip" to fail in some cases.

To fix the issue, read the final character of the current line before it
is (possibly) overwritten by '\0'.
1 year ago
Romain Vimont f77e1c474e Fix copy-paste for some devices
On Honor Magic 5 Pro, the method to get the clipboard content has been
modified in the framework.

Adapt the call to make it work also on this device.

Fixes #3885 <https://github.com/Genymobile/scrcpy/issues/3885>
1 year ago
Romain Vimont 8f0b38cc4f Specify in README that OTG does not require adb 1 year ago
Romain Vimont 2f9396e24a Simplify clock estimation
The slope encodes the drift between the device clock and the computer
clock. Its real value is expected very close to 1.

To estimate it, just assume it is exactly 1.

Since the clock is used to estimate very close points in the future, the
error caused by clock drift is totally negligible, and in practice it is
way lower than the slope estimation error.

Therefore, only estimate the offset.
1 year ago
Romain Vimont a1e8a34001 Fix documentation link in FAQ 1 year ago
Romain Vimont 00534b0b2d Fix typo in FAQ 1 year ago
Yan 0ebb3df69c Fix debug build by adding compat.c to tests
Linking of tests that needed something from compat.c failed.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Romain Vimont 21df2c240e Mention necessary reboot
After setting "USB debugging (security settings)", a reboot is
necessary.
1 year ago
Romain Vimont 2d3059e1ab Reference FAQ from HID/OTG documentation
Reference the FAQ section about "HID/OTG issues on Windows" from the
HID/OTG documentation.
1 year ago
Romain Vimont 2fff9b9edf Adapt FakeContext for Android 14
This fixes audio for Android 14 developer preview 2.

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

Suggested-by: Namelesswonder <Namelesswonder@users.noreply.github.com>
1 year ago
Romain Vimont 57f879d68a Adapt clipboard wrappers to Android 14
A new deviceId parameter has been added.

Fixes #3784 <https://github.com/Genymobile/scrcpy/issues/3784>
1 year ago
Romain Vimont 478aece68f Replace "bit-rate" with "bit rate" 1 year ago
NextDev65 55899c091e Fix typo in doc/audio.md
The documentation is about audio bit rate, not video bit rate.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Romain Vimont 3626d90004 Use separate audio capture code for Android 11
The code to start audio capture is more complicated for Android 11
(launch a fake popup, wait, make several attempts, close the shell
package).

Use a distinct code path specific to Android 11.
1 year ago
Romain Vimont 02f4ff7534 Make 3 attempts to start AudioRecord
On Android 11, a fake popup must be briefly opened to make the system
think that the shell app is in the foreground so that audio may be
recorded.

Making the shell app foreground may take some time depending on the
device, so make 3 attempts, waiting 100ms before each.

Fixes #3796 <https://github.com/Genymobile/scrcpy/issues/3796>
1 year ago
Romain Vimont a3871130cc List available encoders on failure
When the creation of an encoder fails, log an explicit error message
with the list of available encoders.
1 year ago
sixg0000d 53cb5635cf Fix pause message
The pause terminates only once the Enter key is pressed, not any key.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Romain Vimont d7841664f4 Simplify logic in setScreenPowerMode()
Refs <f2dee20a20 (r104417398)>

Suggested-by: brunoais <brunoaiss@gmail.com>
1 year ago
Romain Vimont d9a644df9c Clarify V4L2 feature in README
The previous formulation could suggest that the device camera could be
used as a webcam. This is not the case (yet?).
1 year ago
Romain Vimont 45717733a1 Document missing Opus encoder error
And how to solve it.
1 year ago
Romain Vimont 39544f34b4 Add --audio-output-buffer
On some systems, the SDL audio callback is not called frequently enough
(for example it requests 5ms of samples every 10ms), because the output
buffer is too small.

By default, we want to use a small value (5ms) to minimize latency and
buffer underrun, but if it does not work well, users need a way to
increase it.

Refs #3793 <https://github.com/Genymobile/scrcpy/issues/3793>
1 year ago
Romain Vimont 4755b97908 Fix bash auto-completion handling
Options having an argument impossible to auto-complete must be handled
separately.
1 year ago
Romain Vimont cba2501254 Add missing auto-completion for --audio-buffer 1 year ago
Romain Vimont 6ba99a62ff Split workarounds to fix audio on some devices
There were several workarounds applied in a single method. Some of them
are specific to Meizu phones, but cause issues on other devices.

Split the method to be able to only fill the app context for audio
capture without applying the Meizu workarounds.

Fixes #3801 <https://github.com/Genymobile/scrcpy/issues/3801>
1 year ago
Bernard Cafarelli d2b7315ba6 Fix linux desktop files validation
Follow quoting rules from:
<https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables>

PR #3817 <https://github.com/Genymobile/scrcpy/pull/3817>
Fixes #3633 <https://github.com/Genymobile/scrcpy/issues/3633>

Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Bernard Cafarelli 6ad037ea04 Update Gentoo instructions
scrcpy is available directly in the distro, drop link to the overlay
(which only contains older versions).

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Romain Vimont 337d6c2fd3 Fail on empty AudioRecord read()
If read() returns 0, then there is no data. According to the
documentation, it happens if the buffer is not a direct buffer:
<https://developer.android.com/reference/android/media/AudioRecord#read(java.nio.ByteBuffer,%20int)>

Refs #3812 <https://github.com/Genymobile/scrcpy/issues/3812>
1 year ago
Romain Vimont 2eced46a37 Update broken link in documentation
The Android documentation has been updated.
1 year ago
Romain Vimont 1a80333747 Replace link to enable USB debugging in README
Link to a more relevant page in the official documentation to enable USB
debugging.
1 year ago
Romain Vimont fb61b779a6 Add references to prerequisites
Users sometimes only read the OS-specific instructions, they must be
aware of the prerequisites.
1 year ago
Romain Vimont 5899af6a2f Add blogpost link about scrcpy 2.0 1 year ago
Romain Vimont cbca79b95b Fix v4l2 sink
The codec id to write as codec parameters is the one from the v4l2
encoder, not from the decoder.

Regression introduced by be985b8242.

Fixes #3795 <https://github.com/Genymobile/scrcpy/issues/3795>
1 year ago
Romain Vimont 02586cf21f Fix build issue on FFmpeg < 5.1
An include was missing.

Fixes #3783 <https://github.com/Genymobile/scrcpy/issues/3783>
1 year ago
Romain Vimont 80a6fa7a01 Fix comparison warning
An int was compared with an unsigned:

    ../app/src/audio_player.c:290:27: warning: comparison of integers of
    different signs: 'int' and 'unsigned int' [-Wsign-compare]
                if (abs(diff) < ap->sample_rate / 1000) {
                    ~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~~~~
1 year ago
Ruoyu Zhong 6b769675fa Fix an "expected expression" error
In C, a label can only be followed by a statement, not a declaration.
An error in `app/src/screen.c` violated this, and led to a build error
with an error message similar to the one below:

    ../app/src/screen.c:821:13: error: expected expression
                bool ok = sc_screen_init_size(screen);
                ^
    /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/13.0.0/include/stdbool.h:15:14: note: expanded from macro 'bool'
    #define bool _Bool
                 ^
    ../app/src/screen.c:822:18: error: use of undeclared identifier 'ok'
                if (!ok) {
                     ^
    2 errors generated.

This could be fixed by introducing a new block (or compound statement;
as is already being done in the next `case`). That is a statement.

Fixes #3785 <https://github.com/Genymobile/scrcpy/issues/3785>
PR #3787 <https://github.com/Genymobile/scrcpy/pull/3787>

Signed-off-by: Ruoyu Zhong <zhongruoyu@outlook.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Romain Vimont e5aa2ce01f Fix broken link in Windows download page 1 year ago
Romain Vimont cbc638c6ba Fix broken link in shortcuts documentation 1 year ago
Romain Vimont abc1be4872 Update links to v2.0 1 year ago
Romain Vimont f1b2d6bbbb Bump version to 2.0 1 year ago
Romain Vimont 90926d40ad Merge branch 'master' into dev 1 year ago
Romain Vimont f12590ed08 Rework README and documentation
The README.md page is HUGE. Split it up.

Also document audio forwarding and improve installation instructions for
each platform and user documentation.

PR #3774 <https://github.com/Genymobile/scrcpy/pull/3774>
1 year ago
Romain Vimont 05a55e3687 Happy new year 2023! 1 year ago
Romain Vimont affda26bfa Document audio player
Add some high-level documentation on the audio player implementation.
1 year ago
Romain Vimont 0bf866fa8d Apply new compensation only if it changed
If the compensation is the same (typically when it is 0), do not reapply
it.
1 year ago
Romain Vimont 73727e7fdf Disable clock drift compensation for tiny values
For less than 1ms, the estimated drift is just noise.
1 year ago
Romain Vimont c22c87eded Fail on deprecated options
Suggest the video and audio specific options instead.
1 year ago
Romain Vimont 426dfbf21d Remove dead code about the deprecated -F option
The -F option was already removed.
1 year ago
Romain Vimont 5512777404 Remove deprecated option --render-expired-frames
This option did nothing since it was deprecated. Totally remove it.
1 year ago
Romain Vimont cc07f8dac4 Upgrade platform-tools (34.0.1) for Windows
Include the latest version of adb in Windows releases.
1 year ago
Romain Vimont f5bb9e576d Upgrade SDL (2.26.4) for Windows
Include the latest version of SDL in Windows releases.
1 year ago
Romain Vimont 2380879376 Remove unused IOException
IOException may not be thrown from this method.
1 year ago
Romain Vimont eca8766545 Compute buffering and compensation without lock
Once underflow has been read with a lock, the buffering and compensation
may be performed without shared variables.
1 year ago
Romain Vimont 0b8a5ca923 Do not read avg_buffering from the player thread
On buffer underflow, the average buffering must be updated, but it is
intended to be accessed only from the receiver thread.

Make the player and the receiver thread communicate the underflow via a
new field (ap->underflow).
1 year ago
Romain Vimont e06acc1ba2 Simplify bytebuf naming
Rename read_available to can_read and write_available to can_write.
This is more readable.
1 year ago
Romain Vimont 14f9d82fda Add audio sample ring-buffer
Add a thin wrapper around bytebuf to handle samples instead of bytes.
This simplifies the audio player, which mostly handles samples.
1 year ago
Romain Vimont bb509d9317 Define the audio output buffer in milliseconds
In theory, this buffer must be dimensioned for a target duration, so its
size in bytes should depend on the sample rate.
1 year ago
Romain Vimont 238ab872ba Pass video size as codec metadata
On initial connection, scrcpy sent some device metadata:
 - the device name (to be used as window title)
 - the initial video size (before any frame or even SPS/PPS)

But it is better to provide the initial video size as part as the video
stream, so that it can be demuxed and exposed via AVCodecContext to
sinks.

This avoids to pass an explicit "initial frame size" for the screen, the
recorder and the v4l2 sink.
1 year ago
Romain Vimont 3a72f3fb4d Report errors on screen event error
Make scrcpy fail if an important screen event (like frame update) fails.
1 year ago
Romain Vimont aa1efbc35c Rename sendCodecId to sendCodecMeta
This will allow the codec header to contain more than the codec id.
1 year ago
Romain Vimont be985b8242 Copy codec parameters from context
Now that the recorder have access to the codec context, it may
automatically initialize the stream codec parameters.

The V4L2 sink could do the same.
1 year ago
Romain Vimont a9f6001f51 Simplify recorder
After the refactor performed by the previous commit, the functions to
wait the video stream and the audio stream could be inlined.
1 year ago
Romain Vimont 5052e15f7f Create recorder streams from packet sinks ops
Previously, the packet sink push() implementation just set the codec and
notified a wait condition. Then the recorder thread read the codec and
created the AVStream.

But this was racy: an AVFrame could be pushed before the creation of the
AVStream, causing its video_stream_index or audio_stream_index to be
initialized to -1.

Also, in the future, the AVStream initialization might need data
provided by the packet sink open(), so initialize it there (with a
mutex).
1 year ago
Romain Vimont 4bdf632dfa Pass AVCodecContext to packet sinks
Create the codec context from the demuxer, so that it can fill context
data for the decoder and recorder.
1 year ago
Romain Vimont 4db50ddbb7 Enable log signaling buffering threshold exceeded
It is as important as underflow logs.
1 year ago
Romain Vimont 46f6918179 Stop and join sc_file_pusher only if initialized
The sc_file_pusher is lazy-initialized, but it was stopped and joined in
all cases (accessing uninitialized values).

Detected by poisoning the struct scrcpy instance with ASAN enabled.
1 year ago
chengjian.scj d93582724d Initialize interrupted field explicitly
The field sc_fps_counter.interrupted was never initialized explicitly.

Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Romain Vimont 408f458636 Decrease recorder thread priority
Recording is background task, writing the packets to a file is not
urgent.
1 year ago
Romain Vimont aa450ffc3f Increase audio thread priority
The audio demuxer thread is the one filling the audio buffer read by the
SDL audio thread. It is time critical to avoid buffer underflow.
1 year ago
Romain Vimont 5ee59e0f13 Add thread priority API
Expose an API to change the priority of the current thread.
1 year ago
Romain Vimont 4a25f3e53b Print info logs to stdout
All server logs were printed to stdout, while all client logs were
printed to stderr.

Instead, use stderr for warnings and errors, stdout for the others:
 - stdout: verbose, debug, info
 - stderr: warn, error
1 year ago
Romain Vimont bb56472d4e Print server logs and newline in one call
System.out.println() first prints the message, then the new line.
Between these two calls, the client might print a message, breaking
formatting.

Instead, call System.out.print() with '\n' appended to the message.
1 year ago
Romain Vimont 7da45c246e Warn on ignored audio options
For raw audio codec, some audio options are ignored.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont d2952c7e93 Add --audio-codec=raw option
Add support for raw (PCM S16 LE) audio codec (a raw decoder is included
in FFmpeg).

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 66b6c06443 Add raw audio recorder
Add an alternative AudioRecorder to stream raw packets without encoding.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont dc228eaad0 Extract async processor interface
On the server side, several components are started, stopped and joined.
Extract an interface to handle them generically.

This will help to support both encoded and raw audio stream, because
they will be two different concrete components, but implementing the
same interface.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 65cc9d765d Extract audio capture
The audio capture was implemented in AudioEncoder.

In order to reuse it without encoding, extract it to a separate class.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 02dd1be4a1 Stop on decoder frame push error
On push, frame sinks report downstream errors to stop upstream
components. Do not ignore the error.
1 year ago
Romain Vimont df55bc2683 Add --audio-buffer
Expose an option to add a buffering delay (in milliseconds) before
playing audio.

This is similar to the options --display-buffer and --v4l2-buffer for
video frames.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont d66b0b3dcc Add compat support for FFmpeg < 5.1
The new chlayout API has been introduced in FFmpeg 5.1. Use the old
channel_layout API on older versions.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont fbe0f951e1 Add audio player
Play the decoded audio using SDL.

The audio player frame sink receives the audio frames, resample them
and write them to a byte buffer (introduced by this commit).

On SDL audio callback (from an internal SDL thread), copy samples from
this byte buffer to the SDL audio buffer.

The byte buffer is protected by the SDL_AudioDeviceLock(), but it has
been designed so that the producer and the consumer may write and read
in parallel, provided that they don't access the same slices of the
ring-buffer buffer.

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

Co-authored-by: Simon Chan <1330321+yume-chan@users.noreply.github.com>
1 year ago
Romain Vimont e1333f6f3b Optionally do not delay the first frame
A delay buffer delayed all the frames except the first one, to open the
scrcpy window immediately and get a picture.

Make this feature optional, so that the delay buffer might also be used
for audio (especially for simulating a high delay for debugging).

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 9b3ca208bf Accept clock estimation with a single point
If there is only one point, assume the slope is 1.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 48a537d45c Remove anonymous struct in delay buffer
For clarity, the fields used only when a delay was set were wrapped in
an anonymous structure.

Now that the delay buffer has been extracted to a separate component,
the delay is necessarily set (it may not be 0), so the fields are always
used.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 1230149fdd Use delay buffer as a frame source/sink
The components needing delayed frames (sc_screen and sc_v4l2_sink)
managed a sc_video_buffer instance, which itself embedded a
sc_frame_buffer instance (to keep only the most recent frame).

In theory, these components should not be aware of delaying: they should
just receive AVFrames later, and only handle a sc_frame_buffer.

Therefore, refactor sc_delay_buffer as a frame source (it consumes)
frames) and a frame sink (it produces frames, after some delay), and
plug an instance in the pipeline only when a delay is requested.

This also removes the need for a specific sc_video_buffer.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 974227a3fc Use frame source trait in decoder 1 year ago
Romain Vimont 6543964f12 Introduce frame source trait
There was a frame sink trait, implemented by components able to receive
AVFrames, but each frame source had to manually send frame to sinks.

In order to mutualise sink management, add a frame sink trait.
1 year ago
Romain Vimont f3197e178d Use packet source trait in demuxer 1 year ago
Romain Vimont c39054a63d Introduce packet source trait
There was a packet sink trait, implemented by components able to
receive AVPackets, but each packet source had to manually send packets
to sinks.

In order to mutualise sink management, add a packet source trait.
1 year ago
Romain Vimont f410f2bdc4 Extract sc_delay_buffer
A video buffer had 2 responsibilities:
 - handle the frame delaying mechanism (queuing packets and pushing them
   after the expected delay);
 - keep only the most recent frame (using a sc_frame_buffer).

In order to be able to reuse only the frame delaying mechanism, extract
it to a separate component, sc_delay_buffer.
1 year ago
Romain Vimont 6379c08012 Fix buffering pts conversion
The mistake had no effect, because tick is also internally expressed in
microseconds.
1 year ago
Romain Vimont 4540f1d69e Report video buffer downstream errors
Make the video buffer stop if its consumer could not receive a frame.
1 year ago
Romain Vimont ad94ccca0b Stop the video buffer on error
If an error occurs from the video buffer thread (typically an
out-of-memory error), then stop.
1 year ago
Romain Vimont a3703340fc Fix possible race condition on video_buffer end
The video_buffer thread clears the queue once it is stopped, but new
frames might still be pushed asynchronously.

To avoid the problem, do not push any frame once the video_buffer is
stopped.
1 year ago
Romain Vimont 6f38c6311b Remove sc_queue
All uses have been replaced by VecDeque.
1 year ago
Romain Vimont 338310677e Remove cbuf
All uses have been replaced by VecDeque.
1 year ago
Romain Vimont f978e4d6de Use VecDeque in aoa_hid
Replace cbuf by VecDeque in aoa_hid
1 year ago
Romain Vimont a0a65b3c4d Use VecDeque in file_pusher
Replace cbuf by VecDeque in file_pusher.

As a side-effect, the new implementation does not limit the queue to an
arbitrary value.
1 year ago
Romain Vimont 4d989de9ae Use VecDeque in controller
Replace cbuf by VecDeque in controller.
1 year ago
Romain Vimont f25a67f342 Use VecDeque in video_buffer
The packets queued for buffering were wrapped in a dynamically allocated
structure with a "next" field.

To avoid this additional layer of allocation and indirection, use a
VecDeque.
1 year ago
Romain Vimont efc15744da Use VecDeque in recorder
The packets queued for recording were wrapped in a dynamically allocated
structure with a "next" field.

To avoid this additional layer of allocation and indirection, use a
VecDeque.
1 year ago
Romain Vimont 33df484912 Introduce VecDeque
Introduce a double-ended queue implemented with a growable ring buffer.

Inspired from the Rust VecDeque type:
<https://doc.rust-lang.org/std/collections/struct.VecDeque.html>
1 year ago
Romain Vimont 457385d5f4 Add sc_allocarray() util
Add a function to allocate an array, which fails safely in the case
where the multiplication would overflow.
1 year ago
Romain Vimont c735b8c127 Use reallocarray() in sc_vector
This fails safely in case of overflow.
1 year ago
Romain Vimont 6dceb32817 Add compat for reallocarray()
This function fails safely in the case where the multiplication would
overflow.
1 year ago
Romain Vimont 6e05d7047a Call avcodec_receive_frame() in a loop
Since in scrcpy a video packet passed to avcodec_send_packet() is always
a complete video frame, it is sufficient to call avcodec_receive_frame()
exactly once.

In practice, it also works for audio packets: the decoder produces
exactly 1 frame for 1 input packet.

In theory, it is an implementation detail though, so
avcodec_receive_frame() should be called in a loop.
1 year ago
Romain Vimont c1528cdca9 Add --require-audio
By default, scrcpy mirrors only the video when audio capture fails on
the device. Add an option to force scrcpy to fail if audio is enabled
but does not work.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Simon Chan de40cac6ad Add workaround to capture audio on Android 11
On Android 11, it is possible to start the capture only when the running
app is in foreground. But scrcpy is not an app, it's a Java application
started from shell.

As a workaround, start an existing Android shell existing activity just
to start the capture, then close it immediately.

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

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Romain Vimont b60a8aa657 Add two-step write feature to bytebuf
If there is exactly one producer, then it can assume that the remaining
space in the buffer will only increase until it writes something.

This assumption may allow the producer to write to the buffer (up to a
known safe size) without any synchronization mechanism, thus allowing
to read and write different parts of the buffer in parallel.

The producer can then commit the write with a lock held, and update its
knowledge of the safe empty remaining space.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 20d41fdd7e Introduce bytebuf util
Add a ring-buffer for bytes. It will be useful for audio buffering.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 619730edaf Pass AVCodecContext to frame sinks
Frame consumers may need details about the frame format.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont e22660d698 Add an audio decoder
PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 05f0e35d2a Give a name to decoder instances
This will be useful in logs.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 99837fa600 Rename decoder to video_decoder
This prepares the introduction of audio_decoder.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont a205ff6c8b Log display sizes in display list
This is more convenient than just the display id alone.
1 year ago
Romain Vimont b65301f672 Add --list-displays
Add an option to list the device displays properly.
1 year ago
Romain Vimont 2596ca02f0 Move log message helpers to LogUtils
This class will also contain other log helpers.
1 year ago
Romain Vimont 50d56a9a2b Quit on audio configuration failure
When audio capture fails on the device, scrcpy continues mirroring the
video stream. This allows to enable audio by default only when
supported.

However, if an audio configuration occurs (for example the user
explicitly selected an unknown audio encoder), this must be treated as
an error and scrcpy must exit.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 9196dc1563 Add --list-encoders
Add an option to list the device encoders properly.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont b7e5284adf Move await_for_server() logs
Print the logs on the caller side. This will allow to call the function
in another context without printing the logs.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont f9960e959f Add --audio-encoder
Similar to --video-encoder, but for audio.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 6f332a2bc7 Extract unknown encoder error message
This will allow to reuse the same code for audio encoder selection.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont b03c864c70 Add --audio-codec-options
Similar to --video-codec-options, but for audio.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 58cf8e5401 Extract application of codec options
This will allow to reuse the same code for audio codec options.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 4601735e51 Add support for AAC audio codec
Add option --audio-codec=aac.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 839b842aa7 Add --audio-codec
Introduce the selection mechanism. Alternative codecs will be added
later.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 0870b8c8be Add --audio-bit-rate
Add an option to configure the audio bit-rate.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 8e640dc90f Disable MethodLength checkstyle on createOptions()
This method will grow as needed to initialize options.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont e694619d53 Rename --encoder to --video-encoder
This prepares the introduction of --audio-encoder.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 31555fa530 Rename --codec-options to --video-codec-options
This prepares the introduction of --audio-codec-options.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 9087e85c3f Rename --bit-rate to --video-bit-rate
This prepares the introduction of --audio-bit-rate.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont cee40ca047 Rename --codec to --video-codec
This prepares the introduction of --audio-codec.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont a1802dab76 Remove default bit-rate on client side
If no bit-rate is passed, let the server use the default value (8Mbps).

This avoids to define a default value on both sides, and to pass the
default bit-rate as an argument when starting the server.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 17d5301c0f Record at least video packets on stop
If the recorder is stopped while it has not received any audio packet
yet, make sure the video stream is correctly recorded.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 80c0780b77 Disable audio before Android 11
The permission "android.permission.RECORD_AUDIO" has been added for
shell in Android 11.

Moreover, on lower versions, it may make the server segfault on the
device (happened on a Nexus 5 with Android 6.0.1).

Refs <4feeee8891%5E%21/>
PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 13a3395a33 Disable audio on initialization error
By default, audio is enabled (--no-audio must be explicitly passed to
disable it).

However, some devices may not support audio capture (typically devices
below Android 11, or Android 11 when the shell application is not
foreground on start).

In that case, make the server notify the client to dynamically disable
audio forwarding so that it does not wait indefinitely for an audio
stream.

Also disable audio on unknown codec or missing decoder on the
client-side, for the same reasons.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 7de0622214 Add audio recording support
Make the recorder accept two input sources (video and audio), and mux
them into a single file.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 3d29f6ef06 Rename video-specific variables in recorder
This paves the way to add audio-specific variables.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 609b098a97 Do not merge config audio packets
For video streams (at least H.264 and H.265), the config packet
containing SPS/PPS must be prepended to the next packet (the following
keyframe).

For audio streams (at least OPUS), they must not be merged.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont de430bc4aa Add an audio demuxer
Add a demuxer which will read the stream from the audio socket.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont f60b5767f4 Force --no-audio if no display and no recording
The client does not use the audio stream if there is no display and no
recording (i.e. only V4L2), so disable audio so that the device does not
attempt to capture it.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont d499f890e7 Give a name to demuxer instances
This will be useful in logs.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont e9876788c9 Rename demuxer to video_demuxer
There will be another demuxer instance for audio.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 15556d1f3b Extract OPUS extradata
For OPUS codec, FFmpeg expects the raw extradata, but MediaCodec wraps
it in some structure.

Fix the config packet to send only the raw extradata.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 7cf5cf5875 Use a streamer to send the audio stream
Send each encoded audio packet using a streamer.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 5eed2c52c2 Encode recorded audio on the device
For now, the encoded packets are just logged into the console.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 464a35b05e Make streamer more generic
Expose a method to write a packet from raw metadata (without
BufferInfo).
1 year ago
Simon Chan 11d32616a9 Capture device audio
Create an AudioRecorder to capture the audio source REMOTE_SUBMIX.

For now, the captured packets are just logged into the console.

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

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Simon Chan e841241a8e Add a new socket for audio stream
When audio is enabled, open a new socket to send the audio stream from
the device to the client.

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

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Simon Chan 3cf03e4a4b Add --no-audio option
Audio will be enabled by default (when supported). Add an option to
disable it.

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

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Romain Vimont 8487ddba64 Use FakeContext for Application instance
This will expose the correct package name and UID to the application
context.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 84ba6435bb Use shell package name for workarounds
For consistency.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 42285ae869 Use ROOT_UID from FakeContext
Remove USER_ID from ServiceManager, and replace it by a constant in
FakeContext.

This is the same as android.os.Process.ROOT_UID, but this constant has
been introduced in API 29.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 820189b6a6 Use PACKAGE_NAME from FakeContext
Remove duplicated constant.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 9a815ceba8 Use AttributionSource from FakeContext
FakeContext already provides an AttributeSource instance.

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

Co-authored-by: Simon Chan <1330321+yume-chan@users.noreply.github.com>
1 year ago
Simon Chan a5541b3476 Add a fake Android Context
Since scrcpy-server is not an Android application (it's a java
executable), it has no Context.

Some features will require a Context instance to get the package name
and the UID. Add a FakeContext for this purpose.

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

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Romain Vimont 10ef8da95d Improve error message for unknown encoder
The provided encoder name depends on the selected codec. Improve the
error message and the suggestions.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 90d88a6927 Rename "codec" variable to "mediaCodec"
This will allow to use "codec" for the Codec type.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont 10ce0f376a Make streamer independent of codec type
Rename VideoStreamer to Streamer, and extract a Codec interface which
will also support audio codecs.

PR #3757 <https://github.com/Genymobile/scrcpy/pull/3757>
1 year ago
Romain Vimont c226797991 Pass all args to ScreenEncoder constructor
There is no good reason to pass some of them in the constructor and some
others as parameters of the streamScreen() method.
1 year ago
Romain Vimont ae9b08b905 Move screen encoder initialization
This prepares further refactors.
1 year ago
Romain Vimont 51628201b7 Write streamer header from ScreenEncoder
The screen encoder is responsible for writing data to the video
streamer.
1 year ago
Romain Vimont ae29e5b562 Use VideoStreamer directly from ScreenEncoder
The Callbacks interface notifies new packets. But in addition, the
screen encoder will need to write headers on start.

We could add a function onStart(), but for simplicity, just remove the
interface, which brings no value, and call the streamer directly.

Refs 87972e2022
1 year ago
Romain Vimont 5b2ec66222 Simplify error handling on socket creation
On any error, all previously opened sockets must be closed.

Handle these errors in a single catch-block. Currently, there are only 2
sockets, but this will simplify even more with more sockets.

Note: this commit is better displayed with --ignore-space-change (-b).
1 year ago
Romain Vimont ef6a3b97a7 Reorder initialization
Initialize components in the pipeline order: demuxer first, decoder and
recorder second.
1 year ago
Romain Vimont f9efe48aac Refactor recorder logic
Process the initial config packet (necessary to write the header)
separately.
1 year ago
Romain Vimont 4b246cd963 Move last packet recording
Write the last packet at the end.
1 year ago
Romain Vimont 3c407773e9 Add start() function for recorder
For consistency with the other components, do not start the internal
thread from an init() function.
1 year ago
Romain Vimont a039124d5d Open recording file from the recorder thread
The recorder opened the target file from the packet sink open()
callback, called by the demuxer. Only then the recorder thread was
started.

One golden rule for the recorder is to never block the demuxer for I/O,
because it would impact mirroring. This rule is respected on recording
packets, but not for the initial recorder opening.

Therefore, start the recorder thread from sc_recorder_init(), open the
file immediately from the recorder thread, then make it wait for the
stream to start (on packet sink open()).

Now that the recorder can report errors directly (rather than making the
demuxer call fail), it is possible to report file opening error even
before the packet sink is open.
1 year ago
Romain Vimont 6b5dfef923 Inline packet_sink impl in recorder
Remove useless wrappers.
1 year ago
Romain Vimont fb29135591 Initialize recorder fields from init()
The recorder has two initialization phases: one to initialize the
concrete recorder object, and one to open its packet_sink trait.

Initialize mutex and condvar as part of the object initialization.

If there were several packet_sink traits (spoiler: one for video, one
for audio), then the mutex and condvar would still be initialized only
once.
1 year ago
Romain Vimont b1b33e3eaf Report recorder errors
Stop scrcpy on recorder errors.

It was previously indirectly stopped by the demuxer, which failed to
push packets to a recorder in error. Report it directly instead:
 - it avoids to wait for the next demuxer call;
 - it will allow to open the target file from a separate thread and stop
   immediately on any I/O error.
1 year ago
Romain Vimont db5751a76a Move previous packet to a local variable
It is only used from run_recorder().
1 year ago
Romain Vimont b6744e7887 Move pts_origin to a local variable
It is only used from run_recorder().
1 year ago
Romain Vimont 181fb555bb Change PTS origin type from uint64_t to int64_t
It is initialized from AVPacket.pts, which is an int64_t.
1 year ago
Romain Vimont fa99763668 Fix --encoder documentation
Mention that it depends on the codec provided by --codec (which is not
necessarily H264 anymore).
1 year ago
Romain Vimont b43938fa66 Do not print stacktraces when unnecessary
User-friendly error messages are printed on specific configuration
exceptions. In that case, do not print the stacktrace.

Also handle the user-friendly error message directly where the error
occurs, and print multiline messages in a single log call, to avoid
confusing interleaving.
1 year ago
Romain Vimont 9f8e96e895 Fix --no-clipboard-autosync bash completion
Fix typo.
1 year ago
Romain Vimont c78254fcd1 Split server stop() and join()
For consistency with the other components, call stop() and join()
separately.

This allows to stop all components, then join them all.
1 year ago
Romain Vimont e30e692b36 Print FFmpeg logs
FFmpeg logs are redirected to a specific SDL log category.

Initialize the log level for this category to print them as expected.
1 year ago
Romain Vimont 10e8295aea Move FFmpeg callback initialization
Configure FFmpeg log redirection on start from a log helper.
1 year ago
Romain Vimont f30fd963a1 Upgrade FFmpeg custom builds for Windows
Use a build which includes the pcm_s16le decoder, to support RAW audio.

Refs <https://github.com/rom1v/scrcpy-deps/commits/6.0-scrcpy-2>
1 year ago
Romain Vimont 9d60d7880b Upgrade FFmpeg (6.0) for Windows
Use the latest version (specifically built for scrcpy).

Refs <https://www.ffmpeg.org/download.html#release_6.0>
1 year ago
Romain Vimont 0fc62bfcd6 Use minimal prebuilt FFmpeg for Windows
On the scrcpy-deps repo, I built FFmpeg 5.1.2 binaries for Windows with
only the features used by scrcpy.

For comparison, here are the sizes of the dll for FFmpeg 5.1.2:
 - before: 89M
 - after: 4.7M

It also allows to upgrade the old FFmpeg version (4.3.1) used for win32.

Refs <https://github.com/rom1v/scrcpy-deps>
Refs <https://github.com/Genymobile/scrcpy/issues/1753>
1 year ago
Romain Vimont a20615066d Simplify libusb prebuilt scripts
In theory, include/ might be slightly different for win32 and win64
builds. Use each one separately to simplify.
1 year ago
Romain Vimont 14a85fd61e Silence lint warning about constant in API 29
MediaFormat.MIMETYPE_VIDEO_AV1 has been added in API 29, but it is not
a problem to inline the constant in older versions.
1 year ago
Romain Vimont 5bf52a98ed Remove manifest package name
As reported by gradle:

> Setting the namespace via a source AndroidManifest.xml's package
> attribute is deprecated.
>
> Please instead set the namespace (or testNamespace) in the module's
> build.gradle file, as described here:
> https://developer.android.com/studio/build/configure-app-module#set-namespace
1 year ago
Romain Vimont a252194161 Upgrade gradle build tools to 7.4.0
Plugin version 7.4.0.
Gradle version 7.5.

Refs <https://developer.android.com/studio/releases/gradle-plugin#updating-gradle>
1 year ago
Romain Vimont b5d41ad4f6 Fix useless garbage initialization
The variable `p` was initialized with a garbage value (a `const char **`
casted to `char *`). Fortunately, it was never read.

Refs <https://github.com/Genymobile/scrcpy/issues/3765>
1 year ago
Romain Vimont 389dd77b50 Fix MIN/MAX macros
Expressions like "x < MAX(y, z)" were broken.
1 year ago
Romain Vimont 3c3c07db05 Initialize server->serial in all cases
Running scrcpy --tcpip on a device already connected via TCP/IP did not
initialize server->serial.

As a consequence, in debug mode, an assertion failed:

    scrcpy: ../app/src/server.c:770: run_server: Assertion
    `server->serial' failed.

In release mode, scrcpy failed with this error:

    adb: -s requires an argument
1 year ago
Romain Vimont 6b422e21bf Fix error message on icon loading failure 1 year ago
Romain Vimont 8e8b039a63 Do not use avformat network
Scrcpy does not use FFmpeg network features. Initialize network locally
instead (useful only for Windows).

The include block has been moved to fix the following warning:

    Please include winsock2.h before windows.h
1 year ago
Romain Vimont 0702be86d8 Accept Windows Sockets from version 1.1
Version 2.2 is probably not necessary (1.1 is the version required by
FFmpeg when network is enabled).

Refs <891ed24f77/libavformat/network.c (L63)>
Refs <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsastartup>
1 year ago
Romain Vimont 0cea7fb24c Fix WSAStartup() error check on Windows 1 year ago
Romain Vimont 3d10fbd9b4 Fix --bit-rate option in bash completion script
The option is --bit-rate, not --bitrate.
1 year ago
Romain Vimont 3e3756a323 Add auto-completion for --codec option
Add missing command to bash and zsh completion scripts.
1 year ago
Romain Vimont 5d6bcc5966 Use enum for long options constants
This avoids to manually assign values.
1 year ago
Romain Vimont 5973d4cdd7 Initialize mouse_input_mode explicitly
The explicit initialization was missing. It had no consequences because
SC_MOUSE_INPUT_MODE_INJECT == 0.

Fixes #3749 <https://github.com/Genymobile/scrcpy/issues/3749>
1 year ago
Romain Vimont 0a151b96fe Accept muxing AV1 into MP4 container
MP4 supports AV1.

Refs d2dce51038
1 year ago
Romain Vimont ebecbe6bc6 Fix inconsistent quotes
The encoder name started with a simple quote but ended with a
double quote. Use a single quote for both.
1 year ago
Romain Vimont d5dff239c8 Suggest commands with an explicit '=' 1 year ago
Romain Vimont 5cf86ef7ff Move finally-block to fix deadlock on stop
DesktopConnection implements Closeable, so it is implicitly closed after
its try-with-resources block. Closing the DesktopConnection shutdowns
the sockets, so it is necessary in particular to wake up blocking read()
calls from the controller.

But the controller thread was joined before the DesktopConnection was
closed, causing a deadlock. To fix the problem, join the controller
thread only after the DesktopConnection is closed.

Refs 400a1c69b1
1 year ago
Romain Vimont e02f30f895 Remove unnecessary error logs
When a call to a packet or frame sink fails, do not log the error on the
caller side: either the "failure" is expected (explicitly stopped) or it
must be logged by the packet or frame sink implementation.
1 year ago
Romain Vimont 25e2eb7d7c Document default video codec
Mention the default option value, like for other commands.
1 year ago
Romain Vimont 280a9afda8 Fix command-line help typo 1 year ago
Romain Vimont e91618586c Prefix receiver by sc_
Like all other components in scrcpy.
1 year ago
Romain Vimont 680ddf64be Fix demuxer error message
Now that there are several possible codecs, do not hardcode H.264 in the
error message.

Refs 3e517cd40e
1 year ago
Romain Vimont f4e7085c34 Log non-EPIPE I/O exceptions
On close, the client closes the socket. This wakes up socket blocking
calls on the server-side, by throwing an exception. Since this exception
is expected, it was not logged.

However, other IOExceptions might occur, which must not be ignored. For
that purpose, log only IOException when they are not caused by an EPIPE
error.
1 year ago
Romain Vimont 439a1fd4ed Rename 'uid' to 'scid'
A random identifier is generated to differentiate multiple running
scrcpy instances. Rename it from 'uid' to 'scid' (scrcpy id) not to
confuse it with Linux UID.

Fixes #3729 <https://github.com/Genymobile/scrcpy/issues/3729>
Refs 4315be1648
1 year ago
Romain Vimont 49eb326ce9 Extract packet merging
Config packets must be prepended to the next media packet. Extract the
logic to a new sc_packet_merger helper to simplify the demuxer code.
1 year ago
Romain Vimont f03f32267e Remove unused parser
Since 1c02b58412, the parser is not used
anymore.
1 year ago
Romain Vimont 45b2e6db5c Log component stopped in finally clause
The message must be logged even when no exception occurs.
1 year ago
Romain Vimont 400a1c69b1 Join all threads before end of main
Some calls from separate threads may throw exceptions once the main()
method has returned.
1 year ago
Romain Vimont 730eb1086a Properly report demuxer errors
All demuxer errors were reported as "device disconnected", even if the
failure was not related to device socket read.
1 year ago
Romain Vimont 4f9e9c6619 Prefix UI events constants by SC_ 1 year ago
Romain Vimont 953edfd1df Split codec_id reading
Receive codec id and convert it to AVCodecID separately.

This will allow the caller to distinguish between EOS and unknown codec
id.
1 year ago
Romain Vimont 230b8274b9 Fix error return value
The function returns an enum AVCodecID, not a bool.
1 year ago
Romain Vimont 40866ddc10 Fix demuxer error message
The message applies to all packets, not only config packets.
1 year ago
Romain Vimont bd56c0abf7 Remove unused codec context
The demuxer does not need any codec context.
1 year ago
Romain Vimont 6524e90c68 Remove unused constant
This line was committed by error.

Refs 3e517cd40e
1 year ago
Kartik Kushwaha b4caa483dd Add Fedora instructions in README
Add the command to install the scrcpy package for Fedora directly.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Romain Vimont f2dee20a20 Set power mode on all physical displays
Android 10 and above support multiple physical displays. Apply power
mode to all of them.

Fixes #3716 <https://github.com/Genymobile/scrcpy/issues/3716>
1 year ago
Romain Vimont d2dce51038 Add support for AV1
Add option --codec=av1.

PR #3713 <https://github.com/Genymobile/scrcpy/pull/3713>
1 year ago
Romain Vimont 4342c5637d Add support for H265
Add option --codec=h265.

PR #3713 <https://github.com/Genymobile/scrcpy/pull/3713>
Fixes #3092 <https://github.com/Genymobile/scrcpy/issues/3092>
1 year ago
Romain Vimont 3e517cd40e Add option to select video codec
Introduce the selection mechanism. Alternative codecs will be added in
further commits.

PR #3713 <https://github.com/Genymobile/scrcpy/pull/3713>
1 year ago
Romain Vimont f70f6cdd3e Simplify server info initialization
Use sc_read16be() to read 16-bit integer fields.
1 year ago
Romain Vimont 87972e2022 Extract video streaming to a separate class
ScreenEncoder handled both capture/encoding and sending over the
network.

Move the streaming part to a separate VideoStreamer.
1 year ago
Romain Vimont 3aac74e9e9 Move variable assignment
Computing eof flag is not necessary if rotation changed.
1 year ago
Romain Vimont 1c82c3923d Compute relative PTS on the client-side
The PTS received from MediaCodec are expressed relative to an arbitrary
clock origin. We consider the PTS of the first frame to be 0, and the
PTS of every other frame is relative to this first PTS (note that the
PTS is only used for recording, it is ignored for mirroring).

For simplicity, this relative PTS was computed on the server-side.

To prepare support for multiple stream (video and audio), send the
packet with its original PTS, and handle the PTS offset on the
client-side (by the recorder).

Since we can't know in advance which stream will produce the first
packet with the lowest PTS (a packet received later on one stream may
have a PTS lower than a packet received earlier on another stream),
computing the PTS on the server-side would require unnecessary waiting.
1 year ago
Romain Vimont 36d656e91f Improve workarounds call comments 1 year ago
Romain Vimont bdbf1f4eb7 Move Workarounds call
Workarounds are not specific to the screen encoder.

Co-authored-by: Simon Chan <1330321+yume-chan@users.noreply.github.com>
1 year ago
Romain Vimont 4177de5880 Do not expose controller threads
The way the controller executes its events asynchronously is an
implementation detail.
1 year ago
Romain Vimont 6a07e3d470 Fix manpage formatting
Only the option arguments must be underlined.
1 year ago
Simon Chan 9b286ec8a7 Inject additional ACTION_BUTTON_* events for mouse
On mouse click events:
 - the first button pressed must first generate ACTION_DOWN;
 - all button pressed (including the first one) must generate
   ACTION_BUTTON_PRESS;
 - all button released (including the last one) must generate
   ACTION_BUTTON_RELEASE;
 - the last button released must in addition generate ACTION_UP.

Otherwise, Chrome does not work properly.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Simon Chan 8c5c55f9e1 Fix mouse pointer state update
If the pointer is a mouse, the pointer is UP only when no buttons are
pressed (not when a button is released, because there might be other
buttons still pressed).

Refs #3635 <https://github.com/Genymobile/scrcpy/issues/3635>

Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Simon Chan 0afef0c634 Forward action button to device
On click event, only the whole buttons state was passed to the device.
In addition, on ACTION_DOWN and ACTION_UP, pass the button associated to
the action.

Refs #3635 <https://github.com/Genymobile/scrcpy/issues/3635>

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Romain Vimont 07806ba915 Retry on spurious error
MediaCodec may fail spuriously, typically when stopping an encoding and
starting a new one immediately (for example on device rotation).

In that case, retry a few times, in many cases it should work.

Refs #3693 <https://github.com/Genymobile/scrcpy/issues/3693>
1 year ago
Romain Vimont a52053421a Extract retry handling
Move the code to downscale and retry on error out of the catch-block.

Refs 26b4104844
1 year ago
Romain Vimont a9b2697f3e Move local variables declarations
This makes it clear that these local variables are only passed to
setDisplaySurface().
1 year ago
Romain Vimont b53d2c66e0 Remove useless setSize() method
Inline its content. It makes the logic of internalStreamScreen() more
readable (it reduces the number of indirections).
1 year ago
Romain Vimont 6cccf3ab2a Remove useless configure() method
Inline its single call.
1 year ago
Romain Vimont 52f85fd6f1 Keep the same MediaCodec instance across sessions
Calling codec.reset() is sufficient.
1 year ago
Romain Vimont 91c69ad95c Remove useless destroyDisplay() method
The method made exactly one simple call. Just make the call directly.
1 year ago
Romain Vimont 75d7c01a0c Keep the same display binder across sessions
Do not destroy/recreate the display when starting a new encoding session
(on device rotation for example).
1 year ago
Romain Vimont 74d32e612d Terminate loop explicitly on interrupted
Make explicit that the loop terminates when the current thread is
interrupted.
1 year ago
Romain Vimont bdba554118 Use Java lambdas where possible 1 year ago
Romain Vimont 234ad7ee78 Support Java lambdas in build_without_gradle.sh
Building Java source code using lambdas requires core-lambda-stubs.jar.

Refs #3657 <https://github.com/Genymobile/scrcpy/issues/3657>
1 year ago
Romain Vimont 8cbbcc939f Add missing final modifiers 1 year ago
Romain Vimont b22810b17c Use try-with-resources
Replace an explicit try-finally by a try-with-resources block.
1 year ago
Romain Vimont 4315be1648 Use random name for device socket
For the initial connection between the device and the computer, an adb
tunnel is established (with "adb reverse" or "adb forward").

The device-side of the tunnel is a local socket having the hard-coded
name "scrcpy". This may cause issues when several scrcpy instances are
started in a few seconds for the same device, since they will try to
bind the same name.

To avoid conflicts, make the client generate a random UID, and append
this UID to the local socket name ("scrcpy_01234567").
1 year ago
Romain Vimont 74e3f8b253 Add random util
Add a user-friendly tool to generate random numbers.
1 year ago
Romain Vimont 059ec45f82 Add jrand48()/nrand48() compat functions
These functions are not available on all platforms.
1 year ago
Romain Vimont e6cd42355b Use separate gen dir to build without gradle
The generated source files were written to the classes dir. Use a
separate gen dir instead.
1 year ago
Romain Vimont 87da137238 Remove "on Linux" in FAQ
HID now works on all platforms.
1 year ago
Romain Vimont b3f626feee Add FAQ section about HID/OTG on Windows
Refs #3654 <https://github.com/Genymobile/scrcpy/issues/3654>
1 year ago
Romain Vimont bf8696d02e Avoid unnecessary copy on config packets demuxing
Use av_packet_ref() to reference the packet without copy.

This also simplifies the logic, by making the "offset" variable and the
memcpy() call local to the if-block.
1 year ago
Romain Vimont d8c2fe6ef2 Revert "Remove continuous resizing workaround for Windows"
This reverts commit 18082f6069.

I can't reproduce, but it seems the workaround improves the behavior on
some Windows versions.

Fixes #3640 <https://github.com/Genymobile/scrcpy/issues/3640>
Refs #3458 <https://github.com/Genymobile/scrcpy/issues/3458>
1 year ago
Romain Vimont 54c7baceac Use "meson setup" from install_release.sh
Refs 64821466a1
1 year ago
Romain Vimont 4c43784fd1 Update links to v1.25 1 year ago
Romain Vimont fe21158c20 Bump version to 1.25 1 year ago
Romain Vimont 8e0c899218 Upgrade FFmpeg (5.1.2) for Windows 64-bit
Use the latest version of FFmpeg in Windows 64-bit releases.
1 year ago
Romain Vimont 725a922271 Upgrade SDL (2.26.1) for Windows
Include the latest version of SDL in Windows releases.
1 year ago
Romain Vimont b5773a6fe8 Upgrade platform-tools (33.0.3) for Windows
Include the latest version of adb in Windows releases.
1 year ago
Romain Vimont 67fb457dcb Merge branch 'master' into dev 1 year ago
Pawel Jasinski c7b1d0ea9a Force mouse source when --forward-all-clicks
Right click and middle click require the source device to be a mouse,
not a touchscreen. Therefore, the source device was changed only when a
button other than the primary button was pressed (see
adc547fa6e).

However, this led to inconsistencies between the ACTION_DOWN when a
secondary button is pressed (with a mouse as source device) and the
matching ACTION_UP when the secondary button is released (with a
touchscreen as source device, because then there is no button pressed).

To avoid the problem in all cases, force a mouse as source device when
--forward-all-clicks is set.

Concretely, for mouse events in --forward-all-clicks mode:
 - device source is set to InputDevice.SOURCE_MOUSE;
 - motion event toolType is set to MotionEvent.TOOL_TYPE_MOUSE;

Otherwise (when --forward-all-clicks is unset, or for real touch
events), finger events are injected:
 - device source is set to InputDevice.SOURCE_TOUCHSCREEN;
 - motion event toolType is set to MotionEvent.TOOL_TYPE_FINGER.

Fixes #3568 <https://github.com/Genymobile/scrcpy/issues/3568>
PR #3579 <https://github.com/Genymobile/scrcpy/pull/3579>

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
1 year ago
Romain Vimont 18082f6069 Remove continuous resizing workaround for Windows
It turns out that the workaround only worked for MacOS.

Refs #3458 <https://github.com/Genymobile/scrcpy/issues/3458>
Refs SDL/#1059 <https://github.com/libsdl-org/SDL/issues/1059>
1 year ago
Romain Vimont 8b38b11875 Add parent directory in release zipfile
This avoids to mistakenly extract all the files in the current
directory.
1 year ago
Romain Vimont 64821466a1 Use "meson setup"
This fixes the following warning:

> WARNING: Running the setup command as `meson [options]` instead of
> `meson setup [options]` is ambiguous and deprecated.
1 year ago
Romain Vimont 82cb8ab870 Adapt ClipboardManager for Android 13
A new "attributionTag" parameter has been added to the methods
getPrimaryClip(), setPrimaryClip() and addPrimaryClipChangedListener()
of IClipboard.aidl.

Refs <0e3e509b3b%5E%21/>

Fixes #3497 <https://github.com/Genymobile/scrcpy/issues/3497>
1 year ago
Romain Vimont b51841e85d Upgrade junit to 4.13.2 1 year ago
Romain Vimont 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>
2 years ago
Romain Vimont 6469b55861 Fix CommandParserTest code style
Make checkstyle happy.
2 years ago
Romain Vimont c00a9ead5e Always use --key=value in README
Mandatory arguments may be passed in either of these two forms:
 1. --key value
 2. --key=value

Optional argument may only be passed in the second form.

For consistency, always document using --key=value.

Refs f76fe2c0d4
2 years ago
SeungHoon Han 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>
2 years ago
Yu-Chen Lin 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>
2 years ago
Yu-Chen Lin 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>
2 years ago
Romain Vimont 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>
2 years ago
Romain Vimont ffc7b91693 Add missing include <string.h> for strlen() 2 years ago
Romain Vimont cb46e4a64a Add missing include <string.h> for memmove() 2 years ago
Anima C13 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>
2 years ago
Anima C13 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>
2 years ago
Romain Vimont 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).
2 years ago
Romain Vimont 7505f7117e Fix typo in logs 2 years ago
SeungHoon Han 949b64dff2 Add fallback to get DisplayInfo
PR #3416 <https://github.com/Genymobile/scrcpy/pull/3416>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2 years ago
Romain Vimont 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.
2 years ago
Romain Vimont 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.
2 years ago
Romain Vimont 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>
2 years ago
Chih-Hsuan Yen 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>
2 years ago
Addison Snelling 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>
2 years ago
Romain Vimont c1ec1d1023 Replace hardcoded 'share/' by datadir variable
Meson defines a variable for the data directory.

PR #3351 <https://github.com/Genymobile/scrcpy/pull/3351>
2 years ago
Romain Vimont 0a0a446ea6 Upgrade Android SDK to 33 2 years ago
Romain Vimont 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>
2 years ago
Romain Vimont 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/>
2 years ago
Simon Chan 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>
2 years ago
Romain Vimont 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>
2 years ago
Romain Vimont 1ab6c19486 Add unit test for float encoding
PR #3369 <https://github.com/Genymobile/scrcpy/pull/3369>
2 years ago
Romain Vimont fd3483c837 Extract conversion from float to u16 fixed-point
PR #3369 <https://github.com/Genymobile/scrcpy/pull/3369>
2 years ago
Romain Vimont 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>
2 years ago
Romain Vimont 136ab8c199 Add unit test for float decoding
PR #3369 <https://github.com/Genymobile/scrcpy/pull/3369>
2 years ago
Romain Vimont 3848ce86f1 Extract conversion from u16 fixed-point to float
PR #3369 <https://github.com/Genymobile/scrcpy/pull/3369>
2 years ago
Romain Vimont 5b8e9aa0e9 Move toUnsigned() to a Binary util class
PR #3369 <https://github.com/Genymobile/scrcpy/pull/3369>
2 years ago
Romain Vimont 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
2 years ago
Romain Vimont 72ba913324 Move README and FAQ translations to the wiki
This lowers the barrier to contribute to translations, and frees up my
maintenance time used to review and merge translations and their updates
in many languages.
2 years ago
Romain Vimont 77ebe786ea Fix FAQ formatting 2 years ago
Derek Wu 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>
2 years ago
Romain Vimont d19606eb0c Rename net_listen() parameter
For consistency with net_accept(), which necessarily uses a server
socket, name the net_listen() parameter "server_socket".
2 years ago
Romain Vimont 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>
2 years ago
Romain Vimont a47848f304 Detect Windows using _WIN32 in network util
For consistency, always use _WIN32 instead of a mix of __WINDOWS__ and
_WIN32.
2 years ago
Romain Vimont db8c1ce8e1 Fix protocol documentation in comments
Flags were in the correct order in the schema, but their description
were reversed.
2 years ago
Romain Vimont 4aeb78ece2 Add missing allocation failure check 2 years ago
Romain Vimont 396e4bd925 Add missing LOG_OOM() on malloc failure 2 years ago
Romain Vimont 7f2f5950f2 Remove useless dependencies reference
There is no libs/ directory with local jar files.
2 years ago
Romain Vimont ed84e18b1a Document envvars for all platforms
Document how to set environment variables from the terminal for bash,
cmd and PowerShell.
2 years ago
Romain Vimont a83c3e30f3 Fix environment variable configuration in FAQ
In bash, the variable is set using "export".
2 years ago
Romain Vimont af4b7855e1 Remove unused stream.h
The file was not removed by 7dec225ceb.
2 years ago
Romain Vimont b1d8c72780 Rename function to simplify
For consistency with sc_adb_parse_device(), do not include "from_output"
in the function name.
2 years ago
Romain Vimont 55e65fa270 Add missing return 0 in tests 2 years ago
Romain Vimont 69fb5f6ee1 Fix function declarations
Add missing void in function parameters list.
2 years ago
Romain Vimont faf4535487 Reduce SHA-256 size in README and BUILD
This avoids breaking the page layout on GitHub.
2 years ago
Romain Vimont 3a99e129e6 Update links to v1.24 2 years ago
Romain Vimont ef13d394fd Bump version to 1.24 2 years ago
Romain Vimont 0049893e10 Merge branch 'master' into dev 2 years ago
Romain Vimont 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.
2 years ago
Romain Vimont 76b3fcf986 Fix inverted check
SDL_RenderSetLogicalSize() returns 0 on success.

Refs fc8942aa03
2 years ago
Romain Vimont 05d84084ef Fix release script for platform-tools 33.0.1
These paths were not updated by commit
b8d78743f7.
2 years ago
Romain Vimont 471a360099 Use quotes for commands in documentation 2 years ago
Romain Vimont 349dcd8e7b Update installed files list in BUILD documentation 2 years ago
Romain Vimont f9e3275d4e Upgrade FFmpeg (5.0.1) for Windows 64-bit
Use the latest version of FFmpeg in Windows 64-bit releases.
2 years ago
Romain Vimont 91706ae3d0 Upgrade SDL (2.0.22) for Windows
Include the latest version of SDL in Windows releases.
2 years ago
Romain Vimont 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>
2 years ago
Romain Vimont 436b368f9d Make OTG window resizable
PR #3219 <https://github.com/Genymobile/scrcpy/pull/3219>
2 years ago
Romain Vimont 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>
2 years ago
Romain Vimont c6d9711109 Create OTG window with HIGHDPI flag
This will avoid poor quality with HiDPI displays.

PR #3219 <https://github.com/Genymobile/scrcpy/pull/3219>
2 years ago
Romain Vimont 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>
2 years ago
Romain Vimont 326897a0d4 Add missing mouse shortcuts in --help
Document 4th-click and 5th-click shortcuts.

Fixes #3122 <https://github.com/Genymobile/scrcpy/issues/3122>
2 years ago
Sean Wei 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>
2 years ago
Sean Wei 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>
2 years ago
Sean Wei 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>
2 years ago
Romain Vimont 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>
2 years ago
Romain Vimont b8d78743f7 Upgrade platform-tools (33.0.1) for Windows
PR #3206 <https://github.com/Genymobile/scrcpy/pull/3206>
2 years ago
Romain Vimont 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>
2 years ago
Romain Vimont 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>
2 years ago
Gitoffthelawn 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>
2 years ago
John Veness 53b1e16f42 Fix typos/grammar issues in README
PR #3174 <https://github.com/Genymobile/scrcpy/pull/3174>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2 years ago
Alessandro Sarretta 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>
2 years ago
Romain Vimont fa5b2a29e9 Add missing SC_ prefix to header guards 2 years ago
Romain Vimont 0c94887075 Add missing include
Refs c3d45c8397
2 years ago
e1e0 7b7cfc3a3e Fix reference to FAQ in README
PR #3065 <https://github.com/Genymobile/scrcpy/pull/3065>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2 years ago
Romain Vimont 88543cb545 Fix icon path in ./run
The data/ directory has been moved to app/data/.

Refs 36c75e15b8
2 years ago
Romain Vimont aaf3869a54 Fix OpenGL ES prefix skip 2 years ago
IskandarMa 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>
2 years ago
Romain Vimont 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>
2 years ago
Romain Vimont e56f2ac7a9 Log an error on unexpected device state
Refs #3129 <https://github.com/Genymobile/scrcpy/issues/3129>
2 years ago
Romain Vimont 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>
2 years ago
Romain Vimont b1dbc30072 Document exit status in --help
Refs #3085 <https://github.com/Genymobile/scrcpy/pull/3085>
2 years ago
martin f. krafft b3f5dfe1de Add specific exit code for device disconnection
Modify the return logic such that exit code 1 is used when the initial
connection fails, but if a session is established, and then the device
disconnects, exit code 2 is emitted.

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

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

Refs #3085 <https://github.com/Genymobile/scrcpy/pull/3085>
2 years ago
Romain Vimont 8d91cda4f6 Improve HID event push error message
On HID event push failure, add the event type in the error message.
2 years ago
Romain Vimont 59656fe649 Fix typo in error message 2 years ago
Romain Vimont e4bb2b8728 Add libusb error log
Log libusb_get_string_descriptor_ascii() errors.

Refs #3050 <https://github.com/Genymobile/scrcpy/issues/3050>
2 years ago
Romain Vimont adbe7908c6 Fix icon path in README
The data/ directory was moved to app/data/.

Refs 36c75e15b8
2 years ago
Romain Vimont 49434da36e Update links to v1.23 2 years ago

1
.gitignore vendored

@ -7,3 +7,4 @@ build/
.gradle/
/x/
local.properties
/scrcpy-server

@ -1,309 +0,0 @@
# scrcpy for developers
## Overview
This application is composed of two parts:
- the server (`scrcpy-server`), to be executed on the device,
- the client (the `scrcpy` binary), executed on the host computer.
The client is responsible to push the server to the device and start its
execution.
Once the client and the server are connected to each other, the server initially
sends device information (name and initial screen dimensions), then starts to
send a raw H.264 video stream of the device screen. The client decodes the video
frames, and display them as soon as possible, without buffering, to minimize
latency. The client is not aware of the device rotation (which is handled by the
server), it just knows the dimensions of the video frames.
The client captures relevant keyboard and mouse events, that it transmits to the
server, which injects them to the device.
## Server
### Privileges
Capturing the screen requires some privileges, which are granted to `shell`.
The server is a Java application (with a [`public static void main(String...
args)`][main] method), compiled against the Android framework, and executed as
`shell` on the Android device.
[main]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/Server.java#L123
To run such a Java application, the classes must be [_dexed_][dex] (typically,
to `classes.dex`). If `my.package.MainClass` is the main class, compiled to
`classes.dex`, pushed to the device in `/data/local/tmp`, then it can be run
with:
adb shell CLASSPATH=/data/local/tmp/classes.dex \
app_process / my.package.MainClass
_The path `/data/local/tmp` is a good candidate to push the server, since it's
readable and writable by `shell`, but not world-writable, so a malicious
application may not replace the server just before the client executes it._
Instead of a raw _dex_ file, `app_process` accepts a _jar_ containing
`classes.dex` (e.g. an [APK]). For simplicity, and to benefit from the gradle
build system, the server is built to an (unsigned) APK (renamed to
`scrcpy-server`).
[dex]: https://en.wikipedia.org/wiki/Dalvik_(software)
[apk]: https://en.wikipedia.org/wiki/Android_application_package
### Hidden methods
Although compiled against the Android framework, [hidden] methods and classes are
not directly accessible (and they may differ from one Android version to
another).
They can be called using reflection though. The communication with hidden
components is provided by [_wrappers_ classes][wrappers] and [aidl].
[hidden]: https://stackoverflow.com/a/31908373/1987178
[wrappers]: https://github.com/Genymobile/scrcpy/tree/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/wrappers
[aidl]: https://github.com/Genymobile/scrcpy/tree/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/aidl/android/view
### Threading
The server uses 3 threads:
- the **main** thread, encoding and streaming the video to the client;
- the **controller** thread, listening for _control messages_ (typically,
keyboard and mouse events) from the client;
- the **receiver** thread (managed by the controller), sending _device messages_
to the clients (currently, it is only used to send the device clipboard
content).
Since the video encoding is typically hardware, there would be no benefit in
encoding and streaming in two different threads.
### Screen video encoding
The encoding is managed by [`ScreenEncoder`].
The video is encoded using the [`MediaCodec`] API. The codec takes its input
from a [surface] associated to the display, and writes the resulting H.264
stream to the provided output stream (the socket connected to the client).
[`ScreenEncoder`]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java
[`MediaCodec`]: https://developer.android.com/reference/android/media/MediaCodec.html
[surface]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L68-L69
On device [rotation], the codec, surface and display are reinitialized, and a
new video stream is produced.
New frames are produced only when changes occur on the surface. This is good
because it avoids to send unnecessary frames, but there are drawbacks:
- it does not send any frame on start if the device screen does not change,
- after fast motion changes, the last frame may have poor quality.
Both problems are [solved][repeat] by the flag
[`KEY_REPEAT_PREVIOUS_FRAME_AFTER`][repeat-flag].
[rotation]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L90
[repeat]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L147-L148
[repeat-flag]: https://developer.android.com/reference/android/media/MediaFormat.html#KEY_REPEAT_PREVIOUS_FRAME_AFTER
### Input events injection
_Control messages_ are received from the client by the [`Controller`] (run in a
separate thread). There are several types of input events:
- keycode (cf [`KeyEvent`]),
- text (special characters may not be handled by keycodes directly),
- mouse motion/click,
- mouse scroll,
- other commands (e.g. to switch the screen on or to copy the clipboard).
Some of them need to inject input events to the system. To do so, they use the
_hidden_ method [`InputManager.injectInputEvent`] (exposed by our
[`InputManager` wrapper][inject-wrapper]).
[`Controller`]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/Controller.java#L81
[`KeyEvent`]: https://developer.android.com/reference/android/view/KeyEvent.html
[`MotionEvent`]: https://developer.android.com/reference/android/view/MotionEvent.html
[`InputManager.injectInputEvent`]: https://android.googlesource.com/platform/frameworks/base/+/oreo-release/core/java/android/hardware/input/InputManager.java#857
[inject-wrapper]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java#L27
## Client
The client relies on [SDL], which provides cross-platform API for UI, input
events, threading, etc.
The video stream is decoded by [libav] (FFmpeg).
[SDL]: https://www.libsdl.org
[libav]: https://www.libav.org/
### Initialization
On startup, in addition to _libav_ and _SDL_ initialization, the client must
push and start the server on the device, and open two sockets (one for the video
stream, one for control) so that they may communicate.
Note that the client-server roles are expressed at the application level:
- the server _serves_ video stream and handle requests from the client,
- the client _controls_ the device through the server.
However, the roles are reversed at the network level:
- the client opens a server socket and listen on a port before starting the
server,
- the server connects to the client.
This role inversion guarantees that the connection will not fail due to race
conditions, and avoids polling.
_(Note that over TCP/IP, the roles are not reversed, due to a bug in `adb
reverse`. See commit [1038bad] and [issue #5].)_
Once the server is connected, it sends the device information (name and initial
screen dimensions). Thus, the client may init the window and renderer, before
the first frame is available.
To minimize startup time, SDL initialization is performed while listening for
the connection from the server (see commit [90a46b4]).
[1038bad]: https://github.com/Genymobile/scrcpy/commit/1038bad3850f18717a048a4d5c0f8110e54ee172
[issue #5]: https://github.com/Genymobile/scrcpy/issues/5
[90a46b4]: https://github.com/Genymobile/scrcpy/commit/90a46b4c45637d083e877020d85ade52a9a5fa8e
### Threading
The client uses 4 threads:
- the **main** thread, executing the SDL event loop,
- the **stream** thread, receiving the video and used for decoding and
recording,
- the **controller** thread, sending _control messages_ to the server,
- the **receiver** thread (managed by the controller), receiving _device
messages_ from the server.
In addition, another thread can be started if necessary to handle APK
installation or file push requests (via drag&drop on the main window) or to
print the framerate regularly in the console.
### Stream
The video [stream] is received from the socket (connected to the server on the
device) in a separate thread.
If a [decoder] is present (i.e. `--no-display` is not set), then it uses _libav_
to decode the H.264 stream from the socket, and notifies the main thread when a
new frame is available.
There are two [frames][video_buffer] simultaneously in memory:
- the **decoding** frame, written by the decoder from the decoder thread,
- the **rendering** frame, rendered in a texture from the main thread.
When a new decoded frame is available, the decoder _swaps_ the decoding and
rendering frame (with proper synchronization). Thus, it immediately starts
to decode a new frame while the main thread renders the last one.
If a [recorder] is present (i.e. `--record` is enabled), then it muxes the raw
H.264 packet to the output video file.
[stream]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/stream.h
[decoder]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/decoder.h
[video_buffer]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/video_buffer.h
[recorder]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/recorder.h
```
+----------+ +----------+
---> | decoder | ---> | screen |
+---------+ / +----------+ +----------+
socket ---> | stream | ----
+---------+ \ +----------+
---> | recorder |
+----------+
```
### Controller
The [controller] is responsible to send _control messages_ to the device. It
runs in a separate thread, to avoid I/O on the main thread.
On SDL event, received on the main thread, the [input manager][inputmanager]
creates appropriate [_control messages_][controlmsg]. It is responsible to
convert SDL events to Android events (using [convert]). It pushes the _control
messages_ to a queue hold by the controller. On its own thread, the controller
takes messages from the queue, that it serializes and sends to the client.
[controller]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/controller.h
[controlmsg]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/control_msg.h
[inputmanager]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/input_manager.h
[convert]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/convert.h
### UI and event loop
Initialization, input events and rendering are all [managed][scrcpy] in the main
thread.
Events are handled in the [event loop], which either updates the [screen] or
delegates to the [input manager][inputmanager].
[scrcpy]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/scrcpy.c
[event loop]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/scrcpy.c#L201
[screen]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/screen.h
## Hack
For more details, go read the code!
If you find a bug, or have an awesome idea to implement, please discuss and
contribute ;-)
### Debug the server
The server is pushed to the device by the client on startup.
To debug it, enable the server debugger during configuration:
```bash
meson x -Dserver_debugger=true
# or, if x is already configured
meson configure x -Dserver_debugger=true
```
If your device runs Android 8 or below, set the `server_debugger_method` to
`old` in addition:
```bash
meson x -Dserver_debugger=true -Dserver_debugger_method=old
# or, if x is already configured
meson configure x -Dserver_debugger=true -Dserver_debugger_method=old
```
Then recompile.
When you start scrcpy, it will start a debugger on port 5005 on the device.
Redirect that port to the computer:
```bash
adb forward tcp:5005 tcp:5005
```
In Android Studio, _Run_ > _Debug_ > _Edit configurations..._ On the left, click on
`+`, _Remote_, and fill the form:
- Host: `localhost`
- Port: `5005`
Then click on _Debug_.

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

@ -1,84 +0,0 @@
# 자주하는 질문 (FAQ)
다음은 자주 제보되는 문제들과 그들의 현황입니다.
### Windows 운영체제에서, 디바이스가 발견되지 않습니다.
가장 흔한 제보는 `adb`에 발견되지 않는 디바이스 혹은 권한 관련 문제입니다.
다음 명령어를 호출하여 모든 것들에 이상이 없는지 확인하세요:
adb devices
Windows는 당신의 디바이스를 감지하기 위해 [드라이버]가 필요할 수도 있습니다.
[드라이버]: https://developer.android.com/studio/run/oem-usb.html
### 내 디바이스의 미러링만 가능하고, 디바이스와 상호작용을 할 수 없습니다.
일부 디바이스에서는, [simulating input]을 허용하기 위해서 한가지 옵션을 활성화해야 할 수도 있습니다.
개발자 옵션에서 (developer options) 다음을 활성화 하세요:
> **USB debugging (Security settings)**
> _권한 부여와 USB 디버깅을 통한 simulating input을 허용한다_
[simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
### 마우스 클릭이 다른 곳에 적용됩니다.
Mac 운영체제에서, HiDPI support 와 여러 스크린 창이 있는 경우, 입력 위치가 잘못 파악될 수 있습니다.
[issue 15]를 참고하세요.
[issue 15]: https://github.com/Genymobile/scrcpy/issues/15
차선책은 HiDPI support을 비활성화 하고 build하는 방법입니다:
```bash
meson x --buildtype release -Dhidpi_support=false
```
하지만, 동영상은 낮은 해상도로 재생될 것 입니다.
### HiDPI display의 화질이 낮습니다.
Windows에서는, [scaling behavior] 환경을 설정해야 할 수도 있습니다.
> `scrcpy.exe` > Properties > Compatibility > Change high DPI settings >
> Override high DPI scaling behavior > Scaling performed by: _Application_.
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
### KWin compositor가 실행되지 않습니다
Plasma Desktop에서는,_scrcpy_ 가 실행중에는 compositor가 비활성화 됩니다.
차석책으로는, ["Block compositing"를 비활성화하세요][kwin].
[kwin]: https://github.com/Genymobile/scrcpy/issues/114#issuecomment-378778613
###비디오 스트림을 열 수 없는 에러가 발생합니다.(Could not open video stream).
여러가지 원인이 있을 수 있습니다. 가장 흔한 원인은 디바이스의 하드웨어 인코더(hardware encoder)가
주어진 해상도를 인코딩할 수 없는 경우입니다.
```
ERROR: Exception on thread Thread[main,5,main]
android.media.MediaCodec$CodecException: Error 0xfffffc0e
...
Exit due to uncaughtException in main thread:
ERROR: Could not open video stream
INFO: Initial texture: 1080x2336
```
더 낮은 해상도로 시도 해보세요:
```
scrcpy -m 1920
scrcpy -m 1024
scrcpy -m 800
```

185
FAQ.md

@ -7,7 +7,7 @@ Here are the common reported problems and their status.
If you encounter any error, the first step is to upgrade to the latest version.
## `adb` issues
## `adb` and USB issues
`scrcpy` execute `adb` commands to initialize the connection with the device. If
`adb` fails, then scrcpy will not work.
@ -44,9 +44,9 @@ If your device is not detected, you may need some [drivers] (on Windows). There
### Device unauthorized
> ERROR: Device is unauthorized:
> ERROR: --> (usb) 0123456789abcdef unauthorized
> ERROR: A popup should open on the device to request authorization.
> ERROR: Device is unauthorized:
> ERROR: --> (usb) 0123456789abcdef unauthorized
> ERROR: A popup should open on the device to request authorization.
When connecting, a popup should open on the device. You must authorize USB
debugging.
@ -60,10 +60,10 @@ If it does not open, check [stackoverflow][device-unauthorized].
If several devices are connected, you will encounter this error:
ERROR: Multiple (2) ADB devices:
ERROR: --> (usb) 0123456789abcdef device Nexus_5
ERROR: --> (tcpip) 192.168.1.5:5555 device GM1913
ERROR: Select a device via -s (--serial), -d (--select-usb) or -e (--select-tcpip)
> ERROR: Multiple (2) ADB devices:
> ERROR: --> (usb) 0123456789abcdef device Nexus_5
> ERROR: --> (tcpip) 192.168.1.5:5555 device GM1913
> ERROR: Select a device via -s (--serial), -d (--select-usb) or -e (--select-tcpip)
In that case, you can either provide the identifier of the device you want to
mirror:
@ -103,7 +103,20 @@ You could overwrite the `adb` binary in the other program, or ask _scrcpy_ to
use a specific `adb` binary, by setting the `ADB` environment variable:
```bash
set ADB=/path/to/your/adb
# in bash
export ADB=/path/to/your/adb
scrcpy
```
```cmd
:: in cmd
set ADB=C:\path\to\your\adb.exe
scrcpy
```
```powershell
# in PowerShell
$env:ADB = 'C:\path\to\your\adb.exe'
scrcpy
```
@ -120,6 +133,21 @@ Try with another USB cable or plug it into another USB port. See [#281] and
[#283]: https://github.com/Genymobile/scrcpy/issues/283
## OTG issues on Windows
On Windows, if `scrcpy --otg` (or `--keyboard=aoa`/`--mouse=aoa`) results in:
> ERROR: Could not find any USB device
(or if only unrelated USB devices are detected), there might be drivers issues.
Please read [#3654], in particular [this comment][#3654-comment1] and [the next
one][#3654-comment2].
[#3654]: https://github.com/Genymobile/scrcpy/issues/3654
[#3654-comment1]: https://github.com/Genymobile/scrcpy/issues/3654#issuecomment-1369278232
[#3654-comment2]: https://github.com/Genymobile/scrcpy/issues/3654#issuecomment-1369295011
## Control issues
@ -131,6 +159,8 @@ In developer options, enable:
> **USB debugging (Security settings)**
> _Allow granting permissions and simulating input via USB debugging_
Rebooting the device is necessary once this option is set.
[simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
@ -140,43 +170,17 @@ The default text injection method is [limited to ASCII characters][text-input].
A trick allows to also inject some [accented characters][accented-characters],
but that's all. See [#37].
Since scrcpy v1.20 on Linux, it is possible to simulate a [physical
keyboard][hid] (HID).
To avoid the problem, [change the keyboard mode to simulate a physical
keyboard][hid].
[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
[#37]: https://github.com/Genymobile/scrcpy/issues/37
[hid]: README.md#physical-keyboard-simulation-hid
[hid]: doc/keyboard.md#physical-keyboard-simulation
## Client issues
### The quality is low
If the definition of your client window is smaller than that of your device
screen, then you might get poor quality, especially visible on text (see [#40]).
[#40]: https://github.com/Genymobile/scrcpy/issues/40
This problem should be fixed in scrcpy v1.22: **update to the latest version**.
On older versions, you must configure the [scaling behavior]:
> `scrcpy.exe` > Properties > Compatibility > Change high DPI settings >
> Override high DPI scaling behavior > Scaling performed by: _Application_.
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
Also, to improve downscaling quality, trilinear filtering is enabled
automatically if the renderer is OpenGL and if it supports mipmapping.
On Windows, you might want to force OpenGL to enable mipmapping:
```
scrcpy --render-driver=opengl
```
### Issue with Wayland
By default, SDL uses x11 on Linux. The [video driver] can be changed via the
@ -211,108 +215,21 @@ As a workaround, [disable "Block compositing"][kwin].
### Exception
There may be many reasons. One common cause is that the hardware encoder of your
device is not able to encode at the given definition:
> ```
> ERROR: Exception on thread Thread[main,5,main]
> android.media.MediaCodec$CodecException: Error 0xfffffc0e
> ...
> Exit due to uncaughtException in main thread:
> ERROR: Could not open video stream
> INFO: Initial texture: 1080x2336
> ```
or
> ```
> ERROR: Exception on thread Thread[main,5,main]
> java.lang.IllegalStateException
> at android.media.MediaCodec.native_dequeueOutputBuffer(Native Method)
> ```
Just try with a lower definition:
```
scrcpy -m 1920
scrcpy -m 1024
scrcpy -m 800
```
Since scrcpy v1.22, scrcpy automatically tries again with a lower definition
before failing. This behavior can be disabled with `--no-downsize-on-error`.
You could also try another [encoder](README.md#encoder).
If you encounter this exception on Android 12, then just upgrade to scrcpy >=
1.18 (see [#2129]):
If you get any exception related to `MediaCodec`:
```
> ERROR: Exception on thread Thread[main,5,main]
java.lang.AssertionError: java.lang.reflect.InvocationTargetException
at com.genymobile.scrcpy.wrappers.SurfaceControl.setDisplaySurface(SurfaceControl.java:75)
...
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invoke(Native Method)
at com.genymobile.scrcpy.wrappers.SurfaceControl.setDisplaySurface(SurfaceControl.java:73)
... 7 more
Caused by: java.lang.IllegalArgumentException: displayToken must not be null
at android.view.SurfaceControl$Transaction.setDisplaySurface(SurfaceControl.java:3067)
at android.view.SurfaceControl.setDisplaySurface(SurfaceControl.java:2147)
... 9 more
ERROR: Exception on thread Thread[main,5,main]
java.lang.IllegalStateException
at android.media.MediaCodec.native_dequeueOutputBuffer(Native Method)
```
[#2129]: https://github.com/Genymobile/scrcpy/issues/2129
## Command line on Windows
Since v1.22, a "shortcut" has been added to directly open a terminal in the
scrcpy directory. Double-click on `open_a_terminal_here.bat`, then type your
command. For example:
```
scrcpy --record file.mkv
```
You could also open a terminal and go to the scrcpy folder manually:
1. Press <kbd>Windows</kbd>+<kbd>r</kbd>, this opens a dialog box.
2. Type `cmd` and press <kbd>Enter</kbd>, this opens a terminal.
3. Go to your _scrcpy_ directory, by typing (adapt the path):
```bat
cd C:\Users\user\Downloads\scrcpy-win64-xxx
```
and press <kbd>Enter</kbd>
4. Type your command. For example:
```bat
scrcpy --record file.mkv
```
If you plan to always use the same arguments, create a file `myscrcpy.bat`
(enable [show file extensions] to avoid confusion) in the `scrcpy` directory,
containing your command. For example:
```bat
scrcpy --prefer-text --turn-screen-off --stay-awake
```
Then just double-click on that file.
You could also edit (a copy of) `scrcpy-console.bat` or `scrcpy-noconsole.vbs`
to add some arguments.
[show file extensions]: https://www.howtogeek.com/205086/beginner-how-to-make-windows-show-file-extensions/
then try with another [encoder](doc/video.md#encoder).
## Translations
This FAQ is available in other languages:
Translations of this FAQ in other languages are available in the [wiki].
[wiki]: https://github.com/Genymobile/scrcpy/wiki
- [Italiano (Italiano, `it`) - v1.19](FAQ.it.md)
- [한국어 (Korean, `ko`) - v1.11](FAQ.ko.md)
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.22](FAQ.zh-Hans.md)
Only this FAQ file is guaranteed to be up-to-date.

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

@ -188,7 +188,7 @@
identification within third-party archives.
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont
Copyright (C) 2018-2024 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

File diff suppressed because it is too large Load Diff

@ -1,696 +0,0 @@
_Only the original [README](README.md) is guaranteed to be up-to-date._
# scrcpy (v1.16)
Aplikasi ini menyediakan tampilan dan kontrol perangkat Android yang terhubung pada USB (atau [melalui TCP/IP][article-tcpip]). Ini tidak membutuhkan akses _root_ apa pun. Ini bekerja pada _GNU/Linux_, _Windows_ and _macOS_.
![screenshot](assets/screenshot-debian-600.jpg)
Ini berfokus pada:
- **keringanan** (asli, hanya menampilkan layar perangkat)
- **kinerja** (30~60fps)
- **kualitas** (1920×1080 atau lebih)
- **latensi** rendah ([35~70ms][lowlatency])
- **waktu startup rendah** (~1 detik untuk menampilkan gambar pertama)
- **tidak mengganggu** (tidak ada yang terpasang di perangkat)
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
## Persyaratan
Perangkat Android membutuhkan setidaknya API 21 (Android 5.0).
Pastikan Anda [mengaktifkan debugging adb][enable-adb] pada perangkat Anda.
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
Di beberapa perangkat, Anda juga perlu mengaktifkan [opsi tambahan][control] untuk mengontrolnya menggunakan keyboard dan mouse.
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
## Dapatkan aplikasinya
### Linux
Di Debian (_testing_ dan _sid_ untuk saat ini) dan Ubuntu (20.04):
```
apt install scrcpy
```
Paket [Snap] tersedia: [`scrcpy`][snap-link].
[snap-link]: https://snapstats.org/snaps/scrcpy
[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager)
Untuk Fedora, paket [COPR] tersedia: [`scrcpy`][copr-link].
[COPR]: https://fedoraproject.org/wiki/Category:Copr
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
Untuk Arch Linux, paket [AUR] tersedia: [`scrcpy`][aur-link].
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
Untuk Gentoo, tersedia [Ebuild]: [`scrcpy/`][ebuild-link].
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
Anda juga bisa [membangun aplikasi secara manual][BUILD] (jangan khawatir, tidak terlalu sulit).
### Windows
Untuk Windows, untuk kesederhanaan, arsip prebuilt dengan semua dependensi (termasuk `adb`) tersedia :
- [README](README.md#windows)
Ini juga tersedia di [Chocolatey]:
[Chocolatey]: https://chocolatey.org/
```bash
choco install scrcpy
choco install adb # jika Anda belum memilikinya
```
Dan di [Scoop]:
```bash
scoop install scrcpy
scoop install adb # jika Anda belum memilikinya
```
[Scoop]: https://scoop.sh
Anda juga dapat [membangun aplikasi secara manual][BUILD].
### macOS
Aplikasi ini tersedia di [Homebrew]. Instal saja:
[Homebrew]: https://brew.sh/
```bash
brew install scrcpy
```
Anda membutuhkan `adb`, dapat diakses dari `PATH` Anda. Jika Anda belum memilikinya:
```bash
brew cask install android-platform-tools
```
Anda juga dapat [membangun aplikasi secara manual][BUILD].
## Menjalankan
Pasang perangkat Android, dan jalankan:
```bash
scrcpy
```
Ini menerima argumen baris perintah, didaftarkan oleh:
```bash
scrcpy --help
```
## Fitur
### Menangkap konfigurasi
#### Mengurangi ukuran
Kadang-kadang, berguna untuk mencerminkan perangkat Android dengan definisi yang lebih rendah untuk meningkatkan kinerja.
Untuk membatasi lebar dan tinggi ke beberapa nilai (mis. 1024):
```bash
scrcpy --max-size 1024
scrcpy -m 1024 # versi pendek
```
Dimensi lain dihitung agar rasio aspek perangkat dipertahankan.
Dengan begitu, perangkat 1920×1080 akan dicerminkan pada 1024×576.
#### Ubah kecepatan bit
Kecepatan bit default adalah 8 Mbps. Untuk mengubah bitrate video (mis. Menjadi 2 Mbps):
```bash
scrcpy --bit-rate 2M
scrcpy -b 2M # versi pendek
```
#### Batasi frekuensi gambar
Kecepatan bingkai pengambilan dapat dibatasi:
```bash
scrcpy --max-fps 15
```
Ini secara resmi didukung sejak Android 10, tetapi dapat berfungsi pada versi sebelumnya.
#### Memotong
Layar perangkat dapat dipotong untuk mencerminkan hanya sebagian dari layar.
Ini berguna misalnya untuk mencerminkan hanya satu mata dari Oculus Go:
```bash
scrcpy --crop 1224:1440:0:0 # 1224x1440 Mengimbangi (0,0)
```
Jika `--max-size` juga ditentukan, pengubahan ukuran diterapkan setelah pemotongan.
#### Kunci orientasi video
Untuk mengunci orientasi pencerminan:
```bash
scrcpy --lock-video-orientation 0 # orientasi alami
scrcpy --lock-video-orientation 1 # 90° berlawanan arah jarum jam
scrcpy --lock-video-orientation 2 # 180°
scrcpy --lock-video-orientation 3 # 90° searah jarum jam
```
Ini mempengaruhi orientasi perekaman.
### Rekaman
Anda dapat merekam layar saat melakukan mirroring:
```bash
scrcpy --record file.mp4
scrcpy -r file.mkv
```
Untuk menonaktifkan pencerminan saat merekam:
```bash
scrcpy --no-display --record file.mp4
scrcpy -Nr file.mkv
# berhenti merekam dengan Ctrl+C
```
"Skipped frames" are recorded, even if they are not displayed in real time (for
performance reasons). Frames are _timestamped_ on the device, so [packet delay
variation] does not impact the recorded file.
"Frame yang dilewati" direkam, meskipun tidak ditampilkan secara real time (untuk alasan performa). Bingkai *diberi stempel waktu* pada perangkat, jadi [variasi penundaan paket] tidak memengaruhi file yang direkam.
[variasi penundaan paket]: https://en.wikipedia.org/wiki/Packet_delay_variation
### Koneksi
#### Wireless
_Scrcpy_ menggunakan `adb` untuk berkomunikasi dengan perangkat, dan` adb` dapat [terhubung] ke perangkat melalui TCP / IP:
1. Hubungkan perangkat ke Wi-Fi yang sama dengan komputer Anda.
2. Dapatkan alamat IP perangkat Anda (dalam Pengaturan → Tentang ponsel → Status).
3. Aktifkan adb melalui TCP / IP pada perangkat Anda: `adb tcpip 5555`.
4. Cabut perangkat Anda.
5. Hubungkan ke perangkat Anda: `adb connect DEVICE_IP: 5555` (*ganti* *`DEVICE_IP`*).
6. Jalankan `scrcpy` seperti biasa.
Mungkin berguna untuk menurunkan kecepatan bit dan definisi:
```bash
scrcpy --bit-rate 2M --max-size 800
scrcpy -b2M -m800 # versi pendek
```
[terhubung]: https://developer.android.com/studio/command-line/adb.html#wireless
#### Multi-perangkat
Jika beberapa perangkat dicantumkan di `adb devices`, Anda harus menentukan _serial_:
```bash
scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # versi pendek
```
If the device is connected over TCP/IP:
```bash
scrcpy --serial 192.168.0.1:5555
scrcpy -s 192.168.0.1:5555 # versi pendek
```
Anda dapat memulai beberapa contoh _scrcpy_ untuk beberapa perangkat.
#### Mulai otomatis pada koneksi perangkat
Anda bisa menggunakan [AutoAdb]:
```bash
autoadb scrcpy -s '{}'
```
[AutoAdb]: https://github.com/rom1v/autoadb
#### Koneksi via SSH tunnel
Untuk menyambung ke perangkat jarak jauh, dimungkinkan untuk menghubungkan klien `adb` lokal ke server `adb` jarak jauh (asalkan mereka menggunakan versi yang sama dari _adb_ protocol):
```bash
adb kill-server # matikan server adb lokal di 5037
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 komputer_jarak_jauh_anda
# jaga agar tetap terbuka
```
Dari terminal lain:
```bash
scrcpy
```
Untuk menghindari mengaktifkan penerusan port jarak jauh, Anda dapat memaksa sambungan maju sebagai gantinya (perhatikan `-L`, bukan` -R`):
```bash
adb kill-server # matikan server adb lokal di 5037
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 komputer_jarak_jauh_anda
# jaga agar tetap terbuka
```
Dari terminal lain:
```bash
scrcpy --force-adb-forward
```
Seperti koneksi nirkabel, mungkin berguna untuk mengurangi kualitas:
```
scrcpy -b2M -m800 --max-fps 15
```
### Konfigurasi Jendela
#### Judul
Secara default, judul jendela adalah model perangkat. Itu bisa diubah:
```bash
scrcpy --window-title 'Perangkat Saya'
```
#### Posisi dan ukuran
Posisi dan ukuran jendela awal dapat ditentukan:
```bash
scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
```
#### Jendela tanpa batas
Untuk menonaktifkan dekorasi jendela:
```bash
scrcpy --window-borderless
```
#### Selalu di atas
Untuk menjaga jendela scrcpy selalu di atas:
```bash
scrcpy --always-on-top
```
#### Layar penuh
Aplikasi dapat dimulai langsung dalam layar penuh::
```bash
scrcpy --fullscreen
scrcpy -f # versi pendek
```
Layar penuh kemudian dapat diubah secara dinamis dengan <kbd>MOD</kbd>+<kbd>f</kbd>.
#### Rotasi
Jendela mungkin diputar:
```bash
scrcpy --rotation 1
```
Nilai yang mungkin adalah:
- `0`: tidak ada rotasi
- `1`: 90 derajat berlawanan arah jarum jam
- `2`: 180 derajat
- `3`: 90 derajat searah jarum jam
Rotasi juga dapat diubah secara dinamis dengan <kbd>MOD</kbd>+<kbd></kbd>
_(kiri)_ and <kbd>MOD</kbd>+<kbd></kbd> _(kanan)_.
Perhatikan bahwa _scrcpy_ mengelola 3 rotasi berbeda::
- <kbd>MOD</kbd>+<kbd>r</kbd> meminta perangkat untuk beralih antara potret dan lanskap (aplikasi yang berjalan saat ini mungkin menolak, jika mendukung orientasi yang diminta).
- `--lock-video-orientation` mengubah orientasi pencerminan (orientasi video yang dikirim dari perangkat ke komputer). Ini mempengaruhi rekaman.
- `--rotation` (atau <kbd>MOD</kbd>+<kbd></kbd>/<kbd>MOD</kbd>+<kbd></kbd>)
memutar hanya konten jendela. Ini hanya mempengaruhi tampilan, bukan rekaman.
### Opsi pencerminan lainnya
#### Hanya-baca
Untuk menonaktifkan kontrol (semua yang dapat berinteraksi dengan perangkat: tombol input, peristiwa mouse, seret & lepas file):
```bash
scrcpy --no-control
scrcpy -n
```
#### Layar
Jika beberapa tampilan tersedia, Anda dapat memilih tampilan untuk cermin:
```bash
scrcpy --display 1
```
Daftar id tampilan dapat diambil dengan::
```
adb shell dumpsys display # cari "mDisplayId=" di keluaran
```
Tampilan sekunder hanya dapat dikontrol jika perangkat menjalankan setidaknya Android 10 (jika tidak maka akan dicerminkan dalam hanya-baca).
#### Tetap terjaga
Untuk mencegah perangkat tidur setelah beberapa penundaan saat perangkat dicolokkan:
```bash
scrcpy --stay-awake
scrcpy -w
```
Keadaan awal dipulihkan ketika scrcpy ditutup.
#### Matikan layar
Dimungkinkan untuk mematikan layar perangkat saat pencerminan mulai dengan opsi baris perintah:
```bash
scrcpy --turn-screen-off
scrcpy -S
```
Atau dengan menekan <kbd>MOD</kbd>+<kbd>o</kbd> kapan saja.
Untuk menyalakannya kembali, tekan <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>.
Di Android, tombol `POWER` selalu menyalakan layar. Untuk kenyamanan, jika `POWER` dikirim melalui scrcpy (melalui klik kanan atau<kbd>MOD</kbd>+<kbd>p</kbd>), itu akan memaksa untuk mematikan layar setelah penundaan kecil (atas dasar upaya terbaik).
Tombol fisik `POWER` masih akan menyebabkan layar dihidupkan.
Ini juga berguna untuk mencegah perangkat tidur:
```bash
scrcpy --turn-screen-off --stay-awake
scrcpy -Sw
```
#### Render frame kedaluwarsa
Secara default, untuk meminimalkan latensi, _scrcpy_ selalu menampilkan frame yang terakhir didekodekan tersedia, dan menghapus frame sebelumnya.
Untuk memaksa rendering semua frame (dengan kemungkinan peningkatan latensi), gunakan:
```bash
scrcpy --render-expired-frames
```
#### Tunjukkan sentuhan
Untuk presentasi, mungkin berguna untuk menunjukkan sentuhan fisik (pada perangkat fisik).
Android menyediakan fitur ini di _Opsi Pengembang_.
_Scrcpy_ menyediakan opsi untuk mengaktifkan fitur ini saat mulai dan mengembalikan nilai awal saat keluar:
```bash
scrcpy --show-touches
scrcpy -t
```
Perhatikan bahwa ini hanya menunjukkan sentuhan _fisik_ (dengan jari di perangkat).
#### Nonaktifkan screensaver
Secara default, scrcpy tidak mencegah screensaver berjalan di komputer.
Untuk menonaktifkannya:
```bash
scrcpy --disable-screensaver
```
### Kontrol masukan
#### Putar layar perangkat
Tekan <kbd>MOD</kbd>+<kbd>r</kbd> untuk beralih antara mode potret dan lanskap.
Perhatikan bahwa itu berputar hanya jika aplikasi di latar depan mendukung orientasi yang diminta.
#### Salin-tempel
Setiap kali papan klip Android berubah, secara otomatis disinkronkan ke papan klip komputer.
Apa saja <kbd>Ctrl</kbd> pintasan diteruskan ke perangkat. Khususnya:
- <kbd>Ctrl</kbd>+<kbd>c</kbd> biasanya salinan
- <kbd>Ctrl</kbd>+<kbd>x</kbd> biasanya memotong
- <kbd>Ctrl</kbd>+<kbd>v</kbd> biasanya menempel (setelah sinkronisasi papan klip komputer-ke-perangkat)
Ini biasanya berfungsi seperti yang Anda harapkan.
Perilaku sebenarnya tergantung pada aplikasi yang aktif. Sebagai contoh,
_Termux_ mengirim SIGINT ke <kbd>Ctrl</kbd>+<kbd>c</kbd> sebagai gantinya, dan _K-9 Mail_ membuat pesan baru.
Untuk menyalin, memotong dan menempel dalam kasus seperti itu (tetapi hanya didukung di Android> = 7):
- <kbd>MOD</kbd>+<kbd>c</kbd> injeksi `COPY` _(salin)_
- <kbd>MOD</kbd>+<kbd>x</kbd> injeksi `CUT` _(potong)_
- <kbd>MOD</kbd>+<kbd>v</kbd> injeksi `PASTE` (setelah sinkronisasi papan klip komputer-ke-perangkat)
Tambahan, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> memungkinkan untuk memasukkan teks papan klip komputer sebagai urutan peristiwa penting. Ini berguna ketika komponen tidak menerima penempelan teks (misalnya di _Termux_), tetapi dapat merusak konten non-ASCII.
**PERINGATAN:** Menempelkan papan klip komputer ke perangkat (baik melalui
<kbd>Ctrl</kbd>+<kbd>v</kbd> or <kbd>MOD</kbd>+<kbd>v</kbd>) menyalin konten ke clipboard perangkat. Akibatnya, aplikasi Android apa pun dapat membaca kontennya. Anda harus menghindari menempelkan konten sensitif (seperti kata sandi) seperti itu.
#### Cubit untuk memperbesar/memperkecil
Untuk mensimulasikan "cubit-untuk-memperbesar/memperkecil": <kbd>Ctrl</kbd>+_klik-dan-pindah_.
Lebih tepatnya, tahan <kbd>Ctrl</kbd> sambil menekan tombol klik kiri. Hingga tombol klik kiri dilepaskan, semua gerakan mouse berskala dan memutar konten (jika didukung oleh aplikasi) relatif ke tengah layar.
Secara konkret, scrcpy menghasilkan kejadian sentuh tambahan dari "jari virtual" di lokasi yang dibalik melalui bagian tengah layar.
#### Preferensi injeksi teks
Ada dua jenis [peristiwa][textevents] dihasilkan saat mengetik teks:
- _peristiwa penting_, menandakan bahwa tombol ditekan atau dilepaskan;
- _peristiwa teks_, menandakan bahwa teks telah dimasukkan.
Secara default, huruf dimasukkan menggunakan peristiwa kunci, sehingga keyboard berperilaku seperti yang diharapkan dalam game (biasanya untuk tombol WASD).
Tapi ini mungkin [menyebabkan masalah][prefertext]. Jika Anda mengalami masalah seperti itu, Anda dapat menghindarinya dengan:
```bash
scrcpy --prefer-text
```
(tapi ini akan merusak perilaku keyboard dalam game)
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
#### Ulangi kunci
Secara default, menahan tombol akan menghasilkan peristiwa kunci yang berulang. Ini dapat menyebabkan masalah kinerja di beberapa game, di mana acara ini tidak berguna.
Untuk menghindari penerusan peristiwa penting yang berulang:
```bash
scrcpy --no-key-repeat
```
### Seret/jatuhkan file
#### Pasang APK
Untuk menginstal APK, seret & lepas file APK (diakhiri dengan `.apk`) ke jendela _scrcpy_.
Tidak ada umpan balik visual, log dicetak ke konsol.
#### Dorong file ke perangkat
Untuk mendorong file ke `/sdcard/` di perangkat, seret & jatuhkan file (non-APK) ke jendela _scrcpy_.
Tidak ada umpan balik visual, log dicetak ke konsol.
Direktori target dapat diubah saat mulai:
```bash
scrcpy --push-target /sdcard/foo/bar/
```
### Penerusan audio
Audio tidak diteruskan oleh _scrcpy_. Gunakan [sndcpy].
Lihat juga [Masalah #14].
[sndcpy]: https://github.com/rom1v/sndcpy
[Masalah #14]: https://github.com/Genymobile/scrcpy/issues/14
## Pintasan
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:
```bash
# gunakan RCtrl untuk jalan pintas
scrcpy --shortcut-mod=rctrl
# gunakan baik LCtrl+LAlt atau LSuper untuk jalan pintas
scrcpy --shortcut-mod=lctrl+lalt,lsuper
```
_<kbd>[Super]</kbd> biasanya adalah <kbd>Windows</kbd> atau <kbd>Cmd</kbd> key._
[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button)
| Aksi | Pintasan
| ------------------------------------------------------|:-----------------------------
| Alihkan mode layar penuh | <kbd>MOD</kbd>+<kbd>f</kbd>
| Putar layar kiri | <kbd>MOD</kbd>+<kbd></kbd> _(kiri)_
| Putar layar kanan | <kbd>MOD</kbd>+<kbd></kbd> _(kanan)_
| Ubah ukuran jendela menjadi 1:1 (piksel-sempurna) | <kbd>MOD</kbd>+<kbd>g</kbd>
| Ubah ukuran jendela menjadi hapus batas hitam | <kbd>MOD</kbd>+<kbd>w</kbd> \| _klik-dua-kali¹_
| Klik `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Klik-tengah_
| Klik `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Klik-kanan²_
| Klik `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd>
| Klik `MENU` (buka kunci layar) | <kbd>MOD</kbd>+<kbd>m</kbd>
| Klik `VOLUME_UP` | <kbd>MOD</kbd>+<kbd></kbd> _(naik)_
| Klik `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd></kbd> _(turun)_
| Klik `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd>
| Menyalakan | _Klik-kanan²_
| Matikan layar perangkat (tetap mirroring) | <kbd>MOD</kbd>+<kbd>o</kbd>
| Hidupkan layar perangkat | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
| Putar layar perangkat | <kbd>MOD</kbd>+<kbd>r</kbd>
| Luaskan panel notifikasi | <kbd>MOD</kbd>+<kbd>n</kbd>
| Ciutkan panel notifikasi | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| Menyalin ke papan klip³ | <kbd>MOD</kbd>+<kbd>c</kbd>
| Potong ke papan klip³ | <kbd>MOD</kbd>+<kbd>x</kbd>
| Sinkronkan papan klip dan tempel³ | <kbd>MOD</kbd>+<kbd>v</kbd>
| Masukkan teks papan klip komputer | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| Mengaktifkan/menonaktifkan penghitung FPS (di stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| Cubit-untuk-memperbesar/memperkecil | <kbd>Ctrl</kbd>+_klik-dan-pindah_
_¹Klik-dua-kali pada batas hitam untuk menghapusnya._
_²Klik-kanan akan menghidupkan layar jika mati, tekan BACK jika tidak._
_³Hanya di Android >= 7._
Semua <kbd>Ctrl</kbd>+_key_ pintasan diteruskan ke perangkat, demikian adanya
ditangani oleh aplikasi aktif.
## Jalur kustom
Untuk menggunakan biner _adb_ tertentu, konfigurasikan jalurnya di variabel lingkungan `ADB`:
ADB=/path/to/adb scrcpy
Untuk mengganti jalur file `scrcpy-server`, konfigurasikan jalurnya di
`SCRCPY_SERVER_PATH`.
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
## Mengapa _scrcpy_?
Seorang kolega menantang saya untuk menemukan nama yang tidak dapat diucapkan seperti [gnirehtet].
[`strcpy`] menyalin sebuah **str**ing; `scrcpy` menyalin sebuah **scr**een.
[gnirehtet]: https://github.com/Genymobile/gnirehtet
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
## Bagaimana Cara membangun?
Lihat [BUILD].
[BUILD]: BUILD.md
## Masalah umum
Lihat [FAQ](FAQ.md).
## Pengembang
Baca [halaman pengembang].
[halaman pengembang]: DEVELOP.md
## Lisensi
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
## Artikel
- [Introducing scrcpy][article-intro]
- [Scrcpy now works wirelessly][article-tcpip]
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/

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

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

@ -1,498 +0,0 @@
_Only the original [README](README.md) is guaranteed to be up-to-date._
# scrcpy (v1.11)
This document will be updated frequently along with the original Readme file
이 문서는 원어 리드미 파일의 업데이트에 따라 종종 업데이트 될 것입니다
이 어플리케이션은 UBS ( 혹은 [TCP/IP][article-tcpip] ) 로 연결된 Android 디바이스를 화면에 보여주고 관리하는 것을 제공합니다.
_GNU/Linux_, _Windows__macOS_ 상에서 작동합니다.
(아래 설명에서 디바이스는 안드로이드 핸드폰을 의미합니다.)
[article-tcpip]:https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/
![screenshot](https://github.com/Genymobile/scrcpy/blob/master/assets/screenshot-debian-600.jpg?raw=true)
주요 기능은 다음과 같습니다.
- **가벼움** (기본적이며 디바이스의 화면만을 보여줌)
- **뛰어난 성능** (30~60fps)
- **높은 품질** (1920×1080 이상의 해상도)
- **빠른 반응 속도** ([35~70ms][lowlatency])
- **짧은 부팅 시간** (첫 사진을 보여주는데 최대 1초 소요됨)
- **장치 설치와는 무관함** (디바이스에 설치하지 않아도 됨)
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
## 요구사항
안드로이드 장치는 최소 API 21 (Android 5.0) 을 필요로 합니다.
디바이스에 [adb debugging][enable-adb]이 가능한지 확인하십시오.
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
어떤 디바이스에서는, 키보드와 마우스를 사용하기 위해서 [추가 옵션][control] 이 필요하기도 합니다.
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
## 앱 설치하기
### Linux (리눅스)
리눅스 상에서는 보통 [어플을 직접 설치][BUILD] 해야합니다. 어렵지 않으므로 걱정하지 않아도 됩니다.
[BUILD]:https://github.com/Genymobile/scrcpy/blob/master/BUILD.md
[Snap] 패키지가 가능합니다 : [`scrcpy`][snap-link].
[snap-link]: https://snapstats.org/snaps/scrcpy
[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager)
Arch Linux에서, [AUR] 패키지가 가능합니다 : [`scrcpy`][aur-link].
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
Gentoo에서 ,[Ebuild] 가 가능합니다 : [`scrcpy/`][ebuild-link].
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
### Windows (윈도우)
윈도우 상에서, 간단하게 설치하기 위해 종속성이 있는 사전 구축된 아카이브가 제공됩니다 (`adb` 포함) :
해당 파일은 Readme원본 링크를 통해서 다운로드가 가능합니다.
- [README](README.md#windows)
[어플을 직접 설치][BUILD] 할 수도 있습니다.
### macOS (맥 OS)
이 어플리케이션은 아래 사항을 따라 설치한다면 [Homebrew] 에서도 사용 가능합니다 :
[Homebrew]: https://brew.sh/
```bash
brew install scrcpy
```
`PATH` 로부터 접근 가능한 `adb` 가 필요합니다. 아직 설치하지 않았다면 다음을 따라 설치해야 합니다 :
```bash
brew cask install android-platform-tools
```
[어플을 직접 설치][BUILD] 할 수도 있습니다.
## 실행
안드로이드 디바이스를 연결하고 실행하십시오:
```bash
scrcpy
```
다음과 같이 명령창 옵션 기능도 제공합니다.
```bash
scrcpy --help
```
## 기능
### 캡쳐 환경 설정
### 사이즈 재정의
가끔씩 성능을 향상시키기위해 안드로이드 디바이스를 낮은 해상도에서 미러링하는 것이 유용할 때도 있습니다.
너비와 높이를 제한하기 위해 특정 값으로 지정할 수 있습니다 (e.g. 1024) :
```bash
scrcpy --max-size 1024
scrcpy -m 1024 # 축약 버전
```
이 외의 크기도 디바이스의 가로 세로 비율이 유지된 상태에서 계산됩니다.
이러한 방식으로 디바이스 상에서 1920×1080 는 모니터 상에서1024×576로 미러링될 것 입니다.
### bit-rate 변경
기본 bit-rate 는 8 Mbps입니다. 비디오 bit-rate 를 변경하기 위해선 다음과 같이 입력하십시오 (e.g. 2 Mbps로 변경):
```bash
scrcpy --bit-rate 2M
scrcpy -b 2M # 축약 버전
```
### 프레임 비율 제한
안드로이드 버전 10이상의 디바이스에서는, 다음의 명령어로 캡쳐 화면의 프레임 비율을 제한할 수 있습니다:
```bash
scrcpy --max-fps 15
```
### Crop (잘라내기)
디바이스 화면은 화면의 일부만 미러링하기 위해 잘라질 것입니다.
예를 들어, *Oculus Go* 의 한 쪽 눈만 미러링할 때 유용합니다 :
```bash
scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0)
scrcpy -c 1224:1440:0:0 # 축약 버전
```
만약 `--max-size` 도 지정하는 경우, 잘라낸 다음에 재정의된 크기가 적용될 것입니다.
### 화면 녹화
미러링하는 동안 화면 녹화를 할 수 있습니다 :
```bash
scrcpy --record file.mp4
scrcpy -r file.mkv
```
녹화하는 동안 미러링을 멈출 수 있습니다 :
```bash
scrcpy --no-display --record file.mp4
scrcpy -Nr file.mkv
# Ctrl+C 로 녹화를 중단할 수 있습니다.
# 윈도우 상에서 Ctrl+C 는 정상정으로 종료되지 않을 수 있으므로, 디바이스 연결을 해제하십시오.
```
"skipped frames" 은 모니터 화면에 보여지지 않았지만 녹화되었습니다 ( 성능 문제로 인해 ). 프레임은 디바이스 상에서 _타임 스탬프 ( 어느 시점에 데이터가 존재했다는 사실을 증명하기 위해 특정 위치에 시각을 표시 )_ 되었으므로, [packet delay
variation] 은 녹화된 파일에 영향을 끼치지 않습니다.
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
## 연결
### 무선연결
_Scrcpy_ 장치와 정보를 주고받기 위해 `adb` 를 사용합니다. `adb` 는 TCIP/IP 를 통해 디바이스와 [연결][connect] 할 수 있습니다 :
1. 컴퓨터와 디바이스를 동일한 Wi-Fi 에 연결합니다.
2. 디바이스의 IP address 를 확인합니다 (설정 → 내 기기 → 상태 / 혹은 인터넷에 '내 IP'검색 시 확인 가능합니다. ).
3. TCP/IP 를 통해 디바이스에서 adb 를 사용할 수 있게 합니다: `adb tcpip 5555`.
4. 디바이스 연결을 해제합니다.
5. adb 를 통해 디바이스에 연결을 합니다\: `adb connect DEVICE_IP:5555` _(`DEVICE_IP` 대신)_.
6. `scrcpy` 실행합니다.
다음은 bit-rate 와 해상도를 줄이는데 유용합니다 :
```bash
scrcpy --bit-rate 2M --max-size 800
scrcpy -b2M -m800 # 축약 버전
```
[connect]: https://developer.android.com/studio/command-line/adb.html#wireless
### 여러 디바이스 사용 가능
만약에 여러 디바이스들이 `adb devices` 목록에 표시되었다면, _serial_ 을 명시해야합니다:
```bash
scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # 축약 버전
```
_scrcpy_ 로 여러 디바이스를 연결해 사용할 수 있습니다.
#### SSH tunnel
떨어져 있는 디바이스와 연결하기 위해서는, 로컬 `adb` client와 떨어져 있는 `adb` 서버를 연결해야 합니다. (디바이스와 클라이언트가 동일한 버전의 _adb_ protocol을 사용할 경우에 제공됩니다.):
```bash
adb kill-server # 5037의 로컬 local adb server를 중단
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
# 실행 유지
```
다른 터미널에서는 :
```bash
scrcpy
```
무선 연결과 동일하게, 화질을 줄이는 것이 나을 수 있습니다:
```
scrcpy -b2M -m800 --max-fps 15
```
## Window에서의 배치
### 맞춤형 window 제목
기본적으로, window의 이름은 디바이스의 모델명 입니다.
다음의 명령어를 통해 변경하세요.
```bash
scrcpy --window-title 'My device'
```
### 배치와 크기
초기 window창의 배치와 크기는 다음과 같이 설정할 수 있습니다:
```bash
scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
```
### 경계 없애기
윈도우 장식(경계선 등)을 다음과 같이 제거할 수 있습니다:
```bash
scrcpy --window-borderless
```
### 항상 모든 윈도우 위에 실행창 고정
이 어플리케이션의 윈도우 창은 다음의 명령어로 다른 window 위에 디스플레이 할 수 있습니다:
```bash
scrcpy --always-on-top
scrcpy -T # 축약 버전
```
### 전체 화면
이 어플리케이션은 전체화면으로 바로 시작할 수 있습니다.
```bash
scrcpy --fullscreen
scrcpy -f # short version
```
전체 화면은 `Ctrl`+`f`키로 끄거나 켤 수 있습니다.
## 다른 미러링 옵션
### 읽기 전용(Read-only)
권한을 제한하기 위해서는 (디바이스와 관련된 모든 것: 입력 키, 마우스 이벤트 , 파일의 드래그 앤 드랍(drag&drop)):
```bash
scrcpy --no-control
scrcpy -n
```
### 화면 끄기
미러링을 실행하는 와중에 디바이스의 화면을 끌 수 있게 하기 위해서는
다음의 커맨드 라인 옵션을(command line option) 입력하세요:
```bash
scrcpy --turn-screen-off
scrcpy -S
```
혹은 `Ctrl`+`o`을 눌러 언제든지 디바이스의 화면을 끌 수 있습니다.
다시 화면을 켜기 위해서는`POWER` (혹은 `Ctrl`+`p`)를 누르세요.
### 유효기간이 지난 프레임 제공 (Render expired frames)
디폴트로, 대기시간을 최소화하기 위해 _scrcpy_ 는 항상 마지막으로 디코딩된 프레임을 제공합니다
과거의 프레임은 하나씩 삭제합니다.
모든 프레임을 강제로 렌더링하기 위해서는 (대기 시간이 증가될 수 있습니다)
다음의 명령어를 사용하세요:
```bash
scrcpy --render-expired-frames
```
### 화면에 터치 나타내기
발표를 할 때, 물리적인 기기에 한 물리적 터치를 나타내는 것이 유용할 수 있습니다.
안드로이드 운영체제는 이런 기능을 _Developers options_에서 제공합니다.
_Scrcpy_ 는 이런 기능을 시작할 때와 종료할 때 옵션으로 제공합니다.
```bash
scrcpy --show-touches
scrcpy -t
```
화면에 _물리적인 터치만_ 나타나는 것에 유의하세요 (손가락을 디바이스에 대는 행위).
### 입력 제어
#### 복사-붙여넣기
컴퓨터와 디바이스 양방향으로 클립보드를 복사하는 것이 가능합니다:
- `Ctrl`+`c` 디바이스의 클립보드를 컴퓨터로 복사합니다;
- `Ctrl`+`Shift`+`v` 컴퓨터의 클립보드를 디바이스로 복사합니다;
- `Ctrl`+`v` 컴퓨터의 클립보드를 text event 로써 _붙여넣습니다_ ( 그러나, ASCII 코드가 아닌 경우 실행되지 않습니다 )
#### 텍스트 삽입 우선 순위
텍스트를 입력할 때 생성되는 두 가지의 [events][textevents] 가 있습니다:
- _key events_, 키가 눌려있는 지에 대한 신호;
- _text events_, 텍스트가 입력되었는지에 대한 신호.
기본적으로, 글자들은 key event 를 이용해 입력되기 때문에, 키보드는 게임에서처럼 처리합니다 ( 보통 WASD 키에 대해서 ).
그러나 이는 [issues 를 발생][prefertext]시킵니다. 이와 관련된 문제를 접할 경우, 아래와 같이 피할 수 있습니다:
```bash
scrcpy --prefer-text
```
( 그러나 이는 게임에서의 처리를 중단할 수 있습니다 )
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
### 파일 드랍
### APK 실행하기
APK를 실행하기 위해서는, APK file(파일명이`.apk`로 끝나는 파일)을 드래그하고 _scrcpy_ window에 드랍하세요 (drag and drop)
시각적인 피드백은 없고,log 하나가 콘솔에 출력될 것입니다.
### 디바이스에 파일 push하기
디바이스의`/sdcard/`에 파일을 push하기 위해서는,
APK파일이 아닌 파일을_scrcpy_ window에 드래그하고 드랍하세요.(drag and drop).
시각적인 피드백은 없고,log 하나가 콘솔에 출력될 것입니다.
해당 디렉토리는 시작할 때 변경이 가능합니다:
```bash
scrcpy --push-target /sdcard/foo/bar/
```
### 오디오의 전달
_scrcpy_는 오디오를 직접 전달해주지 않습니다. [USBaudio] (Linux-only)를 사용하세요.
추가적으로 [issue #14]를 참고하세요.
[USBaudio]: https://github.com/rom1v/usbaudio
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14
## 단축키
| 실행내용 | 단축키 | 단축키 (macOS)
| -------------------------------------- |:----------------------------- |:-----------------------------
| 전체화면 모드로 전환 | `Ctrl`+`f` | `Cmd`+`f`
| window를 1:1비율로 전환하기(픽셀 맞춤) | `Ctrl`+`g` | `Cmd`+`g`
| 검은 공백 제거 위한 window 크기 조정 | `Ctrl`+`x` \| _Double-click¹_ | `Cmd`+`x` \| _Double-click¹_
|`HOME` 클릭 | `Ctrl`+`h` \| _Middle-click_ | `Ctrl`+`h` \| _Middle-click_
| `BACK` 클릭 | `Ctrl`+`b` \| _Right-click²_ | `Cmd`+`b` \| _Right-click²_
| `APP_SWITCH` 클릭 | `Ctrl`+`s` | `Cmd`+`s`
| `MENU` 클릭 | `Ctrl`+`m` | `Ctrl`+`m`
| `VOLUME_UP` 클릭 | `Ctrl`+`↑` _(up)_ | `Cmd`+`↑` _(up)_
| `VOLUME_DOWN` 클릭 | `Ctrl`+`↓` _(down)_ | `Cmd`+`↓` _(down)_
| `POWER` 클릭 | `Ctrl`+`p` | `Cmd`+`p`
| 전원 켜기 | _Right-click²_ | _Right-click²_
| 미러링 중 디바이스 화면 끄기 | `Ctrl`+`o` | `Cmd`+`o`
| 알림 패널 늘리기 | `Ctrl`+`n` | `Cmd`+`n`
| 알림 패널 닫기 | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n`
| 디바이스의 clipboard 컴퓨터로 복사하기 | `Ctrl`+`c` | `Cmd`+`c`
| 컴퓨터의 clipboard 디바이스에 붙여넣기 | `Ctrl`+`v` | `Cmd`+`v`
| Copy computer clipboard to device | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v`
| Enable/disable FPS counter (on stdout) | `Ctrl`+`i` | `Cmd`+`i`
_¹검은 공백을 제거하기 위해서는 그 부분을 더블 클릭하세요_
_²화면이 꺼진 상태에서 우클릭 시 다시 켜지며, 그 외의 상태에서는 뒤로 돌아갑니다.
## 맞춤 경로 (custom path)
특정한 _adb_ binary를 사용하기 위해서는, 그것의 경로를 환경변수로 설정하세요.
`ADB`:
ADB=/path/to/adb scrcpy
`scrcpy-server.jar`파일의 경로에 오버라이드 하기 위해서는, 그것의 경로를 `SCRCPY_SERVER_PATH`에 저장하세요.
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
## _scrcpy_ 인 이유?
한 동료가 [gnirehtet]와 같이 발음하기 어려운 이름을 찾을 수 있는지 도발했습니다.
[`strcpy`] 는 **str**ing을 copy하고; `scrcpy`는 **scr**een을 copy합니다.
[gnirehtet]: https://github.com/Genymobile/gnirehtet
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
## 빌드하는 방법?
[BUILD]을 참고하세요.
[BUILD]: BUILD.md
## 흔한 issue
[FAQ](FAQ.md)을 참고하세요.
## 개발자들
[developers page]를 참고하세요.
[developers page]: DEVELOP.md
## 라이선스
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
## 관련 글 (articles)
- [scrcpy 소개][article-intro]
- [무선으로 연결하는 Scrcpy][article-tcpip]
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/

File diff suppressed because it is too large Load Diff

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

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

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

@ -1,993 +0,0 @@
_Only the original [README](README.md) is guaranteed to be up-to-date._
_只有原版的 [README](README.md)是保证最新的。_
Current version is based on [f4c7044]
本文根据[f4c7044]进行翻译。
[f4c7044]: https://github.com/Genymobile/scrcpy/blob/f4c7044b46ae28eb64cb5e1a15c9649a44023c70/README.md
# scrcpy (v1.22)
<img src="data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
_发音为 "**scr**een **c**o**py**"_
本应用程序可以显示并控制通过 USB (或 [TCP/IP][article-tcpip]) 连接的安卓设备,且不需要任何 _root_ 权限。本程序支持 _GNU/Linux_, _Windows__macOS_
![screenshot](assets/screenshot-debian-600.jpg)
本应用专注于:
- **轻量** 原生,仅显示设备屏幕
- **性能** 30~120fps取决于设备
- **质量** 分辨率可达 1920×1080 或更高
- **低延迟** [35~70ms][lowlatency]
- **快速启动** 最快 1 秒内即可显示第一帧
- **无侵入性** 不会在设备上遗留任何程序
- **用户利益** 无需帐号,无广告,无需联网
- **自由** 自由和开源软件
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
功能:
- [屏幕录制](#屏幕录制)
- 镜像时[关闭设备屏幕](#关闭设备屏幕)
- 双向[复制粘贴](#复制粘贴)
- [可配置显示质量](#采集设置)
- 以设备屏幕[作为摄像头(V4L2)](#v4l2loopback) (仅限 Linux)
- [模拟物理键盘 (HID)](#物理键盘模拟-hid) (仅限 Linux)
- [物理鼠标模拟 (HID)](#物理鼠标模拟-hid) (仅限 Linux)
- [OTG模式](#otg) (仅限 Linux)
- 更多 ……
## 系统要求
安卓设备最低需要支持 API 21 (Android 5.0)。
确保设备已[开启 adb 调试][enable-adb]。
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
在某些设备上,还需要开启[额外的选项][control]以使用鼠标和键盘进行控制。
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
## 获取本程序
<a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a>
### 概要
- Linux: `apt install scrcpy`
- Windows: [下载][direct-win64]
- macOS: `brew install scrcpy`
从源代码编译: [构建][BUILD] ([简化过程][BUILD_simple])
[BUILD]: BUILD.md
[BUILD_simple]: BUILD.md#simple
### Linux
在 Debian 和 Ubuntu 上:
```
apt install scrcpy
```
在 Arch Linux 上:
```
pacman -S scrcpy
```
我们也提供 [Snap] 包: [`scrcpy`][snap-link]。
[snap-link]: https://snapstats.org/snaps/scrcpy
[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager)
对 Fedora 我们提供 [COPR] 包: [`scrcpy`][copr-link]。
[COPR]: https://fedoraproject.org/wiki/Category:Copr
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
对 Gentoo 我们提供 [Ebuild] 包:[`scrcpy/`][ebuild-link]。
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
您也可以[自行构建][BUILD] ([简化过程][BUILD_simple])。
### Windows
在 Windows 上,为简便起见,我们提供包含了所有依赖 (包括 `adb`) 的预编译包。
- [README](README.md#windows)
也可以使用 [Chocolatey]
[Chocolatey]: https://chocolatey.org/
```bash
choco install scrcpy
choco install adb # 如果还没有 adb
```
或者 [Scoop]:
```bash
scoop install scrcpy
scoop install adb # 如果还没有 adb
```
[Scoop]: https://scoop.sh
您也可以[自行构建][BUILD]。
### macOS
本程序已发布到 [Homebrew]。直接安装即可:
[Homebrew]: https://brew.sh/
```bash
brew install scrcpy
```
你还需要在 `PATH` 内有 `adb`。如果还没有:
```bash
brew install android-platform-tools
```
或者通过 [MacPorts],该方法同时设置好 adb
```bash
sudo port install scrcpy
```
[MacPorts]: https://www.macports.org/
您也可以[自行构建][BUILD]。
## 运行
连接安卓设备,然后执行:
```bash
scrcpy
```
本程序支持命令行参数,查看参数列表:
```bash
scrcpy --help
```
## 功能介绍
### 采集设置
#### 降低分辨率
有时候,可以通过降低镜像的分辨率来提高性能。
要同时限制宽度和高度到某个值 (例如 1024)
```bash
scrcpy --max-size 1024
scrcpy -m 1024 # 简写
```
另一边会被按比例缩小以保持设备的显示比例。这样1920×1080 分辨率的设备会以 1024×576 的分辨率进行镜像。
#### 修改码率
默认码率是 8 Mbps。改变视频码率 (例如改为 2 Mbps)
```bash
scrcpy --bit-rate 2M
scrcpy -b 2M # 简写
```
#### 限制帧率
要限制采集的帧率:
```bash
scrcpy --max-fps 15
```
本功能从 Android 10 开始才被官方支持,但在一些旧版本中也能生效。
#### 画面裁剪
可以对设备屏幕进行裁剪,只镜像屏幕的一部分。
例如可以只镜像 Oculus Go 的一只眼睛。
```bash
scrcpy --crop 1224:1440:0:0 # 以 (0,0) 为原点的 1224x1440 像素
```
如果同时指定了 `--max-size`,会先进行裁剪,再进行缩放。
#### 锁定屏幕方向
要锁定镜像画面的方向:
```bash
scrcpy --lock-video-orientation # 初始(目前)方向
scrcpy --lock-video-orientation=0 # 自然方向
scrcpy --lock-video-orientation=1 # 逆时针旋转 90°
scrcpy --lock-video-orientation=2 # 180°
scrcpy --lock-video-orientation=3 # 顺时针旋转 90°
```
只影响录制的方向。
[窗口可以独立旋转](#旋转)。
#### 编码器
一些设备内置了多种编码器,但是有的编码器会导致问题或崩溃。可以手动选择其它编码器:
```bash
scrcpy --encoder OMX.qcom.video.encoder.avc
```
要列出可用的编码器,可以指定一个不存在的编码器名称,错误信息中会包含所有的编码器:
```bash
scrcpy --encoder _
```
### 采集
#### 屏幕录制
可以在镜像的同时录制视频:
```bash
scrcpy --record file.mp4
scrcpy -r file.mkv
```
仅录制,不显示镜像:
```bash
scrcpy --no-display --record file.mp4
scrcpy -Nr file.mkv
# 按 Ctrl+C 停止录制
```
录制时会包含“被跳过的帧”,即使它们由于性能原因没有实时显示。设备会为每一帧打上 _时间戳_ ,所以 [包时延抖动][packet delay variation] 不会影响录制的文件。
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
#### v4l2loopback
在 Linux 上,可以将视频流发送至 v4l2 回环 (loopback) 设备,因此可以使用任何 v4l2 工具像摄像头一样打开安卓设备。
需安装 `v4l2loopback` 模块:
```bash
sudo apt install v4l2loopback-dkms
```
创建一个 v4l2 设备:
```bash
sudo modprobe v4l2loopback
```
这样会在 `/dev/videoN` 创建一个新的视频设备,其中 `N` 是整数。 ([更多选项](https://github.com/umlaeute/v4l2loopback#options) 可以用来创建多个设备或者特定 ID 的设备)。
列出已启用的设备:
```bash
# 需要 v4l-utils 包
v4l2-ctl --list-devices
# 简单但或许足够
ls /dev/video*
```
使用一个 v4l2 漏开启 scrcpy
```bash
scrcpy --v4l2-sink=/dev/videoN
scrcpy --v4l2-sink=/dev/videoN --no-display # 禁用窗口镜像
scrcpy --v4l2-sink=/dev/videoN -N # 简写
```
(将 `N` 替换为设备 ID使用 `ls /dev/video*` 命令查看)
启用之后,可以使用 v4l2 工具打开视频流:
```bash
ffplay -i /dev/videoN
vlc v4l2:///dev/videoN # VLC 可能存在一些缓冲延迟
```
例如,可以在 [OBS] 中采集视频。
[OBS]: https://obsproject.com/
#### 缓冲
可以加入缓冲,会增加延迟,但可以减少抖动 (见 [#2464])。
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464
对于显示缓冲:
```bash
scrcpy --display-buffer=50 # 为显示增加 50 毫秒的缓冲
```
对于 V4L2 漏:
```bash
scrcpy --v4l2-buffer=500 # 为 v4l2 漏增加 500 毫秒的缓冲
```
### 连接
#### TCP/IP (无线)
_Scrcpy_ 使用 `adb` 与设备通信,并且 `adb` 支持通过 TCP/IP [连接]到设备(设备必须连接与电脑相同的网络)。
##### 自动配置
参数 `--tcpip` 允许自动配置连接。这里有两种方式。
对于传入的 adb 连接如果设备在这个例子中以192.168.1.1为可用地址已经监听了一个端口通常是5555运行
```bash
scrcpy --tcpip=192.168.1.1 # 默认端口是5555
scrcpy --tcpip=192.168.1.1:5555
```
如果adb TCP/IP无线 模式在某些设备上不被启用或者你不知道IP地址用USB连接设备然后运行
```bash
scrcpy --tcpip # 无需其他参数
```
这将会自动寻找设备IP地址启用TCP/IP模式然后在启动之前连接到设备。
##### 手动配置
或者,可以通过 `adb` 使用手动启用 TCP/IP 连接:
1. 将设备和电脑连接至同一 Wi-Fi。
2. 打开 设置 → 关于手机 → 状态信息,获取设备的 IP 地址,也可以执行以下的命令:
```bash
adb shell ip route | awk '{print $9}'
```
3. 启用设备的网络 adb 功能:`adb tcpip 5555`。
4. 断开设备的 USB 连接。
5. 连接到您的设备:`adb connect DEVICE_IP:5555` _(将 `DEVICE_IP` 替换为设备 IP)_。
6. 正常运行 `scrcpy`
降低比特率和分辨率可能很有用:
```bash
scrcpy --bit-rate 2M --max-size 800
scrcpy -b2M -m800 # 简写
```
[连接]: https://developer.android.com/studio/command-line/adb.html#wireless
#### 多设备
如果 `adb devices` 列出了多个设备,您必须指定设备的 _序列号_
```bash
scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # 简写
```
如果设备通过 TCP/IP 连接:
```bash
scrcpy --serial 192.168.0.1:5555
scrcpy -s 192.168.0.1:5555 # 简写
```
您可以同时启动多个 _scrcpy_ 实例以同时显示多个设备的画面。
#### 在设备连接时自动启动
您可以使用 [AutoAdb]:
```bash
autoadb scrcpy -s '{}'
```
[AutoAdb]: https://github.com/rom1v/autoadb
#### 隧道
要远程连接到设备,可以将本地的 adb 客户端连接到远程的 adb 服务端 (需要两端的 _adb_ 协议版本相同)。
##### 远程ADB服务器
要连接到一个远程ADB服务器让服务器在所有接口上监听
```bash
adb kill-server
adb -a nodaemon server start
# 保持该窗口开启
```
**警告所有客户端与ADB服务器的交流都是未加密的。**
假设此服务器可在 192.168.1.2 访问。 然后,从另一个终端,运行 scrcpy
```bash
export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037
scrcpy --tunnel-host=192.168.1.2
```
默认情况下scrcpy使用用于 `adb forward` 隧道建立的本地端口(通常是 `27183`,见 `--port` )。它也可以强制使用一个不同的隧道端口(当涉及更多的重定向时,这在更复杂的情况下可能很有用):
```
scrcpy --tunnel-port=1234
```
##### SSH 隧道
为了安全地与远程ADB服务器通信最好使用SSH隧道。
首先确保ADB服务器正在远程计算机上运行
```bash
adb start-server
```
然后建立一个SSH隧道
```bash
# 本地 5038 --> 远程 5037
# 本地 27183 <-- 27183
ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer
# 保持该窗口开启
```
在另一个终端上运行scrcpy
```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038
scrcpy
```
若要不使用远程端口转发,可以强制使用正向连接(注意是 `-L` 而不是 `-R`
```bash
# 本地 5038 --> 远程 5037
# 本地 27183 <-- 27183
ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer
# 保持该窗口开启
```
在另一个终端上运行scrcpy
```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038
scrcpy --force-adb-forward
```
类似地,对于无线连接,可能需要降低画面质量:
```
scrcpy -b2M -m800 --max-fps 15
```
### 窗口设置
#### 标题
窗口的标题默认为设备型号。可以通过如下命令修改:
```bash
scrcpy --window-title "我的设备"
```
#### 位置和大小
您可以指定初始的窗口位置和大小:
```bash
scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
```
#### 无边框
禁用窗口边框:
```bash
scrcpy --window-borderless
```
#### 保持窗口在最前
您可以通过如下命令保持窗口在最前面:
```bash
scrcpy --always-on-top
```
#### 全屏
您可以通过如下命令直接全屏启动 scrcpy
```bash
scrcpy --fullscreen
scrcpy -f # 简写
```
全屏状态可以通过 <kbd>MOD</kbd>+<kbd>f</kbd> 随时切换。
#### 旋转
可以通过以下命令旋转窗口:
```bash
scrcpy --rotation 1
```
可选的值有:
- `0`: 无旋转
- `1`: 逆时针旋转 90°
- `2`: 旋转 180°
- `3`: 顺时针旋转 90°
也可以使用 <kbd>MOD</kbd>+<kbd></kbd> _(左箭头)_<kbd>MOD</kbd>+<kbd></kbd> _(右箭头)_ 随时更改。
需要注意的是, _scrcpy_ 中有三类旋转方向:
- <kbd>MOD</kbd>+<kbd>r</kbd> 请求设备在竖屏和横屏之间切换 (如果前台应用程序不支持请求的朝向,可能会拒绝该请求)。
- [`--lock-video-orientation`](#锁定屏幕方向) 改变镜像的朝向 (设备传输到电脑的画面的朝向)。这会影响录制。
- `--rotation` (或 <kbd>MOD</kbd>+<kbd></kbd>/<kbd>MOD</kbd>+<kbd></kbd>) 只旋转窗口的内容。这只影响显示,不影响录制。
### 其他镜像设置
#### 只读
禁用电脑对设备的控制 (任何可与设备交互的方式:如键盘输入、鼠标事件和文件拖放)
```bash
scrcpy --no-control
scrcpy -n
```
#### 显示屏
如果设备有多个显示屏,可以选择要镜像的显示屏:
```bash
scrcpy --display 1
```
可以通过如下命令列出所有显示屏的 id
```
adb shell dumpsys display # 在输出中搜索 “mDisplayId=”
```
控制第二显示屏需要设备运行 Android 10 或更高版本 (否则将在只读状态下镜像)。
#### 保持常亮
阻止设备在连接时一段时间后休眠:
```bash
scrcpy --stay-awake
scrcpy -w
```
scrcpy 关闭时会恢复设备原来的设置。
#### 关闭设备屏幕
可以通过以下的命令行参数在关闭设备屏幕的状态下进行镜像:
```bash
scrcpy --turn-screen-off
scrcpy -S
```
或者在任何时候按 <kbd>MOD</kbd>+<kbd>o</kbd>
要重新打开屏幕,按下 <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
在Android上`电源` 按钮始终能把屏幕打开。为了方便,对于在 _scrcpy_ 中发出的 `电源` 事件 (通过鼠标右键或 <kbd>MOD</kbd>+<kbd>p</kbd>),会 (尽最大的努力) 在短暂的延迟后将屏幕关闭。设备上的 `电源` 按钮仍然能打开设备屏幕。
还可以同时阻止设备休眠:
```bash
scrcpy --turn-screen-off --stay-awake
scrcpy -Sw
```
#### 退出时息屏
scrcpy 退出时关闭设备屏幕:
```bash
scrcpy --power-off-on-close
```
#### 显示触摸
在演示时,可能会需要显示 (在物理设备上的) 物理触摸点。
Android 在 _开发者选项_ 中提供了这项功能。
_Scrcpy_ 提供一个选项可以在启动时开启这项功能并在退出时恢复初始设置:
```bash
scrcpy --show-touches
scrcpy -t
```
请注意这项功能只能显示 _物理_ 触摸 (用手指在屏幕上的触摸)。
#### 关闭屏保
_Scrcpy_ 默认不会阻止电脑上开启的屏幕保护。
关闭屏幕保护:
```bash
scrcpy --disable-screensaver
```
### 输入控制
#### 旋转设备屏幕
使用 <kbd>MOD</kbd>+<kbd>r</kbd> 在竖屏和横屏模式之间切换。
需要注意的是,只有在前台应用程序支持所要求的模式时,才会进行切换。
#### 复制粘贴
每次安卓的剪贴板变化时,其内容都会被自动同步到电脑的剪贴板上。
所有的 <kbd>Ctrl</kbd> 快捷键都会被转发至设备。其中:
- <kbd>Ctrl</kbd>+<kbd>c</kbd> 通常执行复制
- <kbd>Ctrl</kbd>+<kbd>x</kbd> 通常执行剪切
- <kbd>Ctrl</kbd>+<kbd>v</kbd> 通常执行粘贴 (在电脑到设备的剪贴板同步完成之后)
大多数时候这些按键都会执行以上的功能。
但实际的行为取决于设备上的前台程序。例如_Termux_ 会在按下 <kbd>Ctrl</kbd>+<kbd>c</kbd> 时发送 SIGINT又如 _K-9 Mail_ 会新建一封邮件。
要在这种情况下进行剪切,复制和粘贴 (仅支持 Android >= 7)
- <kbd>MOD</kbd>+<kbd>c</kbd> 注入 `COPY` (复制)
- <kbd>MOD</kbd>+<kbd>x</kbd> 注入 `CUT` (剪切)
- <kbd>MOD</kbd>+<kbd>v</kbd> 注入 `PASTE` (粘贴) (在电脑到设备的剪贴板同步完成之后)
另外,<kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> 会将电脑的剪贴板内容转换为一串按键事件输入到设备。在应用程序不接受粘贴时 (比如 _Termux_),这项功能可以派上一定的用场。不过这项功能可能会导致非 ASCII 编码的内容出现错误。
**警告:** 将电脑剪贴板的内容粘贴至设备 (无论是通过 <kbd>Ctrl</kbd>+<kbd>v</kbd> 还是 <kbd>MOD</kbd>+<kbd>v</kbd>) 都会将内容复制到设备的剪贴板。如此,任何安卓应用程序都能读取到。您应避免将敏感内容 (如密码) 通过这种方式粘贴。
一些设备不支持通过程序设置剪贴板。通过 `--legacy-paste` 选项可以修改 <kbd>Ctrl</kbd>+<kbd>v</kbd><kbd>MOD</kbd>+<kbd>v</kbd> 的工作方式,使它们通过按键事件 (同 <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>) 来注入电脑剪贴板内容。
要禁用自动剪贴板同步功能,使用`--no-clipboard-autosync`。
#### 双指缩放
模拟“双指缩放”:<kbd>Ctrl</kbd>+_按住并移动鼠标_。
更准确的说,在按住鼠标左键时按住 <kbd>Ctrl</kbd>。直到松开鼠标左键,所有鼠标移动将以屏幕中心为原点,缩放或旋转内容 (如果应用支持)。
实际上_scrcpy_ 会在关于屏幕中心对称的位置上用“虚拟手指”发出触摸事件。
#### 物理键盘模拟 (HID)
默认情况下scrcpy 使用安卓按键或文本注入这在任何情况都可以使用但仅限于ASCII字符。
在 Linux 上scrcpy 可以模拟为 Android 上的物理 USB 键盘,以提供更好地输入体验 (使用 [USB HID over AOAv2][hid-aoav2]):禁用虚拟键盘,并适用于任何字符和输入法。
[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support
不过,这种方法仅支持 USB 连接以及 Linux平台。
启用 HID 模式:
```bash
scrcpy --hid-keyboard
scrcpy -K # 简写
```
如果失败了 (如设备未通过 USB 连接),则自动回退至默认模式 (终端中会输出日志)。这即允许通过 USB 和 TCP/IP 连接时使用相同的命令行参数。
在这种模式下,原始按键事件 (扫描码) 被发送给设备,而与宿主机按键映射无关。因此,若键盘布局不匹配,需要在 Android 设备上进行配置,具体为 设置 → 系统 → 语言和输入法 → [实体键盘]。
[实体键盘]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915
#### 物理鼠标模拟 (HID)
与物理键盘模拟类似,可以模拟一个物理鼠标。 同样,它仅在设备通过 USB 连接时才有效,并且目前仅在 Linux 上受支持。
默认情况下scrcpy 使用 Android 鼠标事件注入,使用绝对坐标。 通过模拟物理鼠标在Android设备上出现鼠标指针并注入鼠标相对运动、点击和滚动。
启用此模式:
```bash
scrcpy --hid-mouse
scrcpy -M # 简写
```
您还可以将 `--forward-all-clicks` 添加到 [转发所有点击][forward_all_clicks].
[forward_all_clicks]: #右键和中键
启用此模式后,计算机鼠标将被“捕获”(鼠标指针从计算机上消失并出现在 Android 设备上)。
特殊的捕获键,<kbd>Alt</kbd><kbd>Super</kbd>,切换(禁用或启用)鼠标捕获。 使用其中之一将鼠标的控制权交还给计算机。
#### OTG
可以仅使用物理键盘和鼠标模拟 (HID) 运行 _scrcpy_,就好像计算机键盘和鼠标通过 OTG 线直接插入设备一样。
在这个模式下_adb_ (USB 调试)是不必要的,且镜像被禁用。
启用 OTG 模式:
```bash
scrcpy --otg
# 如果有多个 USB 设备可用,则通过序列号选择
scrcpy --otg -s 0123456789abcdef
```
只开启 HID 键盘 或 HID 鼠标 是可行的:
```bash
scrcpy --otg --hid-keyboard # 只开启 HID 键盘
scrcpy --otg --hid-mouse # 只开启 HID 鼠标
scrcpy --otg --hid-keyboard --hid-mouse # 开启 HID 键盘 和 HID 鼠标
# 为了方便,默认两者都开启
scrcpy --otg # 开启 HID 键盘 和 HID 鼠标
```
`--hid-keyboard``--hid-mouse` 一样,它只在设备通过 USB 连接时才有效,且目前仅在 Linux 上支持。
#### 文本注入偏好
输入文字的时候,系统会产生两种[事件][textevents]
- _按键事件_ ,代表一个按键被按下或松开。
- _文本事件_ ,代表一个字符被输入。
程序默认使用按键事件来输入字母。只有这样,键盘才会在游戏中正常运作 (例如 WASD 键)。
但这也有可能[造成一些问题][prefertext]。如果您遇到了问题,可以通过以下方式避免:
```bash
scrcpy --prefer-text
```
(但这会导致键盘在游戏中工作不正常)
相反,您可以强制始终注入原始按键事件:
```bash
scrcpy --raw-key-events
```
该选项不影响 HID 键盘 (该模式下,所有按键都发送为扫描码)。
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
#### 按键重复
默认状态下,按住一个按键不放会生成多个重复按键事件。在某些游戏中这通常没有实际用途,且可能会导致性能问题。
避免转发重复按键事件:
```bash
scrcpy --no-key-repeat
```
该选项不影响 HID 键盘 (该模式下,按键重复由 Android 直接管理)。
#### 右键和中键
默认状态下,右键会触发返回键 (或电源键开启),中键会触发 HOME 键。要禁用这些快捷键并把所有点击转发到设备:
```bash
scrcpy --forward-all-clicks
```
### 文件拖放
#### 安装APK
将 APK 文件 (文件名以 `.apk` 结尾) 拖放到 _scrcpy_ 窗口来安装。
不会有视觉反馈,终端会输出一条日志。
#### 将文件推送至设备
要推送文件到设备的 `/sdcard/Download/`,将 (非 APK) 文件拖放至 _scrcpy_ 窗口。
不会有视觉反馈,终端会输出一条日志。
在启动时可以修改目标目录:
```bash
scrcpy --push-target=/sdcard/Movies/
```
### 音频转发
_Scrcpy_ 不支持音频。请使用 [sndcpy]。
另见 [issue #14]。
[sndcpy]: https://github.com/rom1v/sndcpy
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14
## 快捷键
在以下列表中, <kbd>MOD</kbd> 是快捷键的修饰键。
默认是 (左) <kbd>Alt</kbd> 或 (左) <kbd>Super</kbd>
您可以使用 `--shortcut-mod` 来修改。可选的按键有 `lctrl`、`rctrl`、`lalt`、`ralt`、`lsuper` 和 `rsuper`。例如:
```bash
# 使用右 Ctrl 键
scrcpy --shortcut-mod=rctrl
# 使用左 Ctrl 键 + 左 Alt 键,或 Super 键
scrcpy --shortcut-mod=lctrl+lalt,lsuper
```
_<kbd>[Super]</kbd> 键通常是指 <kbd>Windows</kbd><kbd>Cmd</kbd> 键。_
[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button)
| 操作 | 快捷键
| --------------------------------- | :-------------------------------------------
| 全屏 | <kbd>MOD</kbd>+<kbd>f</kbd>
| 向左旋转屏幕 | <kbd>MOD</kbd>+<kbd></kbd> _(左箭头)_
| 向右旋转屏幕 | <kbd>MOD</kbd>+<kbd></kbd> _(右箭头)_
| 将窗口大小重置为1:1 (匹配像素) | <kbd>MOD</kbd>+<kbd>g</kbd>
| 将窗口大小重置为消除黑边 | <kbd>MOD</kbd>+<kbd>w</kbd> \| _双击左键¹_
| 点按 `主屏幕` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _中键_
| 点按 `返回` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _右键²_
| 点按 `切换应用` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _第4键³_
| 点按 `菜单` (解锁屏幕)⁴ | <kbd>MOD</kbd>+<kbd>m</kbd>
| 点按 `音量+` | <kbd>MOD</kbd>+<kbd></kbd> _(上箭头)_
| 点按 `音量-` | <kbd>MOD</kbd>+<kbd></kbd> _(下箭头)_
| 点按 `电源` | <kbd>MOD</kbd>+<kbd>p</kbd>
| 打开屏幕 | _鼠标右键²_
| 关闭设备屏幕 (但继续在电脑上显示) | <kbd>MOD</kbd>+<kbd>o</kbd>
| 打开设备屏幕 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
| 旋转设备屏幕 | <kbd>MOD</kbd>+<kbd>r</kbd>
| 展开通知面板 | <kbd>MOD</kbd>+<kbd>n</kbd> \| _第5键³_
| 展开设置面板 | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _双击第5键³_
| 收起通知面板 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| 复制到剪贴板⁵ | <kbd>MOD</kbd>+<kbd>c</kbd>
| 剪切到剪贴板⁵ | <kbd>MOD</kbd>+<kbd>x</kbd>
| 同步剪贴板并粘贴⁵ | <kbd>MOD</kbd>+<kbd>v</kbd>
| 注入电脑剪贴板文本 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| 打开/关闭FPS显示 (至标准输出) | <kbd>MOD</kbd>+<kbd>i</kbd>
| 捏拉缩放 | <kbd>Ctrl</kbd>+_按住并移动鼠标_
| 拖放 APK 文件 | 从电脑安装 APK 文件
| 拖放非 APK 文件 | [将文件推送至设备](#push-file-to-device)
_¹双击黑边可以去除黑边。_
_²点击鼠标右键将在屏幕熄灭时点亮屏幕其余情况则视为按下返回键 。_
_³鼠标的第4键和第5键。_
_⁴对于开发中的 react-native 应用程序,`MENU` 触发开发菜单。_
_⁵需要安卓版本 Android >= 7。_
有重复按键的快捷键通过松开再按下一个按键来进行,如“展开设置面板”:
1. 按下 <kbd>MOD</kbd> 不放。
2. 双击 <kbd>n</kbd>
3. 松开 <kbd>MOD</kbd>
所有的 <kbd>Ctrl</kbd>+_按键_ 的快捷键都会被转发到设备,所以会由当前应用程序进行处理。
## 自定义路径
要使用指定的 _adb_ 二进制文件,可以设置环境变量 `ADB`
```bash
ADB=/path/to/adb scrcpy
```
要覆盖 `scrcpy-server` 的路径,可以设置 `SCRCPY_SERVER_PATH`
要覆盖图标,可以设置其路径至 `SCRCPY_ICON_PATH`
## 为什么叫 _scrcpy_
一个同事让我找出一个和 [gnirehtet] 一样难以发音的名字。
[`strcpy`] 源于 **str**ing (字符串); `scrcpy` 源于 **scr**een (屏幕)。
[gnirehtet]: https://github.com/Genymobile/gnirehtet
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
## 如何构建?
请查看 [BUILD]。
## 常见问题
请查看 [FAQ](FAQ.md)。
## 开发者
请查看[开发者页面]。
[开发者页面]: DEVELOP.md
## 许可协议
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
## 相关文章
- [Introducing scrcpy][article-intro]
- [Scrcpy now works wirelessly][article-tcpip]
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/

@ -1,702 +0,0 @@
_Only the original [README](README.md) is guaranteed to be up-to-date._
_只有原版的 [README](README.md)是保證最新的。_
本文件翻譯時點: [521f2fe](https://github.com/Genymobile/scrcpy/commit/521f2fe994019065e938aa1a54b56b4f10a4ac4a#diff-04c6e90faac2675aa89e2176d2eec7d8)
# scrcpy (v1.15)
Scrcpy 可以透過 USB、或是 [TCP/IP][article-tcpip] 來顯示或控制 Android 裝置。且 scrcpy 不需要 _root_ 權限。
Scrcpy 目前支援 _GNU/Linux_、_Windows_ 和 _macOS_
![screenshot](assets/screenshot-debian-600.jpg)
特色:
- **輕量** (只顯示裝置螢幕)
- **效能** (30~60fps)
- **品質** (1920×1080 或更高)
- **低延遲** ([35~70ms][lowlatency])
- **快速啟動** (~1 秒就可以顯示第一個畫面)
- **非侵入性** (不安裝、留下任何東西在裝置上)
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
## 需求
Android 裝置必須是 API 21+ (Android 5.0+)。
請確認裝置上的 [adb 偵錯 (通常位於開發者模式內)][enable-adb] 已啟用。
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
在部分的裝置上,你也必須啟用[特定的額外選項][control]才能使用鍵盤和滑鼠控制。
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
## 下載/獲取軟體
### Linux
Debian (目前支援 _testing__sid_) 和 Ubuntu (20.04):
```
apt install scrcpy
```
[Snap] 上也可以下載: [`scrcpy`][snap-link].
[snap-link]: https://snapstats.org/snaps/scrcpy
[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager)
在 Fedora 上也可以使用 [COPR] 下載: [`scrcpy`][copr-link].
[COPR]: https://fedoraproject.org/wiki/Category:Copr
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
在 Arch Linux 上也可以使用 [AUR] 下載: [`scrcpy`][aur-link].
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
在 Gentoo 上也可以使用 [Ebuild] 下載: [`scrcpy/`][ebuild-link].
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
你也可以自己[編譯 _Scrcpy_][BUILD]。別擔心,並沒有想像中的難。
### Windows
為了保持簡單Windows 用戶可以下載一個包含所有必需軟體 (包含 `adb`) 的壓縮包:
- [README](README.md#windows)
[Chocolatey] 上也可以下載:
[Chocolatey]: https://chocolatey.org/
```bash
choco install scrcpy
choco install adb # 如果你還沒有安裝的話
```
[Scoop] 上也可以下載:
```bash
scoop install scrcpy
scoop install adb # 如果你還沒有安裝的話
```
[Scoop]: https://scoop.sh
你也可以自己[編譯 _Scrcpy_][BUILD]。
### macOS
_Scrcpy_ 可以在 [Homebrew] 上直接安裝:
[Homebrew]: https://brew.sh/
```bash
brew install scrcpy
```
由於執行期間需要可以藉由 `PATH` 存取 `adb` 。如果還沒有安裝 `adb` 可以使用下列方式安裝:
```bash
brew cask install android-platform-tools
```
你也可以自己[編譯 _Scrcpy_][BUILD]。
## 執行
將電腦和你的 Android 裝置連線,然後執行:
```bash
scrcpy
```
_Scrcpy_ 可以接受命令列參數。輸入下列指令就可以瀏覽可以使用的命令列參數:
```bash
scrcpy --help
```
## 功能
> 以下說明中,有關快捷鍵的說明可能會出現 <kbd>MOD</kbd> 按鈕。相關說明請參見[快捷鍵]內的說明。
[快捷鍵]: #快捷鍵
### 畫面擷取
#### 縮小尺寸
使用比較低的解析度來投放 Android 裝置在某些情況可以提升效能。
限制寬和高的最大值(例如: 1024):
```bash
scrcpy --max-size 1024
scrcpy -m 1024 # 縮短版本
```
比較小的參數會根據螢幕比例重新計算。
根據上面的範例1920x1080 會被縮小成 1024x576。
#### 更改 bit-rate
預設的 bit-rate 是 8 Mbps。如果要更改 bit-rate (例如: 2 Mbps):
```bash
scrcpy --bit-rate 2M
scrcpy -b 2M # 縮短版本
```
#### 限制 FPS
限制畫面最高的 FPS:
```bash
scrcpy --max-fps 15
```
僅在 Android 10 後正式支援,不過也有可能可以在 Android 10 以前的版本使用。
#### 裁切
裝置的螢幕可以裁切。如此一來,鏡像出來的螢幕就只會是原本的一部份。
假如只要鏡像 Oculus Go 的其中一隻眼睛:
```bash
scrcpy --crop 1224:1440:0:0 # 位於 (0,0)大小1224x1440
```
如果 `--max-size` 也有指定的話,裁切後才會縮放。
#### 鎖定影像方向
如果要鎖定鏡像影像方向:
```bash
scrcpy --lock-video-orientation 0 # 原本的方向
scrcpy --lock-video-orientation 1 # 逆轉 90°
scrcpy --lock-video-orientation 2 # 180°
scrcpy --lock-video-orientation 3 # 順轉 90°
```
這會影響錄影結果的影像方向。
### 錄影
鏡像投放螢幕的同時也可以錄影:
```bash
scrcpy --record file.mp4
scrcpy -r file.mkv
```
如果只要錄影,不要投放螢幕鏡像的話:
```bash
scrcpy --no-display --record file.mp4
scrcpy -Nr file.mkv
# 用 Ctrl+C 停止錄影
```
就算有些幀為了效能而被跳過,它們還是一樣會被錄製。
裝置上的每一幀都有時間戳記,所以 [封包延遲 (Packet Delay Variation, PDV)][packet delay variation] 並不會影響錄影的檔案。
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
### 連線
#### 無線
_Scrcpy_ 利用 `adb` 和裝置通訊,而 `adb` 可以[透過 TCP/IP 連結][connect]:
1. 讓電腦和裝置連到同一個 Wi-Fi。
2. 獲取手機的 IP 位址(設定 → 關於手機 → 狀態).
3. 啟用裝置上的 `adb over TCP/IP`: `adb tcpip 5555`.
4. 拔掉裝置上的線。
5. 透過 TCP/IP 連接裝置: `adb connect DEVICE_IP:5555` _(把 `DEVICE_IP` 換成裝置的IP位址)_.
6. 和平常一樣執行 `scrcpy`
如果效能太差,可以降低 bit-rate 和解析度:
```bash
scrcpy --bit-rate 2M --max-size 800
scrcpy -b2M -m800 # 縮短版本
```
[connect]: https://developer.android.com/studio/command-line/adb.html#wireless
#### 多裝置
如果 `adb devices` 內有多個裝置,則必須附上 _serial_:
```bash
scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # 縮短版本
```
如果裝置是透過 TCP/IP 連線:
```bash
scrcpy --serial 192.168.0.1:5555
scrcpy -s 192.168.0.1:5555 # 縮短版本
```
你可以啟用復數個對應不同裝置的 _scrcpy_
#### 裝置連結後自動啟動
你可以使用 [AutoAdb]:
```bash
autoadb scrcpy -s '{}'
```
[AutoAdb]: https://github.com/rom1v/autoadb
#### SSH tunnel
本地的 `adb` 可以連接到遠端的 `adb` 伺服器(假設兩者使用相同版本的 _adb_ 通訊協定),以連接到遠端裝置:
```bash
adb kill-server # 停止在 Port 5037 的本地 adb 伺服
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
# 保持開啟
```
從另外一個終端機:
```bash
scrcpy
```
如果要避免啟用 remote port forwarding你可以強制它使用 forward connection (注意 `-L``-R` 的差別):
```bash
adb kill-server # 停止在 Port 5037 的本地 adb 伺服
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
# 保持開啟
```
從另外一個終端機:
```bash
scrcpy --force-adb-forward
```
和無線連接一樣,有時候降低品質會比較好:
```
scrcpy -b2M -m800 --max-fps 15
```
### 視窗調整
#### 標題
預設標題是裝置的型號,不過可以透過以下方式修改:
```bash
scrcpy --window-title 'My device'
```
#### 位置 & 大小
初始的視窗位置和大小也可以指定:
```bash
scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
```
#### 無邊框
如果要停用視窗裝飾:
```bash
scrcpy --window-borderless
```
#### 保持最上層
如果要保持 `scrcpy` 的視窗在最上層:
```bash
scrcpy --always-on-top
```
#### 全螢幕
這個軟體可以直接在全螢幕模式下起動:
```bash
scrcpy --fullscreen
scrcpy -f # 縮短版本
```
全螢幕可以使用 <kbd>MOD</kbd>+<kbd>f</kbd> 開關。
#### 旋轉
視窗可以旋轉:
```bash
scrcpy --rotation 1
```
可用的數值:
- `0`: 不旋轉
- `1`: 90 度**逆**轉
- `2`: 180 度
- `3`: 90 度**順**轉
旋轉方向也可以使用 <kbd>MOD</kbd>+<kbd></kbd> _(左方向鍵)_<kbd>MOD</kbd>+<kbd></kbd> _(右方向鍵)_ 調整。
_scrcpy_ 有 3 種不同的旋轉:
- <kbd>MOD</kbd>+<kbd>r</kbd> 要求裝置在垂直、水平之間旋轉 (目前運行中的 App 有可能會因為不支援而拒絕)。
- `--lock-video-orientation` 修改鏡像的方向 (裝置傳給電腦的影像)。這會影響錄影結果的影像方向。
- `--rotation` (或是 <kbd>MOD</kbd>+<kbd></kbd> / <kbd>MOD</kbd>+<kbd></kbd>) 只旋轉視窗的內容。這只會影響鏡像結果,不會影響錄影結果。
### 其他鏡像選項
#### 唯讀
停用所有控制,包含鍵盤輸入、滑鼠事件、拖放檔案:
```bash
scrcpy --no-control
scrcpy -n
```
#### 顯示螢幕
如果裝置有複數個螢幕,可以指定要鏡像哪個螢幕:
```bash
scrcpy --display 1
```
可以透過下列指令獲取螢幕 ID:
```
adb shell dumpsys display # 找輸出結果中的 "mDisplayId="
```
第二螢幕只有在 Android 10+ 時可以控制。如果不是 Android 10+,螢幕就會在唯讀狀態下投放。
#### 保持清醒
如果要避免裝置在連接狀態下進入睡眠:
```bash
scrcpy --stay-awake
scrcpy -w
```
_scrcpy_ 關閉後就會回復成原本的設定。
#### 關閉螢幕
鏡像開始時,可以要求裝置關閉螢幕:
```bash
scrcpy --turn-screen-off
scrcpy -S
```
或是在任何時候輸入 <kbd>MOD</kbd>+<kbd>o</kbd>
如果要開啟螢幕,輸入 <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
在 Android 上,`POWER` 按鈕總是開啟螢幕。
為了方便,如果 `POWER` 是透過 scrcpy 轉送 (右鍵 或 <kbd>MOD</kbd>+<kbd>p</kbd>)的話,螢幕將會在短暫的延遲後關閉。
實際在手機上的 `POWER` 還是會開啟螢幕。
防止裝置進入睡眠狀態:
```bash
scrcpy --turn-screen-off --stay-awake
scrcpy -Sw
```
#### 顯示過期的幀
為了降低延遲, _scrcpy_ 預設只會顯示最後解碼的幀,並且拋棄所有在這之前的幀。
如果要強制顯示所有的幀 (有可能會拉高延遲),輸入:
```bash
scrcpy --render-expired-frames
```
#### 顯示觸控點
對於要報告的人來說,顯示裝置上的實際觸控點有時候是有幫助的。
Android 在_開發者選項_中有提供這個功能。
_Scrcpy_ 可以在啟動時啟用這個功能,並且在停止後恢復成原本的設定:
```bash
scrcpy --show-touches
scrcpy -t
```
這個選項只會顯示**實際觸碰在裝置上的觸碰點**。
### 輸入控制
#### 旋轉裝置螢幕
輸入 <kbd>MOD</kbd>+<kbd>r</kbd> 以在垂直、水平之間切換。
如果使用中的程式不支援,則不會切換。
#### 複製/貼上
如果 Android 剪貼簿上的內容有任何更動,電腦的剪貼簿也會一起更動。
任何與 <kbd>Ctrl</kbd> 相關的快捷鍵事件都會轉送到裝置上。特別來說:
- <kbd>Ctrl</kbd>+<kbd>c</kbd> 通常是複製
- <kbd>Ctrl</kbd>+<kbd>x</kbd> 通常是剪下
- <kbd>Ctrl</kbd>+<kbd>v</kbd> 通常是貼上 (在電腦的剪貼簿與裝置上的剪貼簿同步之後)
這些跟你通常預期的行為一樣。
但是,實際上的行為是根據目前運行中的應用程式而定。
舉例來說, _Termux_ 在收到 <kbd>Ctrl</kbd>+<kbd>c</kbd> 後,會傳送 SIGINT_K-9 Mail_ 則是建立新訊息。
如果在這情況下,要剪下、複製或貼上 (只有在Android 7+時才支援):
- <kbd>MOD</kbd>+<kbd>c</kbd> 注入 `複製`
- <kbd>MOD</kbd>+<kbd>x</kbd> 注入 `剪下`
- <kbd>MOD</kbd>+<kbd>v</kbd> 注入 `貼上` (在電腦的剪貼簿與裝置上的剪貼簿同步之後)
另外,<kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> 則是以一連串的按鍵事件貼上電腦剪貼簿中的內容。當元件不允許文字貼上 (例如 _Termux_) 時,這就很有用。不過,這在非 ASCII 內容上就無法使用。
**警告:** 貼上電腦的剪貼簿內容 (無論是從 <kbd>Ctrl</kbd>+<kbd>v</kbd><kbd>MOD</kbd>+<kbd>v</kbd>) 時,會複製剪貼簿中的內容至裝置的剪貼簿上。這會讓所有 Android 程式讀取剪貼簿的內容。請避免貼上任何敏感內容 (像是密碼)。
#### 文字輸入偏好
輸入文字時,有兩種[事件][textevents]會被觸發:
- _鍵盤事件 (key events)_,代表有一個按鍵被按下或放開
- _文字事件 (text events)_,代表有一個文字被輸入
預設上,文字是被以鍵盤事件 (key events) 輸入的,所以鍵盤和遊戲內所預期的一樣 (通常是指 WASD)。
但是這可能造成[一些問題][prefertext]。如果在這輸入這方面遇到了問題,你可以試試:
```bash
scrcpy --prefer-text
```
(不過遊戲內鍵盤就會不可用)
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
#### 重複輸入
通常來說,長時間按住一個按鍵會重複觸發按鍵事件。這會在一些遊戲中造成效能問題,而且這個重複的按鍵事件是沒有意義的。
如果不要轉送這些重複的按鍵事件:
```bash
scrcpy --no-key-repeat
```
### 檔案
#### 安裝 APK
如果要安裝 APK ,拖放一個 APK 檔案 (以 `.apk` 為副檔名) 到 _scrcpy_ 的視窗上。
視窗上不會有任何反饋;結果會顯示在命令列中。
#### 推送檔案至裝置
如果要推送檔案到裝置上的 `/sdcard/` ,拖放一個非 APK 檔案 (**不**以 `.apk` 為副檔名) 到 _scrcpy_ 的視窗上。
視窗上不會有任何反饋;結果會顯示在命令列中。
推送檔案的目標路徑可以在啟動時指定:
```bash
scrcpy --push-target /sdcard/foo/bar/
```
### 音訊轉送
_scrcpy_ **不**轉送音訊。請使用 [sndcpy]。另外,參見 [issue #14]。
[sndcpy]: https://github.com/rom1v/sndcpy
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14
## 快捷鍵
在以下的清單中,<kbd>MOD</kbd> 是快捷鍵的特殊按鍵。通常來說,這個按鍵是 (左) <kbd>Alt</kbd> 或是 (左) <kbd>Super</kbd>
這個是可以使用 `--shortcut-mod` 更改的。可以用的選項有:
- `lctrl`: 左邊的 <kbd>Ctrl</kbd>
- `rctrl`: 右邊的 <kbd>Ctrl</kbd>
- `lalt`: 左邊的 <kbd>Alt</kbd>
- `ralt`: 右邊的 <kbd>Alt</kbd>
- `lsuper`: 左邊的 <kbd>Super</kbd>
- `rsuper`: 右邊的 <kbd>Super</kbd>
```bash
# 以 右邊的 Ctrl 為快捷鍵特殊按鍵
scrcpy --shortcut-mod=rctrl
# 以 左邊的 Ctrl 和左邊的 Alt 或是 左邊的 Super 為快捷鍵特殊按鍵
scrcpy --shortcut-mod=lctrl+lalt,lsuper
```
_<kbd>[Super]</kbd> 通常是 <kbd>Windows</kbd><kbd>Cmd</kbd> 鍵。_
[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button)
| Action | Shortcut
| ------------------------------------------------- |:-----------------------------
| 切換至全螢幕 | <kbd>MOD</kbd>+<kbd>f</kbd>
| 左旋顯示螢幕 | <kbd>MOD</kbd>+<kbd></kbd> _(左)_
| 右旋顯示螢幕 | <kbd>MOD</kbd>+<kbd></kbd> _(右)_
| 縮放視窗成 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
| 縮放視窗到沒有黑邊框為止 | <kbd>MOD</kbd>+<kbd>w</kbd> \| _雙擊¹_
| 按下 `首頁` 鍵 | <kbd>MOD</kbd>+<kbd>h</kbd> \| _中鍵_
| 按下 `返回` 鍵 | <kbd>MOD</kbd>+<kbd>b</kbd> \| _右鍵²_
| 按下 `切換 APP` 鍵 | <kbd>MOD</kbd>+<kbd>s</kbd>
| 按下 `選單` 鍵 (或解鎖螢幕) | <kbd>MOD</kbd>+<kbd>m</kbd>
| 按下 `音量+` 鍵 | <kbd>MOD</kbd>+<kbd></kbd> _(上)_
| 按下 `音量-` 鍵 | <kbd>MOD</kbd>+<kbd></kbd> _(下)_
| 按下 `電源` 鍵 | <kbd>MOD</kbd>+<kbd>p</kbd>
| 開啟 | _右鍵²_
| 關閉裝置螢幕(持續鏡像) | <kbd>MOD</kbd>+<kbd>o</kbd>
| 開啟裝置螢幕 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
| 旋轉裝置螢幕 | <kbd>MOD</kbd>+<kbd>r</kbd>
| 開啟通知列 | <kbd>MOD</kbd>+<kbd>n</kbd>
| 關閉通知列 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| 複製至剪貼簿³ | <kbd>MOD</kbd>+<kbd>c</kbd>
| 剪下至剪貼簿³ | <kbd>MOD</kbd>+<kbd>x</kbd>
| 同步剪貼簿並貼上³ | <kbd>MOD</kbd>+<kbd>v</kbd>
| 複製電腦剪貼簿中的文字至裝置並貼上 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| 啟用/停用 FPS 計數器(顯示於 stdout - 通常是命令列) | <kbd>MOD</kbd>+<kbd>i</kbd>
_¹在黑邊框上雙擊以移除它們。_
_²右鍵會返回。如果螢幕是關閉狀態則會打開螢幕。_
_³只支援 Android 7+。_
所有 <kbd>Ctrl</kbd>+_按鍵_ 快捷鍵都會傳送到裝置上,所以它們是由目前運作的應用程式處理的。
## 自訂路徑
如果要使用特定的 _adb_ ,將它設定到環境變數中的 `ADB`:
ADB=/path/to/adb scrcpy
如果要覆寫 `scrcpy-server` 檔案的路徑,則將路徑設定到環境變數中的 `SCRCPY_SERVER_PATH`
[相關連結][useful]
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
## 為何叫 _scrcpy_
有一個同事要我找一個跟 [gnirehtet] 一樣難念的名字。
[`strcpy`] 複製一個字串 (**str**ing)`scrcpy` 複製一個螢幕 (**scr**een)。
[gnirehtet]: https://github.com/Genymobile/gnirehtet
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
## 如何編譯?
請看[這份文件 (英文)][BUILD]。
[BUILD]: BUILD.md
## 常見問題
請看[這份文件 (英文)][FAQ]。
[FAQ]: FAQ.md
## 開發者文件
請看[這個頁面 (英文)][developers page].
[developers page]: DEVELOP.md
## Licence
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
## 相關文章
- [Scrcpy 簡介 (英文)][article-intro]
- [Scrcpy 可以無線連線了 (英文)][article-tcpip]
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/

@ -2,56 +2,90 @@ _scrcpy() {
local cur prev words cword
local opts="
--always-on-top
-b --bit-rate=
--codec-options=
--audio-bit-rate=
--audio-buffer=
--audio-codec=
--audio-codec-options=
--audio-encoder=
--audio-source=
--audio-output-buffer=
-b --video-bit-rate=
--camera-ar=
--camera-id=
--camera-facing=
--camera-fps=
--camera-high-speed
--camera-size=
--crop=
-d --select-usb
--disable-screensaver
--display=
--display-buffer=
--display-id=
--display-orientation=
-e --select-tcpip
--encoder=
-f --fullscreen
--force-adb-forward
--forward-all-clicks
-f --fullscreen
-K --hid-keyboard
-h --help
-K
--keyboard=
--kill-adb-on-close
--legacy-paste
--list-camera-sizes
--list-cameras
--list-displays
--list-encoders
--lock-video-orientation
--lock-video-orientation=
--max-fps=
-M --hid-mouse
-m --max-size=
-M
--max-fps=
--mouse=
-n --no-control
-N --no-playback
--no-audio
--no-audio-playback
--no-cleanup
--no-clipboard-on-error
--no-clipboard-autosync
--no-downsize-on-error
-n --no-control
-N --no-display
--no-key-repeat
--no-mipmaps
--no-power-on
--no-video
--no-video-playback
--orientation=
--otg
-p --port=
--pause-on-exit
--pause-on-exit=
--power-off-on-close
--prefer-text
--print-fps
--push-target=
--raw-key-events
-r --record=
--raw-key-events
--record-format=
--record-orientation=
--render-driver=
--require-audio
--rotation=
-s --serial=
--shortcut-mod=
-S --turn-screen-off
--shortcut-mod=
-t --show-touches
--tcpip
--tcpip=
--time-limit=
--tunnel-host=
--tunnel-port=
--v4l2-buffer=
--v4l2-sink=
-V --verbosity=
-v --version
-V --verbosity=
--video-codec=
--video-codec-options=
--video-encoder=
--video-source=
-w --stay-awake
--window-borderless
--window-title=
@ -63,8 +97,48 @@ _scrcpy() {
_init_completion -s || return
case "$prev" in
--video-codec)
COMPREPLY=($(compgen -W 'h264 h265 av1' -- "$cur"))
return
;;
--audio-codec)
COMPREPLY=($(compgen -W 'opus aac flac raw' -- "$cur"))
return
;;
--video-source)
COMPREPLY=($(compgen -W 'display camera' -- "$cur"))
return
;;
--audio-source)
COMPREPLY=($(compgen -W 'output mic' -- "$cur"))
return
;;
--camera-facing)
COMPREPLY=($(compgen -W 'front back external' -- "$cur"))
return
;;
--keyboard)
COMPREPLY=($(compgen -W 'disabled sdk uhid aoa' -- "$cur"))
return
;;
--mouse)
COMPREPLY=($(compgen -W 'disabled sdk uhid aoa' -- "$cur"))
return
;;
--orientation|--display-orientation)
COMPREPLY=($(compgen -W '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur"))
return
;;
--record-orientation)
COMPREPLY=($(compgen -W '0 90 180 270' -- "$cur"))
return
;;
--lock-video-orientation)
COMPREPLY=($(compgen -W 'unlocked initial 0 1 2 3' -- "$cur"))
COMPREPLY=($(compgen -W 'unlocked initial 0 90 180 270' -- "$cur"))
return
;;
--pause-on-exit)
COMPREPLY=($(compgen -W 'true false if-error' -- "$cur"))
return
;;
-r|--record)
@ -72,17 +146,13 @@ _scrcpy() {
return
;;
--record-format)
COMPREPLY=($(compgen -W 'mkv mp4' -- "$cur"))
COMPREPLY=($(compgen -W 'mp4 mkv m4a mka opus aac flac wav' -- "$cur"))
return
;;
--render-driver)
COMPREPLY=($(compgen -W 'direct3d opengl opengles2 opengles metal software' -- "$cur"))
return
;;
--rotation)
COMPREPLY=($(compgen -W '0 1 2 3' -- "$cur"))
return
;;
--shortcut-mod)
# Only auto-complete a single key
COMPREPLY=($(compgen -W 'lctrl rctrl lalt ralt lsuper rsuper' -- "$cur"))
@ -92,21 +162,35 @@ _scrcpy() {
COMPREPLY=($(compgen -W 'verbose debug info warn error' -- "$cur"))
return
;;
-b|--bitrate \
|--codec-options \
-s|--serial)
# Use 'adb devices' to list serial numbers
COMPREPLY=($(compgen -W "$("${ADB:-adb}" devices | awk '$2 == "device" {print $1}')" -- ${cur}))
return
;;
--audio-bit-rate \
|--audio-buffer \
|-b|--video-bit-rate \
|--audio-codec-options \
|--audio-encoder \
|--audio-output-buffer \
|--camera-ar \
|--camera-id \
|--camera-fps \
|--camera-size \
|--crop \
|--display \
|--display-id \
|--display-buffer \
|--encoder \
|--max-fps \
|-m|--max-size \
|-p|--port \
|--push-target \
|-s|--serial \
|--rotation \
|--tunnel-host \
|--tunnel-port \
|--v4l2-buffer \
|--v4l2-sink \
|--video-codec-options \
|--video-encoder \
|--tcpip \
|--window-*)
# Option accepting an argument, but nothing to auto-complete

@ -1,4 +1,2 @@
@echo off
scrcpy.exe %*
:: if the exit code is >= 1, then pause
if errorlevel 1 pause
scrcpy.exe --pause-on-exit=if-error %*

@ -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/sh -c "\\$SHELL -i -c 'scrcpy --pause-on-exit=if-error'"
Icon=scrcpy
Terminal=true
Type=Application
Categories=Utility;RemoteAccess;
StartupNotify=false

@ -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

@ -9,54 +9,86 @@ local arguments
arguments=(
'--always-on-top[Make scrcpy window always on top \(above other windows\)]'
{-b,--bit-rate=}'[Encode the video at the given bit-rate]'
'--codec-options=[Set a list of comma-separated key\:type=value options for the device encoder]'
'--audio-bit-rate=[Encode the audio at the given bit-rate]'
'--audio-buffer=[Configure the audio buffering delay (in milliseconds)]'
'--audio-codec=[Select the audio codec]:codec:(opus aac flac raw)'
'--audio-codec-options=[Set a list of comma-separated key\:type=value options for the device audio encoder]'
'--audio-encoder=[Use a specific MediaCodec audio encoder]'
'--audio-source=[Select the audio source]:source:(output mic)'
'--audio-output-buffer=[Configure the size of the SDL audio output buffer (in milliseconds)]'
{-b,--video-bit-rate=}'[Encode the video at the given bit-rate]'
'--camera-ar=[Select the camera size by its aspect ratio]'
'--camera-high-speed=[Enable high-speed camera capture mode]'
'--camera-id=[Specify the camera id to mirror]'
'--camera-facing=[Select the device camera by its facing direction]:facing:(front back external)'
'--camera-fps=[Specify the camera capture frame rate]'
'--camera-size=[Specify an explicit camera capture size]'
'--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]'
{-d,--select-usb}'[Use USB device]'
'--disable-screensaver[Disable screensaver while scrcpy is running]'
'--display=[Specify the display id to mirror]'
'--display-buffer=[Add a buffering delay \(in milliseconds\) before displaying]'
'--display-id=[Specify the display id to mirror]'
'--display-orientation=[Set the initial display orientation]:orientation values:(0 90 180 270 flip0 flip90 flip180 flip270)'
{-e,--select-tcpip}'[Use TCP/IP device]'
'--encoder=[Use a specific MediaCodec encoder \(must be a H.264 encoder\)]'
{-f,--fullscreen}'[Start in fullscreen]'
'--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]'
'--forward-all-clicks[Forward clicks to device]'
{-f,--fullscreen}'[Start in fullscreen]'
{-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]'
{-h,--help}'[Print the help]'
'-K[Use UHID keyboard (same as --keyboard=uhid)]'
'--keyboard[Set the keyboard input mode]:mode:(disabled sdk uhid aoa)'
'--kill-adb-on-close[Kill adb when scrcpy terminates]'
'--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]'
'--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 1 2 3)'
'--max-fps=[Limit the frame rate of screen capture]'
{-M,--hid-mouse}'[Simulate a physical mouse by using HID over AOAv2]'
'--list-camera-sizes[List the valid camera capture sizes]'
'--list-cameras[List cameras available on the device]'
'--list-displays[List displays available on the device]'
'--list-encoders[List video and audio encoders available on the device]'
'--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 90 180 270)'
{-m,--max-size=}'[Limit both the width and height of the video to value]'
'-M[Use UHID mouse (same as --mouse=uhid)]'
'--max-fps=[Limit the frame rate of screen capture]'
'--mouse[Set the mouse input mode]:mode:(disabled sdk uhid aoa)'
{-n,--no-control}'[Disable device control \(mirror the device in read only\)]'
{-N,--no-playback}'[Disable video and audio playback]'
'--no-audio[Disable audio forwarding]'
'--no-audio-playback[Disable audio playback]'
'--no-cleanup[Disable device cleanup actions on exit]'
'--no-clipboard-autosync[Disable automatic clipboard synchronization]'
'--no-downsize-on-error[Disable lowering definition on MediaCodec error]'
{-n,--no-control}'[Disable device control \(mirror the device in read only\)]'
{-N,--no-display}'[Do not display device \(during screen recording or when V4L2 sink is enabled\)]'
'--no-key-repeat[Do not forward repeated key events when a key is held down]'
'--no-mipmaps[Disable the generation of mipmaps]'
'--no-power-on[Do not power on the device on start]'
'--no-video[Disable video forwarding]'
'--no-video-playback[Disable video playback]'
'--orientation=[Set the video orientation]:orientation values:(0 90 180 270 flip0 flip90 flip180 flip270)'
'--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]'
'--pause-on-exit=[Make scrcpy pause before exiting]:mode:(true false if-error)'
'--power-off-on-close[Turn the device screen off when closing scrcpy]'
'--prefer-text[Inject alpha characters and space as text events instead of key events]'
'--print-fps[Start FPS counter, to print frame logs to the console]'
'--push-target=[Set the target directory for pushing files to the device by drag and drop]'
'--raw-key-events[Inject key events for all input keys, and ignore text events]'
{-r,--record=}'[Record screen to file]:record file:_files'
'--record-format=[Force recording format]:format:(mp4 mkv)'
'--raw-key-events[Inject key events for all input keys, and ignore text events]'
'--record-format=[Force recording format]:format:(mp4 mkv m4a mka opus aac flac wav)'
'--record-orientation=[Set the record orientation]:orientation values:(0 90 180 270)'
'--render-driver=[Request SDL to use the given render driver]:driver name:(direct3d opengl opengles2 opengles metal software)'
'--rotation=[Set the initial display rotation]:rotation values:(0 1 2 3)'
{-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]'
'--shortcut-mod=[\[key1,key2+key3,...\] Specify the modifiers to use for scrcpy shortcuts]:shortcut mod:(lctrl rctrl lalt ralt lsuper rsuper)'
'--require-audio=[Make scrcpy fail if audio is enabled but does not work]'
{-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]:serial:($("${ADB-adb}" devices | awk '\''$2 == "device" {print $1}'\''))'
{-S,--turn-screen-off}'[Turn the device screen off immediately]'
'--shortcut-mod=[\[key1,key2+key3,...\] Specify the modifiers to use for scrcpy shortcuts]:shortcut mod:(lctrl rctrl lalt ralt lsuper rsuper)'
{-t,--show-touches}'[Show physical touches]'
'--tcpip[\(optional \[ip\:port\]\) Configure and connect the device over TCP/IP]'
'--time-limit=[Set the maximum mirroring time, in seconds]'
'--tunnel-host=[Set the IP address of the adb tunnel to reach the scrcpy server]'
'--tunnel-port=[Set the TCP port of the adb tunnel to reach the scrcpy server]'
'--v4l2-buffer=[Add a buffering delay \(in milliseconds\) before pushing frames]'
'--v4l2-sink=[\[\/dev\/videoN\] Output to v4l2loopback device]'
{-V,--verbosity=}'[Set the log level]:verbosity:(verbose debug info warn error)'
{-v,--version}'[Print the version of scrcpy]'
{-V,--verbosity=}'[Set the log level]:verbosity:(verbose debug info warn error)'
'--video-codec=[Select the video codec]:codec:(h264 h265 av1)'
'--video-codec-options=[Set a list of comma-separated key\:type=value options for the device video encoder]'
'--video-encoder=[Use a specific MediaCodec video encoder]'
'--video-source=[Select the video source]:source:(display camera)'
{-w,--stay-awake}'[Keep the device on while scrcpy is running, when the device is plugged in]'
'--window-borderless[Disable window decorations \(display borderless window\)]'
'--window-title=[Set a custom window title]'

@ -0,0 +1 @@
/work

@ -0,0 +1,27 @@
This directory (app/deps/) contains:
*.sh : shell scripts to download and build dependencies
patches/ : patches to fix dependencies (used by scripts)
work/sources/ : downloaded tarballs and extracted folders
ffmpeg-6.1.1.tar.xz
ffmpeg-6.1.1/
libusb-1.0.27.tar.gz
libusb-1.0.27/
...
work/build/ : build dirs for each dependency/version/architecture
ffmpeg-6.1.1/win32/
ffmpeg-6.1.1/win64/
libusb-1.0.27/win32/
libusb-1.0.27/win64/
...
work/install/ : install dirs for each architexture
win32/bin/
win32/include/
win32/lib/
win32/share/
win64/bin/
win64/include/
win64/lib/
win64/share/

@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -ex
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
VERSION=34.0.5
FILENAME=platform-tools_r$VERSION-windows.zip
PROJECT_DIR=platform-tools-$VERSION
SHA256SUM=3f8320152704377de150418a3c4c9d07d16d80a6c0d0d8f7289c22c499e33571
cd "$SOURCES_DIR"
if [[ -d "$PROJECT_DIR" ]]
then
echo "$PWD/$PROJECT_DIR" found
else
get_file "https://dl.google.com/android/repository/$FILENAME" "$FILENAME" "$SHA256SUM"
mkdir -p "$PROJECT_DIR"
cd "$PROJECT_DIR"
ZIP_PREFIX=platform-tools
unzip "../$FILENAME" \
"$ZIP_PREFIX"/AdbWinApi.dll \
"$ZIP_PREFIX"/AdbWinUsbApi.dll \
"$ZIP_PREFIX"/adb.exe
mv "$ZIP_PREFIX"/* .
rmdir "$ZIP_PREFIX"
fi
mkdir -p "$INSTALL_DIR/$HOST/bin"
cd "$INSTALL_DIR/$HOST/bin"
cp -r "$SOURCES_DIR/$PROJECT_DIR"/. "$INSTALL_DIR/$HOST/bin/"

@ -0,0 +1,55 @@
#!/usr/bin/env bash
# This file is intended to be sourced by other scripts, not executed
if [[ $# != 1 ]]
then
# <host>: win32 or win64
echo "Syntax: $0 <host>" >&2
exit 1
fi
HOST="$1"
if [[ "$HOST" = win32 ]]
then
HOST_TRIPLET=i686-w64-mingw32
elif [[ "$HOST" = win64 ]]
then
HOST_TRIPLET=x86_64-w64-mingw32
else
echo "Unsupported host: $HOST" >&2
exit 1
fi
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
PATCHES_DIR="$PWD/patches"
WORK_DIR="$PWD/work"
SOURCES_DIR="$WORK_DIR/sources"
BUILD_DIR="$WORK_DIR/build"
INSTALL_DIR="$WORK_DIR/install"
mkdir -p "$INSTALL_DIR" "$SOURCES_DIR" "$WORK_DIR"
checksum() {
local file="$1"
local sum="$2"
echo "$file: verifying checksum..."
echo "$sum $file" | sha256sum -c
}
get_file() {
local url="$1"
local file="$2"
local sum="$3"
if [[ -f "$file" ]]
then
echo "$file: found"
else
echo "$file: not found, downloading..."
wget "$url" -O "$file"
fi
checksum "$file" "$sum"
}

@ -0,0 +1,91 @@
#!/usr/bin/env bash
set -ex
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
VERSION=6.1.1
FILENAME=ffmpeg-$VERSION.tar.xz
PROJECT_DIR=ffmpeg-$VERSION
SHA256SUM=8684f4b00f94b85461884c3719382f1261f0d9eb3d59640a1f4ac0873616f968
cd "$SOURCES_DIR"
if [[ -d "$PROJECT_DIR" ]]
then
echo "$PWD/$PROJECT_DIR" found
else
get_file "https://ffmpeg.org/releases/$FILENAME" "$FILENAME" "$SHA256SUM"
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
patch -d "$PROJECT_DIR" -p1 < "$PATCHES_DIR"/ffmpeg-6.1-fix-build.patch
fi
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
cd "$BUILD_DIR/$PROJECT_DIR"
if [[ "$HOST" = win32 ]]
then
ARCH=x86
elif [[ "$HOST" = win64 ]]
then
ARCH=x86_64
else
echo "Unsupported host: $HOST" >&2
exit 1
fi
# -static-libgcc to avoid missing libgcc_s_dw2-1.dll
# -static to avoid dynamic dependency to zlib
export CFLAGS='-static-libgcc -static'
export CXXFLAGS="$CFLAGS"
export LDFLAGS='-static-libgcc -static'
if [[ -d "$HOST" ]]
then
echo "'$PWD/$HOST' already exists, not reconfigured"
cd "$HOST"
else
mkdir "$HOST"
cd "$HOST"
"$SOURCES_DIR/$PROJECT_DIR"/configure \
--prefix="$INSTALL_DIR/$HOST" \
--enable-cross-compile \
--target-os=mingw32 \
--arch="$ARCH" \
--cross-prefix="${HOST_TRIPLET}-" \
--cc="${HOST_TRIPLET}-gcc" \
--extra-cflags="-O2 -fPIC" \
--enable-shared \
--disable-static \
--disable-programs \
--disable-doc \
--disable-swscale \
--disable-postproc \
--disable-avfilter \
--disable-avdevice \
--disable-network \
--disable-everything \
--enable-swresample \
--enable-decoder=h264 \
--enable-decoder=hevc \
--enable-decoder=av1 \
--enable-decoder=pcm_s16le \
--enable-decoder=opus \
--enable-decoder=aac \
--enable-decoder=flac \
--enable-decoder=png \
--enable-protocol=file \
--enable-demuxer=image2 \
--enable-parser=png \
--enable-zlib \
--enable-muxer=matroska \
--enable-muxer=mp4 \
--enable-muxer=opus \
--enable-muxer=flac \
--enable-muxer=wav \
--disable-vulkan
fi
make -j
make install

@ -0,0 +1,44 @@
#!/usr/bin/env bash
set -ex
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
VERSION=1.0.27
FILENAME=libusb-$VERSION.tar.bz2
PROJECT_DIR=libusb-$VERSION
SHA256SUM=ffaa41d741a8a3bee244ac8e54a72ea05bf2879663c098c82fc5757853441575
cd "$SOURCES_DIR"
if [[ -d "$PROJECT_DIR" ]]
then
echo "$PWD/$PROJECT_DIR" found
else
get_file "https://github.com/libusb/libusb/releases/download/v$VERSION/libusb-$VERSION.tar.bz2" "$FILENAME" "$SHA256SUM"
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
fi
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
cd "$BUILD_DIR/$PROJECT_DIR"
export CFLAGS='-O2'
export CXXFLAGS="$CFLAGS"
if [[ -d "$HOST" ]]
then
echo "'$PWD/$HOST' already exists, not reconfigured"
cd "$HOST"
else
mkdir "$HOST"
cd "$HOST"
"$SOURCES_DIR/$PROJECT_DIR"/configure \
--prefix="$INSTALL_DIR/$HOST" \
--host="$HOST_TRIPLET" \
--enable-shared \
--disable-static
fi
make -j
make install-strip

@ -0,0 +1,27 @@
From 03c80197afb324da38c9b70254231e3fdcfa68fc Mon Sep 17 00:00:00 2001
From: Romain Vimont <rom@rom1v.com>
Date: Sun, 12 Nov 2023 17:58:50 +0100
Subject: [PATCH] Fix FFmpeg 6.1 build
Build failed on tag n6.1 With --enable-decoder=av1 but without
--enable-muxer=av1.
---
libavcodec/Makefile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 580a8d6b54..aff19b670c 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -249,7 +249,7 @@ OBJS-$(CONFIG_ATRAC3PAL_DECODER) += atrac3plusdec.o atrac3plus.o \
OBJS-$(CONFIG_ATRAC9_DECODER) += atrac9dec.o
OBJS-$(CONFIG_AURA_DECODER) += cyuv.o
OBJS-$(CONFIG_AURA2_DECODER) += aura.o
-OBJS-$(CONFIG_AV1_DECODER) += av1dec.o
+OBJS-$(CONFIG_AV1_DECODER) += av1dec.o av1_parse.o
OBJS-$(CONFIG_AV1_CUVID_DECODER) += cuviddec.o
OBJS-$(CONFIG_AV1_MEDIACODEC_DECODER) += mediacodecdec.o
OBJS-$(CONFIG_AV1_MEDIACODEC_ENCODER) += mediacodecenc.o
--
2.42.0

@ -0,0 +1,47 @@
#!/usr/bin/env bash
set -ex
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
VERSION=2.28.5
FILENAME=SDL-$VERSION.tar.gz
PROJECT_DIR=SDL-release-$VERSION
SHA256SUM=9f0556e4a24ef5b267010038ad9e9948b62f236d5bcc4b22179f95ef62d84023
cd "$SOURCES_DIR"
if [[ -d "$PROJECT_DIR" ]]
then
echo "$PWD/$PROJECT_DIR" found
else
get_file "https://github.com/libsdl-org/SDL/archive/refs/tags/release-$VERSION.tar.gz" "$FILENAME" "$SHA256SUM"
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
fi
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
cd "$BUILD_DIR/$PROJECT_DIR"
export CFLAGS='-O2'
export CXXFLAGS="$CFLAGS"
if [[ -d "$HOST" ]]
then
echo "'$PWD/$HOST' already exists, not reconfigured"
cd "$HOST"
else
mkdir "$HOST"
cd "$HOST"
"$SOURCES_DIR/$PROJECT_DIR"/configure \
--prefix="$INSTALL_DIR/$HOST" \
--host="$HOST_TRIPLET" \
--enable-shared \
--disable-static
fi
make -j
# There is no "make install-strip"
make install
# Strip manually
${HOST_TRIPLET}-strip "$INSTALL_DIR/$HOST/bin/SDL2.dll"

@ -4,44 +4,59 @@ src = [
'src/adb/adb_device.c',
'src/adb/adb_parser.c',
'src/adb/adb_tunnel.c',
'src/audio_player.c',
'src/cli.c',
'src/clock.c',
'src/compat.c',
'src/control_msg.c',
'src/controller.c',
'src/decoder.c',
'src/delay_buffer.c',
'src/demuxer.c',
'src/device_msg.c',
'src/display.c',
'src/icon.c',
'src/file_pusher.c',
'src/fps_counter.c',
'src/frame_buffer.c',
'src/input_manager.c',
'src/keyboard_inject.c',
'src/mouse_inject.c',
'src/keyboard_sdk.c',
'src/mouse_sdk.c',
'src/opengl.c',
'src/options.c',
'src/packet_merger.c',
'src/receiver.c',
'src/recorder.c',
'src/scrcpy.c',
'src/screen.c',
'src/server.c',
'src/version.c',
'src/video_buffer.c',
'src/hid/hid_keyboard.c',
'src/hid/hid_mouse.c',
'src/trait/frame_source.c',
'src/trait/packet_source.c',
'src/uhid/keyboard_uhid.c',
'src/uhid/mouse_uhid.c',
'src/uhid/uhid_output.c',
'src/util/acksync.c',
'src/util/audiobuf.c',
'src/util/average.c',
'src/util/file.c',
'src/util/intmap.c',
'src/util/intr.c',
'src/util/log.c',
'src/util/memory.c',
'src/util/net.c',
'src/util/net_intr.c',
'src/util/process.c',
'src/util/process_intr.c',
'src/util/rand.c',
'src/util/strbuf.c',
'src/util/str.c',
'src/util/term.c',
'src/util/thread.c',
'src/util/tick.c',
'src/util/timeout.c',
]
conf = configuration_data()
@ -78,8 +93,8 @@ usb_support = get_option('usb')
if usb_support
src += [
'src/usb/aoa_hid.c',
'src/usb/hid_keyboard.c',
'src/usb/hid_mouse.c',
'src/usb/keyboard_aoa.c',
'src/usb/mouse_aoa.c',
'src/usb/scrcpy_otg.c',
'src/usb/screen_otg.c',
'src/usb/usb.c',
@ -88,81 +103,24 @@ endif
cc = meson.get_compiler('c')
crossbuild_windows = meson.is_cross_build() and host_machine.system() == 'windows'
if not crossbuild_windows
# native build
dependencies = [
dependency('libavformat', version: '>= 57.33'),
dependency('libavcodec', version: '>= 57.37'),
dependency('libavutil'),
dependency('sdl2', version: '>= 2.0.5'),
]
if v4l2_support
dependencies += dependency('libavdevice')
endif
if usb_support
dependencies += dependency('libusb-1.0')
endif
else
# cross-compile mingw32 build (from Linux to Windows)
prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2')
sdl2_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_sdl2 + '/bin'
sdl2_lib_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_sdl2 + '/lib'
sdl2_include_dir = 'prebuilt-deps/data/' + prebuilt_sdl2 + '/include'
sdl2 = declare_dependency(
dependencies: [
cc.find_library('SDL2', dirs: sdl2_bin_dir),
cc.find_library('SDL2main', dirs: sdl2_lib_dir),
],
include_directories: include_directories(sdl2_include_dir)
)
prebuilt_ffmpeg = meson.get_cross_property('prebuilt_ffmpeg')
ffmpeg_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_ffmpeg + '/bin'
ffmpeg_include_dir = 'prebuilt-deps/data/' + prebuilt_ffmpeg + '/include'
# ffmpeg versions are different for win32 and win64 builds
ffmpeg_avcodec = meson.get_cross_property('ffmpeg_avcodec')
ffmpeg_avformat = meson.get_cross_property('ffmpeg_avformat')
ffmpeg_avutil = meson.get_cross_property('ffmpeg_avutil')
ffmpeg = declare_dependency(
dependencies: [
cc.find_library(ffmpeg_avcodec, dirs: ffmpeg_bin_dir),
cc.find_library(ffmpeg_avformat, dirs: ffmpeg_bin_dir),
cc.find_library(ffmpeg_avutil, dirs: ffmpeg_bin_dir),
],
include_directories: include_directories(ffmpeg_include_dir)
)
prebuilt_libusb = meson.get_cross_property('prebuilt_libusb')
prebuilt_libusb_root = meson.get_cross_property('prebuilt_libusb_root')
libusb_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_libusb + '/dll'
libusb_include_dir = 'prebuilt-deps/data/' + prebuilt_libusb_root + '/include'
libusb = declare_dependency(
dependencies: [
cc.find_library('libusb-1.0', dirs: libusb_bin_dir),
],
include_directories: include_directories(libusb_include_dir)
)
dependencies = [
dependency('libavformat', version: '>= 57.33'),
dependency('libavcodec', version: '>= 57.37'),
dependency('libavutil'),
dependency('libswresample'),
dependency('sdl2', version: '>= 2.0.5'),
]
dependencies = [
ffmpeg,
sdl2,
libusb,
cc.find_library('mingw32')
]
if v4l2_support
dependencies += dependency('libavdevice')
endif
if usb_support
dependencies += dependency('libusb-1.0')
endif
if host_machine.system() == 'windows'
dependencies += cc.find_library('mingw32')
dependencies += cc.find_library('ws2_32')
endif
@ -170,6 +128,9 @@ check_functions = [
'strdup',
'asprintf',
'vasprintf',
'nrand48',
'jrand48',
'reallocarray',
]
foreach f : check_functions
@ -197,10 +158,6 @@ conf.set('PORTABLE', get_option('portable'))
conf.set('DEFAULT_LOCAL_PORT_RANGE_FIRST', '27183')
conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199')
# the default video bitrate, in bits/second
# overridden by option --bit-rate
conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps
# run a server debugger and wait for a client to be attached
conf.set('SERVER_DEBUGGER', get_option('server_debugger'))
@ -223,14 +180,26 @@ executable('scrcpy', src,
install: true,
c_args: [])
# <https://mesonbuild.com/Builtin-options.html#directories>
datadir = get_option('datadir') # by default 'share'
install_man('scrcpy.1')
install_data('data/icon.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_dir: 'share/zsh/site-functions')
install_dir: join_paths(datadir, 'zsh/site-functions'))
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
@ -245,25 +214,24 @@ if get_option('buildtype') == 'debug'
'src/util/str.c',
'src/util/strbuf.c',
]],
['test_buffer_util', [
'tests/test_buffer_util.c',
['test_binary', [
'tests/test_binary.c',
]],
['test_cbuf', [
'tests/test_cbuf.c',
['test_audiobuf', [
'tests/test_audiobuf.c',
'src/util/audiobuf.c',
'src/util/memory.c',
]],
['test_cli', [
'tests/test_cli.c',
'src/cli.c',
'src/options.c',
'src/util/log.c',
'src/util/net.c',
'src/util/str.c',
'src/util/strbuf.c',
'src/util/term.c',
]],
['test_clock', [
'tests/test_clock.c',
'src/clock.c',
]],
['test_control_msg_serialize', [
'tests/test_control_msg_serialize.c',
'src/control_msg.c',
@ -274,8 +242,9 @@ if get_option('buildtype') == 'debug'
'tests/test_device_msg_deserialize.c',
'src/device_msg.c',
]],
['test_queue', [
'tests/test_queue.c',
['test_orientation', [
'tests/test_orientation.c',
'src/options.c',
]],
['test_strbuf', [
'tests/test_strbuf.c',
@ -286,13 +255,18 @@ if get_option('buildtype') == 'debug'
'src/util/str.c',
'src/util/strbuf.c',
]],
['test_vecdeque', [
'tests/test_vecdeque.c',
'src/util/memory.c',
]],
['test_vector', [
'tests/test_vector.c',
]],
]
foreach t : tests
exe = executable(t[0], t[1],
sources = t[1] + ['src/compat.c']
exe = executable(t[0], sources,
include_directories: src_dir,
dependencies: dependencies,
c_args: ['-DSDL_MAIN_HANDLED', '-DSC_TEST'])

@ -1,22 +0,0 @@
PREBUILT_DATA_DIR=data
checksum() {
local file="$1"
local sum="$2"
echo "$file: verifying checksum..."
echo "$sum $file" | sha256sum -c
}
get_file() {
local url="$1"
local file="$2"
local sum="$3"
if [[ -f "$file" ]]
then
echo "$file: found"
else
echo "$file: not found, downloading..."
wget "$url" -O "$file"
fi
checksum "$file" "$sum"
}

@ -1,32 +0,0 @@
#!/usr/bin/env bash
set -e
DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DIR"
. common
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
DEP_DIR=platform-tools-31.0.3
FILENAME=platform-tools_r31.0.3-windows.zip
SHA256SUM=0f4b8fdd26af2c3733539d6eebb3c2ed499ea1d4bb1f4e0ecc2d6016961a6e24
if [[ -d "$DEP_DIR" ]]
then
echo "$DEP_DIR" found
exit 0
fi
get_file "https://dl.google.com/android/repository/$FILENAME" \
"$FILENAME" "$SHA256SUM"
mkdir "$DEP_DIR"
cd "$DEP_DIR"
ZIP_PREFIX=platform-tools
unzip "../$FILENAME" \
"$ZIP_PREFIX"/AdbWinApi.dll \
"$ZIP_PREFIX"/AdbWinUsbApi.dll \
"$ZIP_PREFIX"/adb.exe
mv "$ZIP_PREFIX"/* .
rmdir "$ZIP_PREFIX"

@ -1,45 +0,0 @@
#!/usr/bin/env bash
set -e
DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DIR"
. common
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
DEP_DIR=ffmpeg-win32-4.3.1
FILENAME_SHARED=ffmpeg-4.3.1-win32-shared.zip
SHA256SUM_SHARED=357af9901a456f4dcbacd107e83a934d344c9cb07ddad8aaf80612eeab7d26d2
FILENAME_DEV=ffmpeg-4.3.1-win32-dev.zip
SHA256SUM_DEV=230efb08e9bcf225bd474da29676c70e591fc94d8790a740ca801408fddcb78b
if [[ -d "$DEP_DIR" ]]
then
echo "$DEP_DIR" found
exit 0
fi
get_file "https://github.com/Genymobile/scrcpy/releases/download/v1.16/$FILENAME_SHARED" \
"$FILENAME_SHARED" "$SHA256SUM_SHARED"
get_file "https://github.com/Genymobile/scrcpy/releases/download/v1.16/$FILENAME_DEV" \
"$FILENAME_DEV" "$SHA256SUM_DEV"
mkdir "$DEP_DIR"
cd "$DEP_DIR"
ZIP_PREFIX_SHARED=ffmpeg-4.3.1-win32-shared
unzip "../$FILENAME_SHARED" \
"$ZIP_PREFIX_SHARED"/bin/avutil-56.dll \
"$ZIP_PREFIX_SHARED"/bin/avcodec-58.dll \
"$ZIP_PREFIX_SHARED"/bin/avformat-58.dll \
"$ZIP_PREFIX_SHARED"/bin/swresample-3.dll \
"$ZIP_PREFIX_SHARED"/bin/swscale-5.dll
ZIP_PREFIX_DEV=ffmpeg-4.3.1-win32-dev
unzip "../$FILENAME_DEV" \
"$ZIP_PREFIX_DEV/include/*"
mv "$ZIP_PREFIX_SHARED"/* .
mv "$ZIP_PREFIX_DEV"/* .
rmdir "$ZIP_PREFIX_SHARED" "$ZIP_PREFIX_DEV"

@ -1,35 +0,0 @@
#!/usr/bin/env bash
set -e
DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DIR"
. common
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
DEP_DIR=ffmpeg-win64-5.0
FILENAME=ffmpeg-5.0-full_build-shared.7z
SHA256SUM=e5900f6cecd4c438d398bd2fc308736c10b857cd8dd61c11bcfb05bff5d1211a
if [[ -d "$DEP_DIR" ]]
then
echo "$DEP_DIR" found
exit 0
fi
get_file "https://github.com/GyanD/codexffmpeg/releases/download/5.0/$FILENAME" \
"$FILENAME" "$SHA256SUM"
mkdir "$DEP_DIR"
cd "$DEP_DIR"
ZIP_PREFIX=ffmpeg-5.0-full_build-shared
7z x "../$FILENAME" \
"$ZIP_PREFIX"/bin/avutil-57.dll \
"$ZIP_PREFIX"/bin/avcodec-59.dll \
"$ZIP_PREFIX"/bin/avformat-59.dll \
"$ZIP_PREFIX"/bin/swresample-4.dll \
"$ZIP_PREFIX"/bin/swscale-6.dll \
"$ZIP_PREFIX"/include
mv "$ZIP_PREFIX"/* .
rmdir "$ZIP_PREFIX"

@ -1,28 +0,0 @@
#!/usr/bin/env bash
set -e
DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DIR"
. common
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
DEP_DIR=libusb-1.0.25
FILENAME=libusb-1.0.25.7z
SHA256SUM=3d1c98416f454026034b2b5d67f8a294053898cb70a8b489874e75b136c6674d
if [[ -d "$DEP_DIR" ]]
then
echo "$DEP_DIR" found
exit 0
fi
get_file "https://github.com/libusb/libusb/releases/download/v1.0.25/$FILENAME" "$FILENAME" "$SHA256SUM"
mkdir "$DEP_DIR"
cd "$DEP_DIR"
7z x "../$FILENAME" \
MinGW32/dll/libusb-1.0.dll \
MinGW64/dll/libusb-1.0.dll \
include /

@ -1,32 +0,0 @@
#!/usr/bin/env bash
set -e
DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DIR"
. common
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
DEP_DIR=SDL2-2.0.20
FILENAME=SDL2-devel-2.0.20-mingw.tar.gz
SHA256SUM=38094d82a857d6c62352e5c5cdec74948c5b4d25c59cbd298d6d233568976bd1
if [[ -d "$DEP_DIR" ]]
then
echo "$DEP_DIR" found
exit 0
fi
get_file "https://libsdl.org/release/$FILENAME" "$FILENAME" "$SHA256SUM"
mkdir "$DEP_DIR"
cd "$DEP_DIR"
TAR_PREFIX="$DEP_DIR" # root directory inside the tar has the same name
tar xf "../$FILENAME" --strip-components=1 \
"$TAR_PREFIX"/i686-w64-mingw32/bin/SDL2.dll \
"$TAR_PREFIX"/i686-w64-mingw32/include/ \
"$TAR_PREFIX"/i686-w64-mingw32/lib/ \
"$TAR_PREFIX"/x86_64-w64-mingw32/bin/SDL2.dll \
"$TAR_PREFIX"/x86_64-w64-mingw32/include/ \
"$TAR_PREFIX"/x86_64-w64-mingw32/lib/ \

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

@ -20,20 +20,94 @@ provides display and control of Android devices connected on USB (or over TCP/IP
Make scrcpy window always on top (above other windows).
.TP
.BI "\-b, \-\-bit\-rate " value
Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000).
.BI "\-\-audio\-bit\-rate " value
Encode the audio at the given bit rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000).
Default is 8000000.
Default is 128K (128000).
.TP
.BI "\-\-codec\-options " key[:type]=value[,...]
Set a list of comma-separated key:type=value options for the device encoder.
.BI "\-\-audio\-buffer " ms
Configure the audio buffering delay (in milliseconds).
Lower values decrease the latency, but increase the likelyhood of buffer underrun (causing audio glitches).
Default is 50.
.TP
.BI "\-\-audio\-codec " name
Select an audio codec (opus, aac, flac or raw).
Default is opus.
.TP
.BI "\-\-audio\-codec\-options " key\fR[:\fItype\fR]=\fIvalue\fR[,...]
Set a list of comma-separated key:type=value options for the device audio encoder.
The possible values for 'type' are 'int' (default), 'long', 'float' and 'string'.
The list of possible codec options is available in the Android documentation
.UR https://d.android.com/reference/android/media/MediaFormat
.UE .
The list of possible codec options is available in the Android documentation:
<https://d.android.com/reference/android/media/MediaFormat>
.TP
.BI "\-\-audio\-encoder " name
Use a specific MediaCodec audio encoder (depending on the codec provided by \fB\-\-audio\-codec\fR).
The available encoders can be listed by \fB\-\-list\-encoders\fR.
.TP
.BI "\-\-audio\-source " source
Select the audio source (output or mic).
Default is output.
.TP
.BI "\-\-audio\-output\-buffer " ms
Configure the size of the SDL audio output buffer (in milliseconds).
If you get "robotic" audio playback, you should test with a higher value (10). Do not change this setting otherwise.
Default is 5.
.TP
.BI "\-b, \-\-video\-bit\-rate " value
Encode the video at the given bit rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000).
Default is 8M (8000000).
.TP
.BI "\-\-camera\-ar " ar
Select the camera size by its aspect ratio (+/- 10%).
Possible values are "sensor" (use the camera sensor aspect ratio), "\fInum\fR:\fIden\fR" (e.g. "4:3") and "\fIvalue\fR" (e.g. "1.6").
.TP
.B \-\-camera\-high\-speed
Enable high-speed camera capture mode.
This mode is restricted to specific resolutions and frame rates, listed by \fB\-\-list\-camera\-sizes\fR.
.TP
.BI "\-\-camera\-id " id
Specify the device camera id to mirror.
The available camera ids can be listed by \fB\-\-list\-cameras\fR.
.TP
.BI "\-\-camera\-facing " facing
Select the device camera by its facing direction.
Possible values are "front", "back" and "external".
.TP
.BI "\-\-camera\-fps " fps
Specify the camera capture frame rate.
If not specified, Android's default frame rate (30 fps) is used.
.TP
.BI "\-\-camera\-size " width\fRx\fIheight
Specify an explicit camera capture size.
.TP
.BI "\-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy
@ -50,23 +124,30 @@ Use USB device (if there is exactly one, like adb -d).
Also see \fB\-e\fR (\fB\-\-select\-tcpip\fR).
.TP
.BI "\-\-disable-screensaver"
.BI "\-\-disable\-screensaver"
Disable screensaver while scrcpy is running.
.TP
.BI "\-\-display " id
Specify the display id to mirror.
.BI "\-\-display\-buffer " ms
Add a buffering delay (in milliseconds) before displaying. This increases latency to compensate for jitter.
The list of possible display ids can be listed by "adb shell dumpsys display"
(search "mDisplayId=" in the output).
Default is 0 (no buffering).
.TP
.BI "\-\-display\-id " id
Specify the device display id to mirror.
The available display ids can be listed by \fB\-\-list\-displays\fR.
Default is 0.
.TP
.BI "\-\-display\-buffer ms
Add a buffering delay (in milliseconds) before displaying. This increases latency to compensate for jitter.
.BI "\-\-display\-orientation " value
Set the initial display orientation.
Default is 0 (no buffering).
Possible values are 0, 90, 180, 270, flip0, flip90, flip180 and flip270. The number represents the clockwise rotation in degrees; the "flip" keyword applies a horizontal flip before the rotation.
Default is 0.
.TP
.B \-e, \-\-select\-tcpip
@ -75,8 +156,8 @@ Use TCP/IP device (if there is exactly one, like adb -e).
Also see \fB\-d\fR (\fB\-\-select\-usb\fR).
.TP
.BI "\-\-encoder " name
Use a specific MediaCodec encoder (must be a H.264 encoder).
.B \-f, \-\-fullscreen
Start in fullscreen.
.TP
.B \-\-force\-adb\-forward
@ -86,29 +167,36 @@ Do not attempt to use "adb reverse" to connect to the device.
.B \-\-forward\-all\-clicks
By default, right-click triggers BACK (or POWER on) and middle-click triggers HOME. This option disables these shortcuts and forward the clicks to the device instead.
.TP
.B \-f, \-\-fullscreen
Start in fullscreen.
.TP
.B \-h, \-\-help
Print this help.
.TP
.B \-K, \-\-hid\-keyboard
Simulate a physical keyboard by using HID over AOAv2.
.B \-K
Same as \fB\-\-keyboard=uhid\fR.
.TP
.BI "\-\-keyboard " mode
Select how to send keyboard inputs to the device.
This provides a better experience for IME users, and allows to generate non-ASCII characters, contrary to the default injection method.
Possible values are "disabled", "sdk", "uhid" and "aoa":
It may only work over USB.
- "disabled" does not send keyboard inputs to the device.
- "sdk" uses the Android system API to deliver keyboard events to applications.
- "uhid" simulates a physical HID keyboard using the Linux HID kernel module on the device.
- "aoa" simulates a physical HID keyboard using the AOAv2 protocol. It may only work over USB.
The keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly:
For "uhid" and "aoa", the keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly using the shortcut MOD+k (except in OTG mode), or by executing:
adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS
However, the option is only available when the HID keyboard is enabled (or a physical keyboard is connected).
This option is only available when the HID keyboard is enabled (or a physical keyboard is connected).
Also see \fB\-\-hid\-mouse\fR.
Also see \fB\-\-mouse\fR.
.TP
.B \-\-kill\-adb\-on\-close
Kill adb when scrcpy terminates.
.TP
.B \-\-legacy\-paste
@ -117,16 +205,30 @@ Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+S
This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically.
.TP
.BI "\-\-lock\-video\-orientation[=value]
Lock video orientation to \fIvalue\fR. Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees rotation counterclockwise.
.B \-\-list\-camera\-sizes
List the valid camera capture sizes.
Default is "unlocked".
.TP
.B \-\-list\-cameras
List cameras available on the device.
Passing the option without argument is equivalent to passing "initial".
.TP
.B \-\-list\-encoders
List video and audio encoders available on the device.
.TP
.BI "\-\-max\-fps " value
Limit the framerate of screen capture (officially supported since Android 10, but may work on earlier versions).
.B \-\-list\-displays
List displays available on the device.
.TP
\fB\-\-lock\-video\-orientation\fR[=\fIvalue\fR]
Lock capture video orientation to \fIvalue\fR.
Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 90, 180, and 270. The values represent the clockwise rotation from the natural device orientation, in degrees.
Default is "unlocked".
Passing the option without argument is equivalent to passing "initial".
.TP
.BI "\-m, \-\-max\-size " value
@ -135,16 +237,46 @@ Limit both the width and height of the video to \fIvalue\fR. The other dimension
Default is 0 (unlimited).
.TP
.B \-M, \-\-hid\-mouse
Simulate a physical mouse by using HID over AOAv2.
.B \-M
Same as \fB\-\-mouse=uhid\fR.
.TP
.BI "\-\-max\-fps " value
Limit the framerate of screen capture (officially supported since Android 10, but may work on earlier versions).
.TP
.BI "\-\-mouse " mode
Select how to send mouse inputs to the device.
Possible values are "disabled", "sdk", "uhid" and "aoa":
- "disabled" does not send mouse inputs to the device.
- "sdk" uses the Android system API to deliver mouse events to applications.
- "uhid" simulates a physical HID mouse using the Linux HID kernel module on the device.
- "aoa" simulates a physical mouse using the AOAv2 protocol. It may only work over USB.
In this mode, the computer mouse is captured to control the device directly (relative mouse mode).
In "uhid" and "aoa" modes, the computer mouse is captured to control the device directly (relative mouse mode).
LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse back to the computer.
It may only work over USB.
Also see \fB\-\-keyboard\fR.
.TP
.B \-n, \-\-no\-control
Disable device control (mirror the device in read\-only).
Also see \fB\-\-hid\-keyboard\fR.
.TP
.B \-N, \-\-no\-playback
Disable video and audio playback on the computer (equivalent to \fB\-\-no\-video\-playback \-\-no\-audio\-playback\fR).
.TP
.B \-\-no\-audio
Disable audio forwarding.
.TP
.B \-\-no\-audio\-playback
Disable audio playback on the computer.
.TP
.B \-\-no\-cleanup
@ -164,14 +296,6 @@ By default, on MediaCodec error, scrcpy automatically tries again with a lower d
This option disables this behavior.
.TP
.B \-n, \-\-no\-control
Disable device control (mirror the device in read\-only).
.TP
.B \-N, \-\-no\-display
Do not display device (only when screen recording is enabled).
.TP
.B \-\-no\-key\-repeat
Do not forward repeated key events when a key is held down.
@ -180,6 +304,22 @@ Do not forward repeated key events when a key is held down.
.B \-\-no\-mipmaps
If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps.
.TP
.B \-\-no\-power\-on
Do not power on the device on start.
.TP
.B \-\-no\-video
Disable video forwarding.
.TP
.B \-\-no\-video\-playback
Disable video playback on the computer.
.TP
.BI "\-\-orientation " value
Same as --display-orientation=value --record-orientation=value.
.TP
.B \-\-otg
Run in OTG mode: simulate physical keyboard and mouse, as if the computer keyboard and mouse were plugged directly to the device via an OTG cable.
@ -195,11 +335,21 @@ It may only work over USB.
See \fB\-\-hid\-keyboard\fR and \fB\-\-hid\-mouse\fR.
.TP
.BI "\-p, \-\-port " port[:port]
.BI "\-p, \-\-port " port\fR[:\fIport\fR]
Set the TCP port (range) used by the client to listen.
Default is 27183:27199.
.TP
\fB\-\-pause\-on\-exit\fR[=\fImode\fR]
Configure pause on exit. Possible values are "true" (always pause on exit), "false" (never pause on exit) and "if-error" (pause only if an error occured).
This is useful to prevent the terminal window from automatically closing, so that error messages can be read.
Default is "false".
Passing the option without argument is equivalent to passing "true".
.TP
.B \-\-power\-off\-on\-close
Turn the device screen off when closing scrcpy.
@ -221,10 +371,6 @@ Set the target directory for pushing files to the device by drag & drop. It is p
Default is "/sdcard/Download/".
.TP
.B \-\-raw\-key\-events
Inject key events for all input keys, and ignore text events.
.TP
.BI "\-r, \-\-record " file
Record screen to
@ -232,11 +378,23 @@ Record screen to
The format is determined by the
.B \-\-record\-format
option if set, or by the file extension (.mp4 or .mkv).
option if set, or by the file extension.
.TP
.B \-\-raw\-key\-events
Inject key events for all input keys, and ignore text events.
.TP
.BI "\-\-record\-format " format
Force recording format (either mp4 or mkv).
Force recording format (mp4, mkv, m4a, mka, opus, aac, flac or wav).
.TP
.BI "\-\-record\-orientation " value
Set the record orientation.
Possible values are 0, 90, 180 and 270. The number represents the clockwise rotation in degrees.
Default is 0.
.TP
.BI "\-\-render\-driver " name
@ -244,19 +402,22 @@ Request SDL to use the given render driver (this is just a hint).
Supported names are currently "direct3d", "opengl", "opengles2", "opengles", "metal" and "software".
.UR https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER
.UE
<https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER>
.TP
.BI "\-\-rotation " value
Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each increment adds a 90 degrees rotation counterclockwise.
.B \-\-require\-audio
By default, scrcpy mirrors only the video if audio capture fails on the device. This option makes scrcpy fail if audio is enabled but does not work.
.TP
.BI "\-s, \-\-serial " number
The device serial number. Mandatory only if several devices are connected to adb.
.TP
.BI "\-\-shortcut\-mod " key[+...]][,...]
.B \-S, \-\-turn\-screen\-off
Turn the device screen off immediately.
.TP
.BI "\-\-shortcut\-mod " key\fR[+...]][,...]
Specify the modifiers to use for scrcpy shortcuts. Possible keys are "lctrl", "rctrl", "lalt", "ralt", "lsuper" and "rsuper".
A shortcut can consist in several keys, separated by '+'. Several shortcuts can be specified, separated by ','.
@ -266,35 +427,45 @@ For example, to use either LCtrl+LAlt or LSuper for scrcpy shortcuts, pass "lctr
Default is "lalt,lsuper" (left-Alt or left-Super).
.TP
.BI "\-\-tcpip[=ip[:port]]
.B \-t, \-\-show\-touches
Enable "show touches" on start, restore the initial value on exit.
It only shows physical touches (not clicks from scrcpy).
.TP
.BI "\-\-tcpip\fR[=\fIip\fR[:\fIport\fR]]
Configure and reconnect the device over TCP/IP.
If a destination address is provided, then scrcpy connects to this address before starting. The device must listen on the given TCP port (default is 5555).
If no destination address is provided, then scrcpy attempts to find the IP address of the current device (typically connected over USB), enables TCP/IP mode, then connects to this address before starting.
.TP
.B \-S, \-\-turn\-screen\-off
Turn the device screen off immediately.
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
.B \-t, \-\-show\-touches
Enable "show touches" on start, restore the initial value on exit.
It only shows physical touches (not clicks from scrcpy).
.BI "\-\-time\-limit " seconds
Set the maximum mirroring time, in seconds.
.TP
.BI "\-\-tunnel\-host " ip
Set the IP address of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward.
Set the IP address of the adb tunnel to reach the scrcpy server. This option automatically enables \fB\-\-force\-adb\-forward\fR.
Default is localhost.
.TP
.BI "\-\-tunnel\-port " port
Set the TCP port of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward.
Set the TCP port of the adb tunnel to reach the scrcpy server. This option automatically enables \fB\-\-force\-adb\-forward\fR.
Default is 0 (not forced): the local port used for establishing the tunnel will be used.
.TP
.B \-v, \-\-version
Print the version of scrcpy.
.TP
.BI "\-V, \-\-verbosity " value
Set the log level ("verbose", "debug", "info", "warn" or "error").
Default is "info" for release builds, "debug" for debug builds.
.TP
.BI "\-\-v4l2-sink " /dev/videoN
Output to v4l2loopback device.
@ -310,14 +481,34 @@ This option is similar to \fB\-\-display\-buffer\fR, but specific to V4L2 sink.
Default is 0 (no buffering).
.TP
.BI "\-V, \-\-verbosity " value
Set the log level ("verbose", "debug", "info", "warn" or "error").
.BI "\-\-video\-codec " name
Select a video codec (h264, h265 or av1).
Default is "info" for release builds, "debug" for debug builds.
Default is h264.
.TP
.B \-v, \-\-version
Print the version of scrcpy.
.BI "\-\-video\-codec\-options " key\fR[:\fItype\fR]=\fIvalue\fR[,...]
Set a list of comma-separated key:type=value options for the device video encoder.
The possible values for 'type' are 'int' (default), 'long', 'float' and 'string'.
The list of possible codec options is available in the Android documentation:
<https://d.android.com/reference/android/media/MediaFormat>
.TP
.BI "\-\-video\-encoder " name
Use a specific MediaCodec video encoder (depending on the codec provided by \fB\-\-video\-codec\fR).
The available encoders can be listed by \fB\-\-list\-encoders\fR.
.TP
.BI "\-\-video\-source " source
Select the video source (display or camera).
Camera mirroring requires Android 12+.
Default is display.
.TP
.B \-w, \-\-stay-awake
@ -355,6 +546,12 @@ Set the initial window height.
Default is 0 (automatic).
.SH EXIT STATUS
.B scrcpy
will exit with code 0 on normal program termination. If an initial
connection cannot be established, the exit code 1 will be returned. If the
device disconnects while a session is active, exit code 2 will be returned.
.SH SHORTCUTS
In the following list, MOD is the shortcut modifier. By default, it's (left)
@ -372,6 +569,14 @@ Rotate display left
.B MOD+Right
Rotate display right
.TP
.B MOD+Shift+Left, MOD+Shift+Right
Flip display horizontally
.TP
.B MOD+Shift+Up, MOD+Shift+Down
Flip display vertically
.TP
.B MOD+g
Resize window to 1:1 (pixel\-perfect)
@ -448,13 +653,21 @@ Copy computer clipboard to device, then paste (inject PASTE keycode, Android >=
.B MOD+Shift+v
Inject computer clipboard text as a sequence of key events
.TP
.B MOD+k
Open keyboard settings on the device (for HID keyboard only)
.TP
.B MOD+i
Enable/disable FPS counter (print frames/second in logs)
.TP
.B Ctrl+click-and-move
Pinch-to-zoom from the center of the screen
Pinch-to-zoom and rotate from the center of the screen
.TP
.B Shift+click-and-move
Tilt (slide vertically with two fingers)
.TP
.B Drag & drop APK file
@ -471,6 +684,10 @@ Push file to device (see \fB\-\-push\-target\fR)
.B ADB
Path to adb.
.TP
.B ANDROID_SERIAL
Device serial to use if no selector (\fB-s\fR, \fB-d\fR, \fB-e\fR or \fB\-\-tcpip=\fIaddr\fR) is specified.
.TP
.B SCRCPY_ICON_PATH
Path to the program icon.
@ -492,23 +709,14 @@ for the Debian Project (and may be used by others).
.SH "REPORTING BUGS"
Report bugs to
.UR https://github.com/Genymobile/scrcpy/issues
.UE .
Report bugs to <https://github.com/Genymobile/scrcpy/issues>.
.SH COPYRIGHT
Copyright \(co 2018 Genymobile
.UR https://www.genymobile.com
Genymobile
.UE
Copyright \(co 2018\-2022
.MT rom@rom1v.com
Romain Vimont
.ME
Copyright \(co 2018 Genymobile <https://www.genymobile.com>
Copyright \(co 2018\-2024 Romain Vimont <rom@rom1v.com>
Licensed under the Apache License, Version 2.0.
.SH WWW
.UR https://github.com/Genymobile/scrcpy
.UE
<https://github.com/Genymobile/scrcpy>

@ -70,7 +70,7 @@ argv_to_string(const char *const *argv, char *buf, size_t bufsize) {
}
static void
show_adb_installation_msg() {
show_adb_installation_msg(void) {
#ifndef __WINDOWS__
static const struct {
const char *binary;
@ -218,8 +218,16 @@ sc_adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
const char *device_socket_name, unsigned flags) {
char local[4 + 5 + 1]; // tcp:PORT
char remote[108 + 14 + 1]; // localabstract:NAME
sprintf(local, "tcp:%" PRIu16, local_port);
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
int r = snprintf(local, sizeof(local), "tcp:%" PRIu16, local_port);
assert(r >= 0 && (size_t) r < sizeof(local));
r = snprintf(remote, sizeof(remote), "localabstract:%s",
device_socket_name);
if (r < 0 || (size_t) r >= sizeof(remote)) {
LOGE("Could not write socket name");
return false;
}
assert(serial);
const char *const argv[] =
@ -233,7 +241,9 @@ bool
sc_adb_forward_remove(struct sc_intr *intr, const char *serial,
uint16_t local_port, unsigned flags) {
char local[4 + 5 + 1]; // tcp:PORT
sprintf(local, "tcp:%" PRIu16, local_port);
int r = snprintf(local, sizeof(local), "tcp:%" PRIu16, local_port);
assert(r >= 0 && (size_t) r < sizeof(local));
(void) r;
assert(serial);
const char *const argv[] =
@ -249,8 +259,16 @@ sc_adb_reverse(struct sc_intr *intr, const char *serial,
unsigned flags) {
char local[4 + 5 + 1]; // tcp:PORT
char remote[108 + 14 + 1]; // localabstract:NAME
sprintf(local, "tcp:%" PRIu16, local_port);
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
int r = snprintf(local, sizeof(local), "tcp:%" PRIu16, local_port);
assert(r >= 0 && (size_t) r < sizeof(local));
r = snprintf(remote, sizeof(remote), "localabstract:%s",
device_socket_name);
if (r < 0 || (size_t) r >= sizeof(remote)) {
LOGE("Could not write socket name");
return false;
}
assert(serial);
const char *const argv[] =
SC_ADB_COMMAND("-s", serial, "reverse", remote, local);
@ -263,7 +281,12 @@ bool
sc_adb_reverse_remove(struct sc_intr *intr, const char *serial,
const char *device_socket_name, unsigned flags) {
char remote[108 + 14 + 1]; // localabstract:NAME
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
int r = snprintf(remote, sizeof(remote), "localabstract:%s",
device_socket_name);
if (r < 0 || (size_t) r >= sizeof(remote)) {
LOGE("Device socket name too long");
return false;
}
assert(serial);
const char *const argv[] =
@ -333,7 +356,9 @@ bool
sc_adb_tcpip(struct sc_intr *intr, const char *serial, uint16_t port,
unsigned flags) {
char port_string[5 + 1];
sprintf(port_string, "%" PRIu16, port);
int r = snprintf(port_string, sizeof(port_string), "%" PRIu16, port);
assert(r >= 0 && (size_t) r < sizeof(port_string));
(void) r;
assert(serial);
const char *const argv[] =
@ -401,6 +426,7 @@ sc_adb_list_devices(struct sc_intr *intr, unsigned flags,
#define BUFSIZE 65536
char *buf = malloc(BUFSIZE);
if (!buf) {
LOG_OOM();
return false;
}
@ -432,6 +458,7 @@ sc_adb_list_devices(struct sc_intr *intr, unsigned flags,
// in the buffer in a single pass
LOGW("Result of \"adb devices -l\" does not fit in 64Kb. "
"Please report an issue.");
free(buf);
return false;
}
@ -473,9 +500,12 @@ sc_adb_accept_device(const struct sc_adb_device *device,
}
return !strcmp(selector->serial, device->serial);
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:
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:
assert(!"Missing SC_ADB_DEVICE_SELECT_* handling");
break;
@ -509,8 +539,10 @@ sc_adb_devices_log(enum sc_log_level level, struct sc_adb_device *devices,
for (size_t i = 0; i < count; ++i) {
struct sc_adb_device *d = &devices[i];
const char *selection = d->selected ? "-->" : " ";
const char *type = sc_adb_is_serial_tcpip(d->serial) ? "(tcpip)"
: " (usb)";
bool is_usb =
sc_adb_device_get_type(d->serial) == SC_ADB_DEVICE_TYPE_USB;
const char *type = is_usb ? " (usb)"
: "(tcpip)";
LOG(level, " %s %s %-20s %16s %s",
selection, type, d->serial, d->state, d->model ? d->model : "");
}
@ -531,6 +563,8 @@ sc_adb_device_check_state(struct sc_adb_device *device,
LOGE("A popup should open on the device to request authorization.");
LOGE("Check the FAQ: "
"<https://github.com/Genymobile/scrcpy/blob/master/FAQ.md>");
} else {
LOGE("Device could not be connected (state=%s)", state);
}
return false;
@ -620,8 +654,8 @@ sc_adb_select_device(struct sc_intr *intr,
return false;
}
LOGD("ADB device found:");
sc_adb_devices_log(SC_LOG_LEVEL_DEBUG, vec.data, vec.size);
LOGI("ADB device found:");
sc_adb_devices_log(SC_LOG_LEVEL_INFO, vec.data, vec.size);
// Move devics into out_device (do not destroy device)
sc_adb_device_move(out_device, device);
@ -703,10 +737,5 @@ sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) {
// It is parsed as a NUL-terminated string
buf[r] = '\0';
return sc_adb_parse_device_ip_from_output(buf);
}
bool
sc_adb_is_serial_tcpip(const char *serial) {
return strchr(serial, ':');
return sc_adb_parse_device_ip(buf);
}

@ -114,13 +114,4 @@ sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop,
char *
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

@ -1,6 +1,7 @@
#include "adb_device.h"
#include <stdlib.h>
#include <string.h>
void
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);
}
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;
}

@ -15,6 +15,12 @@ struct sc_adb_device {
bool selected;
};
enum sc_adb_device_type {
SC_ADB_DEVICE_TYPE_USB,
SC_ADB_DEVICE_TYPE_TCPIP,
SC_ADB_DEVICE_TYPE_EMULATOR,
};
struct sc_vec_adb_devices SC_VECTOR(struct sc_adb_device);
void
@ -35,4 +41,10 @@ sc_adb_device_move(struct sc_adb_device *dst, struct sc_adb_device *src);
void
sc_adb_devices_destroy(struct sc_vec_adb_devices *devices);
/**
* Deduce the device type from the serial
*/
enum sc_adb_device_type
sc_adb_device_get_type(const char *serial);
#endif

@ -7,7 +7,7 @@
#include "util/log.h"
#include "util/str.h"
bool
static bool
sc_adb_parse_device(char *line, struct sc_adb_device *device) {
// One device line looks like:
// "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
@ -199,11 +199,12 @@ sc_adb_parse_device_ip_from_line(char *line) {
}
char *
sc_adb_parse_device_ip_from_output(char *str) {
sc_adb_parse_device_ip(char *str) {
size_t idx_line = 0;
while (str[idx_line] != '\0') {
char *line = &str[idx_line];
size_t len = strcspn(line, "\n");
bool is_last_line = line[len] == '\0';
// The same, but without any trailing '\r'
size_t line_len = sc_str_remove_trailing_cr(line, len);
@ -215,12 +216,12 @@ sc_adb_parse_device_ip_from_output(char *str) {
return ip;
}
idx_line += len;
if (str[idx_line] != '\0') {
// The next line starts after the '\n'
++idx_line;
if (is_last_line) {
break;
}
// The next line starts after the '\n'
idx_line += len + 1;
}
return NULL;

@ -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.
*/
char *
sc_adb_parse_device_ip_from_output(char *str);
sc_adb_parse_device_ip(char *str);
#endif

@ -7,8 +7,6 @@
#include "util/net_intr.h"
#include "util/process_intr.h"
#define SC_SOCKET_NAME "scrcpy"
static bool
listen_on_port(struct sc_intr *intr, sc_socket socket, uint16_t port) {
return net_listen_intr(intr, socket, IPV4_LOCALHOST, port, 1);
@ -17,10 +15,11 @@ listen_on_port(struct sc_intr *intr, sc_socket socket, uint16_t port) {
static bool
enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
struct sc_intr *intr, const char *serial,
const char *device_socket_name,
struct sc_port_range port_range) {
uint16_t port = port_range.first;
for (;;) {
if (!sc_adb_reverse(intr, serial, SC_SOCKET_NAME, port,
if (!sc_adb_reverse(intr, serial, device_socket_name, port,
SC_ADB_NO_STDOUT)) {
// the command itself failed, it will fail on any port
return false;
@ -52,7 +51,7 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
}
// failure, disable tunnel and try another port
if (!sc_adb_reverse_remove(intr, serial, SC_SOCKET_NAME,
if (!sc_adb_reverse_remove(intr, serial, device_socket_name,
SC_ADB_NO_STDOUT)) {
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
}
@ -78,12 +77,13 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
static bool
enable_tunnel_forward_any_port(struct sc_adb_tunnel *tunnel,
struct sc_intr *intr, const char *serial,
const char *device_socket_name,
struct sc_port_range port_range) {
tunnel->forward = true;
uint16_t port = port_range.first;
for (;;) {
if (sc_adb_forward(intr, serial, port, SC_SOCKET_NAME,
if (sc_adb_forward(intr, serial, port, device_socket_name,
SC_ADB_NO_STDOUT)) {
// success
tunnel->local_port = port;
@ -123,13 +123,14 @@ sc_adb_tunnel_init(struct sc_adb_tunnel *tunnel) {
bool
sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
const char *serial, struct sc_port_range port_range,
bool force_adb_forward) {
const char *serial, const char *device_socket_name,
struct sc_port_range port_range, bool force_adb_forward) {
assert(!tunnel->enabled);
if (!force_adb_forward) {
// Attempt to use "adb reverse"
if (enable_tunnel_reverse_any_port(tunnel, intr, serial, port_range)) {
if (enable_tunnel_reverse_any_port(tunnel, intr, serial,
device_socket_name, port_range)) {
return true;
}
@ -139,12 +140,13 @@ sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
LOGW("'adb reverse' failed, fallback to 'adb forward'");
}
return enable_tunnel_forward_any_port(tunnel, intr, serial, port_range);
return enable_tunnel_forward_any_port(tunnel, intr, serial,
device_socket_name, port_range);
}
bool
sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
const char *serial) {
const char *serial, const char *device_socket_name) {
assert(tunnel->enabled);
bool ret;
@ -152,7 +154,7 @@ sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
ret = sc_adb_forward_remove(intr, serial, tunnel->local_port,
SC_ADB_NO_STDOUT);
} else {
ret = sc_adb_reverse_remove(intr, serial, SC_SOCKET_NAME,
ret = sc_adb_reverse_remove(intr, serial, device_socket_name,
SC_ADB_NO_STDOUT);
assert(tunnel->server_socket != SC_SOCKET_NONE);

@ -34,14 +34,14 @@ sc_adb_tunnel_init(struct sc_adb_tunnel *tunnel);
*/
bool
sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
const char *serial, struct sc_port_range port_range,
bool force_adb_forward);
const char *serial, const char *device_socket_name,
struct sc_port_range port_range, bool force_adb_forward);
/**
* Close the tunnel
*/
bool
sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
const char *serial);
const char *serial, const char *device_socket_name);
#endif

@ -0,0 +1,487 @@
#include "audio_player.h"
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include "util/log.h"
#define SC_AUDIO_PLAYER_NDEBUG // comment to debug
/**
* Real-time audio player with configurable latency
*
* As input, the player regularly receives AVFrames of decoded audio samples.
* As output, an SDL callback regularly requests audio samples to be played.
* In the middle, an audio buffer stores the samples produced but not consumed
* yet.
*
* The goal of the player is to feed the audio output with a latency as low as
* possible while avoiding buffer underrun (i.e. not being able to provide
* samples when requested).
*
* The player aims to feed the audio output with as little latency as possible
* while avoiding buffer underrun. To achieve this, it attempts to maintain the
* average buffering (the number of samples present in the buffer) around a
* target value. If this target buffering is too low, then buffer underrun will
* occur frequently. If it is too high, then latency will become unacceptable.
* This target value is configured using the scrcpy option --audio-buffer.
*
* The player cannot adjust the sample input rate (it receives samples produced
* in real-time) or the sample output rate (it must provide samples as
* requested by the audio output callback). Therefore, it may only apply
* compensation by resampling (converting _m_ input samples to _n_ output
* samples).
*
* The compensation itself is applied by libswresample (FFmpeg). It is
* configured using swr_set_compensation(). An important work for the player
* is to estimate the compensation value regularly and apply it.
*
* The estimated buffering level is the result of averaging the "natural"
* buffering (samples are produced and consumed by blocks, so it must be
* smoothed), and making instant adjustments resulting of its own actions
* (explicit compensation and silence insertion on underflow), which are not
* smoothed.
*
* Buffer underflow events can occur when packets arrive too late. In that case,
* the player inserts silence. Once the packets finally arrive (late), one
* strategy could be to drop the samples that were replaced by silence, in
* order to keep a minimal latency. However, dropping samples in case of buffer
* underflow is inadvisable, as it would temporarily increase the underflow
* even more and cause very noticeable audio glitches.
*
* Therefore, the player doesn't drop any sample on underflow. The compensation
* mechanism will absorb the delay introduced by the inserted silence.
*/
/** Downcast frame_sink to sc_audio_player */
#define DOWNCAST(SINK) container_of(SINK, struct sc_audio_player, frame_sink)
#define SC_AV_SAMPLE_FMT AV_SAMPLE_FMT_FLT
#define SC_SDL_SAMPLE_FMT AUDIO_F32
#define TO_BYTES(SAMPLES) sc_audiobuf_to_bytes(&ap->buf, (SAMPLES))
#define TO_SAMPLES(BYTES) sc_audiobuf_to_samples(&ap->buf, (BYTES))
static void SDLCALL
sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
struct sc_audio_player *ap = userdata;
// This callback is called with the lock used by SDL_LockAudioDevice()
assert(len_int > 0);
size_t len = len_int;
uint32_t count = TO_SAMPLES(len);
#ifndef SC_AUDIO_PLAYER_NDEBUG
LOGD("[Audio] SDL callback requests %" PRIu32 " samples", count);
#endif
bool played = atomic_load_explicit(&ap->played, memory_order_relaxed);
if (!played) {
uint32_t buffered_samples = sc_audiobuf_can_read(&ap->buf);
// Wait until the buffer is filled up to at least target_buffering
// before playing
if (buffered_samples < ap->target_buffering) {
LOGV("[Audio] Inserting initial buffering silence: %" PRIu32
" samples", count);
// Delay playback starting to reach the target buffering. Fill the
// whole buffer with silence (len is small compared to the
// arbitrary margin value).
memset(stream, 0, len);
return;
}
}
uint32_t read = sc_audiobuf_read(&ap->buf, stream, count);
if (read < count) {
uint32_t silence = count - read;
// Insert silence. In theory, the inserted silent samples replace the
// missing real samples, which will arrive later, so they should be
// dropped to keep the latency minimal. However, this would cause very
// audible glitches, so let the clock compensation restore the target
// latency.
LOGD("[Audio] Buffer underflow, inserting silence: %" PRIu32 " samples",
silence);
memset(stream + TO_BYTES(read), 0, TO_BYTES(silence));
bool received = atomic_load_explicit(&ap->received,
memory_order_relaxed);
if (received) {
// Inserting additional samples immediately increases buffering
atomic_fetch_add_explicit(&ap->underflow, silence,
memory_order_relaxed);
}
}
atomic_store_explicit(&ap->played, true, memory_order_relaxed);
}
static uint8_t *
sc_audio_player_get_swr_buf(struct sc_audio_player *ap, uint32_t min_samples) {
size_t min_buf_size = TO_BYTES(min_samples);
if (min_buf_size > ap->swr_buf_alloc_size) {
size_t new_size = min_buf_size + 4096;
uint8_t *buf = realloc(ap->swr_buf, new_size);
if (!buf) {
LOG_OOM();
// Could not realloc to the requested size
return NULL;
}
ap->swr_buf = buf;
ap->swr_buf_alloc_size = new_size;
}
return ap->swr_buf;
}
static bool
sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
const AVFrame *frame) {
struct sc_audio_player *ap = DOWNCAST(sink);
SwrContext *swr_ctx = ap->swr_ctx;
int64_t swr_delay = swr_get_delay(swr_ctx, ap->sample_rate);
// No need to av_rescale_rnd(), input and output sample rates are the same.
// Add more space (256) for clock compensation.
int dst_nb_samples = swr_delay + frame->nb_samples + 256;
uint8_t *swr_buf = sc_audio_player_get_swr_buf(ap, dst_nb_samples);
if (!swr_buf) {
return false;
}
int ret = swr_convert(swr_ctx, &swr_buf, dst_nb_samples,
(const uint8_t **) frame->data, frame->nb_samples);
if (ret < 0) {
LOGE("Resampling failed: %d", ret);
return false;
}
// swr_convert() returns the number of samples which would have been
// written if the buffer was big enough.
uint32_t samples = MIN(ret, dst_nb_samples);
#ifndef SC_AUDIO_PLAYER_NDEBUG
LOGD("[Audio] %" PRIu32 " samples written to buffer", samples);
#endif
uint32_t cap = sc_audiobuf_capacity(&ap->buf);
if (samples > cap) {
// Very very unlikely: a single resampled frame should never
// exceed the audio buffer size (or something is very wrong).
// Ignore the first bytes in swr_buf to avoid memory corruption anyway.
swr_buf += TO_BYTES(samples - cap);
samples = cap;
}
uint32_t skipped_samples = 0;
uint32_t written = sc_audiobuf_write(&ap->buf, swr_buf, samples);
if (written < samples) {
uint32_t remaining = samples - written;
// All samples that could be written without locking have been written,
// now we need to lock to drop/consume old samples
SDL_LockAudioDevice(ap->device);
// Retry with the lock
written += sc_audiobuf_write(&ap->buf,
swr_buf + TO_BYTES(written),
remaining);
if (written < samples) {
remaining = samples - written;
// Still insufficient, drop old samples to make space
skipped_samples = sc_audiobuf_read(&ap->buf, NULL, remaining);
assert(skipped_samples == remaining);
// Now there is enough space
uint32_t w = sc_audiobuf_write(&ap->buf,
swr_buf + TO_BYTES(written),
remaining);
assert(w == remaining);
(void) w;
}
SDL_UnlockAudioDevice(ap->device);
}
uint32_t underflow = 0;
uint32_t max_buffered_samples;
bool played = atomic_load_explicit(&ap->played, memory_order_relaxed);
if (played) {
underflow = atomic_exchange_explicit(&ap->underflow, 0,
memory_order_relaxed);
max_buffered_samples = ap->target_buffering
+ 12 * ap->output_buffer
+ ap->target_buffering / 10;
} else {
// SDL playback not started yet, do not accumulate more than
// max_initial_buffering samples, this would cause unnecessary delay
// (and glitches to compensate) on start.
max_buffered_samples = ap->target_buffering + 2 * ap->output_buffer;
}
uint32_t can_read = sc_audiobuf_can_read(&ap->buf);
if (can_read > max_buffered_samples) {
uint32_t skip_samples = 0;
SDL_LockAudioDevice(ap->device);
can_read = sc_audiobuf_can_read(&ap->buf);
if (can_read > max_buffered_samples) {
skip_samples = can_read - max_buffered_samples;
uint32_t r = sc_audiobuf_read(&ap->buf, NULL, skip_samples);
assert(r == skip_samples);
(void) r;
skipped_samples += skip_samples;
}
SDL_UnlockAudioDevice(ap->device);
if (skip_samples) {
if (played) {
LOGD("[Audio] Buffering threshold exceeded, skipping %" PRIu32
" samples", skip_samples);
#ifndef SC_AUDIO_PLAYER_NDEBUG
} else {
LOGD("[Audio] Playback not started, skipping %" PRIu32
" samples", skip_samples);
#endif
}
}
}
atomic_store_explicit(&ap->received, true, memory_order_relaxed);
if (!played) {
// Nothing more to do
return true;
}
// Number of samples added (or removed, if negative) for compensation
int32_t instant_compensation = (int32_t) written - frame->nb_samples;
// Inserting silence instantly increases buffering
int32_t inserted_silence = (int32_t) underflow;
// Dropping input samples instantly decreases buffering
int32_t dropped = (int32_t) skipped_samples;
// The compensation must apply instantly, it must not be smoothed
ap->avg_buffering.avg += instant_compensation + inserted_silence - dropped;
if (ap->avg_buffering.avg < 0) {
// Since dropping samples instantly reduces buffering, the difference
// is applied immediately to the average value, assuming that the delay
// between the producer and the consumer will be caught up.
//
// However, when this assumption is not valid, the average buffering
// may decrease indefinitely. Prevent it to become negative to limit
// the consequences.
ap->avg_buffering.avg = 0;
}
// However, the buffering level must be smoothed
sc_average_push(&ap->avg_buffering, can_read);
#ifndef SC_AUDIO_PLAYER_NDEBUG
LOGD("[Audio] can_read=%" PRIu32 " avg_buffering=%f",
can_read, sc_average_get(&ap->avg_buffering));
#endif
ap->samples_since_resync += written;
if (ap->samples_since_resync >= ap->sample_rate) {
// Recompute compensation every second
ap->samples_since_resync = 0;
float avg = sc_average_get(&ap->avg_buffering);
int diff = ap->target_buffering - avg;
// Enable compensation when the difference exceeds +/- 4ms.
// Disable compensation when the difference is lower than +/- 1ms.
int threshold = ap->compensation != 0
? ap->sample_rate / 1000 /* 1ms */
: ap->sample_rate * 4 / 1000; /* 4ms */
if (abs(diff) < threshold) {
// Do not compensate for small values, the error is just noise
diff = 0;
} else if (diff < 0 && can_read < ap->target_buffering) {
// Do not accelerate if the instant buffering level is below the
// target, this would increase underflow
diff = 0;
}
// Compensate the diff over 4 seconds (but will be recomputed after 1
// second)
int distance = 4 * ap->sample_rate;
// Limit compensation rate to 2%
int abs_max_diff = distance / 50;
diff = CLAMP(diff, -abs_max_diff, abs_max_diff);
LOGV("[Audio] Buffering: target=%" PRIu32 " avg=%f cur=%" PRIu32
" compensation=%d", ap->target_buffering, avg, can_read, diff);
if (diff != ap->compensation) {
int ret = swr_set_compensation(swr_ctx, diff, distance);
if (ret < 0) {
LOGW("Resampling compensation failed: %d", ret);
// not fatal
} else {
ap->compensation = diff;
}
}
}
return true;
}
static bool
sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
const AVCodecContext *ctx) {
struct sc_audio_player *ap = DOWNCAST(sink);
#ifdef SCRCPY_LAVU_HAS_CHLAYOUT
assert(ctx->ch_layout.nb_channels > 0);
unsigned nb_channels = ctx->ch_layout.nb_channels;
#else
int tmp = av_get_channel_layout_nb_channels(ctx->channel_layout);
assert(tmp > 0);
unsigned nb_channels = tmp;
#endif
assert(ctx->sample_rate > 0);
assert(!av_sample_fmt_is_planar(SC_AV_SAMPLE_FMT));
int out_bytes_per_sample = av_get_bytes_per_sample(SC_AV_SAMPLE_FMT);
assert(out_bytes_per_sample > 0);
ap->sample_rate = ctx->sample_rate;
ap->nb_channels = nb_channels;
ap->out_bytes_per_sample = out_bytes_per_sample;
ap->target_buffering = ap->target_buffering_delay * ap->sample_rate
/ SC_TICK_FREQ;
uint64_t aout_samples = ap->output_buffer_duration * ap->sample_rate
/ SC_TICK_FREQ;
assert(aout_samples <= 0xFFFF);
ap->output_buffer = (uint16_t) aout_samples;
SDL_AudioSpec desired = {
.freq = ctx->sample_rate,
.format = SC_SDL_SAMPLE_FMT,
.channels = nb_channels,
.samples = aout_samples,
.callback = sc_audio_player_sdl_callback,
.userdata = ap,
};
SDL_AudioSpec obtained;
ap->device = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0);
if (!ap->device) {
LOGE("Could not open audio device: %s", SDL_GetError());
return false;
}
SwrContext *swr_ctx = swr_alloc();
if (!swr_ctx) {
LOG_OOM();
goto error_close_audio_device;
}
ap->swr_ctx = swr_ctx;
#ifdef SCRCPY_LAVU_HAS_CHLAYOUT
av_opt_set_chlayout(swr_ctx, "in_chlayout", &ctx->ch_layout, 0);
av_opt_set_chlayout(swr_ctx, "out_chlayout", &ctx->ch_layout, 0);
#else
av_opt_set_channel_layout(swr_ctx, "in_channel_layout",
ctx->channel_layout, 0);
av_opt_set_channel_layout(swr_ctx, "out_channel_layout",
ctx->channel_layout, 0);
#endif
av_opt_set_int(swr_ctx, "in_sample_rate", ctx->sample_rate, 0);
av_opt_set_int(swr_ctx, "out_sample_rate", ctx->sample_rate, 0);
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", ctx->sample_fmt, 0);
av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", SC_AV_SAMPLE_FMT, 0);
int ret = swr_init(swr_ctx);
if (ret) {
LOGE("Failed to initialize the resampling context");
goto error_free_swr_ctx;
}
// Use a ring-buffer of the target buffering size plus 1 second between the
// producer and the consumer. It's too big on purpose, to guarantee that
// the producer and the consumer will be able to access it in parallel
// without locking.
uint32_t audiobuf_samples = ap->target_buffering + ap->sample_rate;
size_t sample_size = ap->nb_channels * ap->out_bytes_per_sample;
bool ok = sc_audiobuf_init(&ap->buf, sample_size, audiobuf_samples);
if (!ok) {
goto error_free_swr_ctx;
}
size_t initial_swr_buf_size = TO_BYTES(4096);
ap->swr_buf = malloc(initial_swr_buf_size);
if (!ap->swr_buf) {
LOG_OOM();
goto error_destroy_audiobuf;
}
ap->swr_buf_alloc_size = initial_swr_buf_size;
// Samples are produced and consumed by blocks, so the buffering must be
// smoothed to get a relatively stable value.
sc_average_init(&ap->avg_buffering, 128);
ap->samples_since_resync = 0;
ap->received = false;
atomic_init(&ap->played, false);
atomic_init(&ap->received, false);
atomic_init(&ap->underflow, 0);
ap->compensation = 0;
// The thread calling open() is the thread calling push(), which fills the
// audio buffer consumed by the SDL audio thread.
ok = sc_thread_set_priority(SC_THREAD_PRIORITY_TIME_CRITICAL);
if (!ok) {
ok = sc_thread_set_priority(SC_THREAD_PRIORITY_HIGH);
(void) ok; // We don't care if it worked, at least we tried
}
SDL_PauseAudioDevice(ap->device, 0);
return true;
error_destroy_audiobuf:
sc_audiobuf_destroy(&ap->buf);
error_free_swr_ctx:
swr_free(&ap->swr_ctx);
error_close_audio_device:
SDL_CloseAudioDevice(ap->device);
return false;
}
static void
sc_audio_player_frame_sink_close(struct sc_frame_sink *sink) {
struct sc_audio_player *ap = DOWNCAST(sink);
assert(ap->device);
SDL_PauseAudioDevice(ap->device, 1);
SDL_CloseAudioDevice(ap->device);
free(ap->swr_buf);
sc_audiobuf_destroy(&ap->buf);
swr_free(&ap->swr_ctx);
}
void
sc_audio_player_init(struct sc_audio_player *ap, sc_tick target_buffering,
sc_tick output_buffer_duration) {
ap->target_buffering_delay = target_buffering;
ap->output_buffer_duration = output_buffer_duration;
static const struct sc_frame_sink_ops ops = {
.open = sc_audio_player_frame_sink_open,
.close = sc_audio_player_frame_sink_close,
.push = sc_audio_player_frame_sink_push,
};
ap->frame_sink.ops = &ops;
}

@ -0,0 +1,84 @@
#ifndef SC_AUDIO_PLAYER_H
#define SC_AUDIO_PLAYER_H
#include "common.h"
#include <stdatomic.h>
#include <stdbool.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#include <SDL2/SDL.h>
#include "trait/frame_sink.h"
#include "util/audiobuf.h"
#include "util/average.h"
#include "util/thread.h"
#include "util/tick.h"
struct sc_audio_player {
struct sc_frame_sink frame_sink;
SDL_AudioDeviceID device;
// The target buffering between the producer and the consumer. This value
// is directly use for compensation.
// Since audio capture and/or encoding on the device typically produce
// blocks of 960 samples (20ms) or 1024 samples (~21.3ms), this target
// value should be higher.
sc_tick target_buffering_delay;
uint32_t target_buffering; // in samples
// SDL audio output buffer size.
sc_tick output_buffer_duration;
uint16_t output_buffer;
// Audio buffer to communicate between the receiver and the SDL audio
// callback
struct sc_audiobuf buf;
// Resampler (only used from the receiver thread)
struct SwrContext *swr_ctx;
// The sample rate is the same for input and output
unsigned sample_rate;
// The number of channels is the same for input and output
unsigned nb_channels;
// The number of bytes per sample for a single channel
size_t out_bytes_per_sample;
// Target buffer for resampling (only used by the receiver thread)
uint8_t *swr_buf;
size_t swr_buf_alloc_size;
// Number of buffered samples (may be negative on underflow) (only used by
// the receiver thread)
struct sc_average avg_buffering;
// Count the number of samples to trigger a compensation update regularly
// (only used by the receiver thread)
uint32_t samples_since_resync;
// Number of silence samples inserted since the last received packet
atomic_uint_least32_t underflow;
// Current applied compensation value (only used by the receiver thread)
int compensation;
// Set to true the first time a sample is received
atomic_bool received;
// Set to true the first time the SDL callback is called
atomic_bool played;
const struct sc_audio_player_callbacks *cbs;
void *cbs_userdata;
};
struct sc_audio_player_callbacks {
void (*on_ended)(struct sc_audio_player *ap, bool success, void *userdata);
};
void
sc_audio_player_init(struct sc_audio_player *ap, sc_tick target_buffering,
sc_tick audio_output_buffer);
#endif

File diff suppressed because it is too large Load Diff

@ -7,10 +7,17 @@
#include "options.h"
enum sc_pause_on_exit {
SC_PAUSE_ON_EXIT_TRUE,
SC_PAUSE_ON_EXIT_FALSE,
SC_PAUSE_ON_EXIT_IF_ERROR,
};
struct scrcpy_cli_args {
struct scrcpy_options opts;
bool help;
bool version;
enum sc_pause_on_exit pause_on_exit;
};
void

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

@ -3,13 +3,8 @@
#include "common.h"
#include <assert.h>
#include "util/tick.h"
#define SC_CLOCK_RANGE 32
static_assert(!(SC_CLOCK_RANGE & 1), "SC_CLOCK_RANGE must be even");
struct sc_clock_point {
sc_tick system;
sc_tick stream;
@ -21,40 +16,18 @@ struct sc_clock_point {
*
* f(stream) = slope * stream + offset
*
* To that end, it stores the SC_CLOCK_RANGE last clock points (the timestamps
* of a frame expressed both in stream time and system time) in a circular
* array.
* Theoretically, the slope encodes the drift between the device clock and the
* computer clock. It is expected to be very close to 1.
*
* To estimate the slope, it splits the last SC_CLOCK_RANGE points into two
* sets of SC_CLOCK_RANGE/2 points, and computes their centroid ("average
* point"). The slope of the estimated affine function is that of the line
* passing through these two points.
* Since the clock is used to estimate very close points in the future (which
* are reestimated on every clock update, see delay_buffer), the error caused
* by clock drift is totally negligible, so it is better to assume that the
* slope is 1 than to estimate it (the estimation error would be larger).
*
* To estimate the offset, it computes the centroid of all the SC_CLOCK_RANGE
* points. The resulting affine function passes by this centroid.
*
* With a circular array, the rolling sums (and average) are quick to compute.
* In practice, the estimation is stable and the evolution is smooth.
* Therefore, only the offset is estimated.
*/
struct sc_clock {
// Circular array
struct sc_clock_point points[SC_CLOCK_RANGE];
// Number of points in the array (count <= SC_CLOCK_RANGE)
unsigned count;
// Index of the next point to write
unsigned head;
// Sum of the first count/2 points
struct sc_clock_point left_sum;
// Sum of the last (count+1)/2 points
struct sc_clock_point right_sum;
// Estimated slope and offset
// (computed on sc_clock_update(), used by sc_clock_to_system_time())
double slope;
unsigned range;
sc_tick offset;
};

@ -1,12 +1,12 @@
#ifndef COMMON_H
#define COMMON_H
#ifndef SC_COMMON_H
#define SC_COMMON_H
#include "config.h"
#include "compat.h"
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
#define MIN(X,Y) (X) < (Y) ? (X) : (Y)
#define MAX(X,Y) (X) > (Y) ? (X) : (Y)
#define MIN(X,Y) ((X) < (Y) ? (X) : (Y))
#define MAX(X,Y) ((X) > (Y) ? (X) : (Y))
#define CLAMP(V,X,Y) MIN( MAX((V),(X)), (Y) )
#define container_of(ptr, type, member) \

@ -3,6 +3,9 @@
#include "config.h"
#include <assert.h>
#ifndef HAVE_REALLOCARRAY
# include <errno.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
@ -51,3 +54,57 @@ int vasprintf(char **strp, const char *fmt, va_list ap) {
return len;
}
#endif
#if !defined(HAVE_NRAND48) || !defined(HAVE_JRAND48)
#define SC_RAND48_MASK UINT64_C(0xFFFFFFFFFFFF) // 48 bits
#define SC_RAND48_A UINT64_C(0x5DEECE66D)
#define SC_RAND48_C 0xB
static inline uint64_t rand_iter48(uint64_t x) {
assert((x & ~SC_RAND48_MASK) == 0);
return (x * SC_RAND48_A + SC_RAND48_C) & SC_RAND48_MASK;
}
static uint64_t rand_iter48_xsubi(unsigned short xsubi[3]) {
uint64_t x = ((uint64_t) xsubi[0] << 32)
| ((uint64_t) xsubi[1] << 16)
| xsubi[2];
x = rand_iter48(x);
xsubi[0] = (x >> 32) & 0XFFFF;
xsubi[1] = (x >> 16) & 0XFFFF;
xsubi[2] = x & 0XFFFF;
return x;
}
#ifndef HAVE_NRAND48
long nrand48(unsigned short xsubi[3]) {
// range [0, 2^31)
return rand_iter48_xsubi(xsubi) >> 17;
}
#endif
#ifndef HAVE_JRAND48
long jrand48(unsigned short xsubi[3]) {
// range [-2^31, 2^31)
union {
uint32_t u;
int32_t i;
} v;
v.u = rand_iter48_xsubi(xsubi) >> 16;
return v.i;
}
#endif
#endif
#ifndef HAVE_REALLOCARRAY
void *reallocarray(void *ptr, size_t nmemb, size_t size) {
size_t bytes;
if (__builtin_mul_overflow(nmemb, size, &bytes)) {
errno = ENOMEM;
return NULL;
}
return realloc(ptr, bytes);
}
#endif

@ -1,9 +1,11 @@
#ifndef COMPAT_H
#define COMPAT_H
#ifndef SC_COMPAT_H
#define SC_COMPAT_H
#include "config.h"
#include <libavcodec/version.h>
#include <libavformat/version.h>
#include <libavutil/version.h>
#include <SDL2/SDL_version.h>
#ifndef __WIN32
@ -25,6 +27,12 @@
# define SCRCPY_LAVF_REQUIRES_REGISTER_ALL
#endif
// Not documented in ffmpeg/doc/APIchanges, but AV_CODEC_ID_AV1 has been added
// by FFmpeg commit d42809f9835a4e9e5c7c63210abb09ad0ef19cfb (included in tag
// n3.3).
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 89, 100)
# define SCRCPY_LAVC_HAS_AV1
#endif
// In ffmpeg/doc/APIchanges:
// 2018-01-28 - ea3672b7d6 - lavf 58.7.100 - avformat.h
@ -37,6 +45,22 @@
# define SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL
#endif
// Not documented in ffmpeg/doc/APIchanges, but the channel_layout API
// has been replaced by chlayout in FFmpeg commit
// f423497b455da06c1337846902c770028760e094.
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 23, 100)
# define SCRCPY_LAVU_HAS_CHLAYOUT
#endif
// In ffmpeg/doc/APIchanges:
// 2023-10-06 - 5432d2aacad - lavc 60.15.100 - avformat.h
// Deprecate AVFormatContext.{nb_,}side_data, av_stream_add_side_data(),
// av_stream_new_side_data(), and av_stream_get_side_data(). Side data fields
// from AVFormatContext.codecpar should be used from now on.
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(60, 15, 100)
# define SCRCPY_LAVC_HAS_CODECPAR_CODEC_SIDEDATA
#endif
#if SDL_VERSION_ATLEAST(2, 0, 6)
// <https://github.com/libsdl-org/SDL/commit/d7a318de563125e5bb465b1000d6bc9576fbc6fc>
# define SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
@ -47,6 +71,10 @@
# define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
#endif
#if SDL_VERSION_ATLEAST(2, 0, 16)
# define SCRCPY_SDL_HAS_THREAD_PRIORITY_TIME_CRITICAL
#endif
#ifndef HAVE_STRDUP
char *strdup(const char *s);
#endif
@ -59,4 +87,16 @@ int asprintf(char **strp, const char *fmt, ...);
int vasprintf(char **strp, const char *fmt, va_list ap);
#endif
#ifndef HAVE_NRAND48
long nrand48(unsigned short xsubi[3]);
#endif
#ifndef HAVE_JRAND48
long jrand48(unsigned short xsubi[3]);
#endif
#ifndef HAVE_REALLOCARRAY
void *reallocarray(void *ptr, size_t nmemb, size_t size);
#endif
#endif

@ -5,7 +5,7 @@
#include <stdlib.h>
#include <string.h>
#include "util/buffer_util.h"
#include "util/binary.h"
#include "util/log.h"
#include "util/str.h"
@ -37,7 +37,7 @@ static const char *const android_motionevent_action_labels[] = {
"move",
"cancel",
"outside",
"ponter-down",
"pointer-down",
"pointer-up",
"hover-move",
"scroll",
@ -61,6 +61,22 @@ static const char *const copy_key_labels[] = {
"cut",
};
static inline const char *
get_well_known_pointer_id_name(uint64_t pointer_id) {
switch (pointer_id) {
case POINTER_ID_MOUSE:
return "mouse";
case POINTER_ID_GENERIC_FINGER:
return "finger";
case POINTER_ID_VIRTUAL_MOUSE:
return "vmouse";
case POINTER_ID_VIRTUAL_FINGER:
return "vfinger";
default:
return NULL;
}
}
static void
write_position(uint8_t *buf, const struct sc_position *position) {
sc_write32be(&buf[0], position->point.x);
@ -71,25 +87,15 @@ write_position(uint8_t *buf, const struct sc_position *position) {
// write length (4 bytes) + string (non null-terminated)
static size_t
write_string(const char *utf8, size_t max_len, unsigned char *buf) {
write_string(const char *utf8, size_t max_len, uint8_t *buf) {
size_t len = sc_str_utf8_truncation_index(utf8, max_len);
sc_write32be(buf, len);
memcpy(&buf[4], utf8, 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
sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
buf[0] = msg->type;
switch (msg->type) {
case SC_CONTROL_MSG_TYPE_INJECT_KEYCODE:
@ -109,18 +115,21 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
sc_write64be(&buf[2], msg->inject_touch_event.pointer_id);
write_position(&buf[10], &msg->inject_touch_event.position);
uint16_t pressure =
to_fixed_point_16(msg->inject_touch_event.pressure);
sc_float_to_u16fp(msg->inject_touch_event.pressure);
sc_write16be(&buf[22], pressure);
sc_write32be(&buf[24], msg->inject_touch_event.buttons);
return 28;
sc_write32be(&buf[24], msg->inject_touch_event.action_button);
sc_write32be(&buf[28], msg->inject_touch_event.buttons);
return 32;
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
write_position(&buf[1], &msg->inject_scroll_event.position);
sc_write32be(&buf[13],
(uint32_t) msg->inject_scroll_event.hscroll);
sc_write32be(&buf[17],
(uint32_t) msg->inject_scroll_event.vscroll);
sc_write32be(&buf[21], msg->inject_scroll_event.buttons);
return 25;
int16_t hscroll =
sc_float_to_i16fp(msg->inject_scroll_event.hscroll);
int16_t vscroll =
sc_float_to_i16fp(msg->inject_scroll_event.vscroll);
sc_write16be(&buf[13], (uint16_t) hscroll);
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:
buf[1] = msg->inject_keycode.action;
return 2;
@ -137,10 +146,22 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
buf[1] = msg->set_screen_power_mode.mode;
return 2;
case SC_CONTROL_MSG_TYPE_UHID_CREATE:
sc_write16be(&buf[1], msg->uhid_create.id);
sc_write16be(&buf[3], msg->uhid_create.report_desc_size);
memcpy(&buf[5], msg->uhid_create.report_desc,
msg->uhid_create.report_desc_size);
return 5 + msg->uhid_create.report_desc_size;
case SC_CONTROL_MSG_TYPE_UHID_INPUT:
sc_write16be(&buf[1], msg->uhid_input.id);
sc_write16be(&buf[3], msg->uhid_input.size);
memcpy(&buf[5], msg->uhid_input.data, msg->uhid_input.size);
return 5 + msg->uhid_input.size;
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
// no additional data
return 1;
default:
@ -167,32 +188,36 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
int action = msg->inject_touch_event.action
& AMOTION_EVENT_ACTION_MASK;
uint64_t id = msg->inject_touch_event.pointer_id;
if (id == POINTER_ID_MOUSE || id == POINTER_ID_VIRTUAL_FINGER) {
const char *pointer_name = get_well_known_pointer_id_name(id);
if (pointer_name) {
// string pointer id
LOG_CMSG("touch [id=%s] %-4s position=%" PRIi32 ",%" PRIi32
" pressure=%g buttons=%06lx",
id == POINTER_ID_MOUSE ? "mouse" : "vfinger",
" pressure=%f action_button=%06lx buttons=%06lx",
pointer_name,
MOTIONEVENT_ACTION_LABEL(action),
msg->inject_touch_event.position.point.x,
msg->inject_touch_event.position.point.y,
msg->inject_touch_event.pressure,
(long) msg->inject_touch_event.action_button,
(long) msg->inject_touch_event.buttons);
} else {
// numeric pointer id
LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%"
PRIi32 " pressure=%g buttons=%06lx",
PRIi32 " pressure=%f action_button=%06lx"
" buttons=%06lx",
id,
MOTIONEVENT_ACTION_LABEL(action),
msg->inject_touch_event.position.point.x,
msg->inject_touch_event.position.point.y,
msg->inject_touch_event.pressure,
(long) msg->inject_touch_event.action_button,
(long) msg->inject_touch_event.buttons);
}
break;
}
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%" PRIi32
" vscroll=%" PRIi32 " buttons=%06lx",
LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%f"
" vscroll=%f buttons=%06lx",
msg->inject_scroll_event.position.point.x,
msg->inject_scroll_event.position.point.y,
msg->inject_scroll_event.hscroll,
@ -229,6 +254,26 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
LOG_CMSG("rotate device");
break;
case SC_CONTROL_MSG_TYPE_UHID_CREATE:
LOG_CMSG("UHID create [%" PRIu16 "] report_desc_size=%" PRIu16,
msg->uhid_create.id, msg->uhid_create.report_desc_size);
break;
case SC_CONTROL_MSG_TYPE_UHID_INPUT: {
char *hex = sc_str_to_hex_string(msg->uhid_input.data,
msg->uhid_input.size);
if (hex) {
LOG_CMSG("UHID input [%" PRIu16 "] %s",
msg->uhid_input.id, hex);
free(hex);
} else {
LOG_CMSG("UHID input [%" PRIu16 "] size=%" PRIu16,
msg->uhid_input.id, msg->uhid_input.size);
}
break;
}
case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
LOG_CMSG("open hard keyboard settings");
break;
default:
LOG_CMSG("unknown type: %u", (unsigned) msg->type);
break;

@ -1,5 +1,5 @@
#ifndef CONTROLMSG_H
#define CONTROLMSG_H
#ifndef SC_CONTROLMSG_H
#define SC_CONTROLMSG_H
#include "common.h"
@ -10,6 +10,7 @@
#include "android/input.h"
#include "android/keycodes.h"
#include "coords.h"
#include "hid/hid_event.h"
#define SC_CONTROL_MSG_MAX_SIZE (1 << 18) // 256k
@ -18,7 +19,11 @@
#define SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (SC_CONTROL_MSG_MAX_SIZE - 14)
#define POINTER_ID_MOUSE UINT64_C(-1)
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2)
#define POINTER_ID_GENERIC_FINGER UINT64_C(-2)
// Used for injecting an additional virtual pointer for pinch-to-zoom
#define POINTER_ID_VIRTUAL_MOUSE UINT64_C(-3)
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-4)
enum sc_control_msg_type {
SC_CONTROL_MSG_TYPE_INJECT_KEYCODE,
@ -33,6 +38,9 @@ enum sc_control_msg_type {
SC_CONTROL_MSG_TYPE_SET_CLIPBOARD,
SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
SC_CONTROL_MSG_TYPE_ROTATE_DEVICE,
SC_CONTROL_MSG_TYPE_UHID_CREATE,
SC_CONTROL_MSG_TYPE_UHID_INPUT,
SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
};
enum sc_screen_power_mode {
@ -61,6 +69,7 @@ struct sc_control_msg {
} inject_text;
struct {
enum android_motionevent_action action;
enum android_motionevent_buttons action_button;
enum android_motionevent_buttons buttons;
uint64_t pointer_id;
struct sc_position position;
@ -68,8 +77,8 @@ struct sc_control_msg {
} inject_touch_event;
struct {
struct sc_position position;
int32_t hscroll;
int32_t vscroll;
float hscroll;
float vscroll;
enum android_motionevent_buttons buttons;
} inject_scroll_event;
struct {
@ -87,13 +96,23 @@ struct sc_control_msg {
struct {
enum sc_screen_power_mode mode;
} set_screen_power_mode;
struct {
uint16_t id;
uint16_t report_desc_size;
const uint8_t *report_desc; // pointer to static data
} uhid_create;
struct {
uint16_t id;
uint16_t size;
uint8_t data[SC_HID_MAX_SIZE];
} uhid_input;
};
};
// buf size must be at least CONTROL_MSG_MAX_SIZE
// return the number of bytes written
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, uint8_t *buf);
void
sc_control_msg_log(const struct sc_control_msg *msg);

@ -4,26 +4,35 @@
#include "util/log.h"
#define SC_CONTROL_MSG_QUEUE_MAX 64
bool
sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
struct sc_acksync *acksync) {
cbuf_init(&controller->queue);
sc_controller_init(struct sc_controller *controller, sc_socket control_socket) {
sc_vecdeque_init(&controller->queue);
bool ok = receiver_init(&controller->receiver, control_socket, acksync);
bool ok = sc_vecdeque_reserve(&controller->queue, SC_CONTROL_MSG_QUEUE_MAX);
if (!ok) {
return false;
}
ok = sc_receiver_init(&controller->receiver, control_socket);
if (!ok) {
sc_vecdeque_destroy(&controller->queue);
return false;
}
ok = sc_mutex_init(&controller->mutex);
if (!ok) {
receiver_destroy(&controller->receiver);
sc_receiver_destroy(&controller->receiver);
sc_vecdeque_destroy(&controller->queue);
return false;
}
ok = sc_cond_init(&controller->msg_cond);
if (!ok) {
receiver_destroy(&controller->receiver);
sc_receiver_destroy(&controller->receiver);
sc_mutex_destroy(&controller->mutex);
sc_vecdeque_destroy(&controller->queue);
return false;
}
@ -33,17 +42,27 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
return true;
}
void
sc_controller_configure(struct sc_controller *controller,
struct sc_acksync *acksync,
struct sc_uhid_devices *uhid_devices) {
controller->receiver.acksync = acksync;
controller->receiver.uhid_devices = uhid_devices;
}
void
sc_controller_destroy(struct sc_controller *controller) {
sc_cond_destroy(&controller->msg_cond);
sc_mutex_destroy(&controller->mutex);
struct sc_control_msg msg;
while (cbuf_take(&controller->queue, &msg)) {
sc_control_msg_destroy(&msg);
while (!sc_vecdeque_is_empty(&controller->queue)) {
struct sc_control_msg *msg = sc_vecdeque_popref(&controller->queue);
assert(msg);
sc_control_msg_destroy(msg);
}
sc_vecdeque_destroy(&controller->queue);
receiver_destroy(&controller->receiver);
sc_receiver_destroy(&controller->receiver);
}
bool
@ -54,19 +73,25 @@ sc_controller_push_msg(struct sc_controller *controller,
}
sc_mutex_lock(&controller->mutex);
bool was_empty = cbuf_is_empty(&controller->queue);
bool res = cbuf_push(&controller->queue, *msg);
if (was_empty) {
sc_cond_signal(&controller->msg_cond);
bool full = sc_vecdeque_is_full(&controller->queue);
if (!full) {
bool was_empty = sc_vecdeque_is_empty(&controller->queue);
sc_vecdeque_push_noresize(&controller->queue, *msg);
if (was_empty) {
sc_cond_signal(&controller->msg_cond);
}
}
// Otherwise (if the queue is full), the msg is discarded
sc_mutex_unlock(&controller->mutex);
return res;
return !full;
}
static bool
process_msg(struct sc_controller *controller,
const struct sc_control_msg *msg) {
static unsigned char serialized_msg[SC_CONTROL_MSG_MAX_SIZE];
static uint8_t serialized_msg[SC_CONTROL_MSG_MAX_SIZE];
size_t length = sc_control_msg_serialize(msg, serialized_msg);
if (!length) {
return false;
@ -82,7 +107,8 @@ run_controller(void *data) {
for (;;) {
sc_mutex_lock(&controller->mutex);
while (!controller->stopped && cbuf_is_empty(&controller->queue)) {
while (!controller->stopped
&& sc_vecdeque_is_empty(&controller->queue)) {
sc_cond_wait(&controller->msg_cond, &controller->mutex);
}
if (controller->stopped) {
@ -90,10 +116,9 @@ run_controller(void *data) {
sc_mutex_unlock(&controller->mutex);
break;
}
struct sc_control_msg msg;
bool non_empty = cbuf_take(&controller->queue, &msg);
assert(non_empty);
(void) non_empty;
assert(!sc_vecdeque_is_empty(&controller->queue));
struct sc_control_msg msg = sc_vecdeque_pop(&controller->queue);
sc_mutex_unlock(&controller->mutex);
bool ok = process_msg(controller, &msg);
@ -117,7 +142,7 @@ sc_controller_start(struct sc_controller *controller) {
return false;
}
if (!receiver_start(&controller->receiver)) {
if (!sc_receiver_start(&controller->receiver)) {
sc_controller_stop(controller);
sc_thread_join(&controller->thread, NULL);
return false;
@ -137,5 +162,5 @@ sc_controller_stop(struct sc_controller *controller) {
void
sc_controller_join(struct sc_controller *controller) {
sc_thread_join(&controller->thread, NULL);
receiver_join(&controller->receiver);
sc_receiver_join(&controller->receiver);
}

@ -1,5 +1,5 @@
#ifndef CONTROLLER_H
#define CONTROLLER_H
#ifndef SC_CONTROLLER_H
#define SC_CONTROLLER_H
#include "common.h"
@ -8,11 +8,11 @@
#include "control_msg.h"
#include "receiver.h"
#include "util/acksync.h"
#include "util/cbuf.h"
#include "util/net.h"
#include "util/thread.h"
#include "util/vecdeque.h"
struct sc_control_msg_queue CBUF(struct sc_control_msg, 64);
struct sc_control_msg_queue SC_VECDEQUE(struct sc_control_msg);
struct sc_controller {
sc_socket control_socket;
@ -21,12 +21,16 @@ struct sc_controller {
sc_cond msg_cond;
bool stopped;
struct sc_control_msg_queue queue;
struct receiver receiver;
struct sc_receiver receiver;
};
bool
sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
struct sc_acksync *acksync);
sc_controller_init(struct sc_controller *controller, sc_socket control_socket);
void
sc_controller_configure(struct sc_controller *controller,
struct sc_acksync *acksync,
struct sc_uhid_devices *uhid_devices);
void
sc_controller_destroy(struct sc_controller *controller);

@ -2,96 +2,37 @@
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/channel_layout.h>
#include "events.h"
#include "video_buffer.h"
#include "trait/frame_sink.h"
#include "util/log.h"
/** Downcast packet_sink to decoder */
#define DOWNCAST(SINK) container_of(SINK, struct sc_decoder, packet_sink)
static void
sc_decoder_close_first_sinks(struct sc_decoder *decoder, unsigned count) {
while (count) {
struct sc_frame_sink *sink = decoder->sinks[--count];
sink->ops->close(sink);
}
}
static inline void
sc_decoder_close_sinks(struct sc_decoder *decoder) {
sc_decoder_close_first_sinks(decoder, decoder->sink_count);
}
static bool
sc_decoder_open_sinks(struct sc_decoder *decoder) {
for (unsigned i = 0; i < decoder->sink_count; ++i) {
struct sc_frame_sink *sink = decoder->sinks[i];
if (!sink->ops->open(sink)) {
LOGE("Could not open frame sink %d", i);
sc_decoder_close_first_sinks(decoder, i);
return false;
}
}
return true;
}
static bool
sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) {
decoder->codec_ctx = avcodec_alloc_context3(codec);
if (!decoder->codec_ctx) {
LOG_OOM();
return false;
}
decoder->codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
if (avcodec_open2(decoder->codec_ctx, codec, NULL) < 0) {
LOGE("Could not open codec");
avcodec_free_context(&decoder->codec_ctx);
return false;
}
sc_decoder_open(struct sc_decoder *decoder, AVCodecContext *ctx) {
decoder->frame = av_frame_alloc();
if (!decoder->frame) {
LOG_OOM();
avcodec_close(decoder->codec_ctx);
avcodec_free_context(&decoder->codec_ctx);
return false;
}
if (!sc_decoder_open_sinks(decoder)) {
LOGE("Could not open decoder sinks");
if (!sc_frame_source_sinks_open(&decoder->frame_source, ctx)) {
av_frame_free(&decoder->frame);
avcodec_close(decoder->codec_ctx);
avcodec_free_context(&decoder->codec_ctx);
return false;
}
decoder->ctx = ctx;
return true;
}
static void
sc_decoder_close(struct sc_decoder *decoder) {
sc_decoder_close_sinks(decoder);
sc_frame_source_sinks_close(&decoder->frame_source);
av_frame_free(&decoder->frame);
avcodec_close(decoder->codec_ctx);
avcodec_free_context(&decoder->codec_ctx);
}
static bool
push_frame_to_sinks(struct sc_decoder *decoder, const AVFrame *frame) {
for (unsigned i = 0; i < decoder->sink_count; ++i) {
struct sc_frame_sink *sink = decoder->sinks[i];
if (!sink->ops->push(sink, frame)) {
LOGE("Could not send frame to sink %d", i);
return false;
}
}
return true;
}
static bool
@ -102,31 +43,42 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) {
return true;
}
int ret = avcodec_send_packet(decoder->codec_ctx, packet);
int ret = avcodec_send_packet(decoder->ctx, packet);
if (ret < 0 && ret != AVERROR(EAGAIN)) {
LOGE("Could not send video packet: %d", ret);
LOGE("Decoder '%s': could not send video packet: %d",
decoder->name, ret);
return false;
}
ret = avcodec_receive_frame(decoder->codec_ctx, decoder->frame);
if (!ret) {
// a frame was received
bool ok = push_frame_to_sinks(decoder, decoder->frame);
// A frame lost should not make the whole pipeline fail. The error, if
// any, is already logged.
(void) ok;
for (;;) {
ret = avcodec_receive_frame(decoder->ctx, decoder->frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
}
if (ret) {
LOGE("Decoder '%s', could not receive video frame: %d",
decoder->name, ret);
return false;
}
// a frame was received
bool ok = sc_frame_source_sinks_push(&decoder->frame_source,
decoder->frame);
av_frame_unref(decoder->frame);
} else if (ret != AVERROR(EAGAIN)) {
LOGE("Could not receive video frame: %d", ret);
return false;
if (!ok) {
// Error already logged
return false;
}
}
return true;
}
static bool
sc_decoder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) {
sc_decoder_packet_sink_open(struct sc_packet_sink *sink, AVCodecContext *ctx) {
struct sc_decoder *decoder = DOWNCAST(sink);
return sc_decoder_open(decoder, codec);
return sc_decoder_open(decoder, ctx);
}
static void
@ -143,8 +95,9 @@ sc_decoder_packet_sink_push(struct sc_packet_sink *sink,
}
void
sc_decoder_init(struct sc_decoder *decoder) {
decoder->sink_count = 0;
sc_decoder_init(struct sc_decoder *decoder, const char *name) {
decoder->name = name; // statically allocated
sc_frame_source_init(&decoder->frame_source);
static const struct sc_packet_sink_ops ops = {
.open = sc_decoder_packet_sink_open,
@ -154,11 +107,3 @@ sc_decoder_init(struct sc_decoder *decoder) {
decoder->packet_sink.ops = &ops;
}
void
sc_decoder_add_sink(struct sc_decoder *decoder, struct sc_frame_sink *sink) {
assert(decoder->sink_count < SC_DECODER_MAX_SINKS);
assert(sink);
assert(sink->ops);
decoder->sinks[decoder->sink_count++] = sink;
}

@ -3,28 +3,25 @@
#include "common.h"
#include "trait/frame_source.h"
#include "trait/packet_sink.h"
#include <stdbool.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#define SC_DECODER_MAX_SINKS 2
struct sc_decoder {
struct sc_packet_sink packet_sink; // packet sink trait
struct sc_frame_source frame_source; // frame source trait
struct sc_frame_sink *sinks[SC_DECODER_MAX_SINKS];
unsigned sink_count;
const char *name; // must be statically allocated (e.g. a string literal)
AVCodecContext *codec_ctx;
AVCodecContext *ctx;
AVFrame *frame;
};
// The name must be statically allocated (e.g. a string literal)
void
sc_decoder_init(struct sc_decoder *decoder);
void
sc_decoder_add_sink(struct sc_decoder *decoder, struct sc_frame_sink *sink);
sc_decoder_init(struct sc_decoder *decoder, const char *name);
#endif

@ -0,0 +1,244 @@
#include "delay_buffer.h"
#include <assert.h>
#include <stdlib.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>
#include "util/log.h"
#define SC_BUFFERING_NDEBUG // comment to debug
/** Downcast frame_sink to sc_delay_buffer */
#define DOWNCAST(SINK) container_of(SINK, struct sc_delay_buffer, frame_sink)
static bool
sc_delayed_frame_init(struct sc_delayed_frame *dframe, const AVFrame *frame) {
dframe->frame = av_frame_alloc();
if (!dframe->frame) {
LOG_OOM();
return false;
}
if (av_frame_ref(dframe->frame, frame)) {
LOG_OOM();
av_frame_free(&dframe->frame);
return false;
}
return true;
}
static void
sc_delayed_frame_destroy(struct sc_delayed_frame *dframe) {
av_frame_unref(dframe->frame);
av_frame_free(&dframe->frame);
}
static int
run_buffering(void *data) {
struct sc_delay_buffer *db = data;
assert(db->delay > 0);
for (;;) {
sc_mutex_lock(&db->mutex);
while (!db->stopped && sc_vecdeque_is_empty(&db->queue)) {
sc_cond_wait(&db->queue_cond, &db->mutex);
}
if (db->stopped) {
sc_mutex_unlock(&db->mutex);
goto stopped;
}
struct sc_delayed_frame dframe = sc_vecdeque_pop(&db->queue);
sc_tick max_deadline = sc_tick_now() + db->delay;
// PTS (written by the server) are expressed in microseconds
sc_tick pts = SC_TICK_FROM_US(dframe.frame->pts);
bool timed_out = false;
while (!db->stopped && !timed_out) {
sc_tick deadline = sc_clock_to_system_time(&db->clock, pts)
+ db->delay;
if (deadline > max_deadline) {
deadline = max_deadline;
}
timed_out =
!sc_cond_timedwait(&db->wait_cond, &db->mutex, deadline);
}
bool stopped = db->stopped;
sc_mutex_unlock(&db->mutex);
if (stopped) {
sc_delayed_frame_destroy(&dframe);
goto stopped;
}
#ifndef SC_BUFFERING_NDEBUG
LOGD("Buffering: %" PRItick ";%" PRItick ";%" PRItick,
pts, dframe.push_date, sc_tick_now());
#endif
bool ok = sc_frame_source_sinks_push(&db->frame_source, dframe.frame);
sc_delayed_frame_destroy(&dframe);
if (!ok) {
LOGE("Delayed frame could not be pushed, stopping");
sc_mutex_lock(&db->mutex);
// Prevent to push any new frame
db->stopped = true;
sc_mutex_unlock(&db->mutex);
goto stopped;
}
}
stopped:
assert(db->stopped);
// Flush queue
while (!sc_vecdeque_is_empty(&db->queue)) {
struct sc_delayed_frame *dframe = sc_vecdeque_popref(&db->queue);
sc_delayed_frame_destroy(dframe);
}
LOGD("Buffering thread ended");
return 0;
}
static bool
sc_delay_buffer_frame_sink_open(struct sc_frame_sink *sink,
const AVCodecContext *ctx) {
struct sc_delay_buffer *db = DOWNCAST(sink);
(void) ctx;
bool ok = sc_mutex_init(&db->mutex);
if (!ok) {
return false;
}
ok = sc_cond_init(&db->queue_cond);
if (!ok) {
goto error_destroy_mutex;
}
ok = sc_cond_init(&db->wait_cond);
if (!ok) {
goto error_destroy_queue_cond;
}
sc_clock_init(&db->clock);
sc_vecdeque_init(&db->queue);
if (!sc_frame_source_sinks_open(&db->frame_source, ctx)) {
goto error_destroy_wait_cond;
}
ok = sc_thread_create(&db->thread, run_buffering, "scrcpy-dbuf", db);
if (!ok) {
LOGE("Could not start buffering thread");
goto error_close_sinks;
}
return true;
error_close_sinks:
sc_frame_source_sinks_close(&db->frame_source);
error_destroy_wait_cond:
sc_cond_destroy(&db->wait_cond);
error_destroy_queue_cond:
sc_cond_destroy(&db->queue_cond);
error_destroy_mutex:
sc_mutex_destroy(&db->mutex);
return false;
}
static void
sc_delay_buffer_frame_sink_close(struct sc_frame_sink *sink) {
struct sc_delay_buffer *db = DOWNCAST(sink);
sc_mutex_lock(&db->mutex);
db->stopped = true;
sc_cond_signal(&db->queue_cond);
sc_cond_signal(&db->wait_cond);
sc_mutex_unlock(&db->mutex);
sc_thread_join(&db->thread, NULL);
sc_frame_source_sinks_close(&db->frame_source);
sc_cond_destroy(&db->wait_cond);
sc_cond_destroy(&db->queue_cond);
sc_mutex_destroy(&db->mutex);
}
static bool
sc_delay_buffer_frame_sink_push(struct sc_frame_sink *sink,
const AVFrame *frame) {
struct sc_delay_buffer *db = DOWNCAST(sink);
sc_mutex_lock(&db->mutex);
if (db->stopped) {
sc_mutex_unlock(&db->mutex);
return false;
}
sc_tick pts = SC_TICK_FROM_US(frame->pts);
sc_clock_update(&db->clock, sc_tick_now(), pts);
sc_cond_signal(&db->wait_cond);
if (db->first_frame_asap && db->clock.range == 1) {
sc_mutex_unlock(&db->mutex);
return sc_frame_source_sinks_push(&db->frame_source, frame);
}
struct sc_delayed_frame dframe;
bool ok = sc_delayed_frame_init(&dframe, frame);
if (!ok) {
sc_mutex_unlock(&db->mutex);
return false;
}
#ifndef SC_BUFFERING_NDEBUG
dframe.push_date = sc_tick_now();
#endif
ok = sc_vecdeque_push(&db->queue, dframe);
if (!ok) {
sc_mutex_unlock(&db->mutex);
LOG_OOM();
return false;
}
sc_cond_signal(&db->queue_cond);
sc_mutex_unlock(&db->mutex);
return true;
}
void
sc_delay_buffer_init(struct sc_delay_buffer *db, sc_tick delay,
bool first_frame_asap) {
assert(delay > 0);
db->delay = delay;
db->first_frame_asap = first_frame_asap;
sc_frame_source_init(&db->frame_source);
static const struct sc_frame_sink_ops ops = {
.open = sc_delay_buffer_frame_sink_open,
.close = sc_delay_buffer_frame_sink_close,
.push = sc_delay_buffer_frame_sink_push,
};
db->frame_sink.ops = &ops;
}

@ -0,0 +1,60 @@
#ifndef SC_DELAY_BUFFER_H
#define SC_DELAY_BUFFER_H
#include "common.h"
#include <stdbool.h>
#include "clock.h"
#include "trait/frame_source.h"
#include "trait/frame_sink.h"
#include "util/thread.h"
#include "util/tick.h"
#include "util/vecdeque.h"
// forward declarations
typedef struct AVFrame AVFrame;
struct sc_delayed_frame {
AVFrame *frame;
#ifndef NDEBUG
sc_tick push_date;
#endif
};
struct sc_delayed_frame_queue SC_VECDEQUE(struct sc_delayed_frame);
struct sc_delay_buffer {
struct sc_frame_source frame_source; // frame source trait
struct sc_frame_sink frame_sink; // frame sink trait
sc_tick delay;
bool first_frame_asap;
sc_thread thread;
sc_mutex mutex;
sc_cond queue_cond;
sc_cond wait_cond;
struct sc_clock clock;
struct sc_delayed_frame_queue queue;
bool stopped;
};
struct sc_delay_buffer_callbacks {
bool (*on_new_frame)(struct sc_delay_buffer *db, const AVFrame *frame,
void *userdata);
};
/**
* Initialize a delay buffer.
*
* \param delay a (strictly) positive delay
* \param first_frame_asap if true, do not delay the first frame (useful for
a video stream).
*/
void
sc_delay_buffer_init(struct sc_delay_buffer *db, sc_tick delay,
bool first_frame_asap);
#endif

@ -1,13 +1,15 @@
#include "demuxer.h"
#include <assert.h>
#include <libavutil/channel_layout.h>
#include <libavutil/time.h>
#include <unistd.h>
#include "decoder.h"
#include "events.h"
#include "packet_merger.h"
#include "recorder.h"
#include "util/buffer_util.h"
#include "util/binary.h"
#include "util/log.h"
#define SC_PACKET_HEADER_SIZE 12
@ -17,11 +19,71 @@
#define SC_PACKET_PTS_MASK (SC_PACKET_FLAG_KEY_FRAME - 1)
static enum AVCodecID
sc_demuxer_to_avcodec_id(uint32_t codec_id) {
#define SC_CODEC_ID_H264 UINT32_C(0x68323634) // "h264" in ASCII
#define SC_CODEC_ID_H265 UINT32_C(0x68323635) // "h265" in ASCII
#define SC_CODEC_ID_AV1 UINT32_C(0x00617631) // "av1" in ASCII
#define SC_CODEC_ID_OPUS UINT32_C(0x6f707573) // "opus" in ASCII
#define SC_CODEC_ID_AAC UINT32_C(0x00616163) // "aac" in ASCII
#define SC_CODEC_ID_FLAC UINT32_C(0x666c6163) // "flac" in ASCII
#define SC_CODEC_ID_RAW UINT32_C(0x00726177) // "raw" in ASCII
switch (codec_id) {
case SC_CODEC_ID_H264:
return AV_CODEC_ID_H264;
case SC_CODEC_ID_H265:
return AV_CODEC_ID_HEVC;
case SC_CODEC_ID_AV1:
#ifdef SCRCPY_LAVC_HAS_AV1
return AV_CODEC_ID_AV1;
#else
LOGE("AV1 not supported by this FFmpeg version");
return AV_CODEC_ID_NONE;
#endif
case SC_CODEC_ID_OPUS:
return AV_CODEC_ID_OPUS;
case SC_CODEC_ID_AAC:
return AV_CODEC_ID_AAC;
case SC_CODEC_ID_FLAC:
return AV_CODEC_ID_FLAC;
case SC_CODEC_ID_RAW:
return AV_CODEC_ID_PCM_S16LE;
default:
LOGE("Unknown codec id 0x%08" PRIx32, codec_id);
return AV_CODEC_ID_NONE;
}
}
static bool
sc_demuxer_recv_codec_id(struct sc_demuxer *demuxer, uint32_t *codec_id) {
uint8_t data[4];
ssize_t r = net_recv_all(demuxer->socket, data, 4);
if (r < 4) {
return false;
}
*codec_id = sc_read32be(data);
return true;
}
static bool
sc_demuxer_recv_video_size(struct sc_demuxer *demuxer, uint32_t *width,
uint32_t *height) {
uint8_t data[8];
ssize_t r = net_recv_all(demuxer->socket, data, 8);
if (r < 8) {
return false;
}
*width = sc_read32be(data);
*height = sc_read32be(data + 4);
return true;
}
static bool
sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
// The video stream contains raw packets, without time information. When we
// record, we retrieve the timestamps separately, from a "meta" header
// added by the server before each raw packet.
// The video and audio streams contain a sequence of raw packets (as
// provided by MediaCodec), each prefixed with a "meta" header.
//
// The "meta" header length is 12 bytes:
// [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ...
@ -37,8 +99,8 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
// CK...... ........ ........ ........ ........ ........ ........ ........
// ^^<------------------------------------------------------------------->
// || PTS
// | `- config packet
// `-- key frame
// | `- key frame
// `-- config packet
uint8_t header[SC_PACKET_HEADER_SIZE];
ssize_t r = net_recv_all(demuxer->socket, header, SC_PACKET_HEADER_SIZE);
@ -75,200 +137,178 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
return true;
}
static bool
push_packet_to_sinks(struct sc_demuxer *demuxer, const AVPacket *packet) {
for (unsigned i = 0; i < demuxer->sink_count; ++i) {
struct sc_packet_sink *sink = demuxer->sinks[i];
if (!sink->ops->push(sink, packet)) {
LOGE("Could not send config packet to sink %d", i);
return false;
}
}
return true;
}
static bool
sc_demuxer_push_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
bool is_config = packet->pts == AV_NOPTS_VALUE;
// A config packet must not be decoded immediately (it contains no
// frame); instead, it must be concatenated with the future data packet.
if (demuxer->pending || is_config) {
size_t offset;
if (demuxer->pending) {
offset = demuxer->pending->size;
if (av_grow_packet(demuxer->pending, packet->size)) {
LOG_OOM();
return false;
}
} else {
offset = 0;
demuxer->pending = av_packet_alloc();
if (!demuxer->pending) {
LOG_OOM();
return false;
}
if (av_new_packet(demuxer->pending, packet->size)) {
LOG_OOM();
av_packet_free(&demuxer->pending);
return false;
}
}
memcpy(demuxer->pending->data + offset, packet->data, packet->size);
if (!is_config) {
// prepare the concat packet to send to the decoder
demuxer->pending->pts = packet->pts;
demuxer->pending->dts = packet->dts;
demuxer->pending->flags = packet->flags;
packet = demuxer->pending;
}
}
bool ok = push_packet_to_sinks(demuxer, packet);
static int
run_demuxer(void *data) {
struct sc_demuxer *demuxer = data;
if (!is_config && demuxer->pending) {
// the pending packet must be discarded (consumed or error)
av_packet_free(&demuxer->pending);
}
// Flag to report end-of-stream (i.e. device disconnected)
enum sc_demuxer_status status = SC_DEMUXER_STATUS_ERROR;
uint32_t raw_codec_id;
bool ok = sc_demuxer_recv_codec_id(demuxer, &raw_codec_id);
if (!ok) {
LOGE("Could not process packet");
return false;
LOGE("Demuxer '%s': stream disabled due to connection error",
demuxer->name);
goto end;
}
return true;
}
static void
sc_demuxer_close_first_sinks(struct sc_demuxer *demuxer, unsigned count) {
while (count) {
struct sc_packet_sink *sink = demuxer->sinks[--count];
sink->ops->close(sink);
if (raw_codec_id == 0) {
LOGW("Demuxer '%s': stream explicitly disabled by the device",
demuxer->name);
sc_packet_source_sinks_disable(&demuxer->packet_source);
status = SC_DEMUXER_STATUS_DISABLED;
goto end;
}
}
static inline void
sc_demuxer_close_sinks(struct sc_demuxer *demuxer) {
sc_demuxer_close_first_sinks(demuxer, demuxer->sink_count);
}
static bool
sc_demuxer_open_sinks(struct sc_demuxer *demuxer, const AVCodec *codec) {
for (unsigned i = 0; i < demuxer->sink_count; ++i) {
struct sc_packet_sink *sink = demuxer->sinks[i];
if (!sink->ops->open(sink, codec)) {
LOGE("Could not open packet sink %d", i);
sc_demuxer_close_first_sinks(demuxer, i);
return false;
}
if (raw_codec_id == 1) {
LOGE("Demuxer '%s': stream configuration error on the device",
demuxer->name);
goto end;
}
return true;
}
static int
run_demuxer(void *data) {
struct sc_demuxer *demuxer = data;
enum AVCodecID codec_id = sc_demuxer_to_avcodec_id(raw_codec_id);
if (codec_id == AV_CODEC_ID_NONE) {
LOGE("Demuxer '%s': stream disabled due to unsupported codec",
demuxer->name);
sc_packet_source_sinks_disable(&demuxer->packet_source);
goto end;
}
const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
const AVCodec *codec = avcodec_find_decoder(codec_id);
if (!codec) {
LOGE("H.264 decoder not found");
LOGE("Demuxer '%s': stream disabled due to missing decoder",
demuxer->name);
sc_packet_source_sinks_disable(&demuxer->packet_source);
goto end;
}
demuxer->codec_ctx = avcodec_alloc_context3(codec);
if (!demuxer->codec_ctx) {
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
LOG_OOM();
goto end;
}
if (!sc_demuxer_open_sinks(demuxer, codec)) {
LOGE("Could not open demuxer sinks");
goto finally_free_codec_ctx;
codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
if (codec->type == AVMEDIA_TYPE_VIDEO) {
uint32_t width;
uint32_t height;
ok = sc_demuxer_recv_video_size(demuxer, &width, &height);
if (!ok) {
goto finally_free_context;
}
codec_ctx->width = width;
codec_ctx->height = height;
codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
} else {
// Hardcoded audio properties
#ifdef SCRCPY_LAVU_HAS_CHLAYOUT
codec_ctx->ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_STEREO;
#else
codec_ctx->channel_layout = AV_CH_LAYOUT_STEREO;
codec_ctx->channels = 2;
#endif
codec_ctx->sample_rate = 48000;
if (raw_codec_id == SC_CODEC_ID_FLAC) {
// The sample_fmt is not set by the FLAC decoder
codec_ctx->sample_fmt = AV_SAMPLE_FMT_S16;
}
}
demuxer->parser = av_parser_init(AV_CODEC_ID_H264);
if (!demuxer->parser) {
LOGE("Could not initialize parser");
goto finally_close_sinks;
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
LOGE("Demuxer '%s': could not open codec", demuxer->name);
goto finally_free_context;
}
if (!sc_packet_source_sinks_open(&demuxer->packet_source, codec_ctx)) {
goto finally_free_context;
}
// We must only pass complete frames to av_parser_parse2()!
// It's more complicated, but this allows to reduce the latency by 1 frame!
demuxer->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
// Config packets must be merged with the next non-config packet only for
// H.26x
bool must_merge_config_packet = raw_codec_id == SC_CODEC_ID_H264
|| raw_codec_id == SC_CODEC_ID_H265;
struct sc_packet_merger merger;
if (must_merge_config_packet) {
sc_packet_merger_init(&merger);
}
AVPacket *packet = av_packet_alloc();
if (!packet) {
LOG_OOM();
goto finally_close_parser;
goto finally_close_sinks;
}
for (;;) {
bool ok = sc_demuxer_recv_packet(demuxer, packet);
if (!ok) {
// end of stream
status = SC_DEMUXER_STATUS_EOS;
break;
}
ok = sc_demuxer_push_packet(demuxer, packet);
if (must_merge_config_packet) {
// Prepend any config packet to the next media packet
ok = sc_packet_merger_merge(&merger, packet);
if (!ok) {
av_packet_unref(packet);
break;
}
}
ok = sc_packet_source_sinks_push(&demuxer->packet_source, packet);
av_packet_unref(packet);
if (!ok) {
// cannot process packet (error already logged)
// The sink already logged its concrete error
break;
}
}
LOGD("End of frames");
LOGD("Demuxer '%s': end of frames", demuxer->name);
if (demuxer->pending) {
av_packet_free(&demuxer->pending);
if (must_merge_config_packet) {
sc_packet_merger_destroy(&merger);
}
av_packet_free(&packet);
finally_close_parser:
av_parser_close(demuxer->parser);
finally_close_sinks:
sc_demuxer_close_sinks(demuxer);
finally_free_codec_ctx:
avcodec_free_context(&demuxer->codec_ctx);
sc_packet_source_sinks_close(&demuxer->packet_source);
finally_free_context:
// This also calls avcodec_close() internally
avcodec_free_context(&codec_ctx);
end:
demuxer->cbs->on_eos(demuxer, demuxer->cbs_userdata);
demuxer->cbs->on_ended(demuxer, status, demuxer->cbs_userdata);
return 0;
}
void
sc_demuxer_init(struct sc_demuxer *demuxer, sc_socket socket,
sc_demuxer_init(struct sc_demuxer *demuxer, const char *name, sc_socket socket,
const struct sc_demuxer_callbacks *cbs, void *cbs_userdata) {
assert(socket != SC_SOCKET_NONE);
demuxer->name = name; // statically allocated
demuxer->socket = socket;
demuxer->pending = NULL;
demuxer->sink_count = 0;
sc_packet_source_init(&demuxer->packet_source);
assert(cbs && cbs->on_eos);
assert(cbs && cbs->on_ended);
demuxer->cbs = cbs;
demuxer->cbs_userdata = cbs_userdata;
}
void
sc_demuxer_add_sink(struct sc_demuxer *demuxer, struct sc_packet_sink *sink) {
assert(demuxer->sink_count < SC_DEMUXER_MAX_SINKS);
assert(sink);
assert(sink->ops);
demuxer->sinks[demuxer->sink_count++] = sink;
}
bool
sc_demuxer_start(struct sc_demuxer *demuxer) {
LOGD("Starting demuxer thread");
LOGD("Demuxer '%s': starting thread", demuxer->name);
bool ok = sc_thread_create(&demuxer->thread, run_demuxer, "scrcpy-demuxer",
demuxer);
if (!ok) {
LOGE("Could not start demuxer thread");
LOGE("Demuxer '%s': could not start thread", demuxer->name);
return false;
}
return true;

@ -8,40 +8,39 @@
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include "trait/packet_source.h"
#include "trait/packet_sink.h"
#include "util/net.h"
#include "util/thread.h"
#define SC_DEMUXER_MAX_SINKS 2
struct sc_demuxer {
sc_socket socket;
sc_thread thread;
struct sc_packet_source packet_source; // packet source trait
struct sc_packet_sink *sinks[SC_DEMUXER_MAX_SINKS];
unsigned sink_count;
const char *name; // must be statically allocated (e.g. a string literal)
AVCodecContext *codec_ctx;
AVCodecParserContext *parser;
// successive packets may need to be concatenated, until a non-config
// packet is available
AVPacket *pending;
sc_socket socket;
sc_thread thread;
const struct sc_demuxer_callbacks *cbs;
void *cbs_userdata;
};
enum sc_demuxer_status {
SC_DEMUXER_STATUS_EOS,
SC_DEMUXER_STATUS_DISABLED,
SC_DEMUXER_STATUS_ERROR,
};
struct sc_demuxer_callbacks {
void (*on_eos)(struct sc_demuxer *demuxer, void *userdata);
void (*on_ended)(struct sc_demuxer *demuxer, enum sc_demuxer_status,
void *userdata);
};
// The name must be statically allocated (e.g. a string literal)
void
sc_demuxer_init(struct sc_demuxer *demuxer, sc_socket socket,
sc_demuxer_init(struct sc_demuxer *demuxer, const char *name, sc_socket socket,
const struct sc_demuxer_callbacks *cbs, void *cbs_userdata);
void
sc_demuxer_add_sink(struct sc_demuxer *demuxer, struct sc_packet_sink *sink);
bool
sc_demuxer_start(struct sc_demuxer *demuxer);

@ -4,23 +4,26 @@
#include <stdlib.h>
#include <string.h>
#include "util/buffer_util.h"
#include "util/binary.h"
#include "util/log.h"
ssize_t
device_msg_deserialize(const unsigned char *buf, size_t len,
struct device_msg *msg) {
if (len < 5) {
// at least type + empty string length
return 0; // not available
sc_device_msg_deserialize(const uint8_t *buf, size_t len,
struct sc_device_msg *msg) {
if (!len) {
return 0; // no message
}
msg->type = buf[0];
switch (msg->type) {
case DEVICE_MSG_TYPE_CLIPBOARD: {
if (len < 5) {
// at least type + empty string length
return 0; // no complete message
}
size_t clipboard_len = sc_read32be(&buf[1]);
if (clipboard_len > len - 5) {
return 0; // not available
return 0; // no complete message
}
char *text = malloc(clipboard_len + 1);
if (!text) {
@ -36,10 +39,38 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
return 5 + clipboard_len;
}
case DEVICE_MSG_TYPE_ACK_CLIPBOARD: {
if (len < 9) {
return 0; // no complete message
}
uint64_t sequence = sc_read64be(&buf[1]);
msg->ack_clipboard.sequence = sequence;
return 9;
}
case DEVICE_MSG_TYPE_UHID_OUTPUT: {
if (len < 5) {
// at least id + size
return 0; // not available
}
uint16_t id = sc_read16be(&buf[1]);
size_t size = sc_read16be(&buf[3]);
if (size < len - 5) {
return 0; // not available
}
uint8_t *data = malloc(size);
if (!data) {
LOG_OOM();
return -1;
}
if (size) {
memcpy(data, &buf[5], size);
}
msg->uhid_output.id = id;
msg->uhid_output.size = size;
msg->uhid_output.data = data;
return 5 + size;
}
default:
LOGW("Unknown device message type: %d", (int) msg->type);
return -1; // error, we cannot recover
@ -47,8 +78,16 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
}
void
device_msg_destroy(struct device_msg *msg) {
if (msg->type == DEVICE_MSG_TYPE_CLIPBOARD) {
free(msg->clipboard.text);
sc_device_msg_destroy(struct sc_device_msg *msg) {
switch (msg->type) {
case DEVICE_MSG_TYPE_CLIPBOARD:
free(msg->clipboard.text);
break;
case DEVICE_MSG_TYPE_UHID_OUTPUT:
free(msg->uhid_output.data);
break;
default:
// nothing to do
break;
}
}

@ -1,5 +1,5 @@
#ifndef DEVICEMSG_H
#define DEVICEMSG_H
#ifndef SC_DEVICEMSG_H
#define SC_DEVICEMSG_H
#include "common.h"
@ -11,13 +11,14 @@
// type: 1 byte; length: 4 bytes
#define DEVICE_MSG_TEXT_MAX_LENGTH (DEVICE_MSG_MAX_SIZE - 5)
enum device_msg_type {
enum sc_device_msg_type {
DEVICE_MSG_TYPE_CLIPBOARD,
DEVICE_MSG_TYPE_ACK_CLIPBOARD,
DEVICE_MSG_TYPE_UHID_OUTPUT,
};
struct device_msg {
enum device_msg_type type;
struct sc_device_msg {
enum sc_device_msg_type type;
union {
struct {
char *text; // owned, to be freed by free()
@ -25,15 +26,20 @@ struct device_msg {
struct {
uint64_t sequence;
} ack_clipboard;
struct {
uint16_t id;
uint16_t size;
uint8_t *data; // owned, to be freed by free()
} uhid_output;
};
};
// return the number of bytes consumed (0 for no msg available, -1 on error)
ssize_t
device_msg_deserialize(const unsigned char *buf, size_t len,
struct device_msg *msg);
sc_device_msg_deserialize(const uint8_t *buf, size_t len,
struct sc_device_msg *msg);
void
device_msg_destroy(struct device_msg *msg);
sc_device_msg_destroy(struct sc_device_msg *msg);
#endif

@ -0,0 +1,286 @@
#include "display.h"
#include <assert.h>
#include "util/log.h"
bool
sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps) {
display->renderer =
SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (!display->renderer) {
LOGE("Could not create renderer: %s", SDL_GetError());
return false;
}
SDL_RendererInfo renderer_info;
int r = SDL_GetRendererInfo(display->renderer, &renderer_info);
const char *renderer_name = r ? NULL : renderer_info.name;
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
display->mipmaps = false;
// starts with "opengl"
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
if (use_opengl) {
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
// Persuade macOS to give us something better than OpenGL 2.1.
// If we create a Core Profile context, we get the best OpenGL version.
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
SDL_GL_CONTEXT_PROFILE_CORE);
LOGD("Creating OpenGL Core Profile context");
display->gl_context = SDL_GL_CreateContext(window);
if (!display->gl_context) {
LOGE("Could not create OpenGL context: %s", SDL_GetError());
SDL_DestroyRenderer(display->renderer);
return false;
}
#endif
struct sc_opengl *gl = &display->gl;
sc_opengl_init(gl);
LOGI("OpenGL version: %s", gl->version);
if (mipmaps) {
bool supports_mipmaps =
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
2, 0 /* OpenGL ES 2.0+ */);
if (supports_mipmaps) {
LOGI("Trilinear filtering enabled");
display->mipmaps = true;
} else {
LOGW("Trilinear filtering disabled "
"(OpenGL 3.0+ or ES 2.0+ required)");
}
} else {
LOGI("Trilinear filtering disabled");
}
} else if (mipmaps) {
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
}
display->texture = NULL;
display->pending.flags = 0;
display->pending.frame = NULL;
return true;
}
void
sc_display_destroy(struct sc_display *display) {
if (display->pending.frame) {
av_frame_free(&display->pending.frame);
}
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
SDL_GL_DeleteContext(display->gl_context);
#endif
if (display->texture) {
SDL_DestroyTexture(display->texture);
}
SDL_DestroyRenderer(display->renderer);
}
static SDL_Texture *
sc_display_create_texture(struct sc_display *display,
struct sc_size size) {
SDL_Renderer *renderer = display->renderer;
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
SDL_TEXTUREACCESS_STREAMING,
size.width, size.height);
if (!texture) {
LOGD("Could not create texture: %s", SDL_GetError());
return NULL;
}
if (display->mipmaps) {
struct sc_opengl *gl = &display->gl;
SDL_GL_BindTexture(texture, NULL, NULL);
// Enable trilinear filtering for downscaling
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_LINEAR_MIPMAP_LINEAR);
gl->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.f);
SDL_GL_UnbindTexture(texture);
}
return texture;
}
static inline void
sc_display_set_pending_size(struct sc_display *display, struct sc_size size) {
assert(!display->texture);
display->pending.size = size;
display->pending.flags |= SC_DISPLAY_PENDING_FLAG_SIZE;
}
static bool
sc_display_set_pending_frame(struct sc_display *display, const AVFrame *frame) {
if (!display->pending.frame) {
display->pending.frame = av_frame_alloc();
if (!display->pending.frame) {
LOG_OOM();
return false;
}
}
int r = av_frame_ref(display->pending.frame, frame);
if (r) {
LOGE("Could not ref frame: %d", r);
return false;
}
display->pending.flags |= SC_DISPLAY_PENDING_FLAG_FRAME;
return true;
}
static bool
sc_display_apply_pending(struct sc_display *display) {
if (display->pending.flags & SC_DISPLAY_PENDING_FLAG_SIZE) {
assert(!display->texture);
display->texture =
sc_display_create_texture(display, display->pending.size);
if (!display->texture) {
return false;
}
display->pending.flags &= ~SC_DISPLAY_PENDING_FLAG_SIZE;
}
if (display->pending.flags & SC_DISPLAY_PENDING_FLAG_FRAME) {
assert(display->pending.frame);
bool ok = sc_display_update_texture(display, display->pending.frame);
if (!ok) {
return false;
}
av_frame_unref(display->pending.frame);
display->pending.flags &= ~SC_DISPLAY_PENDING_FLAG_FRAME;
}
return true;
}
static bool
sc_display_set_texture_size_internal(struct sc_display *display,
struct sc_size size) {
assert(size.width && size.height);
if (display->texture) {
SDL_DestroyTexture(display->texture);
}
display->texture = sc_display_create_texture(display, size);
if (!display->texture) {
return false;
}
LOGI("Texture: %" PRIu16 "x%" PRIu16, size.width, size.height);
return true;
}
enum sc_display_result
sc_display_set_texture_size(struct sc_display *display, struct sc_size size) {
bool ok = sc_display_set_texture_size_internal(display, size);
if (!ok) {
sc_display_set_pending_size(display, size);
return SC_DISPLAY_RESULT_PENDING;
}
return SC_DISPLAY_RESULT_OK;
}
static bool
sc_display_update_texture_internal(struct sc_display *display,
const AVFrame *frame) {
int ret = SDL_UpdateYUVTexture(display->texture, NULL,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
if (ret) {
LOGD("Could not update texture: %s", SDL_GetError());
return false;
}
if (display->mipmaps) {
SDL_GL_BindTexture(display->texture, NULL, NULL);
display->gl.GenerateMipmap(GL_TEXTURE_2D);
SDL_GL_UnbindTexture(display->texture);
}
return true;
}
enum sc_display_result
sc_display_update_texture(struct sc_display *display, const AVFrame *frame) {
bool ok = sc_display_update_texture_internal(display, frame);
if (!ok) {
ok = sc_display_set_pending_frame(display, frame);
if (!ok) {
LOGE("Could not set pending frame");
return SC_DISPLAY_RESULT_ERROR;
}
return SC_DISPLAY_RESULT_PENDING;
}
return SC_DISPLAY_RESULT_OK;
}
enum sc_display_result
sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
enum sc_orientation orientation) {
SDL_RenderClear(display->renderer);
if (display->pending.flags) {
bool ok = sc_display_apply_pending(display);
if (!ok) {
return SC_DISPLAY_RESULT_PENDING;
}
}
SDL_Renderer *renderer = display->renderer;
SDL_Texture *texture = display->texture;
if (orientation == SC_ORIENTATION_0) {
int ret = SDL_RenderCopy(renderer, texture, NULL, geometry);
if (ret) {
LOGE("Could not render texture: %s", SDL_GetError());
return SC_DISPLAY_RESULT_ERROR;
}
} else {
unsigned cw_rotation = sc_orientation_get_rotation(orientation);
double angle = 90 * cw_rotation;
const SDL_Rect *dstrect = NULL;
SDL_Rect rect;
if (sc_orientation_is_swap(orientation)) {
rect.x = geometry->x + (geometry->w - geometry->h) / 2;
rect.y = geometry->y + (geometry->h - geometry->w) / 2;
rect.w = geometry->h;
rect.h = geometry->w;
dstrect = &rect;
} else {
dstrect = geometry;
}
SDL_RendererFlip flip = sc_orientation_is_mirror(orientation)
? SDL_FLIP_HORIZONTAL : 0;
int ret = SDL_RenderCopyEx(renderer, texture, NULL, dstrect, angle,
NULL, flip);
if (ret) {
LOGE("Could not render texture: %s", SDL_GetError());
return SC_DISPLAY_RESULT_ERROR;
}
}
SDL_RenderPresent(display->renderer);
return SC_DISPLAY_RESULT_OK;
}

@ -0,0 +1,60 @@
#ifndef SC_DISPLAY_H
#define SC_DISPLAY_H
#include "common.h"
#include <stdbool.h>
#include <libavformat/avformat.h>
#include <SDL2/SDL.h>
#include "coords.h"
#include "opengl.h"
#include "options.h"
#ifdef __APPLE__
# define SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
#endif
struct sc_display {
SDL_Renderer *renderer;
SDL_Texture *texture;
struct sc_opengl gl;
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
SDL_GLContext *gl_context;
#endif
bool mipmaps;
struct {
#define SC_DISPLAY_PENDING_FLAG_SIZE 1
#define SC_DISPLAY_PENDING_FLAG_FRAME 2
int8_t flags;
struct sc_size size;
AVFrame *frame;
} pending;
};
enum sc_display_result {
SC_DISPLAY_RESULT_OK,
SC_DISPLAY_RESULT_PENDING,
SC_DISPLAY_RESULT_ERROR,
};
bool
sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps);
void
sc_display_destroy(struct sc_display *display);
enum sc_display_result
sc_display_set_texture_size(struct sc_display *display, struct sc_size size);
enum sc_display_result
sc_display_update_texture(struct sc_display *display, const AVFrame *frame);
enum sc_display_result
sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
enum sc_orientation orientation);
#endif

@ -1,5 +1,9 @@
#define EVENT_NEW_FRAME SDL_USEREVENT
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1)
#define EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2)
#define EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3)
#define EVENT_USB_DEVICE_DISCONNECTED (SDL_USEREVENT + 4)
#define SC_EVENT_NEW_FRAME SDL_USEREVENT
#define SC_EVENT_DEVICE_DISCONNECTED (SDL_USEREVENT + 1)
#define SC_EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2)
#define SC_EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3)
#define SC_EVENT_USB_DEVICE_DISCONNECTED (SDL_USEREVENT + 4)
#define SC_EVENT_DEMUXER_ERROR (SDL_USEREVENT + 5)
#define SC_EVENT_RECORDER_ERROR (SDL_USEREVENT + 6)
#define SC_EVENT_SCREEN_INIT_SIZE (SDL_USEREVENT + 7)
#define SC_EVENT_TIME_LIMIT_REACHED (SDL_USEREVENT + 8)

@ -19,7 +19,7 @@ sc_file_pusher_init(struct sc_file_pusher *fp, const char *serial,
const char *push_target) {
assert(serial);
cbuf_init(&fp->queue);
sc_vecdeque_init(&fp->queue);
bool ok = sc_mutex_init(&fp->mutex);
if (!ok) {
@ -65,9 +65,10 @@ sc_file_pusher_destroy(struct sc_file_pusher *fp) {
sc_intr_destroy(&fp->intr);
free(fp->serial);
struct sc_file_pusher_request req;
while (cbuf_take(&fp->queue, &req)) {
sc_file_pusher_request_destroy(&req);
while (!sc_vecdeque_is_empty(&fp->queue)) {
struct sc_file_pusher_request *req = sc_vecdeque_popref(&fp->queue);
assert(req);
sc_file_pusher_request_destroy(req);
}
}
@ -91,13 +92,20 @@ sc_file_pusher_request(struct sc_file_pusher *fp,
};
sc_mutex_lock(&fp->mutex);
bool was_empty = cbuf_is_empty(&fp->queue);
bool res = cbuf_push(&fp->queue, req);
bool was_empty = sc_vecdeque_is_empty(&fp->queue);
bool res = sc_vecdeque_push(&fp->queue, req);
if (!res) {
LOG_OOM();
sc_mutex_unlock(&fp->mutex);
return false;
}
if (was_empty) {
sc_cond_signal(&fp->event_cond);
}
sc_mutex_unlock(&fp->mutex);
return res;
return true;
}
static int
@ -113,7 +121,7 @@ run_file_pusher(void *data) {
for (;;) {
sc_mutex_lock(&fp->mutex);
while (!fp->stopped && cbuf_is_empty(&fp->queue)) {
while (!fp->stopped && sc_vecdeque_is_empty(&fp->queue)) {
sc_cond_wait(&fp->event_cond, &fp->mutex);
}
if (fp->stopped) {
@ -121,10 +129,9 @@ run_file_pusher(void *data) {
sc_mutex_unlock(&fp->mutex);
break;
}
struct sc_file_pusher_request req;
bool non_empty = cbuf_take(&fp->queue, &req);
assert(non_empty);
(void) non_empty;
assert(!sc_vecdeque_is_empty(&fp->queue));
struct sc_file_pusher_request req = sc_vecdeque_pop(&fp->queue);
sc_mutex_unlock(&fp->mutex);
if (req.action == SC_FILE_PUSHER_ACTION_INSTALL_APK) {
@ -165,14 +172,18 @@ sc_file_pusher_start(struct sc_file_pusher *fp) {
void
sc_file_pusher_stop(struct sc_file_pusher *fp) {
sc_mutex_lock(&fp->mutex);
fp->stopped = true;
sc_cond_signal(&fp->event_cond);
sc_intr_interrupt(&fp->intr);
sc_mutex_unlock(&fp->mutex);
if (fp->initialized) {
sc_mutex_lock(&fp->mutex);
fp->stopped = true;
sc_cond_signal(&fp->event_cond);
sc_intr_interrupt(&fp->intr);
sc_mutex_unlock(&fp->mutex);
}
}
void
sc_file_pusher_join(struct sc_file_pusher *fp) {
sc_thread_join(&fp->thread, NULL);
if (fp->initialized) {
sc_thread_join(&fp->thread, NULL);
}
}

@ -5,9 +5,9 @@
#include <stdbool.h>
#include "util/cbuf.h"
#include "util/thread.h"
#include "util/intr.h"
#include "util/thread.h"
#include "util/vecdeque.h"
enum sc_file_pusher_action {
SC_FILE_PUSHER_ACTION_INSTALL_APK,
@ -19,7 +19,7 @@ struct sc_file_pusher_request {
char *file;
};
struct sc_file_pusher_request_queue CBUF(struct sc_file_pusher_request, 16);
struct sc_file_pusher_request_queue SC_VECDEQUE(struct sc_file_pusher_request);
struct sc_file_pusher {
char *serial;

@ -96,6 +96,7 @@ run_fps_counter(void *data) {
bool
sc_fps_counter_start(struct sc_fps_counter *counter) {
sc_mutex_lock(&counter->mutex);
counter->interrupted = false;
counter->next_timestamp = sc_tick_now() + SC_FPS_COUNTER_INTERVAL;
counter->nr_rendered = 0;
counter->nr_skipped = 0;

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

@ -0,0 +1,15 @@
#ifndef SC_HID_EVENT_H
#define SC_HID_EVENT_H
#include "common.h"
#include <stdint.h>
#define SC_HID_MAX_SIZE 8
struct sc_hid_event {
uint8_t data[SC_HID_MAX_SIZE];
uint8_t size;
};
#endif

@ -1,39 +1,34 @@
#include "hid_keyboard.h"
#include <assert.h>
#include <string.h>
#include "input_events.h"
#include "util/log.h"
/** Downcast key processor to hid_keyboard */
#define DOWNCAST(KP) container_of(KP, struct sc_hid_keyboard, key_processor)
#define SC_HID_MOD_NONE 0x00
#define SC_HID_MOD_LEFT_CONTROL (1 << 0)
#define SC_HID_MOD_LEFT_SHIFT (1 << 1)
#define SC_HID_MOD_LEFT_ALT (1 << 2)
#define SC_HID_MOD_LEFT_GUI (1 << 3)
#define SC_HID_MOD_RIGHT_CONTROL (1 << 4)
#define SC_HID_MOD_RIGHT_SHIFT (1 << 5)
#define SC_HID_MOD_RIGHT_ALT (1 << 6)
#define SC_HID_MOD_RIGHT_GUI (1 << 7)
#define HID_KEYBOARD_ACCESSORY_ID 1
#define HID_MODIFIER_NONE 0x00
#define HID_MODIFIER_LEFT_CONTROL (1 << 0)
#define HID_MODIFIER_LEFT_SHIFT (1 << 1)
#define HID_MODIFIER_LEFT_ALT (1 << 2)
#define HID_MODIFIER_LEFT_GUI (1 << 3)
#define HID_MODIFIER_RIGHT_CONTROL (1 << 4)
#define HID_MODIFIER_RIGHT_SHIFT (1 << 5)
#define HID_MODIFIER_RIGHT_ALT (1 << 6)
#define HID_MODIFIER_RIGHT_GUI (1 << 7)
#define HID_KEYBOARD_INDEX_MODIFIER 0
#define HID_KEYBOARD_INDEX_KEYS 2
#define SC_HID_KEYBOARD_INDEX_MODS 0
#define SC_HID_KEYBOARD_INDEX_KEYS 2
// USB HID protocol says 6 keys in an event is the requirement for BIOS
// keyboard support, though OS could support more keys via modifying the report
// desc. 6 should be enough for scrcpy.
#define HID_KEYBOARD_MAX_KEYS 6
#define HID_KEYBOARD_EVENT_SIZE (2 + HID_KEYBOARD_MAX_KEYS)
#define SC_HID_KEYBOARD_MAX_KEYS 6
#define SC_HID_KEYBOARD_EVENT_SIZE \
(SC_HID_KEYBOARD_INDEX_KEYS + SC_HID_KEYBOARD_MAX_KEYS)
#define HID_RESERVED 0x00
#define HID_ERROR_ROLL_OVER 0x01
#define SC_HID_RESERVED 0x00
#define SC_HID_ERROR_ROLL_OVER 0x01
/**
* For HID over AOAv2, only report descriptor is needed.
* For HID, only report descriptor is needed.
*
* The specification is available here:
* <https://www.usb.org/sites/default/files/hid1_11.pdf>
@ -52,7 +47,7 @@
*
* (change vid:pid' to your device's vendor ID and product ID).
*/
static const unsigned char keyboard_report_desc[] = {
const uint8_t SC_HID_KEYBOARD_REPORT_DESC[] = {
// Usage Page (Generic Desktop)
0x05, 0x01,
// Usage (Keyboard)
@ -118,7 +113,7 @@ static const unsigned char keyboard_report_desc[] = {
// Report Size (8)
0x75, 0x08,
// Report Count (6)
0x95, HID_KEYBOARD_MAX_KEYS,
0x95, SC_HID_KEYBOARD_MAX_KEYS,
// Input (Data, Array): Keys
0x81, 0x00,
@ -126,6 +121,9 @@ static const unsigned char keyboard_report_desc[] = {
0xC0
};
const size_t SC_HID_KEYBOARD_REPORT_DESC_LEN =
sizeof(SC_HID_KEYBOARD_REPORT_DESC);
/**
* A keyboard HID event is 8 bytes long:
*
@ -200,51 +198,50 @@ static const unsigned char keyboard_report_desc[] = {
* +---------------+
*/
static unsigned char
sdl_keymod_to_hid_modifiers(uint16_t mod) {
unsigned char modifiers = HID_MODIFIER_NONE;
static void
sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) {
hid_event->size = SC_HID_KEYBOARD_EVENT_SIZE;
uint8_t *data = hid_event->data;
data[SC_HID_KEYBOARD_INDEX_MODS] = SC_HID_MOD_NONE;
data[1] = SC_HID_RESERVED;
memset(&data[SC_HID_KEYBOARD_INDEX_KEYS], 0, SC_HID_KEYBOARD_MAX_KEYS);
}
static uint16_t
sc_hid_mod_from_sdl_keymod(uint16_t mod) {
uint16_t mods = SC_HID_MOD_NONE;
if (mod & SC_MOD_LCTRL) {
modifiers |= HID_MODIFIER_LEFT_CONTROL;
mods |= SC_HID_MOD_LEFT_CONTROL;
}
if (mod & SC_MOD_LSHIFT) {
modifiers |= HID_MODIFIER_LEFT_SHIFT;
mods |= SC_HID_MOD_LEFT_SHIFT;
}
if (mod & SC_MOD_LALT) {
modifiers |= HID_MODIFIER_LEFT_ALT;
mods |= SC_HID_MOD_LEFT_ALT;
}
if (mod & SC_MOD_LGUI) {
modifiers |= HID_MODIFIER_LEFT_GUI;
mods |= SC_HID_MOD_LEFT_GUI;
}
if (mod & SC_MOD_RCTRL) {
modifiers |= HID_MODIFIER_RIGHT_CONTROL;
mods |= SC_HID_MOD_RIGHT_CONTROL;
}
if (mod & SC_MOD_RSHIFT) {
modifiers |= HID_MODIFIER_RIGHT_SHIFT;
mods |= SC_HID_MOD_RIGHT_SHIFT;
}
if (mod & SC_MOD_RALT) {
modifiers |= HID_MODIFIER_RIGHT_ALT;
mods |= SC_HID_MOD_RIGHT_ALT;
}
if (mod & SC_MOD_RGUI) {
modifiers |= HID_MODIFIER_RIGHT_GUI;
mods |= SC_HID_MOD_RIGHT_GUI;
}
return modifiers;
return mods;
}
static bool
sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) {
unsigned char *buffer = malloc(HID_KEYBOARD_EVENT_SIZE);
if (!buffer) {
LOG_OOM();
return false;
}
buffer[HID_KEYBOARD_INDEX_MODIFIER] = HID_MODIFIER_NONE;
buffer[1] = HID_RESERVED;
memset(&buffer[HID_KEYBOARD_INDEX_KEYS], 0, HID_KEYBOARD_MAX_KEYS);
sc_hid_event_init(hid_event, HID_KEYBOARD_ACCESSORY_ID, buffer,
HID_KEYBOARD_EVENT_SIZE);
return true;
void
sc_hid_keyboard_init(struct sc_hid_keyboard *hid) {
memset(hid->keys, false, SC_HID_KEYBOARD_KEYS);
}
static inline bool
@ -252,10 +249,10 @@ scancode_is_modifier(enum sc_scancode scancode) {
return scancode >= SC_SCANCODE_LCTRL && scancode <= SC_SCANCODE_RGUI;
}
static bool
convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
struct sc_hid_event *hid_event,
const struct sc_key_event *event) {
bool
sc_hid_keyboard_event_from_key(struct sc_hid_keyboard *hid,
struct sc_hid_event *hid_event,
const struct sc_key_event *event) {
enum sc_scancode scancode = event->scancode;
assert(scancode >= 0);
@ -267,39 +264,37 @@ convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
return false;
}
if (!sc_hid_keyboard_event_init(hid_event)) {
LOGW("Could not initialize HID keyboard event");
return false;
}
sc_hid_keyboard_event_init(hid_event);
unsigned char modifiers = sdl_keymod_to_hid_modifiers(event->mods_state);
uint16_t mods = sc_hid_mod_from_sdl_keymod(event->mods_state);
if (scancode < SC_HID_KEYBOARD_KEYS) {
// Pressed is true and released is false
kb->keys[scancode] = (event->action == SC_ACTION_DOWN);
hid->keys[scancode] = (event->action == SC_ACTION_DOWN);
LOGV("keys[%02x] = %s", scancode,
kb->keys[scancode] ? "true" : "false");
hid->keys[scancode] ? "true" : "false");
}
hid_event->buffer[HID_KEYBOARD_INDEX_MODIFIER] = modifiers;
hid_event->data[SC_HID_KEYBOARD_INDEX_MODS] = mods;
unsigned char *keys_buffer = &hid_event->buffer[HID_KEYBOARD_INDEX_KEYS];
uint8_t *keys_data = &hid_event->data[SC_HID_KEYBOARD_INDEX_KEYS];
// Re-calculate pressed keys every time
int keys_pressed_count = 0;
for (int i = 0; i < SC_HID_KEYBOARD_KEYS; ++i) {
if (kb->keys[i]) {
if (hid->keys[i]) {
// USB HID protocol says that if keys exceeds report count, a
// phantom state should be reported
if (keys_pressed_count >= HID_KEYBOARD_MAX_KEYS) {
if (keys_pressed_count >= SC_HID_KEYBOARD_MAX_KEYS) {
// Phantom state:
// - Modifiers
// - Reserved
// - ErrorRollOver * HID_MAX_KEYS
memset(keys_buffer, HID_ERROR_ROLL_OVER, HID_KEYBOARD_MAX_KEYS);
memset(keys_data, SC_HID_ERROR_ROLL_OVER,
SC_HID_KEYBOARD_MAX_KEYS);
goto end;
}
keys_buffer[keys_pressed_count] = i;
keys_data[keys_pressed_count] = i;
++keys_pressed_count;
}
}
@ -307,124 +302,32 @@ convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
end:
LOGV("hid keyboard: key %-4s scancode=%02x (%u) mod=%02x",
event->action == SC_ACTION_DOWN ? "down" : "up", event->scancode,
event->scancode, modifiers);
event->scancode, mods);
return true;
}
static bool
push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t mods_state) {
bool
sc_hid_keyboard_event_from_mods(struct sc_hid_event *event,
uint16_t mods_state) {
bool capslock = mods_state & SC_MOD_CAPS;
bool numlock = mods_state & SC_MOD_NUM;
if (!capslock && !numlock) {
// Nothing to do
return true;
}
struct sc_hid_event hid_event;
if (!sc_hid_keyboard_event_init(&hid_event)) {
LOGW("Could not initialize HID keyboard event");
return false;
}
sc_hid_keyboard_event_init(event);
unsigned i = 0;
if (capslock) {
hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK;
event->data[SC_HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK;
++i;
}
if (numlock) {
hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_NUMLOCK;
event->data[SC_HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_NUMLOCK;
++i;
}
if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) {
sc_hid_event_destroy(&hid_event);
LOGW("Could request HID event");
return false;
}
LOGD("HID keyboard state synchronized");
return true;
}
static void
sc_key_processor_process_key(struct sc_key_processor *kp,
const struct sc_key_event *event,
uint64_t ack_to_wait) {
if (event->repeat) {
// In USB HID protocol, key repeat is handled by the host (Android), so
// just ignore key repeat here.
return;
}
struct sc_hid_keyboard *kb = DOWNCAST(kp);
struct sc_hid_event hid_event;
// Not all keys are supported, just ignore unsupported keys
if (convert_hid_keyboard_event(kb, &hid_event, event)) {
if (!kb->mod_lock_synchronized) {
// Inject CAPSLOCK and/or NUMLOCK if necessary to synchronize
// keyboard state
if (push_mod_lock_state(kb, event->mods_state)) {
kb->mod_lock_synchronized = true;
}
}
if (ack_to_wait) {
// Ctrl+v is pressed, so clipboard synchronization has been
// requested. Wait until clipboard synchronization is acknowledged
// by the server, otherwise it could paste the old clipboard
// content.
hid_event.ack_to_wait = ack_to_wait;
}
if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) {
sc_hid_event_destroy(&hid_event);
LOGW("Could request HID event");
}
}
}
bool
sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) {
kb->aoa = aoa;
bool ok = sc_aoa_setup_hid(aoa, HID_KEYBOARD_ACCESSORY_ID,
keyboard_report_desc,
ARRAY_LEN(keyboard_report_desc));
if (!ok) {
LOGW("Register HID keyboard failed");
return false;
}
// Reset all states
memset(kb->keys, false, SC_HID_KEYBOARD_KEYS);
kb->mod_lock_synchronized = false;
static const struct sc_key_processor_ops ops = {
.process_key = sc_key_processor_process_key,
// Never forward text input via HID (all the keys are injected
// separately)
.process_text = NULL,
};
// Clipboard synchronization is requested over the control socket, while HID
// events are sent over AOA, so it must wait for clipboard synchronization
// to be acknowledged by the device before injecting Ctrl+v.
kb->key_processor.async_paste = true;
kb->key_processor.ops = &ops;
return true;
}
void
sc_hid_keyboard_destroy(struct sc_hid_keyboard *kb) {
// Unregister HID keyboard so the soft keyboard shows again on Android
bool ok = sc_aoa_unregister_hid(kb->aoa, HID_KEYBOARD_ACCESSORY_ID);
if (!ok) {
LOGW("Could not unregister HID keyboard");
}
}

@ -5,8 +5,8 @@
#include <stdbool.h>
#include "aoa_hid.h"
#include "trait/key_processor.h"
#include "hid/hid_event.h"
#include "input_events.h"
// See "SDL2/SDL_scancode.h".
// Maybe SDL_Keycode is used by most people, but SDL_Scancode is taken from USB
@ -14,6 +14,9 @@
// 0x65 is Application, typically AT-101 Keyboard ends here.
#define SC_HID_KEYBOARD_KEYS 0x66
extern const uint8_t SC_HID_KEYBOARD_REPORT_DESC[];
extern const size_t SC_HID_KEYBOARD_REPORT_DESC_LEN;
/**
* HID keyboard events are sequence-based, every time keyboard state changes
* it sends an array of currently pressed keys, the host is responsible for
@ -27,18 +30,19 @@
* phantom state.
*/
struct sc_hid_keyboard {
struct sc_key_processor key_processor; // key processor trait
struct sc_aoa *aoa;
bool keys[SC_HID_KEYBOARD_KEYS];
bool mod_lock_synchronized;
};
void
sc_hid_keyboard_init(struct sc_hid_keyboard *hid);
bool
sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa);
sc_hid_keyboard_event_from_key(struct sc_hid_keyboard *hid,
struct sc_hid_event *hid_event,
const struct sc_key_event *event);
void
sc_hid_keyboard_destroy(struct sc_hid_keyboard *kb);
bool
sc_hid_keyboard_event_from_mods(struct sc_hid_event *event,
uint16_t mods_state);
#endif

@ -0,0 +1,192 @@
#include "hid_mouse.h"
// 1 byte for buttons + padding, 1 byte for X position, 1 byte for Y position,
// 1 byte for wheel motion
#define HID_MOUSE_EVENT_SIZE 4
/**
* Mouse descriptor from the specification:
* <https://www.usb.org/sites/default/files/hid1_11.pdf>
*
* Appendix E (p71): §E.10 Report Descriptor (Mouse)
*
* The usage tags (like Wheel) are listed in "HID Usage Tables":
* <https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf>
* §4 Generic Desktop Page (0x01) (p26)
*/
const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
// Usage Page (Generic Desktop)
0x05, 0x01,
// Usage (Mouse)
0x09, 0x02,
// Collection (Application)
0xA1, 0x01,
// Usage (Pointer)
0x09, 0x01,
// Collection (Physical)
0xA1, 0x00,
// Usage Page (Buttons)
0x05, 0x09,
// Usage Minimum (1)
0x19, 0x01,
// Usage Maximum (5)
0x29, 0x05,
// Logical Minimum (0)
0x15, 0x00,
// Logical Maximum (1)
0x25, 0x01,
// Report Count (5)
0x95, 0x05,
// Report Size (1)
0x75, 0x01,
// Input (Data, Variable, Absolute): 5 buttons bits
0x81, 0x02,
// Report Count (1)
0x95, 0x01,
// Report Size (3)
0x75, 0x03,
// Input (Constant): 3 bits padding
0x81, 0x01,
// Usage Page (Generic Desktop)
0x05, 0x01,
// Usage (X)
0x09, 0x30,
// Usage (Y)
0x09, 0x31,
// Usage (Wheel)
0x09, 0x38,
// Local Minimum (-127)
0x15, 0x81,
// Local Maximum (127)
0x25, 0x7F,
// Report Size (8)
0x75, 0x08,
// Report Count (3)
0x95, 0x03,
// Input (Data, Variable, Relative): 3 position bytes (X, Y, Wheel)
0x81, 0x06,
// End Collection
0xC0,
// End Collection
0xC0,
};
const size_t SC_HID_MOUSE_REPORT_DESC_LEN =
sizeof(SC_HID_MOUSE_REPORT_DESC);
/**
* A mouse HID event is 4 bytes long:
*
* - byte 0: buttons state
* - byte 1: relative x motion (signed byte from -127 to 127)
* - byte 2: relative y motion (signed byte from -127 to 127)
* - byte 3: wheel motion (-1, 0 or 1)
*
* 7 6 5 4 3 2 1 0
* +---------------+
* byte 0: |0 0 0 . . . . .| buttons state
* +---------------+
* ^ ^ ^ ^ ^
* | | | | `- left button
* | | | `--- right button
* | | `----- middle button
* | `------- button 4
* `--------- button 5
*
* +---------------+
* byte 1: |. . . . . . . .| relative x motion
* +---------------+
* byte 2: |. . . . . . . .| relative y motion
* +---------------+
* byte 3: |. . . . . . . .| wheel motion
* +---------------+
*
* As an example, here is the report for a motion of (x=5, y=-4) with left
* button pressed:
*
* +---------------+
* |0 0 0 0 0 0 0 1| left button pressed
* +---------------+
* |0 0 0 0 0 1 0 1| horizontal motion (x = 5)
* +---------------+
* |1 1 1 1 1 1 0 0| relative y motion (y = -4)
* +---------------+
* |0 0 0 0 0 0 0 0| wheel motion
* +---------------+
*/
static void
sc_hid_mouse_event_init(struct sc_hid_event *hid_event) {
hid_event->size = HID_MOUSE_EVENT_SIZE;
// Leave hid_event->data uninitialized, it will be fully initialized by
// callers
}
static uint8_t
sc_hid_buttons_from_buttons_state(uint8_t buttons_state) {
uint8_t c = 0;
if (buttons_state & SC_MOUSE_BUTTON_LEFT) {
c |= 1 << 0;
}
if (buttons_state & SC_MOUSE_BUTTON_RIGHT) {
c |= 1 << 1;
}
if (buttons_state & SC_MOUSE_BUTTON_MIDDLE) {
c |= 1 << 2;
}
if (buttons_state & SC_MOUSE_BUTTON_X1) {
c |= 1 << 3;
}
if (buttons_state & SC_MOUSE_BUTTON_X2) {
c |= 1 << 4;
}
return c;
}
void
sc_hid_mouse_event_from_motion(struct sc_hid_event *hid_event,
const struct sc_mouse_motion_event *event) {
sc_hid_mouse_event_init(hid_event);
uint8_t *data = hid_event->data;
data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state);
data[1] = CLAMP(event->xrel, -127, 127);
data[2] = CLAMP(event->yrel, -127, 127);
data[3] = 0; // wheel coordinates only used for scrolling
}
void
sc_hid_mouse_event_from_click(struct sc_hid_event *hid_event,
const struct sc_mouse_click_event *event) {
sc_hid_mouse_event_init(hid_event);
uint8_t *data = hid_event->data;
data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state);
data[1] = 0; // no x motion
data[2] = 0; // no y motion
data[3] = 0; // wheel coordinates only used for scrolling
}
void
sc_hid_mouse_event_from_scroll(struct sc_hid_event *hid_event,
const struct sc_mouse_scroll_event *event) {
sc_hid_mouse_event_init(hid_event);
uint8_t *data = hid_event->data;
data[0] = 0; // buttons state irrelevant (and unknown)
data[1] = 0; // no x motion
data[2] = 0; // no y motion
// In practice, vscroll is always -1, 0 or 1, but in theory other values
// are possible
data[3] = CLAMP(event->vscroll, -127, 127);
// Horizontal scrolling ignored
}

@ -0,0 +1,26 @@
#ifndef SC_HID_MOUSE_H
#define SC_HID_MOUSE_H
#endif
#include "common.h"
#include <stdbool.h>
#include "hid/hid_event.h"
#include "input_events.h"
extern const uint8_t SC_HID_MOUSE_REPORT_DESC[];
extern const size_t SC_HID_MOUSE_REPORT_DESC_LEN;
void
sc_hid_mouse_event_from_motion(struct sc_hid_event *hid_event,
const struct sc_mouse_motion_event *event);
void
sc_hid_mouse_event_from_click(struct sc_hid_event *hid_event,
const struct sc_mouse_click_event *event);
void
sc_hid_mouse_event_from_scroll(struct sc_hid_event *hid_event,
const struct sc_mouse_scroll_event *event);

@ -69,7 +69,7 @@ decode_image(const char *path) {
}
if (avformat_open_input(&ctx, path, NULL, NULL) < 0) {
LOGE("Could not open image codec: %s", path);
LOGE("Could not open icon image: %s", path);
goto free_ctx;
}
@ -271,7 +271,7 @@ error:
}
SDL_Surface *
scrcpy_icon_load() {
scrcpy_icon_load(void) {
char *icon_path = get_icon_path();
if (!icon_path) {
return NULL;

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

@ -353,18 +353,20 @@ struct sc_mouse_click_event {
struct sc_position position;
enum sc_action action;
enum sc_mouse_button button;
uint64_t pointer_id;
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
};
struct sc_mouse_scroll_event {
struct sc_position position;
int32_t hscroll;
int32_t vscroll;
float hscroll;
float vscroll;
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
};
struct sc_mouse_motion_event {
struct sc_position position;
uint64_t pointer_id;
int32_t xrel;
int32_t yrel;
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values

@ -52,8 +52,11 @@ is_shortcut_mod(struct sc_input_manager *im, uint16_t sdl_mod) {
void
sc_input_manager_init(struct sc_input_manager *im,
const struct sc_input_manager_params *params) {
assert(!params->controller || (params->kp && params->kp->ops));
assert(!params->controller || (params->mp && params->mp->ops));
// A key/mouse processor may not be present if there is no controller
assert((!params->kp && !params->mp) || params->controller);
// A processor must have ops initialized
assert(!params->kp || params->kp->ops);
assert(!params->mp || params->mp->ops);
im->controller = params->controller;
im->fp = params->fp;
@ -76,6 +79,8 @@ sc_input_manager_init(struct sc_input_manager *im,
im->sdl_shortcut_mods.count = shortcut_mods->count;
im->vfinger_down = false;
im->vfinger_invert_x = false;
im->vfinger_invert_y = false;
im->last_keycode = SDLK_UNKNOWN;
im->last_mod = 0;
@ -85,8 +90,10 @@ sc_input_manager_init(struct sc_input_manager *im,
}
static void
send_keycode(struct sc_controller *controller, enum android_keycode keycode,
send_keycode(struct sc_input_manager *im, enum android_keycode keycode,
enum sc_action action, const char *name) {
assert(im->controller && im->kp);
// send DOWN event
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_INJECT_KEYCODE;
@ -97,100 +104,109 @@ send_keycode(struct sc_controller *controller, enum android_keycode keycode,
msg.inject_keycode.metastate = 0;
msg.inject_keycode.repeat = 0;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject %s'", name);
}
}
static inline void
action_home(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_HOME, action, "HOME");
action_home(struct sc_input_manager *im, enum sc_action action) {
send_keycode(im, AKEYCODE_HOME, action, "HOME");
}
static inline void
action_back(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_BACK, action, "BACK");
action_back(struct sc_input_manager *im, enum sc_action action) {
send_keycode(im, AKEYCODE_BACK, action, "BACK");
}
static inline void
action_app_switch(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_APP_SWITCH, action, "APP_SWITCH");
action_app_switch(struct sc_input_manager *im, enum sc_action action) {
send_keycode(im, AKEYCODE_APP_SWITCH, action, "APP_SWITCH");
}
static inline void
action_power(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_POWER, action, "POWER");
action_power(struct sc_input_manager *im, enum sc_action action) {
send_keycode(im, AKEYCODE_POWER, action, "POWER");
}
static inline void
action_volume_up(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_VOLUME_UP, action, "VOLUME_UP");
action_volume_up(struct sc_input_manager *im, enum sc_action action) {
send_keycode(im, AKEYCODE_VOLUME_UP, action, "VOLUME_UP");
}
static inline void
action_volume_down(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_VOLUME_DOWN, action, "VOLUME_DOWN");
action_volume_down(struct sc_input_manager *im, enum sc_action action) {
send_keycode(im, AKEYCODE_VOLUME_DOWN, action, "VOLUME_DOWN");
}
static inline void
action_menu(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_MENU, action, "MENU");
action_menu(struct sc_input_manager *im, enum sc_action action) {
send_keycode(im, AKEYCODE_MENU, action, "MENU");
}
// turn the screen on if it was off, press BACK otherwise
// If the screen is off, it is turned on only on ACTION_DOWN
static void
press_back_or_turn_screen_on(struct sc_controller *controller,
press_back_or_turn_screen_on(struct sc_input_manager *im,
enum sc_action action) {
assert(im->controller && im->kp);
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON;
msg.back_or_screen_on.action = action == SC_ACTION_DOWN
? AKEY_EVENT_ACTION_DOWN
: AKEY_EVENT_ACTION_UP;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'press back or turn screen on'");
}
}
static void
expand_notification_panel(struct sc_controller *controller) {
expand_notification_panel(struct sc_input_manager *im) {
assert(im->controller);
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'expand notification panel'");
}
}
static void
expand_settings_panel(struct sc_controller *controller) {
expand_settings_panel(struct sc_input_manager *im) {
assert(im->controller);
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'expand settings panel'");
}
}
static void
collapse_panels(struct sc_controller *controller) {
collapse_panels(struct sc_input_manager *im) {
assert(im->controller);
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'collapse notification panel'");
}
}
static bool
get_device_clipboard(struct sc_controller *controller,
enum sc_copy_key copy_key) {
get_device_clipboard(struct sc_input_manager *im, enum sc_copy_key copy_key) {
assert(im->controller && im->kp);
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_GET_CLIPBOARD;
msg.get_clipboard.copy_key = copy_key;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'get device clipboard'");
return false;
}
@ -199,8 +215,10 @@ get_device_clipboard(struct sc_controller *controller,
}
static bool
set_device_clipboard(struct sc_controller *controller, bool paste,
set_device_clipboard(struct sc_input_manager *im, bool paste,
uint64_t sequence) {
assert(im->controller && im->kp);
char *text = SDL_GetClipboardText();
if (!text) {
LOGW("Could not get clipboard text: %s", SDL_GetError());
@ -220,7 +238,7 @@ set_device_clipboard(struct sc_controller *controller, bool paste,
msg.set_clipboard.text = text_dup;
msg.set_clipboard.paste = paste;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
free(text_dup);
LOGW("Could not request 'set device clipboard'");
return false;
@ -230,19 +248,23 @@ set_device_clipboard(struct sc_controller *controller, bool paste,
}
static void
set_screen_power_mode(struct sc_controller *controller,
set_screen_power_mode(struct sc_input_manager *im,
enum sc_screen_power_mode mode) {
assert(im->controller);
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
msg.set_screen_power_mode.mode = mode;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'set screen power mode'");
}
}
static void
switch_fps_counter_state(struct sc_fps_counter *fps_counter) {
switch_fps_counter_state(struct sc_input_manager *im) {
struct sc_fps_counter *fps_counter = &im->screen->fps_counter;
// the started state can only be written from the current thread, so there
// is no ToCToU issue
if (sc_fps_counter_is_started(fps_counter)) {
@ -254,7 +276,9 @@ switch_fps_counter_state(struct sc_fps_counter *fps_counter) {
}
static void
clipboard_paste(struct sc_controller *controller) {
clipboard_paste(struct sc_input_manager *im) {
assert(im->controller && im->kp);
char *text = SDL_GetClipboardText();
if (!text) {
LOGW("Could not get clipboard text: %s", SDL_GetError());
@ -276,32 +300,43 @@ clipboard_paste(struct sc_controller *controller) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = text_dup;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
free(text_dup);
LOGW("Could not request 'paste clipboard'");
}
}
static void
rotate_device(struct sc_controller *controller) {
rotate_device(struct sc_input_manager *im) {
assert(im->controller);
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_ROTATE_DEVICE;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request device rotation");
}
}
static void
rotate_client_left(struct sc_screen *screen) {
unsigned new_rotation = (screen->rotation + 1) % 4;
sc_screen_set_rotation(screen, new_rotation);
open_hard_keyboard_settings(struct sc_input_manager *im) {
assert(im->controller);
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS;
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request opening hard keyboard settings");
}
}
static void
rotate_client_right(struct sc_screen *screen) {
unsigned new_rotation = (screen->rotation + 3) % 4;
sc_screen_set_rotation(screen, new_rotation);
apply_orientation_transform(struct sc_input_manager *im,
enum sc_orientation transform) {
struct sc_screen *screen = im->screen;
enum sc_orientation new_orientation =
sc_orientation_apply(screen->orientation, transform);
sc_screen_set_orientation(screen, new_orientation);
}
static void
@ -335,8 +370,11 @@ simulate_virtual_finger(struct sc_input_manager *im,
msg.inject_touch_event.action = action;
msg.inject_touch_event.position.screen_size = im->screen->frame_size;
msg.inject_touch_event.position.point = point;
msg.inject_touch_event.pointer_id = POINTER_ID_VIRTUAL_FINGER;
msg.inject_touch_event.pointer_id =
im->forward_all_clicks ? POINTER_ID_VIRTUAL_MOUSE
: POINTER_ID_VIRTUAL_FINGER;
msg.inject_touch_event.pressure = up ? 0.0f : 1.0f;
msg.inject_touch_event.action_button = 0;
msg.inject_touch_event.buttons = 0;
if (!sc_controller_push_msg(im->controller, &msg)) {
@ -348,9 +386,14 @@ simulate_virtual_finger(struct sc_input_manager *im,
}
static struct sc_point
inverse_point(struct sc_point point, struct sc_size size) {
point.x = size.width - point.x;
point.y = size.height - point.y;
inverse_point(struct sc_point point, struct sc_size size,
bool invert_x, bool invert_y) {
if (invert_x) {
point.x = size.width - point.x;
}
if (invert_y) {
point.y = size.height - point.y;
}
return point;
}
@ -358,7 +401,7 @@ static void
sc_input_manager_process_key(struct sc_input_manager *im,
const SDL_KeyboardEvent *event) {
// controller is NULL if --no-control is requested
struct sc_controller *controller = im->controller;
bool control = im->controller;
SDL_Keycode keycode = event->keysym.sym;
uint16_t mod = event->keysym.mod;
@ -384,81 +427,102 @@ sc_input_manager_process_key(struct sc_input_manager *im,
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
switch (keycode) {
case SDLK_h:
if (controller && !shift && !repeat) {
action_home(controller, action);
if (im->kp && !shift && !repeat) {
action_home(im, action);
}
return;
case SDLK_b: // fall-through
case SDLK_BACKSPACE:
if (controller && !shift && !repeat) {
action_back(controller, action);
if (im->kp && !shift && !repeat) {
action_back(im, action);
}
return;
case SDLK_s:
if (controller && !shift && !repeat) {
action_app_switch(controller, action);
if (im->kp && !shift && !repeat) {
action_app_switch(im, action);
}
return;
case SDLK_m:
if (controller && !shift && !repeat) {
action_menu(controller, action);
if (im->kp && !shift && !repeat) {
action_menu(im, action);
}
return;
case SDLK_p:
if (controller && !shift && !repeat) {
action_power(controller, action);
if (im->kp && !shift && !repeat) {
action_power(im, action);
}
return;
case SDLK_o:
if (controller && !repeat && down) {
if (control && !repeat && down) {
enum sc_screen_power_mode mode = shift
? SC_SCREEN_POWER_MODE_NORMAL
: SC_SCREEN_POWER_MODE_OFF;
set_screen_power_mode(controller, mode);
set_screen_power_mode(im, mode);
}
return;
case SDLK_DOWN:
if (controller && !shift) {
if (shift) {
if (!repeat & down) {
apply_orientation_transform(im,
SC_ORIENTATION_FLIP_180);
}
} else if (im->kp) {
// forward repeated events
action_volume_down(controller, action);
action_volume_down(im, action);
}
return;
case SDLK_UP:
if (controller && !shift) {
if (shift) {
if (!repeat & down) {
apply_orientation_transform(im,
SC_ORIENTATION_FLIP_180);
}
} else if (im->kp) {
// forward repeated events
action_volume_up(controller, action);
action_volume_up(im, action);
}
return;
case SDLK_LEFT:
if (!shift && !repeat && down) {
rotate_client_left(im->screen);
if (!repeat && down) {
if (shift) {
apply_orientation_transform(im,
SC_ORIENTATION_FLIP_0);
} else {
apply_orientation_transform(im,
SC_ORIENTATION_270);
}
}
return;
case SDLK_RIGHT:
if (!shift && !repeat && down) {
rotate_client_right(im->screen);
if (!repeat && down) {
if (shift) {
apply_orientation_transform(im,
SC_ORIENTATION_FLIP_0);
} else {
apply_orientation_transform(im,
SC_ORIENTATION_90);
}
}
return;
case SDLK_c:
if (controller && !shift && !repeat && down) {
get_device_clipboard(controller, SC_COPY_KEY_COPY);
if (im->kp && !shift && !repeat && down) {
get_device_clipboard(im, SC_COPY_KEY_COPY);
}
return;
case SDLK_x:
if (controller && !shift && !repeat && down) {
get_device_clipboard(controller, SC_COPY_KEY_CUT);
if (im->kp && !shift && !repeat && down) {
get_device_clipboard(im, SC_COPY_KEY_CUT);
}
return;
case SDLK_v:
if (controller && !repeat && down) {
if (im->kp && !repeat && down) {
if (shift || im->legacy_paste) {
// inject the text as input events
clipboard_paste(controller);
clipboard_paste(im);
} else {
// store the text in the device clipboard and paste,
// without requesting an acknowledgment
set_device_clipboard(controller, true,
SC_SEQUENCE_INVALID);
set_device_clipboard(im, true, SC_SEQUENCE_INVALID);
}
}
return;
@ -479,23 +543,30 @@ sc_input_manager_process_key(struct sc_input_manager *im,
return;
case SDLK_i:
if (!shift && !repeat && down) {
switch_fps_counter_state(&im->screen->fps_counter);
switch_fps_counter_state(im);
}
return;
case SDLK_n:
if (controller && !repeat && down) {
if (control && !repeat && down) {
if (shift) {
collapse_panels(controller);
collapse_panels(im);
} else if (im->key_repeat == 0) {
expand_notification_panel(controller);
expand_notification_panel(im);
} else {
expand_settings_panel(controller);
expand_settings_panel(im);
}
}
return;
case SDLK_r:
if (controller && !shift && !repeat && down) {
rotate_device(controller);
if (control && !shift && !repeat && down) {
rotate_device(im);
}
return;
case SDLK_k:
if (control && !shift && !repeat && down
&& im->kp && im->kp->hid) {
// Only if the current keyboard is hid
open_hard_keyboard_settings(im);
}
return;
}
@ -503,7 +574,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
return;
}
if (!controller) {
if (!im->kp) {
return;
}
@ -512,7 +583,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
if (im->clipboard_autosync && is_ctrl_v) {
if (im->legacy_paste) {
// inject the text as input events
clipboard_paste(controller);
clipboard_paste(im);
return;
}
@ -522,7 +593,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
// Synchronize the computer clipboard to the device clipboard before
// sending Ctrl+v, to allow seamless copy-paste.
bool ok = set_device_clipboard(controller, false, sequence);
bool ok = set_device_clipboard(im, false, sequence);
if (!ok) {
LOGW("Clipboard could not be synchronized, Ctrl+v not injected");
return;
@ -564,6 +635,8 @@ sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
event->x,
event->y),
},
.pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE
: POINTER_ID_GENERIC_FINGER,
.xrel = event->xrel,
.yrel = event->yrel,
.buttons_state =
@ -582,7 +655,9 @@ sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
struct sc_point mouse =
sc_screen_convert_window_to_frame_coords(im->screen, event->x,
event->y);
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size);
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size,
im->vfinger_invert_x,
im->vfinger_invert_y);
simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger);
}
}
@ -620,7 +695,7 @@ sc_input_manager_process_touch(struct sc_input_manager *im,
static void
sc_input_manager_process_mouse_button(struct sc_input_manager *im,
const SDL_MouseButtonEvent *event) {
struct sc_controller *controller = im->controller;
bool control = im->controller;
if (event->which == SDL_TOUCH_MOUSEID) {
// simulated from touch events, so it's a duplicate
@ -629,27 +704,27 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
bool down = event->type == SDL_MOUSEBUTTONDOWN;
if (!im->forward_all_clicks) {
if (controller) {
if (control) {
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
if (event->button == SDL_BUTTON_X1) {
action_app_switch(controller, action);
if (im->kp && event->button == SDL_BUTTON_X1) {
action_app_switch(im, action);
return;
}
if (event->button == SDL_BUTTON_X2 && down) {
if (event->clicks < 2) {
expand_notification_panel(controller);
expand_notification_panel(im);
} else {
expand_settings_panel(controller);
expand_settings_panel(im);
}
return;
}
if (event->button == SDL_BUTTON_RIGHT) {
press_back_or_turn_screen_on(controller, action);
if (im->kp && event->button == SDL_BUTTON_RIGHT) {
press_back_or_turn_screen_on(im, action);
return;
}
if (event->button == SDL_BUTTON_MIDDLE) {
action_home(controller, action);
if (im->kp && event->button == SDL_BUTTON_MIDDLE) {
action_home(im, action);
return;
}
}
@ -672,7 +747,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
// otherwise, send the click event to the device
}
if (!controller) {
if (!im->mp) {
return;
}
@ -687,6 +762,8 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
},
.action = sc_action_from_sdl_mousebutton_type(event->type),
.button = sc_mouse_button_from_sdl(event->button),
.pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE
: POINTER_ID_GENERIC_FINGER,
.buttons_state =
sc_mouse_buttons_state_from_sdl(sdl_buttons_state,
im->forward_all_clicks),
@ -701,7 +778,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
return;
}
// Pinch-to-zoom simulation.
// Pinch-to-zoom, rotate and tilt simulation.
//
// If Ctrl is hold when the left-click button is pressed, then
// pinch-to-zoom mode is enabled: on every mouse event until the left-click
@ -710,14 +787,29 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
//
// In other words, the center of the rotation/scaling is the center of the
// screen.
#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL))
//
// To simulate a tilt gesture (a vertical slide with two fingers), Shift
// can be used instead of Ctrl. The "virtual finger" has a position
// inverted with respect to the vertical axis of symmetry in the middle of
// the screen.
const SDL_Keymod keymod = SDL_GetModState();
const bool ctrl_pressed = keymod & KMOD_CTRL;
const bool shift_pressed = keymod & KMOD_SHIFT;
if (event->button == SDL_BUTTON_LEFT &&
((down && !im->vfinger_down && CTRL_PRESSED) ||
((down && !im->vfinger_down &&
((ctrl_pressed && !shift_pressed) ||
(!ctrl_pressed && shift_pressed))) ||
(!down && im->vfinger_down))) {
struct sc_point mouse =
sc_screen_convert_window_to_frame_coords(im->screen, event->x,
event->y);
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size);
if (down) {
im->vfinger_invert_x = ctrl_pressed || shift_pressed;
im->vfinger_invert_y = ctrl_pressed;
}
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size,
im->vfinger_invert_x,
im->vfinger_invert_y);
enum android_motionevent_action action = down
? AMOTION_EVENT_ACTION_DOWN
: AMOTION_EVENT_ACTION_UP;
@ -747,8 +839,13 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
.point = sc_screen_convert_window_to_frame_coords(im->screen,
mouse_x, mouse_y),
},
.hscroll = event->x,
.vscroll = event->y,
#if SDL_VERSION_ATLEAST(2, 0, 18)
.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 =
sc_mouse_buttons_state_from_sdl(buttons, im->forward_all_clicks),
};
@ -785,11 +882,12 @@ sc_input_manager_process_file(struct sc_input_manager *im,
}
void
sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event) {
sc_input_manager_handle_event(struct sc_input_manager *im,
const SDL_Event *event) {
bool control = im->controller;
switch (event->type) {
case SDL_TEXTINPUT:
if (!control) {
if (!im->kp) {
break;
}
sc_input_manager_process_text_input(im, &event->text);
@ -801,13 +899,13 @@ sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event) {
sc_input_manager_process_key(im, &event->key);
break;
case SDL_MOUSEMOTION:
if (!control) {
if (!im->mp) {
break;
}
sc_input_manager_process_mouse_motion(im, &event->motion);
break;
case SDL_MOUSEWHEEL:
if (!control) {
if (!im->mp) {
break;
}
sc_input_manager_process_mouse_wheel(im, &event->wheel);
@ -821,7 +919,7 @@ sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event) {
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
if (!control) {
if (!im->mp) {
break;
}
sc_input_manager_process_touch(im, &event->tfinger);

@ -1,5 +1,5 @@
#ifndef INPUTMANAGER_H
#define INPUTMANAGER_H
#ifndef SC_INPUTMANAGER_H
#define SC_INPUTMANAGER_H
#include "common.h"
@ -32,6 +32,8 @@ struct sc_input_manager {
} sdl_shortcut_mods;
bool vfinger_down;
bool vfinger_invert_x;
bool vfinger_invert_y;
// Tracks the number of identical consecutive shortcut key down events.
// Not to be confused with event->repeat, which counts the number of
@ -61,6 +63,7 @@ sc_input_manager_init(struct sc_input_manager *im,
const struct sc_input_manager_params *params);
void
sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event);
sc_input_manager_handle_event(struct sc_input_manager *im,
const SDL_Event *event);
#endif

@ -1,4 +1,4 @@
#include "keyboard_inject.h"
#include "keyboard_sdk.h"
#include <assert.h>
@ -9,8 +9,8 @@
#include "util/intmap.h"
#include "util/log.h"
/** Downcast key processor to sc_keyboard_inject */
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_inject, key_processor)
/** Downcast key processor to sc_keyboard_sdk */
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_sdk, key_processor)
static enum android_keyevent_action
convert_keycode_action(enum sc_action action) {
@ -271,20 +271,20 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
// is set before injecting Ctrl+v.
(void) ack_to_wait;
struct sc_keyboard_inject *ki = DOWNCAST(kp);
struct sc_keyboard_sdk *kb = DOWNCAST(kp);
if (event->repeat) {
if (!ki->forward_key_repeat) {
if (!kb->forward_key_repeat) {
return;
}
++ki->repeat;
++kb->repeat;
} else {
ki->repeat = 0;
kb->repeat = 0;
}
struct sc_control_msg msg;
if (convert_input_key(event, &msg, ki->key_inject_mode, ki->repeat)) {
if (!sc_controller_push_msg(ki->controller, &msg)) {
if (convert_input_key(event, &msg, kb->key_inject_mode, kb->repeat)) {
if (!sc_controller_push_msg(kb->controller, &msg)) {
LOGW("Could not request 'inject keycode'");
}
}
@ -293,14 +293,14 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
static void
sc_key_processor_process_text(struct sc_key_processor *kp,
const struct sc_text_event *event) {
struct sc_keyboard_inject *ki = DOWNCAST(kp);
struct sc_keyboard_sdk *kb = DOWNCAST(kp);
if (ki->key_inject_mode == SC_KEY_INJECT_MODE_RAW) {
if (kb->key_inject_mode == SC_KEY_INJECT_MODE_RAW) {
// Never inject text events
return;
}
if (ki->key_inject_mode == SC_KEY_INJECT_MODE_MIXED) {
if (kb->key_inject_mode == SC_KEY_INJECT_MODE_MIXED) {
char c = event->text[0];
if (isalpha(c) || c == ' ') {
assert(event->text[1] == '\0');
@ -316,22 +316,22 @@ sc_key_processor_process_text(struct sc_key_processor *kp,
LOGW("Could not strdup input text");
return;
}
if (!sc_controller_push_msg(ki->controller, &msg)) {
if (!sc_controller_push_msg(kb->controller, &msg)) {
free(msg.inject_text.text);
LOGW("Could not request 'inject text'");
}
}
void
sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
struct sc_controller *controller,
enum sc_key_inject_mode key_inject_mode,
bool forward_key_repeat) {
ki->controller = controller;
ki->key_inject_mode = key_inject_mode;
ki->forward_key_repeat = forward_key_repeat;
sc_keyboard_sdk_init(struct sc_keyboard_sdk *kb,
struct sc_controller *controller,
enum sc_key_inject_mode key_inject_mode,
bool forward_key_repeat) {
kb->controller = controller;
kb->key_inject_mode = key_inject_mode;
kb->forward_key_repeat = forward_key_repeat;
ki->repeat = 0;
kb->repeat = 0;
static const struct sc_key_processor_ops ops = {
.process_key = sc_key_processor_process_key,
@ -339,6 +339,7 @@ sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
};
// Key injection and clipboard synchronization are serialized
ki->key_processor.async_paste = false;
ki->key_processor.ops = &ops;
kb->key_processor.async_paste = false;
kb->key_processor.hid = false;
kb->key_processor.ops = &ops;
}

@ -1,5 +1,5 @@
#ifndef SC_KEYBOARD_INJECT_H
#define SC_KEYBOARD_INJECT_H
#ifndef SC_KEYBOARD_SDK_H
#define SC_KEYBOARD_SDK_H
#include "common.h"
@ -9,7 +9,7 @@
#include "options.h"
#include "trait/key_processor.h"
struct sc_keyboard_inject {
struct sc_keyboard_sdk {
struct sc_key_processor key_processor; // key processor trait
struct sc_controller *controller;
@ -23,9 +23,9 @@ struct sc_keyboard_inject {
};
void
sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
struct sc_controller *controller,
enum sc_key_inject_mode key_inject_mode,
bool forward_key_repeat);
sc_keyboard_sdk_init(struct sc_keyboard_sdk *kb,
struct sc_controller *controller,
enum sc_key_inject_mode key_inject_mode,
bool forward_key_repeat);
#endif

@ -15,11 +15,17 @@
#include "scrcpy.h"
#include "usb/scrcpy_otg.h"
#include "util/log.h"
#include "util/net.h"
#include "version.h"
int
main(int argc, char *argv[]) {
#ifdef __WINDOWS__
#ifdef _WIN32
#include <windows.h>
#include "util/str.h"
#endif
static int
main_scrcpy(int argc, char *argv[]) {
#ifdef _WIN32
// disable buffering, we want logs immediately
// even line buffering (setvbuf() with mode _IOLBF) is not sufficient
setbuf(stdout, NULL);
@ -33,26 +39,32 @@ main(int argc, char *argv[]) {
.opts = scrcpy_options_default,
.help = false,
.version = false,
.pause_on_exit = SC_PAUSE_ON_EXIT_FALSE,
};
#ifndef NDEBUG
args.opts.log_level = SC_LOG_LEVEL_DEBUG;
#endif
enum scrcpy_exit_code ret;
if (!scrcpy_parse_args(&args, argc, argv)) {
return 1;
ret = SCRCPY_EXIT_FAILURE;
goto end;
}
sc_set_log_level(args.opts.log_level);
if (args.help) {
scrcpy_print_usage(argv[0]);
return 0;
ret = SCRCPY_EXIT_SUCCESS;
goto end;
}
if (args.version) {
scrcpy_print_version();
return 0;
ret = SCRCPY_EXIT_SUCCESS;
goto end;
}
#ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL
@ -65,18 +77,75 @@ main(int argc, char *argv[]) {
}
#endif
if (avformat_network_init()) {
return 1;
if (!net_init()) {
ret = SCRCPY_EXIT_FAILURE;
goto end;
}
sc_log_configure();
#ifdef HAVE_USB
bool ok = args.opts.otg ? scrcpy_otg(&args.opts)
: scrcpy(&args.opts);
ret = args.opts.otg ? scrcpy_otg(&args.opts) : scrcpy(&args.opts);
#else
bool ok = scrcpy(&args.opts);
ret = scrcpy(&args.opts);
#endif
avformat_network_deinit(); // ignore failure
end:
if (args.pause_on_exit == SC_PAUSE_ON_EXIT_TRUE ||
(args.pause_on_exit == SC_PAUSE_ON_EXIT_IF_ERROR &&
ret != SCRCPY_EXIT_SUCCESS)) {
printf("Press Enter to continue...\n");
getchar();
}
return ret;
}
return ok ? 0 : 1;
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
}

@ -1,4 +1,4 @@
#include "mouse_inject.h"
#include "mouse_sdk.h"
#include <assert.h>
@ -9,8 +9,8 @@
#include "util/intmap.h"
#include "util/log.h"
/** Downcast mouse processor to sc_mouse_inject */
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_inject, mouse_processor)
/** Downcast mouse processor to sc_mouse_sdk */
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_sdk, mouse_processor)
static enum android_motionevent_buttons
convert_mouse_buttons(uint32_t state) {
@ -63,20 +63,20 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
return;
}
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct sc_mouse_sdk *m = DOWNCAST(mp);
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
.inject_touch_event = {
.action = AMOTION_EVENT_ACTION_MOVE,
.pointer_id = POINTER_ID_MOUSE,
.pointer_id = event->pointer_id,
.position = event->position,
.pressure = 1.f,
.buttons = convert_mouse_buttons(event->buttons_state),
},
};
if (!sc_controller_push_msg(mi->controller, &msg)) {
if (!sc_controller_push_msg(m->controller, &msg)) {
LOGW("Could not request 'inject mouse motion event'");
}
}
@ -84,20 +84,21 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
static void
sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
const struct sc_mouse_click_event *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct sc_mouse_sdk *m = DOWNCAST(mp);
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
.inject_touch_event = {
.action = convert_mouse_action(event->action),
.pointer_id = POINTER_ID_MOUSE,
.pointer_id = event->pointer_id,
.position = event->position,
.pressure = event->action == SC_ACTION_DOWN ? 1.f : 0.f,
.action_button = convert_mouse_buttons(event->button),
.buttons = convert_mouse_buttons(event->buttons_state),
},
};
if (!sc_controller_push_msg(mi->controller, &msg)) {
if (!sc_controller_push_msg(m->controller, &msg)) {
LOGW("Could not request 'inject mouse click event'");
}
}
@ -105,7 +106,7 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
static void
sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
const struct sc_mouse_scroll_event *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct sc_mouse_sdk *m = DOWNCAST(mp);
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
@ -117,7 +118,7 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
},
};
if (!sc_controller_push_msg(mi->controller, &msg)) {
if (!sc_controller_push_msg(m->controller, &msg)) {
LOGW("Could not request 'inject mouse scroll event'");
}
}
@ -125,7 +126,7 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
static void
sc_mouse_processor_process_touch(struct sc_mouse_processor *mp,
const struct sc_touch_event *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct sc_mouse_sdk *m = DOWNCAST(mp);
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
@ -138,15 +139,14 @@ sc_mouse_processor_process_touch(struct sc_mouse_processor *mp,
},
};
if (!sc_controller_push_msg(mi->controller, &msg)) {
if (!sc_controller_push_msg(m->controller, &msg)) {
LOGW("Could not request 'inject touch event'");
}
}
void
sc_mouse_inject_init(struct sc_mouse_inject *mi,
struct sc_controller *controller) {
mi->controller = controller;
sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller) {
m->controller = controller;
static const struct sc_mouse_processor_ops ops = {
.process_mouse_motion = sc_mouse_processor_process_mouse_motion,
@ -155,7 +155,7 @@ sc_mouse_inject_init(struct sc_mouse_inject *mi,
.process_touch = sc_mouse_processor_process_touch,
};
mi->mouse_processor.ops = &ops;
m->mouse_processor.ops = &ops;
mi->mouse_processor.relative_mode = false;
m->mouse_processor.relative_mode = false;
}

@ -1,5 +1,5 @@
#ifndef SC_MOUSE_INJECT_H
#define SC_MOUSE_INJECT_H
#ifndef SC_MOUSE_SDK_H
#define SC_MOUSE_SDK_H
#include "common.h"
@ -9,14 +9,13 @@
#include "screen.h"
#include "trait/mouse_processor.h"
struct sc_mouse_inject {
struct sc_mouse_sdk {
struct sc_mouse_processor mouse_processor; // mouse processor trait
struct sc_controller *controller;
};
void
sc_mouse_inject_init(struct sc_mouse_inject *mi,
struct sc_controller *controller);
sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller);
#endif

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

@ -7,14 +7,23 @@ const struct scrcpy_options scrcpy_options_default = {
.window_title = NULL,
.push_target = NULL,
.render_driver = NULL,
.codec_options = NULL,
.encoder_name = NULL,
#ifdef HAVE_V4L2
.v4l2_device = NULL,
#endif
.video_codec_options = NULL,
.audio_codec_options = NULL,
.video_encoder = NULL,
.audio_encoder = NULL,
.camera_id = NULL,
.camera_size = NULL,
.camera_ar = NULL,
.camera_fps = 0,
.log_level = SC_LOG_LEVEL_INFO,
.video_codec = SC_CODEC_H264,
.audio_codec = SC_CODEC_OPUS,
.video_source = SC_VIDEO_SOURCE_DISPLAY,
.audio_source = SC_AUDIO_SOURCE_AUTO,
.record_format = SC_RECORD_FORMAT_AUTO,
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT,
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AUTO,
.mouse_input_mode = SC_MOUSE_INPUT_MODE_AUTO,
.camera_facing = SC_CAMERA_FACING_ANY,
.port_range = {
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST,
.last = DEFAULT_LOCAL_PORT_RANGE_LAST,
@ -26,17 +35,25 @@ const struct scrcpy_options scrcpy_options_default = {
.count = 2,
},
.max_size = 0,
.bit_rate = DEFAULT_BIT_RATE,
.video_bit_rate = 0,
.audio_bit_rate = 0,
.max_fps = 0,
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED,
.rotation = 0,
.display_orientation = SC_ORIENTATION_0,
.record_orientation = SC_ORIENTATION_0,
.window_x = SC_WINDOW_POSITION_UNDEFINED,
.window_y = SC_WINDOW_POSITION_UNDEFINED,
.window_width = 0,
.window_height = 0,
.display_id = 0,
.display_buffer = 0,
.audio_buffer = -1, // depends on the audio format,
.audio_output_buffer = SC_TICK_FROM_MS(5),
.time_limit = 0,
#ifdef HAVE_V4L2
.v4l2_device = NULL,
.v4l2_buffer = 0,
#endif
#ifdef HAVE_USB
.otg = false,
#endif
@ -44,7 +61,8 @@ const struct scrcpy_options scrcpy_options_default = {
.fullscreen = false,
.always_on_top = false,
.control = true,
.display = true,
.video_playback = true,
.audio_playback = true,
.turn_screen_off = false,
.key_inject_mode = SC_KEY_INJECT_MODE_MIXED,
.window_borderless = false,
@ -64,4 +82,47 @@ const struct scrcpy_options scrcpy_options_default = {
.select_usb = false,
.cleanup = true,
.start_fps_counter = false,
.power_on = true,
.video = true,
.audio = true,
.require_audio = false,
.kill_adb_on_close = false,
.camera_high_speed = false,
.list = 0,
};
enum sc_orientation
sc_orientation_apply(enum sc_orientation src, enum sc_orientation transform) {
assert(!(src & ~7));
assert(!(transform & ~7));
unsigned transform_hflip = transform & 4;
unsigned transform_rotation = transform & 3;
unsigned src_hflip = src & 4;
unsigned src_rotation = src & 3;
unsigned src_swap = src & 1;
if (src_swap && transform_hflip) {
// If the src is rotated by 90 or 270 degrees, applying a flipped
// transformation requires an additional 180 degrees rotation to
// compensate for the inversion of the order of multiplication:
//
// hflip1 × rotate1 × hflip2 × rotate2
// `--------------' `--------------'
// src transform
//
// In the final result, we want all the hflips then all the rotations,
// so we must move hflip2 to the left:
//
// hflip1 × hflip2 × rotate1' × rotate2
//
// with rotate1' = | rotate1 if src is 0° or 180°
// | rotate1 + 180° if src is 90° or 270°
src_rotation += 2;
}
unsigned result_hflip = src_hflip ^ transform_hflip;
unsigned result_rotation = (transform_rotation + src_rotation) % 4;
enum sc_orientation result = result_hflip | result_rotation;
return result;
}

@ -3,6 +3,7 @@
#include "common.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
@ -21,26 +22,137 @@ enum sc_record_format {
SC_RECORD_FORMAT_AUTO,
SC_RECORD_FORMAT_MP4,
SC_RECORD_FORMAT_MKV,
SC_RECORD_FORMAT_M4A,
SC_RECORD_FORMAT_MKA,
SC_RECORD_FORMAT_OPUS,
SC_RECORD_FORMAT_AAC,
SC_RECORD_FORMAT_FLAC,
SC_RECORD_FORMAT_WAV,
};
static inline bool
sc_record_format_is_audio_only(enum sc_record_format fmt) {
return fmt == SC_RECORD_FORMAT_M4A
|| fmt == SC_RECORD_FORMAT_MKA
|| fmt == SC_RECORD_FORMAT_OPUS
|| fmt == SC_RECORD_FORMAT_AAC
|| fmt == SC_RECORD_FORMAT_FLAC
|| fmt == SC_RECORD_FORMAT_WAV;
}
enum sc_codec {
SC_CODEC_H264,
SC_CODEC_H265,
SC_CODEC_AV1,
SC_CODEC_OPUS,
SC_CODEC_AAC,
SC_CODEC_FLAC,
SC_CODEC_RAW,
};
enum sc_video_source {
SC_VIDEO_SOURCE_DISPLAY,
SC_VIDEO_SOURCE_CAMERA,
};
enum sc_audio_source {
SC_AUDIO_SOURCE_AUTO, // OUTPUT for video DISPLAY, MIC for video CAMERA
SC_AUDIO_SOURCE_OUTPUT,
SC_AUDIO_SOURCE_MIC,
};
enum sc_camera_facing {
SC_CAMERA_FACING_ANY,
SC_CAMERA_FACING_FRONT,
SC_CAMERA_FACING_BACK,
SC_CAMERA_FACING_EXTERNAL,
};
// ,----- hflip (applied before the rotation)
// | ,--- 180°
// | | ,- 90° clockwise
// | | |
enum sc_orientation { // v v v
SC_ORIENTATION_0, // 0 0 0
SC_ORIENTATION_90, // 0 0 1
SC_ORIENTATION_180, // 0 1 0
SC_ORIENTATION_270, // 0 1 1
SC_ORIENTATION_FLIP_0, // 1 0 0
SC_ORIENTATION_FLIP_90, // 1 0 1
SC_ORIENTATION_FLIP_180, // 1 1 0
SC_ORIENTATION_FLIP_270, // 1 1 1
};
static inline bool
sc_orientation_is_mirror(enum sc_orientation orientation) {
assert(!(orientation & ~7));
return orientation & 4;
}
// Does the orientation swap width and height?
static inline bool
sc_orientation_is_swap(enum sc_orientation orientation) {
assert(!(orientation & ~7));
return orientation & 1;
}
static inline enum sc_orientation
sc_orientation_get_rotation(enum sc_orientation orientation) {
assert(!(orientation & ~7));
return orientation & 3;
}
enum sc_orientation
sc_orientation_apply(enum sc_orientation src, enum sc_orientation transform);
static inline const char *
sc_orientation_get_name(enum sc_orientation orientation) {
switch (orientation) {
case SC_ORIENTATION_0:
return "0";
case SC_ORIENTATION_90:
return "90";
case SC_ORIENTATION_180:
return "180";
case SC_ORIENTATION_270:
return "270";
case SC_ORIENTATION_FLIP_0:
return "flip0";
case SC_ORIENTATION_FLIP_90:
return "flip90";
case SC_ORIENTATION_FLIP_180:
return "flip180";
case SC_ORIENTATION_FLIP_270:
return "flip270";
default:
return "(unknown)";
}
}
enum sc_lock_video_orientation {
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
// lock the current orientation when scrcpy starts
SC_LOCK_VIDEO_ORIENTATION_INITIAL = -2,
SC_LOCK_VIDEO_ORIENTATION_0 = 0,
SC_LOCK_VIDEO_ORIENTATION_1,
SC_LOCK_VIDEO_ORIENTATION_2,
SC_LOCK_VIDEO_ORIENTATION_3,
SC_LOCK_VIDEO_ORIENTATION_90 = 3,
SC_LOCK_VIDEO_ORIENTATION_180 = 2,
SC_LOCK_VIDEO_ORIENTATION_270 = 1,
};
enum sc_keyboard_input_mode {
SC_KEYBOARD_INPUT_MODE_INJECT,
SC_KEYBOARD_INPUT_MODE_HID,
SC_KEYBOARD_INPUT_MODE_AUTO,
SC_KEYBOARD_INPUT_MODE_DISABLED,
SC_KEYBOARD_INPUT_MODE_SDK,
SC_KEYBOARD_INPUT_MODE_UHID,
SC_KEYBOARD_INPUT_MODE_AOA,
};
enum sc_mouse_input_mode {
SC_MOUSE_INPUT_MODE_INJECT,
SC_MOUSE_INPUT_MODE_HID,
SC_MOUSE_INPUT_MODE_AUTO,
SC_MOUSE_INPUT_MODE_DISABLED,
SC_MOUSE_INPUT_MODE_SDK,
SC_MOUSE_INPUT_MODE_UHID,
SC_MOUSE_INPUT_MODE_AOA,
};
enum sc_key_inject_mode {
@ -87,31 +199,47 @@ struct scrcpy_options {
const char *window_title;
const char *push_target;
const char *render_driver;
const char *codec_options;
const char *encoder_name;
#ifdef HAVE_V4L2
const char *v4l2_device;
#endif
const char *video_codec_options;
const char *audio_codec_options;
const char *video_encoder;
const char *audio_encoder;
const char *camera_id;
const char *camera_size;
const char *camera_ar;
uint16_t camera_fps;
enum sc_log_level log_level;
enum sc_codec video_codec;
enum sc_codec audio_codec;
enum sc_video_source video_source;
enum sc_audio_source audio_source;
enum sc_record_format record_format;
enum sc_keyboard_input_mode keyboard_input_mode;
enum sc_mouse_input_mode mouse_input_mode;
enum sc_camera_facing camera_facing;
struct sc_port_range port_range;
uint32_t tunnel_host;
uint16_t tunnel_port;
struct sc_shortcut_mods shortcut_mods;
uint16_t max_size;
uint32_t bit_rate;
uint32_t video_bit_rate;
uint32_t audio_bit_rate;
uint16_t max_fps;
enum sc_lock_video_orientation lock_video_orientation;
uint8_t rotation;
enum sc_orientation display_orientation;
enum sc_orientation record_orientation;
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
uint16_t window_width;
uint16_t window_height;
uint32_t display_id;
sc_tick display_buffer;
sc_tick audio_buffer;
sc_tick audio_output_buffer;
sc_tick time_limit;
#ifdef HAVE_V4L2
const char *v4l2_device;
sc_tick v4l2_buffer;
#endif
#ifdef HAVE_USB
bool otg;
#endif
@ -119,7 +247,8 @@ struct scrcpy_options {
bool fullscreen;
bool always_on_top;
bool control;
bool display;
bool video_playback;
bool audio_playback;
bool turn_screen_off;
enum sc_key_inject_mode key_inject_mode;
bool window_borderless;
@ -139,6 +268,17 @@ struct scrcpy_options {
bool select_tcpip;
bool cleanup;
bool start_fps_counter;
bool power_on;
bool video;
bool audio;
bool require_audio;
bool kill_adb_on_close;
bool camera_high_speed;
#define SC_OPTION_LIST_ENCODERS 0x1
#define SC_OPTION_LIST_DISPLAYS 0x2
#define SC_OPTION_LIST_CAMERAS 0x4
#define SC_OPTION_LIST_CAMERA_SIZES 0x8
uint8_t list;
};
extern const struct scrcpy_options scrcpy_options_default;

@ -0,0 +1,48 @@
#include "packet_merger.h"
#include "util/log.h"
void
sc_packet_merger_init(struct sc_packet_merger *merger) {
merger->config = NULL;
}
void
sc_packet_merger_destroy(struct sc_packet_merger *merger) {
free(merger->config);
}
bool
sc_packet_merger_merge(struct sc_packet_merger *merger, AVPacket *packet) {
bool is_config = packet->pts == AV_NOPTS_VALUE;
if (is_config) {
free(merger->config);
merger->config = malloc(packet->size);
if (!merger->config) {
LOG_OOM();
return false;
}
memcpy(merger->config, packet->data, packet->size);
merger->config_size = packet->size;
} else if (merger->config) {
size_t config_size = merger->config_size;
size_t media_size = packet->size;
if (av_grow_packet(packet, config_size)) {
LOG_OOM();
return false;
}
memmove(packet->data + config_size, packet->data, media_size);
memcpy(packet->data, merger->config, config_size);
free(merger->config);
merger->config = NULL;
// merger->size is meaningless when merger->config is NULL
}
return true;
}

@ -0,0 +1,43 @@
#ifndef SC_PACKET_MERGER_H
#define SC_PACKET_MERGER_H
#include "common.h"
#include <stdbool.h>
#include <stdint.h>
#include <libavcodec/avcodec.h>
/**
* Config packets (containing the SPS/PPS) are sent in-band. A new config
* packet is sent whenever a new encoding session is started (on start and on
* device orientation change).
*
* Every time a config packet is received, it must be sent alone (for recorder
* extradata), then concatenated to the next media packet (for correct decoding
* and recording).
*
* This helper reads every input packet and modifies each media packet which
* immediately follows a config packet to prepend the config packet payload.
*/
struct sc_packet_merger {
uint8_t *config;
size_t config_size;
};
void
sc_packet_merger_init(struct sc_packet_merger *merger);
void
sc_packet_merger_destroy(struct sc_packet_merger *merger);
/**
* If the packet is a config packet, then keep its data for later.
* Otherwise (if the packet is a media packet), then if a config packet is
* pending, prepend the config packet to this packet (so the packet is
* modified!).
*/
bool
sc_packet_merger_merge(struct sc_packet_merger *merger, AVPacket *packet);
#endif

@ -1,32 +1,35 @@
#include "receiver.h"
#include <assert.h>
#include <inttypes.h>
#include <stdint.h>
#include <SDL2/SDL_clipboard.h>
#include "device_msg.h"
#include "util/log.h"
#include "util/str.h"
bool
receiver_init(struct receiver *receiver, sc_socket control_socket,
struct sc_acksync *acksync) {
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket) {
bool ok = sc_mutex_init(&receiver->mutex);
if (!ok) {
return false;
}
receiver->control_socket = control_socket;
receiver->acksync = acksync;
receiver->acksync = NULL;
receiver->uhid_devices = NULL;
return true;
}
void
receiver_destroy(struct receiver *receiver) {
sc_receiver_destroy(struct sc_receiver *receiver) {
sc_mutex_destroy(&receiver->mutex);
}
static void
process_msg(struct receiver *receiver, struct device_msg *msg) {
process_msg(struct sc_receiver *receiver, struct sc_device_msg *msg) {
switch (msg->type) {
case DEVICE_MSG_TYPE_CLIPBOARD: {
char *current = SDL_GetClipboardText();
@ -42,20 +45,65 @@ process_msg(struct receiver *receiver, struct device_msg *msg) {
break;
}
case DEVICE_MSG_TYPE_ACK_CLIPBOARD:
assert(receiver->acksync);
LOGD("Ack device clipboard sequence=%" PRIu64_,
msg->ack_clipboard.sequence);
// This is a programming error to receive this message if there is
// no ACK synchronization mechanism
assert(receiver->acksync);
// Also check at runtime (do not trust the server)
if (!receiver->acksync) {
LOGE("Received unexpected ack");
return;
}
sc_acksync_ack(receiver->acksync, msg->ack_clipboard.sequence);
break;
case DEVICE_MSG_TYPE_UHID_OUTPUT:
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
char *hex = sc_str_to_hex_string(msg->uhid_output.data,
msg->uhid_output.size);
if (hex) {
LOGV("UHID output [%" PRIu16 "] %s",
msg->uhid_output.id, hex);
free(hex);
} else {
LOGV("UHID output [%" PRIu16 "] size=%" PRIu16,
msg->uhid_output.id, msg->uhid_output.size);
}
}
// This is a programming error to receive this message if there is
// no uhid_devices instance
assert(receiver->uhid_devices);
// Also check at runtime (do not trust the server)
if (!receiver->uhid_devices) {
LOGE("Received unexpected HID output message");
return;
}
struct sc_uhid_receiver *uhid_receiver =
sc_uhid_devices_get_receiver(receiver->uhid_devices,
msg->uhid_output.id);
if (uhid_receiver) {
uhid_receiver->ops->process_output(uhid_receiver,
msg->uhid_output.data,
msg->uhid_output.size);
} else {
LOGW("No UHID receiver for id %" PRIu16, msg->uhid_output.id);
}
break;
}
}
static ssize_t
process_msgs(struct receiver *receiver, const unsigned char *buf, size_t len) {
process_msgs(struct sc_receiver *receiver, const uint8_t *buf, size_t len) {
size_t head = 0;
for (;;) {
struct device_msg msg;
ssize_t r = device_msg_deserialize(&buf[head], len - head, &msg);
struct sc_device_msg msg;
ssize_t r = sc_device_msg_deserialize(&buf[head], len - head, &msg);
if (r == -1) {
return -1;
}
@ -64,7 +112,7 @@ process_msgs(struct receiver *receiver, const unsigned char *buf, size_t len) {
}
process_msg(receiver, &msg);
device_msg_destroy(&msg);
sc_device_msg_destroy(&msg);
head += r;
assert(head <= len);
@ -76,9 +124,9 @@ process_msgs(struct receiver *receiver, const unsigned char *buf, size_t len) {
static int
run_receiver(void *data) {
struct receiver *receiver = data;
struct sc_receiver *receiver = data;
static unsigned char buf[DEVICE_MSG_MAX_SIZE];
static uint8_t buf[DEVICE_MSG_MAX_SIZE];
size_t head = 0;
for (;;) {
@ -108,7 +156,7 @@ run_receiver(void *data) {
}
bool
receiver_start(struct receiver *receiver) {
sc_receiver_start(struct sc_receiver *receiver) {
LOGD("Starting receiver thread");
bool ok = sc_thread_create(&receiver->thread, run_receiver,
@ -122,6 +170,6 @@ receiver_start(struct receiver *receiver) {
}
void
receiver_join(struct receiver *receiver) {
sc_receiver_join(struct sc_receiver *receiver) {
sc_thread_join(&receiver->thread, NULL);
}

@ -1,37 +1,38 @@
#ifndef RECEIVER_H
#define RECEIVER_H
#ifndef SC_RECEIVER_H
#define SC_RECEIVER_H
#include "common.h"
#include <stdbool.h>
#include "uhid/uhid_output.h"
#include "util/acksync.h"
#include "util/net.h"
#include "util/thread.h"
// receive events from the device
// managed by the controller
struct receiver {
struct sc_receiver {
sc_socket control_socket;
sc_thread thread;
sc_mutex mutex;
struct sc_acksync *acksync;
struct sc_uhid_devices *uhid_devices;
};
bool
receiver_init(struct receiver *receiver, sc_socket control_socket,
struct sc_acksync *acksync);
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket);
void
receiver_destroy(struct receiver *receiver);
sc_receiver_destroy(struct sc_receiver *receiver);
bool
receiver_start(struct receiver *receiver);
sc_receiver_start(struct sc_receiver *receiver);
// no receiver_stop(), it will automatically stop on control_socket shutdown
// no sc_receiver_stop(), it will automatically stop on control_socket shutdown
void
receiver_join(struct receiver *receiver);
sc_receiver_join(struct sc_receiver *receiver);
#endif

File diff suppressed because it is too large Load Diff

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

Loading…
Cancel
Save