Compare commits
27 Commits
v1.11
...
rotatedevi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb0f339271 | ||
|
|
bdd05b4a16 | ||
|
|
525d6d4a75 | ||
|
|
4041043d1c | ||
|
|
fbc86a616c | ||
|
|
b2bf25c52c | ||
|
|
5eeaed09ae | ||
|
|
3100533e56 | ||
|
|
8a694a9785 | ||
|
|
26529d377f | ||
|
|
15a206b7fc | ||
|
|
d0f5a7fd9f | ||
|
|
510caff0cd | ||
|
|
b5ebb234dd | ||
|
|
73e8ec1b35 | ||
|
|
8dc11a0286 | ||
|
|
06104a701b | ||
|
|
7637a113e3 | ||
|
|
31d9d56117 | ||
|
|
6abb8fd0cd | ||
|
|
2b845680b0 | ||
|
|
ebdc2ee8b5 | ||
|
|
dfd0707a29 | ||
|
|
83ace84280 | ||
|
|
c9d886f38b | ||
|
|
7d7f3daff2 | ||
|
|
40c3c57613 |
6
BUILD.md
6
BUILD.md
@@ -233,10 +233,10 @@ You can then [run](README.md#run) _scrcpy_.
|
||||
|
||||
## Prebuilt server
|
||||
|
||||
- [`scrcpy-server-v1.10.jar`][direct-scrcpy-server]
|
||||
_(SHA-256: cbeb1a4e046f1392c1dc73c3ccffd7f86dec4636b505556ea20929687a119390)_
|
||||
- [`scrcpy-server-v1.11`][direct-scrcpy-server]
|
||||
_(SHA-256: ff3a454012e91d9185cfe8ca7691cea16c43a7dcc08e92fa47ab9f0ea675abd1)_
|
||||
|
||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.10/scrcpy-server-v1.10.jar
|
||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.11/scrcpy-server-v1.11
|
||||
|
||||
Download the prebuilt server somewhere, and specify its path during the Meson
|
||||
configuration:
|
||||
|
||||
15
README.md
15
README.md
@@ -1,4 +1,4 @@
|
||||
# scrcpy (v1.10)
|
||||
# scrcpy (v1.11)
|
||||
|
||||
This application provides display and control of Android devices connected on
|
||||
USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access.
|
||||
@@ -62,13 +62,13 @@ For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
|
||||
For Windows, for simplicity, prebuilt archives with all the dependencies
|
||||
(including `adb`) are available:
|
||||
|
||||
- [`scrcpy-win32-v1.10.zip`][direct-win32]
|
||||
_(SHA-256: f98b400b3764404b33b212e9762dd6f1593ddb766c1480fc2609c94768e4a8e1)_
|
||||
- [`scrcpy-win64-v1.10.zip`][direct-win64]
|
||||
_(SHA-256: 95de34575d873c7e95dfcfb5e74d0f6af4f70b2a5bc6fde0f48d1a05480e3a44)_
|
||||
- [`scrcpy-win32-v1.11.zip`][direct-win32]
|
||||
_(SHA-256: f25ed46e6f3e81e0ff9b9b4df7fe1a4bbd13f8396b7391be0a488b64c675b41e)_
|
||||
- [`scrcpy-win64-v1.11.zip`][direct-win64]
|
||||
_(SHA-256: 3802c9ea0307d437947ff150ec65e53990b0beaacd0c8d0bed19c7650ce141bd)_
|
||||
|
||||
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.10/scrcpy-win32-v1.10.zip
|
||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.10/scrcpy-win64-v1.10.zip
|
||||
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.11/scrcpy-win32-v1.11.zip
|
||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.11/scrcpy-win64-v1.11.zip
|
||||
|
||||
You can also [build the app manually][BUILD].
|
||||
|
||||
@@ -425,6 +425,7 @@ Also see [issue #14].
|
||||
| Click on `POWER` | `Ctrl`+`p` | `Cmd`+`p`
|
||||
| Power on | _Right-click²_ | _Right-click²_
|
||||
| Turn device screen off (keep mirroring)| `Ctrl`+`o` | `Cmd`+`o`
|
||||
| Rotate device screen | `Ctrl`+`r` | `Cmd`+`r`
|
||||
| Expand notification panel | `Ctrl`+`n` | `Cmd`+`n`
|
||||
| Collapse notification panel | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n`
|
||||
| Copy device clipboard to computer | `Ctrl`+`c` | `Cmd`+`c`
|
||||
|
||||
@@ -10,16 +10,16 @@ src = [
|
||||
'src/file_handler.c',
|
||||
'src/fps_counter.c',
|
||||
'src/input_manager.c',
|
||||
'src/net.c',
|
||||
'src/receiver.c',
|
||||
'src/recorder.c',
|
||||
'src/scrcpy.c',
|
||||
'src/screen.c',
|
||||
'src/server.c',
|
||||
'src/str_util.c',
|
||||
'src/tiny_xpm.c',
|
||||
'src/stream.c',
|
||||
'src/tiny_xpm.c',
|
||||
'src/video_buffer.c',
|
||||
'src/util/net.c',
|
||||
'src/util/str_util.c'
|
||||
]
|
||||
|
||||
if not get_option('crossbuild_windows')
|
||||
@@ -85,7 +85,7 @@ endif
|
||||
conf = configuration_data()
|
||||
|
||||
# expose the build type
|
||||
conf.set('BUILD_DEBUG', get_option('buildtype') == 'debug')
|
||||
conf.set('NDEBUG', get_option('buildtype') != 'debug')
|
||||
|
||||
# the version, updated on release
|
||||
conf.set_quoted('SCRCPY_VERSION', meson.project_version())
|
||||
@@ -141,13 +141,16 @@ install_man('scrcpy.1')
|
||||
### TESTS
|
||||
|
||||
tests = [
|
||||
['test_buffer_util', [
|
||||
'tests/test_buffer_util.c'
|
||||
]],
|
||||
['test_cbuf', [
|
||||
'tests/test_cbuf.c',
|
||||
]],
|
||||
['test_control_event_serialize', [
|
||||
'tests/test_control_msg_serialize.c',
|
||||
'src/control_msg.c',
|
||||
'src/str_util.c'
|
||||
'src/util/str_util.c'
|
||||
]],
|
||||
['test_device_event_deserialize', [
|
||||
'tests/test_device_msg_deserialize.c',
|
||||
@@ -158,7 +161,7 @@ tests = [
|
||||
]],
|
||||
['test_strutil', [
|
||||
'tests/test_strutil.c',
|
||||
'src/str_util.c'
|
||||
'src/util/str_util.c'
|
||||
]],
|
||||
]
|
||||
|
||||
|
||||
@@ -195,6 +195,10 @@ turn screen on
|
||||
.B Ctrl+o
|
||||
turn device screen off (keep mirroring)
|
||||
|
||||
.TP
|
||||
.B Ctrl+r
|
||||
rotate device screen
|
||||
|
||||
.TP
|
||||
.B Ctrl+n
|
||||
expand notification panel
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "common.h"
|
||||
#include "log.h"
|
||||
#include "str_util.h"
|
||||
#include "util/log.h"
|
||||
#include "util/str_util.h"
|
||||
|
||||
static const char *adb_command;
|
||||
|
||||
@@ -91,7 +91,7 @@ adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
|
||||
|
||||
memcpy(&cmd[i], adb_cmd, len * sizeof(const char *));
|
||||
cmd[len + i] = NULL;
|
||||
enum process_result r = cmd_execute(cmd[0], cmd, &process);
|
||||
enum process_result r = cmd_execute(cmd, &process);
|
||||
if (r != PROCESS_SUCCESS) {
|
||||
show_adb_err_msg(r, cmd);
|
||||
return PROCESS_NONE;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
# define PRIsizet PRIu32
|
||||
# endif
|
||||
# define PROCESS_NONE NULL
|
||||
# define NO_EXIT_CODE -1u // max value as unsigned
|
||||
typedef HANDLE process_t;
|
||||
typedef DWORD exit_code_t;
|
||||
|
||||
@@ -28,6 +29,7 @@
|
||||
# define PRIsizet "zu"
|
||||
# define PRIexitcode "d"
|
||||
# define PROCESS_NONE -1
|
||||
# define NO_EXIT_CODE -1
|
||||
typedef pid_t process_t;
|
||||
typedef int exit_code_t;
|
||||
|
||||
@@ -35,8 +37,6 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
# define NO_EXIT_CODE -1
|
||||
|
||||
enum process_result {
|
||||
PROCESS_SUCCESS,
|
||||
PROCESS_ERROR_GENERIC,
|
||||
@@ -44,7 +44,7 @@ enum process_result {
|
||||
};
|
||||
|
||||
enum process_result
|
||||
cmd_execute(const char *path, const char *const argv[], process_t *process);
|
||||
cmd_execute(const char *const argv[], process_t *process);
|
||||
|
||||
bool
|
||||
cmd_terminate(process_t pid);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#include "control_msg.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <SDL2/SDL_assert.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "buffer_util.h"
|
||||
#include "log.h"
|
||||
#include "str_util.h"
|
||||
#include "util/buffer_util.h"
|
||||
#include "util/log.h"
|
||||
#include "util/str_util.h"
|
||||
|
||||
static void
|
||||
write_position(uint8_t *buf, const struct position *position) {
|
||||
@@ -27,7 +27,7 @@ write_string(const char *utf8, size_t max_len, unsigned char *buf) {
|
||||
|
||||
static uint16_t
|
||||
to_fixed_point_16(float f) {
|
||||
SDL_assert(f >= 0.0f && f <= 1.0f);
|
||||
assert(f >= 0.0f && f <= 1.0f);
|
||||
uint32_t u = f * 0x1p16f; // 2^16
|
||||
if (u >= 0xffff) {
|
||||
u = 0xffff;
|
||||
@@ -78,6 +78,7 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
||||
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||
case CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
||||
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
||||
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||
// no additional data
|
||||
return 1;
|
||||
default:
|
||||
|
||||
@@ -28,6 +28,7 @@ enum control_msg_type {
|
||||
CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
||||
CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||
CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
|
||||
CONTROL_MSG_TYPE_ROTATE_DEVICE,
|
||||
};
|
||||
|
||||
enum screen_power_mode {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#include "controller.h"
|
||||
|
||||
#include <SDL2/SDL_assert.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "lock_util.h"
|
||||
#include "log.h"
|
||||
#include "util/lock.h"
|
||||
#include "util/log.h"
|
||||
|
||||
bool
|
||||
controller_init(struct controller *controller, socket_t control_socket) {
|
||||
@@ -85,7 +85,8 @@ run_controller(void *data) {
|
||||
}
|
||||
struct control_msg msg;
|
||||
bool non_empty = cbuf_take(&controller->queue, &msg);
|
||||
SDL_assert(non_empty);
|
||||
assert(non_empty);
|
||||
(void) non_empty;
|
||||
mutex_unlock(controller->mutex);
|
||||
|
||||
bool ok = process_msg(controller, &msg);
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
#include <SDL2/SDL_thread.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "cbuf.h"
|
||||
#include "control_msg.h"
|
||||
#include "net.h"
|
||||
#include "receiver.h"
|
||||
#include "util/cbuf.h"
|
||||
#include "util/net.h"
|
||||
|
||||
struct control_msg_queue CBUF(struct control_msg, 64);
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/time.h>
|
||||
#include <SDL2/SDL_assert.h>
|
||||
#include <SDL2/SDL_events.h>
|
||||
#include <SDL2/SDL_mutex.h>
|
||||
#include <SDL2/SDL_thread.h>
|
||||
@@ -10,12 +9,11 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "compat.h"
|
||||
#include "buffer_util.h"
|
||||
#include "events.h"
|
||||
#include "lock_util.h"
|
||||
#include "log.h"
|
||||
#include "recorder.h"
|
||||
#include "video_buffer.h"
|
||||
#include "util/buffer_util.h"
|
||||
#include "util/log.h"
|
||||
|
||||
// set the decoded frame as ready for rendering, and notify
|
||||
static void
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "device.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "log.h"
|
||||
#include "util/log.h"
|
||||
|
||||
bool
|
||||
device_read_info(socket_t device_socket, char *device_name, struct size *size) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "common.h"
|
||||
#include "net.h"
|
||||
#include "util/net.h"
|
||||
|
||||
#define DEVICE_NAME_FIELD_LENGTH 64
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
#include "device_msg.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <SDL2/SDL_assert.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "buffer_util.h"
|
||||
#include "log.h"
|
||||
#include "util/buffer_util.h"
|
||||
#include "util/log.h"
|
||||
|
||||
ssize_t
|
||||
device_msg_deserialize(const unsigned char *buf, size_t len,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#include "file_handler.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <SDL2/SDL_assert.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "command.h"
|
||||
#include "lock_util.h"
|
||||
#include "log.h"
|
||||
#include "util/lock.h"
|
||||
#include "util/log.h"
|
||||
|
||||
#define DEFAULT_PUSH_TARGET "/sdcard/"
|
||||
|
||||
@@ -120,7 +120,8 @@ run_file_handler(void *data) {
|
||||
}
|
||||
struct file_handler_request req;
|
||||
bool non_empty = cbuf_take(&file_handler->queue, &req);
|
||||
SDL_assert(non_empty);
|
||||
assert(non_empty);
|
||||
(void) non_empty;
|
||||
|
||||
process_t process;
|
||||
if (req.action == ACTION_INSTALL_APK) {
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
#include <SDL2/SDL_thread.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "cbuf.h"
|
||||
#include "command.h"
|
||||
#include "util/cbuf.h"
|
||||
|
||||
typedef enum {
|
||||
ACTION_INSTALL_APK,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#include "fps_counter.h"
|
||||
|
||||
#include <SDL2/SDL_assert.h>
|
||||
#include <assert.h>
|
||||
#include <SDL2/SDL_timer.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "lock_util.h"
|
||||
#include "log.h"
|
||||
#include "util/lock.h"
|
||||
#include "util/log.h"
|
||||
|
||||
#define FPS_COUNTER_INTERVAL_MS 1000
|
||||
|
||||
@@ -77,7 +77,7 @@ run_fps_counter(void *data) {
|
||||
uint32_t now = SDL_GetTicks();
|
||||
check_interval_expired(counter, now);
|
||||
|
||||
SDL_assert(counter->next_timestamp > now);
|
||||
assert(counter->next_timestamp > now);
|
||||
uint32_t remaining = counter->next_timestamp - now;
|
||||
|
||||
// ignore the reason (timeout or signaled), we just loop anyway
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#include "input_manager.h"
|
||||
|
||||
#include <SDL2/SDL_assert.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "event_converter.h"
|
||||
#include "lock_util.h"
|
||||
#include "log.h"
|
||||
#include "util/lock.h"
|
||||
#include "util/log.h"
|
||||
|
||||
// Convert window coordinates (as provided by SDL_GetMouseState() to renderer
|
||||
// coordinates (as provided in SDL mouse events)
|
||||
@@ -211,13 +211,23 @@ clipboard_paste(struct controller *controller) {
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
rotate_device(struct controller *controller) {
|
||||
struct control_msg msg;
|
||||
msg.type = CONTROL_MSG_TYPE_ROTATE_DEVICE;
|
||||
|
||||
if (!controller_push_msg(controller, &msg)) {
|
||||
LOGW("Could not request device rotation");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
input_manager_process_text_input(struct input_manager *im,
|
||||
const SDL_TextInputEvent *event) {
|
||||
if (!im->prefer_text) {
|
||||
char c = event->text[0];
|
||||
if (isalpha(c) || c == ' ') {
|
||||
SDL_assert(event->text[1] == '\0');
|
||||
assert(event->text[1] == '\0');
|
||||
// letters and space are handled as raw key event
|
||||
return;
|
||||
}
|
||||
@@ -388,6 +398,11 @@ input_manager_process_key(struct input_manager *im,
|
||||
}
|
||||
}
|
||||
return;
|
||||
case SDLK_r:
|
||||
if (control && cmd && !shift && !repeat && down) {
|
||||
rotate_device(controller);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -550,13 +565,8 @@ convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
|
||||
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;
|
||||
|
||||
to->inject_scroll_event.position = position;
|
||||
|
||||
int mul = from->direction == SDL_MOUSEWHEEL_NORMAL ? 1 : -1;
|
||||
// SDL behavior seems inconsistent between horizontal and vertical scrolling
|
||||
// so reverse the horizontal
|
||||
// <https://wiki.libsdl.org/SDL_MouseWheelEvent#Remarks>
|
||||
to->inject_scroll_event.hscroll = -mul * from->x;
|
||||
to->inject_scroll_event.vscroll = mul * from->y;
|
||||
to->inject_scroll_event.hscroll = from->x;
|
||||
to->inject_scroll_event.vscroll = from->y;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
#ifndef LOCKUTIL_H
|
||||
#define LOCKUTIL_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <SDL2/SDL_mutex.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "log.h"
|
||||
|
||||
static inline void
|
||||
mutex_lock(SDL_mutex *mutex) {
|
||||
if (SDL_LockMutex(mutex)) {
|
||||
LOGC("Could not lock mutex");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
mutex_unlock(SDL_mutex *mutex) {
|
||||
if (SDL_UnlockMutex(mutex)) {
|
||||
LOGC("Could not unlock mutex");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
cond_wait(SDL_cond *cond, SDL_mutex *mutex) {
|
||||
if (SDL_CondWait(cond, mutex)) {
|
||||
LOGC("Could not wait on condition");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static inline int
|
||||
cond_wait_timeout(SDL_cond *cond, SDL_mutex *mutex, uint32_t ms) {
|
||||
int r = SDL_CondWaitTimeout(cond, mutex, ms);
|
||||
if (r < 0) {
|
||||
LOGC("Could not wait on condition with timeout");
|
||||
abort();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static inline void
|
||||
cond_signal(SDL_cond *cond) {
|
||||
if (SDL_CondSignal(cond)) {
|
||||
LOGC("Could not signal a condition");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -10,8 +10,8 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "compat.h"
|
||||
#include "log.h"
|
||||
#include "recorder.h"
|
||||
#include "util/log.h"
|
||||
|
||||
struct args {
|
||||
struct scrcpy_options opts;
|
||||
@@ -175,6 +175,9 @@ static void usage(const char *arg0) {
|
||||
" " CTRL_OR_CMD "+o\n"
|
||||
" turn device screen off (keep mirroring)\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+r\n"
|
||||
" rotate device screen\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+n\n"
|
||||
" expand notification panel\n"
|
||||
"\n"
|
||||
@@ -243,7 +246,7 @@ parse_bit_rate(char *optarg, uint32_t *bit_rate) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (value < 0 || ((uint32_t) -1) / mul < value) {
|
||||
if (value < 0 || ((uint32_t) -1) / mul < (unsigned long) value) {
|
||||
LOGE("Bitrate must be positive and less than 2^32: %s", optarg);
|
||||
return false;
|
||||
}
|
||||
@@ -625,7 +628,7 @@ main(int argc, char *argv[]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef BUILD_DEBUG
|
||||
#ifndef NDEBUG
|
||||
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG);
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#include "receiver.h"
|
||||
|
||||
#include <SDL2/SDL_assert.h>
|
||||
#include <assert.h>
|
||||
#include <SDL2/SDL_clipboard.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "device_msg.h"
|
||||
#include "lock_util.h"
|
||||
#include "log.h"
|
||||
#include "util/lock.h"
|
||||
#include "util/log.h"
|
||||
|
||||
bool
|
||||
receiver_init(struct receiver *receiver, socket_t control_socket) {
|
||||
@@ -23,7 +23,7 @@ receiver_destroy(struct receiver *receiver) {
|
||||
}
|
||||
|
||||
static void
|
||||
process_msg(struct receiver *receiver, struct device_msg *msg) {
|
||||
process_msg(struct device_msg *msg) {
|
||||
switch (msg->type) {
|
||||
case DEVICE_MSG_TYPE_CLIPBOARD:
|
||||
LOGI("Device clipboard copied");
|
||||
@@ -33,7 +33,7 @@ process_msg(struct receiver *receiver, struct device_msg *msg) {
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
process_msgs(struct receiver *receiver, const unsigned char *buf, size_t len) {
|
||||
process_msgs(const unsigned char *buf, size_t len) {
|
||||
size_t head = 0;
|
||||
for (;;) {
|
||||
struct device_msg msg;
|
||||
@@ -45,11 +45,11 @@ process_msgs(struct receiver *receiver, const unsigned char *buf, size_t len) {
|
||||
return head;
|
||||
}
|
||||
|
||||
process_msg(receiver, &msg);
|
||||
process_msg(&msg);
|
||||
device_msg_destroy(&msg);
|
||||
|
||||
head += r;
|
||||
SDL_assert(head <= len);
|
||||
assert(head <= len);
|
||||
if (head == len) {
|
||||
return head;
|
||||
}
|
||||
@@ -64,7 +64,7 @@ run_receiver(void *data) {
|
||||
size_t head = 0;
|
||||
|
||||
for (;;) {
|
||||
SDL_assert(head < DEVICE_MSG_SERIALIZED_MAX_SIZE);
|
||||
assert(head < DEVICE_MSG_SERIALIZED_MAX_SIZE);
|
||||
ssize_t r = net_recv(receiver->control_socket, buf,
|
||||
DEVICE_MSG_SERIALIZED_MAX_SIZE - head);
|
||||
if (r <= 0) {
|
||||
@@ -72,7 +72,7 @@ run_receiver(void *data) {
|
||||
break;
|
||||
}
|
||||
|
||||
ssize_t consumed = process_msgs(receiver, buf, r);
|
||||
ssize_t consumed = process_msgs(buf, r);
|
||||
if (consumed == -1) {
|
||||
// an error occurred
|
||||
break;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <SDL2/SDL_thread.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "net.h"
|
||||
#include "util/net.h"
|
||||
|
||||
// receive events from the device
|
||||
// managed by the controller
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#include "recorder.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <libavutil/time.h>
|
||||
#include <SDL2/SDL_assert.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "compat.h"
|
||||
#include "lock_util.h"
|
||||
#include "log.h"
|
||||
#include "util/lock.h"
|
||||
#include "util/log.h"
|
||||
|
||||
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
|
||||
|
||||
@@ -116,7 +116,7 @@ recorder_get_format_name(enum recorder_format format) {
|
||||
bool
|
||||
recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
|
||||
const char *format_name = recorder_get_format_name(recorder->format);
|
||||
SDL_assert(format_name);
|
||||
assert(format_name);
|
||||
const AVOutputFormat *format = find_muxer(format_name);
|
||||
if (!format) {
|
||||
LOGE("Could not find muxer");
|
||||
@@ -357,7 +357,7 @@ recorder_join(struct recorder *recorder) {
|
||||
bool
|
||||
recorder_push(struct recorder *recorder, const AVPacket *packet) {
|
||||
mutex_lock(recorder->mutex);
|
||||
SDL_assert(!recorder->stopped);
|
||||
assert(!recorder->stopped);
|
||||
|
||||
if (recorder->failed) {
|
||||
// reject any new packet (this will stop the stream)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "common.h"
|
||||
#include "queue.h"
|
||||
#include "util/queue.h"
|
||||
|
||||
enum recorder_format {
|
||||
RECORDER_FORMAT_AUTO,
|
||||
|
||||
@@ -18,15 +18,15 @@
|
||||
#include "file_handler.h"
|
||||
#include "fps_counter.h"
|
||||
#include "input_manager.h"
|
||||
#include "log.h"
|
||||
#include "lock_util.h"
|
||||
#include "net.h"
|
||||
#include "recorder.h"
|
||||
#include "screen.h"
|
||||
#include "server.h"
|
||||
#include "stream.h"
|
||||
#include "tiny_xpm.h"
|
||||
#include "video_buffer.h"
|
||||
#include "util/lock.h"
|
||||
#include "util/log.h"
|
||||
#include "util/net.h"
|
||||
|
||||
static struct server server = SERVER_INITIALIZER;
|
||||
static struct screen screen = SCREEN_INITIALIZER;
|
||||
@@ -103,6 +103,7 @@ sdl_init_and_configure(bool display) {
|
||||
// <https://stackoverflow.com/a/40693139/1987178>
|
||||
static int
|
||||
event_watcher(void *data, SDL_Event *event) {
|
||||
(void) data;
|
||||
if (event->type == SDL_WINDOWEVENT
|
||||
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
|
||||
// called from another thread, not very safe, but it's a workaround!
|
||||
@@ -201,6 +202,7 @@ handle_event(SDL_Event *event, bool control) {
|
||||
|
||||
static bool
|
||||
event_loop(bool display, bool control) {
|
||||
(void) display;
|
||||
#ifdef CONTINUOUS_RESIZING_WORKAROUND
|
||||
if (display) {
|
||||
SDL_AddEventWatch(event_watcher, NULL);
|
||||
@@ -256,6 +258,7 @@ sdl_priority_from_av_level(int level) {
|
||||
|
||||
static void
|
||||
av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
|
||||
(void) avcl;
|
||||
SDL_LogPriority priority = sdl_priority_from_av_level(level);
|
||||
if (priority == 0) {
|
||||
return;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "screen.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
@@ -7,10 +8,10 @@
|
||||
#include "common.h"
|
||||
#include "compat.h"
|
||||
#include "icon.xpm"
|
||||
#include "lock_util.h"
|
||||
#include "log.h"
|
||||
#include "tiny_xpm.h"
|
||||
#include "video_buffer.h"
|
||||
#include "util/lock.h"
|
||||
#include "util/log.h"
|
||||
|
||||
#define DISPLAY_MARGINS 96
|
||||
|
||||
@@ -110,7 +111,7 @@ get_optimal_size(struct size current_size, struct size frame_size) {
|
||||
}
|
||||
|
||||
// w and h must fit into 16 bits
|
||||
SDL_assert_release(w < 0x10000 && h < 0x10000);
|
||||
assert(w < 0x10000 && h < 0x10000);
|
||||
return (struct size) {w, h};
|
||||
}
|
||||
|
||||
@@ -185,8 +186,8 @@ screen_init_rendering(struct screen *screen, const char *window_title,
|
||||
window_flags |= SDL_WINDOW_BORDERLESS;
|
||||
}
|
||||
|
||||
int x = window_x != -1 ? window_x : SDL_WINDOWPOS_UNDEFINED;
|
||||
int y = window_y != -1 ? window_y : SDL_WINDOWPOS_UNDEFINED;
|
||||
int x = window_x != -1 ? window_x : (int) SDL_WINDOWPOS_UNDEFINED;
|
||||
int y = window_y != -1 ? window_y : (int) SDL_WINDOWPOS_UNDEFINED;
|
||||
screen->window = SDL_CreateWindow(window_title, x, y,
|
||||
window_size.width, window_size.height,
|
||||
window_flags);
|
||||
@@ -392,8 +393,8 @@ screen_handle_window_event(struct screen *screen,
|
||||
break;
|
||||
case SDL_WINDOWEVENT_MAXIMIZED:
|
||||
// The backup size must be non-nul.
|
||||
SDL_assert(screen->windowed_window_size_backup.width);
|
||||
SDL_assert(screen->windowed_window_size_backup.height);
|
||||
assert(screen->windowed_window_size_backup.width);
|
||||
assert(screen->windowed_window_size_backup.height);
|
||||
// Revert the last size, it was updated while screen was maximized.
|
||||
screen->windowed_window_size = screen->windowed_window_size_backup;
|
||||
#ifdef DEBUG
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
#include "server.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <libgen.h>
|
||||
#include <stdio.h>
|
||||
#include <SDL2/SDL_assert.h>
|
||||
#include <SDL2/SDL_timer.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "command.h"
|
||||
#include "log.h"
|
||||
#include "net.h"
|
||||
#include "util/log.h"
|
||||
#include "util/net.h"
|
||||
|
||||
#define SOCKET_NAME "scrcpy"
|
||||
#define SERVER_FILENAME "scrcpy-server"
|
||||
|
||||
#define DEFAULT_SERVER_PATH PREFIX "/share/scrcpy/" SERVER_FILENAME
|
||||
#define DEVICE_SERVER_PATH "/data/local/tmp/" SERVER_FILENAME
|
||||
#define DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
|
||||
|
||||
static const char *
|
||||
get_server_path(void) {
|
||||
@@ -124,7 +124,7 @@ execute_server(struct server *server, const struct server_params *params) {
|
||||
sprintf(max_fps_string, "%"PRIu16, params->max_fps);
|
||||
const char *const cmd[] = {
|
||||
"shell",
|
||||
"CLASSPATH=/data/local/tmp/" SERVER_FILENAME,
|
||||
"CLASSPATH=" DEVICE_SERVER_PATH,
|
||||
"app_process",
|
||||
#ifdef SERVER_DEBUGGER
|
||||
# define SERVER_DEBUGGER_PORT "5005"
|
||||
@@ -199,7 +199,7 @@ connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
|
||||
|
||||
static void
|
||||
close_socket(socket_t *socket) {
|
||||
SDL_assert(*socket != INVALID_SOCKET);
|
||||
assert(*socket != INVALID_SOCKET);
|
||||
net_shutdown(*socket, SHUT_RDWR);
|
||||
if (!net_close(*socket)) {
|
||||
LOGW("Could not close socket");
|
||||
@@ -323,7 +323,7 @@ server_stop(struct server *server) {
|
||||
close_socket(&server->control_socket);
|
||||
}
|
||||
|
||||
SDL_assert(server->process != PROCESS_NONE);
|
||||
assert(server->process != PROCESS_NONE);
|
||||
|
||||
if (!cmd_terminate(server->process)) {
|
||||
LOGW("Could not terminate server");
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "command.h"
|
||||
#include "net.h"
|
||||
#include "util/net.h"
|
||||
|
||||
struct server {
|
||||
char *serial;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "stream.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/time.h>
|
||||
#include <SDL2/SDL_assert.h>
|
||||
#include <SDL2/SDL_events.h>
|
||||
#include <SDL2/SDL_mutex.h>
|
||||
#include <SDL2/SDL_thread.h>
|
||||
@@ -10,12 +10,11 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "compat.h"
|
||||
#include "buffer_util.h"
|
||||
#include "decoder.h"
|
||||
#include "events.h"
|
||||
#include "lock_util.h"
|
||||
#include "log.h"
|
||||
#include "recorder.h"
|
||||
#include "util/buffer_util.h"
|
||||
#include "util/log.h"
|
||||
|
||||
#define BUFSIZE 0x10000
|
||||
|
||||
@@ -44,7 +43,8 @@ stream_recv_packet(struct stream *stream, AVPacket *packet) {
|
||||
|
||||
uint64_t pts = buffer_read64be(header);
|
||||
uint32_t len = buffer_read32be(&header[8]);
|
||||
SDL_assert(len);
|
||||
assert(pts == NO_PTS || (pts & 0x8000000000000000) == 0);
|
||||
assert(len);
|
||||
|
||||
if (av_new_packet(packet, len)) {
|
||||
LOGE("Could not allocate packet");
|
||||
@@ -52,12 +52,12 @@ stream_recv_packet(struct stream *stream, AVPacket *packet) {
|
||||
}
|
||||
|
||||
r = net_recv_all(stream->socket, packet->data, len);
|
||||
if (r < len) {
|
||||
if (r < 0 || ((uint32_t) r) < len) {
|
||||
av_packet_unref(packet);
|
||||
return false;
|
||||
}
|
||||
|
||||
packet->pts = pts != NO_PTS ? pts : AV_NOPTS_VALUE;
|
||||
packet->pts = pts != NO_PTS ? (int64_t) pts : AV_NOPTS_VALUE;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -107,8 +107,9 @@ stream_parse(struct stream *stream, AVPacket *packet) {
|
||||
AV_NOPTS_VALUE, AV_NOPTS_VALUE, -1);
|
||||
|
||||
// PARSER_FLAG_COMPLETE_FRAMES is set
|
||||
SDL_assert(r == in_len);
|
||||
SDL_assert(out_len == in_len);
|
||||
assert(r == in_len);
|
||||
(void) r;
|
||||
assert(out_len == in_len);
|
||||
|
||||
if (stream->parser->key_frame == 1) {
|
||||
packet->flags |= AV_PKT_FLAG_KEY;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <SDL2/SDL_thread.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "net.h"
|
||||
#include "util/net.h"
|
||||
|
||||
struct video_buffer;
|
||||
|
||||
|
||||
@@ -17,10 +17,11 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include "log.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
enum process_result
|
||||
cmd_execute(const char *path, const char *const argv[], pid_t *pid) {
|
||||
cmd_execute(const char *const argv[], pid_t *pid) {
|
||||
int fd[2];
|
||||
|
||||
if (pipe(fd) == -1) {
|
||||
@@ -51,7 +52,7 @@ cmd_execute(const char *path, const char *const argv[], pid_t *pid) {
|
||||
// child close read side
|
||||
close(fd[0]);
|
||||
if (fcntl(fd[1], F_SETFD, FD_CLOEXEC) == 0) {
|
||||
execvp(path, (char *const *)argv);
|
||||
execvp(argv[0], (char *const *)argv);
|
||||
if (errno == ENOENT) {
|
||||
ret = PROCESS_ERROR_MISSING_BINARY;
|
||||
} else {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "net.h"
|
||||
#include "util/net.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "command.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "log.h"
|
||||
#include "str_util.h"
|
||||
#include "util/log.h"
|
||||
#include "util/str_util.h"
|
||||
|
||||
static int
|
||||
build_cmd(char *cmd, size_t len, const char *const argv[]) {
|
||||
@@ -19,7 +19,7 @@ build_cmd(char *cmd, size_t len, const char *const argv[]) {
|
||||
}
|
||||
|
||||
enum process_result
|
||||
cmd_execute(const char *path, const char *const argv[], HANDLE *handle) {
|
||||
cmd_execute(const char *const argv[], HANDLE *handle) {
|
||||
STARTUPINFOW si;
|
||||
PROCESS_INFORMATION pi;
|
||||
memset(&si, 0, sizeof(si));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "net.h"
|
||||
#include "util/net.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "log.h"
|
||||
#include "util/log.h"
|
||||
|
||||
bool
|
||||
net_init(void) {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#include "tiny_xpm.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "log.h"
|
||||
#include "util/log.h"
|
||||
|
||||
struct index {
|
||||
char c;
|
||||
@@ -36,7 +37,7 @@ find_color(struct index *index, int len, char c, uint32_t *color) {
|
||||
// (non-const) "char *"
|
||||
SDL_Surface *
|
||||
read_xpm(char *xpm[]) {
|
||||
#if SDL_ASSERT_LEVEL >= 2
|
||||
#ifndef NDEBUG
|
||||
// patch the XPM to change the icon color in debug mode
|
||||
xpm[2] = ". c #CC00CC";
|
||||
#endif
|
||||
@@ -51,24 +52,26 @@ read_xpm(char *xpm[]) {
|
||||
int chars = strtol(endptr + 1, &endptr, 10);
|
||||
|
||||
// sanity checks
|
||||
SDL_assert(0 <= width && width < 256);
|
||||
SDL_assert(0 <= height && height < 256);
|
||||
SDL_assert(0 <= colors && colors < 256);
|
||||
SDL_assert(chars == 1); // this implementation does not support more
|
||||
assert(0 <= width && width < 256);
|
||||
assert(0 <= height && height < 256);
|
||||
assert(0 <= colors && colors < 256);
|
||||
assert(chars == 1); // this implementation does not support more
|
||||
|
||||
(void) chars;
|
||||
|
||||
// init index
|
||||
struct index index[colors];
|
||||
for (int i = 0; i < colors; ++i) {
|
||||
const char *line = xpm[1+i];
|
||||
index[i].c = line[0];
|
||||
SDL_assert(line[1] == '\t');
|
||||
SDL_assert(line[2] == 'c');
|
||||
SDL_assert(line[3] == ' ');
|
||||
assert(line[1] == '\t');
|
||||
assert(line[2] == 'c');
|
||||
assert(line[3] == ' ');
|
||||
if (line[4] == '#') {
|
||||
index[i].color = 0xff000000 | strtol(&line[5], &endptr, 0x10);
|
||||
SDL_assert(*endptr == '\0');
|
||||
assert(*endptr == '\0');
|
||||
} else {
|
||||
SDL_assert(!strcmp("None", &line[4]));
|
||||
assert(!strcmp("None", &line[4]));
|
||||
index[i].color = 0;
|
||||
}
|
||||
}
|
||||
@@ -85,7 +88,8 @@ read_xpm(char *xpm[]) {
|
||||
char c = line[x];
|
||||
uint32_t color;
|
||||
bool color_found = find_color(index, colors, c, &color);
|
||||
SDL_assert(color_found);
|
||||
assert(color_found);
|
||||
(void) color_found;
|
||||
pixels[y * width + x] = color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,8 +36,8 @@ buffer_read32be(const uint8_t *buf) {
|
||||
return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
|
||||
}
|
||||
|
||||
static inline
|
||||
uint64_t buffer_read64be(const uint8_t *buf) {
|
||||
static inline uint64_t
|
||||
buffer_read64be(const uint8_t *buf) {
|
||||
uint32_t msb = buffer_read32be(buf);
|
||||
uint32_t lsb = buffer_read32be(&buf[4]);
|
||||
return ((uint64_t) msb << 32) | lsb;
|
||||
74
app/src/util/lock.h
Normal file
74
app/src/util/lock.h
Normal file
@@ -0,0 +1,74 @@
|
||||
#ifndef LOCK_H
|
||||
#define LOCK_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <SDL2/SDL_mutex.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "log.h"
|
||||
|
||||
static inline void
|
||||
mutex_lock(SDL_mutex *mutex) {
|
||||
int r = SDL_LockMutex(mutex);
|
||||
#ifndef NDEBUG
|
||||
if (r) {
|
||||
LOGC("Could not lock mutex: %s", SDL_GetError());
|
||||
abort();
|
||||
}
|
||||
#else
|
||||
(void) r;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void
|
||||
mutex_unlock(SDL_mutex *mutex) {
|
||||
int r = SDL_UnlockMutex(mutex);
|
||||
#ifndef NDEBUG
|
||||
if (r) {
|
||||
LOGC("Could not unlock mutex: %s", SDL_GetError());
|
||||
abort();
|
||||
}
|
||||
#else
|
||||
(void) r;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void
|
||||
cond_wait(SDL_cond *cond, SDL_mutex *mutex) {
|
||||
int r = SDL_CondWait(cond, mutex);
|
||||
#ifndef NDEBUG
|
||||
if (r) {
|
||||
LOGC("Could not wait on condition: %s", SDL_GetError());
|
||||
abort();
|
||||
}
|
||||
#else
|
||||
(void) r;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline int
|
||||
cond_wait_timeout(SDL_cond *cond, SDL_mutex *mutex, uint32_t ms) {
|
||||
int r = SDL_CondWaitTimeout(cond, mutex, ms);
|
||||
#ifndef NDEBUG
|
||||
if (r < 0) {
|
||||
LOGC("Could not wait on condition with timeout: %s", SDL_GetError());
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
return r;
|
||||
}
|
||||
|
||||
static inline void
|
||||
cond_signal(SDL_cond *cond) {
|
||||
int r = SDL_CondSignal(cond);
|
||||
#ifndef NDEBUG
|
||||
if (r) {
|
||||
LOGC("Could not signal a condition: %s", SDL_GetError());
|
||||
abort();
|
||||
}
|
||||
#else
|
||||
(void) r;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -2,9 +2,9 @@
|
||||
#ifndef QUEUE_H
|
||||
#define QUEUE_H
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <SDL2/SDL_assert.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
// type so that we can "return" it)
|
||||
#define queue_take(PQ, NEXTFIELD, PITEM) \
|
||||
(void) ({ \
|
||||
SDL_assert(!queue_is_empty(PQ)); \
|
||||
assert(!queue_is_empty(PQ)); \
|
||||
*(PITEM) = (PQ)->first; \
|
||||
(PQ)->first = (PQ)->first->NEXTFIELD; \
|
||||
})
|
||||
@@ -1,13 +1,13 @@
|
||||
#include "video_buffer.h"
|
||||
|
||||
#include <SDL2/SDL_assert.h>
|
||||
#include <assert.h>
|
||||
#include <SDL2/SDL_mutex.h>
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "lock_util.h"
|
||||
#include "log.h"
|
||||
#include "util/lock.h"
|
||||
#include "util/log.h"
|
||||
|
||||
bool
|
||||
video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
|
||||
@@ -91,7 +91,7 @@ video_buffer_offer_decoded_frame(struct video_buffer *vb,
|
||||
|
||||
const AVFrame *
|
||||
video_buffer_consume_rendered_frame(struct video_buffer *vb) {
|
||||
SDL_assert(!vb->rendering_frame_consumed);
|
||||
assert(!vb->rendering_frame_consumed);
|
||||
vb->rendering_frame_consumed = true;
|
||||
fps_counter_add_rendered_frame(vb->fps_counter);
|
||||
if (vb->render_expired_frames) {
|
||||
|
||||
76
app/tests/test_buffer_util.c
Normal file
76
app/tests/test_buffer_util.c
Normal file
@@ -0,0 +1,76 @@
|
||||
#include <assert.h>
|
||||
|
||||
#include "util/buffer_util.h"
|
||||
|
||||
static void test_buffer_write16be(void) {
|
||||
uint16_t val = 0xABCD;
|
||||
uint8_t buf[2];
|
||||
|
||||
buffer_write16be(buf, val);
|
||||
|
||||
assert(buf[0] == 0xAB);
|
||||
assert(buf[1] == 0xCD);
|
||||
}
|
||||
|
||||
static void test_buffer_write32be(void) {
|
||||
uint32_t val = 0xABCD1234;
|
||||
uint8_t buf[4];
|
||||
|
||||
buffer_write32be(buf, val);
|
||||
|
||||
assert(buf[0] == 0xAB);
|
||||
assert(buf[1] == 0xCD);
|
||||
assert(buf[2] == 0x12);
|
||||
assert(buf[3] == 0x34);
|
||||
}
|
||||
|
||||
static void test_buffer_write64be(void) {
|
||||
uint64_t val = 0xABCD1234567890EF;
|
||||
uint8_t buf[8];
|
||||
|
||||
buffer_write64be(buf, val);
|
||||
|
||||
assert(buf[0] == 0xAB);
|
||||
assert(buf[1] == 0xCD);
|
||||
assert(buf[2] == 0x12);
|
||||
assert(buf[3] == 0x34);
|
||||
assert(buf[4] == 0x56);
|
||||
assert(buf[5] == 0x78);
|
||||
assert(buf[6] == 0x90);
|
||||
assert(buf[7] == 0xEF);
|
||||
}
|
||||
|
||||
static void test_buffer_read16be(void) {
|
||||
uint8_t buf[2] = {0xAB, 0xCD};
|
||||
|
||||
uint16_t val = buffer_read16be(buf);
|
||||
|
||||
assert(val == 0xABCD);
|
||||
}
|
||||
|
||||
static void test_buffer_read32be(void) {
|
||||
uint8_t buf[4] = {0xAB, 0xCD, 0x12, 0x34};
|
||||
|
||||
uint32_t val = buffer_read32be(buf);
|
||||
|
||||
assert(val == 0xABCD1234);
|
||||
}
|
||||
|
||||
static void test_buffer_read64be(void) {
|
||||
uint8_t buf[8] = {0xAB, 0xCD, 0x12, 0x34,
|
||||
0x56, 0x78, 0x90, 0xEF};
|
||||
|
||||
uint64_t val = buffer_read64be(buf);
|
||||
|
||||
assert(val == 0xABCD1234567890EF);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_buffer_write16be();
|
||||
test_buffer_write32be();
|
||||
test_buffer_write64be();
|
||||
test_buffer_read16be();
|
||||
test_buffer_read32be();
|
||||
test_buffer_read64be();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cbuf.h"
|
||||
#include "util/cbuf.h"
|
||||
|
||||
struct int_queue CBUF(int, 32);
|
||||
|
||||
|
||||
@@ -236,6 +236,21 @@ static void test_serialize_set_screen_power_mode(void) {
|
||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||
}
|
||||
|
||||
static void test_serialize_rotate_device(void) {
|
||||
struct control_msg msg = {
|
||||
.type = CONTROL_MSG_TYPE_ROTATE_DEVICE,
|
||||
};
|
||||
|
||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||
int size = control_msg_serialize(&msg, buf);
|
||||
assert(size == 1);
|
||||
|
||||
const unsigned char expected[] = {
|
||||
CONTROL_MSG_TYPE_ROTATE_DEVICE,
|
||||
};
|
||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_serialize_inject_keycode();
|
||||
test_serialize_inject_text();
|
||||
@@ -248,5 +263,6 @@ int main(void) {
|
||||
test_serialize_get_clipboard();
|
||||
test_serialize_set_clipboard();
|
||||
test_serialize_set_screen_power_mode();
|
||||
test_serialize_rotate_device();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <assert.h>
|
||||
|
||||
#include <queue.h>
|
||||
#include "util/queue.h"
|
||||
|
||||
struct foo {
|
||||
int value;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include "str_util.h"
|
||||
#include "util/str_util.h"
|
||||
|
||||
static void test_xstrncpy_simple(void) {
|
||||
char s[] = "xxxxxxxxxx";
|
||||
@@ -126,6 +127,16 @@ static void test_xstrjoin_truncated_after_sep(void) {
|
||||
assert(!strcmp("abc de ", s));
|
||||
}
|
||||
|
||||
static void test_strquote(void) {
|
||||
const char *s = "abcde";
|
||||
char *out = strquote(s);
|
||||
|
||||
// add '"' at the beginning and the end
|
||||
assert(!strcmp("\"abcde\"", out));
|
||||
|
||||
SDL_free(out);
|
||||
}
|
||||
|
||||
static void test_utf8_truncate(void) {
|
||||
const char *s = "aÉbÔc";
|
||||
assert(strlen(s) == 7); // É and Ô are 2 bytes-wide
|
||||
@@ -166,6 +177,7 @@ int main(void) {
|
||||
test_xstrjoin_truncated_in_token();
|
||||
test_xstrjoin_truncated_before_sep();
|
||||
test_xstrjoin_truncated_after_sep();
|
||||
test_strquote();
|
||||
test_utf8_truncate();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
project('scrcpy', 'c',
|
||||
version: '1.11',
|
||||
meson_version: '>= 0.37',
|
||||
default_options: 'c_std=c11')
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
'warning_level=2',
|
||||
])
|
||||
|
||||
if get_option('compile_app')
|
||||
subdir('app')
|
||||
|
||||
@@ -40,7 +40,7 @@ EOF
|
||||
|
||||
echo "Generating java from aidl..."
|
||||
cd "$SERVER_DIR/src/main/aidl"
|
||||
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o "$CLASSES_DIR" \
|
||||
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o"$CLASSES_DIR" \
|
||||
android/view/IRotationWatcher.aidl
|
||||
|
||||
echo "Compiling java sources..."
|
||||
|
||||
@@ -15,6 +15,7 @@ public final class ControlMessage {
|
||||
public static final int TYPE_GET_CLIPBOARD = 7;
|
||||
public static final int TYPE_SET_CLIPBOARD = 8;
|
||||
public static final int TYPE_SET_SCREEN_POWER_MODE = 9;
|
||||
public static final int TYPE_ROTATE_DEVICE = 10;
|
||||
|
||||
private int type;
|
||||
private String text;
|
||||
@@ -47,8 +48,7 @@ public final class ControlMessage {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static ControlMessage createInjectTouchEvent(int action, long pointerId, Position position, float pressure,
|
||||
int buttons) {
|
||||
public static ControlMessage createInjectTouchEvent(int action, long pointerId, Position position, float pressure, int buttons) {
|
||||
ControlMessage msg = new ControlMessage();
|
||||
msg.type = TYPE_INJECT_TOUCH_EVENT;
|
||||
msg.action = action;
|
||||
|
||||
@@ -76,6 +76,7 @@ public class ControlMessageReader {
|
||||
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||
case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
||||
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||
case ControlMessage.TYPE_ROTATE_DEVICE:
|
||||
msg = ControlMessage.createEmpty(type);
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -13,6 +13,8 @@ import java.io.IOException;
|
||||
|
||||
public class Controller {
|
||||
|
||||
private static final int DEVICE_ID_VIRTUAL = -1;
|
||||
|
||||
private final Device device;
|
||||
private final DesktopConnection connection;
|
||||
private final DeviceMessageSender sender;
|
||||
@@ -21,10 +23,8 @@ public class Controller {
|
||||
|
||||
private long lastTouchDown;
|
||||
private final PointersState pointersState = new PointersState();
|
||||
private final MotionEvent.PointerProperties[] pointerProperties =
|
||||
new MotionEvent.PointerProperties[PointersState.MAX_POINTERS];
|
||||
private final MotionEvent.PointerCoords[] pointerCoords =
|
||||
new MotionEvent.PointerCoords[PointersState.MAX_POINTERS];
|
||||
private final MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[PointersState.MAX_POINTERS];
|
||||
private final MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[PointersState.MAX_POINTERS];
|
||||
|
||||
public Controller(Device device, DesktopConnection connection) {
|
||||
this.device = device;
|
||||
@@ -106,6 +106,9 @@ public class Controller {
|
||||
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
||||
device.setScreenPowerMode(msg.getAction());
|
||||
break;
|
||||
case ControlMessage.TYPE_ROTATE_DEVICE:
|
||||
device.rotateDevice();
|
||||
break;
|
||||
default:
|
||||
// do nothing
|
||||
}
|
||||
@@ -176,8 +179,9 @@ public class Controller {
|
||||
}
|
||||
}
|
||||
|
||||
MotionEvent event = MotionEvent.obtain(lastTouchDown, now, action, pointerCount, pointerProperties,
|
||||
pointerCoords, 0, buttons, 1f, 1f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
|
||||
MotionEvent event = MotionEvent
|
||||
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEVICE_ID_VIRTUAL, 0,
|
||||
InputDevice.SOURCE_TOUCHSCREEN, 0);
|
||||
return injectEvent(event);
|
||||
}
|
||||
|
||||
@@ -198,15 +202,16 @@ public class Controller {
|
||||
coords.setAxisValue(MotionEvent.AXIS_HSCROLL, hScroll);
|
||||
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
|
||||
|
||||
MotionEvent event = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties,
|
||||
pointerCoords, 0, 0, 1f, 1f, 0, 0, InputDevice.SOURCE_MOUSE, 0);
|
||||
MotionEvent event = MotionEvent
|
||||
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEVICE_ID_VIRTUAL, 0,
|
||||
InputDevice.SOURCE_MOUSE, 0);
|
||||
return injectEvent(event);
|
||||
}
|
||||
|
||||
private boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) {
|
||||
long now = SystemClock.uptimeMillis();
|
||||
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD,
|
||||
0, 0, InputDevice.SOURCE_KEYBOARD);
|
||||
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
|
||||
InputDevice.SOURCE_KEYBOARD);
|
||||
return injectEvent(event);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.genymobile.scrcpy;
|
||||
|
||||
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
||||
import com.genymobile.scrcpy.wrappers.SurfaceControl;
|
||||
import com.genymobile.scrcpy.wrappers.WindowManager;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
@@ -170,6 +171,27 @@ public final class Device {
|
||||
Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled).
|
||||
*/
|
||||
public void rotateDevice() {
|
||||
WindowManager wm = serviceManager.getWindowManager();
|
||||
|
||||
boolean accelerometerRotation = !wm.isRotationFrozen();
|
||||
|
||||
int currentRotation = wm.getRotation();
|
||||
int newRotation = (currentRotation & 1) ^ 1; // 0->1, 1->0, 2->1, 3->0
|
||||
String newRotationString = newRotation == 0 ? "portrait" : "landscape";
|
||||
|
||||
Ln.i("Device rotation requested: " + newRotationString);
|
||||
wm.freezeRotation(newRotation);
|
||||
|
||||
// restore auto-rotate if necessary
|
||||
if (accelerometerRotation) {
|
||||
wm.thawRotation();
|
||||
}
|
||||
}
|
||||
|
||||
static Rect flipRect(Rect crop) {
|
||||
return new Rect(crop.top, crop.left, crop.bottom, crop.right);
|
||||
}
|
||||
|
||||
@@ -12,10 +12,7 @@ public final class Ln {
|
||||
private static final String PREFIX = "[server] ";
|
||||
|
||||
enum Level {
|
||||
DEBUG,
|
||||
INFO,
|
||||
WARN,
|
||||
ERROR;
|
||||
DEBUG, INFO, WARN, ERROR
|
||||
}
|
||||
|
||||
private static final Level THRESHOLD = BuildConfig.DEBUG ? Level.DEBUG : Level.INFO;
|
||||
|
||||
@@ -28,8 +28,7 @@ public class Point {
|
||||
return false;
|
||||
}
|
||||
Point point = (Point) o;
|
||||
return x == point.x
|
||||
&& y == point.y;
|
||||
return x == point.x && y == point.y;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -39,9 +38,6 @@ public class Point {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Point{"
|
||||
+ "x=" + x
|
||||
+ ", y=" + y
|
||||
+ '}';
|
||||
return "Point{" + "x=" + x + ", y=" + y + '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,7 @@ public class Position {
|
||||
return false;
|
||||
}
|
||||
Position position = (Position) o;
|
||||
return Objects.equals(point, position.point)
|
||||
&& Objects.equals(screenSize, position.screenSize);
|
||||
return Objects.equals(point, position.point) && Objects.equals(screenSize, position.screenSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -43,10 +42,7 @@ public class Position {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Position{"
|
||||
+ "point=" + point
|
||||
+ ", screenSize=" + screenSize
|
||||
+ '}';
|
||||
return "Position{" + "point=" + point + ", screenSize=" + screenSize + '}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.media.MediaCodec;
|
||||
import android.os.Build;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public final class Server {
|
||||
|
||||
private static final String SERVER_PATH = "/data/local/tmp/scrcpy-server";
|
||||
private static final String SERVER_PATH = "/data/local/tmp/scrcpy-server.jar";
|
||||
|
||||
private Server() {
|
||||
// not instantiable
|
||||
@@ -73,8 +75,8 @@ public final class Server {
|
||||
|
||||
String clientVersion = args[0];
|
||||
if (!clientVersion.equals(BuildConfig.VERSION_NAME)) {
|
||||
throw new IllegalArgumentException("The server version (" + clientVersion + ") does not match the client "
|
||||
+ "(" + BuildConfig.VERSION_NAME + ")");
|
||||
throw new IllegalArgumentException(
|
||||
"The server version (" + clientVersion + ") does not match the client " + "(" + BuildConfig.VERSION_NAME + ")");
|
||||
}
|
||||
|
||||
if (args.length != 8) {
|
||||
@@ -133,11 +135,25 @@ public final class Server {
|
||||
}
|
||||
}
|
||||
|
||||
private static void suggestFix(Throwable e) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (e instanceof MediaCodec.CodecException) {
|
||||
MediaCodec.CodecException mce = (MediaCodec.CodecException) e;
|
||||
if (mce.getErrorCode() == 0xfffffc0e) {
|
||||
Ln.e("The hardware encoder is not able to encode at the given definition.");
|
||||
Ln.e("Try with a lower definition:");
|
||||
Ln.e(" scrcpy -m 1024");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String... args) throws Exception {
|
||||
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
||||
@Override
|
||||
public void uncaughtException(Thread t, Throwable e) {
|
||||
Ln.e("Exception on thread " + t, e);
|
||||
suggestFix(e);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -38,8 +38,7 @@ public final class Size {
|
||||
return false;
|
||||
}
|
||||
Size size = (Size) o;
|
||||
return width == size.width
|
||||
&& height == size.height;
|
||||
return width == size.width && height == size.height;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -49,9 +48,6 @@ public final class Size {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Size{"
|
||||
+ "width=" + width
|
||||
+ ", height=" + height
|
||||
+ '}';
|
||||
return "Size{" + "width=" + width + ", height=" + height + '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Application;
|
||||
import android.app.Instrumentation;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.os.Looper;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public final class Workarounds {
|
||||
private Workarounds() {
|
||||
@@ -48,14 +52,25 @@ public final class Workarounds {
|
||||
applicationInfo.packageName = "com.genymobile.scrcpy";
|
||||
|
||||
// appBindData.appInfo = applicationInfo;
|
||||
Field appInfo = appBindDataClass.getDeclaredField("appInfo");
|
||||
appInfo.setAccessible(true);
|
||||
appInfo.set(appBindData, applicationInfo);
|
||||
Field appInfoField = appBindDataClass.getDeclaredField("appInfo");
|
||||
appInfoField.setAccessible(true);
|
||||
appInfoField.set(appBindData, applicationInfo);
|
||||
|
||||
// activityThread.mBoundApplication = appBindData;
|
||||
Field mBoundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication");
|
||||
mBoundApplicationField.setAccessible(true);
|
||||
mBoundApplicationField.set(activityThread, appBindData);
|
||||
|
||||
// Context ctx = activityThread.getSystemContext();
|
||||
Method getSystemContextMethod = activityThreadClass.getDeclaredMethod("getSystemContext");
|
||||
Context ctx = (Context) getSystemContextMethod.invoke(activityThread);
|
||||
|
||||
Application app = Instrumentation.newApplication(Application.class, ctx);
|
||||
|
||||
// activityThread.mInitialApplication = app;
|
||||
Field mInitialApplicationField = activityThreadClass.getDeclaredField("mInitialApplication");
|
||||
mInitialApplicationField.setAccessible(true);
|
||||
mInitialApplicationField.set(activityThread, app);
|
||||
} catch (Throwable throwable) {
|
||||
// this is a workaround, so failing is not an error
|
||||
Ln.w("Could not fill app info: " + throwable.getMessage());
|
||||
|
||||
@@ -22,47 +22,37 @@ public class ClipboardManager {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
private Method getGetPrimaryClipMethod() {
|
||||
private Method getGetPrimaryClipMethod() throws NoSuchMethodException {
|
||||
if (getPrimaryClipMethod == null) {
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class);
|
||||
} else {
|
||||
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
Ln.e("Could not find method", e);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class);
|
||||
} else {
|
||||
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class);
|
||||
}
|
||||
}
|
||||
return getPrimaryClipMethod;
|
||||
}
|
||||
|
||||
private Method getSetPrimaryClipMethod() {
|
||||
private Method getSetPrimaryClipMethod() throws NoSuchMethodException {
|
||||
if (setPrimaryClipMethod == null) {
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class);
|
||||
} else {
|
||||
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class,
|
||||
String.class, int.class);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
Ln.e("Could not find method", e);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class);
|
||||
} else {
|
||||
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, int.class);
|
||||
}
|
||||
}
|
||||
return setPrimaryClipMethod;
|
||||
}
|
||||
|
||||
private static ClipData getPrimaryClip(Method method, IInterface manager) throws InvocationTargetException,
|
||||
IllegalAccessException {
|
||||
private static ClipData getPrimaryClip(Method method, IInterface manager) throws InvocationTargetException, IllegalAccessException {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
return (ClipData) method.invoke(manager, PACKAGE_NAME);
|
||||
}
|
||||
return (ClipData) method.invoke(manager, PACKAGE_NAME, USER_ID);
|
||||
}
|
||||
|
||||
private static void setPrimaryClip(Method method, IInterface manager, ClipData clipData) throws InvocationTargetException,
|
||||
IllegalAccessException {
|
||||
private static void setPrimaryClip(Method method, IInterface manager, ClipData clipData)
|
||||
throws InvocationTargetException, IllegalAccessException {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
method.invoke(manager, clipData, PACKAGE_NAME);
|
||||
} else {
|
||||
@@ -71,32 +61,26 @@ public class ClipboardManager {
|
||||
}
|
||||
|
||||
public CharSequence getText() {
|
||||
Method method = getGetPrimaryClipMethod();
|
||||
if (method == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Method method = getGetPrimaryClipMethod();
|
||||
ClipData clipData = getPrimaryClip(method, manager);
|
||||
if (clipData == null || clipData.getItemCount() == 0) {
|
||||
return null;
|
||||
}
|
||||
return clipData.getItemAt(0).getText();
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
Ln.e("Could not invoke " + method.getName(), e);
|
||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setText(CharSequence text) {
|
||||
Method method = getSetPrimaryClipMethod();
|
||||
if (method == null) {
|
||||
return;
|
||||
}
|
||||
ClipData clipData = ClipData.newPlainText(null, text);
|
||||
try {
|
||||
Method method = getSetPrimaryClipMethod();
|
||||
ClipData clipData = ClipData.newPlainText(null, text);
|
||||
setPrimaryClip(method, manager, clipData);
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
Ln.e("Could not invoke " + method.getName(), e);
|
||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,26 +21,19 @@ public final class InputManager {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
private Method getInjectInputEventMethod() {
|
||||
private Method getInjectInputEventMethod() throws NoSuchMethodException {
|
||||
if (injectInputEventMethod == null) {
|
||||
try {
|
||||
injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class);
|
||||
} catch (NoSuchMethodException e) {
|
||||
Ln.e("Could not find method", e);
|
||||
}
|
||||
injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class);
|
||||
}
|
||||
return injectInputEventMethod;
|
||||
}
|
||||
|
||||
public boolean injectInputEvent(InputEvent inputEvent, int mode) {
|
||||
Method method = getInjectInputEventMethod();
|
||||
if (method == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return (Boolean) method.invoke(manager, inputEvent, mode);
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
Ln.e("Could not invoke " + method.getName(), e);
|
||||
Method method = getInjectInputEventMethod();
|
||||
return (boolean) method.invoke(manager, inputEvent, mode);
|
||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,28 +17,21 @@ public final class PowerManager {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
private Method getIsScreenOnMethod() {
|
||||
private Method getIsScreenOnMethod() throws NoSuchMethodException {
|
||||
if (isScreenOnMethod == null) {
|
||||
try {
|
||||
@SuppressLint("ObsoleteSdkInt") // we may lower minSdkVersion in the future
|
||||
String methodName = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH ? "isInteractive" : "isScreenOn";
|
||||
isScreenOnMethod = manager.getClass().getMethod(methodName);
|
||||
} catch (NoSuchMethodException e) {
|
||||
Ln.e("Could not find method", e);
|
||||
}
|
||||
@SuppressLint("ObsoleteSdkInt") // we may lower minSdkVersion in the future
|
||||
String methodName = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH ? "isInteractive" : "isScreenOn";
|
||||
isScreenOnMethod = manager.getClass().getMethod(methodName);
|
||||
}
|
||||
return isScreenOnMethod;
|
||||
}
|
||||
|
||||
public boolean isScreenOn() {
|
||||
Method method = getIsScreenOnMethod();
|
||||
if (method == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return (Boolean) method.invoke(manager);
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
Ln.e("Could not invoke " + method.getName(), e);
|
||||
Method method = getIsScreenOnMethod();
|
||||
return (boolean) method.invoke(manager);
|
||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,49 +17,35 @@ public class StatusBarManager {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
private Method getExpandNotificationsPanelMethod() {
|
||||
private Method getExpandNotificationsPanelMethod() throws NoSuchMethodException {
|
||||
if (expandNotificationsPanelMethod == null) {
|
||||
try {
|
||||
expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel");
|
||||
} catch (NoSuchMethodException e) {
|
||||
Ln.e("Could not find method", e);
|
||||
}
|
||||
expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel");
|
||||
}
|
||||
return expandNotificationsPanelMethod;
|
||||
}
|
||||
|
||||
private Method getCollapsePanelsMethod() {
|
||||
private Method getCollapsePanelsMethod() throws NoSuchMethodException {
|
||||
if (collapsePanelsMethod == null) {
|
||||
try {
|
||||
collapsePanelsMethod = manager.getClass().getMethod("collapsePanels");
|
||||
} catch (NoSuchMethodException e) {
|
||||
Ln.e("Could not find method", e);
|
||||
}
|
||||
collapsePanelsMethod = manager.getClass().getMethod("collapsePanels");
|
||||
}
|
||||
return collapsePanelsMethod;
|
||||
}
|
||||
|
||||
public void expandNotificationsPanel() {
|
||||
Method method = getExpandNotificationsPanelMethod();
|
||||
if (method == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Method method = getExpandNotificationsPanelMethod();
|
||||
method.invoke(manager);
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
Ln.e("Could not invoke " + method.getName(), e);
|
||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void collapsePanels() {
|
||||
Method method = getCollapsePanelsMethod();
|
||||
if (method == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Method method = getCollapsePanelsMethod();
|
||||
method.invoke(manager);
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
Ln.e("Could not invoke " + method.getName(), e);
|
||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,29 +84,23 @@ public final class SurfaceControl {
|
||||
}
|
||||
}
|
||||
|
||||
private static Method getGetBuiltInDisplayMethod() {
|
||||
private static Method getGetBuiltInDisplayMethod() throws NoSuchMethodException {
|
||||
if (getBuiltInDisplayMethod == null) {
|
||||
try {
|
||||
// the method signature has changed in Android Q
|
||||
// <https://github.com/Genymobile/scrcpy/issues/586>
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
getBuiltInDisplayMethod = CLASS.getMethod("getBuiltInDisplay", int.class);
|
||||
} else {
|
||||
getBuiltInDisplayMethod = CLASS.getMethod("getInternalDisplayToken");
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
Ln.e("Could not find method", e);
|
||||
// the method signature has changed in Android Q
|
||||
// <https://github.com/Genymobile/scrcpy/issues/586>
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
getBuiltInDisplayMethod = CLASS.getMethod("getBuiltInDisplay", int.class);
|
||||
} else {
|
||||
getBuiltInDisplayMethod = CLASS.getMethod("getInternalDisplayToken");
|
||||
}
|
||||
}
|
||||
return getBuiltInDisplayMethod;
|
||||
}
|
||||
|
||||
public static IBinder getBuiltInDisplay() {
|
||||
Method method = getGetBuiltInDisplayMethod();
|
||||
if (method == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
Method method = getGetBuiltInDisplayMethod();
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
// call getBuiltInDisplay(0)
|
||||
return (IBinder) method.invoke(null, 0);
|
||||
@@ -114,32 +108,25 @@ public final class SurfaceControl {
|
||||
|
||||
// call getInternalDisplayToken()
|
||||
return (IBinder) method.invoke(null);
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
Ln.e("Could not invoke " + method.getName(), e);
|
||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Method getSetDisplayPowerModeMethod() {
|
||||
private static Method getSetDisplayPowerModeMethod() throws NoSuchMethodException {
|
||||
if (setDisplayPowerModeMethod == null) {
|
||||
try {
|
||||
setDisplayPowerModeMethod = CLASS.getMethod("setDisplayPowerMode", IBinder.class, int.class);
|
||||
} catch (NoSuchMethodException e) {
|
||||
Ln.e("Could not find method", e);
|
||||
}
|
||||
setDisplayPowerModeMethod = CLASS.getMethod("setDisplayPowerMode", IBinder.class, int.class);
|
||||
}
|
||||
return setDisplayPowerModeMethod;
|
||||
}
|
||||
|
||||
public static void setDisplayPowerMode(IBinder displayToken, int mode) {
|
||||
Method method = getSetDisplayPowerModeMethod();
|
||||
if (method == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Method method = getSetDisplayPowerModeMethod();
|
||||
method.invoke(null, displayToken, mode);
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
Ln.e("Could not invoke " + method.getName(), e);
|
||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +1,95 @@
|
||||
package com.genymobile.scrcpy.wrappers;
|
||||
|
||||
import com.genymobile.scrcpy.Ln;
|
||||
|
||||
import android.os.IInterface;
|
||||
import android.view.IRotationWatcher;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public final class WindowManager {
|
||||
private final IInterface manager;
|
||||
private Method getRotationMethod;
|
||||
private Method freezeRotationMethod;
|
||||
private Method isRotationFrozenMethod;
|
||||
private Method thawRotationMethod;
|
||||
|
||||
public WindowManager(IInterface manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
public int getRotation() {
|
||||
try {
|
||||
private Method getGetRotationMethod() throws NoSuchMethodException {
|
||||
if (getRotationMethod == null) {
|
||||
Class<?> cls = manager.getClass();
|
||||
try {
|
||||
return (Integer) cls.getMethod("getRotation").invoke(manager);
|
||||
} catch (NoSuchMethodException e) {
|
||||
// method changed since this commit:
|
||||
// https://android.googlesource.com/platform/frameworks/base/+/8ee7285128c3843401d4c4d0412cd66e86ba49e3%5E%21/#F2
|
||||
return (Integer) cls.getMethod("getDefaultDisplayRotation").invoke(manager);
|
||||
getRotationMethod = cls.getMethod("getDefaultDisplayRotation");
|
||||
} catch (NoSuchMethodException e) {
|
||||
// old version
|
||||
getRotationMethod = cls.getMethod("getRotation");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
return getRotationMethod;
|
||||
}
|
||||
|
||||
private Method getFreezeRotationMethod() throws NoSuchMethodException {
|
||||
if (freezeRotationMethod == null) {
|
||||
freezeRotationMethod = manager.getClass().getMethod("freezeRotation", int.class);
|
||||
}
|
||||
return freezeRotationMethod;
|
||||
}
|
||||
|
||||
private Method getIsRotationFrozenMethod() throws NoSuchMethodException {
|
||||
if (isRotationFrozenMethod == null) {
|
||||
isRotationFrozenMethod = manager.getClass().getMethod("isRotationFrozen");
|
||||
}
|
||||
return isRotationFrozenMethod;
|
||||
}
|
||||
|
||||
private Method getThawRotationMethod() throws NoSuchMethodException {
|
||||
if (thawRotationMethod == null) {
|
||||
thawRotationMethod = manager.getClass().getMethod("thawRotation");
|
||||
}
|
||||
return thawRotationMethod;
|
||||
}
|
||||
|
||||
public int getRotation() {
|
||||
try {
|
||||
Method method = getGetRotationMethod();
|
||||
return (int) method.invoke(manager);
|
||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void freezeRotation(int rotation) {
|
||||
try {
|
||||
Method method = getFreezeRotationMethod();
|
||||
method.invoke(manager, rotation);
|
||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRotationFrozen() {
|
||||
try {
|
||||
Method method = getIsRotationFrozenMethod();
|
||||
return (boolean) method.invoke(manager);
|
||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void thawRotation() {
|
||||
try {
|
||||
Method method = getThawRotationMethod();
|
||||
method.invoke(manager);
|
||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,11 +97,12 @@ public final class WindowManager {
|
||||
try {
|
||||
Class<?> cls = manager.getClass();
|
||||
try {
|
||||
cls.getMethod("watchRotation", IRotationWatcher.class).invoke(manager, rotationWatcher);
|
||||
} catch (NoSuchMethodException e) {
|
||||
// display parameter added since this commit:
|
||||
// https://android.googlesource.com/platform/frameworks/base/+/35fa3c26adcb5f6577849fd0df5228b1f67cf2c6%5E%21/#F1
|
||||
cls.getMethod("watchRotation", IRotationWatcher.class, int.class).invoke(manager, rotationWatcher, 0);
|
||||
} catch (NoSuchMethodException e) {
|
||||
// old version
|
||||
cls.getMethod("watchRotation", IRotationWatcher.class).invoke(manager, rotationWatcher);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError(e);
|
||||
|
||||
@@ -240,6 +240,22 @@ public class ControlMessageReaderTest {
|
||||
Assert.assertEquals(Device.POWER_MODE_NORMAL, event.getAction());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseRotateDevice() throws IOException {
|
||||
ControlMessageReader reader = new ControlMessageReader();
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(bos);
|
||||
dos.writeByte(ControlMessage.TYPE_ROTATE_DEVICE);
|
||||
|
||||
byte[] packet = bos.toByteArray();
|
||||
|
||||
reader.readFrom(new ByteArrayInputStream(packet));
|
||||
ControlMessage event = reader.next();
|
||||
|
||||
Assert.assertEquals(ControlMessage.TYPE_ROTATE_DEVICE, event.getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiEvents() throws IOException {
|
||||
ControlMessageReader reader = new ControlMessageReader();
|
||||
|
||||
Reference in New Issue
Block a user