Compare commits

..

38 Commits

Author SHA1 Message Date
Romain Vimont
e3a07c3309 update-on-event 2018-09-13 22:24:26 +02:00
Romain Vimont
2e40285244 hidpi-scale-wip 2018-09-13 20:37:45 +02:00
Romain Vimont
eca99d5af7 Fix header guard name 2018-09-13 16:27:19 +02:00
Romain Vimont
6a1fb070f7 Merge branch 'npes87184:dev' into dev (#254)
Return specific error for missing adb binary on Windows
2018-09-09 15:28:56 +02:00
yuchenlin
27bed948d4 Use specific error for missing binary on Windows
Signed-off-by: yuchenlin <npes87184@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2018-09-09 15:28:25 +02:00
Romain Vimont
66def38b73 Avoid additional buffer copy in userspace
Directly send the data from MediaCodec buffers to the LocalSocket,
without an intermediate copy in userspace.
2018-09-09 15:05:06 +02:00
Romain Vimont
a60aef5aaf Merge branch 'philippsandhaus:master' into dev (#252)
Added new command line parameter to start in fullscreen
2018-09-04 18:51:56 +02:00
Romain Vimont
28015c3ee4 Present fullscreen option in README 2018-09-04 18:51:35 +02:00
Philipp Sandhaus
af9808cf02 Add option to start in fullscreen
Signed-off-by: Romain Vimont <rom@rom1v.com>
2018-09-04 18:51:32 +02:00
Romain Vimont
34550311be Merge branch 'npes87184:dev' into dev (#236)
check adb runnable before starting scrcpy
2018-09-04 18:43:17 +02:00
Romain Vimont
55d33ddd5f Do not handle system-specific values in command.c
The common command.c handled process errors from system-specific int
values (errno).

Rather, expose a new enum process_result to handle error cause in a
generic way.
2018-09-04 08:57:07 +02:00
yuchenlin
6d2d803003 Notify adb missing
There are many user who encounters missing adb.
To stop things happens again, we check it and show
sexy response to user.

Signed-off-by: yuchenlin <npes87184@gmail.com>
2018-09-04 08:46:17 +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
53 changed files with 706 additions and 469 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 "$(SERVER_BUILD_DIR)"/server/scrcpy-server.jar "$(DIST)/$(WIN32_TARGET_DIR)/"
cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(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 "$(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.2-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.2-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.2-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/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/adb.exe "$(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/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinUsbApi.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 "$(SERVER_BUILD_DIR)"/server/scrcpy-server.jar "$(DIST)/$(WIN64_TARGET_DIR)/"
cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(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 "$(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.2-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.2-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.2-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/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/adb.exe "$(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/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinUsbApi.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 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. 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 For Windows, for simplicity, prebuilt archives with all the dependencies
(including `adb`) are available: (including `adb`) are available:
- [`scrcpy-win32-v1.2.zip`][direct-win32]. - [`scrcpy-win32-v1.3.zip`][direct-win32].
_(SHA-256: a1fe1de67ec75dcf970ca5d97a04c26ff0f2d61871f2ef51b6f2f0bf666966b2)_ _(SHA-256: 51a2990e631ed469a7a86ff38107d517a91d313fb3f8327eb7bc71dde40870b5)_
- [`scrcpy-win64-v1.2.zip`][direct-win64]. - [`scrcpy-win64-v1.3.zip`][direct-win64].
_(SHA-256: 35ae3bcee51771e7c51b8a8be87aef2295c9f267606a7cf83ebb0a4d583ef536)_ _(SHA-256: 0768a80d3d600d0bbcd220ca150ae88a3a58d1fe85c308a8c61f44480b711e43)_
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.2/scrcpy-win32-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.2/scrcpy-win64-v1.2.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. 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 depend on your system and architecture, you may want to use the prebuilt binary
instead: instead:
- [`scrcpy-server-v1.2.jar`][direct-scrcpy-server]. - [`scrcpy-server-v1.3.jar`][direct-scrcpy-server].
_(SHA-256: cb39654ed2fda3d30ddff292806950ccc5c394375ea12b974f790c7f38f61f60)_ _(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. 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 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_: If several devices are listed in `adb devices`, you must specify the _serial_:
```bash ```bash
@@ -306,6 +312,12 @@ To show physical touches while scrcpy is running:
scrcpy -t scrcpy -t
``` ```
The app may be started directly in fullscreen:
```
scrcpy -f
```
To run without installing: To run without installing:
```bash ```bash
@@ -333,6 +345,7 @@ To run without installing:
| paste computer clipboard to device | `Ctrl`+`v` | | paste computer clipboard to device | `Ctrl`+`v` |
| enable/disable FPS counter (on stdout) | `Ctrl`+`i` | | enable/disable FPS counter (on stdout) | `Ctrl`+`i` |
| install APK from computer | drag & drop APK file | | 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._ _¹Double-click on black borders to remove them._
_²Right-click turns the screen on if it was off, presses BACK otherwise._ _²Right-click turns the screen on if it was off, presses BACK otherwise._

View File

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

View File

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

View File

@@ -18,9 +18,25 @@ static inline const char *get_adb_command() {
return adb_command; return adb_command;
} }
static void show_adb_err_msg(enum process_result err) {
switch (err) {
case PROCESS_ERROR_GENERIC:
LOGE("Failed to execute adb");
break;
case PROCESS_ERROR_MISSING_BINARY:
LOGE("'adb' command not found (make it accessible from your PATH "
"or define its full path in the ADB environment variable)");
break;
case PROCESS_SUCCESS:
/* do nothing */
break;
}
}
process_t adb_execute(const char *serial, const char *const adb_cmd[], int len) { process_t adb_execute(const char *serial, const char *const adb_cmd[], int len) {
const char *cmd[len + 4]; const char *cmd[len + 4];
int i; int i;
process_t process;
cmd[0] = get_adb_command(); cmd[0] = get_adb_command();
if (serial) { if (serial) {
cmd[1] = "-s"; cmd[1] = "-s";
@@ -32,7 +48,12 @@ process_t adb_execute(const char *serial, const char *const adb_cmd[], int len)
memcpy(&cmd[i], adb_cmd, len * sizeof(const char *)); memcpy(&cmd[i], adb_cmd, len * sizeof(const char *));
cmd[len + i] = NULL; cmd[len + i] = NULL;
return cmd_execute(cmd[0], cmd); enum process_result r = cmd_execute(cmd[0], cmd, &process);
if (r != PROCESS_SUCCESS) {
show_adb_err_msg(r);
return PROCESS_NONE;
}
return process;
} }
process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name) { process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name) {

View File

@@ -32,7 +32,13 @@
#endif #endif
# define NO_EXIT_CODE -1 # define NO_EXIT_CODE -1
process_t cmd_execute(const char *path, const char *const argv[]); enum process_result {
PROCESS_SUCCESS,
PROCESS_ERROR_GENERIC,
PROCESS_ERROR_MISSING_BINARY,
};
enum process_result cmd_execute(const char *path, const char *const argv[], process_t *process);
SDL_bool cmd_terminate(process_t pid); SDL_bool cmd_terminate(process_t pid);
SDL_bool cmd_simple_wait(process_t pid, exit_code_t *exit_code); SDL_bool cmd_simple_wait(process_t pid, exit_code_t *exit_code);

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
#include <SDL2/SDL_stdinc.h> #include <SDL2/SDL_stdinc.h>
#include <SDL2/SDL_events.h> #include <SDL2/SDL_events.h>
#include "controlevent.h" #include "control_event.h"
struct complete_mouse_motion_event { struct complete_mouse_motion_event {
SDL_MouseMotionEvent *mouse_motion_event; SDL_MouseMotionEvent *mouse_motion_event;

View File

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

View File

@@ -7,6 +7,7 @@
#include "net.h" #include "net.h"
#define DEVICE_NAME_FIELD_LENGTH 64 #define DEVICE_NAME_FIELD_LENGTH 64
#define DEVICE_SDCARD_PATH "/sdcard/"
// name must be at least DEVICE_NAME_FIELD_LENGTH bytes // 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); 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_HANDLER_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> #include <SDL2/SDL_timer.h>

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
#include "inputmanager.h" #include "input_manager.h"
#include "convert.h" #include "convert.h"
#include "lockutil.h" #include "lock_util.h"
#include "log.h" #include "log.h"
// Convert window coordinates (as provided by SDL_GetMouseState() to renderer coordinates (as provided in SDL mouse events) // Convert window coordinates (as provided by SDL_GetMouseState() to renderer coordinates (as provided in SDL mouse events)
@@ -265,7 +265,7 @@ void input_manager_process_mouse_button(struct input_manager *input_manager,
} }
// otherwise, send the click event to the device // otherwise, send the click event to the device
} }
}; }
struct control_event control_event; struct control_event control_event;
if (mouse_button_from_sdl_to_android(event, input_manager->screen->frame_size, &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)) { if (!controller_push_event(input_manager->controller, &control_event)) {

View File

@@ -3,7 +3,7 @@
#include "common.h" #include "common.h"
#include "controller.h" #include "controller.h"
#include "fpscounter.h" #include "fps_counter.h"
#include "frames.h" #include "frames.h"
#include "screen.h" #include "screen.h"

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 <stdlib.h>
#include <SDL2/SDL_mutex.h> #include <SDL2/SDL_mutex.h>

View File

@@ -10,6 +10,8 @@
struct args { struct args {
const char *serial; const char *serial;
const char *crop;
SDL_bool fullscreen;
SDL_bool help; SDL_bool help;
SDL_bool version; SDL_bool version;
SDL_bool show_touches; SDL_bool show_touches;
@@ -29,6 +31,15 @@ static void usage(const char *arg0) {
" Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n" " Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
" Default is %d.\n" " Default is %d.\n"
"\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"
" -f, --fullscreen\n"
" Start in fullscreen.\n"
"\n"
" -h, --help\n" " -h, --help\n"
" Print this help.\n" " Print this help.\n"
"\n" "\n"
@@ -192,6 +203,8 @@ static SDL_bool parse_port(char *optarg, Uint16 *port) {
static SDL_bool parse_args(struct args *args, int argc, char *argv[]) { static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
static const struct option long_options[] = { static const struct option long_options[] = {
{"bit-rate", required_argument, NULL, 'b'}, {"bit-rate", required_argument, NULL, 'b'},
{"crop", required_argument, NULL, 'c'},
{"fullscreen", no_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'}, {"help", no_argument, NULL, 'h'},
{"max-size", required_argument, NULL, 'm'}, {"max-size", required_argument, NULL, 'm'},
{"port", required_argument, NULL, 'p'}, {"port", required_argument, NULL, 'p'},
@@ -201,13 +214,19 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
{NULL, 0, NULL, 0 }, {NULL, 0, NULL, 0 },
}; };
int c; 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:fhm:p:s:tv", long_options, NULL)) != -1) {
switch (c) { switch (c) {
case 'b': case 'b':
if (!parse_bit_rate(optarg, &args->bit_rate)) { if (!parse_bit_rate(optarg, &args->bit_rate)) {
return SDL_FALSE; return SDL_FALSE;
} }
break; break;
case 'c':
args->crop = optarg;
break;
case 'f':
args->fullscreen = SDL_TRUE;
break;
case 'h': case 'h':
args->help = SDL_TRUE; args->help = SDL_TRUE;
break; break;
@@ -253,6 +272,7 @@ int main(int argc, char *argv[]) {
#endif #endif
struct args args = { struct args args = {
.serial = NULL, .serial = NULL,
.crop = NULL,
.help = SDL_FALSE, .help = SDL_FALSE,
.version = SDL_FALSE, .version = SDL_FALSE,
.show_touches = SDL_FALSE, .show_touches = SDL_FALSE,
@@ -274,7 +294,9 @@ int main(int argc, char *argv[]) {
return 0; return 0;
} }
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
av_register_all(); av_register_all();
#endif
if (avformat_network_init()) { if (avformat_network_init()) {
return 1; return 1;
@@ -286,10 +308,12 @@ int main(int argc, char *argv[]) {
struct scrcpy_options options = { struct scrcpy_options options = {
.serial = args.serial, .serial = args.serial,
.crop = args.crop,
.port = args.port, .port = args.port,
.max_size = args.max_size, .max_size = args.max_size,
.bit_rate = args.bit_rate, .bit_rate = args.bit_rate,
.show_touches = args.show_touches, .show_touches = args.show_touches,
.fullscreen = args.fullscreen,
}; };
int res = scrcpy(&options) ? 0 : 1; int res = scrcpy(&options) ? 0 : 1;

View File

@@ -13,23 +13,23 @@
#include "decoder.h" #include "decoder.h"
#include "device.h" #include "device.h"
#include "events.h" #include "events.h"
#include "file_handler.h"
#include "frames.h" #include "frames.h"
#include "fpscounter.h" #include "fps_counter.h"
#include "inputmanager.h" #include "input_manager.h"
#include "log.h" #include "log.h"
#include "lockutil.h" #include "lock_util.h"
#include "net.h" #include "net.h"
#include "screen.h" #include "screen.h"
#include "server.h" #include "server.h"
#include "tinyxpm.h" #include "tiny_xpm.h"
#include "installer.h"
static struct server server = SERVER_INITIALIZER; static struct server server = SERVER_INITIALIZER;
static struct screen screen = SCREEN_INITIALIZER; static struct screen screen = SCREEN_INITIALIZER;
static struct frames frames; static struct frames frames;
static struct decoder decoder; static struct decoder decoder;
static struct controller controller; static struct controller controller;
static struct installer installer; static struct file_handler file_handler;
static struct input_manager input_manager = { static struct input_manager input_manager = {
.controller = &controller, .controller = &controller,
@@ -56,7 +56,12 @@ static int event_watcher(void *data, SDL_Event *event) {
} }
#endif #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 #ifdef CONTINUOUS_RESIZING_WORKAROUND
SDL_AddEventWatch(event_watcher, NULL); SDL_AddEventWatch(event_watcher, NULL);
#endif #endif
@@ -65,10 +70,10 @@ static void event_loop(void) {
switch (event.type) { switch (event.type) {
case EVENT_DECODER_STOPPED: case EVENT_DECODER_STOPPED:
LOGD("Video decoder stopped"); LOGD("Video decoder stopped");
return; return SDL_FALSE;
case SDL_QUIT: case SDL_QUIT:
LOGD("User requested to quit"); LOGD("User requested to quit");
return; return SDL_TRUE;
case EVENT_NEW_FRAME: case EVENT_NEW_FRAME:
if (!screen.has_frame) { if (!screen.has_frame) {
screen.has_frame = SDL_TRUE; screen.has_frame = SDL_TRUE;
@@ -76,13 +81,14 @@ static void event_loop(void) {
screen_show_window(&screen); screen_show_window(&screen);
} }
if (!screen_update_frame(&screen, &frames)) { if (!screen_update_frame(&screen, &frames)) {
return; return SDL_FALSE;
} }
break; break;
case SDL_WINDOWEVENT: case SDL_WINDOWEVENT:
switch (event.window.event) { switch (event.window.event) {
case SDL_WINDOWEVENT_EXPOSED: case SDL_WINDOWEVENT_EXPOSED:
case SDL_WINDOWEVENT_SIZE_CHANGED: case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_update_scale(&screen);
screen_render(&screen); screen_render(&screen);
break; break;
} }
@@ -104,12 +110,20 @@ static void event_loop(void) {
case SDL_MOUSEBUTTONUP: case SDL_MOUSEBUTTONUP:
input_manager_process_mouse_button(&input_manager, &event.button); input_manager_process_mouse_button(&input_manager, &event.button);
break; break;
case SDL_DROPFILE: case SDL_DROPFILE: {
installer_install_apk(&installer, event.drop.file); 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; break;
} }
} }
} }
return SDL_FALSE;
}
static process_t set_show_touches_enabled(const char *serial, SDL_bool enabled) { static process_t set_show_touches_enabled(const char *serial, SDL_bool enabled) {
const char *value = enabled ? "1" : "0"; const char *value = enabled ? "1" : "0";
@@ -126,7 +140,7 @@ static void wait_show_touches(process_t process) {
SDL_bool scrcpy(const struct scrcpy_options *options) { SDL_bool scrcpy(const struct scrcpy_options *options) {
if (!server_start(&server, options->serial, options->port, 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; return SDL_FALSE;
} }
@@ -174,7 +188,7 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
goto finally_destroy_server; goto finally_destroy_server;
} }
if (!installer_init(&installer, server.serial)) { if (!file_handler_init(&file_handler, server.serial)) {
ret = SDL_FALSE; ret = SDL_FALSE;
server_stop(&server); server_stop(&server);
goto finally_destroy_frames; goto finally_destroy_frames;
@@ -187,7 +201,7 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
if (!decoder_start(&decoder)) { if (!decoder_start(&decoder)) {
ret = SDL_FALSE; ret = SDL_FALSE;
server_stop(&server); server_stop(&server);
goto finally_destroy_installer; goto finally_destroy_file_handler;
} }
if (!controller_init(&controller, device_socket)) { if (!controller_init(&controller, device_socket)) {
@@ -210,7 +224,11 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
show_touches_waited = SDL_TRUE; show_touches_waited = SDL_TRUE;
} }
event_loop(); if (options->fullscreen) {
screen_switch_fullscreen(&screen);
}
ret = event_loop();
LOGD("quit..."); LOGD("quit...");
screen_destroy(&screen); screen_destroy(&screen);
@@ -225,10 +243,10 @@ finally_stop_decoder:
// stop the server before decoder_join() to wake up the decoder // stop the server before decoder_join() to wake up the decoder
server_stop(&server); server_stop(&server);
decoder_join(&decoder); decoder_join(&decoder);
finally_destroy_installer: finally_destroy_file_handler:
installer_stop(&installer); file_handler_stop(&file_handler);
installer_join(&installer); file_handler_join(&file_handler);
installer_destroy(&installer); file_handler_destroy(&file_handler);
finally_destroy_frames: finally_destroy_frames:
frames_destroy(&frames); frames_destroy(&frames);
finally_destroy_server: finally_destroy_server:

View File

@@ -5,10 +5,12 @@
struct scrcpy_options { struct scrcpy_options {
const char *serial; const char *serial;
const char *crop;
Uint16 port; Uint16 port;
Uint16 max_size; Uint16 max_size;
Uint32 bit_rate; Uint32 bit_rate;
SDL_bool show_touches; SDL_bool show_touches;
SDL_bool fullscreen;
}; };
SDL_bool scrcpy(const struct scrcpy_options *options); SDL_bool scrcpy(const struct scrcpy_options *options);

View File

@@ -4,9 +4,9 @@
#include <string.h> #include <string.h>
#include "icon.xpm" #include "icon.xpm"
#include "lockutil.h" #include "lock_util.h"
#include "log.h" #include "log.h"
#include "tinyxpm.h" #include "tiny_xpm.h"
#define DISPLAY_MARGINS 96 #define DISPLAY_MARGINS 96
@@ -132,6 +132,17 @@ static inline struct size get_initial_optimal_size(struct size frame_size) {
return get_optimal_size(frame_size, frame_size); return get_optimal_size(frame_size, frame_size);
} }
// apply hidpi scaling and call SDL_RenderSetLogicalSize
static inline SDL_bool render_set_scaled_logical_size(struct screen *screen, struct size size) {
int w, h, dw, dh;
SDL_GL_GetDrawableSize(screen->window, &w, &h);
SDL_GetWindowSize(screen->window, &dw, &dh);
// use 32 bits unsigned not to lose precision (width and height fit in 16 bits)
int scaled_x = (Uint32) size.width * (Uint32) w / (Uint32) dw;
int scaled_y = (Uint32) size.height * (Uint32) h / (Uint32) dh;
return SDL_RenderSetLogicalSize(screen->renderer, scaled_x, scaled_y);
}
void screen_init(struct screen *screen) { void screen_init(struct screen *screen) {
*screen = (struct screen) SCREEN_INITIALIZER; *screen = (struct screen) SCREEN_INITIALIZER;
} }
@@ -163,7 +174,7 @@ SDL_bool screen_init_rendering(struct screen *screen, const char *device_name, s
return SDL_FALSE; return SDL_FALSE;
} }
if (SDL_RenderSetLogicalSize(screen->renderer, frame_size.width, frame_size.height)) { if (render_set_scaled_logical_size(screen, frame_size)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError()); LOGE("Could not set renderer logical size: %s", SDL_GetError());
screen_destroy(screen); screen_destroy(screen);
return SDL_FALSE; return SDL_FALSE;
@@ -208,7 +219,7 @@ void screen_destroy(struct screen *screen) {
// recreate the texture and resize the window if the frame size has changed // recreate the texture and resize the window if the frame size has changed
static SDL_bool prepare_for_frame(struct screen *screen, struct size new_frame_size) { static SDL_bool prepare_for_frame(struct screen *screen, struct size new_frame_size) {
if (screen->frame_size.width != new_frame_size.width || screen->frame_size.height != new_frame_size.height) { if (screen->frame_size.width != new_frame_size.width || screen->frame_size.height != new_frame_size.height) {
if (SDL_RenderSetLogicalSize(screen->renderer, new_frame_size.width, new_frame_size.height)) { if (render_set_scaled_logical_size(screen, new_frame_size)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError()); LOGE("Could not set renderer logical size: %s", SDL_GetError());
return SDL_FALSE; return SDL_FALSE;
} }
@@ -302,3 +313,7 @@ void screen_resize_to_pixel_perfect(struct screen *screen) {
LOGD("Resized to pixel-perfect"); LOGD("Resized to pixel-perfect");
} }
} }
void screen_update_scale(struct screen *screen) {
render_set_scaled_logical_size(screen, screen->frame_size);
}

View File

@@ -66,4 +66,8 @@ void screen_resize_to_fit(struct screen *screen);
// resize window to 1:1 (pixel-perfect) // resize window to 1:1 (pixel-perfect)
void screen_resize_to_pixel_perfect(struct screen *screen); void screen_resize_to_pixel_perfect(struct screen *screen);
// recompute the scale in case the window moved from/to another screen with a
// different HiDPI
void screen_update_scale(struct screen *screen);
#endif #endif

View File

@@ -77,7 +77,8 @@ static SDL_bool disable_tunnel(struct server *server) {
} }
static process_t execute_server(const char *serial, 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 max_size_string[6];
char bit_rate_string[11]; char bit_rate_string[11];
sprintf(max_size_string, "%"PRIu16, max_size); sprintf(max_size_string, "%"PRIu16, max_size);
@@ -91,6 +92,7 @@ static process_t execute_server(const char *serial,
max_size_string, max_size_string,
bit_rate_string, bit_rate_string,
tunnel_forward ? "true" : "false", tunnel_forward ? "true" : "false",
crop ? crop : "",
}; };
return adb_execute(serial, cmd, sizeof(cmd) / sizeof(cmd[0])); 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, 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; server->local_port = local_port;
if (serial) { 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 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->process == PROCESS_NONE) {
if (!server->tunnel_forward) { if (!server->tunnel_forward) {
close_socket(&server->server_socket); close_socket(&server->server_socket);
@@ -207,7 +210,7 @@ socket_t server_connect_to(struct server *server) {
if (!server->tunnel_forward) { if (!server->tunnel_forward) {
server->device_socket = net_accept(server->server_socket); server->device_socket = net_accept(server->server_socket);
} else { } else {
Uint32 attempts = 50; Uint32 attempts = 100;
Uint32 delay = 100; // ms Uint32 delay = 100; // ms
server->device_socket = connect_to_server(server->local_port, attempts, delay); 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 // push, enable tunnel et start the server
SDL_bool server_start(struct server *server, const char *serial, Uint16 local_port, 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 // block until the communication with the server is established
socket_t server_connect_to(struct server *server); 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 xstrncpy(char *dest, const char *src, size_t n) {
size_t i; size_t i;

View File

@@ -1,5 +1,7 @@
#include "command.h" #include "command.h"
#include <errno.h>
#include <fcntl.h>
#include <signal.h> #include <signal.h>
#include <stdlib.h> #include <stdlib.h>
#include <sys/types.h> #include <sys/types.h>
@@ -7,18 +9,65 @@
#include <unistd.h> #include <unistd.h>
#include "log.h" #include "log.h"
pid_t cmd_execute(const char *path, const char *const argv[]) { enum process_result cmd_execute(const char *path, const char *const argv[], pid_t *pid) {
pid_t pid = fork(); int fd[2];
if (pid == -1) {
perror("fork"); if (pipe(fd) == -1) {
return -1; perror("pipe");
return PROCESS_ERROR_GENERIC;
} }
if (pid == 0) {
enum process_result ret = PROCESS_SUCCESS;
*pid = fork();
if (*pid == -1) {
perror("fork");
ret = PROCESS_ERROR_GENERIC;
goto end;
}
if (*pid > 0) {
// parent close write side
close(fd[1]);
fd[1] = -1;
// wait for EOF or receive errno from child
if (read(fd[0], &ret, sizeof(ret)) == -1) {
perror("read");
ret = PROCESS_ERROR_GENERIC;
goto end;
}
} else if (*pid == 0) {
// child close read side
close(fd[0]);
if (fcntl(fd[1], F_SETFD, FD_CLOEXEC) == 0) {
execvp(path, (char *const *)argv); execvp(path, (char *const *)argv);
if (errno == ENOENT) {
ret = PROCESS_ERROR_MISSING_BINARY;
} else {
ret = PROCESS_ERROR_GENERIC;
}
perror("exec"); perror("exec");
} else {
perror("fcntl");
ret = PROCESS_ERROR_GENERIC;
}
// send ret to the parent
if (write(fd[1], &ret, sizeof(ret)) == -1) {
perror("write");
}
// close write side before exiting
close(fd[1]);
_exit(1); _exit(1);
} }
return pid;
end:
if (fd[0] != -1) {
close(fd[0]);
}
if (fd[1] != -1) {
close(fd[1]);
}
return ret;
} }
SDL_bool cmd_terminate(pid_t pid) { SDL_bool cmd_terminate(pid_t pid) {

View File

@@ -2,9 +2,9 @@
#include "config.h" #include "config.h"
#include "log.h" #include "log.h"
#include "strutil.h" #include "str_util.h"
HANDLE cmd_execute(const char *path, const char *const argv[]) { enum process_result cmd_execute(const char *path, const char *const argv[], HANDLE *handle) {
STARTUPINFO si; STARTUPINFO si;
PROCESS_INFORMATION pi; PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si)); memset(&si, 0, sizeof(si));
@@ -18,7 +18,8 @@ HANDLE cmd_execute(const char *path, const char *const argv[]) {
size_t ret = xstrjoin(cmd, argv, ' ', sizeof(cmd)); size_t ret = xstrjoin(cmd, argv, ' ', sizeof(cmd));
if (ret >= sizeof(cmd)) { if (ret >= sizeof(cmd)) {
LOGE("Command too long (%" PRIsizet " chars)", sizeof(cmd) - 1); LOGE("Command too long (%" PRIsizet " chars)", sizeof(cmd) - 1);
return NULL; *handle = NULL;
return PROCESS_ERROR_GENERIC;
} }
#ifdef WINDOWS_NOCONSOLE #ifdef WINDOWS_NOCONSOLE
@@ -27,10 +28,15 @@ HANDLE cmd_execute(const char *path, const char *const argv[]) {
int flags = 0; int flags = 0;
#endif #endif
if (!CreateProcess(NULL, cmd, NULL, NULL, FALSE, flags, NULL, NULL, &si, &pi)) { if (!CreateProcess(NULL, cmd, NULL, NULL, FALSE, flags, NULL, NULL, &si, &pi)) {
return NULL; *handle = NULL;
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
return PROCESS_ERROR_MISSING_BINARY;
}
return PROCESS_ERROR_GENERIC;
} }
return pi.hProcess; *handle = pi.hProcess;
return PROCESS_SUCCESS;
} }
SDL_bool cmd_terminate(HANDLE handle) { SDL_bool cmd_terminate(HANDLE handle) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,6 +15,6 @@ cpu = 'x86_64'
endian = 'little' endian = 'little'
[properties] [properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.0-win64-shared' prebuilt_ffmpeg_shared = 'ffmpeg-4.0.2-win64-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.0-win64-dev' prebuilt_ffmpeg_dev = 'ffmpeg-4.0.2-win64-dev'
prebuilt_sdl2 = 'SDL2-2.0.8/x86_64-w64-mingw32' 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') if get_option('build_app')
subdir('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-win64: prepare-sdl2 prepare-ffmpeg-shared-win64 prepare-ffmpeg-dev-win64 prepare-adb
prepare-ffmpeg-shared-win32: prepare-ffmpeg-shared-win32:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/shared/ffmpeg-4.0-win32-shared.zip \ @./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/shared/ffmpeg-4.0.2-win32-shared.zip \
530c92df0ca14c35901b4b681847d62da3c50a0cc9b7ced37b04968f6b5c243d \ cc190a3a4cf7bfbd4fbaa92609c1501a1de458055e6cfea8b745c1d515013aa8 \
ffmpeg-4.0-win32-shared ffmpeg-4.0.2-win32-shared
prepare-ffmpeg-dev-win32: prepare-ffmpeg-dev-win32:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/dev/ffmpeg-4.0-win32-dev.zip \ @./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/dev/ffmpeg-4.0.2-win32-dev.zip \
e2f5200b5e73c4d0abb9b89c4ffc0438f92a0aadc54c81cf57e18c81a9f11c6b \ c72c74bad74ac0541f1b43090c26a50017c49041c182a703abd2057bb8cdc238 \
ffmpeg-4.0-win32-dev ffmpeg-4.0.2-win32-dev
prepare-ffmpeg-shared-win64: prepare-ffmpeg-shared-win64:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/shared/ffmpeg-4.0-win64-shared.zip \ @./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/shared/ffmpeg-4.0.2-win64-shared.zip \
8fe2d344463dbefc2db4239a4203a55ed0324faceaae57276a40c4fabda84c37 \ ede566aca8b5348dff85570f9638c6bad33209f9419f79db7bde7daa37599bff \
ffmpeg-4.0-win64-shared ffmpeg-4.0.2-win64-shared
prepare-ffmpeg-dev-win64: prepare-ffmpeg-dev-win64:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/dev/ffmpeg-4.0-win64-dev.zip \ @./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/dev/ffmpeg-4.0.2-win64-dev.zip \
facced738eabfc53fa92834dea8b24426f64db61298688fed480145945be07fa \ 23ee994161c51285cb956b98d3caa499d48083dae7b26c1fdf77f22e98df1c5f \
ffmpeg-4.0-win64-dev ffmpeg-4.0.2-win64-dev
prepare-sdl2: prepare-sdl2:
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.8-mingw.tar.gz \ @./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.8-mingw.tar.gz \
@@ -35,6 +35,6 @@ prepare-sdl2:
SDL2-2.0.8 SDL2-2.0.8
prepare-adb: prepare-adb:
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r27.0.1-windows.zip \ @./prepare-dep https://dl.google.com/android/repository/platform-tools_r28.0.0-windows.zip \
880662adfb0d6911ff250b9e13930ae1a4110fc36d5866afd4f8f56d935f7939 \ e2c1ec7c8e9b71cf1c8befd3bff91d06b26dd334c3f32b3817e9d46ba260b0e8 \
platform-tools platform-tools

View File

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

View File

@@ -5,9 +5,9 @@ import android.net.LocalSocket;
import android.net.LocalSocketAddress; import android.net.LocalSocketAddress;
import java.io.Closeable; import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
public final class DesktopConnection implements Closeable { public final class DesktopConnection implements Closeable {
@@ -18,14 +18,14 @@ public final class DesktopConnection implements Closeable {
private final LocalSocket socket; private final LocalSocket socket;
private final InputStream inputStream; private final InputStream inputStream;
private final OutputStream outputStream; private final FileDescriptor fd;
private final ControlEventReader reader = new ControlEventReader(); private final ControlEventReader reader = new ControlEventReader();
private DesktopConnection(LocalSocket socket) throws IOException { private DesktopConnection(LocalSocket socket) throws IOException {
this.socket = socket; this.socket = socket;
inputStream = socket.getInputStream(); inputStream = socket.getInputStream();
outputStream = socket.getOutputStream(); fd = socket.getFileDescriptor();
} }
private static LocalSocket connect(String abstractName) throws IOException { private static LocalSocket connect(String abstractName) throws IOException {
@@ -78,11 +78,11 @@ public final class DesktopConnection implements Closeable {
buffer[DEVICE_NAME_FIELD_LENGTH + 1] = (byte) width; buffer[DEVICE_NAME_FIELD_LENGTH + 1] = (byte) width;
buffer[DEVICE_NAME_FIELD_LENGTH + 2] = (byte) (height >> 8); buffer[DEVICE_NAME_FIELD_LENGTH + 2] = (byte) (height >> 8);
buffer[DEVICE_NAME_FIELD_LENGTH + 3] = (byte) height; buffer[DEVICE_NAME_FIELD_LENGTH + 3] = (byte) height;
outputStream.write(buffer, 0, buffer.length); IO.writeFully(fd, buffer, 0, buffer.length);
} }
public OutputStream getOutputStream() { public FileDescriptor getFd() {
return outputStream; return fd;
} }
public ControlEvent receiveControlEvent() throws IOException { public ControlEvent receiveControlEvent() throws IOException {

View File

@@ -19,32 +19,14 @@ public final class Device {
private ScreenInfo screenInfo; private ScreenInfo screenInfo;
private RotationListener rotationListener; private RotationListener rotationListener;
private boolean rotated;
public Device(Options options) { public Device(Options options) {
options.setCrop(new Rect(0, 0, 1280, 1440)); screenInfo = computeScreenInfo(options.getCrop(), options.getMaxSize());
// 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);
registerRotationWatcher(new IRotationWatcher.Stub() { registerRotationWatcher(new IRotationWatcher.Stub() {
@Override @Override
public void onRotationChanged(int rotation) { public void onRotationChanged(int rotation) throws RemoteException {
boolean rotated = (rotation & 1) != 0;
synchronized (Device.this) { synchronized (Device.this) {
// Do not call getDisplayInfo(), the resulting rotation may be inconsistent with screenInfo = screenInfo.withRotation(rotation);
// 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;
// notify // notify
if (rotationListener != null) { if (rotationListener != null) {
@@ -59,6 +41,59 @@ public final class Device {
return screenInfo; 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) { public Point getPhysicalPoint(Position position) {
// it hides the field on purpose, to read it with a lock // it hides the field on purpose, to read it with a lock
@SuppressWarnings("checkstyle:HiddenField") @SuppressWarnings("checkstyle:HiddenField")
@@ -72,9 +107,9 @@ public final class Device {
} }
Rect contentRect = screenInfo.getContentRect(); Rect contentRect = screenInfo.getContentRect();
Point point = position.getPoint(); Point point = position.getPoint();
int deviceX = contentRect.left + point.x * contentRect.width() / videoSize.getWidth(); int scaledX = contentRect.left + point.x * contentRect.width() / videoSize.getWidth();
int deviceY = contentRect.top + point.y * contentRect.height() / videoSize.getHeight(); int scaledY = contentRect.top + point.y * contentRect.height() / videoSize.getHeight();
return new Point(deviceX, deviceY); return new Point(scaledX, scaledY);
} }
public static String getDeviceName() { public static String getDeviceName() {
@@ -96,4 +131,8 @@ public final class Device {
public synchronized void setRotationListener(RotationListener rotationListener) { public synchronized void setRotationListener(RotationListener rotationListener) {
this.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() { public int getRotation() {
return rotation; 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

@@ -0,0 +1,31 @@
package com.genymobile.scrcpy;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import java.io.FileDescriptor;
import java.io.IOException;
import java.nio.ByteBuffer;
public class IO {
private IO() {
// not instantiable
}
public static void writeFully(FileDescriptor fd, ByteBuffer from) throws IOException {
while (from.hasRemaining()) {
try {
Os.write(fd, from);
} catch (ErrnoException e) {
if (e.errno != OsConstants.EINTR) {
throw new IOException(e);
}
}
}
}
public static void writeFully(FileDescriptor fd, byte[] buffer, int offset, int len) throws IOException {
writeFully(fd, ByteBuffer.wrap(buffer, offset, len));
}
}

View File

@@ -9,8 +9,8 @@ import android.media.MediaFormat;
import android.os.IBinder; import android.os.IBinder;
import android.view.Surface; import android.view.Surface;
import java.io.FileDescriptor;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -48,7 +48,7 @@ public class ScreenEncoder implements Device.RotationListener {
return rotationChanged.getAndSet(false); return rotationChanged.getAndSet(false);
} }
public void streamScreen(Device device, OutputStream outputStream) throws IOException { public void streamScreen(Device device, FileDescriptor fd) throws IOException {
MediaFormat format = createFormat(bitRate, frameRate, iFrameInterval); MediaFormat format = createFormat(bitRate, frameRate, iFrameInterval);
device.setRotationListener(this); device.setRotationListener(this);
boolean alive; boolean alive;
@@ -64,7 +64,7 @@ public class ScreenEncoder implements Device.RotationListener {
setDisplaySurface(display, surface, contentRect, videoRect); setDisplaySurface(display, surface, contentRect, videoRect);
codec.start(); codec.start();
try { try {
alive = encode(codec, outputStream); alive = encode(codec, fd);
} finally { } finally {
codec.stop(); codec.stop();
destroyDisplay(display); destroyDisplay(display);
@@ -77,9 +77,7 @@ public class ScreenEncoder implements Device.RotationListener {
} }
} }
private boolean encode(MediaCodec codec, OutputStream outputStream) throws IOException { private boolean encode(MediaCodec codec, FileDescriptor fd) throws IOException {
@SuppressWarnings("checkstyle:MagicNumber")
byte[] buf = new byte[bitRate / 8]; // may contain up to 1 second of video
boolean eof = false; boolean eof = false;
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
while (!consumeRotationChange() && !eof) { while (!consumeRotationChange() && !eof) {
@@ -91,15 +89,8 @@ public class ScreenEncoder implements Device.RotationListener {
break; break;
} }
if (outputBufferId >= 0) { if (outputBufferId >= 0) {
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId); ByteBuffer codecBuffer = codec.getOutputBuffer(outputBufferId);
while (outputBuffer.hasRemaining()) { IO.writeFully(fd, codecBuffer);
int remaining = outputBuffer.remaining();
int len = Math.min(buf.length, remaining);
// the outputBuffer is probably direct (it has no underlying array), and LocalSocket does not expose channels,
// so we must copy the data locally to write them manually to the output stream
outputBuffer.get(buf, 0, len);
outputStream.write(buf, 0, len);
}
} }
} finally { } finally {
if (outputBufferId >= 0) { if (outputBufferId >= 0) {

View File

@@ -3,79 +3,29 @@ package com.genymobile.scrcpy;
import android.graphics.Rect; import android.graphics.Rect;
public final class ScreenInfo { public final class ScreenInfo {
private final Size deviceSize; private final Rect contentRect; // device size, possibly cropped
private final Size videoSize; private final Size videoSize;
private final Rect crop; private final boolean rotated;
private ScreenInfo(Size deviceSize, Size videoSize, Rect crop, boolean rotated) { public ScreenInfo(Rect contentRect, Size videoSize, boolean rotated) {
this.deviceSize = deviceSize; this.contentRect = contentRect;
this.videoSize = videoSize; this.videoSize = videoSize;
this.crop = crop; this.rotated = rotated;
} }
public static ScreenInfo create(Size deviceSize, int maxSize, Rect crop, boolean rotated) { public Rect getContentRect() {
Size inputSize; return contentRect;
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 Size getVideoSize() { public Size getVideoSize() {
return videoSize; return videoSize;
} }
public Rect getCrop() { public ScreenInfo withRotation(int rotation) {
return crop; boolean newRotated = (rotation & 1) != 0;
if (rotated == newRotated) {
return this;
} }
return new ScreenInfo(Device.flipRect(contentRect), videoSize.rotate(), newRotated);
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);
} }
} }

View File

@@ -1,5 +1,7 @@
package com.genymobile.scrcpy; package com.genymobile.scrcpy;
import android.graphics.Rect;
import java.io.IOException; import java.io.IOException;
public final class Server { public final class Server {
@@ -19,7 +21,7 @@ public final class Server {
try { try {
// synchronous // synchronous
screenEncoder.streamScreen(device, connection.getOutputStream()); screenEncoder.streamScreen(device, connection.getFd());
} catch (IOException e) { } catch (IOException e) {
// this is expected on close // this is expected on close
Ln.d("Screen streaming stopped"); Ln.d("Screen streaming stopped");
@@ -63,8 +65,30 @@ public final class Server {
boolean tunnelForward = Boolean.parseBoolean(args[2]); boolean tunnelForward = Boolean.parseBoolean(args[2]);
options.setTunnelForward(tunnelForward); options.setTunnelForward(tunnelForward);
if (args.length < 4) {
return options; 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 { public static void main(String... args) throws Exception {
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {