Compare commits
33 Commits
hidmouse.2
...
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.
|
identification within third-party archives.
|
||||||
|
|
||||||
Copyright (C) 2018 Genymobile
|
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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -672,7 +672,7 @@ Baca [halaman pengembang].
|
|||||||
## Lisensi
|
## Lisensi
|
||||||
|
|
||||||
Copyright (C) 2018 Genymobile
|
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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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)
|
## Licenza (in inglese)
|
||||||
|
|
||||||
Copyright (C) 2018 Genymobile
|
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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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 Genymobile
|
||||||
Copyright (C) 2018-2021 Romain Vimont
|
Copyright (C) 2018-2022 Romain Vimont
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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 Genymobile
|
||||||
Copyright (C) 2018-2021 Romain Vimont
|
Copyright (C) 2018-2022 Romain Vimont
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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)
|
- device screen [as a webcam (V4L2)](#v4l2loopback) (Linux-only)
|
||||||
- [physical keyboard simulation (HID)](#physical-keyboard-simulation-hid)
|
- [physical keyboard simulation (HID)](#physical-keyboard-simulation-hid)
|
||||||
(Linux-only)
|
(Linux-only)
|
||||||
|
- [physical mouse simulation (HID)](#physical-mouse-simulation-hid)
|
||||||
|
(Linux-only)
|
||||||
- and more…
|
- and more…
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
@@ -815,6 +817,35 @@ a physical keyboard is connected).
|
|||||||
|
|
||||||
[Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915
|
[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
|
#### Text injection preference
|
||||||
|
|
||||||
@@ -1017,7 +1048,7 @@ Read the [developers page].
|
|||||||
## Licence
|
## Licence
|
||||||
|
|
||||||
Copyright (C) 2018 Genymobile
|
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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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
|
## Licença
|
||||||
|
|
||||||
Copyright (C) 2018 Genymobile
|
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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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
|
## Licencia
|
||||||
|
|
||||||
Copyright (C) 2018 Genymobile
|
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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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
|
## Lisans
|
||||||
|
|
||||||
Copyright (C) 2018 Genymobile
|
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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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 Genymobile
|
||||||
Copyright (C) 2018-2021 Romain Vimont
|
Copyright (C) 2018-2022 Romain Vimont
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -679,7 +679,7 @@ _³只支援 Android 7+。_
|
|||||||
## Licence
|
## Licence
|
||||||
|
|
||||||
Copyright (C) 2018 Genymobile
|
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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
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).
|
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
|
.TP
|
||||||
.B \-\-legacy\-paste
|
.B \-\-legacy\-paste
|
||||||
Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v).
|
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).
|
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
|
.TP
|
||||||
.B \-\-no\-clipboard\-autosync
|
.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.
|
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
|
Genymobile
|
||||||
.UE
|
.UE
|
||||||
|
|
||||||
Copyright \(co 2018\-2021
|
Copyright \(co 2018\-2022
|
||||||
.MT rom@rom1v.com
|
.MT rom@rom1v.com
|
||||||
Romain Vimont
|
Romain Vimont
|
||||||
.ME
|
.ME
|
||||||
|
|||||||
@@ -178,7 +178,8 @@ static const struct sc_option options[] = {
|
|||||||
"directly: `adb shell am start -a "
|
"directly: `adb shell am start -a "
|
||||||
"android.settings.HARD_KEYBOARD_SETTINGS`.\n"
|
"android.settings.HARD_KEYBOARD_SETTINGS`.\n"
|
||||||
"However, the option is only available when the HID keyboard "
|
"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',
|
.shortopt = 'h',
|
||||||
@@ -214,6 +215,18 @@ static const struct sc_option options[] = {
|
|||||||
.text = "Limit the frame rate of screen capture (officially supported "
|
.text = "Limit the frame rate of screen capture (officially supported "
|
||||||
"since Android 10, but may work on earlier versions).",
|
"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',
|
.shortopt = 'm',
|
||||||
.longopt = "max-size",
|
.longopt = "max-size",
|
||||||
@@ -240,11 +253,8 @@ static const struct sc_option options[] = {
|
|||||||
{
|
{
|
||||||
.shortopt = 'N',
|
.shortopt = 'N',
|
||||||
.longopt = "no-display",
|
.longopt = "no-display",
|
||||||
.text = "Do not display device (only when screen recording "
|
.text = "Do not display device (only when screen recording or V4L2 "
|
||||||
#ifdef HAVE_V4L2
|
"sink is enabled).",
|
||||||
"or V4L2 sink "
|
|
||||||
#endif
|
|
||||||
"is enabled).",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_NO_KEY_REPEAT,
|
.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 "
|
"Default is 0 (not forced): the local port used for "
|
||||||
"establishing the tunnel will be used.",
|
"establishing the tunnel will be used.",
|
||||||
},
|
},
|
||||||
#ifdef HAVE_V4L2
|
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_V4L2_SINK,
|
.longopt_id = OPT_V4L2_SINK,
|
||||||
.longopt = "v4l2-sink",
|
.longopt = "v4l2-sink",
|
||||||
.argdesc = "/dev/videoN",
|
.argdesc = "/dev/videoN",
|
||||||
.text = "Output to v4l2loopback device.\n"
|
.text = "Output to v4l2loopback device.\n"
|
||||||
"It requires to lock the video orientation (see "
|
"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,
|
.longopt_id = OPT_V4L2_BUFFER,
|
||||||
@@ -398,9 +408,9 @@ static const struct sc_option options[] = {
|
|||||||
"frames. This increases latency to compensate for jitter.\n"
|
"frames. This increases latency to compensate for jitter.\n"
|
||||||
"This option is similar to --display-buffer, but specific to "
|
"This option is similar to --display-buffer, but specific to "
|
||||||
"V4L2 sink.\n"
|
"V4L2 sink.\n"
|
||||||
"Default is 0 (no buffering).",
|
"Default is 0 (no buffering).\n"
|
||||||
|
"This option is only available on Linux.",
|
||||||
},
|
},
|
||||||
#endif
|
|
||||||
{
|
{
|
||||||
.shortopt = 'V',
|
.shortopt = 'V',
|
||||||
.longopt = "verbosity",
|
.longopt = "verbosity",
|
||||||
@@ -1300,7 +1310,13 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
args->help = true;
|
args->help = true;
|
||||||
break;
|
break;
|
||||||
case 'K':
|
case 'K':
|
||||||
|
#ifdef HAVE_AOA_HID
|
||||||
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_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;
|
break;
|
||||||
case OPT_MAX_FPS:
|
case OPT_MAX_FPS:
|
||||||
if (!parse_max_fps(optarg, &opts->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;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
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:
|
case OPT_LOCK_VIDEO_ORIENTATION:
|
||||||
if (!parse_lock_video_orientation(optarg,
|
if (!parse_lock_video_orientation(optarg,
|
||||||
&opts->lock_video_orientation)) {
|
&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 = true;
|
||||||
opts->tcpip_dst = optarg;
|
opts->tcpip_dst = optarg;
|
||||||
break;
|
break;
|
||||||
#ifdef HAVE_V4L2
|
|
||||||
case OPT_V4L2_SINK:
|
case OPT_V4L2_SINK:
|
||||||
|
#ifdef HAVE_V4L2
|
||||||
opts->v4l2_device = optarg;
|
opts->v4l2_device = optarg;
|
||||||
|
#else
|
||||||
|
LOGE("V4L2 (--v4l2-sink) is only available on Linux.");
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
case OPT_V4L2_BUFFER:
|
case OPT_V4L2_BUFFER:
|
||||||
|
#ifdef HAVE_V4L2
|
||||||
if (!parse_buffering_time(optarg, &opts->v4l2_buffer)) {
|
if (!parse_buffering_time(optarg, &opts->v4l2_buffer)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
#else
|
||||||
|
LOGE("V4L2 (--v4l2-buffer) is only available on Linux.");
|
||||||
|
return false;
|
||||||
#endif
|
#endif
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -119,7 +119,8 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
|||||||
(uint32_t) msg->inject_scroll_event.hscroll);
|
(uint32_t) msg->inject_scroll_event.hscroll);
|
||||||
buffer_write32be(&buf[17],
|
buffer_write32be(&buf[17],
|
||||||
(uint32_t) msg->inject_scroll_event.vscroll);
|
(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:
|
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
||||||
buf[1] = msg->inject_keycode.action;
|
buf[1] = msg->inject_keycode.action;
|
||||||
return 2;
|
return 2;
|
||||||
@@ -192,11 +193,12 @@ control_msg_log(const struct control_msg *msg) {
|
|||||||
}
|
}
|
||||||
case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
|
case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
|
||||||
LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%" PRIi32
|
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.x,
|
||||||
msg->inject_scroll_event.position.point.y,
|
msg->inject_scroll_event.position.point.y,
|
||||||
msg->inject_scroll_event.hscroll,
|
msg->inject_scroll_event.hscroll,
|
||||||
msg->inject_scroll_event.vscroll);
|
msg->inject_scroll_event.vscroll,
|
||||||
|
(long) msg->inject_scroll_event.buttons);
|
||||||
break;
|
break;
|
||||||
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
||||||
LOG_CMSG("back-or-screen-on %s",
|
LOG_CMSG("back-or-screen-on %s",
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ struct control_msg {
|
|||||||
struct sc_position position;
|
struct sc_position position;
|
||||||
int32_t hscroll;
|
int32_t hscroll;
|
||||||
int32_t vscroll;
|
int32_t vscroll;
|
||||||
|
enum android_motionevent_buttons buttons;
|
||||||
} inject_scroll_event;
|
} inject_scroll_event;
|
||||||
struct {
|
struct {
|
||||||
enum android_keyevent_action action; // action for the BACK key
|
enum android_keyevent_action action; // action for the BACK key
|
||||||
|
|||||||
@@ -43,24 +43,24 @@ static const unsigned char mouse_report_desc[] = {
|
|||||||
|
|
||||||
// Usage Minimum (1)
|
// Usage Minimum (1)
|
||||||
0x19, 0x01,
|
0x19, 0x01,
|
||||||
// Usage Maximum (3)
|
// Usage Maximum (5)
|
||||||
0x29, 0x03,
|
0x29, 0x05,
|
||||||
// Logical Minimum (0)
|
// Logical Minimum (0)
|
||||||
0x15, 0x00,
|
0x15, 0x00,
|
||||||
// Logical Maximum (1)
|
// Logical Maximum (1)
|
||||||
0x25, 0x01,
|
0x25, 0x01,
|
||||||
// Report Count (3)
|
// Report Count (5)
|
||||||
0x95, 0x03,
|
0x95, 0x05,
|
||||||
// Report Size (1)
|
// Report Size (1)
|
||||||
0x75, 0x01,
|
0x75, 0x01,
|
||||||
// Input (Data, Variable, Absolute): 3 buttons bits
|
// Input (Data, Variable, Absolute): 5 buttons bits
|
||||||
0x81, 0x02,
|
0x81, 0x02,
|
||||||
|
|
||||||
// Report Count (1)
|
// Report Count (1)
|
||||||
0x95, 0x01,
|
0x95, 0x01,
|
||||||
// Report Size (5)
|
// Report Size (3)
|
||||||
0x75, 0x05,
|
0x75, 0x03,
|
||||||
// Input (Constant): 5 bits padding
|
// Input (Constant): 3 bits padding
|
||||||
0x81, 0x01,
|
0x81, 0x01,
|
||||||
|
|
||||||
// Usage Page (Generic Desktop)
|
// Usage Page (Generic Desktop)
|
||||||
@@ -98,12 +98,14 @@ static const unsigned char mouse_report_desc[] = {
|
|||||||
*
|
*
|
||||||
* 7 6 5 4 3 2 1 0
|
* 7 6 5 4 3 2 1 0
|
||||||
* +---------------+
|
* +---------------+
|
||||||
* byte 0: |0 0 0 0 0 . . .| buttons state
|
* byte 0: |0 0 0 . . . . .| buttons state
|
||||||
* +---------------+
|
* +---------------+
|
||||||
* ^ ^ ^
|
* ^ ^ ^ ^ ^
|
||||||
* | | `- left button
|
* | | | | `- left button
|
||||||
* | `--- right button
|
* | | | `--- right button
|
||||||
* `----- middle button
|
* | | `----- middle button
|
||||||
|
* | `------- button 4
|
||||||
|
* `--------- button 5
|
||||||
*
|
*
|
||||||
* +---------------+
|
* +---------------+
|
||||||
* byte 1: |. . . . . . . .| relative x motion
|
* byte 1: |. . . . . . . .| relative x motion
|
||||||
@@ -152,7 +154,12 @@ buttons_state_to_hid_buttons(uint8_t buttons_state) {
|
|||||||
if (buttons_state & SC_MOUSE_BUTTON_MIDDLE) {
|
if (buttons_state & SC_MOUSE_BUTTON_MIDDLE) {
|
||||||
c |= 1 << 2;
|
c |= 1 << 2;
|
||||||
}
|
}
|
||||||
// TODO buttons 4 and 5?
|
if (buttons_state & SC_MOUSE_BUTTON_X1) {
|
||||||
|
c |= 1 << 3;
|
||||||
|
}
|
||||||
|
if (buttons_state & SC_MOUSE_BUTTON_X2) {
|
||||||
|
c |= 1 << 4;
|
||||||
|
}
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,6 +253,8 @@ sc_hid_mouse_init(struct sc_hid_mouse *mouse, struct sc_aoa *aoa) {
|
|||||||
|
|
||||||
mouse->mouse_processor.ops = &ops;
|
mouse->mouse_processor.ops = &ops;
|
||||||
|
|
||||||
|
mouse->mouse_processor.relative_mode = true;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ enum sc_mod {
|
|||||||
|
|
||||||
SC_MOD_NUM = KMOD_NUM,
|
SC_MOD_NUM = KMOD_NUM,
|
||||||
SC_MOD_CAPS = KMOD_CAPS,
|
SC_MOD_CAPS = KMOD_CAPS,
|
||||||
SC_MOD_SCROLL = KMOD_SCROLL,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum sc_action {
|
enum sc_action {
|
||||||
@@ -361,6 +360,7 @@ struct sc_mouse_scroll_event {
|
|||||||
struct sc_position position;
|
struct sc_position position;
|
||||||
int32_t hscroll;
|
int32_t hscroll;
|
||||||
int32_t vscroll;
|
int32_t vscroll;
|
||||||
|
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_mouse_motion_event {
|
struct sc_mouse_motion_event {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <SDL2/SDL_keycode.h>
|
#include <SDL2/SDL_keycode.h>
|
||||||
|
|
||||||
#include "input_events.h"
|
#include "input_events.h"
|
||||||
|
#include "screen.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
static inline uint16_t
|
static inline uint16_t
|
||||||
@@ -121,24 +122,22 @@ is_shortcut_mod(struct input_manager *im, uint16_t sdl_mod) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
input_manager_init(struct input_manager *im, struct controller *controller,
|
input_manager_init(struct input_manager *im,
|
||||||
struct screen *screen, struct sc_key_processor *kp,
|
const struct input_manager_params *params) {
|
||||||
struct sc_mouse_processor *mp,
|
assert(!params->control || (params->kp && params->kp->ops));
|
||||||
const struct scrcpy_options *options) {
|
assert(!params->control || (params->mp && params->mp->ops));
|
||||||
assert(!options->control || (kp && kp->ops));
|
|
||||||
assert(!options->control || (mp && mp->ops));
|
|
||||||
|
|
||||||
im->controller = controller;
|
im->controller = params->controller;
|
||||||
im->screen = screen;
|
im->screen = params->screen;
|
||||||
im->kp = kp;
|
im->kp = params->kp;
|
||||||
im->mp = mp;
|
im->mp = params->mp;
|
||||||
|
|
||||||
im->control = options->control;
|
im->control = params->control;
|
||||||
im->forward_all_clicks = options->forward_all_clicks;
|
im->forward_all_clicks = params->forward_all_clicks;
|
||||||
im->legacy_paste = options->legacy_paste;
|
im->legacy_paste = params->legacy_paste;
|
||||||
im->clipboard_autosync = options->clipboard_autosync;
|
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);
|
||||||
assert(shortcut_mods->count < SC_MAX_SHORTCUT_MODS);
|
assert(shortcut_mods->count < SC_MAX_SHORTCUT_MODS);
|
||||||
for (unsigned i = 0; i < shortcut_mods->count; ++i) {
|
for (unsigned i = 0; i < shortcut_mods->count; ++i) {
|
||||||
@@ -172,7 +171,6 @@ send_keycode(struct controller *controller, enum android_keycode keycode,
|
|||||||
|
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
LOGW("Could not request 'inject %s'", name);
|
LOGW("Could not request 'inject %s'", name);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +222,6 @@ press_back_or_turn_screen_on(struct controller *controller,
|
|||||||
|
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
LOGW("Could not request 'press back or turn screen on'");
|
LOGW("Could not request 'press back or turn screen on'");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -635,14 +632,6 @@ static void
|
|||||||
input_manager_process_mouse_motion(struct input_manager *im,
|
input_manager_process_mouse_motion(struct input_manager *im,
|
||||||
const SDL_MouseMotionEvent *event) {
|
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) {
|
if (event->which == SDL_TOUCH_MOUSEID) {
|
||||||
// simulated from touch events, so it's a duplicate
|
// simulated from touch events, so it's a duplicate
|
||||||
return;
|
return;
|
||||||
@@ -664,7 +653,11 @@ input_manager_process_mouse_motion(struct input_manager *im,
|
|||||||
assert(im->mp->ops->process_mouse_motion);
|
assert(im->mp->ops->process_mouse_motion);
|
||||||
im->mp->ops->process_mouse_motion(im->mp, &evt);
|
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) {
|
if (im->vfinger_down) {
|
||||||
|
assert(!im->mp->relative_mode); // assert one more time
|
||||||
struct sc_point mouse =
|
struct sc_point mouse =
|
||||||
screen_convert_window_to_frame_coords(im->screen, event->x,
|
screen_convert_window_to_frame_coords(im->screen, event->x,
|
||||||
event->y);
|
event->y);
|
||||||
@@ -777,6 +770,12 @@ input_manager_process_mouse_button(struct input_manager *im,
|
|||||||
assert(im->mp->ops->process_mouse_click);
|
assert(im->mp->ops->process_mouse_click);
|
||||||
im->mp->ops->process_mouse_click(im->mp, &evt);
|
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.
|
// Pinch-to-zoom simulation.
|
||||||
//
|
//
|
||||||
// If Ctrl is hold when the left-click button is pressed, then
|
// If Ctrl is hold when the left-click button is pressed, then
|
||||||
@@ -787,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
|
// In other words, the center of the rotation/scaling is the center of the
|
||||||
// screen.
|
// screen.
|
||||||
#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL))
|
#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL))
|
||||||
if ((down && !im->vfinger_down && CTRL_PRESSED)
|
if (event->button == SDL_BUTTON_LEFT &&
|
||||||
|| (!down && im->vfinger_down)) {
|
((down && !im->vfinger_down && CTRL_PRESSED) ||
|
||||||
|
(!down && im->vfinger_down))) {
|
||||||
struct sc_point mouse =
|
struct sc_point mouse =
|
||||||
screen_convert_window_to_frame_coords(im->screen, event->x,
|
screen_convert_window_to_frame_coords(im->screen, event->x,
|
||||||
event->y);
|
event->y);
|
||||||
@@ -814,7 +814,7 @@ input_manager_process_mouse_wheel(struct input_manager *im,
|
|||||||
// mouse_x and mouse_y are expressed in pixels relative to the window
|
// mouse_x and mouse_y are expressed in pixels relative to the window
|
||||||
int mouse_x;
|
int mouse_x;
|
||||||
int mouse_y;
|
int mouse_y;
|
||||||
SDL_GetMouseState(&mouse_x, &mouse_y);
|
uint32_t buttons = SDL_GetMouseState(&mouse_x, &mouse_y);
|
||||||
|
|
||||||
struct sc_mouse_scroll_event evt = {
|
struct sc_mouse_scroll_event evt = {
|
||||||
.position = {
|
.position = {
|
||||||
@@ -824,6 +824,8 @@ input_manager_process_mouse_wheel(struct input_manager *im,
|
|||||||
},
|
},
|
||||||
.hscroll = event->x,
|
.hscroll = event->x,
|
||||||
.vscroll = event->y,
|
.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);
|
im->mp->ops->process_mouse_scroll(im->mp, &evt);
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "fps_counter.h"
|
#include "fps_counter.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "screen.h"
|
|
||||||
#include "trait/key_processor.h"
|
#include "trait/key_processor.h"
|
||||||
#include "trait/mouse_processor.h"
|
#include "trait/mouse_processor.h"
|
||||||
|
|
||||||
@@ -43,11 +42,22 @@ struct input_manager {
|
|||||||
uint64_t next_sequence; // used for request acknowledgements
|
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
|
void
|
||||||
input_manager_init(struct input_manager *im, struct controller *controller,
|
input_manager_init(struct input_manager *im,
|
||||||
struct screen *screen, struct sc_key_processor *kp,
|
const struct input_manager_params *params);
|
||||||
struct sc_mouse_processor *mp,
|
|
||||||
const struct scrcpy_options *options);
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
input_manager_handle_event(struct input_manager *im, SDL_Event *event);
|
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
|
static void
|
||||||
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
||||||
const struct sc_mouse_motion_event *event) {
|
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 sc_mouse_inject *mi = DOWNCAST(mp);
|
||||||
|
|
||||||
struct control_msg msg = {
|
struct control_msg msg = {
|
||||||
@@ -108,6 +113,7 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
|
|||||||
.position = event->position,
|
.position = event->position,
|
||||||
.hscroll = event->hscroll,
|
.hscroll = event->hscroll,
|
||||||
.vscroll = event->vscroll,
|
.vscroll = event->vscroll,
|
||||||
|
.buttons = convert_mouse_buttons(event->buttons_state),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -150,4 +156,6 @@ sc_mouse_inject_init(struct sc_mouse_inject *mi,
|
|||||||
};
|
};
|
||||||
|
|
||||||
mi->mouse_processor.ops = &ops;
|
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,
|
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 {
|
enum sc_key_inject_mode {
|
||||||
// Inject special keys, letters and space as key events.
|
// Inject special keys, letters and space as key events.
|
||||||
// Inject numbers and punctuation as text events.
|
// Inject numbers and punctuation as text events.
|
||||||
@@ -90,6 +95,7 @@ struct scrcpy_options {
|
|||||||
enum sc_log_level log_level;
|
enum sc_log_level log_level;
|
||||||
enum sc_record_format record_format;
|
enum sc_record_format record_format;
|
||||||
enum sc_keyboard_input_mode keyboard_input_mode;
|
enum sc_keyboard_input_mode keyboard_input_mode;
|
||||||
|
enum sc_mouse_input_mode mouse_input_mode;
|
||||||
struct sc_port_range port_range;
|
struct sc_port_range port_range;
|
||||||
uint32_t tunnel_host;
|
uint32_t tunnel_host;
|
||||||
uint16_t tunnel_port;
|
uint16_t tunnel_port;
|
||||||
|
|||||||
166
app/src/scrcpy.c
166
app/src/scrcpy.c
@@ -17,7 +17,6 @@
|
|||||||
#include "decoder.h"
|
#include "decoder.h"
|
||||||
#include "events.h"
|
#include "events.h"
|
||||||
#include "file_handler.h"
|
#include "file_handler.h"
|
||||||
#include "input_manager.h"
|
|
||||||
#ifdef HAVE_AOA_HID
|
#ifdef HAVE_AOA_HID
|
||||||
# include "hid_keyboard.h"
|
# include "hid_keyboard.h"
|
||||||
# include "hid_mouse.h"
|
# include "hid_mouse.h"
|
||||||
@@ -63,7 +62,6 @@ struct scrcpy {
|
|||||||
struct sc_hid_mouse mouse_hid;
|
struct sc_hid_mouse mouse_hid;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
struct input_manager input_manager;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
@@ -195,11 +193,6 @@ handle_event(struct scrcpy *s, const struct scrcpy_options *options,
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool consumed = screen_handle_event(&s->screen, event);
|
bool consumed = screen_handle_event(&s->screen, event);
|
||||||
if (consumed) {
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
consumed = input_manager_handle_event(&s->input_manager, event);
|
|
||||||
(void) consumed;
|
(void) consumed;
|
||||||
|
|
||||||
end:
|
end:
|
||||||
@@ -342,6 +335,8 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
bool stream_started = false;
|
bool stream_started = false;
|
||||||
#ifdef HAVE_AOA_HID
|
#ifdef HAVE_AOA_HID
|
||||||
bool aoa_hid_initialized = false;
|
bool aoa_hid_initialized = false;
|
||||||
|
bool hid_keyboard_initialized = false;
|
||||||
|
bool hid_mouse_initialized = false;
|
||||||
#endif
|
#endif
|
||||||
bool controller_initialized = false;
|
bool controller_initialized = false;
|
||||||
bool controller_started = false;
|
bool controller_started = false;
|
||||||
@@ -456,17 +451,100 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
stream_add_sink(&s->stream, &rec->packet_sink);
|
stream_add_sink(&s->stream, &rec->packet_sink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct sc_key_processor *kp = NULL;
|
||||||
|
struct sc_mouse_processor *mp = NULL;
|
||||||
|
|
||||||
if (options->control) {
|
if (options->control) {
|
||||||
#ifdef HAVE_AOA_HID
|
#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);
|
bool ok = sc_acksync_init(&s->acksync);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
goto end;
|
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;
|
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
|
#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,
|
if (!controller_init(&s->controller, s->server.control_socket,
|
||||||
acksync)) {
|
acksync)) {
|
||||||
goto end;
|
goto end;
|
||||||
@@ -487,6 +565,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
LOGW("Could not request 'set screen power mode'");
|
LOGW("Could not request 'set screen power mode'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options->display) {
|
if (options->display) {
|
||||||
@@ -494,6 +573,14 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
options->window_title ? options->window_title : info->device_name;
|
options->window_title ? options->window_title : info->device_name;
|
||||||
|
|
||||||
struct screen_params screen_params = {
|
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,
|
.window_title = window_title,
|
||||||
.frame_size = info->frame_size,
|
.frame_size = info->frame_size,
|
||||||
.always_on_top = options->always_on_top,
|
.always_on_top = options->always_on_top,
|
||||||
@@ -536,65 +623,6 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
}
|
}
|
||||||
stream_started = true;
|
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);
|
|
||||||
sc_hid_mouse_init(&s->mouse_hid, &s->aoa);
|
|
||||||
mp = &s->mouse_hid.mouse_processor;
|
|
||||||
}
|
|
||||||
|
|
||||||
input_manager_init(&s->input_manager, &s->controller, &s->screen, kp, mp,
|
|
||||||
options);
|
|
||||||
|
|
||||||
ret = event_loop(s, options);
|
ret = event_loop(s, options);
|
||||||
LOGD("quit...");
|
LOGD("quit...");
|
||||||
|
|
||||||
@@ -607,7 +635,9 @@ end:
|
|||||||
// end-of-stream
|
// end-of-stream
|
||||||
#ifdef HAVE_AOA_HID
|
#ifdef HAVE_AOA_HID
|
||||||
if (aoa_hid_initialized) {
|
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);
|
sc_aoa_stop(&s->aoa);
|
||||||
}
|
}
|
||||||
if (acksync) {
|
if (acksync) {
|
||||||
|
|||||||
@@ -156,6 +156,17 @@ get_initial_optimal_size(struct sc_size content_size, uint16_t req_width,
|
|||||||
return window_size;
|
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
|
static void
|
||||||
screen_update_content_rect(struct screen *screen) {
|
screen_update_content_rect(struct screen *screen) {
|
||||||
int dw;
|
int dw;
|
||||||
@@ -354,6 +365,8 @@ screen_init(struct screen *screen, const struct screen_params *params) {
|
|||||||
screen->fullscreen = false;
|
screen->fullscreen = false;
|
||||||
screen->maximized = false;
|
screen->maximized = false;
|
||||||
screen->event_failed = false;
|
screen->event_failed = false;
|
||||||
|
screen->mouse_captured = false;
|
||||||
|
screen->mouse_capture_key_pressed = 0;
|
||||||
|
|
||||||
static const struct sc_video_buffer_callbacks cbs = {
|
static const struct sc_video_buffer_callbacks cbs = {
|
||||||
.on_new_frame = sc_video_buffer_on_new_frame,
|
.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;
|
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
|
// Reset the window size to trigger a SIZE_CHANGED event, to workaround
|
||||||
// HiDPI issues with some SDL renderers when several displays having
|
// HiDPI issues with some SDL renderers when several displays having
|
||||||
// different HiDPI scaling are connected
|
// different HiDPI scaling are connected
|
||||||
@@ -485,10 +512,6 @@ screen_init(struct screen *screen, const struct screen_params *params) {
|
|||||||
SDL_AddEventWatch(event_watcher, screen);
|
SDL_AddEventWatch(event_watcher, screen);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (SDL_SetRelativeMouseMode(true)) {
|
|
||||||
LOGE("Could not set relative mouse mode: %s", SDL_GetError());
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct sc_frame_sink_ops ops = {
|
static const struct sc_frame_sink_ops ops = {
|
||||||
.open = screen_frame_sink_open,
|
.open = screen_frame_sink_open,
|
||||||
.close = screen_frame_sink_close,
|
.close = screen_frame_sink_close,
|
||||||
@@ -731,6 +754,11 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
|
|||||||
content_size.height);
|
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
|
bool
|
||||||
screen_handle_event(struct screen *screen, SDL_Event *event) {
|
screen_handle_event(struct screen *screen, SDL_Event *event) {
|
||||||
switch (event->type) {
|
switch (event->type) {
|
||||||
@@ -773,11 +801,71 @@ screen_handle_event(struct screen *screen, SDL_Event *event) {
|
|||||||
apply_pending_resize(screen);
|
apply_pending_resize(screen);
|
||||||
screen_render(screen, true);
|
screen_render(screen, true);
|
||||||
break;
|
break;
|
||||||
|
case SDL_WINDOWEVENT_FOCUS_LOST:
|
||||||
|
if (screen->im.mp->relative_mode) {
|
||||||
|
screen_capture_mouse(screen, false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return true;
|
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
|
struct sc_point
|
||||||
|
|||||||
@@ -7,10 +7,14 @@
|
|||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
|
#include "controller.h"
|
||||||
#include "coords.h"
|
#include "coords.h"
|
||||||
#include "fps_counter.h"
|
#include "fps_counter.h"
|
||||||
|
#include "input_manager.h"
|
||||||
#include "opengl.h"
|
#include "opengl.h"
|
||||||
|
#include "trait/key_processor.h"
|
||||||
#include "trait/frame_sink.h"
|
#include "trait/frame_sink.h"
|
||||||
|
#include "trait/mouse_processor.h"
|
||||||
#include "video_buffer.h"
|
#include "video_buffer.h"
|
||||||
|
|
||||||
struct screen {
|
struct screen {
|
||||||
@@ -20,6 +24,7 @@ struct screen {
|
|||||||
bool open; // track the open/close state to assert correct behavior
|
bool open; // track the open/close state to assert correct behavior
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
struct input_manager im;
|
||||||
struct sc_video_buffer vb;
|
struct sc_video_buffer vb;
|
||||||
struct fps_counter fps_counter;
|
struct fps_counter fps_counter;
|
||||||
|
|
||||||
@@ -46,10 +51,25 @@ struct screen {
|
|||||||
|
|
||||||
bool event_failed; // in case SDL_PushEvent() returned an error
|
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;
|
AVFrame *frame;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct screen_params {
|
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;
|
const char *window_title;
|
||||||
struct sc_size frame_size;
|
struct sc_size frame_size;
|
||||||
bool always_on_top;
|
bool always_on_top;
|
||||||
|
|||||||
@@ -16,6 +16,13 @@
|
|||||||
*/
|
*/
|
||||||
struct sc_mouse_processor {
|
struct sc_mouse_processor {
|
||||||
const struct sc_mouse_processor_ops *ops;
|
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 {
|
struct sc_mouse_processor_ops {
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ unwrap(sc_socket socket) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef HAVE_SOCK_CLOEXEC // avoid unused-function warning
|
||||||
static inline bool
|
static inline bool
|
||||||
sc_raw_socket_close(sc_raw_socket raw_sock) {
|
sc_raw_socket_close(sc_raw_socket raw_sock) {
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
@@ -91,6 +92,7 @@ sc_raw_socket_close(sc_raw_socket raw_sock) {
|
|||||||
return !closesocket(raw_sock);
|
return !closesocket(raw_sock);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef HAVE_SOCK_CLOEXEC
|
#ifndef HAVE_SOCK_CLOEXEC
|
||||||
// If SOCK_CLOEXEC does not exist, the flag must be set manually once the
|
// 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));
|
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
|
||||||
assert(ip);
|
assert(ip);
|
||||||
assert(!strcmp(ip, "192.168.12.34"));
|
assert(!strcmp(ip, "192.168.12.34"));
|
||||||
|
free(ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_get_ip_single_line_without_eol() {
|
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));
|
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
|
||||||
assert(ip);
|
assert(ip);
|
||||||
assert(!strcmp(ip, "192.168.12.34"));
|
assert(!strcmp(ip, "192.168.12.34"));
|
||||||
|
free(ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_get_ip_single_line_with_trailing_space() {
|
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));
|
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
|
||||||
assert(ip);
|
assert(ip);
|
||||||
assert(!strcmp(ip, "192.168.12.34"));
|
assert(!strcmp(ip, "192.168.12.34"));
|
||||||
|
free(ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_get_ip_multiline_first_ok() {
|
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));
|
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
|
||||||
assert(ip);
|
assert(ip);
|
||||||
assert(!strcmp(ip, "192.168.1.2"));
|
assert(!strcmp(ip, "192.168.1.2"));
|
||||||
|
free(ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_get_ip_multiline_second_ok() {
|
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));
|
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
|
||||||
assert(ip);
|
assert(ip);
|
||||||
assert(!strcmp(ip, "192.168.1.3"));
|
assert(!strcmp(ip, "192.168.1.3"));
|
||||||
|
free(ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_get_ip_no_wlan() {
|
static void test_get_ip_no_wlan() {
|
||||||
|
|||||||
@@ -126,12 +126,13 @@ static void test_serialize_inject_scroll_event(void) {
|
|||||||
},
|
},
|
||||||
.hscroll = 1,
|
.hscroll = 1,
|
||||||
.vscroll = -1,
|
.vscroll = -1,
|
||||||
|
.buttons = 1,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
size_t size = control_msg_serialize(&msg, buf);
|
size_t size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 21);
|
assert(size == 25);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
|
CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
|
||||||
@@ -139,6 +140,7 @@ static void test_serialize_inject_scroll_event(void) {
|
|||||||
0x04, 0x38, 0x07, 0x80, // 1080 1920
|
0x04, 0x38, 0x07, 0x80, // 1080 1920
|
||||||
0x00, 0x00, 0x00, 0x01, // 1
|
0x00, 0x00, 0x00, 0x01, // 1
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, // -1
|
0xFF, 0xFF, 0xFF, 0xFF, // -1
|
||||||
|
0x00, 0x00, 0x00, 0x01, // 1
|
||||||
};
|
};
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,12 +71,13 @@ public final class ControlMessage {
|
|||||||
return msg;
|
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();
|
ControlMessage msg = new ControlMessage();
|
||||||
msg.type = TYPE_INJECT_SCROLL_EVENT;
|
msg.type = TYPE_INJECT_SCROLL_EVENT;
|
||||||
msg.position = position;
|
msg.position = position;
|
||||||
msg.hScroll = hScroll;
|
msg.hScroll = hScroll;
|
||||||
msg.vScroll = vScroll;
|
msg.vScroll = vScroll;
|
||||||
|
msg.buttons = buttons;
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ public class ControlMessageReader {
|
|||||||
|
|
||||||
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13;
|
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13;
|
||||||
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
|
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 BACK_OR_SCREEN_ON_LENGTH = 1;
|
||||||
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
||||||
static final int GET_CLIPBOARD_LENGTH = 1;
|
static final int GET_CLIPBOARD_LENGTH = 1;
|
||||||
@@ -154,7 +154,8 @@ public class ControlMessageReader {
|
|||||||
Position position = readPosition(buffer);
|
Position position = readPosition(buffer);
|
||||||
int hScroll = buffer.getInt();
|
int hScroll = buffer.getInt();
|
||||||
int vScroll = 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() {
|
private ControlMessage parseBackOrScreenOnEvent() {
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ public class Controller {
|
|||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
|
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
|
||||||
if (device.supportsInputEvents()) {
|
if (device.supportsInputEvents()) {
|
||||||
injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll());
|
injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll(), msg.getButtons());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
||||||
@@ -221,7 +221,7 @@ public class Controller {
|
|||||||
return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
|
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();
|
long now = SystemClock.uptimeMillis();
|
||||||
Point point = device.getPhysicalPoint(position);
|
Point point = device.getPhysicalPoint(position);
|
||||||
if (point == null) {
|
if (point == null) {
|
||||||
@@ -239,7 +239,7 @@ public class Controller {
|
|||||||
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
|
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
|
||||||
|
|
||||||
MotionEvent event = MotionEvent
|
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);
|
InputDevice.SOURCE_MOUSE, 0);
|
||||||
return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
|
return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ public class ControlMessageReaderTest {
|
|||||||
dos.writeShort(1920);
|
dos.writeShort(1920);
|
||||||
dos.writeInt(1);
|
dos.writeInt(1);
|
||||||
dos.writeInt(-1);
|
dos.writeInt(-1);
|
||||||
|
dos.writeInt(1);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
@@ -144,6 +145,7 @@ public class ControlMessageReaderTest {
|
|||||||
Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight());
|
Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight());
|
||||||
Assert.assertEquals(1, event.getHScroll());
|
Assert.assertEquals(1, event.getHScroll());
|
||||||
Assert.assertEquals(-1, event.getVScroll());
|
Assert.assertEquals(-1, event.getVScroll());
|
||||||
|
Assert.assertEquals(1, event.getButtons());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user