Compare commits
23 Commits
pr1626
...
forward_al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab927f00eb | ||
|
|
d50ecf40b6 | ||
|
|
56d237f152 | ||
|
|
acc65f8c9d | ||
|
|
a65ebceac1 | ||
|
|
d662f73bdc | ||
|
|
1c44dc2259 | ||
|
|
02a882a0a2 | ||
|
|
cf7bf3148c | ||
|
|
ae758f99d6 | ||
|
|
bd9f656933 | ||
|
|
c243fd4c3f | ||
|
|
0bf110dd5c | ||
|
|
0c5e0a4f6d | ||
|
|
0be766e71a | ||
|
|
d02789ce21 | ||
|
|
6cc22e1c5b | ||
|
|
479d10dc22 | ||
|
|
d7779d08e8 | ||
|
|
df4ba1b8b0 | ||
|
|
198346d148 | ||
|
|
95f1ea0d80 | ||
|
|
38940ffe89 |
6
BUILD.md
6
BUILD.md
@@ -254,10 +254,10 @@ You can then [run](README.md#run) _scrcpy_.
|
||||
|
||||
## Prebuilt server
|
||||
|
||||
- [`scrcpy-server-v1.15.1`][direct-scrcpy-server]
|
||||
_(SHA-256: fe06bd6a30da8c89860bf5e16eecce2b5054d4644c84289670ce00ca5d1637c3)_
|
||||
- [`scrcpy-server-v1.16`][direct-scrcpy-server]
|
||||
_(SHA-256: 94a79e05b4498d0460ab7bd9d12cbf05156e3a47bf0c5d1420cee1d4493b3832)_
|
||||
|
||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.15.1/scrcpy-server-v1.15.1
|
||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.16/scrcpy-server-v1.16
|
||||
|
||||
Download the prebuilt server somewhere, and specify its path during the Meson
|
||||
configuration:
|
||||
|
||||
2
FAQ.md
2
FAQ.md
@@ -37,6 +37,8 @@ Check [stackoverflow][device-unauthorized].
|
||||
|
||||
### Device not detected
|
||||
|
||||
> adb: error: failed to get feature set: no devices/emulators found
|
||||
|
||||
If your device is not detected, you may need some [drivers] (on Windows).
|
||||
|
||||
[drivers]: https://developer.android.com/studio/run/oem-usb.html
|
||||
|
||||
32
README.md
32
README.md
@@ -1,4 +1,4 @@
|
||||
# scrcpy (v1.15.1)
|
||||
# scrcpy (v1.16)
|
||||
|
||||
[Read in another language](#translations)
|
||||
|
||||
@@ -77,10 +77,10 @@ hard).
|
||||
For Windows, for simplicity, a prebuilt archive with all the dependencies
|
||||
(including `adb`) is available:
|
||||
|
||||
- [`scrcpy-win64-v1.15.1.zip`][direct-win64]
|
||||
_(SHA-256: 78fba4caad6328016ea93219254b5df391f24224c519a2c8e3f070695b8b38ff)_
|
||||
- [`scrcpy-win64-v1.16.zip`][direct-win64]
|
||||
_(SHA-256: 3f30dc5db1a2f95c2b40a0f5de91ec1642d9f53799250a8c529bc882bc0918f0)_
|
||||
|
||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.15.1/scrcpy-win64-v1.15.1.zip
|
||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.16/scrcpy-win64-v1.16.zip
|
||||
|
||||
It is also available in [Chocolatey]:
|
||||
|
||||
@@ -548,6 +548,19 @@ into the device clipboard. As a consequence, any Android application could read
|
||||
its content. You should avoid to paste sensitive content (like passwords) that
|
||||
way.
|
||||
|
||||
|
||||
#### Pinch-to-zoom
|
||||
|
||||
To simulate "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-and-move_.
|
||||
|
||||
More precisely, hold <kbd>Ctrl</kbd> while pressing the left-click button. Until
|
||||
the left-click button is released, all mouse movements scale and rotate the
|
||||
content (if supported by the app) relative to the center of the screen.
|
||||
|
||||
Concretely, scrcpy generates additional touch events from a "virtual finger" at
|
||||
a location inverted through the center of the screen.
|
||||
|
||||
|
||||
#### Text injection preference
|
||||
|
||||
There are two kinds of [events][textevents] generated when typing text:
|
||||
@@ -582,6 +595,16 @@ scrcpy --no-key-repeat
|
||||
```
|
||||
|
||||
|
||||
#### Right-click and middle-click
|
||||
|
||||
By default, right-click triggers BACK (or POWER on) and middle-click triggers
|
||||
HOME. To disable these shortcuts and forward the clicks to the device instead:
|
||||
|
||||
```bash
|
||||
scrcpy --forward-all-clicks
|
||||
```
|
||||
|
||||
|
||||
### File drop
|
||||
|
||||
#### Install APK
|
||||
@@ -661,6 +684,7 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
|
||||
| Synchronize clipboards and paste³ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
||||
| Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
||||
| Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
||||
| Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_
|
||||
|
||||
_¹Double-click on black borders to remove them._
|
||||
_²Right-click turns the screen on if it was off, presses BACK otherwise._
|
||||
|
||||
18
app/scrcpy.1
18
app/scrcpy.1
@@ -60,6 +60,10 @@ Default is 0.
|
||||
.B \-\-force\-adb\-forward
|
||||
Do not attempt to use "adb reverse" to connect to the device.
|
||||
|
||||
.TP
|
||||
.B \-\-forward\-all\-clicks
|
||||
By default, right-click triggers BACK (or POWER on) and middle-click triggers HOME. This option disables these shortcuts and forward the clicks to the device instead.
|
||||
|
||||
.TP
|
||||
.B \-f, \-\-fullscreen
|
||||
Start in fullscreen.
|
||||
@@ -92,14 +96,14 @@ Disable device control (mirror the device in read\-only).
|
||||
.B \-N, \-\-no\-display
|
||||
Do not display device (only when screen recording is enabled).
|
||||
|
||||
.TP
|
||||
.B \-\-no\-mipmaps
|
||||
If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps.
|
||||
|
||||
.TP
|
||||
.B \-\-no\-key\-repeat
|
||||
Do not forward repeated key events when a key is held down.
|
||||
|
||||
.TP
|
||||
.B \-\-no\-mipmaps
|
||||
If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps.
|
||||
|
||||
.TP
|
||||
.BI "\-p, \-\-port " port[:port]
|
||||
Set the TCP port (range) used by the client to listen.
|
||||
@@ -222,7 +226,7 @@ Default is 0 (automatic).\n
|
||||
.SH SHORTCUTS
|
||||
|
||||
In the following list, MOD is the shortcut modifier. By default, it's (left)
|
||||
Alt or (left) Super, but it can be configured by \-\-shortcut-mod.
|
||||
Alt or (left) Super, but it can be configured by \-\-shortcut-mod (see above).
|
||||
|
||||
.TP
|
||||
.B MOD+f
|
||||
@@ -316,6 +320,10 @@ Inject computer clipboard text as a sequence of key events
|
||||
.B MOD+i
|
||||
Enable/disable FPS counter (print frames/second in logs)
|
||||
|
||||
.TP
|
||||
.B Ctrl+click-and-move
|
||||
Pinch-to-zoom from the center of the screen
|
||||
|
||||
.TP
|
||||
.B Drag & drop APK file
|
||||
Install APK from computer
|
||||
|
||||
@@ -57,6 +57,11 @@ scrcpy_print_usage(const char *arg0) {
|
||||
" Do not attempt to use \"adb reverse\" to connect to the\n"
|
||||
" the device.\n"
|
||||
"\n"
|
||||
" --forward-all-clicks\n"
|
||||
" By default, right-click triggers BACK (or POWER on) and\n"
|
||||
" middle-click triggers HOME. This option disables these\n"
|
||||
" shortcuts and forward the clicks to the device instead.\n"
|
||||
"\n"
|
||||
" -f, --fullscreen\n"
|
||||
" Start in fullscreen.\n"
|
||||
"\n"
|
||||
@@ -87,14 +92,14 @@ scrcpy_print_usage(const char *arg0) {
|
||||
" Do not display device (only when screen recording is\n"
|
||||
" enabled).\n"
|
||||
"\n"
|
||||
" --no-key-repeat\n"
|
||||
" Do not forward repeated key events when a key is held down.\n"
|
||||
"\n"
|
||||
" --no-mipmaps\n"
|
||||
" If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then\n"
|
||||
" mipmaps are automatically generated to improve downscaling\n"
|
||||
" quality. This option disables the generation of mipmaps.\n"
|
||||
"\n"
|
||||
" --no-key-repeat\n"
|
||||
" Do not forward repeated key events when a key is held down.\n"
|
||||
"\n"
|
||||
" -p, --port port[:port]\n"
|
||||
" Set the TCP port (range) used by the client to listen.\n"
|
||||
" Default is %d:%d.\n"
|
||||
@@ -203,7 +208,7 @@ scrcpy_print_usage(const char *arg0) {
|
||||
"\n"
|
||||
" In the following list, MOD is the shortcut modifier. By default,\n"
|
||||
" it's (left) Alt or (left) Super, but it can be configured by\n"
|
||||
" --shortcut-mod.\n"
|
||||
" --shortcut-mod (see above).\n"
|
||||
"\n"
|
||||
" MOD+f\n"
|
||||
" Switch fullscreen mode\n"
|
||||
@@ -279,6 +284,9 @@ scrcpy_print_usage(const char *arg0) {
|
||||
" MOD+i\n"
|
||||
" Enable/disable FPS counter (print frames/second in logs)\n"
|
||||
"\n"
|
||||
" Ctrl+click-and-move\n"
|
||||
" Pinch-to-zoom from the center of the screen\n"
|
||||
"\n"
|
||||
" Drag & drop APK file\n"
|
||||
" Install APK from computer\n"
|
||||
"\n",
|
||||
@@ -529,7 +537,9 @@ parse_shortcut_mods_item(const char *item, size_t len) {
|
||||
} else if (STREQ("rsuper", item, key_len)) {
|
||||
mod |= SC_MOD_RSUPER;
|
||||
} else {
|
||||
LOGW("Unknown modifier key: %.*s", (int) key_len, item);
|
||||
LOGE("Unknown modifier key: %.*s "
|
||||
"(must be one of: lctrl, rctrl, lalt, ralt, lsuper, rsuper)",
|
||||
(int) key_len, item);
|
||||
return 0;
|
||||
}
|
||||
#undef STREQ
|
||||
@@ -646,6 +656,7 @@ guess_record_format(const char *filename) {
|
||||
#define OPT_DISABLE_SCREENSAVER 1020
|
||||
#define OPT_SHORTCUT_MOD 1021
|
||||
#define OPT_NO_KEY_REPEAT 1022
|
||||
#define OPT_FORWARD_ALL_CLICKS 1023
|
||||
|
||||
bool
|
||||
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
||||
@@ -659,6 +670,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
||||
{"display", required_argument, NULL, OPT_DISPLAY_ID},
|
||||
{"force-adb-forward", no_argument, NULL,
|
||||
OPT_FORCE_ADB_FORWARD},
|
||||
{"forward-all-clicks", no_argument, NULL,
|
||||
OPT_FORWARD_ALL_CLICKS},
|
||||
{"fullscreen", no_argument, NULL, 'f'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"lock-video-orientation", required_argument, NULL,
|
||||
@@ -667,8 +680,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
||||
{"max-size", required_argument, NULL, 'm'},
|
||||
{"no-control", no_argument, NULL, 'n'},
|
||||
{"no-display", no_argument, NULL, 'N'},
|
||||
{"no-mipmaps", no_argument, NULL, OPT_NO_MIPMAPS},
|
||||
{"no-key-repeat", no_argument, NULL, OPT_NO_KEY_REPEAT},
|
||||
{"no-mipmaps", no_argument, NULL, OPT_NO_MIPMAPS},
|
||||
{"port", required_argument, NULL, 'p'},
|
||||
{"prefer-text", no_argument, NULL, OPT_PREFER_TEXT},
|
||||
{"push-target", required_argument, NULL, OPT_PUSH_TARGET},
|
||||
@@ -851,6 +864,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case OPT_FORWARD_ALL_CLICKS:
|
||||
opts->forward_all_clicks = true;
|
||||
break;
|
||||
default:
|
||||
// getopt prints the error message on stderr
|
||||
return false;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 6)
|
||||
|
||||
#define POINTER_ID_MOUSE UINT64_C(-1);
|
||||
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2);
|
||||
|
||||
enum control_msg_type {
|
||||
CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
||||
|
||||
@@ -60,6 +60,7 @@ input_manager_init(struct input_manager *im,
|
||||
im->control = options->control;
|
||||
im->forward_key_repeat = options->forward_key_repeat;
|
||||
im->prefer_text = options->prefer_text;
|
||||
im->forward_all_clicks = options->forward_all_clicks;
|
||||
|
||||
const struct sc_shortcut_mods *shortcut_mods = &options->shortcut_mods;
|
||||
assert(shortcut_mods->count);
|
||||
@@ -70,6 +71,8 @@ input_manager_init(struct input_manager *im,
|
||||
im->sdl_shortcut_mods.data[i] = sdl_mod;
|
||||
}
|
||||
im->sdl_shortcut_mods.count = shortcut_mods->count;
|
||||
|
||||
im->vfinger_down = false;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -299,6 +302,36 @@ input_manager_process_text_input(struct input_manager *im,
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
simulate_virtual_finger(struct input_manager *im,
|
||||
enum android_motionevent_action action,
|
||||
struct point point) {
|
||||
bool up = action == AMOTION_EVENT_ACTION_UP;
|
||||
|
||||
struct control_msg msg;
|
||||
msg.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
|
||||
msg.inject_touch_event.action = action;
|
||||
msg.inject_touch_event.position.screen_size = im->screen->frame_size;
|
||||
msg.inject_touch_event.position.point = point;
|
||||
msg.inject_touch_event.pointer_id = POINTER_ID_VIRTUAL_FINGER;
|
||||
msg.inject_touch_event.pressure = up ? 0.0f : 1.0f;
|
||||
msg.inject_touch_event.buttons = 0;
|
||||
|
||||
if (!controller_push_msg(im->controller, &msg)) {
|
||||
LOGW("Could not request 'inject virtual finger event'");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct point
|
||||
inverse_point(struct point point, struct size size) {
|
||||
point.x = size.width - point.x;
|
||||
point.y = size.height - point.y;
|
||||
return point;
|
||||
}
|
||||
|
||||
static bool
|
||||
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
|
||||
bool prefer_text, uint32_t repeat) {
|
||||
@@ -512,10 +545,18 @@ input_manager_process_mouse_motion(struct input_manager *im,
|
||||
return;
|
||||
}
|
||||
struct control_msg msg;
|
||||
if (convert_mouse_motion(event, im->screen, &msg)) {
|
||||
if (!controller_push_msg(im->controller, &msg)) {
|
||||
LOGW("Could not request 'inject mouse motion event'");
|
||||
}
|
||||
if (!convert_mouse_motion(event, im->screen, &msg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!controller_push_msg(im->controller, &msg)) {
|
||||
LOGW("Could not request 'inject mouse motion event'");
|
||||
}
|
||||
|
||||
if (im->vfinger_down) {
|
||||
struct point mouse = msg.inject_touch_event.position.point;
|
||||
struct point vfinger = inverse_point(mouse, im->screen->frame_size);
|
||||
simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -587,7 +628,9 @@ input_manager_process_mouse_button(struct input_manager *im,
|
||||
// simulated from touch events, so it's a duplicate
|
||||
return;
|
||||
}
|
||||
if (event->type == SDL_MOUSEBUTTONDOWN) {
|
||||
|
||||
bool down = event->type == SDL_MOUSEBUTTONDOWN;
|
||||
if (!im->forward_all_clicks && down) {
|
||||
if (control && event->button == SDL_BUTTON_RIGHT) {
|
||||
press_back_or_turn_screen_on(im->controller);
|
||||
return;
|
||||
@@ -618,10 +661,36 @@ input_manager_process_mouse_button(struct input_manager *im,
|
||||
}
|
||||
|
||||
struct control_msg msg;
|
||||
if (convert_mouse_button(event, im->screen, &msg)) {
|
||||
if (!controller_push_msg(im->controller, &msg)) {
|
||||
LOGW("Could not request 'inject mouse button event'");
|
||||
if (!convert_mouse_button(event, im->screen, &msg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!controller_push_msg(im->controller, &msg)) {
|
||||
LOGW("Could not request 'inject mouse button event'");
|
||||
return;
|
||||
}
|
||||
|
||||
// Pinch-to-zoom simulation.
|
||||
//
|
||||
// If Ctrl is hold when the left-click button is pressed, then
|
||||
// pinch-to-zoom mode is enabled: on every mouse event until the left-click
|
||||
// button is released, an additional "virtual finger" event is generated,
|
||||
// having a position inverted through the center of the screen.
|
||||
//
|
||||
// In other words, the center of the rotation/scaling is the center of the
|
||||
// screen.
|
||||
#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL))
|
||||
if ((down && !im->vfinger_down && CTRL_PRESSED)
|
||||
|| (!down && im->vfinger_down)) {
|
||||
struct point mouse = msg.inject_touch_event.position.point;
|
||||
struct point vfinger = inverse_point(mouse, im->screen->frame_size);
|
||||
enum android_motionevent_action action = down
|
||||
? AMOTION_EVENT_ACTION_DOWN
|
||||
: AMOTION_EVENT_ACTION_UP;
|
||||
if (!simulate_virtual_finger(im, action, vfinger)) {
|
||||
return;
|
||||
}
|
||||
im->vfinger_down = down;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,11 +25,14 @@ struct input_manager {
|
||||
bool control;
|
||||
bool forward_key_repeat;
|
||||
bool prefer_text;
|
||||
bool forward_all_clicks;
|
||||
|
||||
struct {
|
||||
unsigned data[SC_MAX_SHORTCUT_MODS];
|
||||
unsigned count;
|
||||
} sdl_shortcut_mods;
|
||||
|
||||
bool vfinger_down;
|
||||
};
|
||||
|
||||
void
|
||||
|
||||
@@ -100,7 +100,7 @@ main(int argc, char *argv[]) {
|
||||
|
||||
#if defined (__WINDOWS__) && ! defined (WINDOWS_NOCONSOLE)
|
||||
if (res != 0) {
|
||||
fprintf(stderr, "Press any key to continue...\n");
|
||||
fprintf(stderr, "Press Enter to continue...\n");
|
||||
getchar();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -361,12 +361,14 @@ recorder_push(struct recorder *recorder, const AVPacket *packet) {
|
||||
|
||||
if (recorder->failed) {
|
||||
// reject any new packet (this will stop the stream)
|
||||
mutex_unlock(recorder->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct record_packet *rec = record_packet_new(packet);
|
||||
if (!rec) {
|
||||
LOGC("Could not allocate record packet");
|
||||
mutex_unlock(recorder->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ struct scrcpy_options {
|
||||
bool force_adb_forward;
|
||||
bool disable_screensaver;
|
||||
bool forward_key_repeat;
|
||||
bool forward_all_clicks;
|
||||
};
|
||||
|
||||
#define SCRCPY_OPTIONS_DEFAULT { \
|
||||
@@ -123,6 +124,7 @@ struct scrcpy_options {
|
||||
.force_adb_forward = false, \
|
||||
.disable_screensaver = false, \
|
||||
.forward_key_repeat = true, \
|
||||
.forward_all_clicks = false, \
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@@ -201,7 +201,7 @@ enable_tunnel_forward_any_port(struct server *server,
|
||||
|
||||
if (port < port_range.last) {
|
||||
LOGW("Could not forward port %" PRIu16", retrying on %" PRIu16,
|
||||
port, port + 1);
|
||||
port, (uint16_t) (port + 1));
|
||||
port++;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ struct video_buffer;
|
||||
|
||||
struct stream {
|
||||
socket_t socket;
|
||||
struct video_buffer *video_buffer;
|
||||
SDL_Thread *thread;
|
||||
struct decoder *decoder;
|
||||
struct recorder *recorder;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// for portability
|
||||
#define _POSIX_SOURCE // for kill()
|
||||
#define _BSD_SOURCE // for readlink()
|
||||
// for portability (kill, readlink, strdup, strtok_r)
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#define _BSD_SOURCE
|
||||
|
||||
// modern glibc will complain without this
|
||||
#define _DEFAULT_SOURCE
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
project('scrcpy', 'c',
|
||||
version: '1.15.1',
|
||||
version: '1.16',
|
||||
meson_version: '>= 0.48',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
url="$1"
|
||||
sum="$2"
|
||||
|
||||
2
run
2
run
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
# Run scrcpy generated in the specified BUILDDIR.
|
||||
#
|
||||
# This provides the same feature as "ninja run", except that it is possible to
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
SCRCPY_SERVER_PATH="$MESON_BUILD_ROOT/server/scrcpy-server" "$MESON_BUILD_ROOT/app/scrcpy"
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
compileSdkVersion 30
|
||||
defaultConfig {
|
||||
applicationId "com.genymobile.scrcpy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
versionCode 18
|
||||
versionName "1.15.1"
|
||||
targetSdkVersion 30
|
||||
versionCode 19
|
||||
versionName "1.16"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# This script generates the scrcpy binary "manually" (without gradle).
|
||||
#
|
||||
@@ -12,10 +12,10 @@
|
||||
set -e
|
||||
|
||||
SCRCPY_DEBUG=false
|
||||
SCRCPY_VERSION_NAME=1.15.1
|
||||
SCRCPY_VERSION_NAME=1.16
|
||||
|
||||
PLATFORM=${ANDROID_PLATFORM:-29}
|
||||
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2}
|
||||
PLATFORM=${ANDROID_PLATFORM:-30}
|
||||
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-30.0.0}
|
||||
|
||||
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
|
||||
CLASSES_DIR="$BUILD_DIR/classes"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
# Wrapper script to invoke gradle from meson
|
||||
set -e
|
||||
|
||||
|
||||
@@ -78,7 +78,9 @@ public final class CleanUp {
|
||||
|
||||
if (restoreNormalPowerMode) {
|
||||
Ln.i("Restoring normal power mode");
|
||||
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
|
||||
if (Device.isScreenOn()) {
|
||||
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,11 +54,11 @@ public class Controller {
|
||||
|
||||
public void control() throws IOException {
|
||||
// on start, power on the device
|
||||
if (!device.isScreenOn()) {
|
||||
device.injectKeycode(KeyEvent.KEYCODE_WAKEUP);
|
||||
if (!Device.isScreenOn()) {
|
||||
device.injectKeycode(KeyEvent.KEYCODE_POWER);
|
||||
|
||||
// dirty hack
|
||||
// After the keycode is injected, the device is powered on asynchronously.
|
||||
// After POWER is injected, the device is powered on asynchronously.
|
||||
// To turn the device screen off while mirroring, the client will send a message that
|
||||
// would be handled before the device is actually powered on, so its effect would
|
||||
// be "canceled" once the device is turned back on.
|
||||
@@ -105,13 +105,13 @@ public class Controller {
|
||||
}
|
||||
break;
|
||||
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||
device.expandNotificationPanel();
|
||||
Device.expandNotificationPanel();
|
||||
break;
|
||||
case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
||||
device.collapsePanels();
|
||||
Device.collapsePanels();
|
||||
break;
|
||||
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||
String clipboardText = device.getClipboardText();
|
||||
String clipboardText = Device.getClipboardText();
|
||||
if (clipboardText != null) {
|
||||
sender.pushClipboardText(clipboardText);
|
||||
}
|
||||
@@ -130,7 +130,7 @@ public class Controller {
|
||||
}
|
||||
break;
|
||||
case ControlMessage.TYPE_ROTATE_DEVICE:
|
||||
device.rotateDevice();
|
||||
Device.rotateDevice();
|
||||
break;
|
||||
default:
|
||||
// do nothing
|
||||
@@ -205,9 +205,13 @@ public class Controller {
|
||||
}
|
||||
}
|
||||
|
||||
// Right-click and middle-click only work if the source is a mouse
|
||||
boolean nonPrimaryButtonPressed = (buttons & ~MotionEvent.BUTTON_PRIMARY) != 0;
|
||||
int source = nonPrimaryButtonPressed ? InputDevice.SOURCE_MOUSE : InputDevice.SOURCE_TOUCHSCREEN;
|
||||
|
||||
MotionEvent event = MotionEvent
|
||||
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEVICE_ID_VIRTUAL, 0,
|
||||
InputDevice.SOURCE_TOUCHSCREEN, 0);
|
||||
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEVICE_ID_VIRTUAL, 0, source,
|
||||
0);
|
||||
return device.injectEvent(event);
|
||||
}
|
||||
|
||||
@@ -248,8 +252,8 @@ public class Controller {
|
||||
}
|
||||
|
||||
private boolean pressBackOrTurnScreenOn() {
|
||||
int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_WAKEUP;
|
||||
if (keepPowerModeOff && keycode == KeyEvent.KEYCODE_WAKEUP) {
|
||||
int keycode = Device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER;
|
||||
if (keepPowerModeOff && keycode == KeyEvent.KEYCODE_POWER) {
|
||||
schedulePowerModeOff();
|
||||
}
|
||||
return device.injectKeycode(keycode);
|
||||
|
||||
@@ -25,6 +25,8 @@ public final class Device {
|
||||
public static final int POWER_MODE_OFF = SurfaceControl.POWER_MODE_OFF;
|
||||
public static final int POWER_MODE_NORMAL = SurfaceControl.POWER_MODE_NORMAL;
|
||||
|
||||
private static final ServiceManager SERVICE_MANAGER = new ServiceManager();
|
||||
|
||||
public interface RotationListener {
|
||||
void onRotationChanged(int rotation);
|
||||
}
|
||||
@@ -33,8 +35,6 @@ public final class Device {
|
||||
void onClipboardTextChanged(String text);
|
||||
}
|
||||
|
||||
private final ServiceManager serviceManager = new ServiceManager();
|
||||
|
||||
private ScreenInfo screenInfo;
|
||||
private RotationListener rotationListener;
|
||||
private ClipboardListener clipboardListener;
|
||||
@@ -54,9 +54,9 @@ public final class Device {
|
||||
|
||||
public Device(Options options) {
|
||||
displayId = options.getDisplayId();
|
||||
DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo(displayId);
|
||||
DisplayInfo displayInfo = SERVICE_MANAGER.getDisplayManager().getDisplayInfo(displayId);
|
||||
if (displayInfo == null) {
|
||||
int[] displayIds = serviceManager.getDisplayManager().getDisplayIds();
|
||||
int[] displayIds = SERVICE_MANAGER.getDisplayManager().getDisplayIds();
|
||||
throw new InvalidDisplayIdException(displayId, displayIds);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public final class Device {
|
||||
screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockedVideoOrientation());
|
||||
layerStack = displayInfo.getLayerStack();
|
||||
|
||||
serviceManager.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() {
|
||||
SERVICE_MANAGER.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() {
|
||||
@Override
|
||||
public void onRotationChanged(int rotation) {
|
||||
synchronized (Device.this) {
|
||||
@@ -81,7 +81,7 @@ public final class Device {
|
||||
|
||||
if (options.getControl()) {
|
||||
// If control is enabled, synchronize Android clipboard to the computer automatically
|
||||
ClipboardManager clipboardManager = serviceManager.getClipboardManager();
|
||||
ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager();
|
||||
if (clipboardManager != null) {
|
||||
clipboardManager.addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() {
|
||||
@Override
|
||||
@@ -166,7 +166,7 @@ public final class Device {
|
||||
return false;
|
||||
}
|
||||
|
||||
return serviceManager.getInputManager().injectInputEvent(inputEvent, mode);
|
||||
return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, mode);
|
||||
}
|
||||
|
||||
public boolean injectEvent(InputEvent event) {
|
||||
@@ -184,8 +184,8 @@ public final class Device {
|
||||
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0);
|
||||
}
|
||||
|
||||
public boolean isScreenOn() {
|
||||
return serviceManager.getPowerManager().isScreenOn();
|
||||
public static boolean isScreenOn() {
|
||||
return SERVICE_MANAGER.getPowerManager().isScreenOn();
|
||||
}
|
||||
|
||||
public synchronized void setRotationListener(RotationListener rotationListener) {
|
||||
@@ -196,16 +196,16 @@ public final class Device {
|
||||
this.clipboardListener = clipboardListener;
|
||||
}
|
||||
|
||||
public void expandNotificationPanel() {
|
||||
serviceManager.getStatusBarManager().expandNotificationsPanel();
|
||||
public static void expandNotificationPanel() {
|
||||
SERVICE_MANAGER.getStatusBarManager().expandNotificationsPanel();
|
||||
}
|
||||
|
||||
public void collapsePanels() {
|
||||
serviceManager.getStatusBarManager().collapsePanels();
|
||||
public static void collapsePanels() {
|
||||
SERVICE_MANAGER.getStatusBarManager().collapsePanels();
|
||||
}
|
||||
|
||||
public String getClipboardText() {
|
||||
ClipboardManager clipboardManager = serviceManager.getClipboardManager();
|
||||
public static String getClipboardText() {
|
||||
ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager();
|
||||
if (clipboardManager == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -217,13 +217,13 @@ public final class Device {
|
||||
}
|
||||
|
||||
public boolean setClipboardText(String text) {
|
||||
ClipboardManager clipboardManager = serviceManager.getClipboardManager();
|
||||
ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager();
|
||||
if (clipboardManager == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String currentClipboard = getClipboardText();
|
||||
if (currentClipboard == null || currentClipboard.equals(text)) {
|
||||
if (currentClipboard != null && currentClipboard.equals(text)) {
|
||||
// The clipboard already contains the requested text.
|
||||
// Since pasting text from the computer involves setting the device clipboard, it could be set twice on a copy-paste. This would cause
|
||||
// the clipboard listeners to be notified twice, and that would flood the Android keyboard clipboard history. To workaround this
|
||||
@@ -252,8 +252,8 @@ public final class Device {
|
||||
/**
|
||||
* Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled).
|
||||
*/
|
||||
public void rotateDevice() {
|
||||
WindowManager wm = serviceManager.getWindowManager();
|
||||
public static void rotateDevice() {
|
||||
WindowManager wm = SERVICE_MANAGER.getWindowManager();
|
||||
|
||||
boolean accelerometerRotation = !wm.isRotationFrozen();
|
||||
|
||||
@@ -270,7 +270,7 @@ public final class Device {
|
||||
}
|
||||
}
|
||||
|
||||
public ContentProvider createSettingsProvider() {
|
||||
return serviceManager.getActivityManager().createSettingsProvider();
|
||||
public static ContentProvider createSettingsProvider() {
|
||||
return SERVICE_MANAGER.getActivityManager().createSettingsProvider();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public final class Server {
|
||||
boolean mustDisableShowTouchesOnCleanUp = false;
|
||||
int restoreStayOn = -1;
|
||||
if (options.getShowTouches() || options.getStayAwake()) {
|
||||
try (ContentProvider settings = device.createSettingsProvider()) {
|
||||
try (ContentProvider settings = Device.createSettingsProvider()) {
|
||||
if (options.getShowTouches()) {
|
||||
String oldValue = settings.getAndPutValue(ContentProvider.TABLE_SYSTEM, "show_touches", "1");
|
||||
// If "show touches" was disabled, it must be disabled back on clean up
|
||||
|
||||
@@ -35,7 +35,7 @@ public class ContentProvider implements Closeable {
|
||||
private final IBinder token;
|
||||
|
||||
private Method callMethod;
|
||||
private boolean callMethodLegacy;
|
||||
private int callMethodVersion;
|
||||
|
||||
ContentProvider(ActivityManager manager, Object provider, String name, IBinder token) {
|
||||
this.manager = manager;
|
||||
@@ -46,12 +46,20 @@ public class ContentProvider implements Closeable {
|
||||
|
||||
private Method getCallMethod() throws NoSuchMethodException {
|
||||
if (callMethod == null) {
|
||||
|
||||
try {
|
||||
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, String.class, Bundle.class);
|
||||
callMethod = provider.getClass()
|
||||
.getMethod("call", String.class, String.class, String.class, String.class, String.class, Bundle.class);
|
||||
callMethodVersion = 0;
|
||||
} catch (NoSuchMethodException e) {
|
||||
// old version
|
||||
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, Bundle.class);
|
||||
callMethodLegacy = true;
|
||||
// old versions
|
||||
try {
|
||||
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, String.class, Bundle.class);
|
||||
callMethodVersion = 1;
|
||||
} catch (NoSuchMethodException e2) {
|
||||
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, Bundle.class);
|
||||
callMethodVersion = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
return callMethod;
|
||||
@@ -61,10 +69,16 @@ public class ContentProvider implements Closeable {
|
||||
try {
|
||||
Method method = getCallMethod();
|
||||
Object[] args;
|
||||
if (!callMethodLegacy) {
|
||||
args = new Object[]{ServiceManager.PACKAGE_NAME, "settings", callMethod, arg, extras};
|
||||
} else {
|
||||
args = new Object[]{ServiceManager.PACKAGE_NAME, callMethod, arg, extras};
|
||||
switch (callMethodVersion) {
|
||||
case 0:
|
||||
args = new Object[]{ServiceManager.PACKAGE_NAME, null, "settings", callMethod, arg, extras};
|
||||
break;
|
||||
case 1:
|
||||
args = new Object[]{ServiceManager.PACKAGE_NAME, "settings", callMethod, arg, extras};
|
||||
break;
|
||||
default:
|
||||
args = new Object[]{ServiceManager.PACKAGE_NAME, callMethod, arg, extras};
|
||||
break;
|
||||
}
|
||||
return (Bundle) method.invoke(provider, args);
|
||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||
|
||||
Reference in New Issue
Block a user