Compare commits

...

28 Commits

Author SHA1 Message Date
Romain Vimont
fbdcc42dbb Replace Ctrl by Meta for volume shortcuts on MacOS
Ctrl+UP and Ctrl+DOWN are already used by the window manager on MacOS.

Use Cmd key instead (like on VLC).
2018-10-24 19:11:48 +02:00
Romain Vimont
aa10721c9e Refactor to support Meta in shortcuts
Move the Ctrl and Meta key down checks to each shortcut individually, so
that we can add a shortcut involving Meta.
2018-10-24 19:08:40 +02:00
Romain Vimont
0b92b93358 Capture Alt and Meta keys
Alt and Meta keys should not be forwarded to the device. For now, they
are not used for shortcuts, but they could be.
2018-10-24 19:08:36 +02:00
Romain Vimont
c20245630e Factorize Windows command building
Extract command line building to a separate method.
2018-10-21 18:57:06 +02:00
Romain Vimont
b882322f73 Work around Os.write() not updating position
ByteBuffer position is not updated as expected by Os.write() on old
Android versions. Count the remaining bytes manually.

Fixes <https://github.com/Genymobile/scrcpy/issues/291>.
2018-10-09 08:43:17 +02:00
Romain Vimont
8875955921 Support paths containing spaces on Windows
Quote the arguments of "adb push" to support paths which contain spaces
on Windows.

Fixes <https://github.com/Genymobile/scrcpy/issues/288>.
2018-10-04 21:01:23 +02:00
Romain Vimont
ff4430b2a3 Declare fun(void) functions with no parameters
This is not C++.
2018-10-04 17:04:20 +02:00
Romain Vimont
cea176c210 Update links to v1.4 in README and BUILD 2018-10-04 00:13:28 +02:00
Romain Vimont
f613752606 Update platform-tools (28.0.1) for Windows
Include the latest version of adb in Windows releases.
2018-10-03 23:18:37 +02:00
Romain Vimont
24d107d017 Bump version to 1.4 2018-10-03 23:03:27 +02:00
Romain Vimont
66d1f81f56 Merge branch 'master' into dev 2018-10-03 23:02:09 +02:00
Romain Vimont
411aa4fcfd Handle alpha and space chars as raw events
To handle special chars, text is handled as text input instead of key
events. However, this breaks the separation of DOWN and UP key events.

As a compromise, send letters and space as key events, to preserve
original DOWN/UP events, but send other text input events as text, to be
able to send "special" characters.

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

Suggested-by: pete1414
Suggested-by: King-Slide <kingslide@gmail.com>
2018-10-03 22:07:09 +02:00
Romain Vimont
78d5a4d8a1 Add link to Gentoo Ebuild in README 2018-09-19 22:09:52 +02:00
Romain Vimont
52e2c60190 Merge pull request #261 from npes87184/dev
prevent closing console right after process error in windows
2018-09-18 08:56:40 +02:00
yuchenlin
140b1ef6a5 prevent closing console right after process error in windows
Signed-off-by: yuchenlin <npes87184@gmail.com>
2018-09-14 20:34:59 +08:00
Romain Vimont
eca99d5af7 Fix header guard name 2018-09-13 16:27:19 +02:00
Romain Vimont
6a1fb070f7 Merge branch 'npes87184:dev' into dev (#254)
Return specific error for missing adb binary on Windows
2018-09-09 15:28:56 +02:00
yuchenlin
27bed948d4 Use specific error for missing binary on Windows
Signed-off-by: yuchenlin <npes87184@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2018-09-09 15:28:25 +02:00
Romain Vimont
66def38b73 Avoid additional buffer copy in userspace
Directly send the data from MediaCodec buffers to the LocalSocket,
without an intermediate copy in userspace.
2018-09-09 15:05:06 +02:00
Romain Vimont
a60aef5aaf Merge branch 'philippsandhaus:master' into dev (#252)
Added new command line parameter to start in fullscreen
2018-09-04 18:51:56 +02:00
Romain Vimont
28015c3ee4 Present fullscreen option in README 2018-09-04 18:51:35 +02:00
Philipp Sandhaus
af9808cf02 Add option to start in fullscreen
Signed-off-by: Romain Vimont <rom@rom1v.com>
2018-09-04 18:51:32 +02:00
Romain Vimont
34550311be Merge branch 'npes87184:dev' into dev (#236)
check adb runnable before starting scrcpy
2018-09-04 18:43:17 +02:00
Romain Vimont
55d33ddd5f Do not handle system-specific values in command.c
The common command.c handled process errors from system-specific int
values (errno).

Rather, expose a new enum process_result to handle error cause in a
generic way.
2018-09-04 08:57:07 +02:00
yuchenlin
6d2d803003 Notify adb missing
There are many user who encounters missing adb.
To stop things happens again, we check it and show
sexy response to user.

Signed-off-by: yuchenlin <npes87184@gmail.com>
2018-09-04 08:46:17 +02:00
Romain Vimont
fdbb725436 Add link to FLAG_SECURE in FAQ 2018-08-20 15:00:01 +02:00
Romain Vimont
ce6e5d1969 Explain how to install adb on Mac OS
The package scrcpy from Homebrew does not install adb.
2018-08-17 19:51:25 +02:00
Romain Vimont
963890e9c2 Separate build instructions from README
README included build instructions, which made it complicated to follow.
Move the build instructions to a separate file (BUILD.md).
2018-08-17 17:57:08 +02:00
25 changed files with 621 additions and 328 deletions

236
BUILD.md Normal file
View File

@@ -0,0 +1,236 @@
# Build scrcpy
Here are the instructions to build _scrcpy_ (client and server).
You may want to build only the client: the server binary, which will be pushed
to the Android device, does not depend on your system and architecture. In that
case, use the [prebuilt server] (so you will not need Java or the Android SDK).
[prebuilt server]: #prebuilt-server
## Requirements
You need [adb]. It is available in the [Android SDK platform
tools][platform-tools], or packaged in your distribution (`android-adb-tools`).
On Windows, download the [platform-tools][platform-tools-windows] and extract
the following files to a directory accessible from your `PATH`:
- `adb.exe`
- `AdbWinApi.dll`
- `AdbWinUsbApi.dll`
The client requires [FFmpeg] and [LibSDL2]. Just follow the instructions.
[adb]: https://developer.android.com/studio/command-line/adb.html
[platform-tools]: https://developer.android.com/studio/releases/platform-tools.html
[platform-tools-windows]: https://dl.google.com/android/repository/platform-tools-latest-windows.zip
[ffmpeg]: https://en.wikipedia.org/wiki/FFmpeg
[LibSDL2]: https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer
## System-specific steps
### Linux
Install the required packages from your package manager.
#### Debian/Ubuntu
```bash
# runtime dependencies
sudo apt install ffmpeg libsdl2-2.0.0
# client build dependencies
sudo apt install make gcc pkg-config meson \
libavcodec-dev libavformat-dev libavutil-dev \
libsdl2-dev
# server build dependencies
sudo apt install openjdk-8-jdk
```
#### Fedora
```bash
# enable RPM fusion free
sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
# client build dependencies
sudo dnf install SDL2-devel ffms2-devel meson gcc make
# server build dependencies
sudo dnf install java
```
### Windows
#### Cross-compile from Linux
This is the preferred method (and the way the release is built).
From _Debian_, install _mingw_:
```bash
sudo apt install mingw-w64 mingw-w64-tools
```
You also need the JDK to build the server:
```bash
sudo apt install openjdk-8-jdk
```
Then generate the releases:
```bash
make -f Makefile.CrossWindows
```
It will generate win32 and win64 releases into `dist/`.
#### In MSYS2
From Windows, you need [MSYS2] to build the project. From an MSYS2 terminal,
install the required packages:
[MSYS2]: http://www.msys2.org/
```bash
# runtime dependencies
pacman -S mingw-w64-x86_64-SDL2 \
mingw-w64-x86_64-ffmpeg
# client build dependencies
pacman -S mingw-w64-x86_64-make \
mingw-w64-x86_64-gcc \
mingw-w64-x86_64-pkg-config \
mingw-w64-x86_64-meson
```
For a 32 bits version, replace `x86_64` by `i686`:
```bash
# runtime dependencies
pacman -S mingw-w64-i686-SDL2 \
mingw-w64-i686-ffmpeg
# client build dependencies
pacman -S mingw-w64-i686-make \
mingw-w64-i686-gcc \
mingw-w64-i686-pkg-config \
mingw-w64-i686-meson
```
Java (>= 7) is not available in MSYS2, so if you plan to build the server,
install it manually and make it available from the `PATH`:
```bash
export PATH="$JAVA_HOME/bin:$PATH"
```
### Mac OS
Install the packages with [Homebrew]:
[Homebrew]: https://brew.sh/
```bash
# runtime dependencies
brew install sdl2 ffmpeg
# client build dependencies
brew install pkg-config meson
```
Additionally, if you want to build the server, install Java 8 from Caskroom, and
make it avaliable from the `PATH`:
```bash
brew tap caskroom/versions
brew cask install java8
export JAVA_HOME="$(/usr/libexec/java_home --version 1.8)"
export PATH="$JAVA_HOME/bin:$PATH"
```
### Docker
See [pierlon/scrcpy-docker](https://github.com/pierlon/scrcpy-docker).
## Common steps
If you want to build the server, install the [Android SDK] (_Android Studio_),
and set `ANDROID_HOME` to its directory. For example:
[Android SDK]: https://developer.android.com/studio/index.html
```bash
export ANDROID_HOME=~/android/sdk
```
If you don't want to build the server, use the [prebuilt server].
Clone the project:
```bash
git clone https://github.com/Genymobile/scrcpy
cd scrcpy
```
Then, build:
```bash
meson x --buildtype release --strip -Db_lto=true
cd x
ninja
```
### Run
To run without installing:
```bash
./run x [options]
```
### Install
After a successful build, you can install _scrcpy_ on the system:
```bash
sudo ninja install # without sudo on Windows
```
This installs two files:
- `/usr/local/bin/scrcpy`
- `/usr/local/share/scrcpy/scrcpy-server.jar`
Just remove them to "uninstall" the application.
You can then [run](README.md#run) _scrcpy_.
## Prebuilt server
- [`scrcpy-server-v1.4.jar`][direct-scrcpy-server]
_(SHA-256: 1ff7a72fcfe81dadccfab9d6f86c971cd7c7f38f17196748fe05480e301b443d)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.4/scrcpy-server-v1.4.jar
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:
```bash
meson x --buildtype release --strip -Db_lto=true \
-Dprebuilt_server=/path/to/scrcpy-server.jar
cd x
ninja
sudo ninja install
```

3
FAQ.md
View File

@@ -26,10 +26,11 @@ If you still encounter problems, please see [issue 9].
### I get a black screen for some applications like Silence
This is expected, they requested to protect the screen.
This is expected, they requested to [protect] the screen.
In [Silence], you can disable it in settings → Privacy → Screen security.
[protect]: https://developer.android.com/reference/android/view/Display#FLAG_SECURE
[silence]: https://f-droid.org/en/packages/org.smssecure.smssecure/
See [issue 36].

263
README.md
View File

@@ -1,4 +1,4 @@
# scrcpy (v1.3)
# scrcpy (v1.4)
This application provides display and control of Android devices connected on
USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access.
@@ -11,156 +11,49 @@ It works on _GNU/Linux_, _Windows_ and _MacOS_.
The Android part requires at least API 21 (Android 5.0).
You need [adb]. It is available in the [Android SDK platform
tools][platform-tools], or packaged in your distribution (`android-adb-tools`).
On Windows, just [download scrcpy for Windows](#windows), `adb` is included.
Make sure you [enabled adb debugging][enable-adb] on your device(s).
The client requires [FFmpeg] and [LibSDL2]. On Windows, they are included in the
[prebuilt application](#windows).
[adb]: https://developer.android.com/studio/command-line/adb.html
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
[platform-tools]: https://developer.android.com/studio/releases/platform-tools.html
[platform-tools-windows]: https://dl.google.com/android/repository/platform-tools-latest-windows.zip
[ffmpeg]: https://en.wikipedia.org/wiki/FFmpeg
[LibSDL2]: https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer
## Build and install
### System-specific steps
## Get the app
#### Linux
Install the required packages from your package manager.
### Linux
##### Debian/Ubuntu
On Linux, you typically need to [build the app manually][BUILD]. Don't worry,
it's not that hard.
```bash
# runtime dependencies
sudo apt install ffmpeg libsdl2-2.0.0
# client build dependencies
sudo apt install make gcc pkg-config meson \
libavcodec-dev libavformat-dev libavutil-dev \
libsdl2-dev
# server build dependencies
sudo apt install openjdk-8-jdk
```
##### Fedora
```bash
# enable RPM fusion free
sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
# client build dependencies
sudo dnf install SDL2-devel ffms2-devel meson gcc make
# server build dependencies
sudo dnf install java
```
##### Arch Linux
Two [AUR] packages have been created by users:
For Arch Linux, two [AUR] packages have been created by users:
- [`scrcpy`](https://aur.archlinux.org/packages/scrcpy/)
- [`scrcpy-prebuiltserver`](https://aur.archlinux.org/packages/scrcpy-prebuiltserver/)
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
#### Windows
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
### Windows
For Windows, for simplicity, prebuilt archives with all the dependencies
(including `adb`) are available:
- [`scrcpy-win32-v1.3.zip`][direct-win32].
_(SHA-256: 51a2990e631ed469a7a86ff38107d517a91d313fb3f8327eb7bc71dde40870b5)_
- [`scrcpy-win64-v1.3.zip`][direct-win64].
_(SHA-256: 0768a80d3d600d0bbcd220ca150ae88a3a58d1fe85c308a8c61f44480b711e43)_
- [`scrcpy-win32-v1.4.zip`][direct-win32]
_(SHA-256: 1f72fa520980727e8943b7214b64c66b00b9b5267f7cffefb64fa37c3ca803cf)_
- [`scrcpy-win64-v1.4.zip`][direct-win64]
_(SHA-256: 382f02bd8ed3db2cc7ab15aabdb83674744993b936d602b01e6959a150584a79)_
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.3/scrcpy-win32-v1.3.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.3/scrcpy-win64-v1.3.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.4/scrcpy-win32-v1.4.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.4/scrcpy-win64-v1.4.zip
Instead, you may want to build it manually.
In that case, download the [platform-tools][platform-tools-windows] and extract
the following files to a directory accessible from your `PATH`:
- `adb.exe`
- `AdbWinApi.dll`
- `AdbWinUsbApi.dll`
##### Cross-compile from Linux
This is the preferred method (and the way the release is built).
From _Debian_, install _mingw_:
```bash
sudo apt install mingw-w64 mingw-w64-tools
```
You also need the JDK to build the server:
```bash
sudo apt install openjdk-8-jdk
```
Then generate the releases:
```bash
make -f Makefile.CrossWindows
```
It will generate win32 and win64 releases into `dist/`.
You can also [build the app manually][BUILD].
##### In MSYS2
From Windows, you need [MSYS2] to build the project. From an MSYS2 terminal,
install the required packages:
[MSYS2]: http://www.msys2.org/
```bash
# runtime dependencies
pacman -S mingw-w64-x86_64-SDL2 \
mingw-w64-x86_64-ffmpeg
# client build dependencies
pacman -S mingw-w64-x86_64-make \
mingw-w64-x86_64-gcc \
mingw-w64-x86_64-pkg-config \
mingw-w64-x86_64-meson
```
For a 32 bits version, replace `x86_64` by `i686`:
```bash
# runtime dependencies
pacman -S mingw-w64-i686-SDL2 \
mingw-w64-i686-ffmpeg
# client build dependencies
pacman -S mingw-w64-i686-make \
mingw-w64-i686-gcc \
mingw-w64-i686-pkg-config \
mingw-w64-i686-meson
```
Java (>= 7) is not available in MSYS2, so if you plan to build the server,
install it manually and make it available from the `PATH`:
```bash
export PATH="$JAVA_HOME/bin:$PATH"
```
#### Mac OS
### Mac OS
The application is available in [Homebrew]. Just install it:
@@ -170,106 +63,18 @@ The application is available in [Homebrew]. Just install it:
brew install scrcpy
```
Instead, you may want to build it manually. Install the packages:
You need `adb`, accessible from your `PATH`. If you don't have it yet:
```bash
# runtime dependencies
brew install sdl2 ffmpeg
# client build dependencies
brew install pkg-config meson
brew cask install android-platform-tools
```
Additionally, if you want to build the server, install Java 8 from Caskroom, and
make it avaliable from the `PATH`:
You can also [build the app manually][BUILD].
```bash
brew tap caskroom/versions
brew cask install java8
export JAVA_HOME="$(/usr/libexec/java_home --version 1.8)"
export PATH="$JAVA_HOME/bin:$PATH"
```
#### Docker
See [pierlon/scrcpy-docker](https://github.com/pierlon/scrcpy-docker).
### Common steps
Install the [Android SDK] (_Android Studio_), and set `ANDROID_HOME` to
its directory. For example:
[Android SDK]: https://developer.android.com/studio/index.html
```bash
export ANDROID_HOME=~/android/sdk
```
Clone the project:
```bash
git clone https://github.com/Genymobile/scrcpy
cd scrcpy
```
Then, build:
```bash
meson x --buildtype release --strip -Db_lto=true
cd x
ninja
```
You can test it from here:
```bash
ninja run
```
Or you can install it on the system:
```bash
sudo ninja install # without sudo on Windows
```
This installs two files:
- `/usr/local/bin/scrcpy`
- `/usr/local/share/scrcpy/scrcpy-server.jar`
Just remove them to "uninstall" the application.
#### Prebuilt server
Since the server binary, that will be pushed to the Android device, does not
depend on your system and architecture, you may want to use the prebuilt binary
instead:
- [`scrcpy-server-v1.3.jar`][direct-scrcpy-server].
_(SHA-256: 0f9a5a217f33f0ed7a1498ceb3c0cccf31c53533893aa952e674c1571d2740c1)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.3/scrcpy-server-v1.3.jar
In that case, the build does not require Java or the Android SDK.
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:
```bash
meson x --buildtype release --strip -Db_lto=true \
-Dprebuilt_server=/path/to/scrcpy-server.jar
cd x
ninja
sudo ninja install
```
## Run
_At runtime, `adb` must be accessible from your `PATH`._
If everything is ok, just plug an Android device, and execute:
Plug an Android device, and execute:
```bash
scrcpy
@@ -312,14 +117,11 @@ To show physical touches while scrcpy is running:
scrcpy -t
```
To run without installing:
The app may be started directly in fullscreen:
```bash
./run x [options]
```
(where `x` is your build directory).
scrcpy -f
```
## Shortcuts
@@ -332,8 +134,8 @@ To run without installing:
| click on `BACK` | `Ctrl`+`b` \| _Right-click²_ |
| click on `APP_SWITCH` | `Ctrl`+`s` |
| click on `MENU` | `Ctrl`+`m` |
| click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ |
| click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ |
| click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ (`Cmd`+`↑` on MacOS) |
| click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ (`Cmd`+`↓` on MacOS) |
| click on `POWER` | `Ctrl`+`p` |
| turn screen on | _Right-click²_ |
| paste computer clipboard to device | `Ctrl`+`v` |
@@ -355,6 +157,13 @@ A colleague challenged me to find a name as unpronounceable as [gnirehtet].
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
## How to build?
See [BUILD].
[BUILD]: BUILD.md
## Common issues
See the [FAQ](FAQ.md).

View File

@@ -85,7 +85,7 @@ conf = configuration_data()
conf.set('BUILD_DEBUG', get_option('buildtype') == 'debug')
# the version, updated on release
conf.set_quoted('SCRCPY_VERSION', '1.3')
conf.set_quoted('SCRCPY_VERSION', '1.4')
# the prefix used during configuration (meson --prefix=PREFIX)
conf.set_quoted('PREFIX', get_option('prefix'))

View File

@@ -6,10 +6,11 @@
#include "common.h"
#include "log.h"
#include "str_util.h"
static const char *adb_command;
static inline const char *get_adb_command() {
static inline const char *get_adb_command(void) {
if (!adb_command) {
adb_command = getenv("ADB");
if (!adb_command)
@@ -18,9 +19,25 @@ static inline const char *get_adb_command() {
return adb_command;
}
static void show_adb_err_msg(enum process_result err) {
switch (err) {
case PROCESS_ERROR_GENERIC:
LOGE("Failed to execute adb");
break;
case PROCESS_ERROR_MISSING_BINARY:
LOGE("'adb' command not found (make it accessible from your PATH "
"or define its full path in the ADB environment variable)");
break;
case PROCESS_SUCCESS:
/* do nothing */
break;
}
}
process_t adb_execute(const char *serial, const char *const adb_cmd[], int len) {
const char *cmd[len + 4];
int i;
process_t process;
cmd[0] = get_adb_command();
if (serial) {
cmd[1] = "-s";
@@ -32,7 +49,12 @@ process_t adb_execute(const char *serial, const char *const adb_cmd[], int len)
memcpy(&cmd[i], adb_cmd, len * sizeof(const char *));
cmd[len + i] = NULL;
return cmd_execute(cmd[0], cmd);
enum process_result r = cmd_execute(cmd[0], cmd, &process);
if (r != PROCESS_SUCCESS) {
show_adb_err_msg(r);
return PROCESS_NONE;
}
return process;
}
process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name) {
@@ -68,23 +90,49 @@ process_t adb_reverse_remove(const char *serial, const char *device_socket_name)
}
process_t adb_push(const char *serial, const char *local, const char *remote) {
#ifdef __WINDOWS__
// Windows will parse the string, so the paths must be quoted
// (see sys/win/command.c)
local = strquote(local);
if (!local) {
return PROCESS_NONE;
}
remote = strquote(remote);
if (!remote) {
free((void *) local);
return PROCESS_NONE;
}
#endif
const char *const adb_cmd[] = {"push", local, remote};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__
free((void *) remote);
free((void *) local);
#endif
return proc;
}
process_t adb_install(const char *serial, const char *local) {
#ifdef __WINDOWS__
// Windows will parse the string, so the local name must be quoted (see sys/win/command.c)
size_t len = strlen(local);
char quoted[len + 3];
memcpy(&quoted[1], local, len);
quoted[0] = '"';
quoted[len + 1] = '"';
quoted[len + 2] = '\0';
local = quoted;
// Windows will parse the string, so the local name must be quoted
// (see sys/win/command.c)
local = strquote(local);
if (!local) {
return PROCESS_NONE;
}
#endif
const char *const adb_cmd[] = {"install", "-r", local};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__
free((void *) local);
#endif
return proc;
}
process_t adb_remove_path(const char *serial, const char *path) {

View File

@@ -32,7 +32,13 @@
#endif
# define NO_EXIT_CODE -1
process_t cmd_execute(const char *path, const char *const argv[]);
enum process_result {
PROCESS_SUCCESS,
PROCESS_ERROR_GENERIC,
PROCESS_ERROR_MISSING_BINARY,
};
enum process_result cmd_execute(const char *path, const char *const argv[], process_t *process);
SDL_bool cmd_terminate(process_t pid);
SDL_bool cmd_simple_wait(process_t pid, exit_code_t *exit_code);

View File

@@ -70,7 +70,7 @@ static enum android_metastate convert_meta_state(SDL_Keymod mod) {
return autocomplete_metastate(metastate);
}
static SDL_bool convert_keycode(SDL_Keycode from, enum android_keycode *to) {
static SDL_bool convert_keycode(SDL_Keycode from, enum android_keycode *to, Uint16 mod) {
switch (from) {
MAP(SDLK_RETURN, AKEYCODE_ENTER);
MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER);
@@ -86,6 +86,39 @@ static SDL_bool convert_keycode(SDL_Keycode from, enum android_keycode *to) {
MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT);
MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN);
MAP(SDLK_UP, AKEYCODE_DPAD_UP);
}
if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) {
return SDL_FALSE;
}
// if ALT and META are not pressed, also handle letters and space
switch (from) {
MAP(SDLK_a, AKEYCODE_A);
MAP(SDLK_b, AKEYCODE_B);
MAP(SDLK_c, AKEYCODE_C);
MAP(SDLK_d, AKEYCODE_D);
MAP(SDLK_e, AKEYCODE_E);
MAP(SDLK_f, AKEYCODE_F);
MAP(SDLK_g, AKEYCODE_G);
MAP(SDLK_h, AKEYCODE_H);
MAP(SDLK_i, AKEYCODE_I);
MAP(SDLK_j, AKEYCODE_J);
MAP(SDLK_k, AKEYCODE_K);
MAP(SDLK_l, AKEYCODE_L);
MAP(SDLK_m, AKEYCODE_M);
MAP(SDLK_n, AKEYCODE_N);
MAP(SDLK_o, AKEYCODE_O);
MAP(SDLK_p, AKEYCODE_P);
MAP(SDLK_q, AKEYCODE_Q);
MAP(SDLK_r, AKEYCODE_R);
MAP(SDLK_s, AKEYCODE_S);
MAP(SDLK_t, AKEYCODE_T);
MAP(SDLK_u, AKEYCODE_U);
MAP(SDLK_v, AKEYCODE_V);
MAP(SDLK_w, AKEYCODE_W);
MAP(SDLK_x, AKEYCODE_X);
MAP(SDLK_y, AKEYCODE_Y);
MAP(SDLK_z, AKEYCODE_Z);
MAP(SDLK_SPACE, AKEYCODE_SPACE);
FAIL;
}
}
@@ -126,11 +159,12 @@ SDL_bool input_key_from_sdl_to_android(const SDL_KeyboardEvent *from,
return SDL_FALSE;
}
if (!convert_keycode(from->keysym.sym, &to->keycode_event.keycode)) {
Uint16 mod = from->keysym.mod;
if (!convert_keycode(from->keysym.sym, &to->keycode_event.keycode, mod)) {
return SDL_FALSE;
}
to->keycode_event.metastate = convert_meta_state(from->keysym.mod);
to->keycode_event.metastate = convert_meta_state(mod);
return SDL_TRUE;
}

View File

@@ -1,5 +1,5 @@
#ifndef FILE_HANDLER_H
#define FILE_HADNELR_H
#define FILE_HANDLER_H
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h>

View File

@@ -1,5 +1,6 @@
#include "input_manager.h"
#include <SDL2/SDL_assert.h>
#include "convert.h"
#include "lock_util.h"
#include "log.h"
@@ -129,6 +130,12 @@ static void clipboard_paste(struct controller *controller) {
void input_manager_process_text_input(struct input_manager *input_manager,
const SDL_TextInputEvent *event) {
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_event control_event;
control_event.type = CONTROL_EVENT_TYPE_TEXT;
control_event.text_event.text = SDL_strdup(event->text);
@@ -144,9 +151,17 @@ void input_manager_process_text_input(struct input_manager *input_manager,
void input_manager_process_key(struct input_manager *input_manager,
const SDL_KeyboardEvent *event) {
SDL_bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL);
SDL_bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT);
SDL_bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI);
if (alt) {
// no shortcut involves Alt or Meta, and they should not be forwarded
// to the device
return;
}
// capture all Ctrl events
if (ctrl) {
if (ctrl | meta) {
SDL_bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
if (shift) {
// currently, there is no shortcut involving SHIFT
@@ -158,61 +173,73 @@ void input_manager_process_key(struct input_manager *input_manager,
SDL_bool repeat = event->repeat;
switch (keycode) {
case SDLK_h:
if (!repeat) {
if (ctrl && !meta && !repeat) {
action_home(input_manager->controller, action);
}
return;
case SDLK_b: // fall-through
case SDLK_BACKSPACE:
if (!repeat) {
if (ctrl && !meta && !repeat) {
action_back(input_manager->controller, action);
}
return;
case SDLK_s:
if (!repeat) {
if (ctrl && !meta && !repeat) {
action_app_switch(input_manager->controller, action);
}
return;
case SDLK_m:
if (!repeat) {
if (ctrl && !meta && !repeat) {
action_menu(input_manager->controller, action);
}
return;
case SDLK_p:
if (!repeat) {
if (ctrl && !meta && !repeat) {
action_power(input_manager->controller, action);
}
return;
case SDLK_DOWN:
// forward repeated events
action_volume_down(input_manager->controller, action);
#ifdef __APPLE__
if (!ctrl && meta) {
#else
if (ctrl && !meta) {
#endif
// forward repeated events
action_volume_down(input_manager->controller, action);
}
return;
case SDLK_UP:
// forward repeated events
action_volume_up(input_manager->controller, action);
#ifdef __APPLE__
if (!ctrl && meta) {
#else
if (ctrl && !meta) {
#endif
// forward repeated events
action_volume_up(input_manager->controller, action);
}
return;
case SDLK_v:
if (!repeat && event->type == SDL_KEYDOWN) {
if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) {
clipboard_paste(input_manager->controller);
}
return;
case SDLK_f:
if (!repeat && event->type == SDL_KEYDOWN) {
if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) {
screen_switch_fullscreen(input_manager->screen);
}
return;
case SDLK_x:
if (!repeat && event->type == SDL_KEYDOWN) {
if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) {
screen_resize_to_fit(input_manager->screen);
}
return;
case SDLK_g:
if (!repeat && event->type == SDL_KEYDOWN) {
if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) {
screen_resize_to_pixel_perfect(input_manager->screen);
}
return;
case SDLK_i:
if (!repeat && event->type == SDL_KEYDOWN) {
if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) {
switch_fps_counter_state(input_manager->frames);
}
return;

View File

@@ -11,6 +11,7 @@
struct args {
const char *serial;
const char *crop;
SDL_bool fullscreen;
SDL_bool help;
SDL_bool version;
SDL_bool show_touches;
@@ -36,6 +37,9 @@ static void usage(const char *arg0) {
" (typically, portrait for a phone, landscape for a tablet).\n"
" Any --max-size value is computed on the cropped size.\n"
"\n"
" -f, --fullscreen\n"
" Start in fullscreen.\n"
"\n"
" -h, --help\n"
" Print this help.\n"
"\n"
@@ -200,6 +204,7 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
static const struct option long_options[] = {
{"bit-rate", required_argument, NULL, 'b'},
{"crop", required_argument, NULL, 'c'},
{"fullscreen", no_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'},
{"max-size", required_argument, NULL, 'm'},
{"port", required_argument, NULL, 'p'},
@@ -209,7 +214,7 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
{NULL, 0, NULL, 0 },
};
int c;
while ((c = getopt_long(argc, argv, "b:c:hm:p:s:tv", long_options, NULL)) != -1) {
while ((c = getopt_long(argc, argv, "b:c:fhm:p:s:tv", long_options, NULL)) != -1) {
switch (c) {
case 'b':
if (!parse_bit_rate(optarg, &args->bit_rate)) {
@@ -219,6 +224,9 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
case 'c':
args->crop = optarg;
break;
case 'f':
args->fullscreen = SDL_TRUE;
break;
case 'h':
args->help = SDL_TRUE;
break;
@@ -305,10 +313,17 @@ int main(int argc, char *argv[]) {
.max_size = args.max_size,
.bit_rate = args.bit_rate,
.show_touches = args.show_touches,
.fullscreen = args.fullscreen,
};
int res = scrcpy(&options) ? 0 : 1;
avformat_network_deinit(); // ignore failure
#if defined (__WINDOWS__) && ! defined (WINDOWS_NOCONSOLE)
if (res != 0) {
fprintf(stderr, "Press any key to continue...\n");
getchar();
}
#endif
return res;
}

View File

@@ -223,6 +223,10 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
show_touches_waited = SDL_TRUE;
}
if (options->fullscreen) {
screen_switch_fullscreen(&screen);
}
ret = event_loop();
LOGD("quit...");

View File

@@ -10,6 +10,7 @@ struct scrcpy_options {
Uint16 max_size;
Uint32 bit_rate;
SDL_bool show_touches;
SDL_bool fullscreen;
};
SDL_bool scrcpy(const struct scrcpy_options *options);

View File

@@ -1,5 +1,8 @@
#include "str_util.h"
#include <stdlib.h>
#include <string.h>
size_t xstrncpy(char *dest, const char *src, size_t n) {
size_t i;
for (i = 0; i < n - 1 && src[i] != '\0'; ++i)
@@ -31,3 +34,16 @@ truncated:
dst[n - 1] = '\0';
return n;
}
char *strquote(const char *src) {
size_t len = strlen(src);
char *quoted = malloc(len + 3);
if (!quoted) {
return NULL;
}
memcpy(&quoted[1], src, len);
quoted[0] = '"';
quoted[len + 1] = '"';
quoted[len + 2] = '\0';
return quoted;
}

View File

@@ -16,4 +16,8 @@ size_t xstrncpy(char *dest, const char *src, size_t n);
// occurred, or n if truncated
size_t xstrjoin(char *dst, const char *const tokens[], char sep, size_t n);
// quote a string
// returns the new allocated string, to be freed by the caller
char *strquote(const char *src);
#endif

View File

@@ -1,5 +1,7 @@
#include "command.h"
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
@@ -7,18 +9,65 @@
#include <unistd.h>
#include "log.h"
pid_t cmd_execute(const char *path, const char *const argv[]) {
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return -1;
enum process_result cmd_execute(const char *path, const char *const argv[], pid_t *pid) {
int fd[2];
if (pipe(fd) == -1) {
perror("pipe");
return PROCESS_ERROR_GENERIC;
}
if (pid == 0) {
execvp(path, (char *const *)argv);
perror("exec");
enum process_result ret = PROCESS_SUCCESS;
*pid = fork();
if (*pid == -1) {
perror("fork");
ret = PROCESS_ERROR_GENERIC;
goto end;
}
if (*pid > 0) {
// parent close write side
close(fd[1]);
fd[1] = -1;
// wait for EOF or receive errno from child
if (read(fd[0], &ret, sizeof(ret)) == -1) {
perror("read");
ret = PROCESS_ERROR_GENERIC;
goto end;
}
} else if (*pid == 0) {
// child close read side
close(fd[0]);
if (fcntl(fd[1], F_SETFD, FD_CLOEXEC) == 0) {
execvp(path, (char *const *)argv);
if (errno == ENOENT) {
ret = PROCESS_ERROR_MISSING_BINARY;
} else {
ret = PROCESS_ERROR_GENERIC;
}
perror("exec");
} else {
perror("fcntl");
ret = PROCESS_ERROR_GENERIC;
}
// send ret to the parent
if (write(fd[1], &ret, sizeof(ret)) == -1) {
perror("write");
}
// close write side before exiting
close(fd[1]);
_exit(1);
}
return pid;
end:
if (fd[0] != -1) {
close(fd[0]);
}
if (fd[1] != -1) {
close(fd[1]);
}
return ret;
}
SDL_bool cmd_terminate(pid_t pid) {

View File

@@ -4,21 +4,29 @@
#include "log.h"
#include "str_util.h"
HANDLE cmd_execute(const char *path, const char *const argv[]) {
static int build_cmd(char *cmd, size_t len, const char *const argv[]) {
// Windows command-line parsing is WTF:
// <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS>
// only make it work for this very specific program
// (don't handle escaping nor quotes)
size_t ret = xstrjoin(cmd, argv, ' ', len);
if (ret >= len) {
LOGE("Command too long (%" PRIsizet " chars)", len - 1);
return -1;
}
return 0;
}
enum process_result cmd_execute(const char *path, const char *const argv[], HANDLE *handle) {
STARTUPINFO si;
PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
// Windows command-line parsing is WTF:
// <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS>
// only make it work for this very specific program
// (don't handle escaping nor quotes)
char cmd[256];
size_t ret = xstrjoin(cmd, argv, ' ', sizeof(cmd));
if (ret >= sizeof(cmd)) {
LOGE("Command too long (%" PRIsizet " chars)", sizeof(cmd) - 1);
return NULL;
if (build_cmd(cmd, sizeof(cmd), argv)) {
*handle = NULL;
return PROCESS_ERROR_GENERIC;
}
#ifdef WINDOWS_NOCONSOLE
@@ -27,10 +35,15 @@ HANDLE cmd_execute(const char *path, const char *const argv[]) {
int flags = 0;
#endif
if (!CreateProcess(NULL, cmd, NULL, NULL, FALSE, flags, NULL, NULL, &si, &pi)) {
return NULL;
*handle = NULL;
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
return PROCESS_ERROR_MISSING_BINARY;
}
return PROCESS_ERROR_GENERIC;
}
return pi.hProcess;
*handle = pi.hProcess;
return PROCESS_SUCCESS;
}
SDL_bool cmd_terminate(HANDLE handle) {

View File

@@ -3,7 +3,7 @@
#include "control_event.h"
static void test_control_event_queue_empty() {
static void test_control_event_queue_empty(void) {
struct control_event_queue queue;
SDL_bool init_ok = control_event_queue_init(&queue);
assert(init_ok);
@@ -25,7 +25,7 @@ static void test_control_event_queue_empty() {
control_event_queue_destroy(&queue);
}
static void test_control_event_queue_full() {
static void test_control_event_queue_full(void) {
struct control_event_queue queue;
SDL_bool init_ok = control_event_queue_init(&queue);
assert(init_ok);
@@ -43,7 +43,7 @@ static void test_control_event_queue_full() {
control_event_queue_destroy(&queue);
}
static void test_control_event_queue_push_take() {
static void test_control_event_queue_push_take(void) {
struct control_event_queue queue;
SDL_bool init_ok = control_event_queue_init(&queue);
assert(init_ok);
@@ -87,7 +87,7 @@ static void test_control_event_queue_push_take() {
control_event_queue_destroy(&queue);
}
int main() {
int main(void) {
test_control_event_queue_empty();
test_control_event_queue_full();
test_control_event_queue_push_take();

View File

@@ -3,7 +3,7 @@
#include "control_event.h"
static void test_serialize_keycode_event() {
static void test_serialize_keycode_event(void) {
struct control_event event = {
.type = CONTROL_EVENT_TYPE_KEYCODE,
.keycode_event = {
@@ -26,7 +26,7 @@ static void test_serialize_keycode_event() {
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_text_event() {
static void test_serialize_text_event(void) {
struct control_event event = {
.type = CONTROL_EVENT_TYPE_TEXT,
.text_event = {
@@ -46,7 +46,7 @@ static void test_serialize_text_event() {
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_long_text_event() {
static void test_serialize_long_text_event(void) {
struct control_event event;
event.type = CONTROL_EVENT_TYPE_TEXT;
char text[TEXT_MAX_LENGTH];
@@ -66,7 +66,7 @@ static void test_serialize_long_text_event() {
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_mouse_event() {
static void test_serialize_mouse_event(void) {
struct control_event event = {
.type = CONTROL_EVENT_TYPE_MOUSE,
.mouse_event = {
@@ -99,7 +99,7 @@ static void test_serialize_mouse_event() {
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_scroll_event() {
static void test_serialize_scroll_event(void) {
struct control_event event = {
.type = CONTROL_EVENT_TYPE_SCROLL,
.scroll_event = {
@@ -132,11 +132,10 @@ static void test_serialize_scroll_event() {
assert(!memcmp(buf, expected, sizeof(expected)));
}
int main() {
int main(void) {
test_serialize_keycode_event();
test_serialize_text_event();
test_serialize_long_text_event();
test_serialize_mouse_event();
test_serialize_scroll_event();
return 0;
}

View File

@@ -3,7 +3,7 @@
#include "str_util.h"
static void test_xstrncpy_simple() {
static void test_xstrncpy_simple(void) {
char s[] = "xxxxxxxxxx";
size_t w = xstrncpy(s, "abcdef", sizeof(s));
@@ -20,7 +20,7 @@ static void test_xstrncpy_simple() {
assert(!strcmp("abcdef", s));
}
static void test_xstrncpy_just_fit() {
static void test_xstrncpy_just_fit(void) {
char s[] = "xxxxxx";
size_t w = xstrncpy(s, "abcdef", sizeof(s));
@@ -34,7 +34,7 @@ static void test_xstrncpy_just_fit() {
assert(!strcmp("abcdef", s));
}
static void test_xstrncpy_truncated() {
static void test_xstrncpy_truncated(void) {
char s[] = "xxx";
size_t w = xstrncpy(s, "abcdef", sizeof(s));
@@ -48,7 +48,7 @@ static void test_xstrncpy_truncated() {
assert(!strncmp("abcdef", s, 3));
}
static void test_xstrjoin_simple() {
static void test_xstrjoin_simple(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxxxxxxxxxx";
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
@@ -66,7 +66,7 @@ static void test_xstrjoin_simple() {
assert(!strcmp("abc de fghi", s));
}
static void test_xstrjoin_just_fit() {
static void test_xstrjoin_just_fit(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxxxxxxx";
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
@@ -81,7 +81,7 @@ static void test_xstrjoin_just_fit() {
assert(!strcmp("abc de fghi", s));
}
static void test_xstrjoin_truncated_in_token() {
static void test_xstrjoin_truncated_in_token(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxx";
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
@@ -96,7 +96,7 @@ static void test_xstrjoin_truncated_in_token() {
assert(!strcmp("abc d", s));
}
static void test_xstrjoin_truncated_before_sep() {
static void test_xstrjoin_truncated_before_sep(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxx";
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
@@ -111,7 +111,7 @@ static void test_xstrjoin_truncated_before_sep() {
assert(!strcmp("abc de", s));
}
static void test_xstrjoin_truncated_after_sep() {
static void test_xstrjoin_truncated_after_sep(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxxx";
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
@@ -126,7 +126,7 @@ static void test_xstrjoin_truncated_after_sep() {
assert(!strcmp("abc de ", s));
}
int main() {
int main(void) {
test_xstrncpy_simple();
test_xstrncpy_just_fit();
test_xstrncpy_truncated();

View File

@@ -35,6 +35,6 @@ prepare-sdl2:
SDL2-2.0.8
prepare-adb:
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r28.0.0-windows.zip \
e2c1ec7c8e9b71cf1c8befd3bff91d06b26dd334c3f32b3817e9d46ba260b0e8 \
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r28.0.1-windows.zip \
db78f726d5dc653706dcd15a462ab1b946c643f598df76906c4c1858411c54df \
platform-tools

View File

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

View File

@@ -5,9 +5,9 @@ import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
public final class DesktopConnection implements Closeable {
@@ -18,14 +18,14 @@ public final class DesktopConnection implements Closeable {
private final LocalSocket socket;
private final InputStream inputStream;
private final OutputStream outputStream;
private final FileDescriptor fd;
private final ControlEventReader reader = new ControlEventReader();
private DesktopConnection(LocalSocket socket) throws IOException {
this.socket = socket;
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
fd = socket.getFileDescriptor();
}
private static LocalSocket connect(String abstractName) throws IOException {
@@ -78,11 +78,11 @@ public final class DesktopConnection implements Closeable {
buffer[DEVICE_NAME_FIELD_LENGTH + 1] = (byte) width;
buffer[DEVICE_NAME_FIELD_LENGTH + 2] = (byte) (height >> 8);
buffer[DEVICE_NAME_FIELD_LENGTH + 3] = (byte) height;
outputStream.write(buffer, 0, buffer.length);
IO.writeFully(fd, buffer, 0, buffer.length);
}
public OutputStream getOutputStream() {
return outputStream;
public FileDescriptor getFd() {
return fd;
}
public ControlEvent receiveControlEvent() throws IOException {

View File

@@ -0,0 +1,40 @@
package com.genymobile.scrcpy;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import java.io.FileDescriptor;
import java.io.IOException;
import java.nio.ByteBuffer;
public class IO {
private IO() {
// not instantiable
}
public static void writeFully(FileDescriptor fd, ByteBuffer from) throws IOException {
// ByteBuffer position is not updated as expected by Os.write() on old Android versions, so
// count the remaining bytes manually.
// See <https://github.com/Genymobile/scrcpy/issues/291>.
int remaining = from.remaining();
while (remaining > 0) {
try {
int w = Os.write(fd, from);
if (BuildConfig.DEBUG && w < 0) {
// w should not be negative, since an exception is thrown on error
throw new AssertionError("Os.write() returned a negative value (" + w + ")");
}
remaining -= w;
} catch (ErrnoException e) {
if (e.errno != OsConstants.EINTR) {
throw new IOException(e);
}
}
}
}
public static void writeFully(FileDescriptor fd, byte[] buffer, int offset, int len) throws IOException {
writeFully(fd, ByteBuffer.wrap(buffer, offset, len));
}
}

View File

@@ -9,8 +9,8 @@ import android.media.MediaFormat;
import android.os.IBinder;
import android.view.Surface;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -48,7 +48,7 @@ public class ScreenEncoder implements Device.RotationListener {
return rotationChanged.getAndSet(false);
}
public void streamScreen(Device device, OutputStream outputStream) throws IOException {
public void streamScreen(Device device, FileDescriptor fd) throws IOException {
MediaFormat format = createFormat(bitRate, frameRate, iFrameInterval);
device.setRotationListener(this);
boolean alive;
@@ -64,7 +64,7 @@ public class ScreenEncoder implements Device.RotationListener {
setDisplaySurface(display, surface, contentRect, videoRect);
codec.start();
try {
alive = encode(codec, outputStream);
alive = encode(codec, fd);
} finally {
codec.stop();
destroyDisplay(display);
@@ -77,9 +77,7 @@ public class ScreenEncoder implements Device.RotationListener {
}
}
private boolean encode(MediaCodec codec, OutputStream outputStream) throws IOException {
@SuppressWarnings("checkstyle:MagicNumber")
byte[] buf = new byte[bitRate / 8]; // may contain up to 1 second of video
private boolean encode(MediaCodec codec, FileDescriptor fd) throws IOException {
boolean eof = false;
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
while (!consumeRotationChange() && !eof) {
@@ -91,15 +89,8 @@ public class ScreenEncoder implements Device.RotationListener {
break;
}
if (outputBufferId >= 0) {
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
while (outputBuffer.hasRemaining()) {
int remaining = outputBuffer.remaining();
int len = Math.min(buf.length, remaining);
// the outputBuffer is probably direct (it has no underlying array), and LocalSocket does not expose channels,
// so we must copy the data locally to write them manually to the output stream
outputBuffer.get(buf, 0, len);
outputStream.write(buf, 0, len);
}
ByteBuffer codecBuffer = codec.getOutputBuffer(outputBufferId);
IO.writeFully(fd, codecBuffer);
}
} finally {
if (outputBufferId >= 0) {

View File

@@ -21,7 +21,7 @@ public final class Server {
try {
// synchronous
screenEncoder.streamScreen(device, connection.getOutputStream());
screenEncoder.streamScreen(device, connection.getFd());
} catch (IOException e) {
// this is expected on close
Ln.d("Screen streaming stopped");