Compare commits

...

312 Commits

Author SHA1 Message Date
e51e9c748f Handle frame available from screen.c 2021-02-20 11:54:54 +01:00
c79d3a28ca Initialize screen before starting the stream
As soon as the stream is started, the video buffer could notify a new
frame available.

In order to pass this event to the screen without race condition, the
screen must be initialized before the screen is started.
2021-02-20 11:54:12 +01:00
108fe5818e Expose skipped frames to the consumer
A skipped frame is detected when the producer offers a frame while the
current pending frame has not been consumed.

However, the producer (in practice the decoder) is not interested in the
fact that a frame has been skipped, only the consumer (the renderer) is.
Therefore, expose the skipped count in consumer_take_frame() instead of
a flag in the producer_offer_frame().

This allows to manage the skipped and rendered frames count at the same
place, and remove fps_counter from decoder.
2021-02-20 11:53:37 +01:00
1ff357bd29 Use a callback to notify a new frame
Make the decoder independant of the SDL even mechanism, by registering a
callback on the video_buffer.
2021-02-19 22:02:36 +01:00
5f2f0ce595 Make video buffer more generic
Video buffer is a tool between a frame producer and a frame consumer.

For now, it is used between a decoder and a renderer, but in the future
another instance might be used to swscale decoded frames.
2021-02-19 21:16:57 +01:00
816c158577 Move fps counter out of video buffer
In order to make video buffer more generic, move out its specific
responsibility to count the fps between the decoder and the renderer.
2021-02-19 20:56:09 +01:00
dce0867737 Enable NDEBUG via Meson built-in option 2021-02-17 09:54:03 +01:00
5c4c28c973 Handle im-related events from input_manager.c 2021-02-17 09:54:03 +01:00
98628f25b5 Inline window events handling
Now that all screen-related events are handled from screen.c, there is
no need for a separate method for window events.
2021-02-17 09:54:03 +01:00
e1dbe4f71c Handle screen-related events from screen.c 2021-02-17 09:54:03 +01:00
b1734ab737 Reference video buffer from screen
This paves the way to handle EVENT_NEW_FRAME from screen.c, by allowing
to call screen_update_frame() without an explicit video_buffer instance.
2021-02-17 09:54:03 +01:00
44aa7f2c88 Improve error handling in screen initialization
After the struct screen is initialized, the window and the renderer are
necessarily valid, so there is no need o check in screen_destroy().
2021-02-17 09:54:03 +01:00
626094ad13 Handle window events only once visible
This will avoid corner cases where we need to resize while no frame has
been received yet.
2021-02-17 09:54:03 +01:00
a566635c43 Log mipmaps error only if mipmaps are enabled 2021-02-17 09:54:03 +01:00
862948b132 Make use_opengl local
The flag is used only locally, there is no need to store it in the
screen structure.
2021-02-17 09:54:03 +01:00
c0c4ba7009 Add intermediate frame in video buffer
There were only two frames simultaneously:
 - one used by the decoder;
 - one used by the renderer.

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

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

Now that it is available, use the native version of these functions.
2021-02-17 09:54:03 +01:00
c0dde0fade Provide strdup() compat
Make strdup() available on all platforms.
2021-02-17 09:53:25 +01:00
ace438e52a Remove unused port_range field
The port_range is used from "struct server_params", the copy in
"struct server" was unused.
2021-02-14 14:47:49 +01:00
8e83f3e8af Remove unused custom event 2021-02-14 14:44:05 +01:00
97b001e7c0 Fix undefined left shift
Small unsigned integers promote to signed int. As a consequence, if v is
a uint8_t, then (v << 24) yields an int, so the left shift is undefined
if the MSB is 1.

Cast to uint32_t to yield an unsigned value.

Reported by USAN (meson x -Db_sanitize=undefined):

    runtime error: left shift of 255 by 24 places cannot be represented
    in type 'int'
2021-01-24 15:31:21 +01:00
d8e9ad20b0 Improve file handler error message
Terminating the file handler current process may be either a "push" or
"install" command.
2021-01-24 14:24:24 +01:00
b566700bfd Kill process with SIGKILL signal
An "adb push" command is not terminated by SIGTERM.
2021-01-24 14:24:24 +01:00
7afd149f4b Fix file_handler process race condition
The current process could be waited both by run_file_handler() and
file_handler_stop().

To avoid the race condition, wait the process without closing, then
close with mutex locked.
2021-01-24 14:24:24 +01:00
6a50231698 Expose a single process_wait()
There were two versions: process_wait() and process_wait_noclose().

Expose a single version with a flag (it was already implemented that way
internally).
2021-01-24 14:24:18 +01:00
b8edcf52b0 Simplify process_wait()
The function process_wait() returned a bool (true if the process
terminated successfully) and provided the exit code via an output
parameter exit_code.

But the returned value was always equivalent to exit_code == 0, so just
return the exit code instead.
2021-01-22 18:29:21 +01:00
94eff0a4bb Fix size_t incorrectly assigned to int
The function control_msg_serialize() returns a size_t.
2021-01-17 19:44:23 +01:00
8dbb1676b7 Factorize meson compiler variable initialization 2021-01-17 19:44:23 +01:00
ab912c23e7 Define feature test macros in common.h
This enables necessary functions once for all.

As a consequence, define common.h before any other header.
2021-01-17 14:08:48 +01:00
59feb2a15c Group common includes into common.h
Include config.h and compat.h in common.h, and include common.h from all
source files.
2021-01-08 19:22:10 +01:00
6385b8c162 Move common structs to coords.h
The size, point and position structs were defined in common.h. Move them
to coords.h so that common.h could be used for generic code to be
included in all source files.
2021-01-08 19:22:10 +01:00
037be4af21 Fix compat missing include
The header libavformat/version.h was included, but not
libavcodec/version.h.

As a consequence, the LIBAVCODEC_VERSION_INT definition depended on the
caller includes.
2021-01-08 19:21:54 +01:00
1e215199dc Remove unused struct port_range
It had been replaced by struct sc_port_range in scrcpy.h.
2021-01-08 19:13:53 +01:00
d580ee30f1 Separate process wait and close
On Linux, waitpid() both waits for the process to terminate and reaps it
(closes its handle). On Windows, these actions are separated into
WaitForSingleObject() and CloseHandle().

Expose these actions separately, so that it is possible to send a signal
to a process while waiting for its termination without race condition.

This allows to wait for server termination normally, but kill the
process without race condition if it is not terminated after some delay.
2021-01-08 16:44:21 +01:00
821c175730 Rename process_simple_wait to process_wait
Adding "simple" in the function name brings no benefit.
2021-01-08 16:44:21 +01:00
cc6f5020d8 Move conditional src files in meson.build
Declare all the source files (including the platform-specific ones) at
the beginning.
2021-01-08 16:44:21 +01:00
4bd9da4c93 Split command into process and adb
The process API provides the system-specific implementation, the adb API
uses it to expose adb commands.
2021-01-08 16:44:21 +01:00
aa8b571389 Increase display id range
Some devices use big display id values.

Refs #2009 <https://github.com/Genymobile/scrcpy/issues/2009>
2021-01-04 08:16:32 +01:00
ed130e05d5 Fix possibly uninitialized value
Due to gotos, "ret" may be returned uninitialized.
2021-01-03 22:41:51 +01:00
192fbd8450 Use --cask for latest versions of brew
PR #2004 <https://github.com/Genymobile/scrcpy/pull/2004>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-01-02 18:27:38 +01:00
c5c5fc18ae Update links to v1.17 in README and BUILD 2021-01-02 01:26:23 +01:00
f682b87ba5 Bump version to 1.17 2021-01-02 00:53:32 +01:00
10b749e27d Kill the server only after a small delay
Let the server terminate properly once all the sockets are closed.

If it does not terminate (this can happen if the device is asleep), then
kill it.

Note: since the server process termination is detected by a flag set
after waitpid() returns, there is a small chance that the process
terminates (and the PID assigned to a new process) before the flag is
set but before the kill() call. This race condition already existed
before this commit.

Fixes #1992 <https://github.com/Genymobile/scrcpy/issues/1992>
2021-01-01 23:57:01 +01:00
05e8c1a3c5 Call CloseHandle() after wait on Windows
TerminateProcess() is "equivalent" to kill(), while
WaitForSingleObject() is "equivalent" to waitpid(), so the handle must
be closed after WaitForSingleObject().
2021-01-01 23:57:01 +01:00
83910d3b9c Initialize server struct dynamically
This will allow to add mutex/cond fields.
2021-01-01 17:40:52 +01:00
90f8356630 Interrupt device threads on stop
The (non-daemon) threads were not interrupted on video stream stopped,
leaving the server process alive.

Interrupt them to wake up their blocking call so that they terminate
properly.

Refs #1992 <https://github.com/Genymobile/scrcpy/issues/1992>
2021-01-01 17:32:34 +01:00
3ba51211d6 Mention how to add default arguments on Windows
Mention that it is possible to add default arguments by editing the
wrapper scripts.
2021-01-01 17:31:07 +01:00
ea3582d2c3 Merge branch 'master' into dev 2021-01-01 17:18:13 +01:00
112adbba87 Happy new year 2021! 2021-01-01 17:16:44 +01:00
d039a7a39a Upgrade SDL (2.0.14) for Windows
Include the latest version of SDL in Windows releases.
2021-01-01 16:08:58 +01:00
6ab80e4ce8 Rename release.make to release.mk
It's more standard, and benefits from syntax coloration in vi.
2021-01-01 15:51:10 +01:00
230afd8966 Unify release makefile
Before this change, release.sh built some native stuff, and
Makefile.CrossWindows built the Windows releases.

Instead, use a single release.make to build the whole release. It also
avoids to build the server one more time.
2020-12-22 18:52:05 +01:00
a46733906a Replace noconsole binary by a wrapper script
This simplifies the build system.

Refs <https://github.com/Genymobile/scrcpy/issues/1975#issuecomment-745314161>
2020-12-22 18:51:59 +01:00
431c9ee33b Improve rotation documentation 2020-12-22 01:48:01 +01:00
43d3dcbd97 Document Windows command line usage
PR #1973 <https://github.com/Genymobile/scrcpy/pull/1973>

Reviewed-by: Yu-Chen Lin <npes87184@gmail.com>
2020-12-22 01:44:55 +01:00
a5f4f58295 Remove duplicate include 2020-12-22 01:14:30 +01:00
ea12783bbc Upgrade JUnit to 4.13 2020-12-17 10:53:35 +01:00
904d470579 Pause on error from a wrapper script
On Windows, scrcpy paused on error before exiting to give the user a
chance to see the user message.

This was a hack and causes issues when using scrcpy from batch scripts.

Disable this pause from the scrcpy binary, and provide a batch wrapper
(scrcpy-console.bat) to pause on error.

Fixes #1875 <https://github.com/Genymobile/scrcpy/issues/1875>
2020-12-14 09:44:17 +01:00
6d151eaef9 Reference encoder section from FAQ 2020-12-14 09:04:14 +01:00
5dc3285dbf Reference FFmpeg Windows builds from GitHub
Refs #1753 <https://github.com/Genymobile/scrcpy/issues/1753>
2020-12-12 16:26:08 +01:00
c9a4bdb890 Upgrade platform-tools (30.0.5) for Windows
Include the latest version of adb in Windows releases.
2020-12-12 15:56:32 +01:00
d60ac65b32 Merge branch 'master' into dev 2020-12-12 15:55:29 +01:00
d6078cf202 Fix build errors for macOS
PR #1960 <https://github.com/Genymobile/scrcpy/pull/1960>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-12-12 15:50:33 +01:00
47c8971267 Document shell command to get the device IP
PR #1944 <https://github.com/Genymobile/scrcpy/pull/1944>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-12-10 21:37:23 +01:00
30434afc0a Upgrade gradle build tools to 4.0.1 2020-11-24 09:45:18 +01:00
868e762d71 Fix size_t format specifier for Windows
Use "%Iu" on Windows. This fixes the following warning:

    ../app/src/sys/win/command.c:17:14: warning: unknown conversion type character ‘l’ in format [-Wformat=]
       17 |         LOGE("Command too long (%" PRIsizet " chars)", len - 1);
2020-11-14 22:10:11 +01:00
576814bcec Document --encoder option
Add documentation for the new option --encoder in the manpage and in
README.md.
2020-11-08 21:11:12 +01:00
42ab8fd611 List available encoders on invalid name specified
If an invalid encoder name is given via the --encoder option, list all
the H.264 encoders available on the device.
2020-11-08 21:07:20 +01:00
363eeea19e Log encoder name
When the encoder is selected automatically, log the name of the selected
encoder.
2020-11-08 21:07:20 +01:00
76c2c6e69d Adding new option --encoder
Some devices have more than one encoder, and some encoders may cause
issues or crash. With this option we can specify which encoder we want
the device to use.

PR #1827 <https://github.com/Genymobile/scrcpy/pull/1827>
Fixes #1810 <https://github.com/Genymobile/scrcpy/issues/1810>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-11-08 21:07:17 +01:00
d5f059c7cb Add option to force legacy method for pasting
Some devices do not behave as expected when setting the device clipboard
programmatically.

Add an option --legacy-paste to change the behavior of Ctrl+v and MOD+v
so that they inject the computer clipboard text as a sequence of key
events (the same way as MOD+Shift+v).

Fixes #1750 <https://github.com/Genymobile/scrcpy/issues/1750>
Fixes #1771 <https://github.com/Genymobile/scrcpy/issues/1771>
2020-11-07 15:16:51 +01:00
adc547fa6e Add an option to forward all clicks
Add --forward-all-clicks to disable mouse shortcuts and forward middle
and right clicks to the device instead.

Fixes #1302 <https://github.com/Genymobile/scrcpy/issues/1302>
Fixes #1613 <https://github.com/Genymobile/scrcpy/issues/1613>
2020-11-03 17:12:26 +01:00
5dcfc0ebab Add local.properties to gitignore 2020-11-03 17:09:03 +01:00
ad5f567f07 Remove spurious space 2020-11-03 17:08:21 +01:00
25aff00935 Mention version of Indonesian translation 2020-10-05 21:24:59 +02:00
aade92fd10 Add Indonesian translation for README.md
PR #1802 <https://github.com/Genymobile/scrcpy/pull/1802>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-10-05 21:17:43 +02:00
83082406d3 Enable Java deprecation warnings details
Without the option, gradle reports a lint issue, but without any
details.
2020-10-05 21:11:50 +02:00
2edf192e3a Remove deprecation warning
As a workaround for some devices, we need to prepare the main looper.
The method is now deprecated, but we still want to call it.
2020-10-05 21:09:47 +02:00
d50ecf40b6 Fix options order 2020-10-01 15:08:18 +02:00
15b81367c9 Fix FAQ.ko.md
PR #1767 <https://github.com/Genymobile/scrcpy/pull/1767>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-10-01 14:57:09 +02:00
56d237f152 Fix "press Enter key" message
The message said "Press any key to continue...", whereas only
Enter/Return is accepted.

PR #1783 <https://github.com/Genymobile/scrcpy/pull/1783>
Fixes #1757 <https://github.com/Genymobile/scrcpy/issues/1757>

Reviewed-by: Yu-Chen Lin <npes87184@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-10-01 14:52:24 +02:00
acc65f8c9d Remove unused field
Fixes #1775 <https://github.com/Genymobile/scrcpy/issues/1775>

Reported-by: lordnn
2020-09-20 01:11:22 +02:00
a65ebceac1 Add missing mutex unlock on error
Fixes #1770 <https://github.com/Genymobile/scrcpy/issues/1770>

Reported-by: lordnn
2020-09-20 01:11:13 +02:00
c136edf09d Add simplified Chinese translation for README.md
PR #1723 <https://github.com/Genymobile/scrcpy/pull/1723>

Co-authored-by: Shaw Yu <shawyu.nz@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-09-17 13:09:43 +02:00
52560faa34 Fix README indentation 2020-09-17 13:03:40 +02:00
d662f73bdc Upgrade Android SDK to 30 2020-09-15 14:54:22 +02:00
1c44dc2259 Use portable shebang for all bash scripts
Refs #426 <https://github.com/Genymobile/scrcpy/pull/426>
Refs #1716 <https://github.com/Genymobile/scrcpy/pull/1716>
2020-09-15 13:54:00 +02:00
02a882a0a2 Use a more portable shebang for bash
This should increase the portability of bash scripts across various *nix
systems such as BSD-like distributions.

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

Signed-off-by: Luís Ferreira <contact@lsferreira.net>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-09-15 13:52:50 +02:00
cf7bf3148c Use "/usr/bin/env bash" for build-wrapper.sh
PR #426 <https://github.com/Genymobile/scrcpy/pull/426>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-09-15 13:44:02 +02:00
ae758f99d6 Adapt call() on ContentProvider for Android 11
This commit in AOSP framework_base added a parameter "attributionTag" to
the call() method:
12ac3f406f%5E%21/#F17

As a consequence, the method did not exist, so scrcpy used the legacy
call() method (using the authority "unknown") as a fallback, which fails
for security reasons.

Fixes #1468 <https://github.com/Genymobile/scrcpy/issues/1468>
2020-09-15 13:31:10 +02:00
bd9f656933 Fix feature test macro
The expected feature test macro is _POSIX_C_SOURCE having a value
greater or equal to 200809L.

Fixes #1726 <https://github.com/Genymobile/scrcpy/issues/1726>
2020-08-31 14:02:51 +02:00
c243fd4c3f Fix more log format warning
The expression port + 1 is promoted to int, but printed as uint16_t.

This is the same mistake fixed for a different log by
7eb16ce364.

Refs #1726 <https://github.com/Genymobile/scrcpy/issues/1726>
2020-08-31 13:40:32 +02:00
0bf110dd5c Reset power mode only if screen is on
PR #1670 <https://github.com/Genymobile/scrcpy/pull/1670>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-08-21 12:34:59 +02:00
0c5e0a4f6d Make Device methods static when possible
The behavior of some methods do not depend on the user-provided options.
These methods can be static. This will allow to call them directly from
the cleanup process.
2020-08-21 12:34:50 +02:00
0be766e71a Add undetected device error message in FAQ 2020-08-20 19:14:45 +02:00
d02789ce21 List available shortcut keys on error
Fixes #1681 <https://github.com/Genymobile/scrcpy/issues/1681>

Suggested-by: Moritz Schulz <moritzleni@gmail.com>
2020-08-16 13:52:01 +02:00
6cc22e1c5b Reference --shortcut-mod from shortcuts list
Fixes #1681 <https://github.com/Genymobile/scrcpy/issues/1681>

Suggested-by: Moritz Schulz <moritzleni@gmail.com>
2020-08-16 13:44:42 +02:00
479d10dc22 Update links to v1.16 in README and BUILD 2020-08-10 20:34:51 +02:00
d7779d08e8 Bump version to 1.16 2020-08-10 20:09:28 +02:00
df4ba1b8b0 Merge branch 'master' into dev 2020-08-10 20:08:33 +02:00
198346d148 Add pinch-to-zoom simulation
If Ctrl is hold when the left-click button is pressed, enable
pinch-to-zoom to scale and rotate relative to the center of the screen.

Fixes #24 <https://github.com/Genymobile/scrcpy/issues/24>
2020-08-10 20:08:24 +02:00
8081e9cc11 Add reference of the translations in README
Reviewed-by: Yu-Chen Lin <npes87184@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-08-10 19:58:11 +02:00
b87a0df99a Add Traditional Chinese translation for README
Reviewed-by: Yu-Chen Lin <npes87184@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-08-10 19:58:00 +02:00
95f1ea0d80 Fix clipboard paste condition
To avoid possible copy-paste loops between the computer and the device,
the device clipboard is not set if it already contains the expected
content.

But the condition was wrong: it was not set also if it was empty.

Refs 1223a72eb8
Fixes #1658 <https://github.com/Genymobile/scrcpy/issues/1658>
2020-08-10 14:37:32 +02:00
38940ffe89 Revert "Inject WAKEUP instead of POWER"
WAKEUP does not work on some devices.

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

This reverts commit 322f1512ea.
2020-08-09 17:10:27 +02:00
a59a15777d Fix missing change of Ctrl key in README
PR #1652 <https://github.com/Genymobile/scrcpy/pull/1652>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-08-09 11:14:38 +02:00
a2cb63e344 Add packaging status
PR #1650 <https://github.com/Genymobile/scrcpy/pull/1650>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-08-08 13:18:23 +02:00
f03a3edde6 Update links to v1.15.1 in README and BUILD 2020-08-07 12:13:27 +02:00
633a51e9c4 Bump version to 1.15.1 2020-08-07 12:01:34 +02:00
976761956f Fix uninitialized repeat count in key events
A new "repeat" field has been added by
3c1ed5d86c, but it was not initialized in
every code path.

As a consequence, keycodes generated by shortcuts were sent with an
undetermined value, breaking some shortcuts (especially HOME) randomly.

Fixes #1643 <https://github.com/Genymobile/scrcpy/issues/1643>
2020-08-07 11:32:11 +02:00
521f2fe994 Update links to v1.15 in README and BUILD 2020-08-06 21:56:06 +02:00
edc4f7675f Bump version to 1.15 2020-08-06 21:00:48 +02:00
712f1fa6b2 Upgrade FFmpeg (4.3.1) for Windows
Include the latest version of FFmpeg in Windows releases.
2020-08-06 21:00:48 +02:00
1ba06037f8 Upgrade platform-tools (30.0.4) for Windows
Include the latest version of adb in Windows releases.
2020-08-06 21:00:48 +02:00
da63e3774b Merge branch 'master' into dev 2020-08-06 21:00:15 +02:00
cf9d44979c Keep the screen off on powering on
PR #1577 <https://github.com/Genymobile/scrcpy/pull/1577>
Fixes #1573 <https://github.com/Genymobile/scrcpy/issues/1573>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-08-03 19:51:58 +02:00
84f1d9e375 Add --no-key-repeat cli option
Add an option to avoid forwarding repeated key events.

PR #1623 <https://github.com/Genymobile/scrcpy/pull/1623>
Refs #1013 <https://github.com/Genymobile/scrcpy/issues/1013>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-08-03 19:51:55 +02:00
65d06a3663 Pass full options struct to static functions
This avoids to pass specific options values individually. Since these
function are static (internal to the file), this is not a problem to
make them depend on scrcpy_options.

Refs #1623 <https://github.com/Genymobile/scrcpy/pull/1623>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-08-02 18:20:14 +02:00
74079ea5e4 Copy the options used in input manager init
This avoids to pass additional options to some input manager functions.

Refs #1623 <https://github.com/Genymobile/scrcpy/pull/1623>
2020-08-02 18:19:41 +02:00
0870d8620f Mention that MENU unlocks screen
Pressing MENU while in lock screen unlocks (it still asks for the schema
or code if enabled).
2020-08-01 17:10:09 +02:00
d49cffb938 Use <kbd> HTML tag for keys
It's prettyier in a browser.
2020-08-01 17:04:16 +02:00
dfb7324d7b Mention in README that Ctrl is forwarded 2020-08-01 16:51:38 +02:00
d8b3ba170c Update copy-paste section in README
Update documentation regarding the recent copy-paste changes.
2020-08-01 16:51:37 +02:00
7ad47dfaab Swap paste shortcuts
For consistency with MOD+c and MOD+x, use MOD+v to inject PASTE.

Use Mod+Shift+v to inject clipboard content as a sequence of text
events.
2020-08-01 16:47:44 +02:00
56a115b5c5 Add shortcuts for COPY and CUT
Send COPY and CUT on MOD+c and MOD+x (only supported for Android >= 7).

The shortcuts Ctrl+c and Ctrl+x should generally also work (even before
Android 7), but the active Android app may use them for other actions
instead.
2020-08-01 16:47:43 +02:00
8f64a5984b Change "resize to fit" shortcut to MOD+w
For convenience, MOD+x will be used for injecting the CUT keycode.
2020-08-01 16:31:31 +02:00
bccd12bf5c Remove "get clipboard" call
Now that every device clipboard change is automatically synchronized to
the computer, the "get clipboard" request (bound to MOD+c) is useless.
2020-08-01 16:31:31 +02:00
20d3925099 Set computer clipboard only if necessary
Do not explicitly set the clipboard text if it already contains the
expected content.

Even if copy-paste loops are avoided by the previous commit, this avoids
to trigger a clipboard change on the computer-side.

Refs #1580 <https://github.com/Genymobile/scrcpy/issues/1580>
2020-08-01 16:31:31 +02:00
1223a72eb8 Set device clipboard only if necessary
Do not explicitly set the clipboard text if it already contains the
expected content. This avoids possible copy-paste loops between the
computer and the device.
2020-08-01 16:31:31 +02:00
7683be8159 Synchronize clipboard on Ctrl+v
Pressing Ctrl+v on the device will typically paste the clipboard
content.

Before sending the key event, synchronize the computer clipboard to the
device clipboard to allow seamless copy-paste.
2020-08-01 16:31:31 +02:00
d4ca85d6a8 Forward Shift to the device
This allows to select text using Shift+(arrow keys).

Fixes #942 <https://github.com/Genymobile/scrcpy/issues/942>
2020-08-01 16:31:31 +02:00
e6e528f228 Forward Ctrl to the device
Now that the scrcpy shortcut modifier is Alt by default (and can be
configured), forward Ctrl to the device.

This allows to trigger Android shortcuts.

Fixes #555 <https://github.com/Genymobile/scrcpy/issues/555>
2020-08-01 16:31:31 +02:00
a5f8b577c5 Ignore text events for shortcuts
Pressing Alt+c generates a text event containing "c", so "c" was sent to
the device when --prefer-text was enabled.

Ignore text events when the mod state matches a shortcut modifier.
2020-08-01 16:31:31 +02:00
e4bb7c1d1f Accept Super as shortcut modifier
In addition to (left) Alt, also accept (left) Super. This is especially
convenient for macOS users (Super is the Cmd key).
2020-08-01 16:31:31 +02:00
1b76d9fd78 Customize shortcut modifier
Add --shortcut-mod, and use Alt as default modifier.

This paves the way to forward the Ctrl key to the device.
2020-08-01 16:31:27 +02:00
63cb93d7d7 Use Ctrl for all shortcuts
Remove the Cmd modifier on macOS, which was possible only for some
shortcuts but not all.

This paves the way to make the shortcut modifier customizable.
2020-08-01 16:31:08 +02:00
9d9dd1f143 Make expression order consistent
The condition "cmd" was always before "shift" in all expressions except
4.
2020-07-17 00:00:42 +02:00
eabaf6f7bd Simplify PASTE option for "set clipboard"
When the client requests to set the clipboard, it may request to press
the PASTE key in addition. To be a bit generic, it was stored as a flag
in ControlMessage.java.

But flags suggest that it represents a bitwise union. Use a simple
boolean instead.
2020-07-17 00:00:42 +02:00
199c74f62f Declare main() with argc/argv params in tests
Declaring the main method as "int main(void)" causes issues with SDL.

Fixes #1209 <https://github.com/Genymobile/scrcpy/issues/1209>
2020-07-15 12:17:04 +02:00
322f1512ea Inject WAKEUP instead of POWER
To power the device on, inject KEYCODE_WAKEUP to avoid a possible
race condition (the device might become off between the test
isScreenOn() and the POWER keycode injection).
2020-07-15 12:03:44 +02:00
30714aba34 Restore power mode to normal on cleanup
This avoids to let the device screen turned off (as enabled by Ctrl+o or
--turn-screen-off).

PR #1576 <https://github.com/Genymobile/scrcpy/pull/1576>
Fixes #1572 <https://github.com/Genymobile/scrcpy/issues/1572>
2020-07-09 22:33:11 +02:00
a973757fd1 Warn on ignored touch event
In theory, this was expected to only happen when a touch event is sent
just before the device is rotated, but some devices do not respect the
encoding size, causing an unexpected mismatch.

Refs #1518 <https://github.com/Genymobile/scrcpy/issues/1518>
2020-07-09 22:32:14 +02:00
deea29f52a Send touch event without pressure on button up
Refs #1518 <https://github.com/Genymobile/scrcpy/issues/1518>
2020-07-09 22:31:47 +02:00
5086e7b744 Update BUILD.md
Update the macOS section.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-07-07 20:50:09 +02:00
5fa46ad0c7 Fix constants name in comment 2020-07-07 15:47:25 +02:00
334964c380 Make setScreenPowerMode() method static
Its implementation does not use the instance at all.
2020-07-07 15:34:34 +02:00
f7d4b6d0db Do not crash on missing clipboard manager
Some devices have no clipboard manager.

In that case, do not try to enable clipboard synchronization to avoid
a crash.

Fixes #1440 <https://github.com/Genymobile/scrcpy/issues/1440>
Fixes #1556 <https://github.com/Genymobile/scrcpy/issues/1556>
2020-07-03 08:53:51 +02:00
e99b896ae2 README: Add Fedora install instructions
PR #1549 <https://github.com/Genymobile/scrcpy/pull/1549>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-06-27 15:51:44 +02:00
e8a565f9ea Fix touch events HiDPI-scaling
Touch events were HiDPI-scaled twice:
 - once because the position (provided as floats between 0 and 1) were
   converted in pixels using the drawable size (not the window size)
 - once due to screen_convert_to_frame_coords()

One possible fix could be to compute the position in pixels from the
window size instead, but this would unnecessarily round the event
position to the nearest window coordinates (instead of drawable
coordinates).

Instead, expose two separate functions to convert to frame coordinates
from either window or drawable coordinates.

Fixes #1536 <https://github.com/Genymobile/scrcpy/issues/1536>
Refs #15 <https://github.com/Genymobile/scrcpy/issues/15>
Refs e40532a376
2020-06-27 15:45:28 +02:00
8c27f59aa5 Improve linguistic
PR #1543 <https://github.com/Genymobile/scrcpy/pull/1543>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-06-25 22:08:02 +02:00
3c1ed5d86c Handle repeating keycodes
Initialize "repeat" count on key events.

PR #1519 <https://github.com/Genymobile/scrcpy/pull/1519>
Refs #1013 <https://github.com/Genymobile/scrcpy/pull/1013>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-06-19 22:30:17 +02:00
0ba74fbd9a Make scrcpy.h independant of other headers
The header scrcpy.h is intended to be the "public" API. It should not
depend on other internal headers.

Therefore, declare all required structs in this header and adapt
internal code.
2020-06-19 22:30:02 +02:00
29e5af76d4 Remove fprintf() call in tests
It should never have been committed.
2020-06-19 21:54:46 +02:00
dc7b60e619 Add option for disabling screensaver
PR #1502 <https://github.com/Genymobile/scrcpy/pull/1502>
Fixes #1370 <https://github.com/Genymobile/scrcpy/issues/1370>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-06-18 21:35:28 +02:00
42641d2737 Fix typo in README.md
PR #1523 <https://github.com/Genymobile/scrcpy/pull/1523>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-06-18 21:13:35 +02:00
1e4ee547b5 Make message buffer static
Now that the message max size is 256k, do not put the buffer on the
stack.
2020-06-11 23:11:20 +02:00
488d22d4e2 Increase clipboard size from 4k to 256k
Beyond 256k, SDL_GetClipboardText() returns an empty string on my
computer.

Fixes #1117 <https://github.com/Genymobile/scrcpy/issues/1117>
2020-06-11 23:11:20 +02:00
00d292b2f5 Fix receiver on partial device messages
If a single device message was read in several chunks from the control
socket, the communication was broken.
2020-06-11 23:11:20 +02:00
245999aec4 Serialize text size on 4 bytes
This will allow to send text having a size greater than 65535 bytes.
2020-06-11 23:11:17 +02:00
d91c5dcfd5 Rename MSG_SERIALIZED_MAX_SIZE to MSG_MAX_SIZE
For simplicity and consistency with the server part.
2020-06-11 23:08:04 +02:00
d202d7b205 Add unit test for big clipboard device message
Test clipboard synchronization from the device to the computer.
2020-06-11 23:06:02 +02:00
08c0c64af6 Rename test names from "event" to "msg"
The meson test names had not been changed when "event" had been renamed
to "message".

Ref: 28980bbc90
2020-06-11 23:06:02 +02:00
a00a8763d6 Avoid additional copy on Java text parsing
Directly pass the buffer range to the String constructor.
2020-06-11 23:06:02 +02:00
8f314c74b0 Reorganize message size constants
Make the max clipboard text length depend on the max message size.
2020-06-11 22:58:54 +02:00
3c0fc8f54f Mention sndcpy 2020-06-09 22:09:23 +02:00
1b73eff3c9 Add missing file in build_without_gradle.sh
Fixes #1481 <https://github.com/Genymobile/scrcpy/issues/1481>
PR #1482 <https://github.com/Genymobile/scrcpy/pull/1482>

Signed-off-by: Louis Leseur <louis.leseur@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-06-07 22:00:35 +02:00
6e1069a822 Configure log level for application only
Do not expose internal SDL logs to users.

Fixes #1441 <https://github.com/Genymobile/scrcpy/issues/1441>
2020-06-02 18:27:23 +02:00
c4323df976 Fix incorrect log return value
The function must return a SDL_LogPriority, but returned an enum
sc_log_level.

(It was harmless because this specific return should never happen, as
asserted.)
2020-06-02 18:27:14 +02:00
8ff07e0c88 Git-ignore release directories 2020-06-02 18:25:22 +02:00
e4efd75766 Avoid repetition for some shortcuts
Keeping the key pressed generate "repeat" events. It does not make sense
to repeat the event for rotation or turn screen off.
2020-05-29 22:02:41 +02:00
0e4a6f462b Mention stay awake limitation
The "stay awake" feature only works when the device is plugged in.

Refs #1445 <https://github.com/Genymobile/scrcpy/issues/1445>
2020-05-28 23:07:28 +02:00
8b73c90427 Mention how to turn the screen on in README
Now that Ctrl+Shift+o has been reactivated, mention it in the "turn
screen off" section.
2020-05-27 19:32:02 +02:00
ef91ab2841 Update links to v1.14 in README and BUILD 2020-05-27 19:31:12 +02:00
44fa4a090e Bump version to 1.14 2020-05-27 18:26:46 +02:00
dcde578a50 Reactivate "turn device screen on" feature
This reverts commit 8c8649cfcd.

I cannot reproduce the issue with Ctrl+Shift+o on any device, so in
practice it works, it's too bad to remove the feature for a random bug
on some Android versions on some devices.
2020-05-27 18:26:46 +02:00
93a5c5149d Push clipboard text only if not null
In practice, it does not change anything (it just avoids a spurious
wake-up), but semantically, it makes no sense to call
pushClipboardText() with a null value.
2020-05-27 18:26:39 +02:00
2ca8318b9d Improve manpage formatting
Add a new line to avoid unwanted text justification
2020-05-27 12:39:42 +02:00
d499ee53c9 Initialize a default log level
Clean up has been broken by 3df63c579d.

The verbosity was correctly initialized for the Server process, but not
for the CleanUp process.

To avoid the problem, initialize a default log level.
2020-05-27 12:05:29 +02:00
8f619f337b Upgrade platform-tools (30.0.0) for Windows
Include the latest version of adb in Windows releases.
2020-05-26 19:21:05 +02:00
fc1dec0270 Paste on "set clipboard" if possible
Ctrl+Shift+v synchronizes the computer clipboard to the Android device
clipboard. This feature had been added to provide a way to copy UTF-8
text from the computer to the device.

To make such a paste more straightforward, if the device runs at least
Android 7, also send a PASTE keycode to paste immediately.

<https://developer.android.com/reference/android/view/KeyEvent.html#KEYCODE_PASTE>

Fixes #786 <https://github.com/Genymobile/scrcpy/issues/786>
2020-05-25 20:59:21 +02:00
274b591d18 Fix union typo
The "set clipboard" event used the wrong union type to store its text.

In practice, it worked because both are at the same offset.
2020-05-25 18:41:05 +02:00
4bbabfb4ef Move injection methods to Device
Only the main injection method was exposed on Device, the convenience
methods were implemented in Controller.

For consistency, move them all to the Device class.
2020-05-25 03:28:33 +02:00
ffc57512b3 Avoid clipboard synchronization loop
The Android device listens for clipboard changes to synchronize with the
computer clipboard.

However, if the change comes from scrcpy (for example via Ctrl+Shift+v),
do not notify the change.
2020-05-25 03:28:33 +02:00
c7a33fac36 Log actions on the caller side
Some actions are exposed by the Device class, but logging success should
be done by the caller.
2020-05-25 03:28:33 +02:00
81573d81a0 Pass a Locale to toUpperCase()
Make lint happy.
2020-05-25 03:28:15 +02:00
5c2cf88a1d Rename THRESHOLD to threshold
Since the field is not final anymore, lint expects the name not to be
capitalized.
2020-05-25 03:22:07 +02:00
8f46e18426 Add --force-adb-forward
Add a command-line option to force "adb forward", without attempting
"adb reverse" first.

This is especially useful for using SSH tunnels without enabling remote
port forwarding.
2020-05-24 23:28:31 +02:00
ee3882f8be Fix typo in manpage 2020-05-24 23:14:30 +02:00
a3ef461d73 Add cli option to set the verbosity level
The verbosity was set either to info (in release mode) or debug (in
debug mode).

Add a command-line argument to change it, so that users can enable debug
logs using the release:

    scrcpy -Vdebug
2020-05-24 22:01:51 +02:00
3df63c579d Configure server verbosity from the client
Send the requested log level from the client.

This paves the way to configure it via a command-line argument.
2020-05-24 21:14:25 +02:00
56bff2f718 Avoid compiler warning
The field lock_video_orientation may only take values between -1 and 3
(included). But the compiler may trigger a warning on the sprintf()
call, because its type could represent values which could overflow the
string (like "-128"):

> warning: ‘%i’ directive writing between 1 and 4 bytes into a region of
> size 3 [-Wformat-overflow=]

Increase the buffer size to remove the warning.
2020-05-24 21:11:21 +02:00
080a4ee365 Add --codec-options
Add a command-line parameter to pass custom options to the device
encoder (as a comma-separated list of "key[:type]=value").

The list of possible codec options is available in the Android
documentation:
<https://d.android.com/reference/android/media/MediaFormat>

PR #1325 <https://github.com/Genymobile/scrcpy/pull/1325>
Refs #1226 <https://github.com/Genymobile/scrcpy/pull/1226>

Co-authored-by: Romain Vimont <rom@rom1v.com>
2020-05-24 14:54:34 +02:00
c7155a1954 Add unit test for big "set clipboard" event
Add a unit test to avoid regressions.

Refs #1425 <https://github.com/Genymobile/scrcpy/issues/1425>
2020-05-24 14:33:43 +02:00
517dbd9c85 Increase buffer size to fix "set clipboard" event
The buffer size must be greater than any event message.

Clipboard events may take up to 4096 bytes, so increase the buffer size.

Fixes #1425 <https://github.com/Genymobile/scrcpy/issues/1425>
2020-05-24 14:33:23 +02:00
acc4ef31df Synchronize device clipboard to computer
Automatically synchronize the device clipboard to the computer any time
it changes.

This allows seamless copy-paste from Android to the computer.

Fixes #1056 <https://github.com/Genymobile/scrcpy/issues/1056#issuecomment-631363684>
PR #1423 <https://github.com/Genymobile/scrcpy/pull/1423>
2020-05-24 14:31:49 +02:00
73e722784d Remove useless exception declaration
The interface declares it can throw a RemoteException, but the
implementation never throws such exception.
2020-05-23 18:21:54 +02:00
e1cd75792c Simplify rotation watcher call
Remove unnecessary private method (which was wrongly public).
2020-05-23 14:39:09 +02:00
ac4c8b4a3f Increase LOD bias for mipmapping
Mipmapping caused too much blurring.

Using a LOD bias of -1 instead of -0.5 seems a better compromise to
avoid low-quality downscaling while keeping sharp edges in any case.

Refs <https://github.com/Genymobile/scrcpy/issues/1394>
Refs <https://github.com/Genymobile/scrcpy/issues/40#issuecomment-591917787>
2020-05-23 14:32:16 +02:00
fae3f9eeab Remove warning when renderer is not OpenGL
Trilinear filtering can currently only be enabled for OpenGL renderers.

Do not print a warning if the renderer is not OpenGL, as it can confuses
users, while nothing is wrong.
2020-05-23 14:23:30 +02:00
f5aeecbc62 Reset window size on initialization
On macOS with renderer "metal", HiDPI scaling may be incorrect on
initialization when several displays are connected.

Resetting the window size fixes the problem.

Refs #15 <https://github.com/Genymobile/scrcpy/issues/15>
2020-05-23 14:19:09 +02:00
e40532a376 Manually position and scale the content
Position and scale the content "manually" instead of relying on the
renderer "logical size".

This avoids possible rounding differences between the computed window
size and the content size, causing one row or column of black pixels on
the bottom or on the right.

This also avoids HiDPI scale issues, by computing the scaling manually.

This will also enable to draw items at their expected size on the screen
(unscaled).

Fixes #15 <https://github.com/Genymobile/scrcpy/issues/15>
2020-05-23 14:19:09 +02:00
d860ad48e6 Extract optimal window size detection
Extract the computation to detect whether the current size of the window
is already optimal.

This will allow to reuse it for rendering.
2020-05-23 14:19:09 +02:00
ec047b501e Disable "resize to fit" in maximized state
In maximized state (but not fullscreen), it was possible to resize to
fit the device screen (with Ctrl+x or double-clicking on black borders).

This caused problems on macOS with the "expand to fullscreen" feature,
which behaves like a fullscreen mode but is seen as maximized by SDL.
In that state, resizing to fit causes unexpected results.

To keep the behavior consistent on all platforms, just disable "resize
to fit" when the window is maximized.
2020-05-23 14:19:09 +02:00
4c2e10fd74 Workaround maximized+fullscreen on Windows
On Windows, in maximized+fullscreen state, disabling fullscreen mode
unexpectedly triggers the "restored" then "maximized" events, leaving
the window in a weird state (maximized according to the events, but not
maximized visually).

Moreover, apply_pending_resize() asserts that fullscreen is disabled.

To avoid the issue, if fullscreen is set, just ignore the "restored"
event.
2020-05-23 14:19:09 +02:00
6b1da2fcff Simplify size changes in fullscreen or maximized
If the content size changes (due to rotation for example) while the
window is maximized or fullscreen, the resize must be applied once
fullscreen and maximized are disabled.

The previous strategy consisted in storing the windowed size, computing
the target size on rotation, and applying it on window restoration. But
tracking the windowed size (while ignoring the non-windowed size) was
tricky, due to unspecified order of SDL events (e.g. size changes can be
notified before "maximized" events), race conditions when reading window
flags, different behaviors on different platforms...

To simplify the whole resize management, store the old content size (the
frame size, possibly rotated) when it changes while the window is
maximized or fullscreen, so that the new optimal size can be computed on
window restoration.
2020-05-23 14:19:09 +02:00
2608b1dc62 Factorize window resize
When the content size changes, either on frame size or client rotation
changes, the window must be resized. Factorize for both cases.
2020-05-23 14:19:09 +02:00
31fa115655 Merge branch 'master' into dev 2020-05-23 14:18:47 +02:00
a85848a541 Fix Windows Ctrl Handler declaration
The handler function signature must include the calling convention
declaration.

Ref: <https://stackoverflow.com/questions/61717439/why-calling-setconsolectrlhandler-triggers-a-warning>
2020-05-11 01:32:54 +02:00
82446b6b0d Reference README.md from localized versions
Mention that the README.md is the only one being up-to-date.
2020-05-10 11:47:56 +02:00
24c8039244 Add Brazilian Portuguese translation
Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-05-10 11:46:24 +02:00
28abd98f7f Properly handle Ctrl+C on Windows
By default, Ctrl+C just kills the process on Windows. This caused
corrupted video files on recording.

Handle Ctrl+C properly to clean up properly.

Fixes #818 <https://github.com/Genymobile/scrcpy/issues/818>
2020-05-08 14:54:33 +02:00
e2d5f0e7fc Send scroll events as a touchscreen
Scroll events were sent with a mouse input device. When scrolling on a
list, this could cause the whole list to be focused, and drawn with the
focus color as background.

Send scroll events with a touchscreen input device instead (like motion
events).

Fixes #1362 <https://github.com/Genymobile/scrcpy/issues/1362>
2020-05-07 15:08:11 +02:00
1a9429f3c7 Improve bug report template
Insert a code block to (hopefully) increase the chances that users
format their terminal output.
2020-05-06 22:26:43 +02:00
0b4e484da0 Add a note about multi-display limitation
Secondary display mirroring is read-only before Android 10.
2020-05-06 22:21:33 +02:00
ead7ee4a03 Revert "Improve resizing workaround"
This reverts commit 92cb3a6661, which
broke the fullscreen/maximized restoration size on Windows.

Fixes #1346 <https://github.com/Genymobile/scrcpy/issues/1346>
2020-05-06 01:10:25 +02:00
74ece9b45b Simplify ScreenEncoder more
Commit 927d655ff6 removed the
iFrameInternal field and constructor argument.

Also remove it from createFormat() arguments.
2020-05-05 19:01:44 +02:00
c77024314d Add an option to keep the device awake
Add an option to prevent the device to sleep:

    scrcpy --stay-awake
    scrcpy -w

The initial state is restored on exit.

Fixes #631 <https://github.com/Genymobile/scrcpy/issues/631>
2020-05-02 02:14:25 +02:00
828327365a Reorder options in alphabetical order 2020-05-02 01:55:32 +02:00
4668638ee1 Handle "show touches" on the device-side
Now that the server can access the Android settings and clean up
properly, handle the "show touches" option from the server.

The initial state is now correctly restored, even on device
disconnection.
2020-05-02 01:55:30 +02:00
dbb0df607c Move constants to ServiceManager
PACKAGE_NAME and USER_ID could be use by several "managers", so move
them to the service manager.
2020-05-02 01:22:18 +02:00
2f74ec2518 Add a clean up process on the device
In order to clean up on close, use a separate process which is not
killed when the device is disconnected (even if the main process itself
is killed).
2020-05-02 01:22:18 +02:00
8c6799297b Implement access to settings without Context
Expose methods to access the Android settings using private APIs.

This allows to read and write settings values immediately without
starting a new process to call "settings".
2020-05-02 01:22:10 +02:00
62c0c1321f Apply workarounds only on error
To avoid NullPointerException on some devices, workarounds have been
implemented. But these workaround produce (harmless) internal errors
causing exceptions to be printed in the console.

To avoid this problem, apply the workarounds only if it fails without
them.

Fixes #994 <https://github.com/Genymobile/scrcpy/issues/994>
Refs #365 <https://github.com/Genymobile/scrcpy/issues/365>
Refs #940 <https://github.com/Genymobile/scrcpy/issues/940>
2020-05-01 19:42:31 +02:00
d4eeb1c84d Fix AutoAdb url
PR #1344 <https://github.com/Genymobile/scrcpy/pull/1344>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-05-01 19:34:08 +02:00
4e9e712312 Update links to v1.13 in README and BUILD 2020-04-29 22:56:26 +02:00
9babe26805 Bump version to 1.13 2020-04-29 22:24:08 +02:00
76567e684a Upgrade SDL (2.0.12) for Windows
Include the latest version of SDL in Windows releases.
2020-04-27 21:45:15 +02:00
b55ca127f8 Upgrade FFmpeg (4.2.2) for Windows
Include the latest version of FFmpeg in Windows releases.
2020-04-27 21:45:15 +02:00
a12b938234 Merge branch 'master' into dev 2020-04-27 21:44:59 +02:00
a14840a515 Fix typo in comments 2020-04-24 23:01:58 +02:00
8581d6850b Stabilize auto-resize
The window dimensions are integers, so resizing to fit the content may
not be exact.

When computing the optimal size, it could cause to reduce alternatively
the width and height by few pixels, making the "optimal size" unstable.

To avoid this problem, check if the optimal size is already correct
either by keeping the width or the height.
2020-04-24 22:52:02 +02:00
92cb3a6661 Improve resizing workaround
Call the same method as when the event is received on the event loop, so
that the behavior is the same in both cases.
2020-04-24 21:36:25 +02:00
561ede444e Mention Ubuntu 20.04 package
Ubuntu 20.04 has been released today, and scrcpy is available in their
repositories: <https://packages.ubuntu.com/focal/scrcpy>
2020-04-23 16:36:48 +02:00
3c9ae99dda Move rotation coordinates to screen
Move the window-to-frame coordinates conversion from the input manager
to the screen.

This will allow to apply more screen-related transformations without
impacting the input manager.
2020-04-18 02:15:22 +02:00
44f720e4a4 Log new size on auto-resize request
On "resize to fit" and "resize to pixel-perfect", log the new size.
2020-04-18 02:15:22 +02:00
125c5561e8 Use MediaFormat constant for MIME type
Replace "video/avc" by MIMETYPE_VIDEO_AVC.

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-04-18 02:14:42 +02:00
14ead499fd Fix touch coordinates on rotated display
The touch coordinates were not rotated.
2020-04-17 18:17:12 +02:00
94a7f1a0f8 Disable input events when necessary
Disable input events on secondary displays before Android 10, even if
FLAG_PRESENTATION is not set.

Refs #1288 <https://github.com/Genymobile/scrcpy/issues/1288>
2020-04-16 20:54:00 +02:00
cc22f4622a Mention mipmapping in FAQ 2020-04-15 17:39:51 +02:00
11a61b2cb3 Add option --no-mipmaps
Add an option to disable trilinear filtering even if mipmapping is
available.
2020-04-15 17:39:51 +02:00
bea7658807 Enable trilinear filtering for OpenGL
Improve downscaling quality if mipmapping is available.

Suggested-by: Giumo Clanjor (哆啦比猫/兰威举) <cjxgm2@gmail.com>

Fixes #40 <https://github.com/Genymobile/scrcpy/issues/40>
Ref: <https://github.com/Genymobile/scrcpy/issues/40#issuecomment-591917787>
2020-04-15 17:39:51 +02:00
8a9b20b27e Add --render-driver command-line option
Add an option to set a render driver hint (SDL_HINT_RENDER_DRIVER).
2020-04-15 17:39:51 +02:00
d62eb2b11c Fix typo in README 2020-04-15 09:57:59 +02:00
eb8f7a1f28 Require Meson 0.48 to get rid of warnings
Debian buster (stable) provides Meson 0.49, which is also available in
stretch (oldstable) backports. It's time to abandon Meson 0.37.

Ref: 20b3f101a4
2020-04-13 22:47:03 +02:00
270d0bf639 Rename max length constant for text injection
To avoid confusion with the max text size for clipboard, rename the
constant limiting the text injection length.
2020-04-13 19:38:43 +02:00
95fa1a69e4 Workaround compiler warning
Some compilers warns on uninitialized value in impossible case:

    warning: variable 'result' is used uninitialized whenever switch
    default is taken [-Wsometimes-uninitialized]
2020-04-13 16:33:21 +02:00
ea46d3ab68 Add missing include string.h
Include <string.h> for strdup() and strtok_r().
2020-04-13 16:33:21 +02:00
7eb16ce364 Fix log format warning
The expression port + 1 is promoted to int, but printed as uint16_t.
2020-04-13 16:33:19 +02:00
927d655ff6 Simplify ScreenEncoder
Do not handle iFrameInterval field and parameter, it is never used
dynamically.
2020-04-12 02:09:28 +02:00
ee2894779a Remove unused lockedVideoOrientation field
During PR #1151, this field has been moved to ScreenInfo, but has not
been removed from ScreenEncoder.
2020-04-12 02:08:16 +02:00
1c6207f8ce Merge branch 'master' into dev 2020-04-12 00:31:57 +02:00
ab52b36895 Reorder options in alphabetical order 2020-04-11 14:31:14 +02:00
9f4735ede3 Fix double click on rotated display
A double-click outside the device content (in the black borders) resizes
so that black borders are removed. But the display rotation was not
taken into account to detect the content.

Use the content size instead of the frame size to fix the issue.

Ref: <https://github.com/Genymobile/scrcpy/issues/898#issuecomment-610993695>
2020-04-08 16:37:33 +02:00
6295c1a110 Remap event positions on rotated display
If the display is rotated, the position of clicks must be adapted.
2020-04-08 14:27:25 +02:00
f3fba3c4b9 Store rotated content size
This avoids to compute it every time from the frame size.
2020-04-08 14:12:54 +02:00
c1ebea26e6 Register rotation watcher on selected display
PR #1275 <https://github.com/Genymobile/scrcpy/pull/1275>

Signed-off-by: Kostiantyn Luzan <vblack2006@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-04-08 12:09:24 +02:00
f07d21f050 Suppress DiscouragedPrivateApi lint warning 2020-04-08 12:09:24 +02:00
a8fd4aec9a Remove --fullscreen validation
Many options are meaningless if --no-display is set.

We don't want to validate all possible combinations, so don't make an
exception for --fullscreen.
2020-04-08 12:09:24 +02:00
cbde7b964a Improve documentation for consistency
Make --lock-video-orientation documentation consistent with that of
--rotation.
2020-04-08 12:09:24 +02:00
28c71c528f Add --rotation command-line option
In addition to Ctrl+Left and Ctrl+Right shortcuts, add a command-line
parameter to set the initial rotation.
2020-04-08 12:09:22 +02:00
d48b375a1d Add shortcuts to rotate display
Add Ctrl+Left and Ctrl+Right shortcuts to rotate the display (the
content of the scrcpy window).

Contrary to --lock-video-orientation, the rotation has no impact on
recording, and can be changed dynamically (and immediately).

Fixes #218 <https://github.com/Genymobile/scrcpy/issues/218>
2020-04-08 12:02:26 +02:00
fd63e7eb5a Format shortcut documentation
For consistency, start the descriptions with a capital letter.
2020-04-08 12:02:15 +02:00
cdd8edbbb6 Add a note about prebuilt server in BUILD.md
Mention that it works with a matching client version.
2020-04-07 23:06:33 +02:00
9b9e717c41 Explain master and dev branches in BUILD
People may not guess that `master` is not the development branch.
2020-04-07 10:43:20 +02:00
15e4da08a3 Improve "low quality" section in FAQ 2020-04-07 10:37:19 +02:00
2afbfc2c75 Add Android device and version in issue template 2020-04-03 21:29:09 +02:00
2cf022491f Add issue templates
Closes #1157 <https://github.com/Genymobile/scrcpy/issues/1157>
2020-04-03 18:51:18 +02:00
d30593e1d5 gitignore: Add x/ directory
People following default build instructions can be caught off guard by
seeing the build artifacts in the git tree.

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-04-03 18:20:33 +02:00
9e78b765da Update to Gradle 6.3
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
Signed-off-by: Romain Vimont <rom@rom1v.com>

-- Note from committer:

The binary gradle/wrapper/gradle-wrapper.jar has the expected SHA-256
checksum:

    $ curl -L https://services.gradle.org/distributions/gradle-6.3-wrapper.jar.sha256
    1cef53de8dc192036e7b0cc47584449b0cf570a00d560bfaa6c9eabe06e1fc06

All the changed files match an upgrade executed independently:
<https://docs.gradle.org/current/userguide/gradle_wrapper.html#sec:upgrading_wrapper>
2020-04-03 18:11:35 +02:00
271de0954a Update to AGP 3.6.2
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2020-04-03 18:10:51 +02:00
54ccccd883 Replace SDL_Atomic by stdatomic from C11
There is no reason to use SDL atomics.
2020-04-02 21:05:26 +02:00
bea1c11f8e Do not log success on failure
If calling the private API does not work, an exception is printed. In
that case, do not log that the action succeeded.
2020-04-02 21:05:26 +02:00
94e1696869 Do not warn on terminating the server
If the server is already dead, terminating it fails. This is expected.
2020-04-02 21:05:26 +02:00
a346bb80f4 Do not block on accept() if server died
The server may die before connecting to the client. In that case, the
client was blocked indefinitely (until Ctrl+C) on accept().

To avoid the problem, close the server socket once the server process is
dead.
2020-04-02 21:05:26 +02:00
d421741a83 Wait server from a separate thread
Create a thread just to wait for the server process exit.

This paves the way to simply wake up a blocking accept() in a portable
way.
2020-04-02 21:05:26 +02:00
64d5edce92 Refactor server_start() error handling
This avoids cleanup duplication.
2020-04-02 21:05:26 +02:00
4150eedcdf Add display id parameter
Add --display command line parameter to specify a display id.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-04-02 21:02:52 +02:00
5031b2c8ff Remove MagicNumber checkstyle
There are a lot of "magic numbers" that we really don't want to extract
as a constant.

Until now, many @SuppressWarnings annotations were added, but it makes
no sense to check for magic number if we silent the warnings everywhere.
2020-03-28 22:08:16 +01:00
4adf5fde6d Log device details on server start 2020-03-28 15:52:02 +01:00
e050cfdcd6 Fix static_assert() parameters
In C11, static_assert() expects a message.
2020-03-27 14:01:56 +01:00
dc7c677728 Accept negative window position
It seems to work on some window managers.

Fixes #1242 <https://github.com/Genymobile/scrcpy/issues/1242>
2020-03-26 22:52:41 +01:00
3504c0016b Add tests for control message length
This will avoid regressions for #1245.

<https://github.com/Genymobile/scrcpy/issues/1245>
2020-03-26 22:48:01 +01:00
89d1602185 Fix expected message length for touch events
The expected length for a touch event control message was incorrect. As
a consequence, a BufferUnderflowException could occur.

Fixes #1245 <https://github.com/Genymobile/scrcpy/issues/1245>
2020-03-26 22:45:43 +01:00
566ba766af Remove unused constant
It has not been removed when mouse and touch events have been merged.
2020-03-26 22:43:53 +01:00
a0af402d96 Fix the printed versions (were opposite)
PR #1224 <https://github.com/Genymobile/scrcpy/pull/1224>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-03-19 19:28:57 +01:00
7bb91638ad Improve FAQ 2020-03-19 19:22:58 +01:00
600df37753 Mention AutoAdb in README 2020-03-19 19:16:08 +01:00
902b99174d Fix server debugger for Android >= 9
Add a compilation flag to select the debugger method to use:
 - old: Android < 9
 - new: Android >= 9

See <https://github.com/Genymobile/scrcpy/issues/1187#issuecomment-599075661>
2020-03-19 19:15:43 +01:00
cd69eb4a4f Handle NumPad events when NumLock is disabled
PR #1188 <https://github.com/Genymobile/scrcpy/pull/1188>
Fixes #1048 <https://github.com/Genymobile/scrcpy/issues/1048>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-03-14 17:37:14 +01:00
bc7508427b Add scoop instructions for Windows
PR #1202 <https://github.com/Genymobile/scrcpy/pull/1202>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-03-14 17:11:49 +01:00
24ade6ad77 Simplify Chocolatey documentation 2020-03-14 17:07:20 +01:00
ae2d094362 Handle locked video orientation from ScreenInfo
Centralize video size management in ScreenInfo.

This allows to always send the correct initial video size to the client
if the video orientation is locked.
2020-03-08 10:38:31 +01:00
c5f5d1e456 Rename "rotation" to "device rotation"
This paves the way to reduce confusion in ScreenInfo when it will handle
locked video orientation.
2020-03-04 22:15:10 +01:00
63286424bb Compute all screen info from ScreenInfo
Screen information was partially initialized from Device. Move this
initialization to ScreenInfo.
2020-03-04 22:15:10 +01:00
da18c9cdab Remove useles import 2020-03-04 22:15:10 +01:00
c396758b4e Remove link to Windows 32 bits release
Binaries created with MinGW (even a simple Hello World) are detected as
malware by some anti-virus. For some reason, only the 32 bits version of
scrcpy is impacted.

Since users should use the 64 bits version by default anyway, remove the
link to the 32 bits version from the main page.

The 32 bits release is still available in the "releases" tab.

See <https://github.com/Genymobile/scrcpy/issues/1102>
2020-03-03 21:39:27 +01:00
d3281f4b67 Show a friendly hint for adb installation
Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-02-27 21:28:42 +01:00
1982bc439b Add option to lock video orientation
PR #1151 <https://github.com/Genymobile/scrcpy/pull/1151>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-02-27 21:24:37 +01:00
ef56cc6ff7 Retrieve screen info once
The method getScreenInfo() is synchronized, and the result may change
between calls.

Call it once and store the result in a local variable.
2020-02-27 21:24:37 +01:00
c0f428eb05 Merge branch 'master' into dev 2020-02-27 21:24:32 +01:00
4794ca8ae7 Use linear filtering
Anisotropic filtering makes no sense for scrcpy use case.

This (semantically) reverts 9e328ef98b.
2020-02-25 12:20:18 +01:00
f903cd376d Documentation rectifications
PR #1151 <https://github.com/Genymobile/scrcpy/pull/1151>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-02-16 16:04:17 +01:00
e8127375ae Add Chocolatey for Windows install
PR #1144 <https://github.com/Genymobile/scrcpy/pull/1144>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-02-15 22:26:30 +01:00
1144f64214 Indicate that -s can also be used for TCP/IP 2020-02-06 18:42:08 +01:00
39356602ed Mention scrcpy Debian package in README 2020-01-19 16:19:51 +01:00
0fb22c3e98 Happy new year 2020! 2020-01-19 16:04:20 +01:00
96bd2c974d Do not report workarounds errors
Some workarounds are needed on some devices. But applying them may cause
exceptions on other devices, where they are not necessary anyway.

Do not report these errors in release builds.

Closes #994 <https://github.com/Genymobile/scrcpy/issues/994>
2020-01-19 15:52:24 +01:00
137 changed files with 8137 additions and 1888 deletions

29
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,29 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
- [ ] I have read the [FAQ](https://github.com/Genymobile/scrcpy/blob/master/FAQ.md).
- [ ] I have searched in existing [issues](https://github.com/Genymobile/scrcpy/issues).
**Environment**
- OS: [e.g. Debian, Windows, macOS...]
- scrcpy version: [e.g. 1.12.1]
- installation method: [e.g. manual build, apt, snap, brew, Windows release...]
- device model:
- Android version: [e.g. 10]
**Describe the bug**
A clear and concise description of what the bug is.
On errors, please provide the output of the console (and `adb logcat` if relevant).
```
Please paste terminal output in a code block.
```
Please do not post screenshots of your terminal, just post the content as text instead.

View File

@ -0,0 +1,22 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
- [ ] I have checked that a similar [feature request](https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3A%22feature+request%22) does not already exist.
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

5
.gitignore vendored
View File

@ -1,4 +1,9 @@
build/
/dist/
/build-*/
/build_*/
/release-*/
.idea/
.gradle/
/x/
local.properties

View File

@ -8,6 +8,22 @@ case, use the [prebuilt server] (so you will not need Java or the Android SDK).
[prebuilt server]: #prebuilt-server
## Branches
### `master`
The `master` branch concerns the latest release, and is the home page of the
project on Github.
### `dev`
`dev` is the current development branch. Every commit present in `dev` will be
in the next release.
If you want to contribute code, please base your commits on the latest `dev`
branch.
## Requirements
@ -160,8 +176,8 @@ Additionally, if you want to build the server, install Java 8 from Caskroom, and
make it avaliable from the `PATH`:
```bash
brew tap caskroom/versions
brew cask install java8
brew tap homebrew/cask-versions
brew cask install adoptopenjdk/openjdk/adoptopenjdk8
export JAVA_HOME="$(/usr/libexec/java_home --version 1.8)"
export PATH="$JAVA_HOME/bin:$PATH"
```
@ -174,12 +190,17 @@ See [pierlon/scrcpy-docker](https://github.com/pierlon/scrcpy-docker).
## Common steps
If you want to build the server, install the [Android SDK] (_Android Studio_),
and set `ANDROID_HOME` to its directory. For example:
and set `ANDROID_SDK_ROOT` to its directory. For example:
[Android SDK]: https://developer.android.com/studio/index.html
```bash
export ANDROID_HOME=~/android/sdk
# Linux
export ANDROID_SDK_ROOT=~/Android/Sdk
# Mac
export ANDROID_SDK_ROOT=~/Library/Android/sdk
# Windows
set ANDROID_SDK_ROOT=%LOCALAPPDATA%\Android\sdk
```
If you don't want to build the server, use the [prebuilt server].
@ -233,10 +254,10 @@ You can then [run](README.md#run) _scrcpy_.
## Prebuilt server
- [`scrcpy-server-v1.12.1`][direct-scrcpy-server]
_(SHA-256: 63e569c8a1d0c1df31d48c4214871c479a601782945fed50c1e61167d78266ea)_
- [`scrcpy-server-v1.17`][direct-scrcpy-server]
_(SHA-256: 11b5ad2d1bc9b9730fb7254a78efd71a8ff46b1938ff468e47a21b653a1b6725_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.12.1/scrcpy-server-v1.12.1
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.17/scrcpy-server-v1.17
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:
@ -247,3 +268,6 @@ meson x --buildtype release --strip -Db_lto=true \
ninja -Cx
sudo ninja -Cx install
```
The server only works with a matching client version (this server works with the
`master` branch).

View File

@ -189,7 +189,7 @@ The client uses 4 threads:
recording,
- the **controller** thread, sending _control messages_ to the server,
- the **receiver** thread (managed by the controller), receiving _device
messages_ from the client.
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
@ -214,7 +214,7 @@ When a new decoded frame is available, the decoder _swaps_ the decoding and
rendering frame (with proper synchronization). Thus, it immediatly 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 its muxes the raw
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
@ -282,6 +282,15 @@ meson x -Dserver_debugger=true
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.

View File

@ -3,16 +3,16 @@
다음은 자주 제보되는 문제들과 그들의 현황입니다.
### Window 운영체제에서, 디바이스가 발견되지 않습니다.
### Windows 운영체제에서, 디바이스가 발견되지 않습니다.
가장 흔한 제보는 `adb`에 발견되지 않는 디바이스 혹은 권한 관련 문제입니다.
다음 명령어를 호출하여 모든 것들에 이상이 없는지 확인하세요:
adb devices
Window는 당신의 디바이스를 감지하기 위해 [drivers]가 필요할 수도 있습니다.
Windows는 당신의 디바이스를 감지하기 위해 [드라이버]가 필요할 수도 있습니다.
[drivers]: https://developer.android.com/studio/run/oem-usb.html
[드라이버]: https://developer.android.com/studio/run/oem-usb.html
### 내 디바이스의 미러링만 가능하고, 디바이스와 상호작용을 할 수 없습니다.

207
FAQ.md
View File

@ -3,19 +3,104 @@
Here are the common reported problems and their status.
### On Windows, my device is not detected
## `adb` issues
The most common is your device not being detected by `adb`, or is unauthorized.
Check everything is ok by calling:
`scrcpy` execute `adb` commands to initialize the connection with the device. If
`adb` fails, then scrcpy will not work.
adb devices
In that case, it will print this error:
Windows may need some [drivers] to detect your device.
> ERROR: "adb push" returned with value 1
This is typically not a bug in _scrcpy_, but a problem in your environment.
To find out the cause, execute:
```bash
adb devices
```
### `adb` not found
You need `adb` accessible from your `PATH`.
On Windows, the current directory is in your `PATH`, and `adb.exe` is included
in the release, so it should work out-of-the-box.
### Device unauthorized
Check [stackoverflow][device-unauthorized].
[device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized
### Device not detected
> adb: error: failed to get feature set: no devices/emulators found
If your device is not detected, you may need some [drivers] (on Windows).
[drivers]: https://developer.android.com/studio/run/oem-usb.html
### I can only mirror, I cannot interact with the device
### Several devices connected
If several devices are connected, you will encounter this error:
> adb: error: failed to get feature set: more than one device/emulator
the identifier of the device you want to mirror must be provided:
```bash
scrcpy -s 01234567890abcdef
```
Note that if your device is connected over TCP/IP, you'll get this message:
> adb: error: more than one device/emulator
> ERROR: "adb reverse" returned with value 1
> WARN: 'adb reverse' failed, fallback to 'adb forward'
This is expected (due to a bug on old Android versions, see [#5]), but in that
case, scrcpy fallbacks to a different method, which should work.
[#5]: https://github.com/Genymobile/scrcpy/issues/5
### Conflicts between adb versions
> adb server version (41) doesn't match this client (39); killing...
This error occurs when you use several `adb` versions simultaneously. You must
find the program using a different `adb` version, and use the same `adb` version
everywhere.
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
scrcpy
```
### Device disconnected
If _scrcpy_ stops itself with the warning "Device disconnected", then the
`adb` connection has been closed.
Try with another USB cable or plug it into another USB port. See [#281] and
[#283].
[#281]: https://github.com/Genymobile/scrcpy/issues/281
[#283]: https://github.com/Genymobile/scrcpy/issues/283
## Control issues
### Mouse and keyboard do not work
On some devices, you may need to enable an option to allow [simulating input].
In developer options, enable:
@ -29,22 +114,43 @@ In developer options, enable:
### Mouse clicks at wrong location
On MacOS, with HiDPI support and multiple screens, input location are wrongly
scaled. See [issue 15].
scaled. See [#15].
[issue 15]: https://github.com/Genymobile/scrcpy/issues/15
[#15]: https://github.com/Genymobile/scrcpy/issues/15
A workaround is to build with HiDPI support disabled:
Open _scrcpy_ directly on the monitor you use it.
```bash
meson x --buildtype release -Dhidpi_support=false
### Special characters do not work
Injecting text input is [limited to ASCII characters][text-input]. A trick
allows to also inject some [accented characters][accented-characters], but
that's all. See [#37].
[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
[#37]: https://github.com/Genymobile/scrcpy/issues/37
## 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
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:
```
scrcpy --render-driver=opengl
```
However, the video will be displayed at lower resolution.
### The quality is low on HiDPI display
On Windows, you may need to configure the [scaling behavior].
You may also need to configure the [scaling behavior]:
> `scrcpy.exe` > Properties > Compatibility > Change high DPI settings >
> Override high DPI scaling behavior > Scaling performed by: _Application_.
@ -52,6 +158,7 @@ On Windows, you may need to configure the [scaling behavior].
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
### KWin compositor crashes
On Plasma Desktop, compositor is disabled while _scrcpy_ is running.
@ -61,19 +168,29 @@ As a workaround, [disable "Block compositing"][kwin].
[kwin]: https://github.com/Genymobile/scrcpy/issues/114#issuecomment-378778613
### I get an error "Could not open video stream"
## Crashes
### 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
```
> ```
> 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:
@ -82,3 +199,41 @@ scrcpy -m 1920
scrcpy -m 1024
scrcpy -m 800
```
You could also try another [encoder](README.md#encoder).
## Command line on Windows
Some Windows users are not familiar with the command line. Here is how to open a
terminal and run `scrcpy` with arguments:
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/

View File

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

699
README.id.md Normal file
View File

@ -0,0 +1,699 @@
_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 :
- [`scrcpy-win64-v1.16.zip`][direct-win64]
_(SHA-256: 3f30dc5db1a2f95c2b40a0f5de91ec1642d9f53799250a8c529bc882bc0918f0)_
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.16/scrcpy-win64-v1.16.zip
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-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
## 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/

View File

@ -1,3 +1,5 @@
_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
@ -475,7 +477,7 @@ _²화면이 꺼진 상태에서 우클릭 시 다시 켜지며, 그 외의 상
## 라이선스
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2019 Romain Vimont
Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

391
README.md
View File

@ -1,4 +1,6 @@
# scrcpy (v1.12.1)
# scrcpy (v1.17)
[Read in another language](#translations)
This application provides display and control of Android devices connected on
USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access.
@ -34,11 +36,15 @@ control it using keyboard and mouse.
## Get the 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>
### Linux
On Linux, you typically need to [build the app manually][BUILD]. Don't worry,
it's not that hard.
On Debian (_testing_ and _sid_ for now) and Ubuntu (20.04):
```
apt install scrcpy
```
A [Snap] package is available: [`scrcpy`][snap-link].
@ -46,6 +52,11 @@ A [Snap] package is available: [`scrcpy`][snap-link].
[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager)
For Fedora, a [COPR] package is available: [`scrcpy`][copr-link].
[COPR]: https://fedoraproject.org/wiki/Category:Copr
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
For Arch Linux, an [AUR] package is available: [`scrcpy`][aur-link].
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
@ -56,19 +67,38 @@ For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
You could also [build the app manually][BUILD] (don't worry, it's not that
hard).
### Windows
For Windows, for simplicity, prebuilt archives with all the dependencies
(including `adb`) are available:
For Windows, for simplicity, a prebuilt archive with all the dependencies
(including `adb`) is available:
- [`scrcpy-win32-v1.12.1.zip`][direct-win32]
_(SHA-256: 0f4b3b063536b50a2df05dc42c760f9cc0093a9a26dbdf02d8232c74dab43480)_
- [`scrcpy-win64-v1.12.1.zip`][direct-win64]
_(SHA-256: 57d34b6d16cfd9fe169bc37c4df58ebd256d05c1ea3febc63d9cb0a027ab47c9)_
- [`scrcpy-win64-v1.17.zip`][direct-win64]
_(SHA-256: 8b9e57993c707367ed10ebfe0e1ef563c7a29d9af4a355cd8b6a52a317c73eea)_
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.12.1/scrcpy-win32-v1.12.1.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.12.1/scrcpy-win64-v1.12.1.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.17/scrcpy-win64-v1.17.zip
It is also available in [Chocolatey]:
[Chocolatey]: https://chocolatey.org/
```bash
choco install scrcpy
choco install adb # if you don't have it yet
```
And in [Scoop]:
```bash
scoop install scrcpy
scoop install adb # if you don't have it yet
```
[Scoop]: https://scoop.sh
You can also [build the app manually][BUILD].
@ -86,6 +116,10 @@ brew install scrcpy
You need `adb`, accessible from your `PATH`. If you don't have it yet:
```bash
# Homebrew >= 2.6.0
brew install --cask android-platform-tools
# Homebrew < 2.6.0
brew cask install android-platform-tools
```
@ -158,6 +192,39 @@ scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0)
If `--max-size` is also specified, resizing is applied after cropping.
#### Lock video orientation
To lock the orientation of the mirroring:
```bash
scrcpy --lock-video-orientation 0 # natural orientation
scrcpy --lock-video-orientation 1 # 90° counterclockwise
scrcpy --lock-video-orientation 2 # 180°
scrcpy --lock-video-orientation 3 # 90° clockwise
```
This affects recording orientation.
The [window may also be rotated](#rotation) independently.
#### Encoder
Some devices have more than one encoder, and some of them may cause issues or
crash. It is possible to select a different encoder:
```bash
scrcpy --encoder OMX.qcom.video.encoder.avc
```
To list the available encoders, you could pass an invalid encoder name, the
error will give the available encoders:
```bash
scrcpy --encoder _
```
### Recording
It is possible to record the screen while mirroring:
@ -173,7 +240,6 @@ To disable mirroring while recording:
scrcpy --no-display --record file.mp4
scrcpy -Nr file.mkv
# interrupt recording with Ctrl+C
# Ctrl+C does not terminate properly on Windows, so disconnect the device
```
"Skipped frames" are recorded, even if they are not displayed in real time (for
@ -191,7 +257,13 @@ _Scrcpy_ uses `adb` to communicate with the device, and `adb` can [connect] to a
device over TCP/IP:
1. Connect the device to the same Wi-Fi as your computer.
2. Get your device IP address (in Settings → About phone → Status).
2. Get your device IP address, in Settings → About phone → Status, or by
executing this command:
```bash
adb shell ip route | awk '{print $9}'
```
3. Enable adb over TCP/IP on your device: `adb tcpip 5555`.
4. Unplug your device.
5. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP`)_.
@ -216,8 +288,25 @@ scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # short version
```
If the device is connected over TCP/IP:
```bash
scrcpy --serial 192.168.0.1:5555
scrcpy -s 192.168.0.1:5555 # short version
```
You can start several instances of _scrcpy_ for several devices.
#### Autostart on device connection
You could use [AutoAdb]:
```bash
autoadb scrcpy -s '{}'
```
[AutoAdb]: https://github.com/rom1v/autoadb
#### SSH tunnel
To connect to a remote device, it is possible to connect a local `adb` client to
@ -236,6 +325,22 @@ From another terminal:
scrcpy
```
To avoid enabling remote port forwarding, you could force a forward connection
instead (notice the `-L` instead of `-R`):
```bash
adb kill-server # kill the local adb server on 5037
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
# keep this open
```
From another terminal:
```bash
scrcpy --force-adb-forward
```
Like for wireless connections, it may be useful to reduce quality:
```
@ -285,7 +390,35 @@ scrcpy --fullscreen
scrcpy -f # short version
```
Fullscreen can then be toggled dynamically with `Ctrl`+`f`.
Fullscreen can then be toggled dynamically with <kbd>MOD</kbd>+<kbd>f</kbd>.
#### Rotation
The window may be rotated:
```bash
scrcpy --rotation 1
```
Possibles values are:
- `0`: no rotation
- `1`: 90 degrees counterclockwise
- `2`: 180 degrees
- `3`: 90 degrees clockwise
The rotation can also be changed dynamically with <kbd>MOD</kbd>+<kbd>←</kbd>
_(left)_ and <kbd>MOD</kbd>+<kbd>→</kbd> _(right)_.
Note that _scrcpy_ manages 3 different rotations:
- <kbd>MOD</kbd>+<kbd>r</kbd> requests the device to switch between portrait
and landscape (the current running app may refuse, if it does support the
requested orientation).
- [`--lock-video-orientation`](#lock-video-orientation) changes the mirroring
orientation (the orientation of the video sent from the device to the
computer). This affects the recording.
- `--rotation` (or <kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>)
rotates only the window content. This affects only the display, not the
recording.
### Other mirroring options
@ -300,6 +433,37 @@ scrcpy --no-control
scrcpy -n
```
#### Display
If several displays are available, it is possible to select the display to
mirror:
```bash
scrcpy --display 1
```
The list of display ids can be retrieved by:
```
adb shell dumpsys display # search "mDisplayId=" in the output
```
The secondary display may only be controlled if the device runs at least Android
10 (otherwise it is mirrored in read-only).
#### Stay awake
To prevent the device to sleep after some delay when the device is plugged in:
```bash
scrcpy --stay-awake
scrcpy -w
```
The initial state is restored when scrcpy is closed.
#### Turn screen off
It is possible to turn the device screen off while mirroring on start with a
@ -310,9 +474,22 @@ scrcpy --turn-screen-off
scrcpy -S
```
Or by pressing `Ctrl`+`o` at any time.
Or by pressing <kbd>MOD</kbd>+<kbd>o</kbd> at any time.
To turn it back on, press <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>.
On Android, the `POWER` button always turns the screen on. For convenience, if
`POWER` is sent via scrcpy (via right-click or <kbd>MOD</kbd>+<kbd>p</kbd>), it
will force to turn the screen off after a small delay (on a best effort basis).
The physical `POWER` button will still cause the screen to be turned on.
It can also be useful to prevent the device from sleeping:
```bash
scrcpy --turn-screen-off --stay-awake
scrcpy -Sw
```
To turn it back on, press `POWER` (or `Ctrl`+`p`).
#### Render expired frames
@ -333,7 +510,8 @@ device).
Android provides this feature in _Developers options_.
_Scrcpy_ provides an option to enable this feature on start and disable on exit:
_Scrcpy_ provides an option to enable this feature on start and restore the
initial value on exit:
```bash
scrcpy --show-touches
@ -343,24 +521,78 @@ scrcpy -t
Note that it only shows _physical_ touches (with the finger on the device).
#### Disable screensaver
By default, scrcpy does not prevent the screensaver to run on the computer.
To disable it:
```bash
scrcpy --disable-screensaver
```
### Input control
#### Rotate device screen
Press `Ctrl`+`r` to switch between portrait and landscape modes.
Press <kbd>MOD</kbd>+<kbd>r</kbd> to switch between portrait and landscape
modes.
Note that it rotates only if the application in foreground supports the
requested orientation.
#### Copy-paste
It is possible to synchronize clipboards between the computer and the device, in
both directions:
Any time the Android clipboard changes, it is automatically synchronized to the
computer clipboard.
Any <kbd>Ctrl</kbd> shortcut is forwarded to the device. In particular:
- <kbd>Ctrl</kbd>+<kbd>c</kbd> typically copies
- <kbd>Ctrl</kbd>+<kbd>x</kbd> typically cuts
- <kbd>Ctrl</kbd>+<kbd>v</kbd> typically pastes (after computer-to-device
clipboard synchronization)
This typically works as you expect.
The actual behavior depends on the active application though. For example,
_Termux_ sends SIGINT on <kbd>Ctrl</kbd>+<kbd>c</kbd> instead, and _K-9 Mail_
composes a new message.
To copy, cut and paste in such cases (but only supported on Android >= 7):
- <kbd>MOD</kbd>+<kbd>c</kbd> injects `COPY`
- <kbd>MOD</kbd>+<kbd>x</kbd> injects `CUT`
- <kbd>MOD</kbd>+<kbd>v</kbd> injects `PASTE` (after computer-to-device
clipboard synchronization)
In addition, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> allows to inject the
computer clipboard text as a sequence of key events. This is useful when the
component does not accept text pasting (for example in _Termux_), but it can
break non-ASCII content.
**WARNING:** Pasting the computer clipboard to the device (either via
<kbd>Ctrl</kbd>+<kbd>v</kbd> or <kbd>MOD</kbd>+<kbd>v</kbd>) copies the content
into the device clipboard. As a consequence, any Android application could read
its content. You should avoid to paste sensitive content (like passwords) that
way.
Some devices do not behave as expected when setting the device clipboard
programmatically. An option `--legacy-paste` is provided to change the behavior
of <kbd>Ctrl</kbd>+<kbd>v</kbd> and <kbd>MOD</kbd>+<kbd>v</kbd> so that they
also inject the computer clipboard text as a sequence of key events (the same
way as <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
#### Pinch-to-zoom
To simulate "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-and-move_.
More precisely, hold <kbd>Ctrl</kbd> while pressing the left-click button. Until
the left-click button is released, all mouse movements scale and rotate the
content (if supported by the app) relative to the center of the screen.
Concretely, scrcpy generates additional touch events from a "virtual finger" at
a location inverted through the center of the screen.
- `Ctrl`+`c` copies the device clipboard to the computer clipboard;
- `Ctrl`+`Shift`+`v` copies the computer clipboard to the device clipboard;
- `Ctrl`+`v` _pastes_ the computer clipboard as a sequence of text events (but
breaks non-ASCII characters).
#### Text injection preference
@ -384,6 +616,28 @@ scrcpy --prefer-text
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
#### Key repeat
By default, holding a key down generates repeated key events. This can cause
performance problems in some games, where these events are useless anyway.
To avoid forwarding repeated key events:
```bash
scrcpy --no-key-repeat
```
#### Right-click and middle-click
By default, right-click triggers BACK (or POWER on) and middle-click triggers
HOME. To disable these shortcuts and forward the clicks to the device instead:
```bash
scrcpy --forward-all-clicks
```
### File drop
#### Install APK
@ -410,40 +664,67 @@ scrcpy --push-target /sdcard/foo/bar/
### Audio forwarding
Audio is not forwarded by _scrcpy_. Use [USBaudio] (Linux-only).
Audio is not forwarded by _scrcpy_. Use [sndcpy].
Also see [issue #14].
[USBaudio]: https://github.com/rom1v/usbaudio
[sndcpy]: https://github.com/rom1v/sndcpy
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14
## Shortcuts
| Action | Shortcut | Shortcut (macOS)
| -------------------------------------- |:----------------------------- |:-----------------------------
| Switch fullscreen mode | `Ctrl`+`f` | `Cmd`+`f`
| Resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` | `Cmd`+`g`
| Resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ | `Cmd`+`x` \| _Double-click¹_
| Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ | `Ctrl`+`h` \| _Middle-click_
| Click on `BACK` | `Ctrl`+`b` \| _Right-click²_ | `Cmd`+`b` \| _Right-click²_
| Click on `APP_SWITCH` | `Ctrl`+`s` | `Cmd`+`s`
| Click on `MENU` | `Ctrl`+`m` | `Ctrl`+`m`
| Click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ | `Cmd`+`↑` _(up)_
| Click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ | `Cmd`+`↓` _(down)_
| Click on `POWER` | `Ctrl`+`p` | `Cmd`+`p`
| Power on | _Right-click²_ | _Right-click²_
| Turn device screen off (keep mirroring)| `Ctrl`+`o` | `Cmd`+`o`
| Rotate device screen | `Ctrl`+`r` | `Cmd`+`r`
| Expand notification panel | `Ctrl`+`n` | `Cmd`+`n`
| Collapse notification panel | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n`
| Copy device clipboard to computer | `Ctrl`+`c` | `Cmd`+`c`
| Paste computer clipboard to device | `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`
In the following list, <kbd>MOD</kbd> is the shortcut modifier. By default, it's
(left) <kbd>Alt</kbd> or (left) <kbd>Super</kbd>.
It can be changed using `--shortcut-mod`. Possible keys are `lctrl`, `rctrl`,
`lalt`, `ralt`, `lsuper` and `rsuper`. For example:
```bash
# use RCtrl for shortcuts
scrcpy --shortcut-mod=rctrl
# use either LCtrl+LAlt or LSuper for shortcuts
scrcpy --shortcut-mod=lctrl+lalt,lsuper
```
_<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button)
| Action | Shortcut
| ------------------------------------------- |:-----------------------------
| Switch fullscreen mode | <kbd>MOD</kbd>+<kbd>f</kbd>
| Rotate display left | <kbd>MOD</kbd>+<kbd>←</kbd> _(left)_
| Rotate display right | <kbd>MOD</kbd>+<kbd>→</kbd> _(right)_
| Resize window to 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
| Resize window to remove black borders | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Double-click¹_
| Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_
| Click on `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Right-click²_
| Click on `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd>
| Click on `MENU` (unlock screen) | <kbd>MOD</kbd>+<kbd>m</kbd>
| Click on `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(up)_
| Click on `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(down)_
| Click on `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd>
| Power on | _Right-click²_
| Turn device screen off (keep mirroring) | <kbd>MOD</kbd>+<kbd>o</kbd>
| Turn device screen on | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
| Rotate device screen | <kbd>MOD</kbd>+<kbd>r</kbd>
| Expand notification panel | <kbd>MOD</kbd>+<kbd>n</kbd>
| Collapse notification panel | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| Copy to clipboard³ | <kbd>MOD</kbd>+<kbd>c</kbd>
| Cut to clipboard³ | <kbd>MOD</kbd>+<kbd>x</kbd>
| Synchronize clipboards and paste³ | <kbd>MOD</kbd>+<kbd>v</kbd>
| Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_
_¹Double-click on black borders to remove them._
_²Right-click turns the screen on if it was off, presses BACK otherwise._
_²Right-click turns the screen on if it was off, presses BACK otherwise._
_³Only on Android >= 7._
All <kbd>Ctrl</kbd>+_key_ shortcuts are forwarded to the device, so they are
handled by the active application.
## Custom paths
@ -491,7 +772,7 @@ Read the [developers page].
## Licence
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2019 Romain Vimont
Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -512,3 +793,15 @@ Read the [developers page].
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/
## Translations
This README is available in other languages:
- [Indonesian (Indonesia, `id`) - v1.16](README.id.md)
- [한국어 (Korean, `ko`) - v1.11](README.ko.md)
- [português brasileiro (Brazilian Portuguese, `pt-BR`) - v1.12.1](README.pt-br.md)
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.16](README.zh-Hans.md)
- [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md)
Only this README file is guaranteed to be up-to-date.

531
README.pt-br.md Normal file
View File

@ -0,0 +1,531 @@
_Only the original [README](README.md) is guaranteed to be up-to-date._
# scrcpy (v1.12.1)
Esta aplicação fornece visualizaçã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 USB][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 precisará ativar [uma opção adicional][control] para controlá-lo usando o teclado e mouse.
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
## Obtendo o app
### Linux
No Debian (_em testes_ e _sid_ por enquanto):
```
apt install scrcpy
```
O 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 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 a aplicação manualmente][BUILD] (não se preocupe, não é tão difícil).
### Windows
Para Windows, para simplicidade, um arquivo pré-compilado com todas as dependências
(incluindo `adb`) está disponível:
- [`scrcpy-win64-v1.12.1.zip`][direct-win64]
_(SHA-256: 57d34b6d16cfd9fe169bc37c4df58ebd256d05c1ea3febc63d9cb0a027ab47c9)_
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.12.1/scrcpy-win64-v1.12.1.zip
Também 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 a aplicação manualmente][BUILD].
### macOS
A aplicação está disponível em [Homebrew]. Apenas a instale:
[Homebrew]: https://brew.sh/
```bash
brew install scrcpy
```
Você precisa do `adb`, acessível através do seu `PATH`. Se você ainda não o tem:
```bash
brew cask install android-platform-tools
```
Você também pode [compilar a aplicação manualmente][BUILD].
## Executar
Plugue 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
#### Redução de tamanho
Algumas vezes, é útil espelhar um dispositivo Android em uma resolução menor para
aumentar performance.
Para limitar ambos(largura e altura) para algum valor (ex: 1024):
```bash
scrcpy --max-size 1024
scrcpy -m 1024 # versão reduzida
```
A outra dimensão é calculada para que a proporção do dispositivo seja preservada.
Dessa forma, um dispositivo em 1920x1080 será espelhado em 1024x576.
#### Mudanças no bit-rate
O Padrão de bit-rate é 8 mbps. Para mudar o bitrate do vídeo (ex: para 2 Mbps):
```bash
scrcpy --bit-rate 2M
scrcpy -b 2M # versão reduzida
```
#### Limitar frame rates
Em dispositivos com Android >= 10, a captura de frame rate pode ser limitada:
```bash
scrcpy --max-fps 15
```
#### Cortar
A tela do dispositivo pode ser cortada para espelhar apenas uma parte da tela.
Isso é útil por exemplo, ao espelhar apenas um olho do Oculus Go:
```bash
scrcpy --crop 1224:1440:0:0 # 1224x1440 no deslocamento (0,0)
```
Se `--max-size` também for especificado, redimensionar é aplicado após os cortes.
### 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
# interrompe a gravação com Ctrl+C
# Ctrl+C não encerrar propriamente no Windows, então desconecte o dispositivo
```
"Frames pulados" são gravados, mesmo que não sejam mostrado em tempo real (por motivos de performance).
Frames tem seu _horário_ _carimbado_ no dispositivo, então [Variação de atraso nos pacotes] não impacta na gravação do arquivo.
[Variação de atraso de pacote]: https://en.wikipedia.org/wiki/Packet_delay_variation
### Conexão
#### Wireless/Sem fio
_Scrcpy_ usa `adb` para se comunicar com o dispositivo, e `adb` pode [conectar-se] à um dispositivo via TCP/IP:
1. Conecte o dispositivo a mesma rede Wi-Fi do seu computador.
2. Pegue o endereço de IP do seu dispositivo (Em Configurações → Sobre o Telefone → Status).
3. Ative o adb via TCP/IP no seu dispositivo: `adb tcpip 5555`.
4. Desplugue seu dispositivo.
5. Conecte-se ao seu dispositivo: `adb connect DEVICE_IP:5555` _(substitua o `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 reduzida
```
[conectar-se]: https://developer.android.com/studio/command-line/adb.html#wireless
#### N-dispositivos
Se alguns dispositivos estão listados em `adb devices`, você precisa especificar o _serial_:
```bash
scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # versão reduzida
```
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 reduzida
```
Você pode iniciar algumas instâncias do _scrcpy_ para alguns dispositivos.
#### Conexão via SSH
Para conectar-se à um dispositivo remoto, é possível se conectar um cliente local `adb` à um servidor `adb` remoto (contanto que eles usem a mesma versão do protocolo _adb_):
```bash
adb kill-server # encerra o servidor local na 5037
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
# mantém isso aberto
```
De outro terminal:
```bash
scrcpy
```
Igual para conexões sem fio, pode ser útil reduzir a qualidade:
```
scrcpy -b2M -m800 --max-fps 15
```
### Configurações de Janela
#### Título
Por padrão, o título da janela é o modelo do dispositivo. Isto 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 da janela:
```bash
scrcpy --window-borderless
```
#### Sempre visível
Para manter a janela do scrcpy sempre visível:
```bash
scrcpy --always-on-top
```
#### Tela cheia
A aplicação pode ser iniciada diretamente em tela cheia:
```bash
scrcpy --fullscreen
scrcpy -f # versão reduzida
```
Tela cheia pode ser alternada dinamicamente com `Ctrl`+`f`.
### 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
```
#### Desligar a 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 `Ctrl`+`o` durante qualquer momento.
Para ligar novamente, pressione `POWER` (ou `Ctrl`+`p`).
#### Frames expirados de renderização
Por padrão, para minimizar a latência, _scrcpy_ sempre renderiza o último frame decodificado disponível e descarta o anterior.
Para forçar a renderização de todos os frames ( com o custo de aumento de latência), use:
```bash
scrcpy --render-expired-frames
```
#### Mostrar toques
Para apresentações, pode ser útil mostrar toques físicos(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 desativar no encerramento:
```bash
scrcpy --show-touches
scrcpy -t
```
Note que isto mostra apenas toques _físicos_ (com o dedo no dispositivo).
### Controle de entrada
#### Rotacionar a tela do dispositivo
Pressione `Ctrl`+`r` para mudar entre os modos Retrato e Paisagem.
Note que só será rotacionado se a aplicação em primeiro plano tiver suporte para o modo requisitado.
#### Copiar-Colar
É possível sincronizar áreas de transferência entre computador e o dispositivo,
para ambas direções:
- `Ctrl`+`c` copia a área de transferência do dispositivo para a área de trasferência do computador;
- `Ctrl`+`Shift`+`v` copia a área de transferência do computador para a área de transferência do dispositivo;
- `Ctrl`+`v` _cola_ a área de transferência do computador como uma sequência de eventos de texto (mas
quebra caracteres não-ASCII).
#### Preferências de injeção de texto
Existe dois tipos de [eventos][textevents] gerados ao digitar um texto:
- _eventos de teclas_, 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 teclas, assim teclados comportam-se
como esperado em jogos (normalmente para tecladas WASD)
Mas isto pode [causar problemas][prefertext]. Se você encontrar tal problema,
pode evitá-lo usando:
```bash
scrcpy --prefer-text
```
(mas isto 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
### Transferência de 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 o dispositivo
Para enviar um arquivo para o diretório `/sdcard/` 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 é encaminhando pelo _scrcpy_. Use [USBaudio] (Apenas linux).
Também veja [issue #14].
[USBaudio]: https://github.com/rom1v/usbaudio
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14
## Atalhos
| Ação | Atalho | Atalho (macOS)
| ------------------------------------------------------------- |:------------------------------- |:-----------------------------
| Alternar para modo de tela cheia | `Ctrl`+`f` | `Cmd`+`f`
| Redimensionar janela para pixel-perfect(Escala 1:1) | `Ctrl`+`g` | `Cmd`+`g`
| Redimensionar janela para tirar as bordas pretas | `Ctrl`+`x` \| _Clique-duplo¹_ | `Cmd`+`x` \| _Clique-duplo¹_
| Clicar em `HOME` | `Ctrl`+`h` \| _Clique-central_ | `Ctrl`+`h` \| _Clique-central_
| Clicar em `BACK` | `Ctrl`+`b` \| _Clique-direito²_ | `Cmd`+`b` \| _Clique-direito²_
| Clicar em `APP_SWITCH` | `Ctrl`+`s` | `Cmd`+`s`
| Clicar em `MENU` | `Ctrl`+`m` | `Ctrl`+`m`
| Clicar em `VOLUME_UP` | `Ctrl`+`↑` _(cima)_ | `Cmd`+`↑` _(cima)_
| Clicar em `VOLUME_DOWN` | `Ctrl`+`↓` _(baixo)_ | `Cmd`+`↓` _(baixo)_
| Clicar em `POWER` | `Ctrl`+`p` | `Cmd`+`p`
| Ligar | _Clique-direito²_ | _Clique-direito²_
| Desligar a tela do dispositivo | `Ctrl`+`o` | `Cmd`+`o`
| Rotacionar tela do dispositivo | `Ctrl`+`r` | `Cmd`+`r`
| Expandir painel de notificação | `Ctrl`+`n` | `Cmd`+`n`
| Esconder painel de notificação | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n`
| Copiar área de transferência do dispositivo para o computador | `Ctrl`+`c` | `Cmd`+`c`
| Colar área de transferência do computador para o dispositivo | `Ctrl`+`v` | `Cmd`+`v`
| Copiar área de transferência do computador para dispositivo | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v`
| Ativar/desativar contador de FPS(Frames por segundo) | `Ctrl`+`i` | `Cmd`+`i`
_¹Clique-duplo em bordas pretas para removê-las._
_²Botão direito liga a tela se ela estiver desligada, clique BACK para o contrário._
## Caminhos personalizados
Para usar um binário específico _adb_, configure seu caminho na variável de ambiente `ADB`:
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 impronunciável como [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].
[BUILD]: BUILD.md
## Problemas comuns
Veja [FAQ](FAQ.md).
## Desenvolvedores
Leia a [developers page].
[developers page]: DEVELOP.md
## Licença
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
## 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/

726
README.zh-Hans.md Normal file
View File

@ -0,0 +1,726 @@
_Only the original [README](README.md) is guaranteed to be up-to-date._
只有原版的[README](README.md)会保持最新。
本文根据[479d10d]进行翻译。
[479d10d]: https://github.com/Genymobile/scrcpy/commit/479d10dc22b70272187e0963c6ad24d754a669a2#diff-04c6e90faac2675aa89e2176d2eec7d8
# scrcpy (v1.16)
本应用程序可以通过USB或 [TCP/IP][article-tcpip] )连接用于显示或控制安卓设备。这不需要获取 _root_ 权限。
该应用程序可以在 _GNU/Linux_, _Windows__macOS_ 环境下运行。
[article-tcpip]:https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/
![screenshot](assets/screenshot-debian-600.jpg)
它专注于:
- **轻量** (原生,仅显示设备屏幕)
- **性能** 30~60fps
- **质量** 分辨率可达1920x1080或更高
- **低延迟** (35-70ms)
- **快速启动** (数秒内即能开始显示)
- **无侵入性** (不需要在安卓设备上安装任何程序)
## 使用要求
安卓设备系统版本需要在Android 5.0API 21)或以上。
确保您在设备上开启了[adb调试]。
[adb调试]: https://developer.android.com/studio/command-line/adb.html#Enabling
在某些设备上,你还需要开启[额外的选项]以用鼠标和键盘进行控制。
[额外的选项]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
## 获取scrcpy
<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
您也可以[自行编译][编译](不必担心,这并不困难)。
### Windows
在Windows上简便起见我们准备了包含所有依赖项包括adb的程序包。
- [`scrcpy-win64-v1.16.zip`][direct-win64]
_(SHA-256: 3f30dc5db1a2f95c2b40a0f5de91ec1642d9f53799250a8c529bc882bc0918f0)_
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.16/scrcpy-win64-v1.16.zip
您也可以在[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
您也可以[自行编译][编译]。
### macOS
您可以使用[Homebrew]下载scrcpy。直接安装就可以了
[Homebrew]: https://brew.sh/
```bash
brew install scrcpy
```
您需要 `adb`以使用scrcpy并且它需要可以通过 `PATH`被访问。如果您没有:
```bash
brew cask install android-platform-tools
```
您也可以[自行编译][编译]。
## 运行scrcpy
用USB链接电脑和安卓设备并执行
```bash
scrcpy
```
支持带命令行参数执行,查看参数列表:
```bash
scrcpy --help
```
## 功能介绍
### 画面设置
#### 缩小分辨率
有时候,将设备屏幕镜像分辨率降低可以有效地提升性能。
我们可以将高度和宽度都限制在一定大小内(如 1024
```bash
scrcpy --max-size 1024
scrcpy -m 1024 # short version
```
较短的一边会被按比例缩小以保持设备的显示比例。
这样1920x1080 的设备会以 1024x576 的分辨率显示。
#### 修改画面比特率
默认的比特率是8Mbps。如果要改变画面的比特率 (比如说改成2Mbps)
```bash
scrcpy --bit-rate 2M
scrcpy -b 2M # short version
```
#### 限制画面帧率
画面的帧率可以通过下面的命令被限制:
```bash
scrcpy --max-fps 15
```
这个功能仅在Android 10和以后的版本被Android官方支持但也有可能在更早的版本可用。
#### 画面裁剪
设备画面可在裁切后进行镜像,以显示部分屏幕。
这项功能可以用于例如只显示Oculus Go的一只眼睛。
```bash
scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0)
```
如果`--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以停止录制
```
在显示中“被跳过的帧”会被录制,虽然它们由于性能原因没有实时显示。
在传输中每一帧都有 _时间戳_ ,所以 [包时延变化] 并不影响录制的文件。
[包时延变化]: https://en.wikipedia.org/wiki/Packet_delay_variation
### 连接方式
#### 无线
_Scrcpy_ 使用`adb`来与安卓设备连接。同时,`adb`能够通过TCP/IP[连接]到安卓设备:
1. 将您的安卓设备和电脑连接至同一Wi-Fi。
2. 获取安卓设备的IP地址在设置-关于手机-状态信息)。
3. 打开安卓设备的网络adb功能`adb tcpip 5555`
4. 将您的设备与电脑断开连接。
5. 连接到您的设备:`adb connect DEVICE_IP:5555` _(用设备IP替换 `DEVICE_IP`)_.
6. 运行`scrcpy`
降低比特率和分辨率可能有助于性能:
```bash
scrcpy --bit-rate 2M --max-size 800
scrcpy -b2M -m800 # short version
```
[连接]: https://developer.android.com/studio/command-line/adb.html#wireless
#### 多设备
如果多个设备在执行`adb devices`后被列出,您必须指定设备的 _序列号_
```bash
scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # short version
```
如果设备是通过TCP/IP方式连接到电脑的
```bash
scrcpy --serial 192.168.0.1:5555
scrcpy -s 192.168.0.1:5555 # short version
```
您可以同时启动多个 _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
```
为了避免启动远程端口转发,你可以强制启动一个转发连接(注意`-L``-R`的区别:
```bash
adb kill-server # kill the local adb server on 5037
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
```
#### 保持窗口在最前
您可以通过如下命令保持窗口在最前面:
```bash
scrcpy --always-on-top
```
#### 全屏
您可以通过如下命令直接全屏启动scrcpy
```bash
scrcpy --fullscreen
scrcpy -f # short version
```
全屏状态可以通过<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
```
程序关闭后,设备设置会恢复原样。
#### 关闭设备屏幕
在启动屏幕镜像时,可以通过如下命令关闭设备的屏幕:
```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 --render-expired-frames
```
#### 显示触摸
在展示时,有些时候可能会用到显示触摸点这项功能(在设备上显示)。
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>
都需要将内容保存至设备的剪贴板。如此,任何一个应用程序都可以读取它。
您应当避免将敏感内容通过这种方式传输(如密码)。
#### 捏拉缩放
模拟 “捏拉缩放”:<kbd>Ctrl</kbd>+_按住并移动鼠标_。
更准确的说,您需要在按住<kbd>Ctrl</kbd>的同时按住并移动鼠标。
在鼠标左键松开之后,光标的任何操作都会相对于屏幕的中央进行。
具体来说, _scrcpy_ 使用“虚拟手指”以在相对于屏幕中央相反的位置产生触摸事件。
#### 文字注入偏好
打字的时候,系统会产生两种[事件][textevents]
- _按键事件_ ,代表一个按键被按下/松开。
- _文本事件_ ,代表一个文本被输入。
程序默认使用按键事件来输入字母。只有这样键盘才会在游戏中正常运作尤其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文件_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``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>
| 点按 `菜单` (解锁屏幕) | <kbd>MOD</kbd>+<kbd>m</kbd>
| 点按 `音量+` | <kbd>MOD</kbd>+<kbd></kbd> _(up)_
| 点按 `音量-` | <kbd>MOD</kbd>+<kbd></kbd> _(down)_
| 点按 `电源` | <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>
| 捏拉缩放 | <kbd>Ctrl</kbd>+_点按并移动鼠标_
_¹双击黑色边界以关闭黑色边界_
_²点击鼠标右键将在屏幕熄灭时点亮屏幕其余情况则视为按下 返回键 。_
_³需要安卓版本 Android >= 7。_
所有的 <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.md
## 常见问题
请查看[FAQ](FAQ.md).
## 开发者
请查看[开发者页面]。
[开发者页面]: DEVELOP.md
## 许可协议
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
## 相关文章
- [Introducing scrcpy][article-intro]
- [Scrcpy now works wirelessly][article-tcpip]
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/

705
README.zh-Hant.md Normal file
View File

@ -0,0 +1,705 @@
_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`) 的壓縮包:
- [`scrcpy-win64-v1.15.zip`][direct-win64]
_(SHA-256: dd514bb591e63ef4cd52a53c30f1153a28f59722d64690eb07bd017849edcba2)_
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.15/scrcpy-win64-v1.15.zip
[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-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
## 相關文章
- [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/

View File

@ -1,7 +1,8 @@
src = [
'src/main.c',
'src/adb.c',
'src/cli.c',
'src/command.c',
'src/compat.c',
'src/control_msg.c',
'src/controller.c',
'src/decoder.c',
@ -11,6 +12,7 @@ src = [
'src/file_handler.c',
'src/fps_counter.c',
'src/input_manager.c',
'src/opengl.c',
'src/receiver.c',
'src/recorder.c',
'src/scrcpy.c',
@ -20,9 +22,23 @@ src = [
'src/tiny_xpm.c',
'src/video_buffer.c',
'src/util/net.c',
'src/util/str_util.c'
'src/util/process.c',
'src/util/str_util.c',
'src/util/thread.c',
]
if host_machine.system() == 'windows'
src += [ 'src/sys/win/process.c' ]
else
src += [ 'src/sys/unix/process.c' ]
endif
check_functions = [
'strdup'
]
cc = meson.get_compiler('c')
if not get_option('crossbuild_windows')
# native build
@ -36,8 +52,6 @@ if not get_option('crossbuild_windows')
else
# cross-compile mingw32 build (from Linux to Windows)
cc = meson.get_compiler('c')
prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2')
sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/bin'
sdl2_lib_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/lib'
@ -72,19 +86,18 @@ else
endif
cc = meson.get_compiler('c')
if host_machine.system() == 'windows'
src += [ 'src/sys/win/command.c' ]
dependencies += cc.find_library('ws2_32')
else
src += [ 'src/sys/unix/command.c' ]
endif
conf = configuration_data()
# expose the build type
conf.set('NDEBUG', get_option('buildtype') != 'debug')
foreach f : check_functions
if cc.has_function(f)
define = 'HAVE_' + f.underscorify().to_upper()
conf.set(define, true)
endif
endforeach
# the version, updated on release
conf.set_quoted('SCRCPY_VERSION', meson.project_version())
@ -105,6 +118,12 @@ conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199')
# overridden by option --max-size
conf.set('DEFAULT_MAX_SIZE', '0') # 0: unlimited
# the default video orientation
# natural device orientation is 0 and each increment adds 90 degrees
# counterclockwise
# overridden by option --lock-video-orientation
conf.set('DEFAULT_LOCK_VIDEO_ORIENTATION', '-1') # -1: unlocked
# the default video bitrate, in bits/second
# overridden by option --bit-rate
conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps
@ -112,28 +131,21 @@ conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps
# enable High DPI support
conf.set('HIDPI_SUPPORT', get_option('hidpi_support'))
# disable console on Windows
conf.set('WINDOWS_NOCONSOLE', get_option('windows_noconsole'))
# run a server debugger and wait for a client to be attached
conf.set('SERVER_DEBUGGER', get_option('server_debugger'))
# select the debugger method ('old' for Android < 9, 'new' for Android >= 9)
conf.set('SERVER_DEBUGGER_METHOD_NEW', get_option('server_debugger_method') == 'new')
configure_file(configuration: conf, output: 'config.h')
src_dir = include_directories('src')
if get_option('windows_noconsole')
link_args = [ '-Wl,--subsystem,windows' ]
else
link_args = []
endif
executable('scrcpy', src,
dependencies: dependencies,
include_directories: src_dir,
install: true,
c_args: [],
link_args: link_args)
c_args: [])
install_man('scrcpy.1')
@ -154,12 +166,12 @@ if get_option('buildtype') == 'debug'
'src/cli.c',
'src/util/str_util.c',
]],
['test_control_event_serialize', [
['test_control_msg_serialize', [
'tests/test_control_msg_serialize.c',
'src/control_msg.c',
'src/util/str_util.c',
]],
['test_device_event_deserialize', [
['test_device_msg_deserialize', [
'tests/test_device_msg_deserialize.c',
'src/device_msg.c',
]],
@ -176,7 +188,7 @@ if get_option('buildtype') == 'debug'
exe = executable(t[0], t[1],
include_directories: src_dir,
dependencies: dependencies,
c_args: ['-DSDL_MAIN_HANDLED'])
c_args: ['-DSDL_MAIN_HANDLED', '-DSC_TEST'])
test(t[0], exe)
endforeach
endif

View File

@ -25,6 +25,16 @@ Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are
Default is 8000000.
.TP
.BI "\-\-codec\-options " key[:type]=value[,...]
Set a list of comma-separated key:type=value options for the device 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 .
.TP
.BI "\-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy
Crop the device screen on the server.
@ -33,6 +43,31 @@ The values are expressed in the device natural orientation (typically, portrait
.B \-\-max\-size
value is computed on the cropped size.
.TP
.BI "\-\-disable-screensaver"
Disable screensaver while scrcpy is running.
.TP
.BI "\-\-display " id
Specify the display id to mirror.
The list of possible display ids can be listed by "adb shell dumpsys display"
(search "mDisplayId=" in the output).
Default is 0.
.TP
.BI "\-\-encoder " name
Use a specific MediaCodec encoder (must be a H.264 encoder).
.TP
.B \-\-force\-adb\-forward
Do not attempt to use "adb reverse" to connect to the device.
.TP
.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.
@ -41,6 +76,18 @@ Start in fullscreen.
.B \-h, \-\-help
Print this help.
.TP
.B \-\-legacy\-paste
Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v).
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 -1 (unlocked), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees otation counterclockwise.
Default is -1 (unlocked).
.TP
.BI "\-\-max\-fps " value
Limit the framerate of screen capture (officially supported since Android 10, but may work on earlier versions).
@ -59,6 +106,14 @@ Disable device control (mirror the device in read\-only).
.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.
.TP
.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
.BI "\-p, \-\-port " port[:port]
Set the TCP port (range) used by the client to listen.
@ -91,21 +146,44 @@ option if set, or by the file extension (.mp4 or .mkv).
.BI "\-\-record\-format " format
Force recording format (either mp4 or mkv).
.TP
.BI "\-\-render\-driver " name
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
.TP
.B \-\-render\-expired\-frames
By default, to minimize latency, scrcpy always renders the last available decoded frame, and drops any previous ones. This flag forces to render all frames, at a cost of a possible increased latency.
.TP
.BI "\-\-rotation " value
Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each increment adds a 90 degrees rotation counterclockwise.
.TP
.BI "\-s, \-\-serial " number
The device serial number. Mandatory only if several devices are connected to adb.
.TP
.BI "\-\-shortcut\-mod " key[+...]][,...]
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 ','.
For example, to use either LCtrl+LAlt or LSuper for scrcpy shortcuts, pass "lctrl+lalt,lsuper".
Default is "lalt,lsuper" (left-Alt or left-Super).
.TP
.B \-S, \-\-turn\-screen\-off
Turn the device screen off immediately.
.TP
.B \-t, \-\-show\-touches
Enable "show touches" on start, disable on quit.
Enable "show touches" on start, restore the initial value on exit.
It only shows physical touches (not clicks from scrcpy).
@ -113,6 +191,16 @@ It only shows physical touches (not clicks from scrcpy).
.B \-v, \-\-version
Print the version of scrcpy.
.TP
.BI "\-V, \-\-verbosity " value
Set the log level ("debug", "info", "warn" or "error").
Default is "info" for release builds, "debug" for debug builds.
.TP
.B \-w, \-\-stay-awake
Keep the device on while scrcpy is running, when the device is plugged in.
.TP
.B \-\-window\-borderless
Disable window decorations (display borderless window).
@ -125,13 +213,13 @@ Set a custom window title.
.BI "\-\-window\-x " value
Set the initial window horizontal position.
Default is -1 (automatic).\n
Default is "auto".\n
.TP
.BI "\-\-window\-y " value
Set the initial window vertical position.
Default is -1 (automatic).\n
Default is "auto".\n
.TP
.BI "\-\-window\-width " value
@ -147,85 +235,108 @@ Default is 0 (automatic).\n
.SH SHORTCUTS
.TP
.B Ctrl+f
switch fullscreen mode
In the following list, MOD is the shortcut modifier. By default, it's (left)
Alt or (left) Super, but it can be configured by \-\-shortcut-mod (see above).
.TP
.B Ctrl+g
resize window to 1:1 (pixel\-perfect)
.B MOD+f
Switch fullscreen mode
.TP
.B Ctrl+x, Double\-click on black borders
resize window to remove black borders
.B MOD+Left
Rotate display left
.TP
.B Ctrl+h, Home, Middle\-click
.B MOD+Right
Rotate display right
.TP
.B MOD+g
Resize window to 1:1 (pixel\-perfect)
.TP
.B MOD+w, Double\-click on black borders
Resize window to remove black borders
.TP
.B MOD+h, Home, Middle\-click
Click on HOME
.TP
.B Ctrl+b, Ctrl+Backspace, Right\-click (when screen is on)
.B MOD+b, MOD+Backspace, Right\-click (when screen is on)
Click on BACK
.TP
.B Ctrl+s
.B MOD+s
Click on APP_SWITCH
.TP
.B Ctrl+m
.B MOD+m
Click on MENU
.TP
.B Ctrl+Up
.B MOD+Up
Click on VOLUME_UP
.TP
.B Ctrl+Down
.B MOD+Down
Click on VOLUME_DOWN
.TP
.B Ctrl+p
.B MOD+p
Click on POWER (turn screen on/off)
.TP
.B Right\-click (when screen is off)
turn screen on
Turn screen on
.TP
.B Ctrl+o
turn device screen off (keep mirroring)
.B MOD+o
Turn device screen off (keep mirroring)
.TP
.B Ctrl+r
rotate device screen
.B MOD+Shift+o
Turn device screen on
.TP
.B Ctrl+n
expand notification panel
.B MOD+r
Rotate device screen
.TP
.B Ctrl+Shift+n
collapse notification panel
.B MOD+n
Expand notification panel
.TP
.B Ctrl+c
copy device clipboard to computer
.B MOD+Shift+n
Collapse notification panel
.TP
.B Ctrl+v
paste computer clipboard to device
.B Mod+c
Copy to clipboard (inject COPY keycode, Android >= 7 only)
.TP
.B Ctrl+Shift+v
copy computer clipboard to device
.B Mod+x
Cut to clipboard (inject CUT keycode, Android >= 7 only)
.TP
.B Ctrl+i
enable/disable FPS counter (print frames/second in logs)
.B MOD+v
Copy computer clipboard to device, then paste (inject PASTE keycode, Android >= 7 only)
.TP
.B MOD+Shift+v
Inject computer clipboard text as a sequence of key events
.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
.TP
.B Drag & drop APK file
install APK from computer
Install APK from computer
.SH Environment variables
@ -261,7 +372,7 @@ Copyright \(co 2018 Genymobile
Genymobile
.UE
Copyright \(co 2018\-2019
Copyright \(co 2018\-2020
.MT rom@rom1v.com
Romain Vimont
.ME

View File

@ -1,12 +1,10 @@
#include "command.h"
#include "adb.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "config.h"
#include "common.h"
#include "util/log.h"
#include "util/str_util.h"
@ -55,6 +53,32 @@ argv_to_string(const char *const *argv, char *buf, size_t bufsize) {
return idx;
}
static void
show_adb_installation_msg() {
#ifndef __WINDOWS__
static const struct {
const char *binary;
const char *command;
} pkg_managers[] = {
{"apt", "apt install adb"},
{"apt-get", "apt-get install adb"},
{"brew", "brew cask install android-platform-tools"},
{"dnf", "dnf install android-tools"},
{"emerge", "emerge dev-util/android-tools"},
{"pacman", "pacman -S android-tools"},
};
for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) {
if (search_executable(pkg_managers[i].binary)) {
LOGI("You may install 'adb' by \"%s\"", pkg_managers[i].command);
return;
}
}
#endif
LOGI("You may download and install 'adb' from "
"https://developer.android.com/studio/releases/platform-tools");
}
static void
show_adb_err_msg(enum process_result err, const char *const argv[]) {
char buf[512];
@ -68,6 +92,7 @@ show_adb_err_msg(enum process_result err, const char *const argv[]) {
LOGE("Command not found: %s", buf);
LOGE("(make 'adb' accessible from your PATH or define its full"
"path in the ADB environment variable)");
show_adb_installation_msg();
break;
case PROCESS_SUCCESS:
// do nothing
@ -91,7 +116,7 @@ adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
memcpy(&cmd[i], adb_cmd, len * sizeof(const char *));
cmd[len + i] = NULL;
enum process_result r = cmd_execute(cmd, &process);
enum process_result r = process_execute(cmd, &process);
if (r != PROCESS_SUCCESS) {
show_adb_err_msg(r, cmd);
return PROCESS_NONE;
@ -148,7 +173,7 @@ adb_push(const char *serial, const char *local, const char *remote) {
}
remote = strquote(remote);
if (!remote) {
SDL_free((void *) local);
free((void *) local);
return PROCESS_NONE;
}
#endif
@ -157,8 +182,8 @@ adb_push(const char *serial, const char *local, const char *remote) {
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__
SDL_free((void *) remote);
SDL_free((void *) local);
free((void *) remote);
free((void *) local);
#endif
return proc;
@ -179,26 +204,8 @@ adb_install(const char *serial, const char *local) {
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__
SDL_free((void *) local);
free((void *) local);
#endif
return proc;
}
bool
process_check_success(process_t proc, const char *name) {
if (proc == PROCESS_NONE) {
LOGE("Could not execute \"%s\"", name);
return false;
}
exit_code_t exit_code;
if (!cmd_simple_wait(proc, &exit_code)) {
if (exit_code != NO_EXIT_CODE) {
LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code);
} else {
LOGE("\"%s\" exited unexpectedly", name);
}
return false;
}
return true;
}

34
app/src/adb.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef SC_ADB_H
#define SC_ADB_H
#include "common.h"
#include <stdbool.h>
#include <inttypes.h>
#include "util/process.h"
process_t
adb_execute(const char *serial, const char *const adb_cmd[], size_t len);
process_t
adb_forward(const char *serial, uint16_t local_port,
const char *device_socket_name);
process_t
adb_forward_remove(const char *serial, uint16_t local_port);
process_t
adb_reverse(const char *serial, const char *device_socket_name,
uint16_t local_port);
process_t
adb_reverse_remove(const char *serial, const char *device_socket_name);
process_t
adb_push(const char *serial, const char *local, const char *remote);
process_t
adb_install(const char *serial, const char *local);
#endif

View File

@ -1,21 +1,17 @@
#include "cli.h"
#include <assert.h>
#include <getopt.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include "config.h"
#include "recorder.h"
#include "scrcpy.h"
#include "util/log.h"
#include "util/str_util.h"
void
scrcpy_print_usage(const char *arg0) {
#ifdef __APPLE__
# define CTRL_OR_CMD "Cmd"
#else
# define CTRL_OR_CMD "Ctrl"
#endif
fprintf(stderr,
"Usage: %s [options]\n"
"\n"
@ -29,18 +25,64 @@ scrcpy_print_usage(const char *arg0) {
" Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
" Default is %d.\n"
"\n"
" --codec-options key[:type]=value[,...]\n"
" Set a list of comma-separated key:type=value options for the\n"
" device encoder.\n"
" The possible values for 'type' are 'int' (default), 'long',\n"
" 'float' and 'string'.\n"
" The list of possible codec options is available in the\n"
" Android documentation:\n"
" <https://d.android.com/reference/android/media/MediaFormat>\n"
"\n"
" --crop width:height:x:y\n"
" Crop the device screen on the server.\n"
" The values are expressed in the device natural orientation\n"
" (typically, portrait for a phone, landscape for a tablet).\n"
" Any --max-size value is computed on the cropped size.\n"
"\n"
" --disable-screensaver\n"
" Disable screensaver while scrcpy is running.\n"
"\n"
" --display id\n"
" Specify the display id to mirror.\n"
"\n"
" The list of possible display ids can be listed by:\n"
" adb shell dumpsys display\n"
" (search \"mDisplayId=\" in the output)\n"
"\n"
" Default is 0.\n"
"\n"
" --encoder name\n"
" Use a specific MediaCodec encoder (must be a H.264 encoder).\n"
"\n"
" --force-adb-forward\n"
" Do not attempt to use \"adb reverse\" to connect to the\n"
" the device.\n"
"\n"
" --forward-all-clicks\n"
" By default, right-click triggers BACK (or POWER on) and\n"
" middle-click triggers HOME. This option disables these\n"
" shortcuts and forward the clicks to the device instead.\n"
"\n"
" -f, --fullscreen\n"
" Start in fullscreen.\n"
"\n"
" -h, --help\n"
" Print this help.\n"
"\n"
" --legacy-paste\n"
" Inject computer clipboard text as a sequence of key events\n"
" on Ctrl+v (like MOD+Shift+v).\n"
" This is a workaround for some devices not behaving as\n"
" expected when setting the device clipboard programmatically.\n"
"\n"
" --lock-video-orientation value\n"
" Lock video orientation to value.\n"
" Possible values are -1 (unlocked), 0, 1, 2 and 3.\n"
" Natural device orientation is 0, and each increment adds a\n"
" 90 degrees rotation counterclockwise.\n"
" Default is %d%s.\n"
"\n"
" --max-fps value\n"
" Limit the frame rate of screen capture (officially supported\n"
" since Android 10, but may work on earlier versions).\n"
@ -58,6 +100,14 @@ scrcpy_print_usage(const char *arg0) {
" Do not display device (only when screen recording is\n"
" enabled).\n"
"\n"
" --no-key-repeat\n"
" Do not forward repeated key events when a key is held down.\n"
"\n"
" --no-mipmaps\n"
" If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then\n"
" mipmaps are automatically generated to improve downscaling\n"
" quality. This option disables the generation of mipmaps.\n"
"\n"
" -p, --port port[:port]\n"
" Set the TCP port (range) used by the client to listen.\n"
" Default is %d:%d.\n"
@ -82,26 +132,64 @@ scrcpy_print_usage(const char *arg0) {
" --record-format format\n"
" Force recording format (either mp4 or mkv).\n"
"\n"
" --render-driver name\n"
" Request SDL to use the given render driver (this is just a\n"
" hint).\n"
" Supported names are currently \"direct3d\", \"opengl\",\n"
" \"opengles2\", \"opengles\", \"metal\" and \"software\".\n"
" <https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER>\n"
"\n"
" --render-expired-frames\n"
" By default, to minimize latency, scrcpy always renders the\n"
" last available decoded frame, and drops any previous ones.\n"
" This flag forces to render all frames, at a cost of a\n"
" possible increased latency.\n"
"\n"
" --rotation value\n"
" Set the initial display rotation.\n"
" Possibles values are 0, 1, 2 and 3. Each increment adds a 90\n"
" degrees rotation counterclockwise.\n"
"\n"
" -s, --serial serial\n"
" The device serial number. Mandatory only if several devices\n"
" are connected to adb.\n"
"\n"
" --shortcut-mod key[+...]][,...]\n"
" Specify the modifiers to use for scrcpy shortcuts.\n"
" Possible keys are \"lctrl\", \"rctrl\", \"lalt\", \"ralt\",\n"
" \"lsuper\" and \"rsuper\".\n"
"\n"
" A shortcut can consist in several keys, separated by '+'.\n"
" Several shortcuts can be specified, separated by ','.\n"
"\n"
" For example, to use either LCtrl+LAlt or LSuper for scrcpy\n"
" shortcuts, pass \"lctrl+lalt,lsuper\".\n"
"\n"
" Default is \"lalt,lsuper\" (left-Alt or left-Super).\n"
"\n"
" -S, --turn-screen-off\n"
" Turn the device screen off immediately.\n"
"\n"
" -t, --show-touches\n"
" Enable \"show touches\" on start, disable on quit.\n"
" Enable \"show touches\" on start, restore the initial value\n"
" on exit.\n"
" It only shows physical touches (not clicks from scrcpy).\n"
"\n"
" -v, --version\n"
" Print the version of scrcpy.\n"
"\n"
" -V, --verbosity value\n"
" Set the log level (debug, info, warn or error).\n"
#ifndef NDEBUG
" Default is debug.\n"
#else
" Default is info.\n"
#endif
"\n"
" -w, --stay-awake\n"
" Keep the device on while scrcpy is running, when the device\n"
" is plugged in.\n"
"\n"
" --window-borderless\n"
" Disable window decorations (display borderless window).\n"
"\n"
@ -110,11 +198,11 @@ scrcpy_print_usage(const char *arg0) {
"\n"
" --window-x value\n"
" Set the initial window horizontal position.\n"
" Default is -1 (automatic).\n"
" Default is \"auto\".\n"
"\n"
" --window-y value\n"
" Set the initial window vertical position.\n"
" Default is -1 (automatic).\n"
" Default is \"auto\".\n"
"\n"
" --window-width value\n"
" Set the initial window width.\n"
@ -126,72 +214,93 @@ scrcpy_print_usage(const char *arg0) {
"\n"
"Shortcuts:\n"
"\n"
" " CTRL_OR_CMD "+f\n"
" switch fullscreen mode\n"
" In the following list, MOD is the shortcut modifier. By default,\n"
" it's (left) Alt or (left) Super, but it can be configured by\n"
" --shortcut-mod (see above).\n"
"\n"
" " CTRL_OR_CMD "+g\n"
" resize window to 1:1 (pixel-perfect)\n"
" MOD+f\n"
" Switch fullscreen mode\n"
"\n"
" " CTRL_OR_CMD "+x\n"
" MOD+Left\n"
" Rotate display left\n"
"\n"
" MOD+Right\n"
" Rotate display right\n"
"\n"
" MOD+g\n"
" Resize window to 1:1 (pixel-perfect)\n"
"\n"
" MOD+w\n"
" Double-click on black borders\n"
" resize window to remove black borders\n"
" Resize window to remove black borders\n"
"\n"
" Ctrl+h\n"
" MOD+h\n"
" Middle-click\n"
" click on HOME\n"
" Click on HOME\n"
"\n"
" " CTRL_OR_CMD "+b\n"
" " CTRL_OR_CMD "+Backspace\n"
" MOD+b\n"
" MOD+Backspace\n"
" Right-click (when screen is on)\n"
" click on BACK\n"
" Click on BACK\n"
"\n"
" " CTRL_OR_CMD "+s\n"
" click on APP_SWITCH\n"
" MOD+s\n"
" Click on APP_SWITCH\n"
"\n"
" Ctrl+m\n"
" click on MENU\n"
" MOD+m\n"
" Click on MENU\n"
"\n"
" " CTRL_OR_CMD "+Up\n"
" click on VOLUME_UP\n"
" MOD+Up\n"
" Click on VOLUME_UP\n"
"\n"
" " CTRL_OR_CMD "+Down\n"
" click on VOLUME_DOWN\n"
" MOD+Down\n"
" Click on VOLUME_DOWN\n"
"\n"
" " CTRL_OR_CMD "+p\n"
" click on POWER (turn screen on/off)\n"
" MOD+p\n"
" Click on POWER (turn screen on/off)\n"
"\n"
" Right-click (when screen is off)\n"
" power on\n"
" Power on\n"
"\n"
" " CTRL_OR_CMD "+o\n"
" turn device screen off (keep mirroring)\n"
" MOD+o\n"
" Turn device screen off (keep mirroring)\n"
"\n"
" " CTRL_OR_CMD "+r\n"
" rotate device screen\n"
" MOD+Shift+o\n"
" Turn device screen on\n"
"\n"
" " CTRL_OR_CMD "+n\n"
" expand notification panel\n"
" MOD+r\n"
" Rotate device screen\n"
"\n"
" " CTRL_OR_CMD "+Shift+n\n"
" collapse notification panel\n"
" MOD+n\n"
" Expand notification panel\n"
"\n"
" " CTRL_OR_CMD "+c\n"
" copy device clipboard to computer\n"
" MOD+Shift+n\n"
" Collapse notification panel\n"
"\n"
" " CTRL_OR_CMD "+v\n"
" paste computer clipboard to device\n"
" MOD+c\n"
" Copy to clipboard (inject COPY keycode, Android >= 7 only)\n"
"\n"
" " CTRL_OR_CMD "+Shift+v\n"
" copy computer clipboard to device\n"
" MOD+x\n"
" Cut to clipboard (inject CUT keycode, Android >= 7 only)\n"
"\n"
" " CTRL_OR_CMD "+i\n"
" enable/disable FPS counter (print frames/second in logs)\n"
" MOD+v\n"
" Copy computer clipboard to device, then paste (inject PASTE\n"
" keycode, Android >= 7 only)\n"
"\n"
" MOD+Shift+v\n"
" Inject computer clipboard text as a sequence of key events\n"
"\n"
" MOD+i\n"
" Enable/disable FPS counter (print frames/second in logs)\n"
"\n"
" Ctrl+click-and-move\n"
" Pinch-to-zoom from the center of the screen\n"
"\n"
" Drag & drop APK file\n"
" install APK from computer\n"
" Install APK from computer\n"
"\n",
arg0,
DEFAULT_BIT_RATE,
DEFAULT_LOCK_VIDEO_ORIENTATION, DEFAULT_LOCK_VIDEO_ORIENTATION >= 0 ? "" : " (unlocked)",
DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)",
DEFAULT_LOCAL_PORT_RANGE_FIRST, DEFAULT_LOCAL_PORT_RANGE_LAST);
}
@ -281,9 +390,42 @@ parse_max_fps(const char *s, uint16_t *max_fps) {
}
static bool
parse_window_position(const char *s, int16_t *position) {
parse_lock_video_orientation(const char *s, int8_t *lock_video_orientation) {
long value;
bool ok = parse_integer_arg(s, &value, false, -1, 0x7FFF,
bool ok = parse_integer_arg(s, &value, false, -1, 3,
"lock video orientation");
if (!ok) {
return false;
}
*lock_video_orientation = (int8_t) value;
return true;
}
static bool
parse_rotation(const char *s, uint8_t *rotation) {
long value;
bool ok = parse_integer_arg(s, &value, false, 0, 3, "rotation");
if (!ok) {
return false;
}
*rotation = (uint8_t) value;
return true;
}
static bool
parse_window_position(const char *s, int16_t *position) {
// special value for "auto"
static_assert(SC_WINDOW_POSITION_UNDEFINED == -0x8000, "unexpected value");
if (!strcmp(s, "auto")) {
*position = SC_WINDOW_POSITION_UNDEFINED;
return true;
}
long value;
bool ok = parse_integer_arg(s, &value, false, -0x7FFF, 0x7FFF,
"window position");
if (!ok) {
return false;
@ -307,7 +449,7 @@ parse_window_dimension(const char *s, uint16_t *dimension) {
}
static bool
parse_port_range(const char *s, struct port_range *port_range) {
parse_port_range(const char *s, struct sc_port_range *port_range) {
long values[2];
size_t count = parse_integers_arg(s, 2, values, 0, 0xFFFF, "port");
if (!count) {
@ -335,20 +477,155 @@ parse_port_range(const char *s, struct port_range *port_range) {
}
static bool
parse_record_format(const char *optarg, enum recorder_format *format) {
parse_display_id(const char *s, uint32_t *display_id) {
long value;
bool ok = parse_integer_arg(s, &value, false, 0, 0x7FFFFFFF, "display id");
if (!ok) {
return false;
}
*display_id = (uint32_t) value;
return true;
}
static bool
parse_log_level(const char *s, enum sc_log_level *log_level) {
if (!strcmp(s, "debug")) {
*log_level = SC_LOG_LEVEL_DEBUG;
return true;
}
if (!strcmp(s, "info")) {
*log_level = SC_LOG_LEVEL_INFO;
return true;
}
if (!strcmp(s, "warn")) {
*log_level = SC_LOG_LEVEL_WARN;
return true;
}
if (!strcmp(s, "error")) {
*log_level = SC_LOG_LEVEL_ERROR;
return true;
}
LOGE("Could not parse log level: %s", s);
return false;
}
// item is a list of mod keys separated by '+' (e.g. "lctrl+lalt")
// returns a bitwise-or of SC_MOD_* constants (or 0 on error)
static unsigned
parse_shortcut_mods_item(const char *item, size_t len) {
unsigned mod = 0;
for (;;) {
char *plus = strchr(item, '+');
// strchr() does not consider the "len" parameter, to it could find an
// occurrence too far in the string (there is no strnchr())
bool has_plus = plus && plus < item + len;
assert(!has_plus || plus > item);
size_t key_len = has_plus ? (size_t) (plus - item) : len;
#define STREQ(literal, s, len) \
((sizeof(literal)-1 == len) && !memcmp(literal, s, len))
if (STREQ("lctrl", item, key_len)) {
mod |= SC_MOD_LCTRL;
} else if (STREQ("rctrl", item, key_len)) {
mod |= SC_MOD_RCTRL;
} else if (STREQ("lalt", item, key_len)) {
mod |= SC_MOD_LALT;
} else if (STREQ("ralt", item, key_len)) {
mod |= SC_MOD_RALT;
} else if (STREQ("lsuper", item, key_len)) {
mod |= SC_MOD_LSUPER;
} else if (STREQ("rsuper", item, key_len)) {
mod |= SC_MOD_RSUPER;
} else {
LOGE("Unknown modifier key: %.*s "
"(must be one of: lctrl, rctrl, lalt, ralt, lsuper, rsuper)",
(int) key_len, item);
return 0;
}
#undef STREQ
if (!has_plus) {
break;
}
item = plus + 1;
assert(len >= key_len + 1);
len -= key_len + 1;
}
return mod;
}
static bool
parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) {
unsigned count = 0;
unsigned current = 0;
// LCtrl+LAlt or RCtrl or LCtrl+RSuper: "lctrl+lalt,rctrl,lctrl+rsuper"
for (;;) {
char *comma = strchr(s, ',');
if (comma && count == SC_MAX_SHORTCUT_MODS - 1) {
assert(count < SC_MAX_SHORTCUT_MODS);
LOGW("Too many shortcut modifiers alternatives");
return false;
}
assert(!comma || comma > s);
size_t limit = comma ? (size_t) (comma - s) : strlen(s);
unsigned mod = parse_shortcut_mods_item(s, limit);
if (!mod) {
LOGE("Invalid modifier keys: %.*s", (int) limit, s);
return false;
}
mods->data[current++] = mod;
++count;
if (!comma) {
break;
}
s = comma + 1;
}
mods->count = count;
return true;
}
#ifdef SC_TEST
// expose the function to unit-tests
bool
sc_parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) {
return parse_shortcut_mods(s, mods);
}
#endif
static bool
parse_record_format(const char *optarg, enum sc_record_format *format) {
if (!strcmp(optarg, "mp4")) {
*format = RECORDER_FORMAT_MP4;
*format = SC_RECORD_FORMAT_MP4;
return true;
}
if (!strcmp(optarg, "mkv")) {
*format = RECORDER_FORMAT_MKV;
*format = SC_RECORD_FORMAT_MKV;
return true;
}
LOGE("Unsupported format: %s (expected mp4 or mkv)", optarg);
return false;
}
static enum recorder_format
static enum sc_record_format
guess_record_format(const char *filename) {
size_t len = strlen(filename);
if (len < 4) {
@ -356,59 +633,91 @@ guess_record_format(const char *filename) {
}
const char *ext = &filename[len - 4];
if (!strcmp(ext, ".mp4")) {
return RECORDER_FORMAT_MP4;
return SC_RECORD_FORMAT_MP4;
}
if (!strcmp(ext, ".mkv")) {
return RECORDER_FORMAT_MKV;
return SC_RECORD_FORMAT_MKV;
}
return 0;
}
#define OPT_RENDER_EXPIRED_FRAMES 1000
#define OPT_WINDOW_TITLE 1001
#define OPT_PUSH_TARGET 1002
#define OPT_ALWAYS_ON_TOP 1003
#define OPT_CROP 1004
#define OPT_RECORD_FORMAT 1005
#define OPT_PREFER_TEXT 1006
#define OPT_WINDOW_X 1007
#define OPT_WINDOW_Y 1008
#define OPT_WINDOW_WIDTH 1009
#define OPT_WINDOW_HEIGHT 1010
#define OPT_WINDOW_BORDERLESS 1011
#define OPT_MAX_FPS 1012
#define OPT_RENDER_EXPIRED_FRAMES 1000
#define OPT_WINDOW_TITLE 1001
#define OPT_PUSH_TARGET 1002
#define OPT_ALWAYS_ON_TOP 1003
#define OPT_CROP 1004
#define OPT_RECORD_FORMAT 1005
#define OPT_PREFER_TEXT 1006
#define OPT_WINDOW_X 1007
#define OPT_WINDOW_Y 1008
#define OPT_WINDOW_WIDTH 1009
#define OPT_WINDOW_HEIGHT 1010
#define OPT_WINDOW_BORDERLESS 1011
#define OPT_MAX_FPS 1012
#define OPT_LOCK_VIDEO_ORIENTATION 1013
#define OPT_DISPLAY_ID 1014
#define OPT_ROTATION 1015
#define OPT_RENDER_DRIVER 1016
#define OPT_NO_MIPMAPS 1017
#define OPT_CODEC_OPTIONS 1018
#define OPT_FORCE_ADB_FORWARD 1019
#define OPT_DISABLE_SCREENSAVER 1020
#define OPT_SHORTCUT_MOD 1021
#define OPT_NO_KEY_REPEAT 1022
#define OPT_FORWARD_ALL_CLICKS 1023
#define OPT_LEGACY_PASTE 1024
#define OPT_ENCODER_NAME 1025
bool
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
static const struct option long_options[] = {
{"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP},
{"bit-rate", required_argument, NULL, 'b'},
{"crop", required_argument, NULL, OPT_CROP},
{"fullscreen", no_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'},
{"max-fps", required_argument, NULL, OPT_MAX_FPS},
{"max-size", required_argument, NULL, 'm'},
{"no-control", no_argument, NULL, 'n'},
{"no-display", no_argument, NULL, 'N'},
{"port", required_argument, NULL, 'p'},
{"push-target", required_argument, NULL, OPT_PUSH_TARGET},
{"record", required_argument, NULL, 'r'},
{"record-format", required_argument, NULL, OPT_RECORD_FORMAT},
{"render-expired-frames", no_argument, NULL,
OPT_RENDER_EXPIRED_FRAMES},
{"serial", required_argument, NULL, 's'},
{"show-touches", no_argument, NULL, 't'},
{"turn-screen-off", no_argument, NULL, 'S'},
{"prefer-text", no_argument, NULL, OPT_PREFER_TEXT},
{"version", no_argument, NULL, 'v'},
{"window-title", required_argument, NULL, OPT_WINDOW_TITLE},
{"window-x", required_argument, NULL, OPT_WINDOW_X},
{"window-y", required_argument, NULL, OPT_WINDOW_Y},
{"window-width", required_argument, NULL, OPT_WINDOW_WIDTH},
{"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT},
{"window-borderless", no_argument, NULL,
OPT_WINDOW_BORDERLESS},
{NULL, 0, NULL, 0 },
{"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP},
{"bit-rate", required_argument, NULL, 'b'},
{"codec-options", required_argument, NULL, OPT_CODEC_OPTIONS},
{"crop", required_argument, NULL, OPT_CROP},
{"disable-screensaver", no_argument, NULL,
OPT_DISABLE_SCREENSAVER},
{"display", required_argument, NULL, OPT_DISPLAY_ID},
{"encoder", required_argument, NULL, OPT_ENCODER_NAME},
{"force-adb-forward", no_argument, NULL,
OPT_FORCE_ADB_FORWARD},
{"forward-all-clicks", no_argument, NULL,
OPT_FORWARD_ALL_CLICKS},
{"fullscreen", no_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'},
{"legacy-paste", no_argument, NULL, OPT_LEGACY_PASTE},
{"lock-video-orientation", required_argument, NULL,
OPT_LOCK_VIDEO_ORIENTATION},
{"max-fps", required_argument, NULL, OPT_MAX_FPS},
{"max-size", required_argument, NULL, 'm'},
{"no-control", no_argument, NULL, 'n'},
{"no-display", no_argument, NULL, 'N'},
{"no-key-repeat", no_argument, NULL, OPT_NO_KEY_REPEAT},
{"no-mipmaps", no_argument, NULL, OPT_NO_MIPMAPS},
{"port", required_argument, NULL, 'p'},
{"prefer-text", no_argument, NULL, OPT_PREFER_TEXT},
{"push-target", required_argument, NULL, OPT_PUSH_TARGET},
{"record", required_argument, NULL, 'r'},
{"record-format", required_argument, NULL, OPT_RECORD_FORMAT},
{"render-driver", required_argument, NULL, OPT_RENDER_DRIVER},
{"render-expired-frames", no_argument, NULL,
OPT_RENDER_EXPIRED_FRAMES},
{"rotation", required_argument, NULL, OPT_ROTATION},
{"serial", required_argument, NULL, 's'},
{"shortcut-mod", required_argument, NULL, OPT_SHORTCUT_MOD},
{"show-touches", no_argument, NULL, 't'},
{"stay-awake", no_argument, NULL, 'w'},
{"turn-screen-off", no_argument, NULL, 'S'},
{"verbosity", required_argument, NULL, 'V'},
{"version", no_argument, NULL, 'v'},
{"window-title", required_argument, NULL, OPT_WINDOW_TITLE},
{"window-x", required_argument, NULL, OPT_WINDOW_X},
{"window-y", required_argument, NULL, OPT_WINDOW_Y},
{"window-width", required_argument, NULL, OPT_WINDOW_WIDTH},
{"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT},
{"window-borderless", no_argument, NULL,
OPT_WINDOW_BORDERLESS},
{NULL, 0, NULL, 0 },
};
struct scrcpy_options *opts = &args->opts;
@ -416,8 +725,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
optind = 0; // reset to start from the first argument in tests
int c;
while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTv", long_options,
NULL)) != -1) {
while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTvV:w",
long_options, NULL)) != -1) {
switch (c) {
case 'b':
if (!parse_bit_rate(optarg, &opts->bit_rate)) {
@ -430,6 +739,11 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
case OPT_CROP:
opts->crop = optarg;
break;
case OPT_DISPLAY_ID:
if (!parse_display_id(optarg, &opts->display_id)) {
return false;
}
break;
case 'f':
opts->fullscreen = true;
break;
@ -454,6 +768,11 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
return false;
}
break;
case OPT_LOCK_VIDEO_ORIENTATION:
if (!parse_lock_video_orientation(optarg, &opts->lock_video_orientation)) {
return false;
}
break;
case 'n':
opts->control = false;
break;
@ -486,6 +805,14 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
case 'v':
args->version = true;
break;
case 'V':
if (!parse_log_level(optarg, &opts->log_level)) {
return false;
}
break;
case 'w':
opts->stay_awake = true;
break;
case OPT_RENDER_EXPIRED_FRAMES:
opts->render_expired_frames = true;
break;
@ -521,6 +848,43 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
case OPT_PREFER_TEXT:
opts->prefer_text = true;
break;
case OPT_ROTATION:
if (!parse_rotation(optarg, &opts->rotation)) {
return false;
}
break;
case OPT_RENDER_DRIVER:
opts->render_driver = optarg;
break;
case OPT_NO_MIPMAPS:
opts->mipmaps = false;
break;
case OPT_NO_KEY_REPEAT:
opts->forward_key_repeat = false;
break;
case OPT_CODEC_OPTIONS:
opts->codec_options = optarg;
break;
case OPT_ENCODER_NAME:
opts->encoder_name = optarg;
break;
case OPT_FORCE_ADB_FORWARD:
opts->force_adb_forward = true;
break;
case OPT_DISABLE_SCREENSAVER:
opts->disable_screensaver = true;
break;
case OPT_SHORTCUT_MOD:
if (!parse_shortcut_mods(optarg, &opts->shortcut_mods)) {
return false;
}
break;
case OPT_FORWARD_ALL_CLICKS:
opts->forward_all_clicks = true;
break;
case OPT_LEGACY_PASTE:
opts->legacy_paste = true;
break;
default:
// getopt prints the error message on stderr
return false;
@ -532,11 +896,6 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
return false;
}
if (!opts->display && opts->fullscreen) {
LOGE("-f/--fullscreen-window is incompatible with -N/--no-display");
return false;
}
int index = optind;
if (index < argc) {
LOGE("Unexpected additional argument: %s", argv[index]);
@ -562,5 +921,10 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
return false;
}
if (!opts->control && opts->stay_awake) {
LOGE("Could not request to stay awake if control is disabled");
return false;
}
return true;
}

View File

@ -1,9 +1,10 @@
#ifndef SCRCPY_CLI_H
#define SCRCPY_CLI_H
#include "common.h"
#include <stdbool.h>
#include "config.h"
#include "scrcpy.h"
struct scrcpy_cli_args {
@ -18,4 +19,9 @@ scrcpy_print_usage(const char *arg0);
bool
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]);
#ifdef SC_TEST
bool
sc_parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods);
#endif
#endif

View File

@ -1,35 +1,11 @@
#ifndef COMMON_H
#define COMMON_H
#include <stdint.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)
struct size {
uint16_t width;
uint16_t height;
};
struct point {
int32_t x;
int32_t y;
};
struct position {
// The video screen size may be different from the real device screen size,
// so store to which size the absolute position apply, to scale it
// accordingly.
struct size screen_size;
struct point point;
};
struct port_range {
uint16_t first;
uint16_t last;
};
#endif

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

@ -0,0 +1,14 @@
#include "compat.h"
#include "config.h"
#ifndef HAVE_STRDUP
char *strdup(const char *s) {
size_t size = strlen(s) + 1;
char *dup = malloc(size);
if (dup) {
memcpy(dup, s, size);
}
return dup;
}
#endif

View File

@ -1,6 +1,14 @@
#ifndef COMPAT_H
#define COMPAT_H
#define _POSIX_C_SOURCE 200809L
#define _XOPEN_SOURCE 700
#define _GNU_SOURCE
#ifdef __APPLE__
# define _DARWIN_C_SOURCE
#endif
#include <libavcodec/version.h>
#include <libavformat/version.h>
#include <SDL2/SDL_version.h>
@ -48,4 +56,8 @@
# define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
#endif
#ifndef HAVE_STRDUP
char *strdup(const char *s);
#endif
#endif

View File

@ -1,9 +1,9 @@
#include "control_msg.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "config.h"
#include "util/buffer_util.h"
#include "util/log.h"
#include "util/str_util.h"
@ -20,9 +20,9 @@ write_position(uint8_t *buf, const struct position *position) {
static size_t
write_string(const char *utf8, size_t max_len, unsigned char *buf) {
size_t len = utf8_truncation_index(utf8, max_len);
buffer_write16be(buf, (uint16_t) len);
memcpy(&buf[2], utf8, len);
return 2 + len;
buffer_write32be(buf, len);
memcpy(&buf[4], utf8, len);
return 4 + len;
}
static uint16_t
@ -42,11 +42,13 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
case CONTROL_MSG_TYPE_INJECT_KEYCODE:
buf[1] = msg->inject_keycode.action;
buffer_write32be(&buf[2], msg->inject_keycode.keycode);
buffer_write32be(&buf[6], msg->inject_keycode.metastate);
return 10;
buffer_write32be(&buf[6], msg->inject_keycode.repeat);
buffer_write32be(&buf[10], msg->inject_keycode.metastate);
return 14;
case CONTROL_MSG_TYPE_INJECT_TEXT: {
size_t len = write_string(msg->inject_text.text,
CONTROL_MSG_TEXT_MAX_LENGTH, &buf[1]);
size_t len =
write_string(msg->inject_text.text,
CONTROL_MSG_INJECT_TEXT_MAX_LENGTH, &buf[1]);
return 1 + len;
}
case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
@ -66,10 +68,11 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
(uint32_t) msg->inject_scroll_event.vscroll);
return 21;
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
size_t len = write_string(msg->inject_text.text,
buf[1] = !!msg->set_clipboard.paste;
size_t len = write_string(msg->set_clipboard.text,
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
&buf[1]);
return 1 + len;
&buf[2]);
return 2 + len;
}
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
buf[1] = msg->set_screen_power_mode.mode;
@ -91,10 +94,10 @@ void
control_msg_destroy(struct control_msg *msg) {
switch (msg->type) {
case CONTROL_MSG_TYPE_INJECT_TEXT:
SDL_free(msg->inject_text.text);
free(msg->inject_text.text);
break;
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
SDL_free(msg->set_clipboard.text);
free(msg->set_clipboard.text);
break;
default:
// do nothing

View File

@ -1,21 +1,24 @@
#ifndef CONTROLMSG_H
#define CONTROLMSG_H
#include "common.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "config.h"
#include "android/input.h"
#include "android/keycodes.h"
#include "common.h"
#include "coords.h"
#define CONTROL_MSG_TEXT_MAX_LENGTH 300
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4093
#define CONTROL_MSG_SERIALIZED_MAX_SIZE \
(3 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH)
#define CONTROL_MSG_MAX_SIZE (1 << 18) // 256k
#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
// type: 1 byte; paste flag: 1 byte; length: 4 bytes
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 6)
#define POINTER_ID_MOUSE UINT64_C(-1);
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2);
enum control_msg_type {
CONTROL_MSG_TYPE_INJECT_KEYCODE,
@ -43,10 +46,11 @@ struct control_msg {
struct {
enum android_keyevent_action action;
enum android_keycode keycode;
uint32_t repeat;
enum android_metastate metastate;
} inject_keycode;
struct {
char *text; // owned, to be freed by SDL_free()
char *text; // owned, to be freed by free()
} inject_text;
struct {
enum android_motionevent_action action;
@ -61,7 +65,8 @@ struct control_msg {
int32_t vscroll;
} inject_scroll_event;
struct {
char *text; // owned, to be freed by SDL_free()
char *text; // owned, to be freed by free()
bool paste;
} set_clipboard;
struct {
enum screen_power_mode mode;
@ -69,7 +74,7 @@ struct control_msg {
};
};
// buf size must be at least CONTROL_MSG_SERIALIZED_MAX_SIZE
// buf size must be at least CONTROL_MSG_MAX_SIZE
// return the number of bytes written
size_t
control_msg_serialize(const struct control_msg *msg, unsigned char *buf);

View File

@ -2,26 +2,27 @@
#include <assert.h>
#include "config.h"
#include "util/lock.h"
#include "util/log.h"
bool
controller_init(struct controller *controller, socket_t control_socket) {
cbuf_init(&controller->queue);
if (!receiver_init(&controller->receiver, control_socket)) {
bool ok = receiver_init(&controller->receiver, control_socket);
if (!ok) {
return false;
}
if (!(controller->mutex = SDL_CreateMutex())) {
ok = sc_mutex_init(&controller->mutex);
if (!ok) {
receiver_destroy(&controller->receiver);
return false;
}
if (!(controller->msg_cond = SDL_CreateCond())) {
ok = sc_cond_init(&controller->msg_cond);
if (!ok) {
receiver_destroy(&controller->receiver);
SDL_DestroyMutex(controller->mutex);
sc_mutex_destroy(&controller->mutex);
return false;
}
@ -33,8 +34,8 @@ controller_init(struct controller *controller, socket_t control_socket) {
void
controller_destroy(struct controller *controller) {
SDL_DestroyCond(controller->msg_cond);
SDL_DestroyMutex(controller->mutex);
sc_cond_destroy(&controller->msg_cond);
sc_mutex_destroy(&controller->mutex);
struct control_msg msg;
while (cbuf_take(&controller->queue, &msg)) {
@ -47,26 +48,26 @@ controller_destroy(struct controller *controller) {
bool
controller_push_msg(struct controller *controller,
const struct control_msg *msg) {
mutex_lock(controller->mutex);
sc_mutex_lock(&controller->mutex);
bool was_empty = cbuf_is_empty(&controller->queue);
bool res = cbuf_push(&controller->queue, *msg);
if (was_empty) {
cond_signal(controller->msg_cond);
sc_cond_signal(&controller->msg_cond);
}
mutex_unlock(controller->mutex);
sc_mutex_unlock(&controller->mutex);
return res;
}
static bool
process_msg(struct controller *controller,
const struct control_msg *msg) {
unsigned char serialized_msg[CONTROL_MSG_SERIALIZED_MAX_SIZE];
int length = control_msg_serialize(msg, serialized_msg);
static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE];
size_t length = control_msg_serialize(msg, serialized_msg);
if (!length) {
return false;
}
int w = net_send_all(controller->control_socket, serialized_msg, length);
return w == length;
return (size_t) w == length;
}
static int
@ -74,20 +75,20 @@ run_controller(void *data) {
struct controller *controller = data;
for (;;) {
mutex_lock(controller->mutex);
sc_mutex_lock(&controller->mutex);
while (!controller->stopped && cbuf_is_empty(&controller->queue)) {
cond_wait(controller->msg_cond, controller->mutex);
sc_cond_wait(&controller->msg_cond, &controller->mutex);
}
if (controller->stopped) {
// stop immediately, do not process further msgs
mutex_unlock(controller->mutex);
sc_mutex_unlock(&controller->mutex);
break;
}
struct control_msg msg;
bool non_empty = cbuf_take(&controller->queue, &msg);
assert(non_empty);
(void) non_empty;
mutex_unlock(controller->mutex);
sc_mutex_unlock(&controller->mutex);
bool ok = process_msg(controller, &msg);
control_msg_destroy(&msg);
@ -103,16 +104,16 @@ bool
controller_start(struct controller *controller) {
LOGD("Starting controller thread");
controller->thread = SDL_CreateThread(run_controller, "controller",
controller);
if (!controller->thread) {
bool ok = sc_thread_create(&controller->thread, run_controller,
"controller", controller);
if (!ok) {
LOGC("Could not start controller thread");
return false;
}
if (!receiver_start(&controller->receiver)) {
controller_stop(controller);
SDL_WaitThread(controller->thread, NULL);
sc_thread_join(&controller->thread, NULL);
return false;
}
@ -121,14 +122,14 @@ controller_start(struct controller *controller) {
void
controller_stop(struct controller *controller) {
mutex_lock(controller->mutex);
sc_mutex_lock(&controller->mutex);
controller->stopped = true;
cond_signal(controller->msg_cond);
mutex_unlock(controller->mutex);
sc_cond_signal(&controller->msg_cond);
sc_mutex_unlock(&controller->mutex);
}
void
controller_join(struct controller *controller) {
SDL_WaitThread(controller->thread, NULL);
sc_thread_join(&controller->thread, NULL);
receiver_join(&controller->receiver);
}

View File

@ -1,23 +1,23 @@
#ifndef CONTROLLER_H
#define CONTROLLER_H
#include <stdbool.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "common.h"
#include <stdbool.h>
#include "config.h"
#include "control_msg.h"
#include "receiver.h"
#include "util/cbuf.h"
#include "util/net.h"
#include "util/thread.h"
struct control_msg_queue CBUF(struct control_msg, 64);
struct controller {
socket_t control_socket;
SDL_Thread *thread;
SDL_mutex *mutex;
SDL_cond *msg_cond;
sc_thread thread;
sc_mutex mutex;
sc_cond msg_cond;
bool stopped;
struct control_msg_queue queue;
struct receiver receiver;

24
app/src/coords.h Normal file
View File

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

View File

@ -3,12 +3,8 @@
#include <libavformat/avformat.h>
#include <libavutil/time.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include <unistd.h>
#include "config.h"
#include "compat.h"
#include "events.h"
#include "recorder.h"
#include "video_buffer.h"
@ -18,17 +14,7 @@
// set the decoded frame as ready for rendering, and notify
static void
push_frame(struct decoder *decoder) {
bool previous_frame_skipped;
video_buffer_offer_decoded_frame(decoder->video_buffer,
&previous_frame_skipped);
if (previous_frame_skipped) {
// the previous EVENT_NEW_FRAME will consume this frame
return;
}
static SDL_Event new_frame_event = {
.type = EVENT_NEW_FRAME,
};
SDL_PushEvent(&new_frame_event);
video_buffer_producer_offer_frame(decoder->video_buffer);
}
void
@ -70,7 +56,7 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) {
return false;
}
ret = avcodec_receive_frame(decoder->codec_ctx,
decoder->video_buffer->decoding_frame);
decoder->video_buffer->producer_frame);
if (!ret) {
// a frame was received
push_frame(decoder);

View File

@ -1,11 +1,11 @@
#ifndef DECODER_H
#define DECODER_H
#include "common.h"
#include <stdbool.h>
#include <libavformat/avformat.h>
#include "config.h"
struct video_buffer;
struct decoder {

View File

@ -1,6 +1,5 @@
#include "device.h"
#include "config.h"
#include "util/log.h"
bool

View File

@ -1,10 +1,11 @@
#ifndef DEVICE_H
#define DEVICE_H
#include "common.h"
#include <stdbool.h>
#include "config.h"
#include "common.h"
#include "coords.h"
#include "util/net.h"
#define DEVICE_NAME_FIELD_LENGTH 64

View File

@ -1,15 +1,15 @@
#include "device_msg.h"
#include <stdlib.h>
#include <string.h>
#include "config.h"
#include "util/buffer_util.h"
#include "util/log.h"
ssize_t
device_msg_deserialize(const unsigned char *buf, size_t len,
struct device_msg *msg) {
if (len < 3) {
if (len < 5) {
// at least type + empty string length
return 0; // not available
}
@ -17,22 +17,22 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
msg->type = buf[0];
switch (msg->type) {
case DEVICE_MSG_TYPE_CLIPBOARD: {
uint16_t clipboard_len = buffer_read16be(&buf[1]);
if (clipboard_len > len - 3) {
size_t clipboard_len = buffer_read32be(&buf[1]);
if (clipboard_len > len - 5) {
return 0; // not available
}
char *text = SDL_malloc(clipboard_len + 1);
char *text = malloc(clipboard_len + 1);
if (!text) {
LOGW("Could not allocate text for clipboard");
return -1;
}
if (clipboard_len) {
memcpy(text, &buf[3], clipboard_len);
memcpy(text, &buf[5], clipboard_len);
}
text[clipboard_len] = '\0';
msg->clipboard.text = text;
return 3 + clipboard_len;
return 5 + clipboard_len;
}
default:
LOGW("Unknown device message type: %d", (int) msg->type);
@ -43,6 +43,6 @@ 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) {
SDL_free(msg->clipboard.text);
free(msg->clipboard.text);
}
}

View File

@ -1,14 +1,15 @@
#ifndef DEVICEMSG_H
#define DEVICEMSG_H
#include "common.h"
#include <stdbool.h>
#include <stdint.h>
#include <unistd.h>
#include "config.h"
#define DEVICE_MSG_TEXT_MAX_LENGTH 4093
#define DEVICE_MSG_SERIALIZED_MAX_SIZE (3 + DEVICE_MSG_TEXT_MAX_LENGTH)
#define DEVICE_MSG_MAX_SIZE (1 << 18) // 256k
// type: 1 byte; length: 4 bytes
#define DEVICE_MSG_TEXT_MAX_LENGTH (DEVICE_MSG_MAX_SIZE - 5)
enum device_msg_type {
DEVICE_MSG_TYPE_CLIPBOARD,
@ -18,7 +19,7 @@ struct device_msg {
enum device_msg_type type;
union {
struct {
char *text; // owned, to be freed by SDL_free()
char *text; // owned, to be freed by free()
} clipboard;
};
};

View File

@ -1,7 +1,5 @@
#include "event_converter.h"
#include "config.h"
#define MAP(FROM, TO) case FROM: *to = TO; return true
#define FAIL default: return false
@ -92,10 +90,31 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT);
MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN);
MAP(SDLK_UP, AKEYCODE_DPAD_UP);
MAP(SDLK_LCTRL, AKEYCODE_CTRL_LEFT);
MAP(SDLK_RCTRL, AKEYCODE_CTRL_RIGHT);
MAP(SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT);
MAP(SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT);
}
if (prefer_text) {
// do not forward alpha and space key events
if (!(mod & (KMOD_NUM | KMOD_SHIFT))) {
// Handle Numpad events when Num Lock is disabled
// If SHIFT is pressed, a text event will be sent instead
switch(from) {
MAP(SDLK_KP_0, AKEYCODE_INSERT);
MAP(SDLK_KP_1, AKEYCODE_MOVE_END);
MAP(SDLK_KP_2, AKEYCODE_DPAD_DOWN);
MAP(SDLK_KP_3, AKEYCODE_PAGE_DOWN);
MAP(SDLK_KP_4, AKEYCODE_DPAD_LEFT);
MAP(SDLK_KP_6, AKEYCODE_DPAD_RIGHT);
MAP(SDLK_KP_7, AKEYCODE_MOVE_HOME);
MAP(SDLK_KP_8, AKEYCODE_DPAD_UP);
MAP(SDLK_KP_9, AKEYCODE_PAGE_UP);
MAP(SDLK_KP_PERIOD, AKEYCODE_FORWARD_DEL);
}
}
if (prefer_text && !(mod & KMOD_CTRL)) {
// do not forward alpha and space key events (unless Ctrl is pressed)
return false;
}

View File

@ -1,10 +1,11 @@
#ifndef CONVERT_H
#define CONVERT_H
#include "common.h"
#include <stdbool.h>
#include <SDL2/SDL_events.h>
#include "config.h"
#include "control_msg.h"
bool

View File

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

View File

@ -3,16 +3,14 @@
#include <assert.h>
#include <string.h>
#include "config.h"
#include "command.h"
#include "util/lock.h"
#include "adb.h"
#include "util/log.h"
#define DEFAULT_PUSH_TARGET "/sdcard/"
static void
file_handler_request_destroy(struct file_handler_request *req) {
SDL_free(req->file);
free(req->file);
}
bool
@ -21,21 +19,23 @@ file_handler_init(struct file_handler *file_handler, const char *serial,
cbuf_init(&file_handler->queue);
if (!(file_handler->mutex = SDL_CreateMutex())) {
bool ok = sc_mutex_init(&file_handler->mutex);
if (!ok) {
return false;
}
if (!(file_handler->event_cond = SDL_CreateCond())) {
SDL_DestroyMutex(file_handler->mutex);
ok = sc_cond_init(&file_handler->event_cond);
if (!ok) {
sc_mutex_destroy(&file_handler->mutex);
return false;
}
if (serial) {
file_handler->serial = SDL_strdup(serial);
file_handler->serial = strdup(serial);
if (!file_handler->serial) {
LOGW("Could not strdup serial");
SDL_DestroyCond(file_handler->event_cond);
SDL_DestroyMutex(file_handler->mutex);
sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex);
return false;
}
} else {
@ -55,9 +55,9 @@ file_handler_init(struct file_handler *file_handler, const char *serial,
void
file_handler_destroy(struct file_handler *file_handler) {
SDL_DestroyCond(file_handler->event_cond);
SDL_DestroyMutex(file_handler->mutex);
SDL_free(file_handler->serial);
sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex);
free(file_handler->serial);
struct file_handler_request req;
while (cbuf_take(&file_handler->queue, &req)) {
@ -93,13 +93,13 @@ file_handler_request(struct file_handler *file_handler,
.file = file,
};
mutex_lock(file_handler->mutex);
sc_mutex_lock(&file_handler->mutex);
bool was_empty = cbuf_is_empty(&file_handler->queue);
bool res = cbuf_push(&file_handler->queue, req);
if (was_empty) {
cond_signal(file_handler->event_cond);
sc_cond_signal(&file_handler->event_cond);
}
mutex_unlock(file_handler->mutex);
sc_mutex_unlock(&file_handler->mutex);
return res;
}
@ -108,14 +108,14 @@ run_file_handler(void *data) {
struct file_handler *file_handler = data;
for (;;) {
mutex_lock(file_handler->mutex);
sc_mutex_lock(&file_handler->mutex);
file_handler->current_process = PROCESS_NONE;
while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) {
cond_wait(file_handler->event_cond, file_handler->mutex);
sc_cond_wait(&file_handler->event_cond, &file_handler->mutex);
}
if (file_handler->stopped) {
// stop immediately, do not process further events
mutex_unlock(file_handler->mutex);
sc_mutex_unlock(&file_handler->mutex);
break;
}
struct file_handler_request req;
@ -133,16 +133,16 @@ run_file_handler(void *data) {
file_handler->push_target);
}
file_handler->current_process = process;
mutex_unlock(file_handler->mutex);
sc_mutex_unlock(&file_handler->mutex);
if (req.action == ACTION_INSTALL_APK) {
if (process_check_success(process, "adb install")) {
if (process_check_success(process, "adb install", false)) {
LOGI("%s successfully installed", req.file);
} else {
LOGE("Failed to install %s", req.file);
}
} else {
if (process_check_success(process, "adb push")) {
if (process_check_success(process, "adb push", false)) {
LOGI("%s successfully pushed to %s", req.file,
file_handler->push_target);
} else {
@ -151,6 +151,14 @@ run_file_handler(void *data) {
}
}
sc_mutex_lock(&file_handler->mutex);
// Close the process (it is necessary already terminated)
// Execute this call with mutex locked to avoid race conditions with
// file_handler_stop()
process_close(file_handler->current_process);
file_handler->current_process = PROCESS_NONE;
sc_mutex_unlock(&file_handler->mutex);
file_handler_request_destroy(&req);
}
return 0;
@ -160,9 +168,9 @@ bool
file_handler_start(struct file_handler *file_handler) {
LOGD("Starting file_handler thread");
file_handler->thread = SDL_CreateThread(run_file_handler, "file_handler",
file_handler);
if (!file_handler->thread) {
bool ok = sc_thread_create(&file_handler->thread, run_file_handler,
"file_handler", file_handler);
if (!ok) {
LOGC("Could not start file_handler thread");
return false;
}
@ -172,20 +180,18 @@ file_handler_start(struct file_handler *file_handler) {
void
file_handler_stop(struct file_handler *file_handler) {
mutex_lock(file_handler->mutex);
sc_mutex_lock(&file_handler->mutex);
file_handler->stopped = true;
cond_signal(file_handler->event_cond);
sc_cond_signal(&file_handler->event_cond);
if (file_handler->current_process != PROCESS_NONE) {
if (!cmd_terminate(file_handler->current_process)) {
LOGW("Could not terminate install process");
if (!process_terminate(file_handler->current_process)) {
LOGW("Could not terminate push/install process");
}
cmd_simple_wait(file_handler->current_process, NULL);
file_handler->current_process = PROCESS_NONE;
}
mutex_unlock(file_handler->mutex);
sc_mutex_unlock(&file_handler->mutex);
}
void
file_handler_join(struct file_handler *file_handler) {
SDL_WaitThread(file_handler->thread, NULL);
sc_thread_join(&file_handler->thread, NULL);
}

View File

@ -1,13 +1,13 @@
#ifndef FILE_HANDLER_H
#define FILE_HANDLER_H
#include <stdbool.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "common.h"
#include "config.h"
#include "command.h"
#include <stdbool.h>
#include "adb.h"
#include "util/cbuf.h"
#include "util/thread.h"
typedef enum {
ACTION_INSTALL_APK,
@ -24,9 +24,9 @@ struct file_handler_request_queue CBUF(struct file_handler_request, 16);
struct file_handler {
char *serial;
const char *push_target;
SDL_Thread *thread;
SDL_mutex *mutex;
SDL_cond *event_cond;
sc_thread thread;
sc_mutex mutex;
sc_cond event_cond;
bool stopped;
bool initialized;
process_t current_process;
@ -49,7 +49,7 @@ file_handler_stop(struct file_handler *file_handler);
void
file_handler_join(struct file_handler *file_handler);
// take ownership of file, and will SDL_free() it
// take ownership of file, and will free() it
bool
file_handler_request(struct file_handler *file_handler,
file_handler_action_t action,

View File

@ -3,27 +3,25 @@
#include <assert.h>
#include <SDL2/SDL_timer.h>
#include "config.h"
#include "util/lock.h"
#include "util/log.h"
#define FPS_COUNTER_INTERVAL_MS 1000
bool
fps_counter_init(struct fps_counter *counter) {
counter->mutex = SDL_CreateMutex();
if (!counter->mutex) {
bool ok = sc_mutex_init(&counter->mutex);
if (!ok) {
return false;
}
counter->state_cond = SDL_CreateCond();
if (!counter->state_cond) {
SDL_DestroyMutex(counter->mutex);
ok = sc_cond_init(&counter->state_cond);
if (!ok) {
sc_mutex_destroy(&counter->mutex);
return false;
}
counter->thread = NULL;
SDL_AtomicSet(&counter->started, 0);
counter->thread_started = false;
atomic_init(&counter->started, 0);
// no need to initialize the other fields, they are unused until started
return true;
@ -31,8 +29,18 @@ fps_counter_init(struct fps_counter *counter) {
void
fps_counter_destroy(struct fps_counter *counter) {
SDL_DestroyCond(counter->state_cond);
SDL_DestroyMutex(counter->mutex);
sc_cond_destroy(&counter->state_cond);
sc_mutex_destroy(&counter->mutex);
}
static inline bool
is_started(struct fps_counter *counter) {
return atomic_load_explicit(&counter->started, memory_order_acquire);
}
static inline void
set_started(struct fps_counter *counter, bool started) {
atomic_store_explicit(&counter->started, started, memory_order_release);
}
// must be called with mutex locked
@ -68,12 +76,12 @@ static int
run_fps_counter(void *data) {
struct fps_counter *counter = data;
mutex_lock(counter->mutex);
sc_mutex_lock(&counter->mutex);
while (!counter->interrupted) {
while (!counter->interrupted && !SDL_AtomicGet(&counter->started)) {
cond_wait(counter->state_cond, counter->mutex);
while (!counter->interrupted && !is_started(counter)) {
sc_cond_wait(&counter->state_cond, &counter->mutex);
}
while (!counter->interrupted && SDL_AtomicGet(&counter->started)) {
while (!counter->interrupted && is_started(counter)) {
uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now);
@ -81,32 +89,35 @@ run_fps_counter(void *data) {
uint32_t remaining = counter->next_timestamp - now;
// ignore the reason (timeout or signaled), we just loop anyway
cond_wait_timeout(counter->state_cond, counter->mutex, remaining);
sc_cond_timedwait(&counter->state_cond, &counter->mutex, remaining);
}
}
mutex_unlock(counter->mutex);
sc_mutex_unlock(&counter->mutex);
return 0;
}
bool
fps_counter_start(struct fps_counter *counter) {
mutex_lock(counter->mutex);
sc_mutex_lock(&counter->mutex);
counter->next_timestamp = SDL_GetTicks() + FPS_COUNTER_INTERVAL_MS;
counter->nr_rendered = 0;
counter->nr_skipped = 0;
mutex_unlock(counter->mutex);
sc_mutex_unlock(&counter->mutex);
SDL_AtomicSet(&counter->started, 1);
cond_signal(counter->state_cond);
set_started(counter, true);
sc_cond_signal(&counter->state_cond);
// counter->thread is always accessed from the same thread, no need to lock
if (!counter->thread) {
counter->thread =
SDL_CreateThread(run_fps_counter, "fps counter", counter);
if (!counter->thread) {
// counter->thread_started and counter->thread are always accessed from the
// same thread, no need to lock
if (!counter->thread_started) {
bool ok = sc_thread_create(&counter->thread, run_fps_counter,
"fps counter", counter);
if (!ok) {
LOGE("Could not start FPS counter thread");
return false;
}
counter->thread_started = true;
}
return true;
@ -114,57 +125,57 @@ fps_counter_start(struct fps_counter *counter) {
void
fps_counter_stop(struct fps_counter *counter) {
SDL_AtomicSet(&counter->started, 0);
cond_signal(counter->state_cond);
set_started(counter, false);
sc_cond_signal(&counter->state_cond);
}
bool
fps_counter_is_started(struct fps_counter *counter) {
return SDL_AtomicGet(&counter->started);
return is_started(counter);
}
void
fps_counter_interrupt(struct fps_counter *counter) {
if (!counter->thread) {
if (!counter->thread_started) {
return;
}
mutex_lock(counter->mutex);
sc_mutex_lock(&counter->mutex);
counter->interrupted = true;
mutex_unlock(counter->mutex);
sc_mutex_unlock(&counter->mutex);
// wake up blocking wait
cond_signal(counter->state_cond);
sc_cond_signal(&counter->state_cond);
}
void
fps_counter_join(struct fps_counter *counter) {
if (counter->thread) {
SDL_WaitThread(counter->thread, NULL);
if (counter->thread_started) {
sc_thread_join(&counter->thread, NULL);
}
}
void
fps_counter_add_rendered_frame(struct fps_counter *counter) {
if (!SDL_AtomicGet(&counter->started)) {
if (!is_started(counter)) {
return;
}
mutex_lock(counter->mutex);
sc_mutex_lock(&counter->mutex);
uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now);
++counter->nr_rendered;
mutex_unlock(counter->mutex);
sc_mutex_unlock(&counter->mutex);
}
void
fps_counter_add_skipped_frame(struct fps_counter *counter) {
if (!SDL_AtomicGet(&counter->started)) {
fps_counter_add_skipped_frames(struct fps_counter *counter, unsigned n) {
if (!is_started(counter)) {
return;
}
mutex_lock(counter->mutex);
sc_mutex_lock(&counter->mutex);
uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now);
++counter->nr_skipped;
mutex_unlock(counter->mutex);
counter->nr_skipped += n;
sc_mutex_unlock(&counter->mutex);
}

View File

@ -1,22 +1,24 @@
#ifndef FPSCOUNTER_H
#define FPSCOUNTER_H
#include "common.h"
#include <stdatomic.h>
#include <stdbool.h>
#include <stdint.h>
#include <SDL2/SDL_atomic.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "config.h"
#include "util/thread.h"
struct fps_counter {
SDL_Thread *thread;
SDL_mutex *mutex;
SDL_cond *state_cond;
sc_thread thread;
sc_mutex mutex;
sc_cond state_cond;
bool thread_started;
// atomic so that we can check without locking the mutex
// if the FPS counter is disabled, we don't want to lock unnecessarily
SDL_atomic_t started;
atomic_bool started;
// the following fields are protected by the mutex
bool interrupted;
@ -52,6 +54,6 @@ void
fps_counter_add_rendered_frame(struct fps_counter *counter);
void
fps_counter_add_skipped_frame(struct fps_counter *counter);
fps_counter_add_skipped_frames(struct fps_counter *counter, unsigned n);
#endif

View File

@ -1,42 +1,79 @@
#include "input_manager.h"
#include <assert.h>
#include <SDL2/SDL_keycode.h>
#include "config.h"
#include "event_converter.h"
#include "util/lock.h"
#include "util/log.h"
// Convert window coordinates (as provided by SDL_GetMouseState() to renderer
// coordinates (as provided in SDL mouse events)
//
// See my question:
// <https://stackoverflow.com/questions/49111054/how-to-get-mouse-position-on-mouse-wheel-event>
static void
convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int *y) {
SDL_Rect viewport;
float scale_x, scale_y;
SDL_RenderGetViewport(renderer, &viewport);
SDL_RenderGetScale(renderer, &scale_x, &scale_y);
*x = (int) (*x / scale_x) - viewport.x;
*y = (int) (*y / scale_y) - viewport.y;
}
static struct point
get_mouse_point(struct screen *screen) {
int x;
int y;
SDL_GetMouseState(&x, &y);
convert_to_renderer_coordinates(screen->renderer, &x, &y);
return (struct point) {
.x = x,
.y = y,
};
}
static const int ACTION_DOWN = 1;
static const int ACTION_UP = 1 << 1;
#define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI)
static inline uint16_t
to_sdl_mod(unsigned mod) {
uint16_t sdl_mod = 0;
if (mod & SC_MOD_LCTRL) {
sdl_mod |= KMOD_LCTRL;
}
if (mod & SC_MOD_RCTRL) {
sdl_mod |= KMOD_RCTRL;
}
if (mod & SC_MOD_LALT) {
sdl_mod |= KMOD_LALT;
}
if (mod & SC_MOD_RALT) {
sdl_mod |= KMOD_RALT;
}
if (mod & SC_MOD_LSUPER) {
sdl_mod |= KMOD_LGUI;
}
if (mod & SC_MOD_RSUPER) {
sdl_mod |= KMOD_RGUI;
}
return sdl_mod;
}
static bool
is_shortcut_mod(struct input_manager *im, uint16_t sdl_mod) {
// keep only the relevant modifier keys
sdl_mod &= SC_SDL_SHORTCUT_MODS_MASK;
assert(im->sdl_shortcut_mods.count);
assert(im->sdl_shortcut_mods.count < SC_MAX_SHORTCUT_MODS);
for (unsigned i = 0; i < im->sdl_shortcut_mods.count; ++i) {
if (im->sdl_shortcut_mods.data[i] == sdl_mod) {
return true;
}
}
return false;
}
void
input_manager_init(struct input_manager *im,
const struct scrcpy_options *options)
{
im->control = options->control;
im->forward_key_repeat = options->forward_key_repeat;
im->prefer_text = options->prefer_text;
im->forward_all_clicks = options->forward_all_clicks;
im->legacy_paste = options->legacy_paste;
const struct sc_shortcut_mods *shortcut_mods = &options->shortcut_mods;
assert(shortcut_mods->count);
assert(shortcut_mods->count < SC_MAX_SHORTCUT_MODS);
for (unsigned i = 0; i < shortcut_mods->count; ++i) {
uint16_t sdl_mod = to_sdl_mod(shortcut_mods->data[i]);
assert(sdl_mod);
im->sdl_shortcut_mods.data[i] = sdl_mod;
}
im->sdl_shortcut_mods.count = shortcut_mods->count;
im->vfinger_down = false;
}
static void
send_keycode(struct controller *controller, enum android_keycode keycode,
int actions, const char *name) {
@ -45,6 +82,7 @@ send_keycode(struct controller *controller, enum android_keycode keycode,
msg.type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
msg.inject_keycode.keycode = keycode;
msg.inject_keycode.metastate = 0;
msg.inject_keycode.repeat = 0;
if (actions & ACTION_DOWN) {
msg.inject_keycode.action = AKEY_EVENT_ACTION_DOWN;
@ -97,6 +135,16 @@ action_menu(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_MENU, actions, "MENU");
}
static inline void
action_copy(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_COPY, actions, "COPY");
}
static inline void
action_cut(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_CUT, actions, "CUT");
}
// turn the screen on if it was off, press BACK otherwise
static void
press_back_or_turn_screen_on(struct controller *controller) {
@ -129,17 +177,7 @@ collapse_notification_panel(struct controller *controller) {
}
static void
request_device_clipboard(struct controller *controller) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD;
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request device clipboard");
}
}
static void
set_device_clipboard(struct controller *controller) {
set_device_clipboard(struct controller *controller, bool paste) {
char *text = SDL_GetClipboardText();
if (!text) {
LOGW("Could not get clipboard text: %s", SDL_GetError());
@ -151,12 +189,20 @@ set_device_clipboard(struct controller *controller) {
return;
}
char *text_dup = strdup(text);
SDL_free(text);
if (!text_dup) {
LOGW("Could not strdup input text");
return;
}
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD;
msg.set_clipboard.text = text;
msg.set_clipboard.text = text_dup;
msg.set_clipboard.paste = paste;
if (!controller_push_msg(controller, &msg)) {
SDL_free(text);
free(text_dup);
LOGW("Could not request 'set device clipboard'");
}
}
@ -202,11 +248,18 @@ clipboard_paste(struct controller *controller) {
return;
}
char *text_dup = strdup(text);
SDL_free(text);
if (!text_dup) {
LOGW("Could not strdup input text");
return;
}
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = text;
msg.inject_text.text = text_dup;
if (!controller_push_msg(controller, &msg)) {
SDL_free(text);
free(text_dup);
LOGW("Could not request 'paste clipboard'");
}
}
@ -221,9 +274,25 @@ rotate_device(struct controller *controller) {
}
}
void
static void
rotate_client_left(struct screen *screen) {
unsigned new_rotation = (screen->rotation + 1) % 4;
screen_set_rotation(screen, new_rotation);
}
static void
rotate_client_right(struct screen *screen) {
unsigned new_rotation = (screen->rotation + 3) % 4;
screen_set_rotation(screen, new_rotation);
}
static void
input_manager_process_text_input(struct input_manager *im,
const SDL_TextInputEvent *event) {
if (is_shortcut_mod(im, SDL_GetModState())) {
// A shortcut must never generate text events
return;
}
if (!im->prefer_text) {
char c = event->text[0];
if (isalpha(c) || c == ' ') {
@ -235,20 +304,50 @@ input_manager_process_text_input(struct input_manager *im,
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = SDL_strdup(event->text);
msg.inject_text.text = strdup(event->text);
if (!msg.inject_text.text) {
LOGW("Could not strdup input text");
return;
}
if (!controller_push_msg(im->controller, &msg)) {
SDL_free(msg.inject_text.text);
free(msg.inject_text.text);
LOGW("Could not request 'inject text'");
}
}
static bool
simulate_virtual_finger(struct input_manager *im,
enum android_motionevent_action action,
struct point point) {
bool up = action == AMOTION_EVENT_ACTION_UP;
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
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.pressure = up ? 0.0f : 1.0f;
msg.inject_touch_event.buttons = 0;
if (!controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject virtual finger event'");
return false;
}
return true;
}
static struct point
inverse_point(struct point point, struct size size) {
point.x = size.width - point.x;
point.y = size.height - point.y;
return point;
}
static bool
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
bool prefer_text) {
bool prefer_text, uint32_t repeat) {
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) {
@ -261,136 +360,131 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
return false;
}
to->inject_keycode.repeat = repeat;
to->inject_keycode.metastate = convert_meta_state(mod);
return true;
}
void
static void
input_manager_process_key(struct input_manager *im,
const SDL_KeyboardEvent *event,
bool control) {
const SDL_KeyboardEvent *event) {
// control: indicates the state of the command-line option --no-control
// ctrl: the Ctrl key
bool control = im->control;
bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL);
bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT);
bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI);
// use Cmd on macOS, Ctrl on other platforms
#ifdef __APPLE__
bool cmd = !ctrl && meta;
#else
if (meta) {
// no shortcuts involve Meta on platforms other than macOS, and it must
// not be forwarded to the device
return;
}
bool cmd = ctrl; // && !meta, already guaranteed
#endif
if (alt) {
// no shortcuts involve Alt, and it must not be forwarded to the device
return;
}
bool smod = is_shortcut_mod(im, event->keysym.mod);
struct controller *controller = im->controller;
// capture all Ctrl events
if (ctrl || cmd) {
SDL_Keycode keycode = event->keysym.sym;
bool down = event->type == SDL_KEYDOWN;
SDL_Keycode keycode = event->keysym.sym;
bool down = event->type == SDL_KEYDOWN;
bool ctrl = event->keysym.mod & KMOD_CTRL;
bool shift = event->keysym.mod & KMOD_SHIFT;
bool repeat = event->repeat;
// The shortcut modifier is pressed
if (smod) {
int action = down ? ACTION_DOWN : ACTION_UP;
bool repeat = event->repeat;
bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
switch (keycode) {
case SDLK_h:
// Ctrl+h on all platform, since Cmd+h is already captured by
// the system on macOS to hide the window
if (control && ctrl && !meta && !shift && !repeat) {
if (control && !shift && !repeat) {
action_home(controller, action);
}
return;
case SDLK_b: // fall-through
case SDLK_BACKSPACE:
if (control && cmd && !shift && !repeat) {
if (control && !shift && !repeat) {
action_back(controller, action);
}
return;
case SDLK_s:
if (control && cmd && !shift && !repeat) {
if (control && !shift && !repeat) {
action_app_switch(controller, action);
}
return;
case SDLK_m:
// Ctrl+m on all platform, since Cmd+m is already captured by
// the system on macOS to minimize the window
if (control && ctrl && !meta && !shift && !repeat) {
if (control && !shift && !repeat) {
action_menu(controller, action);
}
return;
case SDLK_p:
if (control && cmd && !shift && !repeat) {
if (control && !shift && !repeat) {
action_power(controller, action);
}
return;
case SDLK_o:
if (control && cmd && !shift && down) {
set_screen_power_mode(controller, SCREEN_POWER_MODE_OFF);
if (control && !repeat && down) {
enum screen_power_mode mode = shift
? SCREEN_POWER_MODE_NORMAL
: SCREEN_POWER_MODE_OFF;
set_screen_power_mode(controller, mode);
}
return;
case SDLK_DOWN:
if (control && cmd && !shift) {
if (control && !shift) {
// forward repeated events
action_volume_down(controller, action);
}
return;
case SDLK_UP:
if (control && cmd && !shift) {
if (control && !shift) {
// forward repeated events
action_volume_up(controller, action);
}
return;
case SDLK_LEFT:
if (!shift && !repeat && down) {
rotate_client_left(im->screen);
}
return;
case SDLK_RIGHT:
if (!shift && !repeat && down) {
rotate_client_right(im->screen);
}
return;
case SDLK_c:
if (control && cmd && !shift && !repeat && down) {
request_device_clipboard(controller);
if (control && !shift && !repeat) {
action_copy(controller, action);
}
return;
case SDLK_x:
if (control && !shift && !repeat) {
action_cut(controller, action);
}
return;
case SDLK_v:
if (control && cmd && !repeat && down) {
if (shift) {
// store the text in the device clipboard
set_device_clipboard(controller);
} else {
if (control && !repeat && down) {
if (shift || im->legacy_paste) {
// inject the text as input events
clipboard_paste(controller);
} else {
// store the text in the device clipboard and paste
set_device_clipboard(controller, true);
}
}
return;
case SDLK_f:
if (!shift && cmd && !repeat && down) {
if (!shift && !repeat && down) {
screen_switch_fullscreen(im->screen);
}
return;
case SDLK_x:
if (!shift && cmd && !repeat && down) {
case SDLK_w:
if (!shift && !repeat && down) {
screen_resize_to_fit(im->screen);
}
return;
case SDLK_g:
if (!shift && cmd && !repeat && down) {
if (!shift && !repeat && down) {
screen_resize_to_pixel_perfect(im->screen);
}
return;
case SDLK_i:
if (!shift && cmd && !repeat && down) {
struct fps_counter *fps_counter =
im->video_buffer->fps_counter;
switch_fps_counter_state(fps_counter);
if (!shift && !repeat && down) {
switch_fps_counter_state(im->fps_counter);
}
return;
case SDLK_n:
if (control && cmd && !repeat && down) {
if (control && !repeat && down) {
if (shift) {
collapse_notification_panel(controller);
} else {
@ -399,7 +493,7 @@ input_manager_process_key(struct input_manager *im,
}
return;
case SDLK_r:
if (control && cmd && !shift && !repeat && down) {
if (control && !shift && !repeat && down) {
rotate_device(controller);
}
return;
@ -412,8 +506,28 @@ input_manager_process_key(struct input_manager *im,
return;
}
if (event->repeat) {
if (!im->forward_key_repeat) {
return;
}
++im->repeat;
} else {
im->repeat = 0;
}
if (ctrl && !shift && keycode == SDLK_v && down && !repeat) {
if (im->legacy_paste) {
// inject the text as input events
clipboard_paste(controller);
return;
}
// Synchronize the computer clipboard to the device clipboard before
// sending Ctrl+v, to allow seamless copy-paste.
set_device_clipboard(controller, false);
}
struct control_msg msg;
if (convert_input_key(event, &msg, im->prefer_text)) {
if (convert_input_key(event, &msg, im->prefer_text, im->repeat)) {
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'inject keycode'");
}
@ -427,15 +541,15 @@ convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
to->inject_touch_event.action = AMOTION_EVENT_ACTION_MOVE;
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = screen->frame_size;
to->inject_touch_event.position.point.x = from->x;
to->inject_touch_event.position.point.y = from->y;
to->inject_touch_event.position.point =
screen_convert_window_to_frame_coords(screen, from->x, from->y);
to->inject_touch_event.pressure = 1.f;
to->inject_touch_event.buttons = convert_mouse_buttons(from->state);
return true;
}
void
static void
input_manager_process_mouse_motion(struct input_manager *im,
const SDL_MouseMotionEvent *event) {
if (!event->state) {
@ -447,10 +561,18 @@ input_manager_process_mouse_motion(struct input_manager *im,
return;
}
struct control_msg msg;
if (convert_mouse_motion(event, im->screen, &msg)) {
if (!controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject mouse motion event'");
}
if (!convert_mouse_motion(event, im->screen, &msg)) {
return;
}
if (!controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject mouse motion event'");
}
if (im->vfinger_down) {
struct point mouse = msg.inject_touch_event.position.point;
struct point vfinger = inverse_point(mouse, im->screen->frame_size);
simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger);
}
}
@ -463,19 +585,25 @@ convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
return false;
}
struct size frame_size = screen->frame_size;
to->inject_touch_event.pointer_id = from->fingerId;
to->inject_touch_event.position.screen_size = frame_size;
to->inject_touch_event.position.screen_size = screen->frame_size;
int dw;
int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
// SDL touch event coordinates are normalized in the range [0; 1]
to->inject_touch_event.position.point.x = from->x * frame_size.width;
to->inject_touch_event.position.point.y = from->y * frame_size.height;
int32_t x = from->x * dw;
int32_t y = from->y * dh;
to->inject_touch_event.position.point =
screen_convert_drawable_to_frame_coords(screen, x, y);
to->inject_touch_event.pressure = from->pressure;
to->inject_touch_event.buttons = 0;
return true;
}
void
static void
input_manager_process_touch(struct input_manager *im,
const SDL_TouchFingerEvent *event) {
struct control_msg msg;
@ -486,13 +614,6 @@ input_manager_process_touch(struct input_manager *im,
}
}
static bool
is_outside_device_screen(struct input_manager *im, int x, int y)
{
return x < 0 || x >= im->screen->frame_size.width ||
y < 0 || y >= im->screen->frame_size.height;
}
static bool
convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
struct control_msg *to) {
@ -504,24 +625,28 @@ convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = screen->frame_size;
to->inject_touch_event.position.point.x = from->x;
to->inject_touch_event.position.point.y = from->y;
to->inject_touch_event.pressure = 1.f;
to->inject_touch_event.position.point =
screen_convert_window_to_frame_coords(screen, from->x, from->y);
to->inject_touch_event.pressure =
from->type == SDL_MOUSEBUTTONDOWN ? 1.f : 0.f;
to->inject_touch_event.buttons =
convert_mouse_buttons(SDL_BUTTON(from->button));
return true;
}
void
static void
input_manager_process_mouse_button(struct input_manager *im,
const SDL_MouseButtonEvent *event,
bool control) {
const SDL_MouseButtonEvent *event) {
bool control = im->control;
if (event->which == SDL_TOUCH_MOUSEID) {
// simulated from touch events, so it's a duplicate
return;
}
if (event->type == SDL_MOUSEBUTTONDOWN) {
bool down = event->type == SDL_MOUSEBUTTONDOWN;
if (!im->forward_all_clicks && down) {
if (control && event->button == SDL_BUTTON_RIGHT) {
press_back_or_turn_screen_on(im->controller);
return;
@ -530,10 +655,15 @@ input_manager_process_mouse_button(struct input_manager *im,
action_home(im->controller, ACTION_DOWN | ACTION_UP);
return;
}
// double-click on black borders resize to fit the device screen
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
bool outside =
is_outside_device_screen(im, event->x, event->y);
int32_t x = event->x;
int32_t y = event->y;
screen_hidpi_scale_coords(im->screen, &x, &y);
SDL_Rect *r = &im->screen->rect;
bool outside = x < r->x || x >= r->x + r->w
|| y < r->y || y >= r->y + r->h;
if (outside) {
screen_resize_to_fit(im->screen);
return;
@ -547,19 +677,52 @@ input_manager_process_mouse_button(struct input_manager *im,
}
struct control_msg msg;
if (convert_mouse_button(event, im->screen, &msg)) {
if (!controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject mouse button event'");
if (!convert_mouse_button(event, im->screen, &msg)) {
return;
}
if (!controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject mouse button event'");
return;
}
// Pinch-to-zoom 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
// button is released, an additional "virtual finger" event is generated,
// having a position inverted through the center of the screen.
//
// In other words, the center of the rotation/scaling is the center of the
// screen.
#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL))
if ((down && !im->vfinger_down && CTRL_PRESSED)
|| (!down && im->vfinger_down)) {
struct point mouse = msg.inject_touch_event.position.point;
struct point vfinger = inverse_point(mouse, im->screen->frame_size);
enum android_motionevent_action action = down
? AMOTION_EVENT_ACTION_DOWN
: AMOTION_EVENT_ACTION_UP;
if (!simulate_virtual_finger(im, action, vfinger)) {
return;
}
im->vfinger_down = down;
}
}
static bool
convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
struct control_msg *to) {
// mouse_x and mouse_y are expressed in pixels relative to the window
int mouse_x;
int mouse_y;
SDL_GetMouseState(&mouse_x, &mouse_y);
struct position position = {
.screen_size = screen->frame_size,
.point = get_mouse_point(screen),
.point = screen_convert_window_to_frame_coords(screen,
mouse_x, mouse_y),
};
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;
@ -571,7 +734,7 @@ convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
return true;
}
void
static void
input_manager_process_mouse_wheel(struct input_manager *im,
const SDL_MouseWheelEvent *event) {
struct control_msg msg;
@ -581,3 +744,46 @@ input_manager_process_mouse_wheel(struct input_manager *im,
}
}
}
bool
input_manager_handle_event(struct input_manager *im, SDL_Event *event) {
switch (event->type) {
case SDL_TEXTINPUT:
if (!im->control) {
return true;
}
input_manager_process_text_input(im, &event->text);
return true;
case SDL_KEYDOWN:
case SDL_KEYUP:
// some key events do not interact with the device, so process the
// event even if control is disabled
input_manager_process_key(im, &event->key);
return true;
case SDL_MOUSEMOTION:
if (!im->control) {
break;
}
input_manager_process_mouse_motion(im, &event->motion);
return true;
case SDL_MOUSEWHEEL:
if (!im->control) {
break;
}
input_manager_process_mouse_wheel(im, &event->wheel);
return true;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
// some mouse events do not interact with the device, so process
// the event even if control is disabled
input_manager_process_mouse_button(im, &event->button);
return true;
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
input_manager_process_touch(im, &event->tfinger);
return true;
}
return false;
}

View File

@ -1,46 +1,47 @@
#ifndef INPUTMANAGER_H
#define INPUTMANAGER_H
#include "common.h"
#include <stdbool.h>
#include "config.h"
#include "common.h"
#include <SDL2/SDL.h>
#include "controller.h"
#include "fps_counter.h"
#include "video_buffer.h"
#include "scrcpy.h"
#include "screen.h"
#include "video_buffer.h"
struct input_manager {
struct controller *controller;
struct video_buffer *video_buffer;
struct fps_counter *fps_counter;
struct screen *screen;
// SDL reports repeated events as a boolean, but Android expects the actual
// number of repetitions. This variable keeps track of the count.
unsigned repeat;
bool control;
bool forward_key_repeat;
bool prefer_text;
bool forward_all_clicks;
bool legacy_paste;
struct {
unsigned data[SC_MAX_SHORTCUT_MODS];
unsigned count;
} sdl_shortcut_mods;
bool vfinger_down;
};
void
input_manager_process_text_input(struct input_manager *im,
const SDL_TextInputEvent *event);
input_manager_init(struct input_manager *im,
const struct scrcpy_options *options);
void
input_manager_process_key(struct input_manager *im,
const SDL_KeyboardEvent *event,
bool control);
void
input_manager_process_mouse_motion(struct input_manager *im,
const SDL_MouseMotionEvent *event);
void
input_manager_process_touch(struct input_manager *im,
const SDL_TouchFingerEvent *event);
void
input_manager_process_mouse_button(struct input_manager *im,
const SDL_MouseButtonEvent *event,
bool control);
void
input_manager_process_mouse_wheel(struct input_manager *im,
const SDL_MouseWheelEvent *event);
bool
input_manager_handle_event(struct input_manager *im, SDL_Event *event);
#endif

View File

@ -1,14 +1,15 @@
#include "scrcpy.h"
#include "common.h"
#include <assert.h>
#include <stdbool.h>
#include <unistd.h>
#include <libavformat/avformat.h>
#define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem
#include <SDL2/SDL.h>
#include "config.h"
#include "cli.h"
#include "compat.h"
#include "util/log.h"
static void
@ -29,6 +30,24 @@ print_version(void) {
LIBAVUTIL_VERSION_MICRO);
}
static SDL_LogPriority
convert_log_level_to_sdl(enum sc_log_level level) {
switch (level) {
case SC_LOG_LEVEL_DEBUG:
return SDL_LOG_PRIORITY_DEBUG;
case SC_LOG_LEVEL_INFO:
return SDL_LOG_PRIORITY_INFO;
case SC_LOG_LEVEL_WARN:
return SDL_LOG_PRIORITY_WARN;
case SC_LOG_LEVEL_ERROR:
return SDL_LOG_PRIORITY_ERROR;
default:
assert(!"unexpected log level");
return SDL_LOG_PRIORITY_INFO;
}
}
int
main(int argc, char *argv[]) {
#ifdef __WINDOWS__
@ -38,20 +57,23 @@ main(int argc, char *argv[]) {
setbuf(stderr, NULL);
#endif
#ifndef NDEBUG
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG);
#endif
struct scrcpy_cli_args args = {
.opts = SCRCPY_OPTIONS_DEFAULT,
.help = false,
.version = false,
};
#ifndef NDEBUG
args.opts.log_level = SC_LOG_LEVEL_DEBUG;
#endif
if (!scrcpy_parse_args(&args, argc, argv)) {
return 1;
}
SDL_LogPriority sdl_log = convert_log_level_to_sdl(args.opts.log_level);
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log);
if (args.help) {
scrcpy_print_usage(argv[0]);
return 0;
@ -76,11 +98,5 @@ main(int argc, char *argv[]) {
avformat_network_deinit(); // ignore failure
#if defined (__WINDOWS__) && ! defined (WINDOWS_NOCONSOLE)
if (res != 0) {
fprintf(stderr, "Press any key to continue...\n");
getchar();
}
#endif
return res;
}

56
app/src/opengl.c Normal file
View File

@ -0,0 +1,56 @@
#include "opengl.h"
#include <assert.h>
#include <stdio.h>
#include "SDL2/SDL.h"
void
sc_opengl_init(struct sc_opengl *gl) {
gl->GetString = SDL_GL_GetProcAddress("glGetString");
assert(gl->GetString);
gl->TexParameterf = SDL_GL_GetProcAddress("glTexParameterf");
assert(gl->TexParameterf);
gl->TexParameteri = SDL_GL_GetProcAddress("glTexParameteri");
assert(gl->TexParameteri);
// optional
gl->GenerateMipmap = SDL_GL_GetProcAddress("glGenerateMipmap");
const char *version = (const char *) gl->GetString(GL_VERSION);
assert(version);
gl->version = version;
#define OPENGL_ES_PREFIX "OpenGL ES "
/* starts with "OpenGL ES " */
gl->is_opengles = !strncmp(gl->version, OPENGL_ES_PREFIX,
sizeof(OPENGL_ES_PREFIX) - 1);
if (gl->is_opengles) {
/* skip the prefix */
version += sizeof(PREFIX) - 1;
}
int r = sscanf(version, "%d.%d", &gl->version_major, &gl->version_minor);
if (r != 2) {
// failed to parse the version
gl->version_major = 0;
gl->version_minor = 0;
}
}
bool
sc_opengl_version_at_least(struct sc_opengl *gl,
int minver_major, int minver_minor,
int minver_es_major, int minver_es_minor)
{
if (gl->is_opengles) {
return gl->version_major > minver_es_major
|| (gl->version_major == minver_es_major
&& gl->version_minor >= minver_es_minor);
}
return gl->version_major > minver_major
|| (gl->version_major == minver_major
&& gl->version_minor >= minver_minor);
}

36
app/src/opengl.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef SC_OPENGL_H
#define SC_OPENGL_H
#include "common.h"
#include <stdbool.h>
#include <SDL2/SDL_opengl.h>
struct sc_opengl {
const char *version;
bool is_opengles;
int version_major;
int version_minor;
const GLubyte *
(*GetString)(GLenum name);
void
(*TexParameterf)(GLenum target, GLenum pname, GLfloat param);
void
(*TexParameteri)(GLenum target, GLenum pname, GLint param);
void
(*GenerateMipmap)(GLenum target);
};
void
sc_opengl_init(struct sc_opengl *gl);
bool
sc_opengl_version_at_least(struct sc_opengl *gl,
int minver_major, int minver_minor,
int minver_es_major, int minver_es_minor);
#endif

View File

@ -3,14 +3,13 @@
#include <assert.h>
#include <SDL2/SDL_clipboard.h>
#include "config.h"
#include "device_msg.h"
#include "util/lock.h"
#include "util/log.h"
bool
receiver_init(struct receiver *receiver, socket_t control_socket) {
if (!(receiver->mutex = SDL_CreateMutex())) {
bool ok = sc_mutex_init(&receiver->mutex);
if (!ok) {
return false;
}
receiver->control_socket = control_socket;
@ -19,16 +18,25 @@ receiver_init(struct receiver *receiver, socket_t control_socket) {
void
receiver_destroy(struct receiver *receiver) {
SDL_DestroyMutex(receiver->mutex);
sc_mutex_destroy(&receiver->mutex);
}
static void
process_msg(struct device_msg *msg) {
switch (msg->type) {
case DEVICE_MSG_TYPE_CLIPBOARD:
case DEVICE_MSG_TYPE_CLIPBOARD: {
char *current = SDL_GetClipboardText();
bool same = current && !strcmp(current, msg->clipboard.text);
SDL_free(current);
if (same) {
LOGD("Computer clipboard unchanged");
return;
}
LOGI("Device clipboard copied");
SDL_SetClipboardText(msg->clipboard.text);
break;
}
}
}
@ -60,28 +68,29 @@ static int
run_receiver(void *data) {
struct receiver *receiver = data;
unsigned char buf[DEVICE_MSG_SERIALIZED_MAX_SIZE];
static unsigned char buf[DEVICE_MSG_MAX_SIZE];
size_t head = 0;
for (;;) {
assert(head < DEVICE_MSG_SERIALIZED_MAX_SIZE);
ssize_t r = net_recv(receiver->control_socket, buf,
DEVICE_MSG_SERIALIZED_MAX_SIZE - head);
assert(head < DEVICE_MSG_MAX_SIZE);
ssize_t r = net_recv(receiver->control_socket, buf + head,
DEVICE_MSG_MAX_SIZE - head);
if (r <= 0) {
LOGD("Receiver stopped");
break;
}
ssize_t consumed = process_msgs(buf, r);
head += r;
ssize_t consumed = process_msgs(buf, head);
if (consumed == -1) {
// an error occurred
break;
}
if (consumed) {
head -= consumed;
// shift the remaining data in the buffer
memmove(buf, &buf[consumed], r - consumed);
head = r - consumed;
memmove(buf, &buf[consumed], head);
}
}
@ -92,8 +101,9 @@ bool
receiver_start(struct receiver *receiver) {
LOGD("Starting receiver thread");
receiver->thread = SDL_CreateThread(run_receiver, "receiver", receiver);
if (!receiver->thread) {
bool ok = sc_thread_create(&receiver->thread, run_receiver, "receiver",
receiver);
if (!ok) {
LOGC("Could not start receiver thread");
return false;
}
@ -103,5 +113,5 @@ receiver_start(struct receiver *receiver) {
void
receiver_join(struct receiver *receiver) {
SDL_WaitThread(receiver->thread, NULL);
sc_thread_join(&receiver->thread, NULL);
}

View File

@ -1,19 +1,19 @@
#ifndef RECEIVER_H
#define RECEIVER_H
#include <stdbool.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "common.h"
#include <stdbool.h>
#include "config.h"
#include "util/net.h"
#include "util/thread.h"
// receive events from the device
// managed by the controller
struct receiver {
socket_t control_socket;
SDL_Thread *thread;
SDL_mutex *mutex;
sc_thread thread;
sc_mutex mutex;
};
bool

View File

@ -3,9 +3,6 @@
#include <assert.h>
#include <libavutil/time.h>
#include "config.h"
#include "compat.h"
#include "util/lock.h"
#include "util/log.h"
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
@ -29,7 +26,7 @@ find_muxer(const char *name) {
static struct record_packet *
record_packet_new(const AVPacket *packet) {
struct record_packet *rec = SDL_malloc(sizeof(*rec));
struct record_packet *rec = malloc(sizeof(*rec));
if (!rec) {
return NULL;
}
@ -39,7 +36,7 @@ record_packet_new(const AVPacket *packet) {
av_init_packet(&rec->packet);
if (av_packet_ref(&rec->packet, packet)) {
SDL_free(rec);
free(rec);
return NULL;
}
return rec;
@ -48,7 +45,7 @@ record_packet_new(const AVPacket *packet) {
static void
record_packet_delete(struct record_packet *rec) {
av_packet_unref(&rec->packet);
SDL_free(rec);
free(rec);
}
static void
@ -63,26 +60,26 @@ recorder_queue_clear(struct recorder_queue *queue) {
bool
recorder_init(struct recorder *recorder,
const char *filename,
enum recorder_format format,
enum sc_record_format format,
struct size declared_frame_size) {
recorder->filename = SDL_strdup(filename);
recorder->filename = strdup(filename);
if (!recorder->filename) {
LOGE("Could not strdup filename");
return false;
}
recorder->mutex = SDL_CreateMutex();
if (!recorder->mutex) {
bool ok = sc_mutex_init(&recorder->mutex);
if (!ok) {
LOGC("Could not create mutex");
SDL_free(recorder->filename);
free(recorder->filename);
return false;
}
recorder->queue_cond = SDL_CreateCond();
if (!recorder->queue_cond) {
ok = sc_cond_init(&recorder->queue_cond);
if (!ok) {
LOGC("Could not create cond");
SDL_DestroyMutex(recorder->mutex);
SDL_free(recorder->filename);
sc_mutex_destroy(&recorder->mutex);
free(recorder->filename);
return false;
}
@ -99,16 +96,16 @@ recorder_init(struct recorder *recorder,
void
recorder_destroy(struct recorder *recorder) {
SDL_DestroyCond(recorder->queue_cond);
SDL_DestroyMutex(recorder->mutex);
SDL_free(recorder->filename);
sc_cond_destroy(&recorder->queue_cond);
sc_mutex_destroy(&recorder->mutex);
free(recorder->filename);
}
static const char *
recorder_get_format_name(enum recorder_format format) {
recorder_get_format_name(enum sc_record_format format) {
switch (format) {
case RECORDER_FORMAT_MP4: return "mp4";
case RECORDER_FORMAT_MKV: return "matroska";
case SC_RECORD_FORMAT_MP4: return "mp4";
case SC_RECORD_FORMAT_MKV: return "matroska";
default: return NULL;
}
}
@ -260,17 +257,17 @@ run_recorder(void *data) {
struct recorder *recorder = data;
for (;;) {
mutex_lock(recorder->mutex);
sc_mutex_lock(&recorder->mutex);
while (!recorder->stopped && queue_is_empty(&recorder->queue)) {
cond_wait(recorder->queue_cond, recorder->mutex);
sc_cond_wait(&recorder->queue_cond, &recorder->mutex);
}
// if stopped is set, continue to process the remaining events (to
// finish the recording) before actually stopping
if (recorder->stopped && queue_is_empty(&recorder->queue)) {
mutex_unlock(recorder->mutex);
sc_mutex_unlock(&recorder->mutex);
struct record_packet *last = recorder->previous;
if (last) {
// assign an arbitrary duration to the last packet
@ -290,7 +287,7 @@ run_recorder(void *data) {
struct record_packet *rec;
queue_take(&recorder->queue, next, &rec);
mutex_unlock(recorder->mutex);
sc_mutex_unlock(&recorder->mutex);
// recorder->previous is only written from this thread, no need to lock
struct record_packet *previous = recorder->previous;
@ -313,11 +310,11 @@ run_recorder(void *data) {
if (!ok) {
LOGE("Could not record packet");
mutex_lock(recorder->mutex);
sc_mutex_lock(&recorder->mutex);
recorder->failed = true;
// discard pending packets
recorder_queue_clear(&recorder->queue);
mutex_unlock(recorder->mutex);
sc_mutex_unlock(&recorder->mutex);
break;
}
@ -332,8 +329,9 @@ bool
recorder_start(struct recorder *recorder) {
LOGD("Starting recorder thread");
recorder->thread = SDL_CreateThread(run_recorder, "recorder", recorder);
if (!recorder->thread) {
bool ok = sc_thread_create(&recorder->thread, run_recorder, "recorder",
recorder);
if (!ok) {
LOGC("Could not start recorder thread");
return false;
}
@ -343,36 +341,38 @@ recorder_start(struct recorder *recorder) {
void
recorder_stop(struct recorder *recorder) {
mutex_lock(recorder->mutex);
sc_mutex_lock(&recorder->mutex);
recorder->stopped = true;
cond_signal(recorder->queue_cond);
mutex_unlock(recorder->mutex);
sc_cond_signal(&recorder->queue_cond);
sc_mutex_unlock(&recorder->mutex);
}
void
recorder_join(struct recorder *recorder) {
SDL_WaitThread(recorder->thread, NULL);
sc_thread_join(&recorder->thread, NULL);
}
bool
recorder_push(struct recorder *recorder, const AVPacket *packet) {
mutex_lock(recorder->mutex);
sc_mutex_lock(&recorder->mutex);
assert(!recorder->stopped);
if (recorder->failed) {
// reject any new packet (this will stop the stream)
sc_mutex_unlock(&recorder->mutex);
return false;
}
struct record_packet *rec = record_packet_new(packet);
if (!rec) {
LOGC("Could not allocate record packet");
sc_mutex_unlock(&recorder->mutex);
return false;
}
queue_push(&recorder->queue, next, rec);
cond_signal(recorder->queue_cond);
sc_cond_signal(&recorder->queue_cond);
mutex_unlock(recorder->mutex);
sc_mutex_unlock(&recorder->mutex);
return true;
}

View File

@ -1,20 +1,15 @@
#ifndef RECORDER_H
#define RECORDER_H
#include "common.h"
#include <stdbool.h>
#include <libavformat/avformat.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "config.h"
#include "common.h"
#include "coords.h"
#include "scrcpy.h"
#include "util/queue.h"
enum recorder_format {
RECORDER_FORMAT_AUTO,
RECORDER_FORMAT_MP4,
RECORDER_FORMAT_MKV,
};
#include "util/thread.h"
struct record_packet {
AVPacket packet;
@ -25,14 +20,14 @@ struct recorder_queue QUEUE(struct record_packet);
struct recorder {
char *filename;
enum recorder_format format;
enum sc_record_format format;
AVFormatContext *ctx;
struct size declared_frame_size;
bool header_written;
SDL_Thread *thread;
SDL_mutex *mutex;
SDL_cond *queue_cond;
sc_thread thread;
sc_mutex mutex;
sc_cond queue_cond;
bool stopped; // set on recorder_stop() by the stream reader
bool failed; // set on packet write failure
struct recorder_queue queue;
@ -46,7 +41,7 @@ struct recorder {
bool
recorder_init(struct recorder *recorder, const char *filename,
enum recorder_format format, struct size declared_frame_size);
enum sc_record_format format, struct size declared_frame_size);
void
recorder_destroy(struct recorder *recorder);

View File

@ -7,10 +7,12 @@
#include <sys/time.h>
#include <SDL2/SDL.h>
#include "config.h"
#include "command.h"
#include "common.h"
#include "compat.h"
#ifdef _WIN32
// not needed here, but winsock2.h must never be included AFTER windows.h
# include <winsock2.h>
# include <windows.h>
#endif
#include "controller.h"
#include "decoder.h"
#include "device.h"
@ -24,12 +26,11 @@
#include "stream.h"
#include "tiny_xpm.h"
#include "video_buffer.h"
#include "util/lock.h"
#include "util/log.h"
#include "util/net.h"
static struct server server = SERVER_INITIALIZER;
static struct screen screen = SCREEN_INITIALIZER;
static struct server server;
static struct screen screen;
static struct fps_counter fps_counter;
static struct video_buffer video_buffer;
static struct stream stream;
@ -41,13 +42,34 @@ static struct file_handler file_handler;
static struct input_manager input_manager = {
.controller = &controller,
.video_buffer = &video_buffer,
.fps_counter = &fps_counter,
.screen = &screen,
.prefer_text = false, // initialized later
.repeat = 0,
// initialized later
.prefer_text = false,
.sdl_shortcut_mods = {
.data = {0},
.count = 0,
},
};
#ifdef _WIN32
BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
if (ctrl_type == CTRL_C_EVENT) {
SDL_Event event;
event.type = SDL_QUIT;
SDL_PushEvent(&event);
return TRUE;
}
return FALSE;
}
#endif // _WIN32
// init SDL and set appropriate hints
static bool
sdl_init_and_configure(bool display) {
sdl_init_and_configure(bool display, const char *render_driver,
bool disable_screensaver) {
uint32_t flags = display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS;
if (SDL_Init(flags)) {
LOGC("Could not initialize SDL: %s", SDL_GetError());
@ -56,13 +78,25 @@ sdl_init_and_configure(bool display) {
atexit(SDL_Quit);
#ifdef _WIN32
// Clean up properly on Ctrl+C on Windows
bool ok = SetConsoleCtrlHandler(windows_ctrl_handler, TRUE);
if (!ok) {
LOGW("Could not set Ctrl+C handler");
}
#endif // _WIN32
if (!display) {
return true;
}
// Use the best available scale quality
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2")) {
LOGW("Could not enable bilinear filtering");
if (render_driver && !SDL_SetHint(SDL_HINT_RENDER_DRIVER, render_driver)) {
LOGW("Could not set render driver");
}
// Linear filtering
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) {
LOGW("Could not enable linear filtering");
}
#ifdef SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH
@ -84,8 +118,13 @@ sdl_init_and_configure(bool display) {
LOGW("Could not disable minimize on focus loss");
}
// Do not disable the screensaver when scrcpy is running
SDL_EnableScreenSaver();
if (disable_screensaver) {
LOGD("Screensaver disabled");
SDL_DisableScreenSaver();
} else {
LOGD("Screensaver enabled");
SDL_EnableScreenSaver();
}
return true;
}
@ -106,8 +145,9 @@ event_watcher(void *data, SDL_Event *event) {
(void) data;
if (event->type == SDL_WINDOWEVENT
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
// called from another thread, not very safe, but it's a workaround!
screen_render(&screen);
// In practice, it seems to always be called from the same thread in
// that specific case. Anyway, it's just a workaround.
screen_render(&screen, true);
}
return 0;
}
@ -126,7 +166,7 @@ enum event_result {
};
static enum event_result
handle_event(SDL_Event *event, bool control) {
handle_event(SDL_Event *event, const struct scrcpy_options *options) {
switch (event->type) {
case EVENT_STREAM_STOPPED:
LOGD("Video stream stopped");
@ -134,83 +174,50 @@ handle_event(SDL_Event *event, bool control) {
case SDL_QUIT:
LOGD("User requested to quit");
return EVENT_RESULT_STOPPED_BY_USER;
case EVENT_NEW_FRAME:
if (!screen.has_frame) {
screen.has_frame = true;
// this is the very first frame, show the window
screen_show_window(&screen);
}
if (!screen_update_frame(&screen, &video_buffer)) {
return EVENT_RESULT_CONTINUE;
}
break;
case SDL_WINDOWEVENT:
screen_handle_window_event(&screen, &event->window);
break;
case SDL_TEXTINPUT:
if (!control) {
break;
}
input_manager_process_text_input(&input_manager, &event->text);
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
// some key events do not interact with the device, so process the
// event even if control is disabled
input_manager_process_key(&input_manager, &event->key, control);
break;
case SDL_MOUSEMOTION:
if (!control) {
break;
}
input_manager_process_mouse_motion(&input_manager, &event->motion);
break;
case SDL_MOUSEWHEEL:
if (!control) {
break;
}
input_manager_process_mouse_wheel(&input_manager, &event->wheel);
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
// some mouse events do not interact with the device, so process
// the event even if control is disabled
input_manager_process_mouse_button(&input_manager, &event->button,
control);
break;
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
input_manager_process_touch(&input_manager, &event->tfinger);
break;
case SDL_DROPFILE: {
if (!control) {
if (!options->control) {
break;
}
char *file = strdup(event->drop.file);
SDL_free(event->drop.file);
if (!file) {
LOGW("Could not strdup drop filename\n");
break;
}
file_handler_action_t action;
if (is_apk(event->drop.file)) {
if (is_apk(file)) {
action = ACTION_INSTALL_APK;
} else {
action = ACTION_PUSH_FILE;
}
file_handler_request(&file_handler, action, event->drop.file);
file_handler_request(&file_handler, action, file);
break;
}
}
bool consumed = screen_handle_event(&screen, event);
if (consumed) {
goto end;
}
consumed = input_manager_handle_event(&input_manager, event);
(void) consumed;
end:
return EVENT_RESULT_CONTINUE;
}
static bool
event_loop(bool display, bool control) {
(void) display;
event_loop(const struct scrcpy_options *options) {
#ifdef CONTINUOUS_RESIZING_WORKAROUND
if (display) {
if (options->display) {
SDL_AddEventWatch(event_watcher, NULL);
}
#endif
SDL_Event event;
while (SDL_WaitEvent(&event)) {
enum event_result result = handle_event(&event, control);
enum event_result result = handle_event(&event, options);
switch (result) {
case EVENT_RESULT_STOPPED_BY_USER:
return true;
@ -224,21 +231,6 @@ event_loop(bool display, bool control) {
return false;
}
static process_t
set_show_touches_enabled(const char *serial, bool enabled) {
const char *value = enabled ? "1" : "0";
const char *const adb_cmd[] = {
"shell", "settings", "put", "system", "show_touches", value
};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
static void
wait_show_touches(process_t process) {
// reap the process, ignore the result
process_check_success(process, "show_touches");
}
static SDL_LogPriority
sdl_priority_from_av_level(int level) {
switch (level) {
@ -263,7 +255,7 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
if (priority == 0) {
return;
}
char *local_fmt = SDL_malloc(strlen(fmt) + 10);
char *local_fmt = malloc(strlen(fmt) + 10);
if (!local_fmt) {
LOGC("Could not allocate string");
return;
@ -272,34 +264,26 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
strcpy(local_fmt, "[FFmpeg] ");
strcpy(local_fmt + 9, fmt);
SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl);
SDL_free(local_fmt);
free(local_fmt);
}
static void
video_buffer_on_frame_available(struct video_buffer *vb, void *userdata) {
(void) vb;
(void) userdata;
screen_on_frame_available(&screen);
}
bool
scrcpy(const struct scrcpy_options *options) {
bool record = !!options->record_filename;
struct server_params params = {
.crop = options->crop,
.port_range = options->port_range,
.max_size = options->max_size,
.bit_rate = options->bit_rate,
.max_fps = options->max_fps,
.control = options->control,
};
if (!server_start(&server, options->serial, &params)) {
if (!server_init(&server)) {
return false;
}
process_t proc_show_touches = PROCESS_NONE;
bool show_touches_waited;
if (options->show_touches) {
LOGI("Enable show_touches");
proc_show_touches = set_show_touches_enabled(options->serial, true);
show_touches_waited = false;
}
bool ret = false;
bool server_started = false;
bool fps_counter_initialized = false;
bool video_buffer_initialized = false;
bool file_handler_initialized = false;
@ -308,7 +292,31 @@ scrcpy(const struct scrcpy_options *options) {
bool controller_initialized = false;
bool controller_started = false;
if (!sdl_init_and_configure(options->display)) {
bool record = !!options->record_filename;
struct server_params params = {
.log_level = options->log_level,
.crop = options->crop,
.port_range = options->port_range,
.max_size = options->max_size,
.bit_rate = options->bit_rate,
.max_fps = options->max_fps,
.lock_video_orientation = options->lock_video_orientation,
.control = options->control,
.display_id = options->display_id,
.show_touches = options->show_touches,
.stay_awake = options->stay_awake,
.codec_options = options->codec_options,
.encoder_name = options->encoder_name,
.force_adb_forward = options->force_adb_forward,
};
if (!server_start(&server, options->serial, &params)) {
goto end;
}
server_started = true;
if (!sdl_init_and_configure(options->display, options->render_driver,
options->disable_screensaver)) {
goto end;
}
@ -333,8 +341,12 @@ scrcpy(const struct scrcpy_options *options) {
}
fps_counter_initialized = true;
if (!video_buffer_init(&video_buffer, &fps_counter,
options->render_expired_frames)) {
static const struct video_buffer_callbacks video_buffer_cbs = {
.on_frame_available = video_buffer_on_frame_available,
};
if (!video_buffer_init(&video_buffer, options->render_expired_frames,
&video_buffer_cbs, NULL)) {
goto end;
}
video_buffer_initialized = true;
@ -367,13 +379,6 @@ scrcpy(const struct scrcpy_options *options) {
stream_init(&stream, server.video_socket, dec, rec);
// now we consumed the header values, the socket receives the video stream
// start the stream
if (!stream_start(&stream)) {
goto end;
}
stream_started = true;
if (options->display) {
if (options->control) {
if (!controller_init(&controller, server.control_socket)) {
@ -390,11 +395,14 @@ scrcpy(const struct scrcpy_options *options) {
const char *window_title =
options->window_title ? options->window_title : device_name;
screen_init(&screen, &video_buffer, &fps_counter);
if (!screen_init_rendering(&screen, window_title, frame_size,
options->always_on_top, options->window_x,
options->window_y, options->window_width,
options->window_height,
options->window_borderless)) {
options->window_borderless,
options->rotation, options->mipmaps)) {
goto end;
}
@ -413,14 +421,16 @@ scrcpy(const struct scrcpy_options *options) {
}
}
if (options->show_touches) {
wait_show_touches(proc_show_touches);
show_touches_waited = true;
// now we consumed the header values, the socket receives the video stream
// start the stream
if (!stream_start(&stream)) {
goto end;
}
stream_started = true;
input_manager.prefer_text = options->prefer_text;
input_manager_init(&input_manager, options);
ret = event_loop(options->display, options->control);
ret = event_loop(options);
LOGD("quit...");
screen_destroy(&screen);
@ -441,8 +451,10 @@ end:
fps_counter_interrupt(&fps_counter);
}
// shutdown the sockets and kill the server
server_stop(&server);
if (server_started) {
// shutdown the sockets and kill the server
server_stop(&server);
}
// now that the sockets are shutdown, the stream and controller are
// interrupted, we can join them
@ -474,16 +486,6 @@ end:
fps_counter_destroy(&fps_counter);
}
if (options->show_touches) {
if (!show_touches_waited) {
// wait the process which enabled "show touches"
wait_show_touches(proc_show_touches);
}
LOGI("Disable show_touches");
proc_show_touches = set_show_touches_enabled(options->serial, false);
wait_show_touches(proc_show_touches);
}
server_destroy(&server);
return ret;

View File

@ -1,13 +1,47 @@
#ifndef SCRCPY_H
#define SCRCPY_H
#include "common.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "config.h"
#include "common.h"
#include "input_manager.h"
#include "recorder.h"
enum sc_log_level {
SC_LOG_LEVEL_DEBUG,
SC_LOG_LEVEL_INFO,
SC_LOG_LEVEL_WARN,
SC_LOG_LEVEL_ERROR,
};
enum sc_record_format {
SC_RECORD_FORMAT_AUTO,
SC_RECORD_FORMAT_MP4,
SC_RECORD_FORMAT_MKV,
};
#define SC_MAX_SHORTCUT_MODS 8
enum sc_shortcut_mod {
SC_MOD_LCTRL = 1 << 0,
SC_MOD_RCTRL = 1 << 1,
SC_MOD_LALT = 1 << 2,
SC_MOD_RALT = 1 << 3,
SC_MOD_LSUPER = 1 << 4,
SC_MOD_RSUPER = 1 << 5,
};
struct sc_shortcut_mods {
unsigned data[SC_MAX_SHORTCUT_MODS];
unsigned count;
};
struct sc_port_range {
uint16_t first;
uint16_t last;
};
#define SC_WINDOW_POSITION_UNDEFINED (-0x8000)
struct scrcpy_options {
const char *serial;
@ -15,15 +49,23 @@ struct scrcpy_options {
const char *record_filename;
const char *window_title;
const char *push_target;
enum recorder_format record_format;
struct port_range port_range;
const char *render_driver;
const char *codec_options;
const char *encoder_name;
enum sc_log_level log_level;
enum sc_record_format record_format;
struct sc_port_range port_range;
struct sc_shortcut_mods shortcut_mods;
uint16_t max_size;
uint32_t bit_rate;
uint16_t max_fps;
int16_t window_x;
int16_t window_y;
int8_t lock_video_orientation;
uint8_t rotation;
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
uint16_t window_width;
uint16_t window_height;
uint32_t display_id;
bool show_touches;
bool fullscreen;
bool always_on_top;
@ -33,6 +75,13 @@ struct scrcpy_options {
bool render_expired_frames;
bool prefer_text;
bool window_borderless;
bool mipmaps;
bool stay_awake;
bool force_adb_forward;
bool disable_screensaver;
bool forward_key_repeat;
bool forward_all_clicks;
bool legacy_paste;
};
#define SCRCPY_OPTIONS_DEFAULT { \
@ -41,18 +90,29 @@ struct scrcpy_options {
.record_filename = NULL, \
.window_title = NULL, \
.push_target = NULL, \
.record_format = RECORDER_FORMAT_AUTO, \
.render_driver = NULL, \
.codec_options = NULL, \
.encoder_name = NULL, \
.log_level = SC_LOG_LEVEL_INFO, \
.record_format = SC_RECORD_FORMAT_AUTO, \
.port_range = { \
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \
.last = DEFAULT_LOCAL_PORT_RANGE_LAST, \
}, \
.shortcut_mods = { \
.data = {SC_MOD_LALT, SC_MOD_LSUPER}, \
.count = 2, \
}, \
.max_size = DEFAULT_MAX_SIZE, \
.bit_rate = DEFAULT_BIT_RATE, \
.max_fps = 0, \
.window_x = -1, \
.window_y = -1, \
.lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \
.rotation = 0, \
.window_x = SC_WINDOW_POSITION_UNDEFINED, \
.window_y = SC_WINDOW_POSITION_UNDEFINED, \
.window_width = 0, \
.window_height = 0, \
.display_id = 0, \
.show_touches = false, \
.fullscreen = false, \
.always_on_top = false, \
@ -62,6 +122,13 @@ struct scrcpy_options {
.render_expired_frames = false, \
.prefer_text = false, \
.window_borderless = false, \
.mipmaps = true, \
.stay_awake = false, \
.force_adb_forward = false, \
.disable_screensaver = false, \
.forward_key_repeat = true, \
.forward_all_clicks = false, \
.legacy_paste = false, \
}
bool

View File

@ -4,23 +4,34 @@
#include <string.h>
#include <SDL2/SDL.h>
#include "config.h"
#include "common.h"
#include "compat.h"
#include "events.h"
#include "icon.xpm"
#include "scrcpy.h"
#include "tiny_xpm.h"
#include "video_buffer.h"
#include "util/lock.h"
#include "util/log.h"
#define DISPLAY_MARGINS 96
static inline struct size
get_rotated_size(struct size size, int rotation) {
struct size rotated_size;
if (rotation & 1) {
rotated_size.width = size.height;
rotated_size.height = size.width;
} else {
rotated_size.width = size.width;
rotated_size.height = size.height;
}
return rotated_size;
}
// get the window size in a struct size
static struct size
get_window_size(SDL_Window *window) {
get_window_size(const struct screen *screen) {
int width;
int height;
SDL_GetWindowSize(window, &width, &height);
SDL_GetWindowSize(screen->window, &width, &height);
struct size size;
size.width = width;
@ -28,31 +39,12 @@ get_window_size(SDL_Window *window) {
return size;
}
// get the windowed window size
static struct size
get_windowed_window_size(const struct screen *screen) {
if (screen->fullscreen || screen->maximized) {
return screen->windowed_window_size;
}
return get_window_size(screen->window);
}
// apply the windowed window size if fullscreen and maximized are disabled
static void
apply_windowed_size(struct screen *screen) {
if (!screen->fullscreen && !screen->maximized) {
SDL_SetWindowSize(screen->window, screen->windowed_window_size.width,
screen->windowed_window_size.height);
}
}
// set the window size to be applied when fullscreen is disabled
static void
set_window_size(struct screen *screen, struct size new_size) {
// setting the window size during fullscreen is implementation defined,
// so apply the resize only after fullscreen is disabled
screen->windowed_window_size = new_size;
apply_windowed_size(screen);
assert(!screen->fullscreen);
assert(!screen->maximized);
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
}
// get the preferred display bounds (i.e. the screen bounds with some margins)
@ -74,102 +66,182 @@ get_preferred_display_bounds(struct size *bounds) {
return true;
}
static bool
is_optimal_size(struct size current_size, struct size content_size) {
// The size is optimal if we can recompute one dimension of the current
// size from the other
return current_size.height == current_size.width * content_size.height
/ content_size.width
|| current_size.width == current_size.height * content_size.width
/ content_size.height;
}
// return the optimal size of the window, with the following constraints:
// - it attempts to keep at least one dimension of the current_size (i.e. it
// crops the black borders)
// - it keeps the aspect ratio
// - it scales down to make it fit in the display_size
static struct size
get_optimal_size(struct size current_size, struct size frame_size) {
if (frame_size.width == 0 || frame_size.height == 0) {
get_optimal_size(struct size current_size, struct size content_size) {
if (content_size.width == 0 || content_size.height == 0) {
// avoid division by 0
return current_size;
}
struct size display_size;
// 32 bits because we need to multiply two 16 bits values
uint32_t w;
uint32_t h;
struct size window_size;
struct size display_size;
if (!get_preferred_display_bounds(&display_size)) {
// could not get display bounds, do not constraint the size
w = current_size.width;
h = current_size.height;
window_size.width = current_size.width;
window_size.height = current_size.height;
} else {
w = MIN(current_size.width, display_size.width);
h = MIN(current_size.height, display_size.height);
window_size.width = MIN(current_size.width, display_size.width);
window_size.height = MIN(current_size.height, display_size.height);
}
bool keep_width = frame_size.width * h > frame_size.height * w;
if (is_optimal_size(window_size, content_size)) {
return window_size;
}
bool keep_width = content_size.width * window_size.height
> content_size.height * window_size.width;
if (keep_width) {
// remove black borders on top and bottom
h = frame_size.height * w / frame_size.width;
window_size.height = content_size.height * window_size.width
/ content_size.width;
} else {
// remove black borders on left and right (or none at all if it already
// fits)
w = frame_size.width * h / frame_size.height;
window_size.width = content_size.width * window_size.height
/ content_size.height;
}
// w and h must fit into 16 bits
assert(w < 0x10000 && h < 0x10000);
return (struct size) {w, h};
return window_size;
}
// same as get_optimal_size(), but read the current size from the window
static inline struct size
get_optimal_window_size(const struct screen *screen, struct size frame_size) {
struct size windowed_size = get_windowed_window_size(screen);
return get_optimal_size(windowed_size, frame_size);
get_optimal_window_size(const struct screen *screen, struct size content_size) {
struct size window_size = get_window_size(screen);
return get_optimal_size(window_size, content_size);
}
// initially, there is no current size, so use the frame size as current size
// req_width and req_height, if not 0, are the sizes requested by the user
static inline struct size
get_initial_optimal_size(struct size frame_size, uint16_t req_width,
get_initial_optimal_size(struct size content_size, uint16_t req_width,
uint16_t req_height) {
struct size window_size;
if (!req_width && !req_height) {
window_size = get_optimal_size(frame_size, frame_size);
window_size = get_optimal_size(content_size, content_size);
} else {
if (req_width) {
window_size.width = req_width;
} else {
// compute from the requested height
window_size.width = (uint32_t) req_height * frame_size.width
/ frame_size.height;
window_size.width = (uint32_t) req_height * content_size.width
/ content_size.height;
}
if (req_height) {
window_size.height = req_height;
} else {
// compute from the requested width
window_size.height = (uint32_t) req_width * frame_size.height
/ frame_size.width;
window_size.height = (uint32_t) req_width * content_size.height
/ content_size.width;
}
}
return window_size;
}
static void
screen_update_content_rect(struct screen *screen) {
int dw;
int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
struct size content_size = screen->content_size;
// The drawable size is the window size * the HiDPI scale
struct size drawable_size = {dw, dh};
SDL_Rect *rect = &screen->rect;
if (is_optimal_size(drawable_size, content_size)) {
rect->x = 0;
rect->y = 0;
rect->w = drawable_size.width;
rect->h = drawable_size.height;
return;
}
bool keep_width = content_size.width * drawable_size.height
> content_size.height * drawable_size.width;
if (keep_width) {
rect->x = 0;
rect->w = drawable_size.width;
rect->h = drawable_size.width * content_size.height
/ content_size.width;
rect->y = (drawable_size.height - rect->h) / 2;
} else {
rect->y = 0;
rect->h = drawable_size.height;
rect->w = drawable_size.height * content_size.width
/ content_size.height;
rect->x = (drawable_size.width - rect->w) / 2;
}
}
void
screen_init(struct screen *screen) {
screen_init(struct screen *screen, struct video_buffer *vb,
struct fps_counter *fps_counter) {
*screen = (struct screen) SCREEN_INITIALIZER;
screen->vb = vb;
screen->fps_counter = fps_counter;
}
static inline SDL_Texture *
create_texture(SDL_Renderer *renderer, struct size frame_size) {
return SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
SDL_TEXTUREACCESS_STREAMING,
frame_size.width, frame_size.height);
create_texture(struct screen *screen) {
SDL_Renderer *renderer = screen->renderer;
struct size size = screen->frame_size;
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
SDL_TEXTUREACCESS_STREAMING,
size.width, size.height);
if (!texture) {
return NULL;
}
if (screen->mipmaps) {
struct sc_opengl *gl = &screen->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;
}
bool
screen_init_rendering(struct screen *screen, const char *window_title,
struct size frame_size, bool always_on_top,
int16_t window_x, int16_t window_y, uint16_t window_width,
uint16_t window_height, bool window_borderless) {
uint16_t window_height, bool window_borderless,
uint8_t rotation, bool mipmaps) {
screen->frame_size = frame_size;
screen->rotation = rotation;
if (rotation) {
LOGI("Initial display rotation set to %u", rotation);
}
struct size content_size = get_rotated_size(frame_size, screen->rotation);
screen->content_size = content_size;
struct size window_size =
get_initial_optimal_size(frame_size, window_width, window_height);
get_initial_optimal_size(content_size, window_width, window_height);
uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE;
#ifdef HIDPI_SUPPORT
window_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
@ -186,8 +258,10 @@ screen_init_rendering(struct screen *screen, const char *window_title,
window_flags |= SDL_WINDOW_BORDERLESS;
}
int x = window_x != -1 ? window_x : (int) SDL_WINDOWPOS_UNDEFINED;
int y = window_y != -1 ? window_y : (int) SDL_WINDOWPOS_UNDEFINED;
int x = window_x != SC_WINDOW_POSITION_UNDEFINED
? window_x : (int) SDL_WINDOWPOS_UNDEFINED;
int y = window_y != SC_WINDOW_POSITION_UNDEFINED
? window_y : (int) SDL_WINDOWPOS_UNDEFINED;
screen->window = SDL_CreateWindow(window_title, x, y,
window_size.width, window_size.height,
window_flags);
@ -200,15 +274,39 @@ screen_init_rendering(struct screen *screen, const char *window_title,
SDL_RENDERER_ACCELERATED);
if (!screen->renderer) {
LOGC("Could not create renderer: %s", SDL_GetError());
screen_destroy(screen);
SDL_DestroyWindow(screen->window);
return false;
}
if (SDL_RenderSetLogicalSize(screen->renderer, frame_size.width,
frame_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
screen_destroy(screen);
return false;
SDL_RendererInfo renderer_info;
int r = SDL_GetRendererInfo(screen->renderer, &renderer_info);
const char *renderer_name = r ? NULL : renderer_info.name;
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
// starts with "opengl"
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
if (use_opengl) {
struct sc_opengl *gl = &screen->gl;
sc_opengl_init(gl);
LOGI("OpenGL version: %s", gl->version);
if (mipmaps) {
bool supports_mipmaps =
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
2, 0 /* OpenGL ES 2.0+ */);
if (supports_mipmaps) {
LOGI("Trilinear filtering enabled");
screen->mipmaps = true;
} else {
LOGW("Trilinear filtering disabled "
"(OpenGL 3.0+ or ES 2.0+ required)");
}
} else {
LOGI("Trilinear filtering disabled");
}
} else if (mipmaps) {
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
}
SDL_Surface *icon = read_xpm(icon_xpm);
@ -221,14 +319,21 @@ screen_init_rendering(struct screen *screen, const char *window_title,
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width,
frame_size.height);
screen->texture = create_texture(screen->renderer, frame_size);
screen->texture = create_texture(screen);
if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError());
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
screen_destroy(screen);
return false;
}
screen->windowed_window_size = window_size;
// Reset the window size to trigger a SIZE_CHANGED event, to workaround
// HiDPI issues with some SDL renderers when several displays having
// different HiDPI scaling are connected
SDL_SetWindowSize(screen->window, window_size.width, window_size.height);
screen_update_content_rect(screen);
return true;
}
@ -243,43 +348,86 @@ screen_destroy(struct screen *screen) {
if (screen->texture) {
SDL_DestroyTexture(screen->texture);
}
if (screen->renderer) {
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
}
static void
resize_for_content(struct screen *screen, struct size old_content_size,
struct size new_content_size) {
struct size window_size = get_window_size(screen);
struct size target_size = {
.width = (uint32_t) window_size.width * new_content_size.width
/ old_content_size.width,
.height = (uint32_t) window_size.height * new_content_size.height
/ old_content_size.height,
};
target_size = get_optimal_size(target_size, new_content_size);
set_window_size(screen, target_size);
}
static void
set_content_size(struct screen *screen, struct size new_content_size) {
if (!screen->fullscreen && !screen->maximized) {
resize_for_content(screen, screen->content_size, new_content_size);
} else if (!screen->resize_pending) {
// Store the windowed size to be able to compute the optimal size once
// fullscreen and maximized are disabled
screen->windowed_content_size = screen->content_size;
screen->resize_pending = true;
}
if (screen->window) {
SDL_DestroyWindow(screen->window);
screen->content_size = new_content_size;
}
static void
apply_pending_resize(struct screen *screen) {
assert(!screen->fullscreen);
assert(!screen->maximized);
if (screen->resize_pending) {
resize_for_content(screen, screen->windowed_content_size,
screen->content_size);
screen->resize_pending = false;
}
}
void
screen_set_rotation(struct screen *screen, unsigned rotation) {
assert(rotation < 4);
if (rotation == screen->rotation) {
return;
}
struct size new_content_size =
get_rotated_size(screen->frame_size, rotation);
set_content_size(screen, new_content_size);
screen->rotation = rotation;
LOGI("Display rotation set to %u", rotation);
screen_render(screen, true);
}
// recreate the texture and resize the window if the frame size has changed
static bool
prepare_for_frame(struct screen *screen, struct size new_frame_size) {
if (screen->frame_size.width != new_frame_size.width
|| screen->frame_size.height != new_frame_size.height) {
if (SDL_RenderSetLogicalSize(screen->renderer, new_frame_size.width,
new_frame_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
return false;
}
// frame dimension changed, destroy texture
SDL_DestroyTexture(screen->texture);
struct size windowed_size = get_windowed_window_size(screen);
struct size target_size = {
(uint32_t) windowed_size.width * new_frame_size.width
/ screen->frame_size.width,
(uint32_t) windowed_size.height * new_frame_size.height
/ screen->frame_size.height,
};
target_size = get_optimal_size(target_size, new_frame_size);
set_window_size(screen, target_size);
screen->frame_size = new_frame_size;
struct size new_content_size =
get_rotated_size(new_frame_size, screen->rotation);
set_content_size(screen, new_content_size);
screen_update_content_rect(screen);
LOGI("New texture: %" PRIu16 "x%" PRIu16,
screen->frame_size.width, screen->frame_size.height);
screen->texture = create_texture(screen->renderer, new_frame_size);
screen->texture = create_texture(screen);
if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError());
return false;
@ -296,28 +444,64 @@ update_texture(struct screen *screen, const AVFrame *frame) {
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
if (screen->mipmaps) {
SDL_GL_BindTexture(screen->texture, NULL, NULL);
screen->gl.GenerateMipmap(GL_TEXTURE_2D);
SDL_GL_UnbindTexture(screen->texture);
}
}
bool
screen_update_frame(struct screen *screen, struct video_buffer *vb) {
mutex_lock(vb->mutex);
const AVFrame *frame = video_buffer_consume_rendered_frame(vb);
static bool
screen_update_frame(struct screen *screen) {
unsigned skipped;
const AVFrame *frame =
video_buffer_consumer_take_frame(screen->vb, &skipped);
fps_counter_add_skipped_frames(screen->fps_counter, skipped);
fps_counter_add_rendered_frame(screen->fps_counter);
struct size new_frame_size = {frame->width, frame->height};
if (!prepare_for_frame(screen, new_frame_size)) {
mutex_unlock(vb->mutex);
return false;
}
update_texture(screen, frame);
mutex_unlock(vb->mutex);
screen_render(screen);
screen_render(screen, false);
return true;
}
void
screen_render(struct screen *screen) {
screen_render(struct screen *screen, bool update_content_rect) {
if (update_content_rect) {
screen_update_content_rect(screen);
}
SDL_RenderClear(screen->renderer);
SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL);
if (screen->rotation == 0) {
SDL_RenderCopy(screen->renderer, screen->texture, NULL, &screen->rect);
} else {
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
// counterclockwise (to be consistent with --lock-video-orientation)
int cw_rotation = (4 - screen->rotation) % 4;
double angle = 90 * cw_rotation;
SDL_Rect *dstrect = NULL;
SDL_Rect rect;
if (screen->rotation & 1) {
rect.x = screen->rect.x + (screen->rect.w - screen->rect.h) / 2;
rect.y = screen->rect.y + (screen->rect.h - screen->rect.w) / 2;
rect.w = screen->rect.h;
rect.h = screen->rect.w;
dstrect = &rect;
} else {
assert(screen->rotation == 2);
dstrect = &screen->rect;
}
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
angle, NULL, 0);
}
SDL_RenderPresent(screen->renderer);
}
@ -330,27 +514,25 @@ screen_switch_fullscreen(struct screen *screen) {
}
screen->fullscreen = !screen->fullscreen;
apply_windowed_size(screen);
if (!screen->fullscreen && !screen->maximized) {
apply_pending_resize(screen);
}
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
screen_render(screen);
screen_render(screen, true);
}
void
screen_resize_to_fit(struct screen *screen) {
if (screen->fullscreen) {
if (screen->fullscreen || screen->maximized) {
return;
}
if (screen->maximized) {
SDL_RestoreWindow(screen->window);
screen->maximized = false;
}
struct size optimal_size =
get_optimal_window_size(screen, screen->frame_size);
get_optimal_window_size(screen, screen->content_size);
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
LOGD("Resized to optimal size");
LOGD("Resized to optimal size: %ux%u", optimal_size.width,
optimal_size.height);
}
void
@ -364,49 +546,124 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
screen->maximized = false;
}
SDL_SetWindowSize(screen->window, screen->frame_size.width,
screen->frame_size.height);
LOGD("Resized to pixel-perfect");
struct size content_size = screen->content_size;
SDL_SetWindowSize(screen->window, content_size.width, content_size.height);
LOGD("Resized to pixel-perfect: %ux%u", content_size.width,
content_size.height);
}
bool
screen_handle_event(struct screen *screen, SDL_Event *event) {
switch (event->type) {
case EVENT_NEW_FRAME:
if (!screen->has_frame) {
screen->has_frame = true;
// this is the very first frame, show the window
screen_show_window(screen);
}
bool ok = screen_update_frame(screen);
if (!ok) {
LOGW("Frame update failed\n");
}
return true;
case SDL_WINDOWEVENT:
if (!screen->has_frame) {
// Do nothing
return true;
}
switch (event->window.event) {
case SDL_WINDOWEVENT_EXPOSED:
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_MAXIMIZED:
screen->maximized = true;
break;
case SDL_WINDOWEVENT_RESTORED:
if (screen->fullscreen) {
// On Windows, in maximized+fullscreen, disabling
// fullscreen mode unexpectedly triggers the "restored"
// then "maximized" events, leaving the window in a
// weird state (maximized according to the events, but
// not maximized visually).
break;
}
screen->maximized = false;
apply_pending_resize(screen);
break;
}
return true;
}
return false;
}
struct point
screen_convert_drawable_to_frame_coords(struct screen *screen,
int32_t x, int32_t y) {
unsigned rotation = screen->rotation;
assert(rotation < 4);
int32_t w = screen->content_size.width;
int32_t h = screen->content_size.height;
x = (int64_t) (x - screen->rect.x) * w / screen->rect.w;
y = (int64_t) (y - screen->rect.y) * h / screen->rect.h;
// rotate
struct point result;
switch (rotation) {
case 0:
result.x = x;
result.y = y;
break;
case 1:
result.x = h - y;
result.y = x;
break;
case 2:
result.x = w - x;
result.y = h - y;
break;
default:
assert(rotation == 3);
result.x = y;
result.y = w - x;
break;
}
return result;
}
struct point
screen_convert_window_to_frame_coords(struct screen *screen,
int32_t x, int32_t y) {
screen_hidpi_scale_coords(screen, &x, &y);
return screen_convert_drawable_to_frame_coords(screen, x, y);
}
void
screen_handle_window_event(struct screen *screen,
const SDL_WindowEvent *event) {
switch (event->event) {
case SDL_WINDOWEVENT_EXPOSED:
screen_render(screen);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
if (!screen->fullscreen && !screen->maximized) {
// Backup the previous size: if we receive the MAXIMIZED event,
// then the new size must be ignored (it's the maximized size).
// We could not rely on the window flags due to race conditions
// (they could be updated asynchronously, at least on X11).
screen->windowed_window_size_backup =
screen->windowed_window_size;
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y) {
// take the HiDPI scaling (dw/ww and dh/wh) into account
int ww, wh, dw, dh;
SDL_GetWindowSize(screen->window, &ww, &wh);
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
// Save the windowed size, so that it is available once the
// window is maximized or fullscreen is enabled.
screen->windowed_window_size = get_window_size(screen->window);
}
screen_render(screen);
break;
case SDL_WINDOWEVENT_MAXIMIZED:
// The backup size must be non-nul.
assert(screen->windowed_window_size_backup.width);
assert(screen->windowed_window_size_backup.height);
// Revert the last size, it was updated while screen was maximized.
screen->windowed_window_size = screen->windowed_window_size_backup;
#ifdef DEBUG
// Reset the backup to invalid values to detect unexpected usage
screen->windowed_window_size_backup.width = 0;
screen->windowed_window_size_backup.height = 0;
#endif
screen->maximized = true;
break;
case SDL_WINDOWEVENT_RESTORED:
screen->maximized = false;
apply_windowed_size(screen);
break;
}
// scale for HiDPI (64 bits for intermediate multiplications)
*x = (int64_t) *x * dw / ww;
*y = (int64_t) *y * dh / wh;
}
void
screen_on_frame_available(struct screen *screen) {
(void) screen;
static SDL_Event new_frame_event = {
.type = EVENT_NEW_FRAME,
};
// Post the event on the UI thread
SDL_PushEvent(&new_frame_event);
}

View File

@ -1,63 +1,91 @@
#ifndef SCREEN_H
#define SCREEN_H
#include "common.h"
#include <stdbool.h>
#include <SDL2/SDL.h>
#include <libavformat/avformat.h>
#include "config.h"
#include "common.h"
#include "coords.h"
#include "opengl.h"
struct video_buffer;
struct screen {
struct video_buffer *vb;
struct fps_counter *fps_counter;
SDL_Window *window;
SDL_Renderer *renderer;
SDL_Texture *texture;
struct sc_opengl gl;
struct size frame_size;
// The window size the last time it was not maximized or fullscreen.
struct size windowed_window_size;
// Since we receive the event SIZE_CHANGED before MAXIMIZED, we must be
// able to revert the size to its non-maximized value.
struct size windowed_window_size_backup;
struct size content_size; // rotated frame_size
bool resize_pending; // resize requested while fullscreen or maximized
// The content size the last time the window was not maximized or
// fullscreen (meaningful only when resize_pending is true)
struct size windowed_content_size;
// client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise)
unsigned rotation;
// rectangle of the content (excluding black borders)
struct SDL_Rect rect;
bool has_frame;
bool fullscreen;
bool maximized;
bool no_window;
bool mipmaps;
};
#define SCREEN_INITIALIZER { \
.vb = NULL, \
.fps_counter = NULL, \
.window = NULL, \
.renderer = NULL, \
.texture = NULL, \
.gl = {0}, \
.frame_size = { \
.width = 0, \
.height = 0, \
}, \
.windowed_window_size = { \
.width = 0, \
.height = 0, \
}, \
.windowed_window_size_backup = { \
.content_size = { \
.width = 0, \
.height = 0, \
}, \
.resize_pending = false, \
.windowed_content_size = { \
.width = 0, \
.height = 0, \
}, \
.rotation = 0, \
.rect = { \
.x = 0, \
.y = 0, \
.w = 0, \
.h = 0, \
}, \
.has_frame = false, \
.fullscreen = false, \
.maximized = false, \
.no_window = false, \
.mipmaps = false, \
}
// initialize default values
void
screen_init(struct screen *screen);
screen_init(struct screen *screen, struct video_buffer *vb,
struct fps_counter *fps_counter);
// initialize screen, create window, renderer and texture (window is hidden)
// window_x and window_y accept SC_WINDOW_POSITION_UNDEFINED
bool
screen_init_rendering(struct screen *screen, const char *window_title,
struct size frame_size, bool always_on_top,
int16_t window_x, int16_t window_y, uint16_t window_width,
uint16_t window_height, bool window_borderless);
uint16_t window_height, bool window_borderless,
uint8_t rotation, bool mipmaps);
// show the window
void
@ -67,13 +95,12 @@ screen_show_window(struct screen *screen);
void
screen_destroy(struct screen *screen);
// resize if necessary and write the rendered frame into the texture
bool
screen_update_frame(struct screen *screen, struct video_buffer *vb);
// render the texture to the renderer
//
// Set the update_content_rect flag if the window or content size may have
// changed, so that the content rectangle is recomputed
void
screen_render(struct screen *screen);
screen_render(struct screen *screen, bool update_content_rect);
// switch the fullscreen mode
void
@ -87,8 +114,36 @@ screen_resize_to_fit(struct screen *screen);
void
screen_resize_to_pixel_perfect(struct screen *screen);
// react to window events
// set the display rotation (0, 1, 2 or 3, x90 degrees counterclockwise)
void
screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);
screen_set_rotation(struct screen *screen, unsigned rotation);
// react to SDL events
bool
screen_handle_event(struct screen *screen, SDL_Event *event);
// convert point from window coordinates to frame coordinates
// x and y are expressed in pixels
struct point
screen_convert_window_to_frame_coords(struct screen *screen,
int32_t x, int32_t y);
// convert point from drawable coordinates to frame coordinates
// x and y are expressed in pixels
struct point
screen_convert_drawable_to_frame_coords(struct screen *screen,
int32_t x, int32_t y);
// Convert coordinates from window to drawable.
// Events are expressed in window coordinates, but content is expressed in
// drawable coordinates. They are the same if HiDPI scaling is 1, but differ
// otherwise.
void
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y);
// Notify the screen that a new frame is available in the video_buffer.
// Called from a separate thread.
void
screen_on_frame_available(struct screen *screen);
#endif

View File

@ -8,8 +8,7 @@
#include <SDL2/SDL_timer.h>
#include <SDL2/SDL_platform.h>
#include "config.h"
#include "command.h"
#include "adb.h"
#include "util/log.h"
#include "util/net.h"
#include "util/str_util.h"
@ -32,7 +31,7 @@ get_server_path(void) {
#ifdef __WINDOWS__
char *server_path = utf8_from_wide_char(server_path_env);
#else
char *server_path = SDL_strdup(server_path_env);
char *server_path = strdup(server_path_env);
#endif
if (!server_path) {
LOGE("Could not allocate memory");
@ -44,7 +43,7 @@ get_server_path(void) {
#ifndef PORTABLE
LOGD("Using server: " DEFAULT_SERVER_PATH);
char *server_path = SDL_strdup(DEFAULT_SERVER_PATH);
char *server_path = strdup(DEFAULT_SERVER_PATH);
if (!server_path) {
LOGE("Could not allocate memory");
return NULL;
@ -66,11 +65,11 @@ get_server_path(void) {
// sizeof(SERVER_FILENAME) gives statically the size including the null byte
size_t len = dirlen + 1 + sizeof(SERVER_FILENAME);
char *server_path = SDL_malloc(len);
char *server_path = malloc(len);
if (!server_path) {
LOGE("Could not alloc server path string, "
"using " SERVER_FILENAME " from current directory");
SDL_free(executable_path);
free(executable_path);
return SERVER_FILENAME;
}
@ -79,7 +78,7 @@ get_server_path(void) {
memcpy(&server_path[dirlen + 1], SERVER_FILENAME, sizeof(SERVER_FILENAME));
// the final null byte has been copied with SERVER_FILENAME
SDL_free(executable_path);
free(executable_path);
LOGD("Using server (portable): %s", server_path);
return server_path;
@ -94,36 +93,36 @@ push_server(const char *serial) {
}
if (!is_regular_file(server_path)) {
LOGE("'%s' does not exist or is not a regular file\n", server_path);
SDL_free(server_path);
free(server_path);
return false;
}
process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH);
SDL_free(server_path);
return process_check_success(process, "adb push");
free(server_path);
return process_check_success(process, "adb push", true);
}
static bool
enable_tunnel_reverse(const char *serial, uint16_t local_port) {
process_t process = adb_reverse(serial, SOCKET_NAME, local_port);
return process_check_success(process, "adb reverse");
return process_check_success(process, "adb reverse", true);
}
static bool
disable_tunnel_reverse(const char *serial) {
process_t process = adb_reverse_remove(serial, SOCKET_NAME);
return process_check_success(process, "adb reverse --remove");
return process_check_success(process, "adb reverse --remove", true);
}
static bool
enable_tunnel_forward(const char *serial, uint16_t local_port) {
process_t process = adb_forward(serial, local_port, SOCKET_NAME);
return process_check_success(process, "adb forward");
return process_check_success(process, "adb forward", true);
}
static bool
disable_tunnel_forward(const char *serial, uint16_t local_port) {
process_t process = adb_forward_remove(serial, local_port);
return process_check_success(process, "adb forward --remove");
return process_check_success(process, "adb forward --remove", true);
}
static bool
@ -142,7 +141,7 @@ listen_on_port(uint16_t port) {
static bool
enable_tunnel_reverse_any_port(struct server *server,
struct port_range port_range) {
struct sc_port_range port_range) {
uint16_t port = port_range.first;
for (;;) {
if (!enable_tunnel_reverse(server->serial, port)) {
@ -171,7 +170,7 @@ enable_tunnel_reverse_any_port(struct server *server,
// check before incrementing to avoid overflow on port 65535
if (port < port_range.last) {
LOGW("Could not listen on port %" PRIu16", retrying on %" PRIu16,
port, port + 1);
port, (uint16_t) (port + 1));
port++;
continue;
}
@ -188,7 +187,7 @@ enable_tunnel_reverse_any_port(struct server *server,
static bool
enable_tunnel_forward_any_port(struct server *server,
struct port_range port_range) {
struct sc_port_range port_range) {
server->tunnel_forward = true;
uint16_t port = port_range.first;
for (;;) {
@ -200,7 +199,7 @@ enable_tunnel_forward_any_port(struct server *server,
if (port < port_range.last) {
LOGW("Could not forward port %" PRIu16", retrying on %" PRIu16,
port, port + 1);
port, (uint16_t) (port + 1));
port++;
continue;
}
@ -216,45 +215,84 @@ enable_tunnel_forward_any_port(struct server *server,
}
static bool
enable_tunnel_any_port(struct server *server, struct port_range port_range) {
if (enable_tunnel_reverse_any_port(server, port_range)) {
return true;
enable_tunnel_any_port(struct server *server, struct sc_port_range port_range,
bool force_adb_forward) {
if (!force_adb_forward) {
// Attempt to use "adb reverse"
if (enable_tunnel_reverse_any_port(server, port_range)) {
return true;
}
// if "adb reverse" does not work (e.g. over "adb connect"), it
// fallbacks to "adb forward", so the app socket is the client
LOGW("'adb reverse' failed, fallback to 'adb forward'");
}
// if "adb reverse" does not work (e.g. over "adb connect"), it fallbacks to
// "adb forward", so the app socket is the client
LOGW("'adb reverse' failed, fallback to 'adb forward'");
return enable_tunnel_forward_any_port(server, port_range);
}
static const char *
log_level_to_server_string(enum sc_log_level level) {
switch (level) {
case SC_LOG_LEVEL_DEBUG:
return "debug";
case SC_LOG_LEVEL_INFO:
return "info";
case SC_LOG_LEVEL_WARN:
return "warn";
case SC_LOG_LEVEL_ERROR:
return "error";
default:
assert(!"unexpected log level");
return "(unknown)";
}
}
static process_t
execute_server(struct server *server, const struct server_params *params) {
char max_size_string[6];
char bit_rate_string[11];
char max_fps_string[6];
char lock_video_orientation_string[5];
char display_id_string[11];
sprintf(max_size_string, "%"PRIu16, params->max_size);
sprintf(bit_rate_string, "%"PRIu32, params->bit_rate);
sprintf(max_fps_string, "%"PRIu16, params->max_fps);
sprintf(lock_video_orientation_string, "%"PRIi8, params->lock_video_orientation);
sprintf(display_id_string, "%"PRIu32, params->display_id);
const char *const cmd[] = {
"shell",
"CLASSPATH=" DEVICE_SERVER_PATH,
"app_process",
#ifdef SERVER_DEBUGGER
# define SERVER_DEBUGGER_PORT "5005"
# ifdef SERVER_DEBUGGER_METHOD_NEW
/* Android 9 and above */
"-XjdwpProvider:internal -XjdwpOptions:transport=dt_socket,suspend=y,server=y,address="
# else
/* Android 8 and below */
"-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address="
# endif
SERVER_DEBUGGER_PORT,
#endif
"/", // unused
"com.genymobile.scrcpy.Server",
SCRCPY_VERSION,
log_level_to_server_string(params->log_level),
max_size_string,
bit_rate_string,
max_fps_string,
lock_video_orientation_string,
server->tunnel_forward ? "true" : "false",
params->crop ? params->crop : "-",
"true", // always send frame meta (packet boundaries + timestamp)
params->control ? "true" : "false",
display_id_string,
params->show_touches ? "true" : "false",
params->stay_awake ? "true" : "false",
params->codec_options ? params->codec_options : "-",
params->encoder_name ? params->encoder_name : "-",
};
#ifdef SERVER_DEBUGGER
LOGI("Server debugger waiting for a client on device port "
@ -305,58 +343,124 @@ connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
}
static void
close_socket(socket_t *socket) {
assert(*socket != INVALID_SOCKET);
net_shutdown(*socket, SHUT_RDWR);
if (!net_close(*socket)) {
close_socket(socket_t socket) {
assert(socket != INVALID_SOCKET);
net_shutdown(socket, SHUT_RDWR);
if (!net_close(socket)) {
LOGW("Could not close socket");
return;
}
*socket = INVALID_SOCKET;
}
void
bool
server_init(struct server *server) {
*server = (struct server) SERVER_INITIALIZER;
server->serial = NULL;
server->process = PROCESS_NONE;
atomic_flag_clear_explicit(&server->server_socket_closed,
memory_order_relaxed);
bool ok = sc_mutex_init(&server->mutex);
if (!ok) {
return false;
}
ok = sc_cond_init(&server->process_terminated_cond);
if (!ok) {
sc_mutex_destroy(&server->mutex);
return false;
}
server->process_terminated = false;
server->server_socket = INVALID_SOCKET;
server->video_socket = INVALID_SOCKET;
server->control_socket = INVALID_SOCKET;
server->local_port = 0;
server->tunnel_enabled = false;
server->tunnel_forward = false;
return true;
}
static int
run_wait_server(void *data) {
struct server *server = data;
process_wait(server->process, false); // ignore exit code
sc_mutex_lock(&server->mutex);
server->process_terminated = true;
sc_cond_signal(&server->process_terminated_cond);
sc_mutex_unlock(&server->mutex);
// no need for synchronization, server_socket is initialized before this
// thread was created
if (server->server_socket != INVALID_SOCKET
&& !atomic_flag_test_and_set(&server->server_socket_closed)) {
// On Linux, accept() is unblocked by shutdown(), but on Windows, it is
// unblocked by closesocket(). Therefore, call both (close_socket()).
close_socket(server->server_socket);
}
LOGD("Server terminated");
return 0;
}
bool
server_start(struct server *server, const char *serial,
const struct server_params *params) {
server->port_range = params->port_range;
if (serial) {
server->serial = SDL_strdup(serial);
server->serial = strdup(serial);
if (!server->serial) {
return false;
}
}
if (!push_server(serial)) {
SDL_free(server->serial);
return false;
goto error1;
}
if (!enable_tunnel_any_port(server, params->port_range)) {
SDL_free(server->serial);
return false;
if (!enable_tunnel_any_port(server, params->port_range,
params->force_adb_forward)) {
goto error1;
}
// server will connect to our server socket
server->process = execute_server(server, params);
if (server->process == PROCESS_NONE) {
if (!server->tunnel_forward) {
close_socket(&server->server_socket);
}
disable_tunnel(server);
SDL_free(server->serial);
return false;
goto error2;
}
// If the server process dies before connecting to the server socket, then
// the client will be stuck forever on accept(). To avoid the problem, we
// must be able to wake up the accept() call when the server dies. To keep
// things simple and multiplatform, just spawn a new thread waiting for the
// server process and calling shutdown()/close() on the server socket if
// necessary to wake up any accept() blocking call.
bool ok = sc_thread_create(&server->wait_server_thread, run_wait_server,
"wait-server", server);
if (!ok) {
process_terminate(server->process);
process_wait(server->process, true); // ignore exit code
goto error2;
}
server->tunnel_enabled = true;
return true;
error2:
if (!server->tunnel_forward) {
bool was_closed =
atomic_flag_test_and_set(&server->server_socket_closed);
// the thread is not started, the flag could not be already set
assert(!was_closed);
(void) was_closed;
close_socket(server->server_socket);
}
disable_tunnel(server);
error1:
free(server->serial);
return false;
}
bool
@ -374,7 +478,11 @@ server_connect_to(struct server *server) {
}
// we don't need the server socket anymore
close_socket(&server->server_socket);
if (!atomic_flag_test_and_set(&server->server_socket_closed)) {
// close it from here
close_socket(server->server_socket);
// otherwise, it is closed by run_wait_server()
}
} else {
uint32_t attempts = 100;
uint32_t delay = 100; // ms
@ -401,32 +509,52 @@ server_connect_to(struct server *server) {
void
server_stop(struct server *server) {
if (server->server_socket != INVALID_SOCKET) {
close_socket(&server->server_socket);
if (server->server_socket != INVALID_SOCKET
&& !atomic_flag_test_and_set(&server->server_socket_closed)) {
close_socket(server->server_socket);
}
if (server->video_socket != INVALID_SOCKET) {
close_socket(&server->video_socket);
close_socket(server->video_socket);
}
if (server->control_socket != INVALID_SOCKET) {
close_socket(&server->control_socket);
close_socket(server->control_socket);
}
assert(server->process != PROCESS_NONE);
if (!cmd_terminate(server->process)) {
LOGW("Could not terminate server");
}
cmd_simple_wait(server->process, NULL); // ignore exit code
LOGD("Server terminated");
if (server->tunnel_enabled) {
// ignore failure
disable_tunnel(server);
}
// Give some delay for the server to terminate properly
sc_mutex_lock(&server->mutex);
bool signaled = false;
if (!server->process_terminated) {
#define WATCHDOG_DELAY_MS 1000
signaled = sc_cond_timedwait(&server->process_terminated_cond,
&server->mutex,
WATCHDOG_DELAY_MS);
}
sc_mutex_unlock(&server->mutex);
// After this delay, kill the server if it's not dead already.
// On some devices, closing the sockets is not sufficient to wake up the
// blocking calls while the device is asleep.
if (!signaled) {
// The process is terminated, but not reaped (closed) yet, so its PID
// is still valid.
LOGW("Killing the server...");
process_terminate(server->process);
}
sc_thread_join(&server->wait_server_thread, NULL);
process_close(server->process);
}
void
server_destroy(struct server *server) {
SDL_free(server->serial);
free(server->serial);
sc_cond_destroy(&server->process_terminated_cond);
sc_mutex_destroy(&server->mutex);
}

View File

@ -1,52 +1,55 @@
#ifndef SERVER_H
#define SERVER_H
#include "common.h"
#include <stdatomic.h>
#include <stdbool.h>
#include <stdint.h>
#include "config.h"
#include "command.h"
#include "common.h"
#include "adb.h"
#include "scrcpy.h"
#include "util/log.h"
#include "util/net.h"
#include "util/thread.h"
struct server {
char *serial;
process_t process;
sc_thread wait_server_thread;
atomic_flag server_socket_closed;
sc_mutex mutex;
sc_cond process_terminated_cond;
bool process_terminated;
socket_t server_socket; // only used if !tunnel_forward
socket_t video_socket;
socket_t control_socket;
struct port_range port_range;
uint16_t local_port; // selected from port_range
bool tunnel_enabled;
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
};
#define SERVER_INITIALIZER { \
.serial = NULL, \
.process = PROCESS_NONE, \
.server_socket = INVALID_SOCKET, \
.video_socket = INVALID_SOCKET, \
.control_socket = INVALID_SOCKET, \
.port_range = { \
.first = 0, \
.last = 0, \
}, \
.local_port = 0, \
.tunnel_enabled = false, \
.tunnel_forward = false, \
}
struct server_params {
enum sc_log_level log_level;
const char *crop;
struct port_range port_range;
const char *codec_options;
const char *encoder_name;
struct sc_port_range port_range;
uint16_t max_size;
uint32_t bit_rate;
uint16_t max_fps;
int8_t lock_video_orientation;
bool control;
uint32_t display_id;
bool show_touches;
bool stay_awake;
bool force_adb_forward;
};
// init default values
void
bool
server_init(struct server *server);
// push, enable tunnel et start the server

View File

@ -4,12 +4,8 @@
#include <libavformat/avformat.h>
#include <libavutil/time.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include <unistd.h>
#include "config.h"
#include "compat.h"
#include "decoder.h"
#include "events.h"
#include "recorder.h"
@ -281,8 +277,8 @@ bool
stream_start(struct stream *stream) {
LOGD("Starting stream thread");
stream->thread = SDL_CreateThread(run_stream, "stream", stream);
if (!stream->thread) {
bool ok = sc_thread_create(&stream->thread, run_stream, "stream", stream);
if (!ok) {
LOGC("Could not start stream thread");
return false;
}
@ -298,5 +294,5 @@ stream_stop(struct stream *stream) {
void
stream_join(struct stream *stream) {
SDL_WaitThread(stream->thread, NULL);
sc_thread_join(&stream->thread, NULL);
}

View File

@ -1,21 +1,21 @@
#ifndef STREAM_H
#define STREAM_H
#include "common.h"
#include <stdbool.h>
#include <stdint.h>
#include <libavformat/avformat.h>
#include <SDL2/SDL_atomic.h>
#include <SDL2/SDL_thread.h>
#include "config.h"
#include "util/net.h"
#include "util/thread.h"
struct video_buffer;
struct stream {
socket_t socket;
struct video_buffer *video_buffer;
SDL_Thread *thread;
sc_thread thread;
struct decoder *decoder;
struct recorder *recorder;
AVCodecContext *codec_ctx;

View File

@ -1,28 +1,56 @@
// for portability
#define _POSIX_SOURCE // for kill()
#define _BSD_SOURCE // for readlink()
// modern glibc will complain without this
#define _DEFAULT_SOURCE
#include "command.h"
#include "config.h"
#include "util/process.h"
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "util/log.h"
bool
search_executable(const char *file) {
char *path = getenv("PATH");
if (!path)
return false;
path = strdup(path);
if (!path)
return false;
bool ret = false;
size_t file_len = strlen(file);
char *saveptr;
for (char *dir = strtok_r(path, ":", &saveptr); dir;
dir = strtok_r(NULL, ":", &saveptr)) {
size_t dir_len = strlen(dir);
char *fullpath = malloc(dir_len + file_len + 2);
if (!fullpath)
continue;
memcpy(fullpath, dir, dir_len);
fullpath[dir_len] = '/';
memcpy(fullpath + dir_len + 1, file, file_len + 1);
struct stat sb;
bool fullpath_executable = stat(fullpath, &sb) == 0 &&
sb.st_mode & S_IXUSR;
free(fullpath);
if (fullpath_executable) {
ret = true;
break;
}
}
free(path);
return ret;
}
enum process_result
cmd_execute(const char *const argv[], pid_t *pid) {
process_execute(const char *const argv[], pid_t *pid) {
int fd[2];
if (pipe(fd) == -1) {
@ -84,29 +112,37 @@ end:
}
bool
cmd_terminate(pid_t pid) {
process_terminate(pid_t pid) {
if (pid <= 0) {
LOGC("Requested to kill %d, this is an error. Please report the bug.\n",
(int) pid);
abort();
}
return kill(pid, SIGTERM) != -1;
return kill(pid, SIGKILL) != -1;
}
bool
cmd_simple_wait(pid_t pid, int *exit_code) {
int status;
exit_code_t
process_wait(pid_t pid, bool close) {
int code;
if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status)) {
int options = WEXITED;
if (!close) {
options |= WNOWAIT;
}
siginfo_t info;
int r = waitid(P_PID, pid, &info, options);
if (r == -1 || info.si_code != CLD_EXITED) {
// could not wait, or exited unexpectedly, probably by a signal
code = -1;
code = NO_EXIT_CODE;
} else {
code = WEXITSTATUS(status);
code = info.si_status;
}
if (exit_code) {
*exit_code = code;
}
return !code;
return code;
}
void
process_close(pid_t pid) {
process_wait(pid, true); // ignore exit code
}
char *
@ -120,7 +156,7 @@ get_executable_path(void) {
return NULL;
}
buf[len] = '\0';
return SDL_strdup(buf);
return strdup(buf);
#else
// in practice, we only need this feature for portable builds, only used on
// Windows, so we don't care implementing it for every platform

View File

@ -1,8 +1,8 @@
#include "command.h"
#include "util/process.h"
#include <assert.h>
#include <sys/stat.h>
#include "config.h"
#include "util/log.h"
#include "util/str_util.h"
@ -21,7 +21,7 @@ build_cmd(char *cmd, size_t len, const char *const argv[]) {
}
enum process_result
cmd_execute(const char *const argv[], HANDLE *handle) {
process_execute(const char *const argv[], HANDLE *handle) {
STARTUPINFOW si;
PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si));
@ -39,14 +39,9 @@ cmd_execute(const char *const argv[], HANDLE *handle) {
return PROCESS_ERROR_GENERIC;
}
#ifdef WINDOWS_NOCONSOLE
int flags = CREATE_NO_WINDOW;
#else
int flags = 0;
#endif
if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, flags, NULL, NULL, &si,
if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, 0, NULL, NULL, &si,
&pi)) {
SDL_free(wide);
free(wide);
*handle = NULL;
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
return PROCESS_ERROR_MISSING_BINARY;
@ -54,28 +49,35 @@ cmd_execute(const char *const argv[], HANDLE *handle) {
return PROCESS_ERROR_GENERIC;
}
SDL_free(wide);
free(wide);
*handle = pi.hProcess;
return PROCESS_SUCCESS;
}
bool
cmd_terminate(HANDLE handle) {
return TerminateProcess(handle, 1) && CloseHandle(handle);
process_terminate(HANDLE handle) {
return TerminateProcess(handle, 1);
}
bool
cmd_simple_wait(HANDLE handle, DWORD *exit_code) {
exit_code_t
process_wait(HANDLE handle, bool close) {
DWORD code;
if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0
|| !GetExitCodeProcess(handle, &code)) {
// could not wait or retrieve the exit code
code = -1; // max value, it's unsigned
code = NO_EXIT_CODE; // max value, it's unsigned
}
if (exit_code) {
*exit_code = code;
if (close) {
CloseHandle(handle);
}
return !code;
return code;
}
void
process_close(HANDLE handle) {
bool closed = CloseHandle(handle);
assert(closed);
(void) closed;
}
char *
@ -103,7 +105,7 @@ is_regular_file(const char *path) {
struct _stat path_stat;
int r = _wstat(wide_path, &path_stat);
SDL_free(wide_path);
free(wide_path);
if (r) {
perror("stat");

View File

@ -6,7 +6,6 @@
#include <stdio.h>
#include <stdlib.h>
#include "config.h"
#include "util/log.h"
struct index {

View File

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

View File

@ -1,11 +1,11 @@
#ifndef BUFFER_UTIL_H
#define BUFFER_UTIL_H
#include "common.h"
#include <stdbool.h>
#include <stdint.h>
#include "config.h"
static inline void
buffer_write16be(uint8_t *buf, uint16_t value) {
buf[0] = value >> 8;
@ -33,7 +33,7 @@ buffer_read16be(const uint8_t *buf) {
static inline uint32_t
buffer_read32be(const uint8_t *buf) {
return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
return ((uint32_t) buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
}
static inline uint64_t

View File

@ -2,11 +2,11 @@
#ifndef CBUF_H
#define CBUF_H
#include "common.h"
#include <stdbool.h>
#include <unistd.h>
#include "config.h"
// To define a circular buffer type of 20 ints:
// struct cbuf_int CBUF(int, 20);
//

View File

@ -1,74 +0,0 @@
#ifndef LOCK_H
#define LOCK_H
#include <stdint.h>
#include <SDL2/SDL_mutex.h>
#include "config.h"
#include "log.h"
static inline void
mutex_lock(SDL_mutex *mutex) {
int r = SDL_LockMutex(mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not lock mutex: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
static inline void
mutex_unlock(SDL_mutex *mutex) {
int r = SDL_UnlockMutex(mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not unlock mutex: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
static inline void
cond_wait(SDL_cond *cond, SDL_mutex *mutex) {
int r = SDL_CondWait(cond, mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not wait on condition: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
static inline int
cond_wait_timeout(SDL_cond *cond, SDL_mutex *mutex, uint32_t ms) {
int r = SDL_CondWaitTimeout(cond, mutex, ms);
#ifndef NDEBUG
if (r < 0) {
LOGC("Could not wait on condition with timeout: %s", SDL_GetError());
abort();
}
#endif
return r;
}
static inline void
cond_signal(SDL_cond *cond) {
int r = SDL_CondSignal(cond);
#ifndef NDEBUG
if (r) {
LOGC("Could not signal a condition: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
#endif

View File

@ -3,7 +3,6 @@
#include <stdio.h>
#include <SDL2/SDL_platform.h>
#include "config.h"
#include "log.h"
#ifdef __WINDOWS__

View File

@ -1,6 +1,8 @@
#ifndef NET_H
#define NET_H
#include "common.h"
#include <stdbool.h>
#include <stdint.h>
#include <SDL2/SDL_platform.h>
@ -17,8 +19,6 @@
typedef int socket_t;
#endif
#include "config.h"
bool
net_init(void);

21
app/src/util/process.c Normal file
View File

@ -0,0 +1,21 @@
#include "process.h"
#include "log.h"
bool
process_check_success(process_t proc, const char *name, bool close) {
if (proc == PROCESS_NONE) {
LOGE("Could not execute \"%s\"", name);
return false;
}
exit_code_t exit_code = process_wait(proc, close);
if (exit_code) {
if (exit_code != NO_EXIT_CODE) {
LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code);
} else {
LOGE("\"%s\" exited unexpectedly", name);
}
return false;
}
return true;
}

View File

@ -1,8 +1,9 @@
#ifndef COMMAND_H
#define COMMAND_H
#ifndef SC_PROCESS_H
#define SC_PROCESS_H
#include "common.h"
#include <stdbool.h>
#include <inttypes.h>
#ifdef _WIN32
@ -12,11 +13,7 @@
# define PATH_SEPARATOR '\\'
# define PRIexitcode "lu"
// <https://stackoverflow.com/a/44383330/1987178>
# ifdef _WIN64
# define PRIsizet PRIu64
# else
# define PRIsizet PRIu32
# endif
# define PRIsizet "Iu"
# define PROCESS_NONE NULL
# define NO_EXIT_CODE -1u // max value as unsigned
typedef HANDLE process_t;
@ -35,53 +32,45 @@
#endif
#include "config.h"
enum process_result {
PROCESS_SUCCESS,
PROCESS_ERROR_GENERIC,
PROCESS_ERROR_MISSING_BINARY,
};
// execute the command and write the result to the output parameter "process"
enum process_result
cmd_execute(const char *const argv[], process_t *process);
process_execute(const char *const argv[], process_t *process);
// kill the process
bool
cmd_terminate(process_t pid);
process_terminate(process_t pid);
bool
cmd_simple_wait(process_t pid, exit_code_t *exit_code);
// wait and close the process (like waitpid())
// the "close" flag indicates if the process must be "closed" (reaped)
// (passing false is equivalent to enable WNOWAIT in waitid())
exit_code_t
process_wait(process_t pid, bool close);
process_t
adb_execute(const char *serial, const char *const adb_cmd[], size_t len);
process_t
adb_forward(const char *serial, uint16_t local_port,
const char *device_socket_name);
process_t
adb_forward_remove(const char *serial, uint16_t local_port);
process_t
adb_reverse(const char *serial, const char *device_socket_name,
uint16_t local_port);
process_t
adb_reverse_remove(const char *serial, const char *device_socket_name);
process_t
adb_push(const char *serial, const char *local, const char *remote);
process_t
adb_install(const char *serial, const char *local);
// close the process
//
// Semantically, process_wait(close) = process_wait(noclose) + process_close
void
process_close(process_t pid);
// convenience function to wait for a successful process execution
// automatically log process errors with the provided process name
bool
process_check_success(process_t proc, const char *name);
process_check_success(process_t proc, const char *name, bool close);
#ifndef _WIN32
// only used to find package manager, not implemented for Windows
bool
search_executable(const char *file);
#endif
// return the absolute path of the executable (the scrcpy binary)
// may be NULL on error; to be freed by SDL_free
// may be NULL on error; to be freed by free()
char *
get_executable_path(void);

View File

@ -2,12 +2,12 @@
#ifndef QUEUE_H
#define QUEUE_H
#include "common.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include "config.h"
// To define a queue type of "struct foo":
// struct queue_foo QUEUE(struct foo);
#define QUEUE(TYPE) { \

View File

@ -10,10 +10,6 @@
# include <tchar.h>
#endif
#include <SDL2/SDL_stdinc.h>
#include "config.h"
size_t
xstrncpy(char *dest, const char *src, size_t n) {
size_t i;
@ -51,7 +47,7 @@ truncated:
char *
strquote(const char *src) {
size_t len = strlen(src);
char *quoted = SDL_malloc(len + 3);
char *quoted = malloc(len + 3);
if (!quoted) {
return NULL;
}
@ -169,7 +165,7 @@ utf8_to_wide_char(const char *utf8) {
return NULL;
}
wchar_t *wide = SDL_malloc(len * sizeof(wchar_t));
wchar_t *wide = malloc(len * sizeof(wchar_t));
if (!wide) {
return NULL;
}
@ -185,7 +181,7 @@ utf8_from_wide_char(const wchar_t *ws) {
return NULL;
}
char *utf8 = SDL_malloc(len);
char *utf8 = malloc(len);
if (!utf8) {
return NULL;
}

View File

@ -1,11 +1,11 @@
#ifndef STRUTIL_H
#define STRUTIL_H
#include "common.h"
#include <stdbool.h>
#include <stddef.h>
#include "config.h"
// like strncpy, except:
// - it copies at most n-1 chars
// - the dest string is nul-terminated

160
app/src/util/thread.c Normal file
View File

@ -0,0 +1,160 @@
#include "thread.h"
#include <assert.h>
#include <SDL2/SDL_thread.h>
#include "log.h"
bool
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
void *userdata) {
SDL_Thread *sdl_thread = SDL_CreateThread(fn, name, userdata);
if (!sdl_thread) {
return false;
}
thread->thread = sdl_thread;
return true;
}
void
sc_thread_join(sc_thread *thread, int *status) {
SDL_WaitThread(thread->thread, status);
}
bool
sc_mutex_init(sc_mutex *mutex) {
SDL_mutex *sdl_mutex = SDL_CreateMutex();
if (!sdl_mutex) {
return false;
}
mutex->mutex = sdl_mutex;
#ifndef NDEBUG
mutex->locker = 0;
#endif
return true;
}
void
sc_mutex_destroy(sc_mutex *mutex) {
SDL_DestroyMutex(mutex->mutex);
}
void
sc_mutex_lock(sc_mutex *mutex) {
// SDL mutexes are recursive, but we don't want to use recursive mutexes
assert(!sc_mutex_held(mutex));
int r = SDL_LockMutex(mutex->mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not lock mutex: %s", SDL_GetError());
abort();
}
mutex->locker = sc_thread_get_id();
#else
(void) r;
#endif
}
void
sc_mutex_unlock(sc_mutex *mutex) {
#ifndef NDEBUG
assert(sc_mutex_held(mutex));
mutex->locker = 0;
#endif
int r = SDL_UnlockMutex(mutex->mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not lock mutex: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
sc_thread_id
sc_thread_get_id(void) {
return SDL_ThreadID();
}
#ifndef NDEBUG
bool
sc_mutex_held(struct sc_mutex *mutex) {
return mutex->locker == sc_thread_get_id();
}
#endif
bool
sc_cond_init(sc_cond *cond) {
SDL_cond *sdl_cond = SDL_CreateCond();
if (!sdl_cond) {
return false;
}
cond->cond = sdl_cond;
return true;
}
void
sc_cond_destroy(sc_cond *cond) {
SDL_DestroyCond(cond->cond);
}
void
sc_cond_wait(sc_cond *cond, sc_mutex *mutex) {
int r = SDL_CondWait(cond->cond, mutex->mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not wait on condition: %s", SDL_GetError());
abort();
}
mutex->locker = sc_thread_get_id();
#else
(void) r;
#endif
}
bool
sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, uint32_t ms) {
int r = SDL_CondWaitTimeout(cond->cond, mutex->mutex, ms);
#ifndef NDEBUG
if (r < 0) {
LOGC("Could not wait on condition with timeout: %s", SDL_GetError());
abort();
}
mutex->locker = sc_thread_get_id();
#endif
assert(r == 0 || r == SDL_MUTEX_TIMEDOUT);
return r == 0;
}
void
sc_cond_signal(sc_cond *cond) {
int r = SDL_CondSignal(cond->cond);
#ifndef NDEBUG
if (r) {
LOGC("Could not signal a condition: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
void
sc_cond_broadcast(sc_cond *cond) {
int r = SDL_CondBroadcast(cond->cond);
#ifndef NDEBUG
if (r) {
LOGC("Could not broadcast a condition: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}

81
app/src/util/thread.h Normal file
View File

@ -0,0 +1,81 @@
#ifndef SC_THREAD_H
#define SC_THREAD_H
#include "common.h"
#include <stdbool.h>
#include <stdint.h>
/* Forward declarations */
typedef struct SDL_Thread SDL_Thread;
typedef struct SDL_mutex SDL_mutex;
typedef struct SDL_cond SDL_cond;
typedef int sc_thread_fn(void *);
typedef unsigned int sc_thread_id;
typedef struct sc_thread {
SDL_Thread *thread;
} sc_thread;
typedef struct sc_mutex {
SDL_mutex *mutex;
#ifndef NDEBUG
sc_thread_id locker;
#endif
} sc_mutex;
typedef struct sc_cond {
SDL_cond *cond;
} sc_cond;
bool
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
void *userdata);
void
sc_thread_join(sc_thread *thread, int *status);
bool
sc_mutex_init(sc_mutex *mutex);
void
sc_mutex_destroy(sc_mutex *mutex);
void
sc_mutex_lock(sc_mutex *mutex);
void
sc_mutex_unlock(sc_mutex *mutex);
sc_thread_id
sc_thread_get_id(void);
#ifndef NDEBUG
bool
sc_mutex_held(struct sc_mutex *mutex);
# define sc_mutex_assert(mutex) assert(sc_mutex_held(mutex))
#else
# define sc_mutex_assert(mutex)
#endif
bool
sc_cond_init(sc_cond *cond);
void
sc_cond_destroy(sc_cond *cond);
void
sc_cond_wait(sc_cond *cond, sc_mutex *mutex);
// return true on signaled, false on timeout
bool
sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, uint32_t ms);
void
sc_cond_signal(sc_cond *cond);
void
sc_cond_broadcast(sc_cond *cond);
#endif

View File

@ -1,35 +1,40 @@
#include "video_buffer.h"
#include <assert.h>
#include <SDL2/SDL_mutex.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>
#include "config.h"
#include "util/lock.h"
#include "util/log.h"
bool
video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
bool render_expired_frames) {
vb->fps_counter = fps_counter;
if (!(vb->decoding_frame = av_frame_alloc())) {
video_buffer_init(struct video_buffer *vb, bool wait_consumer,
const struct video_buffer_callbacks *cbs,
void *cbs_userdata) {
vb->producer_frame = av_frame_alloc();
if (!vb->producer_frame) {
goto error_0;
}
if (!(vb->rendering_frame = av_frame_alloc())) {
vb->pending_frame = av_frame_alloc();
if (!vb->pending_frame) {
goto error_1;
}
if (!(vb->mutex = SDL_CreateMutex())) {
vb->consumer_frame = av_frame_alloc();
if (!vb->consumer_frame) {
goto error_2;
}
vb->render_expired_frames = render_expired_frames;
if (render_expired_frames) {
if (!(vb->rendering_frame_consumed_cond = SDL_CreateCond())) {
SDL_DestroyMutex(vb->mutex);
bool ok = sc_mutex_init(&vb->mutex);
if (!ok) {
goto error_3;
}
vb->wait_consumer = wait_consumer;
if (wait_consumer) {
ok = sc_cond_init(&vb->pending_frame_consumed_cond);
if (!ok) {
sc_mutex_destroy(&vb->mutex);
goto error_2;
}
// interrupted is not used if expired frames are not rendered
@ -39,75 +44,113 @@ video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
// there is initially no rendering frame, so consider it has already been
// consumed
vb->rendering_frame_consumed = true;
vb->pending_frame_consumed = true;
vb->skipped = 0;
assert(cbs);
assert(cbs->on_frame_available);
vb->cbs = cbs;
vb->cbs_userdata = cbs_userdata;
return true;
error_3:
av_frame_free(&vb->consumer_frame);
error_2:
av_frame_free(&vb->rendering_frame);
av_frame_free(&vb->pending_frame);
error_1:
av_frame_free(&vb->decoding_frame);
av_frame_free(&vb->producer_frame);
error_0:
return false;
}
void
video_buffer_destroy(struct video_buffer *vb) {
if (vb->render_expired_frames) {
SDL_DestroyCond(vb->rendering_frame_consumed_cond);
if (vb->wait_consumer) {
sc_cond_destroy(&vb->pending_frame_consumed_cond);
}
SDL_DestroyMutex(vb->mutex);
av_frame_free(&vb->rendering_frame);
av_frame_free(&vb->decoding_frame);
sc_mutex_destroy(&vb->mutex);
av_frame_free(&vb->consumer_frame);
av_frame_free(&vb->pending_frame);
av_frame_free(&vb->producer_frame);
}
static void
video_buffer_swap_frames(struct video_buffer *vb) {
AVFrame *tmp = vb->decoding_frame;
vb->decoding_frame = vb->rendering_frame;
vb->rendering_frame = tmp;
video_buffer_swap_producer_frame(struct video_buffer *vb) {
sc_mutex_assert(&vb->mutex);
AVFrame *tmp = vb->producer_frame;
vb->producer_frame = vb->pending_frame;
vb->pending_frame = tmp;
}
static void
video_buffer_swap_consumer_frame(struct video_buffer *vb) {
sc_mutex_assert(&vb->mutex);
AVFrame *tmp = vb->consumer_frame;
vb->consumer_frame = vb->pending_frame;
vb->pending_frame = tmp;
}
void
video_buffer_offer_decoded_frame(struct video_buffer *vb,
bool *previous_frame_skipped) {
mutex_lock(vb->mutex);
if (vb->render_expired_frames) {
video_buffer_producer_offer_frame(struct video_buffer *vb) {
sc_mutex_lock(&vb->mutex);
if (vb->wait_consumer) {
// wait for the current (expired) frame to be consumed
while (!vb->rendering_frame_consumed && !vb->interrupted) {
cond_wait(vb->rendering_frame_consumed_cond, vb->mutex);
while (!vb->pending_frame_consumed && !vb->interrupted) {
sc_cond_wait(&vb->pending_frame_consumed_cond, &vb->mutex);
}
} else if (!vb->rendering_frame_consumed) {
fps_counter_add_skipped_frame(vb->fps_counter);
}
video_buffer_swap_frames(vb);
video_buffer_swap_producer_frame(vb);
*previous_frame_skipped = !vb->rendering_frame_consumed;
vb->rendering_frame_consumed = false;
bool skipped = !vb->pending_frame_consumed;
if (skipped) {
++vb->skipped;
}
mutex_unlock(vb->mutex);
vb->pending_frame_consumed = false;
sc_mutex_unlock(&vb->mutex);
if (!skipped) {
// If skipped, then the previous call will consume this frame, the
// callback must not be called
vb->cbs->on_frame_available(vb, vb->cbs_userdata);
}
}
const AVFrame *
video_buffer_consume_rendered_frame(struct video_buffer *vb) {
assert(!vb->rendering_frame_consumed);
vb->rendering_frame_consumed = true;
fps_counter_add_rendered_frame(vb->fps_counter);
if (vb->render_expired_frames) {
video_buffer_consumer_take_frame(struct video_buffer *vb, unsigned *skipped) {
sc_mutex_lock(&vb->mutex);
assert(!vb->pending_frame_consumed);
vb->pending_frame_consumed = true;
video_buffer_swap_consumer_frame(vb);
if (vb->wait_consumer) {
// unblock video_buffer_offer_decoded_frame()
cond_signal(vb->rendering_frame_consumed_cond);
sc_cond_signal(&vb->pending_frame_consumed_cond);
}
return vb->rendering_frame;
if (skipped) {
*skipped = vb->skipped;
}
vb->skipped = 0; // reset
sc_mutex_unlock(&vb->mutex);
// consumer_frame is only written from this thread, no need to lock
return vb->consumer_frame;
}
void
video_buffer_interrupt(struct video_buffer *vb) {
if (vb->render_expired_frames) {
mutex_lock(vb->mutex);
if (vb->wait_consumer) {
sc_mutex_lock(&vb->mutex);
vb->interrupted = true;
mutex_unlock(vb->mutex);
sc_mutex_unlock(&vb->mutex);
// wake up blocking wait
cond_signal(vb->rendering_frame_consumed_cond);
sc_cond_signal(&vb->pending_frame_consumed_cond);
}
}

View File

@ -1,46 +1,74 @@
#ifndef VIDEO_BUFFER_H
#define VIDEO_BUFFER_H
#include <stdbool.h>
#include <SDL2/SDL_mutex.h>
#include "common.h"
#include <stdbool.h>
#include "config.h"
#include "fps_counter.h"
#include "util/thread.h"
// forward declarations
typedef struct AVFrame AVFrame;
/**
* There are 3 frames in memory:
* - one frame is held by the producer (producer_frame)
* - one frame is held by the consumer (consumer_frame)
* - one frame is shared between the producer and the consumer (pending_frame)
*
* The producer generates a frame into the producer_frame (it may takes time).
*
* Once the frame is produced, it calls video_buffer_producer_offer_frame(),
* which swaps the producer and pending frames.
*
* When the consumer is notified that a new frame is available, it calls
* video_buffer_consumer_take_frame() to retrieve it, which swaps the pending
* and consumer frames. The frame is valid until the next call, without
* blocking the producer.
*/
struct video_buffer {
AVFrame *decoding_frame;
AVFrame *rendering_frame;
SDL_mutex *mutex;
bool render_expired_frames;
AVFrame *producer_frame;
AVFrame *pending_frame;
AVFrame *consumer_frame;
sc_mutex mutex;
bool wait_consumer; // never overwrite a pending frame if it is not consumed
bool interrupted;
SDL_cond *rendering_frame_consumed_cond;
bool rendering_frame_consumed;
struct fps_counter *fps_counter;
sc_cond pending_frame_consumed_cond;
bool pending_frame_consumed;
unsigned skipped;
const struct video_buffer_callbacks *cbs;
void *cbs_userdata;
};
struct video_buffer_callbacks {
// Called when a new frame can be consumed by
// video_buffer_consumer_take_frame(vb)
void (*on_frame_available)(struct video_buffer *vb, void *userdata);
};
bool
video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
bool render_expired_frames);
video_buffer_init(struct video_buffer *vb, bool wait_consumer,
const struct video_buffer_callbacks *cbs, void *cbs_userdata);
void
video_buffer_destroy(struct video_buffer *vb);
// set the decoded frame as ready for rendering
// this function locks frames->mutex during its execution
// the output flag is set to report whether the previous frame has been skipped
// set the producer frame as ready for consuming
void
video_buffer_offer_decoded_frame(struct video_buffer *vb,
bool *previous_frame_skipped);
video_buffer_producer_offer_frame(struct video_buffer *vb);
// mark the rendering frame as consumed and return it
// MUST be called with frames->mutex locked!!!
// the caller is expected to render the returned frame to some texture before
// unlocking frames->mutex
// mark the consumer frame as consumed and return it
// the frame is valid until the next call to this function
// the output parameter "skipped" indicates how many produced frames have been
// skipped
const AVFrame *
video_buffer_consume_rendered_frame(struct video_buffer *vb);
video_buffer_consumer_take_frame(struct video_buffer *vb, unsigned *skipped);
// wake up and avoid any blocking call
void

View File

@ -1,3 +1,5 @@
#include "common.h"
#include <assert.h>
#include "util/buffer_util.h"
@ -65,7 +67,10 @@ static void test_buffer_read64be(void) {
assert(val == 0xABCD1234567890EF);
}
int main(void) {
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_buffer_write16be();
test_buffer_write32be();
test_buffer_write64be();

View File

@ -1,3 +1,5 @@
#include "common.h"
#include <assert.h>
#include <string.h>
@ -65,7 +67,10 @@ static void test_cbuf_push_take(void) {
assert(item == 35);
}
int main(void) {
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_cbuf_empty();
test_cbuf_full();
test_cbuf_push_take();

View File

@ -1,7 +1,10 @@
#include "common.h"
#include <assert.h>
#include <string.h>
#include "cli.h"
#include "common.h"
#include "scrcpy.h"
static void test_flag_version(void) {
struct scrcpy_cli_args args = {
@ -48,6 +51,7 @@ static void test_options(void) {
"--fullscreen",
"--max-fps", "30",
"--max-size", "1024",
"--lock-video-orientation", "2",
// "--no-control" is not compatible with "--turn-screen-off"
// "--no-display" is not compatible with "--fulscreen"
"--port", "1234:1236",
@ -72,17 +76,17 @@ static void test_options(void) {
const struct scrcpy_options *opts = &args.opts;
assert(opts->always_on_top);
fprintf(stderr, "%d\n", (int) opts->bit_rate);
assert(opts->bit_rate == 5000000);
assert(!strcmp(opts->crop, "100:200:300:400"));
assert(opts->fullscreen);
assert(opts->max_fps == 30);
assert(opts->max_size == 1024);
assert(opts->lock_video_orientation == 2);
assert(opts->port_range.first == 1234);
assert(opts->port_range.last == 1236);
assert(!strcmp(opts->push_target, "/sdcard/Movies"));
assert(!strcmp(opts->record_filename, "file"));
assert(opts->record_format == RECORDER_FORMAT_MKV);
assert(opts->record_format == SC_RECORD_FORMAT_MKV);
assert(opts->render_expired_frames);
assert(!strcmp(opts->serial, "0123456789abcdef"));
assert(opts->show_touches);
@ -117,13 +121,54 @@ static void test_options2(void) {
assert(!opts->control);
assert(!opts->display);
assert(!strcmp(opts->record_filename, "file.mp4"));
assert(opts->record_format == RECORDER_FORMAT_MP4);
assert(opts->record_format == SC_RECORD_FORMAT_MP4);
}
int main(void) {
static void test_parse_shortcut_mods(void) {
struct sc_shortcut_mods mods;
bool ok;
ok = sc_parse_shortcut_mods("lctrl", &mods);
assert(ok);
assert(mods.count == 1);
assert(mods.data[0] == SC_MOD_LCTRL);
ok = sc_parse_shortcut_mods("lctrl+lalt", &mods);
assert(ok);
assert(mods.count == 1);
assert(mods.data[0] == (SC_MOD_LCTRL | SC_MOD_LALT));
ok = sc_parse_shortcut_mods("rctrl,lalt", &mods);
assert(ok);
assert(mods.count == 2);
assert(mods.data[0] == SC_MOD_RCTRL);
assert(mods.data[1] == SC_MOD_LALT);
ok = sc_parse_shortcut_mods("lsuper,rsuper+lalt,lctrl+rctrl+ralt", &mods);
assert(ok);
assert(mods.count == 3);
assert(mods.data[0] == SC_MOD_LSUPER);
assert(mods.data[1] == (SC_MOD_RSUPER | SC_MOD_LALT));
assert(mods.data[2] == (SC_MOD_LCTRL | SC_MOD_RCTRL | SC_MOD_RALT));
ok = sc_parse_shortcut_mods("", &mods);
assert(!ok);
ok = sc_parse_shortcut_mods("lctrl+", &mods);
assert(!ok);
ok = sc_parse_shortcut_mods("lctrl,", &mods);
assert(!ok);
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_flag_version();
test_flag_help();
test_options();
test_options2();
test_parse_shortcut_mods();
return 0;
};

View File

@ -1,3 +1,5 @@
#include "common.h"
#include <assert.h>
#include <string.h>
@ -9,18 +11,20 @@ static void test_serialize_inject_keycode(void) {
.inject_keycode = {
.action = AKEY_EVENT_ACTION_UP,
.keycode = AKEYCODE_ENTER,
.repeat = 5,
.metastate = AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON,
},
};
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
int size = control_msg_serialize(&msg, buf);
assert(size == 10);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 14);
const unsigned char expected[] = {
CONTROL_MSG_TYPE_INJECT_KEYCODE,
0x01, // AKEY_EVENT_ACTION_UP
0x00, 0x00, 0x00, 0x42, // AKEYCODE_ENTER
0x00, 0x00, 0x00, 0X05, // repeat
0x00, 0x00, 0x00, 0x41, // AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON
};
assert(!memcmp(buf, expected, sizeof(expected)));
@ -34,13 +38,13 @@ static void test_serialize_inject_text(void) {
},
};
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
int size = control_msg_serialize(&msg, buf);
assert(size == 16);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 18);
const unsigned char expected[] = {
CONTROL_MSG_TYPE_INJECT_TEXT,
0x00, 0x0d, // text length
0x00, 0x00, 0x00, 0x0d, // text length
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
};
assert(!memcmp(buf, expected, sizeof(expected)));
@ -49,20 +53,22 @@ static void test_serialize_inject_text(void) {
static void test_serialize_inject_text_long(void) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
char text[CONTROL_MSG_TEXT_MAX_LENGTH + 1];
char text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH + 1];
memset(text, 'a', sizeof(text));
text[CONTROL_MSG_TEXT_MAX_LENGTH] = '\0';
text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH] = '\0';
msg.inject_text.text = text;
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
int size = control_msg_serialize(&msg, buf);
assert(size == 3 + CONTROL_MSG_TEXT_MAX_LENGTH);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
unsigned char expected[3 + CONTROL_MSG_TEXT_MAX_LENGTH];
unsigned char expected[5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH];
expected[0] = CONTROL_MSG_TYPE_INJECT_TEXT;
expected[1] = 0x01;
expected[2] = 0x2c; // text length (16 bits)
memset(&expected[3], 'a', CONTROL_MSG_TEXT_MAX_LENGTH);
expected[1] = 0x00;
expected[2] = 0x00;
expected[3] = 0x01;
expected[4] = 0x2c; // text length (32 bits)
memset(&expected[5], 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
assert(!memcmp(buf, expected, sizeof(expected)));
}
@ -88,8 +94,8 @@ static void test_serialize_inject_touch_event(void) {
},
};
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
int size = control_msg_serialize(&msg, buf);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 28);
const unsigned char expected[] = {
@ -123,8 +129,8 @@ static void test_serialize_inject_scroll_event(void) {
},
};
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
int size = control_msg_serialize(&msg, buf);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 21);
const unsigned char expected[] = {
@ -142,8 +148,8 @@ static void test_serialize_back_or_screen_on(void) {
.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
};
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
int size = control_msg_serialize(&msg, buf);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 1);
const unsigned char expected[] = {
@ -157,8 +163,8 @@ static void test_serialize_expand_notification_panel(void) {
.type = CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
};
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
int size = control_msg_serialize(&msg, buf);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 1);
const unsigned char expected[] = {
@ -172,8 +178,8 @@ static void test_serialize_collapse_notification_panel(void) {
.type = CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL,
};
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
int size = control_msg_serialize(&msg, buf);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 1);
const unsigned char expected[] = {
@ -187,8 +193,8 @@ static void test_serialize_get_clipboard(void) {
.type = CONTROL_MSG_TYPE_GET_CLIPBOARD,
};
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
int size = control_msg_serialize(&msg, buf);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 1);
const unsigned char expected[] = {
@ -200,18 +206,20 @@ static void test_serialize_get_clipboard(void) {
static void test_serialize_set_clipboard(void) {
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_SET_CLIPBOARD,
.inject_text = {
.set_clipboard = {
.paste = true,
.text = "hello, world!",
},
};
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
int size = control_msg_serialize(&msg, buf);
assert(size == 16);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 19);
const unsigned char expected[] = {
CONTROL_MSG_TYPE_SET_CLIPBOARD,
0x00, 0x0d, // text length
1, // paste
0x00, 0x00, 0x00, 0x0d, // text length
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
};
assert(!memcmp(buf, expected, sizeof(expected)));
@ -225,8 +233,8 @@ static void test_serialize_set_screen_power_mode(void) {
},
};
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
int size = control_msg_serialize(&msg, buf);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 2);
const unsigned char expected[] = {
@ -241,8 +249,8 @@ static void test_serialize_rotate_device(void) {
.type = CONTROL_MSG_TYPE_ROTATE_DEVICE,
};
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
int size = control_msg_serialize(&msg, buf);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 1);
const unsigned char expected[] = {
@ -251,7 +259,10 @@ static void test_serialize_rotate_device(void) {
assert(!memcmp(buf, expected, sizeof(expected)));
}
int main(void) {
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_serialize_inject_keycode();
test_serialize_inject_text();
test_serialize_inject_text_long();

View File

@ -1,19 +1,22 @@
#include "common.h"
#include <assert.h>
#include <string.h>
#include "device_msg.h"
#include <stdio.h>
static void test_deserialize_clipboard(void) {
const unsigned char input[] = {
DEVICE_MSG_TYPE_CLIPBOARD,
0x00, 0x03, // text length
0x00, 0x00, 0x00, 0x03, // text length
0x41, 0x42, 0x43, // "ABC"
};
struct device_msg msg;
ssize_t r = device_msg_deserialize(input, sizeof(input), &msg);
assert(r == 6);
assert(r == 8);
assert(msg.type == DEVICE_MSG_TYPE_CLIPBOARD);
assert(msg.clipboard.text);
@ -22,7 +25,33 @@ static void test_deserialize_clipboard(void) {
device_msg_destroy(&msg);
}
int main(void) {
static void test_deserialize_clipboard_big(void) {
unsigned char input[DEVICE_MSG_MAX_SIZE];
input[0] = DEVICE_MSG_TYPE_CLIPBOARD;
input[1] = (DEVICE_MSG_TEXT_MAX_LENGTH & 0xff000000u) >> 24;
input[2] = (DEVICE_MSG_TEXT_MAX_LENGTH & 0x00ff0000u) >> 16;
input[3] = (DEVICE_MSG_TEXT_MAX_LENGTH & 0x0000ff00u) >> 8;
input[4] = DEVICE_MSG_TEXT_MAX_LENGTH & 0x000000ffu;
memset(input + 5, 'a', DEVICE_MSG_TEXT_MAX_LENGTH);
struct device_msg msg;
ssize_t r = device_msg_deserialize(input, sizeof(input), &msg);
assert(r == DEVICE_MSG_MAX_SIZE);
assert(msg.type == DEVICE_MSG_TYPE_CLIPBOARD);
assert(msg.clipboard.text);
assert(strlen(msg.clipboard.text) == DEVICE_MSG_TEXT_MAX_LENGTH);
assert(msg.clipboard.text[0] == 'a');
device_msg_destroy(&msg);
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_deserialize_clipboard();
test_deserialize_clipboard_big();
return 0;
}

View File

@ -1,3 +1,5 @@
#include "common.h"
#include <assert.h>
#include "util/queue.h"
@ -32,7 +34,10 @@ static void test_queue(void) {
assert(queue_is_empty(&queue));
}
int main(void) {
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_queue();
return 0;
}

View File

@ -1,8 +1,9 @@
#include "common.h"
#include <assert.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <SDL2/SDL.h>
#include "util/str_util.h"
@ -136,7 +137,7 @@ static void test_strquote(void) {
// add '"' at the beginning and the end
assert(!strcmp("\"abcde\"", out));
SDL_free(out);
free(out);
}
static void test_utf8_truncate(void) {
@ -286,7 +287,10 @@ static void test_parse_integer_with_suffix(void) {
assert(!ok);
}
int main(void) {
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_xstrncpy_simple();
test_xstrncpy_just_fit();
test_xstrncpy_truncated();

View File

@ -7,7 +7,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.2'
classpath 'com.android.tools.build:gradle:4.0.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@ -19,6 +19,9 @@ allprojects {
google()
jcenter()
}
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint:deprecation"
}
}
task clean(type: Delete) {

View File

@ -129,11 +129,6 @@ page at http://checkstyle.sourceforge.net/config.html -->
</module>
<module name="IllegalInstantiation" />
<module name="InnerAssignment" />
<module name="MagicNumber">
<property name="severity" value="info" />
<property name="ignoreHashCodeMethod" value="true" />
<property name="ignoreAnnotation" value="true" />
</module>
<module name="MissingSwitchDefault" />
<module name="SimplifyBooleanExpression" />
<module name="SimplifyBooleanReturn" />

View File

@ -15,6 +15,6 @@ cpu = 'i686'
endian = 'little'
[properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.2.1-win32-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.2.1-win32-dev'
prebuilt_sdl2 = 'SDL2-2.0.10/i686-w64-mingw32'
prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win32-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win32-dev'
prebuilt_sdl2 = 'SDL2-2.0.14/i686-w64-mingw32'

View File

@ -15,6 +15,6 @@ cpu = 'x86_64'
endian = 'little'
[properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.2.1-win64-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.2.1-win64-dev'
prebuilt_sdl2 = 'SDL2-2.0.10/x86_64-w64-mingw32'
prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win64-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win64-dev'
prebuilt_sdl2 = 'SDL2-2.0.14/x86_64-w64-mingw32'

4
data/scrcpy-console.bat Normal file
View File

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

View File

@ -0,0 +1 @@
CreateObject("Wscript.Shell").Run "cmd /c scrcpy.exe", 0, false

Binary file not shown.

View File

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

33
gradlew vendored
View File

@ -125,8 +125,8 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
@ -154,19 +154,19 @@ if $cygwin ; then
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
i=`expr $i + 1`
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
@ -175,14 +175,9 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

3
gradlew.bat vendored
View File

@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"

View File

@ -1,9 +1,10 @@
project('scrcpy', 'c',
version: '1.12.1',
meson_version: '>= 0.37',
version: '1.17',
meson_version: '>= 0.48',
default_options: [
'c_std=c11',
'warning_level=2',
'b_ndebug=if-release',
])
if get_option('compile_app')

View File

@ -1,8 +1,8 @@
option('compile_app', type: 'boolean', value: true, description: 'Build the client')
option('compile_server', type: 'boolean', value: true, description: 'Build the server')
option('crossbuild_windows', type: 'boolean', value: false, description: 'Build for Windows from Linux')
option('windows_noconsole', type: 'boolean', value: false, description: 'Disable console on Windows (pass -mwindows flag)')
option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server')
option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable')
option('hidpi_support', type: 'boolean', value: true, description: 'Enable High DPI support')
option('server_debugger', type: 'boolean', value: false, description: 'Run a server debugger and wait for a client to be attached')
option('server_debugger_method', type: 'combo', choices: ['old', 'new'], value: 'new', description: 'Select the debugger method (Android < 9: "old", Android >= 9: "new")')

View File

@ -10,31 +10,31 @@ prepare-win32: prepare-sdl2 prepare-ffmpeg-shared-win32 prepare-ffmpeg-dev-win32
prepare-win64: prepare-sdl2 prepare-ffmpeg-shared-win64 prepare-ffmpeg-dev-win64 prepare-adb
prepare-ffmpeg-shared-win32:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/shared/ffmpeg-4.2.1-win32-shared.zip \
9208255f409410d95147151d7e829b5699bf8d91bfe1e81c3f470f47c2fa66d2 \
ffmpeg-4.2.1-win32-shared
@./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win32-shared.zip \
357af9901a456f4dcbacd107e83a934d344c9cb07ddad8aaf80612eeab7d26d2 \
ffmpeg-4.3.1-win32-shared
prepare-ffmpeg-dev-win32:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/dev/ffmpeg-4.2.1-win32-dev.zip \
c3469e6c5f031cbcc8cba88dee92d6548c5c6b6ff14f4097f18f72a92d0d70c4 \
ffmpeg-4.2.1-win32-dev
@./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win32-dev.zip \
230efb08e9bcf225bd474da29676c70e591fc94d8790a740ca801408fddcb78b \
ffmpeg-4.3.1-win32-dev
prepare-ffmpeg-shared-win64:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/shared/ffmpeg-4.2.1-win64-shared.zip \
55063d3cf750a75485c7bf196031773d81a1b25d0980c7db48ecfc7701a42331 \
ffmpeg-4.2.1-win64-shared
@./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win64-shared.zip \
dd29b7f92f48dead4dd940492c7509138c0f99db445076d0a597007298a79940 \
ffmpeg-4.3.1-win64-shared
prepare-ffmpeg-dev-win64:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/dev/ffmpeg-4.2.1-win64-dev.zip \
5af393be5f25c0a71aa29efce768e477c35347f7f8e0d9696767d5b9d405b74e \
ffmpeg-4.2.1-win64-dev
@./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win64-dev.zip \
2e8038242cf8e1bd095c2978f196ff0462b122cc6ef7e74626a6af15459d8b81 \
ffmpeg-4.3.1-win64-dev
prepare-sdl2:
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.10-mingw.tar.gz \
a90a7cddaec4996f4d7be6d80c57ec69b062e132bffc513965f99217f603274a \
SDL2-2.0.10
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.14-mingw.tar.gz \
405eaff3eb18f2e08fe669ef9e63bc9a8710b7d343756f238619761e9b60407d \
SDL2-2.0.14
prepare-adb:
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r29.0.5-windows.zip \
2df06160056ec9a84c7334af2a1e42740befbb1a2e34370e7af544a2cc78152c \
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r30.0.5-windows.zip \
549ba2bdc31f335eb8a504f005f77606a479cc216d6b64a3e8b64c780003661f \
platform-tools

View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
set -e
url="$1"
sum="$2"

View File

@ -9,21 +9,20 @@
# the server to the device.
.PHONY: default clean \
test \
build-server \
prepare-deps-win32 prepare-deps-win64 \
build-win32 build-win32-noconsole \
build-win64 build-win64-noconsole \
build-win32 build-win64 \
dist-win32 dist-win64 \
zip-win32 zip-win64 \
sums release
release
GRADLE ?= ./gradlew
TEST_BUILD_DIR := build-test
SERVER_BUILD_DIR := build-server
WIN32_BUILD_DIR := build-win32
WIN32_NOCONSOLE_BUILD_DIR := build-win32-noconsole
WIN64_BUILD_DIR := build-win64
WIN64_NOCONSOLE_BUILD_DIR := build-win64-noconsole
DIST := dist
WIN32_TARGET_DIR := scrcpy-win32
@ -33,19 +32,35 @@ VERSION := $(shell git describe --tags --always)
WIN32_TARGET := $(WIN32_TARGET_DIR)-$(VERSION).zip
WIN64_TARGET := $(WIN64_TARGET_DIR)-$(VERSION).zip
release: clean zip-win32 zip-win64 sums
@echo "Windows archives generated in $(DIST)/"
RELEASE_DIR := release-$(VERSION)
release: clean test build-server zip-win32 zip-win64
mkdir -p "$(RELEASE_DIR)"
cp "$(SERVER_BUILD_DIR)/server/scrcpy-server" \
"$(RELEASE_DIR)/scrcpy-server-$(VERSION)"
cp "$(DIST)/$(WIN32_TARGET)" "$(RELEASE_DIR)"
cp "$(DIST)/$(WIN64_TARGET)" "$(RELEASE_DIR)"
cd "$(RELEASE_DIR)" && \
sha256sum "scrcpy-server-$(VERSION)" \
"scrcpy-win32-$(VERSION).zip" \
"scrcpy-win64-$(VERSION).zip" > SHA256SUMS.txt
@echo "Release generated in $(RELEASE_DIR)/"
clean:
$(GRADLE) clean
rm -rf "$(SERVER_BUILD_DIR)" "$(WIN32_BUILD_DIR)" "$(WIN64_BUILD_DIR)" \
"$(WIN32_NOCONSOLE_BUILD_DIR)" "$(WIN64_NOCONSOLE_BUILD_DIR)" "$(DIST)"
rm -rf "$(DIST)" "$(TEST_BUILD_DIR)" "$(SERVER_BUILD_DIR)" \
"$(WIN32_BUILD_DIR)" "$(WIN64_BUILD_DIR)"
test:
[ -d "$(TEST_BUILD_DIR)" ] || ( mkdir "$(TEST_BUILD_DIR)" && \
meson "$(TEST_BUILD_DIR)" -Db_sanitize=address )
ninja -C "$(TEST_BUILD_DIR)"
$(GRADLE) -p server check
build-server:
[ -d "$(SERVER_BUILD_DIR)" ] || ( mkdir "$(SERVER_BUILD_DIR)" && \
meson "$(SERVER_BUILD_DIR)" \
--buildtype release -Dcompile_app=false )
ninja -C "$(SERVER_BUILD_DIR)"
meson "$(SERVER_BUILD_DIR)" --buildtype release -Dcompile_app=false )
ninja -C "$(SERVER_BUILD_DIR)"
prepare-deps-win32:
-$(MAKE) -C prebuilt-deps prepare-win32
@ -60,17 +75,6 @@ build-win32: prepare-deps-win32
-Dportable=true )
ninja -C "$(WIN32_BUILD_DIR)"
build-win32-noconsole: prepare-deps-win32
[ -d "$(WIN32_NOCONSOLE_BUILD_DIR)" ] || ( mkdir "$(WIN32_NOCONSOLE_BUILD_DIR)" && \
meson "$(WIN32_NOCONSOLE_BUILD_DIR)" \
--cross-file cross_win32.txt \
--buildtype release --strip -Db_lto=true \
-Dcrossbuild_windows=true \
-Dcompile_server=false \
-Dwindows_noconsole=true \
-Dportable=true )
ninja -C "$(WIN32_NOCONSOLE_BUILD_DIR)"
prepare-deps-win64:
-$(MAKE) -C prebuilt-deps prepare-win64
@ -84,46 +88,37 @@ build-win64: prepare-deps-win64
-Dportable=true )
ninja -C "$(WIN64_BUILD_DIR)"
build-win64-noconsole: prepare-deps-win64
[ -d "$(WIN64_NOCONSOLE_BUILD_DIR)" ] || ( mkdir "$(WIN64_NOCONSOLE_BUILD_DIR)" && \
meson "$(WIN64_NOCONSOLE_BUILD_DIR)" \
--cross-file cross_win64.txt \
--buildtype release --strip -Db_lto=true \
-Dcrossbuild_windows=true \
-Dcompile_server=false \
-Dwindows_noconsole=true \
-Dportable=true )
ninja -C "$(WIN64_NOCONSOLE_BUILD_DIR)"
dist-win32: build-server build-win32 build-win32-noconsole
dist-win32: build-server build-win32
mkdir -p "$(DIST)/$(WIN32_TARGET_DIR)"
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN32_TARGET_DIR)/"
cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp "$(WIN32_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/scrcpy-noconsole.exe"
cp prebuilt-deps/ffmpeg-4.2.1-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.2.1-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.2.1-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.2.1-win32-shared/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.2.1-win32-shared/bin/swscale-5.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp data/scrcpy-console.bat "$(DIST)/$(WIN32_TARGET_DIR)"
cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)"
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/swscale-5.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/SDL2-2.0.10/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/SDL2-2.0.14/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
dist-win64: build-server build-win64 build-win64-noconsole
dist-win64: build-server build-win64
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN64_TARGET_DIR)/"
cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp "$(WIN64_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/scrcpy-noconsole.exe"
cp prebuilt-deps/ffmpeg-4.2.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.2.1-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.2.1-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.2.1-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.2.1-win64-shared/bin/swscale-5.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)"
cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/swscale-5.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/SDL2-2.0.10/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/SDL2-2.0.14/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
zip-win32: dist-win32
cd "$(DIST)/$(WIN32_TARGET_DIR)"; \
@ -132,7 +127,3 @@ zip-win32: dist-win32
zip-win64: dist-win64
cd "$(DIST)/$(WIN64_TARGET_DIR)"; \
zip -r "../$(WIN64_TARGET)" .
sums:
cd "$(DIST)"; \
sha256sum *.zip > SHA256SUMS.txt

View File

@ -1,44 +1,2 @@
#!/bin/bash
set -e
# test locally
TESTDIR=build_test
rm -rf "$TESTDIR"
# run client tests with ASAN enabled
meson "$TESTDIR" -Db_sanitize=address
ninja -C"$TESTDIR" test
# test server
GRADLE=${GRADLE:-./gradlew}
$GRADLE -p server check
BUILDDIR=build_release
rm -rf "$BUILDDIR"
meson "$BUILDDIR" --buildtype release --strip -Db_lto=true
cd "$BUILDDIR"
ninja
cd -
# build Windows releases
make -f Makefile.CrossWindows
# the generated server must be the same everywhere
cmp "$BUILDDIR/server/scrcpy-server" dist/scrcpy-win32/scrcpy-server
cmp "$BUILDDIR/server/scrcpy-server" dist/scrcpy-win64/scrcpy-server
# get version name
TAG=$(git describe --tags --always)
# create release directory
mkdir -p "release-$TAG"
cp "$BUILDDIR/server/scrcpy-server" "release-$TAG/scrcpy-server-$TAG"
cp "dist/scrcpy-win32-$TAG.zip" "release-$TAG/"
cp "dist/scrcpy-win64-$TAG.zip" "release-$TAG/"
# generate checksums
cd "release-$TAG"
sha256sum "scrcpy-server-$TAG" \
"scrcpy-win32-$TAG.zip" \
"scrcpy-win64-$TAG.zip" > SHA256SUMS.txt
echo "Release generated in release-$TAG/"
make -f release.mk

2
run
View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
# Run scrcpy generated in the specified BUILDDIR.
#
# This provides the same feature as "ninja run", except that it is possible to

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