Compare commits
40 Commits
finger
...
textevents
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
532843d856 | ||
|
|
44791d6b40 | ||
|
|
2d90e1befd | ||
|
|
0e301ddf19 | ||
|
|
c42ff75b74 | ||
|
|
b0db1178d1 | ||
|
|
8d601d3210 | ||
|
|
683f7ca848 | ||
|
|
120f08ee96 | ||
|
|
0415672a75 | ||
|
|
3ea4742321 | ||
|
|
95fd64b5de | ||
|
|
ab205084dc | ||
|
|
4696878a97 | ||
|
|
3da95b52bd | ||
|
|
c72f677435 | ||
|
|
1380f6e00f | ||
|
|
d841718956 | ||
|
|
17d53be3ef | ||
|
|
f9938dbf88 | ||
|
|
f6c8460ebb | ||
|
|
c33a147fd0 | ||
|
|
8b33c6c108 | ||
|
|
5b7a0cd8e9 | ||
|
|
bab9361948 | ||
|
|
6220456def | ||
|
|
7e1d52c119 | ||
|
|
280d5b718c | ||
|
|
30168f0428 | ||
|
|
b5a2d99bc2 | ||
|
|
f765aae352 | ||
|
|
77f876e29c | ||
|
|
d90549d1e6 | ||
|
|
810ff80ba7 | ||
|
|
1f8ba1ca79 | ||
|
|
129dabcfa4 | ||
|
|
f510f1de1c | ||
|
|
7d1932b907 | ||
|
|
795d103032 | ||
|
|
513d1ac96d |
16
BUILD.md
16
BUILD.md
@@ -43,7 +43,7 @@ Install the required packages from your package manager.
|
|||||||
sudo apt install ffmpeg libsdl2-2.0-0
|
sudo apt install ffmpeg libsdl2-2.0-0
|
||||||
|
|
||||||
# client build dependencies
|
# client build dependencies
|
||||||
sudo apt install make gcc git pkg-config meson ninja-build \
|
sudo apt install gcc git pkg-config meson ninja-build \
|
||||||
libavcodec-dev libavformat-dev libavutil-dev \
|
libavcodec-dev libavformat-dev libavutil-dev \
|
||||||
libsdl2-dev
|
libsdl2-dev
|
||||||
|
|
||||||
@@ -195,8 +195,7 @@ Then, build:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
meson x --buildtype release --strip -Db_lto=true
|
meson x --buildtype release --strip -Db_lto=true
|
||||||
cd x
|
ninja -Cx
|
||||||
ninja
|
|
||||||
```
|
```
|
||||||
|
|
||||||
_Note: `ninja` [must][ninja-user] be run as a non-root user (only `ninja
|
_Note: `ninja` [must][ninja-user] be run as a non-root user (only `ninja
|
||||||
@@ -219,13 +218,13 @@ To run without installing:
|
|||||||
After a successful build, you can install _scrcpy_ on the system:
|
After a successful build, you can install _scrcpy_ on the system:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo ninja install # without sudo on Windows
|
sudo ninja -Cx install # without sudo on Windows
|
||||||
```
|
```
|
||||||
|
|
||||||
This installs two files:
|
This installs two files:
|
||||||
|
|
||||||
- `/usr/local/bin/scrcpy`
|
- `/usr/local/bin/scrcpy`
|
||||||
- `/usr/local/share/scrcpy/scrcpy-server.jar`
|
- `/usr/local/share/scrcpy/scrcpy-server`
|
||||||
|
|
||||||
Just remove them to "uninstall" the application.
|
Just remove them to "uninstall" the application.
|
||||||
|
|
||||||
@@ -244,8 +243,7 @@ configuration:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
meson x --buildtype release --strip -Db_lto=true \
|
meson x --buildtype release --strip -Db_lto=true \
|
||||||
-Dprebuilt_server=/path/to/scrcpy-server.jar
|
-Dprebuilt_server=/path/to/scrcpy-server
|
||||||
cd x
|
ninja -Cx
|
||||||
ninja
|
sudo ninja -Cx install
|
||||||
sudo ninja install
|
|
||||||
```
|
```
|
||||||
|
|||||||
34
DEVELOP.md
34
DEVELOP.md
@@ -3,7 +3,7 @@
|
|||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
This application is composed of two parts:
|
This application is composed of two parts:
|
||||||
- the server (`scrcpy-server.jar`), to be executed on the device,
|
- the server (`scrcpy-server`), to be executed on the device,
|
||||||
- the client (the `scrcpy` binary), executed on the host computer.
|
- the client (the `scrcpy` binary), executed on the host computer.
|
||||||
|
|
||||||
The client is responsible to push the server to the device and start its
|
The client is responsible to push the server to the device and start its
|
||||||
@@ -49,7 +49,7 @@ application may not replace the server just before the client executes it._
|
|||||||
Instead of a raw _dex_ file, `app_process` accepts a _jar_ containing
|
Instead of a raw _dex_ file, `app_process` accepts a _jar_ containing
|
||||||
`classes.dex` (e.g. an [APK]). For simplicity, and to benefit from the gradle
|
`classes.dex` (e.g. an [APK]). For simplicity, and to benefit from the gradle
|
||||||
build system, the server is built to an (unsigned) APK (renamed to
|
build system, the server is built to an (unsigned) APK (renamed to
|
||||||
`scrcpy-server.jar`).
|
`scrcpy-server`).
|
||||||
|
|
||||||
[dex]: https://en.wikipedia.org/wiki/Dalvik_(software)
|
[dex]: https://en.wikipedia.org/wiki/Dalvik_(software)
|
||||||
[apk]: https://en.wikipedia.org/wiki/Android_application_package
|
[apk]: https://en.wikipedia.org/wiki/Android_application_package
|
||||||
@@ -268,3 +268,33 @@ For more details, go read the code!
|
|||||||
|
|
||||||
If you find a bug, or have an awesome idea to implement, please discuss and
|
If you find a bug, or have an awesome idea to implement, please discuss and
|
||||||
contribute ;-)
|
contribute ;-)
|
||||||
|
|
||||||
|
|
||||||
|
### Debug the server
|
||||||
|
|
||||||
|
The server is pushed to the device by the client on startup.
|
||||||
|
|
||||||
|
To debug it, enable the server debugger during configuration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
meson x -Dserver_debugger=true
|
||||||
|
# or, if x is already configured
|
||||||
|
meson configure x -Dserver_debugger=true
|
||||||
|
```
|
||||||
|
|
||||||
|
Then recompile.
|
||||||
|
|
||||||
|
When you start scrcpy, it will start a debugger on port 5005 on the device.
|
||||||
|
Redirect that port to the computer:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb forward tcp:5005 tcp:5005
|
||||||
|
```
|
||||||
|
|
||||||
|
In Android Studio, _Run_ > _Debug_ > _Edit configurations..._ On the left, click on
|
||||||
|
`+`, _Remote_, and fill the form:
|
||||||
|
|
||||||
|
- Host: `localhost`
|
||||||
|
- Port: `5005`
|
||||||
|
|
||||||
|
Then click on _Debug_.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
#
|
#
|
||||||
# Here, "portable" means that the client and server binaries are expected to be
|
# Here, "portable" means that the client and server binaries are expected to be
|
||||||
# anywhere, but in the same directory, instead of well-defined separate
|
# anywhere, but in the same directory, instead of well-defined separate
|
||||||
# locations (e.g. /usr/bin/scrcpy and /usr/share/scrcpy/scrcpy-server.jar).
|
# locations (e.g. /usr/bin/scrcpy and /usr/share/scrcpy/scrcpy-server).
|
||||||
#
|
#
|
||||||
# In particular, this implies to change the location from where the client push
|
# In particular, this implies to change the location from where the client push
|
||||||
# the server to the device.
|
# the server to the device.
|
||||||
@@ -97,7 +97,7 @@ build-win64-noconsole: prepare-deps-win64
|
|||||||
|
|
||||||
dist-win32: build-server build-win32 build-win32-noconsole
|
dist-win32: build-server build-win32 build-win32-noconsole
|
||||||
mkdir -p "$(DIST)/$(WIN32_TARGET_DIR)"
|
mkdir -p "$(DIST)/$(WIN32_TARGET_DIR)"
|
||||||
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server.jar "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp "$(WIN32_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 "$(WIN32_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/scrcpy-noconsole.exe"
|
||||||
cp prebuilt-deps/ffmpeg-4.1.4-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp prebuilt-deps/ffmpeg-4.1.4-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
@@ -112,7 +112,7 @@ dist-win32: build-server build-win32 build-win32-noconsole
|
|||||||
|
|
||||||
dist-win64: build-server build-win64 build-win64-noconsole
|
dist-win64: build-server build-win64 build-win64-noconsole
|
||||||
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
|
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
|
||||||
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server.jar "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp "$(WIN64_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 "$(WIN64_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/scrcpy-noconsole.exe"
|
||||||
cp prebuilt-deps/ffmpeg-4.1.4-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp prebuilt-deps/ffmpeg-4.1.4-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
|
|||||||
@@ -358,7 +358,7 @@ To use a specific _adb_ binary, configure its path in the environment variable
|
|||||||
|
|
||||||
ADB=/path/to/adb scrcpy
|
ADB=/path/to/adb scrcpy
|
||||||
|
|
||||||
To override the path of the `scrcpy-server.jar` file, configure its path in
|
To override the path of the `scrcpy-server` file, configure its path in
|
||||||
`SCRCPY_SERVER_PATH`.
|
`SCRCPY_SERVER_PATH`.
|
||||||
|
|
||||||
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
|
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ conf.set_quoted('SCRCPY_VERSION', meson.project_version())
|
|||||||
# the prefix used during configuration (meson --prefix=PREFIX)
|
# the prefix used during configuration (meson --prefix=PREFIX)
|
||||||
conf.set_quoted('PREFIX', get_option('prefix'))
|
conf.set_quoted('PREFIX', get_option('prefix'))
|
||||||
|
|
||||||
# build a "portable" version (with scrcpy-server.jar accessible from the same
|
# build a "portable" version (with scrcpy-server accessible from the same
|
||||||
# directory as the executable)
|
# directory as the executable)
|
||||||
conf.set('PORTABLE', get_option('portable'))
|
conf.set('PORTABLE', get_option('portable'))
|
||||||
|
|
||||||
@@ -115,6 +115,9 @@ conf.set('HIDPI_SUPPORT', get_option('hidpi_support'))
|
|||||||
# disable console on Windows
|
# disable console on Windows
|
||||||
conf.set('WINDOWS_NOCONSOLE', get_option('windows_noconsole'))
|
conf.set('WINDOWS_NOCONSOLE', get_option('windows_noconsole'))
|
||||||
|
|
||||||
|
# run a server debugger and wait for a client to be attached
|
||||||
|
conf.set('SERVER_DEBUGGER', get_option('server_debugger'))
|
||||||
|
|
||||||
configure_file(configuration: conf, output: 'config.h')
|
configure_file(configuration: conf, output: 'config.h')
|
||||||
|
|
||||||
src_dir = include_directories('src')
|
src_dir = include_directories('src')
|
||||||
@@ -134,6 +137,8 @@ executable('scrcpy', src,
|
|||||||
c_args: c_args,
|
c_args: c_args,
|
||||||
link_args: link_args)
|
link_args: link_args)
|
||||||
|
|
||||||
|
install_man('scrcpy.1')
|
||||||
|
|
||||||
|
|
||||||
### TESTS
|
### TESTS
|
||||||
|
|
||||||
|
|||||||
241
app/scrcpy.1
Normal file
241
app/scrcpy.1
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
.TH "scrcpy" "1"
|
||||||
|
.SH NAME
|
||||||
|
scrcpy \- Display and control your Android device
|
||||||
|
|
||||||
|
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B scrcpy
|
||||||
|
.RI [ options ]
|
||||||
|
|
||||||
|
|
||||||
|
.SH DESCRIPTION
|
||||||
|
.B scrcpy
|
||||||
|
provides display and control of Android devices connected on USB (or over TCP/IP). It does not require any root access.
|
||||||
|
|
||||||
|
|
||||||
|
.SH OPTIONS
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-b, \-\-bit\-rate " value
|
||||||
|
Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000).
|
||||||
|
|
||||||
|
Default is 8000000.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-c, \-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy
|
||||||
|
Crop the device screen on the server.
|
||||||
|
|
||||||
|
The values are expressed in the device natural orientation (typically, portrait for a phone, landscape for a tablet). Any
|
||||||
|
.B \-\-max\-size
|
||||||
|
value is computed on the cropped size.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-f, \-\-fullscreen
|
||||||
|
Start in fullscreen.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-F, \-\-record\-format " format
|
||||||
|
Force recording format (either mp4 or mkv).
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-h, \-\-help
|
||||||
|
Print this help.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-m, \-\-max\-size " value
|
||||||
|
Limit both the width and height of the video to \fIvalue\fR. The other dimension is computed so that the device aspect\-ratio is preserved.
|
||||||
|
|
||||||
|
Default is 0 (unlimited).
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-n, \-\-no\-control
|
||||||
|
Disable device control (mirror the device in read\-only).
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-N, \-\-no\-display
|
||||||
|
Do not display device (only when screen recording is enabled).
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-p, \-\-port " port
|
||||||
|
Set the TCP port the client listens on.
|
||||||
|
|
||||||
|
Default is 27183.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI \-\-prefer\-text\-events " mode
|
||||||
|
Configure how key/text events are forwarded to the Android device.
|
||||||
|
|
||||||
|
Possible \fImode\fRs are "always" (every text is sent as text), "non-alpha"
|
||||||
|
(only letters are sent as a sequence of key events, other characters are sent
|
||||||
|
as text) and "never" (every text is sent as a sequence of key events).
|
||||||
|
|
||||||
|
Default is "always".
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-\-push\-target " path
|
||||||
|
Set the target directory for pushing files to the device by drag & drop. It is passed as\-is to "adb push".
|
||||||
|
|
||||||
|
Default is "/sdcard/".
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-r, \-\-record " file
|
||||||
|
Record screen to
|
||||||
|
.IR file .
|
||||||
|
|
||||||
|
The format is determined by the
|
||||||
|
.B \-F/\-\-record\-format
|
||||||
|
option if set, or by the file extension (.mp4 or .mkv).
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-\-render\-expired\-frames
|
||||||
|
By default, to minimize latency, scrcpy always renders the last available decoded frame, and drops any previous ones. This flag forces to render all frames, at a cost of a possible increased latency.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-s, \-\-serial " number
|
||||||
|
The device serial number. Mandatory only if several devices are connected to adb.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-S, \-\-turn\-screen\-off
|
||||||
|
Turn the device screen off immediately.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-t, \-\-show\-touches
|
||||||
|
Enable "show touches" on start, disable on quit.
|
||||||
|
|
||||||
|
It only shows physical touches (not clicks from scrcpy).
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-T, \-\-always\-on\-top
|
||||||
|
Make scrcpy window always on top (above other windows).
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-v, \-\-version
|
||||||
|
Print the version of scrcpy.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI \-\-window\-title " text
|
||||||
|
Set a custom window title.
|
||||||
|
|
||||||
|
|
||||||
|
.SH SHORTCUTS
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B Ctrl+f
|
||||||
|
switch fullscreen mode
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B Ctrl+g
|
||||||
|
resize window to 1:1 (pixel\-perfect)
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B Ctrl+x, Double\-click on black borders
|
||||||
|
resize window to remove black borders
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B Ctrl+h, Home, Middle\-click
|
||||||
|
Click on HOME
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B Ctrl+b, Ctrl+Backspace, Right\-click (when screen is on)
|
||||||
|
Click on BACK
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B Ctrl+s
|
||||||
|
Click on APP_SWITCH
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B Ctrl+m
|
||||||
|
Click on MENU
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B Ctrl+Up
|
||||||
|
Click on VOLUME_UP
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B Ctrl+Down
|
||||||
|
Click on VOLUME_DOWN
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B Ctrl+p
|
||||||
|
Click on POWER (turn screen on/off)
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B Right\-click (when screen is off)
|
||||||
|
turn screen on
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B Ctrl+o
|
||||||
|
turn device screen off (keep mirroring)
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B Ctrl+n
|
||||||
|
expand notification panel
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B Ctrl+Shift+n
|
||||||
|
collapse notification panel
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B Ctrl+c
|
||||||
|
copy device clipboard to computer
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B Ctrl+v
|
||||||
|
paste computer clipboard to device
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B Ctrl+Shift+v
|
||||||
|
copy computer clipboard to device
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B Ctrl+i
|
||||||
|
enable/disable FPS counter (print frames/second in logs)
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B Drag & drop APK file
|
||||||
|
install APK from computer
|
||||||
|
|
||||||
|
|
||||||
|
.SH Environment variables
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B ADB
|
||||||
|
Specify the path to adb.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B SCRCPY_SERVER_PATH
|
||||||
|
Specify the path to server binary.
|
||||||
|
|
||||||
|
|
||||||
|
.SH AUTHORS
|
||||||
|
.B scrcpy
|
||||||
|
is written by Romain Vimont.
|
||||||
|
|
||||||
|
This manual page was written by
|
||||||
|
.MT mmyangfl@gmail.com
|
||||||
|
Yangfl
|
||||||
|
.ME
|
||||||
|
for the Debian Project (and may be used by others).
|
||||||
|
|
||||||
|
|
||||||
|
.SH "REPORTING BUGS"
|
||||||
|
Report bugs to
|
||||||
|
.UR https://github.com/Genymobile/scrcpy/issues
|
||||||
|
.UE .
|
||||||
|
|
||||||
|
.SH COPYRIGHT
|
||||||
|
Copyright \(co 2018 Genymobile
|
||||||
|
.UR https://www.genymobile.com
|
||||||
|
Genymobile
|
||||||
|
.UE
|
||||||
|
|
||||||
|
Copyright \(co 2018\-2019
|
||||||
|
.MT rom@rom1v.com
|
||||||
|
Romain Vimont
|
||||||
|
.ME
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
.SH WWW
|
||||||
|
.UR https://github.com/Genymobile/scrcpy
|
||||||
|
.UE
|
||||||
@@ -4,6 +4,8 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
buffer_write16be(uint8_t *buf, uint16_t value) {
|
buffer_write16be(uint8_t *buf, uint16_t value) {
|
||||||
buf[0] = value >> 8;
|
buf[0] = value >> 8;
|
||||||
@@ -18,6 +20,12 @@ buffer_write32be(uint8_t *buf, uint32_t value) {
|
|||||||
buf[3] = value;
|
buf[3] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
buffer_write64be(uint8_t *buf, uint64_t value) {
|
||||||
|
buffer_write32be(buf, value >> 32);
|
||||||
|
buffer_write32be(&buf[4], (uint32_t) value);
|
||||||
|
}
|
||||||
|
|
||||||
static inline uint16_t
|
static inline uint16_t
|
||||||
buffer_read16be(const uint8_t *buf) {
|
buffer_read16be(const uint8_t *buf) {
|
||||||
return (buf[0] << 8) | buf[1];
|
return (buf[0] << 8) | buf[1];
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
// To define a circular buffer type of 20 ints:
|
// To define a circular buffer type of 20 ints:
|
||||||
// struct cbuf_int CBUF(int, 20);
|
// struct cbuf_int CBUF(int, 20);
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "str_util.h"
|
#include "str_util.h"
|
||||||
|
|||||||
@@ -33,6 +33,8 @@
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
# define NO_EXIT_CODE -1
|
# define NO_EXIT_CODE -1
|
||||||
|
|
||||||
enum process_result {
|
enum process_result {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
|
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
|
||||||
#define MIN(X,Y) (X) < (Y) ? (X) : (Y)
|
#define MIN(X,Y) (X) < (Y) ? (X) : (Y)
|
||||||
#define MAX(X,Y) (X) > (Y) ? (X) : (Y)
|
#define MAX(X,Y) (X) > (Y) ? (X) : (Y)
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
#include "control_msg.h"
|
#include "control_msg.h"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <SDL2/SDL_assert.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "buffer_util.h"
|
#include "buffer_util.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "str_util.h"
|
#include "str_util.h"
|
||||||
@@ -23,6 +25,16 @@ write_string(const char *utf8, size_t max_len, unsigned char *buf) {
|
|||||||
return 2 + len;
|
return 2 + len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint16_t
|
||||||
|
to_fixed_point_16(float f) {
|
||||||
|
SDL_assert(f >= 0.0f && f <= 1.0f);
|
||||||
|
uint32_t u = f * 0x1p16f; // 2^16
|
||||||
|
if (u >= 0xffff) {
|
||||||
|
u = 0xffff;
|
||||||
|
}
|
||||||
|
return (uint16_t) u;
|
||||||
|
}
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
||||||
buf[0] = msg->type;
|
buf[0] = msg->type;
|
||||||
@@ -37,11 +49,15 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
|||||||
CONTROL_MSG_TEXT_MAX_LENGTH, &buf[1]);
|
CONTROL_MSG_TEXT_MAX_LENGTH, &buf[1]);
|
||||||
return 1 + len;
|
return 1 + len;
|
||||||
}
|
}
|
||||||
case CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT:
|
case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
|
||||||
buf[1] = msg->inject_mouse_event.action;
|
buf[1] = msg->inject_touch_event.action;
|
||||||
buffer_write32be(&buf[2], msg->inject_mouse_event.buttons);
|
buffer_write64be(&buf[2], msg->inject_touch_event.pointer_id);
|
||||||
write_position(&buf[6], &msg->inject_mouse_event.position);
|
write_position(&buf[10], &msg->inject_touch_event.position);
|
||||||
return 18;
|
uint16_t pressure =
|
||||||
|
to_fixed_point_16(msg->inject_touch_event.pressure);
|
||||||
|
buffer_write16be(&buf[22], pressure);
|
||||||
|
buffer_write32be(&buf[24], msg->inject_touch_event.buttons);
|
||||||
|
return 28;
|
||||||
case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
|
case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
|
||||||
write_position(&buf[1], &msg->inject_scroll_event.position);
|
write_position(&buf[1], &msg->inject_scroll_event.position);
|
||||||
buffer_write32be(&buf[13],
|
buffer_write32be(&buf[13],
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "android/input.h"
|
#include "android/input.h"
|
||||||
#include "android/keycodes.h"
|
#include "android/keycodes.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
@@ -14,10 +15,12 @@
|
|||||||
#define CONTROL_MSG_SERIALIZED_MAX_SIZE \
|
#define CONTROL_MSG_SERIALIZED_MAX_SIZE \
|
||||||
(3 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH)
|
(3 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH)
|
||||||
|
|
||||||
|
#define POINTER_ID_MOUSE UINT64_C(-1);
|
||||||
|
|
||||||
enum control_msg_type {
|
enum control_msg_type {
|
||||||
CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
||||||
CONTROL_MSG_TYPE_INJECT_TEXT,
|
CONTROL_MSG_TYPE_INJECT_TEXT,
|
||||||
CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT,
|
CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
||||||
CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
|
CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
|
||||||
CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
||||||
CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
|
CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
|
||||||
@@ -47,8 +50,10 @@ struct control_msg {
|
|||||||
struct {
|
struct {
|
||||||
enum android_motionevent_action action;
|
enum android_motionevent_action action;
|
||||||
enum android_motionevent_buttons buttons;
|
enum android_motionevent_buttons buttons;
|
||||||
|
uint64_t pointer_id;
|
||||||
struct position position;
|
struct position position;
|
||||||
} inject_mouse_event;
|
float pressure;
|
||||||
|
} inject_touch_event;
|
||||||
struct {
|
struct {
|
||||||
struct position position;
|
struct position position;
|
||||||
int32_t hscroll;
|
int32_t hscroll;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <SDL2/SDL_mutex.h>
|
#include <SDL2/SDL_mutex.h>
|
||||||
#include <SDL2/SDL_thread.h>
|
#include <SDL2/SDL_thread.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "cbuf.h"
|
#include "cbuf.h"
|
||||||
#include "control_msg.h"
|
#include "control_msg.h"
|
||||||
#include "net.h"
|
#include "net.h"
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
#include <SDL2/SDL_thread.h>
|
#include <SDL2/SDL_thread.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "compat.h"
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "compat.h"
|
||||||
#include "buffer_util.h"
|
#include "buffer_util.h"
|
||||||
#include "events.h"
|
#include "events.h"
|
||||||
#include "lock_util.h"
|
#include "lock_util.h"
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
struct video_buffer;
|
struct video_buffer;
|
||||||
|
|
||||||
struct decoder {
|
struct decoder {
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
#include "device.h"
|
#include "device.h"
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "net.h"
|
#include "net.h"
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <SDL2/SDL_assert.h>
|
#include <SDL2/SDL_assert.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "buffer_util.h"
|
#include "buffer_util.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
#define DEVICE_MSG_TEXT_MAX_LENGTH 4093
|
#define DEVICE_MSG_TEXT_MAX_LENGTH 4093
|
||||||
#define DEVICE_MSG_SERIALIZED_MAX_SIZE (3 + DEVICE_MSG_TEXT_MAX_LENGTH)
|
#define DEVICE_MSG_SERIALIZED_MAX_SIZE (3 + DEVICE_MSG_TEXT_MAX_LENGTH)
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
#include "event_converter.h"
|
#include "event_converter.h"
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
#define MAP(FROM, TO) case FROM: *to = TO; return true
|
#define MAP(FROM, TO) case FROM: *to = TO; return true
|
||||||
#define FAIL default: return false
|
#define FAIL default: return false
|
||||||
|
|
||||||
static bool
|
bool
|
||||||
convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) {
|
convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) {
|
||||||
switch (from) {
|
switch (from) {
|
||||||
MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN);
|
MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN);
|
||||||
@@ -31,7 +33,7 @@ autocomplete_metastate(enum android_metastate metastate) {
|
|||||||
return metastate;
|
return metastate;
|
||||||
}
|
}
|
||||||
|
|
||||||
static enum android_metastate
|
enum android_metastate
|
||||||
convert_meta_state(SDL_Keymod mod) {
|
convert_meta_state(SDL_Keymod mod) {
|
||||||
enum android_metastate metastate = 0;
|
enum android_metastate metastate = 0;
|
||||||
if (mod & KMOD_LSHIFT) {
|
if (mod & KMOD_LSHIFT) {
|
||||||
@@ -72,8 +74,9 @@ convert_meta_state(SDL_Keymod mod) {
|
|||||||
return autocomplete_metastate(metastate);
|
return autocomplete_metastate(metastate);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
bool
|
||||||
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod) {
|
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
||||||
|
enum text_events_pref pref) {
|
||||||
switch (from) {
|
switch (from) {
|
||||||
MAP(SDLK_RETURN, AKEYCODE_ENTER);
|
MAP(SDLK_RETURN, AKEYCODE_ENTER);
|
||||||
MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER);
|
MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER);
|
||||||
@@ -90,6 +93,16 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod) {
|
|||||||
MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN);
|
MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN);
|
||||||
MAP(SDLK_UP, AKEYCODE_DPAD_UP);
|
MAP(SDLK_UP, AKEYCODE_DPAD_UP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pref == PREFER_TEXT_EVENTS_ALWAYS) {
|
||||||
|
// never forward key events
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// forward all supported key events
|
||||||
|
SDL_assert(pref == PREFER_TEXT_EVENTS_NEVER ||
|
||||||
|
pref == PREFER_TEXT_EVENTS_NON_ALPHA);
|
||||||
|
|
||||||
if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) {
|
if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -126,16 +139,7 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
enum android_motionevent_buttons
|
||||||
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
|
|
||||||
switch (from) {
|
|
||||||
MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN);
|
|
||||||
MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP);
|
|
||||||
FAIL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static enum android_motionevent_buttons
|
|
||||||
convert_mouse_buttons(uint32_t state) {
|
convert_mouse_buttons(uint32_t state) {
|
||||||
enum android_motionevent_buttons buttons = 0;
|
enum android_motionevent_buttons buttons = 0;
|
||||||
if (state & SDL_BUTTON_LMASK) {
|
if (state & SDL_BUTTON_LMASK) {
|
||||||
@@ -147,77 +151,30 @@ convert_mouse_buttons(uint32_t state) {
|
|||||||
if (state & SDL_BUTTON_MMASK) {
|
if (state & SDL_BUTTON_MMASK) {
|
||||||
buttons |= AMOTION_EVENT_BUTTON_TERTIARY;
|
buttons |= AMOTION_EVENT_BUTTON_TERTIARY;
|
||||||
}
|
}
|
||||||
if (state & SDL_BUTTON_X1) {
|
if (state & SDL_BUTTON_X1MASK) {
|
||||||
buttons |= AMOTION_EVENT_BUTTON_BACK;
|
buttons |= AMOTION_EVENT_BUTTON_BACK;
|
||||||
}
|
}
|
||||||
if (state & SDL_BUTTON_X2) {
|
if (state & SDL_BUTTON_X2MASK) {
|
||||||
buttons |= AMOTION_EVENT_BUTTON_FORWARD;
|
buttons |= AMOTION_EVENT_BUTTON_FORWARD;
|
||||||
}
|
}
|
||||||
return buttons;
|
return buttons;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to) {
|
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
|
||||||
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
|
switch (from) {
|
||||||
|
MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN);
|
||||||
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) {
|
MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP);
|
||||||
return false;
|
FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t mod = from->keysym.mod;
|
|
||||||
if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
to->inject_keycode.metastate = convert_meta_state(mod);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
convert_mouse_button(const SDL_MouseButtonEvent *from, struct size screen_size,
|
convert_touch_action(SDL_EventType from, enum android_motionevent_action *to) {
|
||||||
struct control_msg *to) {
|
switch (from) {
|
||||||
to->type = CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT;
|
MAP(SDL_FINGERMOTION, AMOTION_EVENT_ACTION_MOVE);
|
||||||
|
MAP(SDL_FINGERDOWN, AMOTION_EVENT_ACTION_DOWN);
|
||||||
if (!convert_mouse_action(from->type, &to->inject_mouse_event.action)) {
|
MAP(SDL_FINGERUP, AMOTION_EVENT_ACTION_UP);
|
||||||
return false;
|
FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
to->inject_mouse_event.buttons =
|
|
||||||
convert_mouse_buttons(SDL_BUTTON(from->button));
|
|
||||||
to->inject_mouse_event.position.screen_size = screen_size;
|
|
||||||
to->inject_mouse_event.position.point.x = from->x;
|
|
||||||
to->inject_mouse_event.position.point.y = from->y;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
convert_mouse_motion(const SDL_MouseMotionEvent *from, struct size screen_size,
|
|
||||||
struct control_msg *to) {
|
|
||||||
to->type = CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT;
|
|
||||||
to->inject_mouse_event.action = AMOTION_EVENT_ACTION_MOVE;
|
|
||||||
to->inject_mouse_event.buttons = convert_mouse_buttons(from->state);
|
|
||||||
to->inject_mouse_event.position.screen_size = screen_size;
|
|
||||||
to->inject_mouse_event.position.point.x = from->x;
|
|
||||||
to->inject_mouse_event.position.point.y = from->y;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct position position,
|
|
||||||
struct control_msg *to) {
|
|
||||||
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;
|
|
||||||
|
|
||||||
to->inject_scroll_event.position = position;
|
|
||||||
|
|
||||||
int mul = from->direction == SDL_MOUSEWHEEL_NORMAL ? 1 : -1;
|
|
||||||
// SDL behavior seems inconsistent between horizontal and vertical scrolling
|
|
||||||
// so reverse the horizontal
|
|
||||||
// <https://wiki.libsdl.org/SDL_MouseWheelEvent#Remarks>
|
|
||||||
to->inject_scroll_event.hscroll = -mul * from->x;
|
|
||||||
to->inject_scroll_event.vscroll = mul * from->y;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,36 +2,30 @@
|
|||||||
#define CONVERT_H
|
#define CONVERT_H
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <SDL2/SDL_assert.h>
|
||||||
#include <SDL2/SDL_events.h>
|
#include <SDL2/SDL_events.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "control_msg.h"
|
#include "control_msg.h"
|
||||||
|
#include "input_manager.h"
|
||||||
struct complete_mouse_motion_event {
|
|
||||||
SDL_MouseMotionEvent *mouse_motion_event;
|
|
||||||
struct size screen_size;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct complete_mouse_wheel_event {
|
|
||||||
SDL_MouseWheelEvent *mouse_wheel_event;
|
|
||||||
struct point position;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to);
|
convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to);
|
||||||
|
|
||||||
|
enum android_metastate
|
||||||
|
convert_meta_state(SDL_Keymod mod);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
convert_mouse_button(const SDL_MouseButtonEvent *from, struct size screen_size,
|
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
||||||
struct control_msg *to);
|
enum text_events_pref pref);
|
||||||
|
|
||||||
// the video size may be different from the real device size, so we need the
|
enum android_motionevent_buttons
|
||||||
// size to which the absolute position apply, to scale it accordingly
|
convert_mouse_buttons(uint32_t state);
|
||||||
bool
|
|
||||||
convert_mouse_motion(const SDL_MouseMotionEvent *from, struct size screen_size,
|
|
||||||
struct control_msg *to);
|
|
||||||
|
|
||||||
// on Android, a scroll event requires the current mouse position
|
|
||||||
bool
|
bool
|
||||||
convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct position position,
|
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to);
|
||||||
struct control_msg *to);
|
|
||||||
|
bool
|
||||||
|
convert_touch_action(SDL_EventType from, enum android_motionevent_action *to);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <SDL2/SDL_mutex.h>
|
#include <SDL2/SDL_mutex.h>
|
||||||
#include <SDL2/SDL_thread.h>
|
#include <SDL2/SDL_thread.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "cbuf.h"
|
#include "cbuf.h"
|
||||||
#include "command.h"
|
#include "command.h"
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <SDL2/SDL_assert.h>
|
#include <SDL2/SDL_assert.h>
|
||||||
#include <SDL2/SDL_timer.h>
|
#include <SDL2/SDL_timer.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "lock_util.h"
|
#include "lock_util.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
#include <SDL2/SDL_mutex.h>
|
#include <SDL2/SDL_mutex.h>
|
||||||
#include <SDL2/SDL_thread.h>
|
#include <SDL2/SDL_thread.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
struct fps_counter {
|
struct fps_counter {
|
||||||
SDL_Thread *thread;
|
SDL_Thread *thread;
|
||||||
SDL_mutex *mutex;
|
SDL_mutex *mutex;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#include "input_manager.h"
|
#include "input_manager.h"
|
||||||
|
|
||||||
#include <SDL2/SDL_assert.h>
|
#include <SDL2/SDL_assert.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "event_converter.h"
|
#include "event_converter.h"
|
||||||
#include "lock_util.h"
|
#include "lock_util.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
@@ -102,7 +104,7 @@ press_back_or_turn_screen_on(struct controller *controller) {
|
|||||||
msg.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON;
|
msg.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON;
|
||||||
|
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
LOGW("Could not request 'turn screen on'");
|
LOGW("Could not request 'press back or turn screen on'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,14 +212,22 @@ clipboard_paste(struct controller *controller) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
input_manager_process_text_input(struct input_manager *input_manager,
|
input_manager_process_text_input(struct input_manager *im,
|
||||||
const SDL_TextInputEvent *event) {
|
const SDL_TextInputEvent *event) {
|
||||||
char c = event->text[0];
|
if (im->text_events_pref == PREFER_TEXT_EVENTS_NEVER) {
|
||||||
if (isalpha(c) || c == ' ') {
|
// ignore all text events (key events will be injected instead)
|
||||||
SDL_assert(event->text[1] == '\0');
|
|
||||||
// letters and space are handled as raw key event
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (im->text_events_pref == PREFER_TEXT_EVENTS_NON_ALPHA) {
|
||||||
|
char c = event->text[0];
|
||||||
|
if (isalpha(c) || c == ' ') {
|
||||||
|
SDL_assert(event->text[1] == '\0');
|
||||||
|
// letters and space are handled as raw key event
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
|
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
|
||||||
msg.inject_text.text = SDL_strdup(event->text);
|
msg.inject_text.text = SDL_strdup(event->text);
|
||||||
@@ -225,14 +235,34 @@ input_manager_process_text_input(struct input_manager *input_manager,
|
|||||||
LOGW("Could not strdup input text");
|
LOGW("Could not strdup input text");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!controller_push_msg(input_manager->controller, &msg)) {
|
if (!controller_push_msg(im->controller, &msg)) {
|
||||||
SDL_free(msg.inject_text.text);
|
SDL_free(msg.inject_text.text);
|
||||||
LOGW("Could not request 'inject text'");
|
LOGW("Could not request 'inject text'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
|
||||||
|
enum text_events_pref pref) {
|
||||||
|
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
|
||||||
|
|
||||||
|
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t mod = from->keysym.mod;
|
||||||
|
if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod,
|
||||||
|
pref)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
to->inject_keycode.metastate = convert_meta_state(mod);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
input_manager_process_key(struct input_manager *input_manager,
|
input_manager_process_key(struct input_manager *im,
|
||||||
const SDL_KeyboardEvent *event,
|
const SDL_KeyboardEvent *event,
|
||||||
bool control) {
|
bool control) {
|
||||||
// control: indicates the state of the command-line option --no-control
|
// control: indicates the state of the command-line option --no-control
|
||||||
@@ -259,7 +289,7 @@ input_manager_process_key(struct input_manager *input_manager,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct controller *controller = input_manager->controller;
|
struct controller *controller = im->controller;
|
||||||
|
|
||||||
// capture all Ctrl events
|
// capture all Ctrl events
|
||||||
if (ctrl || cmd) {
|
if (ctrl || cmd) {
|
||||||
@@ -334,23 +364,23 @@ input_manager_process_key(struct input_manager *input_manager,
|
|||||||
return;
|
return;
|
||||||
case SDLK_f:
|
case SDLK_f:
|
||||||
if (!shift && cmd && !repeat && down) {
|
if (!shift && cmd && !repeat && down) {
|
||||||
screen_switch_fullscreen(input_manager->screen);
|
screen_switch_fullscreen(im->screen);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_x:
|
case SDLK_x:
|
||||||
if (!shift && cmd && !repeat && down) {
|
if (!shift && cmd && !repeat && down) {
|
||||||
screen_resize_to_fit(input_manager->screen);
|
screen_resize_to_fit(im->screen);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_g:
|
case SDLK_g:
|
||||||
if (!shift && cmd && !repeat && down) {
|
if (!shift && cmd && !repeat && down) {
|
||||||
screen_resize_to_pixel_perfect(input_manager->screen);
|
screen_resize_to_pixel_perfect(im->screen);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_i:
|
case SDLK_i:
|
||||||
if (!shift && cmd && !repeat && down) {
|
if (!shift && cmd && !repeat && down) {
|
||||||
struct fps_counter *fps_counter =
|
struct fps_counter *fps_counter =
|
||||||
input_manager->video_buffer->fps_counter;
|
im->video_buffer->fps_counter;
|
||||||
switch_fps_counter_state(fps_counter);
|
switch_fps_counter_state(fps_counter);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -373,54 +403,129 @@ input_manager_process_key(struct input_manager *input_manager,
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
if (convert_input_key(event, &msg)) {
|
if (convert_input_key(event, &msg, im->text_events_pref)) {
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
LOGW("Could not request 'inject keycode'");
|
LOGW("Could not request 'inject keycode'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
|
||||||
|
struct control_msg *to) {
|
||||||
|
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
|
||||||
|
to->inject_touch_event.action = AMOTION_EVENT_ACTION_MOVE;
|
||||||
|
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
|
||||||
|
to->inject_touch_event.position.screen_size = screen->frame_size;
|
||||||
|
to->inject_touch_event.position.point.x = from->x;
|
||||||
|
to->inject_touch_event.position.point.y = from->y;
|
||||||
|
to->inject_touch_event.pressure = 1.f;
|
||||||
|
to->inject_touch_event.buttons = convert_mouse_buttons(from->state);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
input_manager_process_mouse_motion(struct input_manager *input_manager,
|
input_manager_process_mouse_motion(struct input_manager *im,
|
||||||
const SDL_MouseMotionEvent *event) {
|
const SDL_MouseMotionEvent *event) {
|
||||||
if (!event->state) {
|
if (!event->state) {
|
||||||
// do not send motion events when no button is pressed
|
// do not send motion events when no button is pressed
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (event->which == SDL_TOUCH_MOUSEID) {
|
||||||
|
// simulated from touch events, so it's a duplicate
|
||||||
|
return;
|
||||||
|
}
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
if (convert_mouse_motion(event, input_manager->screen->frame_size, &msg)) {
|
if (convert_mouse_motion(event, im->screen, &msg)) {
|
||||||
if (!controller_push_msg(input_manager->controller, &msg)) {
|
if (!controller_push_msg(im->controller, &msg)) {
|
||||||
LOGW("Could not request 'inject mouse motion event'");
|
LOGW("Could not request 'inject mouse motion event'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
is_outside_device_screen(struct input_manager *input_manager, int x, int y)
|
convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
|
||||||
{
|
struct control_msg *to) {
|
||||||
return x < 0 || x >= input_manager->screen->frame_size.width ||
|
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
|
||||||
y < 0 || y >= input_manager->screen->frame_size.height;
|
|
||||||
|
if (!convert_touch_action(from->type, &to->inject_touch_event.action)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct size frame_size = screen->frame_size;
|
||||||
|
|
||||||
|
to->inject_touch_event.pointer_id = from->fingerId;
|
||||||
|
to->inject_touch_event.position.screen_size = frame_size;
|
||||||
|
// SDL touch event coordinates are normalized in the range [0; 1]
|
||||||
|
to->inject_touch_event.position.point.x = from->x * frame_size.width;
|
||||||
|
to->inject_touch_event.position.point.y = from->y * frame_size.height;
|
||||||
|
to->inject_touch_event.pressure = from->pressure;
|
||||||
|
to->inject_touch_event.buttons = 0;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
input_manager_process_mouse_button(struct input_manager *input_manager,
|
input_manager_process_touch(struct input_manager *im,
|
||||||
|
const SDL_TouchFingerEvent *event) {
|
||||||
|
struct control_msg msg;
|
||||||
|
if (convert_touch(event, im->screen, &msg)) {
|
||||||
|
if (!controller_push_msg(im->controller, &msg)) {
|
||||||
|
LOGW("Could not request 'inject touch event'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
is_outside_device_screen(struct input_manager *im, int x, int y)
|
||||||
|
{
|
||||||
|
return x < 0 || x >= im->screen->frame_size.width ||
|
||||||
|
y < 0 || y >= im->screen->frame_size.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
|
||||||
|
struct control_msg *to) {
|
||||||
|
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
|
||||||
|
|
||||||
|
if (!convert_mouse_action(from->type, &to->inject_touch_event.action)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
|
||||||
|
to->inject_touch_event.position.screen_size = screen->frame_size;
|
||||||
|
to->inject_touch_event.position.point.x = from->x;
|
||||||
|
to->inject_touch_event.position.point.y = from->y;
|
||||||
|
to->inject_touch_event.pressure = 1.f;
|
||||||
|
to->inject_touch_event.buttons =
|
||||||
|
convert_mouse_buttons(SDL_BUTTON(from->button));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
input_manager_process_mouse_button(struct input_manager *im,
|
||||||
const SDL_MouseButtonEvent *event,
|
const SDL_MouseButtonEvent *event,
|
||||||
bool control) {
|
bool control) {
|
||||||
|
if (event->which == SDL_TOUCH_MOUSEID) {
|
||||||
|
// simulated from touch events, so it's a duplicate
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (event->type == SDL_MOUSEBUTTONDOWN) {
|
if (event->type == SDL_MOUSEBUTTONDOWN) {
|
||||||
if (control && event->button == SDL_BUTTON_RIGHT) {
|
if (control && event->button == SDL_BUTTON_RIGHT) {
|
||||||
press_back_or_turn_screen_on(input_manager->controller);
|
press_back_or_turn_screen_on(im->controller);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (control && event->button == SDL_BUTTON_MIDDLE) {
|
if (control && event->button == SDL_BUTTON_MIDDLE) {
|
||||||
action_home(input_manager->controller, ACTION_DOWN | ACTION_UP);
|
action_home(im->controller, ACTION_DOWN | ACTION_UP);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// double-click on black borders resize to fit the device screen
|
// double-click on black borders resize to fit the device screen
|
||||||
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
|
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
|
||||||
bool outside =
|
bool outside =
|
||||||
is_outside_device_screen(input_manager, event->x, event->y);
|
is_outside_device_screen(im, event->x, event->y);
|
||||||
if (outside) {
|
if (outside) {
|
||||||
screen_resize_to_fit(input_manager->screen);
|
screen_resize_to_fit(im->screen);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -432,23 +537,41 @@ input_manager_process_mouse_button(struct input_manager *input_manager,
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
if (convert_mouse_button(event, input_manager->screen->frame_size, &msg)) {
|
if (convert_mouse_button(event, im->screen, &msg)) {
|
||||||
if (!controller_push_msg(input_manager->controller, &msg)) {
|
if (!controller_push_msg(im->controller, &msg)) {
|
||||||
LOGW("Could not request 'inject mouse button event'");
|
LOGW("Could not request 'inject mouse button event'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static bool
|
||||||
input_manager_process_mouse_wheel(struct input_manager *input_manager,
|
convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
|
||||||
const SDL_MouseWheelEvent *event) {
|
struct control_msg *to) {
|
||||||
struct position position = {
|
struct position position = {
|
||||||
.screen_size = input_manager->screen->frame_size,
|
.screen_size = screen->frame_size,
|
||||||
.point = get_mouse_point(input_manager->screen),
|
.point = get_mouse_point(screen),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;
|
||||||
|
|
||||||
|
to->inject_scroll_event.position = position;
|
||||||
|
|
||||||
|
int mul = from->direction == SDL_MOUSEWHEEL_NORMAL ? 1 : -1;
|
||||||
|
// SDL behavior seems inconsistent between horizontal and vertical scrolling
|
||||||
|
// so reverse the horizontal
|
||||||
|
// <https://wiki.libsdl.org/SDL_MouseWheelEvent#Remarks>
|
||||||
|
to->inject_scroll_event.hscroll = -mul * from->x;
|
||||||
|
to->inject_scroll_event.vscroll = mul * from->y;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
input_manager_process_mouse_wheel(struct input_manager *im,
|
||||||
|
const SDL_MouseWheelEvent *event) {
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
if (convert_mouse_wheel(event, position, &msg)) {
|
if (convert_mouse_wheel(event, im->screen, &msg)) {
|
||||||
if (!controller_push_msg(input_manager->controller, &msg)) {
|
if (!controller_push_msg(im->controller, &msg)) {
|
||||||
LOGW("Could not request 'inject mouse wheel event'");
|
LOGW("Could not request 'inject mouse wheel event'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,38 +3,50 @@
|
|||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "fps_counter.h"
|
#include "fps_counter.h"
|
||||||
#include "video_buffer.h"
|
#include "video_buffer.h"
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
|
|
||||||
|
enum text_events_pref {
|
||||||
|
PREFER_TEXT_EVENTS_ALWAYS,
|
||||||
|
PREFER_TEXT_EVENTS_NON_ALPHA,
|
||||||
|
PREFER_TEXT_EVENTS_NEVER,
|
||||||
|
};
|
||||||
|
|
||||||
struct input_manager {
|
struct input_manager {
|
||||||
struct controller *controller;
|
struct controller *controller;
|
||||||
struct video_buffer *video_buffer;
|
struct video_buffer *video_buffer;
|
||||||
struct screen *screen;
|
struct screen *screen;
|
||||||
|
enum text_events_pref text_events_pref;
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
input_manager_process_text_input(struct input_manager *input_manager,
|
input_manager_process_text_input(struct input_manager *im,
|
||||||
const SDL_TextInputEvent *event);
|
const SDL_TextInputEvent *event);
|
||||||
|
|
||||||
void
|
void
|
||||||
input_manager_process_key(struct input_manager *input_manager,
|
input_manager_process_key(struct input_manager *im,
|
||||||
const SDL_KeyboardEvent *event,
|
const SDL_KeyboardEvent *event,
|
||||||
bool control);
|
bool control);
|
||||||
|
|
||||||
void
|
void
|
||||||
input_manager_process_mouse_motion(struct input_manager *input_manager,
|
input_manager_process_mouse_motion(struct input_manager *im,
|
||||||
const SDL_MouseMotionEvent *event);
|
const SDL_MouseMotionEvent *event);
|
||||||
|
|
||||||
void
|
void
|
||||||
input_manager_process_mouse_button(struct input_manager *input_manager,
|
input_manager_process_touch(struct input_manager *im,
|
||||||
|
const SDL_TouchFingerEvent *event);
|
||||||
|
|
||||||
|
void
|
||||||
|
input_manager_process_mouse_button(struct input_manager *im,
|
||||||
const SDL_MouseButtonEvent *event,
|
const SDL_MouseButtonEvent *event,
|
||||||
bool control);
|
bool control);
|
||||||
|
|
||||||
void
|
void
|
||||||
input_manager_process_mouse_wheel(struct input_manager *input_manager,
|
input_manager_process_mouse_wheel(struct input_manager *im,
|
||||||
const SDL_MouseWheelEvent *event);
|
const SDL_MouseWheelEvent *event);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <SDL2/SDL_mutex.h>
|
#include <SDL2/SDL_mutex.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
|
|||||||
156
app/src/main.c
156
app/src/main.c
@@ -8,30 +8,16 @@
|
|||||||
#define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem
|
#define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
#include "compat.h"
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "compat.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
#include "input_manager.h"
|
||||||
#include "recorder.h"
|
#include "recorder.h"
|
||||||
|
|
||||||
struct args {
|
struct args {
|
||||||
const char *serial;
|
struct scrcpy_options opts;
|
||||||
const char *crop;
|
|
||||||
const char *record_filename;
|
|
||||||
const char *window_title;
|
|
||||||
const char *push_target;
|
|
||||||
enum recorder_format record_format;
|
|
||||||
bool fullscreen;
|
|
||||||
bool no_control;
|
|
||||||
bool no_display;
|
|
||||||
bool help;
|
bool help;
|
||||||
bool version;
|
bool version;
|
||||||
bool show_touches;
|
|
||||||
uint16_t port;
|
|
||||||
uint16_t max_size;
|
|
||||||
uint32_t bit_rate;
|
|
||||||
bool always_on_top;
|
|
||||||
bool turn_screen_off;
|
|
||||||
bool render_expired_frames;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static void usage(const char *arg0) {
|
static void usage(const char *arg0) {
|
||||||
@@ -59,7 +45,7 @@ static void usage(const char *arg0) {
|
|||||||
" -f, --fullscreen\n"
|
" -f, --fullscreen\n"
|
||||||
" Start in fullscreen.\n"
|
" Start in fullscreen.\n"
|
||||||
"\n"
|
"\n"
|
||||||
" -F, --record-format\n"
|
" -F, --record-format format\n"
|
||||||
" Force recording format (either mp4 or mkv).\n"
|
" Force recording format (either mp4 or mkv).\n"
|
||||||
"\n"
|
"\n"
|
||||||
" -h, --help\n"
|
" -h, --help\n"
|
||||||
@@ -82,6 +68,18 @@ static void usage(const char *arg0) {
|
|||||||
" Set the TCP port the client listens on.\n"
|
" Set the TCP port the client listens on.\n"
|
||||||
" Default is %d.\n"
|
" Default is %d.\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
" --prefer-text-events mode\n"
|
||||||
|
" Configure how key/text events are forwarded to the Android\n"
|
||||||
|
" device.\n"
|
||||||
|
" Possible values are:\n"
|
||||||
|
" always:\n"
|
||||||
|
" Every text is sent as text. (default)\n"
|
||||||
|
" non-alpha:\n"
|
||||||
|
" Only letters are sent as a sequence of key events, other\n"
|
||||||
|
" characters are sent as text.\n"
|
||||||
|
" never:\n"
|
||||||
|
" Every text is sent as a sequence of key events.\n"
|
||||||
|
"\n"
|
||||||
" --push-target path\n"
|
" --push-target path\n"
|
||||||
" Set the target directory for pushing files to the device by\n"
|
" Set the target directory for pushing files to the device by\n"
|
||||||
" drag & drop. It is passed as-is to \"adb push\".\n"
|
" drag & drop. It is passed as-is to \"adb push\".\n"
|
||||||
@@ -309,9 +307,33 @@ guess_record_format(const char *filename) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_prefer_text_events(const char *optarg,
|
||||||
|
enum text_events_pref *pref) {
|
||||||
|
if (!strcmp(optarg, "always")) {
|
||||||
|
*pref = PREFER_TEXT_EVENTS_ALWAYS;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(optarg, "non-alpha")) {
|
||||||
|
*pref = PREFER_TEXT_EVENTS_NON_ALPHA;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(optarg, "never")) {
|
||||||
|
*pref = PREFER_TEXT_EVENTS_NEVER;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGE("Unsupported text events preference: %s"
|
||||||
|
"(expected 'always', 'non-alpha' or 'never')", optarg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
#define OPT_RENDER_EXPIRED_FRAMES 1000
|
#define OPT_RENDER_EXPIRED_FRAMES 1000
|
||||||
#define OPT_WINDOW_TITLE 1001
|
#define OPT_WINDOW_TITLE 1001
|
||||||
#define OPT_PUSH_TARGET 1002
|
#define OPT_PUSH_TARGET 1002
|
||||||
|
#define OPT_PREFER_TEXT_EVENTS 1003
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_args(struct args *args, int argc, char *argv[]) {
|
parse_args(struct args *args, int argc, char *argv[]) {
|
||||||
@@ -328,34 +350,39 @@ parse_args(struct args *args, int argc, char *argv[]) {
|
|||||||
{"push-target", required_argument, NULL,
|
{"push-target", required_argument, NULL,
|
||||||
OPT_PUSH_TARGET},
|
OPT_PUSH_TARGET},
|
||||||
{"record", required_argument, NULL, 'r'},
|
{"record", required_argument, NULL, 'r'},
|
||||||
{"record-format", required_argument, NULL, 'f'},
|
{"record-format", required_argument, NULL, 'F'},
|
||||||
{"render-expired-frames", no_argument, NULL,
|
{"render-expired-frames", no_argument, NULL,
|
||||||
OPT_RENDER_EXPIRED_FRAMES},
|
OPT_RENDER_EXPIRED_FRAMES},
|
||||||
{"serial", required_argument, NULL, 's'},
|
{"serial", required_argument, NULL, 's'},
|
||||||
{"show-touches", no_argument, NULL, 't'},
|
{"show-touches", no_argument, NULL, 't'},
|
||||||
{"turn-screen-off", no_argument, NULL, 'S'},
|
{"turn-screen-off", no_argument, NULL, 'S'},
|
||||||
|
{"prefer-text-events", required_argument, NULL,
|
||||||
|
OPT_PREFER_TEXT_EVENTS},
|
||||||
{"version", no_argument, NULL, 'v'},
|
{"version", no_argument, NULL, 'v'},
|
||||||
{"window-title", required_argument, NULL,
|
{"window-title", required_argument, NULL,
|
||||||
OPT_WINDOW_TITLE},
|
OPT_WINDOW_TITLE},
|
||||||
{NULL, 0, NULL, 0 },
|
{NULL, 0, NULL, 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct scrcpy_options *opts = &args->opts;
|
||||||
|
|
||||||
int c;
|
int c;
|
||||||
while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTv", long_options,
|
while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTv", long_options,
|
||||||
NULL)) != -1) {
|
NULL)) != -1) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 'b':
|
case 'b':
|
||||||
if (!parse_bit_rate(optarg, &args->bit_rate)) {
|
if (!parse_bit_rate(optarg, &opts->bit_rate)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'c':
|
case 'c':
|
||||||
args->crop = optarg;
|
opts->crop = optarg;
|
||||||
break;
|
break;
|
||||||
case 'f':
|
case 'f':
|
||||||
args->fullscreen = true;
|
opts->fullscreen = true;
|
||||||
break;
|
break;
|
||||||
case 'F':
|
case 'F':
|
||||||
if (!parse_record_format(optarg, &args->record_format)) {
|
if (!parse_record_format(optarg, &opts->record_format)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -363,47 +390,53 @@ parse_args(struct args *args, int argc, char *argv[]) {
|
|||||||
args->help = true;
|
args->help = true;
|
||||||
break;
|
break;
|
||||||
case 'm':
|
case 'm':
|
||||||
if (!parse_max_size(optarg, &args->max_size)) {
|
if (!parse_max_size(optarg, &opts->max_size)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'n':
|
case 'n':
|
||||||
args->no_control = true;
|
opts->control = false;
|
||||||
break;
|
break;
|
||||||
case 'N':
|
case 'N':
|
||||||
args->no_display = true;
|
opts->display = false;
|
||||||
break;
|
break;
|
||||||
case 'p':
|
case 'p':
|
||||||
if (!parse_port(optarg, &args->port)) {
|
if (!parse_port(optarg, &opts->port)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'r':
|
case 'r':
|
||||||
args->record_filename = optarg;
|
opts->record_filename = optarg;
|
||||||
break;
|
break;
|
||||||
case 's':
|
case 's':
|
||||||
args->serial = optarg;
|
opts->serial = optarg;
|
||||||
break;
|
break;
|
||||||
case 'S':
|
case 'S':
|
||||||
args->turn_screen_off = true;
|
opts->turn_screen_off = true;
|
||||||
break;
|
break;
|
||||||
case 't':
|
case 't':
|
||||||
args->show_touches = true;
|
opts->show_touches = true;
|
||||||
break;
|
break;
|
||||||
case 'T':
|
case 'T':
|
||||||
args->always_on_top = true;
|
opts->always_on_top = true;
|
||||||
break;
|
break;
|
||||||
case 'v':
|
case 'v':
|
||||||
args->version = true;
|
args->version = true;
|
||||||
break;
|
break;
|
||||||
case OPT_RENDER_EXPIRED_FRAMES:
|
case OPT_RENDER_EXPIRED_FRAMES:
|
||||||
args->render_expired_frames = true;
|
opts->render_expired_frames = true;
|
||||||
break;
|
break;
|
||||||
case OPT_WINDOW_TITLE:
|
case OPT_WINDOW_TITLE:
|
||||||
args->window_title = optarg;
|
opts->window_title = optarg;
|
||||||
break;
|
break;
|
||||||
case OPT_PUSH_TARGET:
|
case OPT_PUSH_TARGET:
|
||||||
args->push_target = optarg;
|
opts->push_target = optarg;
|
||||||
|
break;
|
||||||
|
case OPT_PREFER_TEXT_EVENTS:
|
||||||
|
if (!parse_prefer_text_events(optarg,
|
||||||
|
&opts->text_events_pref)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
@@ -411,12 +444,12 @@ parse_args(struct args *args, int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args->no_display && !args->record_filename) {
|
if (!opts->display && !opts->record_filename) {
|
||||||
LOGE("-N/--no-display requires screen recording (-r/--record)");
|
LOGE("-N/--no-display requires screen recording (-r/--record)");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args->no_display && args->fullscreen) {
|
if (!opts->display && opts->fullscreen) {
|
||||||
LOGE("-f/--fullscreen-window is incompatible with -N/--no-display");
|
LOGE("-f/--fullscreen-window is incompatible with -N/--no-display");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -427,21 +460,21 @@ parse_args(struct args *args, int argc, char *argv[]) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args->record_format && !args->record_filename) {
|
if (opts->record_format && !opts->record_filename) {
|
||||||
LOGE("Record format specified without recording");
|
LOGE("Record format specified without recording");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args->record_filename && !args->record_format) {
|
if (opts->record_filename && !opts->record_format) {
|
||||||
args->record_format = guess_record_format(args->record_filename);
|
opts->record_format = guess_record_format(opts->record_filename);
|
||||||
if (!args->record_format) {
|
if (!opts->record_format) {
|
||||||
LOGE("No format specified for \"%s\" (try with -F mkv)",
|
LOGE("No format specified for \"%s\" (try with -F mkv)",
|
||||||
args->record_filename);
|
opts->record_filename);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args->no_control && args->turn_screen_off) {
|
if (!opts->control && opts->turn_screen_off) {
|
||||||
LOGE("Could not request to turn screen off if control is disabled");
|
LOGE("Could not request to turn screen off if control is disabled");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -458,24 +491,11 @@ main(int argc, char *argv[]) {
|
|||||||
setbuf(stderr, NULL);
|
setbuf(stderr, NULL);
|
||||||
#endif
|
#endif
|
||||||
struct args args = {
|
struct args args = {
|
||||||
.serial = NULL,
|
.opts = SCRCPY_OPTIONS_DEFAULT,
|
||||||
.crop = NULL,
|
|
||||||
.record_filename = NULL,
|
|
||||||
.window_title = NULL,
|
|
||||||
.push_target = NULL,
|
|
||||||
.record_format = 0,
|
|
||||||
.help = false,
|
.help = false,
|
||||||
.version = false,
|
.version = false,
|
||||||
.show_touches = false,
|
|
||||||
.port = DEFAULT_LOCAL_PORT,
|
|
||||||
.max_size = DEFAULT_MAX_SIZE,
|
|
||||||
.bit_rate = DEFAULT_BIT_RATE,
|
|
||||||
.always_on_top = false,
|
|
||||||
.no_control = false,
|
|
||||||
.no_display = false,
|
|
||||||
.turn_screen_off = false,
|
|
||||||
.render_expired_frames = false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!parse_args(&args, argc, argv)) {
|
if (!parse_args(&args, argc, argv)) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -504,25 +524,7 @@ main(int argc, char *argv[]) {
|
|||||||
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG);
|
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct scrcpy_options options = {
|
int res = scrcpy(&args.opts) ? 0 : 1;
|
||||||
.serial = args.serial,
|
|
||||||
.crop = args.crop,
|
|
||||||
.port = args.port,
|
|
||||||
.record_filename = args.record_filename,
|
|
||||||
.window_title = args.window_title,
|
|
||||||
.push_target = args.push_target,
|
|
||||||
.record_format = args.record_format,
|
|
||||||
.max_size = args.max_size,
|
|
||||||
.bit_rate = args.bit_rate,
|
|
||||||
.show_touches = args.show_touches,
|
|
||||||
.fullscreen = args.fullscreen,
|
|
||||||
.always_on_top = args.always_on_top,
|
|
||||||
.control = !args.no_control,
|
|
||||||
.display = !args.no_display,
|
|
||||||
.turn_screen_off = args.turn_screen_off,
|
|
||||||
.render_expired_frames = args.render_expired_frames,
|
|
||||||
};
|
|
||||||
int res = scrcpy(&options) ? 0 : 1;
|
|
||||||
|
|
||||||
avformat_network_deinit(); // ignore failure
|
avformat_network_deinit(); // ignore failure
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
#ifdef __WINDOWS__
|
#ifdef __WINDOWS__
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
typedef int socket_t;
|
typedef int socket_t;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
net_init(void);
|
net_init(void);
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <SDL2/SDL_assert.h>
|
#include <SDL2/SDL_assert.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
// To define a queue type of "struct foo":
|
// To define a queue type of "struct foo":
|
||||||
// struct queue_foo QUEUE(struct foo);
|
// struct queue_foo QUEUE(struct foo);
|
||||||
#define QUEUE(TYPE) { \
|
#define QUEUE(TYPE) { \
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <SDL2/SDL_mutex.h>
|
#include <SDL2/SDL_mutex.h>
|
||||||
#include <SDL2/SDL_thread.h>
|
#include <SDL2/SDL_thread.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "net.h"
|
#include "net.h"
|
||||||
|
|
||||||
// receive events from the device
|
// receive events from the device
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
#include <libavutil/time.h>
|
#include <libavutil/time.h>
|
||||||
#include <SDL2/SDL_assert.h>
|
#include <SDL2/SDL_assert.h>
|
||||||
|
|
||||||
#include "compat.h"
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "compat.h"
|
||||||
#include "lock_util.h"
|
#include "lock_util.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
@@ -135,6 +135,9 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
|
|||||||
// <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd>
|
// <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd>
|
||||||
recorder->ctx->oformat = (AVOutputFormat *) format;
|
recorder->ctx->oformat = (AVOutputFormat *) format;
|
||||||
|
|
||||||
|
av_dict_set(&recorder->ctx->metadata, "comment",
|
||||||
|
"Recorded by scrcpy " SCRCPY_VERSION, 0);
|
||||||
|
|
||||||
AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec);
|
AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec);
|
||||||
if (!ostream) {
|
if (!ostream) {
|
||||||
avformat_free_context(recorder->ctx);
|
avformat_free_context(recorder->ctx);
|
||||||
|
|||||||
@@ -6,11 +6,13 @@
|
|||||||
#include <SDL2/SDL_mutex.h>
|
#include <SDL2/SDL_mutex.h>
|
||||||
#include <SDL2/SDL_thread.h>
|
#include <SDL2/SDL_thread.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "queue.h"
|
#include "queue.h"
|
||||||
|
|
||||||
enum recorder_format {
|
enum recorder_format {
|
||||||
RECORDER_FORMAT_MP4 = 1,
|
RECORDER_FORMAT_AUTO,
|
||||||
|
RECORDER_FORMAT_MP4,
|
||||||
RECORDER_FORMAT_MKV,
|
RECORDER_FORMAT_MKV,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "command.h"
|
#include "command.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
@@ -41,6 +42,7 @@ static struct input_manager input_manager = {
|
|||||||
.controller = &controller,
|
.controller = &controller,
|
||||||
.video_buffer = &video_buffer,
|
.video_buffer = &video_buffer,
|
||||||
.screen = &screen,
|
.screen = &screen,
|
||||||
|
.text_events_pref = 0, // initialized later
|
||||||
};
|
};
|
||||||
|
|
||||||
// init SDL and set appropriate hints
|
// init SDL and set appropriate hints
|
||||||
@@ -180,6 +182,11 @@ handle_event(SDL_Event *event, bool control) {
|
|||||||
input_manager_process_mouse_button(&input_manager, &event->button,
|
input_manager_process_mouse_button(&input_manager, &event->button,
|
||||||
control);
|
control);
|
||||||
break;
|
break;
|
||||||
|
case SDL_FINGERMOTION:
|
||||||
|
case SDL_FINGERDOWN:
|
||||||
|
case SDL_FINGERUP:
|
||||||
|
input_manager_process_touch(&input_manager, &event->tfinger);
|
||||||
|
break;
|
||||||
case SDL_DROPFILE: {
|
case SDL_DROPFILE: {
|
||||||
if (!control) {
|
if (!control) {
|
||||||
break;
|
break;
|
||||||
@@ -408,6 +415,8 @@ scrcpy(const struct scrcpy_options *options) {
|
|||||||
show_touches_waited = true;
|
show_touches_waited = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input_manager.text_events_pref = options->text_events_pref;
|
||||||
|
|
||||||
ret = event_loop(options->display, options->control);
|
ret = event_loop(options->display, options->control);
|
||||||
LOGD("quit...");
|
LOGD("quit...");
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,10 @@
|
|||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <recorder.h>
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "input_manager.h"
|
||||||
|
#include "recorder.h"
|
||||||
|
|
||||||
struct scrcpy_options {
|
struct scrcpy_options {
|
||||||
const char *serial;
|
const char *serial;
|
||||||
@@ -12,6 +15,7 @@ struct scrcpy_options {
|
|||||||
const char *window_title;
|
const char *window_title;
|
||||||
const char *push_target;
|
const char *push_target;
|
||||||
enum recorder_format record_format;
|
enum recorder_format record_format;
|
||||||
|
enum text_events_pref text_events_pref;
|
||||||
uint16_t port;
|
uint16_t port;
|
||||||
uint16_t max_size;
|
uint16_t max_size;
|
||||||
uint32_t bit_rate;
|
uint32_t bit_rate;
|
||||||
@@ -24,6 +28,26 @@ struct scrcpy_options {
|
|||||||
bool render_expired_frames;
|
bool render_expired_frames;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define SCRCPY_OPTIONS_DEFAULT { \
|
||||||
|
.serial = NULL, \
|
||||||
|
.crop = NULL, \
|
||||||
|
.record_filename = NULL, \
|
||||||
|
.window_title = NULL, \
|
||||||
|
.push_target = NULL, \
|
||||||
|
.record_format = RECORDER_FORMAT_AUTO, \
|
||||||
|
.text_events_pref = PREFER_TEXT_EVENTS_ALWAYS, \
|
||||||
|
.port = DEFAULT_LOCAL_PORT, \
|
||||||
|
.max_size = DEFAULT_LOCAL_PORT, \
|
||||||
|
.bit_rate = DEFAULT_BIT_RATE, \
|
||||||
|
.show_touches = false, \
|
||||||
|
.fullscreen = false, \
|
||||||
|
.always_on_top = false, \
|
||||||
|
.control = true, \
|
||||||
|
.display = true, \
|
||||||
|
.turn_screen_off = false, \
|
||||||
|
.render_expired_frames = false, \
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
scrcpy(const struct scrcpy_options *options);
|
scrcpy(const struct scrcpy_options *options);
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
#include "icon.xpm"
|
#include "icon.xpm"
|
||||||
@@ -15,7 +16,7 @@
|
|||||||
|
|
||||||
// get the window size in a struct size
|
// get the window size in a struct size
|
||||||
static struct size
|
static struct size
|
||||||
get_native_window_size(SDL_Window *window) {
|
get_window_size(SDL_Window *window) {
|
||||||
int width;
|
int width;
|
||||||
int height;
|
int height;
|
||||||
SDL_GetWindowSize(window, &width, &height);
|
SDL_GetWindowSize(window, &width, &height);
|
||||||
@@ -28,11 +29,11 @@ get_native_window_size(SDL_Window *window) {
|
|||||||
|
|
||||||
// get the windowed window size
|
// get the windowed window size
|
||||||
static struct size
|
static struct size
|
||||||
get_window_size(const struct screen *screen) {
|
get_windowed_window_size(const struct screen *screen) {
|
||||||
if (screen->fullscreen) {
|
if (screen->fullscreen) {
|
||||||
return screen->windowed_window_size;
|
return screen->windowed_window_size;
|
||||||
}
|
}
|
||||||
return get_native_window_size(screen->window);
|
return get_window_size(screen->window);
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the window size to be applied when fullscreen is disabled
|
// set the window size to be applied when fullscreen is disabled
|
||||||
@@ -111,8 +112,8 @@ get_optimal_size(struct size current_size, struct size frame_size) {
|
|||||||
// same as get_optimal_size(), but read the current size from the window
|
// same as get_optimal_size(), but read the current size from the window
|
||||||
static inline struct size
|
static inline struct size
|
||||||
get_optimal_window_size(const struct screen *screen, struct size frame_size) {
|
get_optimal_window_size(const struct screen *screen, struct size frame_size) {
|
||||||
struct size current_size = get_window_size(screen);
|
struct size windowed_size = get_windowed_window_size(screen);
|
||||||
return get_optimal_size(current_size, frame_size);
|
return get_optimal_size(windowed_size, frame_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
// initially, there is no current size, so use the frame size as current size
|
// initially, there is no current size, so use the frame size as current size
|
||||||
@@ -228,11 +229,11 @@ prepare_for_frame(struct screen *screen, struct size new_frame_size) {
|
|||||||
// frame dimension changed, destroy texture
|
// frame dimension changed, destroy texture
|
||||||
SDL_DestroyTexture(screen->texture);
|
SDL_DestroyTexture(screen->texture);
|
||||||
|
|
||||||
struct size current_size = get_window_size(screen);
|
struct size windowed_size = get_windowed_window_size(screen);
|
||||||
struct size target_size = {
|
struct size target_size = {
|
||||||
(uint32_t) current_size.width * new_frame_size.width
|
(uint32_t) windowed_size.width * new_frame_size.width
|
||||||
/ screen->frame_size.width,
|
/ screen->frame_size.width,
|
||||||
(uint32_t) current_size.height * new_frame_size.height
|
(uint32_t) windowed_size.height * new_frame_size.height
|
||||||
/ screen->frame_size.height,
|
/ screen->frame_size.height,
|
||||||
};
|
};
|
||||||
target_size = get_optimal_size(target_size, new_frame_size);
|
target_size = get_optimal_size(target_size, new_frame_size);
|
||||||
@@ -288,7 +289,7 @@ void
|
|||||||
screen_switch_fullscreen(struct screen *screen) {
|
screen_switch_fullscreen(struct screen *screen) {
|
||||||
if (!screen->fullscreen) {
|
if (!screen->fullscreen) {
|
||||||
// going to fullscreen, store the current windowed window size
|
// going to fullscreen, store the current windowed window size
|
||||||
screen->windowed_window_size = get_native_window_size(screen->window);
|
screen->windowed_window_size = get_window_size(screen->window);
|
||||||
}
|
}
|
||||||
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
|
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||||
if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
|
if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
struct video_buffer;
|
struct video_buffer;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
#include "net.h"
|
#include "net.h"
|
||||||
|
|
||||||
#define SOCKET_NAME "scrcpy"
|
#define SOCKET_NAME "scrcpy"
|
||||||
#define SERVER_FILENAME "scrcpy-server.jar"
|
#define SERVER_FILENAME "scrcpy-server"
|
||||||
|
|
||||||
#define DEFAULT_SERVER_PATH PREFIX "/share/scrcpy/" SERVER_FILENAME
|
#define DEFAULT_SERVER_PATH PREFIX "/share/scrcpy/" SERVER_FILENAME
|
||||||
#define DEVICE_SERVER_PATH "/data/local/tmp/" SERVER_FILENAME
|
#define DEVICE_SERVER_PATH "/data/local/tmp/" SERVER_FILENAME
|
||||||
@@ -32,7 +32,7 @@ get_server_path(void) {
|
|||||||
// the absolute path is hardcoded
|
// the absolute path is hardcoded
|
||||||
return DEFAULT_SERVER_PATH;
|
return DEFAULT_SERVER_PATH;
|
||||||
#else
|
#else
|
||||||
// use scrcpy-server.jar in the same directory as the executable
|
// use scrcpy-server in the same directory as the executable
|
||||||
char *executable_path = get_executable_path();
|
char *executable_path = get_executable_path();
|
||||||
if (!executable_path) {
|
if (!executable_path) {
|
||||||
LOGE("Could not get executable path, "
|
LOGE("Could not get executable path, "
|
||||||
@@ -124,6 +124,11 @@ execute_server(struct server *server, const struct server_params *params) {
|
|||||||
"shell",
|
"shell",
|
||||||
"CLASSPATH=/data/local/tmp/" SERVER_FILENAME,
|
"CLASSPATH=/data/local/tmp/" SERVER_FILENAME,
|
||||||
"app_process",
|
"app_process",
|
||||||
|
#ifdef SERVER_DEBUGGER
|
||||||
|
# define SERVER_DEBUGGER_PORT "5005"
|
||||||
|
"-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address="
|
||||||
|
SERVER_DEBUGGER_PORT,
|
||||||
|
#endif
|
||||||
"/", // unused
|
"/", // unused
|
||||||
"com.genymobile.scrcpy.Server",
|
"com.genymobile.scrcpy.Server",
|
||||||
max_size_string,
|
max_size_string,
|
||||||
@@ -133,6 +138,17 @@ execute_server(struct server *server, const struct server_params *params) {
|
|||||||
"true", // always send frame meta (packet boundaries + timestamp)
|
"true", // always send frame meta (packet boundaries + timestamp)
|
||||||
params->control ? "true" : "false",
|
params->control ? "true" : "false",
|
||||||
};
|
};
|
||||||
|
#ifdef SERVER_DEBUGGER
|
||||||
|
LOGI("Server debugger waiting for a client on device port "
|
||||||
|
SERVER_DEBUGGER_PORT "...");
|
||||||
|
// From the computer, run
|
||||||
|
// adb forward tcp:5005 tcp:5005
|
||||||
|
// Then, from Android Studio: Run > Debug > Edit configurations...
|
||||||
|
// On the left, click on '+', "Remote", with:
|
||||||
|
// Host: localhost
|
||||||
|
// Port: 5005
|
||||||
|
// Then click on "Debug"
|
||||||
|
#endif
|
||||||
return adb_execute(server->serial, cmd, sizeof(cmd) / sizeof(cmd[0]));
|
return adb_execute(server->serial, cmd, sizeof(cmd) / sizeof(cmd[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "command.h"
|
#include "command.h"
|
||||||
#include "net.h"
|
#include "net.h"
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
#include <SDL2/SDL_stdinc.h>
|
#include <SDL2/SDL_stdinc.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
xstrncpy(char *dest, const char *src, size_t n) {
|
xstrncpy(char *dest, const char *src, size_t n) {
|
||||||
size_t i;
|
size_t i;
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
// like strncpy, except:
|
// like strncpy, except:
|
||||||
// - it copies at most n-1 chars
|
// - it copies at most n-1 chars
|
||||||
// - the dest string is nul-terminated
|
// - the dest string is nul-terminated
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
#include <SDL2/SDL_thread.h>
|
#include <SDL2/SDL_thread.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "compat.h"
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "compat.h"
|
||||||
#include "buffer_util.h"
|
#include "buffer_util.h"
|
||||||
#include "decoder.h"
|
#include "decoder.h"
|
||||||
#include "events.h"
|
#include "events.h"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <SDL2/SDL_atomic.h>
|
#include <SDL2/SDL_atomic.h>
|
||||||
#include <SDL2/SDL_thread.h>
|
#include <SDL2/SDL_thread.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "net.h"
|
#include "net.h"
|
||||||
|
|
||||||
struct video_buffer;
|
struct video_buffer;
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
#include "command.h"
|
#include "command.h"
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
net_init(void) {
|
net_init(void) {
|
||||||
// do nothing
|
// do nothing
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "net.h"
|
#include "net.h"
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
struct index {
|
struct index {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
SDL_Surface *
|
SDL_Surface *
|
||||||
read_xpm(char *xpm[]);
|
read_xpm(char *xpm[]);
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <SDL2/SDL_mutex.h>
|
#include <SDL2/SDL_mutex.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "fps_counter.h"
|
#include "fps_counter.h"
|
||||||
|
|
||||||
// forward declarations
|
// forward declarations
|
||||||
|
|||||||
@@ -67,35 +67,39 @@ static void test_serialize_inject_text_long(void) {
|
|||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_serialize_inject_mouse_event(void) {
|
static void test_serialize_inject_touch_event(void) {
|
||||||
struct control_msg msg = {
|
struct control_msg msg = {
|
||||||
.type = CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT,
|
.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
||||||
.inject_mouse_event = {
|
.inject_touch_event = {
|
||||||
.action = AMOTION_EVENT_ACTION_DOWN,
|
.action = AMOTION_EVENT_ACTION_DOWN,
|
||||||
.buttons = AMOTION_EVENT_BUTTON_PRIMARY,
|
.pointer_id = 0x1234567887654321L,
|
||||||
.position = {
|
.position = {
|
||||||
.point = {
|
.point = {
|
||||||
.x = 260,
|
.x = 100,
|
||||||
.y = 1026,
|
.y = 200,
|
||||||
},
|
},
|
||||||
.screen_size = {
|
.screen_size = {
|
||||||
.width = 1080,
|
.width = 1080,
|
||||||
.height = 1920,
|
.height = 1920,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
.pressure = 1.0f,
|
||||||
|
.buttons = AMOTION_EVENT_BUTTON_PRIMARY,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 18);
|
assert(size == 28);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT,
|
CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
||||||
0x00, // AKEY_EVENT_ACTION_DOWN
|
0x00, // AKEY_EVENT_ACTION_DOWN
|
||||||
0x00, 0x00, 0x00, 0x01, // AMOTION_EVENT_BUTTON_PRIMARY
|
0x12, 0x34, 0x56, 0x78, 0x87, 0x65, 0x43, 0x21, // pointer id
|
||||||
0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026
|
0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0xc8, // 100 200
|
||||||
0x04, 0x38, 0x07, 0x80, // 1080 1920
|
0x04, 0x38, 0x07, 0x80, // 1080 1920
|
||||||
|
0xff, 0xff, // pressure
|
||||||
|
0x00, 0x00, 0x00, 0x01 // AMOTION_EVENT_BUTTON_PRIMARY
|
||||||
};
|
};
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
}
|
}
|
||||||
@@ -236,7 +240,7 @@ int main(void) {
|
|||||||
test_serialize_inject_keycode();
|
test_serialize_inject_keycode();
|
||||||
test_serialize_inject_text();
|
test_serialize_inject_text();
|
||||||
test_serialize_inject_text_long();
|
test_serialize_inject_text_long();
|
||||||
test_serialize_inject_mouse_event();
|
test_serialize_inject_touch_event();
|
||||||
test_serialize_inject_scroll_event();
|
test_serialize_inject_scroll_event();
|
||||||
test_serialize_back_or_screen_on();
|
test_serialize_back_or_screen_on();
|
||||||
test_serialize_expand_notification_panel();
|
test_serialize_expand_notification_panel();
|
||||||
|
|||||||
@@ -3,5 +3,6 @@ option('compile_server', type: 'boolean', value: true, description: 'Build the s
|
|||||||
option('crossbuild_windows', type: 'boolean', value: false, description: 'Build for Windows from Linux')
|
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('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('prebuilt_server', type: 'string', description: 'Path of the prebuilt server')
|
||||||
option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server.jar from the same directory as the scrcpy executable')
|
option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable')
|
||||||
option('hidpi_support', type: 'boolean', value: true, description: 'Enable High DPI support')
|
option('hidpi_support', type: 'boolean', value: true, description: 'Enable High DPI support')
|
||||||
|
option('server_debugger', type: 'boolean', value: false, description: 'Run a server debugger and wait for a client to be attached')
|
||||||
|
|||||||
@@ -23,21 +23,21 @@ cd -
|
|||||||
make -f Makefile.CrossWindows
|
make -f Makefile.CrossWindows
|
||||||
|
|
||||||
# the generated server must be the same everywhere
|
# the generated server must be the same everywhere
|
||||||
cmp "$BUILDDIR/server/scrcpy-server.jar" dist/scrcpy-win32/scrcpy-server.jar
|
cmp "$BUILDDIR/server/scrcpy-server" dist/scrcpy-win32/scrcpy-server
|
||||||
cmp "$BUILDDIR/server/scrcpy-server.jar" dist/scrcpy-win64/scrcpy-server.jar
|
cmp "$BUILDDIR/server/scrcpy-server" dist/scrcpy-win64/scrcpy-server
|
||||||
|
|
||||||
# get version name
|
# get version name
|
||||||
TAG=$(git describe --tags --always)
|
TAG=$(git describe --tags --always)
|
||||||
|
|
||||||
# create release directory
|
# create release directory
|
||||||
mkdir -p "release-$TAG"
|
mkdir -p "release-$TAG"
|
||||||
cp "$BUILDDIR/server/scrcpy-server.jar" "release-$TAG/scrcpy-server-$TAG.jar"
|
cp "$BUILDDIR/server/scrcpy-server" "release-$TAG/scrcpy-server-$TAG"
|
||||||
cp "dist/scrcpy-win32-$TAG.zip" "release-$TAG/"
|
cp "dist/scrcpy-win32-$TAG.zip" "release-$TAG/"
|
||||||
cp "dist/scrcpy-win64-$TAG.zip" "release-$TAG/"
|
cp "dist/scrcpy-win64-$TAG.zip" "release-$TAG/"
|
||||||
|
|
||||||
# generate checksums
|
# generate checksums
|
||||||
cd "release-$TAG"
|
cd "release-$TAG"
|
||||||
sha256sum "scrcpy-server-$TAG.jar" \
|
sha256sum "scrcpy-server-$TAG" \
|
||||||
"scrcpy-win32-$TAG.zip" \
|
"scrcpy-win32-$TAG.zip" \
|
||||||
"scrcpy-win64-$TAG.zip" > SHA256SUMS.txt
|
"scrcpy-win64-$TAG.zip" > SHA256SUMS.txt
|
||||||
|
|
||||||
|
|||||||
2
run
2
run
@@ -20,4 +20,4 @@ then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
SCRCPY_SERVER_PATH="$BUILDDIR/server/scrcpy-server.jar" "$BUILDDIR/app/scrcpy" "$@"
|
SCRCPY_SERVER_PATH="$BUILDDIR/server/scrcpy-server" "$BUILDDIR/app/scrcpy" "$@"
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
SCRCPY_SERVER_PATH="$MESON_BUILD_ROOT/server/scrcpy-server.jar" "$MESON_BUILD_ROOT/app/scrcpy"
|
SCRCPY_SERVER_PATH="$MESON_BUILD_ROOT/server/scrcpy-server" "$MESON_BUILD_ROOT/app/scrcpy"
|
||||||
|
|||||||
62
server/build_without_gradle.sh
Executable file
62
server/build_without_gradle.sh
Executable file
@@ -0,0 +1,62 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# This script generates the scrcpy binary "manually" (without gradle).
|
||||||
|
#
|
||||||
|
# Adapt Android platform and build tools versions (via ANDROID_PLATFORM and
|
||||||
|
# ANDROID_BUILD_TOOLS environment variables).
|
||||||
|
#
|
||||||
|
# Then execute:
|
||||||
|
#
|
||||||
|
# BUILD_DIR=my_build_dir ./build_without_gradle.sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
PLATFORM=${ANDROID_PLATFORM:-29}
|
||||||
|
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2}
|
||||||
|
|
||||||
|
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
|
||||||
|
CLASSES_DIR="$BUILD_DIR/classes"
|
||||||
|
SERVER_DIR=$(dirname "$0")
|
||||||
|
SERVER_BINARY=scrcpy-server
|
||||||
|
|
||||||
|
echo "Platform: android-$PLATFORM"
|
||||||
|
echo "Build-tools: $BUILD_TOOLS"
|
||||||
|
echo "Build dir: $BUILD_DIR"
|
||||||
|
|
||||||
|
rm -rf "$CLASSES_DIR" "$BUILD_DIR/$SERVER_BINARY" classes.dex
|
||||||
|
mkdir -p "$CLASSES_DIR/com/genymobile/scrcpy"
|
||||||
|
|
||||||
|
<< EOF cat > "$CLASSES_DIR/com/genymobile/scrcpy/BuildConfig.java"
|
||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
public final class BuildConfig {
|
||||||
|
public static final boolean DEBUG = false;
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "Generating java from aidl..."
|
||||||
|
cd "$SERVER_DIR/src/main/aidl"
|
||||||
|
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o "$CLASSES_DIR" \
|
||||||
|
android/view/IRotationWatcher.aidl
|
||||||
|
|
||||||
|
echo "Compiling java sources..."
|
||||||
|
cd ../java
|
||||||
|
javac -bootclasspath "$ANDROID_HOME/platforms/android-$PLATFORM/android.jar" \
|
||||||
|
-cp "$CLASSES_DIR" -d "$CLASSES_DIR" -source 1.8 -target 1.8 \
|
||||||
|
com/genymobile/scrcpy/*.java \
|
||||||
|
com/genymobile/scrcpy/wrappers/*.java
|
||||||
|
|
||||||
|
echo "Dexing..."
|
||||||
|
cd "$CLASSES_DIR"
|
||||||
|
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \
|
||||||
|
--output "$BUILD_DIR/classes.dex" \
|
||||||
|
android/view/*.class \
|
||||||
|
com/genymobile/scrcpy/*.class \
|
||||||
|
com/genymobile/scrcpy/wrappers/*.class
|
||||||
|
|
||||||
|
echo "Archiving..."
|
||||||
|
cd "$BUILD_DIR"
|
||||||
|
jar cvf "$SERVER_BINARY" classes.dex
|
||||||
|
rm -rf classes.dex classes
|
||||||
|
|
||||||
|
echo "Server generated in $BUILD_DIR/$SERVER_BINARY"
|
||||||
@@ -4,7 +4,7 @@ prebuilt_server = get_option('prebuilt_server')
|
|||||||
if prebuilt_server == ''
|
if prebuilt_server == ''
|
||||||
custom_target('scrcpy-server',
|
custom_target('scrcpy-server',
|
||||||
build_always: true, # gradle is responsible for tracking source changes
|
build_always: true, # gradle is responsible for tracking source changes
|
||||||
output: 'scrcpy-server.jar',
|
output: 'scrcpy-server',
|
||||||
command: [find_program('./scripts/build-wrapper.sh'), meson.current_source_dir(), '@OUTPUT@', get_option('buildtype')],
|
command: [find_program('./scripts/build-wrapper.sh'), meson.current_source_dir(), '@OUTPUT@', get_option('buildtype')],
|
||||||
console: true,
|
console: true,
|
||||||
install: true,
|
install: true,
|
||||||
@@ -16,7 +16,7 @@ else
|
|||||||
endif
|
endif
|
||||||
custom_target('scrcpy-server-prebuilt',
|
custom_target('scrcpy-server-prebuilt',
|
||||||
input: prebuilt_server,
|
input: prebuilt_server,
|
||||||
output: 'scrcpy-server.jar',
|
output: 'scrcpy-server',
|
||||||
command: ['cp', '@INPUT@', '@OUTPUT@'],
|
command: ['cp', '@INPUT@', '@OUTPUT@'],
|
||||||
install: true,
|
install: true,
|
||||||
install_dir: 'share/scrcpy')
|
install_dir: 'share/scrcpy')
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ public final class ControlMessage {
|
|||||||
|
|
||||||
public static final int TYPE_INJECT_KEYCODE = 0;
|
public static final int TYPE_INJECT_KEYCODE = 0;
|
||||||
public static final int TYPE_INJECT_TEXT = 1;
|
public static final int TYPE_INJECT_TEXT = 1;
|
||||||
public static final int TYPE_INJECT_MOUSE_EVENT = 2;
|
public static final int TYPE_INJECT_TOUCH_EVENT = 2;
|
||||||
public static final int TYPE_INJECT_SCROLL_EVENT = 3;
|
public static final int TYPE_INJECT_SCROLL_EVENT = 3;
|
||||||
public static final int TYPE_BACK_OR_SCREEN_ON = 4;
|
public static final int TYPE_BACK_OR_SCREEN_ON = 4;
|
||||||
public static final int TYPE_EXPAND_NOTIFICATION_PANEL = 5;
|
public static final int TYPE_EXPAND_NOTIFICATION_PANEL = 5;
|
||||||
@@ -22,6 +22,8 @@ public final class ControlMessage {
|
|||||||
private int action; // KeyEvent.ACTION_* or MotionEvent.ACTION_* or POWER_MODE_*
|
private int action; // KeyEvent.ACTION_* or MotionEvent.ACTION_* or POWER_MODE_*
|
||||||
private int keycode; // KeyEvent.KEYCODE_*
|
private int keycode; // KeyEvent.KEYCODE_*
|
||||||
private int buttons; // MotionEvent.BUTTON_*
|
private int buttons; // MotionEvent.BUTTON_*
|
||||||
|
private long pointerId;
|
||||||
|
private float pressure;
|
||||||
private Position position;
|
private Position position;
|
||||||
private int hScroll;
|
private int hScroll;
|
||||||
private int vScroll;
|
private int vScroll;
|
||||||
@@ -45,12 +47,15 @@ public final class ControlMessage {
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ControlMessage createInjectMouseEvent(int action, int buttons, Position position) {
|
public static ControlMessage createInjectTouchEvent(int action, long pointerId, Position position, float pressure,
|
||||||
|
int buttons) {
|
||||||
ControlMessage msg = new ControlMessage();
|
ControlMessage msg = new ControlMessage();
|
||||||
msg.type = TYPE_INJECT_MOUSE_EVENT;
|
msg.type = TYPE_INJECT_TOUCH_EVENT;
|
||||||
msg.action = action;
|
msg.action = action;
|
||||||
msg.buttons = buttons;
|
msg.pointerId = pointerId;
|
||||||
|
msg.pressure = pressure;
|
||||||
msg.position = position;
|
msg.position = position;
|
||||||
|
msg.buttons = buttons;
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +115,14 @@ public final class ControlMessage {
|
|||||||
return buttons;
|
return buttons;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getPointerId() {
|
||||||
|
return pointerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getPressure() {
|
||||||
|
return pressure;
|
||||||
|
}
|
||||||
|
|
||||||
public Position getPosition() {
|
public Position getPosition() {
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ public class ControlMessageReader {
|
|||||||
|
|
||||||
private static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 9;
|
private static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 9;
|
||||||
private static final int INJECT_MOUSE_EVENT_PAYLOAD_LENGTH = 17;
|
private static final int INJECT_MOUSE_EVENT_PAYLOAD_LENGTH = 17;
|
||||||
|
private static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 21;
|
||||||
private static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
private static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
||||||
private static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
private static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
||||||
|
|
||||||
@@ -59,8 +60,8 @@ public class ControlMessageReader {
|
|||||||
case ControlMessage.TYPE_INJECT_TEXT:
|
case ControlMessage.TYPE_INJECT_TEXT:
|
||||||
msg = parseInjectText();
|
msg = parseInjectText();
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_INJECT_MOUSE_EVENT:
|
case ControlMessage.TYPE_INJECT_TOUCH_EVENT:
|
||||||
msg = parseInjectMouseEvent();
|
msg = parseInjectTouchEvent();
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
|
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
|
||||||
msg = parseInjectScrollEvent();
|
msg = parseInjectScrollEvent();
|
||||||
@@ -120,14 +121,20 @@ public class ControlMessageReader {
|
|||||||
return ControlMessage.createInjectText(text);
|
return ControlMessage.createInjectText(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseInjectMouseEvent() {
|
@SuppressWarnings("checkstyle:MagicNumber")
|
||||||
if (buffer.remaining() < INJECT_MOUSE_EVENT_PAYLOAD_LENGTH) {
|
private ControlMessage parseInjectTouchEvent() {
|
||||||
|
if (buffer.remaining() < INJECT_TOUCH_EVENT_PAYLOAD_LENGTH) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int action = toUnsigned(buffer.get());
|
int action = toUnsigned(buffer.get());
|
||||||
int buttons = buffer.getInt();
|
long pointerId = buffer.getLong();
|
||||||
Position position = readPosition(buffer);
|
Position position = readPosition(buffer);
|
||||||
return ControlMessage.createInjectMouseEvent(action, buttons, position);
|
// 16 bits fixed-point
|
||||||
|
int pressureInt = toUnsigned(buffer.getShort());
|
||||||
|
// convert it to a float between 0 and 1 (0x1p16f is 2^16 as float)
|
||||||
|
float pressure = pressureInt == 0xffff ? 1f : (pressureInt / 0x1p16f);
|
||||||
|
int buttons = buffer.getInt();
|
||||||
|
return ControlMessage.createInjectTouchEvent(action, pointerId, position, pressure, buttons);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseInjectScrollEvent() {
|
private ControlMessage parseInjectScrollEvent() {
|
||||||
|
|||||||
@@ -19,38 +19,32 @@ public class Controller {
|
|||||||
|
|
||||||
private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
|
private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
|
||||||
|
|
||||||
private long lastMouseDown;
|
private long lastTouchDown;
|
||||||
private final MotionEvent.PointerProperties[] pointerProperties = {new MotionEvent.PointerProperties()};
|
private final PointersState pointersState = new PointersState();
|
||||||
private final MotionEvent.PointerCoords[] pointerCoords = {new MotionEvent.PointerCoords()};
|
private final MotionEvent.PointerProperties[] pointerProperties =
|
||||||
|
new MotionEvent.PointerProperties[PointersState.MAX_POINTERS];
|
||||||
|
private final MotionEvent.PointerCoords[] pointerCoords =
|
||||||
|
new MotionEvent.PointerCoords[PointersState.MAX_POINTERS];
|
||||||
|
|
||||||
public Controller(Device device, DesktopConnection connection) {
|
public Controller(Device device, DesktopConnection connection) {
|
||||||
this.device = device;
|
this.device = device;
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
initPointer();
|
initPointers();
|
||||||
sender = new DeviceMessageSender(connection);
|
sender = new DeviceMessageSender(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initPointer() {
|
private void initPointers() {
|
||||||
MotionEvent.PointerProperties props = pointerProperties[0];
|
for (int i = 0; i < PointersState.MAX_POINTERS; ++i) {
|
||||||
props.id = 0;
|
MotionEvent.PointerProperties props = new MotionEvent.PointerProperties();
|
||||||
props.toolType = MotionEvent.TOOL_TYPE_FINGER;
|
props.toolType = MotionEvent.TOOL_TYPE_FINGER;
|
||||||
|
|
||||||
MotionEvent.PointerCoords coords = pointerCoords[0];
|
MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
|
||||||
coords.orientation = 0;
|
coords.orientation = 0;
|
||||||
coords.pressure = 1;
|
coords.size = 1;
|
||||||
coords.size = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setPointerCoords(Point point) {
|
pointerProperties[i] = props;
|
||||||
MotionEvent.PointerCoords coords = pointerCoords[0];
|
pointerCoords[i] = coords;
|
||||||
coords.x = point.getX();
|
}
|
||||||
coords.y = point.getY();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setScroll(int hScroll, int vScroll) {
|
|
||||||
MotionEvent.PointerCoords coords = pointerCoords[0];
|
|
||||||
coords.setAxisValue(MotionEvent.AXIS_HSCROLL, hScroll);
|
|
||||||
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("checkstyle:MagicNumber")
|
@SuppressWarnings("checkstyle:MagicNumber")
|
||||||
@@ -87,8 +81,8 @@ public class Controller {
|
|||||||
case ControlMessage.TYPE_INJECT_TEXT:
|
case ControlMessage.TYPE_INJECT_TEXT:
|
||||||
injectText(msg.getText());
|
injectText(msg.getText());
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_INJECT_MOUSE_EVENT:
|
case ControlMessage.TYPE_INJECT_TOUCH_EVENT:
|
||||||
injectMouse(msg.getAction(), msg.getButtons(), msg.getPosition());
|
injectTouch(msg.getAction(), msg.getPointerId(), msg.getPosition(), msg.getPressure(), msg.getButtons());
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
|
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
|
||||||
injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll());
|
injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll());
|
||||||
@@ -148,19 +142,42 @@ public class Controller {
|
|||||||
return successCount;
|
return successCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean injectMouse(int action, int buttons, Position position) {
|
private boolean injectTouch(int action, long pointerId, Position position, float pressure, int buttons) {
|
||||||
long now = SystemClock.uptimeMillis();
|
long now = SystemClock.uptimeMillis();
|
||||||
if (action == MotionEvent.ACTION_DOWN) {
|
|
||||||
lastMouseDown = now;
|
|
||||||
}
|
|
||||||
Point point = device.getPhysicalPoint(position);
|
Point point = device.getPhysicalPoint(position);
|
||||||
if (point == null) {
|
if (point == null) {
|
||||||
// ignore event
|
// ignore event
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
setPointerCoords(point);
|
|
||||||
MotionEvent event = MotionEvent.obtain(lastMouseDown, now, action, 1, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, 0, 0,
|
int pointerIndex = pointersState.getPointerIndex(pointerId);
|
||||||
InputDevice.SOURCE_TOUCHSCREEN, 0);
|
if (pointerIndex == -1) {
|
||||||
|
Ln.w("Too many pointers for touch event");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Pointer pointer = pointersState.get(pointerIndex);
|
||||||
|
pointer.setPoint(point);
|
||||||
|
pointer.setPressure(pressure);
|
||||||
|
pointer.setUp(action == MotionEvent.ACTION_UP);
|
||||||
|
|
||||||
|
int pointerCount = pointersState.update(pointerProperties, pointerCoords);
|
||||||
|
|
||||||
|
if (pointerCount == 1) {
|
||||||
|
if (action == MotionEvent.ACTION_DOWN) {
|
||||||
|
lastTouchDown = now;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// secondary pointers must use ACTION_POINTER_* ORed with the pointerIndex
|
||||||
|
if (action == MotionEvent.ACTION_UP) {
|
||||||
|
action = MotionEvent.ACTION_POINTER_UP | (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
|
||||||
|
} else if (action == MotionEvent.ACTION_DOWN) {
|
||||||
|
action = MotionEvent.ACTION_POINTER_DOWN | (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionEvent event = MotionEvent.obtain(lastTouchDown, now, action, pointerCount, pointerProperties,
|
||||||
|
pointerCoords, 0, buttons, 1f, 1f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
|
||||||
return injectEvent(event);
|
return injectEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,23 +188,30 @@ public class Controller {
|
|||||||
// ignore event
|
// ignore event
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
setPointerCoords(point);
|
|
||||||
setScroll(hScroll, vScroll);
|
MotionEvent.PointerProperties props = pointerProperties[0];
|
||||||
MotionEvent event = MotionEvent.obtain(lastMouseDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, 0,
|
props.id = 0;
|
||||||
0, InputDevice.SOURCE_MOUSE, 0);
|
|
||||||
|
MotionEvent.PointerCoords coords = pointerCoords[0];
|
||||||
|
coords.x = point.getX();
|
||||||
|
coords.y = point.getY();
|
||||||
|
coords.setAxisValue(MotionEvent.AXIS_HSCROLL, hScroll);
|
||||||
|
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
|
||||||
|
|
||||||
|
MotionEvent event = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties,
|
||||||
|
pointerCoords, 0, 0, 1f, 1f, 0, 0, InputDevice.SOURCE_MOUSE, 0);
|
||||||
return injectEvent(event);
|
return injectEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) {
|
private boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) {
|
||||||
long now = SystemClock.uptimeMillis();
|
long now = SystemClock.uptimeMillis();
|
||||||
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
|
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD,
|
||||||
InputDevice.SOURCE_KEYBOARD);
|
0, 0, InputDevice.SOURCE_KEYBOARD);
|
||||||
return injectEvent(event);
|
return injectEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean injectKeycode(int keyCode) {
|
private boolean injectKeycode(int keyCode) {
|
||||||
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0)
|
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0);
|
||||||
&& injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean injectEvent(InputEvent event) {
|
private boolean injectEvent(InputEvent event) {
|
||||||
|
|||||||
@@ -161,7 +161,11 @@ public final class Device {
|
|||||||
* @param mode one of the {@code SCREEN_POWER_MODE_*} constants
|
* @param mode one of the {@code SCREEN_POWER_MODE_*} constants
|
||||||
*/
|
*/
|
||||||
public void setScreenPowerMode(int mode) {
|
public void setScreenPowerMode(int mode) {
|
||||||
IBinder d = SurfaceControl.getBuiltInDisplay(0);
|
IBinder d = SurfaceControl.getBuiltInDisplay();
|
||||||
|
if (d == null) {
|
||||||
|
Ln.e("Could not get built-in display");
|
||||||
|
return;
|
||||||
|
}
|
||||||
SurfaceControl.setDisplayPowerMode(d, mode);
|
SurfaceControl.setDisplayPowerMode(d, mode);
|
||||||
Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
|
Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
|
||||||
}
|
}
|
||||||
|
|||||||
55
server/src/main/java/com/genymobile/scrcpy/Pointer.java
Normal file
55
server/src/main/java/com/genymobile/scrcpy/Pointer.java
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
public class Pointer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pointer id as received from the client.
|
||||||
|
*/
|
||||||
|
private final long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Local pointer id, using the lowest possible values to fill the {@link android.view.MotionEvent.PointerProperties PointerProperties}.
|
||||||
|
*/
|
||||||
|
private final int localId;
|
||||||
|
|
||||||
|
private Point point;
|
||||||
|
private float pressure;
|
||||||
|
private boolean up;
|
||||||
|
|
||||||
|
public Pointer(long id, int localId) {
|
||||||
|
this.id = id;
|
||||||
|
this.localId = localId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLocalId() {
|
||||||
|
return localId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Point getPoint() {
|
||||||
|
return point;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPoint(Point point) {
|
||||||
|
this.point = point;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getPressure() {
|
||||||
|
return pressure;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPressure(float pressure) {
|
||||||
|
this.pressure = pressure;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUp() {
|
||||||
|
return up;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUp(boolean up) {
|
||||||
|
this.up = up;
|
||||||
|
}
|
||||||
|
}
|
||||||
103
server/src/main/java/com/genymobile/scrcpy/PointersState.java
Normal file
103
server/src/main/java/com/genymobile/scrcpy/PointersState.java
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class PointersState {
|
||||||
|
|
||||||
|
public static final int MAX_POINTERS = 10;
|
||||||
|
|
||||||
|
private final List<Pointer> pointers = new ArrayList<>();
|
||||||
|
|
||||||
|
private int indexOf(long id) {
|
||||||
|
for (int i = 0; i < pointers.size(); ++i) {
|
||||||
|
Pointer pointer = pointers.get(i);
|
||||||
|
if (pointer.getId() == id) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLocalIdAvailable(int localId) {
|
||||||
|
for (int i = 0; i < pointers.size(); ++i) {
|
||||||
|
Pointer pointer = pointers.get(i);
|
||||||
|
if (pointer.getLocalId() == localId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int nextUnusedLocalId() {
|
||||||
|
for (int localId = 0; localId < MAX_POINTERS; ++localId) {
|
||||||
|
if (isLocalIdAvailable(localId)) {
|
||||||
|
return localId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pointer get(int index) {
|
||||||
|
return pointers.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPointerIndex(long id) {
|
||||||
|
int index = indexOf(id);
|
||||||
|
if (index != -1) {
|
||||||
|
// already exists, return it
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
if (pointers.size() >= MAX_POINTERS) {
|
||||||
|
// it's full
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// id 0 is reserved for mouse events
|
||||||
|
int localId = nextUnusedLocalId();
|
||||||
|
if (localId == -1) {
|
||||||
|
throw new AssertionError("pointers.size() < maxFingers implies that a local id is available");
|
||||||
|
}
|
||||||
|
Pointer pointer = new Pointer(id, localId);
|
||||||
|
pointers.add(pointer);
|
||||||
|
// return the index of the pointer
|
||||||
|
return pointers.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the motion event parameters.
|
||||||
|
*
|
||||||
|
* @param props the pointer properties
|
||||||
|
* @param coords the pointer coordinates
|
||||||
|
* @return The number of items initialized (the number of pointers).
|
||||||
|
*/
|
||||||
|
public int update(MotionEvent.PointerProperties[] props, MotionEvent.PointerCoords[] coords) {
|
||||||
|
int count = pointers.size();
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
Pointer pointer = pointers.get(i);
|
||||||
|
|
||||||
|
// id 0 is reserved for mouse events
|
||||||
|
props[i].id = pointer.getLocalId();
|
||||||
|
|
||||||
|
Point point = pointer.getPoint();
|
||||||
|
coords[i].x = point.getX();
|
||||||
|
coords[i].y = point.getY();
|
||||||
|
coords[i].pressure = pointer.getPressure();
|
||||||
|
}
|
||||||
|
cleanUp();
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all pointers which are UP.
|
||||||
|
*/
|
||||||
|
private void cleanUp() {
|
||||||
|
for (int i = pointers.size() - 1; i >= 0; --i) {
|
||||||
|
Pointer pointer = pointers.get(i);
|
||||||
|
if (pointer.isUp()) {
|
||||||
|
pointers.remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ import java.io.IOException;
|
|||||||
|
|
||||||
public final class Server {
|
public final class Server {
|
||||||
|
|
||||||
private static final String SERVER_PATH = "/data/local/tmp/scrcpy-server.jar";
|
private static final String SERVER_PATH = "/data/local/tmp/scrcpy-server";
|
||||||
|
|
||||||
private Server() {
|
private Server() {
|
||||||
// not instantiable
|
// not instantiable
|
||||||
|
|||||||
@@ -1,44 +1,102 @@
|
|||||||
package com.genymobile.scrcpy.wrappers;
|
package com.genymobile.scrcpy.wrappers;
|
||||||
|
|
||||||
|
import com.genymobile.scrcpy.Ln;
|
||||||
|
|
||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.IInterface;
|
import android.os.IInterface;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
public class ClipboardManager {
|
public class ClipboardManager {
|
||||||
|
|
||||||
|
private static final String PACKAGE_NAME = "com.android.shell";
|
||||||
|
private static final int USER_ID = 0;
|
||||||
|
|
||||||
private final IInterface manager;
|
private final IInterface manager;
|
||||||
private final Method getPrimaryClipMethod;
|
private Method getPrimaryClipMethod;
|
||||||
private final Method setPrimaryClipMethod;
|
private Method setPrimaryClipMethod;
|
||||||
|
|
||||||
public ClipboardManager(IInterface manager) {
|
public ClipboardManager(IInterface manager) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
try {
|
}
|
||||||
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class);
|
|
||||||
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class);
|
private Method getGetPrimaryClipMethod() {
|
||||||
} catch (NoSuchMethodException e) {
|
if (getPrimaryClipMethod == null) {
|
||||||
throw new AssertionError(e);
|
try {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class);
|
||||||
|
} else {
|
||||||
|
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class);
|
||||||
|
}
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
Ln.e("Could not find method", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getPrimaryClipMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Method getSetPrimaryClipMethod() {
|
||||||
|
if (setPrimaryClipMethod == null) {
|
||||||
|
try {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class);
|
||||||
|
} else {
|
||||||
|
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class,
|
||||||
|
String.class, int.class);
|
||||||
|
}
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
Ln.e("Could not find method", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return setPrimaryClipMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClipData getPrimaryClip(Method method, IInterface manager) throws InvocationTargetException,
|
||||||
|
IllegalAccessException {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
return (ClipData) method.invoke(manager, PACKAGE_NAME);
|
||||||
|
}
|
||||||
|
return (ClipData) method.invoke(manager, PACKAGE_NAME, USER_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setPrimaryClip(Method method, IInterface manager, ClipData clipData) throws InvocationTargetException,
|
||||||
|
IllegalAccessException {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
method.invoke(manager, clipData, PACKAGE_NAME);
|
||||||
|
} else {
|
||||||
|
method.invoke(manager, clipData, PACKAGE_NAME, USER_ID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public CharSequence getText() {
|
public CharSequence getText() {
|
||||||
|
Method method = getGetPrimaryClipMethod();
|
||||||
|
if (method == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
ClipData clipData = (ClipData) getPrimaryClipMethod.invoke(manager, "com.android.shell");
|
ClipData clipData = getPrimaryClip(method, manager);
|
||||||
if (clipData == null || clipData.getItemCount() == 0) {
|
if (clipData == null || clipData.getItemCount() == 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return clipData.getItemAt(0).getText();
|
return clipData.getItemAt(0).getText();
|
||||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||||
throw new AssertionError(e);
|
Ln.e("Could not invoke " + method.getName(), e);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setText(CharSequence text) {
|
public void setText(CharSequence text) {
|
||||||
|
Method method = getSetPrimaryClipMethod();
|
||||||
|
if (method == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
ClipData clipData = ClipData.newPlainText(null, text);
|
ClipData clipData = ClipData.newPlainText(null, text);
|
||||||
try {
|
try {
|
||||||
setPrimaryClipMethod.invoke(manager, clipData, "com.android.shell");
|
setPrimaryClip(method, manager, clipData);
|
||||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||||
throw new AssertionError(e);
|
Ln.e("Could not invoke " + method.getName(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.genymobile.scrcpy.wrappers;
|
package com.genymobile.scrcpy.wrappers;
|
||||||
|
|
||||||
|
import com.genymobile.scrcpy.Ln;
|
||||||
|
|
||||||
import android.os.IInterface;
|
import android.os.IInterface;
|
||||||
import android.view.InputEvent;
|
import android.view.InputEvent;
|
||||||
|
|
||||||
@@ -13,22 +15,33 @@ public final class InputManager {
|
|||||||
public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2;
|
public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2;
|
||||||
|
|
||||||
private final IInterface manager;
|
private final IInterface manager;
|
||||||
private final Method injectInputEventMethod;
|
private Method injectInputEventMethod;
|
||||||
|
|
||||||
public InputManager(IInterface manager) {
|
public InputManager(IInterface manager) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
try {
|
}
|
||||||
injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class);
|
|
||||||
} catch (NoSuchMethodException e) {
|
private Method getInjectInputEventMethod() {
|
||||||
throw new AssertionError(e);
|
if (injectInputEventMethod == null) {
|
||||||
|
try {
|
||||||
|
injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
Ln.e("Could not find method", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return injectInputEventMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean injectInputEvent(InputEvent inputEvent, int mode) {
|
public boolean injectInputEvent(InputEvent inputEvent, int mode) {
|
||||||
|
Method method = getInjectInputEventMethod();
|
||||||
|
if (method == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
return (Boolean) injectInputEventMethod.invoke(manager, inputEvent, mode);
|
return (Boolean) method.invoke(manager, inputEvent, mode);
|
||||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||||
throw new AssertionError(e);
|
Ln.e("Could not invoke " + method.getName(), e);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.genymobile.scrcpy.wrappers;
|
package com.genymobile.scrcpy.wrappers;
|
||||||
|
|
||||||
|
import com.genymobile.scrcpy.Ln;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.IInterface;
|
import android.os.IInterface;
|
||||||
@@ -9,24 +11,35 @@ import java.lang.reflect.Method;
|
|||||||
|
|
||||||
public final class PowerManager {
|
public final class PowerManager {
|
||||||
private final IInterface manager;
|
private final IInterface manager;
|
||||||
private final Method isScreenOnMethod;
|
private Method isScreenOnMethod;
|
||||||
|
|
||||||
public PowerManager(IInterface manager) {
|
public PowerManager(IInterface manager) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
try {
|
}
|
||||||
@SuppressLint("ObsoleteSdkInt") // we may lower minSdkVersion in the future
|
|
||||||
String methodName = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH ? "isInteractive" : "isScreenOn";
|
private Method getIsScreenOnMethod() {
|
||||||
isScreenOnMethod = manager.getClass().getMethod(methodName);
|
if (isScreenOnMethod == null) {
|
||||||
} catch (NoSuchMethodException e) {
|
try {
|
||||||
throw new AssertionError(e);
|
@SuppressLint("ObsoleteSdkInt") // we may lower minSdkVersion in the future
|
||||||
|
String methodName = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH ? "isInteractive" : "isScreenOn";
|
||||||
|
isScreenOnMethod = manager.getClass().getMethod(methodName);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
Ln.e("Could not find method", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return isScreenOnMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isScreenOn() {
|
public boolean isScreenOn() {
|
||||||
|
Method method = getIsScreenOnMethod();
|
||||||
|
if (method == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
return (Boolean) isScreenOnMethod.invoke(manager);
|
return (Boolean) method.invoke(manager);
|
||||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||||
throw new AssertionError(e);
|
Ln.e("Could not invoke " + method.getName(), e);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,35 +17,49 @@ public class StatusBarManager {
|
|||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void expandNotificationsPanel() {
|
private Method getExpandNotificationsPanelMethod() {
|
||||||
if (expandNotificationsPanelMethod == null) {
|
if (expandNotificationsPanelMethod == null) {
|
||||||
try {
|
try {
|
||||||
expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel");
|
expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel");
|
||||||
} catch (NoSuchMethodException e) {
|
} catch (NoSuchMethodException e) {
|
||||||
Ln.e("ServiceBarManager.expandNotificationsPanel() is not available on this device");
|
Ln.e("Could not find method", e);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
return expandNotificationsPanelMethod;
|
||||||
expandNotificationsPanelMethod.invoke(manager);
|
|
||||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
|
||||||
Ln.e("Could not invoke ServiceBarManager.expandNotificationsPanel()", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void collapsePanels() {
|
private Method getCollapsePanelsMethod() {
|
||||||
if (collapsePanelsMethod == null) {
|
if (collapsePanelsMethod == null) {
|
||||||
try {
|
try {
|
||||||
collapsePanelsMethod = manager.getClass().getMethod("collapsePanels");
|
collapsePanelsMethod = manager.getClass().getMethod("collapsePanels");
|
||||||
} catch (NoSuchMethodException e) {
|
} catch (NoSuchMethodException e) {
|
||||||
Ln.e("ServiceBarManager.collapsePanels() is not available on this device");
|
Ln.e("Could not find method", e);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return collapsePanelsMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void expandNotificationsPanel() {
|
||||||
|
Method method = getExpandNotificationsPanelMethod();
|
||||||
|
if (method == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
collapsePanelsMethod.invoke(manager);
|
method.invoke(manager);
|
||||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||||
Ln.e("Could not invoke ServiceBarManager.collapsePanels()", e);
|
Ln.e("Could not invoke " + method.getName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void collapsePanels() {
|
||||||
|
Method method = getCollapsePanelsMethod();
|
||||||
|
if (method == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
method.invoke(manager);
|
||||||
|
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||||
|
Ln.e("Could not invoke " + method.getName(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
package com.genymobile.scrcpy.wrappers;
|
package com.genymobile.scrcpy.wrappers;
|
||||||
|
|
||||||
|
import com.genymobile.scrcpy.Ln;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
@SuppressLint("PrivateApi")
|
@SuppressLint("PrivateApi")
|
||||||
public final class SurfaceControl {
|
public final class SurfaceControl {
|
||||||
|
|
||||||
@@ -23,6 +28,9 @@ public final class SurfaceControl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Method getBuiltInDisplayMethod;
|
||||||
|
private static Method setDisplayPowerModeMethod;
|
||||||
|
|
||||||
private SurfaceControl() {
|
private SurfaceControl() {
|
||||||
// only static methods
|
// only static methods
|
||||||
}
|
}
|
||||||
@@ -76,24 +84,62 @@ public final class SurfaceControl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IBinder getBuiltInDisplay(int builtInDisplayId) {
|
private static Method getGetBuiltInDisplayMethod() {
|
||||||
try {
|
if (getBuiltInDisplayMethod == null) {
|
||||||
// the method signature has changed in Android Q
|
try {
|
||||||
// <https://github.com/Genymobile/scrcpy/issues/586>
|
// the method signature has changed in Android Q
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
// <https://github.com/Genymobile/scrcpy/issues/586>
|
||||||
return (IBinder) CLASS.getMethod("getBuiltInDisplay", int.class).invoke(null, builtInDisplayId);
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
getBuiltInDisplayMethod = CLASS.getMethod("getBuiltInDisplay", int.class);
|
||||||
|
} else {
|
||||||
|
getBuiltInDisplayMethod = CLASS.getMethod("getInternalDisplayToken");
|
||||||
|
}
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
Ln.e("Could not find method", e);
|
||||||
}
|
}
|
||||||
return (IBinder) CLASS.getMethod("getPhysicalDisplayToken", long.class).invoke(null, builtInDisplayId);
|
}
|
||||||
} catch (Exception e) {
|
return getBuiltInDisplayMethod;
|
||||||
throw new AssertionError(e);
|
}
|
||||||
|
|
||||||
|
public static IBinder getBuiltInDisplay() {
|
||||||
|
Method method = getGetBuiltInDisplayMethod();
|
||||||
|
if (method == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
// call getBuiltInDisplay(0)
|
||||||
|
return (IBinder) method.invoke(null, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// call getInternalDisplayToken()
|
||||||
|
return (IBinder) method.invoke(null);
|
||||||
|
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||||
|
Ln.e("Could not invoke " + method.getName(), e);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Method getSetDisplayPowerModeMethod() {
|
||||||
|
if (setDisplayPowerModeMethod == null) {
|
||||||
|
try {
|
||||||
|
setDisplayPowerModeMethod = CLASS.getMethod("setDisplayPowerMode", IBinder.class, int.class);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
Ln.e("Could not find method", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return setDisplayPowerModeMethod;
|
||||||
|
}
|
||||||
|
|
||||||
public static void setDisplayPowerMode(IBinder displayToken, int mode) {
|
public static void setDisplayPowerMode(IBinder displayToken, int mode) {
|
||||||
|
Method method = getSetDisplayPowerModeMethod();
|
||||||
|
if (method == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
CLASS.getMethod("setDisplayPowerMode", IBinder.class, int.class).invoke(null, displayToken, mode);
|
method.invoke(null, displayToken, mode);
|
||||||
} catch (Exception e) {
|
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||||
throw new AssertionError(e);
|
Ln.e("Could not invoke " + method.getName(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -77,31 +77,36 @@ public class ControlMessageReaderTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseMouseEvent() throws IOException {
|
@SuppressWarnings("checkstyle:MagicNumber")
|
||||||
|
public void testParseTouchEvent() throws IOException {
|
||||||
ControlMessageReader reader = new ControlMessageReader();
|
ControlMessageReader reader = new ControlMessageReader();
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_INJECT_MOUSE_EVENT);
|
dos.writeByte(ControlMessage.TYPE_INJECT_TOUCH_EVENT);
|
||||||
dos.writeByte(MotionEvent.ACTION_DOWN);
|
dos.writeByte(MotionEvent.ACTION_DOWN);
|
||||||
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
|
dos.writeLong(-42); // pointerId
|
||||||
dos.writeInt(100);
|
dos.writeInt(100);
|
||||||
dos.writeInt(200);
|
dos.writeInt(200);
|
||||||
dos.writeShort(1080);
|
dos.writeShort(1080);
|
||||||
dos.writeShort(1920);
|
dos.writeShort(1920);
|
||||||
|
dos.writeShort(0xffff); // pressure
|
||||||
|
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
reader.readFrom(new ByteArrayInputStream(packet));
|
||||||
ControlMessage event = reader.next();
|
ControlMessage event = reader.next();
|
||||||
|
|
||||||
Assert.assertEquals(ControlMessage.TYPE_INJECT_MOUSE_EVENT, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_TOUCH_EVENT, event.getType());
|
||||||
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
|
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
|
||||||
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getButtons());
|
Assert.assertEquals(-42, event.getPointerId());
|
||||||
Assert.assertEquals(100, event.getPosition().getPoint().getX());
|
Assert.assertEquals(100, event.getPosition().getPoint().getX());
|
||||||
Assert.assertEquals(200, event.getPosition().getPoint().getY());
|
Assert.assertEquals(200, event.getPosition().getPoint().getY());
|
||||||
Assert.assertEquals(1080, event.getPosition().getScreenSize().getWidth());
|
Assert.assertEquals(1080, event.getPosition().getScreenSize().getWidth());
|
||||||
Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight());
|
Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight());
|
||||||
|
Assert.assertEquals(1f, event.getPressure(), 0f); // must be exact
|
||||||
|
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getButtons());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user