Compare commits

..

11 Commits

Author SHA1 Message Date
Romain Vimont
e8510a8cc3 Add links to AUR packages in README 2018-03-12 16:00:12 +01:00
Romain Vimont
f9a63ec272 Reverse horizontal scrolling behavior
The SDL mouse wheel event seems inconsistent between horizontal and
vertical scrolling.

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

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

Reverse the horizontal.

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

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

84
FAQ.md Normal file
View File

@@ -0,0 +1,84 @@
# Frequently Asked Questions
## Common issues
The application is very young, it is not unlikely that you encounter problems
with it.
Here are the common reported problems and their status.
### On Windows, I have no output in the console
When run in `cmd.exe`, the application does not print anything. Even `scrcpy
--help` have no output. We don't know why yet.
However, if you run the very same `scrcpy.exe` from
[MSYS2](https://www.msys2.org/) (`mingw64`), then it correctly prints output.
### On Windows, when I start the application, nothing happens
The previous problem does not help to get a clue about the cause.
The most common is your device not being detected by `adb`, or is unauthorized.
Check everything is ok by calling:
adb devices
Windows may need some [drivers] to detect your device.
[drivers]: https://developer.android.com/studio/run/oem-usb.html
If you still encounter problems, please see [issue 9].
[issue 9]: https://github.com/Genymobile/scrcpy/issues/9
### The application does not work over `adb connect`
If the device is connected using `adb connect`, then you'll get this error:
error: more than one device/emulator
ERROR: "adb reverse" returned with value 1
We plan to support it in future versions. See [issue 5].
[issue 5]: https://github.com/Genymobile/scrcpy/issues/5
### Mouse clicks do not work
On many LG devices, [mouse clicks do not work][issue 18]. We're [working][pr27]
on it.
[issue 18]: https://github.com/Genymobile/scrcpy/issues/18
[pr27]: https://github.com/Genymobile/scrcpy/pull/27
### I get a black screen for some applications like Silence
This is expected, they requested to protect the screen.
In [Silence], you can disable it in settings → Privacy → Screen security.
[silence]: https://f-droid.org/en/packages/org.smssecure.smssecure/
See [issue 36].
[issue 36]: https://github.com/Genymobile/scrcpy/issues/36
### Mouse clicks at wrong location
On MacOS, with HiDPI support and multiple screens, input location are wrongly
scaled. See [issue 15].
[issue 15]: https://github.com/Genymobile/scrcpy/issues/15
A workaround is to build with HiDPI support disabled:
```bash
meson x --buildtype release -Dhidpi_support=false
```
However, the video will be displayed at lower resolution.

176
README.md
View File

@@ -2,7 +2,7 @@
This application provides display and control of Android devices connected on
USB. It does not require any _root_ access. It works on _GNU/Linux_, _Windows_
and _Mac OS_.
and _MacOS_.
![screenshot](assets/screenshot-debian-600.jpg)
@@ -37,15 +37,44 @@ The client requires _FFmpeg_ and _LibSDL2_.
#### Linux
Install the required packages from your package manager (here, for Debian):
Install the required packages from your package manager.
# runtime dependencies
sudo apt install ffmpeg libsdl2-2.0.0
##### Debian/Ubuntu
# build dependencies
sudo apt install make gcc openjdk-8-jdk pkg-config meson zip \
libavcodec-dev libavformat-dev libavutil-dev \
libsdl2-dev
```bash
# runtime dependencies
sudo apt install ffmpeg libsdl2-2.0.0
# client build dependencies
sudo apt install make gcc pkg-config meson \
libavcodec-dev libavformat-dev libavutil-dev \
libsdl2-dev
# server build dependencies
sudo apt install openjdk-8-jdk
```
##### Fedora
```bash
# enable RPM fusion free
sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
# client build dependencies
sudo dnf install SDL2-devel ffms2-devel meson gcc make
# server build dependencies
sudo dnf install java
```
##### Arch Linux
Two [AUR] packages have been created by users:
- [`scrcpy`](https://aur.archlinux.org/packages/scrcpy/)
- [`scrcpy-prebuiltserver`](https://aur.archlinux.org/packages/scrcpy-prebuiltserver/)
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
#### Windows
@@ -65,22 +94,24 @@ project. From an MSYS2 terminal, install the required packages:
[MSYS2]: http://www.msys2.org/
# runtime dependencies
pacman -S mingw-w64-x86_64-SDL2 \
mingw-w64-x86_64-ffmpeg
```bash
# runtime dependencies
pacman -S mingw-w64-x86_64-SDL2 \
mingw-w64-x86_64-ffmpeg
# build dependencies
pacman -S mingw-w64-x86_64-make \
mingw-w64-x86_64-gcc \
mingw-w64-x86_64-pkg-config \
mingw-w64-x86_64-meson \
zip
# client build dependencies
pacman -S mingw-w64-x86_64-make \
mingw-w64-x86_64-gcc \
mingw-w64-x86_64-pkg-config \
mingw-w64-x86_64-meson
```
Java (>= 7) is not available in MSYS2, so if you plan to build the server,
install it manually and make it available from the `PATH`:
export PATH="$JAVA_HOME/bin:$PATH"
```bash
export PATH="$JAVA_HOME/bin:$PATH"
```
#### Mac OS
@@ -88,17 +119,20 @@ Use [Homebrew] to install the packages:
[Homebrew]: https://brew.sh/
# runtime dependencies
brew install sdl2 ffmpeg
```bash
# runtime dependencies
brew install sdl2 ffmpeg
# build dependencies
brew install gcc pkg-config meson zip
# client build dependencies
brew install gcc pkg-config meson
```
Java (>= 7) is not available in Homebrew, so if you plan to build the server,
install it manually and make it available from the `PATH`:
export PATH="$JAVA_HOME/bin:$PATH"
```bash
export PATH="$JAVA_HOME/bin:$PATH"
```
### Common steps
@@ -107,21 +141,29 @@ its directory. For example:
[Android SDK]: https://developer.android.com/studio/index.html
export ANDROID_HOME=~/android/sdk
```bash
export ANDROID_HOME=~/android/sdk
```
Then, build `scrcpy`:
meson x --buildtype release --strip -Db_lto=true
cd x
ninja
```bash
meson x --buildtype release --strip -Db_lto=true
cd x
ninja
```
You can test it from here:
ninja run
```bash
ninja run
```
Or you can install it on the system:
sudo ninja install # without sudo on Windows
```bash
sudo ninja install # without sudo on Windows
```
This installs two files:
@@ -147,12 +189,13 @@ In that case, the build does not require Java or the Android SDK.
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:
meson x --buildtype release --strip -Db_lto=true \
-Dprebuilt_server=/path/to/scrcpy-server.jar
cd x
ninja
sudo ninja install
```bash
meson x --buildtype release --strip -Db_lto=true \
-Dprebuilt_server=/path/to/scrcpy-server.jar
cd x
ninja
sudo ninja install
```
## Run
@@ -160,50 +203,60 @@ _At runtime, `adb` must be accessible from your `PATH`._
If everything is ok, just plug an Android device, and execute:
scrcpy
```bash
scrcpy
```
It accepts command-line arguments, listed by:
scrcpy --help
```bash
scrcpy --help
```
For example, to decrease video bitrate to 2Mbps (default is 8Mbps):
scrcpy -b 2M
```bash
scrcpy -b 2M
```
To limit the video dimensions (e.g. if the device is 2540×1440, but the host
screen is smaller, or cannot decode such a high definition):
scrcpy -m 1024
```bash
scrcpy -m 1024
```
If several devices are listed in `adb devices`, you must specify the _serial_:
scrcpy -s 0123456789abcdef
```bash
scrcpy -s 0123456789abcdef
```
To run without installing:
./run x [options]
```bash
./run x [options]
```
(where `x` is your build directory).
## Shortcuts
| Action | Shortcut |
| -------------------------------------- |:---------------------------- |
| switch fullscreen mode | `Ctrl`+`f` |
| resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` |
| resize window to remove black borders | `Ctrl`+`x` |
| click on `HOME` | `Ctrl`+`h` \| _Middle-click_ |
| click on `BACK` | `Ctrl`+`b` \| _Right-click¹_ |
| click on `APP_SWITCH` | `Ctrl`+`m` |
| click on `VOLUME_UP` | `Ctrl`+`+` |
| click on `VOLUME_DOWN` | `Ctrl`+`-` |
| click on `POWER` | `Ctrl`+`p` |
| turn screen on | _Right-click¹_ |
| paste computer clipboard to device | `Ctrl`+`v` |
| enable/disable FPS counter (on stdout) | `Ctrl`+`i` |
_¹Right-click turns the screen on if it was off, presses BACK otherwise._
| Action | Shortcut |
| ------------------------------------- | -------------:|
| switch fullscreen mode | `Ctrl`+`f` |
| resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` |
| resize window to remove black borders | `Ctrl`+`x` |
| click on `HOME` | `Ctrl`+`h` |
| click on `BACK` | `Ctrl`+`b` |
| click on `APP_SWITCH` | `Ctrl`+`m` |
| click on `VOLUME_UP` | `Ctrl`+`+` |
| click on `VOLUME_DOWN` | `Ctrl`+`-` |
| click on `POWER` | `Ctrl`+`p` |
| turn screen on | _Right-click_ |
| paste computer clipboard to device | `Ctrl`+`v` |
| enable/disable FPS counter (on stdout) | `Ctrl`+`i` |
## Why _scrcpy_?
@@ -216,6 +269,11 @@ A colleague challenged me to find a name as unpronounceable as [gnirehtet].
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
## Common issues
See the [FAQ](FAQ.md).
## Developers
Read the [developers page].

View File

@@ -45,13 +45,6 @@ process_t adb_forward(const char *serial, uint16_t local_port, const char *devic
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
process_t adb_forward_remove(const char *serial, uint16_t local_port) {
char local[4 + 5 + 1]; // tcp:PORT
sprintf(local, "tcp:%" PRIu16, local_port);
const char *const adb_cmd[] = {"forward", "--remove", local};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
process_t adb_reverse(const char *serial, const char *device_socket_name, uint16_t local_port) {
char local[4 + 5 + 1]; // tcp:PORT
char remote[108 + 14 + 1]; // localabstract:NAME

View File

@@ -39,7 +39,6 @@ SDL_bool cmd_simple_wait(process_t pid, exit_code_t *exit_code);
process_t adb_execute(const char *serial, const char *const adb_cmd[], int len);
process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name);
process_t adb_forward_remove(const char *serial, uint16_t local_port);
process_t adb_reverse(const char *serial, const char *device_socket_name, uint16_t local_port);
process_t adb_reverse_remove(const char *serial, const char *device_socket_name);
process_t adb_push(const char *serial, const char *local, const char *remote);

View File

@@ -39,9 +39,9 @@ int control_event_serialize(const struct control_event *event, unsigned char *bu
// injecting a text takes time, so limit the text length
len = TEXT_MAX_LENGTH;
}
write16(&buf[1], (Uint16) len);
memcpy(&buf[3], event->text_event.text, len);
return 3 + len;
buf[1] = (Uint8) len;
memcpy(&buf[2], event->text_event.text, len);
return 2 + len;
}
case CONTROL_EVENT_TYPE_MOUSE:
buf[1] = event->mouse_event.action;

View File

@@ -9,8 +9,8 @@
#include "common.h"
#define CONTROL_EVENT_QUEUE_SIZE 64
#define TEXT_MAX_LENGTH 300
#define SERIALIZED_EVENT_MAX_SIZE 3 + TEXT_MAX_LENGTH
#define SERIALIZED_EVENT_MAX_SIZE 33
#define TEXT_MAX_LENGTH 256
enum control_event_type {
CONTROL_EVENT_TYPE_KEYCODE,
@@ -20,7 +20,7 @@ enum control_event_type {
CONTROL_EVENT_TYPE_COMMAND,
};
#define CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON 0
#define CONTROL_EVENT_COMMAND_SCREEN_ON 0
struct control_event {
enum control_event_type type;

View File

@@ -172,7 +172,10 @@ SDL_bool mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from,
to->scroll_event.position = position;
int mul = from->direction == SDL_MOUSEWHEEL_NORMAL ? 1 : -1;
to->scroll_event.hscroll = mul * from->x;
// SDL behavior seems inconsistent between horizontal and vertical scrolling
// so reverse the horizontal
// <https://wiki.libsdl.org/SDL_MouseWheelEvent#Remarks>
to->scroll_event.hscroll = -mul * from->x;
to->scroll_event.vscroll = mul * from->y;
return SDL_TRUE;

View File

@@ -78,11 +78,10 @@ static inline void action_volume_down(struct controller *controller) {
send_keycode(controller, AKEYCODE_VOLUME_DOWN, "VOLUME_DOWN");
}
// turn the screen on if it was off, press BACK otherwise
static void press_back_or_turn_screen_on(struct controller *controller) {
static void turn_screen_on(struct controller *controller) {
struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_COMMAND;
control_event.command_event.action = CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON;
control_event.command_event.action = CONTROL_EVENT_COMMAND_SCREEN_ON;
if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot turn screen on");
@@ -226,15 +225,9 @@ void input_manager_process_mouse_motion(struct input_manager *input_manager,
void input_manager_process_mouse_button(struct input_manager *input_manager,
const SDL_MouseButtonEvent *event) {
if (event->type == SDL_MOUSEBUTTONDOWN) {
if (event->button == SDL_BUTTON_RIGHT) {
press_back_or_turn_screen_on(input_manager->controller);
return;
}
if (event->button == SDL_BUTTON_MIDDLE) {
action_home(input_manager->controller);
return;
}
if (event->button == SDL_BUTTON_RIGHT && event->type == SDL_MOUSEBUTTONDOWN) {
turn_screen_on(input_manager->controller);
return;
};
struct control_event control_event;
if (mouse_button_from_sdl_to_android(event, input_manager->screen->frame_size, &control_event)) {

View File

@@ -61,12 +61,10 @@ static void usage(const char *arg0) {
"\n"
" Ctrl+h\n"
" Home\n"
" Middle-click\n"
" click on HOME\n"
"\n"
" Ctrl+b\n"
" Ctrl+Backspace\n"
" Right-click (when screen is on)\n"
" click on BACK\n"
"\n"
" Ctrl+m\n"
@@ -81,7 +79,7 @@ static void usage(const char *arg0) {
" Ctrl+p\n"
" click on POWER (turn screen on/off)\n"
"\n"
" Right-click (when screen is off)\n"
" Right-click\n"
" turn screen on\n"
"\n"
" Ctrl+v\n"

View File

@@ -18,26 +18,6 @@
typedef struct in_addr IN_ADDR;
#endif
socket_t net_connect(Uint32 addr, Uint16 port) {
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) {
perror("socket");
return INVALID_SOCKET;
}
SOCKADDR_IN sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(addr);
sin.sin_port = htons(port);
if (connect(sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
perror("connect");
return INVALID_SOCKET;
}
return sock;
}
socket_t net_listen(Uint32 addr, Uint16 port, int backlog) {
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) {

View File

@@ -21,7 +21,6 @@
SDL_bool net_init(void);
void net_cleanup(void);
socket_t net_connect(Uint32 addr, Uint16 port);
socket_t net_listen(Uint32 addr, Uint16 port, int backlog);
socket_t net_accept(socket_t server_socket);

View File

@@ -103,9 +103,9 @@ SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 b
// managed by the event loop. This blocking call blocks the event loop, so
// timeout the connection not to block indefinitely in case of SIGTERM.
#define SERVER_CONNECT_TIMEOUT_MS 2000
socket_t device_socket = server_connect_to(&server, SERVER_CONNECT_TIMEOUT_MS);
socket_t device_socket = server_connect_to(&server, serial, SERVER_CONNECT_TIMEOUT_MS);
if (device_socket == INVALID_SOCKET) {
server_stop(&server);
server_stop(&server, serial);
ret = SDL_FALSE;
goto finally_destroy_server;
}
@@ -117,13 +117,13 @@ SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 b
// therefore, we transmit the screen size before the video stream, to be able
// to init the window immediately
if (!device_read_info(device_socket, device_name, &frame_size)) {
server_stop(&server);
server_stop(&server, serial);
ret = SDL_FALSE;
goto finally_destroy_server;
}
if (!frames_init(&frames)) {
server_stop(&server);
server_stop(&server, serial);
ret = SDL_FALSE;
goto finally_destroy_server;
}
@@ -134,7 +134,7 @@ SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 b
// start the decoder
if (!decoder_start(&decoder)) {
ret = SDL_FALSE;
server_stop(&server);
server_stop(&server, serial);
goto finally_destroy_frames;
}
@@ -165,7 +165,7 @@ finally_destroy_controller:
finally_stop_decoder:
decoder_stop(&decoder);
// stop the server before decoder_join() to wake up the decoder
server_stop(&server);
server_stop(&server, serial);
decoder_join(&decoder);
finally_destroy_frames:
frames_destroy(&frames);

View File

@@ -18,8 +18,8 @@ SDL_bool sdl_init_and_configure(void) {
atexit(SDL_Quit);
// Use the best available scale quality
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2")) {
// Bilinear resizing
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) {
LOGW("Could not enable bilinear filtering");
}

View File

@@ -4,7 +4,6 @@
#include <inttypes.h>
#include <stdint.h>
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_timer.h>
#include "config.h"
#include "log.h"
@@ -38,45 +37,17 @@ static SDL_bool remove_server(const char *serial) {
return process_check_success(process, "adb shell rm");
}
static SDL_bool enable_tunnel_reverse(const char *serial, Uint16 local_port) {
static SDL_bool enable_tunnel(const char *serial, Uint16 local_port) {
process_t process = adb_reverse(serial, SOCKET_NAME, local_port);
return process_check_success(process, "adb reverse");
}
static SDL_bool disable_tunnel_reverse(const char *serial) {
static SDL_bool disable_tunnel(const char *serial) {
process_t process = adb_reverse_remove(serial, SOCKET_NAME);
return process_check_success(process, "adb reverse --remove");
}
static SDL_bool enable_tunnel_forward(const char *serial, Uint16 local_port) {
process_t process = adb_forward(serial, local_port, SOCKET_NAME);
return process_check_success(process, "adb forward");
}
static SDL_bool disable_tunnel_forward(const char *serial, Uint16 local_port) {
process_t process = adb_forward_remove(serial, local_port);
return process_check_success(process, "adb forward --remove");
}
static SDL_bool enable_tunnel(struct server *server) {
if (enable_tunnel_reverse(server->serial, server->local_port)) {
return SDL_TRUE;
}
LOGW("'adb reverse' failed, fallback to 'adb forward'");
server->tunnel_forward = SDL_TRUE;
return enable_tunnel_forward(server->serial, server->local_port);
}
static SDL_bool disable_tunnel(struct server *server) {
if (server->tunnel_forward) {
return disable_tunnel_forward(server->serial, server->local_port);
}
return disable_tunnel_reverse(server->serial);
}
static process_t execute_server(const char *serial,
Uint16 max_size, Uint32 bit_rate, SDL_bool tunnel_forward) {
static process_t execute_server(const char *serial, Uint16 max_size, Uint32 bit_rate) {
char max_size_string[6];
char bit_rate_string[11];
sprintf(max_size_string, "%"PRIu16, max_size);
@@ -89,48 +60,15 @@ static process_t execute_server(const char *serial,
"com.genymobile.scrcpy.Server",
max_size_string,
bit_rate_string,
tunnel_forward ? "true" : "false",
};
return adb_execute(serial, cmd, sizeof(cmd) / sizeof(cmd[0]));
}
#define IPV4_LOCALHOST 0x7F000001
static socket_t listen_on_port(Uint16 port) {
#define IPV4_LOCALHOST 0x7F000001
return net_listen(IPV4_LOCALHOST, port, 1);
}
static socket_t connect_and_read_byte(Uint16 port) {
socket_t socket = net_connect(IPV4_LOCALHOST, port);
if (socket == INVALID_SOCKET) {
return INVALID_SOCKET;
}
char byte;
// the connection may succeed even if the server behind the "adb tunnel"
// is not listening, so read one byte to detect a working connection
if (net_recv_all(socket, &byte, 1) != 1) {
// the server is not listening yet behind the adb tunnel
return INVALID_SOCKET;
}
return socket;
}
static socket_t connect_to_server(Uint16 port, Uint32 attempts, Uint32 delay) {
do {
LOGD("Remaining connection attempts: %d", (int) attempts);
socket_t socket = connect_and_read_byte(port);
if (socket != INVALID_SOCKET) {
// it worked!
return socket;
}
if (attempts) {
SDL_Delay(delay);
}
} while (--attempts > 0);
return INVALID_SOCKET;
}
static void close_socket(socket_t *socket) {
SDL_assert(*socket != INVALID_SOCKET);
net_shutdown(*socket, SHUT_RDWR);
@@ -147,84 +85,63 @@ 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) {
server->local_port = local_port;
if (serial) {
server->serial = SDL_strdup(serial);
}
if (!push_server(serial)) {
return SDL_FALSE;
}
server->server_copied_to_device = SDL_TRUE;
if (!enable_tunnel(server)) {
if (!enable_tunnel(serial, local_port)) {
return SDL_FALSE;
}
// if "adb reverse" does not work (e.g. over "adb connect"), it fallbacks to
// "adb forward", so the app socket is the client
if (!server->tunnel_forward) {
// At the application level, the device part is "the server" because it
// serves video stream and control. However, at the network level, the
// client listens and the server connects to the client. That way, the
// client can listen before starting the server app, so there is no need to
// try to connect until the server socket is listening on the device.
// At the application level, the device part is "the server" because it
// serves video stream and control. However, at the network level, the
// client listens and the server connects to the client. That way, the
// client can listen before starting the server app, so there is no need to
// try to connect until the server socket is listening on the device.
server->server_socket = listen_on_port(local_port);
if (server->server_socket == INVALID_SOCKET) {
LOGE("Could not listen on port %" PRIu16, local_port);
disable_tunnel(server);
return SDL_FALSE;
}
server->server_socket = listen_on_port(local_port);
if (server->server_socket == INVALID_SOCKET) {
LOGE("Could not listen on port %" PRIu16, local_port);
disable_tunnel(serial);
return SDL_FALSE;
}
// server will connect to our server socket
server->process = execute_server(serial, max_size, bit_rate, server->tunnel_forward);
server->process = execute_server(serial, max_size, bit_rate);
if (server->process == PROCESS_NONE) {
if (!server->tunnel_forward) {
close_socket(&server->server_socket);
}
disable_tunnel(server);
close_socket(&server->server_socket);
disable_tunnel(serial);
return SDL_FALSE;
}
server->tunnel_enabled = SDL_TRUE;
server->adb_reverse_enabled = SDL_TRUE;
return SDL_TRUE;
}
socket_t server_connect_to(struct server *server, Uint32 timeout_ms) {
if (!server->tunnel_forward) {
server->device_socket = net_accept(server->server_socket);
} else {
Uint32 attempts = 10;
Uint32 delay = 100; // ms
server->device_socket = connect_to_server(server->local_port, attempts, delay);
}
socket_t server_connect_to(struct server *server, const char *serial, Uint32 timeout_ms) {
server->device_socket = net_accept(server->server_socket);
if (server->device_socket == INVALID_SOCKET) {
return INVALID_SOCKET;
}
if (!server->tunnel_forward) {
// we don't need the server socket anymore
close_socket(&server->server_socket);
}
// we don't need the server socket anymore
close_socket(&server->server_socket);
// the server is started, we can clean up the jar from the temporary folder
remove_server(server->serial); // ignore failure
remove_server(serial); // ignore failure
server->server_copied_to_device = SDL_FALSE;
// we don't need the adb tunnel anymore
disable_tunnel(server); // ignore failure
server->tunnel_enabled = SDL_FALSE;
disable_tunnel(serial); // ignore failure
server->adb_reverse_enabled = SDL_FALSE;
return server->device_socket;
}
void server_stop(struct server *server) {
void server_stop(struct server *server, const char *serial) {
SDL_assert(server->process != PROCESS_NONE);
if (!cmd_terminate(server->process)) {
@@ -234,13 +151,13 @@ void server_stop(struct server *server) {
cmd_simple_wait(server->process, NULL); // ignore exit code
LOGD("Server terminated");
if (server->tunnel_enabled) {
if (server->adb_reverse_enabled) {
// ignore failure
disable_tunnel(server);
disable_tunnel(serial);
}
if (server->server_copied_to_device) {
remove_server(server->serial); // ignore failure
remove_server(serial); // ignore failure
}
}
@@ -251,5 +168,4 @@ void server_destroy(struct server *server) {
if (server->device_socket != INVALID_SOCKET) {
close_socket(&server->device_socket);
}
SDL_free((void *) server->serial);
}

View File

@@ -5,24 +5,18 @@
#include "net.h"
struct server {
const char *serial;
process_t process;
socket_t server_socket; // only used if !tunnel_forward
socket_t server_socket;
socket_t device_socket;
Uint16 local_port;
SDL_bool tunnel_enabled;
SDL_bool tunnel_forward; // use "adb forward" instead of "adb reverse"
SDL_bool adb_reverse_enabled;
SDL_bool server_copied_to_device;
};
#define SERVER_INITIALIZER { \
.serial = NULL, \
.process = PROCESS_NONE, \
.server_socket = INVALID_SOCKET, \
.device_socket = INVALID_SOCKET, \
.local_port = 0, \
.tunnel_enabled = SDL_FALSE, \
.tunnel_forward = SDL_FALSE, \
.adb_reverse_enabled = SDL_FALSE, \
.server_copied_to_device = SDL_FALSE, \
}
@@ -34,10 +28,10 @@ SDL_bool server_start(struct server *server, const char *serial, Uint16 local_po
Uint16 max_size, Uint32 bit_rate);
// block until the communication with the server is established
socket_t server_connect_to(struct server *server, Uint32 timeout_ms);
socket_t server_connect_to(struct server *server, const char *serial, Uint32 timeout_ms);
// disconnect and kill the server process
void server_stop(struct server *server);
void server_stop(struct server *server, const char *serial);
// close and release sockets
void server_destroy(struct server *server);

View File

@@ -35,36 +35,16 @@ static void test_serialize_text_event() {
unsigned char buf[SERIALIZED_EVENT_MAX_SIZE];
int size = control_event_serialize(&event, buf);
assert(size == 16);
assert(size == 15);
const unsigned char expected[] = {
0x01, // CONTROL_EVENT_TYPE_KEYCODE
0x00, 0x0d, // text length
0x0d, // text length
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_long_text_event() {
struct control_event event;
event.type = CONTROL_EVENT_TYPE_TEXT;
char text[TEXT_MAX_LENGTH];
memset(text, 'a', sizeof(text));
event.text_event.text = text;
unsigned char buf[SERIALIZED_EVENT_MAX_SIZE];
int size = control_event_serialize(&event, buf);
assert(size == 3 + sizeof(text));
unsigned char expected[3 + TEXT_MAX_LENGTH];
expected[0] = 0x01; // CONTROL_EVENT_TYPE_KEYCODE
expected[1] = 0x01;
expected[2] = 0x2c; // text length (16 bits)
memset(&expected[3], 'a', TEXT_MAX_LENGTH);
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_mouse_event() {
struct control_event event = {
.type = CONTROL_EVENT_TYPE_MOUSE,
@@ -134,7 +114,6 @@ static void test_serialize_scroll_event() {
int main() {
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

@@ -11,7 +11,7 @@ public final class ControlEvent {
public static final int TYPE_SCROLL = 3;
public static final int TYPE_COMMAND = 4;
public static final int COMMAND_BACK_OR_SCREEN_ON = 0;
public static final int COMMAND_SCREEN_ON = 0;
private int type;
private String text;

View File

@@ -13,8 +13,8 @@ public class ControlEventReader {
private static final int SCROLL_PAYLOAD_LENGTH = 16;
private static final int COMMAND_PAYLOAD_LENGTH = 1;
public static final int TEXT_MAX_LENGTH = 300;
private static final int RAW_BUFFER_SIZE = 1024;
private static final int TEXT_MAX_LENGTH = 256;
private static final int RAW_BUFFER_SIZE = 128;
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
@@ -94,7 +94,7 @@ public class ControlEventReader {
if (buffer.remaining() < 1) {
return null;
}
int len = toUnsigned(buffer.getShort());
int len = toUnsigned(buffer.get());
if (buffer.remaining() < len) {
return null;
}

View File

@@ -1,6 +1,5 @@
package com.genymobile.scrcpy;
import android.net.LocalServerSocket;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
@@ -34,20 +33,8 @@ public final class DesktopConnection implements Closeable {
return localSocket;
}
private static LocalSocket listenAndAccept(String abstractName) throws IOException {
LocalServerSocket localServerSocket = new LocalServerSocket(abstractName);
return localServerSocket.accept();
}
public static DesktopConnection open(Device device, boolean tunnelForward) throws IOException {
LocalSocket socket;
if (tunnelForward) {
socket = listenAndAccept(SOCKET_NAME);
// send one byte so the client may read() to detect a connection error
socket.getOutputStream().write(0);
} else {
socket = connect(SOCKET_NAME);
}
public static DesktopConnection open(Device device) throws IOException {
LocalSocket socket = connect(SOCKET_NAME);
DesktopConnection connection = new DesktopConnection(socket);
Size videoSize = device.getScreenInfo().getVideoSize();

View File

@@ -50,8 +50,8 @@ public final class Device {
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;
int w = deviceSize.getWidth();
int h = deviceSize.getHeight();
if (maxSize > 0) {
if (BuildConfig.DEBUG && maxSize % 8 != 0) {
throw new AssertionError("Max size must be a multiple of 8");

View File

@@ -39,6 +39,10 @@ public class EventController {
coords.orientation = 0;
coords.pressure = 1;
coords.size = 1;
coords.toolMajor = 1;
coords.toolMinor = 1;
coords.touchMajor = 1;
coords.touchMinor = 1;
}
private void setPointerCoords(Point point) {
@@ -163,15 +167,10 @@ public class EventController {
return device.isScreenOn() || injectKeycode(KeyEvent.KEYCODE_POWER);
}
private boolean pressBackOrTurnScreenOn() {
int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER;
return injectKeycode(keycode);
}
private boolean executeCommand(int action) {
switch (action) {
case ControlEvent.COMMAND_BACK_OR_SCREEN_ON:
return pressBackOrTurnScreenOn();
case ControlEvent.COMMAND_SCREEN_ON:
return turnScreenOn();
default:
Ln.w("Unsupported command: " + action);
}

View File

@@ -3,7 +3,6 @@ package com.genymobile.scrcpy;
public class Options {
private int maxSize;
private int bitRate;
private boolean tunnelForward;
public int getMaxSize() {
return maxSize;
@@ -20,12 +19,4 @@ public class Options {
public void setBitRate(int bitRate) {
this.bitRate = bitRate;
}
public boolean isTunnelForward() {
return tunnelForward;
}
public void setTunnelForward(boolean tunnelForward) {
this.tunnelForward = tunnelForward;
}
}

View File

@@ -10,8 +10,7 @@ public final class Server {
private static void scrcpy(Options options) throws IOException {
final Device device = new Device(options);
boolean tunnelForward = options.isTunnelForward();
try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) {
try (DesktopConnection connection = DesktopConnection.open(device)) {
ScreenEncoder screenEncoder = new ScreenEncoder(options.getBitRate());
// asynchronous
@@ -56,13 +55,6 @@ public final class Server {
int bitRate = Integer.parseInt(args[1]);
options.setBitRate(bitRate);
if (args.length < 3) {
return options;
}
// use "adb forward" instead of "adb tunnel"? (so the server must listen)
boolean tunnelForward = Boolean.parseBoolean(args[2]);
options.setTunnelForward(tunnelForward);
return options;
}

View File

@@ -3,15 +3,14 @@ package com.genymobile.scrcpy;
import android.view.KeyEvent;
import android.view.MotionEvent;
import org.junit.Assert;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import org.junit.Assert;
import org.junit.Test;
public class ControlEventReaderTest {
@@ -44,8 +43,8 @@ public class ControlEventReaderTest {
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlEvent.TYPE_TEXT);
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
dos.writeShort(text.length);
dos.write(text);
dos.writeByte(text.length);
dos.write("testé".getBytes(StandardCharsets.UTF_8));
byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
@@ -55,26 +54,6 @@ public class ControlEventReaderTest {
Assert.assertEquals("testé", event.getText());
}
@Test
public void testParseLongTextEvent() throws IOException {
ControlEventReader reader = new ControlEventReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlEvent.TYPE_TEXT);
byte[] text = new byte[ControlEventReader.TEXT_MAX_LENGTH];
Arrays.fill(text, (byte) 'a');
dos.writeShort(text.length);
dos.write(text);
byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
ControlEvent event = reader.next();
Assert.assertEquals(ControlEvent.TYPE_TEXT, event.getType());
Assert.assertEquals(new String(text, StandardCharsets.US_ASCII), event.getText());
}
@Test
public void testParseMouseEvent() throws IOException {
ControlEventReader reader = new ControlEventReader();