Compare commits

..

18 Commits
v1.2 ... v1.3

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

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

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

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

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

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

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

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

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

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

Instead, add an explicit option "crossbuild_windows".

Fixes <https://github.com/Genymobile/scrcpy/issues/165>.
2018-06-05 20:45:41 +02:00
Romain Vimont
27a9bd3424 Fix clean recipe in cross Makefile
Make the "clean" recipe also remove the noconsole build directories.
2018-06-05 20:42:35 +02:00
Romain Vimont
cf121a0f65 Upgrade gradle 2018-06-05 20:33:37 +02:00
Romain Vimont
c8a5f9dc63 Update links to v1.2 in README 2018-05-28 22:52:52 +02:00
21 changed files with 226 additions and 98 deletions

View File

@@ -38,7 +38,8 @@ release: clean zip-win32 zip-win64 sums
clean:
$(GRADLE) clean
rm -rf "$(SERVER_BUILD_DIR)" "$(WIN32_BUILD_DIR)" "$(WIN64_BUILD_DIR)" "$(DIST)"
rm -rf "$(SERVER_BUILD_DIR)" "$(WIN32_BUILD_DIR)" "$(WIN64_BUILD_DIR)" \
"$(WIN32_NOCONSOLE_BUILD_DIR)" "$(WIN64_NOCONSOLE_BUILD_DIR)" "$(DIST)"
build-server:
[ -d "$(SERVER_BUILD_DIR)" ] || ( mkdir "$(SERVER_BUILD_DIR)" && \
@@ -54,6 +55,7 @@ build-win32: prepare-deps-win32
meson "$(WIN32_BUILD_DIR)" \
--cross-file cross_win32.txt \
--buildtype release --strip -Db_lto=true \
-Dcrossbuild_windows=true \
-Dbuild_server=false \
-Doverride_server_path=scrcpy-server.jar )
ninja -C "$(WIN32_BUILD_DIR)"
@@ -63,6 +65,7 @@ build-win32-noconsole: prepare-deps-win32
meson "$(WIN32_NOCONSOLE_BUILD_DIR)" \
--cross-file cross_win32.txt \
--buildtype release --strip -Db_lto=true \
-Dcrossbuild_windows=true \
-Dbuild_server=false \
-Dwindows_noconsole=true \
-Doverride_server_path=scrcpy-server.jar )
@@ -76,6 +79,7 @@ build-win64: prepare-deps-win64
meson "$(WIN64_BUILD_DIR)" \
--cross-file cross_win64.txt \
--buildtype release --strip -Db_lto=true \
-Dcrossbuild_windows=true \
-Dbuild_server=false \
-Doverride_server_path=scrcpy-server.jar )
ninja -C "$(WIN64_BUILD_DIR)"
@@ -85,6 +89,7 @@ build-win64-noconsole: prepare-deps-win64
meson "$(WIN64_NOCONSOLE_BUILD_DIR)" \
--cross-file cross_win64.txt \
--buildtype release --strip -Db_lto=true \
-Dcrossbuild_windows=true \
-Dbuild_server=false \
-Dwindows_noconsole=true \
-Doverride_server_path=scrcpy-server.jar )

View File

@@ -1,4 +1,4 @@
# scrcpy
# scrcpy (v1.2)
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.
@@ -14,12 +14,7 @@ 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, if you use the [prebuilt application](#windows), it is already
included. Otherwise, just download the [platform-tools][platform-tools-windows]
and extract the following files to a directory accessible from your `PATH`:
- `adb.exe`
- `AdbWinApi.dll`
- `AdbWinUsbApi.dll`
On Windows, just [download scrcpy for Windows](#windows), `adb` is included.
Make sure you [enabled adb debugging][enable-adb] on your device(s).
@@ -81,18 +76,25 @@ Two [AUR] packages have been created by users:
#### Windows
For Windows, for simplicity, a prebuilt archive with all the dependencies
(including `adb`) is available:
For Windows, for simplicity, prebuilt archives with all the dependencies
(including `adb`) are available:
- [`scrcpy-windows-with-deps-v1.1.zip`][direct-windows-with-deps].
_(SHA-256: 27eb36c15937601d1062c1dc0b45faae0e06fefea2019aadeb4fa7f76a07bb4c)_
- [`scrcpy-win32-v1.2.zip`][direct-win32].
_(SHA-256: a1fe1de67ec75dcf970ca5d97a04c26ff0f2d61871f2ef51b6f2f0bf666966b2)_
- [`scrcpy-win64-v1.2.zip`][direct-win64].
_(SHA-256: 35ae3bcee51771e7c51b8a8be87aef2295c9f267606a7cf83ebb0a4d583ef536)_
[direct-windows-with-deps]: https://github.com/Genymobile/scrcpy/releases/download/v1.1/scrcpy-windows-with-deps-v1.1.zip
_(It's just a portable version including _dll_ copied from MSYS2.)_
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.2/scrcpy-win32-v1.2.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.2/scrcpy-win64-v1.2.zip
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).
@@ -245,10 +247,10 @@ Since the server binary, that will be pushed to the Android device, does not
depend on your system and architecture, you may want to use the prebuilt binary
instead:
- [`scrcpy-server-v1.1.jar`][direct-scrcpy-server].
_(SHA-256: 14826512bf38447ec94adf3b531676ce038d19e7e06757fb4e537882b17e77b3)_
- [`scrcpy-server-v1.2.jar`][direct-scrcpy-server].
_(SHA-256: cb39654ed2fda3d30ddff292806950ccc5c394375ea12b974f790c7f38f61f60)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.1/scrcpy-server-v1.1.jar
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.2/scrcpy-server-v1.2.jar
In that case, the build does not require Java or the Android SDK.
@@ -292,6 +294,12 @@ screen is smaller, or cannot decode such a high definition):
scrcpy -m 1024
```
The device screen may be cropped to mirror only part of the screen:
```bash
scrcpy -c 1224:1440:0:0 # 1224x1440 at offset (0,0)
```
If several devices are listed in `adb devices`, you must specify the _serial_:
```bash

View File

@@ -19,7 +19,7 @@ src = [
'src/tinyxpm.c',
]
if not meson.is_cross_build()
if not get_option('crossbuild_windows')
# native build
dependencies = [
@@ -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.2')
conf.set_quoted('SCRCPY_VERSION', '1.3')
# the prefix used during configuration (meson --prefix=PREFIX)
conf.set_quoted('PREFIX', get_option('prefix'))

View File

@@ -29,52 +29,58 @@ static struct point get_mouse_point(struct screen *screen) {
};
}
static void send_keycode(struct controller *controller, enum android_keycode keycode, const char *name) {
static const int ACTION_DOWN = 1;
static const int ACTION_UP = 1 << 1;
static void send_keycode(struct controller *controller, enum android_keycode keycode, int actions, const char *name) {
// send DOWN event
struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_KEYCODE;
control_event.keycode_event.action = AKEY_EVENT_ACTION_DOWN;
control_event.keycode_event.keycode = keycode;
control_event.keycode_event.metastate = 0;
if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot send %s (DOWN)", name);
return;
if (actions & ACTION_DOWN) {
control_event.keycode_event.action = AKEY_EVENT_ACTION_DOWN;
if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot send %s (DOWN)", name);
return;
}
}
// send UP event
control_event.keycode_event.action = AKEY_EVENT_ACTION_UP;
if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot send %s (UP)", name);
if (actions & ACTION_UP) {
control_event.keycode_event.action = AKEY_EVENT_ACTION_UP;
if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot send %s (UP)", name);
}
}
}
static inline void action_home(struct controller *controller) {
send_keycode(controller, AKEYCODE_HOME, "HOME");
static inline void action_home(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_HOME, actions, "HOME");
}
static inline void action_back(struct controller *controller) {
send_keycode(controller, AKEYCODE_BACK, "BACK");
static inline void action_back(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_BACK, actions, "BACK");
}
static inline void action_app_switch(struct controller *controller) {
send_keycode(controller, AKEYCODE_APP_SWITCH, "APP_SWITCH");
static inline void action_app_switch(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_APP_SWITCH, actions, "APP_SWITCH");
}
static inline void action_power(struct controller *controller) {
send_keycode(controller, AKEYCODE_POWER, "POWER");
static inline void action_power(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_POWER, actions, "POWER");
}
static inline void action_volume_up(struct controller *controller) {
send_keycode(controller, AKEYCODE_VOLUME_UP, "VOLUME_UP");
static inline void action_volume_up(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_VOLUME_UP, actions, "VOLUME_UP");
}
static inline void action_volume_down(struct controller *controller) {
send_keycode(controller, AKEYCODE_VOLUME_DOWN, "VOLUME_DOWN");
static inline void action_volume_down(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_VOLUME_DOWN, actions, "VOLUME_DOWN");
}
static inline void action_menu(struct controller *controller) {
send_keycode(controller, AKEYCODE_MENU, "MENU");
static inline void action_menu(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_MENU, actions, "MENU");
}
// turn the screen on if it was off, press BACK otherwise
@@ -141,57 +147,74 @@ void input_manager_process_key(struct input_manager *input_manager,
// capture all Ctrl events
if (ctrl) {
SDL_bool repeat = event->repeat;
// only consider keydown events, and ignore repeated events
if (repeat || event->type != SDL_KEYDOWN) {
return;
}
SDL_bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
if (shift) {
// currently, there is no shortcut implying SHIFT
// currently, there is no shortcut involving SHIFT
return;
}
SDL_Keycode keycode = event->keysym.sym;
int action = event->type == SDL_KEYDOWN ? ACTION_DOWN : ACTION_UP;
SDL_bool repeat = event->repeat;
switch (keycode) {
case SDLK_h:
action_home(input_manager->controller);
if (!repeat) {
action_home(input_manager->controller, action);
}
return;
case SDLK_b: // fall-through
case SDLK_BACKSPACE:
action_back(input_manager->controller);
if (!repeat) {
action_back(input_manager->controller, action);
}
return;
case SDLK_s:
action_app_switch(input_manager->controller);
if (!repeat) {
action_app_switch(input_manager->controller, action);
}
return;
case SDLK_m:
action_menu(input_manager->controller);
if (!repeat) {
action_menu(input_manager->controller, action);
}
return;
case SDLK_p:
action_power(input_manager->controller);
if (!repeat) {
action_power(input_manager->controller, action);
}
return;
case SDLK_DOWN:
action_volume_down(input_manager->controller);
// forward repeated events
action_volume_down(input_manager->controller, action);
return;
case SDLK_UP:
action_volume_up(input_manager->controller);
// forward repeated events
action_volume_up(input_manager->controller, action);
return;
case SDLK_v:
clipboard_paste(input_manager->controller);
if (!repeat && event->type == SDL_KEYDOWN) {
clipboard_paste(input_manager->controller);
}
return;
case SDLK_f:
screen_switch_fullscreen(input_manager->screen);
if (!repeat && event->type == SDL_KEYDOWN) {
screen_switch_fullscreen(input_manager->screen);
}
return;
case SDLK_x:
screen_resize_to_fit(input_manager->screen);
if (!repeat && event->type == SDL_KEYDOWN) {
screen_resize_to_fit(input_manager->screen);
}
return;
case SDLK_g:
screen_resize_to_pixel_perfect(input_manager->screen);
if (!repeat && event->type == SDL_KEYDOWN) {
screen_resize_to_pixel_perfect(input_manager->screen);
}
return;
case SDLK_i:
switch_fps_counter_state(input_manager->frames);
if (!repeat && event->type == SDL_KEYDOWN) {
switch_fps_counter_state(input_manager->frames);
}
return;
}
@@ -228,7 +251,7 @@ void input_manager_process_mouse_button(struct input_manager *input_manager,
return;
}
if (event->button == SDL_BUTTON_MIDDLE) {
action_home(input_manager->controller);
action_home(input_manager->controller, ACTION_DOWN | ACTION_UP);
return;
}
// double-click on black borders resize to fit the device screen

View File

@@ -79,6 +79,8 @@ SDL_bool installer_init(struct installer *installer, const char *serial) {
installer->initialized = SDL_FALSE;
installer->stopped = SDL_FALSE;
installer->current_process = PROCESS_NONE;
return SDL_TRUE;
}

View File

@@ -10,6 +10,7 @@
struct args {
const char *serial;
const char *crop;
SDL_bool help;
SDL_bool version;
SDL_bool show_touches;
@@ -29,6 +30,12 @@ static void usage(const char *arg0) {
" Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
" Default is %d.\n"
"\n"
" -c, --crop width:height:x:y\n"
" Crop the device screen on the server.\n"
" The values are expressed in the device natural orientation\n"
" (typically, portrait for a phone, landscape for a tablet).\n"
" Any --max-size value is computed on the cropped size.\n"
"\n"
" -h, --help\n"
" Print this help.\n"
"\n"
@@ -192,6 +199,7 @@ static SDL_bool parse_port(char *optarg, Uint16 *port) {
static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
static const struct option long_options[] = {
{"bit-rate", required_argument, NULL, 'b'},
{"crop", required_argument, NULL, 'c'},
{"help", no_argument, NULL, 'h'},
{"max-size", required_argument, NULL, 'm'},
{"port", required_argument, NULL, 'p'},
@@ -201,13 +209,16 @@ 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:hm:p:s:tv", long_options, NULL)) != -1) {
while ((c = getopt_long(argc, argv, "b:c:hm:p:s:tv", long_options, NULL)) != -1) {
switch (c) {
case 'b':
if (!parse_bit_rate(optarg, &args->bit_rate)) {
return SDL_FALSE;
}
break;
case 'c':
args->crop = optarg;
break;
case 'h':
args->help = SDL_TRUE;
break;
@@ -253,6 +264,7 @@ int main(int argc, char *argv[]) {
#endif
struct args args = {
.serial = NULL,
.crop = NULL,
.help = SDL_FALSE,
.version = SDL_FALSE,
.show_touches = SDL_FALSE,
@@ -274,7 +286,9 @@ int main(int argc, char *argv[]) {
return 0;
}
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
av_register_all();
#endif
if (avformat_network_init()) {
return 1;
@@ -286,6 +300,7 @@ int main(int argc, char *argv[]) {
struct scrcpy_options options = {
.serial = args.serial,
.crop = args.crop,
.port = args.port,
.max_size = args.max_size,
.bit_rate = args.bit_rate,

View File

@@ -126,7 +126,7 @@ static void wait_show_touches(process_t process) {
SDL_bool scrcpy(const struct scrcpy_options *options) {
if (!server_start(&server, options->serial, options->port,
options->max_size, options->bit_rate)) {
options->max_size, options->bit_rate, options->crop)) {
return SDL_FALSE;
}

View File

@@ -5,6 +5,7 @@
struct scrcpy_options {
const char *serial;
const char *crop;
Uint16 port;
Uint16 max_size;
Uint32 bit_rate;

View File

@@ -77,7 +77,8 @@ static SDL_bool disable_tunnel(struct server *server) {
}
static process_t execute_server(const char *serial,
Uint16 max_size, Uint32 bit_rate, SDL_bool tunnel_forward) {
Uint16 max_size, Uint32 bit_rate,
const char *crop, SDL_bool tunnel_forward) {
char max_size_string[6];
char bit_rate_string[11];
sprintf(max_size_string, "%"PRIu16, max_size);
@@ -91,6 +92,7 @@ static process_t execute_server(const char *serial,
max_size_string,
bit_rate_string,
tunnel_forward ? "true" : "false",
crop ? crop : "",
};
return adb_execute(serial, cmd, sizeof(cmd) / sizeof(cmd[0]));
}
@@ -147,7 +149,7 @@ void server_init(struct server *server) {
}
SDL_bool server_start(struct server *server, const char *serial, Uint16 local_port,
Uint16 max_size, Uint32 bit_rate) {
Uint16 max_size, Uint32 bit_rate, const char *crop) {
server->local_port = local_port;
if (serial) {
@@ -188,7 +190,8 @@ SDL_bool server_start(struct server *server, const char *serial, Uint16 local_po
}
// server will connect to our server socket
server->process = execute_server(serial, max_size, bit_rate, server->tunnel_forward);
server->process = execute_server(serial, max_size, bit_rate, crop,
server->tunnel_forward);
if (server->process == PROCESS_NONE) {
if (!server->tunnel_forward) {
close_socket(&server->server_socket);
@@ -207,7 +210,7 @@ socket_t server_connect_to(struct server *server) {
if (!server->tunnel_forward) {
server->device_socket = net_accept(server->server_socket);
} else {
Uint32 attempts = 50;
Uint32 attempts = 100;
Uint32 delay = 100; // ms
server->device_socket = connect_to_server(server->local_port, attempts, delay);
}

View File

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

View File

@@ -1,9 +1,11 @@
#include "command.h"
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "log.h"
pid_t cmd_execute(const char *path, const char *const argv[]) {
pid_t pid = fork();
@@ -20,6 +22,10 @@ pid_t cmd_execute(const char *path, const char *const argv[]) {
}
SDL_bool cmd_terminate(pid_t pid) {
if (pid <= 0) {
LOGC("Requested to kill %d, this is an error. Please report the bug.\n", (int) pid);
abort();
}
return kill(pid, SIGTERM) != -1;
}

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
option('build_app', type: 'boolean', value: true, description: 'Build the client')
option('build_server', type: 'boolean', value: true, description: 'Build the server')
option('crossbuild_windows', type: 'boolean', value: false, description: 'Build for Windows from Linux')
option('windows_noconsole', type: 'boolean', value: false, description: 'Disable console on Windows (pass -mwindows flag)')
option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server')
option('override_server_path', type: 'string', description: 'Hardcoded path to find the server at runtime')

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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