Compare commits

..

27 Commits

Author SHA1 Message Date
Romain Vimont
eb0f339271 Add shortcut to rotate screen
On Ctrl+r, disable auto-rotation (if enabled), set the screen rotation
and re-enable auto-rotation (if it was enabled).

Closes #11 <https://github.com/Genymobile/scrcpy/issues/11>
2019-12-04 22:03:25 +01:00
Romain Vimont
bdd05b4a16 Refactor wrappers for Android SDK classes
Internally, a failure to invoke a method via reflection was partially
managed using exceptions, partially using a null return value.

Handle all errors at the same place, by not catching
NoSuchMethodException too early.
2019-12-04 19:34:20 +01:00
Romain Vimont
525d6d4a75 Try new methods before legacy ones
Use the legacy methods when the new ones do not exist.
2019-12-04 19:32:38 +01:00
Romain Vimont
4041043d1c Merge pull request #967 into dev
Add some tests

<https://github.com/Genymobile/scrcpy/pull/967>
2019-12-04 19:25:58 +01:00
Yu-Chen Lin
fbc86a616c Correct coding style
Signed-off-by: Yu-Chen Lin <npes87184@gmail.com>
2019-12-04 19:24:59 +01:00
Yu-Chen Lin
b2bf25c52c Add test_buffer_util
Signed-off-by: Yu-Chen Lin <npes87184@gmail.com>
2019-12-04 19:24:59 +01:00
Yu-Chen Lin
5eeaed09ae Add test_strquote
Signed-off-by: Yu-Chen Lin <npes87184@gmail.com>
2019-12-04 19:24:59 +01:00
Romain Vimont
3100533e56 Fix "natural scrolling"
> Movements down (scroll backward) generate negative y values and up
> (scroll forward) generate positive y values.

> If direction is SDL_MOUSEWHEEL_FLIPPED the values in x and y will be
> opposite. Multiply by -1 to change them back.

<https://wiki.libsdl.org/SDL_MouseWheelEvent#Remarks>

The x and y values already take the scrolling configuration into
account. Reversing the values when the direction is flipped cancels the
scrolling configuration.

Therefore, just ignore the direction field.

Fixes <https://github.com/Genymobile/scrcpy/issues/966>
2019-12-03 21:10:43 +01:00
Romain Vimont
8a694a9785 Suggest workaround for error 0xfffffc0e
When the hardware encoder is not able to encode at the given definition,
it fails with an error 0xfffffc0e.

It is documented in the FAQ:
<https://github.com/Genymobile/scrcpy/blob/master/FAQ.md#i-get-an-error-could-not-open-video-stream>

But it is better to directly suggest the workaround in the console.
2019-11-30 10:46:22 +01:00
Romain Vimont
26529d377f Use virtual device id to avoid NPE
Inject mouse events using id -1 (virtual device) instead of 0 which
does not exist (and causes the InputDevice to be null).

Fixes <https://github.com/Genymobile/scrcpy/issues/962>
2019-11-29 14:31:19 +01:00
Romain Vimont
15a206b7fc Assert return value of mutex functions
Mutex functions may only fail due to a programming error.

Use assertions in debug builds, and ignore the value in release builds.
2019-11-27 21:40:58 +01:00
Romain Vimont
d0f5a7fd9f Remove unused includes
No mutex is used in decoder.c and stream.c.
2019-11-27 21:40:54 +01:00
Romain Vimont
510caff0cd Replace SDL_assert() by assert()
SDL_assert() open a dialog on assertion failure.

There is no reason not to use assert() directly.
2019-11-27 21:19:46 +01:00
Romain Vimont
b5ebb234dd Replace BUILD_DEBUG by NDEBUG
Use the "standard" NDEBUG definition, which is used by assert().
2019-11-27 21:11:52 +01:00
Romain Vimont
73e8ec1b35 Remove path argument from cmd_execute()
It is always equal to argv[0] (or not used on Windows).
2019-11-27 21:11:52 +01:00
Romain Vimont
8dc11a0286 Fix warnings on Windows
fix warnings reported with -Dwarning_level=2 on Windows.
2019-11-27 21:11:52 +01:00
Romain Vimont
06104a701b Fix windows build
Utilities have been moved to util/, but includes had not been updated
in Windows-specific files.

Ref: dfd0707a29
2019-11-27 13:25:56 +01:00
Romain Vimont
7637a113e3 Compile with warning_level=2 by default 2019-11-26 09:22:20 +01:00
Romain Vimont
31d9d56117 Fix warnings
Fix warnings reported with -Dwarning_level=2.
2019-11-26 09:10:41 +01:00
Romain Vimont
6abb8fd0cd Reformat Java code
Reformated by Android studio to match the 150 characters column defined
in checkstyle.
2019-11-25 17:33:42 +01:00
Romain Vimont
2b845680b0 Initialize Application object to avoid NPE
When an ApplicationInfo is set (commit
90293240cc), some devices (Nvidia Shield
TV) attempt to access the Application object, causing a
NullPointerException.

As a workaround, initialize an Application object.

Fixes <https://github.com/Genymobile/scrcpy/issues/940>
2019-11-24 11:58:40 +01:00
Romain Vimont
ebdc2ee8b5 Rename variable for consistency
Use suffix "Field" for fields variables.
2019-11-24 11:55:42 +01:00
Romain Vimont
dfd0707a29 Move utilities to util/ 2019-11-24 11:53:23 +01:00
Romain Vimont
83ace84280 Restore the .jar extension on the device side
Commit 3da95b52bd renamed
'scrcpy-server.jar' to 'scrcpy-server' to avoid issues on the client
side.

However, removing the extension may cause issues with app_process, so
restore the extension only on the device side.

Fixes <https://github.com/Genymobile/scrcpy/issues/944>
2019-11-22 15:23:57 +01:00
Romain Vimont
c9d886f38b Use the existing constants for device server path 2019-11-22 15:16:00 +01:00
yangfl
7d7f3daff2 Fix aidl option in build_without_gradle.sh
Debian's aidl complains about the missing path for -o option.

Signed-off-by: Romain Vimont <rom@rom1v.com>
2019-11-20 17:16:46 +01:00
Romain Vimont
40c3c57613 Update links to v1.11 in README and BUILD 2019-11-19 23:39:30 +01:00
69 changed files with 594 additions and 361 deletions

View File

@@ -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:

View File

@@ -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`

View File

@@ -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'
]],
]

View File

@@ -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

View File

@@ -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;

View File

@@ -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);

View File

@@ -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:

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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);

View File

@@ -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

View File

@@ -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) {

View File

@@ -5,7 +5,7 @@
#include "config.h"
#include "common.h"
#include "net.h"
#include "util/net.h"
#define DEVICE_NAME_FIELD_LENGTH 64

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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)

View File

@@ -8,7 +8,7 @@
#include "config.h"
#include "common.h"
#include "queue.h"
#include "util/queue.h"
enum recorder_format {
RECORDER_FORMAT_AUTO,

View File

@@ -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;

View File

@@ -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

View File

@@ -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");

View File

@@ -6,7 +6,7 @@
#include "config.h"
#include "command.h"
#include "net.h"
#include "util/net.h"
struct server {
char *serial;

View File

@@ -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;

View File

@@ -8,7 +8,7 @@
#include <SDL2/SDL_thread.h>
#include "config.h"
#include "net.h"
#include "util/net.h"
struct video_buffer;

View File

@@ -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 {

View File

@@ -1,4 +1,4 @@
#include "net.h"
#include "util/net.h"
#include <unistd.h>

View File

@@ -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));

View File

@@ -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) {

View File

@@ -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;
}
}

View File

@@ -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
View 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

View File

@@ -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; \
})

View File

@@ -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) {

View 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;
}

View File

@@ -1,7 +1,7 @@
#include <assert.h>
#include <string.h>
#include "cbuf.h"
#include "util/cbuf.h"
struct int_queue CBUF(int, 32);

View File

@@ -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;
}

View File

@@ -1,6 +1,6 @@
#include <assert.h>
#include <queue.h>
#include "util/queue.h"
struct foo {
int value;

View File

@@ -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;
}

View File

@@ -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')

View File

@@ -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..."

View File

@@ -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;

View File

@@ -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:

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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 + '}';
}
}

View File

@@ -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 + '}';
}
}

View File

@@ -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);
}
});

View File

@@ -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 + '}';
}
}

View File

@@ -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());

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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();