Compare commits
53 Commits
traits.7
...
server_thr
Author | SHA1 | Date | |
---|---|---|---|
c37d455fa2 | |||
6e8df74c41 | |||
52f5c6d4c1 | |||
efb531943d | |||
4dcda82582 | |||
5369c4f754 | |||
233f8e6cc4 | |||
9a7d351d67 | |||
d00ee640c0 | |||
ae6ec7a194 | |||
84f17fdeab | |||
1cde68a1fa | |||
45e7280148 | |||
41a0383d7c | |||
d39161f753 | |||
5af9d0ee0f | |||
fd0dc6c0cd | |||
151bc16644 | |||
ffc00210e9 | |||
243854a408 | |||
8b90dc61b9 | |||
2a5dfc1c17 | |||
e3fccc5a5e | |||
0541f1bff2 | |||
0272e6dc77 | |||
2a94a2b119 | |||
e91acdb0c4 | |||
6f5ad21f57 | |||
08b3086ffc | |||
deab7da761 | |||
f7a1b67d66 | |||
cbed38799e | |||
5beb7d6c02 | |||
5980183a33 | |||
fe8de893ca | |||
a974483c15 | |||
1b072a24c4 | |||
08f1fd46c8 | |||
2ddf760c09 | |||
5d9e96dc4e | |||
de9b79ec2d | |||
55806e7d31 | |||
21b590b766 | |||
d7e6589677 | |||
b4ee9f27ce | |||
6fa63cf6f8 | |||
50eecdab28 | |||
9576283907 | |||
66c581851f | |||
bb4614d558 | |||
aaf7875d92 | |||
b9c3f65fd8 | |||
d0739911a3 |
82
README.md
82
README.md
@ -198,6 +198,7 @@ If `--max-size` is also specified, resizing is applied after cropping.
|
|||||||
To lock the orientation of the mirroring:
|
To lock the orientation of the mirroring:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
scrcpy --lock-video-orientation # initial (current) orientation
|
||||||
scrcpy --lock-video-orientation 0 # natural orientation
|
scrcpy --lock-video-orientation 0 # natural orientation
|
||||||
scrcpy --lock-video-orientation 1 # 90° counterclockwise
|
scrcpy --lock-video-orientation 1 # 90° counterclockwise
|
||||||
scrcpy --lock-video-orientation 2 # 180°
|
scrcpy --lock-video-orientation 2 # 180°
|
||||||
@ -225,7 +226,9 @@ error will give the available encoders:
|
|||||||
scrcpy --encoder _
|
scrcpy --encoder _
|
||||||
```
|
```
|
||||||
|
|
||||||
### Recording
|
### Capture
|
||||||
|
|
||||||
|
#### Recording
|
||||||
|
|
||||||
It is possible to record the screen while mirroring:
|
It is possible to record the screen while mirroring:
|
||||||
|
|
||||||
@ -249,6 +252,58 @@ variation] does not impact the recorded file.
|
|||||||
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
||||||
|
|
||||||
|
|
||||||
|
#### v4l2loopback
|
||||||
|
|
||||||
|
On Linux, it is possible to send the video stream to a v4l2 loopback device, so
|
||||||
|
that the Android device can be opened like a webcam by any v4l2-capable tool.
|
||||||
|
|
||||||
|
The module `v4l2loopback` must be installed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install v4l2loopback-dkms
|
||||||
|
```
|
||||||
|
|
||||||
|
To create a v4l2 device:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo modprobe v4l2loopback
|
||||||
|
```
|
||||||
|
|
||||||
|
This will create a new video device in `/dev/videoN`, where `N` is an integer
|
||||||
|
(more [options](https://github.com/umlaeute/v4l2loopback#options) are available
|
||||||
|
to create several devices or devices with specific IDs).
|
||||||
|
|
||||||
|
To list the enabled devices:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# requires v4l-utils package
|
||||||
|
v4l2-ctl --list-devices
|
||||||
|
|
||||||
|
# simple but might be sufficient
|
||||||
|
ls /dev/video*
|
||||||
|
```
|
||||||
|
|
||||||
|
To start scrcpy using a v4l2 sink:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --v4l2-sink=/dev/videoN
|
||||||
|
scrcpy --v4l2-sink=/dev/videoN -N # --no-display to disable mirroring window
|
||||||
|
```
|
||||||
|
|
||||||
|
(replace `N` by the device ID, check with `ls /dev/video*`)
|
||||||
|
|
||||||
|
Once enabled, you can open your video stream with a v4l2-capable tool:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ffplay -i /dev/videoN
|
||||||
|
vlc v4l2:///dev/videoN # VLC might add some buffering delay
|
||||||
|
```
|
||||||
|
|
||||||
|
For example, you could capture the video within [OBS].
|
||||||
|
|
||||||
|
[OBS]: https://obsproject.com/fr
|
||||||
|
|
||||||
|
|
||||||
### Connection
|
### Connection
|
||||||
|
|
||||||
#### Wireless
|
#### Wireless
|
||||||
@ -686,10 +741,10 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
|
|||||||
| Rotate display left | <kbd>MOD</kbd>+<kbd>←</kbd> _(left)_
|
| Rotate display left | <kbd>MOD</kbd>+<kbd>←</kbd> _(left)_
|
||||||
| Rotate display right | <kbd>MOD</kbd>+<kbd>→</kbd> _(right)_
|
| Rotate display right | <kbd>MOD</kbd>+<kbd>→</kbd> _(right)_
|
||||||
| Resize window to 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
|
| Resize window to 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
|
||||||
| Resize window to remove black borders | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Double-click¹_
|
| Resize window to remove black borders | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Double-left-click¹_
|
||||||
| Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_
|
| Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_
|
||||||
| Click on `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Right-click²_
|
| Click on `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Right-click²_
|
||||||
| Click on `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd>
|
| Click on `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _4th-click³_
|
||||||
| Click on `MENU` (unlock screen) | <kbd>MOD</kbd>+<kbd>m</kbd>
|
| Click on `MENU` (unlock screen) | <kbd>MOD</kbd>+<kbd>m</kbd>
|
||||||
| Click on `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(up)_
|
| Click on `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(up)_
|
||||||
| Click on `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(down)_
|
| Click on `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(down)_
|
||||||
@ -698,18 +753,27 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
|
|||||||
| Turn device screen off (keep mirroring) | <kbd>MOD</kbd>+<kbd>o</kbd>
|
| Turn device screen off (keep mirroring) | <kbd>MOD</kbd>+<kbd>o</kbd>
|
||||||
| Turn device screen on | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
|
| Turn device screen on | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
|
||||||
| Rotate device screen | <kbd>MOD</kbd>+<kbd>r</kbd>
|
| Rotate device screen | <kbd>MOD</kbd>+<kbd>r</kbd>
|
||||||
| Expand notification panel | <kbd>MOD</kbd>+<kbd>n</kbd>
|
| Expand notification panel | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5th-click³_
|
||||||
| Collapse notification panel | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
| Expand settings panel | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Double-5th-click³_
|
||||||
| Copy to clipboard³ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
| Collapse panels | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
||||||
| Cut to clipboard³ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
| Copy to clipboard⁴ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
||||||
| Synchronize clipboards and paste³ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
| Cut to clipboard⁴ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
||||||
|
| Synchronize clipboards and paste⁴ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
||||||
| Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
| Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
||||||
| Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
| Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
||||||
| Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_
|
| Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_
|
||||||
|
|
||||||
_¹Double-click on black borders to remove them._
|
_¹Double-click on black borders to remove them._
|
||||||
_²Right-click turns the screen on if it was off, presses BACK otherwise._
|
_²Right-click turns the screen on if it was off, presses BACK otherwise._
|
||||||
_³Only on Android >= 7._
|
_³4th and 5th mouse buttons, if your mouse has them._
|
||||||
|
_⁴Only on Android >= 7._
|
||||||
|
|
||||||
|
Shortcuts with repeated keys are executted by releasing and pressing the key a
|
||||||
|
second time. For example, to execute "Expand settings panel":
|
||||||
|
|
||||||
|
1. Press and keep pressing <kbd>MOD</kbd>.
|
||||||
|
2. Then double-press <kbd>n</kbd>.
|
||||||
|
3. Finally, release <kbd>MOD</kbd>.
|
||||||
|
|
||||||
All <kbd>Ctrl</kbd>+_key_ shortcuts are forwarded to the device, so they are
|
All <kbd>Ctrl</kbd>+_key_ shortcuts are forwarded to the device, so they are
|
||||||
handled by the active application.
|
handled by the active application.
|
||||||
|
14
app/scrcpy.1
14
app/scrcpy.1
@ -83,10 +83,12 @@ Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+S
|
|||||||
This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically.
|
This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-lock\-video\-orientation " value
|
.BI "\-\-lock\-video\-orientation " [value]
|
||||||
Lock video orientation to \fIvalue\fR. Possible values are -1 (unlocked), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees otation counterclockwise.
|
Lock video orientation to \fIvalue\fR. Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees otation counterclockwise.
|
||||||
|
|
||||||
Default is -1 (unlocked).
|
Default is "unlocked".
|
||||||
|
|
||||||
|
Passing the option without argument is equivalent to passing "initial".
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-max\-fps " value
|
.BI "\-\-max\-fps " value
|
||||||
@ -183,6 +185,12 @@ Enable "show touches" on start, restore the initial value on exit.
|
|||||||
|
|
||||||
It only shows physical touches (not clicks from scrcpy).
|
It only shows physical touches (not clicks from scrcpy).
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-\-v4l2-sink " /dev/videoN
|
||||||
|
Output to v4l2loopback device.
|
||||||
|
|
||||||
|
It requires to lock the video orientation (see --lock-video-orientation).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-V, \-\-verbosity " value
|
.BI "\-V, \-\-verbosity " value
|
||||||
Set the log level ("debug", "info", "warn" or "error").
|
Set the log level ("debug", "info", "warn" or "error").
|
||||||
|
@ -79,12 +79,15 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" This is a workaround for some devices not behaving as\n"
|
" This is a workaround for some devices not behaving as\n"
|
||||||
" expected when setting the device clipboard programmatically.\n"
|
" expected when setting the device clipboard programmatically.\n"
|
||||||
"\n"
|
"\n"
|
||||||
" --lock-video-orientation value\n"
|
" --lock-video-orientation [value]\n"
|
||||||
" Lock video orientation to value.\n"
|
" Lock video orientation to value.\n"
|
||||||
" Possible values are -1 (unlocked), 0, 1, 2 and 3.\n"
|
" Possible values are \"unlocked\", \"initial\" (locked to the\n"
|
||||||
|
" initial orientation), 0, 1, 2 and 3.\n"
|
||||||
" Natural device orientation is 0, and each increment adds a\n"
|
" Natural device orientation is 0, and each increment adds a\n"
|
||||||
" 90 degrees rotation counterclockwise.\n"
|
" 90 degrees rotation counterclockwise.\n"
|
||||||
" Default is -1 (unlocked).\n"
|
" Default is \"unlocked\".\n"
|
||||||
|
" Passing the option without argument is equivalent to passing\n"
|
||||||
|
" \"initial\".\n"
|
||||||
"\n"
|
"\n"
|
||||||
" --max-fps value\n"
|
" --max-fps value\n"
|
||||||
" Limit the frame rate of screen capture (officially supported\n"
|
" Limit the frame rate of screen capture (officially supported\n"
|
||||||
@ -173,11 +176,13 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" on exit.\n"
|
" on exit.\n"
|
||||||
" It only shows physical touches (not clicks from scrcpy).\n"
|
" It only shows physical touches (not clicks from scrcpy).\n"
|
||||||
"\n"
|
"\n"
|
||||||
" --v4l2_sink /dev/videoN\n"
|
#ifdef HAVE_V4L2
|
||||||
|
" --v4l2-sink /dev/videoN\n"
|
||||||
" Output to v4l2loopback device.\n"
|
" Output to v4l2loopback device.\n"
|
||||||
" It requires to lock the video orientation (see\n"
|
" It requires to lock the video orientation (see\n"
|
||||||
" --lock-video-orientation).\n"
|
" --lock-video-orientation).\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
#endif
|
||||||
" -V, --verbosity value\n"
|
" -V, --verbosity value\n"
|
||||||
" Set the log level (debug, info, warn or error).\n"
|
" Set the log level (debug, info, warn or error).\n"
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
@ -388,15 +393,27 @@ parse_max_fps(const char *s, uint16_t *max_fps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_lock_video_orientation(const char *s, int8_t *lock_video_orientation) {
|
parse_lock_video_orientation(const char *s,
|
||||||
|
enum sc_lock_video_orientation *lock_mode) {
|
||||||
|
if (!s || !strcmp(s, "initial")) {
|
||||||
|
// Without argument, lock the initial orientation
|
||||||
|
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_INITIAL;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(s, "unlocked")) {
|
||||||
|
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
long value;
|
long value;
|
||||||
bool ok = parse_integer_arg(s, &value, false, -1, 3,
|
bool ok = parse_integer_arg(s, &value, false, 0, 3,
|
||||||
"lock video orientation");
|
"lock video orientation");
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
*lock_video_orientation = (int8_t) value;
|
*lock_mode = (enum sc_lock_video_orientation) value;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -686,7 +703,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
{"fullscreen", no_argument, NULL, 'f'},
|
{"fullscreen", no_argument, NULL, 'f'},
|
||||||
{"help", no_argument, NULL, 'h'},
|
{"help", no_argument, NULL, 'h'},
|
||||||
{"legacy-paste", no_argument, NULL, OPT_LEGACY_PASTE},
|
{"legacy-paste", no_argument, NULL, OPT_LEGACY_PASTE},
|
||||||
{"lock-video-orientation", required_argument, NULL,
|
{"lock-video-orientation", optional_argument, NULL,
|
||||||
OPT_LOCK_VIDEO_ORIENTATION},
|
OPT_LOCK_VIDEO_ORIENTATION},
|
||||||
{"max-fps", required_argument, NULL, OPT_MAX_FPS},
|
{"max-fps", required_argument, NULL, OPT_MAX_FPS},
|
||||||
{"max-size", required_argument, NULL, 'm'},
|
{"max-size", required_argument, NULL, 'm'},
|
||||||
@ -709,7 +726,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
{"stay-awake", no_argument, NULL, 'w'},
|
{"stay-awake", no_argument, NULL, 'w'},
|
||||||
{"turn-screen-off", no_argument, NULL, 'S'},
|
{"turn-screen-off", no_argument, NULL, 'S'},
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
{"v4l2_sink", required_argument, NULL, OPT_V4L2_SINK},
|
{"v4l2-sink", required_argument, NULL, OPT_V4L2_SINK},
|
||||||
#endif
|
#endif
|
||||||
{"verbosity", required_argument, NULL, 'V'},
|
{"verbosity", required_argument, NULL, 'V'},
|
||||||
{"version", no_argument, NULL, 'v'},
|
{"version", no_argument, NULL, 'v'},
|
||||||
@ -774,7 +791,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case OPT_LOCK_VIDEO_ORIENTATION:
|
case OPT_LOCK_VIDEO_ORIENTATION:
|
||||||
if (!parse_lock_video_orientation(optarg, &opts->lock_video_orientation)) {
|
if (!parse_lock_video_orientation(optarg,
|
||||||
|
&opts->lock_video_orientation)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -894,29 +912,34 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
case OPT_POWER_OFF_ON_CLOSE:
|
case OPT_POWER_OFF_ON_CLOSE:
|
||||||
opts->power_off_on_close = true;
|
opts->power_off_on_close = true;
|
||||||
break;
|
break;
|
||||||
|
#ifdef HAVE_V4L2
|
||||||
case OPT_V4L2_SINK:
|
case OPT_V4L2_SINK:
|
||||||
opts->v4l2_device = optarg;
|
opts->v4l2_device = optarg;
|
||||||
break;
|
break;
|
||||||
|
#endif
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_V4L2
|
||||||
if (!opts->display && !opts->record_filename && !opts->v4l2_device) {
|
if (!opts->display && !opts->record_filename && !opts->v4l2_device) {
|
||||||
LOGE("-N/--no-display requires either screen recording (-r/--record)"
|
LOGE("-N/--no-display requires either screen recording (-r/--record)"
|
||||||
#ifdef HAVE_V4L2
|
" or sink to v4l2loopback device (--v4l2-sink)");
|
||||||
" or sink to v4l2loopback device (--v4l2_sink)"
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_V4L2
|
if (opts->v4l2_device && opts->lock_video_orientation
|
||||||
if (opts->v4l2_device && opts->lock_video_orientation == -1) {
|
== SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
|
||||||
LOGI("Video orientation is locked for v4l2 sink. "
|
LOGI("Video orientation is locked for v4l2 sink. "
|
||||||
"See --lock-video-orientation.");
|
"See --lock-video-orientation.");
|
||||||
opts->lock_video_orientation = 0;
|
opts->lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_INITIAL;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (!opts->display && !opts->record_filename) {
|
||||||
|
LOGE("-N/--no-display requires screen recording (-r/--record)");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -81,7 +81,8 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
|||||||
buf[1] = msg->set_screen_power_mode.mode;
|
buf[1] = msg->set_screen_power_mode.mode;
|
||||||
return 2;
|
return 2;
|
||||||
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
case CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
||||||
|
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||||
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
||||||
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||||
// no additional data
|
// no additional data
|
||||||
|
@ -27,7 +27,8 @@ enum control_msg_type {
|
|||||||
CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
|
CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
|
||||||
CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
||||||
CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
|
CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
|
||||||
CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL,
|
CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
|
||||||
|
CONTROL_MSG_TYPE_COLLAPSE_PANELS,
|
||||||
CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
||||||
CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||||
CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
|
CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
|
||||||
|
@ -111,6 +111,8 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) {
|
|||||||
// A frame lost should not make the whole pipeline fail. The error, if
|
// A frame lost should not make the whole pipeline fail. The error, if
|
||||||
// any, is already logged.
|
// any, is already logged.
|
||||||
(void) ok;
|
(void) ok;
|
||||||
|
|
||||||
|
av_frame_unref(decoder->frame);
|
||||||
} else if (ret != AVERROR(EAGAIN)) {
|
} else if (ret != AVERROR(EAGAIN)) {
|
||||||
LOGE("Could not receive video frame: %d", ret);
|
LOGE("Could not receive video frame: %d", ret);
|
||||||
return false;
|
return false;
|
||||||
|
@ -72,6 +72,10 @@ input_manager_init(struct input_manager *im,
|
|||||||
im->sdl_shortcut_mods.count = shortcut_mods->count;
|
im->sdl_shortcut_mods.count = shortcut_mods->count;
|
||||||
|
|
||||||
im->vfinger_down = false;
|
im->vfinger_down = false;
|
||||||
|
|
||||||
|
im->last_keycode = SDLK_UNKNOWN;
|
||||||
|
im->last_mod = 0;
|
||||||
|
im->key_repeat = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -179,9 +183,19 @@ expand_notification_panel(struct controller *controller) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
collapse_notification_panel(struct controller *controller) {
|
expand_settings_panel(struct controller *controller) {
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
msg.type = CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL;
|
msg.type = CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL;
|
||||||
|
|
||||||
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
|
LOGW("Could not request 'expand settings panel'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
collapse_panels(struct controller *controller) {
|
||||||
|
struct control_msg msg;
|
||||||
|
msg.type = CONTROL_MSG_TYPE_COLLAPSE_PANELS;
|
||||||
|
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
LOGW("Could not request 'collapse notification panel'");
|
LOGW("Could not request 'collapse notification panel'");
|
||||||
@ -384,16 +398,27 @@ input_manager_process_key(struct input_manager *im,
|
|||||||
// control: indicates the state of the command-line option --no-control
|
// control: indicates the state of the command-line option --no-control
|
||||||
bool control = im->control;
|
bool control = im->control;
|
||||||
|
|
||||||
bool smod = is_shortcut_mod(im, event->keysym.mod);
|
|
||||||
|
|
||||||
struct controller *controller = im->controller;
|
struct controller *controller = im->controller;
|
||||||
|
|
||||||
SDL_Keycode keycode = event->keysym.sym;
|
SDL_Keycode keycode = event->keysym.sym;
|
||||||
|
uint16_t mod = event->keysym.mod;
|
||||||
bool down = event->type == SDL_KEYDOWN;
|
bool down = event->type == SDL_KEYDOWN;
|
||||||
bool ctrl = event->keysym.mod & KMOD_CTRL;
|
bool ctrl = event->keysym.mod & KMOD_CTRL;
|
||||||
bool shift = event->keysym.mod & KMOD_SHIFT;
|
bool shift = event->keysym.mod & KMOD_SHIFT;
|
||||||
bool repeat = event->repeat;
|
bool repeat = event->repeat;
|
||||||
|
|
||||||
|
bool smod = is_shortcut_mod(im, mod);
|
||||||
|
|
||||||
|
if (down && !repeat) {
|
||||||
|
if (keycode == im->last_keycode && mod == im->last_mod) {
|
||||||
|
++im->key_repeat;
|
||||||
|
} else {
|
||||||
|
im->key_repeat = 0;
|
||||||
|
im->last_keycode = keycode;
|
||||||
|
im->last_mod = mod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The shortcut modifier is pressed
|
// The shortcut modifier is pressed
|
||||||
if (smod) {
|
if (smod) {
|
||||||
int action = down ? ACTION_DOWN : ACTION_UP;
|
int action = down ? ACTION_DOWN : ACTION_UP;
|
||||||
@ -498,9 +523,11 @@ input_manager_process_key(struct input_manager *im,
|
|||||||
case SDLK_n:
|
case SDLK_n:
|
||||||
if (control && !repeat && down) {
|
if (control && !repeat && down) {
|
||||||
if (shift) {
|
if (shift) {
|
||||||
collapse_notification_panel(controller);
|
collapse_panels(controller);
|
||||||
} else {
|
} else if (im->key_repeat == 0) {
|
||||||
expand_notification_panel(controller);
|
expand_notification_panel(controller);
|
||||||
|
} else {
|
||||||
|
expand_settings_panel(controller);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -666,7 +693,11 @@ input_manager_process_mouse_button(struct input_manager *im,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (control && event->button == SDL_BUTTON_X2 && down) {
|
if (control && event->button == SDL_BUTTON_X2 && down) {
|
||||||
expand_notification_panel(im->controller);
|
if (event->clicks < 2) {
|
||||||
|
expand_notification_panel(im->controller);
|
||||||
|
} else {
|
||||||
|
expand_settings_panel(im->controller);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (control && event->button == SDL_BUTTON_RIGHT) {
|
if (control && event->button == SDL_BUTTON_RIGHT) {
|
||||||
|
@ -33,6 +33,13 @@ struct input_manager {
|
|||||||
} sdl_shortcut_mods;
|
} sdl_shortcut_mods;
|
||||||
|
|
||||||
bool vfinger_down;
|
bool vfinger_down;
|
||||||
|
|
||||||
|
// Tracks the number of identical consecutive shortcut key down events.
|
||||||
|
// Not to be confused with event->repeat, which counts the number of
|
||||||
|
// system-generated repeated key presses.
|
||||||
|
unsigned key_repeat;
|
||||||
|
SDL_Keycode last_keycode;
|
||||||
|
uint16_t last_mod;
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -243,10 +243,6 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
scrcpy(const struct scrcpy_options *options) {
|
scrcpy(const struct scrcpy_options *options) {
|
||||||
if (!server_init(&server)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
|
|
||||||
bool server_started = false;
|
bool server_started = false;
|
||||||
@ -263,6 +259,7 @@ scrcpy(const struct scrcpy_options *options) {
|
|||||||
|
|
||||||
bool record = !!options->record_filename;
|
bool record = !!options->record_filename;
|
||||||
struct server_params params = {
|
struct server_params params = {
|
||||||
|
.serial = options->serial,
|
||||||
.log_level = options->log_level,
|
.log_level = options->log_level,
|
||||||
.crop = options->crop,
|
.crop = options->crop,
|
||||||
.port_range = options->port_range,
|
.port_range = options->port_range,
|
||||||
@ -279,7 +276,12 @@ scrcpy(const struct scrcpy_options *options) {
|
|||||||
.force_adb_forward = options->force_adb_forward,
|
.force_adb_forward = options->force_adb_forward,
|
||||||
.power_off_on_close = options->power_off_on_close,
|
.power_off_on_close = options->power_off_on_close,
|
||||||
};
|
};
|
||||||
if (!server_start(&server, options->serial, ¶ms)) {
|
|
||||||
|
if (!server_init(&server, ¶ms)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!server_start(&server)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,7 +306,6 @@ scrcpy(const struct scrcpy_options *options) {
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct decoder *dec = NULL;
|
|
||||||
if (options->display) {
|
if (options->display) {
|
||||||
if (!fps_counter_init(&fps_counter)) {
|
if (!fps_counter_init(&fps_counter)) {
|
||||||
goto end;
|
goto end;
|
||||||
@ -312,13 +313,20 @@ scrcpy(const struct scrcpy_options *options) {
|
|||||||
fps_counter_initialized = true;
|
fps_counter_initialized = true;
|
||||||
|
|
||||||
if (options->control) {
|
if (options->control) {
|
||||||
if (!file_handler_init(&file_handler, server.serial,
|
if (!file_handler_init(&file_handler, options->serial,
|
||||||
options->push_target)) {
|
options->push_target)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
file_handler_initialized = true;
|
file_handler_initialized = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct decoder *dec = NULL;
|
||||||
|
bool needs_decoder = options->display;
|
||||||
|
#ifdef HAVE_V4L2
|
||||||
|
needs_decoder |= !!options->v4l2_device;
|
||||||
|
#endif
|
||||||
|
if (needs_decoder) {
|
||||||
decoder_init(&decoder);
|
decoder_init(&decoder);
|
||||||
dec = &decoder;
|
dec = &decoder;
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,16 @@ enum sc_record_format {
|
|||||||
SC_RECORD_FORMAT_MKV,
|
SC_RECORD_FORMAT_MKV,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum sc_lock_video_orientation {
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
|
||||||
|
// lock the current orientation when scrcpy starts
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_INITIAL = -2,
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_0 = 0,
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_1,
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_2,
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_3,
|
||||||
|
};
|
||||||
|
|
||||||
#define SC_MAX_SHORTCUT_MODS 8
|
#define SC_MAX_SHORTCUT_MODS 8
|
||||||
|
|
||||||
enum sc_shortcut_mod {
|
enum sc_shortcut_mod {
|
||||||
@ -60,7 +70,7 @@ struct scrcpy_options {
|
|||||||
uint16_t max_size;
|
uint16_t max_size;
|
||||||
uint32_t bit_rate;
|
uint32_t bit_rate;
|
||||||
uint16_t max_fps;
|
uint16_t max_fps;
|
||||||
int8_t lock_video_orientation;
|
enum sc_lock_video_orientation lock_video_orientation;
|
||||||
uint8_t rotation;
|
uint8_t rotation;
|
||||||
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
||||||
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
||||||
@ -108,7 +118,7 @@ struct scrcpy_options {
|
|||||||
.max_size = 0, \
|
.max_size = 0, \
|
||||||
.bit_rate = DEFAULT_BIT_RATE, \
|
.bit_rate = DEFAULT_BIT_RATE, \
|
||||||
.max_fps = 0, \
|
.max_fps = 0, \
|
||||||
.lock_video_orientation = -1, \
|
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED, \
|
||||||
.rotation = 0, \
|
.rotation = 0, \
|
||||||
.window_x = SC_WINDOW_POSITION_UNDEFINED, \
|
.window_x = SC_WINDOW_POSITION_UNDEFINED, \
|
||||||
.window_y = SC_WINDOW_POSITION_UNDEFINED, \
|
.window_y = SC_WINDOW_POSITION_UNDEFINED, \
|
||||||
|
119
app/src/server.c
119
app/src/server.c
@ -58,7 +58,7 @@ get_server_path(void) {
|
|||||||
LOGE("Could not get executable path, "
|
LOGE("Could not get executable path, "
|
||||||
"using " SERVER_FILENAME " from current directory");
|
"using " SERVER_FILENAME " from current directory");
|
||||||
// not found, use current directory
|
// not found, use current directory
|
||||||
return SERVER_FILENAME;
|
return strdup(SERVER_FILENAME);
|
||||||
}
|
}
|
||||||
char *dir = dirname(executable_path);
|
char *dir = dirname(executable_path);
|
||||||
size_t dirlen = strlen(dir);
|
size_t dirlen = strlen(dir);
|
||||||
@ -70,7 +70,7 @@ get_server_path(void) {
|
|||||||
LOGE("Could not alloc server path string, "
|
LOGE("Could not alloc server path string, "
|
||||||
"using " SERVER_FILENAME " from current directory");
|
"using " SERVER_FILENAME " from current directory");
|
||||||
free(executable_path);
|
free(executable_path);
|
||||||
return SERVER_FILENAME;
|
return strdup(SERVER_FILENAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(server_path, dir, dirlen);
|
memcpy(server_path, dir, dirlen);
|
||||||
@ -128,9 +128,10 @@ disable_tunnel_forward(const char *serial, uint16_t local_port) {
|
|||||||
static bool
|
static bool
|
||||||
disable_tunnel(struct server *server) {
|
disable_tunnel(struct server *server) {
|
||||||
if (server->tunnel_forward) {
|
if (server->tunnel_forward) {
|
||||||
return disable_tunnel_forward(server->serial, server->local_port);
|
return disable_tunnel_forward(server->params.serial,
|
||||||
|
server->local_port);
|
||||||
}
|
}
|
||||||
return disable_tunnel_reverse(server->serial);
|
return disable_tunnel_reverse(server->params.serial);
|
||||||
}
|
}
|
||||||
|
|
||||||
static socket_t
|
static socket_t
|
||||||
@ -144,7 +145,7 @@ enable_tunnel_reverse_any_port(struct server *server,
|
|||||||
struct sc_port_range port_range) {
|
struct sc_port_range port_range) {
|
||||||
uint16_t port = port_range.first;
|
uint16_t port = port_range.first;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (!enable_tunnel_reverse(server->serial, port)) {
|
if (!enable_tunnel_reverse(server->params.serial, port)) {
|
||||||
// the command itself failed, it will fail on any port
|
// the command itself failed, it will fail on any port
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -163,7 +164,7 @@ enable_tunnel_reverse_any_port(struct server *server,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// failure, disable tunnel and try another port
|
// failure, disable tunnel and try another port
|
||||||
if (!disable_tunnel_reverse(server->serial)) {
|
if (!disable_tunnel_reverse(server->params.serial)) {
|
||||||
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
|
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,7 +192,7 @@ enable_tunnel_forward_any_port(struct server *server,
|
|||||||
server->tunnel_forward = true;
|
server->tunnel_forward = true;
|
||||||
uint16_t port = port_range.first;
|
uint16_t port = port_range.first;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (enable_tunnel_forward(server->serial, port)) {
|
if (enable_tunnel_forward(server->params.serial, port)) {
|
||||||
// success
|
// success
|
||||||
server->local_port = port;
|
server->local_port = port;
|
||||||
return true;
|
return true;
|
||||||
@ -306,7 +307,7 @@ execute_server(struct server *server, const struct server_params *params) {
|
|||||||
// Port: 5005
|
// Port: 5005
|
||||||
// Then click on "Debug"
|
// Then click on "Debug"
|
||||||
#endif
|
#endif
|
||||||
return adb_execute(server->serial, cmd, sizeof(cmd) / sizeof(cmd[0]));
|
return adb_execute(server->params.serial, cmd, ARRAY_LEN(cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
static socket_t
|
static socket_t
|
||||||
@ -352,21 +353,75 @@ close_socket(socket_t socket) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
server_params_destroy(struct server_params *params) {
|
||||||
|
// The server stores a copy of the params provided by the user
|
||||||
|
free((char *) params->crop);
|
||||||
|
free((char *) params->codec_options);
|
||||||
|
free((char *) params->encoder_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
server_params_copy(struct server_params *dst, const struct server_params *src) {
|
||||||
|
// params reference user-allocated memory, so we must copy them to handle
|
||||||
|
// them from a separate thread
|
||||||
|
|
||||||
|
*dst = *src;
|
||||||
|
|
||||||
|
dst->crop = NULL;
|
||||||
|
dst->codec_options = NULL;
|
||||||
|
dst->encoder_name = NULL;
|
||||||
|
|
||||||
|
if (src->crop) {
|
||||||
|
dst->crop = strdup(src->crop);
|
||||||
|
if (!dst->crop) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (src->codec_options) {
|
||||||
|
dst->codec_options = strdup(src->codec_options);
|
||||||
|
if (!dst->codec_options) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (src->encoder_name) {
|
||||||
|
dst->encoder_name = strdup(src->encoder_name);
|
||||||
|
if (!dst->encoder_name) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
error:
|
||||||
|
server_params_destroy(dst);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
server_init(struct server *server) {
|
server_init(struct server *server, const struct server_params *params) {
|
||||||
server->serial = NULL;
|
if (!server_params_copy(&server->params, params)) {
|
||||||
|
LOGE("Could not copy server params");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
server->process = PROCESS_NONE;
|
server->process = PROCESS_NONE;
|
||||||
atomic_flag_clear_explicit(&server->server_socket_closed,
|
atomic_flag_clear_explicit(&server->server_socket_closed,
|
||||||
memory_order_relaxed);
|
memory_order_relaxed);
|
||||||
|
|
||||||
bool ok = sc_mutex_init(&server->mutex);
|
bool ok = sc_mutex_init(&server->mutex);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
|
server_params_destroy(&server->params);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = sc_cond_init(&server->process_terminated_cond);
|
ok = sc_cond_init(&server->process_terminated_cond);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
sc_mutex_destroy(&server->mutex);
|
sc_mutex_destroy(&server->mutex);
|
||||||
|
server_params_destroy(&server->params);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,31 +461,41 @@ run_wait_server(void *data) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
static int
|
||||||
server_start(struct server *server, const char *serial,
|
run_server(void *data) {
|
||||||
const struct server_params *params) {
|
struct server *server = data;
|
||||||
if (serial) {
|
|
||||||
server->serial = strdup(serial);
|
|
||||||
if (!server->serial) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!push_server(serial)) {
|
const struct server_params *params = &server->params;
|
||||||
goto error1;
|
const struct server_callbacks *cbs = &server->cbs;
|
||||||
|
void *userdata = server->userdata;
|
||||||
|
|
||||||
|
if (!push_server(params->serial)) {
|
||||||
|
cbs->on_connection_failed(server);
|
||||||
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!enable_tunnel_any_port(server, params->port_range,
|
if (!enable_tunnel_any_port(server, params->port_range,
|
||||||
params->force_adb_forward)) {
|
params->force_adb_forward)) {
|
||||||
goto error1;
|
cbs->on_connection_failed(server);
|
||||||
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
// server will connect to our server socket
|
// server will connect to our server socket
|
||||||
server->process = execute_server(server, params);
|
server->process = execute_server(server, params);
|
||||||
if (server->process == PROCESS_NONE) {
|
if (server->process == PROCESS_NONE) {
|
||||||
goto error2;
|
cbs->on_connection_failed(server);
|
||||||
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
process_wait(server->process, false); // ignore exit code
|
||||||
|
|
||||||
|
end:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
server_start(struct server *server) {
|
||||||
|
|
||||||
// If the server process dies before connecting to the server socket, then
|
// If the server process dies before connecting to the server socket, then
|
||||||
// the client will be stuck forever on accept(). To avoid the problem, we
|
// the client will be stuck forever on accept(). To avoid the problem, we
|
||||||
// must be able to wake up the accept() call when the server dies. To keep
|
// must be able to wake up the accept() call when the server dies. To keep
|
||||||
@ -442,14 +507,14 @@ server_start(struct server *server, const char *serial,
|
|||||||
if (!ok) {
|
if (!ok) {
|
||||||
process_terminate(server->process);
|
process_terminate(server->process);
|
||||||
process_wait(server->process, true); // ignore exit code
|
process_wait(server->process, true); // ignore exit code
|
||||||
goto error2;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
server->tunnel_enabled = true;
|
server->tunnel_enabled = true;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
error2:
|
error:
|
||||||
if (!server->tunnel_forward) {
|
if (!server->tunnel_forward) {
|
||||||
bool was_closed =
|
bool was_closed =
|
||||||
atomic_flag_test_and_set(&server->server_socket_closed);
|
atomic_flag_test_and_set(&server->server_socket_closed);
|
||||||
@ -459,8 +524,7 @@ error2:
|
|||||||
close_socket(server->server_socket);
|
close_socket(server->server_socket);
|
||||||
}
|
}
|
||||||
disable_tunnel(server);
|
disable_tunnel(server);
|
||||||
error1:
|
|
||||||
free(server->serial);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -558,4 +622,5 @@ server_destroy(struct server *server) {
|
|||||||
free(server->serial);
|
free(server->serial);
|
||||||
sc_cond_destroy(&server->process_terminated_cond);
|
sc_cond_destroy(&server->process_terminated_cond);
|
||||||
sc_mutex_destroy(&server->mutex);
|
sc_mutex_destroy(&server->mutex);
|
||||||
|
server_params_destroy(&server->params);
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,25 @@
|
|||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
#include "util/thread.h"
|
#include "util/thread.h"
|
||||||
|
|
||||||
|
struct server_params {
|
||||||
|
enum sc_log_level log_level;
|
||||||
|
const char *serial;
|
||||||
|
const char *crop;
|
||||||
|
const char *codec_options;
|
||||||
|
const char *encoder_name;
|
||||||
|
struct sc_port_range port_range;
|
||||||
|
uint16_t max_size;
|
||||||
|
uint32_t bit_rate;
|
||||||
|
uint16_t max_fps;
|
||||||
|
int8_t lock_video_orientation;
|
||||||
|
bool control;
|
||||||
|
uint32_t display_id;
|
||||||
|
bool show_touches;
|
||||||
|
bool stay_awake;
|
||||||
|
bool force_adb_forward;
|
||||||
|
bool power_off_on_close;
|
||||||
|
};
|
||||||
|
|
||||||
struct server {
|
struct server {
|
||||||
char *serial;
|
char *serial;
|
||||||
process_t process;
|
process_t process;
|
||||||
@ -29,34 +48,29 @@ struct server {
|
|||||||
uint16_t local_port; // selected from port_range
|
uint16_t local_port; // selected from port_range
|
||||||
bool tunnel_enabled;
|
bool tunnel_enabled;
|
||||||
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
|
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
|
||||||
|
|
||||||
|
// The internal allocated strings are copies owned by the server
|
||||||
|
struct server_params params;
|
||||||
|
|
||||||
|
const struct server_callbacks *cbs;
|
||||||
|
void *userdata;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct server_params {
|
struct server_callbacks {
|
||||||
enum sc_log_level log_level;
|
void (*on_connection_failed)(struct server *server);
|
||||||
const char *crop;
|
void (*on_connected)(struct server *server, const char *name,
|
||||||
const char *codec_options;
|
struct size size, void *userdata);
|
||||||
const char *encoder_name;
|
void (*on_disconnected)(struct server *server, void *userdata);
|
||||||
struct sc_port_range port_range;
|
|
||||||
uint16_t max_size;
|
|
||||||
uint32_t bit_rate;
|
|
||||||
uint16_t max_fps;
|
|
||||||
int8_t lock_video_orientation;
|
|
||||||
bool control;
|
|
||||||
uint32_t display_id;
|
|
||||||
bool show_touches;
|
|
||||||
bool stay_awake;
|
|
||||||
bool force_adb_forward;
|
|
||||||
bool power_off_on_close;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// init default values
|
// init server fields
|
||||||
bool
|
bool
|
||||||
server_init(struct server *server);
|
server_init(struct server *server, const struct server_params *params);
|
||||||
|
|
||||||
// push, enable tunnel et start the server
|
// push, enable tunnel et start the server
|
||||||
bool
|
bool
|
||||||
server_start(struct server *server, const char *serial,
|
server_start(struct server *server, const struct server_callbacks *cbs,
|
||||||
const struct server_params *params);
|
void *userdata);
|
||||||
|
|
||||||
// block until the communication with the server is established
|
// block until the communication with the server is established
|
||||||
bool
|
bool
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
/** Downcast frame_sink to sc_v4l2_sink */
|
/** Downcast frame_sink to sc_v4l2_sink */
|
||||||
#define DOWNCAST(SINK) container_of(SINK, struct sc_v4l2_sink, frame_sink)
|
#define DOWNCAST(SINK) container_of(SINK, struct sc_v4l2_sink, frame_sink)
|
||||||
|
|
||||||
|
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
|
||||||
|
|
||||||
static const AVOutputFormat *
|
static const AVOutputFormat *
|
||||||
find_muxer(const char *name) {
|
find_muxer(const char *name) {
|
||||||
#ifdef SCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API
|
#ifdef SCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API
|
||||||
@ -24,7 +26,60 @@ find_muxer(const char *name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
encode_and_send_frame(struct sc_v4l2_sink *vs, const AVFrame *frame) {
|
write_header(struct sc_v4l2_sink *vs, const AVPacket *packet) {
|
||||||
|
AVStream *ostream = vs->format_ctx->streams[0];
|
||||||
|
|
||||||
|
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t));
|
||||||
|
if (!extradata) {
|
||||||
|
LOGC("Could not allocate extradata");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy the first packet to the extra data
|
||||||
|
memcpy(extradata, packet->data, packet->size);
|
||||||
|
|
||||||
|
ostream->codecpar->extradata = extradata;
|
||||||
|
ostream->codecpar->extradata_size = packet->size;
|
||||||
|
|
||||||
|
int ret = avformat_write_header(vs->format_ctx, NULL);
|
||||||
|
if (ret < 0) {
|
||||||
|
LOGE("Failed to write header to %s", vs->device_name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
rescale_packet(struct sc_v4l2_sink *vs, AVPacket *packet) {
|
||||||
|
AVStream *ostream = vs->format_ctx->streams[0];
|
||||||
|
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
write_packet(struct sc_v4l2_sink *vs, AVPacket *packet) {
|
||||||
|
if (!vs->header_written) {
|
||||||
|
bool ok = write_header(vs, packet);
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
vs->header_written = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
rescale_packet(vs, packet);
|
||||||
|
|
||||||
|
bool ok = av_write_frame(vs->format_ctx, packet) >= 0;
|
||||||
|
|
||||||
|
// Failing to write the last frame is not very serious, no future frame may
|
||||||
|
// depend on it, so the resulting file will still be valid
|
||||||
|
(void) ok;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
encode_and_write_frame(struct sc_v4l2_sink *vs, const AVFrame *frame) {
|
||||||
int ret = avcodec_send_frame(vs->encoder_ctx, frame);
|
int ret = avcodec_send_frame(vs->encoder_ctx, frame);
|
||||||
if (ret < 0 && ret != AVERROR(EAGAIN)) {
|
if (ret < 0 && ret != AVERROR(EAGAIN)) {
|
||||||
LOGE("Could not send v4l2 video frame: %d", ret);
|
LOGE("Could not send v4l2 video frame: %d", ret);
|
||||||
@ -35,8 +90,13 @@ encode_and_send_frame(struct sc_v4l2_sink *vs, const AVFrame *frame) {
|
|||||||
ret = avcodec_receive_packet(vs->encoder_ctx, packet);
|
ret = avcodec_receive_packet(vs->encoder_ctx, packet);
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
// A packet was received
|
// A packet was received
|
||||||
av_write_frame(vs->format_ctx, packet);
|
|
||||||
|
bool ok = write_packet(vs, packet);
|
||||||
av_packet_unref(packet);
|
av_packet_unref(packet);
|
||||||
|
if (!ok) {
|
||||||
|
LOGW("Could not send packet to v4l2 sink");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (ret != AVERROR(EAGAIN)) {
|
} else if (ret != AVERROR(EAGAIN)) {
|
||||||
LOGE("Could not receive v4l2 video packet: %d", ret);
|
LOGE("Could not receive v4l2 video packet: %d", ret);
|
||||||
return false;
|
return false;
|
||||||
@ -64,7 +124,8 @@ run_v4l2_sink(void *data) {
|
|||||||
sc_mutex_unlock(&vs->mutex);
|
sc_mutex_unlock(&vs->mutex);
|
||||||
|
|
||||||
video_buffer_consume(&vs->vb, vs->frame);
|
video_buffer_consume(&vs->vb, vs->frame);
|
||||||
bool ok = encode_and_send_frame(vs, vs->frame);
|
bool ok = encode_and_write_frame(vs, vs->frame);
|
||||||
|
av_frame_unref(vs->frame);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not send frame to v4l2 sink");
|
LOGE("Could not send frame to v4l2 sink");
|
||||||
break;
|
break;
|
||||||
@ -119,6 +180,12 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
|
|||||||
// still expects a pointer-to-non-const (it has not be updated accordingly)
|
// still expects a pointer-to-non-const (it has not be updated accordingly)
|
||||||
// <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd>
|
// <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd>
|
||||||
vs->format_ctx->oformat = (AVOutputFormat *) format;
|
vs->format_ctx->oformat = (AVOutputFormat *) format;
|
||||||
|
vs->format_ctx->url = strdup(vs->device_name);
|
||||||
|
if (!vs->format_ctx->url) {
|
||||||
|
LOGE("Could not strdup v4l2 device name");
|
||||||
|
goto error_avformat_free_context;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
AVStream *ostream = avformat_new_stream(vs->format_ctx, encoder);
|
AVStream *ostream = avformat_new_stream(vs->format_ctx, encoder);
|
||||||
if (!ostream) {
|
if (!ostream) {
|
||||||
@ -170,6 +237,9 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
|
|||||||
goto error_av_frame_free;
|
goto error_av_frame_free;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vs->header_written = false;
|
||||||
|
vs->stopped = false;
|
||||||
|
|
||||||
LOGI("v4l2 sink started to device: %s", vs->device_name);
|
LOGI("v4l2 sink started to device: %s", vs->device_name);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -196,6 +266,13 @@ error_video_buffer_destroy:
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
sc_v4l2_sink_close(struct sc_v4l2_sink *vs) {
|
sc_v4l2_sink_close(struct sc_v4l2_sink *vs) {
|
||||||
|
sc_mutex_lock(&vs->mutex);
|
||||||
|
vs->stopped = true;
|
||||||
|
sc_cond_signal(&vs->cond);
|
||||||
|
sc_mutex_unlock(&vs->mutex);
|
||||||
|
|
||||||
|
sc_thread_join(&vs->thread, NULL);
|
||||||
|
|
||||||
av_frame_free(&vs->frame);
|
av_frame_free(&vs->frame);
|
||||||
avcodec_close(vs->encoder_ctx);
|
avcodec_close(vs->encoder_ctx);
|
||||||
avcodec_free_context(&vs->encoder_ctx);
|
avcodec_free_context(&vs->encoder_ctx);
|
||||||
|
@ -23,6 +23,7 @@ struct sc_v4l2_sink {
|
|||||||
sc_mutex mutex;
|
sc_mutex mutex;
|
||||||
sc_cond cond;
|
sc_cond cond;
|
||||||
bool stopped;
|
bool stopped;
|
||||||
|
bool header_written;
|
||||||
|
|
||||||
AVFrame *frame;
|
AVFrame *frame;
|
||||||
AVPacket packet;
|
AVPacket packet;
|
||||||
|
@ -177,9 +177,9 @@ static void test_serialize_expand_notification_panel(void) {
|
|||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_serialize_collapse_notification_panel(void) {
|
static void test_serialize_expand_settings_panel(void) {
|
||||||
struct control_msg msg = {
|
struct control_msg msg = {
|
||||||
.type = CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL,
|
.type = CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
@ -187,7 +187,22 @@ static void test_serialize_collapse_notification_panel(void) {
|
|||||||
assert(size == 1);
|
assert(size == 1);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL,
|
CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
|
||||||
|
};
|
||||||
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_serialize_collapse_panels(void) {
|
||||||
|
struct control_msg msg = {
|
||||||
|
.type = CONTROL_MSG_TYPE_COLLAPSE_PANELS,
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
|
size_t size = control_msg_serialize(&msg, buf);
|
||||||
|
assert(size == 1);
|
||||||
|
|
||||||
|
const unsigned char expected[] = {
|
||||||
|
CONTROL_MSG_TYPE_COLLAPSE_PANELS,
|
||||||
};
|
};
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
}
|
}
|
||||||
@ -274,7 +289,8 @@ int main(int argc, char *argv[]) {
|
|||||||
test_serialize_inject_scroll_event();
|
test_serialize_inject_scroll_event();
|
||||||
test_serialize_back_or_screen_on();
|
test_serialize_back_or_screen_on();
|
||||||
test_serialize_expand_notification_panel();
|
test_serialize_expand_notification_panel();
|
||||||
test_serialize_collapse_notification_panel();
|
test_serialize_expand_settings_panel();
|
||||||
|
test_serialize_collapse_panels();
|
||||||
test_serialize_get_clipboard();
|
test_serialize_get_clipboard();
|
||||||
test_serialize_set_clipboard();
|
test_serialize_set_clipboard();
|
||||||
test_serialize_set_screen_power_mode();
|
test_serialize_set_screen_power_mode();
|
||||||
|
@ -3,6 +3,10 @@ package com.genymobile.scrcpy;
|
|||||||
import com.genymobile.scrcpy.wrappers.ContentProvider;
|
import com.genymobile.scrcpy.wrappers.ContentProvider;
|
||||||
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.util.Base64;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@ -15,25 +19,123 @@ public final class CleanUp {
|
|||||||
|
|
||||||
public static final String SERVER_PATH = "/data/local/tmp/scrcpy-server.jar";
|
public static final String SERVER_PATH = "/data/local/tmp/scrcpy-server.jar";
|
||||||
|
|
||||||
|
// A simple struct to be passed from the main process to the cleanup process
|
||||||
|
public static class Config implements Parcelable {
|
||||||
|
|
||||||
|
public static final Creator<Config> CREATOR = new Creator<Config>() {
|
||||||
|
@Override
|
||||||
|
public Config createFromParcel(Parcel in) {
|
||||||
|
return new Config(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Config[] newArray(int size) {
|
||||||
|
return new Config[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final int FLAG_DISABLE_SHOW_TOUCHES = 1;
|
||||||
|
private static final int FLAG_RESTORE_NORMAL_POWER_MODE = 2;
|
||||||
|
private static final int FLAG_POWER_OFF_SCREEN = 4;
|
||||||
|
|
||||||
|
private int displayId;
|
||||||
|
|
||||||
|
// Restore the value (between 0 and 7), -1 to not restore
|
||||||
|
// <https://developer.android.com/reference/android/provider/Settings.Global#STAY_ON_WHILE_PLUGGED_IN>
|
||||||
|
private int restoreStayOn = -1;
|
||||||
|
|
||||||
|
private boolean disableShowTouches;
|
||||||
|
private boolean restoreNormalPowerMode;
|
||||||
|
private boolean powerOffScreen;
|
||||||
|
|
||||||
|
public Config() {
|
||||||
|
// Default constructor, the fields are initialized by CleanUp.configure()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Config(Parcel in) {
|
||||||
|
displayId = in.readInt();
|
||||||
|
restoreStayOn = in.readInt();
|
||||||
|
byte options = in.readByte();
|
||||||
|
disableShowTouches = (options & FLAG_DISABLE_SHOW_TOUCHES) != 0;
|
||||||
|
restoreNormalPowerMode = (options & FLAG_RESTORE_NORMAL_POWER_MODE) != 0;
|
||||||
|
powerOffScreen = (options & FLAG_POWER_OFF_SCREEN) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeInt(displayId);
|
||||||
|
dest.writeInt(restoreStayOn);
|
||||||
|
byte options = 0;
|
||||||
|
if (disableShowTouches) {
|
||||||
|
options |= FLAG_DISABLE_SHOW_TOUCHES;
|
||||||
|
}
|
||||||
|
if (restoreNormalPowerMode) {
|
||||||
|
options |= FLAG_RESTORE_NORMAL_POWER_MODE;
|
||||||
|
}
|
||||||
|
if (powerOffScreen) {
|
||||||
|
options |= FLAG_POWER_OFF_SCREEN;
|
||||||
|
}
|
||||||
|
dest.writeByte(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasWork() {
|
||||||
|
return disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode || powerOffScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] serialize() {
|
||||||
|
Parcel parcel = Parcel.obtain();
|
||||||
|
writeToParcel(parcel, 0);
|
||||||
|
byte[] bytes = parcel.marshall();
|
||||||
|
parcel.recycle();
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Config deserialize(byte[] bytes) {
|
||||||
|
Parcel parcel = Parcel.obtain();
|
||||||
|
parcel.unmarshall(bytes, 0, bytes.length);
|
||||||
|
parcel.setDataPosition(0);
|
||||||
|
return CREATOR.createFromParcel(parcel);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Config fromBase64(String base64) {
|
||||||
|
byte[] bytes = Base64.decode(base64, Base64.NO_WRAP);
|
||||||
|
return deserialize(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
String toBase64() {
|
||||||
|
byte[] bytes = serialize();
|
||||||
|
return Base64.encodeToString(bytes, Base64.NO_WRAP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private CleanUp() {
|
private CleanUp() {
|
||||||
// not instantiable
|
// not instantiable
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void configure(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode, boolean powerOffScreen, int displayId)
|
public static void configure(int displayId, int restoreStayOn, boolean disableShowTouches, boolean restoreNormalPowerMode, boolean powerOffScreen)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
boolean needProcess = disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode || powerOffScreen;
|
Config config = new Config();
|
||||||
if (needProcess) {
|
config.displayId = displayId;
|
||||||
startProcess(disableShowTouches, restoreStayOn, restoreNormalPowerMode, powerOffScreen, displayId);
|
config.disableShowTouches = disableShowTouches;
|
||||||
|
config.restoreStayOn = restoreStayOn;
|
||||||
|
config.restoreNormalPowerMode = restoreNormalPowerMode;
|
||||||
|
config.powerOffScreen = powerOffScreen;
|
||||||
|
|
||||||
|
if (config.hasWork()) {
|
||||||
|
startProcess(config);
|
||||||
} else {
|
} else {
|
||||||
// There is no additional clean up to do when scrcpy dies
|
// There is no additional clean up to do when scrcpy dies
|
||||||
unlinkSelf();
|
unlinkSelf();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void startProcess(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode, boolean powerOffScreen,
|
private static void startProcess(Config config) throws IOException {
|
||||||
int displayId) throws IOException {
|
String[] cmd = {"app_process", "/", CleanUp.class.getName(), config.toBase64()};
|
||||||
String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(disableShowTouches), String.valueOf(
|
|
||||||
restoreStayOn), String.valueOf(restoreNormalPowerMode), String.valueOf(powerOffScreen), String.valueOf(displayId)};
|
|
||||||
|
|
||||||
ProcessBuilder builder = new ProcessBuilder(cmd);
|
ProcessBuilder builder = new ProcessBuilder(cmd);
|
||||||
builder.environment().put("CLASSPATH", SERVER_PATH);
|
builder.environment().put("CLASSPATH", SERVER_PATH);
|
||||||
@ -60,31 +162,27 @@ public final class CleanUp {
|
|||||||
|
|
||||||
Ln.i("Cleaning up");
|
Ln.i("Cleaning up");
|
||||||
|
|
||||||
boolean disableShowTouches = Boolean.parseBoolean(args[0]);
|
Config config = Config.fromBase64(args[0]);
|
||||||
int restoreStayOn = Integer.parseInt(args[1]);
|
|
||||||
boolean restoreNormalPowerMode = Boolean.parseBoolean(args[2]);
|
|
||||||
boolean powerOffScreen = Boolean.parseBoolean(args[3]);
|
|
||||||
int displayId = Integer.parseInt(args[4]);
|
|
||||||
|
|
||||||
if (disableShowTouches || restoreStayOn != -1) {
|
if (config.disableShowTouches || config.restoreStayOn != -1) {
|
||||||
ServiceManager serviceManager = new ServiceManager();
|
ServiceManager serviceManager = new ServiceManager();
|
||||||
try (ContentProvider settings = serviceManager.getActivityManager().createSettingsProvider()) {
|
try (ContentProvider settings = serviceManager.getActivityManager().createSettingsProvider()) {
|
||||||
if (disableShowTouches) {
|
if (config.disableShowTouches) {
|
||||||
Ln.i("Disabling \"show touches\"");
|
Ln.i("Disabling \"show touches\"");
|
||||||
settings.putValue(ContentProvider.TABLE_SYSTEM, "show_touches", "0");
|
settings.putValue(ContentProvider.TABLE_SYSTEM, "show_touches", "0");
|
||||||
}
|
}
|
||||||
if (restoreStayOn != -1) {
|
if (config.restoreStayOn != -1) {
|
||||||
Ln.i("Restoring \"stay awake\"");
|
Ln.i("Restoring \"stay awake\"");
|
||||||
settings.putValue(ContentProvider.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(restoreStayOn));
|
settings.putValue(ContentProvider.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Device.isScreenOn()) {
|
if (Device.isScreenOn()) {
|
||||||
if (powerOffScreen) {
|
if (config.powerOffScreen) {
|
||||||
Ln.i("Power off screen");
|
Ln.i("Power off screen");
|
||||||
Device.powerOffScreen(displayId);
|
Device.powerOffScreen(config.displayId);
|
||||||
} else if (restoreNormalPowerMode) {
|
} else if (config.restoreNormalPowerMode) {
|
||||||
Ln.i("Restoring normal power mode");
|
Ln.i("Restoring normal power mode");
|
||||||
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
|
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
|
||||||
}
|
}
|
||||||
|
@ -11,11 +11,12 @@ public final class ControlMessage {
|
|||||||
public static final int TYPE_INJECT_SCROLL_EVENT = 3;
|
public static final int TYPE_INJECT_SCROLL_EVENT = 3;
|
||||||
public static final int TYPE_BACK_OR_SCREEN_ON = 4;
|
public static final int TYPE_BACK_OR_SCREEN_ON = 4;
|
||||||
public static final int TYPE_EXPAND_NOTIFICATION_PANEL = 5;
|
public static final int TYPE_EXPAND_NOTIFICATION_PANEL = 5;
|
||||||
public static final int TYPE_COLLAPSE_NOTIFICATION_PANEL = 6;
|
public static final int TYPE_EXPAND_SETTINGS_PANEL = 6;
|
||||||
public static final int TYPE_GET_CLIPBOARD = 7;
|
public static final int TYPE_COLLAPSE_PANELS = 7;
|
||||||
public static final int TYPE_SET_CLIPBOARD = 8;
|
public static final int TYPE_GET_CLIPBOARD = 8;
|
||||||
public static final int TYPE_SET_SCREEN_POWER_MODE = 9;
|
public static final int TYPE_SET_CLIPBOARD = 9;
|
||||||
public static final int TYPE_ROTATE_DEVICE = 10;
|
public static final int TYPE_SET_SCREEN_POWER_MODE = 10;
|
||||||
|
public static final int TYPE_ROTATE_DEVICE = 11;
|
||||||
|
|
||||||
private int type;
|
private int type;
|
||||||
private String text;
|
private String text;
|
||||||
|
@ -77,7 +77,8 @@ public class ControlMessageReader {
|
|||||||
msg = parseSetScreenPowerMode();
|
msg = parseSetScreenPowerMode();
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL:
|
||||||
|
case ControlMessage.TYPE_COLLAPSE_PANELS:
|
||||||
case ControlMessage.TYPE_GET_CLIPBOARD:
|
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||||
case ControlMessage.TYPE_ROTATE_DEVICE:
|
case ControlMessage.TYPE_ROTATE_DEVICE:
|
||||||
msg = ControlMessage.createEmpty(type);
|
msg = ControlMessage.createEmpty(type);
|
||||||
|
@ -55,7 +55,7 @@ public class Controller {
|
|||||||
public void control() throws IOException {
|
public void control() throws IOException {
|
||||||
// on start, power on the device
|
// on start, power on the device
|
||||||
if (!Device.isScreenOn()) {
|
if (!Device.isScreenOn()) {
|
||||||
device.injectKeycode(KeyEvent.KEYCODE_POWER);
|
device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER);
|
||||||
|
|
||||||
// dirty hack
|
// dirty hack
|
||||||
// After POWER is injected, the device is powered on asynchronously.
|
// After POWER is injected, the device is powered on asynchronously.
|
||||||
@ -107,7 +107,10 @@ public class Controller {
|
|||||||
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
Device.expandNotificationPanel();
|
Device.expandNotificationPanel();
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL:
|
||||||
|
Device.expandSettingsPanel();
|
||||||
|
break;
|
||||||
|
case ControlMessage.TYPE_COLLAPSE_PANELS:
|
||||||
Device.collapsePanels();
|
Device.collapsePanels();
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_GET_CLIPBOARD:
|
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||||
@ -270,7 +273,7 @@ public class Controller {
|
|||||||
if (keepPowerModeOff) {
|
if (keepPowerModeOff) {
|
||||||
schedulePowerModeOff();
|
schedulePowerModeOff();
|
||||||
}
|
}
|
||||||
return device.injectKeycode(KeyEvent.KEYCODE_POWER);
|
return device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean setClipboard(String text, boolean paste) {
|
private boolean setClipboard(String text, boolean paste) {
|
||||||
@ -281,7 +284,7 @@ public class Controller {
|
|||||||
|
|
||||||
// On Android >= 7, also press the PASTE key if requested
|
// On Android >= 7, also press the PASTE key if requested
|
||||||
if (paste && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) {
|
if (paste && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) {
|
||||||
device.injectKeycode(KeyEvent.KEYCODE_PASTE);
|
device.pressReleaseKeycode(KeyEvent.KEYCODE_PASTE);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ok;
|
return ok;
|
||||||
|
@ -25,6 +25,9 @@ public final class Device {
|
|||||||
public static final int POWER_MODE_OFF = SurfaceControl.POWER_MODE_OFF;
|
public static final int POWER_MODE_OFF = SurfaceControl.POWER_MODE_OFF;
|
||||||
public static final int POWER_MODE_NORMAL = SurfaceControl.POWER_MODE_NORMAL;
|
public static final int POWER_MODE_NORMAL = SurfaceControl.POWER_MODE_NORMAL;
|
||||||
|
|
||||||
|
public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1;
|
||||||
|
public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2;
|
||||||
|
|
||||||
private static final ServiceManager SERVICE_MANAGER = new ServiceManager();
|
private static final ServiceManager SERVICE_MANAGER = new ServiceManager();
|
||||||
|
|
||||||
public interface RotationListener {
|
public interface RotationListener {
|
||||||
@ -161,54 +164,39 @@ public final class Device {
|
|||||||
return supportsInputEvents;
|
return supportsInputEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean injectEvent(InputEvent inputEvent, int mode, int displayId) {
|
public static boolean injectEvent(InputEvent inputEvent, int displayId) {
|
||||||
if (!supportsInputEvents(displayId)) {
|
if (!supportsInputEvents(displayId)) {
|
||||||
return false;
|
throw new AssertionError("Could not inject input event if !supportsInputEvents()");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (displayId != 0 && !InputManager.setDisplayId(inputEvent, displayId)) {
|
if (displayId != 0 && !InputManager.setDisplayId(inputEvent, displayId)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, mode);
|
return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
|
||||||
}
|
|
||||||
|
|
||||||
public boolean injectEvent(InputEvent inputEvent, int mode) {
|
|
||||||
if (!supportsInputEvents()) {
|
|
||||||
throw new AssertionError("Could not inject input event if !supportsInputEvents()");
|
|
||||||
}
|
|
||||||
|
|
||||||
return injectEvent(inputEvent, mode, displayId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean injectEventOnDisplay(InputEvent event, int displayId) {
|
|
||||||
return injectEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, displayId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean injectEvent(InputEvent event) {
|
public boolean injectEvent(InputEvent event) {
|
||||||
return injectEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
|
return injectEvent(event, displayId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int displayId) {
|
public static boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int displayId) {
|
||||||
long now = SystemClock.uptimeMillis();
|
long now = SystemClock.uptimeMillis();
|
||||||
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
|
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
|
||||||
InputDevice.SOURCE_KEYBOARD);
|
InputDevice.SOURCE_KEYBOARD);
|
||||||
return injectEventOnDisplay(event, displayId);
|
return injectEvent(event, displayId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) {
|
public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) {
|
||||||
long now = SystemClock.uptimeMillis();
|
return injectKeyEvent(action, keyCode, repeat, metaState, displayId);
|
||||||
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
|
|
||||||
InputDevice.SOURCE_KEYBOARD);
|
|
||||||
return injectEvent(event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean injectKeycode(int keyCode, int displayId) {
|
public static boolean pressReleaseKeycode(int keyCode, int displayId) {
|
||||||
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0, displayId) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0, displayId);
|
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0, displayId) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0, displayId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean injectKeycode(int keyCode) {
|
public boolean pressReleaseKeycode(int keyCode) {
|
||||||
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0);
|
return pressReleaseKeycode(keyCode, displayId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isScreenOn() {
|
public static boolean isScreenOn() {
|
||||||
@ -227,6 +215,10 @@ public final class Device {
|
|||||||
SERVICE_MANAGER.getStatusBarManager().expandNotificationsPanel();
|
SERVICE_MANAGER.getStatusBarManager().expandNotificationsPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void expandSettingsPanel() {
|
||||||
|
SERVICE_MANAGER.getStatusBarManager().expandSettingsPanel();
|
||||||
|
}
|
||||||
|
|
||||||
public static void collapsePanels() {
|
public static void collapsePanels() {
|
||||||
SERVICE_MANAGER.getStatusBarManager().collapsePanels();
|
SERVICE_MANAGER.getStatusBarManager().collapsePanels();
|
||||||
}
|
}
|
||||||
@ -280,7 +272,7 @@ public final class Device {
|
|||||||
if (!isScreenOn()) {
|
if (!isScreenOn()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return injectKeycode(KeyEvent.KEYCODE_POWER, displayId);
|
return pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -82,6 +82,12 @@ public final class ScreenInfo {
|
|||||||
|
|
||||||
public static ScreenInfo computeScreenInfo(DisplayInfo displayInfo, Rect crop, int maxSize, int lockedVideoOrientation) {
|
public static ScreenInfo computeScreenInfo(DisplayInfo displayInfo, Rect crop, int maxSize, int lockedVideoOrientation) {
|
||||||
int rotation = displayInfo.getRotation();
|
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();
|
Size deviceSize = displayInfo.getSize();
|
||||||
Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight());
|
Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight());
|
||||||
if (crop != null) {
|
if (crop != null) {
|
||||||
|
@ -50,7 +50,7 @@ public final class Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CleanUp.configure(mustDisableShowTouchesOnCleanUp, restoreStayOn, true, options.getPowerOffScreenOnClose(), options.getDisplayId());
|
CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, true, options.getPowerOffScreenOnClose());
|
||||||
|
|
||||||
boolean tunnelForward = options.isTunnelForward();
|
boolean tunnelForward = options.isTunnelForward();
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ public class ActivityManager {
|
|||||||
|
|
||||||
private final IInterface manager;
|
private final IInterface manager;
|
||||||
private Method getContentProviderExternalMethod;
|
private Method getContentProviderExternalMethod;
|
||||||
private boolean getContentProviderExternalMethodLegacy;
|
private boolean getContentProviderExternalMethodNewVersion = true;
|
||||||
private Method removeContentProviderExternalMethod;
|
private Method removeContentProviderExternalMethod;
|
||||||
|
|
||||||
public ActivityManager(IInterface manager) {
|
public ActivityManager(IInterface manager) {
|
||||||
@ -29,7 +29,7 @@ public class ActivityManager {
|
|||||||
} catch (NoSuchMethodException e) {
|
} catch (NoSuchMethodException e) {
|
||||||
// old version
|
// old version
|
||||||
getContentProviderExternalMethod = manager.getClass().getMethod("getContentProviderExternal", String.class, int.class, IBinder.class);
|
getContentProviderExternalMethod = manager.getClass().getMethod("getContentProviderExternal", String.class, int.class, IBinder.class);
|
||||||
getContentProviderExternalMethodLegacy = true;
|
getContentProviderExternalMethodNewVersion = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return getContentProviderExternalMethod;
|
return getContentProviderExternalMethod;
|
||||||
@ -46,7 +46,7 @@ public class ActivityManager {
|
|||||||
try {
|
try {
|
||||||
Method method = getGetContentProviderExternalMethod();
|
Method method = getGetContentProviderExternalMethod();
|
||||||
Object[] args;
|
Object[] args;
|
||||||
if (!getContentProviderExternalMethodLegacy) {
|
if (getContentProviderExternalMethodNewVersion) {
|
||||||
// new version
|
// new version
|
||||||
args = new Object[]{name, ServiceManager.USER_ID, token, null};
|
args = new Object[]{name, ServiceManager.USER_ID, token, null};
|
||||||
} else {
|
} else {
|
||||||
|
@ -11,6 +11,8 @@ public class StatusBarManager {
|
|||||||
|
|
||||||
private final IInterface manager;
|
private final IInterface manager;
|
||||||
private Method expandNotificationsPanelMethod;
|
private Method expandNotificationsPanelMethod;
|
||||||
|
private Method expandSettingsPanelMethod;
|
||||||
|
private boolean expandSettingsPanelMethodNewVersion = true;
|
||||||
private Method collapsePanelsMethod;
|
private Method collapsePanelsMethod;
|
||||||
|
|
||||||
public StatusBarManager(IInterface manager) {
|
public StatusBarManager(IInterface manager) {
|
||||||
@ -24,6 +26,20 @@ public class StatusBarManager {
|
|||||||
return expandNotificationsPanelMethod;
|
return expandNotificationsPanelMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Method getExpandSettingsPanel() throws NoSuchMethodException {
|
||||||
|
if (expandSettingsPanelMethod == null) {
|
||||||
|
try {
|
||||||
|
// Since Android 7: https://android.googlesource.com/platform/frameworks/base.git/+/a9927325eda025504d59bb6594fee8e240d95b01%5E%21/
|
||||||
|
expandSettingsPanelMethod = manager.getClass().getMethod("expandSettingsPanel", String.class);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
// old version
|
||||||
|
expandSettingsPanelMethod = manager.getClass().getMethod("expandSettingsPanel");
|
||||||
|
expandSettingsPanelMethodNewVersion = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expandSettingsPanelMethod;
|
||||||
|
}
|
||||||
|
|
||||||
private Method getCollapsePanelsMethod() throws NoSuchMethodException {
|
private Method getCollapsePanelsMethod() throws NoSuchMethodException {
|
||||||
if (collapsePanelsMethod == null) {
|
if (collapsePanelsMethod == null) {
|
||||||
collapsePanelsMethod = manager.getClass().getMethod("collapsePanels");
|
collapsePanelsMethod = manager.getClass().getMethod("collapsePanels");
|
||||||
@ -40,6 +56,21 @@ public class StatusBarManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void expandSettingsPanel() {
|
||||||
|
try {
|
||||||
|
Method method = getExpandSettingsPanel();
|
||||||
|
if (expandSettingsPanelMethodNewVersion) {
|
||||||
|
// new version
|
||||||
|
method.invoke(manager, (Object) null);
|
||||||
|
} else {
|
||||||
|
// old version
|
||||||
|
method.invoke(manager);
|
||||||
|
}
|
||||||
|
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||||
|
Ln.e("Could not invoke method", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void collapsePanels() {
|
public void collapsePanels() {
|
||||||
try {
|
try {
|
||||||
Method method = getCollapsePanelsMethod();
|
Method method = getCollapsePanelsMethod();
|
||||||
|
@ -162,7 +162,7 @@ public class ControlMessageReaderTest {
|
|||||||
ControlMessage event = reader.next();
|
ControlMessage event = reader.next();
|
||||||
|
|
||||||
Assert.assertEquals(ControlMessage.TYPE_BACK_OR_SCREEN_ON, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_BACK_OR_SCREEN_ON, event.getType());
|
||||||
Assert.assertEquals(KeyEvent.ACTION_DOWN, event.getAction());
|
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -182,19 +182,35 @@ public class ControlMessageReaderTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseCollapseNotificationPanelEvent() throws IOException {
|
public void testParseExpandSettingsPanelEvent() throws IOException {
|
||||||
ControlMessageReader reader = new ControlMessageReader();
|
ControlMessageReader reader = new ControlMessageReader();
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL);
|
dos.writeByte(ControlMessage.TYPE_EXPAND_SETTINGS_PANEL);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
reader.readFrom(new ByteArrayInputStream(packet));
|
||||||
ControlMessage event = reader.next();
|
ControlMessage event = reader.next();
|
||||||
|
|
||||||
Assert.assertEquals(ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_EXPAND_SETTINGS_PANEL, event.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseCollapsePanelsEvent() throws IOException {
|
||||||
|
ControlMessageReader reader = new ControlMessageReader();
|
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
|
dos.writeByte(ControlMessage.TYPE_COLLAPSE_PANELS);
|
||||||
|
|
||||||
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
|
reader.readFrom(new ByteArrayInputStream(packet));
|
||||||
|
ControlMessage event = reader.next();
|
||||||
|
|
||||||
|
Assert.assertEquals(ControlMessage.TYPE_COLLAPSE_PANELS, event.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
Reference in New Issue
Block a user