Compare commits
33 Commits
input_even
...
hidmouse
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43aff4af73 | ||
|
|
cba84f6999 | ||
|
|
ed2e45ee29 | ||
|
|
aee1b39790 | ||
|
|
17d01b5bf7 | ||
|
|
40fca82b60 | ||
|
|
643293752d | ||
|
|
b5855e5deb | ||
|
|
924375487e | ||
|
|
7121a0dc53 | ||
|
|
f04812fc71 | ||
|
|
5ce1ccde85 | ||
|
|
6102a0b5bb | ||
|
|
2b34e1224e | ||
|
|
a9d23400cd | ||
|
|
cca3c953da | ||
|
|
57f1655d4b | ||
|
|
bc674721dc | ||
|
|
63e29b1782 | ||
|
|
3c15cbdaf8 | ||
|
|
96e0e89740 | ||
|
|
a1f2f5fbd3 | ||
|
|
9460bdd87b | ||
|
|
b4b638e8fe | ||
|
|
e4396e34c2 | ||
|
|
b8fed50639 | ||
|
|
d540c72e7c | ||
|
|
cd5891fee6 | ||
|
|
26ee7ce566 | ||
|
|
ba28d817fb | ||
|
|
37124e1452 | ||
|
|
6b9f397733 | ||
|
|
1fbc590b26 |
2
LICENSE
2
LICENSE
@@ -188,7 +188,7 @@
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright (C) 2018 Genymobile
|
||||
Copyright (C) 2018-2021 Romain Vimont
|
||||
Copyright (C) 2018-2022 Romain Vimont
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -672,7 +672,7 @@ Baca [halaman pengembang].
|
||||
## Lisensi
|
||||
|
||||
Copyright (C) 2018 Genymobile
|
||||
Copyright (C) 2018-2021 Romain Vimont
|
||||
Copyright (C) 2018-2022 Romain Vimont
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -790,7 +790,7 @@ Leggi la [pagina per sviluppatori].
|
||||
## Licenza (in inglese)
|
||||
|
||||
Copyright (C) 2018 Genymobile
|
||||
Copyright (C) 2018-2021 Romain Vimont
|
||||
Copyright (C) 2018-2022 Romain Vimont
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -776,7 +776,7 @@ _⁴Android 7以上のみ._
|
||||
## ライセンス
|
||||
|
||||
Copyright (C) 2018 Genymobile
|
||||
Copyright (C) 2018-2021 Romain Vimont
|
||||
Copyright (C) 2018-2022 Romain Vimont
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -475,7 +475,7 @@ _²화면이 꺼진 상태에서 우클릭 시 다시 켜지며, 그 외의 상
|
||||
## 라이선스
|
||||
|
||||
Copyright (C) 2018 Genymobile
|
||||
Copyright (C) 2018-2021 Romain Vimont
|
||||
Copyright (C) 2018-2022 Romain Vimont
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
33
README.md
33
README.md
@@ -31,6 +31,8 @@ Its features include:
|
||||
- device screen [as a webcam (V4L2)](#v4l2loopback) (Linux-only)
|
||||
- [physical keyboard simulation (HID)](#physical-keyboard-simulation-hid)
|
||||
(Linux-only)
|
||||
- [physical mouse simulation (HID)](#physical-mouse-simulation-hid)
|
||||
(Linux-only)
|
||||
- and more…
|
||||
|
||||
## Requirements
|
||||
@@ -815,6 +817,35 @@ a physical keyboard is connected).
|
||||
|
||||
[Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915
|
||||
|
||||
#### Physical mouse simulation (HID)
|
||||
|
||||
Similarly to the physical keyboard simulation, it is possible to simulate a
|
||||
physical mouse. Likewise, it only works if the device is connected by USB, and
|
||||
is currently only supported on Linux.
|
||||
|
||||
By default, scrcpy uses Android mouse events injection, using absolute
|
||||
coordinates. By simulating a physical mouse, a mouse pointer appears on the
|
||||
Android device, and relative mouse motion, clicks and scrolls are injected.
|
||||
|
||||
To enable this mode:
|
||||
|
||||
```bash
|
||||
scrcpy --hid-mouse
|
||||
scrcpy -M # short version
|
||||
```
|
||||
|
||||
You could also add `--forward-all-clicks` to [forward all mouse
|
||||
buttons][forward_all_clicks].
|
||||
|
||||
[forward_all_clicks]: #right-click-and-middle-click
|
||||
|
||||
When this mode is enabled, the computer mouse is "captured" (the mouse pointer
|
||||
disappears from the computer and appears on the Android device instead).
|
||||
|
||||
Special capture keys, either <kbd>Alt</kbd> or <kbd>Super</kbd>, toggle
|
||||
(disable or enable) the mouse capture. Use one of them to give the control of
|
||||
the mouse back to the computer.
|
||||
|
||||
|
||||
#### Text injection preference
|
||||
|
||||
@@ -1017,7 +1048,7 @@ Read the [developers page].
|
||||
## Licence
|
||||
|
||||
Copyright (C) 2018 Genymobile
|
||||
Copyright (C) 2018-2021 Romain Vimont
|
||||
Copyright (C) 2018-2022 Romain Vimont
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -857,7 +857,7 @@ Leia a [página dos desenvolvedores][developers page].
|
||||
## Licença
|
||||
|
||||
Copyright (C) 2018 Genymobile
|
||||
Copyright (C) 2018-2021 Romain Vimont
|
||||
Copyright (C) 2018-2022 Romain Vimont
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -720,7 +720,7 @@ Lea la [hoja de desarrolladores (en inglés)](DEVELOP.md).
|
||||
## Licencia
|
||||
|
||||
Copyright (C) 2018 Genymobile
|
||||
Copyright (C) 2018-2021 Romain Vimont
|
||||
Copyright (C) 2018-2022 Romain Vimont
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -801,7 +801,7 @@ Bakınız [FAQ](FAQ.md).
|
||||
## Lisans
|
||||
|
||||
Copyright (C) 2018 Genymobile
|
||||
Copyright (C) 2018-2021 Romain Vimont
|
||||
Copyright (C) 2018-2022 Romain Vimont
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -842,7 +842,7 @@ ADB=/path/to/adb scrcpy
|
||||
## 许可协议
|
||||
|
||||
Copyright (C) 2018 Genymobile
|
||||
Copyright (C) 2018-2021 Romain Vimont
|
||||
Copyright (C) 2018-2022 Romain Vimont
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -679,7 +679,7 @@ _³只支援 Android 7+。_
|
||||
## Licence
|
||||
|
||||
Copyright (C) 2018 Genymobile
|
||||
Copyright (C) 2018-2021 Romain Vimont
|
||||
Copyright (C) 2018-2022 Romain Vimont
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -77,6 +77,7 @@ if aoa_hid_support
|
||||
src += [
|
||||
'src/aoa_hid.c',
|
||||
'src/hid_keyboard.c',
|
||||
'src/hid_mouse.c',
|
||||
]
|
||||
endif
|
||||
|
||||
|
||||
16
app/scrcpy.1
16
app/scrcpy.1
@@ -96,6 +96,8 @@ The keyboard layout must be configured (once and for all) on the device, via Set
|
||||
|
||||
However, the option is only available when the HID keyboard is enabled (or a physical keyboard is connected).
|
||||
|
||||
Also see \fB\-\-hid\-mouse\fR.
|
||||
|
||||
.TP
|
||||
.B \-\-legacy\-paste
|
||||
Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v).
|
||||
@@ -120,6 +122,18 @@ Limit both the width and height of the video to \fIvalue\fR. The other dimension
|
||||
|
||||
Default is 0 (unlimited).
|
||||
|
||||
.TP
|
||||
.B \-M, \-\-hid\-mouse
|
||||
Simulate a physical mouse by using HID over AOAv2.
|
||||
|
||||
In this mode, the computer mouse is captured to control the device directly (relative mouse mode).
|
||||
|
||||
LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse back to the computer.
|
||||
|
||||
It may only work over USB, and is currently only supported on Linux.
|
||||
|
||||
Also see \fB\-\-hid\-keyboard\fR.
|
||||
|
||||
.TP
|
||||
.B \-\-no\-clipboard\-autosync
|
||||
By default, scrcpy automatically synchronizes the computer clipboard to the device clipboard before injecting Ctrl+v, and the device clipboard to the computer clipboard whenever it changes.
|
||||
@@ -446,7 +460,7 @@ Copyright \(co 2018 Genymobile
|
||||
Genymobile
|
||||
.UE
|
||||
|
||||
Copyright \(co 2018\-2021
|
||||
Copyright \(co 2018\-2022
|
||||
.MT rom@rom1v.com
|
||||
Romain Vimont
|
||||
.ME
|
||||
|
||||
@@ -178,7 +178,8 @@ static const struct sc_option options[] = {
|
||||
"directly: `adb shell am start -a "
|
||||
"android.settings.HARD_KEYBOARD_SETTINGS`.\n"
|
||||
"However, the option is only available when the HID keyboard "
|
||||
"is enabled (or a physical keyboard is connected).",
|
||||
"is enabled (or a physical keyboard is connected).\n"
|
||||
"Also see --hid-mouse.",
|
||||
},
|
||||
{
|
||||
.shortopt = 'h',
|
||||
@@ -214,6 +215,18 @@ static const struct sc_option options[] = {
|
||||
.text = "Limit the frame rate of screen capture (officially supported "
|
||||
"since Android 10, but may work on earlier versions).",
|
||||
},
|
||||
{
|
||||
.shortopt = 'M',
|
||||
.longopt = "hid-mouse",
|
||||
.text = "Simulate a physical mouse by using HID over AOAv2.\n"
|
||||
"In this mode, the computer mouse is captured to control the "
|
||||
"device directly (relative mouse mode).\n"
|
||||
"LAlt, LSuper or RSuper toggle the capture mode, to give "
|
||||
"control of the mouse back to the computer.\n"
|
||||
"It may only work over USB, and is currently only supported "
|
||||
"on Linux.\n"
|
||||
"Also see --hid-keyboard.",
|
||||
},
|
||||
{
|
||||
.shortopt = 'm',
|
||||
.longopt = "max-size",
|
||||
@@ -240,11 +253,8 @@ static const struct sc_option options[] = {
|
||||
{
|
||||
.shortopt = 'N',
|
||||
.longopt = "no-display",
|
||||
.text = "Do not display device (only when screen recording "
|
||||
#ifdef HAVE_V4L2
|
||||
"or V4L2 sink "
|
||||
#endif
|
||||
"is enabled).",
|
||||
.text = "Do not display device (only when screen recording or V4L2 "
|
||||
"sink is enabled).",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_NO_KEY_REPEAT,
|
||||
@@ -381,14 +391,14 @@ static const struct sc_option options[] = {
|
||||
"Default is 0 (not forced): the local port used for "
|
||||
"establishing the tunnel will be used.",
|
||||
},
|
||||
#ifdef HAVE_V4L2
|
||||
{
|
||||
.longopt_id = OPT_V4L2_SINK,
|
||||
.longopt = "v4l2-sink",
|
||||
.argdesc = "/dev/videoN",
|
||||
.text = "Output to v4l2loopback device.\n"
|
||||
"It requires to lock the video orientation (see "
|
||||
"--lock-video-orientation).",
|
||||
"--lock-video-orientation).\n"
|
||||
"This feature is only available on Linux.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_V4L2_BUFFER,
|
||||
@@ -398,9 +408,9 @@ static const struct sc_option options[] = {
|
||||
"frames. This increases latency to compensate for jitter.\n"
|
||||
"This option is similar to --display-buffer, but specific to "
|
||||
"V4L2 sink.\n"
|
||||
"Default is 0 (no buffering).",
|
||||
"Default is 0 (no buffering).\n"
|
||||
"This option is only available on Linux.",
|
||||
},
|
||||
#endif
|
||||
{
|
||||
.shortopt = 'V',
|
||||
.longopt = "verbosity",
|
||||
@@ -1300,7 +1310,13 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
args->help = true;
|
||||
break;
|
||||
case 'K':
|
||||
#ifdef HAVE_AOA_HID
|
||||
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_HID;
|
||||
#else
|
||||
LOGE("HID over AOA (-K/--hid-keyboard) is not supported on "
|
||||
"this platform. It is only available on Linux.");
|
||||
return false;
|
||||
#endif
|
||||
break;
|
||||
case OPT_MAX_FPS:
|
||||
if (!parse_max_fps(optarg, &opts->max_fps)) {
|
||||
@@ -1312,6 +1328,15 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'M':
|
||||
#ifdef HAVE_AOA_HID
|
||||
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_HID;
|
||||
#else
|
||||
LOGE("HID over AOA (-M/--hid-mouse) is not supported on this"
|
||||
"platform. It is only available on Linux.");
|
||||
return false;
|
||||
#endif
|
||||
break;
|
||||
case OPT_LOCK_VIDEO_ORIENTATION:
|
||||
if (!parse_lock_video_orientation(optarg,
|
||||
&opts->lock_video_orientation)) {
|
||||
@@ -1464,16 +1489,24 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
opts->tcpip = true;
|
||||
opts->tcpip_dst = optarg;
|
||||
break;
|
||||
#ifdef HAVE_V4L2
|
||||
case OPT_V4L2_SINK:
|
||||
#ifdef HAVE_V4L2
|
||||
opts->v4l2_device = optarg;
|
||||
#else
|
||||
LOGE("V4L2 (--v4l2-sink) is only available on Linux.");
|
||||
return false;
|
||||
#endif
|
||||
break;
|
||||
case OPT_V4L2_BUFFER:
|
||||
#ifdef HAVE_V4L2
|
||||
if (!parse_buffering_time(optarg, &opts->v4l2_buffer)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
#else
|
||||
LOGE("V4L2 (--v4l2-buffer) is only available on Linux.");
|
||||
return false;
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
// getopt prints the error message on stderr
|
||||
return false;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
|
||||
#define MIN(X,Y) (X) < (Y) ? (X) : (Y)
|
||||
#define MAX(X,Y) (X) > (Y) ? (X) : (Y)
|
||||
#define CLAMP(V,X,Y) MIN( MAX((V),(X)), (Y) )
|
||||
|
||||
#define container_of(ptr, type, member) \
|
||||
((type *) (((char *) (ptr)) - offsetof(type, member)))
|
||||
|
||||
@@ -119,7 +119,8 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
||||
(uint32_t) msg->inject_scroll_event.hscroll);
|
||||
buffer_write32be(&buf[17],
|
||||
(uint32_t) msg->inject_scroll_event.vscroll);
|
||||
return 21;
|
||||
buffer_write32be(&buf[21], msg->inject_scroll_event.buttons);
|
||||
return 25;
|
||||
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
||||
buf[1] = msg->inject_keycode.action;
|
||||
return 2;
|
||||
@@ -192,11 +193,12 @@ control_msg_log(const struct control_msg *msg) {
|
||||
}
|
||||
case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
|
||||
LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%" PRIi32
|
||||
" vscroll=%" PRIi32,
|
||||
" vscroll=%" PRIi32 " buttons=%06lx",
|
||||
msg->inject_scroll_event.position.point.x,
|
||||
msg->inject_scroll_event.position.point.y,
|
||||
msg->inject_scroll_event.hscroll,
|
||||
msg->inject_scroll_event.vscroll);
|
||||
msg->inject_scroll_event.vscroll,
|
||||
(long) msg->inject_scroll_event.buttons);
|
||||
break;
|
||||
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
||||
LOG_CMSG("back-or-screen-on %s",
|
||||
|
||||
@@ -70,6 +70,7 @@ struct control_msg {
|
||||
struct sc_position position;
|
||||
int32_t hscroll;
|
||||
int32_t vscroll;
|
||||
enum android_motionevent_buttons buttons;
|
||||
} inject_scroll_event;
|
||||
struct {
|
||||
enum android_keyevent_action action; // action for the BACK key
|
||||
|
||||
@@ -387,15 +387,6 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sc_key_processor_process_text(struct sc_key_processor *kp,
|
||||
const struct sc_text_event *event) {
|
||||
(void) kp;
|
||||
(void) event;
|
||||
|
||||
// Never forward text input via HID (all the keys are injected separately)
|
||||
}
|
||||
|
||||
bool
|
||||
sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) {
|
||||
kb->aoa = aoa;
|
||||
@@ -415,7 +406,9 @@ sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) {
|
||||
|
||||
static const struct sc_key_processor_ops ops = {
|
||||
.process_key = sc_key_processor_process_key,
|
||||
.process_text = sc_key_processor_process_text,
|
||||
// Never forward text input via HID (all the keys are injected
|
||||
// separately)
|
||||
.process_text = NULL,
|
||||
};
|
||||
|
||||
// Clipboard synchronization is requested over the control socket, while HID
|
||||
|
||||
267
app/src/hid_mouse.c
Normal file
267
app/src/hid_mouse.c
Normal file
@@ -0,0 +1,267 @@
|
||||
#include "hid_mouse.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "input_events.h"
|
||||
#include "util/log.h"
|
||||
|
||||
/** Downcast mouse processor to hid_mouse */
|
||||
#define DOWNCAST(MP) container_of(MP, struct sc_hid_mouse, mouse_processor)
|
||||
|
||||
#define HID_MOUSE_ACCESSORY_ID 2
|
||||
|
||||
// 1 byte for buttons + padding, 1 byte for X position, 1 byte for Y position
|
||||
#define HID_MOUSE_EVENT_SIZE 4
|
||||
|
||||
/**
|
||||
* Mouse descriptor from the specification:
|
||||
* <https://www.usb.org/sites/default/files/hid1_11.pdf>
|
||||
*
|
||||
* Appendix E (p71): §E.10 Report Descriptor (Mouse)
|
||||
*
|
||||
* The usage tags (like Wheel) are listed in "HID Usage Tables":
|
||||
* <https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf>
|
||||
* §4 Generic Desktop Page (0x01) (p26)
|
||||
*/
|
||||
static const unsigned char mouse_report_desc[] = {
|
||||
// Usage Page (Generic Desktop)
|
||||
0x05, 0x01,
|
||||
// Usage (Mouse)
|
||||
0x09, 0x02,
|
||||
|
||||
// Collection (Application)
|
||||
0xA1, 0x01,
|
||||
|
||||
// Usage (Pointer)
|
||||
0x09, 0x01,
|
||||
|
||||
// Collection (Physical)
|
||||
0xA1, 0x00,
|
||||
|
||||
// Usage Page (Buttons)
|
||||
0x05, 0x09,
|
||||
|
||||
// Usage Minimum (1)
|
||||
0x19, 0x01,
|
||||
// Usage Maximum (5)
|
||||
0x29, 0x05,
|
||||
// Logical Minimum (0)
|
||||
0x15, 0x00,
|
||||
// Logical Maximum (1)
|
||||
0x25, 0x01,
|
||||
// Report Count (5)
|
||||
0x95, 0x05,
|
||||
// Report Size (1)
|
||||
0x75, 0x01,
|
||||
// Input (Data, Variable, Absolute): 5 buttons bits
|
||||
0x81, 0x02,
|
||||
|
||||
// Report Count (1)
|
||||
0x95, 0x01,
|
||||
// Report Size (3)
|
||||
0x75, 0x03,
|
||||
// Input (Constant): 3 bits padding
|
||||
0x81, 0x01,
|
||||
|
||||
// Usage Page (Generic Desktop)
|
||||
0x05, 0x01,
|
||||
// Usage (X)
|
||||
0x09, 0x30,
|
||||
// Usage (Y)
|
||||
0x09, 0x31,
|
||||
// Usage (Wheel)
|
||||
0x09, 0x38,
|
||||
// Local Minimum (-127)
|
||||
0x15, 0x81,
|
||||
// Local Maximum (127)
|
||||
0x25, 0x7F,
|
||||
// Report Size (8)
|
||||
0x75, 0x08,
|
||||
// Report Count (3)
|
||||
0x95, 0x03,
|
||||
// Input (Data, Variable, Relative): 3 position bytes (X, Y, Wheel)
|
||||
0x81, 0x06,
|
||||
|
||||
// End Collection
|
||||
0xC0,
|
||||
|
||||
// End Collection
|
||||
0xC0,
|
||||
};
|
||||
|
||||
/**
|
||||
* A mouse HID event is 3 bytes long:
|
||||
*
|
||||
* - byte 0: buttons state
|
||||
* - byte 1: relative x motion (signed byte from -127 to 127)
|
||||
* - byte 2: relative y motion (signed byte from -127 to 127)
|
||||
*
|
||||
* 7 6 5 4 3 2 1 0
|
||||
* +---------------+
|
||||
* byte 0: |0 0 0 . . . . .| buttons state
|
||||
* +---------------+
|
||||
* ^ ^ ^ ^ ^
|
||||
* | | | | `- left button
|
||||
* | | | `--- right button
|
||||
* | | `----- middle button
|
||||
* | `------- button 4
|
||||
* `--------- button 5
|
||||
*
|
||||
* +---------------+
|
||||
* byte 1: |. . . . . . . .| relative x motion
|
||||
* +---------------+
|
||||
* byte 2: |. . . . . . . .| relative y motion
|
||||
* +---------------+
|
||||
* byte 3: |. . . . . . . .| wheel motion (-1, 0 or 1)
|
||||
* +---------------+
|
||||
*
|
||||
* As an example, here is the report for a motion of (x=5, y=-4) with left
|
||||
* button pressed:
|
||||
*
|
||||
* +---------------+
|
||||
* |0 0 0 0 0 0 0 1| left button pressed
|
||||
* +---------------+
|
||||
* |0 0 0 0 0 1 0 1| horizontal motion (x = 5)
|
||||
* +---------------+
|
||||
* |1 1 1 1 1 1 0 0| relative y motion (y = -4)
|
||||
* +---------------+
|
||||
* |0 0 0 0 0 0 0 0| wheel motion
|
||||
* +---------------+
|
||||
*/
|
||||
|
||||
static bool
|
||||
sc_hid_mouse_event_init(struct sc_hid_event *hid_event) {
|
||||
unsigned char *buffer = calloc(1, HID_MOUSE_EVENT_SIZE);
|
||||
if (!buffer) {
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
|
||||
sc_hid_event_init(hid_event, HID_MOUSE_ACCESSORY_ID, buffer,
|
||||
HID_MOUSE_EVENT_SIZE);
|
||||
return true;
|
||||
}
|
||||
|
||||
static unsigned char
|
||||
buttons_state_to_hid_buttons(uint8_t buttons_state) {
|
||||
unsigned char c = 0;
|
||||
if (buttons_state & SC_MOUSE_BUTTON_LEFT) {
|
||||
c |= 1 << 0;
|
||||
}
|
||||
if (buttons_state & SC_MOUSE_BUTTON_RIGHT) {
|
||||
c |= 1 << 1;
|
||||
}
|
||||
if (buttons_state & SC_MOUSE_BUTTON_MIDDLE) {
|
||||
c |= 1 << 2;
|
||||
}
|
||||
if (buttons_state & SC_MOUSE_BUTTON_X1) {
|
||||
c |= 1 << 3;
|
||||
}
|
||||
if (buttons_state & SC_MOUSE_BUTTON_X2) {
|
||||
c |= 1 << 4;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
static void
|
||||
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
||||
const struct sc_mouse_motion_event *event) {
|
||||
struct sc_hid_mouse *mouse = DOWNCAST(mp);
|
||||
|
||||
struct sc_hid_event hid_event;
|
||||
if (!sc_hid_mouse_event_init(&hid_event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned char *buffer = hid_event.buffer;
|
||||
buffer[0] = buttons_state_to_hid_buttons(event->buttons_state);
|
||||
buffer[1] = CLAMP(event->xrel, -127, 127);
|
||||
buffer[2] = CLAMP(event->yrel, -127, 127);
|
||||
buffer[3] = 0; // wheel coordinates only used for scrolling
|
||||
|
||||
if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) {
|
||||
sc_hid_event_destroy(&hid_event);
|
||||
LOGW("Could request HID event");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
|
||||
const struct sc_mouse_click_event *event) {
|
||||
struct sc_hid_mouse *mouse = DOWNCAST(mp);
|
||||
|
||||
struct sc_hid_event hid_event;
|
||||
if (!sc_hid_mouse_event_init(&hid_event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned char *buffer = hid_event.buffer;
|
||||
buffer[0] = buttons_state_to_hid_buttons(event->buttons_state);
|
||||
buffer[1] = 0; // no x motion
|
||||
buffer[2] = 0; // no y motion
|
||||
buffer[3] = 0; // wheel coordinates only used for scrolling
|
||||
|
||||
if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) {
|
||||
sc_hid_event_destroy(&hid_event);
|
||||
LOGW("Could request HID event");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
|
||||
const struct sc_mouse_scroll_event *event) {
|
||||
struct sc_hid_mouse *mouse = DOWNCAST(mp);
|
||||
|
||||
struct sc_hid_event hid_event;
|
||||
if (!sc_hid_mouse_event_init(&hid_event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned char *buffer = hid_event.buffer;
|
||||
buffer[0] = 0; // buttons state irrelevant (and unknown)
|
||||
buffer[1] = 0; // no x motion
|
||||
buffer[2] = 0; // no y motion
|
||||
// In practice, vscroll is always -1, 0 or 1, but in theory other values
|
||||
// are possible
|
||||
buffer[3] = CLAMP(event->vscroll, -127, 127);
|
||||
// Horizontal scrolling ignored
|
||||
|
||||
if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) {
|
||||
sc_hid_event_destroy(&hid_event);
|
||||
LOGW("Could request HID event");
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
sc_hid_mouse_init(struct sc_hid_mouse *mouse, struct sc_aoa *aoa) {
|
||||
mouse->aoa = aoa;
|
||||
|
||||
bool ok = sc_aoa_setup_hid(aoa, HID_MOUSE_ACCESSORY_ID, mouse_report_desc,
|
||||
ARRAY_LEN(mouse_report_desc));
|
||||
if (!ok) {
|
||||
LOGW("Register HID mouse failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
static const struct sc_mouse_processor_ops ops = {
|
||||
.process_mouse_motion = sc_mouse_processor_process_mouse_motion,
|
||||
.process_mouse_click = sc_mouse_processor_process_mouse_click,
|
||||
.process_mouse_scroll = sc_mouse_processor_process_mouse_scroll,
|
||||
// Touch events not supported (coordinates are not relative)
|
||||
.process_touch = NULL,
|
||||
};
|
||||
|
||||
mouse->mouse_processor.ops = &ops;
|
||||
|
||||
mouse->mouse_processor.relative_mode = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_hid_mouse_destroy(struct sc_hid_mouse *mouse) {
|
||||
bool ok = sc_aoa_unregister_hid(mouse->aoa, HID_MOUSE_ACCESSORY_ID);
|
||||
if (!ok) {
|
||||
LOGW("Could not unregister HID");
|
||||
}
|
||||
}
|
||||
23
app/src/hid_mouse.h
Normal file
23
app/src/hid_mouse.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#ifndef HID_MOUSE_H
|
||||
#define HID_MOUSE_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "aoa_hid.h"
|
||||
#include "trait/mouse_processor.h"
|
||||
|
||||
struct sc_hid_mouse {
|
||||
struct sc_mouse_processor mouse_processor; // mouse processor trait
|
||||
|
||||
struct sc_aoa *aoa;
|
||||
};
|
||||
|
||||
bool
|
||||
sc_hid_mouse_init(struct sc_hid_mouse *mouse, struct sc_aoa *aoa);
|
||||
|
||||
void
|
||||
sc_hid_mouse_destroy(struct sc_hid_mouse *mouse);
|
||||
|
||||
#endif
|
||||
@@ -54,7 +54,6 @@ enum sc_mod {
|
||||
|
||||
SC_MOD_NUM = KMOD_NUM,
|
||||
SC_MOD_CAPS = KMOD_CAPS,
|
||||
SC_MOD_SCROLL = KMOD_SCROLL,
|
||||
};
|
||||
|
||||
enum sc_action {
|
||||
@@ -361,10 +360,13 @@ struct sc_mouse_scroll_event {
|
||||
struct sc_position position;
|
||||
int32_t hscroll;
|
||||
int32_t vscroll;
|
||||
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
|
||||
};
|
||||
|
||||
struct sc_mouse_motion_event {
|
||||
struct sc_position position;
|
||||
int32_t xrel;
|
||||
int32_t yrel;
|
||||
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <SDL2/SDL_keycode.h>
|
||||
|
||||
#include "input_events.h"
|
||||
#include "screen.h"
|
||||
#include "util/log.h"
|
||||
|
||||
static inline uint16_t
|
||||
@@ -63,9 +64,19 @@ sc_mouse_button_from_sdl(uint8_t button) {
|
||||
}
|
||||
|
||||
static inline uint8_t
|
||||
sc_mouse_buttons_state_from_sdl(uint32_t buttons_state) {
|
||||
sc_mouse_buttons_state_from_sdl(uint32_t buttons_state,
|
||||
bool forward_all_clicks) {
|
||||
assert(buttons_state < 0x100); // fits in uint8_t
|
||||
return buttons_state;
|
||||
|
||||
uint8_t mask = SC_MOUSE_BUTTON_LEFT;
|
||||
if (forward_all_clicks) {
|
||||
mask |= SC_MOUSE_BUTTON_RIGHT
|
||||
| SC_MOUSE_BUTTON_MIDDLE
|
||||
| SC_MOUSE_BUTTON_X1
|
||||
| SC_MOUSE_BUTTON_X2;
|
||||
}
|
||||
|
||||
return buttons_state & mask;
|
||||
}
|
||||
|
||||
#define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI)
|
||||
@@ -111,24 +122,22 @@ is_shortcut_mod(struct input_manager *im, uint16_t sdl_mod) {
|
||||
}
|
||||
|
||||
void
|
||||
input_manager_init(struct input_manager *im, struct controller *controller,
|
||||
struct screen *screen, struct sc_key_processor *kp,
|
||||
struct sc_mouse_processor *mp,
|
||||
const struct scrcpy_options *options) {
|
||||
assert(!options->control || (kp && kp->ops));
|
||||
assert(!options->control || (mp && mp->ops));
|
||||
input_manager_init(struct input_manager *im,
|
||||
const struct input_manager_params *params) {
|
||||
assert(!params->control || (params->kp && params->kp->ops));
|
||||
assert(!params->control || (params->mp && params->mp->ops));
|
||||
|
||||
im->controller = controller;
|
||||
im->screen = screen;
|
||||
im->kp = kp;
|
||||
im->mp = mp;
|
||||
im->controller = params->controller;
|
||||
im->screen = params->screen;
|
||||
im->kp = params->kp;
|
||||
im->mp = params->mp;
|
||||
|
||||
im->control = options->control;
|
||||
im->forward_all_clicks = options->forward_all_clicks;
|
||||
im->legacy_paste = options->legacy_paste;
|
||||
im->clipboard_autosync = options->clipboard_autosync;
|
||||
im->control = params->control;
|
||||
im->forward_all_clicks = params->forward_all_clicks;
|
||||
im->legacy_paste = params->legacy_paste;
|
||||
im->clipboard_autosync = params->clipboard_autosync;
|
||||
|
||||
const struct sc_shortcut_mods *shortcut_mods = &options->shortcut_mods;
|
||||
const struct sc_shortcut_mods *shortcut_mods = params->shortcut_mods;
|
||||
assert(shortcut_mods->count);
|
||||
assert(shortcut_mods->count < SC_MAX_SHORTCUT_MODS);
|
||||
for (unsigned i = 0; i < shortcut_mods->count; ++i) {
|
||||
@@ -162,7 +171,6 @@ send_keycode(struct controller *controller, enum android_keycode keycode,
|
||||
|
||||
if (!controller_push_msg(controller, &msg)) {
|
||||
LOGW("Could not request 'inject %s'", name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,7 +222,6 @@ press_back_or_turn_screen_on(struct controller *controller,
|
||||
|
||||
if (!controller_push_msg(controller, &msg)) {
|
||||
LOGW("Could not request 'press back or turn screen on'");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,6 +383,11 @@ rotate_client_right(struct screen *screen) {
|
||||
static void
|
||||
input_manager_process_text_input(struct input_manager *im,
|
||||
const SDL_TextInputEvent *event) {
|
||||
if (!im->kp->ops->process_text) {
|
||||
// The key processor does not support text input
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_shortcut_mod(im, SDL_GetModState())) {
|
||||
// A shortcut must never generate text events
|
||||
return;
|
||||
@@ -612,20 +624,14 @@ input_manager_process_key(struct input_manager *im,
|
||||
.mods_state = sc_mods_state_from_sdl(event->keysym.mod),
|
||||
};
|
||||
|
||||
assert(im->kp->ops->process_key);
|
||||
im->kp->ops->process_key(im->kp, &evt, ack_to_wait);
|
||||
}
|
||||
|
||||
static void
|
||||
input_manager_process_mouse_motion(struct input_manager *im,
|
||||
const SDL_MouseMotionEvent *event) {
|
||||
uint32_t mask = SDL_BUTTON_LMASK;
|
||||
if (im->forward_all_clicks) {
|
||||
mask |= SDL_BUTTON_MMASK | SDL_BUTTON_RMASK;
|
||||
}
|
||||
if (!(event->state & mask)) {
|
||||
// do not send motion events when no click is pressed
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->which == SDL_TOUCH_MOUSEID) {
|
||||
// simulated from touch events, so it's a duplicate
|
||||
return;
|
||||
@@ -637,12 +643,21 @@ input_manager_process_mouse_motion(struct input_manager *im,
|
||||
.point = screen_convert_window_to_frame_coords(im->screen,
|
||||
event->x, event->y),
|
||||
},
|
||||
.buttons_state = sc_mouse_buttons_state_from_sdl(event->state),
|
||||
.xrel = event->xrel,
|
||||
.yrel = event->yrel,
|
||||
.buttons_state =
|
||||
sc_mouse_buttons_state_from_sdl(event->state,
|
||||
im->forward_all_clicks),
|
||||
};
|
||||
|
||||
assert(im->mp->ops->process_mouse_motion);
|
||||
im->mp->ops->process_mouse_motion(im->mp, &evt);
|
||||
|
||||
// vfinger must never be used in relative mode
|
||||
assert(!im->mp->relative_mode || !im->vfinger_down);
|
||||
|
||||
if (im->vfinger_down) {
|
||||
assert(!im->mp->relative_mode); // assert one more time
|
||||
struct sc_point mouse =
|
||||
screen_convert_window_to_frame_coords(im->screen, event->x,
|
||||
event->y);
|
||||
@@ -654,6 +669,11 @@ input_manager_process_mouse_motion(struct input_manager *im,
|
||||
static void
|
||||
input_manager_process_touch(struct input_manager *im,
|
||||
const SDL_TouchFingerEvent *event) {
|
||||
if (!im->mp->ops->process_touch) {
|
||||
// The mouse processor does not support touch events
|
||||
return;
|
||||
}
|
||||
|
||||
int dw;
|
||||
int dh;
|
||||
SDL_GL_GetDrawableSize(im->screen->window, &dw, &dh);
|
||||
@@ -742,11 +762,20 @@ input_manager_process_mouse_button(struct input_manager *im,
|
||||
},
|
||||
.action = sc_action_from_sdl_mousebutton_type(event->type),
|
||||
.button = sc_mouse_button_from_sdl(event->button),
|
||||
.buttons_state = sc_mouse_buttons_state_from_sdl(sdl_buttons_state),
|
||||
.buttons_state =
|
||||
sc_mouse_buttons_state_from_sdl(sdl_buttons_state,
|
||||
im->forward_all_clicks),
|
||||
};
|
||||
|
||||
assert(im->mp->ops->process_mouse_click);
|
||||
im->mp->ops->process_mouse_click(im->mp, &evt);
|
||||
|
||||
if (im->mp->relative_mode) {
|
||||
assert(!im->vfinger_down); // vfinger must not be used in relative mode
|
||||
// No pinch-to-zoom simulation
|
||||
return;
|
||||
}
|
||||
|
||||
// Pinch-to-zoom simulation.
|
||||
//
|
||||
// If Ctrl is hold when the left-click button is pressed, then
|
||||
@@ -757,8 +786,9 @@ input_manager_process_mouse_button(struct input_manager *im,
|
||||
// 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)) {
|
||||
if (event->button == SDL_BUTTON_LEFT &&
|
||||
((down && !im->vfinger_down && CTRL_PRESSED) ||
|
||||
(!down && im->vfinger_down))) {
|
||||
struct sc_point mouse =
|
||||
screen_convert_window_to_frame_coords(im->screen, event->x,
|
||||
event->y);
|
||||
@@ -776,10 +806,15 @@ input_manager_process_mouse_button(struct input_manager *im,
|
||||
static void
|
||||
input_manager_process_mouse_wheel(struct input_manager *im,
|
||||
const SDL_MouseWheelEvent *event) {
|
||||
if (!im->mp->ops->process_mouse_scroll) {
|
||||
// The mouse processor does not support scroll events
|
||||
return;
|
||||
}
|
||||
|
||||
// mouse_x and mouse_y are expressed in pixels relative to the window
|
||||
int mouse_x;
|
||||
int mouse_y;
|
||||
SDL_GetMouseState(&mouse_x, &mouse_y);
|
||||
uint32_t buttons = SDL_GetMouseState(&mouse_x, &mouse_y);
|
||||
|
||||
struct sc_mouse_scroll_event evt = {
|
||||
.position = {
|
||||
@@ -789,6 +824,8 @@ input_manager_process_mouse_wheel(struct input_manager *im,
|
||||
},
|
||||
.hscroll = event->x,
|
||||
.vscroll = event->y,
|
||||
.buttons_state =
|
||||
sc_mouse_buttons_state_from_sdl(buttons, im->forward_all_clicks),
|
||||
};
|
||||
|
||||
im->mp->ops->process_mouse_scroll(im->mp, &evt);
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "controller.h"
|
||||
#include "fps_counter.h"
|
||||
#include "options.h"
|
||||
#include "screen.h"
|
||||
#include "trait/key_processor.h"
|
||||
#include "trait/mouse_processor.h"
|
||||
|
||||
@@ -43,11 +42,22 @@ struct input_manager {
|
||||
uint64_t next_sequence; // used for request acknowledgements
|
||||
};
|
||||
|
||||
struct input_manager_params {
|
||||
struct controller *controller;
|
||||
struct screen *screen;
|
||||
struct sc_key_processor *kp;
|
||||
struct sc_mouse_processor *mp;
|
||||
|
||||
bool control;
|
||||
bool forward_all_clicks;
|
||||
bool legacy_paste;
|
||||
bool clipboard_autosync;
|
||||
const struct sc_shortcut_mods *shortcut_mods;
|
||||
};
|
||||
|
||||
void
|
||||
input_manager_init(struct input_manager *im, struct controller *controller,
|
||||
struct screen *screen, struct sc_key_processor *kp,
|
||||
struct sc_mouse_processor *mp,
|
||||
const struct scrcpy_options *options);
|
||||
input_manager_init(struct input_manager *im,
|
||||
const struct input_manager_params *params);
|
||||
|
||||
bool
|
||||
input_manager_handle_event(struct input_manager *im, SDL_Event *event);
|
||||
|
||||
@@ -58,6 +58,11 @@ convert_touch_action(enum sc_touch_action action) {
|
||||
static void
|
||||
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
||||
const struct sc_mouse_motion_event *event) {
|
||||
if (!event->buttons_state) {
|
||||
// Do not send motion events when no click is pressed
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_mouse_inject *mi = DOWNCAST(mp);
|
||||
|
||||
struct control_msg msg = {
|
||||
@@ -76,27 +81,6 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sc_mouse_processor_process_touch(struct sc_mouse_processor *mp,
|
||||
const struct sc_touch_event *event) {
|
||||
struct sc_mouse_inject *mi = DOWNCAST(mp);
|
||||
|
||||
struct control_msg msg = {
|
||||
.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
||||
.inject_touch_event = {
|
||||
.action = convert_touch_action(event->action),
|
||||
.pointer_id = event->pointer_id,
|
||||
.position = event->position,
|
||||
.pressure = event->pressure,
|
||||
.buttons = 0,
|
||||
},
|
||||
};
|
||||
|
||||
if (!controller_push_msg(mi->controller, &msg)) {
|
||||
LOGW("Could not request 'inject touch event'");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
|
||||
const struct sc_mouse_click_event *event) {
|
||||
@@ -129,6 +113,7 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
|
||||
.position = event->position,
|
||||
.hscroll = event->hscroll,
|
||||
.vscroll = event->vscroll,
|
||||
.buttons = convert_mouse_buttons(event->buttons_state),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -137,6 +122,27 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sc_mouse_processor_process_touch(struct sc_mouse_processor *mp,
|
||||
const struct sc_touch_event *event) {
|
||||
struct sc_mouse_inject *mi = DOWNCAST(mp);
|
||||
|
||||
struct control_msg msg = {
|
||||
.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
||||
.inject_touch_event = {
|
||||
.action = convert_touch_action(event->action),
|
||||
.pointer_id = event->pointer_id,
|
||||
.position = event->position,
|
||||
.pressure = event->pressure,
|
||||
.buttons = 0,
|
||||
},
|
||||
};
|
||||
|
||||
if (!controller_push_msg(mi->controller, &msg)) {
|
||||
LOGW("Could not request 'inject touch event'");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sc_mouse_inject_init(struct sc_mouse_inject *mi,
|
||||
struct controller *controller) {
|
||||
@@ -144,10 +150,12 @@ sc_mouse_inject_init(struct sc_mouse_inject *mi,
|
||||
|
||||
static const struct sc_mouse_processor_ops ops = {
|
||||
.process_mouse_motion = sc_mouse_processor_process_mouse_motion,
|
||||
.process_touch = sc_mouse_processor_process_touch,
|
||||
.process_mouse_click = sc_mouse_processor_process_mouse_click,
|
||||
.process_mouse_scroll = sc_mouse_processor_process_mouse_scroll,
|
||||
.process_touch = sc_mouse_processor_process_touch,
|
||||
};
|
||||
|
||||
mi->mouse_processor.ops = &ops;
|
||||
|
||||
mi->mouse_processor.relative_mode = false;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,11 @@ enum sc_keyboard_input_mode {
|
||||
SC_KEYBOARD_INPUT_MODE_HID,
|
||||
};
|
||||
|
||||
enum sc_mouse_input_mode {
|
||||
SC_MOUSE_INPUT_MODE_INJECT,
|
||||
SC_MOUSE_INPUT_MODE_HID,
|
||||
};
|
||||
|
||||
enum sc_key_inject_mode {
|
||||
// Inject special keys, letters and space as key events.
|
||||
// Inject numbers and punctuation as text events.
|
||||
@@ -90,6 +95,7 @@ struct scrcpy_options {
|
||||
enum sc_log_level log_level;
|
||||
enum sc_record_format record_format;
|
||||
enum sc_keyboard_input_mode keyboard_input_mode;
|
||||
enum sc_mouse_input_mode mouse_input_mode;
|
||||
struct sc_port_range port_range;
|
||||
uint32_t tunnel_host;
|
||||
uint16_t tunnel_port;
|
||||
|
||||
173
app/src/scrcpy.c
173
app/src/scrcpy.c
@@ -17,9 +17,9 @@
|
||||
#include "decoder.h"
|
||||
#include "events.h"
|
||||
#include "file_handler.h"
|
||||
#include "input_manager.h"
|
||||
#ifdef HAVE_AOA_HID
|
||||
# include "hid_keyboard.h"
|
||||
# include "hid_mouse.h"
|
||||
#endif
|
||||
#include "keyboard_inject.h"
|
||||
#include "mouse_inject.h"
|
||||
@@ -56,8 +56,12 @@ struct scrcpy {
|
||||
struct sc_hid_keyboard keyboard_hid;
|
||||
#endif
|
||||
};
|
||||
struct sc_mouse_inject mouse_inject;
|
||||
struct input_manager input_manager;
|
||||
union {
|
||||
struct sc_mouse_inject mouse_inject;
|
||||
#ifdef HAVE_AOA_HID
|
||||
struct sc_hid_mouse mouse_hid;
|
||||
#endif
|
||||
};
|
||||
};
|
||||
|
||||
static inline void
|
||||
@@ -189,11 +193,6 @@ handle_event(struct scrcpy *s, const struct scrcpy_options *options,
|
||||
}
|
||||
|
||||
bool consumed = screen_handle_event(&s->screen, event);
|
||||
if (consumed) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
consumed = input_manager_handle_event(&s->input_manager, event);
|
||||
(void) consumed;
|
||||
|
||||
end:
|
||||
@@ -336,6 +335,8 @@ scrcpy(struct scrcpy_options *options) {
|
||||
bool stream_started = false;
|
||||
#ifdef HAVE_AOA_HID
|
||||
bool aoa_hid_initialized = false;
|
||||
bool hid_keyboard_initialized = false;
|
||||
bool hid_mouse_initialized = false;
|
||||
#endif
|
||||
bool controller_initialized = false;
|
||||
bool controller_started = false;
|
||||
@@ -450,17 +451,100 @@ scrcpy(struct scrcpy_options *options) {
|
||||
stream_add_sink(&s->stream, &rec->packet_sink);
|
||||
}
|
||||
|
||||
struct sc_key_processor *kp = NULL;
|
||||
struct sc_mouse_processor *mp = NULL;
|
||||
|
||||
if (options->control) {
|
||||
#ifdef HAVE_AOA_HID
|
||||
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID) {
|
||||
bool use_hid_keyboard =
|
||||
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID;
|
||||
bool use_hid_mouse =
|
||||
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID;
|
||||
if (use_hid_keyboard || use_hid_mouse) {
|
||||
bool ok = sc_acksync_init(&s->acksync);
|
||||
if (!ok) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
ok = sc_aoa_init(&s->aoa, serial, &s->acksync);
|
||||
if (!ok) {
|
||||
LOGE("Failed to enable HID over AOA");
|
||||
sc_acksync_destroy(&s->acksync);
|
||||
goto aoa_hid_end;
|
||||
}
|
||||
|
||||
if (use_hid_keyboard) {
|
||||
if (sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa)) {
|
||||
hid_keyboard_initialized = true;
|
||||
kp = &s->keyboard_hid.key_processor;
|
||||
} else {
|
||||
LOGE("Could not initialize HID keyboard");
|
||||
}
|
||||
}
|
||||
|
||||
if (use_hid_mouse) {
|
||||
if (sc_hid_mouse_init(&s->mouse_hid, &s->aoa)) {
|
||||
hid_mouse_initialized = true;
|
||||
mp = &s->mouse_hid.mouse_processor;
|
||||
} else {
|
||||
LOGE("Could not initialized HID mouse");
|
||||
}
|
||||
}
|
||||
|
||||
bool need_aoa = hid_keyboard_initialized || hid_mouse_initialized;
|
||||
|
||||
if (!need_aoa || !sc_aoa_start(&s->aoa)) {
|
||||
sc_acksync_destroy(&s->acksync);
|
||||
sc_aoa_destroy(&s->aoa);
|
||||
goto aoa_hid_end;
|
||||
}
|
||||
|
||||
acksync = &s->acksync;
|
||||
|
||||
aoa_hid_initialized = true;
|
||||
|
||||
aoa_hid_end:
|
||||
if (!aoa_hid_initialized) {
|
||||
if (hid_keyboard_initialized) {
|
||||
sc_hid_keyboard_destroy(&s->keyboard_hid);
|
||||
hid_keyboard_initialized = false;
|
||||
}
|
||||
if (hid_mouse_initialized) {
|
||||
sc_hid_mouse_destroy(&s->mouse_hid);
|
||||
hid_mouse_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (use_hid_keyboard && !hid_keyboard_initialized) {
|
||||
LOGE("Fallback to default keyboard injection method "
|
||||
"(-K/--hid-keyboard ignored)");
|
||||
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
|
||||
}
|
||||
|
||||
if (use_hid_mouse && !hid_mouse_initialized) {
|
||||
LOGE("Fallback to default mouse injection method "
|
||||
"(-M/--hid-mouse ignored)");
|
||||
options->mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT;
|
||||
}
|
||||
}
|
||||
#else
|
||||
assert(options->keyboard_input_mode != SC_KEYBOARD_INPUT_MODE_HID);
|
||||
assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_HID);
|
||||
#endif
|
||||
|
||||
// keyboard_input_mode may have been reset if HID mode failed
|
||||
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_INJECT) {
|
||||
sc_keyboard_inject_init(&s->keyboard_inject, &s->controller,
|
||||
options);
|
||||
kp = &s->keyboard_inject.key_processor;
|
||||
}
|
||||
|
||||
// mouse_input_mode may have been reset if HID mode failed
|
||||
if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_INJECT) {
|
||||
sc_mouse_inject_init(&s->mouse_inject, &s->controller);
|
||||
mp = &s->mouse_inject.mouse_processor;
|
||||
}
|
||||
|
||||
if (!controller_init(&s->controller, s->server.control_socket,
|
||||
acksync)) {
|
||||
goto end;
|
||||
@@ -481,6 +565,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
LOGW("Could not request 'set screen power mode'");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (options->display) {
|
||||
@@ -488,6 +573,14 @@ scrcpy(struct scrcpy_options *options) {
|
||||
options->window_title ? options->window_title : info->device_name;
|
||||
|
||||
struct screen_params screen_params = {
|
||||
.controller = &s->controller,
|
||||
.kp = kp,
|
||||
.mp = mp,
|
||||
.control = options->control,
|
||||
.forward_all_clicks = options->forward_all_clicks,
|
||||
.legacy_paste = options->legacy_paste,
|
||||
.clipboard_autosync = options->clipboard_autosync,
|
||||
.shortcut_mods = &options->shortcut_mods,
|
||||
.window_title = window_title,
|
||||
.frame_size = info->frame_size,
|
||||
.always_on_top = options->always_on_top,
|
||||
@@ -530,64 +623,6 @@ scrcpy(struct scrcpy_options *options) {
|
||||
}
|
||||
stream_started = true;
|
||||
|
||||
struct sc_key_processor *kp = NULL;
|
||||
struct sc_mouse_processor *mp = NULL;
|
||||
|
||||
if (options->control) {
|
||||
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID) {
|
||||
#ifdef HAVE_AOA_HID
|
||||
bool aoa_hid_ok = false;
|
||||
|
||||
bool ok = sc_aoa_init(&s->aoa, serial, acksync);
|
||||
if (!ok) {
|
||||
goto aoa_hid_end;
|
||||
}
|
||||
|
||||
if (!sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa)) {
|
||||
sc_aoa_destroy(&s->aoa);
|
||||
goto aoa_hid_end;
|
||||
}
|
||||
|
||||
if (!sc_aoa_start(&s->aoa)) {
|
||||
sc_hid_keyboard_destroy(&s->keyboard_hid);
|
||||
sc_aoa_destroy(&s->aoa);
|
||||
goto aoa_hid_end;
|
||||
}
|
||||
|
||||
aoa_hid_ok = true;
|
||||
kp = &s->keyboard_hid.key_processor;
|
||||
|
||||
aoa_hid_initialized = true;
|
||||
|
||||
aoa_hid_end:
|
||||
if (!aoa_hid_ok) {
|
||||
LOGE("Failed to enable HID over AOA, "
|
||||
"fallback to default keyboard injection method "
|
||||
"(-K/--hid-keyboard ignored)");
|
||||
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
|
||||
}
|
||||
#else
|
||||
LOGE("HID over AOA is not supported on this platform, "
|
||||
"fallback to default keyboard injection method "
|
||||
"(-K/--hid-keyboard ignored)");
|
||||
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
|
||||
#endif
|
||||
}
|
||||
|
||||
// keyboard_input_mode may have been reset if HID mode failed
|
||||
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_INJECT) {
|
||||
sc_keyboard_inject_init(&s->keyboard_inject, &s->controller,
|
||||
options);
|
||||
kp = &s->keyboard_inject.key_processor;
|
||||
}
|
||||
|
||||
sc_mouse_inject_init(&s->mouse_inject, &s->controller);
|
||||
mp = &s->mouse_inject.mouse_processor;
|
||||
}
|
||||
|
||||
input_manager_init(&s->input_manager, &s->controller, &s->screen, kp, mp,
|
||||
options);
|
||||
|
||||
ret = event_loop(s, options);
|
||||
LOGD("quit...");
|
||||
|
||||
@@ -600,7 +635,9 @@ end:
|
||||
// end-of-stream
|
||||
#ifdef HAVE_AOA_HID
|
||||
if (aoa_hid_initialized) {
|
||||
sc_hid_keyboard_destroy(&s->keyboard_hid);
|
||||
if (hid_keyboard_initialized) {
|
||||
sc_hid_keyboard_destroy(&s->keyboard_hid);
|
||||
}
|
||||
sc_aoa_stop(&s->aoa);
|
||||
}
|
||||
if (acksync) {
|
||||
|
||||
@@ -156,6 +156,17 @@ get_initial_optimal_size(struct sc_size content_size, uint16_t req_width,
|
||||
return window_size;
|
||||
}
|
||||
|
||||
static inline void
|
||||
screen_capture_mouse(struct screen *screen, bool capture) {
|
||||
if (SDL_SetRelativeMouseMode(capture)) {
|
||||
LOGE("Could not set relative mouse mode to %s: %s",
|
||||
capture ? "true" : "false", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
screen->mouse_captured = capture;
|
||||
}
|
||||
|
||||
static void
|
||||
screen_update_content_rect(struct screen *screen) {
|
||||
int dw;
|
||||
@@ -354,6 +365,8 @@ screen_init(struct screen *screen, const struct screen_params *params) {
|
||||
screen->fullscreen = false;
|
||||
screen->maximized = false;
|
||||
screen->event_failed = false;
|
||||
screen->mouse_captured = false;
|
||||
screen->mouse_capture_key_pressed = 0;
|
||||
|
||||
static const struct sc_video_buffer_callbacks cbs = {
|
||||
.on_new_frame = sc_video_buffer_on_new_frame,
|
||||
@@ -470,6 +483,20 @@ screen_init(struct screen *screen, const struct screen_params *params) {
|
||||
goto error_destroy_texture;
|
||||
}
|
||||
|
||||
struct input_manager_params im_params = {
|
||||
.controller = params->controller,
|
||||
.screen = screen,
|
||||
.kp = params->kp,
|
||||
.mp = params->mp,
|
||||
.control = params->control,
|
||||
.forward_all_clicks = params->forward_all_clicks,
|
||||
.legacy_paste = params->legacy_paste,
|
||||
.clipboard_autosync = params->clipboard_autosync,
|
||||
.shortcut_mods = params->shortcut_mods,
|
||||
};
|
||||
|
||||
input_manager_init(&screen->im, &im_params);
|
||||
|
||||
// Reset the window size to trigger a SIZE_CHANGED event, to workaround
|
||||
// HiDPI issues with some SDL renderers when several displays having
|
||||
// different HiDPI scaling are connected
|
||||
@@ -727,6 +754,11 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
|
||||
content_size.height);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
screen_is_mouse_capture_key(SDL_Keycode key) {
|
||||
return key == SDLK_LALT || key == SDLK_LGUI || key == SDLK_RGUI;
|
||||
}
|
||||
|
||||
bool
|
||||
screen_handle_event(struct screen *screen, SDL_Event *event) {
|
||||
switch (event->type) {
|
||||
@@ -769,11 +801,71 @@ screen_handle_event(struct screen *screen, SDL_Event *event) {
|
||||
apply_pending_resize(screen);
|
||||
screen_render(screen, true);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_FOCUS_LOST:
|
||||
if (screen->im.mp->relative_mode) {
|
||||
screen_capture_mouse(screen, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
case SDL_KEYDOWN:
|
||||
if (screen->im.mp->relative_mode) {
|
||||
SDL_Keycode key = event->key.keysym.sym;
|
||||
if (screen_is_mouse_capture_key(key)) {
|
||||
if (!screen->mouse_capture_key_pressed) {
|
||||
screen->mouse_capture_key_pressed = key;
|
||||
return true;
|
||||
} else {
|
||||
// Another mouse capture key has been pressed, cancel
|
||||
// mouse (un)capture
|
||||
screen->mouse_capture_key_pressed = 0;
|
||||
// Do not return, the event must be forwarded to the
|
||||
// input manager
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SDL_KEYUP:
|
||||
if (screen->im.mp->relative_mode) {
|
||||
SDL_Keycode key = event->key.keysym.sym;
|
||||
SDL_Keycode cap = screen->mouse_capture_key_pressed;
|
||||
screen->mouse_capture_key_pressed = 0;
|
||||
if (key == cap) {
|
||||
// A mouse capture key has been pressed then released:
|
||||
// toggle the capture mouse mode
|
||||
screen_capture_mouse(screen, !screen->mouse_captured);
|
||||
return true;
|
||||
}
|
||||
// Do not return, the event must be forwarded to the input
|
||||
// manager
|
||||
}
|
||||
break;
|
||||
case SDL_MOUSEWHEEL:
|
||||
case SDL_MOUSEMOTION:
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
if (screen->im.mp->relative_mode && !screen->mouse_captured) {
|
||||
// Do not forward to input manager, the mouse will be captured
|
||||
// on SDL_MOUSEBUTTONUP
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case SDL_FINGERMOTION:
|
||||
case SDL_FINGERDOWN:
|
||||
case SDL_FINGERUP:
|
||||
if (screen->im.mp->relative_mode) {
|
||||
// Touch events are not compatible with relative mode
|
||||
// (coordinates are not relative)
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
if (screen->im.mp->relative_mode && !screen->mouse_captured) {
|
||||
screen_capture_mouse(screen, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return input_manager_handle_event(&screen->im, event);
|
||||
}
|
||||
|
||||
struct sc_point
|
||||
|
||||
@@ -7,10 +7,14 @@
|
||||
#include <SDL2/SDL.h>
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
#include "controller.h"
|
||||
#include "coords.h"
|
||||
#include "fps_counter.h"
|
||||
#include "input_manager.h"
|
||||
#include "opengl.h"
|
||||
#include "trait/key_processor.h"
|
||||
#include "trait/frame_sink.h"
|
||||
#include "trait/mouse_processor.h"
|
||||
#include "video_buffer.h"
|
||||
|
||||
struct screen {
|
||||
@@ -20,6 +24,7 @@ struct screen {
|
||||
bool open; // track the open/close state to assert correct behavior
|
||||
#endif
|
||||
|
||||
struct input_manager im;
|
||||
struct sc_video_buffer vb;
|
||||
struct fps_counter fps_counter;
|
||||
|
||||
@@ -46,10 +51,25 @@ struct screen {
|
||||
|
||||
bool event_failed; // in case SDL_PushEvent() returned an error
|
||||
|
||||
bool mouse_captured; // only relevant in relative mouse mode
|
||||
// To enable/disable mouse capture, a mouse capture key (LALT, LGUI or
|
||||
// RGUI) must be pressed. This variable tracks the pressed capture key.
|
||||
SDL_Keycode mouse_capture_key_pressed;
|
||||
|
||||
AVFrame *frame;
|
||||
};
|
||||
|
||||
struct screen_params {
|
||||
struct controller *controller;
|
||||
struct sc_key_processor *kp;
|
||||
struct sc_mouse_processor *mp;
|
||||
|
||||
bool control;
|
||||
bool forward_all_clicks;
|
||||
bool legacy_paste;
|
||||
bool clipboard_autosync;
|
||||
const struct sc_shortcut_mods *shortcut_mods;
|
||||
|
||||
const char *window_title;
|
||||
struct sc_size frame_size;
|
||||
bool always_on_top;
|
||||
|
||||
@@ -29,17 +29,24 @@ struct sc_key_processor {
|
||||
struct sc_key_processor_ops {
|
||||
|
||||
/**
|
||||
* Process the keyboard event
|
||||
* Process a keyboard event
|
||||
*
|
||||
* The `sequence` number (if different from `SC_SEQUENCE_INVALID`) indicates
|
||||
* the acknowledgement number to wait for before injecting this event.
|
||||
* This allows to ensure that the device clipboard is set before injecting
|
||||
* Ctrl+v on the device.
|
||||
*
|
||||
* This function is mandatory.
|
||||
*/
|
||||
void
|
||||
(*process_key)(struct sc_key_processor *kp,
|
||||
const struct sc_key_event *event, uint64_t ack_to_wait);
|
||||
|
||||
/**
|
||||
* Process an input text
|
||||
*
|
||||
* This function is optional.
|
||||
*/
|
||||
void
|
||||
(*process_text)(struct sc_key_processor *kp,
|
||||
const struct sc_text_event *event);
|
||||
|
||||
@@ -16,24 +16,51 @@
|
||||
*/
|
||||
struct sc_mouse_processor {
|
||||
const struct sc_mouse_processor_ops *ops;
|
||||
|
||||
/**
|
||||
* If set, the mouse processor works in relative mode (the absolute
|
||||
* position is irrelevant). In particular, it indicates that the mouse
|
||||
* pointer must be "captured" by the UI.
|
||||
*/
|
||||
bool relative_mode;
|
||||
};
|
||||
|
||||
struct sc_mouse_processor_ops {
|
||||
/**
|
||||
* Process a mouse motion event
|
||||
*
|
||||
* This function is mandatory.
|
||||
*/
|
||||
void
|
||||
(*process_mouse_motion)(struct sc_mouse_processor *mp,
|
||||
const struct sc_mouse_motion_event *event);
|
||||
|
||||
void
|
||||
(*process_touch)(struct sc_mouse_processor *mp,
|
||||
const struct sc_touch_event *event);
|
||||
|
||||
/**
|
||||
* Process a mouse click event
|
||||
*
|
||||
* This function is mandatory.
|
||||
*/
|
||||
void
|
||||
(*process_mouse_click)(struct sc_mouse_processor *mp,
|
||||
const struct sc_mouse_click_event *event);
|
||||
|
||||
/**
|
||||
* Process a mouse scroll event
|
||||
*
|
||||
* This function is optional.
|
||||
*/
|
||||
void
|
||||
(*process_mouse_scroll)(struct sc_mouse_processor *mp,
|
||||
const struct sc_mouse_scroll_event *event);
|
||||
|
||||
/**
|
||||
* Process a touch event
|
||||
*
|
||||
* This function is optional.
|
||||
*/
|
||||
void
|
||||
(*process_touch)(struct sc_mouse_processor *mp,
|
||||
const struct sc_touch_event *event);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -83,6 +83,7 @@ unwrap(sc_socket socket) {
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef HAVE_SOCK_CLOEXEC // avoid unused-function warning
|
||||
static inline bool
|
||||
sc_raw_socket_close(sc_raw_socket raw_sock) {
|
||||
#ifndef _WIN32
|
||||
@@ -91,6 +92,7 @@ sc_raw_socket_close(sc_raw_socket raw_sock) {
|
||||
return !closesocket(raw_sock);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_SOCK_CLOEXEC
|
||||
// If SOCK_CLOEXEC does not exist, the flag must be set manually once the
|
||||
|
||||
@@ -11,6 +11,7 @@ static void test_get_ip_single_line() {
|
||||
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
|
||||
assert(ip);
|
||||
assert(!strcmp(ip, "192.168.12.34"));
|
||||
free(ip);
|
||||
}
|
||||
|
||||
static void test_get_ip_single_line_without_eol() {
|
||||
@@ -20,6 +21,7 @@ static void test_get_ip_single_line_without_eol() {
|
||||
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
|
||||
assert(ip);
|
||||
assert(!strcmp(ip, "192.168.12.34"));
|
||||
free(ip);
|
||||
}
|
||||
|
||||
static void test_get_ip_single_line_with_trailing_space() {
|
||||
@@ -29,6 +31,7 @@ static void test_get_ip_single_line_with_trailing_space() {
|
||||
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
|
||||
assert(ip);
|
||||
assert(!strcmp(ip, "192.168.12.34"));
|
||||
free(ip);
|
||||
}
|
||||
|
||||
static void test_get_ip_multiline_first_ok() {
|
||||
@@ -40,6 +43,7 @@ static void test_get_ip_multiline_first_ok() {
|
||||
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
|
||||
assert(ip);
|
||||
assert(!strcmp(ip, "192.168.1.2"));
|
||||
free(ip);
|
||||
}
|
||||
|
||||
static void test_get_ip_multiline_second_ok() {
|
||||
@@ -51,6 +55,7 @@ static void test_get_ip_multiline_second_ok() {
|
||||
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
|
||||
assert(ip);
|
||||
assert(!strcmp(ip, "192.168.1.3"));
|
||||
free(ip);
|
||||
}
|
||||
|
||||
static void test_get_ip_no_wlan() {
|
||||
|
||||
@@ -126,12 +126,13 @@ static void test_serialize_inject_scroll_event(void) {
|
||||
},
|
||||
.hscroll = 1,
|
||||
.vscroll = -1,
|
||||
.buttons = 1,
|
||||
},
|
||||
};
|
||||
|
||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||
size_t size = control_msg_serialize(&msg, buf);
|
||||
assert(size == 21);
|
||||
assert(size == 25);
|
||||
|
||||
const unsigned char expected[] = {
|
||||
CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
|
||||
@@ -139,6 +140,7 @@ static void test_serialize_inject_scroll_event(void) {
|
||||
0x04, 0x38, 0x07, 0x80, // 1080 1920
|
||||
0x00, 0x00, 0x00, 0x01, // 1
|
||||
0xFF, 0xFF, 0xFF, 0xFF, // -1
|
||||
0x00, 0x00, 0x00, 0x01, // 1
|
||||
};
|
||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||
}
|
||||
|
||||
@@ -71,12 +71,13 @@ public final class ControlMessage {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static ControlMessage createInjectScrollEvent(Position position, int hScroll, int vScroll) {
|
||||
public static ControlMessage createInjectScrollEvent(Position position, int hScroll, int vScroll, int buttons) {
|
||||
ControlMessage msg = new ControlMessage();
|
||||
msg.type = TYPE_INJECT_SCROLL_EVENT;
|
||||
msg.position = position;
|
||||
msg.hScroll = hScroll;
|
||||
msg.vScroll = vScroll;
|
||||
msg.buttons = buttons;
|
||||
return msg;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ public class ControlMessageReader {
|
||||
|
||||
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13;
|
||||
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
|
||||
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
||||
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 24;
|
||||
static final int BACK_OR_SCREEN_ON_LENGTH = 1;
|
||||
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
||||
static final int GET_CLIPBOARD_LENGTH = 1;
|
||||
@@ -154,7 +154,8 @@ public class ControlMessageReader {
|
||||
Position position = readPosition(buffer);
|
||||
int hScroll = buffer.getInt();
|
||||
int vScroll = buffer.getInt();
|
||||
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll);
|
||||
int buttons = buffer.getInt();
|
||||
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll, buttons);
|
||||
}
|
||||
|
||||
private ControlMessage parseBackOrScreenOnEvent() {
|
||||
|
||||
@@ -98,7 +98,7 @@ public class Controller {
|
||||
break;
|
||||
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
|
||||
if (device.supportsInputEvents()) {
|
||||
injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll());
|
||||
injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll(), msg.getButtons());
|
||||
}
|
||||
break;
|
||||
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
||||
@@ -221,7 +221,7 @@ public class Controller {
|
||||
return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
|
||||
}
|
||||
|
||||
private boolean injectScroll(Position position, int hScroll, int vScroll) {
|
||||
private boolean injectScroll(Position position, int hScroll, int vScroll, int buttons) {
|
||||
long now = SystemClock.uptimeMillis();
|
||||
Point point = device.getPhysicalPoint(position);
|
||||
if (point == null) {
|
||||
@@ -239,7 +239,7 @@ public class Controller {
|
||||
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
|
||||
|
||||
MotionEvent event = MotionEvent
|
||||
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEFAULT_DEVICE_ID, 0,
|
||||
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0,
|
||||
InputDevice.SOURCE_MOUSE, 0);
|
||||
return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
|
||||
}
|
||||
|
||||
@@ -128,6 +128,7 @@ public class ControlMessageReaderTest {
|
||||
dos.writeShort(1920);
|
||||
dos.writeInt(1);
|
||||
dos.writeInt(-1);
|
||||
dos.writeInt(1);
|
||||
|
||||
byte[] packet = bos.toByteArray();
|
||||
|
||||
@@ -144,6 +145,7 @@ public class ControlMessageReaderTest {
|
||||
Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight());
|
||||
Assert.assertEquals(1, event.getHScroll());
|
||||
Assert.assertEquals(-1, event.getVScroll());
|
||||
Assert.assertEquals(1, event.getButtons());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user