Compare commits

...

165 Commits

Author SHA1 Message Date
ba09254b38 refilter_on_resize 2021-02-12 23:26:39 +01:00
03cc0cf543 swscalewip 2021-02-12 23:26:39 +01:00
f6570a7a41 scale_filter 2021-02-12 23:20:26 +01:00
87e3ad7756 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-12 23:20:26 +01:00
c6de5b0a15 Log mipmaps error only if mipmaps were requested 2021-02-12 22:04:50 +01:00
0b4b4609dc Extract texture management to a separate file
Move texture management out of screen.c, which already handles the
window and the rendering.

This paves the way to implement optional software filtering (swscale)
properly, as an alternative to mipmaps (which are not available
everywhere).
2021-02-12 21:58:38 +01:00
edf710af3c Make use_opengl local
The flag is used only locally, there is no need to store it in the
screen structure.
2021-02-07 19:12:14 +01:00
46bf4d55a7 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-05 17:28:26 +01:00
017036ddab Assert non-recursive usage of mutexes 2021-02-05 17:27:26 +01:00
a9582b1d43 Add mutex assertions 2021-02-04 08:12:22 +01:00
fa3e84b700 Expose mutex assertions
Add a function to assert that the mutex is held (or not).
2021-02-04 08:12:22 +01:00
1521de9051 Expose thread id 2021-02-04 08:12:22 +01:00
eabaabdb78 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-04 08:12:22 +01:00
8b48003074 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-04 08:12:22 +01:00
74bd25a0ed Provide strdup() compat
Make strdup() available on all platforms.
2021-02-04 08:12:22 +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
112 changed files with 5040 additions and 1370 deletions

4
.gitignore vendored
View File

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

View File

@ -176,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"
```
@ -190,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].
@ -249,10 +254,10 @@ You can then [run](README.md#run) _scrcpy_.
## Prebuilt server
- [`scrcpy-server-v1.14`][direct-scrcpy-server]
_(SHA-256: 1d1b18a2b80e956771fd63b99b414d2d028713a8f12ddfa5a369709ad4295620)_
- [`scrcpy-server-v1.17`][direct-scrcpy-server]
_(SHA-256: 11b5ad2d1bc9b9730fb7254a78efd71a8ff46b1938ff468e47a21b653a1b6725_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.14/scrcpy-server-v1.14
[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:

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
### 내 디바이스의 미러링만 가능하고, 디바이스와 상호작용을 할 수 없습니다.

40
FAQ.md
View File

@ -37,6 +37,8 @@ Check [stackoverflow][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
@ -197,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-2020 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

@ -477,7 +477,7 @@ _²화면이 꺼진 상태에서 우클릭 시 다시 켜지며, 그 외의 상
## 라이선스
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2020 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.

264
README.md
View File

@ -1,4 +1,6 @@
# scrcpy (v1.14)
# 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,6 +36,7 @@ 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
@ -49,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
@ -69,10 +77,10 @@ hard).
For Windows, for simplicity, a prebuilt archive with all the dependencies
(including `adb`) is available:
- [`scrcpy-win64-v1.14.zip`][direct-win64]
_(SHA-256: 2be9139e46e29cf2f5f695848bb2b75a543b8f38be1133257dc5068252abc25f)_
- [`scrcpy-win64-v1.17.zip`][direct-win64]
_(SHA-256: 8b9e57993c707367ed10ebfe0e1ef563c7a29d9af4a355cd8b6a52a317c73eea)_
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.14/scrcpy-win64-v1.14.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.17/scrcpy-win64-v1.17.zip
It is also available in [Chocolatey]:
@ -108,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
```
@ -194,6 +206,24 @@ 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
@ -227,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`)_.
@ -301,7 +337,7 @@ ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
From another terminal:
```bash
scrcpy --force-adb-forwrad
scrcpy --force-adb-forward
```
@ -354,7 +390,7 @@ 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
@ -370,18 +406,19 @@ Possibles values are:
- `2`: 180 degrees
- `3`: 90 degrees clockwise
The rotation can also be changed dynamically with `Ctrl`+`←` _(left)_ and
`Ctrl`+`→` _(right)_.
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:
- `Ctrl`+`r` 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` changes the mirroring orientation (the orientation
of the video sent from the device to the computer). This affects the
- <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.
- `--rotation` (or `Ctrl`+`←`/`Ctrl`+`→`) rotates only the window content. This
affects only the display, not the recording.
### Other mirroring options
@ -437,11 +474,16 @@ 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 `Ctrl`+`Shift`+`o` (or `POWER`, `Ctrl`+`p`).
To turn it back on, press <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>.
It can be useful to also prevent the device to sleep:
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
@ -479,28 +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.
- `Ctrl`+`c` copies the device clipboard to the computer clipboard;
- `Ctrl`+`Shift`+`v` copies the computer clipboard to the device clipboard (and
pastes if the device runs Android >= 7);
- `Ctrl`+`v` _pastes_ the computer clipboard as a sequence of text events (but
breaks non-ASCII characters).
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.
Moreover, any time the Android clipboard changes, it is automatically
synchronized to the computer clipboard.
#### Text injection preference
@ -524,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
@ -550,43 +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`
| Rotate display left | `Ctrl`+`←` _(left)_ | `Cmd`+`←` _(left)_
| Rotate display right | `Ctrl`+`→` _(right)_ | `Cmd`+`→` _(right)_
| 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`
| Turn device screen on | `Ctrl`+`Shift`+`o` | `Cmd`+`Shift`+`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 and paste | `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
@ -634,7 +772,7 @@ Read the [developers page].
## Licence
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2020 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.
@ -655,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.

View File

@ -508,7 +508,7 @@ Leia a [developers page].
## Licença
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2020 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.

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',
@ -10,6 +11,7 @@ src = [
'src/event_converter.c',
'src/file_handler.c',
'src/fps_counter.c',
'src/frame_texture.c',
'src/input_manager.c',
'src/opengl.c',
'src/receiver.c',
@ -21,9 +23,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
@ -31,14 +47,13 @@ if not get_option('crossbuild_windows')
dependency('libavformat'),
dependency('libavcodec'),
dependency('libavutil'),
dependency('libswscale'),
dependency('sdl2'),
]
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'
@ -73,17 +88,19 @@ 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()
foreach f : check_functions
if cc.has_function(f)
define = 'HAVE_' + f.underscorify().to_upper()
conf.set(define, true)
endif
endforeach
# expose the build type
conf.set('NDEBUG', get_option('buildtype') != 'debug')
@ -119,9 +136,6 @@ 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'))
@ -132,18 +146,11 @@ 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')
@ -164,12 +171,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',
]],
@ -186,7 +193,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

@ -43,6 +43,10 @@ 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.
@ -52,10 +56,18 @@ The list of possible display ids can be listed by "adb shell dumpsys display"
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.
@ -64,6 +76,12 @@ 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.
@ -88,6 +106,10 @@ 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.
@ -145,6 +167,23 @@ Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each incre
.BI "\-s, \-\-serial " number
The device serial number. Mandatory only if several devices are connected to adb.
.TP
.BI "\-\-scale\-filter filter
Supported filters are "none" and "trilinear".
Trilinear filtering is only available if the renderer is OpenGL 3.0+ or OpenGL
ES 2.0+.
.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.
@ -203,52 +242,55 @@ Default is 0 (automatic).\n
.SH SHORTCUTS
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+f
.B MOD+f
Switch fullscreen mode
.TP
.B Ctrl+Left
.B MOD+Left
Rotate display left
.TP
.B Ctrl+Right
.B MOD+Right
Rotate display right
.TP
.B Ctrl+g
.B MOD+g
Resize window to 1:1 (pixel\-perfect)
.TP
.B Ctrl+x, Double\-click on black borders
.B MOD+w, Double\-click on black borders
Resize window to remove black borders
.TP
.B Ctrl+h, Home, Middle\-click
.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
@ -256,41 +298,49 @@ Click on POWER (turn screen on/off)
Turn screen on
.TP
.B Ctrl+o
.B MOD+o
Turn device screen off (keep mirroring)
.TP
.B Ctrl+Shift+o
.B MOD+Shift+o
Turn device screen on
.TP
.B Ctrl+r
.B MOD+r
Rotate device screen
.TP
.B Ctrl+n
.B MOD+n
Expand notification panel
.TP
.B Ctrl+Shift+n
.B MOD+Shift+n
Collapse notification panel
.TP
.B Ctrl+c
Copy device clipboard to computer
.B Mod+c
Copy to clipboard (inject COPY keycode, Android >= 7 only)
.TP
.B Ctrl+v
Paste computer clipboard to device
.B Mod+x
Cut to clipboard (inject CUT keycode, Android >= 7 only)
.TP
.B Ctrl+Shift+v
Copy computer clipboard to device (and paste if the device runs Android >= 7)
.B MOD+v
Copy computer clipboard to device, then paste (inject PASTE keycode, Android >= 7 only)
.TP
.B Ctrl+i
.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

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"
@ -70,7 +68,7 @@ show_adb_installation_msg() {
{"pacman", "pacman -S android-tools"},
};
for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) {
if (cmd_search(pkg_managers[i].binary)) {
if (search_executable(pkg_managers[i].binary)) {
LOGI("You may install 'adb' by \"%s\"", pkg_managers[i].command);
return;
}
@ -118,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;
@ -175,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
@ -184,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;
@ -206,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

@ -3,20 +3,15 @@
#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"
@ -45,6 +40,9 @@ scrcpy_print_usage(const char *arg0) {
" (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"
@ -54,16 +52,30 @@ scrcpy_print_usage(const char *arg0) {
"\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"
@ -88,10 +100,8 @@ scrcpy_print_usage(const char *arg0) {
" Do not display device (only when screen recording is\n"
" enabled).\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"
" --no-key-repeat\n"
" Do not forward repeated key events when a key is held down.\n"
"\n"
" -p, --port port[:port]\n"
" Set the TCP port (range) used by the client to listen.\n"
@ -139,6 +149,24 @@ scrcpy_print_usage(const char *arg0) {
" The device serial number. Mandatory only if several devices\n"
" are connected to adb.\n"
"\n"
" --scale-filter filter\n"
" Supported filters are \"none\" and \"trilinear\".\n"
" Trilinear filtering is only available if the renderer is\n"
" OpenGL 3.0+ or OpenGL ES 2.0+.\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"
@ -186,77 +214,87 @@ scrcpy_print_usage(const char *arg0) {
"\n"
"Shortcuts:\n"
"\n"
" " CTRL_OR_CMD "+f\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"
" MOD+f\n"
" Switch fullscreen mode\n"
"\n"
" " CTRL_OR_CMD "+Left\n"
" MOD+Left\n"
" Rotate display left\n"
"\n"
" " CTRL_OR_CMD "+Right\n"
" MOD+Right\n"
" Rotate display right\n"
"\n"
" " CTRL_OR_CMD "+g\n"
" MOD+g\n"
" Resize window to 1:1 (pixel-perfect)\n"
"\n"
" " CTRL_OR_CMD "+x\n"
" MOD+w\n"
" Double-click on black borders\n"
" Resize window to remove black borders\n"
"\n"
" Ctrl+h\n"
" MOD+h\n"
" Middle-click\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"
"\n"
" " CTRL_OR_CMD "+s\n"
" MOD+s\n"
" Click on APP_SWITCH\n"
"\n"
" Ctrl+m\n"
" MOD+m\n"
" Click on MENU\n"
"\n"
" " CTRL_OR_CMD "+Up\n"
" MOD+Up\n"
" Click on VOLUME_UP\n"
"\n"
" " CTRL_OR_CMD "+Down\n"
" MOD+Down\n"
" Click on VOLUME_DOWN\n"
"\n"
" " CTRL_OR_CMD "+p\n"
" MOD+p\n"
" Click on POWER (turn screen on/off)\n"
"\n"
" Right-click (when screen is off)\n"
" Power on\n"
"\n"
" " CTRL_OR_CMD "+o\n"
" MOD+o\n"
" Turn device screen off (keep mirroring)\n"
"\n"
" " CTRL_OR_CMD "+Shift+o\n"
" MOD+Shift+o\n"
" Turn device screen on\n"
"\n"
" " CTRL_OR_CMD "+r\n"
" MOD+r\n"
" Rotate device screen\n"
"\n"
" " CTRL_OR_CMD "+n\n"
" MOD+n\n"
" Expand notification panel\n"
"\n"
" " CTRL_OR_CMD "+Shift+n\n"
" MOD+Shift+n\n"
" Collapse notification panel\n"
"\n"
" " CTRL_OR_CMD "+c\n"
" Copy device clipboard to computer\n"
" MOD+c\n"
" Copy to clipboard (inject COPY keycode, Android >= 7 only)\n"
"\n"
" " CTRL_OR_CMD "+v\n"
" Paste computer clipboard to device\n"
" MOD+x\n"
" Cut to clipboard (inject CUT keycode, Android >= 7 only)\n"
"\n"
" " CTRL_OR_CMD "+Shift+v\n"
" Copy computer clipboard to device (and paste if the device\n"
" runs Android >= 7)\n"
" MOD+v\n"
" Copy computer clipboard to device, then paste (inject PASTE\n"
" keycode, Android >= 7 only)\n"
"\n"
" " CTRL_OR_CMD "+i\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"
"\n",
@ -379,10 +417,10 @@ parse_rotation(const char *s, uint8_t *rotation) {
static bool
parse_window_position(const char *s, int16_t *position) {
// special value for "auto"
static_assert(WINDOW_POSITION_UNDEFINED == -0x8000, "unexpected value");
static_assert(SC_WINDOW_POSITION_UNDEFINED == -0x8000, "unexpected value");
if (!strcmp(s, "auto")) {
*position = WINDOW_POSITION_UNDEFINED;
*position = SC_WINDOW_POSITION_UNDEFINED;
return true;
}
@ -411,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) {
@ -439,14 +477,14 @@ parse_port_range(const char *s, struct port_range *port_range) {
}
static bool
parse_display_id(const char *s, uint16_t *display_id) {
parse_display_id(const char *s, uint32_t *display_id) {
long value;
bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF, "display id");
bool ok = parse_integer_arg(s, &value, false, 0, 0x7FFFFFFF, "display id");
if (!ok) {
return false;
}
*display_id = (uint16_t) value;
*display_id = (uint32_t) value;
return true;
}
@ -476,21 +514,118 @@ parse_log_level(const char *s, enum sc_log_level *log_level) {
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_record_format(const char *optarg, enum recorder_format *format) {
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) {
@ -498,14 +633,70 @@ 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;
}
static bool
parse_scale_filter(const char *optarg, enum sc_scale_filter *filter) {
// TODO store in a map and loop over the entries instead
if (!strcmp(optarg, "none")) {
*filter = SC_SCALE_FILTER_NONE;
return true;
}
if (!strcmp(optarg, "trilinear")) {
*filter = SC_SCALE_FILTER_TRILINEAR;
return true;
}
if (!strcmp(optarg, "bilinear")) {
*filter = SC_SCALE_FILTER_BILINEAR;
return true;
}
if (!strcmp(optarg, "bicubic")) {
*filter = SC_SCALE_FILTER_BICUBIC;
return true;
}
if (!strcmp(optarg, "x")) {
*filter = SC_SCALE_FILTER_X;
return true;
}
if (!strcmp(optarg, "point")) {
*filter = SC_SCALE_FILTER_POINT;
return true;
}
if (!strcmp(optarg, "area")) {
*filter = SC_SCALE_FILTER_AREA;
return true;
}
if (!strcmp(optarg, "bicublin")) {
*filter = SC_SCALE_FILTER_BICUBLIN;
return true;
}
if (!strcmp(optarg, "gauss")) {
*filter = SC_SCALE_FILTER_GAUSS;
return true;
}
if (!strcmp(optarg, "sinc")) {
*filter = SC_SCALE_FILTER_SINC;
return true;
}
if (!strcmp(optarg, "lanczos")) {
*filter = SC_SCALE_FILTER_LANCZOS;
return true;
}
if (!strcmp(optarg, "spline")) {
*filter = SC_SCALE_FILTER_SPLINE;
return true;
}
LOGE("Unsupported scale filter: %s "
"(expected one of [TODO])", optarg);
return false;
}
#define OPT_RENDER_EXPIRED_FRAMES 1000
#define OPT_WINDOW_TITLE 1001
#define OPT_PUSH_TARGET 1002
@ -526,6 +717,13 @@ guess_record_format(const char *filename) {
#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
#define OPT_SCALE_FILTER 1026
bool
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
@ -534,17 +732,24 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
{"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},
@ -555,7 +760,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
{"render-expired-frames", no_argument, NULL,
OPT_RENDER_EXPIRED_FRAMES},
{"rotation", required_argument, NULL, OPT_ROTATION},
{"scale-filter", required_argument, NULL, OPT_SCALE_FILTER},
{"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'},
@ -708,14 +915,41 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
opts->render_driver = optarg;
break;
case OPT_NO_MIPMAPS:
opts->mipmaps = false;
LOGW("Deprecated option --no-mipmaps. "
"Use --scale-filter=none instead.");
opts->scale_filter = SC_SCALE_FILTER_NONE;
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;
case OPT_SCALE_FILTER:
if (!parse_scale_filter(optarg, &opts->scale_filter)) {
return false;
}
break;
default:
// getopt prints the error message on stderr
return false;

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
#ifdef 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,8 +42,9 @@ 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,
@ -93,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_MAX_SIZE (1 << 18) // 256k
#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4092
#define CONTROL_MSG_SERIALIZED_MAX_SIZE \
(4 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH)
// 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,7 @@ 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 {
@ -70,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"

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,6 +90,10 @@ 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 (!(mod & (KMOD_NUM | KMOD_SHIFT))) {
@ -111,8 +113,8 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
}
}
if (prefer_text) {
// do not forward alpha and space key events
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

@ -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,26 +3,24 @@
#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;
counter->thread_started = false;
atomic_init(&counter->started, 0);
// no need to initialize the other fields, they are unused until started
@ -31,8 +29,8 @@ 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
@ -78,10 +76,10 @@ 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 && !is_started(counter)) {
cond_wait(counter->state_cond, counter->mutex);
sc_cond_wait(&counter->state_cond, &counter->mutex);
}
while (!counter->interrupted && is_started(counter)) {
uint32_t now = SDL_GetTicks();
@ -91,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);
set_started(counter, true);
cond_signal(counter->state_cond);
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;
@ -125,7 +126,7 @@ fps_counter_start(struct fps_counter *counter) {
void
fps_counter_stop(struct fps_counter *counter) {
set_started(counter, false);
cond_signal(counter->state_cond);
sc_cond_signal(&counter->state_cond);
}
bool
@ -135,21 +136,21 @@ fps_counter_is_started(struct fps_counter *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);
}
}
@ -159,11 +160,11 @@ fps_counter_add_rendered_frame(struct fps_counter *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
@ -172,9 +173,9 @@ fps_counter_add_skipped_frame(struct fps_counter *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);
sc_mutex_unlock(&counter->mutex);
}

View File

@ -1,18 +1,20 @@
#ifndef FPSCOUNTER_H
#define FPSCOUNTER_H
#include "common.h"
#include <stdatomic.h>
#include <stdbool.h>
#include <stdint.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

247
app/src/frame_texture.c Normal file
View File

@ -0,0 +1,247 @@
#include "frame_texture.h"
#include <assert.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#include "util/log.h"
static inline bool
is_swscale_enabled(enum sc_scale_filter scale_filter) {
return scale_filter != SC_SCALE_FILTER_NONE
&& scale_filter != SC_SCALE_FILTER_TRILINEAR;
}
static SDL_Texture *
create_texture(struct sc_frame_texture *ftex, struct size size) {
SDL_Renderer *renderer = ftex->renderer;
SDL_PixelFormatEnum fmt = is_swscale_enabled(ftex->scale_filter)
? SDL_PIXELFORMAT_RGB24
: SDL_PIXELFORMAT_YV12;
SDL_Texture *texture = SDL_CreateTexture(renderer, fmt,
SDL_TEXTUREACCESS_STREAMING,
size.width, size.height);
if (!texture) {
return NULL;
}
if (ftex->scale_filter == SC_SCALE_FILTER_TRILINEAR) {
struct sc_opengl *gl = &ftex->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
sc_frame_texture_init(struct sc_frame_texture *ftex, SDL_Renderer *renderer,
enum sc_scale_filter scale_filter,
struct size initial_size) {
SDL_RendererInfo renderer_info;
int r = SDL_GetRendererInfo(renderer, &renderer_info);
const char *renderer_name = r ? NULL : renderer_info.name;
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
ftex->scale_filter = scale_filter;
// starts with "opengl"
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
if (use_opengl) {
struct sc_opengl *gl = &ftex->gl;
sc_opengl_init(gl);
LOGI("OpenGL version: %s", gl->version);
if (scale_filter == SC_SCALE_FILTER_TRILINEAR) {
bool supports_mipmaps =
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
2, 0 /* OpenGL ES 2.0+ */);
if (!supports_mipmaps) {
LOGW("Trilinear filtering disabled "
"(OpenGL 3.0+ or ES 2.0+ required)");
ftex->scale_filter = SC_SCALE_FILTER_NONE;
}
}
} else if (scale_filter == SC_SCALE_FILTER_TRILINEAR) {
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
}
LOGD("Scale filter: %s\n", sc_scale_filter_name(ftex->scale_filter));
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, initial_size.width,
initial_size.height);
ftex->renderer = renderer;
ftex->texture = create_texture(ftex, initial_size);
if (!ftex->texture) {
LOGC("Could not create texture: %s", SDL_GetError());
SDL_DestroyRenderer(ftex->renderer);
return false;
}
ftex->texture_size = initial_size;
ftex->decoded_frame = NULL;
memset(ftex->data, 0, sizeof(ftex->data));
memset(ftex->linesize, 0, sizeof(ftex->linesize));
return true;
}
void
sc_frame_texture_destroy(struct sc_frame_texture *ftex) {
if (ftex->texture) {
SDL_DestroyTexture(ftex->texture);
}
}
static int
to_sws_filter(enum sc_scale_filter scale_filter) {
switch (scale_filter) {
case SC_SCALE_FILTER_BILINEAR: return SWS_BILINEAR;
case SC_SCALE_FILTER_BICUBIC: return SWS_BICUBIC;
case SC_SCALE_FILTER_X: return SWS_X;
case SC_SCALE_FILTER_POINT: return SWS_POINT;
case SC_SCALE_FILTER_AREA: return SWS_AREA;
case SC_SCALE_FILTER_BICUBLIN: return SWS_BICUBLIN;
case SC_SCALE_FILTER_GAUSS: return SWS_GAUSS;
case SC_SCALE_FILTER_SINC: return SWS_SINC;
case SC_SCALE_FILTER_LANCZOS: return SWS_LANCZOS;
case SC_SCALE_FILTER_SPLINE: return SWS_SPLINE;
default: assert(!"unsupported filter");
}
}
static bool
screen_generate_resized_frame(struct sc_frame_texture *ftex,
struct size target_size) {
assert(is_swscale_enabled(ftex->scale_filter));
// TODO
if (ftex->data[0]) {
av_freep(&ftex->data[0]);
}
int ret = av_image_alloc(ftex->data, ftex->linesize, target_size.width,
target_size.height, AV_PIX_FMT_RGB24, 16);
if (ret < 0) {
return false;
}
const AVFrame *input = ftex->decoded_frame;
assert(input);
int flags = to_sws_filter(ftex->scale_filter);
struct SwsContext *ctx =
sws_getContext(input->width, input->height, AV_PIX_FMT_YUV420P,
target_size.width, target_size.height, AV_PIX_FMT_RGB24,
flags, NULL, NULL, NULL);
if (!ctx) {
return false;
}
sws_scale(ctx, (const uint8_t *const *) input->data, input->linesize, 0,
input->height, ftex->data, ftex->linesize);
sws_freeContext(ctx);
return true;
}
static bool
sc_frame_texture_update_direct(struct sc_frame_texture *ftex,
const AVFrame *frame) {
struct size frame_size = {frame->width, frame->height};
if (ftex->texture_size.width != frame_size.width
|| ftex->texture_size.height != frame_size.height) {
// Frame dimensions changed, destroy texture
SDL_DestroyTexture(ftex->texture);
LOGI("New texture: %" PRIu16 "x%" PRIu16, frame_size.width,
frame_size.height);
ftex->texture = create_texture(ftex, frame_size);
if (!ftex->texture) {
LOGC("Could not create texture: %s", SDL_GetError());
return false;
}
ftex->texture_size = frame_size;
}
SDL_UpdateYUVTexture(ftex->texture, NULL,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
if (ftex->scale_filter == SC_SCALE_FILTER_TRILINEAR) {
SDL_GL_BindTexture(ftex->texture, NULL, NULL);
ftex->gl.GenerateMipmap(GL_TEXTURE_2D);
SDL_GL_UnbindTexture(ftex->texture);
}
return true;
}
static bool
sc_frame_texture_update_swscale(struct sc_frame_texture *ftex,
const AVFrame *frame, struct size target_size) {
if (ftex->texture_size.width != target_size.width
|| ftex->texture_size.height != target_size.height) {
// Frame dimensions changed, destroy texture
SDL_DestroyTexture(ftex->texture);
ftex->texture = create_texture(ftex, target_size);
if (!ftex->texture) {
LOGC("Could not create texture: %s", SDL_GetError());
return false;
}
ftex->texture_size = target_size;
}
// The frame is valid until the next call to
// video_buffer_take_rendering_frame()
ftex->decoded_frame = frame;
bool ok = screen_generate_resized_frame(ftex, target_size);
if (!ok) {
LOGE("Failed to resize frame\n");
return false;
}
SDL_UpdateTexture(ftex->texture, NULL, ftex->data[0], ftex->linesize[0]);
if (ftex->scale_filter == SC_SCALE_FILTER_TRILINEAR) {
SDL_GL_BindTexture(ftex->texture, NULL, NULL);
ftex->gl.GenerateMipmap(GL_TEXTURE_2D);
SDL_GL_UnbindTexture(ftex->texture);
}
return true;
}
bool
sc_frame_texture_update(struct sc_frame_texture *ftex, const AVFrame *frame,
struct size target_size) {
if (is_swscale_enabled(ftex->scale_filter)) {
return sc_frame_texture_update_swscale(ftex, frame, target_size);
}
return sc_frame_texture_update_direct(ftex, frame);
}
bool
sc_frame_texture_resize(struct sc_frame_texture *ftex,
struct size target_size) {
if (is_swscale_enabled(ftex->scale_filter)) {
return sc_frame_texture_update_swscale(ftex, ftex->decoded_frame,
target_size);
}
// Nothing to do
return true;
}

44
app/src/frame_texture.h Normal file
View File

@ -0,0 +1,44 @@
#ifndef SC_FRAME_TEXTURE_H
#define SC_FRAME_TEXTURE_H
#include "common.h"
#include <stdbool.h>
#include <SDL2/SDL.h>
#include <libavformat/avformat.h>
#include "coords.h"
#include "opengl.h"
#include "scrcpy.h"
struct sc_frame_texture {
SDL_Renderer *renderer; // owned by struct screen
enum sc_scale_filter scale_filter;
struct sc_opengl gl;
SDL_Texture *texture;
struct size texture_size;
// For swscaling
const AVFrame *decoded_frame; // owned by the video_buffer
uint8_t *data[4];
int linesize[4];
};
bool
sc_frame_texture_init(struct sc_frame_texture *ftex, SDL_Renderer *renderer,
enum sc_scale_filter scale_filter,
struct size initial_size);
void
sc_frame_texture_destroy(struct sc_frame_texture *ftex);
bool
sc_frame_texture_update(struct sc_frame_texture *ftex, const AVFrame *frame,
struct size target_size);
bool
sc_frame_texture_resize(struct sc_frame_texture *ftex, struct size target_size);
#endif

View File

@ -1,15 +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"
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) {
@ -18,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;
@ -70,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) {
@ -101,16 +176,6 @@ 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, bool paste) {
char *text = SDL_GetClipboardText();
@ -124,13 +189,20 @@ set_device_clipboard(struct controller *controller, bool paste) {
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'");
}
}
@ -176,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'");
}
}
@ -210,6 +289,10 @@ rotate_client_right(struct screen *screen) {
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 == ' ') {
@ -221,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)) {
@ -247,6 +360,7 @@ 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;
@ -254,74 +368,52 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
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 && down) {
if (control && !repeat && down) {
enum screen_power_mode mode = shift
? SCREEN_POWER_MODE_NORMAL
: SCREEN_POWER_MODE_OFF;
@ -329,67 +421,72 @@ input_manager_process_key(struct input_manager *im,
}
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 (cmd && !shift && down) {
if (!shift && !repeat && down) {
rotate_client_left(im->screen);
}
return;
case SDLK_RIGHT:
if (cmd && !shift && down) {
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 and paste
set_device_clipboard(controller, true);
} 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) {
if (!shift && !repeat && down) {
struct fps_counter *fps_counter =
im->video_buffer->fps_counter;
switch_fps_counter_state(fps_counter);
}
return;
case SDLK_n:
if (control && cmd && !repeat && down) {
if (control && !repeat && down) {
if (shift) {
collapse_notification_panel(controller);
} else {
@ -398,7 +495,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;
@ -411,8 +508,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,7 +544,7 @@ convert_mouse_motion(const SDL_MouseMotionEvent *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 =
screen_convert_to_frame_coords(screen, from->x, from->y);
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);
@ -446,10 +563,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);
}
}
@ -465,15 +590,15 @@ convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
to->inject_touch_event.pointer_id = from->fingerId;
to->inject_touch_event.position.screen_size = screen->frame_size;
int ww;
int wh;
SDL_GL_GetDrawableSize(screen->window, &ww, &wh);
int dw;
int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
// SDL touch event coordinates are normalized in the range [0; 1]
int32_t x = from->x * ww;
int32_t y = from->y * wh;
int32_t x = from->x * dw;
int32_t y = from->y * dh;
to->inject_touch_event.position.point =
screen_convert_to_frame_coords(screen, x, y);
screen_convert_drawable_to_frame_coords(screen, x, y);
to->inject_touch_event.pressure = from->pressure;
to->inject_touch_event.buttons = 0;
@ -503,8 +628,9 @@ 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 =
screen_convert_to_frame_coords(screen, from->x, from->y);
to->inject_touch_event.pressure = 1.f;
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));
@ -513,13 +639,16 @@ convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
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;
@ -550,10 +679,36 @@ 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;
}
}
@ -568,7 +723,8 @@ convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
struct position position = {
.screen_size = screen->frame_size,
.point = screen_convert_to_frame_coords(screen, mouse_x, mouse_y),
.point = screen_convert_window_to_frame_coords(screen,
mouse_x, mouse_y),
};
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;

View File

@ -1,30 +1,52 @@
#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 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_init(struct input_manager *im,
const struct scrcpy_options *options);
void
input_manager_process_text_input(struct input_manager *im,
const SDL_TextInputEvent *event);
void
input_manager_process_key(struct input_manager *im,
const SDL_KeyboardEvent *event,
bool control);
const SDL_KeyboardEvent *event);
void
input_manager_process_mouse_motion(struct input_manager *im,
@ -36,8 +58,7 @@ input_manager_process_touch(struct input_manager *im,
void
input_manager_process_mouse_button(struct input_manager *im,
const SDL_MouseButtonEvent *event,
bool control);
const SDL_MouseButtonEvent *event);
void
input_manager_process_mouse_wheel(struct input_manager *im,

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
@ -42,7 +43,7 @@ convert_log_level_to_sdl(enum sc_log_level level) {
return SDL_LOG_PRIORITY_ERROR;
default:
assert(!"unexpected log level");
return SC_LOG_LEVEL_INFO;
return SDL_LOG_PRIORITY_INFO;
}
}
@ -71,7 +72,7 @@ main(int argc, char *argv[]) {
}
SDL_LogPriority sdl_log = convert_log_level_to_sdl(args.opts.log_level);
SDL_LogSetAllPriority(sdl_log);
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log);
if (args.help) {
scrcpy_print_usage(argv[0]);
@ -97,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;
}

View File

@ -1,11 +1,11 @@
#ifndef SC_OPENGL_H
#define SC_OPENGL_H
#include "common.h"
#include <stdbool.h>
#include <SDL2/SDL_opengl.h>
#include "config.h"
struct sc_opengl {
const char *version;
bool is_opengles;

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

@ -8,13 +8,11 @@
#include <SDL2/SDL.h>
#ifdef _WIN32
// not needed here, but winsock2.h must never be included AFTER windows.h
# include <winsock2.h>
# include <windows.h>
#endif
#include "config.h"
#include "command.h"
#include "common.h"
#include "compat.h"
#include "controller.h"
#include "decoder.h"
#include "device.h"
@ -28,11 +26,30 @@
#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 const char *sc_scale_filter_names[SC_SCALE_FILTER__COUNT] = {
[SC_SCALE_FILTER_NONE] = "none",
[SC_SCALE_FILTER_TRILINEAR] = "trilinear",
[SC_SCALE_FILTER_BILINEAR] = "bilinear",
[SC_SCALE_FILTER_BICUBIC] = "bicubic",
[SC_SCALE_FILTER_X] = "x",
[SC_SCALE_FILTER_POINT] = "point",
[SC_SCALE_FILTER_AREA] = "area",
[SC_SCALE_FILTER_BICUBLIN] = "bicublin",
[SC_SCALE_FILTER_GAUSS] = "gauss",
[SC_SCALE_FILTER_SINC] = "sinc",
[SC_SCALE_FILTER_LANCZOS] = "lanczos",
[SC_SCALE_FILTER_SPLINE] = "spline",
};
const char *
sc_scale_filter_name(enum sc_scale_filter scale_filter) {
return sc_scale_filter_names[scale_filter];
}
static struct server server;
static struct screen screen = SCREEN_INITIALIZER;
static struct fps_counter fps_counter;
static struct video_buffer video_buffer;
@ -46,7 +63,14 @@ static struct input_manager input_manager = {
.controller = &controller,
.video_buffer = &video_buffer,
.screen = &screen,
.prefer_text = false, // initialized later
.repeat = 0,
// initialized later
.prefer_text = false,
.sdl_shortcut_mods = {
.data = {0},
.count = 0,
},
};
#ifdef _WIN32
@ -63,7 +87,8 @@ BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
// init SDL and set appropriate hints
static bool
sdl_init_and_configure(bool display, const char *render_driver) {
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());
@ -112,8 +137,13 @@ sdl_init_and_configure(bool display, const char *render_driver) {
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;
}
@ -155,7 +185,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");
@ -174,10 +204,12 @@ handle_event(SDL_Event *event, bool control) {
}
break;
case SDL_WINDOWEVENT:
screen_handle_window_event(&screen, &event->window);
if (screen.has_frame) {
screen_handle_window_event(&screen, &event->window);
}
break;
case SDL_TEXTINPUT:
if (!control) {
if (!options->control) {
break;
}
input_manager_process_text_input(&input_manager, &event->text);
@ -186,16 +218,16 @@ handle_event(SDL_Event *event, bool control) {
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);
input_manager_process_key(&input_manager, &event->key);
break;
case SDL_MOUSEMOTION:
if (!control) {
if (!options->control) {
break;
}
input_manager_process_mouse_motion(&input_manager, &event->motion);
break;
case SDL_MOUSEWHEEL:
if (!control) {
if (!options->control) {
break;
}
input_manager_process_mouse_wheel(&input_manager, &event->wheel);
@ -204,8 +236,7 @@ handle_event(SDL_Event *event, bool control) {
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);
input_manager_process_mouse_button(&input_manager, &event->button);
break;
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
@ -213,16 +244,23 @@ handle_event(SDL_Event *event, bool control) {
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;
}
}
@ -230,16 +268,15 @@ handle_event(SDL_Event *event, bool control) {
}
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;
@ -277,7 +314,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;
@ -286,11 +323,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);
}
bool
scrcpy(const struct scrcpy_options *options) {
if (!server_init(&server)) {
return false;
}
bool ret = false;
bool server_started = false;
bool fps_counter_initialized = false;
bool video_buffer_initialized = false;
bool file_handler_initialized = false;
bool recorder_initialized = false;
bool stream_started = false;
bool controller_initialized = false;
bool controller_started = false;
bool record = !!options->record_filename;
struct server_params params = {
.log_level = options->log_level,
@ -305,23 +357,17 @@ scrcpy(const struct scrcpy_options *options) {
.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)) {
return false;
goto end;
}
bool ret = false;
server_started = true;
bool fps_counter_initialized = false;
bool video_buffer_initialized = false;
bool file_handler_initialized = false;
bool recorder_initialized = false;
bool stream_started = false;
bool controller_initialized = false;
bool controller_started = false;
if (!sdl_init_and_configure(options->display, options->render_driver)) {
if (!sdl_init_and_configure(options->display, options->render_driver,
options->disable_screensaver)) {
goto end;
}
@ -408,7 +454,7 @@ scrcpy(const struct scrcpy_options *options) {
options->window_y, options->window_width,
options->window_height,
options->window_borderless,
options->rotation, options-> mipmaps)) {
options->rotation, options->scale_filter)) {
goto end;
}
@ -427,9 +473,9 @@ scrcpy(const struct scrcpy_options *options) {
}
}
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);
@ -450,8 +496,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

View File

@ -1,14 +1,66 @@
#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"
#include "util/log.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;
};
enum sc_scale_filter {
SC_SCALE_FILTER_NONE,
SC_SCALE_FILTER_TRILINEAR, // mipmaps
SC_SCALE_FILTER_BILINEAR,
SC_SCALE_FILTER_BICUBIC,
SC_SCALE_FILTER_X,
SC_SCALE_FILTER_POINT,
SC_SCALE_FILTER_AREA,
SC_SCALE_FILTER_BICUBLIN,
SC_SCALE_FILTER_GAUSS,
SC_SCALE_FILTER_SINC,
SC_SCALE_FILTER_LANCZOS,
SC_SCALE_FILTER_SPLINE,
SC_SCALE_FILTER__COUNT,
};
const char *
sc_scale_filter_name(enum sc_scale_filter scale_filter);
#define SC_WINDOW_POSITION_UNDEFINED (-0x8000)
struct scrcpy_options {
const char *serial;
@ -18,19 +70,22 @@ struct scrcpy_options {
const char *push_target;
const char *render_driver;
const char *codec_options;
const char *encoder_name;
enum sc_log_level log_level;
enum recorder_format record_format;
struct port_range port_range;
enum sc_record_format record_format;
struct sc_port_range port_range;
struct sc_shortcut_mods shortcut_mods;
enum sc_scale_filter scale_filter;
uint16_t max_size;
uint32_t bit_rate;
uint16_t max_fps;
int8_t lock_video_orientation;
uint8_t rotation;
int16_t window_x; // WINDOW_POSITION_UNDEFINED for "auto"
int16_t window_y; // WINDOW_POSITION_UNDEFINED for "auto"
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;
uint16_t display_id;
uint32_t display_id;
bool show_touches;
bool fullscreen;
bool always_on_top;
@ -40,9 +95,12 @@ 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 { \
@ -53,19 +111,25 @@ struct scrcpy_options {
.push_target = NULL, \
.render_driver = NULL, \
.codec_options = NULL, \
.encoder_name = NULL, \
.log_level = SC_LOG_LEVEL_INFO, \
.record_format = RECORDER_FORMAT_AUTO, \
.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, \
}, \
.scale_filter = SC_SCALE_FILTER_TRILINEAR, \
.max_size = DEFAULT_MAX_SIZE, \
.bit_rate = DEFAULT_BIT_RATE, \
.max_fps = 0, \
.lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \
.rotation = 0, \
.window_x = WINDOW_POSITION_UNDEFINED, \
.window_y = WINDOW_POSITION_UNDEFINED, \
.window_x = SC_WINDOW_POSITION_UNDEFINED, \
.window_y = SC_WINDOW_POSITION_UNDEFINED, \
.window_width = 0, \
.window_height = 0, \
.display_id = 0, \
@ -78,9 +142,12 @@ 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,13 +4,9 @@
#include <string.h>
#include <SDL2/SDL.h>
#include "config.h"
#include "common.h"
#include "compat.h"
#include "icon.xpm"
#include "tiny_xpm.h"
#include "video_buffer.h"
#include "util/lock.h"
#include "util/log.h"
#define DISPLAY_MARGINS 96
@ -198,39 +194,12 @@ screen_init(struct screen *screen) {
*screen = (struct screen) SCREEN_INITIALIZER;
}
static inline SDL_Texture *
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,
uint8_t rotation, bool mipmaps) {
uint8_t rotation, enum sc_scale_filter scale_filter) {
screen->frame_size = frame_size;
screen->rotation = rotation;
if (rotation) {
@ -257,9 +226,9 @@ screen_init_rendering(struct screen *screen, const char *window_title,
window_flags |= SDL_WINDOW_BORDERLESS;
}
int x = window_x != WINDOW_POSITION_UNDEFINED
int x = window_x != SC_WINDOW_POSITION_UNDEFINED
? window_x : (int) SDL_WINDOWPOS_UNDEFINED;
int y = window_y != WINDOW_POSITION_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,
@ -273,39 +242,17 @@ 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;
}
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"
screen->use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
if (screen->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 {
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
bool ok = sc_frame_texture_init(&screen->ftex, screen->renderer,
scale_filter, frame_size);
if (!ok) {
LOGC("Could not init frame texture");
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
return false;
}
SDL_Surface *icon = read_xpm(icon_xpm);
@ -316,15 +263,6 @@ screen_init_rendering(struct screen *screen, const char *window_title,
LOGW("Could not load icon");
}
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width,
frame_size.height);
screen->texture = create_texture(screen);
if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError());
screen_destroy(screen);
return false;
}
// 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
@ -342,15 +280,9 @@ screen_show_window(struct screen *screen) {
void
screen_destroy(struct screen *screen) {
if (screen->texture) {
SDL_DestroyTexture(screen->texture);
}
if (screen->renderer) {
SDL_DestroyRenderer(screen->renderer);
}
if (screen->window) {
SDL_DestroyWindow(screen->window);
}
sc_frame_texture_destroy(&screen->ftex);
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
}
static void
@ -411,13 +343,10 @@ screen_set_rotation(struct screen *screen, unsigned rotation) {
}
// recreate the texture and resize the window if the frame size has changed
static bool
static void
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) {
// frame dimension changed, destroy texture
SDL_DestroyTexture(screen->texture);
screen->frame_size = new_frame_size;
struct size new_content_size =
@ -425,46 +354,20 @@ prepare_for_frame(struct screen *screen, struct size new_frame_size) {
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);
if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError());
return false;
}
}
return true;
}
// write the frame into the texture
static void
update_texture(struct screen *screen, const AVFrame *frame) {
SDL_UpdateYUVTexture(screen->texture, NULL,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
if (screen->mipmaps) {
assert(screen->use_opengl);
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);
const AVFrame *frame = video_buffer_take_rendering_frame(vb);
struct size new_frame_size = {frame->width, frame->height};
if (!prepare_for_frame(screen, new_frame_size)) {
mutex_unlock(vb->mutex);
prepare_for_frame(screen, new_frame_size);
struct size rect_size = {screen->rect.w, screen->rect.h};
bool ok = sc_frame_texture_update(&screen->ftex, frame, rect_size);
if (!ok) {
return false;
}
update_texture(screen, frame);
mutex_unlock(vb->mutex);
screen_render(screen, false);
return true;
@ -474,11 +377,18 @@ void
screen_render(struct screen *screen, bool update_content_rect) {
if (update_content_rect) {
screen_update_content_rect(screen);
struct size rect_size = {screen->rect.w, screen->rect.h};
if (!sc_frame_texture_resize(&screen->ftex, rect_size)) {
// FIXME return error
LOGC("oops");
}
}
SDL_RenderClear(screen->renderer);
struct sc_frame_texture *ftex = &screen->ftex;
SDL_RenderClear(ftex->renderer);
if (screen->rotation == 0) {
SDL_RenderCopy(screen->renderer, screen->texture, NULL, &screen->rect);
SDL_RenderCopy(screen->renderer, ftex->texture, NULL,
&screen->rect);
} else {
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
// counterclockwise (to be consistent with --lock-video-orientation)
@ -498,10 +408,10 @@ screen_render(struct screen *screen, bool update_content_rect) {
dstrect = &screen->rect;
}
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
SDL_RenderCopyEx(screen->renderer, ftex->texture, NULL, dstrect,
angle, NULL, 0);
}
SDL_RenderPresent(screen->renderer);
SDL_RenderPresent(ftex->renderer);
}
void
@ -579,14 +489,14 @@ screen_handle_window_event(struct screen *screen,
}
struct point
screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y) {
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;
screen_hidpi_scale_coords(screen, &x, &y);
x = (int64_t) (x - screen->rect.x) * w / screen->rect.w;
y = (int64_t) (y - screen->rect.y) * h / screen->rect.h;
@ -615,6 +525,13 @@ screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y) {
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_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y) {
// take the HiDPI scaling (dw/ww and dh/wh) into account

View File

@ -1,24 +1,24 @@
#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 "frame_texture.h"
#include "opengl.h"
#define WINDOW_POSITION_UNDEFINED (-0x8000)
#include "scrcpy.h"
struct video_buffer;
struct screen {
SDL_Window *window;
SDL_Renderer *renderer;
SDL_Texture *texture;
bool use_opengl;
struct sc_opengl gl;
struct sc_frame_texture ftex;
struct size frame_size;
struct size content_size; // rotated frame_size
@ -35,15 +35,12 @@ struct screen {
bool fullscreen;
bool maximized;
bool no_window;
bool mipmaps;
};
#define SCREEN_INITIALIZER { \
.window = NULL, \
.renderer = NULL, \
.texture = NULL, \
.use_opengl = false, \
.gl = {0}, \
.ftex = {0}, \
.frame_size = { \
.width = 0, \
.height = 0, \
@ -68,7 +65,6 @@ struct screen {
.fullscreen = false, \
.maximized = false, \
.no_window = false, \
.mipmaps = false, \
}
// initialize default values
@ -76,13 +72,13 @@ void
screen_init(struct screen *screen);
// initialize screen, create window, renderer and texture (window is hidden)
// window_x and window_y accept WINDOW_POSITION_UNDEFINED
// 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,
uint8_t rotation, bool mipmaps);
uint8_t rotation, enum sc_scale_filter scale_filter);
// show the window
void
@ -126,7 +122,14 @@ screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);
// convert point from window coordinates to frame coordinates
// x and y are expressed in pixels
struct point
screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y);
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

View File

@ -5,12 +5,10 @@
#include <inttypes.h>
#include <libgen.h>
#include <stdio.h>
#include <SDL2/SDL_thread.h>
#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"
@ -33,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");
@ -45,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;
@ -67,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;
}
@ -80,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;
@ -95,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
@ -143,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)) {
@ -189,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 (;;) {
@ -201,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;
}
@ -217,7 +215,7 @@ enable_tunnel_forward_any_port(struct server *server,
}
static bool
enable_tunnel_any_port(struct server *server, struct port_range port_range,
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"
@ -257,12 +255,12 @@ execute_server(struct server *server, const struct server_params *params) {
char bit_rate_string[11];
char max_fps_string[6];
char lock_video_orientation_string[5];
char display_id_string[6];
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, "%"PRIu16, params->display_id);
sprintf(display_id_string, "%"PRIu32, params->display_id);
const char *const cmd[] = {
"shell",
"CLASSPATH=" DEVICE_SERVER_PATH,
@ -294,6 +292,7 @@ execute_server(struct server *server, const struct server_params *params) {
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 "
@ -352,15 +351,50 @@ close_socket(socket_t 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->port_range.first = 0;
server->port_range.last = 0;
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;
cmd_simple_wait(server->process, NULL); // ignore exit code
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
@ -379,7 +413,7 @@ server_start(struct server *server, const char *serial,
server->port_range = params->port_range;
if (serial) {
server->serial = SDL_strdup(serial);
server->serial = strdup(serial);
if (!server->serial) {
return false;
}
@ -406,11 +440,11 @@ server_start(struct server *server, const char *serial,
// 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.
server->wait_server_thread =
SDL_CreateThread(run_wait_server, "wait-server", server);
if (!server->wait_server_thread) {
cmd_terminate(server->process);
cmd_simple_wait(server->process, NULL); // ignore exit code
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;
}
@ -429,7 +463,7 @@ error2:
}
disable_tunnel(server);
error1:
SDL_free(server->serial);
free(server->serial);
return false;
}
@ -492,17 +526,39 @@ server_stop(struct server *server) {
assert(server->process != PROCESS_NONE);
cmd_terminate(server->process);
if (server->tunnel_enabled) {
// ignore failure
disable_tunnel(server);
}
SDL_WaitThread(server->wait_server_thread, NULL);
// 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,66 +1,56 @@
#ifndef SERVER_H
#define SERVER_H
#include "common.h"
#include <stdatomic.h>
#include <stdbool.h>
#include <stdint.h>
#include <SDL2/SDL_thread.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;
SDL_Thread *wait_server_thread;
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;
struct sc_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, \
.wait_server_thread = NULL, \
.server_socket_closed = ATOMIC_FLAG_INIT, \
.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;
const char *codec_options;
struct port_range port_range;
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;
uint16_t display_id;
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,13 +1,4 @@
// 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>
@ -17,14 +8,13 @@
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include "util/log.h"
bool
cmd_search(const char *file) {
search_executable(const char *file) {
char *path = getenv("PATH");
if (!path)
return false;
@ -60,7 +50,7 @@ cmd_search(const char *file) {
}
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) {
@ -122,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 *
@ -158,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,13 +3,6 @@
#include <SDL2/SDL_log.h>
enum sc_log_level {
SC_LOG_LEVEL_DEBUG,
SC_LOG_LEVEL_INFO,
SC_LOG_LEVEL_WARN,
SC_LOG_LEVEL_ERROR,
};
#define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)

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,58 +32,45 @@
#endif
#include "config.h"
enum process_result {
PROCESS_SUCCESS,
PROCESS_ERROR_GENERIC,
PROCESS_ERROR_MISSING_BINARY,
};
#ifndef __WINDOWS__
bool
cmd_search(const char *file);
#endif
// 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,12 +1,9 @@
#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
@ -14,22 +11,31 @@ 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())) {
vb->decoding_frame = av_frame_alloc();
if (!vb->decoding_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->rendering_frame = av_frame_alloc();
if (!vb->rendering_frame) {
goto error_2;
}
bool ok = sc_mutex_init(&vb->mutex);
if (!ok) {
goto error_3;
}
vb->render_expired_frames = render_expired_frames;
if (render_expired_frames) {
if (!(vb->rendering_frame_consumed_cond = SDL_CreateCond())) {
SDL_DestroyMutex(vb->mutex);
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,12 +45,14 @@ 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;
return true;
error_2:
error_3:
av_frame_free(&vb->rendering_frame);
error_2:
av_frame_free(&vb->pending_frame);
error_1:
av_frame_free(&vb->decoding_frame);
error_0:
@ -54,60 +62,78 @@ error_0:
void
video_buffer_destroy(struct video_buffer *vb) {
if (vb->render_expired_frames) {
SDL_DestroyCond(vb->rendering_frame_consumed_cond);
sc_cond_destroy(&vb->pending_frame_consumed_cond);
}
SDL_DestroyMutex(vb->mutex);
sc_mutex_destroy(&vb->mutex);
av_frame_free(&vb->rendering_frame);
av_frame_free(&vb->pending_frame);
av_frame_free(&vb->decoding_frame);
}
static void
video_buffer_swap_frames(struct video_buffer *vb) {
video_buffer_swap_decoding_frame(struct video_buffer *vb) {
sc_mutex_assert(&vb->mutex);
AVFrame *tmp = vb->decoding_frame;
vb->decoding_frame = vb->rendering_frame;
vb->rendering_frame = tmp;
vb->decoding_frame = vb->pending_frame;
vb->pending_frame = tmp;
}
static void
video_buffer_swap_rendering_frame(struct video_buffer *vb) {
sc_mutex_assert(&vb->mutex);
AVFrame *tmp = vb->rendering_frame;
vb->rendering_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);
sc_mutex_lock(&vb->mutex);
if (vb->render_expired_frames) {
// 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) {
} else if (!vb->pending_frame_consumed) {
fps_counter_add_skipped_frame(vb->fps_counter);
}
video_buffer_swap_frames(vb);
video_buffer_swap_decoding_frame(vb);
*previous_frame_skipped = !vb->rendering_frame_consumed;
vb->rendering_frame_consumed = false;
*previous_frame_skipped = !vb->pending_frame_consumed;
vb->pending_frame_consumed = false;
mutex_unlock(vb->mutex);
sc_mutex_unlock(&vb->mutex);
}
const AVFrame *
video_buffer_consume_rendered_frame(struct video_buffer *vb) {
assert(!vb->rendering_frame_consumed);
vb->rendering_frame_consumed = true;
video_buffer_take_rendering_frame(struct video_buffer *vb) {
sc_mutex_lock(&vb->mutex);
assert(!vb->pending_frame_consumed);
vb->pending_frame_consumed = true;
fps_counter_add_rendered_frame(vb->fps_counter);
video_buffer_swap_rendering_frame(vb);
if (vb->render_expired_frames) {
// unblock video_buffer_offer_decoded_frame()
cond_signal(vb->rendering_frame_consumed_cond);
sc_cond_signal(&vb->pending_frame_consumed_cond);
}
sc_mutex_unlock(&vb->mutex);
// rendering_frame is only written from this thread, no need to lock
return vb->rendering_frame;
}
void
video_buffer_interrupt(struct video_buffer *vb) {
if (vb->render_expired_frames) {
mutex_lock(vb->mutex);
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,23 +1,45 @@
#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 decoder (decoding_frame)
* - one frame is held by the renderer (rendering_frame)
* - one frame is shared between the decoder and the renderer (pending_frame)
*
* The decoder decodes a packet into the decoding_frame (it may takes time).
*
* Once the frame is decoded, it calls video_buffer_offer_decoded_frame(),
* which swaps the decoding and pending frames.
*
* When the renderer is notified that a new frame is available, it calls
* video_buffer_take_rendering_frame() to retrieve it, which swaps the pending
* and rendering frames. The frame is valid until the next call, without
* blocking the decoder.
*/
struct video_buffer {
AVFrame *decoding_frame;
AVFrame *pending_frame;
AVFrame *rendering_frame;
SDL_mutex *mutex;
sc_mutex mutex;
bool render_expired_frames;
bool interrupted;
SDL_cond *rendering_frame_consumed_cond;
bool rendering_frame_consumed;
sc_cond pending_frame_consumed_cond;
bool pending_frame_consumed;
struct fps_counter *fps_counter;
};
@ -29,18 +51,15 @@ 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
void
video_buffer_offer_decoded_frame(struct video_buffer *vb,
bool *previous_frame_skipped);
// 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
// the frame is valid until the next call to this function
const AVFrame *
video_buffer_consume_rendered_frame(struct video_buffer *vb);
video_buffer_take_rendering_frame(struct video_buffer *vb);
// 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 = {
@ -73,7 +76,6 @@ 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);
@ -84,7 +86,7 @@ static void test_options(void) {
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);
@ -119,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)));
@ -54,15 +58,17 @@ static void test_serialize_inject_text_long(void) {
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_INJECT_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_INJECT_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_INJECT_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[] = {
@ -206,14 +212,14 @@ static void test_serialize_set_clipboard(void) {
},
};
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
int size = control_msg_serialize(&msg, buf);
assert(size == 17);
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,
1, // paste
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)));
@ -227,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[] = {
@ -243,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[] = {
@ -253,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.6.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

@ -15,6 +15,6 @@ cpu = 'i686'
endian = 'little'
[properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.2.2-win32-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.2.2-win32-dev'
prebuilt_sdl2 = 'SDL2-2.0.12/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.2-win64-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.2.2-win64-dev'
prebuilt_sdl2 = 'SDL2-2.0.12/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

View File

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

View File

@ -1,7 +1,6 @@
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')

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.2-win32-shared.zip \
ab5d603aaa54de360db2c2ffe378c82376b9343ea1175421dd644639aa07ee31 \
ffmpeg-4.2.2-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.2-win32-dev.zip \
8d224be567a2950cad4be86f4aabdd045bfa52ad758e87c72cedd278613bc6c8 \
ffmpeg-4.2.2-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.2-win64-shared.zip \
5aedf268952b7d9f6541dbfcb47cd86a7e7881a3b7ba482fd3bc4ca33bda7bf5 \
ffmpeg-4.2.2-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.2-win64-dev.zip \
f4885f859c5b0d6663c2a0a4c1cf035b1c60b146402790b796bd3ad84f4f3ca2 \
ffmpeg-4.2.2-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.12-mingw.tar.gz \
e614a60f797e35ef9f3f96aef3dc6a1d786de3cc7ca6216f97e435c0b6aafc46 \
SDL2-2.0.12
@./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_r30.0.0-windows.zip \
854305f9a702f5ea2c3de73edde402bd26afa0ee944c9b0c4380420f5a862e0d \
@./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.2-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.2.2-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.2.2-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.2.2-win32-shared/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.2.2-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.12/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.2-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.2.2-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.2.2-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.2.2-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.2.2-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.12/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

View File

@ -1,2 +1,2 @@
#!/bin/bash
#!/usr/bin/env bash
SCRCPY_SERVER_PATH="$MESON_BUILD_ROOT/server/scrcpy-server" "$MESON_BUILD_ROOT/app/scrcpy"

View File

@ -1,13 +1,13 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
compileSdkVersion 30
defaultConfig {
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
targetSdkVersion 29
versionCode 16
versionName "1.14"
targetSdkVersion 30
versionCode 20
versionName "1.17"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
@ -20,7 +20,7 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation 'junit:junit:4.12'
testImplementation 'junit:junit:4.13'
}
apply from: "$project.rootDir/config/android-checkstyle.gradle"

View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
#
# This script generates the scrcpy binary "manually" (without gradle).
#
@ -12,10 +12,10 @@
set -e
SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=1.14
SCRCPY_VERSION_NAME=1.17
PLATFORM=${ANDROID_PLATFORM:-29}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2}
PLATFORM=${ANDROID_PLATFORM:-30}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-30.0.0}
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
CLASSES_DIR="$BUILD_DIR/classes"
@ -42,6 +42,8 @@ echo "Generating java from aidl..."
cd "$SERVER_DIR/src/main/aidl"
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o"$CLASSES_DIR" \
android/view/IRotationWatcher.aidl
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o"$CLASSES_DIR" \
android/content/IOnPrimaryClipChangedListener.aidl
echo "Compiling java sources..."
cd ../java
@ -55,6 +57,7 @@ cd "$CLASSES_DIR"
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \
--output "$BUILD_DIR/classes.dex" \
android/view/*.class \
android/content/*.class \
com/genymobile/scrcpy/*.class \
com/genymobile/scrcpy/wrappers/*.class

View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
# Wrapper script to invoke gradle from meson
set -e

View File

@ -19,18 +19,19 @@ public final class CleanUp {
// not instantiable
}
public static void configure(boolean disableShowTouches, int restoreStayOn) throws IOException {
boolean needProcess = disableShowTouches || restoreStayOn != -1;
public static void configure(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode) throws IOException {
boolean needProcess = disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode;
if (needProcess) {
startProcess(disableShowTouches, restoreStayOn);
startProcess(disableShowTouches, restoreStayOn, restoreNormalPowerMode);
} else {
// There is no additional clean up to do when scrcpy dies
unlinkSelf();
}
}
private static void startProcess(boolean disableShowTouches, int restoreStayOn) throws IOException {
String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(disableShowTouches), String.valueOf(restoreStayOn)};
private static void startProcess(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode) throws IOException {
String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(disableShowTouches), String.valueOf(
restoreStayOn), String.valueOf(restoreNormalPowerMode)};
ProcessBuilder builder = new ProcessBuilder(cmd);
builder.environment().put("CLASSPATH", SERVER_PATH);
@ -59,6 +60,7 @@ public final class CleanUp {
boolean disableShowTouches = Boolean.parseBoolean(args[0]);
int restoreStayOn = Integer.parseInt(args[1]);
boolean restoreNormalPowerMode = Boolean.parseBoolean(args[2]);
if (disableShowTouches || restoreStayOn != -1) {
ServiceManager serviceManager = new ServiceManager();
@ -73,5 +75,12 @@ public final class CleanUp {
}
}
}
if (restoreNormalPowerMode) {
Ln.i("Restoring normal power mode");
if (Device.isScreenOn()) {
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
}
}
}
}

View File

@ -17,8 +17,6 @@ public final class ControlMessage {
public static final int TYPE_SET_SCREEN_POWER_MODE = 9;
public static final int TYPE_ROTATE_DEVICE = 10;
public static final int FLAGS_PASTE = 1;
private int type;
private String text;
private int metaState; // KeyEvent.META_*
@ -30,16 +28,18 @@ public final class ControlMessage {
private Position position;
private int hScroll;
private int vScroll;
private int flags;
private boolean paste;
private int repeat;
private ControlMessage() {
}
public static ControlMessage createInjectKeycode(int action, int keycode, int metaState) {
public static ControlMessage createInjectKeycode(int action, int keycode, int repeat, int metaState) {
ControlMessage msg = new ControlMessage();
msg.type = TYPE_INJECT_KEYCODE;
msg.action = action;
msg.keycode = keycode;
msg.repeat = repeat;
msg.metaState = metaState;
return msg;
}
@ -75,9 +75,7 @@ public final class ControlMessage {
ControlMessage msg = new ControlMessage();
msg.type = TYPE_SET_CLIPBOARD;
msg.text = text;
if (paste) {
msg.flags = FLAGS_PASTE;
}
msg.paste = paste;
return msg;
}
@ -141,7 +139,11 @@ public final class ControlMessage {
return vScroll;
}
public int getFlags() {
return flags;
public boolean getPaste() {
return paste;
}
public int getRepeat() {
return repeat;
}
}

View File

@ -8,20 +8,19 @@ import java.nio.charset.StandardCharsets;
public class ControlMessageReader {
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 9;
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13;
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1;
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4092; // 4096 - 1 (type) - 1 (parse flag) - 2 (length)
private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 6; // type: 1 byte; paste flag: 1 byte; length: 4 bytes
public static final int INJECT_TEXT_MAX_LENGTH = 300;
private static final int RAW_BUFFER_SIZE = 4096;
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];
private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE];
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
private final byte[] textBuffer = new byte[CLIPBOARD_TEXT_MAX_LENGTH];
public ControlMessageReader() {
// invariant: the buffer is always in "get" mode
@ -99,20 +98,23 @@ public class ControlMessageReader {
}
int action = toUnsigned(buffer.get());
int keycode = buffer.getInt();
int repeat = buffer.getInt();
int metaState = buffer.getInt();
return ControlMessage.createInjectKeycode(action, keycode, metaState);
return ControlMessage.createInjectKeycode(action, keycode, repeat, metaState);
}
private String parseString() {
if (buffer.remaining() < 2) {
if (buffer.remaining() < 4) {
return null;
}
int len = toUnsigned(buffer.getShort());
int len = buffer.getInt();
if (buffer.remaining() < len) {
return null;
}
buffer.get(textBuffer, 0, len);
return new String(textBuffer, 0, len, StandardCharsets.UTF_8);
int position = buffer.position();
// Move the buffer position to consume the text
buffer.position(position + len);
return new String(rawBuffer, position, len, StandardCharsets.UTF_8);
}
private ControlMessage parseInjectText() {
@ -152,12 +154,12 @@ public class ControlMessageReader {
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
return null;
}
boolean parse = buffer.get() != 0;
boolean paste = buffer.get() != 0;
String text = parseString();
if (text == null) {
return null;
}
return ControlMessage.createSetClipboard(text, parse);
return ControlMessage.createSetClipboard(text, paste);
}
private ControlMessage parseSetScreenPowerMode() {

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