Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64ea0d1a4a | ||
|
|
4dd78f523c |
@@ -303,8 +303,7 @@ To start scrcpy using a v4l2 sink:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --v4l2-sink=/dev/videoN
|
scrcpy --v4l2-sink=/dev/videoN
|
||||||
scrcpy --v4l2-sink=/dev/videoN --no-display # disable mirroring window
|
scrcpy --v4l2-sink=/dev/videoN -N # --no-display to disable mirroring window
|
||||||
scrcpy --v4l2-sink=/dev/videoN -N # short version
|
|
||||||
```
|
```
|
||||||
|
|
||||||
(replace `N` by the device ID, check with `ls /dev/video*`)
|
(replace `N` by the device ID, check with `ls /dev/video*`)
|
||||||
@@ -710,15 +709,15 @@ There is no visual feedback, a log is printed to the console.
|
|||||||
|
|
||||||
#### Push file to device
|
#### Push file to device
|
||||||
|
|
||||||
To push a file to `/sdcard/Download/` on the device, drag & drop a (non-APK)
|
To push a file to `/sdcard/` on the device, drag & drop a (non-APK) file to the
|
||||||
file to the _scrcpy_ window.
|
_scrcpy_ window.
|
||||||
|
|
||||||
There is no visual feedback, a log is printed to the console.
|
There is no visual feedback, a log is printed to the console.
|
||||||
|
|
||||||
The target directory can be changed on start:
|
The target directory can be changed on start:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --push-target=/sdcard/Movies/
|
scrcpy --push-target=/sdcard/Download/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ src = [
|
|||||||
'src/stream.c',
|
'src/stream.c',
|
||||||
'src/tiny_xpm.c',
|
'src/tiny_xpm.c',
|
||||||
'src/video_buffer.c',
|
'src/video_buffer.c',
|
||||||
'src/util/log.c',
|
|
||||||
'src/util/net.c',
|
'src/util/net.c',
|
||||||
'src/util/process.c',
|
'src/util/process.c',
|
||||||
'src/util/str_util.c',
|
'src/util/str_util.c',
|
||||||
|
|||||||
+2
-2
@@ -133,7 +133,7 @@ but breaks the expected behavior of alpha keys in games (typically WASD).
|
|||||||
.BI "\-\-push\-target " path
|
.BI "\-\-push\-target " path
|
||||||
Set the target directory for pushing files to the device by drag & drop. It is passed as\-is to "adb push".
|
Set the target directory for pushing files to the device by drag & drop. It is passed as\-is to "adb push".
|
||||||
|
|
||||||
Default is "/sdcard/Download/".
|
Default is "/sdcard/".
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-r, \-\-record " file
|
.BI "\-r, \-\-record " file
|
||||||
@@ -193,7 +193,7 @@ 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 ("verbose", "debug", "info", "warn" or "error").
|
Set the log level ("debug", "info", "warn" or "error").
|
||||||
|
|
||||||
Default is "info" for release builds, "debug" for debug builds.
|
Default is "info" for release builds, "debug" for debug builds.
|
||||||
|
|
||||||
|
|||||||
+2
-7
@@ -129,7 +129,7 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" --push-target path\n"
|
" --push-target path\n"
|
||||||
" Set the target directory for pushing files to the device by\n"
|
" Set the target directory for pushing files to the device by\n"
|
||||||
" drag & drop. It is passed as-is to \"adb push\".\n"
|
" drag & drop. It is passed as-is to \"adb push\".\n"
|
||||||
" Default is \"/sdcard/Download/\".\n"
|
" Default is \"/sdcard/\".\n"
|
||||||
"\n"
|
"\n"
|
||||||
" -r, --record file.mp4\n"
|
" -r, --record file.mp4\n"
|
||||||
" Record screen to file.\n"
|
" Record screen to file.\n"
|
||||||
@@ -184,7 +184,7 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
"\n"
|
"\n"
|
||||||
#endif
|
#endif
|
||||||
" -V, --verbosity value\n"
|
" -V, --verbosity value\n"
|
||||||
" Set the log level (verbose, debug, info, warn or error).\n"
|
" Set the log level (debug, info, warn or error).\n"
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
" Default is debug.\n"
|
" Default is debug.\n"
|
||||||
#else
|
#else
|
||||||
@@ -505,11 +505,6 @@ parse_display_id(const char *s, uint32_t *display_id) {
|
|||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_log_level(const char *s, enum sc_log_level *log_level) {
|
parse_log_level(const char *s, enum sc_log_level *log_level) {
|
||||||
if (!strcmp(s, "verbose")) {
|
|
||||||
*log_level = SC_LOG_LEVEL_VERBOSE;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!strcmp(s, "debug")) {
|
if (!strcmp(s, "debug")) {
|
||||||
*log_level = SC_LOG_LEVEL_DEBUG;
|
*log_level = SC_LOG_LEVEL_DEBUG;
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -22,18 +22,6 @@
|
|||||||
# define SCRCPY_LAVF_REQUIRES_REGISTER_ALL
|
# define SCRCPY_LAVF_REQUIRES_REGISTER_ALL
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
// In ffmpeg/doc/APIchanges:
|
|
||||||
// 2018-01-28 - ea3672b7d6 - lavf 58.7.100 - avformat.h
|
|
||||||
// Deprecate AVFormatContext filename field which had limited length, use the
|
|
||||||
// new dynamically allocated url field instead.
|
|
||||||
//
|
|
||||||
// 2018-01-28 - ea3672b7d6 - lavf 58.7.100 - avformat.h
|
|
||||||
// Add url field to AVFormatContext and add ff_format_set_url helper function.
|
|
||||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 7, 100)
|
|
||||||
# define SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if SDL_VERSION_ATLEAST(2, 0, 5)
|
#if SDL_VERSION_ATLEAST(2, 0, 5)
|
||||||
// <https://wiki.libsdl.org/SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH>
|
// <https://wiki.libsdl.org/SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH>
|
||||||
# define SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH
|
# define SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH
|
||||||
|
|||||||
+10
-135
@@ -1,7 +1,6 @@
|
|||||||
#include "control_msg.h"
|
#include "control_msg.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <inttypes.h>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
@@ -9,52 +8,6 @@
|
|||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str_util.h"
|
#include "util/str_util.h"
|
||||||
|
|
||||||
/**
|
|
||||||
* Map an enum value to a string based on an array, without crashing on an
|
|
||||||
* out-of-bounds index.
|
|
||||||
*/
|
|
||||||
#define ENUM_TO_LABEL(labels, value) \
|
|
||||||
((size_t) (value) < ARRAY_LEN(labels) ? labels[value] : "???")
|
|
||||||
|
|
||||||
#define KEYEVENT_ACTION_LABEL(value) \
|
|
||||||
ENUM_TO_LABEL(android_keyevent_action_labels, value)
|
|
||||||
|
|
||||||
#define MOTIONEVENT_ACTION_LABEL(value) \
|
|
||||||
ENUM_TO_LABEL(android_motionevent_action_labels, value)
|
|
||||||
|
|
||||||
#define SCREEN_POWER_MODE_LABEL(value) \
|
|
||||||
ENUM_TO_LABEL(screen_power_mode_labels, value)
|
|
||||||
|
|
||||||
static const char *const android_keyevent_action_labels[] = {
|
|
||||||
"down",
|
|
||||||
"up",
|
|
||||||
"multi",
|
|
||||||
};
|
|
||||||
|
|
||||||
static const char *const android_motionevent_action_labels[] = {
|
|
||||||
"down",
|
|
||||||
"up",
|
|
||||||
"move",
|
|
||||||
"cancel",
|
|
||||||
"outside",
|
|
||||||
"ponter-down",
|
|
||||||
"pointer-up",
|
|
||||||
"hover-move",
|
|
||||||
"scroll",
|
|
||||||
"hover-enter"
|
|
||||||
"hover-exit",
|
|
||||||
"btn-press",
|
|
||||||
"btn-release",
|
|
||||||
};
|
|
||||||
|
|
||||||
static const char *const screen_power_mode_labels[] = {
|
|
||||||
"off",
|
|
||||||
"doze",
|
|
||||||
"normal",
|
|
||||||
"doze-suspend",
|
|
||||||
"suspend",
|
|
||||||
};
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
write_position(uint8_t *buf, const struct position *position) {
|
write_position(uint8_t *buf, const struct position *position) {
|
||||||
buffer_write32be(&buf[0], position->point.x);
|
buffer_write32be(&buf[0], position->point.x);
|
||||||
@@ -127,6 +80,13 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
|||||||
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
||||||
buf[1] = msg->set_screen_power_mode.mode;
|
buf[1] = msg->set_screen_power_mode.mode;
|
||||||
return 2;
|
return 2;
|
||||||
|
case CONTROL_MSG_TYPE_SCAN_MEDIA:
|
||||||
|
{
|
||||||
|
size_t len = write_string(msg->scan_media.path,
|
||||||
|
CONTROL_MSG_SCAN_MEDIA_PATH_MAX_LENGTH,
|
||||||
|
&buf[1]);
|
||||||
|
return 1 + len;
|
||||||
|
}
|
||||||
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
||||||
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||||
@@ -140,94 +100,6 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
control_msg_log(const struct control_msg *msg) {
|
|
||||||
#define LOG_CMSG(fmt, ...) LOGV("input: " fmt, ## __VA_ARGS__)
|
|
||||||
switch (msg->type) {
|
|
||||||
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 CONTROL_MSG_TYPE_INJECT_TEXT:
|
|
||||||
LOG_CMSG("text \"%s\"", msg->inject_text.text);
|
|
||||||
break;
|
|
||||||
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;
|
|
||||||
if (id == POINTER_ID_MOUSE || id == POINTER_ID_VIRTUAL_FINGER) {
|
|
||||||
// string pointer id
|
|
||||||
LOG_CMSG("touch [id=%s] %-4s position=%" PRIi32 ",%" PRIi32
|
|
||||||
" pressure=%g buttons=%06lx",
|
|
||||||
id == POINTER_ID_MOUSE ? "mouse" : "vfinger",
|
|
||||||
MOTIONEVENT_ACTION_LABEL(action),
|
|
||||||
msg->inject_touch_event.position.point.x,
|
|
||||||
msg->inject_touch_event.position.point.y,
|
|
||||||
msg->inject_touch_event.pressure,
|
|
||||||
(long) msg->inject_touch_event.buttons);
|
|
||||||
} else {
|
|
||||||
// numeric pointer id
|
|
||||||
#ifndef __WIN32
|
|
||||||
# define PRIu64_ PRIu64
|
|
||||||
#else
|
|
||||||
# define PRIu64_ "I64u" // Windows...
|
|
||||||
#endif
|
|
||||||
LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%"
|
|
||||||
PRIi32 " pressure=%g buttons=%06lx",
|
|
||||||
id,
|
|
||||||
MOTIONEVENT_ACTION_LABEL(action),
|
|
||||||
msg->inject_touch_event.position.point.x,
|
|
||||||
msg->inject_touch_event.position.point.y,
|
|
||||||
msg->inject_touch_event.pressure,
|
|
||||||
(long) msg->inject_touch_event.buttons);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
|
|
||||||
LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%" PRIi32
|
|
||||||
" 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);
|
|
||||||
break;
|
|
||||||
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
|
||||||
LOG_CMSG("back-or-screen-on %s",
|
|
||||||
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action));
|
|
||||||
break;
|
|
||||||
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
|
||||||
LOG_CMSG("clipboard %s \"%s\"",
|
|
||||||
msg->set_clipboard.paste ? "paste" : "copy",
|
|
||||||
msg->set_clipboard.text);
|
|
||||||
break;
|
|
||||||
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 CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
|
||||||
LOG_CMSG("expand notification panel");
|
|
||||||
break;
|
|
||||||
case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
|
||||||
LOG_CMSG("expand settings panel");
|
|
||||||
break;
|
|
||||||
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
|
||||||
LOG_CMSG("collapse panels");
|
|
||||||
break;
|
|
||||||
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
|
||||||
LOG_CMSG("get clipboard");
|
|
||||||
break;
|
|
||||||
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
|
||||||
LOG_CMSG("rotate device");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
LOG_CMSG("unknown type: %u", (unsigned) msg->type);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
control_msg_destroy(struct control_msg *msg) {
|
control_msg_destroy(struct control_msg *msg) {
|
||||||
switch (msg->type) {
|
switch (msg->type) {
|
||||||
@@ -237,6 +109,9 @@ control_msg_destroy(struct control_msg *msg) {
|
|||||||
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
||||||
free(msg->set_clipboard.text);
|
free(msg->set_clipboard.text);
|
||||||
break;
|
break;
|
||||||
|
case CONTROL_MSG_TYPE_SCAN_MEDIA:
|
||||||
|
free(msg->scan_media.path);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// do nothing
|
// do nothing
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -17,8 +17,10 @@
|
|||||||
// type: 1 byte; paste flag: 1 byte; length: 4 bytes
|
// type: 1 byte; paste flag: 1 byte; length: 4 bytes
|
||||||
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 6)
|
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 6)
|
||||||
|
|
||||||
#define POINTER_ID_MOUSE UINT64_C(-1)
|
#define CONTROL_MSG_SCAN_MEDIA_PATH_MAX_LENGTH 256
|
||||||
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2)
|
|
||||||
|
#define POINTER_ID_MOUSE UINT64_C(-1);
|
||||||
|
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2);
|
||||||
|
|
||||||
enum control_msg_type {
|
enum control_msg_type {
|
||||||
CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
||||||
@@ -33,6 +35,7 @@ enum control_msg_type {
|
|||||||
CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||||
CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
|
CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
|
||||||
CONTROL_MSG_TYPE_ROTATE_DEVICE,
|
CONTROL_MSG_TYPE_ROTATE_DEVICE,
|
||||||
|
CONTROL_MSG_TYPE_SCAN_MEDIA,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum screen_power_mode {
|
enum screen_power_mode {
|
||||||
@@ -76,6 +79,9 @@ struct control_msg {
|
|||||||
struct {
|
struct {
|
||||||
enum screen_power_mode mode;
|
enum screen_power_mode mode;
|
||||||
} set_screen_power_mode;
|
} set_screen_power_mode;
|
||||||
|
struct {
|
||||||
|
char *path; // owned, to be freed by free()
|
||||||
|
} scan_media;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -84,9 +90,6 @@ struct control_msg {
|
|||||||
size_t
|
size_t
|
||||||
control_msg_serialize(const struct control_msg *msg, unsigned char *buf);
|
control_msg_serialize(const struct control_msg *msg, unsigned char *buf);
|
||||||
|
|
||||||
void
|
|
||||||
control_msg_log(const struct control_msg *msg);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
control_msg_destroy(struct control_msg *msg);
|
control_msg_destroy(struct control_msg *msg);
|
||||||
|
|
||||||
|
|||||||
@@ -48,10 +48,6 @@ controller_destroy(struct controller *controller) {
|
|||||||
bool
|
bool
|
||||||
controller_push_msg(struct controller *controller,
|
controller_push_msg(struct controller *controller,
|
||||||
const struct control_msg *msg) {
|
const struct control_msg *msg) {
|
||||||
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
|
|
||||||
control_msg_log(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
sc_mutex_lock(&controller->mutex);
|
sc_mutex_lock(&controller->mutex);
|
||||||
bool was_empty = cbuf_is_empty(&controller->queue);
|
bool was_empty = cbuf_is_empty(&controller->queue);
|
||||||
bool res = cbuf_push(&controller->queue, *msg);
|
bool res = cbuf_push(&controller->queue, *msg);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
#include "adb.h"
|
#include "adb.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
#define DEFAULT_PUSH_TARGET "/sdcard/Download/"
|
#define DEFAULT_PUSH_TARGET "/sdcard/"
|
||||||
|
|
||||||
static void
|
static void
|
||||||
file_handler_request_destroy(struct file_handler_request *req) {
|
file_handler_request_destroy(struct file_handler_request *req) {
|
||||||
|
|||||||
@@ -595,12 +595,8 @@ convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
|
|||||||
static void
|
static void
|
||||||
input_manager_process_mouse_motion(struct input_manager *im,
|
input_manager_process_mouse_motion(struct input_manager *im,
|
||||||
const SDL_MouseMotionEvent *event) {
|
const SDL_MouseMotionEvent *event) {
|
||||||
uint32_t mask = SDL_BUTTON_LMASK;
|
if (!event->state) {
|
||||||
if (im->forward_all_clicks) {
|
// do not send motion events when no button is pressed
|
||||||
mask |= SDL_BUTTON_MMASK | SDL_BUTTON_RMASK;
|
|
||||||
}
|
|
||||||
if (!(event->state & mask)) {
|
|
||||||
// do not send motion events when no click is pressed
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event->which == SDL_TOUCH_MOUSEID) {
|
if (event->which == SDL_TOUCH_MOUSEID) {
|
||||||
|
|||||||
+20
-1
@@ -38,6 +38,24 @@ print_version(void) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static SDL_LogPriority
|
||||||
|
convert_log_level_to_sdl(enum sc_log_level level) {
|
||||||
|
switch (level) {
|
||||||
|
case SC_LOG_LEVEL_DEBUG:
|
||||||
|
return SDL_LOG_PRIORITY_DEBUG;
|
||||||
|
case SC_LOG_LEVEL_INFO:
|
||||||
|
return SDL_LOG_PRIORITY_INFO;
|
||||||
|
case SC_LOG_LEVEL_WARN:
|
||||||
|
return SDL_LOG_PRIORITY_WARN;
|
||||||
|
case SC_LOG_LEVEL_ERROR:
|
||||||
|
return SDL_LOG_PRIORITY_ERROR;
|
||||||
|
default:
|
||||||
|
assert(!"unexpected log level");
|
||||||
|
return SDL_LOG_PRIORITY_INFO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int
|
int
|
||||||
main(int argc, char *argv[]) {
|
main(int argc, char *argv[]) {
|
||||||
#ifdef __WINDOWS__
|
#ifdef __WINDOWS__
|
||||||
@@ -61,7 +79,8 @@ main(int argc, char *argv[]) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_set_log_level(args.opts.log_level);
|
SDL_LogPriority sdl_log = convert_log_level_to_sdl(args.opts.log_level);
|
||||||
|
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log);
|
||||||
|
|
||||||
if (args.help) {
|
if (args.help) {
|
||||||
scrcpy_print_usage(argv[0]);
|
scrcpy_print_usage(argv[0]);
|
||||||
|
|||||||
+11
-16
@@ -35,14 +35,11 @@ record_packet_new(const AVPacket *packet) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
rec->packet = av_packet_alloc();
|
// av_packet_ref() does not initialize all fields in old FFmpeg versions
|
||||||
if (!rec->packet) {
|
// See <https://github.com/Genymobile/scrcpy/issues/707>
|
||||||
free(rec);
|
av_init_packet(&rec->packet);
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (av_packet_ref(rec->packet, packet)) {
|
if (av_packet_ref(&rec->packet, packet)) {
|
||||||
av_packet_free(&rec->packet);
|
|
||||||
free(rec);
|
free(rec);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -51,8 +48,7 @@ record_packet_new(const AVPacket *packet) {
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
record_packet_delete(struct record_packet *rec) {
|
record_packet_delete(struct record_packet *rec) {
|
||||||
av_packet_unref(rec->packet);
|
av_packet_unref(&rec->packet);
|
||||||
av_packet_free(&rec->packet);
|
|
||||||
free(rec);
|
free(rec);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,8 +144,8 @@ run_recorder(void *data) {
|
|||||||
struct record_packet *last = recorder->previous;
|
struct record_packet *last = recorder->previous;
|
||||||
if (last) {
|
if (last) {
|
||||||
// assign an arbitrary duration to the last packet
|
// assign an arbitrary duration to the last packet
|
||||||
last->packet->duration = 100000;
|
last->packet.duration = 100000;
|
||||||
bool ok = recorder_write(recorder, last->packet);
|
bool ok = recorder_write(recorder, &last->packet);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
// failing to write the last frame is not very serious, no
|
// failing to write the last frame is not very serious, no
|
||||||
// future frame may depend on it, so the resulting file
|
// future frame may depend on it, so the resulting file
|
||||||
@@ -176,14 +172,13 @@ run_recorder(void *data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// config packets have no PTS, we must ignore them
|
// config packets have no PTS, we must ignore them
|
||||||
if (rec->packet->pts != AV_NOPTS_VALUE
|
if (rec->packet.pts != AV_NOPTS_VALUE
|
||||||
&& previous->packet->pts != AV_NOPTS_VALUE) {
|
&& previous->packet.pts != AV_NOPTS_VALUE) {
|
||||||
// we now know the duration of the previous packet
|
// we now know the duration of the previous packet
|
||||||
previous->packet->duration =
|
previous->packet.duration = rec->packet.pts - previous->packet.pts;
|
||||||
rec->packet->pts - previous->packet->pts;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ok = recorder_write(recorder, previous->packet);
|
bool ok = recorder_write(recorder, &previous->packet);
|
||||||
record_packet_delete(previous);
|
record_packet_delete(previous);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not record packet");
|
LOGE("Could not record packet");
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@
|
|||||||
#include "util/thread.h"
|
#include "util/thread.h"
|
||||||
|
|
||||||
struct record_packet {
|
struct record_packet {
|
||||||
AVPacket *packet;
|
AVPacket packet;
|
||||||
struct record_packet *next;
|
struct record_packet *next;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+4
-5
@@ -216,15 +216,14 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
|
|||||||
if (priority == 0) {
|
if (priority == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
char *local_fmt = malloc(strlen(fmt) + 10);
|
||||||
size_t fmt_len = strlen(fmt);
|
|
||||||
char *local_fmt = malloc(fmt_len + 10);
|
|
||||||
if (!local_fmt) {
|
if (!local_fmt) {
|
||||||
LOGC("Could not allocate string");
|
LOGC("Could not allocate string");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
memcpy(local_fmt, "[FFmpeg] ", 9); // do not write the final '\0'
|
// strcpy is safe here, the destination is large enough
|
||||||
memcpy(local_fmt + 9, fmt, fmt_len + 1); // include '\0'
|
strcpy(local_fmt, "[FFmpeg] ");
|
||||||
|
strcpy(local_fmt + 9, fmt);
|
||||||
SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl);
|
SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl);
|
||||||
free(local_fmt);
|
free(local_fmt);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
enum sc_log_level {
|
enum sc_log_level {
|
||||||
SC_LOG_LEVEL_VERBOSE,
|
|
||||||
SC_LOG_LEVEL_DEBUG,
|
SC_LOG_LEVEL_DEBUG,
|
||||||
SC_LOG_LEVEL_INFO,
|
SC_LOG_LEVEL_INFO,
|
||||||
SC_LOG_LEVEL_WARN,
|
SC_LOG_LEVEL_WARN,
|
||||||
|
|||||||
+8
-25
@@ -41,18 +41,6 @@ get_window_size(const struct screen *screen) {
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct point
|
|
||||||
get_window_position(const struct screen *screen) {
|
|
||||||
int x;
|
|
||||||
int y;
|
|
||||||
SDL_GetWindowPosition(screen->window, &x, &y);
|
|
||||||
|
|
||||||
struct point point;
|
|
||||||
point.x = x;
|
|
||||||
point.y = y;
|
|
||||||
return point;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the window size to be applied when fullscreen is disabled
|
// set the window size to be applied when fullscreen is disabled
|
||||||
static void
|
static void
|
||||||
set_window_size(struct screen *screen, struct size new_size) {
|
set_window_size(struct screen *screen, struct size new_size) {
|
||||||
@@ -134,6 +122,13 @@ get_optimal_size(struct size current_size, struct size content_size) {
|
|||||||
return window_size;
|
return window_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// same as get_optimal_size(), but read the current size from the window
|
||||||
|
static inline struct size
|
||||||
|
get_optimal_window_size(const struct screen *screen, struct size content_size) {
|
||||||
|
struct size window_size = get_window_size(screen);
|
||||||
|
return get_optimal_size(window_size, content_size);
|
||||||
|
}
|
||||||
|
|
||||||
// initially, there is no current size, so use the frame size as current size
|
// initially, there is no current size, so use the frame size as current size
|
||||||
// req_width and req_height, if not 0, are the sizes requested by the user
|
// req_width and req_height, if not 0, are the sizes requested by the user
|
||||||
static inline struct size
|
static inline struct size
|
||||||
@@ -667,20 +662,9 @@ screen_resize_to_fit(struct screen *screen) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct point point = get_window_position(screen);
|
|
||||||
struct size window_size = get_window_size(screen);
|
|
||||||
|
|
||||||
struct size optimal_size =
|
struct size optimal_size =
|
||||||
get_optimal_size(window_size, screen->content_size);
|
get_optimal_window_size(screen, screen->content_size);
|
||||||
|
|
||||||
// Center the window related to the device screen
|
|
||||||
assert(optimal_size.width <= window_size.width);
|
|
||||||
assert(optimal_size.height <= window_size.height);
|
|
||||||
uint32_t new_x = point.x + (window_size.width - optimal_size.width) / 2;
|
|
||||||
uint32_t new_y = point.y + (window_size.height - optimal_size.height) / 2;
|
|
||||||
|
|
||||||
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
|
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
|
||||||
SDL_SetWindowPosition(screen->window, new_x, new_y);
|
|
||||||
LOGD("Resized to optimal size: %ux%u", optimal_size.width,
|
LOGD("Resized to optimal size: %ux%u", optimal_size.width,
|
||||||
optimal_size.height);
|
optimal_size.height);
|
||||||
}
|
}
|
||||||
@@ -742,7 +726,6 @@ screen_handle_event(struct screen *screen, SDL_Event *event) {
|
|||||||
}
|
}
|
||||||
screen->maximized = false;
|
screen->maximized = false;
|
||||||
apply_pending_resize(screen);
|
apply_pending_resize(screen);
|
||||||
screen_render(screen, true);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -235,8 +235,6 @@ enable_tunnel_any_port(struct server *server, struct sc_port_range port_range,
|
|||||||
static const char *
|
static const char *
|
||||||
log_level_to_server_string(enum sc_log_level level) {
|
log_level_to_server_string(enum sc_log_level level) {
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case SC_LOG_LEVEL_VERBOSE:
|
|
||||||
return "verbose";
|
|
||||||
case SC_LOG_LEVEL_DEBUG:
|
case SC_LOG_LEVEL_DEBUG:
|
||||||
return "debug";
|
return "debug";
|
||||||
case SC_LOG_LEVEL_INFO:
|
case SC_LOG_LEVEL_INFO:
|
||||||
|
|||||||
+22
-35
@@ -104,38 +104,33 @@ static bool
|
|||||||
stream_push_packet(struct stream *stream, AVPacket *packet) {
|
stream_push_packet(struct stream *stream, AVPacket *packet) {
|
||||||
bool is_config = packet->pts == AV_NOPTS_VALUE;
|
bool is_config = packet->pts == AV_NOPTS_VALUE;
|
||||||
|
|
||||||
// A config packet must not be decoded immediately (it contains no
|
// A config packet must not be decoded immetiately (it contains no
|
||||||
// frame); instead, it must be concatenated with the future data packet.
|
// frame); instead, it must be concatenated with the future data packet.
|
||||||
if (stream->pending || is_config) {
|
if (stream->has_pending || is_config) {
|
||||||
size_t offset;
|
size_t offset;
|
||||||
if (stream->pending) {
|
if (stream->has_pending) {
|
||||||
offset = stream->pending->size;
|
offset = stream->pending.size;
|
||||||
if (av_grow_packet(stream->pending, packet->size)) {
|
if (av_grow_packet(&stream->pending, packet->size)) {
|
||||||
LOGE("Could not grow packet");
|
LOGE("Could not grow packet");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
offset = 0;
|
offset = 0;
|
||||||
stream->pending = av_packet_alloc();
|
if (av_new_packet(&stream->pending, packet->size)) {
|
||||||
if (!stream->pending) {
|
|
||||||
LOGE("Could not allocate packet");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (av_new_packet(stream->pending, packet->size)) {
|
|
||||||
LOGE("Could not create packet");
|
LOGE("Could not create packet");
|
||||||
av_packet_free(&stream->pending);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
stream->has_pending = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(stream->pending->data + offset, packet->data, packet->size);
|
memcpy(stream->pending.data + offset, packet->data, packet->size);
|
||||||
|
|
||||||
if (!is_config) {
|
if (!is_config) {
|
||||||
// prepare the concat packet to send to the decoder
|
// prepare the concat packet to send to the decoder
|
||||||
stream->pending->pts = packet->pts;
|
stream->pending.pts = packet->pts;
|
||||||
stream->pending->dts = packet->dts;
|
stream->pending.dts = packet->dts;
|
||||||
stream->pending->flags = packet->flags;
|
stream->pending.flags = packet->flags;
|
||||||
packet = stream->pending;
|
packet = &stream->pending;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,10 +144,10 @@ stream_push_packet(struct stream *stream, AVPacket *packet) {
|
|||||||
// data packet
|
// data packet
|
||||||
bool ok = stream_parse(stream, packet);
|
bool ok = stream_parse(stream, packet);
|
||||||
|
|
||||||
if (stream->pending) {
|
if (stream->has_pending) {
|
||||||
// the pending packet must be discarded (consumed or error)
|
// the pending packet must be discarded (consumed or error)
|
||||||
av_packet_unref(stream->pending);
|
stream->has_pending = false;
|
||||||
av_packet_free(&stream->pending);
|
av_packet_unref(&stream->pending);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
@@ -220,21 +215,16 @@ run_stream(void *data) {
|
|||||||
// It's more complicated, but this allows to reduce the latency by 1 frame!
|
// It's more complicated, but this allows to reduce the latency by 1 frame!
|
||||||
stream->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
|
stream->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
|
||||||
|
|
||||||
AVPacket *packet = av_packet_alloc();
|
|
||||||
if (!packet) {
|
|
||||||
LOGE("Could not allocate packet");
|
|
||||||
goto finally_close_parser;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
bool ok = stream_recv_packet(stream, packet);
|
AVPacket packet;
|
||||||
|
bool ok = stream_recv_packet(stream, &packet);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
// end of stream
|
// end of stream
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = stream_push_packet(stream, packet);
|
ok = stream_push_packet(stream, &packet);
|
||||||
av_packet_unref(packet);
|
av_packet_unref(&packet);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
// cannot process packet (error already logged)
|
// cannot process packet (error already logged)
|
||||||
break;
|
break;
|
||||||
@@ -243,13 +233,10 @@ run_stream(void *data) {
|
|||||||
|
|
||||||
LOGD("End of frames");
|
LOGD("End of frames");
|
||||||
|
|
||||||
if (stream->pending) {
|
if (stream->has_pending) {
|
||||||
av_packet_unref(stream->pending);
|
av_packet_unref(&stream->pending);
|
||||||
av_packet_free(&stream->pending);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
av_packet_free(&packet);
|
|
||||||
finally_close_parser:
|
|
||||||
av_parser_close(stream->parser);
|
av_parser_close(stream->parser);
|
||||||
finally_close_sinks:
|
finally_close_sinks:
|
||||||
stream_close_sinks(stream);
|
stream_close_sinks(stream);
|
||||||
@@ -265,7 +252,7 @@ void
|
|||||||
stream_init(struct stream *stream, socket_t socket,
|
stream_init(struct stream *stream, socket_t socket,
|
||||||
const struct stream_callbacks *cbs, void *cbs_userdata) {
|
const struct stream_callbacks *cbs, void *cbs_userdata) {
|
||||||
stream->socket = socket;
|
stream->socket = socket;
|
||||||
stream->pending = NULL;
|
stream->has_pending = false;
|
||||||
stream->sink_count = 0;
|
stream->sink_count = 0;
|
||||||
|
|
||||||
assert(cbs && cbs->on_eos);
|
assert(cbs && cbs->on_eos);
|
||||||
|
|||||||
+2
-1
@@ -24,7 +24,8 @@ struct stream {
|
|||||||
AVCodecParserContext *parser;
|
AVCodecParserContext *parser;
|
||||||
// successive packets may need to be concatenated, until a non-config
|
// successive packets may need to be concatenated, until a non-config
|
||||||
// packet is available
|
// packet is available
|
||||||
AVPacket *pending;
|
bool has_pending;
|
||||||
|
AVPacket pending;
|
||||||
|
|
||||||
const struct stream_callbacks *cbs;
|
const struct stream_callbacks *cbs;
|
||||||
void *cbs_userdata;
|
void *cbs_userdata;
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
#include "log.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
static SDL_LogPriority
|
|
||||||
log_level_sc_to_sdl(enum sc_log_level level) {
|
|
||||||
switch (level) {
|
|
||||||
case SC_LOG_LEVEL_VERBOSE:
|
|
||||||
return SDL_LOG_PRIORITY_VERBOSE;
|
|
||||||
case SC_LOG_LEVEL_DEBUG:
|
|
||||||
return SDL_LOG_PRIORITY_DEBUG;
|
|
||||||
case SC_LOG_LEVEL_INFO:
|
|
||||||
return SDL_LOG_PRIORITY_INFO;
|
|
||||||
case SC_LOG_LEVEL_WARN:
|
|
||||||
return SDL_LOG_PRIORITY_WARN;
|
|
||||||
case SC_LOG_LEVEL_ERROR:
|
|
||||||
return SDL_LOG_PRIORITY_ERROR;
|
|
||||||
default:
|
|
||||||
assert(!"unexpected log level");
|
|
||||||
return SDL_LOG_PRIORITY_INFO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static enum sc_log_level
|
|
||||||
log_level_sdl_to_sc(SDL_LogPriority priority) {
|
|
||||||
switch (priority) {
|
|
||||||
case SDL_LOG_PRIORITY_VERBOSE:
|
|
||||||
return SC_LOG_LEVEL_VERBOSE;
|
|
||||||
case SDL_LOG_PRIORITY_DEBUG:
|
|
||||||
return SC_LOG_LEVEL_DEBUG;
|
|
||||||
case SDL_LOG_PRIORITY_INFO:
|
|
||||||
return SC_LOG_LEVEL_INFO;
|
|
||||||
case SDL_LOG_PRIORITY_WARN:
|
|
||||||
return SC_LOG_LEVEL_WARN;
|
|
||||||
case SDL_LOG_PRIORITY_ERROR:
|
|
||||||
return SC_LOG_LEVEL_ERROR;
|
|
||||||
default:
|
|
||||||
assert(!"unexpected log level");
|
|
||||||
return SC_LOG_LEVEL_INFO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_set_log_level(enum sc_log_level level) {
|
|
||||||
SDL_LogPriority sdl_log = log_level_sc_to_sdl(level);
|
|
||||||
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum sc_log_level
|
|
||||||
sc_get_log_level(void) {
|
|
||||||
SDL_LogPriority sdl_log = SDL_LogGetPriority(SDL_LOG_CATEGORY_APPLICATION);
|
|
||||||
return log_level_sdl_to_sc(sdl_log);
|
|
||||||
}
|
|
||||||
+2
-12
@@ -1,12 +1,8 @@
|
|||||||
#ifndef SC_LOG_H
|
#ifndef LOG_H
|
||||||
#define SC_LOG_H
|
#define LOG_H
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <SDL2/SDL_log.h>
|
#include <SDL2/SDL_log.h>
|
||||||
|
|
||||||
#include "scrcpy.h"
|
|
||||||
|
|
||||||
#define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
#define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
||||||
#define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
#define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
||||||
#define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
#define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
||||||
@@ -14,10 +10,4 @@
|
|||||||
#define LOGE(...) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
#define LOGE(...) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
||||||
#define LOGC(...) SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
#define LOGC(...) SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
||||||
|
|
||||||
void
|
|
||||||
sc_set_log_level(enum sc_log_level level);
|
|
||||||
|
|
||||||
enum sc_log_level
|
|
||||||
sc_get_log_level(void);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+2
-16
@@ -86,7 +86,7 @@ encode_and_write_frame(struct sc_v4l2_sink *vs, const AVFrame *frame) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
AVPacket *packet = vs->packet;
|
AVPacket *packet = &vs->packet;
|
||||||
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
|
||||||
@@ -180,17 +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;
|
||||||
#ifdef SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL
|
|
||||||
vs->format_ctx->url = strdup(vs->device_name);
|
vs->format_ctx->url = strdup(vs->device_name);
|
||||||
if (!vs->format_ctx->url) {
|
if (!vs->format_ctx->url) {
|
||||||
LOGE("Could not strdup v4l2 device name");
|
LOGE("Could not strdup v4l2 device name");
|
||||||
goto error_avformat_free_context;
|
goto error_avformat_free_context;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
strncpy(vs->format_ctx->filename, vs->device_name,
|
|
||||||
sizeof(vs->format_ctx->filename));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
AVStream *ostream = avformat_new_stream(vs->format_ctx, encoder);
|
AVStream *ostream = avformat_new_stream(vs->format_ctx, encoder);
|
||||||
if (!ostream) {
|
if (!ostream) {
|
||||||
@@ -235,17 +230,11 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
|
|||||||
goto error_avcodec_close;
|
goto error_avcodec_close;
|
||||||
}
|
}
|
||||||
|
|
||||||
vs->packet = av_packet_alloc();
|
|
||||||
if (!vs->packet) {
|
|
||||||
LOGE("Could not allocate packet");
|
|
||||||
goto error_av_frame_free;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGD("Starting v4l2 thread");
|
LOGD("Starting v4l2 thread");
|
||||||
ok = sc_thread_create(&vs->thread, run_v4l2_sink, "v4l2", vs);
|
ok = sc_thread_create(&vs->thread, run_v4l2_sink, "v4l2", vs);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGC("Could not start v4l2 thread");
|
LOGC("Could not start v4l2 thread");
|
||||||
goto error_av_packet_free;
|
goto error_av_frame_free;
|
||||||
}
|
}
|
||||||
|
|
||||||
vs->header_written = false;
|
vs->header_written = false;
|
||||||
@@ -255,8 +244,6 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
error_av_packet_free:
|
|
||||||
av_packet_free(&vs->packet);
|
|
||||||
error_av_frame_free:
|
error_av_frame_free:
|
||||||
av_frame_free(&vs->frame);
|
av_frame_free(&vs->frame);
|
||||||
error_avcodec_close:
|
error_avcodec_close:
|
||||||
@@ -286,7 +273,6 @@ sc_v4l2_sink_close(struct sc_v4l2_sink *vs) {
|
|||||||
|
|
||||||
sc_thread_join(&vs->thread, NULL);
|
sc_thread_join(&vs->thread, NULL);
|
||||||
|
|
||||||
av_packet_free(&vs->packet);
|
|
||||||
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);
|
||||||
|
|||||||
+1
-1
@@ -26,7 +26,7 @@ struct sc_v4l2_sink {
|
|||||||
bool header_written;
|
bool header_written;
|
||||||
|
|
||||||
AVFrame *frame;
|
AVFrame *frame;
|
||||||
AVPacket *packet;
|
AVPacket packet;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|||||||
@@ -278,6 +278,28 @@ static void test_serialize_rotate_device(void) {
|
|||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_serialize_scan_media(void) {
|
||||||
|
struct control_msg msg = {
|
||||||
|
.type = CONTROL_MSG_TYPE_SCAN_MEDIA,
|
||||||
|
.scan_media = {
|
||||||
|
.path = "/sdcard/Download/",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
|
size_t size = control_msg_serialize(&msg, buf);
|
||||||
|
assert(size == 22);
|
||||||
|
|
||||||
|
const unsigned char expected[] = {
|
||||||
|
CONTROL_MSG_TYPE_SCAN_MEDIA,
|
||||||
|
0x00, 0x00, 0x00, 0x11, // path length
|
||||||
|
'/', 's', 'd', 'c', 'a', 'r', 'd', '/',
|
||||||
|
'D', 'o', 'w', 'n', 'l', 'o', 'a', 'd',
|
||||||
|
'/' // path
|
||||||
|
};
|
||||||
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
(void) argc;
|
(void) argc;
|
||||||
(void) argv;
|
(void) argv;
|
||||||
@@ -295,5 +317,6 @@ int main(int argc, char *argv[]) {
|
|||||||
test_serialize_set_clipboard();
|
test_serialize_set_clipboard();
|
||||||
test_serialize_set_screen_power_mode();
|
test_serialize_set_screen_power_mode();
|
||||||
test_serialize_rotate_device();
|
test_serialize_rotate_device();
|
||||||
|
test_serialize_scan_media();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ public final class ControlMessage {
|
|||||||
public static final int TYPE_SET_CLIPBOARD = 9;
|
public static final int TYPE_SET_CLIPBOARD = 9;
|
||||||
public static final int TYPE_SET_SCREEN_POWER_MODE = 10;
|
public static final int TYPE_SET_SCREEN_POWER_MODE = 10;
|
||||||
public static final int TYPE_ROTATE_DEVICE = 11;
|
public static final int TYPE_ROTATE_DEVICE = 11;
|
||||||
|
public static final int TYPE_SCAN_MEDIA = 12;
|
||||||
|
|
||||||
private int type;
|
private int type;
|
||||||
private String text;
|
private String text;
|
||||||
@@ -97,6 +98,13 @@ public final class ControlMessage {
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ControlMessage createScanMedia(String path) {
|
||||||
|
ControlMessage msg = new ControlMessage();
|
||||||
|
msg.type = TYPE_SCAN_MEDIA;
|
||||||
|
msg.text = path;
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
public static ControlMessage createEmpty(int type) {
|
public static ControlMessage createEmpty(int type) {
|
||||||
ControlMessage msg = new ControlMessage();
|
ControlMessage msg = new ControlMessage();
|
||||||
msg.type = type;
|
msg.type = type;
|
||||||
|
|||||||
@@ -76,6 +76,9 @@ public class ControlMessageReader {
|
|||||||
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
||||||
msg = parseSetScreenPowerMode();
|
msg = parseSetScreenPowerMode();
|
||||||
break;
|
break;
|
||||||
|
case ControlMessage.TYPE_SCAN_MEDIA:
|
||||||
|
msg = parseScanMedia();
|
||||||
|
break;
|
||||||
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL:
|
case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL:
|
||||||
case ControlMessage.TYPE_COLLAPSE_PANELS:
|
case ControlMessage.TYPE_COLLAPSE_PANELS:
|
||||||
@@ -182,6 +185,14 @@ public class ControlMessageReader {
|
|||||||
return ControlMessage.createSetScreenPowerMode(mode);
|
return ControlMessage.createSetScreenPowerMode(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ControlMessage parseScanMedia() {
|
||||||
|
String path = parseString();
|
||||||
|
if (path == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ControlMessage.createScanMedia(path);
|
||||||
|
}
|
||||||
|
|
||||||
private static Position readPosition(ByteBuffer buffer) {
|
private static Position readPosition(ByteBuffer buffer) {
|
||||||
int x = buffer.getInt();
|
int x = buffer.getInt();
|
||||||
int y = buffer.getInt();
|
int y = buffer.getInt();
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.genymobile.scrcpy;
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
@@ -7,6 +9,7 @@ import android.view.KeyCharacterMap;
|
|||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
@@ -135,6 +138,12 @@ public class Controller {
|
|||||||
case ControlMessage.TYPE_ROTATE_DEVICE:
|
case ControlMessage.TYPE_ROTATE_DEVICE:
|
||||||
Device.rotateDevice();
|
Device.rotateDevice();
|
||||||
break;
|
break;
|
||||||
|
case ControlMessage.TYPE_SCAN_MEDIA:
|
||||||
|
String path = msg.getText();
|
||||||
|
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
|
||||||
|
intent.setData(Uri.fromFile(new File(path)));
|
||||||
|
Device.sendBroadcast(intent);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import com.genymobile.scrcpy.wrappers.SurfaceControl;
|
|||||||
import com.genymobile.scrcpy.wrappers.WindowManager;
|
import com.genymobile.scrcpy.wrappers.WindowManager;
|
||||||
|
|
||||||
import android.content.IOnPrimaryClipChangedListener;
|
import android.content.IOnPrimaryClipChangedListener;
|
||||||
|
import android.content.Intent;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
@@ -299,4 +300,8 @@ public final class Device {
|
|||||||
public static ContentProvider createSettingsProvider() {
|
public static ContentProvider createSettingsProvider() {
|
||||||
return SERVICE_MANAGER.getActivityManager().createSettingsProvider();
|
return SERVICE_MANAGER.getActivityManager().createSettingsProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void sendBroadcast(Intent intent) {
|
||||||
|
SERVICE_MANAGER.getActivityManager().sendBroadcast(intent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public final class Ln {
|
|||||||
private static final String PREFIX = "[server] ";
|
private static final String PREFIX = "[server] ";
|
||||||
|
|
||||||
enum Level {
|
enum Level {
|
||||||
VERBOSE, DEBUG, INFO, WARN, ERROR
|
DEBUG, INFO, WARN, ERROR
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Level threshold = Level.INFO;
|
private static Level threshold = Level.INFO;
|
||||||
@@ -36,13 +36,6 @@ public final class Ln {
|
|||||||
return level.ordinal() >= threshold.ordinal();
|
return level.ordinal() >= threshold.ordinal();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void v(String message) {
|
|
||||||
if (isEnabled(Level.VERBOSE)) {
|
|
||||||
Log.v(TAG, message);
|
|
||||||
System.out.println(PREFIX + "VERBOSE: " + message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void d(String message) {
|
public static void d(String message) {
|
||||||
if (isEnabled(Level.DEBUG)) {
|
if (isEnabled(Level.DEBUG)) {
|
||||||
Log.d(TAG, message);
|
Log.d(TAG, message);
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package com.genymobile.scrcpy.wrappers;
|
|||||||
|
|
||||||
import com.genymobile.scrcpy.Ln;
|
import com.genymobile.scrcpy.Ln;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.IInterface;
|
import android.os.IInterface;
|
||||||
|
|
||||||
@@ -16,6 +18,7 @@ public class ActivityManager {
|
|||||||
private Method getContentProviderExternalMethod;
|
private Method getContentProviderExternalMethod;
|
||||||
private boolean getContentProviderExternalMethodNewVersion = true;
|
private boolean getContentProviderExternalMethodNewVersion = true;
|
||||||
private Method removeContentProviderExternalMethod;
|
private Method removeContentProviderExternalMethod;
|
||||||
|
private Method broadcastIntentMethod;
|
||||||
|
|
||||||
public ActivityManager(IInterface manager) {
|
public ActivityManager(IInterface manager) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
@@ -42,6 +45,22 @@ public class ActivityManager {
|
|||||||
return removeContentProviderExternalMethod;
|
return removeContentProviderExternalMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Method getBroadcastIntentMethod() throws NoSuchMethodException {
|
||||||
|
if (broadcastIntentMethod == null) {
|
||||||
|
try {
|
||||||
|
Class<?> iApplicationThreadClass = Class.forName("android.app.IApplicationThread");
|
||||||
|
Class<?> iIntentReceiverClass = Class.forName("android.content.IIntentReceiver");
|
||||||
|
broadcastIntentMethod = manager.getClass()
|
||||||
|
.getMethod("broadcastIntent", iApplicationThreadClass, Intent.class, String.class, iIntentReceiverClass, int.class,
|
||||||
|
String.class, Bundle.class, String[].class, int.class, Bundle.class, boolean.class, boolean.class, int.class);
|
||||||
|
return broadcastIntentMethod;
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return broadcastIntentMethod;
|
||||||
|
}
|
||||||
|
|
||||||
private ContentProvider getContentProviderExternal(String name, IBinder token) {
|
private ContentProvider getContentProviderExternal(String name, IBinder token) {
|
||||||
try {
|
try {
|
||||||
Method method = getGetContentProviderExternalMethod();
|
Method method = getGetContentProviderExternalMethod();
|
||||||
@@ -84,4 +103,13 @@ public class ActivityManager {
|
|||||||
public ContentProvider createSettingsProvider() {
|
public ContentProvider createSettingsProvider() {
|
||||||
return getContentProviderExternal("settings", new Binder());
|
return getContentProviderExternal("settings", new Binder());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void sendBroadcast(Intent intent) {
|
||||||
|
try {
|
||||||
|
Method method = getBroadcastIntentMethod();
|
||||||
|
method.invoke(manager, null, intent, null, null, 0, null, null, null, -1, null, true, false, ServiceManager.USER_ID);
|
||||||
|
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||||
|
Ln.e("Could not invoke method", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.genymobile.scrcpy.wrappers;
|
|||||||
|
|
||||||
import com.genymobile.scrcpy.Ln;
|
import com.genymobile.scrcpy.Ln;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
|
||||||
@@ -38,8 +37,6 @@ public class ContentProvider implements Closeable {
|
|||||||
private Method callMethod;
|
private Method callMethod;
|
||||||
private int callMethodVersion;
|
private int callMethodVersion;
|
||||||
|
|
||||||
private Object attributionSource;
|
|
||||||
|
|
||||||
ContentProvider(ActivityManager manager, Object provider, String name, IBinder token) {
|
ContentProvider(ActivityManager manager, Object provider, String name, IBinder token) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
@@ -47,58 +44,36 @@ public class ContentProvider implements Closeable {
|
|||||||
this.token = token;
|
this.token = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("PrivateApi")
|
|
||||||
private Method getCallMethod() throws NoSuchMethodException {
|
private Method getCallMethod() throws NoSuchMethodException {
|
||||||
if (callMethod == null) {
|
if (callMethod == null) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Class<?> attributionSourceClass = Class.forName("android.content.AttributionSource");
|
callMethod = provider.getClass()
|
||||||
callMethod = provider.getClass().getMethod("call", attributionSourceClass, String.class, String.class, String.class, Bundle.class);
|
.getMethod("call", String.class, String.class, String.class, String.class, String.class, Bundle.class);
|
||||||
callMethodVersion = 0;
|
callMethodVersion = 0;
|
||||||
} catch (NoSuchMethodException | ClassNotFoundException e0) {
|
} catch (NoSuchMethodException e) {
|
||||||
// old versions
|
// old versions
|
||||||
try {
|
try {
|
||||||
callMethod = provider.getClass()
|
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, String.class, Bundle.class);
|
||||||
.getMethod("call", String.class, String.class, String.class, String.class, String.class, Bundle.class);
|
|
||||||
callMethodVersion = 1;
|
callMethodVersion = 1;
|
||||||
} catch (NoSuchMethodException e1) {
|
} catch (NoSuchMethodException e2) {
|
||||||
try {
|
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, Bundle.class);
|
||||||
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, String.class, Bundle.class);
|
callMethodVersion = 2;
|
||||||
callMethodVersion = 2;
|
|
||||||
} catch (NoSuchMethodException e2) {
|
|
||||||
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, Bundle.class);
|
|
||||||
callMethodVersion = 3;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return callMethod;
|
return callMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("PrivateApi")
|
|
||||||
private Object getAttributionSource()
|
|
||||||
throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
|
|
||||||
if (attributionSource == null) {
|
|
||||||
Class<?> cl = Class.forName("android.content.AttributionSource$Builder");
|
|
||||||
Object builder = cl.getConstructor(int.class).newInstance(ServiceManager.USER_ID);
|
|
||||||
cl.getDeclaredMethod("setPackageName", String.class).invoke(builder, ServiceManager.PACKAGE_NAME);
|
|
||||||
attributionSource = cl.getDeclaredMethod("build").invoke(builder);
|
|
||||||
}
|
|
||||||
|
|
||||||
return attributionSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Bundle call(String callMethod, String arg, Bundle extras) {
|
private Bundle call(String callMethod, String arg, Bundle extras) {
|
||||||
try {
|
try {
|
||||||
Method method = getCallMethod();
|
Method method = getCallMethod();
|
||||||
Object[] args;
|
Object[] args;
|
||||||
switch (callMethodVersion) {
|
switch (callMethodVersion) {
|
||||||
case 0:
|
case 0:
|
||||||
args = new Object[]{getAttributionSource(), "settings", callMethod, arg, extras};
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
args = new Object[]{ServiceManager.PACKAGE_NAME, null, "settings", callMethod, arg, extras};
|
args = new Object[]{ServiceManager.PACKAGE_NAME, null, "settings", callMethod, arg, extras};
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 1:
|
||||||
args = new Object[]{ServiceManager.PACKAGE_NAME, "settings", callMethod, arg, extras};
|
args = new Object[]{ServiceManager.PACKAGE_NAME, "settings", callMethod, arg, extras};
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -106,7 +81,7 @@ public class ContentProvider implements Closeable {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return (Bundle) method.invoke(provider, args);
|
return (Bundle) method.invoke(provider, args);
|
||||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException | ClassNotFoundException | InstantiationException e) {
|
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||||
Ln.e("Could not invoke method", e);
|
Ln.e("Could not invoke method", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -314,6 +314,26 @@ public class ControlMessageReaderTest {
|
|||||||
Assert.assertEquals(ControlMessage.TYPE_ROTATE_DEVICE, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_ROTATE_DEVICE, event.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testScanMedia() throws IOException {
|
||||||
|
ControlMessageReader reader = new ControlMessageReader();
|
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
|
dos.writeByte(ControlMessage.TYPE_SCAN_MEDIA);
|
||||||
|
byte[] text = "/sdcard/Download/".getBytes(StandardCharsets.UTF_8);
|
||||||
|
dos.writeInt(text.length);
|
||||||
|
dos.write(text);
|
||||||
|
|
||||||
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
|
reader.readFrom(new ByteArrayInputStream(packet));
|
||||||
|
ControlMessage event = reader.next();
|
||||||
|
|
||||||
|
Assert.assertEquals(ControlMessage.TYPE_SCAN_MEDIA, event.getType());
|
||||||
|
Assert.assertEquals("/sdcard/Download/", event.getText());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMultiEvents() throws IOException {
|
public void testMultiEvents() throws IOException {
|
||||||
ControlMessageReader reader = new ControlMessageReader();
|
ControlMessageReader reader = new ControlMessageReader();
|
||||||
|
|||||||
Reference in New Issue
Block a user