Compare commits

...

74 Commits

Author SHA1 Message Date
57b8dd76c8 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-27 21:53:13 +02:00
6bc39acda3 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-27 21:39:53 +02:00
295dd6f63a 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-27 21:38:24 +02:00
053c534e26 Fix net_send_all() warning
If len is not positive, w could be returned uninitialized.
2018-05-27 15:50:50 +02:00
cf7414c735 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-27 15:50:17 +02:00
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
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
435a7fe314 Add missing includes
Include string.h and stdio.h explicitly.
2018-05-13 15:33:13 +02:00
78da66f126 Merge branch 'master' into dev 2018-04-08 12:41:51 +02:00
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
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
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
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
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
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
16a3de1796 Make checkstyle happy
Reorder the imports to remove checkstyle warnings.
2018-03-28 22:05:34 +02:00
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
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
71f50fb697 Merge branch 'master' into dev 2018-03-28 10:45:23 +02:00
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
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
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
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
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
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
b449c09442 Merge branch 'master' into dev 2018-03-25 15:16:19 +02:00
8b84492830 Merge branch 'stek29/macos' (#56)
macOS specific README changes
2018-03-25 14:31:02 +02:00
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
e0e8dfeb3b Merge pull request #94 from pierlon/pierlon-patch-1
Add instructions to run via Docker
2018-03-25 14:12:29 +02:00
f4d6449af7 Merge pull request #95 from Sea-n/patch-1
Update README.md
2018-03-25 14:00:20 +02:00
64963fff62 Update README.md
Fix Typo
2018-03-25 17:51:29 +08:00
b7d9b8739c Add instructions to run via Docker 2018-03-24 23:15:14 -05:00
88f6b43c8f Merge pull request #93 from CampbellOwen/patch-1
Add links to FFmpeg and LibSDL2 dependencies
2018-03-24 09:26:12 +01:00
324a264233 Change links to wikipedia 2018-03-24 01:09:41 -07:00
3bb2cda955 Add links to FFmpeg and LibSDL2 dependencies 2018-03-24 00:55:05 -07:00
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
73c332e3e4 Unref last packet on exit 2018-03-23 13:57:32 +01:00
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
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
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
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
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
301c52b603 Add support for CTRL+S to send hardware "shake" to device w/readme 2018-03-22 16:15:24 -07:00
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
3b3803da0d Remove useless blocks in switch/case
Remove unnecessary additional blocks.
2018-03-21 11:14:15 +01:00
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
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
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
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
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
f3e8834a3c Fix warning message
Mouse "wheel button" is meaningless :)
2018-03-15 16:14:40 +01:00
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
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
2992dda497 Add link to the article for v1.1 in README 2018-03-14 18:03:34 +01:00
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
f3f19d4e1d Update links to v1.1 in README 2018-03-14 09:53:55 +01:00
d744837f13 Bump version to 1.1 2018-03-14 09:34:00 +01:00
f7bc0bd5b5 Merge branch 'dev' into release 2018-03-14 09:33:53 +01:00
8a3c6a3ae7 Remove useless argument
Do not pass any data to the event watcher, it is unused.
2018-03-14 09:32:05 +01:00
c530d95881 Immediately close the server socket on the device
In "adb forward" mode, close the server socket as soon as the client is
connected.

Even if unlikely to be useful, it allows to run several instances of
scrcpy also in "adb forward" mode.
2018-03-14 09:28:25 +01:00
0b1e59186f Workaround continuous resizing on Windows/MacOS
On Windows and MacOS, resizing blocks the event loop, so resizing events
are not triggered:
 - <https://bugzilla.libsdl.org/show_bug.cgi?id=2077>
 - <https://stackoverflow.com/a/40693139/1987178>

As a workaround, register an event watcher to render the screen from
another thread.

Since the whole event loop is blocked during resizing, the screen
content is not refreshed (on Windows and MacOS) until resizing ends.
2018-03-13 22:48:04 +01:00
e69f6f710d Disable stdout/stderr buffering on Windows
In MSYS2 on Windows, the output is buffered by default. Disable
buffering to print output immediately.

Note that in cmd.exe, it still prints nothing.
2018-03-13 10:20:09 +01:00
b858204786 Remove black borders on double-click
Resize the window to fit the device screen on click on black borders
(same as Ctrl+x).

Suggested-by: Guillaume Roche <groche@genymobile.com>
2018-03-13 08:37:46 +01:00
e8510a8cc3 Add links to AUR packages in README 2018-03-12 16:00:12 +01:00
f9a63ec272 Reverse horizontal scrolling behavior
The SDL mouse wheel event seems inconsistent between horizontal and
vertical scrolling.

> Movements to the left generate negative x values and to the right
> generate positive x values. Movements down (scroll backward) generate
> negative y values and up (scroll forward) generate positive y values.

<https://wiki.libsdl.org/SDL_MouseWheelEvent#Remarks>

Reverse the horizontal.

Fixes <https://github.com/Genymobile/scrcpy/issues/49>.
2018-03-12 09:37:46 +01:00
f6d0893316 Merge branch 'fedora_install' (pull request #29)
Document how to install on Fedora
2018-03-11 22:07:12 +01:00
ed65cd72fd Use one subsection by distribution in README
Now that instructions are given for both Debian/Ubuntu and Fedora, use
subsections.
2018-03-11 22:06:15 +01:00
70599998eb Document how to install on Fedora 2018-03-11 22:06:15 +01:00
e87cd175cc Improve dependencies in README
Document server and client dependencies separately, to avoid unneeded
packages installation when building using the prebuilt server.

Also remove "zip", since it's only used for building a portable version
(which is not documented in README).
2018-03-11 22:05:41 +01:00
14b15ceb06 Add a FAQ for common issues 2018-03-11 10:40:46 +01:00
b9bb4ff740 Merge branch 'sdushantha' (pull request #28)
Improve README syntax highlighting.
2018-03-10 16:31:17 +01:00
cc4a015256 Add empty lines around code blocks
And fix spaces (do not randomly use non-breaking spaces for
indentation).
2018-03-10 16:24:03 +01:00
8476b4aab8 removed "$" and changed Mac OS ---> MacOS 2018-03-10 15:34:59 +01:00
a1491862e4 added "$" in front of terminal commands 2018-03-10 09:12:47 +01:00
36 changed files with 844 additions and 257 deletions

69
FAQ.md Normal file
View File

@ -0,0 +1,69 @@
# Frequently Asked Questions
## Common issues
The application is very young, it is not unlikely that you encounter problems
with it.
Here are the common reported problems and their status.
### 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:
adb devices
Windows may need some [drivers] to detect your device.
[drivers]: https://developer.android.com/studio/run/oem-usb.html
If you still encounter problems, please see [issue 9].
[issue 9]: https://github.com/Genymobile/scrcpy/issues/9
### I get a black screen for some applications like Silence
This is expected, they requested to protect the screen.
In [Silence], you can disable it in settings → Privacy → Screen security.
[silence]: https://f-droid.org/en/packages/org.smssecure.smssecure/
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
scaled. See [issue 15].
[issue 15]: https://github.com/Genymobile/scrcpy/issues/15
A workaround is to build with HiDPI support disabled:
```bash
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

131
Makefile.CrossWindows Normal file
View File

@ -0,0 +1,131 @@
# 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)" "$(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 \
-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 \
-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 \
-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 \
-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 > SHA256SUM.txt

290
README.md
View File

@ -2,7 +2,7 @@
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 _Mac OS_.
and _MacOS_.
![screenshot](assets/screenshot-debian-600.jpg)
@ -11,25 +11,26 @@ and _Mac OS_.
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`:
On Windows, if you use the [prebuilt application](#windows), it is already
included. Otherwise, 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`
Make sure you [enabled adb debugging][enable-adb] on your device(s).
The client requires [FFmpeg] and [LibSDL2].
[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
@ -37,15 +38,44 @@ The client requires _FFmpeg_ and _LibSDL2_.
#### Linux
Install the required packages from your package manager (here, for Debian):
Install the required packages from your package manager.
# runtime dependencies
sudo apt install ffmpeg libsdl2-2.0.0
##### Debian/Ubuntu
# build dependencies
sudo apt install make gcc openjdk-8-jdk pkg-config meson zip \
libavcodec-dev libavformat-dev libavutil-dev \
libsdl2-dev
```bash
# runtime dependencies
sudo apt install ffmpeg libsdl2-2.0.0
# client build dependencies
sudo apt install make gcc pkg-config meson \
libavcodec-dev libavformat-dev libavutil-dev \
libsdl2-dev
# server build dependencies
sudo apt install openjdk-8-jdk
```
##### Fedora
```bash
# enable RPM fusion free
sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
# client build dependencies
sudo dnf install SDL2-devel ffms2-devel meson gcc make
# server build dependencies
sudo dnf install java
```
##### Arch Linux
Two [AUR] packages have been created by users:
- [`scrcpy`](https://aur.archlinux.org/packages/scrcpy/)
- [`scrcpy-prebuiltserver`](https://aur.archlinux.org/packages/scrcpy-prebuiltserver/)
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
#### Windows
@ -53,52 +83,114 @@ Install the required packages from your package manager (here, for Debian):
For Windows, for simplicity, a prebuilt archive with all the dependencies
(including `adb`) is available:
- [`scrcpy-windows-with-deps-v1.0.zip`][direct-windows-with-deps].
_(SHA-256: bc4bf32600e8548cdce490f94bed5dcba0006cdd38aff95748972e5d9877dd62)_
- [`scrcpy-windows-with-deps-v1.1.zip`][direct-windows-with-deps].
_(SHA-256: 27eb36c15937601d1062c1dc0b45faae0e06fefea2019aadeb4fa7f76a07bb4c)_
[direct-windows-with-deps]: https://github.com/Genymobile/scrcpy/releases/download/v1.0/scrcpy-windows-with-deps-v1.0.zip
[direct-windows-with-deps]: https://github.com/Genymobile/scrcpy/releases/download/v1.1/scrcpy-windows-with-deps-v1.1.zip
_(It's just a portable version including _dll_ copied from MSYS2.)_
Instead, you may want to build it manually. You need [MSYS2] to build the
project. From an MSYS2 terminal, install the required packages:
Instead, you may want to build it manually.
##### 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/
# runtime dependencies
pacman -S mingw-w64-x86_64-SDL2 \
mingw-w64-x86_64-ffmpeg
```bash
# runtime dependencies
pacman -S mingw-w64-x86_64-SDL2 \
mingw-w64-x86_64-ffmpeg
# build dependencies
pacman -S mingw-w64-x86_64-make \
mingw-w64-x86_64-gcc \
mingw-w64-x86_64-pkg-config \
mingw-w64-x86_64-meson \
zip
# client build dependencies
pacman -S mingw-w64-x86_64-make \
mingw-w64-x86_64-gcc \
mingw-w64-x86_64-pkg-config \
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`:
export PATH="$JAVA_HOME/bin:$PATH"
```bash
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/
# runtime dependencies
brew install sdl2 ffmpeg
```bash
brew install scrcpy
```
# build dependencies
brew install gcc pkg-config meson zip
Instead, you may want to build it manually. Install the packages:
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`:
export PATH="$JAVA_HOME/bin:$PATH"
```bash
# runtime dependencies
brew install sdl2 ffmpeg
# client build dependencies
brew install pkg-config meson
```
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
@ -107,21 +199,36 @@ its directory. For example:
[Android SDK]: https://developer.android.com/studio/index.html
export ANDROID_HOME=~/android/sdk
```bash
export ANDROID_HOME=~/android/sdk
```
Then, build `scrcpy`:
Clone the project:
meson x --buildtype release --strip -Db_lto=true
cd x
ninja
```bash
git clone https://github.com/Genymobile/scrcpy
cd scrcpy
```
Then, build:
```bash
meson x --buildtype release --strip -Db_lto=true
cd x
ninja
```
You can test it from here:
ninja run
```bash
ninja run
```
Or you can install it on the system:
sudo ninja install # without sudo on Windows
```bash
sudo ninja install # without sudo on Windows
```
This installs two files:
@ -137,22 +244,23 @@ 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.1.jar`][direct-scrcpy-server].
_(SHA-256: 14826512bf38447ec94adf3b531676ce038d19e7e06757fb4e537882b17e77b3)_
[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.1/scrcpy-server-v1.1.jar
In that case, the build does not require Java or the Android SDK.
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:
meson x --buildtype release --strip -Db_lto=true \
-Dprebuilt_server=/path/to/scrcpy-server.jar
cd x
ninja
sudo ninja install
```bash
meson x --buildtype release --strip -Db_lto=true \
-Dprebuilt_server=/path/to/scrcpy-server.jar
cd x
ninja
sudo ninja install
```
## Run
@ -160,50 +268,70 @@ _At runtime, `adb` must be accessible from your `PATH`._
If everything is ok, just plug an Android device, and execute:
scrcpy
```bash
scrcpy
```
It accepts command-line arguments, listed by:
scrcpy --help
```bash
scrcpy --help
```
For example, to decrease video bitrate to 2Mbps (default is 8Mbps):
scrcpy -b 2M
```bash
scrcpy -b 2M
```
To limit the video dimensions (e.g. if the device is 2540×1440, but the host
screen is smaller, or cannot decode such a high definition):
scrcpy -m 1024
```bash
scrcpy -m 1024
```
If several devices are listed in `adb devices`, you must specify the _serial_:
scrcpy -s 0123456789abcdef
```bash
scrcpy -s 0123456789abcdef
```
To show physical touches while scrcpy is running:
```bash
scrcpy -t
```
To run without installing:
./run x [options]
```bash
./run x [options]
```
(where `x` is your build directory).
## Shortcuts
| Action | Shortcut |
| -------------------------------------- |:---------------------------- |
| switch fullscreen mode | `Ctrl`+`f` |
| resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` |
| resize window to remove black borders | `Ctrl`+`x` |
| 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 `POWER` | `Ctrl`+`p` |
| turn screen on | _Right-click¹_ |
| paste computer clipboard to device | `Ctrl`+`v` |
| enable/disable FPS counter (on stdout) | `Ctrl`+`i` |
| Action | Shortcut |
| -------------------------------------- |:---------------------------- |
| switch fullscreen mode | `Ctrl`+`f` |
| resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` |
| 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`+`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²_ |
| paste computer clipboard to device | `Ctrl`+`v` |
| enable/disable FPS counter (on stdout) | `Ctrl`+`i` |
Right-click turns the screen on if it was off, presses BACK otherwise._
Double-click on black borders to remove them._
_²Right-click turns the screen on if it was off, presses BACK otherwise._
## Why _scrcpy_?
@ -216,6 +344,11 @@ A colleague challenged me to find a name as unpronounceable as [gnirehtet].
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
## Common issues
See the [FAQ](FAQ.md).
## Developers
Read the [developers page].
@ -239,6 +372,7 @@ 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/)
- [Scrcpy now works wirelessly](https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/)

View File

@ -18,12 +18,54 @@ src = [
'src/tinyxpm.c',
]
dependencies = [
dependency('libavformat'),
dependency('libavcodec'),
dependency('libavutil'),
dependency('sdl2'),
]
if not meson.is_cross_build()
# 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 +84,7 @@ conf = configuration_data()
conf.set('BUILD_DEBUG', get_option('buildtype') == 'debug')
# the version, updated on release
conf.set_quoted('SCRCPY_VERSION', '1.0')
conf.set_quoted('SCRCPY_VERSION', '1.1')
# the prefix used during configuration (meson --prefix=PREFIX)
conf.set_quoted('PREFIX', get_option('prefix'))
@ -85,7 +127,17 @@ conf.set('HIDPI_SUPPORT', get_option('hidpi_support'))
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 +148,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() {

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"

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;
}
@ -172,7 +173,10 @@ SDL_bool mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from,
to->scroll_event.position = position;
int mul = from->direction == SDL_MOUSEWHEEL_NORMAL ? 1 : -1;
to->scroll_event.hscroll = mul * from->x;
// SDL behavior seems inconsistent between horizontal and vertical scrolling
// so reverse the horizontal
// <https://wiki.libsdl.org/SDL_MouseWheelEvent#Remarks>
to->scroll_event.hscroll = -mul * from->x;
to->scroll_event.vscroll = mul * 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,11 +29,6 @@ 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 void send_keycode(struct controller *controller, enum android_keycode keycode, const char *name) {
// send DOWN event
struct control_event control_event;
@ -78,6 +73,10 @@ static inline void action_volume_down(struct controller *controller) {
send_keycode(controller, AKEYCODE_VOLUME_DOWN, "VOLUME_DOWN");
}
static inline void action_menu(struct controller *controller) {
send_keycode(controller, AKEYCODE_MENU, "MENU");
}
// turn the screen on if it was off, press BACK otherwise
static void press_back_or_turn_screen_on(struct controller *controller) {
struct control_event control_event;
@ -124,18 +123,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);
@ -176,12 +163,21 @@ void input_manager_process_key(struct input_manager *input_manager,
case SDLK_BACKSPACE:
action_back(input_manager->controller);
return;
case SDLK_m:
case SDLK_s:
action_app_switch(input_manager->controller);
return;
case SDLK_m:
action_menu(input_manager->controller);
return;
case SDLK_p:
action_power(input_manager->controller);
return;
case SDLK_DOWN:
action_volume_down(input_manager->controller);
return;
case SDLK_UP:
action_volume_up(input_manager->controller);
return;
case SDLK_v:
clipboard_paste(input_manager->controller);
return;
@ -235,6 +231,17 @@ void input_manager_process_mouse_button(struct input_manager *input_manager,
action_home(input_manager->controller);
return;
}
// double-click on black borders resize to fit the device screen
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
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;
}
// otherwise, send the click event to the device
}
};
struct control_event control_event;
if (mouse_button_from_sdl_to_android(event, input_manager->screen->frame_size, &control_event)) {
@ -253,7 +260,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");
}
}
}

View File

@ -12,6 +12,7 @@ struct args {
const char *serial;
SDL_bool help;
SDL_bool version;
SDL_bool show_touches;
Uint16 port;
Uint16 max_size;
Uint32 bit_rate;
@ -45,6 +46,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"
@ -57,6 +62,7 @@ static void usage(const char *arg0) {
" resize window to 1:1 (pixel-perfect)\n"
"\n"
" Ctrl+x\n"
" Double-click on black borders\n"
" resize window to remove black borders\n"
"\n"
" Ctrl+h\n"
@ -69,13 +75,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"
@ -179,47 +188,45 @@ 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'},
{"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: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 '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;
@ -235,10 +242,17 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
}
int main(int argc, char *argv[]) {
#ifdef __WINDOWS__
// disable buffering, we want logs immediately
// even line buffering (setvbuf() with mode _IOLBF) is not sufficient
setbuf(stdout, NULL);
setbuf(stderr, NULL);
#endif
struct args args = {
.serial = 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,
@ -267,7 +281,14 @@ 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,
.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

@ -35,7 +35,29 @@ static struct input_manager input_manager = {
.screen = &screen,
};
#if defined(__APPLE__) || defined(__WINDOWS__)
# define CONTINUOUS_RESIZING_WORKAROUND
#endif
#ifdef CONTINUOUS_RESIZING_WORKAROUND
// On Windows and MacOS, resizing blocks the event loop, so resizing events are
// not triggered. As a workaround, handle them in an event handler.
//
// <https://bugzilla.libsdl.org/show_bug.cgi?id=2077>
// <https://stackoverflow.com/a/40693139/1987178>
static int event_watcher(void *data, SDL_Event *event) {
if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_RESIZED) {
// called from another thread, not very safe, but it's a workaround!
screen_render(&screen);
}
return 0;
}
#endif
static void event_loop(void) {
#ifdef CONTINUOUS_RESIZING_WORKAROUND
SDL_AddEventWatch(event_watcher, NULL);
#endif
SDL_Event event;
while (SDL_WaitEvent(&event)) {
switch (event.type) {
@ -57,16 +79,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);
@ -74,36 +95,56 @@ 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;
}
}
}
}
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)) {
return SDL_FALSE;
}
process_t proc_show_touches = NULL;
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;
@ -153,10 +194,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);
@ -170,6 +217,16 @@ finally_stop_decoder:
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,14 @@
#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;
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>
@ -151,15 +152,20 @@ SDL_bool server_start(struct server *server, const char *serial, Uint16 local_po
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,6 +182,7 @@ 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;
}
}
@ -187,6 +194,7 @@ SDL_bool server_start(struct server *server, const char *serial, Uint16 local_po
close_socket(&server->server_socket);
}
disable_tunnel(server);
SDL_free((void *) server->serial);
return SDL_FALSE;
}
@ -195,11 +203,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 = 50;
Uint32 delay = 100; // ms
server->device_socket = connect_to_server(server->local_port, attempts, delay);
}

View File

@ -34,7 +34,7 @@ SDL_bool server_start(struct server *server, const char *serial, Uint16 local_po
Uint16 max_size, Uint32 bit_rate);
// 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,4 +1,4 @@
#include "../../command.h"
#include "command.h"
#include <signal.h>
#include <sys/types.h>

View File

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

View File

@ -1,7 +1,7 @@
#include "../../command.h"
#include "command.h"
#include "../../log.h"
#include "../../strutil.h"
#include "log.h"
#include "strutil.h"
HANDLE cmd_execute(const char *path, const char *const argv[]) {
STARTUPINFO si;

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"

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,5 +1,6 @@
option('build_app', type: 'boolean', value: true, description: 'Build the client')
option('build_server', type: 'boolean', value: true, description: 'Build the server')
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 1
versionName "1.0"
versionCode 2
versionName "1.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {

View File

@ -36,7 +36,11 @@ public final class DesktopConnection implements Closeable {
private static LocalSocket listenAndAccept(String abstractName) throws IOException {
LocalServerSocket localServerSocket = new LocalServerSocket(abstractName);
return localServerSocket.accept();
try {
return localServerSocket.accept();
} finally {
localServerSocket.close();
}
}
public static DesktopConnection open(Device device, boolean tunnelForward) throws IOException {

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 {