Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9648b7a387 | ||
|
|
149702753f | ||
|
|
83d48267a7 | ||
|
|
db6252e52b | ||
|
|
229eeb24a2 | ||
|
|
f9786e5034 | ||
|
|
78a320a763 | ||
|
|
7d5845196e | ||
|
|
31bd95022b | ||
|
|
4687a0ebac | ||
|
|
6965d051ae | ||
|
|
71df3175bd |
6
BUILD.md
6
BUILD.md
@@ -233,10 +233,10 @@ You can then [run](README.md#run) _scrcpy_.
|
|||||||
|
|
||||||
## Prebuilt server
|
## Prebuilt server
|
||||||
|
|
||||||
- [`scrcpy-server-v1.11`][direct-scrcpy-server]
|
- [`scrcpy-server-v1.12.1`][direct-scrcpy-server]
|
||||||
_(SHA-256: ff3a454012e91d9185cfe8ca7691cea16c43a7dcc08e92fa47ab9f0ea675abd1)_
|
_(SHA-256: 63e569c8a1d0c1df31d48c4214871c479a601782945fed50c1e61167d78266ea)_
|
||||||
|
|
||||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.11/scrcpy-server-v1.11
|
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.12.1/scrcpy-server-v1.12.1
|
||||||
|
|
||||||
Download the prebuilt server somewhere, and specify its path during the Meson
|
Download the prebuilt server somewhere, and specify its path during the Meson
|
||||||
configuration:
|
configuration:
|
||||||
|
|||||||
18
README.md
18
README.md
@@ -1,4 +1,4 @@
|
|||||||
# scrcpy (v1.11)
|
# scrcpy (v1.12.1)
|
||||||
|
|
||||||
This application provides display and control of Android devices connected on
|
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.
|
USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access.
|
||||||
@@ -62,13 +62,13 @@ For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
|
|||||||
For Windows, for simplicity, prebuilt archives with all the dependencies
|
For Windows, for simplicity, prebuilt archives with all the dependencies
|
||||||
(including `adb`) are available:
|
(including `adb`) are available:
|
||||||
|
|
||||||
- [`scrcpy-win32-v1.11.zip`][direct-win32]
|
- [`scrcpy-win32-v1.12.1.zip`][direct-win32]
|
||||||
_(SHA-256: f25ed46e6f3e81e0ff9b9b4df7fe1a4bbd13f8396b7391be0a488b64c675b41e)_
|
_(SHA-256: 0f4b3b063536b50a2df05dc42c760f9cc0093a9a26dbdf02d8232c74dab43480)_
|
||||||
- [`scrcpy-win64-v1.11.zip`][direct-win64]
|
- [`scrcpy-win64-v1.12.1.zip`][direct-win64]
|
||||||
_(SHA-256: 3802c9ea0307d437947ff150ec65e53990b0beaacd0c8d0bed19c7650ce141bd)_
|
_(SHA-256: 57d34b6d16cfd9fe169bc37c4df58ebd256d05c1ea3febc63d9cb0a027ab47c9)_
|
||||||
|
|
||||||
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.11/scrcpy-win32-v1.11.zip
|
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.12.1/scrcpy-win32-v1.12.1.zip
|
||||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.11/scrcpy-win64-v1.11.zip
|
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.12.1/scrcpy-win64-v1.12.1.zip
|
||||||
|
|
||||||
You can also [build the app manually][BUILD].
|
You can also [build the app manually][BUILD].
|
||||||
|
|
||||||
@@ -137,12 +137,14 @@ scrcpy -b 2M # short version
|
|||||||
|
|
||||||
#### Limit frame rate
|
#### Limit frame rate
|
||||||
|
|
||||||
On devices with Android >= 10, the capture frame rate can be limited:
|
The capture frame rate can be limited:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --max-fps 15
|
scrcpy --max-fps 15
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This is officially supported since Android 10, but may work on earlier versions.
|
||||||
|
|
||||||
#### Crop
|
#### Crop
|
||||||
|
|
||||||
The device screen may be cropped to mirror only part of the screen.
|
The device screen may be cropped to mirror only part of the screen.
|
||||||
|
|||||||
@@ -76,11 +76,9 @@ cc = meson.get_compiler('c')
|
|||||||
|
|
||||||
if host_machine.system() == 'windows'
|
if host_machine.system() == 'windows'
|
||||||
src += [ 'src/sys/win/command.c' ]
|
src += [ 'src/sys/win/command.c' ]
|
||||||
src += [ 'src/sys/win/net.c' ]
|
|
||||||
dependencies += cc.find_library('ws2_32')
|
dependencies += cc.find_library('ws2_32')
|
||||||
else
|
else
|
||||||
src += [ 'src/sys/unix/command.c' ]
|
src += [ 'src/sys/unix/command.c' ]
|
||||||
src += [ 'src/sys/unix/net.c' ]
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
conf = configuration_data()
|
conf = configuration_data()
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ Print this help.
|
|||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-max\-fps " value
|
.BI "\-\-max\-fps " value
|
||||||
Limit the framerate of screen capture (only supported on devices with Android >= 10).
|
Limit the framerate of screen capture (officially supported since Android 10, but may work on earlier versions).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-m, \-\-max\-size " value
|
.BI "\-m, \-\-max\-size " value
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" Print this help.\n"
|
" Print this help.\n"
|
||||||
"\n"
|
"\n"
|
||||||
" --max-fps value\n"
|
" --max-fps value\n"
|
||||||
" Limit the frame rate of screen capture (only supported on\n"
|
" Limit the frame rate of screen capture (officially supported\n"
|
||||||
" devices with Android >= 10).\n"
|
" since Android 10, but may work on earlier versions).\n"
|
||||||
"\n"
|
"\n"
|
||||||
" -m, --max-size value\n"
|
" -m, --max-size value\n"
|
||||||
" Limit both the width and height of the video to value. The\n"
|
" Limit both the width and height of the video to value. The\n"
|
||||||
@@ -224,7 +224,9 @@ parse_integer_arg(const char *s, long *out, bool accept_suffix, long min,
|
|||||||
static bool
|
static bool
|
||||||
parse_bit_rate(const char *s, uint32_t *bit_rate) {
|
parse_bit_rate(const char *s, uint32_t *bit_rate) {
|
||||||
long value;
|
long value;
|
||||||
bool ok = parse_integer_arg(s, &value, true, 0, 0xFFFFFFFF, "bit-rate");
|
// long may be 32 bits (it is the case on mingw), so do not use more than
|
||||||
|
// 31 bits (long is signed)
|
||||||
|
bool ok = parse_integer_arg(s, &value, true, 0, 0x7FFFFFFF, "bit-rate");
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,6 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
@@ -205,14 +202,3 @@ process_check_success(process_t proc, const char *name) {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
|
||||||
is_regular_file(const char *path) {
|
|
||||||
struct stat path_stat;
|
|
||||||
int r = stat(path, &path_stat);
|
|
||||||
if (r) {
|
|
||||||
perror("stat");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return S_ISREG(path_stat.st_mode);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
|||||||
buf[1] = msg->set_screen_power_mode.mode;
|
buf[1] = msg->set_screen_power_mode.mode;
|
||||||
return 2;
|
return 2;
|
||||||
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
||||||
|
buf[1] = msg->back_or_screen_on.action;
|
||||||
|
return 2;
|
||||||
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
case CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
case CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
||||||
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
||||||
|
|||||||
@@ -66,6 +66,9 @@ struct control_msg {
|
|||||||
struct {
|
struct {
|
||||||
enum screen_power_mode mode;
|
enum screen_power_mode mode;
|
||||||
} set_screen_power_mode;
|
} set_screen_power_mode;
|
||||||
|
struct {
|
||||||
|
enum android_keyevent_action action;
|
||||||
|
} back_or_screen_on;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -99,9 +99,14 @@ action_menu(struct controller *controller, int actions) {
|
|||||||
|
|
||||||
// turn the screen on if it was off, press BACK otherwise
|
// turn the screen on if it was off, press BACK otherwise
|
||||||
static void
|
static void
|
||||||
press_back_or_turn_screen_on(struct controller *controller) {
|
press_back_or_turn_screen_on(struct controller *controller, int action) {
|
||||||
|
assert(action == ACTION_DOWN || action == ACTION_UP);
|
||||||
|
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
msg.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON;
|
msg.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON;
|
||||||
|
msg.back_or_screen_on.action = action == ACTION_DOWN
|
||||||
|
? AKEY_EVENT_ACTION_DOWN
|
||||||
|
: AKEY_EVENT_ACTION_UP;
|
||||||
|
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
LOGW("Could not request 'press back or turn screen on'");
|
LOGW("Could not request 'press back or turn screen on'");
|
||||||
@@ -521,31 +526,36 @@ input_manager_process_mouse_button(struct input_manager *im,
|
|||||||
// simulated from touch events, so it's a duplicate
|
// simulated from touch events, so it's a duplicate
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event->type == SDL_MOUSEBUTTONDOWN) {
|
|
||||||
if (control && event->button == SDL_BUTTON_RIGHT) {
|
// double-click on black borders resize to fit the device screen
|
||||||
press_back_or_turn_screen_on(im->controller);
|
if (event->type == SDL_MOUSEBUTTONDOWN
|
||||||
|
&& event->button == SDL_BUTTON_LEFT
|
||||||
|
&& event->clicks == 2) {
|
||||||
|
bool outside = is_outside_device_screen(im, event->x, event->y);
|
||||||
|
if (outside) {
|
||||||
|
screen_resize_to_fit(im->screen);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (control && event->button == SDL_BUTTON_MIDDLE) {
|
|
||||||
action_home(im->controller, ACTION_DOWN | ACTION_UP);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// double-click on black borders resize to fit the device screen
|
|
||||||
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
|
|
||||||
bool outside =
|
|
||||||
is_outside_device_screen(im, event->x, event->y);
|
|
||||||
if (outside) {
|
|
||||||
screen_resize_to_fit(im->screen);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// otherwise, send the click event to the device
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!control) {
|
if (!control) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event->button == SDL_BUTTON_MIDDLE) {
|
||||||
|
int action = event->type == SDL_MOUSEBUTTONDOWN ? ACTION_DOWN
|
||||||
|
: ACTION_UP;
|
||||||
|
action_home(im->controller, action);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event->button == SDL_BUTTON_RIGHT) {
|
||||||
|
int action = event->type == SDL_MOUSEBUTTONDOWN ? ACTION_DOWN
|
||||||
|
: ACTION_UP;
|
||||||
|
press_back_or_turn_screen_on(im->controller, action);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
if (convert_mouse_button(event, im->screen, &msg)) {
|
if (convert_mouse_button(event, im->screen, &msg)) {
|
||||||
if (!controller_push_msg(im->controller, &msg)) {
|
if (!controller_push_msg(im->controller, &msg)) {
|
||||||
|
|||||||
@@ -6,11 +6,13 @@
|
|||||||
#include <libgen.h>
|
#include <libgen.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <SDL2/SDL_timer.h>
|
#include <SDL2/SDL_timer.h>
|
||||||
|
#include <SDL2/SDL_platform.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "command.h"
|
#include "command.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
|
#include "util/str_util.h"
|
||||||
|
|
||||||
#define SOCKET_NAME "scrcpy"
|
#define SOCKET_NAME "scrcpy"
|
||||||
#define SERVER_FILENAME "scrcpy-server"
|
#define SERVER_FILENAME "scrcpy-server"
|
||||||
@@ -18,20 +20,39 @@
|
|||||||
#define DEFAULT_SERVER_PATH PREFIX "/share/scrcpy/" SERVER_FILENAME
|
#define DEFAULT_SERVER_PATH PREFIX "/share/scrcpy/" SERVER_FILENAME
|
||||||
#define DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
|
#define DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
|
||||||
|
|
||||||
static const char *
|
static char *
|
||||||
get_server_path(void) {
|
get_server_path(void) {
|
||||||
|
#ifdef __WINDOWS__
|
||||||
|
const wchar_t *server_path_env = _wgetenv(L"SCRCPY_SERVER_PATH");
|
||||||
|
#else
|
||||||
const char *server_path_env = getenv("SCRCPY_SERVER_PATH");
|
const char *server_path_env = getenv("SCRCPY_SERVER_PATH");
|
||||||
|
#endif
|
||||||
if (server_path_env) {
|
if (server_path_env) {
|
||||||
LOGD("Using SCRCPY_SERVER_PATH: %s", server_path_env);
|
|
||||||
// if the envvar is set, use it
|
// if the envvar is set, use it
|
||||||
return server_path_env;
|
#ifdef __WINDOWS__
|
||||||
|
char *server_path = utf8_from_wide_char(server_path_env);
|
||||||
|
#else
|
||||||
|
char *server_path = SDL_strdup(server_path_env);
|
||||||
|
#endif
|
||||||
|
if (!server_path) {
|
||||||
|
LOGE("Could not allocate memory");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
LOGD("Using SCRCPY_SERVER_PATH: %s", server_path);
|
||||||
|
return server_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef PORTABLE
|
#ifndef PORTABLE
|
||||||
LOGD("Using server: " DEFAULT_SERVER_PATH);
|
LOGD("Using server: " DEFAULT_SERVER_PATH);
|
||||||
|
char *server_path = SDL_strdup(DEFAULT_SERVER_PATH);
|
||||||
|
if (!server_path) {
|
||||||
|
LOGE("Could not allocate memory");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
// the absolute path is hardcoded
|
// the absolute path is hardcoded
|
||||||
return DEFAULT_SERVER_PATH;
|
return server_path;
|
||||||
#else
|
#else
|
||||||
|
|
||||||
// use scrcpy-server in the same directory as the executable
|
// use scrcpy-server in the same directory as the executable
|
||||||
char *executable_path = get_executable_path();
|
char *executable_path = get_executable_path();
|
||||||
if (!executable_path) {
|
if (!executable_path) {
|
||||||
@@ -67,12 +88,17 @@ get_server_path(void) {
|
|||||||
|
|
||||||
static bool
|
static bool
|
||||||
push_server(const char *serial) {
|
push_server(const char *serial) {
|
||||||
const char *server_path = get_server_path();
|
char *server_path = get_server_path();
|
||||||
|
if (!server_path) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (!is_regular_file(server_path)) {
|
if (!is_regular_file(server_path)) {
|
||||||
LOGE("'%s' does not exist or is not a regular file\n", server_path);
|
LOGE("'%s' does not exist or is not a regular file\n", server_path);
|
||||||
|
SDL_free(server_path);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH);
|
process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH);
|
||||||
|
SDL_free(server_path);
|
||||||
return process_check_success(process, "adb push");
|
return process_check_success(process, "adb push");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
@@ -127,3 +128,14 @@ get_executable_path(void) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
is_regular_file(const char *path) {
|
||||||
|
struct stat path_stat;
|
||||||
|
|
||||||
|
if (stat(path, &path_stat)) {
|
||||||
|
perror("stat");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return S_ISREG(path_stat.st_mode);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
#include "util/net.h"
|
|
||||||
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
bool
|
|
||||||
net_init(void) {
|
|
||||||
// do nothing
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
net_cleanup(void) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
net_close(socket_t socket) {
|
|
||||||
return !close(socket);
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,8 @@
|
|||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str_util.h"
|
#include "util/str_util.h"
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
static int
|
static int
|
||||||
build_cmd(char *cmd, size_t len, const char *const argv[]) {
|
build_cmd(char *cmd, size_t len, const char *const argv[]) {
|
||||||
// Windows command-line parsing is WTF:
|
// Windows command-line parsing is WTF:
|
||||||
@@ -90,3 +92,22 @@ get_executable_path(void) {
|
|||||||
buf[len] = '\0';
|
buf[len] = '\0';
|
||||||
return utf8_from_wide_char(buf);
|
return utf8_from_wide_char(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
is_regular_file(const char *path) {
|
||||||
|
wchar_t *wide_path = utf8_to_wide_char(path);
|
||||||
|
if (!wide_path) {
|
||||||
|
LOGC("Could not allocate wide char string");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct _stat path_stat;
|
||||||
|
int r = _wstat(wide_path, &path_stat);
|
||||||
|
SDL_free(wide_path);
|
||||||
|
|
||||||
|
if (r) {
|
||||||
|
perror("stat");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return S_ISREG(path_stat.st_mode);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
#include "util/net.h"
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "util/log.h"
|
|
||||||
|
|
||||||
bool
|
|
||||||
net_init(void) {
|
|
||||||
WSADATA wsa;
|
|
||||||
int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0;
|
|
||||||
if (res < 0) {
|
|
||||||
LOGC("WSAStartup failed with error %d", res);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
net_cleanup(void) {
|
|
||||||
WSACleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
net_close(socket_t socket) {
|
|
||||||
return !closesocket(socket);
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "net.h"
|
#include "net.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <SDL2/SDL_platform.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
@@ -115,3 +116,32 @@ bool
|
|||||||
net_shutdown(socket_t socket, int how) {
|
net_shutdown(socket_t socket, int how) {
|
||||||
return !shutdown(socket, how);
|
return !shutdown(socket, how);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
net_init(void) {
|
||||||
|
#ifdef __WINDOWS__
|
||||||
|
WSADATA wsa;
|
||||||
|
int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0;
|
||||||
|
if (res < 0) {
|
||||||
|
LOGC("WSAStartup failed with error %d", res);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
net_cleanup(void) {
|
||||||
|
#ifdef __WINDOWS__
|
||||||
|
WSACleanup();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
net_close(socket_t socket) {
|
||||||
|
#ifdef __WINDOWS__
|
||||||
|
return !closesocket(socket);
|
||||||
|
#else
|
||||||
|
return !close(socket);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|||||||
@@ -140,14 +140,18 @@ static void test_serialize_inject_scroll_event(void) {
|
|||||||
static void test_serialize_back_or_screen_on(void) {
|
static void test_serialize_back_or_screen_on(void) {
|
||||||
struct control_msg msg = {
|
struct control_msg msg = {
|
||||||
.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
||||||
|
.back_or_screen_on = {
|
||||||
|
.action = AKEY_EVENT_ACTION_UP,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 1);
|
assert(size == 2);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
||||||
|
AKEY_EVENT_ACTION_UP,
|
||||||
};
|
};
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
project('scrcpy', 'c',
|
project('scrcpy', 'c',
|
||||||
version: '1.12',
|
version: '1.12.1',
|
||||||
meson_version: '>= 0.37',
|
meson_version: '>= 0.37',
|
||||||
default_options: [
|
default_options: [
|
||||||
'c_std=c11',
|
'c_std=c11',
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ android {
|
|||||||
applicationId "com.genymobile.scrcpy"
|
applicationId "com.genymobile.scrcpy"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode 13
|
versionCode 14
|
||||||
versionName "1.12"
|
versionName "1.12.1"
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
SCRCPY_DEBUG=false
|
SCRCPY_DEBUG=false
|
||||||
SCRCPY_VERSION_NAME=1.12
|
SCRCPY_VERSION_NAME=1.12.1
|
||||||
|
|
||||||
PLATFORM=${ANDROID_PLATFORM:-29}
|
PLATFORM=${ANDROID_PLATFORM:-29}
|
||||||
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2}
|
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2}
|
||||||
|
|||||||
@@ -85,6 +85,13 @@ public final class ControlMessage {
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ControlMessage createBackOrScreenOn(int action) {
|
||||||
|
ControlMessage msg = new ControlMessage();
|
||||||
|
msg.type = TYPE_BACK_OR_SCREEN_ON;
|
||||||
|
msg.action = action;
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
public static ControlMessage createEmpty(int type) {
|
public static ControlMessage createEmpty(int type) {
|
||||||
ControlMessage msg = new ControlMessage();
|
ControlMessage msg = new ControlMessage();
|
||||||
msg.type = type;
|
msg.type = type;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ public class ControlMessageReader {
|
|||||||
private static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 21;
|
private static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 21;
|
||||||
private static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
private static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
||||||
private static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
private static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
||||||
|
private static final int BACK_OR_SCREEN_ON_PAYLOAD_LENGTH = 1;
|
||||||
|
|
||||||
public static final int TEXT_MAX_LENGTH = 300;
|
public static final int TEXT_MAX_LENGTH = 300;
|
||||||
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093;
|
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093;
|
||||||
@@ -73,6 +74,8 @@ public class ControlMessageReader {
|
|||||||
msg = parseSetScreenPowerMode();
|
msg = parseSetScreenPowerMode();
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
||||||
|
msg = parseBackOrScreenOn();
|
||||||
|
break;
|
||||||
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
||||||
case ControlMessage.TYPE_GET_CLIPBOARD:
|
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||||
@@ -164,6 +167,14 @@ public class ControlMessageReader {
|
|||||||
return ControlMessage.createSetScreenPowerMode(mode);
|
return ControlMessage.createSetScreenPowerMode(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ControlMessage parseBackOrScreenOn() {
|
||||||
|
if (buffer.remaining() < BACK_OR_SCREEN_ON_PAYLOAD_LENGTH) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int action = toUnsigned(buffer.get());
|
||||||
|
return ControlMessage.createBackOrScreenOn(action);
|
||||||
|
}
|
||||||
|
|
||||||
private static Position readPosition(ByteBuffer buffer) {
|
private static Position readPosition(ByteBuffer buffer) {
|
||||||
int x = buffer.getInt();
|
int x = buffer.getInt();
|
||||||
int y = buffer.getInt();
|
int y = buffer.getInt();
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ public class Controller {
|
|||||||
private final MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[PointersState.MAX_POINTERS];
|
private final MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[PointersState.MAX_POINTERS];
|
||||||
private final MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[PointersState.MAX_POINTERS];
|
private final MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[PointersState.MAX_POINTERS];
|
||||||
|
|
||||||
|
private boolean backPressed;
|
||||||
|
private boolean powerPressed;
|
||||||
|
|
||||||
public Controller(Device device, DesktopConnection connection) {
|
public Controller(Device device, DesktopConnection connection) {
|
||||||
this.device = device;
|
this.device = device;
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
@@ -88,7 +91,7 @@ public class Controller {
|
|||||||
injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll());
|
injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll());
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
||||||
pressBackOrTurnScreenOn();
|
pressBackOrTurnScreenOn(msg.getAction());
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
device.expandNotificationPanel();
|
device.expandNotificationPanel();
|
||||||
@@ -223,8 +226,33 @@ public class Controller {
|
|||||||
return device.injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
|
return device.injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean pressBackOrTurnScreenOn() {
|
private boolean pressBackOrTurnScreenOn(int action) {
|
||||||
int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER;
|
int keycode = -1;
|
||||||
return injectKeycode(keycode);
|
if (action == KeyEvent.ACTION_UP) {
|
||||||
|
// if BACK or POWER were pressed, this action is the corresponding release event (regardless of the screen state)
|
||||||
|
if (backPressed) {
|
||||||
|
keycode = KeyEvent.KEYCODE_BACK;
|
||||||
|
backPressed = false;
|
||||||
|
} else if (powerPressed) {
|
||||||
|
keycode = KeyEvent.KEYCODE_POWER;
|
||||||
|
powerPressed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keycode == -1) {
|
||||||
|
keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action == KeyEvent.ACTION_DOWN) {
|
||||||
|
if (keycode == KeyEvent.KEYCODE_BACK) {
|
||||||
|
backPressed = true;
|
||||||
|
powerPressed = false;
|
||||||
|
} else {
|
||||||
|
backPressed = false;
|
||||||
|
powerPressed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return injectKeyEvent(action, keycode, 0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
|
|
||||||
private static final int DEFAULT_I_FRAME_INTERVAL = 10; // seconds
|
private static final int DEFAULT_I_FRAME_INTERVAL = 10; // seconds
|
||||||
private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms
|
private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms
|
||||||
|
private static final String KEY_MAX_FPS_TO_ENCODER = "max-fps-to-encoder";
|
||||||
|
|
||||||
private static final int NO_PTS = -1;
|
private static final int NO_PTS = -1;
|
||||||
|
|
||||||
@@ -150,11 +151,10 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
// display the very first frame, and recover from bad quality when no new frames
|
// display the very first frame, and recover from bad quality when no new frames
|
||||||
format.setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, REPEAT_FRAME_DELAY_US); // µs
|
format.setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, REPEAT_FRAME_DELAY_US); // µs
|
||||||
if (maxFps > 0) {
|
if (maxFps > 0) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
// The key existed privately before Android 10:
|
||||||
format.setFloat(MediaFormat.KEY_MAX_FPS_TO_ENCODER, maxFps);
|
// <https://android.googlesource.com/platform/frameworks/base/+/625f0aad9f7a259b6881006ad8710adce57d1384%5E%21/>
|
||||||
} else {
|
// <https://github.com/Genymobile/scrcpy/issues/488#issuecomment-567321437>
|
||||||
Ln.w("Max FPS is only supported since Android 10, the option has been ignored");
|
format.setFloat(KEY_MAX_FPS_TO_ENCODER, maxFps);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return format;
|
return format;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ public class ControlMessageReaderTest {
|
|||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_BACK_OR_SCREEN_ON);
|
dos.writeByte(ControlMessage.TYPE_BACK_OR_SCREEN_ON);
|
||||||
|
dos.writeByte(KeyEvent.ACTION_UP);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
@@ -152,6 +153,7 @@ public class ControlMessageReaderTest {
|
|||||||
ControlMessage event = reader.next();
|
ControlMessage event = reader.next();
|
||||||
|
|
||||||
Assert.assertEquals(ControlMessage.TYPE_BACK_OR_SCREEN_ON, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_BACK_OR_SCREEN_ON, event.getType());
|
||||||
|
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user