Compare commits

..

28 Commits

Author SHA1 Message Date
Romain Vimont
7cd1e39766 Toggle "raw key events" via shortcut
The "raw key events" mode may need to be toggled while scrcpy is
running (e.g. to type text then play a game).

Remove the command-line argument and bind Ctrl+k to toggle the mode.
2018-08-15 21:07:50 +02:00
Romain Vimont
9de332bbe5 Add "raw key events" mode
Add a command-line option to enable "raw key events" mode
(-k,--raw-key-events).

This disable text inputs and forwards "text" key events (which are
not forwarded by default).

This is helpful for gaming:
<https://github.com/Genymobile/scrcpy/issues/87>
<https://github.com/Genymobile/scrcpy/issues/127>
2018-08-15 20:29:07 +02:00
Romain Vimont
3b5e54278e Update FFmpeg (4.0.2) for Windows
Include the last version of FFmpeg in Windows releases.
2018-08-15 19:30:02 +02:00
Romain Vimont
dd3ba685e4 Update platform-tools (28.0.0) for Windows
Include the last version of adb in Windows releases.

Fixes <https://github.com/Genymobile/scrcpy/issues/224>.
2018-08-15 19:30:02 +02:00
Romain Vimont
89e0203682 Add missing include for lock_util.h
lock_util.c did not include lock_util.h. This was catched by the gcc
option -Wmissing-prototypes.
2018-08-15 19:30:02 +02:00
Romain Vimont
536b31829a Separate multi-words filenames by '_'
Rename foobar.ext to foo_bar.ext.

<https://github.com/Genymobile/scrcpy/pull/226#discussion_r209454865>
2018-08-15 19:30:01 +02:00
Romain Vimont
f3f704d1ed Document "push file" feature
Document how to push a file to /sdcard/ in the shortcuts list.
2018-08-15 17:19:32 +02:00
Romain Vimont
6581f9feb9 Make request_queue functions static
These functions are local to file_handler.c.
2018-08-15 17:19:32 +02:00
Romain Vimont
359685b1db Simplify SDL_assert() calls
SDL_assert() already prevents "unused variable" warnings.
2018-08-15 17:19:32 +02:00
Romain Vimont
4527be4cde Add missing include config.h
When config.h is not included, BUILD_DEBUG is not set.
2018-08-15 17:19:32 +02:00
Romain Vimont
92d1aff85f Merge branch 'npes87184:push_file_to_sdcard' into dev (#226)
support drag & drop file to device /sdcard
2018-08-15 17:18:15 +02:00
npes87184
66f45f9dae Support drag&drop a file to transfer it to device
Signed-off-by: npes87184 <npes87184@gmail.com>
2018-08-15 17:12:10 +02:00
npes87184
aa97eed24b installer -> file_handler
Signed-off-by: npes87184 <npes87184@gmail.com>
2018-08-15 17:11:41 +02:00
Romain Vimont
2daeb1fd5f Reset current installer process
The current_process field was never reset after an installation is
complete. As a consequence, installer_stop() attempted to terminate it,
leading to a warning, at best.
2018-08-15 17:10:07 +02:00
Grief
cde0b3d248 Return non-zero value on connection loss
Make scrscpy to return 0 exit code only in case when the user closes its
app, otherwise, i.e. in case of connection loss, return 1.
2018-08-15 14:20:45 +02:00
Romain Vimont
6fa209fa82 Remove AINPUT_SOURCE_ANY value
In the Android input header file, an enum has a value taking more than
31 bits, leading to the following warning:

    ISO C restricts enumerator values to range of ‘int’

Since we don't use it, remove it.
2018-08-12 15:35:29 +02:00
Romain Vimont
b37c0f6cd6 Replace Uint32 by int to fix warnings in tinyxpm 2018-08-12 15:35:25 +02:00
Romain Vimont
c4a1fc746b Explicitly use ISO C11
ISO C99 doesn't support unnamed structs/unions.
2018-08-12 15:21:30 +02:00
npes87184
a3ab92226d Destroy mutex if strdup failed
Signed-off-by: npes87184 <npes87184@gmail.com>
2018-08-12 14:57:36 +02:00
Romain Vimont
ec66b3be82 Merge pull request #225 from npes87184/master
remove redundant semicolon
2018-08-12 14:56:13 +02:00
npes87184
f8ef4f1cf7 remove redundant semicolon
Signed-off-by: npes87184 <npes87184@gmail.com>
2018-08-11 21:16:36 +08:00
Romain Vimont
9e683b7dac Update links to v1.3 in README 2018-08-09 19:26:31 +02:00
Romain Vimont
7e42358a7b Bump version to 1.3 2018-08-09 19:14:17 +02:00
Romain Vimont
caa9e30004 Add crop feature
Add an option to crop the screen on the server. This allows to mirror
only part of the device screen.
2018-08-09 19:12:27 +02:00
Romain Vimont
e85010fbc2 Move annotation comment
This confused the Android Studio code formatter.
2018-08-09 18:23:38 +02:00
Romain Vimont
820cd2bb54 Extract video size computation
One method, one thing.
2018-08-09 18:22:52 +02:00
Romain Vimont
8793c104ee Increase "adb forward" connection attempts
5 seconds might not be sufficient:
<https://github.com/Genymobile/scrcpy/issues/213>

Increase to 10 seconds (it does not harm).
2018-08-09 18:22:42 +02:00
Romain Vimont
fca806e095 Do not call deprecated av_register_all()
av_register_all() is deprecated in FFmpeg since this commit:
<http://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=0694d8702421e7aff1340038559c438b61bb30dd>

It is now useless to call it:
<https://ffmpeg.org/pipermail/ffmpeg-devel/2018-February/225051.html>

Fixes <https://github.com/Genymobile/scrcpy/issues/203>.
2018-08-09 18:18:22 +02:00
47 changed files with 627 additions and 434 deletions

View File

@@ -100,10 +100,10 @@ dist-win32: build-server build-win32 build-win32-noconsole
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server.jar "$(DIST)/$(WIN32_TARGET_DIR)/"
cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp "$(WIN32_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/scrcpy-noconsole.exe"
cp prebuilt-deps/ffmpeg-4.0-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0-win32-shared/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0.2-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0.2-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0.2-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0.2-win32-shared/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
@@ -114,10 +114,10 @@ dist-win64: build-server build-win64 build-win64-noconsole
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server.jar "$(DIST)/$(WIN64_TARGET_DIR)/"
cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp "$(WIN64_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/scrcpy-noconsole.exe"
cp prebuilt-deps/ffmpeg-4.0-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0.2-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0.2-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0.2-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0.2-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"

View File

@@ -1,4 +1,4 @@
# scrcpy (v1.2)
# scrcpy (v1.3)
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.
@@ -79,13 +79,13 @@ Two [AUR] packages have been created by users:
For Windows, for simplicity, prebuilt archives with all the dependencies
(including `adb`) are available:
- [`scrcpy-win32-v1.2.zip`][direct-win32].
_(SHA-256: a1fe1de67ec75dcf970ca5d97a04c26ff0f2d61871f2ef51b6f2f0bf666966b2)_
- [`scrcpy-win64-v1.2.zip`][direct-win64].
_(SHA-256: 35ae3bcee51771e7c51b8a8be87aef2295c9f267606a7cf83ebb0a4d583ef536)_
- [`scrcpy-win32-v1.3.zip`][direct-win32].
_(SHA-256: 51a2990e631ed469a7a86ff38107d517a91d313fb3f8327eb7bc71dde40870b5)_
- [`scrcpy-win64-v1.3.zip`][direct-win64].
_(SHA-256: 0768a80d3d600d0bbcd220ca150ae88a3a58d1fe85c308a8c61f44480b711e43)_
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.2/scrcpy-win32-v1.2.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.2/scrcpy-win64-v1.2.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.3/scrcpy-win32-v1.3.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.3/scrcpy-win64-v1.3.zip
Instead, you may want to build it manually.
@@ -247,10 +247,10 @@ Since the server binary, that will be pushed to the Android device, does not
depend on your system and architecture, you may want to use the prebuilt binary
instead:
- [`scrcpy-server-v1.2.jar`][direct-scrcpy-server].
_(SHA-256: cb39654ed2fda3d30ddff292806950ccc5c394375ea12b974f790c7f38f61f60)_
- [`scrcpy-server-v1.3.jar`][direct-scrcpy-server].
_(SHA-256: 0f9a5a217f33f0ed7a1498ceb3c0cccf31c53533893aa952e674c1571d2740c1)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.2/scrcpy-server-v1.2.jar
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.3/scrcpy-server-v1.3.jar
In that case, the build does not require Java or the Android SDK.
@@ -294,6 +294,12 @@ screen is smaller, or cannot decode such a high definition):
scrcpy -m 1024
```
The device screen may be cropped to mirror only part of the screen:
```bash
scrcpy -c 1224:1440:0:0 # 1224x1440 at offset (0,0)
```
If several devices are listed in `adb devices`, you must specify the _serial_:
```bash
@@ -332,11 +338,15 @@ To run without installing:
| turn screen on | _Right-click²_ |
| paste computer clipboard to device | `Ctrl`+`v` |
| enable/disable FPS counter (on stdout) | `Ctrl`+`i` |
| toggle "raw key events" mode ([#87]) | `Ctrl`+`k` |
| install APK from computer | drag & drop APK file |
| push file to `/sdcard/` | drag & drop non-APK file |
_¹Double-click on black borders to remove them._
_²Right-click turns the screen on if it was off, presses BACK otherwise._
[#87]: https://github.com/Genymobile/scrcpy/issues/87
## Why _scrcpy_?

View File

@@ -1,22 +1,22 @@
src = [
'src/main.c',
'src/command.c',
'src/controlevent.c',
'src/control_event.c',
'src/controller.c',
'src/convert.c',
'src/decoder.c',
'src/device.c',
'src/fpscounter.c',
'src/file_handler.c',
'src/fps_counter.c',
'src/frames.c',
'src/inputmanager.c',
'src/installer.c',
'src/lockutil.c',
'src/input_manager.c',
'src/lock_util.c',
'src/net.c',
'src/scrcpy.c',
'src/screen.c',
'src/server.c',
'src/strutil.c',
'src/tinyxpm.c',
'src/str_util.c',
'src/tiny_xpm.c',
]
if not get_option('crossbuild_windows')
@@ -85,7 +85,7 @@ conf = configuration_data()
conf.set('BUILD_DEBUG', get_option('buildtype') == 'debug')
# the version, updated on release
conf.set_quoted('SCRCPY_VERSION', '1.2')
conf.set_quoted('SCRCPY_VERSION', '1.3')
# the prefix used during configuration (meson --prefix=PREFIX)
conf.set_quoted('PREFIX', get_option('prefix'))
@@ -147,9 +147,9 @@ executable('scrcpy', src, dependencies: dependencies, include_directories: src_d
### TESTS
tests = [
['test_control_event_queue', ['tests/test_control_event_queue.c', 'src/controlevent.c']],
['test_control_event_serialize', ['tests/test_control_event_serialize.c', 'src/controlevent.c']],
['test_strutil', ['tests/test_strutil.c', 'src/strutil.c']],
['test_control_event_queue', ['tests/test_control_event_queue.c', 'src/control_event.c']],
['test_control_event_serialize', ['tests/test_control_event_serialize.c', 'src/control_event.c']],
['test_strutil', ['tests/test_strutil.c', 'src/str_util.c']],
]
foreach t : tests

View File

@@ -777,9 +777,6 @@ enum android_input_source {
AINPUT_SOURCE_JOYSTICK = 0x01000000 | AINPUT_SOURCE_CLASS_JOYSTICK,
/** rotary encoder */
AINPUT_SOURCE_ROTARY_ENCODER = 0x00400000 | AINPUT_SOURCE_CLASS_NONE,
/** any */
AINPUT_SOURCE_ANY = 0xffffff00,
};
/**

View File

@@ -1,9 +1,9 @@
#include "controlevent.h"
#include "control_event.h"
#include <SDL2/SDL_stdinc.h>
#include <string.h>
#include "lockutil.h"
#include "lock_util.h"
#include "log.h"
static inline void write16(Uint8 *buf, Uint16 value) {

View File

@@ -1,6 +1,8 @@
#include "controller.h"
#include "lockutil.h"
#include <SDL2/SDL_assert.h>
#include "config.h"
#include "lock_util.h"
#include "log.h"
SDL_bool controller_init(struct controller *controller, socket_t video_socket) {
@@ -65,12 +67,8 @@ static int run_controller(void *data) {
break;
}
struct control_event event;
#ifdef BUILD_DEBUG
bool non_empty = control_event_queue_take(&controller->queue, &event);
SDL_bool non_empty = control_event_queue_take(&controller->queue, &event);
SDL_assert(non_empty);
#else
control_event_queue_take(&controller->queue, &event);
#endif
mutex_unlock(controller->mutex);
SDL_bool ok = process_event(controller, &event);

View File

@@ -1,7 +1,7 @@
#ifndef CONTROL_H
#define CONTROL_H
#include "controlevent.h"
#include "control_event.h"
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h>

View File

@@ -70,6 +70,83 @@ static enum android_metastate convert_meta_state(SDL_Keymod mod) {
return autocomplete_metastate(metastate);
}
// only used if raw_key_events is enabled
static SDL_bool convert_text_keycode(SDL_Keycode from, enum android_keycode *to) {
switch (from) {
MAP(SDLK_SPACE, AKEYCODE_SPACE);
MAP(SDLK_HASH, AKEYCODE_POUND);
MAP(SDLK_QUOTE, AKEYCODE_APOSTROPHE);
MAP(SDLK_ASTERISK, AKEYCODE_STAR);
MAP(SDLK_COMMA, AKEYCODE_COMMA);
MAP(SDLK_MINUS, AKEYCODE_MINUS);
MAP(SDLK_PERIOD, AKEYCODE_PERIOD);
MAP(SDLK_SLASH, AKEYCODE_SLASH);
MAP(SDLK_0, AKEYCODE_0);
MAP(SDLK_1, AKEYCODE_1);
MAP(SDLK_2, AKEYCODE_2);
MAP(SDLK_3, AKEYCODE_3);
MAP(SDLK_4, AKEYCODE_4);
MAP(SDLK_5, AKEYCODE_5);
MAP(SDLK_6, AKEYCODE_6);
MAP(SDLK_7, AKEYCODE_7);
MAP(SDLK_8, AKEYCODE_8);
MAP(SDLK_9, AKEYCODE_9);
MAP(SDLK_SEMICOLON, AKEYCODE_SEMICOLON);
MAP(SDLK_EQUALS, AKEYCODE_EQUALS);
MAP(SDLK_AT, AKEYCODE_AT);
MAP(SDLK_LEFTBRACKET, AKEYCODE_LEFT_BRACKET);
MAP(SDLK_BACKSLASH, AKEYCODE_BACKSLASH);
MAP(SDLK_RIGHTBRACKET, AKEYCODE_RIGHT_BRACKET);
MAP(SDLK_BACKQUOTE, AKEYCODE_GRAVE);
MAP(SDLK_a, AKEYCODE_A);
MAP(SDLK_b, AKEYCODE_B);
MAP(SDLK_c, AKEYCODE_C);
MAP(SDLK_d, AKEYCODE_D);
MAP(SDLK_e, AKEYCODE_E);
MAP(SDLK_f, AKEYCODE_F);
MAP(SDLK_g, AKEYCODE_G);
MAP(SDLK_h, AKEYCODE_H);
MAP(SDLK_i, AKEYCODE_I);
MAP(SDLK_j, AKEYCODE_J);
MAP(SDLK_k, AKEYCODE_K);
MAP(SDLK_l, AKEYCODE_L);
MAP(SDLK_m, AKEYCODE_M);
MAP(SDLK_n, AKEYCODE_N);
MAP(SDLK_o, AKEYCODE_O);
MAP(SDLK_p, AKEYCODE_P);
MAP(SDLK_q, AKEYCODE_Q);
MAP(SDLK_r, AKEYCODE_R);
MAP(SDLK_s, AKEYCODE_S);
MAP(SDLK_t, AKEYCODE_T);
MAP(SDLK_u, AKEYCODE_U);
MAP(SDLK_v, AKEYCODE_V);
MAP(SDLK_w, AKEYCODE_W);
MAP(SDLK_x, AKEYCODE_X);
MAP(SDLK_y, AKEYCODE_Y);
MAP(SDLK_z, AKEYCODE_Z);
MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER);
MAP(SDLK_KP_1, AKEYCODE_NUMPAD_1);
MAP(SDLK_KP_2, AKEYCODE_NUMPAD_2);
MAP(SDLK_KP_3, AKEYCODE_NUMPAD_3);
MAP(SDLK_KP_4, AKEYCODE_NUMPAD_4);
MAP(SDLK_KP_5, AKEYCODE_NUMPAD_5);
MAP(SDLK_KP_6, AKEYCODE_NUMPAD_6);
MAP(SDLK_KP_7, AKEYCODE_NUMPAD_7);
MAP(SDLK_KP_8, AKEYCODE_NUMPAD_8);
MAP(SDLK_KP_9, AKEYCODE_NUMPAD_9);
MAP(SDLK_KP_0, AKEYCODE_NUMPAD_0);
MAP(SDLK_KP_DIVIDE, AKEYCODE_NUMPAD_DIVIDE);
MAP(SDLK_KP_MULTIPLY, AKEYCODE_NUMPAD_MULTIPLY);
MAP(SDLK_KP_MINUS, AKEYCODE_NUMPAD_SUBTRACT);
MAP(SDLK_KP_PLUS, AKEYCODE_NUMPAD_ADD);
MAP(SDLK_KP_PERIOD, AKEYCODE_NUMPAD_DOT);
MAP(SDLK_KP_EQUALS, AKEYCODE_NUMPAD_EQUALS);
MAP(SDLK_KP_LEFTPAREN, AKEYCODE_NUMPAD_LEFT_PAREN);
MAP(SDLK_KP_RIGHTPAREN, AKEYCODE_NUMPAD_RIGHT_PAREN);
FAIL;
}
}
static SDL_bool convert_keycode(SDL_Keycode from, enum android_keycode *to) {
switch (from) {
MAP(SDLK_RETURN, AKEYCODE_ENTER);
@@ -119,7 +196,8 @@ static enum android_motionevent_buttons convert_mouse_buttons(Uint32 state) {
}
SDL_bool input_key_from_sdl_to_android(const SDL_KeyboardEvent *from,
struct control_event *to) {
struct control_event *to,
SDL_bool raw_key_events) {
to->type = CONTROL_EVENT_TYPE_KEYCODE;
if (!convert_keycode_action(from->type, &to->keycode_event.action)) {
@@ -127,7 +205,10 @@ SDL_bool input_key_from_sdl_to_android(const SDL_KeyboardEvent *from,
}
if (!convert_keycode(from->keysym.sym, &to->keycode_event.keycode)) {
return SDL_FALSE;
if (!raw_key_events ||
!convert_text_keycode(from->keysym.sym, &to->keycode_event.keycode)) {
return SDL_FALSE;
}
}
to->keycode_event.metastate = convert_meta_state(from->keysym.mod);

View File

@@ -3,7 +3,7 @@
#include <SDL2/SDL_stdinc.h>
#include <SDL2/SDL_events.h>
#include "controlevent.h"
#include "control_event.h"
struct complete_mouse_motion_event {
SDL_MouseMotionEvent *mouse_motion_event;
@@ -16,7 +16,9 @@ struct complete_mouse_wheel_event {
};
SDL_bool input_key_from_sdl_to_android(const SDL_KeyboardEvent *from,
struct control_event *to);
struct control_event *to,
SDL_bool raw_key_events);
SDL_bool mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
struct size screen_size,
struct control_event *to);

View File

@@ -9,7 +9,7 @@
#include "config.h"
#include "events.h"
#include "frames.h"
#include "lockutil.h"
#include "lock_util.h"
#include "log.h"
#define BUFSIZE 0x10000

View File

@@ -7,6 +7,7 @@
#include "net.h"
#define DEVICE_NAME_FIELD_LENGTH 64
#define DEVICE_SDCARD_PATH "/sdcard/"
// name must be at least DEVICE_NAME_FIELD_LENGTH bytes
SDL_bool device_read_info(socket_t device_socket, char *name, struct size *frame_size);

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

@@ -0,0 +1,231 @@
#include "file_handler.h"
#include <string.h>
#include <SDL2/SDL_assert.h>
#include "config.h"
#include "command.h"
#include "device.h"
#include "lock_util.h"
#include "log.h"
struct request {
file_handler_action_t action;
const char *file;
};
static struct request *request_new(file_handler_action_t action, const char *file) {
struct request *req = SDL_malloc(sizeof(*req));
if (!req) {
return NULL;
}
req->action = action;
req->file = file;
return req;
}
static void request_free(struct request *req) {
if (!req) {
return;
}
SDL_free((void *) req->file);
SDL_free((void *) req);
}
static SDL_bool request_queue_is_empty(const struct request_queue *queue) {
return queue->head == queue->tail;
}
static SDL_bool request_queue_is_full(const struct request_queue *queue) {
return (queue->head + 1) % REQUEST_QUEUE_SIZE == queue->tail;
}
static SDL_bool request_queue_init(struct request_queue *queue) {
queue->head = 0;
queue->tail = 0;
return SDL_TRUE;
}
static void request_queue_destroy(struct request_queue *queue) {
int i = queue->tail;
while (i != queue->head) {
request_free(queue->reqs[i]);
i = (i + 1) % REQUEST_QUEUE_SIZE;
}
}
static SDL_bool request_queue_push(struct request_queue *queue, struct request *req) {
if (request_queue_is_full(queue)) {
return SDL_FALSE;
}
queue->reqs[queue->head] = req;
queue->head = (queue->head + 1) % REQUEST_QUEUE_SIZE;
return SDL_TRUE;
}
static SDL_bool request_queue_take(struct request_queue *queue, struct request **req) {
if (request_queue_is_empty(queue)) {
return SDL_FALSE;
}
// transfer ownership
*req = queue->reqs[queue->tail];
queue->tail = (queue->tail + 1) % REQUEST_QUEUE_SIZE;
return SDL_TRUE;
}
SDL_bool file_handler_init(struct file_handler *file_handler, const char *serial) {
if (!request_queue_init(&file_handler->queue)) {
return SDL_FALSE;
}
if (!(file_handler->mutex = SDL_CreateMutex())) {
return SDL_FALSE;
}
if (!(file_handler->event_cond = SDL_CreateCond())) {
SDL_DestroyMutex(file_handler->mutex);
return SDL_FALSE;
}
if (serial) {
file_handler->serial = SDL_strdup(serial);
if (!file_handler->serial) {
LOGW("Cannot strdup serial");
SDL_DestroyMutex(file_handler->mutex);
return SDL_FALSE;
}
} else {
file_handler->serial = NULL;
}
// lazy initialization
file_handler->initialized = SDL_FALSE;
file_handler->stopped = SDL_FALSE;
file_handler->current_process = PROCESS_NONE;
return SDL_TRUE;
}
void file_handler_destroy(struct file_handler *file_handler) {
SDL_DestroyCond(file_handler->event_cond);
SDL_DestroyMutex(file_handler->mutex);
request_queue_destroy(&file_handler->queue);
SDL_free((void *) file_handler->serial);
}
static process_t install_apk(const char *serial, const char *file) {
return adb_install(serial, file);
}
static process_t push_file(const char *serial, const char *file) {
return adb_push(serial, file, DEVICE_SDCARD_PATH);
}
SDL_bool file_handler_request(struct file_handler *file_handler,
file_handler_action_t action,
const char *file) {
SDL_bool res;
// start file_handler if it's used for the first time
if (!file_handler->initialized) {
if (!file_handler_start(file_handler)) {
return SDL_FALSE;
}
file_handler->initialized = SDL_TRUE;
}
LOGI("Request to %s %s", action == ACTION_INSTALL_APK ? "install" : "push", file);
struct request *req = request_new(action, file);
if (!req) {
LOGE("Could not create request");
return SDL_FALSE;
}
mutex_lock(file_handler->mutex);
SDL_bool was_empty = request_queue_is_empty(&file_handler->queue);
res = request_queue_push(&file_handler->queue, req);
if (was_empty) {
cond_signal(file_handler->event_cond);
}
mutex_unlock(file_handler->mutex);
return res;
}
static int run_file_handler(void *data) {
struct file_handler *file_handler = data;
for (;;) {
mutex_lock(file_handler->mutex);
file_handler->current_process = PROCESS_NONE;
while (!file_handler->stopped && request_queue_is_empty(&file_handler->queue)) {
cond_wait(file_handler->event_cond, file_handler->mutex);
}
if (file_handler->stopped) {
// stop immediately, do not process further events
mutex_unlock(file_handler->mutex);
break;
}
struct request *req;
SDL_bool non_empty = request_queue_take(&file_handler->queue, &req);
SDL_assert(non_empty);
process_t process;
if (req->action == ACTION_INSTALL_APK) {
LOGI("Installing %s...", req->file);
process = install_apk(file_handler->serial, req->file);
} else {
LOGI("Pushing %s...", req->file);
process = push_file(file_handler->serial, req->file);
}
file_handler->current_process = process;
mutex_unlock(file_handler->mutex);
if (req->action == ACTION_INSTALL_APK) {
if (process_check_success(process, "adb install")) {
LOGI("%s successfully installed", req->file);
} else {
LOGE("Failed to install %s", req->file);
}
} else {
if (process_check_success(process, "adb push")) {
LOGI("%s successfully pushed to /sdcard/", req->file);
} else {
LOGE("Failed to push %s to /sdcard/", req->file);
}
}
request_free(req);
}
return 0;
}
SDL_bool file_handler_start(struct file_handler *file_handler) {
LOGD("Starting file_handler thread");
file_handler->thread = SDL_CreateThread(run_file_handler, "file_handler", file_handler);
if (!file_handler->thread) {
LOGC("Could not start file_handler thread");
return SDL_FALSE;
}
return SDL_TRUE;
}
void file_handler_stop(struct file_handler *file_handler) {
mutex_lock(file_handler->mutex);
file_handler->stopped = SDL_TRUE;
cond_signal(file_handler->event_cond);
if (file_handler->current_process != PROCESS_NONE) {
if (!cmd_terminate(file_handler->current_process)) {
LOGW("Cannot terminate install process");
}
cmd_simple_wait(file_handler->current_process, NULL);
file_handler->current_process = PROCESS_NONE;
}
mutex_unlock(file_handler->mutex);
}
void file_handler_join(struct file_handler *file_handler) {
SDL_WaitThread(file_handler->thread, NULL);
}

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

@@ -0,0 +1,44 @@
#ifndef FILE_HANDLER_H
#define FILE_HADNELR_H
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h>
#include <SDL2/SDL_thread.h>
#include "command.h"
#define REQUEST_QUEUE_SIZE 16
typedef enum {
ACTION_INSTALL_APK,
ACTION_PUSH_FILE,
} file_handler_action_t;
struct request_queue {
struct request *reqs[REQUEST_QUEUE_SIZE];
int tail;
int head;
};
struct file_handler {
const char *serial;
SDL_Thread *thread;
SDL_mutex *mutex;
SDL_cond *event_cond;
SDL_bool stopped;
SDL_bool initialized;
process_t current_process;
struct request_queue queue;
};
SDL_bool file_handler_init(struct file_handler *file_handler, const char *serial);
void file_handler_destroy(struct file_handler *file_handler);
SDL_bool file_handler_start(struct file_handler *file_handler);
void file_handler_stop(struct file_handler *file_handler);
void file_handler_join(struct file_handler *file_handler);
SDL_bool file_handler_request(struct file_handler *file_handler,
file_handler_action_t action,
const char *file);
#endif

View File

@@ -1,4 +1,4 @@
#include "fpscounter.h"
#include "fps_counter.h"
#include <SDL2/SDL_timer.h>

View File

@@ -6,7 +6,7 @@
#include <libavformat/avformat.h>
#include "config.h"
#include "lockutil.h"
#include "lock_util.h"
#include "log.h"
SDL_bool frames_init(struct frames *frames) {

View File

@@ -5,7 +5,7 @@
#include <SDL2/SDL_stdinc.h>
#include "config.h"
#include "fpscounter.h"
#include "fps_counter.h"
// forward declarations
typedef struct AVFrame AVFrame;

View File

@@ -1,7 +1,7 @@
#include "inputmanager.h"
#include "input_manager.h"
#include "convert.h"
#include "lockutil.h"
#include "lock_util.h"
#include "log.h"
// Convert window coordinates (as provided by SDL_GetMouseState() to renderer coordinates (as provided in SDL mouse events)
@@ -129,6 +129,10 @@ static void clipboard_paste(struct controller *controller) {
void input_manager_process_text_input(struct input_manager *input_manager,
const SDL_TextInputEvent *event) {
if (input_manager->raw_key_events) {
// we will forward the raw key events instead
return;
}
struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_TEXT;
control_event.text_event.text = SDL_strdup(event->text);
@@ -216,13 +220,21 @@ void input_manager_process_key(struct input_manager *input_manager,
switch_fps_counter_state(input_manager->frames);
}
return;
case SDLK_k:
if (!repeat && event->type == SDL_KEYDOWN) {
input_manager->raw_key_events ^= SDL_TRUE; // toggle
LOGI("Raw key events mode %s",
input_manager->raw_key_events ? "enabled" : "disabled");
}
return;
}
return;
}
struct control_event control_event;
if (input_key_from_sdl_to_android(event, &control_event)) {
SDL_bool raw_key_events = input_manager->raw_key_events;
if (input_key_from_sdl_to_android(event, &control_event, raw_key_events)) {
if (!controller_push_event(input_manager->controller, &control_event)) {
LOGW("Cannot send control event");
}
@@ -265,7 +277,7 @@ void input_manager_process_mouse_button(struct input_manager *input_manager,
}
// otherwise, send the click event to the device
}
};
}
struct control_event control_event;
if (mouse_button_from_sdl_to_android(event, input_manager->screen->frame_size, &control_event)) {
if (!controller_push_event(input_manager->controller, &control_event)) {

View File

@@ -3,7 +3,7 @@
#include "common.h"
#include "controller.h"
#include "fpscounter.h"
#include "fps_counter.h"
#include "frames.h"
#include "screen.h"
@@ -11,6 +11,7 @@ struct input_manager {
struct controller *controller;
struct frames *frames;
struct screen *screen;
SDL_bool raw_key_events;
};
void input_manager_process_text_input(struct input_manager *input_manager,

View File

@@ -1,180 +0,0 @@
#include "installer.h"
#include <string.h>
#include "command.h"
#include "lockutil.h"
#include "log.h"
// NOTE(adopi) this can be more generic:
// it could be used with a command queue instead of a filename queue
// then we would have a generic invoker (useful if we want to handle more async commands)
SDL_bool apk_queue_is_empty(const struct apk_queue *queue) {
return queue->head == queue->tail;
}
SDL_bool apk_queue_is_full(const struct apk_queue *queue) {
return (queue->head + 1) % APK_QUEUE_SIZE == queue->tail;
}
SDL_bool apk_queue_init(struct apk_queue *queue) {
queue->head = 0;
queue->tail = 0;
return SDL_TRUE;
}
void apk_queue_destroy(struct apk_queue *queue) {
int i = queue->tail;
while (i != queue->head) {
SDL_free(queue->data[i]);
i = (i + 1) % APK_QUEUE_SIZE;
}
}
SDL_bool apk_queue_push(struct apk_queue *queue, const char *apk) {
if (apk_queue_is_full(queue)) {
return SDL_FALSE;
}
queue->data[queue->head] = SDL_strdup(apk);
queue->head = (queue->head + 1) % APK_QUEUE_SIZE;
return SDL_TRUE;
}
SDL_bool apk_queue_take(struct apk_queue *queue, char **apk) {
if (apk_queue_is_empty(queue)) {
return SDL_FALSE;
}
// transfer ownership
*apk = queue->data[queue->tail];
queue->tail = (queue->tail + 1) % APK_QUEUE_SIZE;
return SDL_TRUE;
}
SDL_bool installer_init(struct installer *installer, const char *serial) {
if (!apk_queue_init(&installer->queue)) {
return SDL_FALSE;
}
if (!(installer->mutex = SDL_CreateMutex())) {
return SDL_FALSE;
}
if (!(installer->event_cond = SDL_CreateCond())) {
SDL_DestroyMutex(installer->mutex);
return SDL_FALSE;
}
if (serial) {
installer->serial = SDL_strdup(serial);
if (!installer->serial) {
LOGW("Cannot strdup serial");
return SDL_FALSE;
}
} else {
installer->serial = NULL;
}
// lazy initialization
installer->initialized = SDL_FALSE;
installer->stopped = SDL_FALSE;
installer->current_process = PROCESS_NONE;
return SDL_TRUE;
}
void installer_destroy(struct installer *installer) {
SDL_DestroyCond(installer->event_cond);
SDL_DestroyMutex(installer->mutex);
apk_queue_destroy(&installer->queue);
SDL_free((void *) installer->serial);
}
SDL_bool installer_install_apk(struct installer *installer, const char *apk) {
SDL_bool res;
// start installer if it's used for the first time
if (!installer->initialized) {
if (!installer_start(installer)) {
return SDL_FALSE;
}
installer->initialized = SDL_TRUE;
}
mutex_lock(installer->mutex);
SDL_bool was_empty = apk_queue_is_empty(&installer->queue);
res = apk_queue_push(&installer->queue, apk);
if (was_empty) {
cond_signal(installer->event_cond);
}
mutex_unlock(installer->mutex);
return res;
}
static int run_installer(void *data) {
struct installer *installer = data;
for (;;) {
mutex_lock(installer->mutex);
while (!installer->stopped && apk_queue_is_empty(&installer->queue)) {
cond_wait(installer->event_cond, installer->mutex);
}
if (installer->stopped) {
// stop immediately, do not process further events
mutex_unlock(installer->mutex);
break;
}
char *current_apk;
#ifdef BUILD_DEBUG
bool non_empty = apk_queue_take(&installer->queue, &current_apk);
SDL_assert(non_empty);
#else
apk_queue_take(&installer->queue, &current_apk);
#endif
LOGI("Installing %s...", current_apk);
process_t process = adb_install(installer->serial, current_apk);
installer->current_process = process;
mutex_unlock(installer->mutex);
if (process_check_success(process, "adb install")) {
LOGI("%s installed successfully", current_apk);
} else {
LOGE("Failed to install %s", current_apk);
}
SDL_free(current_apk);
}
return 0;
}
SDL_bool installer_start(struct installer *installer) {
LOGD("Starting installer thread");
installer->thread = SDL_CreateThread(run_installer, "installer", installer);
if (!installer->thread) {
LOGC("Could not start installer thread");
return SDL_FALSE;
}
return SDL_TRUE;
}
void installer_stop(struct installer *installer) {
mutex_lock(installer->mutex);
installer->stopped = SDL_TRUE;
cond_signal(installer->event_cond);
if (installer->current_process != PROCESS_NONE) {
if (!cmd_terminate(installer->current_process)) {
LOGW("Cannot terminate install process");
}
cmd_simple_wait(installer->current_process, NULL);
installer->current_process = PROCESS_NONE;
}
mutex_unlock(installer->mutex);
}
void installer_join(struct installer *installer) {
SDL_WaitThread(installer->thread, NULL);
}

View File

@@ -1,40 +0,0 @@
#ifndef APK_INSTALLER_H
#define APK_INSTALLER_H
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h>
#include <SDL2/SDL_thread.h>
#include "command.h"
#define APK_QUEUE_SIZE 16
// NOTE(AdoPi) apk_queue and control_event can use a generic queue
struct apk_queue {
char *data[APK_QUEUE_SIZE];
int tail;
int head;
};
struct installer {
const char *serial;
SDL_Thread *thread;
SDL_mutex *mutex;
SDL_cond *event_cond;
SDL_bool stopped;
SDL_bool initialized;
process_t current_process;
struct apk_queue queue;
};
SDL_bool installer_init(struct installer *installer, const char *serial);
void installer_destroy(struct installer *installer);
SDL_bool installer_start(struct installer *installer);
void installer_stop(struct installer *installer);
void installer_join(struct installer *installer);
// install an apk
SDL_bool installer_install_apk(struct installer *installer, const char *filename);
#endif

View File

@@ -1,3 +1,4 @@
#include <lock_util.h>
#include <stdlib.h>
#include <SDL2/SDL_mutex.h>

View File

@@ -10,6 +10,7 @@
struct args {
const char *serial;
const char *crop;
SDL_bool help;
SDL_bool version;
SDL_bool show_touches;
@@ -29,6 +30,12 @@ static void usage(const char *arg0) {
" Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
" Default is %d.\n"
"\n"
" -c, --crop width:height:x:y\n"
" Crop the device screen on the server.\n"
" The values are expressed in the device natural orientation\n"
" (typically, portrait for a phone, landscape for a tablet).\n"
" Any --max-size value is computed on the cropped size.\n"
"\n"
" -h, --help\n"
" Print this help.\n"
"\n"
@@ -192,6 +199,7 @@ static SDL_bool parse_port(char *optarg, Uint16 *port) {
static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
static const struct option long_options[] = {
{"bit-rate", required_argument, NULL, 'b'},
{"crop", required_argument, NULL, 'c'},
{"help", no_argument, NULL, 'h'},
{"max-size", required_argument, NULL, 'm'},
{"port", required_argument, NULL, 'p'},
@@ -201,13 +209,16 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
{NULL, 0, NULL, 0 },
};
int c;
while ((c = getopt_long(argc, argv, "b:hm:p:s:tv", long_options, NULL)) != -1) {
while ((c = getopt_long(argc, argv, "b:c:hm:p:s:tv", long_options, NULL)) != -1) {
switch (c) {
case 'b':
if (!parse_bit_rate(optarg, &args->bit_rate)) {
return SDL_FALSE;
}
break;
case 'c':
args->crop = optarg;
break;
case 'h':
args->help = SDL_TRUE;
break;
@@ -253,6 +264,7 @@ int main(int argc, char *argv[]) {
#endif
struct args args = {
.serial = NULL,
.crop = NULL,
.help = SDL_FALSE,
.version = SDL_FALSE,
.show_touches = SDL_FALSE,
@@ -274,7 +286,9 @@ int main(int argc, char *argv[]) {
return 0;
}
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
av_register_all();
#endif
if (avformat_network_init()) {
return 1;
@@ -286,6 +300,7 @@ int main(int argc, char *argv[]) {
struct scrcpy_options options = {
.serial = args.serial,
.crop = args.crop,
.port = args.port,
.max_size = args.max_size,
.bit_rate = args.bit_rate,

View File

@@ -13,28 +13,29 @@
#include "decoder.h"
#include "device.h"
#include "events.h"
#include "file_handler.h"
#include "frames.h"
#include "fpscounter.h"
#include "inputmanager.h"
#include "fps_counter.h"
#include "input_manager.h"
#include "log.h"
#include "lockutil.h"
#include "lock_util.h"
#include "net.h"
#include "screen.h"
#include "server.h"
#include "tinyxpm.h"
#include "installer.h"
#include "tiny_xpm.h"
static struct server server = SERVER_INITIALIZER;
static struct screen screen = SCREEN_INITIALIZER;
static struct frames frames;
static struct decoder decoder;
static struct controller controller;
static struct installer installer;
static struct file_handler file_handler;
static struct input_manager input_manager = {
.controller = &controller,
.frames = &frames,
.screen = &screen,
.raw_key_events = SDL_FALSE,
};
#if defined(__APPLE__) || defined(__WINDOWS__)
@@ -56,7 +57,12 @@ static int event_watcher(void *data, SDL_Event *event) {
}
#endif
static void event_loop(void) {
static SDL_bool is_apk(const char *file) {
const char *ext = strrchr(file, '.');
return ext && !strcmp(ext, ".apk");
}
static SDL_bool event_loop(void) {
#ifdef CONTINUOUS_RESIZING_WORKAROUND
SDL_AddEventWatch(event_watcher, NULL);
#endif
@@ -65,10 +71,10 @@ static void event_loop(void) {
switch (event.type) {
case EVENT_DECODER_STOPPED:
LOGD("Video decoder stopped");
return;
return SDL_FALSE;
case SDL_QUIT:
LOGD("User requested to quit");
return;
return SDL_TRUE;
case EVENT_NEW_FRAME:
if (!screen.has_frame) {
screen.has_frame = SDL_TRUE;
@@ -76,7 +82,7 @@ static void event_loop(void) {
screen_show_window(&screen);
}
if (!screen_update_frame(&screen, &frames)) {
return;
return SDL_FALSE;
}
break;
case SDL_WINDOWEVENT:
@@ -104,11 +110,19 @@ static void event_loop(void) {
case SDL_MOUSEBUTTONUP:
input_manager_process_mouse_button(&input_manager, &event.button);
break;
case SDL_DROPFILE:
installer_install_apk(&installer, event.drop.file);
case SDL_DROPFILE: {
file_handler_action_t action;
if (is_apk(event.drop.file)) {
action = ACTION_INSTALL_APK;
} else {
action = ACTION_PUSH_FILE;
}
file_handler_request(&file_handler, action, event.drop.file);
break;
}
}
}
return SDL_FALSE;
}
static process_t set_show_touches_enabled(const char *serial, SDL_bool enabled) {
@@ -126,7 +140,7 @@ static void wait_show_touches(process_t process) {
SDL_bool scrcpy(const struct scrcpy_options *options) {
if (!server_start(&server, options->serial, options->port,
options->max_size, options->bit_rate)) {
options->max_size, options->bit_rate, options->crop)) {
return SDL_FALSE;
}
@@ -174,7 +188,7 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
goto finally_destroy_server;
}
if (!installer_init(&installer, server.serial)) {
if (!file_handler_init(&file_handler, server.serial)) {
ret = SDL_FALSE;
server_stop(&server);
goto finally_destroy_frames;
@@ -187,7 +201,7 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
if (!decoder_start(&decoder)) {
ret = SDL_FALSE;
server_stop(&server);
goto finally_destroy_installer;
goto finally_destroy_file_handler;
}
if (!controller_init(&controller, device_socket)) {
@@ -210,7 +224,7 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
show_touches_waited = SDL_TRUE;
}
event_loop();
ret = event_loop();
LOGD("quit...");
screen_destroy(&screen);
@@ -225,10 +239,10 @@ finally_stop_decoder:
// stop the server before decoder_join() to wake up the decoder
server_stop(&server);
decoder_join(&decoder);
finally_destroy_installer:
installer_stop(&installer);
installer_join(&installer);
installer_destroy(&installer);
finally_destroy_file_handler:
file_handler_stop(&file_handler);
file_handler_join(&file_handler);
file_handler_destroy(&file_handler);
finally_destroy_frames:
frames_destroy(&frames);
finally_destroy_server:

View File

@@ -5,6 +5,7 @@
struct scrcpy_options {
const char *serial;
const char *crop;
Uint16 port;
Uint16 max_size;
Uint32 bit_rate;

View File

@@ -4,9 +4,9 @@
#include <string.h>
#include "icon.xpm"
#include "lockutil.h"
#include "lock_util.h"
#include "log.h"
#include "tinyxpm.h"
#include "tiny_xpm.h"
#define DISPLAY_MARGINS 96

View File

@@ -77,7 +77,8 @@ static SDL_bool disable_tunnel(struct server *server) {
}
static process_t execute_server(const char *serial,
Uint16 max_size, Uint32 bit_rate, SDL_bool tunnel_forward) {
Uint16 max_size, Uint32 bit_rate,
const char *crop, SDL_bool tunnel_forward) {
char max_size_string[6];
char bit_rate_string[11];
sprintf(max_size_string, "%"PRIu16, max_size);
@@ -91,6 +92,7 @@ static process_t execute_server(const char *serial,
max_size_string,
bit_rate_string,
tunnel_forward ? "true" : "false",
crop ? crop : "",
};
return adb_execute(serial, cmd, sizeof(cmd) / sizeof(cmd[0]));
}
@@ -147,7 +149,7 @@ void server_init(struct server *server) {
}
SDL_bool server_start(struct server *server, const char *serial, Uint16 local_port,
Uint16 max_size, Uint32 bit_rate) {
Uint16 max_size, Uint32 bit_rate, const char *crop) {
server->local_port = local_port;
if (serial) {
@@ -188,7 +190,8 @@ SDL_bool server_start(struct server *server, const char *serial, Uint16 local_po
}
// server will connect to our server socket
server->process = execute_server(serial, max_size, bit_rate, server->tunnel_forward);
server->process = execute_server(serial, max_size, bit_rate, crop,
server->tunnel_forward);
if (server->process == PROCESS_NONE) {
if (!server->tunnel_forward) {
close_socket(&server->server_socket);
@@ -207,7 +210,7 @@ socket_t server_connect_to(struct server *server) {
if (!server->tunnel_forward) {
server->device_socket = net_accept(server->server_socket);
} else {
Uint32 attempts = 50;
Uint32 attempts = 100;
Uint32 delay = 100; // ms
server->device_socket = connect_to_server(server->local_port, attempts, delay);
}

View File

@@ -31,7 +31,7 @@ void server_init(struct server *server);
// push, enable tunnel et start the server
SDL_bool server_start(struct server *server, const char *serial, Uint16 local_port,
Uint16 max_size, Uint32 bit_rate);
Uint16 max_size, Uint32 bit_rate, const char *crop);
// block until the communication with the server is established
socket_t server_connect_to(struct server *server);

View File

@@ -1,4 +1,4 @@
#include "strutil.h"
#include "str_util.h"
size_t xstrncpy(char *dest, const char *src, size_t n) {
size_t i;

View File

@@ -2,7 +2,7 @@
#include "config.h"
#include "log.h"
#include "strutil.h"
#include "str_util.h"
HANDLE cmd_execute(const char *path, const char *const argv[]) {
STARTUPINFO si;

View File

@@ -1,4 +1,4 @@
#include "tinyxpm.h"
#include "tiny_xpm.h"
#include <stdio.h>
#include <stdlib.h>
@@ -40,15 +40,15 @@ SDL_Surface *read_xpm(char *xpm[]) {
// *** No error handling, assume the XPM source is valid ***
// (it's in our source repo)
// Assertions are only checked in debug
Uint32 width = strtol(xpm[0], &endptr, 10);
Uint32 height = strtol(endptr + 1, &endptr, 10);
Uint32 colors = strtol(endptr + 1, &endptr, 10);
Uint32 chars = strtol(endptr + 1, &endptr, 10);
int width = strtol(xpm[0], &endptr, 10);
int height = strtol(endptr + 1, &endptr, 10);
int colors = strtol(endptr + 1, &endptr, 10);
int chars = strtol(endptr + 1, &endptr, 10);
// sanity checks
SDL_assert(width < 256);
SDL_assert(height < 256);
SDL_assert(colors < 256);
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
// init index

View File

@@ -1,7 +1,7 @@
#include <assert.h>
#include <string.h>
#include "controlevent.h"
#include "control_event.h"
static void test_control_event_queue_empty() {
struct control_event_queue queue;

View File

@@ -1,7 +1,7 @@
#include <assert.h>
#include <string.h>
#include "controlevent.h"
#include "control_event.h"
static void test_serialize_keycode_event() {
struct control_event event = {

View File

@@ -1,7 +1,7 @@
#include <assert.h>
#include <string.h>
#include "strutil.h"
#include "str_util.h"
static void test_xstrncpy_simple() {
char s[] = "xxxxxxxxxx";

View File

@@ -15,6 +15,6 @@ cpu = 'i686'
endian = 'little'
[properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.0-win32-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.0-win32-dev'
prebuilt_ffmpeg_shared = 'ffmpeg-4.0.2-win32-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.0.2-win32-dev'
prebuilt_sdl2 = 'SDL2-2.0.8/i686-w64-mingw32'

View File

@@ -15,6 +15,6 @@ cpu = 'x86_64'
endian = 'little'
[properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.0-win64-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.0-win64-dev'
prebuilt_ffmpeg_shared = 'ffmpeg-4.0.2-win64-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.0.2-win64-dev'
prebuilt_sdl2 = 'SDL2-2.0.8/x86_64-w64-mingw32'

View File

@@ -1,4 +1,4 @@
project('scrcpy', 'c', meson_version: '>= 0.37', default_options : 'c_std=c99')
project('scrcpy', 'c', meson_version: '>= 0.37', default_options : 'c_std=c11')
if get_option('build_app')
subdir('app')

View File

@@ -10,24 +10,24 @@ prepare-win32: prepare-sdl2 prepare-ffmpeg-shared-win32 prepare-ffmpeg-dev-win32
prepare-win64: prepare-sdl2 prepare-ffmpeg-shared-win64 prepare-ffmpeg-dev-win64 prepare-adb
prepare-ffmpeg-shared-win32:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/shared/ffmpeg-4.0-win32-shared.zip \
530c92df0ca14c35901b4b681847d62da3c50a0cc9b7ced37b04968f6b5c243d \
ffmpeg-4.0-win32-shared
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/shared/ffmpeg-4.0.2-win32-shared.zip \
cc190a3a4cf7bfbd4fbaa92609c1501a1de458055e6cfea8b745c1d515013aa8 \
ffmpeg-4.0.2-win32-shared
prepare-ffmpeg-dev-win32:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/dev/ffmpeg-4.0-win32-dev.zip \
e2f5200b5e73c4d0abb9b89c4ffc0438f92a0aadc54c81cf57e18c81a9f11c6b \
ffmpeg-4.0-win32-dev
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/dev/ffmpeg-4.0.2-win32-dev.zip \
c72c74bad74ac0541f1b43090c26a50017c49041c182a703abd2057bb8cdc238 \
ffmpeg-4.0.2-win32-dev
prepare-ffmpeg-shared-win64:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/shared/ffmpeg-4.0-win64-shared.zip \
8fe2d344463dbefc2db4239a4203a55ed0324faceaae57276a40c4fabda84c37 \
ffmpeg-4.0-win64-shared
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/shared/ffmpeg-4.0.2-win64-shared.zip \
ede566aca8b5348dff85570f9638c6bad33209f9419f79db7bde7daa37599bff \
ffmpeg-4.0.2-win64-shared
prepare-ffmpeg-dev-win64:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/dev/ffmpeg-4.0-win64-dev.zip \
facced738eabfc53fa92834dea8b24426f64db61298688fed480145945be07fa \
ffmpeg-4.0-win64-dev
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/dev/ffmpeg-4.0.2-win64-dev.zip \
23ee994161c51285cb956b98d3caa499d48083dae7b26c1fdf77f22e98df1c5f \
ffmpeg-4.0.2-win64-dev
prepare-sdl2:
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.8-mingw.tar.gz \
@@ -35,6 +35,6 @@ prepare-sdl2:
SDL2-2.0.8
prepare-adb:
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r27.0.1-windows.zip \
880662adfb0d6911ff250b9e13930ae1a4110fc36d5866afd4f8f56d935f7939 \
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r28.0.0-windows.zip \
e2c1ec7c8e9b71cf1c8befd3bff91d06b26dd334c3f32b3817e9d46ba260b0e8 \
platform-tools

View File

@@ -6,8 +6,8 @@ android {
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
targetSdkVersion 27
versionCode 3
versionName "1.2"
versionCode 4
versionName "1.3"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {

View File

@@ -19,32 +19,14 @@ public final class Device {
private ScreenInfo screenInfo;
private RotationListener rotationListener;
private boolean rotated;
public Device(Options options) {
options.setCrop(new Rect(0, 0, 1280, 1440));
// TODO crop the crop to the device size!!!
final int maxSize = options.getMaxSize();
final Rect crop = options.getCrop();
DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo();
rotated = (displayInfo.getRotation() & 1) != 0;
screenInfo = ScreenInfo.create(displayInfo.getSize(), maxSize, crop, rotated);
screenInfo = computeScreenInfo(options.getCrop(), options.getMaxSize());
registerRotationWatcher(new IRotationWatcher.Stub() {
@Override
public void onRotationChanged(int rotation) {
boolean rotated = (rotation & 1) != 0;
public void onRotationChanged(int rotation) throws RemoteException {
synchronized (Device.this) {
// Do not call getDisplayInfo(), the resulting rotation may be inconsistent with
// the rotation parameter (race condition).
// Instead, compute the new size from the (rotated) old size.
Size oldSize = screenInfo.getDeviceSize();
Size newSize = rotated != Device.this.rotated ? oldSize.rotate() : oldSize;
screenInfo = ScreenInfo.create(newSize, maxSize, crop, rotated);
Device.this.rotated = rotated;
screenInfo = screenInfo.withRotation(rotation);
// notify
if (rotationListener != null) {
@@ -59,6 +41,59 @@ public final class Device {
return screenInfo;
}
private ScreenInfo computeScreenInfo(Rect crop, int maxSize) {
DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo();
boolean rotated = (displayInfo.getRotation() & 1) != 0;
Size deviceSize = displayInfo.getSize();
Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight());
if (crop != null) {
if (rotated) {
// the crop (provided by the user) is expressed in the natural orientation
crop = flipRect(crop);
}
if (!contentRect.intersect(crop)) {
// intersect() changes contentRect so that it is intersected with crop
Ln.w("Crop rectangle (" + formatCrop(crop) + ") does not intersect device screen (" + formatCrop(deviceSize.toRect()) + ")");
contentRect = new Rect(); // empty
}
}
Size videoSize = computeVideoSize(contentRect.width(), contentRect.height(), maxSize);
return new ScreenInfo(contentRect, videoSize, rotated);
}
private static String formatCrop(Rect rect) {
return rect.width() + ":" + rect.height() + ":" + rect.left + ":" + rect.top;
}
@SuppressWarnings("checkstyle:MagicNumber")
private static Size computeVideoSize(int w, int h, int maxSize) {
// Compute the video size and the padding of the content inside this video.
// Principle:
// - scale down the great side of the screen to maxSize (if necessary);
// - scale down the other side so that the aspect ratio is preserved;
// - round this value to the nearest multiple of 8 (H.264 only accepts multiples of 8)
w &= ~7; // in case it's not a multiple of 8
h &= ~7;
if (maxSize > 0) {
if (BuildConfig.DEBUG && maxSize % 8 != 0) {
throw new AssertionError("Max size must be a multiple of 8");
}
boolean portrait = h > w;
int major = portrait ? h : w;
int minor = portrait ? w : h;
if (major > maxSize) {
int minorExact = minor * maxSize / major;
// +4 to round the value to the nearest multiple of 8
minor = (minorExact + 4) & ~7;
major = maxSize;
}
w = portrait ? minor : major;
h = portrait ? major : minor;
}
return new Size(w, h);
}
public Point getPhysicalPoint(Position position) {
// it hides the field on purpose, to read it with a lock
@SuppressWarnings("checkstyle:HiddenField")
@@ -72,9 +107,9 @@ public final class Device {
}
Rect contentRect = screenInfo.getContentRect();
Point point = position.getPoint();
int deviceX = contentRect.left + point.x * contentRect.width() / videoSize.getWidth();
int deviceY = contentRect.top + point.y * contentRect.height() / videoSize.getHeight();
return new Point(deviceX, deviceY);
int scaledX = contentRect.left + point.x * contentRect.width() / videoSize.getWidth();
int scaledY = contentRect.top + point.y * contentRect.height() / videoSize.getHeight();
return new Point(scaledX, scaledY);
}
public static String getDeviceName() {
@@ -96,4 +131,8 @@ public final class Device {
public synchronized void setRotationListener(RotationListener rotationListener) {
this.rotationListener = rotationListener;
}
static Rect flipRect(Rect crop) {
return new Rect(crop.top, crop.left, crop.bottom, crop.right);
}
}

View File

@@ -16,16 +16,5 @@ public final class DisplayInfo {
public int getRotation() {
return rotation;
}
public DisplayInfo withRotation(int rotation) {
if (rotation == this.rotation) {
return this;
}
Size newSize = size;
if ((rotation & 1) != (this.rotation & 1)) {
newSize = size.rotate();
}
return new DisplayInfo(newSize, rotation);
}
}

View File

@@ -3,79 +3,29 @@ package com.genymobile.scrcpy;
import android.graphics.Rect;
public final class ScreenInfo {
private final Size deviceSize;
private final Rect contentRect; // device size, possibly cropped
private final Size videoSize;
private final Rect crop;
private final boolean rotated;
private ScreenInfo(Size deviceSize, Size videoSize, Rect crop, boolean rotated) {
this.deviceSize = deviceSize;
public ScreenInfo(Rect contentRect, Size videoSize, boolean rotated) {
this.contentRect = contentRect;
this.videoSize = videoSize;
this.crop = crop;
this.rotated = rotated;
}
public static ScreenInfo create(Size deviceSize, int maxSize, Rect crop, boolean rotated) {
Size inputSize;
if (crop == null) {
inputSize = deviceSize;
} else {
if (rotated) {
// the crop (provided by the user) is expressed in the natural orientation
// the device size (provided by the system) already takes the rotation into account
crop = rotateCrop(crop, deviceSize);
}
inputSize = new Size(crop.width(), crop.height());
}
Size videoSize = computeVideoSize(inputSize, maxSize);
return new ScreenInfo(deviceSize, videoSize, crop, rotated);
}
@SuppressWarnings("checkstyle:MagicNumber")
private static Size computeVideoSize(Size inputSize, int maxSize) {
// Compute the video size and the padding of the content inside this video.
// Principle:
// - scale down the great side of the screen to maxSize (if necessary);
// - scale down the other side so that the aspect ratio is preserved;
// - round this value to the nearest multiple of 8 (H.264 only accepts multiples of 8)
int w = inputSize.getWidth() & ~7; // in case it's not a multiple of 8
int h = inputSize.getHeight() & ~7;
if (maxSize > 0) {
if (BuildConfig.DEBUG && maxSize % 8 != 0) {
throw new AssertionError("Max size must be a multiple of 8");
}
boolean portrait = h > w;
int major = portrait ? h : w;
int minor = portrait ? w : h;
if (major > maxSize) {
int minorExact = minor * maxSize / major;
// +4 to round the value to the nearest multiple of 8
minor = (minorExact + 4) & ~7;
major = maxSize;
}
w = portrait ? minor : major;
h = portrait ? major : minor;
}
return new Size(w, h);
}
public Size getDeviceSize() {
return deviceSize;
public Rect getContentRect() {
return contentRect;
}
public Size getVideoSize() {
return videoSize;
}
public Rect getCrop() {
return crop;
}
public Rect getContentRect() {
Rect crop = getCrop();
return crop != null ? crop : deviceSize.toRect();
}
private static Rect rotateCrop(Rect crop, Size rotatedSize) {
int w = rotatedSize.getHeight(); // the size is already rotated
return new Rect(crop.top, w - crop.right, crop.bottom, w - crop.left);
public ScreenInfo withRotation(int rotation) {
boolean newRotated = (rotation & 1) != 0;
if (rotated == newRotated) {
return this;
}
return new ScreenInfo(Device.flipRect(contentRect), videoSize.rotate(), newRotated);
}
}

View File

@@ -1,5 +1,7 @@
package com.genymobile.scrcpy;
import android.graphics.Rect;
import java.io.IOException;
public final class Server {
@@ -63,9 +65,31 @@ public final class Server {
boolean tunnelForward = Boolean.parseBoolean(args[2]);
options.setTunnelForward(tunnelForward);
if (args.length < 4) {
return options;
}
Rect crop = parseCrop(args[3]);
options.setCrop(crop);
return options;
}
private static Rect parseCrop(String crop) {
if (crop.isEmpty()) {
return null;
}
// input format: "width:height:x:y"
String[] tokens = crop.split(":");
if (tokens.length != 4) {
throw new IllegalArgumentException("Crop must contains 4 values separated by colons: \"" + crop + "\"");
}
int width = Integer.parseInt(tokens[0]);
int height = Integer.parseInt(tokens[1]);
int x = Integer.parseInt(tokens[2]);
int y = Integer.parseInt(tokens[3]);
return new Rect(x, y, x + width, y + height);
}
public static void main(String... args) throws Exception {
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override