mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-02-24 07:14:29 +01:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a77f3bab4 | ||
|
|
0f6cdc56fa | ||
|
|
09bb6a68cf | ||
|
|
f9ddf31c32 | ||
|
|
8b73c90427 | ||
|
|
ef91ab2841 | ||
|
|
44fa4a090e | ||
|
|
dcde578a50 | ||
|
|
93a5c5149d | ||
|
|
2ca8318b9d | ||
|
|
d499ee53c9 | ||
|
|
8f619f337b | ||
|
|
fc1dec0270 | ||
|
|
274b591d18 |
6
BUILD.md
6
BUILD.md
@@ -249,10 +249,10 @@ You can then [run](README.md#run) _scrcpy_.
|
||||
|
||||
## Prebuilt server
|
||||
|
||||
- [`scrcpy-server-v1.13`][direct-scrcpy-server]
|
||||
_(SHA-256: 5fee64ca1ccdc2f38550f31f5353c66de3de30c2e929a964e30fa2d005d5f885)_
|
||||
- [`scrcpy-server-v1.14`][direct-scrcpy-server]
|
||||
_(SHA-256: 1d1b18a2b80e956771fd63b99b414d2d028713a8f12ddfa5a369709ad4295620)_
|
||||
|
||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.13/scrcpy-server-v1.13
|
||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.14/scrcpy-server-v1.14
|
||||
|
||||
Download the prebuilt server somewhere, and specify its path during the Meson
|
||||
configuration:
|
||||
|
||||
60
README.md
60
README.md
@@ -1,4 +1,4 @@
|
||||
# scrcpy (v1.13)
|
||||
# scrcpy (v1.14)
|
||||
|
||||
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.
|
||||
@@ -69,10 +69,10 @@ hard).
|
||||
For Windows, for simplicity, a prebuilt archive with all the dependencies
|
||||
(including `adb`) is available:
|
||||
|
||||
- [`scrcpy-win64-v1.13.zip`][direct-win64]
|
||||
_(SHA-256: 806aafc00d4db01513193addaa24f47858893ba5efe75770bfef6ae1ea987d27)_
|
||||
- [`scrcpy-win64-v1.14.zip`][direct-win64]
|
||||
_(SHA-256: 2be9139e46e29cf2f5f695848bb2b75a543b8f38be1133257dc5068252abc25f)_
|
||||
|
||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.13/scrcpy-win64-v1.13.zip
|
||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.14/scrcpy-win64-v1.14.zip
|
||||
|
||||
It is also available in [Chocolatey]:
|
||||
|
||||
@@ -439,7 +439,7 @@ scrcpy -S
|
||||
|
||||
Or by pressing `Ctrl`+`o` at any time.
|
||||
|
||||
To turn it back on, press `POWER` (or `Ctrl`+`p`).
|
||||
To turn it back on, press `Ctrl`+`Shift`+`o` (or `POWER`, `Ctrl`+`p`).
|
||||
|
||||
It can be useful to also prevent the device to sleep:
|
||||
|
||||
@@ -494,7 +494,8 @@ It is possible to synchronize clipboards between the computer and the device, in
|
||||
both directions:
|
||||
|
||||
- `Ctrl`+`c` copies the device clipboard to the computer clipboard;
|
||||
- `Ctrl`+`Shift`+`v` copies the computer clipboard to the device clipboard;
|
||||
- `Ctrl`+`Shift`+`v` copies the computer clipboard to the device clipboard (and
|
||||
pastes if the device runs Android >= 7);
|
||||
- `Ctrl`+`v` _pastes_ the computer clipboard as a sequence of text events (but
|
||||
breaks non-ASCII characters).
|
||||
|
||||
@@ -559,29 +560,30 @@ Also see [issue #14].
|
||||
|
||||
## Shortcuts
|
||||
|
||||
| Action | Shortcut | Shortcut (macOS)
|
||||
| -------------------------------------- |:----------------------------- |:-----------------------------
|
||||
| Switch fullscreen mode | `Ctrl`+`f` | `Cmd`+`f`
|
||||
| Rotate display left | `Ctrl`+`←` _(left)_ | `Cmd`+`←` _(left)_
|
||||
| Rotate display right | `Ctrl`+`→` _(right)_ | `Cmd`+`→` _(right)_
|
||||
| Resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` | `Cmd`+`g`
|
||||
| Resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ | `Cmd`+`x` \| _Double-click¹_
|
||||
| Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ | `Ctrl`+`h` \| _Middle-click_
|
||||
| Click on `BACK` | `Ctrl`+`b` \| _Right-click²_ | `Cmd`+`b` \| _Right-click²_
|
||||
| Click on `APP_SWITCH` | `Ctrl`+`s` | `Cmd`+`s`
|
||||
| Click on `MENU` | `Ctrl`+`m` | `Ctrl`+`m`
|
||||
| Click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ | `Cmd`+`↑` _(up)_
|
||||
| Click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ | `Cmd`+`↓` _(down)_
|
||||
| Click on `POWER` | `Ctrl`+`p` | `Cmd`+`p`
|
||||
| Power on | _Right-click²_ | _Right-click²_
|
||||
| Turn device screen off (keep mirroring)| `Ctrl`+`o` | `Cmd`+`o`
|
||||
| Rotate device screen | `Ctrl`+`r` | `Cmd`+`r`
|
||||
| Expand notification panel | `Ctrl`+`n` | `Cmd`+`n`
|
||||
| Collapse notification panel | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n`
|
||||
| Copy device clipboard to computer | `Ctrl`+`c` | `Cmd`+`c`
|
||||
| Paste computer clipboard to device | `Ctrl`+`v` | `Cmd`+`v`
|
||||
| Copy computer clipboard to device | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v`
|
||||
| Enable/disable FPS counter (on stdout) | `Ctrl`+`i` | `Cmd`+`i`
|
||||
| Action | Shortcut | Shortcut (macOS)
|
||||
| ------------------------------------------- |:----------------------------- |:-----------------------------
|
||||
| Switch fullscreen mode | `Ctrl`+`f` | `Cmd`+`f`
|
||||
| Rotate display left | `Ctrl`+`←` _(left)_ | `Cmd`+`←` _(left)_
|
||||
| Rotate display right | `Ctrl`+`→` _(right)_ | `Cmd`+`→` _(right)_
|
||||
| Resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` | `Cmd`+`g`
|
||||
| Resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ | `Cmd`+`x` \| _Double-click¹_
|
||||
| Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ | `Ctrl`+`h` \| _Middle-click_
|
||||
| Click on `BACK` | `Ctrl`+`b` \| _Right-click²_ | `Cmd`+`b` \| _Right-click²_
|
||||
| Click on `APP_SWITCH` | `Ctrl`+`s` | `Cmd`+`s`
|
||||
| Click on `MENU` | `Ctrl`+`m` | `Ctrl`+`m`
|
||||
| Click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ | `Cmd`+`↑` _(up)_
|
||||
| Click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ | `Cmd`+`↓` _(down)_
|
||||
| Click on `POWER` | `Ctrl`+`p` | `Cmd`+`p`
|
||||
| Power on | _Right-click²_ | _Right-click²_
|
||||
| Turn device screen off (keep mirroring) | `Ctrl`+`o` | `Cmd`+`o`
|
||||
| Turn device screen on | `Ctrl`+`Shift`+`o` | `Cmd`+`Shift`+`o`
|
||||
| Rotate device screen | `Ctrl`+`r` | `Cmd`+`r`
|
||||
| Expand notification panel | `Ctrl`+`n` | `Cmd`+`n`
|
||||
| Collapse notification panel | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n`
|
||||
| Copy device clipboard to computer | `Ctrl`+`c` | `Cmd`+`c`
|
||||
| Paste computer clipboard to device | `Ctrl`+`v` | `Cmd`+`v`
|
||||
| Copy computer clipboard to device and paste | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v`
|
||||
| Enable/disable FPS counter (on stdout) | `Ctrl`+`i` | `Cmd`+`i`
|
||||
|
||||
_¹Double-click on black borders to remove them._
|
||||
_²Right-click turns the screen on if it was off, presses BACK otherwise._
|
||||
|
||||
@@ -129,6 +129,7 @@ Force recording format (either mp4 or mkv).
|
||||
Request SDL to use the given render driver (this is just a hint).
|
||||
|
||||
Supported names are currently "direct3d", "opengl", "opengles2", "opengles", "metal" and "software".
|
||||
|
||||
.UR https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER
|
||||
.UE
|
||||
|
||||
@@ -258,6 +259,10 @@ Turn screen on
|
||||
.B Ctrl+o
|
||||
Turn device screen off (keep mirroring)
|
||||
|
||||
.TP
|
||||
.B Ctrl+Shift+o
|
||||
Turn device screen on
|
||||
|
||||
.TP
|
||||
.B Ctrl+r
|
||||
Rotate device screen
|
||||
@@ -280,7 +285,7 @@ Paste computer clipboard to device
|
||||
|
||||
.TP
|
||||
.B Ctrl+Shift+v
|
||||
Copy computer clipboard to device
|
||||
Copy computer clipboard to device (and paste if the device runs Android >= 7)
|
||||
|
||||
.TP
|
||||
.B Ctrl+i
|
||||
|
||||
@@ -231,6 +231,9 @@ scrcpy_print_usage(const char *arg0) {
|
||||
" " CTRL_OR_CMD "+o\n"
|
||||
" Turn device screen off (keep mirroring)\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+Shift+o\n"
|
||||
" Turn device screen on\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+r\n"
|
||||
" Rotate device screen\n"
|
||||
"\n"
|
||||
@@ -247,7 +250,8 @@ scrcpy_print_usage(const char *arg0) {
|
||||
" Paste computer clipboard to device\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+Shift+v\n"
|
||||
" Copy computer clipboard to device\n"
|
||||
" Copy computer clipboard to device (and paste if the device\n"
|
||||
" runs Android >= 7)\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+i\n"
|
||||
" Enable/disable FPS counter (print frames/second in logs)\n"
|
||||
|
||||
@@ -66,11 +66,15 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
||||
buffer_write32be(&buf[17],
|
||||
(uint32_t) msg->inject_scroll_event.vscroll);
|
||||
return 21;
|
||||
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
||||
buf[1] = msg->get_clipboard.copy;
|
||||
return 2;
|
||||
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
|
||||
size_t len = write_string(msg->inject_text.text,
|
||||
buf[1] = !!msg->set_clipboard.paste;
|
||||
size_t len = write_string(msg->set_clipboard.text,
|
||||
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
|
||||
&buf[1]);
|
||||
return 1 + len;
|
||||
&buf[2]);
|
||||
return 2 + len;
|
||||
}
|
||||
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
||||
buf[1] = msg->set_screen_power_mode.mode;
|
||||
@@ -78,7 +82,6 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
||||
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
||||
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||
case CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
||||
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
||||
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||
// no additional data
|
||||
return 1;
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
#include "common.h"
|
||||
|
||||
#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
|
||||
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4093
|
||||
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4092
|
||||
#define CONTROL_MSG_SERIALIZED_MAX_SIZE \
|
||||
(3 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH)
|
||||
(4 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH)
|
||||
|
||||
#define POINTER_ID_MOUSE UINT64_C(-1);
|
||||
|
||||
@@ -60,8 +60,12 @@ struct control_msg {
|
||||
int32_t hscroll;
|
||||
int32_t vscroll;
|
||||
} inject_scroll_event;
|
||||
struct {
|
||||
bool copy;
|
||||
} get_clipboard;
|
||||
struct {
|
||||
char *text; // owned, to be freed by SDL_free()
|
||||
bool paste;
|
||||
} set_clipboard;
|
||||
struct {
|
||||
enum screen_power_mode mode;
|
||||
|
||||
@@ -92,6 +92,8 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
||||
MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT);
|
||||
MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN);
|
||||
MAP(SDLK_UP, AKEYCODE_DPAD_UP);
|
||||
MAP(SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT);
|
||||
MAP(SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT);
|
||||
}
|
||||
|
||||
if (!(mod & (KMOD_NUM | KMOD_SHIFT))) {
|
||||
|
||||
@@ -102,9 +102,10 @@ collapse_notification_panel(struct controller *controller) {
|
||||
}
|
||||
|
||||
static void
|
||||
request_device_clipboard(struct controller *controller) {
|
||||
request_device_clipboard(struct controller *controller, bool copy) {
|
||||
struct control_msg msg;
|
||||
msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD;
|
||||
msg.get_clipboard.copy = copy;
|
||||
|
||||
if (!controller_push_msg(controller, &msg)) {
|
||||
LOGW("Could not request device clipboard");
|
||||
@@ -112,7 +113,7 @@ request_device_clipboard(struct controller *controller) {
|
||||
}
|
||||
|
||||
static void
|
||||
set_device_clipboard(struct controller *controller) {
|
||||
set_device_clipboard(struct controller *controller, bool paste) {
|
||||
char *text = SDL_GetClipboardText();
|
||||
if (!text) {
|
||||
LOGW("Could not get clipboard text: %s", SDL_GetError());
|
||||
@@ -127,6 +128,7 @@ set_device_clipboard(struct controller *controller) {
|
||||
struct control_msg msg;
|
||||
msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD;
|
||||
msg.set_clipboard.text = text;
|
||||
msg.set_clipboard.paste = paste;
|
||||
|
||||
if (!controller_push_msg(controller, &msg)) {
|
||||
SDL_free(text);
|
||||
@@ -320,8 +322,11 @@ input_manager_process_key(struct input_manager *im,
|
||||
}
|
||||
return;
|
||||
case SDLK_o:
|
||||
if (control && cmd && !shift && down) {
|
||||
set_screen_power_mode(controller, SCREEN_POWER_MODE_OFF);
|
||||
if (control && cmd && down) {
|
||||
enum screen_power_mode mode = shift
|
||||
? SCREEN_POWER_MODE_NORMAL
|
||||
: SCREEN_POWER_MODE_OFF;
|
||||
set_screen_power_mode(controller, mode);
|
||||
}
|
||||
return;
|
||||
case SDLK_DOWN:
|
||||
@@ -348,14 +353,15 @@ input_manager_process_key(struct input_manager *im,
|
||||
return;
|
||||
case SDLK_c:
|
||||
if (control && cmd && !shift && !repeat && down) {
|
||||
request_device_clipboard(controller);
|
||||
// press COPY and get the clipboard content
|
||||
request_device_clipboard(controller, true);
|
||||
}
|
||||
return;
|
||||
case SDLK_v:
|
||||
if (control && cmd && !repeat && down) {
|
||||
if (shift) {
|
||||
// store the text in the device clipboard
|
||||
set_device_clipboard(controller);
|
||||
// store the text in the device clipboard and paste
|
||||
set_device_clipboard(controller, true);
|
||||
} else {
|
||||
// inject the text as input events
|
||||
clipboard_paste(controller);
|
||||
|
||||
@@ -185,14 +185,18 @@ static void test_serialize_collapse_notification_panel(void) {
|
||||
static void test_serialize_get_clipboard(void) {
|
||||
struct control_msg msg = {
|
||||
.type = CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
||||
.get_clipboard = {
|
||||
.copy = true,
|
||||
},
|
||||
};
|
||||
|
||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||
int size = control_msg_serialize(&msg, buf);
|
||||
assert(size == 1);
|
||||
assert(size == 2);
|
||||
|
||||
const unsigned char expected[] = {
|
||||
CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
||||
1, // copy
|
||||
};
|
||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||
}
|
||||
@@ -200,17 +204,19 @@ static void test_serialize_get_clipboard(void) {
|
||||
static void test_serialize_set_clipboard(void) {
|
||||
struct control_msg msg = {
|
||||
.type = CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||
.inject_text = {
|
||||
.set_clipboard = {
|
||||
.paste = true,
|
||||
.text = "hello, world!",
|
||||
},
|
||||
};
|
||||
|
||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||
int size = control_msg_serialize(&msg, buf);
|
||||
assert(size == 16);
|
||||
assert(size == 17);
|
||||
|
||||
const unsigned char expected[] = {
|
||||
CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||
1, // paste
|
||||
0x00, 0x0d, // text length
|
||||
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
project('scrcpy', 'c',
|
||||
version: '1.13',
|
||||
version: '1.14',
|
||||
meson_version: '>= 0.48',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
|
||||
@@ -35,6 +35,6 @@ prepare-sdl2:
|
||||
SDL2-2.0.12
|
||||
|
||||
prepare-adb:
|
||||
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r29.0.5-windows.zip \
|
||||
2df06160056ec9a84c7334af2a1e42740befbb1a2e34370e7af544a2cc78152c \
|
||||
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r30.0.0-windows.zip \
|
||||
854305f9a702f5ea2c3de73edde402bd26afa0ee944c9b0c4380420f5a862e0d \
|
||||
platform-tools
|
||||
|
||||
@@ -6,8 +6,8 @@ android {
|
||||
applicationId "com.genymobile.scrcpy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
versionCode 15
|
||||
versionName "1.13"
|
||||
versionCode 16
|
||||
versionName "1.14"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
set -e
|
||||
|
||||
SCRCPY_DEBUG=false
|
||||
SCRCPY_VERSION_NAME=1.13
|
||||
SCRCPY_VERSION_NAME=1.14
|
||||
|
||||
PLATFORM=${ANDROID_PLATFORM:-29}
|
||||
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2}
|
||||
|
||||
@@ -17,6 +17,9 @@ public final class ControlMessage {
|
||||
public static final int TYPE_SET_SCREEN_POWER_MODE = 9;
|
||||
public static final int TYPE_ROTATE_DEVICE = 10;
|
||||
|
||||
public static final int FLAGS_PASTE = 1;
|
||||
public static final int FLAGS_COPY = 2;
|
||||
|
||||
private int type;
|
||||
private String text;
|
||||
private int metaState; // KeyEvent.META_*
|
||||
@@ -28,6 +31,7 @@ public final class ControlMessage {
|
||||
private Position position;
|
||||
private int hScroll;
|
||||
private int vScroll;
|
||||
private int flags;
|
||||
|
||||
private ControlMessage() {
|
||||
}
|
||||
@@ -68,10 +72,22 @@ public final class ControlMessage {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static ControlMessage createSetClipboard(String text) {
|
||||
public static ControlMessage createSetClipboard(String text, boolean paste) {
|
||||
ControlMessage msg = new ControlMessage();
|
||||
msg.type = TYPE_SET_CLIPBOARD;
|
||||
msg.text = text;
|
||||
if (paste) {
|
||||
msg.flags = FLAGS_PASTE;
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static ControlMessage createGetClipboard(boolean copy) {
|
||||
ControlMessage msg = new ControlMessage();
|
||||
msg.type = TYPE_GET_CLIPBOARD;
|
||||
if (copy) {
|
||||
msg.flags = FLAGS_COPY;
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
@@ -134,4 +150,8 @@ public final class ControlMessage {
|
||||
public int getVScroll() {
|
||||
return vScroll;
|
||||
}
|
||||
|
||||
public int getFlags() {
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,10 @@ public class ControlMessageReader {
|
||||
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
|
||||
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
||||
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
||||
static final int GET_CLIPBOARD_PAYLOAD_LENGTH = 1;
|
||||
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1;
|
||||
|
||||
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093;
|
||||
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4092; // 4096 - 1 (type) - 1 (parse flag) - 2 (length)
|
||||
public static final int INJECT_TEXT_MAX_LENGTH = 300;
|
||||
|
||||
private static final int RAW_BUFFER_SIZE = 4096;
|
||||
@@ -66,6 +68,9 @@ public class ControlMessageReader {
|
||||
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
|
||||
msg = parseInjectScrollEvent();
|
||||
break;
|
||||
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||
msg = parseGetClipboard();
|
||||
break;
|
||||
case ControlMessage.TYPE_SET_CLIPBOARD:
|
||||
msg = parseSetClipboard();
|
||||
break;
|
||||
@@ -75,7 +80,6 @@ public class ControlMessageReader {
|
||||
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
||||
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||
case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
||||
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||
case ControlMessage.TYPE_ROTATE_DEVICE:
|
||||
msg = ControlMessage.createEmpty(type);
|
||||
break;
|
||||
@@ -147,12 +151,24 @@ public class ControlMessageReader {
|
||||
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll);
|
||||
}
|
||||
|
||||
private ControlMessage parseGetClipboard() {
|
||||
if (buffer.remaining() < GET_CLIPBOARD_PAYLOAD_LENGTH) {
|
||||
return null;
|
||||
}
|
||||
boolean copy = buffer.get() != 0;
|
||||
return ControlMessage.createGetClipboard(copy);
|
||||
}
|
||||
|
||||
private ControlMessage parseSetClipboard() {
|
||||
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
|
||||
return null;
|
||||
}
|
||||
boolean parse = buffer.get() != 0;
|
||||
String text = parseString();
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
return ControlMessage.createSetClipboard(text);
|
||||
return ControlMessage.createSetClipboard(text, parse);
|
||||
}
|
||||
|
||||
private ControlMessage parseSetScreenPowerMode() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.SystemClock;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyCharacterMap;
|
||||
@@ -103,14 +104,12 @@ public class Controller {
|
||||
device.collapsePanels();
|
||||
break;
|
||||
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||
String clipboardText = device.getClipboardText();
|
||||
sender.pushClipboardText(clipboardText);
|
||||
boolean copy = (msg.getFlags() & ControlMessage.FLAGS_COPY) != 0;
|
||||
getClipboard(copy);
|
||||
break;
|
||||
case ControlMessage.TYPE_SET_CLIPBOARD:
|
||||
boolean setClipboardOk = device.setClipboardText(msg.getText());
|
||||
if (setClipboardOk) {
|
||||
Ln.i("Device clipboard set");
|
||||
}
|
||||
boolean paste = (msg.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
|
||||
setClipboard(msg.getText(), paste);
|
||||
break;
|
||||
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
||||
if (device.supportsInputEvents()) {
|
||||
@@ -149,10 +148,6 @@ public class Controller {
|
||||
}
|
||||
|
||||
private int injectText(String text) {
|
||||
if (device.injectTextPaste(text)) {
|
||||
// The best method (fastest and UTF-8) worked!
|
||||
return text.length();
|
||||
}
|
||||
int successCount = 0;
|
||||
for (char c : text.toCharArray()) {
|
||||
if (!injectChar(c)) {
|
||||
@@ -231,4 +226,31 @@ public class Controller {
|
||||
int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER;
|
||||
return device.injectKeycode(keycode);
|
||||
}
|
||||
|
||||
private void getClipboard(boolean copy) {
|
||||
// On Android >= 7, also press the COPY key if requested
|
||||
if (copy && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) {
|
||||
// Must wait until the COPY has been executed
|
||||
device.injectCopyKeycode();
|
||||
}
|
||||
|
||||
String clipboardText = device.getClipboardText();
|
||||
if (clipboardText != null) {
|
||||
sender.pushClipboardText(clipboardText);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean setClipboard(String text, boolean paste) {
|
||||
boolean ok = device.setClipboardText(text);
|
||||
if (ok) {
|
||||
Ln.i("Device clipboard set");
|
||||
}
|
||||
|
||||
// On Android >= 7, also press the PASTE key if requested
|
||||
if (paste && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) {
|
||||
device.injectPasteKeycode();
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,25 +186,16 @@ public final class Device {
|
||||
return injectKeycode(keyCode, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
|
||||
}
|
||||
|
||||
public boolean injectTextPaste(String text) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// On Android >= 7, we can inject UTF-8 text as follow:
|
||||
// - set the clipboard
|
||||
// - inject the PASTE key event
|
||||
// - restore the clipboard
|
||||
|
||||
String clipboardBackup = getClipboardText();
|
||||
public boolean injectCopyKeycode() {
|
||||
isSettingClipboard.set(true);
|
||||
|
||||
rawSetClipboardText(text);
|
||||
boolean ok = injectKeycode(KeyEvent.KEYCODE_PASTE, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT);
|
||||
rawSetClipboardText(clipboardBackup);
|
||||
|
||||
// Must wait until the COPY has been executed
|
||||
boolean ret = injectKeycode(KeyEvent.KEYCODE_COPY, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
|
||||
isSettingClipboard.set(false);
|
||||
return ok;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public boolean injectPasteKeycode() {
|
||||
return injectKeycode(KeyEvent.KEYCODE_PASTE);
|
||||
}
|
||||
|
||||
public boolean isScreenOn() {
|
||||
@@ -235,13 +226,9 @@ public final class Device {
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
private boolean rawSetClipboardText(String text) {
|
||||
return serviceManager.getClipboardManager().setText(text);
|
||||
}
|
||||
|
||||
public boolean setClipboardText(String text) {
|
||||
isSettingClipboard.set(true);
|
||||
boolean ok = rawSetClipboardText(text);
|
||||
boolean ok = serviceManager.getClipboardManager().setText(text);
|
||||
isSettingClipboard.set(false);
|
||||
return ok;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ public final class Ln {
|
||||
DEBUG, INFO, WARN, ERROR
|
||||
}
|
||||
|
||||
private static Level threshold;
|
||||
private static Level threshold = Level.INFO;
|
||||
|
||||
private Ln() {
|
||||
// not instantiable
|
||||
|
||||
@@ -200,6 +200,7 @@ public class ControlMessageReaderTest {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(bos);
|
||||
dos.writeByte(ControlMessage.TYPE_GET_CLIPBOARD);
|
||||
dos.writeByte(1); // copy
|
||||
|
||||
byte[] packet = bos.toByteArray();
|
||||
|
||||
@@ -207,6 +208,7 @@ public class ControlMessageReaderTest {
|
||||
ControlMessage event = reader.next();
|
||||
|
||||
Assert.assertEquals(ControlMessage.TYPE_GET_CLIPBOARD, event.getType());
|
||||
Assert.assertEquals(1, (event.getFlags() & ControlMessage.FLAGS_COPY));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -216,6 +218,7 @@ public class ControlMessageReaderTest {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(bos);
|
||||
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
|
||||
dos.writeByte(1); // paste
|
||||
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
|
||||
dos.writeShort(text.length);
|
||||
dos.write(text);
|
||||
@@ -227,6 +230,9 @@ public class ControlMessageReaderTest {
|
||||
|
||||
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
||||
Assert.assertEquals("testé", event.getText());
|
||||
|
||||
boolean parse = (event.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
|
||||
Assert.assertTrue(parse);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -238,6 +244,7 @@ public class ControlMessageReaderTest {
|
||||
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
|
||||
|
||||
byte[] rawText = new byte[ControlMessageReader.CLIPBOARD_TEXT_MAX_LENGTH];
|
||||
dos.writeByte(1); // paste
|
||||
Arrays.fill(rawText, (byte) 'a');
|
||||
String text = new String(rawText, 0, rawText.length);
|
||||
|
||||
@@ -251,6 +258,9 @@ public class ControlMessageReaderTest {
|
||||
|
||||
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
||||
Assert.assertEquals(text, event.getText());
|
||||
|
||||
boolean parse = (event.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
|
||||
Assert.assertTrue(parse);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user