Compare commits

..

82 Commits
v1.1 ... v1.3

Author SHA1 Message Date
Romain Vimont
7e42358a7b Bump version to 1.3 2018-08-09 19:14:17 +02:00
Romain Vimont
caa9e30004 Add crop feature
Add an option to crop the screen on the server. This allows to mirror
only part of the device screen.
2018-08-09 19:12:27 +02:00
Romain Vimont
e85010fbc2 Move annotation comment
This confused the Android Studio code formatter.
2018-08-09 18:23:38 +02:00
Romain Vimont
820cd2bb54 Extract video size computation
One method, one thing.
2018-08-09 18:22:52 +02:00
Romain Vimont
8793c104ee Increase "adb forward" connection attempts
5 seconds might not be sufficient:
<https://github.com/Genymobile/scrcpy/issues/213>

Increase to 10 seconds (it does not harm).
2018-08-09 18:22:42 +02:00
Romain Vimont
fca806e095 Do not call deprecated av_register_all()
av_register_all() is deprecated in FFmpeg since this commit:
<http://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=0694d8702421e7aff1340038559c438b61bb30dd>

It is now useless to call it:
<https://ffmpeg.org/pipermail/ffmpeg-devel/2018-February/225051.html>

Fixes <https://github.com/Genymobile/scrcpy/issues/203>.
2018-08-09 18:18:22 +02:00
Romain Vimont
280891837e Simplify README for Windows users
So that users stop downloading platform-tools unnecessarily.
2018-07-16 17:13:36 +02:00
Romain Vimont
49b2e63d13 Forward repeated volume events
Send repeated events when holding volume up/down shortcuts.
2018-06-24 21:51:54 +02:00
Romain Vimont
c12c64ed41 Send separate DOWN/UP key events
Shortcuts generated instant DOWN/UP key events. Instead, generate DOWN
event on Ctrl+key down and UP event on Ctrl+key up.

Fixes <https://github.com/Genymobile/scrcpy/issues/166>.
2018-06-24 21:50:53 +02:00
Romain Vimont
2f66acd75d Improve English comment
Replace "implying" by "involving" (both "impliquant" in French).
2018-06-24 20:46:14 +02:00
Romain Vimont
1846d2f078 Prevent killing unexpected process
A missing initialization (fixed by the previous commit) leaded to kill
unexpected process.

In order to prevent consequences of similar errors in the future, never
call kill() with a non-positive PID.

See <https://github.com/Genymobile/scrcpy/issues/182>.
2018-06-22 19:56:58 +02:00
Romain Vimont
1a0139321b Fix missing installer initialization
The current_process field of struct installer was not initialized.
Since the installer instance is static, its default value was 0.

The call to installer_stop() then called kill(0, SIGTERM) (on Linux),
which sent SIGTERM to every process in the process group. In particular,
the scrcpy process was killed.

As a consequence, the last cleanup steps, like disabling "show touches",
were not executed.

Fixes <https://github.com/Genymobile/scrcpy/issues/183>.
2018-06-22 18:35:58 +02:00
Romain Vimont
8890750681 Merge pull request #169 from zopelee/master
Fix meson error: ‘for’ loop initial declarations are only allowed in …
2018-06-08 08:08:00 +02:00
zack
aac9d5057f Fix meson error: ‘for’ loop initial declarations are only allowed in C99 mode. 2018-06-08 12:21:04 +08:00
Romain Vimont
f705a73149 Use a meson option to crossbuild for Windows
Meson decided to crossbuild for Windows as soon as
meson.is_cross_build() returned true. This made non-Windows crossbuilds
fail.

Instead, add an explicit option "crossbuild_windows".

Fixes <https://github.com/Genymobile/scrcpy/issues/165>.
2018-06-05 20:45:41 +02:00
Romain Vimont
27a9bd3424 Fix clean recipe in cross Makefile
Make the "clean" recipe also remove the noconsole build directories.
2018-06-05 20:42:35 +02:00
Romain Vimont
cf121a0f65 Upgrade gradle 2018-06-05 20:33:37 +02:00
Romain Vimont
c8a5f9dc63 Update links to v1.2 in README 2018-05-28 22:52:52 +02:00
Romain Vimont
8106bb8215 Bump version to 1.2 2018-05-28 22:32:14 +02:00
Romain Vimont
f1b3a40375 Rename SHA256SUM to SHA256SUMS
It contains the checksums for several files.
2018-05-28 22:32:14 +02:00
Romain Vimont
a63dd47f2d Make CreateProcess() flags depend on "noconsole"
On Windows, display the output of external commands (adb) when a console
is available.
2018-05-28 22:28:20 +02:00
Romain Vimont
6b4bbb1fb3 Update README and FAQ for the new Windows releases
Document how to generate a Windows release from Linux.

It solves the "no output" issue on Windows, so update the FAQ.
2018-05-28 20:45:54 +02:00
Romain Vimont
aedc2c2da9 Also build "noconsole" binary for Windows
On Windows, an application is either console or gui, it cannot be both.

Scrcpy should be both: it outputs important information to console, but
we still want to be able to ignore the console and launch it without a
visible cmd.exe window.

Therefore, build two binaries:
 - scrcpy.exe
 - scrcpy-noconsole.exe
2018-05-28 20:45:54 +02:00
Romain Vimont
106b87a4d2 Add cross-compilation scripts for Windows
Build the Windows binary from mingw on Linux, using the official
prebuilt binaries for ffmpeg, SDL2 and adb.

MSYS2 and all its packaged dll are not necessary anymore.
2018-05-28 20:45:54 +02:00
Romain Vimont
7d68316f45 Indicate that libs are included for Windows 2018-05-28 20:45:54 +02:00
Romain Vimont
e729b76176 Indicate that scrcpy also works over TCP/IP 2018-05-28 20:45:54 +02:00
Romain Vimont
6274cc3767 Document APK drag & drop
Add the drag & drop shortcut for installing an APK file to the README
and the help.
2018-05-28 20:45:54 +02:00
Romain Vimont
9c6f9b24f9 Quote apk path on Windows
Windows will parse the string, so the local name must be quoted.
2018-05-28 20:45:54 +02:00
Adonis Najimi
e2a2973990 Drag and drop to install apk files from computer
<https://github.com/Genymobile/scrcpy/pull/133>
2018-05-28 20:45:54 +02:00
Romain Vimont
41419a2e77 Fix net_send_all() warning
If len is not positive, w could be returned uninitialized.
2018-05-28 20:21:56 +02:00
Romain Vimont
e3d0a59c80 Fix proc_show_touches warning
In practice, proc_show_touches may not be used uninitialized, since it
checks the flag options->show_touches, but the compiler can't know that,
so initialize it to avoid the warning.
2018-05-28 20:21:50 +02:00
Romain Vimont
bb3a7f05ac Release controller lock while processing events
Once the controller took a control event, release the mutex before
processing it, so that the main thread is not blocked to push a new
event.
2018-05-26 15:14:46 +02:00
Romain Vimont
ec02823045 Fix leak on server start error
Fail on SDL_strdup() failure, and free the duplicated serial on further
error.
2018-05-26 14:20:05 +02:00
Romain Vimont
435a7fe314 Add missing includes
Include string.h and stdio.h explicitly.
2018-05-13 15:33:13 +02:00
Romain Vimont
78da66f126 Merge branch 'master' into dev 2018-04-08 12:41:51 +02:00
Romain Vimont
9aa88b6fc3 Map numpad ENTER key
Forward numpad ENTER key to the device.

Fixes <https://github.com/Genymobile/scrcpy/issues/117>.
2018-04-08 12:40:05 +02:00
Romain Vimont
f8ad7df3be Add FAQ section about KWin crash
Link to the workaround to keep the compositor enabled while _scrcpy_ is
running.
2018-04-05 02:03:26 +02:00
Romain Vimont
0871bca9c7 Avoid pointer arithmetic on "void *"
Fix the following warning (with -Wpedantic enabled):

    pointer of type ‘void *’ used in arithmetic [-Wpointer-arith]
2018-04-04 10:50:12 +02:00
Romain Vimont
b2b5404883 Use const pointers when possible
Sending data only require to read the input buffer, so declare it const.
2018-04-04 10:50:07 +02:00
Romain Vimont
1bddb80b5f Change volume shortcuts
I could not make Ctrl+'+' and Ctrl+'-' work for every keyboard on every
platform.

Instead, use Ctrl+UP and Ctrl+DOWN (like in VLC) to change the volume.

Fixes <https://github.com/Genymobile/scrcpy/issues/103>.
2018-04-03 14:25:11 +02:00
Romain Vimont
6323f3974f Document 32 bits packages Windows in README
To build for Windows 32 bits, use the i686 packages instead.
2018-03-31 11:07:25 +02:00
Romain Vimont
16a3de1796 Make checkstyle happy
Reorder the imports to remove checkstyle warnings.
2018-03-28 22:05:34 +02:00
Romain Vimont
6161f7688c Install on macOS via Homebrew in README
The application is now packaged for Homebrew:
<https://github.com/Homebrew/homebrew-core/pull/25173>

Give instructions to install it from Homebrew for macOS (it's much
easier).

Thanks to @stek29 for the formula ;-)
2018-03-28 14:14:56 +02:00
Romain Vimont
2a02fb3611 Document how to make a portable build on Windows
On MSYS2, ./gradlew does not work as expected, so use its absolute path.
2018-03-28 11:08:01 +02:00
Romain Vimont
71f50fb697 Merge branch 'master' into dev 2018-03-28 10:45:23 +02:00
Romain Vimont
82efff34e8 Factorize texture creation
SDL_CreateTexture() is called both during initialization and on frame
size change.

To avoid inconsistent changes to arguments value, factorize them to a
single function create_texture().
2018-03-27 11:01:40 +02:00
Romain Vimont
860006e082 Forward double-click events
Double-clicks were not sent to the device anymore since the
"double-click on black borders" feature.

When a double click occurs inside the device screen, send the event to
the device normally.

Fixes <https://github.com/Genymobile/scrcpy/issues/97>.
2018-03-26 14:49:10 +02:00
Romain Vimont
57eaf05289 Improve startup time when show_touches is enabled
Enabling "show touches" involves the execution of an adb command, which
takes some time.

In order to parallelize, execute the command as soon as possible, but
reap the process only once everything is initialized.
2018-03-25 16:39:36 +02:00
Romain Vimont
dd2a5c1ecf Disable "show touches" once window is closed
If --show-touches is set, then the option must be disabled on quit.

Since it executes an adb command, it takes some time, so close the
window beforehand so that the close window button does not seem
unresponsive.
2018-03-25 16:03:02 +02:00
Romain Vimont
66ec252893 Add an option to enable "show touches"
Add -t/--show-touches option to show physical touches while scrcpy is
running.

See <https://github.com/Genymobile/scrcpy/issues/96>.
2018-03-25 15:43:27 +02:00
Romain Vimont
b13d25b9f4 Group scrcpy options into a struct
The scrcpy() function accepts as many parameters as there are options.

To simplify, group all options in a separate struct.
2018-03-25 15:16:29 +02:00
Romain Vimont
b449c09442 Merge branch 'master' into dev 2018-03-25 15:16:19 +02:00
Romain Vimont
8b84492830 Merge branch 'stek29/macos' (#56)
macOS specific README changes
2018-03-25 14:31:02 +02:00
Viktor Oreshkin
4d50832f8e Add instructions to install Java 8 on macOS
And remove gcc from the packages list, clang is available by default.
2018-03-25 14:29:22 +02:00
Romain Vimont
e0e8dfeb3b Merge pull request #94 from pierlon/pierlon-patch-1
Add instructions to run via Docker
2018-03-25 14:12:29 +02:00
Romain Vimont
f4d6449af7 Merge pull request #95 from Sea-n/patch-1
Update README.md
2018-03-25 14:00:20 +02:00
Sean
64963fff62 Update README.md
Fix Typo
2018-03-25 17:51:29 +08:00
Pierre Gordon
b7d9b8739c Add instructions to run via Docker 2018-03-24 23:15:14 -05:00
Romain Vimont
88f6b43c8f Merge pull request #93 from CampbellOwen/patch-1
Add links to FFmpeg and LibSDL2 dependencies
2018-03-24 09:26:12 +01:00
Owen Campbell
324a264233 Change links to wikipedia 2018-03-24 01:09:41 -07:00
Owen Campbell
3bb2cda955 Add links to FFmpeg and LibSDL2 dependencies 2018-03-24 00:55:05 -07:00
Romain Vimont
35298bb0c6 Process the last video frame
On H.264 stream EOF, the eof_reached flag is set, but av_read_frame()
still provides a frame, so check the flag only afterwards.

As a side-effect, it also fixes a memory leak (the very last packet was
not unref).
2018-03-23 14:01:58 +01:00
Romain Vimont
73c332e3e4 Unref last packet on exit 2018-03-23 13:57:32 +01:00
Romain Vimont
15014f2689 Clarify adb requirements
Since _scrcpy_ also supports `adb forward`, remove the part about `adb
reverse`.

Make explicit that _adb_ is included in the prebuilt application for
Windows (many users manually download the platform-tools for no reason).
2018-03-23 10:55:52 +01:00
Romain Vimont
29b5c5b8f4 Merge branch 'arich/addShake' into dev (#85)
Add support for CTRL+S to send hardware "shake" to device
2018-03-23 10:13:19 +01:00
Romain Vimont
88ee6bc928 Swap MENU and APP_SWITCH shortcuts
Ctrl+s was mapped to MENU, while Ctrl+m was mapped to APP_SWITCH.

To avoid confusion, swap the shortcuts:
 - Ctrl+m like _M_enu
 - Ctrl+s like _S_witch
2018-03-23 10:10:24 +01:00
Romain Vimont
35e9a64c34 Rename "shake" to "menu"
The action sends AKEYCODE_MENU, so just name it "menu".

See <https://github.com/Genymobile/scrcpy/pull/85>.
2018-03-23 10:07:48 +01:00
Romain Vimont
9cac38fe58 Describe workaround to get output on Windows
Since nothing is printed to the console, we need a way to get the output
in case of errors.

Describe how in the README.
2018-03-23 09:57:45 +01:00
Andy Rich
301c52b603 Add support for CTRL+S to send hardware "shake" to device w/readme 2018-03-22 16:15:24 -07:00
Romain Vimont
f00c6c5b13 Disable custom SDL signal handlers
Request SDL not to replace the SIGINT and SIGTERM handlers, so that the
process is immediately terminated on Ctrl+C.

This avoids process hanging on Ctrl+C during network calls on
initialization.

Some of them accepted a timeout, but it was not used since
commit 9b056f5091 anymore.
2018-03-21 21:43:12 +01:00
Romain Vimont
3b3803da0d Remove useless blocks in switch/case
Remove unnecessary additional blocks.
2018-03-21 11:14:15 +01:00
Romain Vimont
f5cf6c1b2c Include source root directory
All headers and sources are in src/. To avoid using relative includes
from subdirectories ("../../"), include the source root directory.
2018-03-20 21:32:41 +01:00
Romain Vimont
2573df9727 Document the step to clone the project
This is not obvious to everyone, especially non-developers.
2018-03-18 12:10:06 +01:00
Romain Vimont
c65cb36d3b Increase the number of connection attempts
In "adb forward" mode, it may take a while before the server socket is
listening, so increase the number of connection attempts.

See <https://github.com/Genymobile/scrcpy/issues/5#issuecomment-373718551>.
2018-03-16 14:59:08 +01:00
Romain Vimont
821ec9843c Fix win32 build
The types size_t and ssize_t are defined on Windows (in MSYS2), so there
is no need to typedef SIZE_T and SSIZE_T.

Exit code is "unsigned long" both on Windows 32 and 64 bits.

See <https://github.com/Genymobile/scrcpy/issues/46#issuecomment-373603596>.
2018-03-16 08:58:59 +01:00
Romain Vimont
f16bd88802 Remove useless cast
For consistency with mouse button events handling, directly assign from
Sint32 to Uint16.
2018-03-15 16:30:51 +01:00
Romain Vimont
f3e8834a3c Fix warning message
Mouse "wheel button" is meaningless :)
2018-03-15 16:14:40 +01:00
Romain Vimont
080df5eb5d Fix switch/case code style
For readability and consistency, indent case statatements, and remove
unnecessary additional blocks.
2018-03-15 16:00:40 +01:00
Romain Vimont
047179f232 Add FAQ section about mouse clicks
On some devices, mouse clicks do not work by default. Enabling an option
is required.
2018-03-15 09:29:14 +01:00
Romain Vimont
2992dda497 Add link to the article for v1.1 in README 2018-03-14 18:03:34 +01:00
Romain Vimont
6406f64edc Update FAQ after v1.1 release
Two issues described in the FAQ have been fixed by V1.1. Remove them
from the FAQ.
2018-03-14 09:56:36 +01:00
Romain Vimont
f3f19d4e1d Update links to v1.1 in README 2018-03-14 09:53:55 +01:00
45 changed files with 1108 additions and 317 deletions

49
FAQ.md
View File

@@ -7,18 +7,8 @@ with it.
Here are the common reported problems and their status.
### On Windows, I have no output in the console
When run in `cmd.exe`, the application does not print anything. Even `scrcpy
--help` have no output. We don't know why yet.
However, if you run the very same `scrcpy.exe` from
[MSYS2](https://www.msys2.org/) (`mingw64`), then it correctly prints output.
### On Windows, when I start the application, nothing happens
The previous problem does not help to get a clue about the cause.
### On Windows, my device is not detected
The most common is your device not being detected by `adb`, or is unauthorized.
Check everything is ok by calling:
@@ -34,27 +24,6 @@ If you still encounter problems, please see [issue 9].
[issue 9]: https://github.com/Genymobile/scrcpy/issues/9
### The application does not work over `adb connect`
If the device is connected using `adb connect`, then you'll get this error:
error: more than one device/emulator
ERROR: "adb reverse" returned with value 1
We plan to support it in future versions. See [issue 5].
[issue 5]: https://github.com/Genymobile/scrcpy/issues/5
### Mouse clicks do not work
On many LG devices, [mouse clicks do not work][issue 18]. We're [working][pr27]
on it.
[issue 18]: https://github.com/Genymobile/scrcpy/issues/18
[pr27]: https://github.com/Genymobile/scrcpy/pull/27
### I get a black screen for some applications like Silence
This is expected, they requested to protect the screen.
@@ -68,6 +37,13 @@ See [issue 36].
[issue 36]: https://github.com/Genymobile/scrcpy/issues/36
### Mouse clicks do not work
On some devices, you may need to enable an option to allow [simulating input].
[simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
### Mouse clicks at wrong location
On MacOS, with HiDPI support and multiple screens, input location are wrongly
@@ -82,3 +58,12 @@ meson x --buildtype release -Dhidpi_support=false
```
However, the video will be displayed at lower resolution.
### KWin compositor crashes
On Plasma Desktop, compositor is disabled while _scrcpy_ is running.
As a workaround, [disable "Block compositing"][kwin].
[kwin]: https://github.com/Genymobile/scrcpy/issues/114#issuecomment-378778613

View File

@@ -1,62 +0,0 @@
# This makefile provides recipes to build a "portable" version of scrcpy.
#
# Here, "portable" means that the client and server binaries are expected to be
# anywhere, but in the same directory, instead of well-defined separate
# locations (e.g. /usr/bin/scrcpy and /usr/share/scrcpy/scrcpy-server.jar).
#
# In particular, this implies to change the location from where the client push
# the server to the device.
#
# "make release-portable" builds a zip containing the client and the server.
#
# This is a simple Makefile because Meson is not flexible enough to execute some
# arbitrary commands.
.PHONY: default clean build-portable release-portable dist-portable dist-portable-zip sums test check
GRADLE ?= ./gradlew
PORTABLE_BUILD_DIR := build-portable
DIST := dist
TARGET_DIR := scrcpy
VERSION := $(shell git describe --tags --always)
TARGET := $(TARGET_DIR)-$(VERSION).zip
default:
@echo 'You must specify a target. Try: make release-portable'
clean:
$(GRADLE) clean
rm -rf "$(PORTABLE_BUILD_DIR)" "$(DIST)"
build-portable:
[ -d "$(PORTABLE_BUILD_DIR)" ] || ( mkdir "$(PORTABLE_BUILD_DIR)" && \
meson "$(PORTABLE_BUILD_DIR)" \
--buildtype release --strip -Db_lto=true \
-Doverride_server_path=scrcpy-server.jar )
ninja -C "$(PORTABLE_BUILD_DIR)"
release-portable: clean dist-portable-zip sums
@echo "Release created in $(DIST)/."
dist-portable: build-portable
mkdir -p "$(DIST)/$(TARGET_DIR)"
cp "$(PORTABLE_BUILD_DIR)"/server/scrcpy-server.jar "$(DIST)/$(TARGET_DIR)/"
cp "$(PORTABLE_BUILD_DIR)"/app/scrcpy "$(DIST)/$(TARGET_DIR)/"
dist-portable-zip: dist-portable
cd "$(DIST)"; \
zip -r "$(TARGET)" "$(TARGET_DIR)"
sums:
cd "$(DIST)"; \
sha256sum *.zip > SHA256SUM.txt
test: build-portable
$(GRADLE) test
ninja -C "$(PORTABLE_BUILD_DIR)" test
check: build-portable
$(GRADLE) check
ninja -C "$(PORTABLE_BUILD_DIR)" test

136
Makefile.CrossWindows Normal file
View File

@@ -0,0 +1,136 @@
# This makefile provides recipes to build a "portable" version of scrcpy for
# Windows.
#
# Here, "portable" means that the client and server binaries are expected to be
# anywhere, but in the same directory, instead of well-defined separate
# locations (e.g. /usr/bin/scrcpy and /usr/share/scrcpy/scrcpy-server.jar).
#
# In particular, this implies to change the location from where the client push
# the server to the device.
.PHONY: default clean \
build-server \
prepare-deps-win32 prepare-deps-win64 \
build-win32 build-win32-noconsole \
build-win64 build-win64-noconsole \
dist-win32 dist-win64 \
zip-win32 zip-win64 \
sums release
GRADLE ?= ./gradlew
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
WIN64_TARGET_DIR := scrcpy-win64
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 "Release created in $(DIST)/."
clean:
$(GRADLE) clean
rm -rf "$(SERVER_BUILD_DIR)" "$(WIN32_BUILD_DIR)" "$(WIN64_BUILD_DIR)" \
"$(WIN32_NOCONSOLE_BUILD_DIR)" "$(WIN64_NOCONSOLE_BUILD_DIR)" "$(DIST)"
build-server:
[ -d "$(SERVER_BUILD_DIR)" ] || ( mkdir "$(SERVER_BUILD_DIR)" && \
meson "$(SERVER_BUILD_DIR)" \
--buildtype release -Dbuild_app=false )
ninja -C "$(SERVER_BUILD_DIR)"
prepare-deps-win32:
-$(MAKE) -C prebuilt-deps prepare-win32
build-win32: prepare-deps-win32
[ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \
meson "$(WIN32_BUILD_DIR)" \
--cross-file cross_win32.txt \
--buildtype release --strip -Db_lto=true \
-Dcrossbuild_windows=true \
-Dbuild_server=false \
-Doverride_server_path=scrcpy-server.jar )
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 \
-Dbuild_server=false \
-Dwindows_noconsole=true \
-Doverride_server_path=scrcpy-server.jar )
ninja -C "$(WIN32_NOCONSOLE_BUILD_DIR)"
prepare-deps-win64:
-$(MAKE) -C prebuilt-deps prepare-win64
build-win64: prepare-deps-win64
[ -d "$(WIN64_BUILD_DIR)" ] || ( mkdir "$(WIN64_BUILD_DIR)" && \
meson "$(WIN64_BUILD_DIR)" \
--cross-file cross_win64.txt \
--buildtype release --strip -Db_lto=true \
-Dcrossbuild_windows=true \
-Dbuild_server=false \
-Doverride_server_path=scrcpy-server.jar )
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 \
-Dbuild_server=false \
-Dwindows_noconsole=true \
-Doverride_server_path=scrcpy-server.jar )
ninja -C "$(WIN64_NOCONSOLE_BUILD_DIR)"
dist-win32: build-server build-win32 build-win32-noconsole
mkdir -p "$(DIST)/$(WIN32_TARGET_DIR)"
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server.jar "$(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.0-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0-win32-shared/bin/swresample-3.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.8/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
dist-win64: build-server build-win64 build-win64-noconsole
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server.jar "$(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.0-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0-win64-shared/bin/swresample-3.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.8/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
zip-win32: dist-win32
cd "$(DIST)"; \
zip -r "$(WIN32_TARGET)" "$(WIN32_TARGET_DIR)"
zip-win64: dist-win64
cd "$(DIST)"; \
zip -r "$(WIN64_TARGET)" "$(WIN64_TARGET_DIR)"
sums:
cd "$(DIST)"; \
sha256sum *.zip > SHA256SUMS.txt

158
README.md
View File

@@ -1,8 +1,8 @@
# scrcpy
# scrcpy (v1.2)
This application provides display and control of Android devices connected on
USB. It does not require any _root_ access. It works on _GNU/Linux_, _Windows_
and _MacOS_.
USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access.
It works on _GNU/Linux_, _Windows_ and _MacOS_.
![screenshot](assets/screenshot-debian-600.jpg)
@@ -11,25 +11,22 @@ and _MacOS_.
The Android part requires at least API 21 (Android 5.0).
You need [adb] (recent enough so that `adb reverse` is implemented, it works
with 1.0.36). It is available in the [Android SDK platform
tools][platform-tools], on packaged in your distribution (`android-adb-tools`).
You need [adb]. It is available in the [Android SDK platform
tools][platform-tools], or packaged in your distribution (`android-adb-tools`).
On Windows, just download the [platform-tools][platform-tools-windows] and
extract the following files to a directory accessible from your `PATH`:
- `adb.exe`
- `AdbWinApi.dll`
- `AdbWinUsbApi.dll`
On Windows, just [download scrcpy for Windows](#windows), `adb` is included.
Make sure you [enabled adb debugging][enable-adb] on your device(s).
The client requires [FFmpeg] and [LibSDL2]. On Windows, they are included in the
[prebuilt application](#windows).
[adb]: https://developer.android.com/studio/command-line/adb.html
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
[platform-tools]: https://developer.android.com/studio/releases/platform-tools.html
[platform-tools-windows]: https://dl.google.com/android/repository/platform-tools-latest-windows.zip
The client requires _FFmpeg_ and _LibSDL2_.
[ffmpeg]: https://en.wikipedia.org/wiki/FFmpeg
[LibSDL2]: https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer
## Build and install
@@ -79,18 +76,54 @@ Two [AUR] packages have been created by users:
#### Windows
For Windows, for simplicity, a prebuilt archive with all the dependencies
(including `adb`) is available:
For Windows, for simplicity, prebuilt archives with all the dependencies
(including `adb`) are available:
- [`scrcpy-windows-with-deps-v1.0.zip`][direct-windows-with-deps].
_(SHA-256: bc4bf32600e8548cdce490f94bed5dcba0006cdd38aff95748972e5d9877dd62)_
- [`scrcpy-win32-v1.2.zip`][direct-win32].
_(SHA-256: a1fe1de67ec75dcf970ca5d97a04c26ff0f2d61871f2ef51b6f2f0bf666966b2)_
- [`scrcpy-win64-v1.2.zip`][direct-win64].
_(SHA-256: 35ae3bcee51771e7c51b8a8be87aef2295c9f267606a7cf83ebb0a4d583ef536)_
[direct-windows-with-deps]: https://github.com/Genymobile/scrcpy/releases/download/v1.0/scrcpy-windows-with-deps-v1.0.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.2/scrcpy-win32-v1.2.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.2/scrcpy-win64-v1.2.zip
_(It's just a portable version including _dll_ copied from MSYS2.)_
Instead, you may want to build it manually.
Instead, you may want to build it manually. You need [MSYS2] to build the
project. From an MSYS2 terminal, install the required packages:
In that case, download the [platform-tools][platform-tools-windows] and extract
the following files to a directory accessible from your `PATH`:
- `adb.exe`
- `AdbWinApi.dll`
- `AdbWinUsbApi.dll`
##### Cross-compile from Linux
This is the preferred method (and the way the release is built).
From _Debian_, install _mingw_:
```bash
sudo apt install mingw-w64 mingw-w64-tools
```
You also need the JDK to build the server:
```bash
sudo apt install openjdk-8-jdk
```
Then generate the releases:
```bash
make -f Makefile.CrossWindows
```
It will generate win32 and win64 releases into `dist/`.
##### In MSYS2
From Windows, you need [MSYS2] to build the project. From an MSYS2 terminal,
install the required packages:
[MSYS2]: http://www.msys2.org/
@@ -106,6 +139,20 @@ pacman -S mingw-w64-x86_64-make \
mingw-w64-x86_64-meson
```
For a 32 bits version, replace `x86_64` by `i686`:
```bash
# runtime dependencies
pacman -S mingw-w64-i686-SDL2 \
mingw-w64-i686-ffmpeg
# client build dependencies
pacman -S mingw-w64-i686-make \
mingw-w64-i686-gcc \
mingw-w64-i686-pkg-config \
mingw-w64-i686-meson
```
Java (>= 7) is not available in MSYS2, so if you plan to build the server,
install it manually and make it available from the `PATH`:
@@ -115,25 +162,39 @@ export PATH="$JAVA_HOME/bin:$PATH"
#### Mac OS
Use [Homebrew] to install the packages:
The application is available in [Homebrew]. Just install it:
[Homebrew]: https://brew.sh/
```bash
brew install scrcpy
```
Instead, you may want to build it manually. Install the packages:
```bash
# runtime dependencies
brew install sdl2 ffmpeg
# client build dependencies
brew install gcc pkg-config meson
brew install pkg-config meson
```
Java (>= 7) is not available in Homebrew, so if you plan to build the server,
install it manually and make it available from the `PATH`:
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
export JAVA_HOME="$(/usr/libexec/java_home --version 1.8)"
export PATH="$JAVA_HOME/bin:$PATH"
```
#### Docker
See [pierlon/scrcpy-docker](https://github.com/pierlon/scrcpy-docker).
### Common steps
Install the [Android SDK] (_Android Studio_), and set `ANDROID_HOME` to
@@ -145,7 +206,14 @@ its directory. For example:
export ANDROID_HOME=~/android/sdk
```
Then, build `scrcpy`:
Clone the project:
```bash
git clone https://github.com/Genymobile/scrcpy
cd scrcpy
```
Then, build:
```bash
meson x --buildtype release --strip -Db_lto=true
@@ -179,10 +247,10 @@ Since the server binary, that will be pushed to the Android device, does not
depend on your system and architecture, you may want to use the prebuilt binary
instead:
- [`scrcpy-server-v1.0.jar`][direct-scrcpy-server].
_(SHA-256: b573b06a6072476b85b6308e3ad189f2665ad5be4f8ca3a6b9ec81d64df20558)_
- [`scrcpy-server-v1.2.jar`][direct-scrcpy-server].
_(SHA-256: cb39654ed2fda3d30ddff292806950ccc5c394375ea12b974f790c7f38f61f60)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.0/scrcpy-server-v1.0.jar
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.2/scrcpy-server-v1.2.jar
In that case, the build does not require Java or the Android SDK.
@@ -226,12 +294,24 @@ screen is smaller, or cannot decode such a high definition):
scrcpy -m 1024
```
The device screen may be cropped to mirror only part of the screen:
```bash
scrcpy -c 1224:1440:0:0 # 1224x1440 at offset (0,0)
```
If several devices are listed in `adb devices`, you must specify the _serial_:
```bash
scrcpy -s 0123456789abcdef
```
To show physical touches while scrcpy is running:
```bash
scrcpy -t
```
To run without installing:
```bash
@@ -250,13 +330,15 @@ To run without installing:
| resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ |
| click on `HOME` | `Ctrl`+`h` \| _Middle-click_ |
| click on `BACK` | `Ctrl`+`b` \| _Right-click²_ |
| click on `APP_SWITCH` | `Ctrl`+`m` |
| click on `VOLUME_UP` | `Ctrl`+`+` |
| click on `VOLUME_DOWN` | `Ctrl`+`-` |
| click on `APP_SWITCH` | `Ctrl`+`s` |
| click on `MENU` | `Ctrl`+`m` |
| click on `VOLUME_UP` | `Ctrl`+`` _(up)_ |
| click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ |
| click on `POWER` | `Ctrl`+`p` |
| turn screen on | _Right-click¹_ |
| turn screen on | _Right-click²_ |
| paste computer clipboard to device | `Ctrl`+`v` |
| enable/disable FPS counter (on stdout) | `Ctrl`+`i` |
| install APK from computer | drag & drop APK file |
_¹Double-click on black borders to remove them._
_²Right-click turns the screen on if it was off, presses BACK otherwise._
@@ -300,6 +382,10 @@ Read the [developers page].
See the License for the specific language governing permissions and
limitations under the License.
## Article
## Articles
- [Introducing scrcpy](https://blog.rom1v.com/2018/03/introducing-scrcpy/)
- [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

@@ -9,6 +9,7 @@ src = [
'src/fpscounter.c',
'src/frames.c',
'src/inputmanager.c',
'src/installer.c',
'src/lockutil.c',
'src/net.c',
'src/scrcpy.c',
@@ -18,12 +19,54 @@ src = [
'src/tinyxpm.c',
]
dependencies = [
dependency('libavformat'),
dependency('libavcodec'),
dependency('libavutil'),
dependency('sdl2'),
]
if not get_option('crossbuild_windows')
# native build
dependencies = [
dependency('libavformat'),
dependency('libavcodec'),
dependency('libavutil'),
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'
sdl2_include_dir = '../prebuilt-deps/' + prebuilt_sdl2 + '/include'
sdl2 = declare_dependency(
dependencies: [
cc.find_library('SDL2', dirs: sdl2_bin_dir),
cc.find_library('SDL2main', dirs: sdl2_lib_dir),
],
include_directories: include_directories(sdl2_include_dir)
)
prebuilt_ffmpeg_shared = meson.get_cross_property('prebuilt_ffmpeg_shared')
prebuilt_ffmpeg_dev = meson.get_cross_property('prebuilt_ffmpeg_dev')
ffmpeg_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_ffmpeg_shared + '/bin'
ffmpeg_include_dir = '../prebuilt-deps/' + prebuilt_ffmpeg_dev + '/include'
ffmpeg = declare_dependency(
dependencies: [
cc.find_library('avcodec-58', dirs: ffmpeg_bin_dir),
cc.find_library('avformat-58', dirs: ffmpeg_bin_dir),
cc.find_library('avutil-56', dirs: ffmpeg_bin_dir),
],
include_directories: include_directories(ffmpeg_include_dir)
)
dependencies = [
ffmpeg,
sdl2,
cc.find_library('mingw32')
]
endif
cc = meson.get_compiler('c')
@@ -42,7 +85,7 @@ conf = configuration_data()
conf.set('BUILD_DEBUG', get_option('buildtype') == 'debug')
# the version, updated on release
conf.set_quoted('SCRCPY_VERSION', '1.1')
conf.set_quoted('SCRCPY_VERSION', '1.3')
# the prefix used during configuration (meson --prefix=PREFIX)
conf.set_quoted('PREFIX', get_option('prefix'))
@@ -83,9 +126,22 @@ conf.set('SKIP_FRAMES', get_option('skip_frames'))
# enable High DPI support
conf.set('HIDPI_SUPPORT', get_option('hidpi_support'))
# disable console on Windows
conf.set('WINDOWS_NOCONSOLE', get_option('windows_noconsole'))
configure_file(configuration: conf, output: 'config.h')
executable('scrcpy', src, dependencies: dependencies, install: true)
src_dir = include_directories('src')
if get_option('windows_noconsole')
c_args = [ '-mwindows' ]
link_args = [ '-mwindows' ]
else
c_args = []
link_args = []
endif
executable('scrcpy', src, dependencies: dependencies, include_directories: src_dir, install: true, c_args: c_args, link_args: link_args)
### TESTS
@@ -96,8 +152,6 @@ tests = [
['test_strutil', ['tests/test_strutil.c', 'src/strutil.c']],
]
src_dir = include_directories('src')
foreach t : tests
exe = executable(t[0], t[1], include_directories: src_dir, dependencies: dependencies)
test(t[0], exe)

View File

@@ -4,10 +4,9 @@
#include <stdlib.h>
#include <string.h>
#include "common.h"
#include "log.h"
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
static const char *adb_command;
static inline const char *get_adb_command() {
@@ -73,6 +72,21 @@ process_t adb_push(const char *serial, const char *local, const char *remote) {
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
process_t adb_install(const char *serial, const char *local) {
#ifdef __WINDOWS__
// Windows will parse the string, so the local name must be quoted (see sys/win/command.c)
size_t len = strlen(local);
char quoted[len + 3];
memcpy(&quoted[1], local, len);
quoted[0] = '"';
quoted[len + 1] = '"';
quoted[len + 2] = '\0';
local = quoted;
#endif
const char *const adb_cmd[] = {"install", "-r", local};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
process_t adb_remove_path(const char *serial, const char *path) {
const char *const adb_cmd[] = {"shell", "rm", path};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));

View File

@@ -7,12 +7,11 @@
// <https://stackoverflow.com/a/44383330/1987178>
#ifdef _WIN32
# define PRIexitcode "lu"
# ifdef _WIN64
# define PRIsizet PRIu64
# define PRIexitcode "lu"
# else
# define PRIsizet PRIu32
# define PRIexitcode "u"
# endif
#else
# define PRIsizet "zu"
@@ -43,6 +42,7 @@ 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);
process_t adb_remove_path(const char *serial, const char *path);
// convenience function to wait for a successful process execution

View File

@@ -3,6 +3,7 @@
#include <SDL2/SDL_stdinc.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)

View File

@@ -1,6 +1,7 @@
#include "controlevent.h"
#include <SDL2/SDL_stdinc.h>
#include <string.h>
#include "lockutil.h"
#include "log.h"

View File

@@ -54,27 +54,32 @@ static SDL_bool process_event(struct controller *controller, const struct contro
static int run_controller(void *data) {
struct controller *controller = data;
mutex_lock(controller->mutex);
for (;;) {
mutex_lock(controller->mutex);
while (!controller->stopped && control_event_queue_is_empty(&controller->queue)) {
cond_wait(controller->event_cond, controller->mutex);
}
if (controller->stopped) {
// stop immediately, do not process further events
mutex_unlock(controller->mutex);
break;
}
struct control_event event;
while (control_event_queue_take(&controller->queue, &event)) {
SDL_bool ok = process_event(controller, &event);
control_event_destroy(&event);
if (!ok) {
LOGD("Cannot write event to socket");
goto end;
}
#ifdef BUILD_DEBUG
bool non_empty = control_event_queue_take(&controller->queue, &event);
SDL_assert(non_empty);
#else
control_event_queue_take(&controller->queue, &event);
#endif
mutex_unlock(controller->mutex);
SDL_bool ok = process_event(controller, &event);
control_event_destroy(&event);
if (!ok) {
LOGD("Cannot write event to socket");
break;
}
}
end:
mutex_unlock(controller->mutex);
return 0;
}

View File

@@ -73,6 +73,7 @@ static enum android_metastate convert_meta_state(SDL_Keymod mod) {
static SDL_bool convert_keycode(SDL_Keycode from, enum android_keycode *to) {
switch (from) {
MAP(SDLK_RETURN, AKEYCODE_ENTER);
MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER);
MAP(SDLK_ESCAPE, AKEYCODE_ESCAPE);
MAP(SDLK_BACKSPACE, AKEYCODE_DEL);
MAP(SDLK_TAB, AKEYCODE_TAB);
@@ -145,8 +146,8 @@ SDL_bool mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
to->mouse_event.buttons = convert_mouse_buttons(SDL_BUTTON(from->button));
to->mouse_event.position.screen_size = screen_size;
to->mouse_event.position.point.x = (Uint16) from->x;
to->mouse_event.position.point.y = (Uint16) from->y;
to->mouse_event.position.point.x = from->x;
to->mouse_event.position.point.y = from->y;
return SDL_TRUE;
}

View File

@@ -91,7 +91,7 @@ static int run_decoder(void *data) {
packet.data = NULL;
packet.size = 0;
while (!av_read_frame(format_ctx, &packet) && !avio_ctx->eof_reached) {
while (!av_read_frame(format_ctx, &packet)) {
// the new decoding/encoding API has been introduced by:
// <http://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=7fc329e2dd6226dfecaa4a1d7adf353bf2773726>
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 0)
@@ -125,6 +125,10 @@ static int run_decoder(void *data) {
}
#endif
av_packet_unref(&packet);
if (avio_ctx->eof_reached) {
break;
}
}
LOGD("End of frames");

View File

@@ -29,53 +29,58 @@ static struct point get_mouse_point(struct screen *screen) {
};
}
static SDL_bool is_ctrl_down(void) {
const Uint8 *state = SDL_GetKeyboardState(NULL);
return state[SDL_SCANCODE_LCTRL] || state[SDL_SCANCODE_RCTRL];
}
static const int ACTION_DOWN = 1;
static const int ACTION_UP = 1 << 1;
static void send_keycode(struct controller *controller, enum android_keycode keycode, const char *name) {
static void send_keycode(struct controller *controller, enum android_keycode keycode, int actions, const char *name) {
// send DOWN event
struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_KEYCODE;
control_event.keycode_event.action = AKEY_EVENT_ACTION_DOWN;
control_event.keycode_event.keycode = keycode;
control_event.keycode_event.metastate = 0;
if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot send %s (DOWN)", name);
return;
if (actions & ACTION_DOWN) {
control_event.keycode_event.action = AKEY_EVENT_ACTION_DOWN;
if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot send %s (DOWN)", name);
return;
}
}
// send UP event
control_event.keycode_event.action = AKEY_EVENT_ACTION_UP;
if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot send %s (UP)", name);
if (actions & ACTION_UP) {
control_event.keycode_event.action = AKEY_EVENT_ACTION_UP;
if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot send %s (UP)", name);
}
}
}
static inline void action_home(struct controller *controller) {
send_keycode(controller, AKEYCODE_HOME, "HOME");
static inline void action_home(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_HOME, actions, "HOME");
}
static inline void action_back(struct controller *controller) {
send_keycode(controller, AKEYCODE_BACK, "BACK");
static inline void action_back(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_BACK, actions, "BACK");
}
static inline void action_app_switch(struct controller *controller) {
send_keycode(controller, AKEYCODE_APP_SWITCH, "APP_SWITCH");
static inline void action_app_switch(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_APP_SWITCH, actions, "APP_SWITCH");
}
static inline void action_power(struct controller *controller) {
send_keycode(controller, AKEYCODE_POWER, "POWER");
static inline void action_power(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_POWER, actions, "POWER");
}
static inline void action_volume_up(struct controller *controller) {
send_keycode(controller, AKEYCODE_VOLUME_UP, "VOLUME_UP");
static inline void action_volume_up(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_VOLUME_UP, actions, "VOLUME_UP");
}
static inline void action_volume_down(struct controller *controller) {
send_keycode(controller, AKEYCODE_VOLUME_DOWN, "VOLUME_DOWN");
static inline void action_volume_down(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_VOLUME_DOWN, actions, "VOLUME_DOWN");
}
static inline void action_menu(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_MENU, actions, "MENU");
}
// turn the screen on if it was off, press BACK otherwise
@@ -124,18 +129,6 @@ static void clipboard_paste(struct controller *controller) {
void input_manager_process_text_input(struct input_manager *input_manager,
const SDL_TextInputEvent *event) {
if (is_ctrl_down()) {
switch (event->text[0]) {
case '+':
action_volume_up(input_manager->controller);
break;
case '-':
action_volume_down(input_manager->controller);
break;
}
return;
}
struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_TEXT;
control_event.text_event.text = SDL_strdup(event->text);
@@ -154,48 +147,74 @@ void input_manager_process_key(struct input_manager *input_manager,
// capture all Ctrl events
if (ctrl) {
SDL_bool repeat = event->repeat;
// only consider keydown events, and ignore repeated events
if (repeat || event->type != SDL_KEYDOWN) {
return;
}
SDL_bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
if (shift) {
// currently, there is no shortcut implying SHIFT
// currently, there is no shortcut involving SHIFT
return;
}
SDL_Keycode keycode = event->keysym.sym;
int action = event->type == SDL_KEYDOWN ? ACTION_DOWN : ACTION_UP;
SDL_bool repeat = event->repeat;
switch (keycode) {
case SDLK_h:
action_home(input_manager->controller);
if (!repeat) {
action_home(input_manager->controller, action);
}
return;
case SDLK_b: // fall-through
case SDLK_BACKSPACE:
action_back(input_manager->controller);
if (!repeat) {
action_back(input_manager->controller, action);
}
return;
case SDLK_s:
if (!repeat) {
action_app_switch(input_manager->controller, action);
}
return;
case SDLK_m:
action_app_switch(input_manager->controller);
if (!repeat) {
action_menu(input_manager->controller, action);
}
return;
case SDLK_p:
action_power(input_manager->controller);
if (!repeat) {
action_power(input_manager->controller, action);
}
return;
case SDLK_DOWN:
// forward repeated events
action_volume_down(input_manager->controller, action);
return;
case SDLK_UP:
// forward repeated events
action_volume_up(input_manager->controller, action);
return;
case SDLK_v:
clipboard_paste(input_manager->controller);
if (!repeat && event->type == SDL_KEYDOWN) {
clipboard_paste(input_manager->controller);
}
return;
case SDLK_f:
screen_switch_fullscreen(input_manager->screen);
if (!repeat && event->type == SDL_KEYDOWN) {
screen_switch_fullscreen(input_manager->screen);
}
return;
case SDLK_x:
screen_resize_to_fit(input_manager->screen);
if (!repeat && event->type == SDL_KEYDOWN) {
screen_resize_to_fit(input_manager->screen);
}
return;
case SDLK_g:
screen_resize_to_pixel_perfect(input_manager->screen);
if (!repeat && event->type == SDL_KEYDOWN) {
screen_resize_to_pixel_perfect(input_manager->screen);
}
return;
case SDLK_i:
switch_fps_counter_state(input_manager->frames);
if (!repeat && event->type == SDL_KEYDOWN) {
switch_fps_counter_state(input_manager->frames);
}
return;
}
@@ -232,7 +251,7 @@ void input_manager_process_mouse_button(struct input_manager *input_manager,
return;
}
if (event->button == SDL_BUTTON_MIDDLE) {
action_home(input_manager->controller);
action_home(input_manager->controller, ACTION_DOWN | ACTION_UP);
return;
}
// double-click on black borders resize to fit the device screen
@@ -240,10 +259,11 @@ void input_manager_process_mouse_button(struct input_manager *input_manager,
SDL_bool outside_device_screen =
event->x < 0 || event->x >= input_manager->screen->frame_size.width ||
event->y < 0 || event->y >= input_manager->screen->frame_size.height;
if (outside_device_screen) {
screen_resize_to_fit(input_manager->screen);
}
return;
if (outside_device_screen) {
screen_resize_to_fit(input_manager->screen);
return;
}
// otherwise, send the click event to the device
}
};
struct control_event control_event;
@@ -263,7 +283,7 @@ void input_manager_process_mouse_wheel(struct input_manager *input_manager,
struct control_event control_event;
if (mouse_wheel_from_sdl_to_android(event, position, &control_event)) {
if (!controller_push_event(input_manager->controller, &control_event)) {
LOGW("Cannot send wheel button event");
LOGW("Cannot send mouse wheel event");
}
}
}

180
app/src/installer.c Normal file
View File

@@ -0,0 +1,180 @@
#include "installer.h"
#include <string.h>
#include "command.h"
#include "lockutil.h"
#include "log.h"
// NOTE(adopi) this can be more generic:
// it could be used with a command queue instead of a filename queue
// then we would have a generic invoker (useful if we want to handle more async commands)
SDL_bool apk_queue_is_empty(const struct apk_queue *queue) {
return queue->head == queue->tail;
}
SDL_bool apk_queue_is_full(const struct apk_queue *queue) {
return (queue->head + 1) % APK_QUEUE_SIZE == queue->tail;
}
SDL_bool apk_queue_init(struct apk_queue *queue) {
queue->head = 0;
queue->tail = 0;
return SDL_TRUE;
}
void apk_queue_destroy(struct apk_queue *queue) {
int i = queue->tail;
while (i != queue->head) {
SDL_free(queue->data[i]);
i = (i + 1) % APK_QUEUE_SIZE;
}
}
SDL_bool apk_queue_push(struct apk_queue *queue, const char *apk) {
if (apk_queue_is_full(queue)) {
return SDL_FALSE;
}
queue->data[queue->head] = SDL_strdup(apk);
queue->head = (queue->head + 1) % APK_QUEUE_SIZE;
return SDL_TRUE;
}
SDL_bool apk_queue_take(struct apk_queue *queue, char **apk) {
if (apk_queue_is_empty(queue)) {
return SDL_FALSE;
}
// transfer ownership
*apk = queue->data[queue->tail];
queue->tail = (queue->tail + 1) % APK_QUEUE_SIZE;
return SDL_TRUE;
}
SDL_bool installer_init(struct installer *installer, const char *serial) {
if (!apk_queue_init(&installer->queue)) {
return SDL_FALSE;
}
if (!(installer->mutex = SDL_CreateMutex())) {
return SDL_FALSE;
}
if (!(installer->event_cond = SDL_CreateCond())) {
SDL_DestroyMutex(installer->mutex);
return SDL_FALSE;
}
if (serial) {
installer->serial = SDL_strdup(serial);
if (!installer->serial) {
LOGW("Cannot strdup serial");
return SDL_FALSE;
}
} else {
installer->serial = NULL;
}
// lazy initialization
installer->initialized = SDL_FALSE;
installer->stopped = SDL_FALSE;
installer->current_process = PROCESS_NONE;
return SDL_TRUE;
}
void installer_destroy(struct installer *installer) {
SDL_DestroyCond(installer->event_cond);
SDL_DestroyMutex(installer->mutex);
apk_queue_destroy(&installer->queue);
SDL_free((void *) installer->serial);
}
SDL_bool installer_install_apk(struct installer *installer, const char *apk) {
SDL_bool res;
// start installer if it's used for the first time
if (!installer->initialized) {
if (!installer_start(installer)) {
return SDL_FALSE;
}
installer->initialized = SDL_TRUE;
}
mutex_lock(installer->mutex);
SDL_bool was_empty = apk_queue_is_empty(&installer->queue);
res = apk_queue_push(&installer->queue, apk);
if (was_empty) {
cond_signal(installer->event_cond);
}
mutex_unlock(installer->mutex);
return res;
}
static int run_installer(void *data) {
struct installer *installer = data;
for (;;) {
mutex_lock(installer->mutex);
while (!installer->stopped && apk_queue_is_empty(&installer->queue)) {
cond_wait(installer->event_cond, installer->mutex);
}
if (installer->stopped) {
// stop immediately, do not process further events
mutex_unlock(installer->mutex);
break;
}
char *current_apk;
#ifdef BUILD_DEBUG
bool non_empty = apk_queue_take(&installer->queue, &current_apk);
SDL_assert(non_empty);
#else
apk_queue_take(&installer->queue, &current_apk);
#endif
LOGI("Installing %s...", current_apk);
process_t process = adb_install(installer->serial, current_apk);
installer->current_process = process;
mutex_unlock(installer->mutex);
if (process_check_success(process, "adb install")) {
LOGI("%s installed successfully", current_apk);
} else {
LOGE("Failed to install %s", current_apk);
}
SDL_free(current_apk);
}
return 0;
}
SDL_bool installer_start(struct installer *installer) {
LOGD("Starting installer thread");
installer->thread = SDL_CreateThread(run_installer, "installer", installer);
if (!installer->thread) {
LOGC("Could not start installer thread");
return SDL_FALSE;
}
return SDL_TRUE;
}
void installer_stop(struct installer *installer) {
mutex_lock(installer->mutex);
installer->stopped = SDL_TRUE;
cond_signal(installer->event_cond);
if (installer->current_process != PROCESS_NONE) {
if (!cmd_terminate(installer->current_process)) {
LOGW("Cannot terminate install process");
}
cmd_simple_wait(installer->current_process, NULL);
installer->current_process = PROCESS_NONE;
}
mutex_unlock(installer->mutex);
}
void installer_join(struct installer *installer) {
SDL_WaitThread(installer->thread, NULL);
}

40
app/src/installer.h Normal file
View File

@@ -0,0 +1,40 @@
#ifndef APK_INSTALLER_H
#define APK_INSTALLER_H
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h>
#include <SDL2/SDL_thread.h>
#include "command.h"
#define APK_QUEUE_SIZE 16
// NOTE(AdoPi) apk_queue and control_event can use a generic queue
struct apk_queue {
char *data[APK_QUEUE_SIZE];
int tail;
int head;
};
struct installer {
const char *serial;
SDL_Thread *thread;
SDL_mutex *mutex;
SDL_cond *event_cond;
SDL_bool stopped;
SDL_bool initialized;
process_t current_process;
struct apk_queue queue;
};
SDL_bool installer_init(struct installer *installer, const char *serial);
void installer_destroy(struct installer *installer);
SDL_bool installer_start(struct installer *installer);
void installer_stop(struct installer *installer);
void installer_join(struct installer *installer);
// install an apk
SDL_bool installer_install_apk(struct installer *installer, const char *filename);
#endif

View File

@@ -10,8 +10,10 @@
struct args {
const char *serial;
const char *crop;
SDL_bool help;
SDL_bool version;
SDL_bool show_touches;
Uint16 port;
Uint16 max_size;
Uint32 bit_rate;
@@ -28,6 +30,12 @@ static void usage(const char *arg0) {
" Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
" Default is %d.\n"
"\n"
" -c, --crop width:height:x:y\n"
" Crop the device screen on the server.\n"
" The values are expressed in the device natural orientation\n"
" (typically, portrait for a phone, landscape for a tablet).\n"
" Any --max-size value is computed on the cropped size.\n"
"\n"
" -h, --help\n"
" Print this help.\n"
"\n"
@@ -45,6 +53,10 @@ static void usage(const char *arg0) {
" The device serial number. Mandatory only if several devices\n"
" are connected to adb.\n"
"\n"
" -t, --show-touches\n"
" Enable \"show touches\" on start, disable on quit.\n"
" It only shows physical touches (not clicks from scrcpy).\n"
"\n"
" -v, --version\n"
" Print the version of scrcpy.\n"
"\n"
@@ -70,13 +82,16 @@ static void usage(const char *arg0) {
" Right-click (when screen is on)\n"
" click on BACK\n"
"\n"
" Ctrl+m\n"
" Ctrl+s\n"
" click on APP_SWITCH\n"
"\n"
" Ctrl+'+'\n"
" Ctrl+m\n"
" click on MENU\n"
"\n"
" Ctrl+Up\n"
" click on VOLUME_UP\n"
"\n"
" Ctrl+'-'\n"
" Ctrl+Down\n"
" click on VOLUME_DOWN\n"
"\n"
" Ctrl+p\n"
@@ -90,6 +105,9 @@ static void usage(const char *arg0) {
"\n"
" Ctrl+i\n"
" enable/disable FPS counter (print frames/second in logs)\n"
"\n"
" Drag & drop APK file\n"
" install APK from computer\n"
"\n",
arg0,
DEFAULT_BIT_RATE,
@@ -180,47 +198,49 @@ static SDL_bool parse_port(char *optarg, Uint16 *port) {
static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
static const struct option long_options[] = {
{"bit-rate", required_argument, NULL, 'b'},
{"help", no_argument, NULL, 'h'},
{"max-size", required_argument, NULL, 'm'},
{"port", required_argument, NULL, 'p'},
{"serial", required_argument, NULL, 's'},
{"version", no_argument, NULL, 'v'},
{NULL, 0, NULL, 0 },
{"bit-rate", required_argument, NULL, 'b'},
{"crop", required_argument, NULL, 'c'},
{"help", no_argument, NULL, 'h'},
{"max-size", required_argument, NULL, 'm'},
{"port", required_argument, NULL, 'p'},
{"serial", required_argument, NULL, 's'},
{"show-touches", no_argument, NULL, 't'},
{"version", no_argument, NULL, 'v'},
{NULL, 0, NULL, 0 },
};
int c;
while ((c = getopt_long(argc, argv, "b:hm:p:s:v", long_options, NULL)) != -1) {
while ((c = getopt_long(argc, argv, "b:c:hm:p:s:tv", long_options, NULL)) != -1) {
switch (c) {
case 'b': {
case 'b':
if (!parse_bit_rate(optarg, &args->bit_rate)) {
return SDL_FALSE;
}
break;
}
case 'h': {
case 'c':
args->crop = optarg;
break;
case 'h':
args->help = SDL_TRUE;
break;
}
case 'm': {
case 'm':
if (!parse_max_size(optarg, &args->max_size)) {
return SDL_FALSE;
}
break;
}
case 'p': {
case 'p':
if (!parse_port(optarg, &args->port)) {
return SDL_FALSE;
}
break;
}
case 's': {
case 's':
args->serial = optarg;
break;
}
case 'v': {
case 't':
args->show_touches = SDL_TRUE;
break;
case 'v':
args->version = SDL_TRUE;
break;
}
default:
// getopt prints the error message on stderr
return SDL_FALSE;
@@ -244,8 +264,10 @@ int main(int argc, char *argv[]) {
#endif
struct args args = {
.serial = NULL,
.crop = NULL,
.help = SDL_FALSE,
.version = SDL_FALSE,
.show_touches = SDL_FALSE,
.port = DEFAULT_LOCAL_PORT,
.max_size = DEFAULT_MAX_SIZE,
.bit_rate = DEFAULT_BIT_RATE,
@@ -264,7 +286,9 @@ int main(int argc, char *argv[]) {
return 0;
}
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
av_register_all();
#endif
if (avformat_network_init()) {
return 1;
@@ -274,7 +298,15 @@ int main(int argc, char *argv[]) {
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG);
#endif
int res = scrcpy(args.serial, args.port, args.max_size, args.bit_rate) ? 0 : 1;
struct scrcpy_options options = {
.serial = args.serial,
.crop = args.crop,
.port = args.port,
.max_size = args.max_size,
.bit_rate = args.bit_rate,
.show_touches = args.show_touches,
};
int res = scrcpy(&options) ? 0 : 1;
avformat_network_deinit(); // ignore failure

View File

@@ -82,19 +82,19 @@ ssize_t net_recv_all(socket_t socket, void *buf, size_t len) {
return recv(socket, buf, len, MSG_WAITALL);
}
ssize_t net_send(socket_t socket, void *buf, size_t len) {
ssize_t net_send(socket_t socket, const void *buf, size_t len) {
return send(socket, buf, len, 0);
}
ssize_t net_send_all(socket_t socket, void *buf, size_t len) {
ssize_t w;
ssize_t net_send_all(socket_t socket, const void *buf, size_t len) {
ssize_t w = 0;
while (len > 0) {
w = send(socket, buf, len, 0);
if (w == -1) {
return -1;
}
len -= w;
buf += w;
buf = (char *) buf + w;
}
return w;
}

View File

@@ -9,8 +9,6 @@
#define SHUT_RD SD_RECEIVE
#define SHUT_WR SD_SEND
#define SHUT_RDWR SD_BOTH
typedef SIZE_T size_t;
typedef SSIZE_T ssize_t;
typedef SOCKET socket_t;
#else
# include <sys/socket.h>
@@ -28,8 +26,8 @@ socket_t net_accept(socket_t server_socket);
// the _all versions wait/retry until len bytes have been written/read
ssize_t net_recv(socket_t socket, void *buf, size_t len);
ssize_t net_recv_all(socket_t socket, void *buf, size_t len);
ssize_t net_send(socket_t socket, void *buf, size_t len);
ssize_t net_send_all(socket_t socket, void *buf, size_t len);
ssize_t net_send(socket_t socket, const void *buf, size_t len);
ssize_t net_send_all(socket_t socket, const void *buf, size_t len);
// how is SHUT_RD (read), SHUT_WR (write) or SHUT_RDWR (both)
SDL_bool net_shutdown(socket_t socket, int how);
SDL_bool net_close(socket_t socket);

View File

@@ -22,12 +22,14 @@
#include "screen.h"
#include "server.h"
#include "tinyxpm.h"
#include "installer.h"
static struct server server = SERVER_INITIALIZER;
static struct screen screen = SCREEN_INITIALIZER;
static struct frames frames;
static struct decoder decoder;
static struct controller controller;
static struct installer installer;
static struct input_manager input_manager = {
.controller = &controller,
@@ -79,16 +81,15 @@ static void event_loop(void) {
break;
case SDL_WINDOWEVENT:
switch (event.window.event) {
case SDL_WINDOWEVENT_EXPOSED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_render(&screen);
break;
case SDL_WINDOWEVENT_EXPOSED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_render(&screen);
break;
}
break;
case SDL_TEXTINPUT: {
case SDL_TEXTINPUT:
input_manager_process_text_input(&input_manager, &event.text);
break;
}
case SDL_KEYDOWN:
case SDL_KEYUP:
input_manager_process_key(&input_manager, &event.key);
@@ -96,36 +97,59 @@ static void event_loop(void) {
case SDL_MOUSEMOTION:
input_manager_process_mouse_motion(&input_manager, &event.motion);
break;
case SDL_MOUSEWHEEL: {
case SDL_MOUSEWHEEL:
input_manager_process_mouse_wheel(&input_manager, &event.wheel);
break;
}
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP: {
case SDL_MOUSEBUTTONUP:
input_manager_process_mouse_button(&input_manager, &event.button);
break;
}
case SDL_DROPFILE:
installer_install_apk(&installer, event.drop.file);
break;
}
}
}
SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 bit_rate) {
if (!server_start(&server, serial, local_port, max_size, bit_rate)) {
static process_t set_show_touches_enabled(const char *serial, SDL_bool enabled) {
const char *value = enabled ? "1" : "0";
const char *const adb_cmd[] = {
"shell", "settings", "put", "system", "show_touches", value
};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
static void wait_show_touches(process_t process) {
// reap the process, ignore the result
process_check_success(process, "show_touches");
}
SDL_bool scrcpy(const struct scrcpy_options *options) {
if (!server_start(&server, options->serial, options->port,
options->max_size, options->bit_rate, options->crop)) {
return SDL_FALSE;
}
process_t proc_show_touches = PROCESS_NONE;
SDL_bool show_touches_waited;
if (options->show_touches) {
LOGI("Enable show_touches");
proc_show_touches = set_show_touches_enabled(options->serial, SDL_TRUE);
show_touches_waited = SDL_FALSE;
}
SDL_bool ret = SDL_TRUE;
if (!SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1")) {
LOGW("Cannot request to keep default signal handlers");
}
if (!sdl_init_and_configure()) {
ret = SDL_FALSE;
goto finally_destroy_server;
}
// SDL initialization replace the signal handler for SIGTERM, so Ctrl+C is
// managed by the event loop. This blocking call blocks the event loop, so
// timeout the connection not to block indefinitely in case of SIGTERM.
#define SERVER_CONNECT_TIMEOUT_MS 2000
socket_t device_socket = server_connect_to(&server, SERVER_CONNECT_TIMEOUT_MS);
socket_t device_socket = server_connect_to(&server);
if (device_socket == INVALID_SOCKET) {
server_stop(&server);
ret = SDL_FALSE;
@@ -150,6 +174,12 @@ SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 b
goto finally_destroy_server;
}
if (!installer_init(&installer, server.serial)) {
ret = SDL_FALSE;
server_stop(&server);
goto finally_destroy_frames;
}
decoder_init(&decoder, &frames, device_socket);
// now we consumed the header values, the socket receives the video stream
@@ -157,7 +187,7 @@ SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 b
if (!decoder_start(&decoder)) {
ret = SDL_FALSE;
server_stop(&server);
goto finally_destroy_frames;
goto finally_destroy_installer;
}
if (!controller_init(&controller, device_socket)) {
@@ -175,10 +205,16 @@ SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 b
goto finally_stop_and_join_controller;
}
event_loop();
if (options->show_touches) {
wait_show_touches(proc_show_touches);
show_touches_waited = SDL_TRUE;
}
event_loop();
LOGD("quit...");
screen_destroy(&screen);
finally_stop_and_join_controller:
controller_stop(&controller);
controller_join(&controller);
@@ -189,9 +225,23 @@ finally_stop_decoder:
// stop the server before decoder_join() to wake up the decoder
server_stop(&server);
decoder_join(&decoder);
finally_destroy_installer:
installer_stop(&installer);
installer_join(&installer);
installer_destroy(&installer);
finally_destroy_frames:
frames_destroy(&frames);
finally_destroy_server:
if (options->show_touches) {
if (!show_touches_waited) {
// wait the process which enabled "show touches"
wait_show_touches(proc_show_touches);
}
LOGI("Disable show_touches");
proc_show_touches = set_show_touches_enabled(options->serial, SDL_FALSE);
wait_show_touches(proc_show_touches);
}
server_destroy(&server);
return ret;

View File

@@ -3,6 +3,15 @@
#include <SDL2/SDL_stdinc.h>
SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 bit_rate);
struct scrcpy_options {
const char *serial;
const char *crop;
Uint16 port;
Uint16 max_size;
Uint32 bit_rate;
SDL_bool show_touches;
};
SDL_bool scrcpy(const struct scrcpy_options *options);
#endif

View File

@@ -136,6 +136,11 @@ void screen_init(struct screen *screen) {
*screen = (struct screen) SCREEN_INITIALIZER;
}
static inline SDL_Texture *create_texture(SDL_Renderer *renderer, struct size frame_size) {
return SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING,
frame_size.width, frame_size.height);
}
SDL_bool screen_init_rendering(struct screen *screen, const char *device_name, struct size frame_size) {
screen->frame_size = frame_size;
@@ -174,8 +179,7 @@ SDL_bool screen_init_rendering(struct screen *screen, const char *device_name, s
SDL_FreeSurface(icon);
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width, frame_size.height);
screen->texture = SDL_CreateTexture(screen->renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING,
frame_size.width, frame_size.height);
screen->texture = create_texture(screen->renderer, frame_size);
if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError());
screen_destroy(screen);
@@ -224,8 +228,7 @@ static SDL_bool prepare_for_frame(struct screen *screen, struct size new_frame_s
LOGD("New texture: %" PRIu16 "x%" PRIu16,
screen->frame_size.width, screen->frame_size.height);
screen->texture = SDL_CreateTexture(screen->renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING,
new_frame_size.width, new_frame_size.height);
screen->texture = create_texture(screen->renderer, new_frame_size);
if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError());
return SDL_FALSE;

View File

@@ -3,6 +3,7 @@
#include <errno.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_timer.h>
@@ -76,7 +77,8 @@ static SDL_bool disable_tunnel(struct server *server) {
}
static process_t execute_server(const char *serial,
Uint16 max_size, Uint32 bit_rate, SDL_bool tunnel_forward) {
Uint16 max_size, Uint32 bit_rate,
const char *crop, SDL_bool tunnel_forward) {
char max_size_string[6];
char bit_rate_string[11];
sprintf(max_size_string, "%"PRIu16, max_size);
@@ -90,6 +92,7 @@ static process_t execute_server(const char *serial,
max_size_string,
bit_rate_string,
tunnel_forward ? "true" : "false",
crop ? crop : "",
};
return adb_execute(serial, cmd, sizeof(cmd) / sizeof(cmd[0]));
}
@@ -146,20 +149,25 @@ void server_init(struct server *server) {
}
SDL_bool server_start(struct server *server, const char *serial, Uint16 local_port,
Uint16 max_size, Uint32 bit_rate) {
Uint16 max_size, Uint32 bit_rate, const char *crop) {
server->local_port = local_port;
if (serial) {
server->serial = SDL_strdup(serial);
if (!server->serial) {
return SDL_FALSE;
}
}
if (!push_server(serial)) {
SDL_free((void *) server->serial);
return SDL_FALSE;
}
server->server_copied_to_device = SDL_TRUE;
if (!enable_tunnel(server)) {
SDL_free((void *) server->serial);
return SDL_FALSE;
}
@@ -176,17 +184,20 @@ SDL_bool server_start(struct server *server, const char *serial, Uint16 local_po
if (server->server_socket == INVALID_SOCKET) {
LOGE("Could not listen on port %" PRIu16, local_port);
disable_tunnel(server);
SDL_free((void *) server->serial);
return SDL_FALSE;
}
}
// server will connect to our server socket
server->process = execute_server(serial, max_size, bit_rate, server->tunnel_forward);
server->process = execute_server(serial, max_size, bit_rate, crop,
server->tunnel_forward);
if (server->process == PROCESS_NONE) {
if (!server->tunnel_forward) {
close_socket(&server->server_socket);
}
disable_tunnel(server);
SDL_free((void *) server->serial);
return SDL_FALSE;
}
@@ -195,11 +206,11 @@ SDL_bool server_start(struct server *server, const char *serial, Uint16 local_po
return SDL_TRUE;
}
socket_t server_connect_to(struct server *server, Uint32 timeout_ms) {
socket_t server_connect_to(struct server *server) {
if (!server->tunnel_forward) {
server->device_socket = net_accept(server->server_socket);
} else {
Uint32 attempts = 10;
Uint32 attempts = 100;
Uint32 delay = 100; // ms
server->device_socket = connect_to_server(server->local_port, attempts, delay);
}

View File

@@ -31,10 +31,10 @@ void server_init(struct server *server);
// push, enable tunnel et start the server
SDL_bool server_start(struct server *server, const char *serial, Uint16 local_port,
Uint16 max_size, Uint32 bit_rate);
Uint16 max_size, Uint32 bit_rate, const char *crop);
// block until the communication with the server is established
socket_t server_connect_to(struct server *server, Uint32 timeout_ms);
socket_t server_connect_to(struct server *server);
// disconnect and kill the server process
void server_stop(struct server *server);

View File

@@ -1,9 +1,11 @@
#include "../../command.h"
#include "command.h"
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "log.h"
pid_t cmd_execute(const char *path, const char *const argv[]) {
pid_t pid = fork();
@@ -20,6 +22,10 @@ pid_t cmd_execute(const char *path, const char *const argv[]) {
}
SDL_bool cmd_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;
}

View File

@@ -1,4 +1,4 @@
#include "../../net.h"
#include "net.h"
# include <unistd.h>

View File

@@ -1,7 +1,8 @@
#include "../../command.h"
#include "command.h"
#include "../../log.h"
#include "../../strutil.h"
#include "config.h"
#include "log.h"
#include "strutil.h"
HANDLE cmd_execute(const char *path, const char *const argv[]) {
STARTUPINFO si;
@@ -20,7 +21,12 @@ HANDLE cmd_execute(const char *path, const char *const argv[]) {
return NULL;
}
if (!CreateProcess(NULL, cmd, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
#ifdef WINDOWS_NOCONSOLE
int flags = CREATE_NO_WINDOW;
#else
int flags = 0;
#endif
if (!CreateProcess(NULL, cmd, NULL, NULL, FALSE, flags, NULL, NULL, &si, &pi)) {
return NULL;
}

View File

@@ -1,6 +1,6 @@
#include "../../net.h"
#include "net.h"
#include "../../log.h"
#include "log.h"
SDL_bool net_init(void) {
WSADATA wsa;

View File

@@ -1,4 +1,5 @@
#include <assert.h>
#include <string.h>
#include "controlevent.h"

View File

@@ -1,4 +1,5 @@
#include <assert.h>
#include <string.h>
#include "controlevent.h"

View File

@@ -7,7 +7,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath 'com.android.tools.build:gradle:3.1.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

20
cross_win32.txt Normal file
View File

@@ -0,0 +1,20 @@
# apt install mingw-w64 mingw-w64-tools
[binaries]
name = 'mingw'
c = '/usr/bin/i686-w64-mingw32-gcc'
cpp = '/usr/bin/i686-w64-mingw32-g++'
ar = '/usr/bin/i686-w64-mingw32-ar'
strip = '/usr/bin/i686-w64-mingw32-strip'
pkgconfig = '/usr/bin/i686-w64-mingw32-pkg-config'
[host_machine]
system = 'windows'
cpu_family = 'x86'
cpu = 'i686'
endian = 'little'
[properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.0-win32-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.0-win32-dev'
prebuilt_sdl2 = 'SDL2-2.0.8/i686-w64-mingw32'

20
cross_win64.txt Normal file
View File

@@ -0,0 +1,20 @@
# apt install mingw-w64 mingw-w64-tools
[binaries]
name = 'mingw'
c = '/usr/bin/x86_64-w64-mingw32-gcc'
cpp = '/usr/bin/x86_64-w64-mingw32-g++'
ar = '/usr/bin/x86_64-w64-mingw32-ar'
strip = '/usr/bin/x86_64-w64-mingw32-strip'
pkgconfig = '/usr/bin/x86_64-w64-mingw32-pkg-config'
[host_machine]
system = 'windows'
cpu_family = 'x86'
cpu = 'x86_64'
endian = 'little'
[properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.0-win64-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.0-win64-dev'
prebuilt_sdl2 = 'SDL2-2.0.8/x86_64-w64-mingw32'

View File

@@ -1,6 +1,6 @@
#Mon Jan 29 16:38:49 CET 2018
#Mon Jun 04 11:48:32 CEST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip

View File

@@ -1,4 +1,4 @@
project('scrcpy', 'c', meson_version: '>= 0.37')
project('scrcpy', 'c', meson_version: '>= 0.37', default_options : 'c_std=c99')
if get_option('build_app')
subdir('app')

View File

@@ -1,5 +1,7 @@
option('build_app', type: 'boolean', value: true, description: 'Build the client')
option('build_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('override_server_path', type: 'string', description: 'Hardcoded path to find the server at runtime')
option('skip_frames', type: 'boolean', value: true, description: 'Always display the most recent frame')

4
prebuilt-deps/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
*
!/.gitignore
!/Makefile
!/prepare-dep

40
prebuilt-deps/Makefile Normal file
View File

@@ -0,0 +1,40 @@
.PHONY: prepare-win32 prepare-win64 \
prepare-ffmpeg-shared-win32 \
prepare-ffmpeg-dev-win32 \
prepare-ffmpeg-shared-win64 \
prepare-ffmpeg-dev-win64 \
prepare-sdl2 \
prepare-adb
prepare-win32: prepare-sdl2 prepare-ffmpeg-shared-win32 prepare-ffmpeg-dev-win32 prepare-adb
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.0-win32-shared.zip \
530c92df0ca14c35901b4b681847d62da3c50a0cc9b7ced37b04968f6b5c243d \
ffmpeg-4.0-win32-shared
prepare-ffmpeg-dev-win32:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/dev/ffmpeg-4.0-win32-dev.zip \
e2f5200b5e73c4d0abb9b89c4ffc0438f92a0aadc54c81cf57e18c81a9f11c6b \
ffmpeg-4.0-win32-dev
prepare-ffmpeg-shared-win64:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/shared/ffmpeg-4.0-win64-shared.zip \
8fe2d344463dbefc2db4239a4203a55ed0324faceaae57276a40c4fabda84c37 \
ffmpeg-4.0-win64-shared
prepare-ffmpeg-dev-win64:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/dev/ffmpeg-4.0-win64-dev.zip \
facced738eabfc53fa92834dea8b24426f64db61298688fed480145945be07fa \
ffmpeg-4.0-win64-dev
prepare-sdl2:
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.8-mingw.tar.gz \
ffff7305d634aff5e1df5b7bb935435c3a02c8b03ad94a1a2be9169a558a7961 \
SDL2-2.0.8
prepare-adb:
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r27.0.1-windows.zip \
880662adfb0d6911ff250b9e13930ae1a4110fc36d5866afd4f8f56d935f7939 \
platform-tools

58
prebuilt-deps/prepare-dep Executable file
View File

@@ -0,0 +1,58 @@
#!/bin/bash
set -e
url="$1"
sum="$2"
dir="$3"
checksum() {
local file="$1"
local sum="$2"
echo "$file: verifying checksum..."
echo "$sum $file" | sha256sum -c
}
get_file() {
local url="$1"
local file="$2"
local sum="$3"
if [[ -f "$file" ]]
then
echo "$file: found"
else
echo "$file: not found, downloading..."
wget "$url" -O "$file"
fi
checksum "$file" "$sum"
}
extract() {
local file="$1"
echo "Extracting $file..."
if [[ "$file" == *.zip ]]
then
unzip -q "$file"
elif [[ "$file" == *.tar.gz ]]
then
tar xf "$file"
else
echo "Unsupported file: $file"
return 1
fi
}
get_dep() {
local url="$1"
local sum="$2"
local dir="$3"
local file="${url##*/}"
if [[ -d "$dir" ]]
then
echo "$dir: found"
else
echo "$dir: not found"
get_file "$url" "$file" "$sum"
extract "$file"
fi
}
get_dep "$1" "$2" "$3"

View File

@@ -6,8 +6,8 @@ android {
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
targetSdkVersion 27
versionCode 2
versionName "1.1"
versionCode 4
versionName "1.3"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {

View File

@@ -3,6 +3,7 @@ package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Build;
import android.os.RemoteException;
import android.view.IRotationWatcher;
@@ -20,7 +21,7 @@ public final class Device {
private RotationListener rotationListener;
public Device(Options options) {
screenInfo = computeScreenInfo(options.getMaxSize());
screenInfo = computeScreenInfo(options.getCrop(), options.getMaxSize());
registerRotationWatcher(new IRotationWatcher.Stub() {
@Override
public void onRotationChanged(int rotation) throws RemoteException {
@@ -40,18 +41,40 @@ public final class Device {
return screenInfo;
}
private ScreenInfo computeScreenInfo(Rect crop, int maxSize) {
DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo();
boolean rotated = (displayInfo.getRotation() & 1) != 0;
Size deviceSize = displayInfo.getSize();
Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight());
if (crop != null) {
if (rotated) {
// the crop (provided by the user) is expressed in the natural orientation
crop = flipRect(crop);
}
if (!contentRect.intersect(crop)) {
// intersect() changes contentRect so that it is intersected with crop
Ln.w("Crop rectangle (" + formatCrop(crop) + ") does not intersect device screen (" + formatCrop(deviceSize.toRect()) + ")");
contentRect = new Rect(); // empty
}
}
Size videoSize = computeVideoSize(contentRect.width(), contentRect.height(), maxSize);
return new ScreenInfo(contentRect, videoSize, rotated);
}
private static String formatCrop(Rect rect) {
return rect.width() + ":" + rect.height() + ":" + rect.left + ":" + rect.top;
}
@SuppressWarnings("checkstyle:MagicNumber")
private ScreenInfo computeScreenInfo(int maxSize) {
private static Size computeVideoSize(int w, int h, int maxSize) {
// Compute the video size and the padding of the content inside this video.
// Principle:
// - scale down the great side of the screen to maxSize (if necessary);
// - scale down the other side so that the aspect ratio is preserved;
// - round this value to the nearest multiple of 8 (H.264 only accepts multiples of 8)
DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo();
boolean rotated = (displayInfo.getRotation() & 1) != 0;
Size deviceSize = displayInfo.getSize();
int w = deviceSize.getWidth() & ~7; // in case it's not a multiple of 8
int h = deviceSize.getHeight() & ~7;
w &= ~7; // in case it's not a multiple of 8
h &= ~7;
if (maxSize > 0) {
if (BuildConfig.DEBUG && maxSize % 8 != 0) {
throw new AssertionError("Max size must be a multiple of 8");
@@ -68,12 +91,12 @@ public final class Device {
w = portrait ? minor : major;
h = portrait ? major : minor;
}
Size videoSize = new Size(w, h);
return new ScreenInfo(deviceSize, videoSize, rotated);
return new Size(w, h);
}
public Point getPhysicalPoint(Position position) {
@SuppressWarnings("checkstyle:HiddenField") // it hides the field on purpose, to read it with a lock
// it hides the field on purpose, to read it with a lock
@SuppressWarnings("checkstyle:HiddenField")
ScreenInfo screenInfo = getScreenInfo(); // read with synchronization
Size videoSize = screenInfo.getVideoSize();
Size clientVideoSize = position.getScreenSize();
@@ -82,10 +105,10 @@ public final class Device {
// the device may have been rotated since the event was generated, so ignore the event
return null;
}
Size deviceSize = screenInfo.getDeviceSize();
Rect contentRect = screenInfo.getContentRect();
Point point = position.getPoint();
int scaledX = point.x * deviceSize.getWidth() / videoSize.getWidth();
int scaledY = point.y * deviceSize.getHeight() / videoSize.getHeight();
int scaledX = contentRect.left + point.x * contentRect.width() / videoSize.getWidth();
int scaledY = contentRect.top + point.y * contentRect.height() / videoSize.getHeight();
return new Point(scaledX, scaledY);
}
@@ -108,4 +131,8 @@ public final class Device {
public synchronized void setRotationListener(RotationListener rotationListener) {
this.rotationListener = rotationListener;
}
static Rect flipRect(Rect crop) {
return new Rect(crop.top, crop.left, crop.bottom, crop.right);
}
}

View File

@@ -1,9 +1,12 @@
package com.genymobile.scrcpy;
import android.graphics.Rect;
public class Options {
private int maxSize;
private int bitRate;
private boolean tunnelForward;
private Rect crop;
public int getMaxSize() {
return maxSize;
@@ -28,4 +31,12 @@ public class Options {
public void setTunnelForward(boolean tunnelForward) {
this.tunnelForward = tunnelForward;
}
public Rect getCrop() {
return crop;
}
public void setCrop(Rect crop) {
this.crop = crop;
}
}

View File

@@ -56,12 +56,12 @@ public class ScreenEncoder implements Device.RotationListener {
do {
MediaCodec codec = createCodec();
IBinder display = createDisplay();
Rect deviceRect = device.getScreenInfo().getDeviceSize().toRect();
Rect contentRect = device.getScreenInfo().getContentRect();
Rect videoRect = device.getScreenInfo().getVideoSize().toRect();
setSize(format, videoRect.width(), videoRect.height());
configure(codec, format);
Surface surface = codec.createInputSurface();
setDisplaySurface(display, surface, deviceRect, videoRect);
setDisplaySurface(display, surface, contentRect, videoRect);
codec.start();
try {
alive = encode(codec, outputStream);

View File

@@ -1,18 +1,20 @@
package com.genymobile.scrcpy;
import android.graphics.Rect;
public final class ScreenInfo {
private final Size deviceSize;
private final Rect contentRect; // device size, possibly cropped
private final Size videoSize;
private final boolean rotated;
public ScreenInfo(Size deviceSize, Size videoSize, boolean rotated) {
this.deviceSize = deviceSize;
public ScreenInfo(Rect contentRect, Size videoSize, boolean rotated) {
this.contentRect = contentRect;
this.videoSize = videoSize;
this.rotated = rotated;
}
public Size getDeviceSize() {
return deviceSize;
public Rect getContentRect() {
return contentRect;
}
public Size getVideoSize() {
@@ -24,6 +26,6 @@ public final class ScreenInfo {
if (rotated == newRotated) {
return this;
}
return new ScreenInfo(deviceSize.rotate(), videoSize.rotate(), newRotated);
return new ScreenInfo(Device.flipRect(contentRect), videoSize.rotate(), newRotated);
}
}

View File

@@ -1,5 +1,7 @@
package com.genymobile.scrcpy;
import android.graphics.Rect;
import java.io.IOException;
public final class Server {
@@ -63,9 +65,31 @@ public final class Server {
boolean tunnelForward = Boolean.parseBoolean(args[2]);
options.setTunnelForward(tunnelForward);
if (args.length < 4) {
return options;
}
Rect crop = parseCrop(args[3]);
options.setCrop(crop);
return options;
}
private static Rect parseCrop(String crop) {
if (crop.isEmpty()) {
return null;
}
// input format: "width:height:x:y"
String[] tokens = crop.split(":");
if (tokens.length != 4) {
throw new IllegalArgumentException("Crop must contains 4 values separated by colons: \"" + crop + "\"");
}
int width = Integer.parseInt(tokens[0]);
int height = Integer.parseInt(tokens[1]);
int x = Integer.parseInt(tokens[2]);
int y = Integer.parseInt(tokens[3]);
return new Rect(x, y, x + width, y + height);
}
public static void main(String... args) throws Exception {
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override

View File

@@ -3,6 +3,9 @@ package com.genymobile.scrcpy;
import android.view.KeyEvent;
import android.view.MotionEvent;
import org.junit.Assert;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
@@ -10,8 +13,6 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import org.junit.Assert;
import org.junit.Test;
public class ControlEventReaderTest {