Compare commits

..

9 Commits

Author SHA1 Message Date
Romain Vimont
3b6774174d wip 2021-12-29 01:42:28 +01:00
Romain Vimont
4640b088ef wip 2021-12-29 01:42:27 +01:00
Romain Vimont
bbe8a99a3e wip 2021-12-29 01:40:10 +01:00
Romain Vimont
43416b6654 wip 2021-12-29 01:40:07 +01:00
Romain Vimont
7f781f1085 wip 2021-12-29 01:40:04 +01:00
Romain Vimont
c4648f1078 wip 2021-12-29 01:40:01 +01:00
Romain Vimont
eec2d1d160 input_events 2021-12-29 01:39:58 +01:00
Romain Vimont
ccfb4d3cfd Rename SC_MOD_* to SC_SHORTCUT_MOD_*
This will avoid conflicts with new SC_MOD_* constants.
2021-12-29 01:39:55 +01:00
Romain Vimont
49a788841d Remove actions bitset
The input manager exposed functions taking an "actions" parameter,
containing a bitmask-OR of ACTION_UP and ACTION_DOWN.

But they are never called with both actions simultaneously anymore, so
simplify.

Refs 964b6d2243
Refs d0739911a3
2021-12-29 01:34:54 +01:00
70 changed files with 1415 additions and 2026 deletions

14
FAQ.md
View File

@@ -219,9 +219,6 @@ scrcpy -m 1024
scrcpy -m 800
```
Since scrcpy v1.22, scrcpy automatically tries again with a lower definition
before failing. This behavior can be disabled with `--no-downsize-on-error`.
You could also try another [encoder](README.md#encoder).
@@ -248,15 +245,8 @@ Caused by: java.lang.IllegalArgumentException: displayToken must not be null
## Command line on Windows
Since v1.22, a "shortcut" has been added to directly open a terminal in the
scrcpy directory. Double-click on `open_a_terminal_here.bat`, then type your
command. For example:
```
scrcpy --record file.mkv
```
You could also open a terminal and go to the scrcpy folder manually:
Some Windows users are not familiar with the command line. Here is how to open a
terminal and run `scrcpy` with arguments:
1. Press <kbd>Windows</kbd>+<kbd>r</kbd>, this opens a dialog box.
2. Type `cmd` and press <kbd>Enter</kbd>, this opens a terminal.

View File

@@ -188,7 +188,7 @@
identification within third-party archives.
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont
Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -672,7 +672,7 @@ Baca [halaman pengembang].
## Lisensi
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont
Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -790,7 +790,7 @@ Leggi la [pagina per sviluppatori].
## Licenza (in inglese)
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont
Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -776,7 +776,7 @@ _⁴Android 7以上のみ._
## ライセンス
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont
Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -475,7 +475,7 @@ _²화면이 꺼진 상태에서 우클릭 시 다시 켜지며, 그 외의 상
## 라이선스
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont
Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -31,8 +31,6 @@ 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
@@ -817,35 +815,6 @@ 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
@@ -1048,7 +1017,7 @@ Read the [developers page].
## Licence
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont
Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -857,7 +857,7 @@ Leia a [página dos desenvolvedores][developers page].
## Licença
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont
Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -720,7 +720,7 @@ Lea la [hoja de desarrolladores (en inglés)](DEVELOP.md).
## Licencia
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont
Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -801,7 +801,7 @@ Bakınız [FAQ](FAQ.md).
## Lisans
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont
Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -842,7 +842,7 @@ ADB=/path/to/adb scrcpy
## 许可协议
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont
Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -679,7 +679,7 @@ _³只支援 Android 7+。_
## Licence
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont
Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -11,9 +11,10 @@ src = [
'src/decoder.c',
'src/device_msg.c',
'src/icon.c',
'src/file_pusher.c',
'src/file_handler.c',
'src/fps_counter.c',
'src/frame_buffer.c',
'src/input_events.c',
'src/input_manager.c',
'src/keyboard_inject.c',
'src/mouse_inject.c',
@@ -77,7 +78,6 @@ if aoa_hid_support
src += [
'src/aoa_hid.c',
'src/hid_keyboard.c',
'src/hid_mouse.c',
]
endif
@@ -118,20 +118,15 @@ else
include_directories: include_directories(sdl2_include_dir)
)
prebuilt_ffmpeg = meson.get_cross_property('prebuilt_ffmpeg')
ffmpeg_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_ffmpeg + '/bin'
ffmpeg_include_dir = '../prebuilt-deps/' + prebuilt_ffmpeg + '/include'
# ffmpeg versions are different for win32 and win64 builds
ffmpeg_avcodec = meson.get_cross_property('ffmpeg_avcodec')
ffmpeg_avformat = meson.get_cross_property('ffmpeg_avformat')
ffmpeg_avutil = meson.get_cross_property('ffmpeg_avutil')
prebuilt_ffmpeg_shared = meson.get_cross_property('prebuilt_ffmpeg_shared')
prebuilt_ffmpeg_dev = meson.get_cross_property('prebuilt_ffmpeg_dev')
ffmpeg_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_ffmpeg_shared + '/bin'
ffmpeg_include_dir = '../prebuilt-deps/' + prebuilt_ffmpeg_dev + '/include'
ffmpeg = declare_dependency(
dependencies: [
cc.find_library(ffmpeg_avcodec, dirs: ffmpeg_bin_dir),
cc.find_library(ffmpeg_avformat, dirs: ffmpeg_bin_dir),
cc.find_library(ffmpeg_avutil, dirs: ffmpeg_bin_dir),
cc.find_library('avcodec-58', dirs: ffmpeg_bin_dir),
cc.find_library('avformat-58', dirs: ffmpeg_bin_dir),
cc.find_library('avutil-56', dirs: ffmpeg_bin_dir),
],
include_directories: include_directories(ffmpeg_include_dir)
)

View File

@@ -96,8 +96,6 @@ 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).
@@ -122,30 +120,12 @@ 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.
This option disables this automatic synchronization.
.TP
.B \-\-no\-downsize\-on\-error
By default, on MediaCodec error, scrcpy automatically tries again with a lower definition.
This option disables this behavior.
.TP
.B \-n, \-\-no\-control
Disable device control (mirror the device in read\-only).
@@ -466,7 +446,7 @@ Copyright \(co 2018 Genymobile
Genymobile
.UE
Copyright \(co 2018\-2022
Copyright \(co 2018\-2021
.MT rom@rom1v.com
Romain Vimont
.ME

View File

@@ -56,13 +56,14 @@ accept_device(libusb_device *device, const char *serial) {
// devices available on the computer have permission restrictions
struct libusb_device_descriptor desc;
int result = libusb_get_device_descriptor(device, &desc);
if (result < 0 || !desc.iSerialNumber) {
libusb_get_device_descriptor(device, &desc);
if (!desc.iSerialNumber) {
return false;
}
libusb_device_handle *handle;
result = libusb_open(device, &handle);
int result = libusb_open(device, &handle);
if (result < 0) {
return false;
}
@@ -130,22 +131,31 @@ sc_aoa_init(struct sc_aoa *aoa, const char *serial,
}
if (!sc_cond_init(&aoa->event_cond)) {
goto error_destroy_mutex;
sc_mutex_destroy(&aoa->mutex);
return false;
}
if (libusb_init(&aoa->usb_context) != LIBUSB_SUCCESS) {
goto error_destroy_cond;
sc_cond_destroy(&aoa->event_cond);
sc_mutex_destroy(&aoa->mutex);
return false;
}
aoa->usb_device = sc_aoa_find_usb_device(serial);
if (!aoa->usb_device) {
LOGW("USB device of serial %s not found", serial);
goto error_exit_libusb;
libusb_exit(aoa->usb_context);
sc_mutex_destroy(&aoa->mutex);
sc_cond_destroy(&aoa->event_cond);
return false;
}
if (sc_aoa_open_usb_handle(aoa->usb_device, &aoa->usb_handle) < 0) {
LOGW("Open USB handle failed");
goto error_unref_device;
libusb_unref_device(aoa->usb_device);
libusb_exit(aoa->usb_context);
sc_cond_destroy(&aoa->event_cond);
sc_mutex_destroy(&aoa->mutex);
return false;
}
@@ -153,16 +163,6 @@ sc_aoa_init(struct sc_aoa *aoa, const char *serial,
aoa->acksync = acksync;
return true;
error_unref_device:
libusb_unref_device(aoa->usb_device);
error_exit_libusb:
libusb_exit(aoa->usb_context);
error_destroy_cond:
sc_cond_destroy(&aoa->event_cond);
error_destroy_mutex:
sc_mutex_destroy(&aoa->mutex);
return false;
}
void
@@ -343,8 +343,8 @@ run_aoa_thread(void *data) {
if (ack_to_wait != SC_SEQUENCE_INVALID) {
LOGD("Waiting ack from server sequence=%" PRIu64_, ack_to_wait);
// Do not block the loop indefinitely if the ack never comes (it
// should never happen)
// Do not block the loop indefinitely if the ack never comes (it should
// never happen)
sc_tick deadline = sc_tick_now() + SC_TICK_FROM_MS(500);
enum sc_acksync_wait_result result =
sc_acksync_wait(aoa->acksync, ack_to_wait, deadline);

View File

@@ -52,7 +52,6 @@
#define OPT_NO_CLIPBOARD_AUTOSYNC 1032
#define OPT_TCPIP 1033
#define OPT_RAW_KEY_EVENTS 1034
#define OPT_NO_DOWNSIZE_ON_ERROR 1035
struct sc_option {
char shortopt;
@@ -179,8 +178,7 @@ 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).\n"
"Also see --hid-mouse.",
"is enabled (or a physical keyboard is connected).",
},
{
.shortopt = 'h',
@@ -216,18 +214,6 @@ 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",
@@ -237,13 +223,6 @@ static const struct sc_option options[] = {
"is preserved.\n"
"Default is 0 (unlimited).",
},
{
.longopt_id = OPT_NO_DOWNSIZE_ON_ERROR,
.longopt = "no-downsize-on-error",
.text = "By default, on MediaCodec error, scrcpy automatically tries "
"again with a lower definition.\n"
"This option disables this behavior.",
},
{
.longopt_id = OPT_NO_CLIPBOARD_AUTOSYNC,
.longopt = "no-clipboard-autosync",
@@ -261,8 +240,11 @@ static const struct sc_option options[] = {
{
.shortopt = 'N',
.longopt = "no-display",
.text = "Do not display device (only when screen recording or V4L2 "
"sink is enabled).",
.text = "Do not display device (only when screen recording "
#ifdef HAVE_V4L2
"or V4L2 sink "
#endif
"is enabled).",
},
{
.longopt_id = OPT_NO_KEY_REPEAT,
@@ -399,14 +381,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).\n"
"This feature is only available on Linux.",
"--lock-video-orientation).",
},
{
.longopt_id = OPT_V4L2_BUFFER,
@@ -416,9 +398,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).\n"
"This option is only available on Linux.",
"Default is 0 (no buffering).",
},
#endif
{
.shortopt = 'V',
.longopt = "verbosity",
@@ -1318,14 +1300,8 @@ 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;
break;
#else
LOGE("HID over AOA (-K/--hid-keyboard) is not supported on "
"this platform. It is only available on Linux.");
return false;
#endif
case OPT_MAX_FPS:
if (!parse_max_fps(optarg, &opts->max_fps)) {
return false;
@@ -1336,15 +1312,6 @@ 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;
break;
#else
LOGE("HID over AOA (-M/--hid-mouse) is not supported on this"
"platform. It is only available on Linux.");
return false;
#endif
case OPT_LOCK_VIDEO_ORIENTATION:
if (!parse_lock_video_orientation(optarg,
&opts->lock_video_orientation)) {
@@ -1497,26 +1464,15 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
opts->tcpip = true;
opts->tcpip_dst = optarg;
break;
case OPT_NO_DOWNSIZE_ON_ERROR:
opts->downsize_on_error = false;
break;
case OPT_V4L2_SINK:
#ifdef HAVE_V4L2
case OPT_V4L2_SINK:
opts->v4l2_device = optarg;
break;
#else
LOGE("V4L2 (--v4l2-sink) is only available on Linux.");
return false;
#endif
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
default:
// getopt prints the error message on stderr
@@ -1545,18 +1501,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false;
}
if (opts->v4l2_device) {
if (opts->lock_video_orientation ==
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
LOGI("Video orientation is locked for v4l2 sink. "
"See --lock-video-orientation.");
opts->lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_INITIAL;
}
// V4L2 could not handle size change.
// Do not log because downsizing on error is the default behavior,
// not an explicit request from the user.
opts->downsize_on_error = false;
if (opts->v4l2_device && opts->lock_video_orientation
== SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
LOGI("Video orientation is locked for v4l2 sink. "
"See --lock-video-orientation.");
opts->lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_INITIAL;
}
if (opts->v4l2_buffer && !opts->v4l2_device) {
@@ -1591,23 +1540,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
}
}
if (!opts->control) {
if (opts->turn_screen_off) {
LOGE("Could not request to turn screen off if control is disabled");
return false;
}
if (opts->stay_awake) {
LOGE("Could not request to stay awake if control is disabled");
return false;
}
if (opts->show_touches) {
LOGE("Could not request to show touches if control is disabled");
return false;
}
if (opts->power_off_on_close) {
LOGE("Could not request power off on close if control is disabled");
return false;
}
if (!opts->control && opts->turn_screen_off) {
LOGE("Could not request to turn screen off if control is disabled");
return false;
}
if (!opts->control && opts->stay_awake) {
LOGE("Could not request to stay awake if control is disabled");
return false;
}
return true;

View File

@@ -7,7 +7,6 @@
#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)))

View File

@@ -89,22 +89,22 @@ to_fixed_point_16(float f) {
}
size_t
sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
buf[0] = msg->type;
switch (msg->type) {
case SC_CONTROL_MSG_TYPE_INJECT_KEYCODE:
case CONTROL_MSG_TYPE_INJECT_KEYCODE:
buf[1] = msg->inject_keycode.action;
buffer_write32be(&buf[2], msg->inject_keycode.keycode);
buffer_write32be(&buf[6], msg->inject_keycode.repeat);
buffer_write32be(&buf[10], msg->inject_keycode.metastate);
return 14;
case SC_CONTROL_MSG_TYPE_INJECT_TEXT: {
case CONTROL_MSG_TYPE_INJECT_TEXT: {
size_t len =
write_string(msg->inject_text.text,
SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH, &buf[1]);
CONTROL_MSG_INJECT_TEXT_MAX_LENGTH, &buf[1]);
return 1 + len;
}
case SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
buf[1] = msg->inject_touch_event.action;
buffer_write64be(&buf[2], msg->inject_touch_event.pointer_id);
write_position(&buf[10], &msg->inject_touch_event.position);
@@ -113,34 +113,34 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
buffer_write16be(&buf[22], pressure);
buffer_write32be(&buf[24], msg->inject_touch_event.buttons);
return 28;
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
write_position(&buf[1], &msg->inject_scroll_event.position);
buffer_write32be(&buf[13],
(uint32_t) msg->inject_scroll_event.hscroll);
buffer_write32be(&buf[17],
(uint32_t) msg->inject_scroll_event.vscroll);
buffer_write32be(&buf[21], msg->inject_scroll_event.buttons);
return 25;
case SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
return 21;
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
buf[1] = msg->inject_keycode.action;
return 2;
case SC_CONTROL_MSG_TYPE_GET_CLIPBOARD:
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
buf[1] = msg->get_clipboard.copy_key;
return 2;
case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD:
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
buffer_write64be(&buf[1], msg->set_clipboard.sequence);
buf[9] = !!msg->set_clipboard.paste;
size_t len = write_string(msg->set_clipboard.text,
SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
&buf[10]);
return 10 + len;
case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
}
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
buf[1] = msg->set_screen_power_mode.mode;
return 2;
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
// no additional data
return 1;
default:
@@ -150,20 +150,20 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
}
void
sc_control_msg_log(const struct sc_control_msg *msg) {
control_msg_log(const struct control_msg *msg) {
#define LOG_CMSG(fmt, ...) LOGV("input: " fmt, ## __VA_ARGS__)
switch (msg->type) {
case SC_CONTROL_MSG_TYPE_INJECT_KEYCODE:
case CONTROL_MSG_TYPE_INJECT_KEYCODE:
LOG_CMSG("key %-4s code=%d repeat=%" PRIu32 " meta=%06lx",
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action),
(int) msg->inject_keycode.keycode,
msg->inject_keycode.repeat,
(long) msg->inject_keycode.metastate);
break;
case SC_CONTROL_MSG_TYPE_INJECT_TEXT:
case CONTROL_MSG_TYPE_INJECT_TEXT:
LOG_CMSG("text \"%s\"", msg->inject_text.text);
break;
case SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT: {
case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT: {
int action = msg->inject_touch_event.action
& AMOTION_EVENT_ACTION_MASK;
uint64_t id = msg->inject_touch_event.pointer_id;
@@ -190,43 +190,42 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
}
break;
}
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%" PRIi32
" vscroll=%" PRIi32 " buttons=%06lx",
" vscroll=%" PRIi32,
msg->inject_scroll_event.position.point.x,
msg->inject_scroll_event.position.point.y,
msg->inject_scroll_event.hscroll,
msg->inject_scroll_event.vscroll,
(long) msg->inject_scroll_event.buttons);
msg->inject_scroll_event.vscroll);
break;
case SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
LOG_CMSG("back-or-screen-on %s",
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action));
break;
case SC_CONTROL_MSG_TYPE_GET_CLIPBOARD:
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
LOG_CMSG("get clipboard copy_key=%s",
copy_key_labels[msg->get_clipboard.copy_key]);
break;
case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD:
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
LOG_CMSG("clipboard %" PRIu64_ " %s \"%s\"",
msg->set_clipboard.sequence,
msg->set_clipboard.paste ? "paste" : "nopaste",
msg->set_clipboard.text);
break;
case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
LOG_CMSG("power mode %s",
SCREEN_POWER_MODE_LABEL(msg->set_screen_power_mode.mode));
break;
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
LOG_CMSG("expand notification panel");
break;
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
LOG_CMSG("expand settings panel");
break;
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
LOG_CMSG("collapse panels");
break;
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
LOG_CMSG("rotate device");
break;
default:
@@ -236,12 +235,12 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
}
void
sc_control_msg_destroy(struct sc_control_msg *msg) {
control_msg_destroy(struct control_msg *msg) {
switch (msg->type) {
case SC_CONTROL_MSG_TYPE_INJECT_TEXT:
case CONTROL_MSG_TYPE_INJECT_TEXT:
free(msg->inject_text.text);
break;
case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD:
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
free(msg->set_clipboard.text);
break;
default:

View File

@@ -11,44 +11,44 @@
#include "android/keycodes.h"
#include "coords.h"
#define SC_CONTROL_MSG_MAX_SIZE (1 << 18) // 256k
#define CONTROL_MSG_MAX_SIZE (1 << 18) // 256k
#define SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
// type: 1 byte; sequence: 8 bytes; paste flag: 1 byte; length: 4 bytes
#define SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (SC_CONTROL_MSG_MAX_SIZE - 14)
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 14)
#define POINTER_ID_MOUSE UINT64_C(-1)
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2)
enum sc_control_msg_type {
SC_CONTROL_MSG_TYPE_INJECT_KEYCODE,
SC_CONTROL_MSG_TYPE_INJECT_TEXT,
SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS,
SC_CONTROL_MSG_TYPE_GET_CLIPBOARD,
SC_CONTROL_MSG_TYPE_SET_CLIPBOARD,
SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
SC_CONTROL_MSG_TYPE_ROTATE_DEVICE,
enum control_msg_type {
CONTROL_MSG_TYPE_INJECT_KEYCODE,
CONTROL_MSG_TYPE_INJECT_TEXT,
CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
CONTROL_MSG_TYPE_COLLAPSE_PANELS,
CONTROL_MSG_TYPE_GET_CLIPBOARD,
CONTROL_MSG_TYPE_SET_CLIPBOARD,
CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
CONTROL_MSG_TYPE_ROTATE_DEVICE,
};
enum sc_screen_power_mode {
enum screen_power_mode {
// see <https://android.googlesource.com/platform/frameworks/base.git/+/pie-release-2/core/java/android/view/SurfaceControl.java#305>
SC_SCREEN_POWER_MODE_OFF = 0,
SC_SCREEN_POWER_MODE_NORMAL = 2,
SCREEN_POWER_MODE_OFF = 0,
SCREEN_POWER_MODE_NORMAL = 2,
};
enum sc_copy_key {
SC_COPY_KEY_NONE,
SC_COPY_KEY_COPY,
SC_COPY_KEY_CUT,
enum get_clipboard_copy_key {
GET_CLIPBOARD_COPY_KEY_NONE,
GET_CLIPBOARD_COPY_KEY_COPY,
GET_CLIPBOARD_COPY_KEY_CUT,
};
struct sc_control_msg {
enum sc_control_msg_type type;
struct control_msg {
enum control_msg_type type;
union {
struct {
enum android_keyevent_action action;
@@ -70,14 +70,13 @@ struct sc_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
// screen may only be turned on on ACTION_DOWN
} back_or_screen_on;
struct {
enum sc_copy_key copy_key;
enum get_clipboard_copy_key copy_key;
} get_clipboard;
struct {
uint64_t sequence;
@@ -85,7 +84,7 @@ struct sc_control_msg {
bool paste;
} set_clipboard;
struct {
enum sc_screen_power_mode mode;
enum screen_power_mode mode;
} set_screen_power_mode;
};
};
@@ -93,12 +92,12 @@ struct sc_control_msg {
// buf size must be at least CONTROL_MSG_MAX_SIZE
// return the number of bytes written
size_t
sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf);
control_msg_serialize(const struct control_msg *msg, unsigned char *buf);
void
sc_control_msg_log(const struct sc_control_msg *msg);
control_msg_log(const struct control_msg *msg);
void
sc_control_msg_destroy(struct sc_control_msg *msg);
control_msg_destroy(struct control_msg *msg);
#endif

View File

@@ -5,8 +5,8 @@
#include "util/log.h"
bool
sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
struct sc_acksync *acksync) {
controller_init(struct controller *controller, sc_socket control_socket,
struct sc_acksync *acksync) {
cbuf_init(&controller->queue);
bool ok = receiver_init(&controller->receiver, control_socket, acksync);
@@ -34,23 +34,23 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
}
void
sc_controller_destroy(struct sc_controller *controller) {
controller_destroy(struct controller *controller) {
sc_cond_destroy(&controller->msg_cond);
sc_mutex_destroy(&controller->mutex);
struct sc_control_msg msg;
struct control_msg msg;
while (cbuf_take(&controller->queue, &msg)) {
sc_control_msg_destroy(&msg);
control_msg_destroy(&msg);
}
receiver_destroy(&controller->receiver);
}
bool
sc_controller_push_msg(struct sc_controller *controller,
const struct sc_control_msg *msg) {
controller_push_msg(struct controller *controller,
const struct control_msg *msg) {
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
sc_control_msg_log(msg);
control_msg_log(msg);
}
sc_mutex_lock(&controller->mutex);
@@ -64,10 +64,9 @@ sc_controller_push_msg(struct sc_controller *controller,
}
static bool
process_msg(struct sc_controller *controller,
const struct sc_control_msg *msg) {
static unsigned char serialized_msg[SC_CONTROL_MSG_MAX_SIZE];
size_t length = sc_control_msg_serialize(msg, serialized_msg);
process_msg(struct controller *controller, const struct control_msg *msg) {
static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE];
size_t length = control_msg_serialize(msg, serialized_msg);
if (!length) {
return false;
}
@@ -78,7 +77,7 @@ process_msg(struct sc_controller *controller,
static int
run_controller(void *data) {
struct sc_controller *controller = data;
struct controller *controller = data;
for (;;) {
sc_mutex_lock(&controller->mutex);
@@ -90,14 +89,14 @@ run_controller(void *data) {
sc_mutex_unlock(&controller->mutex);
break;
}
struct sc_control_msg msg;
struct control_msg msg;
bool non_empty = cbuf_take(&controller->queue, &msg);
assert(non_empty);
(void) non_empty;
sc_mutex_unlock(&controller->mutex);
bool ok = process_msg(controller, &msg);
sc_control_msg_destroy(&msg);
control_msg_destroy(&msg);
if (!ok) {
LOGD("Could not write msg to socket");
break;
@@ -107,7 +106,7 @@ run_controller(void *data) {
}
bool
sc_controller_start(struct sc_controller *controller) {
controller_start(struct controller *controller) {
LOGD("Starting controller thread");
bool ok = sc_thread_create(&controller->thread, run_controller,
@@ -118,7 +117,7 @@ sc_controller_start(struct sc_controller *controller) {
}
if (!receiver_start(&controller->receiver)) {
sc_controller_stop(controller);
controller_stop(controller);
sc_thread_join(&controller->thread, NULL);
return false;
}
@@ -127,7 +126,7 @@ sc_controller_start(struct sc_controller *controller) {
}
void
sc_controller_stop(struct sc_controller *controller) {
controller_stop(struct controller *controller) {
sc_mutex_lock(&controller->mutex);
controller->stopped = true;
sc_cond_signal(&controller->msg_cond);
@@ -135,7 +134,7 @@ sc_controller_stop(struct sc_controller *controller) {
}
void
sc_controller_join(struct sc_controller *controller) {
controller_join(struct controller *controller) {
sc_thread_join(&controller->thread, NULL);
receiver_join(&controller->receiver);
}

View File

@@ -12,36 +12,36 @@
#include "util/net.h"
#include "util/thread.h"
struct sc_control_msg_queue CBUF(struct sc_control_msg, 64);
struct control_msg_queue CBUF(struct control_msg, 64);
struct sc_controller {
struct controller {
sc_socket control_socket;
sc_thread thread;
sc_mutex mutex;
sc_cond msg_cond;
bool stopped;
struct sc_control_msg_queue queue;
struct control_msg_queue queue;
struct receiver receiver;
};
bool
sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
struct sc_acksync *acksync);
controller_init(struct controller *controller, sc_socket control_socket,
struct sc_acksync *acksync);
void
sc_controller_destroy(struct sc_controller *controller);
controller_destroy(struct controller *controller);
bool
sc_controller_start(struct sc_controller *controller);
controller_start(struct controller *controller);
void
sc_controller_stop(struct sc_controller *controller);
controller_stop(struct controller *controller);
void
sc_controller_join(struct sc_controller *controller);
controller_join(struct controller *controller);
bool
sc_controller_push_msg(struct sc_controller *controller,
const struct sc_control_msg *msg);
controller_push_msg(struct controller *controller,
const struct control_msg *msg);
#endif

View File

@@ -6,7 +6,6 @@
#include "trait/packet_sink.h"
#include <stdbool.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#define DECODER_MAX_SINKS 2

178
app/src/file_handler.c Normal file
View File

@@ -0,0 +1,178 @@
#include "file_handler.h"
#include <assert.h>
#include <string.h>
#include "adb.h"
#include "util/log.h"
#include "util/process_intr.h"
#define DEFAULT_PUSH_TARGET "/sdcard/Download/"
static void
file_handler_request_destroy(struct file_handler_request *req) {
free(req->file);
}
bool
file_handler_init(struct file_handler *file_handler, const char *serial,
const char *push_target) {
assert(serial);
cbuf_init(&file_handler->queue);
bool ok = sc_mutex_init(&file_handler->mutex);
if (!ok) {
return false;
}
ok = sc_cond_init(&file_handler->event_cond);
if (!ok) {
sc_mutex_destroy(&file_handler->mutex);
return false;
}
ok = sc_intr_init(&file_handler->intr);
if (!ok) {
sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex);
return false;
}
file_handler->serial = strdup(serial);
if (!file_handler->serial) {
LOG_OOM();
sc_intr_destroy(&file_handler->intr);
sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex);
return false;
}
// lazy initialization
file_handler->initialized = false;
file_handler->stopped = false;
file_handler->push_target = push_target ? push_target : DEFAULT_PUSH_TARGET;
return true;
}
void
file_handler_destroy(struct file_handler *file_handler) {
sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex);
sc_intr_destroy(&file_handler->intr);
free(file_handler->serial);
struct file_handler_request req;
while (cbuf_take(&file_handler->queue, &req)) {
file_handler_request_destroy(&req);
}
}
bool
file_handler_request(struct file_handler *file_handler,
file_handler_action_t action, char *file) {
// start file_handler if it's used for the first time
if (!file_handler->initialized) {
if (!file_handler_start(file_handler)) {
return false;
}
file_handler->initialized = true;
}
LOGI("Request to %s %s", action == ACTION_INSTALL_APK ? "install" : "push",
file);
struct file_handler_request req = {
.action = action,
.file = file,
};
sc_mutex_lock(&file_handler->mutex);
bool was_empty = cbuf_is_empty(&file_handler->queue);
bool res = cbuf_push(&file_handler->queue, req);
if (was_empty) {
sc_cond_signal(&file_handler->event_cond);
}
sc_mutex_unlock(&file_handler->mutex);
return res;
}
static int
run_file_handler(void *data) {
struct file_handler *file_handler = data;
struct sc_intr *intr = &file_handler->intr;
const char *serial = file_handler->serial;
assert(serial);
const char *push_target = file_handler->push_target;
assert(push_target);
for (;;) {
sc_mutex_lock(&file_handler->mutex);
while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) {
sc_cond_wait(&file_handler->event_cond, &file_handler->mutex);
}
if (file_handler->stopped) {
// stop immediately, do not process further events
sc_mutex_unlock(&file_handler->mutex);
break;
}
struct file_handler_request req;
bool non_empty = cbuf_take(&file_handler->queue, &req);
assert(non_empty);
(void) non_empty;
sc_mutex_unlock(&file_handler->mutex);
if (req.action == ACTION_INSTALL_APK) {
LOGI("Installing %s...", req.file);
bool ok = adb_install(intr, serial, req.file, 0);
if (ok) {
LOGI("%s successfully installed", req.file);
} else {
LOGE("Failed to install %s", req.file);
}
} else {
LOGI("Pushing %s...", req.file);
bool ok = adb_push(intr, serial, req.file, push_target, 0);
if (ok) {
LOGI("%s successfully pushed to %s", req.file, push_target);
} else {
LOGE("Failed to push %s to %s", req.file, push_target);
}
}
file_handler_request_destroy(&req);
}
return 0;
}
bool
file_handler_start(struct file_handler *file_handler) {
LOGD("Starting file_handler thread");
bool ok = sc_thread_create(&file_handler->thread, run_file_handler,
"scrcpy-file", file_handler);
if (!ok) {
LOGC("Could not start file_handler thread");
return false;
}
return true;
}
void
file_handler_stop(struct file_handler *file_handler) {
sc_mutex_lock(&file_handler->mutex);
file_handler->stopped = true;
sc_cond_signal(&file_handler->event_cond);
sc_intr_interrupt(&file_handler->intr);
sc_mutex_unlock(&file_handler->mutex);
}
void
file_handler_join(struct file_handler *file_handler) {
sc_thread_join(&file_handler->thread, NULL);
}

60
app/src/file_handler.h Normal file
View File

@@ -0,0 +1,60 @@
#ifndef FILE_HANDLER_H
#define FILE_HANDLER_H
#include "common.h"
#include <stdbool.h>
#include "adb.h"
#include "util/cbuf.h"
#include "util/thread.h"
#include "util/intr.h"
typedef enum {
ACTION_INSTALL_APK,
ACTION_PUSH_FILE,
} file_handler_action_t;
struct file_handler_request {
file_handler_action_t action;
char *file;
};
struct file_handler_request_queue CBUF(struct file_handler_request, 16);
struct file_handler {
char *serial;
const char *push_target;
sc_thread thread;
sc_mutex mutex;
sc_cond event_cond;
bool stopped;
bool initialized;
struct file_handler_request_queue queue;
struct sc_intr intr;
};
bool
file_handler_init(struct file_handler *file_handler, const char *serial,
const char *push_target);
void
file_handler_destroy(struct file_handler *file_handler);
bool
file_handler_start(struct file_handler *file_handler);
void
file_handler_stop(struct file_handler *file_handler);
void
file_handler_join(struct file_handler *file_handler);
// take ownership of file, and will free() it
bool
file_handler_request(struct file_handler *file_handler,
file_handler_action_t action,
char *file);
#endif

View File

@@ -1,178 +0,0 @@
#include "file_pusher.h"
#include <assert.h>
#include <string.h>
#include "adb.h"
#include "util/log.h"
#include "util/process_intr.h"
#define DEFAULT_PUSH_TARGET "/sdcard/Download/"
static void
sc_file_pusher_request_destroy(struct sc_file_pusher_request *req) {
free(req->file);
}
bool
sc_file_pusher_init(struct sc_file_pusher *fp, const char *serial,
const char *push_target) {
assert(serial);
cbuf_init(&fp->queue);
bool ok = sc_mutex_init(&fp->mutex);
if (!ok) {
return false;
}
ok = sc_cond_init(&fp->event_cond);
if (!ok) {
sc_mutex_destroy(&fp->mutex);
return false;
}
ok = sc_intr_init(&fp->intr);
if (!ok) {
sc_cond_destroy(&fp->event_cond);
sc_mutex_destroy(&fp->mutex);
return false;
}
fp->serial = strdup(serial);
if (!fp->serial) {
LOG_OOM();
sc_intr_destroy(&fp->intr);
sc_cond_destroy(&fp->event_cond);
sc_mutex_destroy(&fp->mutex);
return false;
}
// lazy initialization
fp->initialized = false;
fp->stopped = false;
fp->push_target = push_target ? push_target : DEFAULT_PUSH_TARGET;
return true;
}
void
sc_file_pusher_destroy(struct sc_file_pusher *fp) {
sc_cond_destroy(&fp->event_cond);
sc_mutex_destroy(&fp->mutex);
sc_intr_destroy(&fp->intr);
free(fp->serial);
struct sc_file_pusher_request req;
while (cbuf_take(&fp->queue, &req)) {
sc_file_pusher_request_destroy(&req);
}
}
bool
sc_file_pusher_request(struct sc_file_pusher *fp,
enum sc_file_pusher_action action, char *file) {
// start file_pusher if it's used for the first time
if (!fp->initialized) {
if (!sc_file_pusher_start(fp)) {
return false;
}
fp->initialized = true;
}
LOGI("Request to %s %s", action == SC_FILE_PUSHER_ACTION_INSTALL_APK
? "install" : "push",
file);
struct sc_file_pusher_request req = {
.action = action,
.file = file,
};
sc_mutex_lock(&fp->mutex);
bool was_empty = cbuf_is_empty(&fp->queue);
bool res = cbuf_push(&fp->queue, req);
if (was_empty) {
sc_cond_signal(&fp->event_cond);
}
sc_mutex_unlock(&fp->mutex);
return res;
}
static int
run_file_pusher(void *data) {
struct sc_file_pusher *fp = data;
struct sc_intr *intr = &fp->intr;
const char *serial = fp->serial;
assert(serial);
const char *push_target = fp->push_target;
assert(push_target);
for (;;) {
sc_mutex_lock(&fp->mutex);
while (!fp->stopped && cbuf_is_empty(&fp->queue)) {
sc_cond_wait(&fp->event_cond, &fp->mutex);
}
if (fp->stopped) {
// stop immediately, do not process further events
sc_mutex_unlock(&fp->mutex);
break;
}
struct sc_file_pusher_request req;
bool non_empty = cbuf_take(&fp->queue, &req);
assert(non_empty);
(void) non_empty;
sc_mutex_unlock(&fp->mutex);
if (req.action == SC_FILE_PUSHER_ACTION_INSTALL_APK) {
LOGI("Installing %s...", req.file);
bool ok = adb_install(intr, serial, req.file, 0);
if (ok) {
LOGI("%s successfully installed", req.file);
} else {
LOGE("Failed to install %s", req.file);
}
} else {
LOGI("Pushing %s...", req.file);
bool ok = adb_push(intr, serial, req.file, push_target, 0);
if (ok) {
LOGI("%s successfully pushed to %s", req.file, push_target);
} else {
LOGE("Failed to push %s to %s", req.file, push_target);
}
}
sc_file_pusher_request_destroy(&req);
}
return 0;
}
bool
sc_file_pusher_start(struct sc_file_pusher *fp) {
LOGD("Starting file_pusher thread");
bool ok = sc_thread_create(&fp->thread, run_file_pusher, "scrcpy-file", fp);
if (!ok) {
LOGC("Could not start file_pusher thread");
return false;
}
return true;
}
void
sc_file_pusher_stop(struct sc_file_pusher *fp) {
sc_mutex_lock(&fp->mutex);
fp->stopped = true;
sc_cond_signal(&fp->event_cond);
sc_intr_interrupt(&fp->intr);
sc_mutex_unlock(&fp->mutex);
}
void
sc_file_pusher_join(struct sc_file_pusher *fp) {
sc_thread_join(&fp->thread, NULL);
}

View File

@@ -1,59 +0,0 @@
#ifndef SC_FILE_PUSHER_H
#define SC_FILE_PUSHER_H
#include "common.h"
#include <stdbool.h>
#include "adb.h"
#include "util/cbuf.h"
#include "util/thread.h"
#include "util/intr.h"
enum sc_file_pusher_action {
SC_FILE_PUSHER_ACTION_INSTALL_APK,
SC_FILE_PUSHER_ACTION_PUSH_FILE,
};
struct sc_file_pusher_request {
enum sc_file_pusher_action action;
char *file;
};
struct sc_file_pusher_request_queue CBUF(struct sc_file_pusher_request, 16);
struct sc_file_pusher {
char *serial;
const char *push_target;
sc_thread thread;
sc_mutex mutex;
sc_cond event_cond;
bool stopped;
bool initialized;
struct sc_file_pusher_request_queue queue;
struct sc_intr intr;
};
bool
sc_file_pusher_init(struct sc_file_pusher *fp, const char *serial,
const char *push_target);
void
sc_file_pusher_destroy(struct sc_file_pusher *fp);
bool
sc_file_pusher_start(struct sc_file_pusher *fp);
void
sc_file_pusher_stop(struct sc_file_pusher *fp);
void
sc_file_pusher_join(struct sc_file_pusher *fp);
// take ownership of file, and will free() it
bool
sc_file_pusher_request(struct sc_file_pusher *fp,
enum sc_file_pusher_action action, char *file);
#endif

View File

@@ -387,6 +387,15 @@ 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;
@@ -406,9 +415,7 @@ 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,
// Never forward text input via HID (all the keys are injected
// separately)
.process_text = NULL,
.process_text = sc_key_processor_process_text,
};
// Clipboard synchronization is requested over the control socket, while HID

View File

@@ -1,267 +0,0 @@
#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 mouse");
}
}

View File

@@ -1,23 +0,0 @@
#ifndef SC_HID_MOUSE_H
#define SC_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

View File

@@ -2,7 +2,6 @@
#include <assert.h>
#include <stdbool.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/pixdesc.h>
#include <libavutil/pixfmt.h>
@@ -86,7 +85,7 @@ decode_image(const char *path) {
AVCodecParameters *params = ctx->streams[stream]->codecpar;
const AVCodec *codec = avcodec_find_decoder(params->codec_id);
AVCodec *codec = avcodec_find_decoder(params->codec_id);
if (!codec) {
LOGE("Could not find image decoder");
goto close_input;

76
app/src/input_events.c Normal file
View File

@@ -0,0 +1,76 @@
#include <input_events.h>
static inline uint16_t
sc_mods_state_from_sdl(uint16_t mods_state) {
return mods_state;
}
static inline enum sc_keycode
sc_keycode_from_sdl(SDL_Keycode keycode) {
return (enum sc_keycode) keycode;
}
static inline enum sc_scancode
sc_scancode_from_sdl(SDL_Scancode scancode) {
return (enum sc_scancode) scancode;
}
static inline enum sc_action
sc_action_from_sdl_keyboard_type(uint32_t type) {
assert(type == SDL_KEYDOWN || type == SDL_KEYUP);
if (type == SDL_KEYDOWN) {
return SC_ACTION_DOWN;
}
return SC_ACTION_UP;
}
static inline enum sc_action
sc_action_from_sdl_mousebutton_type(uint32_t type) {
assert(type == SDL_MOUSEBUTTONDOWN || type == SDL_MOUSEBUTTONUP);
if (type == SDL_MOUSEBUTTONDOWN) {
return SC_ACTION_DOWN;
}
return SC_ACTION_UP;
}
static inline enum sc_mouse_button
sc_mouse_button_from_sdl(uint8_t button) {
if (button >= SDL_BUTTON_LEFT && button <= SDL_BUTTON_X2) {
// SC_MOUSE_BUTTON_* constants are initialized from SDL_BUTTON(index)
return SDL_BUTTON(button);
}
return SC_MOUSE_BUTTON_UNKNOWN;
}
static inline uint8_t
sc_mouse_buttons_state_from_sdl(uint32_t buttons_state) {
assert(buttons_state < 0x100); // fits in uint8_t
return buttons_state;
}
void
sc_key_event_from_sdl(struct sc_key_event *event,
const SDL_KeyboardEvent *sdl) {
event->action = sc_action_from_sdl_keyboard_type(sdl->type);
event->keycode = sc_keycode_from_sdl(sdl->keysym.sym);
event->scancode = sc_scancode_from_sdl(sdl->keysym.scancode);
event->repeat = sdl->repeat;
event->mods_state = sc_mods_state_from_sdl(sdl->keysym.mod);
}
void
sc_text_event_from_sdl(struct sc_text_event *event,
const SDL_TextInputEvent *sdl) {
event->text = sdl->text;
}
void
sc_mouse_click_event_from_sdl(struct sc_mouse_click_event *event,
const SDL_MouseButtonEvent *sdl,
const SDL_Window *window,
struct sc_size screen_size) {
event->action = sc_action_from_sdl_mousebutton_type(sdl->type);
event->button = sc_mouse_button_from_sdl(sdl->button);
event->position.screen_size = screen_size;
}

View File

@@ -10,37 +10,9 @@
#include "coords.h"
/* The representation of input events in scrcpy is very close to the SDL API,
* for simplicity.
*
* This scrcpy input events API is designed to be consumed by input event
* processors (sc_key_processor and sc_mouse_processor, see app/src/trait/).
*
* One major semantic difference between SDL input events and scrcpy input
* events is their frame of reference (for mouse and touch events): SDL events
* coordinates are expressed in SDL window coordinates (the visible UI), while
* scrcpy events are expressed in device frame coordinates.
*
* In particular, the window may be visually scaled or rotated (with --rotation
* or MOD+Left/Right), but this does not impact scrcpy input events (contrary
* to SDL input events). This allows to abstract these display details from the
* input event processors (and to make them independent from the "screen").
*
* For many enums below, the values are purposely the same as the SDL
* constants (though not all SDL values are represented), so that the
/* The values are purposely the same as the SDL constants, so that the
* implementation to convert from the SDL version to the scrcpy version is
* straightforward.
*
* In practice, there are 3 levels of input events:
* 1. SDL input events (as received from SDL)
* 2. scrcpy input events (this API)
* 3. the key/mouse processors input events (Android API or HID events)
*
* An input event is first received (1), then (if accepted) converted to an
* scrcpy input event (2), then submitted to the relevant key/mouse processor,
* which (if accepted) is converted to an Android event (to be sent to the
* server) or to an HID event (to be sent over USB/AOA directly).
*/
* straightforward */
enum sc_mod {
SC_MOD_LSHIFT = KMOD_LSHIFT,
@@ -54,6 +26,7 @@ enum sc_mod {
SC_MOD_NUM = KMOD_NUM,
SC_MOD_CAPS = KMOD_CAPS,
SC_MOD_SCROLL = KMOD_SCROLL,
};
enum sc_action {
@@ -360,7 +333,6 @@ 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 {
@@ -370,6 +342,32 @@ struct sc_mouse_motion_event {
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
};
//enum sc_mouse_event_type {
// SC_MOUSE_EVENT_TYPE_CLICK,
// SC_MOUSE_EVENT_TYPE_MOVE,
// SC_MOUSE_EVENT_TYPE_SCROLL,
//};
//
//struct sc_mouse_event {
// enum sc_mouse_event_type type;
// struct sc_position position;
// uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
// union {
// struct {
// enum sc_action action;
// enum sc_mouse_button button;
// } click;
// struct {
// int32_t xrel;
// int32_t yrel;
// } move;
// struct {
// int32_t h;
// int32_t v;
// } scroll;
// };
//};
struct sc_touch_event {
struct sc_position position;
enum sc_touch_action action;
@@ -377,4 +375,30 @@ struct sc_touch_event {
float pressure;
};
//void
//sc_key_event_from_sdl(struct sc_key_event *event, const SDL_KeyboardEvent *sdl);
//
//void
//sc_text_event_from_sdl(struct sc_text_event *event,
// const SDL_TextInputEvent *sdl);
//
//void
//sc_mouse_click_event_from_sdl(struct sc_mouse_click_event *event,
// const SDL_MouseButtonEvent *sdl,
// const SDL_Window *window,
// struct sc_size screen_size);
//
//void
//sc_mouse_wheel_event_from_sdl(struct sc_mouse_wheel_event *event,
// const SDL_MouseWheelEvent *sdl);
//
//void
//sc_mouse_motion_event_from_sdl(struct sc_mouse_motion_event *event,
// const SDL_MouseMotionEvent *sdl);
//
//void
//sc_touch_event_from_sdl(struct sc_touch_event *event,
// const SDL_TouchFingerEvent *sdl);
#endif

View File

@@ -4,7 +4,6 @@
#include <SDL2/SDL_keycode.h>
#include "input_events.h"
#include "screen.h"
#include "util/log.h"
static inline uint16_t
@@ -64,21 +63,36 @@ sc_mouse_button_from_sdl(uint8_t button) {
}
static inline uint8_t
sc_mouse_buttons_state_from_sdl(uint32_t buttons_state,
bool forward_all_clicks) {
sc_mouse_buttons_state_from_sdl(uint32_t buttons_state) {
assert(buttons_state < 0x100); // fits in uint8_t
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;
return buttons_state;
}
//void
//sc_key_event_from_sdl(struct sc_key_event *event,
// const SDL_KeyboardEvent *sdl) {
// event->action = sc_action_from_sdl_keyboard_type(sdl->type);
// event->keycode = sc_keycode_from_sdl(sdl->keysym.sym);
// event->scancode = sc_scancode_from_sdl(sdl->keysym.scancode);
// event->repeat = sdl->repeat;
// event->mods_state = sc_mods_state_from_sdl(sdl->keysym.mod);
//}
//
//void
//sc_text_event_from_sdl(struct sc_text_event *event,
// const SDL_TextInputEvent *sdl) {
// event->text = sdl->text;
//}
//
//void
//sc_mouse_click_event_from_sdl(struct sc_mouse_click_event *event,
// const SDL_MouseButtonEvent *sdl,
// struct sc_size screen_size) {
// event->action = sc_action_from_sdl_mousebutton_type(sdl->type);
// event->button = sc_mouse_button_from_sdl(sdl->button);
// event->position.screen_size = screen_size;
//}
#define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI)
static inline uint16_t
@@ -106,7 +120,7 @@ to_sdl_mod(unsigned shortcut_mod) {
}
static bool
is_shortcut_mod(struct sc_input_manager *im, uint16_t sdl_mod) {
is_shortcut_mod(struct input_manager *im, uint16_t sdl_mod) {
// keep only the relevant modifier keys
sdl_mod &= SC_SDL_SHORTCUT_MODS_MASK;
@@ -122,22 +136,24 @@ is_shortcut_mod(struct sc_input_manager *im, uint16_t sdl_mod) {
}
void
sc_input_manager_init(struct sc_input_manager *im,
const struct sc_input_manager_params *params) {
assert(!params->controller || (params->kp && params->kp->ops));
assert(!params->controller || (params->mp && params->mp->ops));
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));
im->controller = params->controller;
im->fp = params->fp;
im->screen = params->screen;
im->kp = params->kp;
im->mp = params->mp;
im->controller = controller;
im->screen = screen;
im->kp = kp;
im->mp = mp;
im->forward_all_clicks = params->forward_all_clicks;
im->legacy_paste = params->legacy_paste;
im->clipboard_autosync = params->clipboard_autosync;
im->control = options->control;
im->forward_all_clicks = options->forward_all_clicks;
im->legacy_paste = options->legacy_paste;
im->clipboard_autosync = options->clipboard_autosync;
const struct sc_shortcut_mods *shortcut_mods = params->shortcut_mods;
const struct sc_shortcut_mods *shortcut_mods = &options->shortcut_mods;
assert(shortcut_mods->count);
assert(shortcut_mods->count < SC_MAX_SHORTCUT_MODS);
for (unsigned i = 0; i < shortcut_mods->count; ++i) {
@@ -157,11 +173,11 @@ sc_input_manager_init(struct sc_input_manager *im,
}
static void
send_keycode(struct sc_controller *controller, enum android_keycode keycode,
send_keycode(struct controller *controller, enum android_keycode keycode,
enum sc_action action, const char *name) {
// send DOWN event
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_INJECT_KEYCODE;
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
msg.inject_keycode.action = action == SC_ACTION_DOWN
? AKEY_EVENT_ACTION_DOWN
: AKEY_EVENT_ACTION_UP;
@@ -169,100 +185,102 @@ send_keycode(struct sc_controller *controller, enum android_keycode keycode,
msg.inject_keycode.metastate = 0;
msg.inject_keycode.repeat = 0;
if (!sc_controller_push_msg(controller, &msg)) {
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'inject %s'", name);
return;
}
}
static inline void
action_home(struct sc_controller *controller, enum sc_action action) {
action_home(struct controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_HOME, action, "HOME");
}
static inline void
action_back(struct sc_controller *controller, enum sc_action action) {
action_back(struct controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_BACK, action, "BACK");
}
static inline void
action_app_switch(struct sc_controller *controller, enum sc_action action) {
action_app_switch(struct controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_APP_SWITCH, action, "APP_SWITCH");
}
static inline void
action_power(struct sc_controller *controller, enum sc_action action) {
action_power(struct controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_POWER, action, "POWER");
}
static inline void
action_volume_up(struct sc_controller *controller, enum sc_action action) {
action_volume_up(struct controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_VOLUME_UP, action, "VOLUME_UP");
}
static inline void
action_volume_down(struct sc_controller *controller, enum sc_action action) {
action_volume_down(struct controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_VOLUME_DOWN, action, "VOLUME_DOWN");
}
static inline void
action_menu(struct sc_controller *controller, enum sc_action action) {
action_menu(struct controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_MENU, action, "MENU");
}
// turn the screen on if it was off, press BACK otherwise
// If the screen is off, it is turned on only on ACTION_DOWN
static void
press_back_or_turn_screen_on(struct sc_controller *controller,
press_back_or_turn_screen_on(struct controller *controller,
enum sc_action action) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON;
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON;
msg.back_or_screen_on.action = action == SC_ACTION_DOWN
? AKEY_EVENT_ACTION_DOWN
: AKEY_EVENT_ACTION_UP;
if (!sc_controller_push_msg(controller, &msg)) {
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'press back or turn screen on'");
return;
}
}
static void
expand_notification_panel(struct sc_controller *controller) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL;
expand_notification_panel(struct controller *controller) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL;
if (!sc_controller_push_msg(controller, &msg)) {
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'expand notification panel'");
}
}
static void
expand_settings_panel(struct sc_controller *controller) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL;
expand_settings_panel(struct controller *controller) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL;
if (!sc_controller_push_msg(controller, &msg)) {
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'expand settings panel'");
}
}
static void
collapse_panels(struct sc_controller *controller) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS;
collapse_panels(struct controller *controller) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_COLLAPSE_PANELS;
if (!sc_controller_push_msg(controller, &msg)) {
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'collapse notification panel'");
}
}
static bool
get_device_clipboard(struct sc_controller *controller,
enum sc_copy_key copy_key) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_GET_CLIPBOARD;
get_device_clipboard(struct controller *controller,
enum get_clipboard_copy_key copy_key) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD;
msg.get_clipboard.copy_key = copy_key;
if (!sc_controller_push_msg(controller, &msg)) {
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'get device clipboard'");
return false;
}
@@ -271,7 +289,7 @@ get_device_clipboard(struct sc_controller *controller,
}
static bool
set_device_clipboard(struct sc_controller *controller, bool paste,
set_device_clipboard(struct controller *controller, bool paste,
uint64_t sequence) {
char *text = SDL_GetClipboardText();
if (!text) {
@@ -286,13 +304,13 @@ set_device_clipboard(struct sc_controller *controller, bool paste,
return false;
}
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_SET_CLIPBOARD;
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD;
msg.set_clipboard.sequence = sequence;
msg.set_clipboard.text = text_dup;
msg.set_clipboard.paste = paste;
if (!sc_controller_push_msg(controller, &msg)) {
if (!controller_push_msg(controller, &msg)) {
free(text_dup);
LOGW("Could not request 'set device clipboard'");
return false;
@@ -302,13 +320,13 @@ set_device_clipboard(struct sc_controller *controller, bool paste,
}
static void
set_screen_power_mode(struct sc_controller *controller,
enum sc_screen_power_mode mode) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
set_screen_power_mode(struct controller *controller,
enum screen_power_mode mode) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
msg.set_screen_power_mode.mode = mode;
if (!sc_controller_push_msg(controller, &msg)) {
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'set screen power mode'");
}
}
@@ -330,7 +348,7 @@ switch_fps_counter_state(struct fps_counter *fps_counter) {
}
static void
clipboard_paste(struct sc_controller *controller) {
clipboard_paste(struct controller *controller) {
char *text = SDL_GetClipboardText();
if (!text) {
LOGW("Could not get clipboard text: %s", SDL_GetError());
@@ -349,45 +367,40 @@ clipboard_paste(struct sc_controller *controller) {
return;
}
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_INJECT_TEXT;
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = text_dup;
if (!sc_controller_push_msg(controller, &msg)) {
if (!controller_push_msg(controller, &msg)) {
free(text_dup);
LOGW("Could not request 'paste clipboard'");
}
}
static void
rotate_device(struct sc_controller *controller) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_ROTATE_DEVICE;
rotate_device(struct controller *controller) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_ROTATE_DEVICE;
if (!sc_controller_push_msg(controller, &msg)) {
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request device rotation");
}
}
static void
rotate_client_left(struct sc_screen *screen) {
rotate_client_left(struct screen *screen) {
unsigned new_rotation = (screen->rotation + 1) % 4;
sc_screen_set_rotation(screen, new_rotation);
screen_set_rotation(screen, new_rotation);
}
static void
rotate_client_right(struct sc_screen *screen) {
rotate_client_right(struct screen *screen) {
unsigned new_rotation = (screen->rotation + 3) % 4;
sc_screen_set_rotation(screen, new_rotation);
screen_set_rotation(screen, new_rotation);
}
static void
sc_input_manager_process_text_input(struct sc_input_manager *im,
const SDL_TextInputEvent *event) {
if (!im->kp->ops->process_text) {
// The key processor does not support text input
return;
}
input_manager_process_text_input(struct input_manager *im,
const SDL_TextInputEvent *event) {
if (is_shortcut_mod(im, SDL_GetModState())) {
// A shortcut must never generate text events
return;
@@ -401,13 +414,13 @@ sc_input_manager_process_text_input(struct sc_input_manager *im,
}
static bool
simulate_virtual_finger(struct sc_input_manager *im,
simulate_virtual_finger(struct input_manager *im,
enum android_motionevent_action action,
struct sc_point point) {
bool up = action == AMOTION_EVENT_ACTION_UP;
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
msg.inject_touch_event.action = action;
msg.inject_touch_event.position.screen_size = im->screen->frame_size;
msg.inject_touch_event.position.point = point;
@@ -415,7 +428,7 @@ simulate_virtual_finger(struct sc_input_manager *im,
msg.inject_touch_event.pressure = up ? 0.0f : 1.0f;
msg.inject_touch_event.buttons = 0;
if (!sc_controller_push_msg(im->controller, &msg)) {
if (!controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject virtual finger event'");
return false;
}
@@ -431,10 +444,12 @@ inverse_point(struct sc_point point, struct sc_size size) {
}
static void
sc_input_manager_process_key(struct sc_input_manager *im,
const SDL_KeyboardEvent *event) {
// controller is NULL if --no-control is requested
struct sc_controller *controller = im->controller;
input_manager_process_key(struct input_manager *im,
const SDL_KeyboardEvent *event) {
// control: indicates the state of the command-line option --no-control
bool control = im->control;
struct controller *controller = im->controller;
SDL_Keycode keycode = event->keysym.sym;
uint16_t mod = event->keysym.mod;
@@ -460,47 +475,47 @@ sc_input_manager_process_key(struct sc_input_manager *im,
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
switch (keycode) {
case SDLK_h:
if (controller && !shift && !repeat) {
if (control && !shift && !repeat) {
action_home(controller, action);
}
return;
case SDLK_b: // fall-through
case SDLK_BACKSPACE:
if (controller && !shift && !repeat) {
if (control && !shift && !repeat) {
action_back(controller, action);
}
return;
case SDLK_s:
if (controller && !shift && !repeat) {
if (control && !shift && !repeat) {
action_app_switch(controller, action);
}
return;
case SDLK_m:
if (controller && !shift && !repeat) {
if (control && !shift && !repeat) {
action_menu(controller, action);
}
return;
case SDLK_p:
if (controller && !shift && !repeat) {
if (control && !shift && !repeat) {
action_power(controller, action);
}
return;
case SDLK_o:
if (controller && !repeat && down) {
enum sc_screen_power_mode mode = shift
? SC_SCREEN_POWER_MODE_NORMAL
: SC_SCREEN_POWER_MODE_OFF;
if (control && !repeat && down) {
enum screen_power_mode mode = shift
? SCREEN_POWER_MODE_NORMAL
: SCREEN_POWER_MODE_OFF;
set_screen_power_mode(controller, mode);
}
return;
case SDLK_DOWN:
if (controller && !shift) {
if (control && !shift) {
// forward repeated events
action_volume_down(controller, action);
}
return;
case SDLK_UP:
if (controller && !shift) {
if (control && !shift) {
// forward repeated events
action_volume_up(controller, action);
}
@@ -516,17 +531,19 @@ sc_input_manager_process_key(struct sc_input_manager *im,
}
return;
case SDLK_c:
if (controller && !shift && !repeat && down) {
get_device_clipboard(controller, SC_COPY_KEY_COPY);
if (control && !shift && !repeat && down) {
get_device_clipboard(controller,
GET_CLIPBOARD_COPY_KEY_COPY);
}
return;
case SDLK_x:
if (controller && !shift && !repeat && down) {
get_device_clipboard(controller, SC_COPY_KEY_CUT);
if (control && !shift && !repeat && down) {
get_device_clipboard(controller,
GET_CLIPBOARD_COPY_KEY_CUT);
}
return;
case SDLK_v:
if (controller && !repeat && down) {
if (control && !repeat && down) {
if (shift || im->legacy_paste) {
// inject the text as input events
clipboard_paste(controller);
@@ -540,17 +557,17 @@ sc_input_manager_process_key(struct sc_input_manager *im,
return;
case SDLK_f:
if (!shift && !repeat && down) {
sc_screen_switch_fullscreen(im->screen);
screen_switch_fullscreen(im->screen);
}
return;
case SDLK_w:
if (!shift && !repeat && down) {
sc_screen_resize_to_fit(im->screen);
screen_resize_to_fit(im->screen);
}
return;
case SDLK_g:
if (!shift && !repeat && down) {
sc_screen_resize_to_pixel_perfect(im->screen);
screen_resize_to_pixel_perfect(im->screen);
}
return;
case SDLK_i:
@@ -559,7 +576,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
}
return;
case SDLK_n:
if (controller && !repeat && down) {
if (control && !repeat && down) {
if (shift) {
collapse_panels(controller);
} else if (im->key_repeat == 0) {
@@ -570,7 +587,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
}
return;
case SDLK_r:
if (controller && !shift && !repeat && down) {
if (control && !shift && !repeat && down) {
rotate_device(controller);
}
return;
@@ -579,7 +596,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
return;
}
if (!controller) {
if (!control) {
return;
}
@@ -620,14 +637,20 @@ sc_input_manager_process_key(struct sc_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
sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
const SDL_MouseMotionEvent *event) {
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;
@@ -636,41 +659,28 @@ sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
struct sc_mouse_motion_event evt = {
.position = {
.screen_size = im->screen->frame_size,
.point = sc_screen_convert_window_to_frame_coords(im->screen,
event->x,
event->y),
.point = screen_convert_window_to_frame_coords(im->screen,
event->x, event->y),
},
.xrel = event->xrel,
.yrel = event->yrel,
.buttons_state =
sc_mouse_buttons_state_from_sdl(event->state,
im->forward_all_clicks),
.buttons_state = sc_mouse_buttons_state_from_sdl(event->state),
};
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);
im->mp->ops->process_mouse_motion(im->mp, event);
if (im->vfinger_down) {
assert(!im->mp->relative_mode); // assert one more time
struct sc_point mouse =
sc_screen_convert_window_to_frame_coords(im->screen, event->x,
event->y);
screen_convert_window_to_frame_coords(im->screen, event->x,
event->y);
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size);
simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger);
}
}
static void
sc_input_manager_process_touch(struct sc_input_manager *im,
const SDL_TouchFingerEvent *event) {
if (!im->mp->ops->process_touch) {
// The mouse processor does not support touch events
return;
}
input_manager_process_touch(struct input_manager *im,
const SDL_TouchFingerEvent *event) {
int dw;
int dh;
SDL_GL_GetDrawableSize(im->screen->window, &dw, &dh);
@@ -682,21 +692,20 @@ sc_input_manager_process_touch(struct sc_input_manager *im,
struct sc_touch_event evt = {
.position = {
.screen_size = im->screen->frame_size,
.point =
sc_screen_convert_drawable_to_frame_coords(im->screen, x, y),
.point = screen_convert_drawable_to_frame_coords(im->screen, x, y),
},
.action = sc_touch_action_from_sdl(event->type),
.pointer_id = event->fingerId,
.pressure = event->pressure,
};
im->mp->ops->process_touch(im->mp, &evt);
im->mp->ops->process_touch(im->mp, event);
}
static void
sc_input_manager_process_mouse_button(struct sc_input_manager *im,
const SDL_MouseButtonEvent *event) {
struct sc_controller *controller = im->controller;
input_manager_process_mouse_button(struct input_manager *im,
const SDL_MouseButtonEvent *event) {
bool control = im->control;
if (event->which == SDL_TOUCH_MOUSEID) {
// simulated from touch events, so it's a duplicate
@@ -705,42 +714,40 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
bool down = event->type == SDL_MOUSEBUTTONDOWN;
if (!im->forward_all_clicks) {
if (controller) {
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
if (event->button == SDL_BUTTON_X1) {
action_app_switch(controller, action);
return;
}
if (event->button == SDL_BUTTON_X2 && down) {
if (event->clicks < 2) {
expand_notification_panel(controller);
} else {
expand_settings_panel(controller);
}
return;
}
if (event->button == SDL_BUTTON_RIGHT) {
press_back_or_turn_screen_on(controller, action);
return;
}
if (event->button == SDL_BUTTON_MIDDLE) {
action_home(controller, action);
return;
if (control && event->button == SDL_BUTTON_X1) {
action_app_switch(im->controller, action);
return;
}
if (control && event->button == SDL_BUTTON_X2 && down) {
if (event->clicks < 2) {
expand_notification_panel(im->controller);
} else {
expand_settings_panel(im->controller);
}
return;
}
if (control && event->button == SDL_BUTTON_RIGHT) {
press_back_or_turn_screen_on(im->controller, action);
return;
}
if (control && event->button == SDL_BUTTON_MIDDLE) {
action_home(im->controller, action);
return;
}
// double-click on black borders resize to fit the device screen
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
int32_t x = event->x;
int32_t y = event->y;
sc_screen_hidpi_scale_coords(im->screen, &x, &y);
screen_hidpi_scale_coords(im->screen, &x, &y);
SDL_Rect *r = &im->screen->rect;
bool outside = x < r->x || x >= r->x + r->w
|| y < r->y || y >= r->y + r->h;
if (outside) {
if (down) {
sc_screen_resize_to_fit(im->screen);
screen_resize_to_fit(im->screen);
}
return;
}
@@ -748,7 +755,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
// otherwise, send the click event to the device
}
if (!controller) {
if (!control) {
return;
}
@@ -757,25 +764,15 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
struct sc_mouse_click_event evt = {
.position = {
.screen_size = im->screen->frame_size,
.point = sc_screen_convert_window_to_frame_coords(im->screen,
event->x,
event->y),
.point = screen_convert_window_to_frame_coords(im->screen, event->x,
event->y),
},
.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,
im->forward_all_clicks),
.buttons_state = sc_mouse_buttons_state_from_sdl(sdl_buttons_state),
};
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;
}
im->mp->ops->process_mouse_button(im->mp, event);
// Pinch-to-zoom simulation.
//
@@ -787,12 +784,11 @@ sc_input_manager_process_mouse_button(struct sc_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 (event->button == SDL_BUTTON_LEFT &&
((down && !im->vfinger_down && CTRL_PRESSED) ||
(!down && im->vfinger_down))) {
if ((down && !im->vfinger_down && CTRL_PRESSED)
|| (!down && im->vfinger_down)) {
struct sc_point mouse =
sc_screen_convert_window_to_frame_coords(im->screen, event->x,
event->y);
screen_convert_window_to_frame_coords(im->screen, event->x,
event->y);
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size);
enum android_motionevent_action action = down
? AMOTION_EVENT_ACTION_DOWN
@@ -805,108 +801,65 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
}
static void
sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
const SDL_MouseWheelEvent *event) {
if (!im->mp->ops->process_mouse_scroll) {
// The mouse processor does not support scroll events
return;
}
input_manager_process_mouse_wheel(struct input_manager *im,
const SDL_MouseWheelEvent *event) {
// mouse_x and mouse_y are expressed in pixels relative to the window
int mouse_x;
int mouse_y;
uint32_t buttons = SDL_GetMouseState(&mouse_x, &mouse_y);
SDL_GetMouseState(&mouse_x, &mouse_y);
struct sc_mouse_scroll_event evt = {
.position = {
.screen_size = im->screen->frame_size,
.point = sc_screen_convert_window_to_frame_coords(im->screen,
mouse_x, mouse_y),
.point = screen_convert_window_to_frame_coords(im->screen,
mouse_x, mouse_y),
},
.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);
im->mp->ops->process_mouse_wheel(im->mp, event);
}
static bool
is_apk(const char *file) {
const char *ext = strrchr(file, '.');
return ext && !strcmp(ext, ".apk");
}
static void
sc_input_manager_process_file(struct sc_input_manager *im,
const SDL_DropEvent *event) {
char *file = strdup(event->file);
SDL_free(event->file);
if (!file) {
LOG_OOM();
return;
}
enum sc_file_pusher_action action;
if (is_apk(file)) {
action = SC_FILE_PUSHER_ACTION_INSTALL_APK;
} else {
action = SC_FILE_PUSHER_ACTION_PUSH_FILE;
}
bool ok = sc_file_pusher_request(im->fp, action, file);
if (!ok) {
free(file);
}
}
void
sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event) {
bool control = im->controller;
bool
input_manager_handle_event(struct input_manager *im, SDL_Event *event) {
switch (event->type) {
case SDL_TEXTINPUT:
if (!control) {
break;
if (!im->control) {
return true;
}
sc_input_manager_process_text_input(im, &event->text);
break;
input_manager_process_text_input(im, &event->text);
return true;
case SDL_KEYDOWN:
case SDL_KEYUP:
// some key events do not interact with the device, so process the
// event even if control is disabled
sc_input_manager_process_key(im, &event->key);
break;
input_manager_process_key(im, &event->key);
return true;
case SDL_MOUSEMOTION:
if (!control) {
if (!im->control) {
break;
}
sc_input_manager_process_mouse_motion(im, &event->motion);
break;
input_manager_process_mouse_motion(im, &event->motion);
return true;
case SDL_MOUSEWHEEL:
if (!control) {
if (!im->control) {
break;
}
sc_input_manager_process_mouse_wheel(im, &event->wheel);
break;
input_manager_process_mouse_wheel(im, &event->wheel);
return true;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
// some mouse events do not interact with the device, so process
// the event even if control is disabled
sc_input_manager_process_mouse_button(im, &event->button);
break;
input_manager_process_mouse_button(im, &event->button);
return true;
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
if (!control) {
break;
}
sc_input_manager_process_touch(im, &event->tfinger);
break;
case SDL_DROPFILE: {
if (!control) {
break;
}
sc_input_manager_process_file(im, &event->drop);
}
input_manager_process_touch(im, &event->tfinger);
return true;
}
return false;
}

View File

@@ -8,20 +8,20 @@
#include <SDL2/SDL.h>
#include "controller.h"
#include "file_pusher.h"
#include "fps_counter.h"
#include "options.h"
#include "screen.h"
#include "trait/key_processor.h"
#include "trait/mouse_processor.h"
struct sc_input_manager {
struct sc_controller *controller;
struct sc_file_pusher *fp;
struct sc_screen *screen;
struct input_manager {
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;
@@ -43,24 +43,13 @@ struct sc_input_manager {
uint64_t next_sequence; // used for request acknowledgements
};
struct sc_input_manager_params {
struct sc_controller *controller;
struct sc_file_pusher *fp;
struct sc_screen *screen;
struct sc_key_processor *kp;
struct sc_mouse_processor *mp;
bool forward_all_clicks;
bool legacy_paste;
bool clipboard_autosync;
const struct sc_shortcut_mods *shortcut_mods;
};
void
sc_input_manager_init(struct sc_input_manager *im,
const struct sc_input_manager_params *params);
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);
void
sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event);
bool
input_manager_handle_event(struct input_manager *im, SDL_Event *event);
#endif

View File

@@ -12,13 +12,20 @@
/** Downcast key processor to sc_keyboard_inject */
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_inject, key_processor)
static enum android_keyevent_action
convert_keycode_action(enum sc_action action) {
if (action == SC_ACTION_DOWN) {
return AKEY_EVENT_ACTION_DOWN;
static bool
convert_keycode_action(enum sc_action from, enum android_keyevent_action *to) {
static const struct sc_intmap_entry actions[] = {
{SC_ACTION_DOWN, AKEY_EVENT_ACTION_DOWN},
{SC_ACTION_UP, AKEY_EVENT_ACTION_UP},
};
const struct sc_intmap_entry *entry = SC_INTMAP_FIND_ENTRY(actions, from);
if (entry) {
*to = entry->value;
return true;
}
assert(action == SC_ACTION_UP);
return AKEY_EVENT_ACTION_UP;
return false;
}
static bool
@@ -246,16 +253,19 @@ convert_meta_state(uint16_t mod) {
}
static bool
convert_input_key(const struct sc_key_event *event, struct sc_control_msg *msg,
convert_input_key(const struct sc_key_event *event, struct control_msg *msg,
enum sc_key_inject_mode key_inject_mode, uint32_t repeat) {
msg->type = SC_CONTROL_MSG_TYPE_INJECT_KEYCODE;
msg->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
if (!convert_keycode_action(event->action, &msg->inject_keycode.action)) {
return false;
}
if (!convert_keycode(event->keycode, &msg->inject_keycode.keycode,
event->mods_state, key_inject_mode)) {
return false;
}
msg->inject_keycode.action = convert_keycode_action(event->action);
msg->inject_keycode.repeat = repeat;
msg->inject_keycode.metastate = convert_meta_state(event->mods_state);
@@ -282,9 +292,9 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
ki->repeat = 0;
}
struct sc_control_msg msg;
struct control_msg msg;
if (convert_input_key(event, &msg, ki->key_inject_mode, ki->repeat)) {
if (!sc_controller_push_msg(ki->controller, &msg)) {
if (!controller_push_msg(ki->controller, &msg)) {
LOGW("Could not request 'inject keycode'");
}
}
@@ -309,14 +319,14 @@ sc_key_processor_process_text(struct sc_key_processor *kp,
}
}
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_INJECT_TEXT;
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = strdup(event->text);
if (!msg.inject_text.text) {
LOGW("Could not strdup input text");
return;
}
if (!sc_controller_push_msg(ki->controller, &msg)) {
if (!controller_push_msg(ki->controller, &msg)) {
free(msg.inject_text.text);
LOGW("Could not request 'inject text'");
}
@@ -324,12 +334,11 @@ sc_key_processor_process_text(struct sc_key_processor *kp,
void
sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
struct sc_controller *controller,
enum sc_key_inject_mode key_inject_mode,
bool forward_key_repeat) {
struct controller *controller,
const struct scrcpy_options *options) {
ki->controller = controller;
ki->key_inject_mode = key_inject_mode;
ki->forward_key_repeat = forward_key_repeat;
ki->key_inject_mode = options->key_inject_mode;
ki->forward_key_repeat = options->forward_key_repeat;
ki->repeat = 0;

View File

@@ -12,7 +12,7 @@
struct sc_keyboard_inject {
struct sc_key_processor key_processor; // key processor trait
struct sc_controller *controller;
struct controller *controller;
// SDL reports repeated events as a boolean, but Android expects the actual
// number of repetitions. This variable keeps track of the count.
@@ -24,8 +24,7 @@ struct sc_keyboard_inject {
void
sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
struct sc_controller *controller,
enum sc_key_inject_mode key_inject_mode,
bool forward_key_repeat);
struct controller *controller,
const struct scrcpy_options *options);
#endif

View File

@@ -1,11 +1,11 @@
#include "mouse_inject.h"
#include <assert.h>
#include <SDL2/SDL_events.h>
#include "android/input.h"
#include "control_msg.h"
#include "controller.h"
#include "input_events.h"
#include "util/intmap.h"
#include "util/log.h"
@@ -15,147 +15,210 @@
static enum android_motionevent_buttons
convert_mouse_buttons(uint32_t state) {
enum android_motionevent_buttons buttons = 0;
if (state & SC_MOUSE_BUTTON_LEFT) {
if (state & SDL_BUTTON_LMASK) {
buttons |= AMOTION_EVENT_BUTTON_PRIMARY;
}
if (state & SC_MOUSE_BUTTON_RIGHT) {
if (state & SDL_BUTTON_RMASK) {
buttons |= AMOTION_EVENT_BUTTON_SECONDARY;
}
if (state & SC_MOUSE_BUTTON_MIDDLE) {
if (state & SDL_BUTTON_MMASK) {
buttons |= AMOTION_EVENT_BUTTON_TERTIARY;
}
if (state & SC_MOUSE_BUTTON_X1) {
if (state & SDL_BUTTON_X1MASK) {
buttons |= AMOTION_EVENT_BUTTON_BACK;
}
if (state & SC_MOUSE_BUTTON_X2) {
if (state & SDL_BUTTON_X2MASK) {
buttons |= AMOTION_EVENT_BUTTON_FORWARD;
}
return buttons;
}
static enum android_motionevent_action
convert_mouse_action(enum sc_action action) {
if (action == SC_ACTION_DOWN) {
return AMOTION_EVENT_ACTION_DOWN;
static bool
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
static const struct sc_intmap_entry actions[] = {
{SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN},
{SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP},
};
const struct sc_intmap_entry *entry = SC_INTMAP_FIND_ENTRY(actions, from);
if (entry) {
*to = entry->value;
return true;
}
assert(action == SC_ACTION_UP);
return AMOTION_EVENT_ACTION_UP;
return false;
}
static enum android_motionevent_action
convert_touch_action(enum sc_touch_action action) {
switch (action) {
case SC_TOUCH_ACTION_MOVE:
return AMOTION_EVENT_ACTION_MOVE;
case SC_TOUCH_ACTION_DOWN:
return AMOTION_EVENT_ACTION_DOWN;
default:
assert(action == SC_TOUCH_ACTION_UP);
return AMOTION_EVENT_ACTION_UP;
static bool
convert_touch_action(SDL_EventType from, enum android_motionevent_action *to) {
static const struct sc_intmap_entry actions[] = {
{SDL_FINGERMOTION, AMOTION_EVENT_ACTION_MOVE},
{SDL_FINGERDOWN, AMOTION_EVENT_ACTION_DOWN},
{SDL_FINGERUP, AMOTION_EVENT_ACTION_UP},
};
const struct sc_intmap_entry *entry = SC_INTMAP_FIND_ENTRY(actions, from);
if (entry) {
*to = entry->value;
return true;
}
return false;
}
static bool
convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
to->inject_touch_event.action = AMOTION_EVENT_ACTION_MOVE;
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = screen->frame_size;
to->inject_touch_event.position.point =
screen_convert_window_to_frame_coords(screen, from->x, from->y);
to->inject_touch_event.pressure = 1.f;
to->inject_touch_event.buttons = convert_mouse_buttons(from->state);
return true;
}
static bool
convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
if (!convert_touch_action(from->type, &to->inject_touch_event.action)) {
return false;
}
to->inject_touch_event.pointer_id = from->fingerId;
to->inject_touch_event.position.screen_size = screen->frame_size;
int dw;
int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
// SDL touch event coordinates are normalized in the range [0; 1]
int32_t x = from->x * dw;
int32_t y = from->y * dh;
to->inject_touch_event.position.point =
screen_convert_drawable_to_frame_coords(screen, x, y);
to->inject_touch_event.pressure = from->pressure;
to->inject_touch_event.buttons = 0;
return true;
}
static bool
convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
if (!convert_mouse_action(from->type, &to->inject_touch_event.action)) {
return false;
}
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = screen->frame_size;
to->inject_touch_event.position.point =
screen_convert_window_to_frame_coords(screen, from->x, from->y);
to->inject_touch_event.pressure =
from->type == SDL_MOUSEBUTTONDOWN ? 1.f : 0.f;
to->inject_touch_event.buttons =
convert_mouse_buttons(SDL_BUTTON(from->button));
return true;
}
static bool
convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
struct control_msg *to) {
// 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);
struct sc_position position = {
.screen_size = screen->frame_size,
.point = screen_convert_window_to_frame_coords(screen,
mouse_x, mouse_y),
};
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;
to->inject_scroll_event.position = position;
to->inject_scroll_event.hscroll = from->x;
to->inject_scroll_event.vscroll = from->y;
return true;
}
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
const SDL_MouseMotionEvent *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct control_msg msg;
if (!convert_mouse_motion(event, mi->screen, &msg)) {
return;
}
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
.inject_touch_event = {
.action = AMOTION_EVENT_ACTION_MOVE,
.pointer_id = POINTER_ID_MOUSE,
.position = event->position,
.pressure = 1.f,
.buttons = convert_mouse_buttons(event->buttons_state),
},
};
if (!sc_controller_push_msg(mi->controller, &msg)) {
if (!controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse motion event'");
}
}
static void
sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
const struct sc_mouse_click_event *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
.inject_touch_event = {
.action = convert_mouse_action(event->action),
.pointer_id = POINTER_ID_MOUSE,
.position = event->position,
.pressure = event->action == SC_ACTION_DOWN ? 1.f : 0.f,
.buttons = convert_mouse_buttons(event->buttons_state),
},
};
if (!sc_controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse click event'");
}
}
static void
sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
const struct sc_mouse_scroll_event *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
.inject_scroll_event = {
.position = event->position,
.hscroll = event->hscroll,
.vscroll = event->vscroll,
.buttons = convert_mouse_buttons(event->buttons_state),
},
};
if (!sc_controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse scroll event'");
}
}
static void
sc_mouse_processor_process_touch(struct sc_mouse_processor *mp,
const struct sc_touch_event *event) {
const SDL_TouchFingerEvent *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct sc_control_msg msg = {
.type = SC_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,
},
};
struct control_msg msg;
if (convert_touch(event, mi->screen, &msg)) {
if (!controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject touch event'");
}
}
}
if (!sc_controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject touch event'");
static void
sc_mouse_processor_process_mouse_button(struct sc_mouse_processor *mp,
const SDL_MouseButtonEvent *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct control_msg msg;
if (convert_mouse_button(event, mi->screen, &msg)) {
if (!controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse button event'");
}
}
}
static void
sc_mouse_processor_process_mouse_wheel(struct sc_mouse_processor *mp,
const SDL_MouseWheelEvent *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct control_msg msg;
if (convert_mouse_wheel(event, mi->screen, &msg)) {
if (!controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse wheel event'");
}
}
}
void
sc_mouse_inject_init(struct sc_mouse_inject *mi,
struct sc_controller *controller) {
sc_mouse_inject_init(struct sc_mouse_inject *mi, struct controller *controller,
struct screen *screen) {
mi->controller = controller;
mi->screen = screen;
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,
.process_touch = sc_mouse_processor_process_touch,
.process_mouse_button = sc_mouse_processor_process_mouse_button,
.process_mouse_wheel = sc_mouse_processor_process_mouse_wheel,
};
mi->mouse_processor.ops = &ops;
mi->mouse_processor.relative_mode = false;
}

View File

@@ -12,11 +12,12 @@
struct sc_mouse_inject {
struct sc_mouse_processor mouse_processor; // mouse processor trait
struct sc_controller *controller;
struct controller *controller;
struct screen *screen;
};
void
sc_mouse_inject_init(struct sc_mouse_inject *mi,
struct sc_controller *controller);
sc_mouse_inject_init(struct sc_mouse_inject *mi, struct controller *controller,
struct screen *screen);
#endif

View File

@@ -54,7 +54,6 @@ const struct scrcpy_options scrcpy_options_default = {
.legacy_paste = false,
.power_off_on_close = false,
.clipboard_autosync = true,
.downsize_on_error = true,
.tcpip = false,
.tcpip_dst = NULL,
};

View File

@@ -38,11 +38,6 @@ 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.
@@ -95,7 +90,6 @@ 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;
@@ -129,7 +123,6 @@ struct scrcpy_options {
bool legacy_paste;
bool power_off_on_close;
bool clipboard_autosync;
bool downsize_on_error;
bool tcpip;
const char *tcpip_dst;
};

View File

@@ -16,10 +16,10 @@
#include "controller.h"
#include "decoder.h"
#include "events.h"
#include "file_pusher.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"
@@ -36,15 +36,15 @@
struct scrcpy {
struct sc_server server;
struct sc_screen screen;
struct screen screen;
struct stream stream;
struct decoder decoder;
struct recorder recorder;
#ifdef HAVE_V4L2
struct sc_v4l2_sink v4l2_sink;
#endif
struct sc_controller controller;
struct sc_file_pusher file_pusher;
struct controller controller;
struct file_handler file_handler;
#ifdef HAVE_AOA_HID
struct sc_aoa aoa;
// sequence/ack helper to synchronize clipboard and Ctrl+v via HID
@@ -56,12 +56,8 @@ struct scrcpy {
struct sc_hid_keyboard keyboard_hid;
#endif
};
union {
struct sc_mouse_inject mouse_inject;
#ifdef HAVE_AOA_HID
struct sc_hid_mouse mouse_hid;
#endif
};
struct sc_mouse_inject mouse_inject;
struct input_manager input_manager;
};
static inline void
@@ -149,18 +145,73 @@ sdl_configure(bool display, bool disable_screensaver) {
}
static bool
event_loop(struct scrcpy *s) {
is_apk(const char *file) {
const char *ext = strrchr(file, '.');
return ext && !strcmp(ext, ".apk");
}
enum event_result {
EVENT_RESULT_CONTINUE,
EVENT_RESULT_STOPPED_BY_USER,
EVENT_RESULT_STOPPED_BY_EOS,
};
static enum event_result
handle_event(struct scrcpy *s, const struct scrcpy_options *options,
SDL_Event *event) {
switch (event->type) {
case EVENT_STREAM_STOPPED:
LOGD("Video stream stopped");
return EVENT_RESULT_STOPPED_BY_EOS;
case SDL_QUIT:
LOGD("User requested to quit");
return EVENT_RESULT_STOPPED_BY_USER;
case SDL_DROPFILE: {
if (!options->control) {
break;
}
char *file = strdup(event->drop.file);
SDL_free(event->drop.file);
if (!file) {
LOGW("Could not strdup drop filename\n");
break;
}
file_handler_action_t action;
if (is_apk(file)) {
action = ACTION_INSTALL_APK;
} else {
action = ACTION_PUSH_FILE;
}
file_handler_request(&s->file_handler, action, file);
goto end;
}
}
bool consumed = screen_handle_event(&s->screen, event);
if (consumed) {
goto end;
}
consumed = input_manager_handle_event(&s->input_manager, event);
(void) consumed;
end:
return EVENT_RESULT_CONTINUE;
}
static bool
event_loop(struct scrcpy *s, const struct scrcpy_options *options) {
SDL_Event event;
while (SDL_WaitEvent(&event)) {
switch (event.type) {
case EVENT_STREAM_STOPPED:
enum event_result result = handle_event(s, options, &event);
switch (result) {
case EVENT_RESULT_STOPPED_BY_USER:
return true;
case EVENT_RESULT_STOPPED_BY_EOS:
LOGW("Device disconnected");
return false;
case SDL_QUIT:
LOGD("User requested to quit");
return true;
default:
sc_screen_handle_event(&s->screen, &event);
case EVENT_RESULT_CONTINUE:
break;
}
}
@@ -277,7 +328,7 @@ scrcpy(struct scrcpy_options *options) {
bool ret = false;
bool server_started = false;
bool file_pusher_initialized = false;
bool file_handler_initialized = false;
bool recorder_initialized = false;
#ifdef HAVE_V4L2
bool v4l2_sink_initialized = false;
@@ -285,8 +336,6 @@ 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;
@@ -314,7 +363,6 @@ scrcpy(struct scrcpy_options *options) {
.force_adb_forward = options->force_adb_forward,
.power_off_on_close = options->power_off_on_close,
.clipboard_autosync = options->clipboard_autosync,
.downsize_on_error = options->downsize_on_error,
.tcpip = options->tcpip,
.tcpip_dst = options->tcpip_dst,
};
@@ -357,15 +405,12 @@ scrcpy(struct scrcpy_options *options) {
const char *serial = s->server.params.serial;
assert(serial);
struct sc_file_pusher *fp = NULL;
if (options->display && options->control) {
if (!sc_file_pusher_init(&s->file_pusher, serial,
options->push_target)) {
if (!file_handler_init(&s->file_handler, serial,
options->push_target)) {
goto end;
}
fp = &s->file_pusher;
file_pusher_initialized = true;
file_handler_initialized = true;
}
struct decoder *dec = NULL;
@@ -405,142 +450,44 @@ scrcpy(struct scrcpy_options *options) {
stream_add_sink(&s->stream, &rec->packet_sink);
}
struct sc_controller *controller = NULL;
struct sc_key_processor *kp = NULL;
struct sc_mouse_processor *mp = NULL;
if (options->control) {
#ifdef HAVE_AOA_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) {
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID) {
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->key_inject_mode,
options->forward_key_repeat);
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 (!sc_controller_init(&s->controller, s->server.control_socket,
acksync)) {
if (!controller_init(&s->controller, s->server.control_socket,
acksync)) {
goto end;
}
controller_initialized = true;
if (!sc_controller_start(&s->controller)) {
if (!controller_start(&s->controller)) {
goto end;
}
controller_started = true;
controller = &s->controller;
if (options->turn_screen_off) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
msg.set_screen_power_mode.mode = SC_SCREEN_POWER_MODE_OFF;
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
msg.set_screen_power_mode.mode = SCREEN_POWER_MODE_OFF;
if (!sc_controller_push_msg(&s->controller, &msg)) {
if (!controller_push_msg(&s->controller, &msg)) {
LOGW("Could not request 'set screen power mode'");
}
}
}
// There is a controller if and only if control is enabled
assert(options->control == !!controller);
if (options->display) {
const char *window_title =
options->window_title ? options->window_title : info->device_name;
struct sc_screen_params screen_params = {
.controller = controller,
.fp = fp,
.kp = kp,
.mp = mp,
.forward_all_clicks = options->forward_all_clicks,
.legacy_paste = options->legacy_paste,
.clipboard_autosync = options->clipboard_autosync,
.shortcut_mods = &options->shortcut_mods,
struct screen_params screen_params = {
.window_title = window_title,
.frame_size = info->frame_size,
.always_on_top = options->always_on_top,
@@ -555,7 +502,7 @@ aoa_hid_end:
.buffering_time = options->display_buffer,
};
if (!sc_screen_init(&s->screen, &screen_params)) {
if (!screen_init(&s->screen, &screen_params)) {
goto end;
}
screen_initialized = true;
@@ -583,24 +530,77 @@ aoa_hid_end:
}
stream_started = true;
ret = event_loop(s);
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, &s->screen);
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...");
// Close the window immediately on closing, because screen_destroy() may
// only be called once the stream thread is joined (it may take time)
sc_screen_hide_window(&s->screen);
screen_hide_window(&s->screen);
end:
// The stream is not stopped explicitly, because it will stop by itself on
// end-of-stream
#ifdef HAVE_AOA_HID
if (aoa_hid_initialized) {
if (hid_keyboard_initialized) {
sc_hid_keyboard_destroy(&s->keyboard_hid);
}
if (hid_mouse_initialized) {
sc_hid_mouse_destroy(&s->mouse_hid);
}
sc_hid_keyboard_destroy(&s->keyboard_hid);
sc_aoa_stop(&s->aoa);
}
if (acksync) {
@@ -608,13 +608,13 @@ end:
}
#endif
if (controller_started) {
sc_controller_stop(&s->controller);
controller_stop(&s->controller);
}
if (file_pusher_initialized) {
sc_file_pusher_stop(&s->file_pusher);
if (file_handler_initialized) {
file_handler_stop(&s->file_handler);
}
if (screen_initialized) {
sc_screen_interrupt(&s->screen);
screen_interrupt(&s->screen);
}
if (server_started) {
@@ -644,24 +644,24 @@ end:
// Destroy the screen only after the stream is guaranteed to be finished,
// because otherwise the screen could receive new frames after destruction
if (screen_initialized) {
sc_screen_join(&s->screen);
sc_screen_destroy(&s->screen);
screen_join(&s->screen);
screen_destroy(&s->screen);
}
if (controller_started) {
sc_controller_join(&s->controller);
controller_join(&s->controller);
}
if (controller_initialized) {
sc_controller_destroy(&s->controller);
controller_destroy(&s->controller);
}
if (recorder_initialized) {
recorder_destroy(&s->recorder);
}
if (file_pusher_initialized) {
sc_file_pusher_join(&s->file_pusher);
sc_file_pusher_destroy(&s->file_pusher);
if (file_handler_initialized) {
file_handler_join(&s->file_handler);
file_handler_destroy(&s->file_handler);
}
sc_server_destroy(&s->server);

View File

@@ -12,7 +12,7 @@
#define DISPLAY_MARGINS 96
#define DOWNCAST(SINK) container_of(SINK, struct sc_screen, frame_sink)
#define DOWNCAST(SINK) container_of(SINK, struct screen, frame_sink)
static inline struct sc_size
get_rotated_size(struct sc_size size, int rotation) {
@@ -29,7 +29,7 @@ get_rotated_size(struct sc_size size, int rotation) {
// get the window size in a struct sc_size
static struct sc_size
get_window_size(const struct sc_screen *screen) {
get_window_size(const struct screen *screen) {
int width;
int height;
SDL_GetWindowSize(screen->window, &width, &height);
@@ -41,7 +41,7 @@ get_window_size(const struct sc_screen *screen) {
}
static struct sc_point
get_window_position(const struct sc_screen *screen) {
get_window_position(const struct screen *screen) {
int x;
int y;
SDL_GetWindowPosition(screen->window, &x, &y);
@@ -54,7 +54,7 @@ get_window_position(const struct sc_screen *screen) {
// set the window size to be applied when fullscreen is disabled
static void
set_window_size(struct sc_screen *screen, struct sc_size new_size) {
set_window_size(struct screen *screen, struct sc_size new_size) {
assert(!screen->fullscreen);
assert(!screen->maximized);
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
@@ -156,25 +156,8 @@ get_initial_optimal_size(struct sc_size content_size, uint16_t req_width,
return window_size;
}
static inline bool
sc_screen_is_relative_mode(struct sc_screen *screen) {
// screen->im.mp may be NULL if --no-control
return screen->im.mp && screen->im.mp->relative_mode;
}
static void
sc_screen_capture_mouse(struct sc_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
sc_screen_update_content_rect(struct sc_screen *screen) {
screen_update_content_rect(struct screen *screen) {
int dw;
int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
@@ -211,7 +194,7 @@ sc_screen_update_content_rect(struct sc_screen *screen) {
}
static inline SDL_Texture *
create_texture(struct sc_screen *screen) {
create_texture(struct screen *screen) {
SDL_Renderer *renderer = screen->renderer;
struct sc_size size = screen->frame_size;
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
@@ -242,9 +225,9 @@ create_texture(struct sc_screen *screen) {
// Set the update_content_rect flag if the window or content size may have
// changed, so that the content rectangle is recomputed
static void
sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
screen_render(struct screen *screen, bool update_content_rect) {
if (update_content_rect) {
sc_screen_update_content_rect(screen);
screen_update_content_rect(screen);
}
SDL_RenderClear(screen->renderer);
@@ -288,20 +271,20 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
// <https://stackoverflow.com/a/40693139/1987178>
static int
event_watcher(void *data, SDL_Event *event) {
struct sc_screen *screen = data;
struct screen *screen = data;
if (event->type == SDL_WINDOWEVENT
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
// In practice, it seems to always be called from the same thread in
// that specific case. Anyway, it's just a workaround.
sc_screen_render(screen, true);
screen_render(screen, true);
}
return 0;
}
#endif
static bool
sc_screen_frame_sink_open(struct sc_frame_sink *sink) {
struct sc_screen *screen = DOWNCAST(sink);
screen_frame_sink_open(struct sc_frame_sink *sink) {
struct screen *screen = DOWNCAST(sink);
(void) screen;
#ifndef NDEBUG
screen->open = true;
@@ -312,8 +295,8 @@ sc_screen_frame_sink_open(struct sc_frame_sink *sink) {
}
static void
sc_screen_frame_sink_close(struct sc_frame_sink *sink) {
struct sc_screen *screen = DOWNCAST(sink);
screen_frame_sink_close(struct sc_frame_sink *sink) {
struct screen *screen = DOWNCAST(sink);
(void) screen;
#ifndef NDEBUG
screen->open = false;
@@ -323,8 +306,8 @@ sc_screen_frame_sink_close(struct sc_frame_sink *sink) {
}
static bool
sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
struct sc_screen *screen = DOWNCAST(sink);
screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
struct screen *screen = DOWNCAST(sink);
return sc_video_buffer_push(&screen->vb, frame);
}
@@ -332,7 +315,7 @@ static void
sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
void *userdata) {
(void) vb;
struct sc_screen *screen = userdata;
struct screen *screen = userdata;
// event_failed implies previous_skipped (the previous frame may not have
// been consumed if the event was not sent)
@@ -365,21 +348,12 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
}
bool
sc_screen_init(struct sc_screen *screen,
const struct sc_screen_params *params) {
screen_init(struct screen *screen, const struct screen_params *params) {
screen->resize_pending = false;
screen->has_frame = false;
screen->fullscreen = false;
screen->maximized = false;
screen->event_failed = false;
screen->mouse_captured = false;
screen->mouse_capture_key_pressed = 0;
screen->req.x = params->window_x;
screen->req.y = params->window_y;
screen->req.width = params->window_width;
screen->req.height = params->window_height;
screen->req.fullscreen = params->fullscreen;
static const struct sc_video_buffer_callbacks cbs = {
.on_new_frame = sc_video_buffer_on_new_frame,
@@ -409,6 +383,9 @@ sc_screen_init(struct sc_screen *screen,
get_rotated_size(screen->frame_size, screen->rotation);
screen->content_size = content_size;
struct sc_size window_size =
get_initial_optimal_size(content_size,params->window_width,
params->window_height);
uint32_t window_flags = SDL_WINDOW_HIDDEN
| SDL_WINDOW_RESIZABLE
| SDL_WINDOW_ALLOW_HIGHDPI;
@@ -419,9 +396,13 @@ sc_screen_init(struct sc_screen *screen,
window_flags |= SDL_WINDOW_BORDERLESS;
}
// The window will be positioned and sized on first video frame
screen->window =
SDL_CreateWindow(params->window_title, 0, 0, 0, 0, window_flags);
int x = params->window_x != SC_WINDOW_POSITION_UNDEFINED
? params->window_x : (int) SDL_WINDOWPOS_UNDEFINED;
int y = params->window_y != SC_WINDOW_POSITION_UNDEFINED
? params->window_y : (int) SDL_WINDOWPOS_UNDEFINED;
screen->window = SDL_CreateWindow(params->window_title, x, y,
window_size.width, window_size.height,
window_flags);
if (!screen->window) {
LOGC("Could not create window: %s", SDL_GetError());
goto error_destroy_fps_counter;
@@ -489,28 +470,25 @@ sc_screen_init(struct sc_screen *screen,
goto error_destroy_texture;
}
struct sc_input_manager_params im_params = {
.controller = params->controller,
.fp = params->fp,
.screen = screen,
.kp = params->kp,
.mp = params->mp,
.forward_all_clicks = params->forward_all_clicks,
.legacy_paste = params->legacy_paste,
.clipboard_autosync = params->clipboard_autosync,
.shortcut_mods = params->shortcut_mods,
};
// 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
SDL_SetWindowSize(screen->window, window_size.width, window_size.height);
sc_input_manager_init(&screen->im, &im_params);
screen_update_content_rect(screen);
if (params->fullscreen) {
screen_switch_fullscreen(screen);
}
#ifdef CONTINUOUS_RESIZING_WORKAROUND
SDL_AddEventWatch(event_watcher, screen);
#endif
static const struct sc_frame_sink_ops ops = {
.open = sc_screen_frame_sink_open,
.close = sc_screen_frame_sink_close,
.push = sc_screen_frame_sink_push,
.open = screen_frame_sink_open,
.close = screen_frame_sink_close,
.push = screen_frame_sink_push,
};
screen->frame_sink.ops = &ops;
@@ -539,45 +517,29 @@ error_destroy_video_buffer:
}
static void
sc_screen_show_initial_window(struct sc_screen *screen) {
int x = screen->req.x != SC_WINDOW_POSITION_UNDEFINED
? screen->req.x : (int) SDL_WINDOWPOS_CENTERED;
int y = screen->req.y != SC_WINDOW_POSITION_UNDEFINED
? screen->req.y : (int) SDL_WINDOWPOS_CENTERED;
struct sc_size window_size =
get_initial_optimal_size(screen->content_size, screen->req.width,
screen->req.height);
set_window_size(screen, window_size);
SDL_SetWindowPosition(screen->window, x, y);
if (screen->req.fullscreen) {
sc_screen_switch_fullscreen(screen);
}
screen_show_window(struct screen *screen) {
SDL_ShowWindow(screen->window);
}
void
sc_screen_hide_window(struct sc_screen *screen) {
screen_hide_window(struct screen *screen) {
SDL_HideWindow(screen->window);
}
void
sc_screen_interrupt(struct sc_screen *screen) {
screen_interrupt(struct screen *screen) {
sc_video_buffer_stop(&screen->vb);
fps_counter_interrupt(&screen->fps_counter);
}
void
sc_screen_join(struct sc_screen *screen) {
screen_join(struct screen *screen) {
sc_video_buffer_join(&screen->vb);
fps_counter_join(&screen->fps_counter);
}
void
sc_screen_destroy(struct sc_screen *screen) {
screen_destroy(struct screen *screen) {
#ifndef NDEBUG
assert(!screen->open);
#endif
@@ -590,7 +552,7 @@ sc_screen_destroy(struct sc_screen *screen) {
}
static void
resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
resize_for_content(struct screen *screen, struct sc_size old_content_size,
struct sc_size new_content_size) {
struct sc_size window_size = get_window_size(screen);
struct sc_size target_size = {
@@ -604,7 +566,7 @@ resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
}
static void
set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
set_content_size(struct screen *screen, struct sc_size new_content_size) {
if (!screen->fullscreen && !screen->maximized) {
resize_for_content(screen, screen->content_size, new_content_size);
} else if (!screen->resize_pending) {
@@ -618,7 +580,7 @@ set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
}
static void
apply_pending_resize(struct sc_screen *screen) {
apply_pending_resize(struct screen *screen) {
assert(!screen->fullscreen);
assert(!screen->maximized);
if (screen->resize_pending) {
@@ -629,7 +591,7 @@ apply_pending_resize(struct sc_screen *screen) {
}
void
sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation) {
screen_set_rotation(struct screen *screen, unsigned rotation) {
assert(rotation < 4);
if (rotation == screen->rotation) {
return;
@@ -643,12 +605,12 @@ sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation) {
screen->rotation = rotation;
LOGI("Display rotation set to %u", rotation);
sc_screen_render(screen, true);
screen_render(screen, true);
}
// recreate the texture and resize the window if the frame size has changed
static bool
prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
prepare_for_frame(struct screen *screen, struct sc_size new_frame_size) {
if (screen->frame_size.width != new_frame_size.width
|| screen->frame_size.height != new_frame_size.height) {
// frame dimension changed, destroy texture
@@ -660,7 +622,7 @@ prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
get_rotated_size(new_frame_size, screen->rotation);
set_content_size(screen, new_content_size);
sc_screen_update_content_rect(screen);
screen_update_content_rect(screen);
LOGI("New texture: %" PRIu16 "x%" PRIu16,
screen->frame_size.width, screen->frame_size.height);
@@ -676,7 +638,7 @@ prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
// write the frame into the texture
static void
update_texture(struct sc_screen *screen, const AVFrame *frame) {
update_texture(struct screen *screen, const AVFrame *frame) {
SDL_UpdateYUVTexture(screen->texture, NULL,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
@@ -690,7 +652,7 @@ update_texture(struct sc_screen *screen, const AVFrame *frame) {
}
static bool
sc_screen_update_frame(struct sc_screen *screen) {
screen_update_frame(struct screen *screen) {
av_frame_unref(screen->frame);
sc_video_buffer_consume(&screen->vb, screen->frame);
AVFrame *frame = screen->frame;
@@ -703,23 +665,12 @@ sc_screen_update_frame(struct sc_screen *screen) {
}
update_texture(screen, frame);
if (!screen->has_frame) {
screen->has_frame = true;
// this is the very first frame, show the window
sc_screen_show_initial_window(screen);
if (sc_screen_is_relative_mode(screen)) {
// Capture mouse on start
sc_screen_capture_mouse(screen, true);
}
}
sc_screen_render(screen, false);
screen_render(screen, false);
return true;
}
void
sc_screen_switch_fullscreen(struct sc_screen *screen) {
screen_switch_fullscreen(struct screen *screen) {
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
@@ -732,11 +683,11 @@ sc_screen_switch_fullscreen(struct sc_screen *screen) {
}
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
sc_screen_render(screen, true);
screen_render(screen, true);
}
void
sc_screen_resize_to_fit(struct sc_screen *screen) {
screen_resize_to_fit(struct screen *screen) {
if (screen->fullscreen || screen->maximized) {
return;
}
@@ -760,7 +711,7 @@ sc_screen_resize_to_fit(struct sc_screen *screen) {
}
void
sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
screen_resize_to_pixel_perfect(struct screen *screen) {
if (screen->fullscreen) {
return;
}
@@ -776,34 +727,31 @@ sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
content_size.height);
}
static inline bool
sc_screen_is_mouse_capture_key(SDL_Keycode key) {
return key == SDLK_LALT || key == SDLK_LGUI || key == SDLK_RGUI;
}
void
sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
bool relative_mode = sc_screen_is_relative_mode(screen);
bool
screen_handle_event(struct screen *screen, SDL_Event *event) {
switch (event->type) {
case EVENT_NEW_FRAME: {
bool ok = sc_screen_update_frame(screen);
case EVENT_NEW_FRAME:
if (!screen->has_frame) {
screen->has_frame = true;
// this is the very first frame, show the window
screen_show_window(screen);
}
bool ok = screen_update_frame(screen);
if (!ok) {
LOGW("Frame update failed\n");
}
return;
}
return true;
case SDL_WINDOWEVENT:
if (!screen->has_frame) {
// Do nothing
return;
return true;
}
switch (event->window.event) {
case SDL_WINDOWEVENT_EXPOSED:
sc_screen_render(screen, true);
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
sc_screen_render(screen, true);
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_MAXIMIZED:
screen->maximized = true;
@@ -819,80 +767,18 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
}
screen->maximized = false;
apply_pending_resize(screen);
sc_screen_render(screen, true);
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
if (relative_mode) {
sc_screen_capture_mouse(screen, false);
}
screen_render(screen, true);
break;
}
return;
case SDL_KEYDOWN:
if (relative_mode) {
SDL_Keycode key = event->key.keysym.sym;
if (sc_screen_is_mouse_capture_key(key)) {
if (!screen->mouse_capture_key_pressed) {
screen->mouse_capture_key_pressed = key;
} else {
// Another mouse capture key has been pressed, cancel
// mouse (un)capture
screen->mouse_capture_key_pressed = 0;
}
// Mouse capture keys are never forwarded to the device
return;
}
}
break;
case SDL_KEYUP:
if (relative_mode) {
SDL_Keycode key = event->key.keysym.sym;
SDL_Keycode cap = screen->mouse_capture_key_pressed;
screen->mouse_capture_key_pressed = 0;
if (sc_screen_is_mouse_capture_key(key)) {
if (key == cap) {
// A mouse capture key has been pressed then released:
// toggle the capture mouse mode
sc_screen_capture_mouse(screen,
!screen->mouse_captured);
}
// Mouse capture keys are never forwarded to the device
return;
}
}
break;
case SDL_MOUSEWHEEL:
case SDL_MOUSEMOTION:
case SDL_MOUSEBUTTONDOWN:
if (relative_mode && !screen->mouse_captured) {
// Do not forward to input manager, the mouse will be captured
// on SDL_MOUSEBUTTONUP
return;
}
break;
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
if (relative_mode) {
// Touch events are not compatible with relative mode
// (coordinates are not relative)
return;
}
break;
case SDL_MOUSEBUTTONUP:
if (relative_mode && !screen->mouse_captured) {
sc_screen_capture_mouse(screen, true);
return;
}
break;
return true;
}
sc_input_manager_handle_event(&screen->im, event);
return false;
}
struct sc_point
sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
int32_t x, int32_t y) {
screen_convert_drawable_to_frame_coords(struct screen *screen,
int32_t x, int32_t y) {
unsigned rotation = screen->rotation;
assert(rotation < 4);
@@ -928,14 +814,14 @@ sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
}
struct sc_point
sc_screen_convert_window_to_frame_coords(struct sc_screen *screen,
int32_t x, int32_t y) {
sc_screen_hidpi_scale_coords(screen, &x, &y);
return sc_screen_convert_drawable_to_frame_coords(screen, x, y);
screen_convert_window_to_frame_coords(struct screen *screen,
int32_t x, int32_t y) {
screen_hidpi_scale_coords(screen, &x, &y);
return screen_convert_drawable_to_frame_coords(screen, x, y);
}
void
sc_screen_hidpi_scale_coords(struct sc_screen *screen, int32_t *x, int32_t *y) {
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y) {
// take the HiDPI scaling (dw/ww and dh/wh) into account
int ww, wh, dw, dh;
SDL_GetWindowSize(screen->window, &ww, &wh);

View File

@@ -7,36 +7,22 @@
#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 sc_screen {
struct screen {
struct sc_frame_sink frame_sink; // frame sink trait
#ifndef NDEBUG
bool open; // track the open/close state to assert correct behavior
#endif
struct sc_input_manager im;
struct sc_video_buffer vb;
struct fps_counter fps_counter;
// The initial requested window properties
struct {
int16_t x;
int16_t y;
uint16_t width;
uint16_t height;
bool fullscreen;
} req;
SDL_Window *window;
SDL_Renderer *renderer;
SDL_Texture *texture;
@@ -60,33 +46,18 @@ struct sc_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 sc_screen_params {
struct sc_controller *controller;
struct sc_file_pusher *fp;
struct sc_key_processor *kp;
struct sc_mouse_processor *mp;
bool forward_all_clicks;
bool legacy_paste;
bool clipboard_autosync;
const struct sc_shortcut_mods *shortcut_mods;
struct screen_params {
const char *window_title;
struct sc_size frame_size;
bool always_on_top;
int16_t window_x; // accepts SC_WINDOW_POSITION_UNDEFINED
int16_t window_y; // accepts SC_WINDOW_POSITION_UNDEFINED
uint16_t window_width;
uint16_t window_height;
int16_t window_x;
int16_t window_y;
uint16_t window_width; // accepts SC_WINDOW_POSITION_UNDEFINED
uint16_t window_height; // accepts SC_WINDOW_POSITION_UNDEFINED
bool window_borderless;
@@ -100,65 +71,65 @@ struct sc_screen_params {
// initialize screen, create window, renderer and texture (window is hidden)
bool
sc_screen_init(struct sc_screen *screen, const struct sc_screen_params *params);
screen_init(struct screen *screen, const struct screen_params *params);
// request to interrupt any inner thread
// must be called before screen_join()
void
sc_screen_interrupt(struct sc_screen *screen);
screen_interrupt(struct screen *screen);
// join any inner thread
void
sc_screen_join(struct sc_screen *screen);
screen_join(struct screen *screen);
// destroy window, renderer and texture (if any)
void
sc_screen_destroy(struct sc_screen *screen);
screen_destroy(struct screen *screen);
// hide the window
//
// It is used to hide the window immediately on closing without waiting for
// screen_destroy()
void
sc_screen_hide_window(struct sc_screen *screen);
screen_hide_window(struct screen *screen);
// switch the fullscreen mode
void
sc_screen_switch_fullscreen(struct sc_screen *screen);
screen_switch_fullscreen(struct screen *screen);
// resize window to optimal size (remove black borders)
void
sc_screen_resize_to_fit(struct sc_screen *screen);
screen_resize_to_fit(struct screen *screen);
// resize window to 1:1 (pixel-perfect)
void
sc_screen_resize_to_pixel_perfect(struct sc_screen *screen);
screen_resize_to_pixel_perfect(struct screen *screen);
// set the display rotation (0, 1, 2 or 3, x90 degrees counterclockwise)
void
sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation);
screen_set_rotation(struct screen *screen, unsigned rotation);
// react to SDL events
void
sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event);
bool
screen_handle_event(struct screen *screen, SDL_Event *event);
// convert point from window coordinates to frame coordinates
// x and y are expressed in pixels
struct sc_point
sc_screen_convert_window_to_frame_coords(struct sc_screen *screen,
int32_t x, int32_t y);
screen_convert_window_to_frame_coords(struct screen *screen,
int32_t x, int32_t y);
// convert point from drawable coordinates to frame coordinates
// x and y are expressed in pixels
struct sc_point
sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
int32_t x, int32_t y);
screen_convert_drawable_to_frame_coords(struct screen *screen,
int32_t x, int32_t y);
// Convert coordinates from window to drawable.
// Events are expressed in window coordinates, but content is expressed in
// drawable coordinates. They are the same if HiDPI scaling is 1, but differ
// otherwise.
void
sc_screen_hidpi_scale_coords(struct sc_screen *screen, int32_t *x, int32_t *y);
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y);
#endif

View File

@@ -188,6 +188,7 @@ execute_server(struct sc_server *server,
} \
cmd[count++] = p; \
}
#define STRBOOL(v) (v ? "true" : "false")
ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level));
ADD_PARAM("bit_rate=%" PRIu32, params->bit_rate);
@@ -203,23 +204,23 @@ execute_server(struct sc_server *server,
params->lock_video_orientation);
}
if (server->tunnel.forward) {
ADD_PARAM("tunnel_forward=true");
ADD_PARAM("tunnel_forward=%s", STRBOOL(server->tunnel.forward));
}
if (params->crop) {
ADD_PARAM("crop=%s", params->crop);
}
if (!params->control) {
// By default, control is true
ADD_PARAM("control=false");
ADD_PARAM("control=%s", STRBOOL(params->control));
}
if (params->display_id) {
ADD_PARAM("display_id=%" PRIu32, params->display_id);
}
if (params->show_touches) {
ADD_PARAM("show_touches=true");
ADD_PARAM("show_touches=%s", STRBOOL(params->show_touches));
}
if (params->stay_awake) {
ADD_PARAM("stay_awake=true");
ADD_PARAM("stay_awake=%s", STRBOOL(params->stay_awake));
}
if (params->codec_options) {
ADD_PARAM("codec_options=%s", params->codec_options);
@@ -228,15 +229,11 @@ execute_server(struct sc_server *server,
ADD_PARAM("encoder_name=%s", params->encoder_name);
}
if (params->power_off_on_close) {
ADD_PARAM("power_off_on_close=true");
ADD_PARAM("power_off_on_close=%s", STRBOOL(params->power_off_on_close));
}
if (!params->clipboard_autosync) {
// By default, clipboard_autosync is true
ADD_PARAM("clipboard_autosync=false");
}
if (!params->downsize_on_error) {
// By default, downsize_on_error is true
ADD_PARAM("downsize_on_error=false");
ADD_PARAM("clipboard_autosync=%s", STRBOOL(params->clipboard_autosync));
}
#undef ADD_PARAM

View File

@@ -42,7 +42,6 @@ struct sc_server_params {
bool force_adb_forward;
bool power_off_on_close;
bool clipboard_autosync;
bool downsize_on_error;
bool tcpip;
const char *tcpip_dst;
};

View File

@@ -1,6 +1,7 @@
#include "stream.h"
#include <assert.h>
#include <libavformat/avformat.h>
#include <libavutil/time.h>
#include <unistd.h>
@@ -191,7 +192,7 @@ static int
run_stream(void *data) {
struct stream *stream = data;
const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!codec) {
LOGE("H.264 decoder not found");
goto end;

View File

@@ -5,7 +5,6 @@
#include <stdbool.h>
#include <stdint.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include "trait/packet_sink.h"

View File

@@ -29,24 +29,17 @@ struct sc_key_processor {
struct sc_key_processor_ops {
/**
* Process a keyboard event
* Process the 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);

View File

@@ -6,7 +6,7 @@
#include <assert.h>
#include <stdbool.h>
#include "input_events.h"
#include <SDL2/SDL_events.h>
/**
* Mouse processor trait.
@@ -16,51 +16,24 @@
*/
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);
const SDL_MouseMotionEvent *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);
const SDL_TouchFingerEvent *event);
void
(*process_mouse_button)(struct sc_mouse_processor *mp,
const SDL_MouseButtonEvent *event);
void
(*process_mouse_wheel)(struct sc_mouse_processor *mp,
const SDL_MouseWheelEvent *event);
};
#endif

View File

@@ -83,7 +83,6 @@ 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
@@ -92,7 +91,6 @@ 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

View File

@@ -3,14 +3,13 @@
#include "common.h"
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include "coords.h"
#include "trait/frame_sink.h"
#include "video_buffer.h"
#include "util/tick.h"
#include <libavformat/avformat.h>
struct sc_v4l2_sink {
struct sc_frame_sink frame_sink; // frame sink trait

View File

@@ -11,7 +11,6 @@ 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() {
@@ -21,7 +20,6 @@ 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() {
@@ -31,7 +29,6 @@ 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() {
@@ -43,7 +40,6 @@ 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() {
@@ -55,7 +51,6 @@ 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() {

View File

@@ -6,8 +6,8 @@
#include "control_msg.h"
static void test_serialize_inject_keycode(void) {
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_KEYCODE,
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_KEYCODE,
.inject_keycode = {
.action = AKEY_EVENT_ACTION_UP,
.keycode = AKEYCODE_ENTER,
@@ -16,12 +16,12 @@ static void test_serialize_inject_keycode(void) {
},
};
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 14);
const unsigned char expected[] = {
SC_CONTROL_MSG_TYPE_INJECT_KEYCODE,
CONTROL_MSG_TYPE_INJECT_KEYCODE,
0x01, // AKEY_EVENT_ACTION_UP
0x00, 0x00, 0x00, 0x42, // AKEYCODE_ENTER
0x00, 0x00, 0x00, 0X05, // repeat
@@ -31,19 +31,19 @@ static void test_serialize_inject_keycode(void) {
}
static void test_serialize_inject_text(void) {
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_TEXT,
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_TEXT,
.inject_text = {
.text = "hello, world!",
},
};
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 18);
const unsigned char expected[] = {
SC_CONTROL_MSG_TYPE_INJECT_TEXT,
CONTROL_MSG_TYPE_INJECT_TEXT,
0x00, 0x00, 0x00, 0x0d, // text length
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
};
@@ -51,31 +51,31 @@ static void test_serialize_inject_text(void) {
}
static void test_serialize_inject_text_long(void) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_INJECT_TEXT;
char text[SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH + 1];
memset(text, 'a', SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
text[SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH] = '\0';
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
char text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH + 1];
memset(text, 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH] = '\0';
msg.inject_text.text = text;
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 5 + SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
unsigned char expected[5 + SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH];
expected[0] = SC_CONTROL_MSG_TYPE_INJECT_TEXT;
unsigned char expected[5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH];
expected[0] = CONTROL_MSG_TYPE_INJECT_TEXT;
expected[1] = 0x00;
expected[2] = 0x00;
expected[3] = 0x01;
expected[4] = 0x2c; // text length (32 bits)
memset(&expected[5], 'a', SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
memset(&expected[5], 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_inject_touch_event(void) {
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
.inject_touch_event = {
.action = AMOTION_EVENT_ACTION_DOWN,
.pointer_id = UINT64_C(0x1234567887654321),
@@ -94,12 +94,12 @@ static void test_serialize_inject_touch_event(void) {
},
};
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 28);
const unsigned char expected[] = {
SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
0x00, // AKEY_EVENT_ACTION_DOWN
0x12, 0x34, 0x56, 0x78, 0x87, 0x65, 0x43, 0x21, // pointer id
0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0xc8, // 100 200
@@ -111,8 +111,8 @@ static void test_serialize_inject_touch_event(void) {
}
static void test_serialize_inject_scroll_event(void) {
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
.inject_scroll_event = {
.position = {
.point = {
@@ -126,111 +126,109 @@ static void test_serialize_inject_scroll_event(void) {
},
.hscroll = 1,
.vscroll = -1,
.buttons = 1,
},
};
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 25);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 21);
const unsigned char expected[] = {
SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026
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)));
}
static void test_serialize_back_or_screen_on(void) {
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
.back_or_screen_on = {
.action = AKEY_EVENT_ACTION_UP,
},
};
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 2);
const unsigned char expected[] = {
SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
0x01, // AKEY_EVENT_ACTION_UP
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_expand_notification_panel(void) {
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
};
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 1);
const unsigned char expected[] = {
SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_expand_settings_panel(void) {
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
};
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 1);
const unsigned char expected[] = {
SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_collapse_panels(void) {
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS,
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_COLLAPSE_PANELS,
};
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 1);
const unsigned char expected[] = {
SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS,
CONTROL_MSG_TYPE_COLLAPSE_PANELS,
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_get_clipboard(void) {
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_GET_CLIPBOARD,
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_GET_CLIPBOARD,
.get_clipboard = {
.copy_key = SC_COPY_KEY_COPY,
.copy_key = GET_CLIPBOARD_COPY_KEY_COPY,
},
};
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 2);
const unsigned char expected[] = {
SC_CONTROL_MSG_TYPE_GET_CLIPBOARD,
SC_COPY_KEY_COPY,
CONTROL_MSG_TYPE_GET_CLIPBOARD,
GET_CLIPBOARD_COPY_KEY_COPY,
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_set_clipboard(void) {
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_SET_CLIPBOARD,
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_SET_CLIPBOARD,
.set_clipboard = {
.sequence = UINT64_C(0x0102030405060708),
.paste = true,
@@ -238,12 +236,12 @@ static void test_serialize_set_clipboard(void) {
},
};
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 27);
const unsigned char expected[] = {
SC_CONTROL_MSG_TYPE_SET_CLIPBOARD,
CONTROL_MSG_TYPE_SET_CLIPBOARD,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // sequence
1, // paste
0x00, 0x00, 0x00, 0x0d, // text length
@@ -253,8 +251,8 @@ static void test_serialize_set_clipboard(void) {
}
static void test_serialize_set_clipboard_long(void) {
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_SET_CLIPBOARD,
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_SET_CLIPBOARD,
.set_clipboard = {
.sequence = UINT64_C(0x0102030405060708),
.paste = true,
@@ -262,60 +260,60 @@ static void test_serialize_set_clipboard_long(void) {
},
};
char text[SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH + 1];
memset(text, 'a', SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH);
text[SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH] = '\0';
char text[CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH + 1];
memset(text, 'a', CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH);
text[CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH] = '\0';
msg.set_clipboard.text = text;
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == SC_CONTROL_MSG_MAX_SIZE);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == CONTROL_MSG_MAX_SIZE);
unsigned char expected[SC_CONTROL_MSG_MAX_SIZE] = {
SC_CONTROL_MSG_TYPE_SET_CLIPBOARD,
unsigned char expected[CONTROL_MSG_MAX_SIZE] = {
CONTROL_MSG_TYPE_SET_CLIPBOARD,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // sequence
1, // paste
// text length
SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH >> 24,
(SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH >> 16) & 0xff,
(SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH >> 8) & 0xff,
SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH & 0xff,
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH >> 24,
(CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH >> 16) & 0xff,
(CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH >> 8) & 0xff,
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH & 0xff,
};
memset(expected + 14, 'a', SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH);
memset(expected + 14, 'a', CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH);
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_set_screen_power_mode(void) {
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
.set_screen_power_mode = {
.mode = SC_SCREEN_POWER_MODE_NORMAL,
.mode = SCREEN_POWER_MODE_NORMAL,
},
};
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 2);
const unsigned char expected[] = {
SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
0x02, // SC_SCREEN_POWER_MODE_NORMAL
CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
0x02, // SCREEN_POWER_MODE_NORMAL
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_rotate_device(void) {
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_ROTATE_DEVICE,
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_ROTATE_DEVICE,
};
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 1);
const unsigned char expected[] = {
SC_CONTROL_MSG_TYPE_ROTATE_DEVICE,
CONTROL_MSG_TYPE_ROTATE_DEVICE,
};
assert(!memcmp(buf, expected, sizeof(expected)));
}

View File

@@ -16,8 +16,6 @@ cpu = 'i686'
endian = 'little'
[properties]
ffmpeg_avcodec = 'avcodec-58'
ffmpeg_avformat = 'avformat-58'
ffmpeg_avutil = 'avutil-56'
prebuilt_ffmpeg = 'ffmpeg-4.3.1-win32-shared'
prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win32-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win32-dev'
prebuilt_sdl2 = 'SDL2-2.0.18/i686-w64-mingw32'

View File

@@ -16,8 +16,6 @@ cpu = 'x86_64'
endian = 'little'
[properties]
ffmpeg_avcodec = 'avcodec-59'
ffmpeg_avformat = 'avformat-59'
ffmpeg_avutil = 'avutil-57'
prebuilt_ffmpeg = 'ffmpeg-5.0-full_build-shared'
prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win64-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win64-dev'
prebuilt_sdl2 = 'SDL2-2.0.18/x86_64-w64-mingw32'

View File

@@ -1 +0,0 @@
@cmd

View File

@@ -1,26 +1,33 @@
.PHONY: prepare-win32 prepare-win64 \
prepare-ffmpeg-win32 \
prepare-ffmpeg-win64 \
prepare-ffmpeg-shared-win32 \
prepare-ffmpeg-dev-win32 \
prepare-ffmpeg-shared-win64 \
prepare-ffmpeg-dev-win64 \
prepare-sdl2 \
prepare-adb
prepare-win32: prepare-sdl2 prepare-ffmpeg-win32 prepare-adb
prepare-win64: prepare-sdl2 prepare-ffmpeg-win64 prepare-adb
prepare-win32: prepare-sdl2 prepare-ffmpeg-shared-win32 prepare-ffmpeg-dev-win32 prepare-adb
prepare-win64: prepare-sdl2 prepare-ffmpeg-shared-win64 prepare-ffmpeg-dev-win64 prepare-adb
# Use old FFmpeg version for win32, there are no new prebuilts
prepare-ffmpeg-win32:
prepare-ffmpeg-shared-win32:
@./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win32-shared.zip \
357af9901a456f4dcbacd107e83a934d344c9cb07ddad8aaf80612eeab7d26d2 \
ffmpeg-4.3.1-win32-shared
prepare-ffmpeg-dev-win32:
@./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win32-dev.zip \
230efb08e9bcf225bd474da29676c70e591fc94d8790a740ca801408fddcb78b \
ffmpeg-4.3.1-win32-dev
ln -sf ../ffmpeg-4.3.1-win32-dev/include ffmpeg-4.3.1-win32-shared/
prepare-ffmpeg-win64:
@./prepare-dep https://github.com/GyanD/codexffmpeg/releases/download/5.0/ffmpeg-5.0-full_build-shared.7z \
e5900f6cecd4c438d398bd2fc308736c10b857cd8dd61c11bcfb05bff5d1211a \
ffmpeg-5.0-full_build-shared
prepare-ffmpeg-shared-win64:
@./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win64-shared.zip \
dd29b7f92f48dead4dd940492c7509138c0f99db445076d0a597007298a79940 \
ffmpeg-4.3.1-win64-shared
prepare-ffmpeg-dev-win64:
@./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win64-dev.zip \
2e8038242cf8e1bd095c2978f196ff0462b122cc6ef7e74626a6af15459d8b81 \
ffmpeg-4.3.1-win64-dev
prepare-sdl2:
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.18-mingw.tar.gz \

View File

@@ -34,9 +34,6 @@ extract() {
elif [[ "$file" == *.tar.gz ]]
then
tar xf "$file"
elif [[ "$file" == *.7z ]]
then
7z x "$file"
else
echo "Unsupported file: $file"
return 1

View File

@@ -93,7 +93,6 @@ dist-win32: build-server build-win32
cp data/scrcpy-console.bat "$(DIST)/$(WIN32_TARGET_DIR)"
cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)"
cp data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)"
cp data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)"
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
@@ -111,12 +110,11 @@ dist-win64: build-server build-win64
cp data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)"
cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)"
cp data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)"
cp data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)"
cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/avutil-57.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/avcodec-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/avformat-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/swscale-6.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/swscale-5.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"

View File

@@ -71,13 +71,12 @@ public final class ControlMessage {
return msg;
}
public static ControlMessage createInjectScrollEvent(Position position, int hScroll, int vScroll, int buttons) {
public static ControlMessage createInjectScrollEvent(Position position, int hScroll, int vScroll) {
ControlMessage msg = new ControlMessage();
msg.type = TYPE_INJECT_SCROLL_EVENT;
msg.position = position;
msg.hScroll = hScroll;
msg.vScroll = vScroll;
msg.buttons = buttons;
return msg;
}

View File

@@ -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 = 24;
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
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,8 +154,7 @@ public class ControlMessageReader {
Position position = readPosition(buffer);
int hScroll = buffer.getInt();
int vScroll = buffer.getInt();
int buttons = buffer.getInt();
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll, buttons);
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll);
}
private ControlMessage parseBackOrScreenOnEvent() {

View File

@@ -98,7 +98,7 @@ public class Controller {
break;
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
if (device.supportsInputEvents()) {
injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll(), msg.getButtons());
injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll());
}
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, int buttons) {
private boolean injectScroll(Position position, int hScroll, int vScroll) {
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, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0,
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEFAULT_DEVICE_ID, 0,
InputDevice.SOURCE_MOUSE, 0);
return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
}

View File

@@ -46,17 +46,15 @@ public final class DesktopConnection implements Closeable {
return localSocket;
}
public static DesktopConnection open(boolean tunnelForward, boolean control, boolean sendDummyByte) throws IOException {
public static DesktopConnection open(Device device, boolean tunnelForward, boolean control) throws IOException {
LocalSocket videoSocket;
LocalSocket controlSocket = null;
if (tunnelForward) {
LocalServerSocket localServerSocket = new LocalServerSocket(SOCKET_NAME);
try {
videoSocket = localServerSocket.accept();
if (sendDummyByte) {
// send one byte so the client may read() to detect a connection error
videoSocket.getOutputStream().write(0);
}
// send one byte so the client may read() to detect a connection error
videoSocket.getOutputStream().write(0);
if (control) {
try {
controlSocket = localServerSocket.accept();
@@ -80,7 +78,10 @@ public final class DesktopConnection implements Closeable {
}
}
return new DesktopConnection(videoSocket, controlSocket);
DesktopConnection connection = new DesktopConnection(videoSocket, controlSocket);
Size videoSize = device.getScreenInfo().getVideoSize();
connection.send(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight());
return connection;
}
public void close() throws IOException {
@@ -94,7 +95,7 @@ public final class DesktopConnection implements Closeable {
}
}
public void sendDeviceMeta(String deviceName, int width, int height) throws IOException {
private void send(String deviceName, int width, int height) throws IOException {
byte[] buffer = new byte[DEVICE_NAME_FIELD_LENGTH + 4];
byte[] deviceNameBytes = deviceName.getBytes(StandardCharsets.UTF_8);

View File

@@ -42,11 +42,6 @@ public final class Device {
void onClipboardTextChanged(String text);
}
private final Size deviceSize;
private final Rect crop;
private int maxSize;
private final int lockVideoOrientation;
private ScreenInfo screenInfo;
private RotationListener rotationListener;
private ClipboardListener clipboardListener;
@@ -74,12 +69,7 @@ public final class Device {
int displayInfoFlags = displayInfo.getFlags();
deviceSize = displayInfo.getSize();
crop = options.getCrop();
maxSize = options.getMaxSize();
lockVideoOrientation = options.getLockVideoOrientation();
screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation);
screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockVideoOrientation());
layerStack = displayInfo.getLayerStack();
SERVICE_MANAGER.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() {
@@ -133,11 +123,6 @@ public final class Device {
}
}
public synchronized void setMaxSize(int newMaxSize) {
maxSize = newMaxSize;
screenInfo = ScreenInfo.computeScreenInfo(screenInfo.getReverseVideoRotation(), deviceSize, crop, newMaxSize, lockVideoOrientation);
}
public synchronized ScreenInfo getScreenInfo() {
return screenInfo;
}

View File

@@ -12,6 +12,7 @@ public class Options {
private int lockVideoOrientation = -1;
private boolean tunnelForward;
private Rect crop;
private boolean sendFrameMeta = true; // send PTS so that the client may record properly
private boolean control = true;
private int displayId;
private boolean showTouches;
@@ -20,12 +21,6 @@ public class Options {
private String encoderName;
private boolean powerOffScreenOnClose;
private boolean clipboardAutosync = true;
private boolean downsizeOnError = true;
// Options not used by the scrcpy client, but useful to use scrcpy-server directly
private boolean sendDeviceMeta = true; // send device name and size
private boolean sendFrameMeta = true; // send PTS so that the client may record properly
private boolean sendDummyByte = true; // write a byte on start to detect connection issues
public Ln.Level getLogLevel() {
return logLevel;
@@ -83,6 +78,14 @@ public class Options {
this.crop = crop;
}
public boolean getSendFrameMeta() {
return sendFrameMeta;
}
public void setSendFrameMeta(boolean sendFrameMeta) {
this.sendFrameMeta = sendFrameMeta;
}
public boolean getControl() {
return control;
}
@@ -146,36 +149,4 @@ public class Options {
public void setClipboardAutosync(boolean clipboardAutosync) {
this.clipboardAutosync = clipboardAutosync;
}
public boolean getDownsizeOnError() {
return downsizeOnError;
}
public void setDownsizeOnError(boolean downsizeOnError) {
this.downsizeOnError = downsizeOnError;
}
public boolean getSendDeviceMeta() {
return sendDeviceMeta;
}
public void setSendDeviceMeta(boolean sendDeviceMeta) {
this.sendDeviceMeta = sendDeviceMeta;
}
public boolean getSendFrameMeta() {
return sendFrameMeta;
}
public void setSendFrameMeta(boolean sendFrameMeta) {
this.sendFrameMeta = sendFrameMeta;
}
public boolean getSendDummyByte() {
return sendDummyByte;
}
public void setSendDummyByte(boolean sendDummyByte) {
this.sendDummyByte = sendDummyByte;
}
}

View File

@@ -25,32 +25,24 @@ public class ScreenEncoder implements Device.RotationListener {
private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms
private static final String KEY_MAX_FPS_TO_ENCODER = "max-fps-to-encoder";
// Keep the values in descending order
private static final int[] MAX_SIZE_FALLBACK = {2560, 1920, 1600, 1280, 1024, 800};
private static final int NO_PTS = -1;
private final AtomicBoolean rotationChanged = new AtomicBoolean();
private final ByteBuffer headerBuffer = ByteBuffer.allocate(12);
private final String encoderName;
private final List<CodecOption> codecOptions;
private final int bitRate;
private final int maxFps;
private final boolean sendFrameMeta;
private final boolean downsizeOnError;
private String encoderName;
private List<CodecOption> codecOptions;
private int bitRate;
private int maxFps;
private boolean sendFrameMeta;
private long ptsOrigin;
private boolean firstFrameSent;
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List<CodecOption> codecOptions, String encoderName,
boolean downsizeOnError) {
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List<CodecOption> codecOptions, String encoderName) {
this.sendFrameMeta = sendFrameMeta;
this.bitRate = bitRate;
this.maxFps = maxFps;
this.codecOptions = codecOptions;
this.encoderName = encoderName;
this.downsizeOnError = downsizeOnError;
}
@Override
@@ -99,23 +91,6 @@ public class ScreenEncoder implements Device.RotationListener {
alive = encode(codec, fd);
// do not call stop() on exception, it would trigger an IllegalStateException
codec.stop();
} catch (IllegalStateException e) {
Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage());
if (!downsizeOnError || firstFrameSent) {
// Fail immediately
throw e;
}
int newMaxSize = chooseMaxSizeFallback(screenInfo.getVideoSize());
if (newMaxSize == 0) {
// Definitively fail
throw e;
}
// Retry with a smaller device size
Ln.i("Retrying with -m" + newMaxSize + "...");
device.setMaxSize(newMaxSize);
alive = true;
} finally {
destroyDisplay(display);
codec.release();
@@ -127,18 +102,6 @@ public class ScreenEncoder implements Device.RotationListener {
}
}
private static int chooseMaxSizeFallback(Size failedSize) {
int currentMaxSize = Math.max(failedSize.getWidth(), failedSize.getHeight());
for (int value : MAX_SIZE_FALLBACK) {
if (value < currentMaxSize) {
// We found a smaller value to reduce the video size
return value;
}
}
// No fallback, fail definitively
return 0;
}
private boolean encode(MediaCodec codec, FileDescriptor fd) throws IOException {
boolean eof = false;
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
@@ -159,10 +122,6 @@ public class ScreenEncoder implements Device.RotationListener {
}
IO.writeFully(fd, codecBuffer);
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
// If this is not a config packet, then it contains a frame
firstFrameSent = true;
}
}
} finally {
if (outputBufferId >= 0) {

View File

@@ -80,12 +80,15 @@ public final class ScreenInfo {
return new ScreenInfo(newContentRect, newUnlockedVideoSize, newDeviceRotation, lockedVideoOrientation);
}
public static ScreenInfo computeScreenInfo(int rotation, Size deviceSize, Rect crop, int maxSize, int lockedVideoOrientation) {
public static ScreenInfo computeScreenInfo(DisplayInfo displayInfo, Rect crop, int maxSize, int lockedVideoOrientation) {
int rotation = displayInfo.getRotation();
if (lockedVideoOrientation == Device.LOCK_VIDEO_ORIENTATION_INITIAL) {
// The user requested to lock the video orientation to the current orientation
lockedVideoOrientation = rotation;
}
Size deviceSize = displayInfo.getSize();
Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight());
if (crop != null) {
if (rotation % 2 != 0) { // 180s preserve dimensions

View File

@@ -1,6 +1,7 @@
package com.genymobile.scrcpy;
import android.graphics.Rect;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.os.BatteryManager;
import android.os.Build;
@@ -19,7 +20,6 @@ public final class Server {
private static void initAndCleanUp(Options options) {
boolean mustDisableShowTouchesOnCleanUp = false;
int restoreStayOn = -1;
boolean restoreNormalPowerMode = options.getControl(); // only restore power mode if control is enabled
if (options.getShowTouches() || options.getStayAwake()) {
Settings settings = Device.getSettings();
if (options.getShowTouches()) {
@@ -52,7 +52,7 @@ public final class Server {
}
try {
CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, restoreNormalPowerMode, options.getPowerOffScreenOnClose());
CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, true, options.getPowerOffScreenOnClose());
} catch (IOException e) {
Ln.e("Could not configure cleanup", e);
}
@@ -67,15 +67,10 @@ public final class Server {
boolean tunnelForward = options.isTunnelForward();
boolean control = options.getControl();
boolean sendDummyByte = options.getSendDummyByte();
try (DesktopConnection connection = DesktopConnection.open(tunnelForward, control, sendDummyByte)) {
if (options.getSendDeviceMeta()) {
Size videoSize = device.getScreenInfo().getVideoSize();
connection.sendDeviceMeta(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight());
}
try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward, control)) {
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), codecOptions,
options.getEncoderName(), options.getDownsizeOnError());
options.getEncoderName());
Thread controllerThread = null;
Thread deviceMessageSenderThread = null;
@@ -205,6 +200,10 @@ public final class Server {
Rect crop = parseCrop(value);
options.setCrop(crop);
break;
case "send_frame_meta":
boolean sendFrameMeta = Boolean.parseBoolean(value);
options.setSendFrameMeta(sendFrameMeta);
break;
case "control":
boolean control = Boolean.parseBoolean(value);
options.setControl(control);
@@ -238,29 +237,6 @@ public final class Server {
boolean clipboardAutosync = Boolean.parseBoolean(value);
options.setClipboardAutosync(clipboardAutosync);
break;
case "downsize_on_error":
boolean downsizeOnError = Boolean.parseBoolean(value);
options.setDownsizeOnError(downsizeOnError);
break;
case "send_device_meta":
boolean sendDeviceMeta = Boolean.parseBoolean(value);
options.setSendDeviceMeta(sendDeviceMeta);
break;
case "send_frame_meta":
boolean sendFrameMeta = Boolean.parseBoolean(value);
options.setSendFrameMeta(sendFrameMeta);
break;
case "send_dummy_byte":
boolean sendDummyByte = Boolean.parseBoolean(value);
options.setSendDummyByte(sendDummyByte);
break;
case "raw_video_stream":
boolean rawVideoStream = Boolean.parseBoolean(value);
if (rawVideoStream) {
options.setSendDeviceMeta(false);
options.setSendFrameMeta(false);
options.setSendDummyByte(false);
}
default:
Ln.w("Unknown server option: " + key);
break;
@@ -287,6 +263,16 @@ public final class Server {
}
private static void suggestFix(Throwable e) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (e instanceof MediaCodec.CodecException) {
MediaCodec.CodecException mce = (MediaCodec.CodecException) e;
if (mce.getErrorCode() == 0xfffffc0e) {
Ln.e("The hardware encoder is not able to encode at the given definition.");
Ln.e("Try with a lower definition:");
Ln.e(" scrcpy -m 1024");
}
}
}
if (e instanceof InvalidDisplayIdException) {
InvalidDisplayIdException idie = (InvalidDisplayIdException) e;
int[] displayIds = idie.getAvailableDisplayIds();

View File

@@ -16,7 +16,6 @@ public final class InputManager {
private final IInterface manager;
private Method injectInputEventMethod;
private boolean alternativeInjectInputEventMethod;
private static Method setDisplayIdMethod;
@@ -26,12 +25,7 @@ public final class InputManager {
private Method getInjectInputEventMethod() throws NoSuchMethodException {
if (injectInputEventMethod == null) {
try {
injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class);
} catch (NoSuchMethodException e) {
injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class, int.class);
alternativeInjectInputEventMethod = true;
}
injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class);
}
return injectInputEventMethod;
}
@@ -39,10 +33,6 @@ public final class InputManager {
public boolean injectInputEvent(InputEvent inputEvent, int mode) {
try {
Method method = getInjectInputEventMethod();
if (alternativeInjectInputEventMethod) {
// See <https://github.com/Genymobile/scrcpy/issues/2250>
return (boolean) method.invoke(manager, inputEvent, mode, 0);
}
return (boolean) method.invoke(manager, inputEvent, mode);
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
Ln.e("Could not invoke method", e);

View File

@@ -128,7 +128,6 @@ public class ControlMessageReaderTest {
dos.writeShort(1920);
dos.writeInt(1);
dos.writeInt(-1);
dos.writeInt(1);
byte[] packet = bos.toByteArray();
@@ -145,7 +144,6 @@ 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