Compare commits

..

1 Commits

Author SHA1 Message Date
Romain Vimont
55187864ba Add some logs to debug issue 386 2019-01-06 12:23:19 +01:00
71 changed files with 1352 additions and 2362 deletions

View File

@@ -234,10 +234,10 @@ You can then [run](README.md#run) _scrcpy_.
## Prebuilt server ## Prebuilt server
- [`scrcpy-server-v1.7.jar`][direct-scrcpy-server] - [`scrcpy-server-v1.5.jar`][direct-scrcpy-server]
_(SHA-256: ee86ec8424f7dc50cacdf927312bdb46e0aa0d68611da584dc4b16d8057bc25e)_ _(SHA-256: d97aab6f60294e33e7ff79c2856ad3e01f912892395131f4f337e9ece03c24de)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.7/scrcpy-server-v1.7.jar [direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.5-fixversion/scrcpy-server-v1.5.jar
Download the prebuilt server somewhere, and specify its path during the Meson Download the prebuilt server somewhere, and specify its path during the Meson
configuration: configuration:

14
FAQ.md
View File

@@ -24,6 +24,20 @@ If you still encounter problems, please see [issue 9].
[issue 9]: https://github.com/Genymobile/scrcpy/issues/9 [issue 9]: https://github.com/Genymobile/scrcpy/issues/9
### I get a black screen for some applications like Silence
This is expected, they requested to [protect] the screen.
In [Silence], you can disable it in settings → Privacy → Screen security.
[protect]: https://developer.android.com/reference/android/view/Display#FLAG_SECURE
[silence]: https://f-droid.org/en/packages/org.smssecure.smssecure/
See [issue 36].
[issue 36]: https://github.com/Genymobile/scrcpy/issues/36
### Mouse clicks do not work ### Mouse clicks do not work
On some devices, you may need to enable an option to allow [simulating input]. On some devices, you may need to enable an option to allow [simulating input].

View File

@@ -34,7 +34,7 @@ WIN32_TARGET := $(WIN32_TARGET_DIR)-$(VERSION).zip
WIN64_TARGET := $(WIN64_TARGET_DIR)-$(VERSION).zip WIN64_TARGET := $(WIN64_TARGET_DIR)-$(VERSION).zip
release: clean zip-win32 zip-win64 sums release: clean zip-win32 zip-win64 sums
@echo "Windows archives generated in $(DIST)/" @echo "Release created in $(DIST)/."
clean: clean:
$(GRADLE) clean $(GRADLE) clean
@@ -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.1-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.1-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.1-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.1-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.1-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.1-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.1-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.1-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.7) # scrcpy (v1.5)
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.
@@ -47,13 +47,13 @@ For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
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.7.zip`][direct-win32] - [`scrcpy-win32-v1.5.zip`][direct-win32]
_(SHA-256: 98ae36f2da0b8212c07066fd93139650554274f863d4cee0781501a0c84f7c23)_ _(SHA-256: 46ae0d4c1c6bd049ec4a30080d2ad91a32b31d3f758afdca2c3a915ecabf02c1)_
- [`scrcpy-win64-v1.7.zip`][direct-win64] - [`scrcpy-win64-v1.5.zip`][direct-win64]
_(SHA-256: b41416547521062f19e3f3f539e89a70e713bd086e69ef1b29c128993f7aa462)_ _(SHA-256: 89daa07325129617cf943a84bc4e304ee5e57118416fe265b9b5d4a1bf87c501)_
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.7/scrcpy-win32-v1.7.zip [direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.5-fixversion/scrcpy-win32-v1.5.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.7/scrcpy-win64-v1.7.zip [direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.5-fixversion/scrcpy-win64-v1.5.zip
You can also [build the app manually][BUILD]. You can also [build the app manually][BUILD].
@@ -162,15 +162,7 @@ It is possible to record the screen while mirroring:
```bash ```bash
scrcpy --record file.mp4 scrcpy --record file.mp4
scrcpy -r file.mkv scrcpy -r file.mp4
```
To disable mirroring while recording:
```bash
scrcpy --no-display --record file.mp4
scrcpy -Nr file.mkv
# interrupt recording with Ctrl+C
``` ```
"Skipped frames" are recorded, even if they are not displayed in real time (for "Skipped frames" are recorded, even if they are not displayed in real time (for
@@ -204,16 +196,6 @@ scrcpy -f # short version
Fullscreen can then be toggled dynamically with `Ctrl`+`f`. Fullscreen can then be toggled dynamically with `Ctrl`+`f`.
### Always on top
The window of app can always be above others by:
```bash
scrcpy --always-on-top
scrcpy -T # short version
```
### Show touches ### Show touches
For presentations, it may be useful to show physical touches (on the physical For presentations, it may be useful to show physical touches (on the physical
@@ -247,17 +229,6 @@ _scrcpy_ window.
There is no visual feedback, a log is printed to the console. There is no visual feedback, a log is printed to the console.
### Read-only
To disable controls (everything which can interact with the device: input keys,
mouse events, drag&drop files):
```bash
scrcpy --no-control
scrcpy -n
```
### Forward audio ### Forward audio
Audio is not forwarded by _scrcpy_. Audio is not forwarded by _scrcpy_.
@@ -286,8 +257,6 @@ you are interested, see [issue 14].
| click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ (`Cmd`+`↓` on MacOS) | | click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ (`Cmd`+`↓` on MacOS) |
| click on `POWER` | `Ctrl`+`p` | | click on `POWER` | `Ctrl`+`p` |
| turn screen on | _Right-click²_ | | turn screen on | _Right-click²_ |
| expand notification panel | `Ctrl`+`n` |
| collapse notification panel | `Ctrl`+`Shift`+`n` |
| 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` |

View File

@@ -8,6 +8,7 @@ src = [
'src/device.c', 'src/device.c',
'src/file_handler.c', 'src/file_handler.c',
'src/fps_counter.c', 'src/fps_counter.c',
'src/frames.c',
'src/input_manager.c', 'src/input_manager.c',
'src/lock_util.c', 'src/lock_util.c',
'src/net.c', 'src/net.c',
@@ -17,8 +18,6 @@ src = [
'src/server.c', 'src/server.c',
'src/str_util.c', 'src/str_util.c',
'src/tiny_xpm.c', 'src/tiny_xpm.c',
'src/stream.c',
'src/video_buffer.c',
] ]
if not get_option('crossbuild_windows') if not get_option('crossbuild_windows')

View File

@@ -1,33 +1,28 @@
#ifndef BUFFER_UTIL_H #ifndef BUFFER_UTIL_H
#define BUFFER_UTIL_H #define BUFFER_UTIL_H
#include <stdbool.h> #include <SDL2/SDL_stdinc.h>
#include <stdint.h>
static inline void static inline void buffer_write16be(Uint8 *buf, Uint16 value) {
buffer_write16be(uint8_t *buf, uint16_t value) {
buf[0] = value >> 8; buf[0] = value >> 8;
buf[1] = value; buf[1] = value;
} }
static inline void static inline void buffer_write32be(Uint8 *buf, Uint32 value) {
buffer_write32be(uint8_t *buf, uint32_t value) {
buf[0] = value >> 24; buf[0] = value >> 24;
buf[1] = value >> 16; buf[1] = value >> 16;
buf[2] = value >> 8; buf[2] = value >> 8;
buf[3] = value; buf[3] = value;
} }
static inline uint32_t static inline Uint32 buffer_read32be(Uint8 *buf) {
buffer_read32be(uint8_t *buf) {
return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
} }
static inline static inline Uint64 buffer_read64be(Uint8 *buf) {
uint64_t buffer_read64be(uint8_t *buf) { Uint32 msb = buffer_read32be(buf);
uint32_t msb = buffer_read32be(buf); Uint32 lsb = buffer_read32be(&buf[4]);
uint32_t lsb = buffer_read32be(&buf[4]); return ((Uint64) msb << 32) | lsb;
return ((uint64_t) msb << 32) | lsb;
} }
#endif #endif

View File

@@ -10,8 +10,7 @@
static const char *adb_command; static const char *adb_command;
static inline const char * static inline const char *get_adb_command(void) {
get_adb_command(void) {
if (!adb_command) { if (!adb_command) {
adb_command = getenv("ADB"); adb_command = getenv("ADB");
if (!adb_command) if (!adb_command)
@@ -20,8 +19,7 @@ get_adb_command(void) {
return adb_command; return adb_command;
} }
static void static void show_adb_err_msg(enum process_result err) {
show_adb_err_msg(enum process_result err) {
switch (err) { switch (err) {
case PROCESS_ERROR_GENERIC: case PROCESS_ERROR_GENERIC:
LOGE("Failed to execute adb"); LOGE("Failed to execute adb");
@@ -36,8 +34,7 @@ show_adb_err_msg(enum process_result err) {
} }
} }
process_t process_t adb_execute(const char *serial, const char *const adb_cmd[], int len) {
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; process_t process;
@@ -60,9 +57,7 @@ adb_execute(const char *serial, const char *const adb_cmd[], int len) {
return process; return process;
} }
process_t process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name) {
adb_forward(const char *serial, uint16_t local_port,
const char *device_socket_name) {
char local[4 + 5 + 1]; // tcp:PORT char local[4 + 5 + 1]; // tcp:PORT
char remote[108 + 14 + 1]; // localabstract:NAME char remote[108 + 14 + 1]; // localabstract:NAME
sprintf(local, "tcp:%" PRIu16, local_port); sprintf(local, "tcp:%" PRIu16, local_port);
@@ -71,17 +66,14 @@ adb_forward(const char *serial, uint16_t local_port,
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
process_t process_t adb_forward_remove(const char *serial, uint16_t local_port) {
adb_forward_remove(const char *serial, uint16_t local_port) {
char local[4 + 5 + 1]; // tcp:PORT char local[4 + 5 + 1]; // tcp:PORT
sprintf(local, "tcp:%" PRIu16, local_port); sprintf(local, "tcp:%" PRIu16, local_port);
const char *const adb_cmd[] = {"forward", "--remove", local}; const char *const adb_cmd[] = {"forward", "--remove", local};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
process_t process_t adb_reverse(const char *serial, const char *device_socket_name, uint16_t local_port) {
adb_reverse(const char *serial, const char *device_socket_name,
uint16_t local_port) {
char local[4 + 5 + 1]; // tcp:PORT char local[4 + 5 + 1]; // tcp:PORT
char remote[108 + 14 + 1]; // localabstract:NAME char remote[108 + 14 + 1]; // localabstract:NAME
sprintf(local, "tcp:%" PRIu16, local_port); sprintf(local, "tcp:%" PRIu16, local_port);
@@ -90,16 +82,14 @@ adb_reverse(const char *serial, const char *device_socket_name,
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
process_t process_t adb_reverse_remove(const char *serial, const char *device_socket_name) {
adb_reverse_remove(const char *serial, const char *device_socket_name) {
char remote[108 + 14 + 1]; // localabstract:NAME char remote[108 + 14 + 1]; // localabstract:NAME
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
const char *const adb_cmd[] = {"reverse", "--remove", remote}; const char *const adb_cmd[] = {"reverse", "--remove", remote};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
process_t process_t adb_push(const char *serial, const char *local, const char *remote) {
adb_push(const char *serial, const char *local, const char *remote) {
#ifdef __WINDOWS__ #ifdef __WINDOWS__
// Windows will parse the string, so the paths must be quoted // Windows will parse the string, so the paths must be quoted
// (see sys/win/command.c) // (see sys/win/command.c)
@@ -125,8 +115,7 @@ adb_push(const char *serial, const char *local, const char *remote) {
return proc; return proc;
} }
process_t process_t adb_install(const char *serial, const char *local) {
adb_install(const char *serial, const char *local) {
#ifdef __WINDOWS__ #ifdef __WINDOWS__
// Windows will parse the string, so the local name must be quoted // Windows will parse the string, so the local name must be quoted
// (see sys/win/command.c) // (see sys/win/command.c)
@@ -146,11 +135,15 @@ adb_install(const char *serial, const char *local) {
return proc; return proc;
} }
bool process_t adb_remove_path(const char *serial, const char *path) {
process_check_success(process_t proc, const char *name) { const char *const adb_cmd[] = {"shell", "rm", path};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
SDL_bool process_check_success(process_t proc, const char *name) {
if (proc == PROCESS_NONE) { if (proc == PROCESS_NONE) {
LOGE("Could not execute \"%s\"", name); LOGE("Could not execute \"%s\"", name);
return false; return SDL_FALSE;
} }
exit_code_t exit_code; exit_code_t exit_code;
if (!cmd_simple_wait(proc, &exit_code)) { if (!cmd_simple_wait(proc, &exit_code)) {
@@ -159,7 +152,7 @@ process_check_success(process_t proc, const char *name) {
} else { } else {
LOGE("\"%s\" exited unexpectedly", name); LOGE("\"%s\" exited unexpectedly", name);
} }
return false; return SDL_FALSE;
} }
return true; return SDL_TRUE;
} }

View File

@@ -1,36 +1,35 @@
#ifndef COMMAND_H #ifndef COMMAND_H
#define COMMAND_H #define COMMAND_H
#include <stdbool.h>
#include <inttypes.h> #include <inttypes.h>
#include <SDL2/SDL_stdinc.h>
#include <SDL2/SDL_platform.h>
#ifdef _WIN32
// not needed here, but winsock2.h must never be included AFTER windows.h
# include <winsock2.h>
# include <windows.h>
# define PRIexitcode "lu"
// <https://stackoverflow.com/a/44383330/1987178> // <https://stackoverflow.com/a/44383330/1987178>
#ifdef _WIN32
# define PRIexitcode "lu"
# ifdef _WIN64 # ifdef _WIN64
# define PRIsizet PRIu64 # define PRIsizet PRIu64
# else # else
# define PRIsizet PRIu32 # define PRIsizet PRIu32
# endif # endif
#else
# define PRIsizet "zu"
# define PRIexitcode "d"
#endif
#ifdef __WINDOWS__
# include <winsock2.h> // not needed here, but must never be included AFTER windows.h
# include <windows.h>
# define PROCESS_NONE NULL # define PROCESS_NONE NULL
typedef HANDLE process_t; typedef HANDLE process_t;
typedef DWORD exit_code_t; typedef DWORD exit_code_t;
#else #else
# include <sys/types.h> # include <sys/types.h>
# define PRIsizet "zu"
# define PRIexitcode "d"
# define PROCESS_NONE -1 # define PROCESS_NONE -1
typedef pid_t process_t; typedef pid_t process_t;
typedef int exit_code_t; typedef int exit_code_t;
#endif #endif
# define NO_EXIT_CODE -1 # define NO_EXIT_CODE -1
enum process_result { enum process_result {
@@ -39,41 +38,21 @@ enum process_result {
PROCESS_ERROR_MISSING_BINARY, PROCESS_ERROR_MISSING_BINARY,
}; };
enum process_result enum process_result cmd_execute(const char *path, const char *const argv[], process_t *process);
cmd_execute(const char *path, const char *const argv[], process_t *process); SDL_bool cmd_terminate(process_t pid);
SDL_bool cmd_simple_wait(process_t pid, exit_code_t *exit_code);
bool process_t adb_execute(const char *serial, const char *const adb_cmd[], int len);
cmd_terminate(process_t pid); process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name);
process_t adb_forward_remove(const char *serial, uint16_t local_port);
bool process_t adb_reverse(const char *serial, const char *device_socket_name, uint16_t local_port);
cmd_simple_wait(process_t pid, exit_code_t *exit_code); process_t adb_reverse_remove(const char *serial, const char *device_socket_name);
process_t adb_push(const char *serial, const char *local, const char *remote);
process_t process_t adb_install(const char *serial, const char *local);
adb_execute(const char *serial, const char *const adb_cmd[], int len); process_t adb_remove_path(const char *serial, const char *path);
process_t
adb_forward(const char *serial, uint16_t local_port,
const char *device_socket_name);
process_t
adb_forward_remove(const char *serial, uint16_t local_port);
process_t
adb_reverse(const char *serial, const char *device_socket_name,
uint16_t local_port);
process_t
adb_reverse_remove(const char *serial, const char *device_socket_name);
process_t
adb_push(const char *serial, const char *local, const char *remote);
process_t
adb_install(const char *serial, const char *local);
// convenience function to wait for a successful process execution // convenience function to wait for a successful process execution
// automatically log process errors with the provided process name // automatically log process errors with the provided process name
bool SDL_bool process_check_success(process_t process, const char *name);
process_check_success(process_t process, const char *name);
#endif #endif

View File

@@ -1,26 +1,25 @@
#ifndef COMMON_H #ifndef COMMON_H
#define COMMON_H #define COMMON_H
#include <stdint.h> #include <SDL2/SDL_stdinc.h>
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
#define MIN(X,Y) (X) < (Y) ? (X) : (Y) #define MIN(X,Y) (X) < (Y) ? (X) : (Y)
#define MAX(X,Y) (X) > (Y) ? (X) : (Y) #define MAX(X,Y) (X) > (Y) ? (X) : (Y)
struct size { struct size {
uint16_t width; Uint16 width;
uint16_t height; Uint16 height;
}; };
struct point { struct point {
int32_t x; Uint16 x;
int32_t y; Uint16 y;
}; };
struct position { struct position {
// The video screen size may be different from the real device screen size, // The video screen size may be different from the real device screen size,
// so store to which size the absolute position apply, to scale it // so store to which size the absolute position apply, to scale it accordingly.
// accordingly.
struct size screen_size; struct size screen_size;
struct point point; struct point point;
}; };

View File

@@ -1,46 +0,0 @@
#ifndef COMPAT_H
#define COMPAT_H
#include <libavformat/version.h>
#include <SDL2/SDL_version.h>
// In ffmpeg/doc/APIchanges:
// 2016-04-11 - 6f69f7a / 9200514 - lavf 57.33.100 / 57.5.0 - avformat.h
// Add AVStream.codecpar, deprecate AVStream.codec.
#if (LIBAVFORMAT_VERSION_MICRO >= 100 /* FFmpeg */ && \
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 33, 100)) \
|| (LIBAVFORMAT_VERSION_MICRO < 100 && /* Libav */ \
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 5, 0))
# define SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
#endif
// In ffmpeg/doc/APIchanges:
// 2018-02-06 - 0694d87024 - lavf 58.9.100 - avformat.h
// Deprecate use of av_register_input_format(), av_register_output_format(),
// av_register_all(), av_iformat_next(), av_oformat_next().
// Add av_demuxer_iterate(), and av_muxer_iterate().
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 9, 100)
# define SCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API
#else
# define SCRCPY_LAVF_REQUIRES_REGISTER_ALL
#endif
// In ffmpeg/doc/APIchanges:
// 2016-04-21 - 7fc329e - lavc 57.37.100 - avcodec.h
// Add a new audio/video encoding and decoding API with decoupled input
// and output -- avcodec_send_packet(), avcodec_receive_frame(),
// avcodec_send_frame() and avcodec_receive_packet().
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 100)
# define SCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API
#endif
#if SDL_VERSION_ATLEAST(2, 0, 5)
// <https://wiki.libsdl.org/SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH>
# define SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH
// <https://wiki.libsdl.org/SDL_GetDisplayUsableBounds>
# define SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS
// <https://wiki.libsdl.org/SDL_WindowFlags>
# define SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
#endif
#endif

View File

@@ -1,21 +1,20 @@
#include "control_event.h" #include "control_event.h"
#include <SDL2/SDL_stdinc.h>
#include <string.h> #include <string.h>
#include "buffer_util.h" #include "buffer_util.h"
#include "lock_util.h" #include "lock_util.h"
#include "log.h" #include "log.h"
static void static void write_position(Uint8 *buf, const struct position *position) {
write_position(uint8_t *buf, const struct position *position) { buffer_write16be(&buf[0], position->point.x);
buffer_write32be(&buf[0], position->point.x); buffer_write16be(&buf[2], position->point.y);
buffer_write32be(&buf[4], position->point.y); buffer_write16be(&buf[4], position->screen_size.width);
buffer_write16be(&buf[8], position->screen_size.width); buffer_write16be(&buf[6], position->screen_size.height);
buffer_write16be(&buf[10], position->screen_size.height);
} }
int int control_event_serialize(const struct control_event *event, unsigned char *buf) {
control_event_serialize(const struct control_event *event, unsigned char *buf) {
buf[0] = event->type; buf[0] = event->type;
switch (event->type) { switch (event->type) {
case CONTROL_EVENT_TYPE_KEYCODE: case CONTROL_EVENT_TYPE_KEYCODE:
@@ -24,13 +23,13 @@ control_event_serialize(const struct control_event *event, unsigned char *buf) {
buffer_write32be(&buf[6], event->keycode_event.metastate); buffer_write32be(&buf[6], event->keycode_event.metastate);
return 10; return 10;
case CONTROL_EVENT_TYPE_TEXT: { case CONTROL_EVENT_TYPE_TEXT: {
// write length (2 bytes) + string (non nul-terminated) // write length (1 byte) + date (non nul-terminated)
size_t len = strlen(event->text_event.text); size_t len = strlen(event->text_event.text);
if (len > TEXT_MAX_LENGTH) { if (len > TEXT_MAX_LENGTH) {
// injecting a text takes time, so limit the text length // injecting a text takes time, so limit the text length
len = TEXT_MAX_LENGTH; len = TEXT_MAX_LENGTH;
} }
buffer_write16be(&buf[1], (uint16_t) len); buffer_write16be(&buf[1], (Uint16) len);
memcpy(&buf[3], event->text_event.text, len); memcpy(&buf[3], event->text_event.text, len);
return 3 + len; return 3 + len;
} }
@@ -38,12 +37,12 @@ control_event_serialize(const struct control_event *event, unsigned char *buf) {
buf[1] = event->mouse_event.action; buf[1] = event->mouse_event.action;
buffer_write32be(&buf[2], event->mouse_event.buttons); buffer_write32be(&buf[2], event->mouse_event.buttons);
write_position(&buf[6], &event->mouse_event.position); write_position(&buf[6], &event->mouse_event.position);
return 18; return 14;
case CONTROL_EVENT_TYPE_SCROLL: case CONTROL_EVENT_TYPE_SCROLL:
write_position(&buf[1], &event->scroll_event.position); write_position(&buf[1], &event->scroll_event.position);
buffer_write32be(&buf[13], (uint32_t) event->scroll_event.hscroll); buffer_write32be(&buf[9], (Uint32) event->scroll_event.hscroll);
buffer_write32be(&buf[17], (uint32_t) event->scroll_event.vscroll); buffer_write32be(&buf[13], (Uint32) event->scroll_event.vscroll);
return 21; return 17;
case CONTROL_EVENT_TYPE_COMMAND: case CONTROL_EVENT_TYPE_COMMAND:
buf[1] = event->command_event.action; buf[1] = event->command_event.action;
return 2; return 2;
@@ -53,33 +52,28 @@ control_event_serialize(const struct control_event *event, unsigned char *buf) {
} }
} }
void void control_event_destroy(struct control_event *event) {
control_event_destroy(struct control_event *event) {
if (event->type == CONTROL_EVENT_TYPE_TEXT) { if (event->type == CONTROL_EVENT_TYPE_TEXT) {
SDL_free(event->text_event.text); SDL_free(event->text_event.text);
} }
} }
bool SDL_bool control_event_queue_is_empty(const struct control_event_queue *queue) {
control_event_queue_is_empty(const struct control_event_queue *queue) {
return queue->head == queue->tail; return queue->head == queue->tail;
} }
bool SDL_bool control_event_queue_is_full(const struct control_event_queue *queue) {
control_event_queue_is_full(const struct control_event_queue *queue) {
return (queue->head + 1) % CONTROL_EVENT_QUEUE_SIZE == queue->tail; return (queue->head + 1) % CONTROL_EVENT_QUEUE_SIZE == queue->tail;
} }
bool SDL_bool control_event_queue_init(struct control_event_queue *queue) {
control_event_queue_init(struct control_event_queue *queue) {
queue->head = 0; queue->head = 0;
queue->tail = 0; queue->tail = 0;
// the current implementation may not fail // the current implementation may not fail
return true; return SDL_TRUE;
} }
void void control_event_queue_destroy(struct control_event_queue *queue) {
control_event_queue_destroy(struct control_event_queue *queue) {
int i = queue->tail; int i = queue->tail;
while (i != queue->head) { while (i != queue->head) {
control_event_destroy(&queue->data[i]); control_event_destroy(&queue->data[i]);
@@ -87,24 +81,20 @@ control_event_queue_destroy(struct control_event_queue *queue) {
} }
} }
bool SDL_bool control_event_queue_push(struct control_event_queue *queue, const struct control_event *event) {
control_event_queue_push(struct control_event_queue *queue,
const struct control_event *event) {
if (control_event_queue_is_full(queue)) { if (control_event_queue_is_full(queue)) {
return false; return SDL_FALSE;
} }
queue->data[queue->head] = *event; queue->data[queue->head] = *event;
queue->head = (queue->head + 1) % CONTROL_EVENT_QUEUE_SIZE; queue->head = (queue->head + 1) % CONTROL_EVENT_QUEUE_SIZE;
return true; return SDL_TRUE;
} }
bool SDL_bool control_event_queue_take(struct control_event_queue *queue, struct control_event *event) {
control_event_queue_take(struct control_event_queue *queue,
struct control_event *event) {
if (control_event_queue_is_empty(queue)) { if (control_event_queue_is_empty(queue)) {
return false; return SDL_FALSE;
} }
*event = queue->data[queue->tail]; *event = queue->data[queue->tail];
queue->tail = (queue->tail + 1) % CONTROL_EVENT_QUEUE_SIZE; queue->tail = (queue->tail + 1) % CONTROL_EVENT_QUEUE_SIZE;
return true; return SDL_TRUE;
} }

View File

@@ -1,9 +1,8 @@
#ifndef CONTROLEVENT_H #ifndef CONTROLEVENT_H
#define CONTROLEVENT_H #define CONTROLEVENT_H
#include <stdbool.h>
#include <stdint.h>
#include <SDL2/SDL_mutex.h> #include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h>
#include "android/input.h" #include "android/input.h"
#include "android/keycodes.h" #include "android/keycodes.h"
@@ -21,11 +20,7 @@ enum control_event_type {
CONTROL_EVENT_TYPE_COMMAND, CONTROL_EVENT_TYPE_COMMAND,
}; };
enum control_event_command { #define CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON 0
CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON,
CONTROL_EVENT_COMMAND_EXPAND_NOTIFICATION_PANEL,
CONTROL_EVENT_COMMAND_COLLAPSE_NOTIFICATION_PANEL,
};
struct control_event { struct control_event {
enum control_event_type type; enum control_event_type type;
@@ -45,11 +40,11 @@ struct control_event {
} mouse_event; } mouse_event;
struct { struct {
struct position position; struct position position;
int32_t hscroll; Sint32 hscroll;
int32_t vscroll; Sint32 vscroll;
} scroll_event; } scroll_event;
struct { struct {
enum control_event_command action; int action;
} command_event; } command_event;
}; };
}; };
@@ -61,31 +56,18 @@ struct control_event_queue {
}; };
// buf size must be at least SERIALIZED_EVENT_MAX_SIZE // buf size must be at least SERIALIZED_EVENT_MAX_SIZE
int int control_event_serialize(const struct control_event *event, unsigned char *buf);
control_event_serialize(const struct control_event *event, unsigned char *buf);
bool SDL_bool control_event_queue_init(struct control_event_queue *queue);
control_event_queue_init(struct control_event_queue *queue); void control_event_queue_destroy(struct control_event_queue *queue);
void SDL_bool control_event_queue_is_empty(const struct control_event_queue *queue);
control_event_queue_destroy(struct control_event_queue *queue); SDL_bool control_event_queue_is_full(const struct control_event_queue *queue);
bool
control_event_queue_is_empty(const struct control_event_queue *queue);
bool
control_event_queue_is_full(const struct control_event_queue *queue);
// event is copied, the queue does not use the event after the function returns // event is copied, the queue does not use the event after the function returns
bool SDL_bool control_event_queue_push(struct control_event_queue *queue, const struct control_event *event);
control_event_queue_push(struct control_event_queue *queue, SDL_bool control_event_queue_take(struct control_event_queue *queue, struct control_event *event);
const struct control_event *event);
bool void control_event_destroy(struct control_event *event);
control_event_queue_take(struct control_event_queue *queue,
struct control_event *event);
void
control_event_destroy(struct control_event *event);
#endif #endif

View File

@@ -1,45 +1,40 @@
#include "controller.h" #include "controller.h"
#include <SDL2/SDL_assert.h> #include <SDL2/SDL_assert.h>
#include "config.h" #include "config.h"
#include "lock_util.h" #include "lock_util.h"
#include "log.h" #include "log.h"
bool SDL_bool controller_init(struct controller *controller, socket_t video_socket) {
controller_init(struct controller *controller, socket_t video_socket) {
if (!control_event_queue_init(&controller->queue)) { if (!control_event_queue_init(&controller->queue)) {
return false; return SDL_FALSE;
} }
if (!(controller->mutex = SDL_CreateMutex())) { if (!(controller->mutex = SDL_CreateMutex())) {
return false; return SDL_FALSE;
} }
if (!(controller->event_cond = SDL_CreateCond())) { if (!(controller->event_cond = SDL_CreateCond())) {
SDL_DestroyMutex(controller->mutex); SDL_DestroyMutex(controller->mutex);
return false; return SDL_FALSE;
} }
controller->video_socket = video_socket; controller->video_socket = video_socket;
controller->stopped = false; controller->stopped = SDL_FALSE;
return true; return SDL_TRUE;
} }
void void controller_destroy(struct controller *controller) {
controller_destroy(struct controller *controller) {
SDL_DestroyCond(controller->event_cond); SDL_DestroyCond(controller->event_cond);
SDL_DestroyMutex(controller->mutex); SDL_DestroyMutex(controller->mutex);
control_event_queue_destroy(&controller->queue); control_event_queue_destroy(&controller->queue);
} }
bool SDL_bool controller_push_event(struct controller *controller, const struct control_event *event) {
controller_push_event(struct controller *controller, SDL_bool res;
const struct control_event *event) {
bool res;
mutex_lock(controller->mutex); mutex_lock(controller->mutex);
bool was_empty = control_event_queue_is_empty(&controller->queue); SDL_bool was_empty = control_event_queue_is_empty(&controller->queue);
res = control_event_queue_push(&controller->queue, event); res = control_event_queue_push(&controller->queue, event);
if (was_empty) { if (was_empty) {
cond_signal(controller->event_cond); cond_signal(controller->event_cond);
@@ -48,26 +43,22 @@ controller_push_event(struct controller *controller,
return res; return res;
} }
static bool static SDL_bool process_event(struct controller *controller, const struct control_event *event) {
process_event(struct controller *controller,
const struct control_event *event) {
unsigned char serialized_event[SERIALIZED_EVENT_MAX_SIZE]; unsigned char serialized_event[SERIALIZED_EVENT_MAX_SIZE];
int length = control_event_serialize(event, serialized_event); int length = control_event_serialize(event, serialized_event);
if (!length) { if (!length) {
return false; return SDL_FALSE;
} }
int w = net_send_all(controller->video_socket, serialized_event, length); int w = net_send_all(controller->video_socket, serialized_event, length);
return w == length; return w == length;
} }
static int static int run_controller(void *data) {
run_controller(void *data) {
struct controller *controller = data; struct controller *controller = data;
for (;;) { for (;;) {
mutex_lock(controller->mutex); mutex_lock(controller->mutex);
while (!controller->stopped while (!controller->stopped && control_event_queue_is_empty(&controller->queue)) {
&& control_event_queue_is_empty(&controller->queue)) {
cond_wait(controller->event_cond, controller->mutex); cond_wait(controller->event_cond, controller->mutex);
} }
if (controller->stopped) { if (controller->stopped) {
@@ -76,12 +67,11 @@ run_controller(void *data) {
break; break;
} }
struct control_event event; struct control_event event;
bool non_empty = control_event_queue_take(&controller->queue, SDL_bool non_empty = control_event_queue_take(&controller->queue, &event);
&event);
SDL_assert(non_empty); SDL_assert(non_empty);
mutex_unlock(controller->mutex); mutex_unlock(controller->mutex);
bool ok = process_event(controller, &event); SDL_bool ok = process_event(controller, &event);
control_event_destroy(&event); control_event_destroy(&event);
if (!ok) { if (!ok) {
LOGD("Cannot write event to socket"); LOGD("Cannot write event to socket");
@@ -91,29 +81,25 @@ run_controller(void *data) {
return 0; return 0;
} }
bool SDL_bool controller_start(struct controller *controller) {
controller_start(struct controller *controller) {
LOGD("Starting controller thread"); LOGD("Starting controller thread");
controller->thread = SDL_CreateThread(run_controller, "controller", controller->thread = SDL_CreateThread(run_controller, "controller", controller);
controller);
if (!controller->thread) { if (!controller->thread) {
LOGC("Could not start controller thread"); LOGC("Could not start controller thread");
return false; return SDL_FALSE;
} }
return true; return SDL_TRUE;
} }
void void controller_stop(struct controller *controller) {
controller_stop(struct controller *controller) {
mutex_lock(controller->mutex); mutex_lock(controller->mutex);
controller->stopped = true; controller->stopped = SDL_TRUE;
cond_signal(controller->event_cond); cond_signal(controller->event_cond);
mutex_unlock(controller->mutex); mutex_unlock(controller->mutex);
} }
void void controller_join(struct controller *controller) {
controller_join(struct controller *controller) {
SDL_WaitThread(controller->thread, NULL); SDL_WaitThread(controller->thread, NULL);
} }

View File

@@ -3,8 +3,8 @@
#include "control_event.h" #include "control_event.h"
#include <stdbool.h>
#include <SDL2/SDL_mutex.h> #include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h>
#include <SDL2/SDL_thread.h> #include <SDL2/SDL_thread.h>
#include "net.h" #include "net.h"
@@ -14,28 +14,18 @@ struct controller {
SDL_Thread *thread; SDL_Thread *thread;
SDL_mutex *mutex; SDL_mutex *mutex;
SDL_cond *event_cond; SDL_cond *event_cond;
bool stopped; SDL_bool stopped;
struct control_event_queue queue; struct control_event_queue queue;
}; };
bool SDL_bool controller_init(struct controller *controller, socket_t video_socket);
controller_init(struct controller *controller, socket_t video_socket); void controller_destroy(struct controller *controller);
void SDL_bool controller_start(struct controller *controller);
controller_destroy(struct controller *controller); void controller_stop(struct controller *controller);
void controller_join(struct controller *controller);
bool
controller_start(struct controller *controller);
void
controller_stop(struct controller *controller);
void
controller_join(struct controller *controller);
// expose simple API to hide control_event_queue // expose simple API to hide control_event_queue
bool SDL_bool controller_push_event(struct controller *controller, const struct control_event *event);
controller_push_event(struct controller *controller,
const struct control_event *event);
#endif #endif

View File

@@ -1,10 +1,9 @@
#include "convert.h" #include "convert.h"
#define MAP(FROM, TO) case FROM: *to = TO; return true #define MAP(FROM, TO) case FROM: *to = TO; return SDL_TRUE
#define FAIL default: return false #define FAIL default: return SDL_FALSE
static bool static SDL_bool convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) {
convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) {
switch (from) { switch (from) {
MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN); MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN);
MAP(SDL_KEYUP, AKEY_EVENT_ACTION_UP); MAP(SDL_KEYUP, AKEY_EVENT_ACTION_UP);
@@ -12,8 +11,7 @@ convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) {
} }
} }
static enum android_metastate static enum android_metastate autocomplete_metastate(enum android_metastate metastate) {
autocomplete_metastate(enum android_metastate metastate) {
// fill dependant flags // fill dependant flags
if (metastate & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) { if (metastate & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) {
metastate |= AMETA_SHIFT_ON; metastate |= AMETA_SHIFT_ON;
@@ -32,8 +30,7 @@ autocomplete_metastate(enum android_metastate metastate) {
} }
static enum android_metastate static enum android_metastate convert_meta_state(SDL_Keymod mod) {
convert_meta_state(SDL_Keymod mod) {
enum android_metastate metastate = 0; enum android_metastate metastate = 0;
if (mod & KMOD_LSHIFT) { if (mod & KMOD_LSHIFT) {
metastate |= AMETA_SHIFT_LEFT_ON; metastate |= AMETA_SHIFT_LEFT_ON;
@@ -73,8 +70,7 @@ convert_meta_state(SDL_Keymod mod) {
return autocomplete_metastate(metastate); return autocomplete_metastate(metastate);
} }
static bool static SDL_bool convert_keycode(SDL_Keycode from, enum android_keycode *to, Uint16 mod) {
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod) {
switch (from) { switch (from) {
MAP(SDLK_RETURN, AKEYCODE_ENTER); MAP(SDLK_RETURN, AKEYCODE_ENTER);
MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER); MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER);
@@ -92,7 +88,7 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod) {
MAP(SDLK_UP, AKEYCODE_DPAD_UP); MAP(SDLK_UP, AKEYCODE_DPAD_UP);
} }
if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) { if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) {
return false; return SDL_FALSE;
} }
// if ALT and META are not pressed, also handle letters and space // if ALT and META are not pressed, also handle letters and space
switch (from) { switch (from) {
@@ -127,8 +123,7 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod) {
} }
} }
static bool static SDL_bool convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
switch (from) { switch (from) {
MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN); MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN);
MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP); MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP);
@@ -136,8 +131,7 @@ convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
} }
} }
static enum android_motionevent_buttons static enum android_motionevent_buttons convert_mouse_buttons(Uint32 state) {
convert_mouse_buttons(uint32_t state) {
enum android_motionevent_buttons buttons = 0; enum android_motionevent_buttons buttons = 0;
if (state & SDL_BUTTON_LMASK) { if (state & SDL_BUTTON_LMASK) {
buttons |= AMOTION_EVENT_BUTTON_PRIMARY; buttons |= AMOTION_EVENT_BUTTON_PRIMARY;
@@ -157,33 +151,31 @@ convert_mouse_buttons(uint32_t state) {
return buttons; return buttons;
} }
bool SDL_bool input_key_from_sdl_to_android(const SDL_KeyboardEvent *from,
input_key_from_sdl_to_android(const SDL_KeyboardEvent *from, struct control_event *to) {
struct control_event *to) {
to->type = CONTROL_EVENT_TYPE_KEYCODE; to->type = CONTROL_EVENT_TYPE_KEYCODE;
if (!convert_keycode_action(from->type, &to->keycode_event.action)) { if (!convert_keycode_action(from->type, &to->keycode_event.action)) {
return false; return SDL_FALSE;
} }
uint16_t mod = from->keysym.mod; Uint16 mod = from->keysym.mod;
if (!convert_keycode(from->keysym.sym, &to->keycode_event.keycode, mod)) { if (!convert_keycode(from->keysym.sym, &to->keycode_event.keycode, mod)) {
return false; return SDL_FALSE;
} }
to->keycode_event.metastate = convert_meta_state(mod); to->keycode_event.metastate = convert_meta_state(mod);
return true; return SDL_TRUE;
} }
bool SDL_bool mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from, struct size screen_size,
struct size screen_size, struct control_event *to) {
struct control_event *to) {
to->type = CONTROL_EVENT_TYPE_MOUSE; to->type = CONTROL_EVENT_TYPE_MOUSE;
if (!convert_mouse_action(from->type, &to->mouse_event.action)) { if (!convert_mouse_action(from->type, &to->mouse_event.action)) {
return false; return SDL_FALSE;
} }
to->mouse_event.buttons = convert_mouse_buttons(SDL_BUTTON(from->button)); to->mouse_event.buttons = convert_mouse_buttons(SDL_BUTTON(from->button));
@@ -191,13 +183,12 @@ mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
to->mouse_event.position.point.x = from->x; to->mouse_event.position.point.x = from->x;
to->mouse_event.position.point.y = from->y; to->mouse_event.position.point.y = from->y;
return true; return SDL_TRUE;
} }
bool SDL_bool mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from, struct size screen_size,
struct size screen_size, struct control_event *to) {
struct control_event *to) {
to->type = CONTROL_EVENT_TYPE_MOUSE; to->type = CONTROL_EVENT_TYPE_MOUSE;
to->mouse_event.action = AMOTION_EVENT_ACTION_MOVE; to->mouse_event.action = AMOTION_EVENT_ACTION_MOVE;
to->mouse_event.buttons = convert_mouse_buttons(from->state); to->mouse_event.buttons = convert_mouse_buttons(from->state);
@@ -205,13 +196,12 @@ mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
to->mouse_event.position.point.x = from->x; to->mouse_event.position.point.x = from->x;
to->mouse_event.position.point.y = from->y; to->mouse_event.position.point.y = from->y;
return true; return SDL_TRUE;
} }
bool SDL_bool mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from,
mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from, struct position position,
struct position position, struct control_event *to) {
struct control_event *to) {
to->type = CONTROL_EVENT_TYPE_SCROLL; to->type = CONTROL_EVENT_TYPE_SCROLL;
to->scroll_event.position = position; to->scroll_event.position = position;
@@ -223,5 +213,5 @@ mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from,
to->scroll_event.hscroll = -mul * from->x; to->scroll_event.hscroll = -mul * from->x;
to->scroll_event.vscroll = mul * from->y; to->scroll_event.vscroll = mul * from->y;
return true; return SDL_TRUE;
} }

View File

@@ -1,9 +1,8 @@
#ifndef CONVERT_H #ifndef CONVERT_H
#define CONVERT_H #define CONVERT_H
#include <stdbool.h> #include <SDL2/SDL_stdinc.h>
#include <SDL2/SDL_events.h> #include <SDL2/SDL_events.h>
#include "control_event.h" #include "control_event.h"
struct complete_mouse_motion_event { struct complete_mouse_motion_event {
@@ -16,26 +15,21 @@ struct complete_mouse_wheel_event {
struct point position; struct point position;
}; };
bool SDL_bool input_key_from_sdl_to_android(const SDL_KeyboardEvent *from,
input_key_from_sdl_to_android(const SDL_KeyboardEvent *from, struct control_event *to);
struct control_event *to); SDL_bool mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
struct size screen_size,
struct control_event *to);
bool // the video size may be different from the real device size, so we need the size
mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from, // to which the absolute position apply, to scale it accordingly
struct size screen_size, SDL_bool mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
struct control_event *to); struct size screen_size,
struct control_event *to);
// the video size may be different from the real device size, so we need the
// size to which the absolute position apply, to scale it accordingly
bool
mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
struct size screen_size,
struct control_event *to);
// on Android, a scroll event requires the current mouse position // on Android, a scroll event requires the current mouse position
bool SDL_bool mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from,
mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from, struct position position,
struct position position, struct control_event *to);
struct control_event *to);
#endif #endif

View File

@@ -8,22 +8,133 @@
#include <SDL2/SDL_thread.h> #include <SDL2/SDL_thread.h>
#include <unistd.h> #include <unistd.h>
#include "compat.h"
#include "config.h" #include "config.h"
#include "buffer_util.h" #include "buffer_util.h"
#include "events.h" #include "events.h"
#include "frames.h"
#include "lock_util.h" #include "lock_util.h"
#include "log.h" #include "log.h"
#include "recorder.h" #include "recorder.h"
#include "video_buffer.h"
#define BUFSIZE 0x10000
#define HEADER_SIZE 12
#define NO_PTS UINT64_C(-1)
static struct frame_meta *frame_meta_new(uint64_t pts) {
struct frame_meta *meta = malloc(sizeof(*meta));
if (!meta) {
return meta;
}
meta->pts = pts;
meta->next = NULL;
return meta;
}
static void frame_meta_delete(struct frame_meta *frame_meta) {
free(frame_meta);
}
static SDL_bool receiver_state_push_meta(struct receiver_state *state,
uint64_t pts) {
struct frame_meta *frame_meta = frame_meta_new(pts);
if (!frame_meta) {
return SDL_FALSE;
}
// append to the list
// (iterate to find the last item, in practice the list should be tiny)
struct frame_meta **p = &state->frame_meta_queue;
while (*p) {
p = &(*p)->next;
}
*p = frame_meta;
return SDL_TRUE;
}
static uint64_t receiver_state_take_meta(struct receiver_state *state) {
struct frame_meta *frame_meta = state->frame_meta_queue; // first item
SDL_assert(frame_meta); // must not be empty
uint64_t pts = frame_meta->pts;
state->frame_meta_queue = frame_meta->next; // remove the item
frame_meta_delete(frame_meta);
return pts;
}
static int read_packet_with_meta(void *opaque, uint8_t *buf, int buf_size) {
struct decoder *decoder = opaque;
struct receiver_state *state = &decoder->receiver_state;
// The video stream contains raw packets, without time information. When we
// record, we retrieve the timestamps separately, from a "meta" header
// added by the server before each raw packet.
//
// The "meta" header length is 12 bytes:
// [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ...
// <-------------> <-----> <-----------------------------...
// PTS packet raw packet
// size
//
// It is followed by <packet_size> bytes containing the packet/frame.
if (!state->remaining) {
#define HEADER_SIZE 12
uint8_t header[HEADER_SIZE];
ssize_t r = net_recv_all(decoder->video_socket, header, HEADER_SIZE);
if (r == -1) {
return AVERROR(errno);
}
if (r == 0) {
return AVERROR_EOF;
}
// no partial read (net_recv_all())
SDL_assert_release(r == HEADER_SIZE);
uint64_t pts = buffer_read64be(header);
state->remaining = buffer_read32be(&header[8]);
if (pts != NO_PTS && !receiver_state_push_meta(state, pts)) {
LOGE("Could not store PTS for recording");
// we cannot save the PTS, the recording would be broken
return AVERROR(ENOMEM);
}
}
SDL_assert(state->remaining);
if (buf_size > state->remaining)
buf_size = state->remaining;
ssize_t r = net_recv(decoder->video_socket, buf, buf_size);
if (r == -1) {
return AVERROR(errno);
}
if (r == 0) {
return AVERROR_EOF;
}
SDL_assert(state->remaining >= r);
state->remaining -= r;
return r;
}
static int read_raw_packet(void *opaque, uint8_t *buf, int buf_size) {
struct decoder *decoder = opaque;
ssize_t r = net_recv(decoder->video_socket, buf, buf_size);
if (r == -1) {
return AVERROR(errno);
}
if (r == 0) {
return AVERROR_EOF;
}
return r;
}
// set the decoded frame as ready for rendering, and notify // set the decoded frame as ready for rendering, and notify
static void static void push_frame(struct decoder *decoder) {
push_frame(struct decoder *decoder) { SDL_bool previous_frame_consumed = frames_offer_decoded_frame(decoder->frames);
bool previous_frame_skipped; if (!previous_frame_consumed) {
video_buffer_offer_decoded_frame(decoder->video_buffer,
&previous_frame_skipped);
if (previous_frame_skipped) {
// the previous EVENT_NEW_FRAME will consume this frame // the previous EVENT_NEW_FRAME will consume this frame
return; return;
} }
@@ -33,71 +144,180 @@ push_frame(struct decoder *decoder) {
SDL_PushEvent(&new_frame_event); SDL_PushEvent(&new_frame_event);
} }
void static void notify_stopped(void) {
decoder_init(struct decoder *decoder, struct video_buffer *vb) { SDL_Event stop_event;
decoder->video_buffer = vb; stop_event.type = EVENT_DECODER_STOPPED;
SDL_PushEvent(&stop_event);
} }
bool static int run_decoder(void *data) {
decoder_open(struct decoder *decoder, const AVCodec *codec) { struct decoder *decoder = data;
decoder->codec_ctx = avcodec_alloc_context3(codec);
if (!decoder->codec_ctx) { AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!codec) {
LOGE("H.264 decoder not found");
goto run_end;
}
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
LOGC("Could not allocate decoder context"); LOGC("Could not allocate decoder context");
return false; goto run_end;
} }
if (avcodec_open2(decoder->codec_ctx, codec, NULL) < 0) { if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
LOGE("Could not open codec"); LOGE("Could not open H.264 codec");
avcodec_free_context(&decoder->codec_ctx); goto run_finally_free_codec_ctx;
return false;
} }
return true; AVFormatContext *format_ctx = avformat_alloc_context();
} if (!format_ctx) {
LOGC("Could not allocate format context");
goto run_finally_close_codec;
}
void unsigned char *buffer = av_malloc(BUFSIZE);
decoder_close(struct decoder *decoder) { if (!buffer) {
avcodec_close(decoder->codec_ctx); LOGC("Could not allocate buffer");
avcodec_free_context(&decoder->codec_ctx); goto run_finally_free_format_ctx;
} }
bool // initialize the receiver state
decoder_push(struct decoder *decoder, const AVPacket *packet) { decoder->receiver_state.frame_meta_queue = NULL;
decoder->receiver_state.remaining = 0;
// if recording is enabled, a "header" is sent between raw packets
int (*read_packet)(void *, uint8_t *, int) =
decoder->recorder ? read_packet_with_meta : read_raw_packet;
AVIOContext *avio_ctx = avio_alloc_context(buffer, BUFSIZE, 0, decoder,
read_packet, NULL, NULL);
if (!avio_ctx) {
LOGC("Could not allocate avio context");
// avformat_open_input takes ownership of 'buffer'
// so only free the buffer before avformat_open_input()
av_free(buffer);
goto run_finally_free_format_ctx;
}
format_ctx->pb = avio_ctx;
if (avformat_open_input(&format_ctx, NULL, NULL, NULL) < 0) {
LOGE("Could not open video stream");
goto run_finally_free_avio_ctx;
}
if (decoder->recorder &&
!recorder_open(decoder->recorder, codec)) {
LOGE("Could not open recorder");
goto run_finally_close_input;
}
AVPacket packet;
av_init_packet(&packet);
packet.data = NULL;
packet.size = 0;
while (!av_read_frame(format_ctx, &packet)) {
// the new decoding/encoding API has been introduced by: // the new decoding/encoding API has been introduced by:
// <http://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=7fc329e2dd6226dfecaa4a1d7adf353bf2773726> // <http://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=7fc329e2dd6226dfecaa4a1d7adf353bf2773726>
#ifdef SCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 0)
int ret; int ret;
if ((ret = avcodec_send_packet(decoder->codec_ctx, packet)) < 0) { if ((ret = avcodec_send_packet(codec_ctx, &packet)) < 0) {
LOGE("Could not send video packet: %d", ret); LOGE("Could not send video packet: %d", ret);
return false; goto run_quit;
} }
ret = avcodec_receive_frame(decoder->codec_ctx, ret = avcodec_receive_frame(codec_ctx, decoder->frames->decoding_frame);
decoder->video_buffer->decoding_frame); if (!ret) {
if (!ret) { // a frame was received
// a frame was received push_frame(decoder);
push_frame(decoder); } else if (ret != AVERROR(EAGAIN)) {
} else if (ret != AVERROR(EAGAIN)) { LOGE("Could not receive video frame: %d", ret);
LOGE("Could not receive video frame: %d", ret); av_packet_unref(&packet);
return false; goto run_quit;
} }
#else #else
int got_picture; while (packet.size > 0) {
int len = avcodec_decode_video2(decoder->codec_ctx, int got_picture;
decoder->video_buffer->decoding_frame, int len = avcodec_decode_video2(codec_ctx, decoder->frames->decoding_frame, &got_picture, &packet);
&got_picture, if (len < 0) {
packet); LOGE("Could not decode video packet: %d", len);
if (len < 0) { av_packet_unref(&packet);
LOGE("Could not decode video packet: %d", len); goto run_quit;
return false; }
} if (got_picture) {
if (got_picture) { push_frame(decoder);
push_frame(decoder); }
} packet.size -= len;
packet.data += len;
}
#endif #endif
return true;
if (decoder->recorder) {
// we retrieve the PTS in order they were received, so they will
// be assigned to the correct frame
uint64_t pts = receiver_state_take_meta(&decoder->receiver_state);
packet.pts = pts;
packet.dts = pts;
// no need to rescale with av_packet_rescale_ts(), the timestamps
// are in microseconds both in input and output
if (!recorder_write(decoder->recorder, &packet)) {
LOGE("Could not write frame to output file");
av_packet_unref(&packet);
goto run_quit;
}
}
av_packet_unref(&packet);
if (avio_ctx->eof_reached) {
break;
}
}
LOGD("End of frames");
run_quit:
if (decoder->recorder) {
recorder_close(decoder->recorder);
}
run_finally_close_input:
avformat_close_input(&format_ctx);
run_finally_free_avio_ctx:
av_freep(&avio_ctx);
run_finally_free_format_ctx:
avformat_free_context(format_ctx);
run_finally_close_codec:
avcodec_close(codec_ctx);
run_finally_free_codec_ctx:
avcodec_free_context(&codec_ctx);
notify_stopped();
run_end:
return 0;
} }
void void decoder_init(struct decoder *decoder, struct frames *frames,
decoder_interrupt(struct decoder *decoder) { socket_t video_socket, struct recorder *recorder) {
video_buffer_interrupt(decoder->video_buffer); decoder->frames = frames;
decoder->video_socket = video_socket;
decoder->recorder = recorder;
}
SDL_bool decoder_start(struct decoder *decoder) {
LOGD("Starting decoder thread");
decoder->thread = SDL_CreateThread(run_decoder, "video_decoder", decoder);
if (!decoder->thread) {
LOGC("Could not start decoder thread");
return SDL_FALSE;
}
return SDL_TRUE;
}
void decoder_stop(struct decoder *decoder) {
frames_stop(decoder->frames);
}
void decoder_join(struct decoder *decoder) {
SDL_WaitThread(decoder->thread, NULL);
} }

View File

@@ -1,29 +1,36 @@
#ifndef DECODER_H #ifndef DECODER_H
#define DECODER_H #define DECODER_H
#include <stdbool.h> #include <SDL2/SDL_stdinc.h>
#include <libavformat/avformat.h> #include <SDL2/SDL_thread.h>
struct video_buffer; #include "common.h"
#include "net.h"
struct decoder { struct frames;
struct video_buffer *video_buffer;
AVCodecContext *codec_ctx; struct frame_meta {
uint64_t pts;
struct frame_meta *next;
}; };
void struct decoder {
decoder_init(struct decoder *decoder, struct video_buffer *vb); struct frames *frames;
socket_t video_socket;
SDL_Thread *thread;
SDL_mutex *mutex;
struct recorder *recorder;
struct receiver_state {
// meta (in order) for frames not consumed yet
struct frame_meta *frame_meta_queue;
size_t remaining; // remaining bytes to receive for the current frame
} receiver_state;
};
bool void decoder_init(struct decoder *decoder, struct frames *frames,
decoder_open(struct decoder *decoder, const AVCodec *codec); socket_t video_socket, struct recorder *recoder);
SDL_bool decoder_start(struct decoder *decoder);
void void decoder_stop(struct decoder *decoder);
decoder_close(struct decoder *decoder); void decoder_join(struct decoder *decoder);
bool
decoder_push(struct decoder *decoder, const AVPacket *packet);
void
decoder_interrupt(struct decoder *decoder);
#endif #endif

View File

@@ -1,22 +1,18 @@
#include "device.h" #include "device.h"
#include "log.h" #include "log.h"
bool SDL_bool device_read_info(socket_t device_socket, char *device_name, struct size *size) {
device_read_info(socket_t device_socket, char *device_name, struct size *size) {
unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4]; unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4];
int r = net_recv_all(device_socket, buf, sizeof(buf)); int r = net_recv_all(device_socket, buf, sizeof(buf));
if (r < DEVICE_NAME_FIELD_LENGTH + 4) { if (r < DEVICE_NAME_FIELD_LENGTH + 4) {
LOGE("Could not retrieve device information"); LOGE("Could not retrieve device information");
return false; return SDL_FALSE;
} }
// in case the client sends garbage buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0'; // in case the client sends garbage
buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0'; // strcpy is safe here, since name contains at least DEVICE_NAME_FIELD_LENGTH bytes
// strcpy is safe here, since name contains at least // and strlen(buf) < DEVICE_NAME_FIELD_LENGTH
// DEVICE_NAME_FIELD_LENGTH bytes and strlen(buf) < DEVICE_NAME_FIELD_LENGTH
strcpy(device_name, (char *) buf); strcpy(device_name, (char *) buf);
size->width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8) size->width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8) | buf[DEVICE_NAME_FIELD_LENGTH + 1];
| buf[DEVICE_NAME_FIELD_LENGTH + 1]; size->height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8) | buf[DEVICE_NAME_FIELD_LENGTH + 3];
size->height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8) return SDL_TRUE;
| buf[DEVICE_NAME_FIELD_LENGTH + 3];
return true;
} }

View File

@@ -1,7 +1,7 @@
#ifndef DEVICE_H #ifndef DEVICE_H
#define DEVICE_H #define DEVICE_H
#include <stdbool.h> #include <SDL2/SDL_stdinc.h>
#include "common.h" #include "common.h"
#include "net.h" #include "net.h"
@@ -10,7 +10,6 @@
#define DEVICE_SDCARD_PATH "/sdcard/" #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
bool SDL_bool device_read_info(socket_t device_socket, char *name, struct size *frame_size);
device_read_info(socket_t device_socket, char *name, struct size *frame_size);
#endif #endif

View File

@@ -1,3 +1,3 @@
#define EVENT_NEW_SESSION SDL_USEREVENT #define EVENT_NEW_SESSION SDL_USEREVENT
#define EVENT_NEW_FRAME (SDL_USEREVENT + 1) #define EVENT_NEW_FRAME (SDL_USEREVENT + 1)
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 2) #define EVENT_DECODER_STOPPED (SDL_USEREVENT + 2)

View File

@@ -13,8 +13,7 @@ struct request {
const char *file; const char *file;
}; };
static struct request * static struct request *request_new(file_handler_action_t action, const char *file) {
request_new(file_handler_action_t action, const char *file) {
struct request *req = SDL_malloc(sizeof(*req)); struct request *req = SDL_malloc(sizeof(*req));
if (!req) { if (!req) {
return NULL; return NULL;
@@ -24,8 +23,7 @@ request_new(file_handler_action_t action, const char *file) {
return req; return req;
} }
static void static void request_free(struct request *req) {
request_free(struct request *req) {
if (!req) { if (!req) {
return; return;
} }
@@ -33,25 +31,21 @@ request_free(struct request *req) {
SDL_free((void *) req); SDL_free((void *) req);
} }
static bool static SDL_bool request_queue_is_empty(const struct request_queue *queue) {
request_queue_is_empty(const struct request_queue *queue) {
return queue->head == queue->tail; return queue->head == queue->tail;
} }
static bool static SDL_bool request_queue_is_full(const struct request_queue *queue) {
request_queue_is_full(const struct request_queue *queue) {
return (queue->head + 1) % REQUEST_QUEUE_SIZE == queue->tail; return (queue->head + 1) % REQUEST_QUEUE_SIZE == queue->tail;
} }
static bool static SDL_bool request_queue_init(struct request_queue *queue) {
request_queue_init(struct request_queue *queue) {
queue->head = 0; queue->head = 0;
queue->tail = 0; queue->tail = 0;
return true; return SDL_TRUE;
} }
static void static void request_queue_destroy(struct request_queue *queue) {
request_queue_destroy(struct request_queue *queue) {
int i = queue->tail; int i = queue->tail;
while (i != queue->head) { while (i != queue->head) {
request_free(queue->reqs[i]); request_free(queue->reqs[i]);
@@ -59,41 +53,38 @@ request_queue_destroy(struct request_queue *queue) {
} }
} }
static bool static SDL_bool request_queue_push(struct request_queue *queue, struct request *req) {
request_queue_push(struct request_queue *queue, struct request *req) {
if (request_queue_is_full(queue)) { if (request_queue_is_full(queue)) {
return false; return SDL_FALSE;
} }
queue->reqs[queue->head] = req; queue->reqs[queue->head] = req;
queue->head = (queue->head + 1) % REQUEST_QUEUE_SIZE; queue->head = (queue->head + 1) % REQUEST_QUEUE_SIZE;
return true; return SDL_TRUE;
} }
static bool static SDL_bool request_queue_take(struct request_queue *queue, struct request **req) {
request_queue_take(struct request_queue *queue, struct request **req) {
if (request_queue_is_empty(queue)) { if (request_queue_is_empty(queue)) {
return false; return SDL_FALSE;
} }
// transfer ownership // transfer ownership
*req = queue->reqs[queue->tail]; *req = queue->reqs[queue->tail];
queue->tail = (queue->tail + 1) % REQUEST_QUEUE_SIZE; queue->tail = (queue->tail + 1) % REQUEST_QUEUE_SIZE;
return true; return SDL_TRUE;
} }
bool SDL_bool file_handler_init(struct file_handler *file_handler, const char *serial) {
file_handler_init(struct file_handler *file_handler, const char *serial) {
if (!request_queue_init(&file_handler->queue)) { if (!request_queue_init(&file_handler->queue)) {
return false; return SDL_FALSE;
} }
if (!(file_handler->mutex = SDL_CreateMutex())) { if (!(file_handler->mutex = SDL_CreateMutex())) {
return false; return SDL_FALSE;
} }
if (!(file_handler->event_cond = SDL_CreateCond())) { if (!(file_handler->event_cond = SDL_CreateCond())) {
SDL_DestroyMutex(file_handler->mutex); SDL_DestroyMutex(file_handler->mutex);
return false; return SDL_FALSE;
} }
if (serial) { if (serial) {
@@ -101,63 +92,58 @@ file_handler_init(struct file_handler *file_handler, const char *serial) {
if (!file_handler->serial) { if (!file_handler->serial) {
LOGW("Cannot strdup serial"); LOGW("Cannot strdup serial");
SDL_DestroyMutex(file_handler->mutex); SDL_DestroyMutex(file_handler->mutex);
return false; return SDL_FALSE;
} }
} else { } else {
file_handler->serial = NULL; file_handler->serial = NULL;
} }
// lazy initialization // lazy initialization
file_handler->initialized = false; file_handler->initialized = SDL_FALSE;
file_handler->stopped = false; file_handler->stopped = SDL_FALSE;
file_handler->current_process = PROCESS_NONE; file_handler->current_process = PROCESS_NONE;
return true; return SDL_TRUE;
} }
void void file_handler_destroy(struct file_handler *file_handler) {
file_handler_destroy(struct file_handler *file_handler) {
SDL_DestroyCond(file_handler->event_cond); SDL_DestroyCond(file_handler->event_cond);
SDL_DestroyMutex(file_handler->mutex); SDL_DestroyMutex(file_handler->mutex);
request_queue_destroy(&file_handler->queue); request_queue_destroy(&file_handler->queue);
SDL_free((void *) file_handler->serial); SDL_free((void *) file_handler->serial);
} }
static process_t static process_t install_apk(const char *serial, const char *file) {
install_apk(const char *serial, const char *file) {
return adb_install(serial, file); return adb_install(serial, file);
} }
static process_t static process_t push_file(const char *serial, const char *file) {
push_file(const char *serial, const char *file) {
return adb_push(serial, file, DEVICE_SDCARD_PATH); return adb_push(serial, file, DEVICE_SDCARD_PATH);
} }
bool SDL_bool file_handler_request(struct file_handler *file_handler,
file_handler_request(struct file_handler *file_handler, file_handler_action_t action,
file_handler_action_t action, const char *file) {
const char *file) { SDL_bool res;
bool res;
// start file_handler if it's used for the first time // start file_handler if it's used for the first time
if (!file_handler->initialized) { if (!file_handler->initialized) {
if (!file_handler_start(file_handler)) { if (!file_handler_start(file_handler)) {
return false; return SDL_FALSE;
} }
file_handler->initialized = true; file_handler->initialized = SDL_TRUE;
} }
LOGI("Request to %s %s", action == ACTION_INSTALL_APK ? "install" : "push", LOGI("Request to %s %s", action == ACTION_INSTALL_APK ? "install" : "push", file);
file);
struct request *req = request_new(action, file); struct request *req = request_new(action, file);
if (!req) { if (!req) {
LOGE("Could not create request"); LOGE("Could not create request");
return false; return SDL_FALSE;
} }
mutex_lock(file_handler->mutex); mutex_lock(file_handler->mutex);
bool was_empty = request_queue_is_empty(&file_handler->queue); SDL_bool was_empty = request_queue_is_empty(&file_handler->queue);
res = request_queue_push(&file_handler->queue, req); res = request_queue_push(&file_handler->queue, req);
if (was_empty) { if (was_empty) {
cond_signal(file_handler->event_cond); cond_signal(file_handler->event_cond);
@@ -166,15 +152,13 @@ file_handler_request(struct file_handler *file_handler,
return res; return res;
} }
static int static int run_file_handler(void *data) {
run_file_handler(void *data) {
struct file_handler *file_handler = data; struct file_handler *file_handler = data;
for (;;) { for (;;) {
mutex_lock(file_handler->mutex); mutex_lock(file_handler->mutex);
file_handler->current_process = PROCESS_NONE; file_handler->current_process = PROCESS_NONE;
while (!file_handler->stopped while (!file_handler->stopped && request_queue_is_empty(&file_handler->queue)) {
&& request_queue_is_empty(&file_handler->queue)) {
cond_wait(file_handler->event_cond, file_handler->mutex); cond_wait(file_handler->event_cond, file_handler->mutex);
} }
if (file_handler->stopped) { if (file_handler->stopped) {
@@ -183,7 +167,7 @@ run_file_handler(void *data) {
break; break;
} }
struct request *req; struct request *req;
bool non_empty = request_queue_take(&file_handler->queue, &req); SDL_bool non_empty = request_queue_take(&file_handler->queue, &req);
SDL_assert(non_empty); SDL_assert(non_empty);
process_t process; process_t process;
@@ -216,24 +200,21 @@ run_file_handler(void *data) {
return 0; return 0;
} }
bool SDL_bool file_handler_start(struct file_handler *file_handler) {
file_handler_start(struct file_handler *file_handler) {
LOGD("Starting file_handler thread"); LOGD("Starting file_handler thread");
file_handler->thread = SDL_CreateThread(run_file_handler, "file_handler", file_handler->thread = SDL_CreateThread(run_file_handler, "file_handler", file_handler);
file_handler);
if (!file_handler->thread) { if (!file_handler->thread) {
LOGC("Could not start file_handler thread"); LOGC("Could not start file_handler thread");
return false; return SDL_FALSE;
} }
return true; return SDL_TRUE;
} }
void void file_handler_stop(struct file_handler *file_handler) {
file_handler_stop(struct file_handler *file_handler) {
mutex_lock(file_handler->mutex); mutex_lock(file_handler->mutex);
file_handler->stopped = true; file_handler->stopped = SDL_TRUE;
cond_signal(file_handler->event_cond); cond_signal(file_handler->event_cond);
if (file_handler->current_process != PROCESS_NONE) { if (file_handler->current_process != PROCESS_NONE) {
if (!cmd_terminate(file_handler->current_process)) { if (!cmd_terminate(file_handler->current_process)) {
@@ -245,7 +226,6 @@ file_handler_stop(struct file_handler *file_handler) {
mutex_unlock(file_handler->mutex); mutex_unlock(file_handler->mutex);
} }
void void file_handler_join(struct file_handler *file_handler) {
file_handler_join(struct file_handler *file_handler) {
SDL_WaitThread(file_handler->thread, NULL); SDL_WaitThread(file_handler->thread, NULL);
} }

View File

@@ -1,10 +1,9 @@
#ifndef FILE_HANDLER_H #ifndef FILE_HANDLER_H
#define FILE_HANDLER_H #define FILE_HANDLER_H
#include <stdbool.h>
#include <SDL2/SDL_mutex.h> #include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h>
#include <SDL2/SDL_thread.h> #include <SDL2/SDL_thread.h>
#include "command.h" #include "command.h"
#define REQUEST_QUEUE_SIZE 16 #define REQUEST_QUEUE_SIZE 16
@@ -25,30 +24,21 @@ struct file_handler {
SDL_Thread *thread; SDL_Thread *thread;
SDL_mutex *mutex; SDL_mutex *mutex;
SDL_cond *event_cond; SDL_cond *event_cond;
bool stopped; SDL_bool stopped;
bool initialized; SDL_bool initialized;
process_t current_process; process_t current_process;
struct request_queue queue; struct request_queue queue;
}; };
bool SDL_bool file_handler_init(struct file_handler *file_handler, const char *serial);
file_handler_init(struct file_handler *file_handler, const char *serial); void file_handler_destroy(struct file_handler *file_handler);
void SDL_bool file_handler_start(struct file_handler *file_handler);
file_handler_destroy(struct file_handler *file_handler); void file_handler_stop(struct file_handler *file_handler);
void file_handler_join(struct file_handler *file_handler);
bool SDL_bool file_handler_request(struct file_handler *file_handler,
file_handler_start(struct file_handler *file_handler); file_handler_action_t action,
const char *file);
void
file_handler_stop(struct file_handler *file_handler);
void
file_handler_join(struct file_handler *file_handler);
bool
file_handler_request(struct file_handler *file_handler,
file_handler_action_t action,
const char *file);
#endif #endif

View File

@@ -4,16 +4,14 @@
#include "log.h" #include "log.h"
void void fps_counter_init(struct fps_counter *counter) {
fps_counter_init(struct fps_counter *counter) { counter->started = SDL_FALSE;
counter->started = false;
// no need to initialize the other fields, they are meaningful only when // no need to initialize the other fields, they are meaningful only when
// started is true // started is true
} }
void void fps_counter_start(struct fps_counter *counter) {
fps_counter_start(struct fps_counter *counter) { counter->started = SDL_TRUE;
counter->started = true;
counter->slice_start = SDL_GetTicks(); counter->slice_start = SDL_GetTicks();
counter->nr_rendered = 0; counter->nr_rendered = 0;
#ifdef SKIP_FRAMES #ifdef SKIP_FRAMES
@@ -21,17 +19,14 @@ fps_counter_start(struct fps_counter *counter) {
#endif #endif
} }
void void fps_counter_stop(struct fps_counter *counter) {
fps_counter_stop(struct fps_counter *counter) { counter->started = SDL_FALSE;
counter->started = false;
} }
static void static void display_fps(struct fps_counter *counter) {
display_fps(struct fps_counter *counter) {
#ifdef SKIP_FRAMES #ifdef SKIP_FRAMES
if (counter->nr_skipped) { if (counter->nr_skipped) {
LOGI("%d fps (+%d frames skipped)", counter->nr_rendered, LOGI("%d fps (+%d frames skipped)", counter->nr_rendered, counter->nr_skipped);
counter->nr_skipped);
} else { } else {
#endif #endif
LOGI("%d fps", counter->nr_rendered); LOGI("%d fps", counter->nr_rendered);
@@ -40,13 +35,12 @@ display_fps(struct fps_counter *counter) {
#endif #endif
} }
static void static void check_expired(struct fps_counter *counter) {
check_expired(struct fps_counter *counter) { Uint32 now = SDL_GetTicks();
uint32_t now = SDL_GetTicks();
if (now - counter->slice_start >= 1000) { if (now - counter->slice_start >= 1000) {
display_fps(counter); display_fps(counter);
// add a multiple of one second // add a multiple of one second
uint32_t elapsed_slices = (now - counter->slice_start) / 1000; Uint32 elapsed_slices = (now - counter->slice_start) / 1000;
counter->slice_start += 1000 * elapsed_slices; counter->slice_start += 1000 * elapsed_slices;
counter->nr_rendered = 0; counter->nr_rendered = 0;
#ifdef SKIP_FRAMES #ifdef SKIP_FRAMES
@@ -55,15 +49,13 @@ check_expired(struct fps_counter *counter) {
} }
} }
void void fps_counter_add_rendered_frame(struct fps_counter *counter) {
fps_counter_add_rendered_frame(struct fps_counter *counter) {
check_expired(counter); check_expired(counter);
++counter->nr_rendered; ++counter->nr_rendered;
} }
#ifdef SKIP_FRAMES #ifdef SKIP_FRAMES
void void fps_counter_add_skipped_frame(struct fps_counter *counter) {
fps_counter_add_skipped_frame(struct fps_counter *counter) {
check_expired(counter); check_expired(counter);
++counter->nr_skipped; ++counter->nr_skipped;
} }

View File

@@ -1,35 +1,26 @@
#ifndef FPSCOUNTER_H #ifndef FPSCOUNTER_H
#define FPSCOUNTER_H #define FPSCOUNTER_H
#include <stdbool.h> #include <SDL2/SDL_stdinc.h>
#include <stdint.h>
#include "config.h" #include "config.h"
struct fps_counter { struct fps_counter {
bool started; SDL_bool started;
uint32_t slice_start; // initialized by SDL_GetTicks() Uint32 slice_start; // initialized by SDL_GetTicks()
int nr_rendered; int nr_rendered;
#ifdef SKIP_FRAMES #ifdef SKIP_FRAMES
int nr_skipped; int nr_skipped;
#endif #endif
}; };
void void fps_counter_init(struct fps_counter *counter);
fps_counter_init(struct fps_counter *counter); void fps_counter_start(struct fps_counter *counter);
void fps_counter_stop(struct fps_counter *counter);
void
fps_counter_start(struct fps_counter *counter);
void
fps_counter_stop(struct fps_counter *counter);
void
fps_counter_add_rendered_frame(struct fps_counter *counter);
void fps_counter_add_rendered_frame(struct fps_counter *counter);
#ifdef SKIP_FRAMES #ifdef SKIP_FRAMES
void void fps_counter_add_skipped_frame(struct fps_counter *counter);
fps_counter_add_skipped_frame(struct fps_counter *counter);
#endif #endif
#endif #endif

110
app/src/frames.c Normal file
View File

@@ -0,0 +1,110 @@
#include "frames.h"
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_mutex.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>
#include "config.h"
#include "lock_util.h"
#include "log.h"
SDL_bool frames_init(struct frames *frames) {
if (!(frames->decoding_frame = av_frame_alloc())) {
goto error_0;
}
if (!(frames->rendering_frame = av_frame_alloc())) {
goto error_1;
}
if (!(frames->mutex = SDL_CreateMutex())) {
goto error_2;
}
#ifndef SKIP_FRAMES
if (!(frames->rendering_frame_consumed_cond = SDL_CreateCond())) {
SDL_DestroyMutex(frames->mutex);
goto error_2;
}
frames->stopped = SDL_FALSE;
#endif
// there is initially no rendering frame, so consider it has already been
// consumed
frames->rendering_frame_consumed = SDL_TRUE;
fps_counter_init(&frames->fps_counter);
return SDL_TRUE;
error_2:
av_frame_free(&frames->rendering_frame);
error_1:
av_frame_free(&frames->decoding_frame);
error_0:
return SDL_FALSE;
}
void frames_destroy(struct frames *frames) {
#ifndef SKIP_FRAMES
SDL_DestroyCond(frames->rendering_frame_consumed_cond);
#endif
SDL_DestroyMutex(frames->mutex);
av_frame_free(&frames->rendering_frame);
av_frame_free(&frames->decoding_frame);
}
static void frames_swap(struct frames *frames) {
AVFrame *tmp = frames->decoding_frame;
frames->decoding_frame = frames->rendering_frame;
frames->rendering_frame = tmp;
}
SDL_bool frames_offer_decoded_frame(struct frames *frames) {
mutex_lock(frames->mutex);
#ifndef SKIP_FRAMES
// if SKIP_FRAMES is disabled, then the decoder must wait for the current
// frame to be consumed
while (!frames->rendering_frame_consumed && !frames->stopped) {
cond_wait(frames->rendering_frame_consumed_cond, frames->mutex);
}
#else
if (frames->fps_counter.started && !frames->rendering_frame_consumed) {
fps_counter_add_skipped_frame(&frames->fps_counter);
}
#endif
frames_swap(frames);
SDL_bool previous_frame_consumed = frames->rendering_frame_consumed;
frames->rendering_frame_consumed = SDL_FALSE;
mutex_unlock(frames->mutex);
return previous_frame_consumed;
}
const AVFrame *frames_consume_rendered_frame(struct frames *frames) {
SDL_assert(!frames->rendering_frame_consumed);
frames->rendering_frame_consumed = SDL_TRUE;
if (frames->fps_counter.started) {
fps_counter_add_rendered_frame(&frames->fps_counter);
}
#ifndef SKIP_FRAMES
// if SKIP_FRAMES is disabled, then notify the decoder the current frame is
// consumed, so that it may push a new one
cond_signal(frames->rendering_frame_consumed_cond);
#endif
return frames->rendering_frame;
}
void frames_stop(struct frames *frames) {
#ifdef SKIP_FRAMES
(void) frames; // unused
#else
mutex_lock(frames->mutex);
frames->stopped = SDL_TRUE;
mutex_unlock(frames->mutex);
// wake up blocking wait
cond_signal(frames->rendering_frame_consumed_cond);
#endif
}

View File

@@ -1,8 +1,8 @@
#ifndef VIDEO_BUFFER_H #ifndef FRAMES_H
#define VIDEO_BUFFER_H #define FRAMES_H
#include <stdbool.h>
#include <SDL2/SDL_mutex.h> #include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h>
#include "config.h" #include "config.h"
#include "fps_counter.h" #include "fps_counter.h"
@@ -10,40 +10,33 @@
// forward declarations // forward declarations
typedef struct AVFrame AVFrame; typedef struct AVFrame AVFrame;
struct video_buffer { struct frames {
AVFrame *decoding_frame; AVFrame *decoding_frame;
AVFrame *rendering_frame; AVFrame *rendering_frame;
SDL_mutex *mutex; SDL_mutex *mutex;
#ifndef SKIP_FRAMES #ifndef SKIP_FRAMES
bool interrupted; SDL_bool stopped;
SDL_cond *rendering_frame_consumed_cond; SDL_cond *rendering_frame_consumed_cond;
#endif #endif
bool rendering_frame_consumed; SDL_bool rendering_frame_consumed;
struct fps_counter fps_counter; struct fps_counter fps_counter;
}; };
bool SDL_bool frames_init(struct frames *frames);
video_buffer_init(struct video_buffer *vb); void frames_destroy(struct frames *frames);
void // set the decoder frame as ready for rendering
video_buffer_destroy(struct video_buffer *vb);
// set the decoded frame as ready for rendering
// this function locks frames->mutex during its execution // this function locks frames->mutex during its execution
// the output flag is set to report whether the previous frame has been skipped // returns true if the previous frame had been consumed
void SDL_bool frames_offer_decoded_frame(struct frames *frames);
video_buffer_offer_decoded_frame(struct video_buffer *vb,
bool *previous_frame_skipped);
// mark the rendering frame as consumed and return it // mark the rendering frame as consumed and return it
// MUST be called with frames->mutex locked!!! // MUST be called with frames->mutex locked!!!
// the caller is expected to render the returned frame to some texture before // the caller is expected to render the returned frame to some texture before
// unlocking frames->mutex // unlocking frames->mutex
const AVFrame * const AVFrame *frames_consume_rendered_frame(struct frames *frames);
video_buffer_consume_rendered_frame(struct video_buffer *vb);
// wake up and avoid any blocking call // wake up and avoid any blocking call
void void frames_stop(struct frames *frames);
video_buffer_interrupt(struct video_buffer *vb);
#endif #endif

View File

@@ -5,13 +5,11 @@
#include "lock_util.h" #include "lock_util.h"
#include "log.h" #include "log.h"
// Convert window coordinates (as provided by SDL_GetMouseState() to renderer // Convert window coordinates (as provided by SDL_GetMouseState() to renderer coordinates (as provided in SDL mouse events)
// coordinates (as provided in SDL mouse events)
// //
// See my question: // See my question:
// <https://stackoverflow.com/questions/49111054/how-to-get-mouse-position-on-mouse-wheel-event> // <https://stackoverflow.com/questions/49111054/how-to-get-mouse-position-on-mouse-wheel-event>
static void static void convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int *y) {
convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int *y) {
SDL_Rect viewport; SDL_Rect viewport;
float scale_x, scale_y; float scale_x, scale_y;
SDL_RenderGetViewport(renderer, &viewport); SDL_RenderGetViewport(renderer, &viewport);
@@ -20,24 +18,15 @@ convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int *y) {
*y = (int) (*y / scale_y) - viewport.y; *y = (int) (*y / scale_y) - viewport.y;
} }
static struct point static void get_mouse_point(struct screen *screen, int *x, int *y) {
get_mouse_point(struct screen *screen) { SDL_GetMouseState(x, y);
int x; convert_to_renderer_coordinates(screen->renderer, x, y);
int y;
SDL_GetMouseState(&x, &y);
convert_to_renderer_coordinates(screen->renderer, &x, &y);
return (struct point) {
.x = x,
.y = y,
};
} }
static const int ACTION_DOWN = 1; static const int ACTION_DOWN = 1;
static const int ACTION_UP = 1 << 1; static const int ACTION_UP = 1 << 1;
static void static void send_keycode(struct controller *controller, enum android_keycode keycode, int actions, const char *name) {
send_keycode(struct controller *controller, enum android_keycode keycode,
int actions, const char *name) {
// send DOWN event // send DOWN event
struct control_event control_event; struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_KEYCODE; control_event.type = CONTROL_EVENT_TYPE_KEYCODE;
@@ -60,93 +49,58 @@ send_keycode(struct controller *controller, enum android_keycode keycode,
} }
} }
static inline void static inline void action_home(struct controller *controller, int actions) {
action_home(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_HOME, actions, "HOME"); send_keycode(controller, AKEYCODE_HOME, actions, "HOME");
} }
static inline void static inline void action_back(struct controller *controller, int actions) {
action_back(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_BACK, actions, "BACK"); send_keycode(controller, AKEYCODE_BACK, actions, "BACK");
} }
static inline void static inline void action_app_switch(struct controller *controller, int actions) {
action_app_switch(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_APP_SWITCH, actions, "APP_SWITCH"); send_keycode(controller, AKEYCODE_APP_SWITCH, actions, "APP_SWITCH");
} }
static inline void static inline void action_power(struct controller *controller, int actions) {
action_power(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_POWER, actions, "POWER"); send_keycode(controller, AKEYCODE_POWER, actions, "POWER");
} }
static inline void static inline void action_volume_up(struct controller *controller, int actions) {
action_volume_up(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_VOLUME_UP, actions, "VOLUME_UP"); send_keycode(controller, AKEYCODE_VOLUME_UP, actions, "VOLUME_UP");
} }
static inline void static inline void action_volume_down(struct controller *controller, int actions) {
action_volume_down(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_VOLUME_DOWN, actions, "VOLUME_DOWN"); send_keycode(controller, AKEYCODE_VOLUME_DOWN, actions, "VOLUME_DOWN");
} }
static inline void static inline void action_menu(struct controller *controller, int actions) {
action_menu(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_MENU, actions, "MENU"); send_keycode(controller, AKEYCODE_MENU, actions, "MENU");
} }
// turn the screen on if it was off, press BACK otherwise // turn the screen on if it was off, press BACK otherwise
static void static void press_back_or_turn_screen_on(struct controller *controller) {
press_back_or_turn_screen_on(struct controller *controller) {
struct control_event control_event; struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_COMMAND; control_event.type = CONTROL_EVENT_TYPE_COMMAND;
control_event.command_event.action = control_event.command_event.action = CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON;
CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON;
if (!controller_push_event(controller, &control_event)) { if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot turn screen on"); LOGW("Cannot turn screen on");
} }
} }
static void static void switch_fps_counter_state(struct frames *frames) {
expand_notification_panel(struct controller *controller) { mutex_lock(frames->mutex);
struct control_event control_event; if (frames->fps_counter.started) {
control_event.type = CONTROL_EVENT_TYPE_COMMAND;
control_event.command_event.action =
CONTROL_EVENT_COMMAND_EXPAND_NOTIFICATION_PANEL;
if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot expand notification panel");
}
}
static void
collapse_notification_panel(struct controller *controller) {
struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_COMMAND;
control_event.command_event.action =
CONTROL_EVENT_COMMAND_COLLAPSE_NOTIFICATION_PANEL;
if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot collapse notification panel");
}
}
static void
switch_fps_counter_state(struct video_buffer *vb) {
mutex_lock(vb->mutex);
if (vb->fps_counter.started) {
LOGI("FPS counter stopped"); LOGI("FPS counter stopped");
fps_counter_stop(&vb->fps_counter); fps_counter_stop(&frames->fps_counter);
} else { } else {
LOGI("FPS counter started"); LOGI("FPS counter started");
fps_counter_start(&vb->fps_counter); fps_counter_start(&frames->fps_counter);
} }
mutex_unlock(vb->mutex); mutex_unlock(frames->mutex);
} }
static void static void clipboard_paste(struct controller *controller) {
clipboard_paste(struct controller *controller) {
char *text = SDL_GetClipboardText(); char *text = SDL_GetClipboardText();
if (!text) { if (!text) {
LOGW("Cannot get clipboard text: %s", SDL_GetError()); LOGW("Cannot get clipboard text: %s", SDL_GetError());
@@ -167,9 +121,8 @@ clipboard_paste(struct controller *controller) {
} }
} }
void void input_manager_process_text_input(struct input_manager *input_manager,
input_manager_process_text_input(struct input_manager *input_manager, const SDL_TextInputEvent *event) {
const SDL_TextInputEvent *event) {
char c = event->text[0]; char c = event->text[0];
if (isalpha(c) || c == ' ') { if (isalpha(c) || c == ' ') {
SDL_assert(event->text[1] == '\0'); SDL_assert(event->text[1] == '\0');
@@ -189,13 +142,11 @@ input_manager_process_text_input(struct input_manager *input_manager,
} }
} }
void void input_manager_process_key(struct input_manager *input_manager,
input_manager_process_key(struct input_manager *input_manager, const SDL_KeyboardEvent *event) {
const SDL_KeyboardEvent *event, SDL_bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL);
bool control) { SDL_bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT);
bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL); SDL_bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI);
bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT);
bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI);
if (alt) { if (alt) {
// no shortcut involves Alt or Meta, and they should not be forwarded // no shortcut involves Alt or Meta, and they should not be forwarded
@@ -205,42 +156,47 @@ input_manager_process_key(struct input_manager *input_manager,
// capture all Ctrl events // capture all Ctrl events
if (ctrl | meta) { if (ctrl | meta) {
SDL_bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
if (shift) {
// currently, there is no shortcut involving SHIFT
return;
}
SDL_Keycode keycode = event->keysym.sym; SDL_Keycode keycode = event->keysym.sym;
int action = event->type == SDL_KEYDOWN ? ACTION_DOWN : ACTION_UP; int action = event->type == SDL_KEYDOWN ? ACTION_DOWN : ACTION_UP;
bool repeat = event->repeat; SDL_bool repeat = event->repeat;
bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
switch (keycode) { switch (keycode) {
case SDLK_h: case SDLK_h:
if (control && ctrl && !meta && !shift && !repeat) { if (ctrl && !meta && !repeat) {
action_home(input_manager->controller, action); action_home(input_manager->controller, action);
} }
return; return;
case SDLK_b: // fall-through case SDLK_b: // fall-through
case SDLK_BACKSPACE: case SDLK_BACKSPACE:
if (control && ctrl && !meta && !shift && !repeat) { if (ctrl && !meta && !repeat) {
action_back(input_manager->controller, action); action_back(input_manager->controller, action);
} }
return; return;
case SDLK_s: case SDLK_s:
if (control && ctrl && !meta && !shift && !repeat) { if (ctrl && !meta && !repeat) {
action_app_switch(input_manager->controller, action); action_app_switch(input_manager->controller, action);
} }
return; return;
case SDLK_m: case SDLK_m:
if (control && ctrl && !meta && !shift && !repeat) { if (ctrl && !meta && !repeat) {
action_menu(input_manager->controller, action); action_menu(input_manager->controller, action);
} }
return; return;
case SDLK_p: case SDLK_p:
if (control && ctrl && !meta && !shift && !repeat) { if (ctrl && !meta && !repeat) {
action_power(input_manager->controller, action); action_power(input_manager->controller, action);
} }
return; return;
case SDLK_DOWN: case SDLK_DOWN:
#ifdef __APPLE__ #ifdef __APPLE__
if (control && !ctrl && meta && !shift) { if (!ctrl && meta) {
#else #else
if (control && ctrl && !meta && !shift) { if (ctrl && !meta) {
#endif #endif
// forward repeated events // forward repeated events
action_volume_down(input_manager->controller, action); action_volume_down(input_manager->controller, action);
@@ -248,52 +204,37 @@ input_manager_process_key(struct input_manager *input_manager,
return; return;
case SDLK_UP: case SDLK_UP:
#ifdef __APPLE__ #ifdef __APPLE__
if (control && !ctrl && meta && !shift) { if (!ctrl && meta) {
#else #else
if (control && ctrl && !meta && !shift) { if (ctrl && !meta) {
#endif #endif
// forward repeated events // forward repeated events
action_volume_up(input_manager->controller, action); action_volume_up(input_manager->controller, action);
} }
return; return;
case SDLK_v: case SDLK_v:
if (control && ctrl && !meta && !shift && !repeat if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) {
&& event->type == SDL_KEYDOWN) {
clipboard_paste(input_manager->controller); clipboard_paste(input_manager->controller);
} }
return; return;
case SDLK_f: case SDLK_f:
if (ctrl && !meta && !shift && !repeat if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) {
&& event->type == SDL_KEYDOWN) {
screen_switch_fullscreen(input_manager->screen); screen_switch_fullscreen(input_manager->screen);
} }
return; return;
case SDLK_x: case SDLK_x:
if (ctrl && !meta && !shift && !repeat if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) {
&& event->type == SDL_KEYDOWN) {
screen_resize_to_fit(input_manager->screen); screen_resize_to_fit(input_manager->screen);
} }
return; return;
case SDLK_g: case SDLK_g:
if (ctrl && !meta && !shift && !repeat if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) {
&& event->type == SDL_KEYDOWN) {
screen_resize_to_pixel_perfect(input_manager->screen); screen_resize_to_pixel_perfect(input_manager->screen);
} }
return; return;
case SDLK_i: case SDLK_i:
if (ctrl && !meta && !shift && !repeat if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) {
&& event->type == SDL_KEYDOWN) { switch_fps_counter_state(input_manager->frames);
switch_fps_counter_state(input_manager->video_buffer);
}
return;
case SDLK_n:
if (control && ctrl && !meta
&& !repeat && event->type == SDL_KEYDOWN) {
if (shift) {
collapse_notification_panel(input_manager->controller);
} else {
expand_notification_panel(input_manager->controller);
}
} }
return; return;
} }
@@ -301,10 +242,6 @@ input_manager_process_key(struct input_manager *input_manager,
return; return;
} }
if (!control) {
return;
}
struct control_event control_event; struct control_event control_event;
if (input_key_from_sdl_to_android(event, &control_event)) { if (input_key_from_sdl_to_android(event, &control_event)) {
if (!controller_push_event(input_manager->controller, &control_event)) { if (!controller_push_event(input_manager->controller, &control_event)) {
@@ -313,76 +250,81 @@ input_manager_process_key(struct input_manager *input_manager,
} }
} }
void void input_manager_process_mouse_motion(struct input_manager *input_manager,
input_manager_process_mouse_motion(struct input_manager *input_manager, const SDL_MouseMotionEvent *event) {
const SDL_MouseMotionEvent *event) {
if (!event->state) { if (!event->state) {
// do not send motion events when no button is pressed // do not send motion events when no button is pressed
return; return;
} }
struct control_event control_event; struct control_event control_event;
if (mouse_motion_from_sdl_to_android(event, if (mouse_motion_from_sdl_to_android(event, input_manager->screen->frame_size, &control_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)) {
LOGW("Cannot send mouse motion event"); LOGW("Cannot send mouse motion event");
} }
} }
} }
static bool static SDL_bool is_outside_device_screen(struct input_manager *input_manager,
is_outside_device_screen(struct input_manager *input_manager, int x, int y) int x, int y)
{ {
return x < 0 || x >= input_manager->screen->frame_size.width || return x < 0 || x >= input_manager->screen->frame_size.width ||
y < 0 || y >= input_manager->screen->frame_size.height; y < 0 || y >= input_manager->screen->frame_size.height;
} }
void void input_manager_process_mouse_button(struct input_manager *input_manager,
input_manager_process_mouse_button(struct input_manager *input_manager, const SDL_MouseButtonEvent *event) {
const SDL_MouseButtonEvent *event, SDL_bool outside_device_screen = is_outside_device_screen(input_manager,
bool control) { event->x,
event->y);
if (event->type == SDL_MOUSEBUTTONDOWN) { if (event->type == SDL_MOUSEBUTTONDOWN) {
if (control && event->button == SDL_BUTTON_RIGHT) { if (event->button == SDL_BUTTON_RIGHT) {
press_back_or_turn_screen_on(input_manager->controller); press_back_or_turn_screen_on(input_manager->controller);
return; return;
} }
if (control && event->button == SDL_BUTTON_MIDDLE) { if (event->button == SDL_BUTTON_MIDDLE) {
action_home(input_manager->controller, ACTION_DOWN | ACTION_UP); action_home(input_manager->controller, ACTION_DOWN | ACTION_UP);
return; return;
} }
// double-click on black borders resize to fit the device screen // double-click on black borders resize to fit the device screen
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) { if (event->button == SDL_BUTTON_LEFT && event->clicks == 2
bool outside = is_outside_device_screen(input_manager, && outside_device_screen) {
event->x, screen_resize_to_fit(input_manager->screen);
event->y); return;
if (outside) {
screen_resize_to_fit(input_manager->screen);
return;
}
} }
// otherwise, send the click event to the device // otherwise, send the click event to the device
} }
if (!control) { if (outside_device_screen) {
// ignore
return; return;
} }
struct control_event control_event; struct control_event control_event;
if (mouse_button_from_sdl_to_android(event, if (mouse_button_from_sdl_to_android(event, input_manager->screen->frame_size, &control_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)) {
LOGW("Cannot send mouse button event"); LOGW("Cannot send mouse button event");
} }
} }
} }
void void input_manager_process_mouse_wheel(struct input_manager *input_manager,
input_manager_process_mouse_wheel(struct input_manager *input_manager, const SDL_MouseWheelEvent *event) {
const SDL_MouseWheelEvent *event) { int x;
int y;
get_mouse_point(input_manager->screen, &x, &y);
if (is_outside_device_screen(input_manager, x, y)) {
// ignore
return;
}
SDL_assert_release(x >= 0 && x < 0x10000 && y >= 0 && y < 0x10000);
struct position position = { struct position position = {
.screen_size = input_manager->screen->frame_size, .screen_size = input_manager->screen->frame_size,
.point = get_mouse_point(input_manager->screen), .point = {
.x = (Uint16) x,
.y = (Uint16) y,
},
}; };
struct control_event control_event; struct control_event control_event;
if (mouse_wheel_from_sdl_to_android(event, position, &control_event)) { if (mouse_wheel_from_sdl_to_android(event, position, &control_event)) {

View File

@@ -1,40 +1,27 @@
#ifndef INPUTMANAGER_H #ifndef INPUTMANAGER_H
#define INPUTMANAGER_H #define INPUTMANAGER_H
#include <stdbool.h>
#include "common.h" #include "common.h"
#include "controller.h" #include "controller.h"
#include "fps_counter.h" #include "fps_counter.h"
#include "video_buffer.h" #include "frames.h"
#include "screen.h" #include "screen.h"
struct input_manager { struct input_manager {
struct controller *controller; struct controller *controller;
struct video_buffer *video_buffer; struct frames *frames;
struct screen *screen; struct screen *screen;
}; };
void void input_manager_process_text_input(struct input_manager *input_manager,
input_manager_process_text_input(struct input_manager *input_manager, const SDL_TextInputEvent *event);
const SDL_TextInputEvent *event); void input_manager_process_key(struct input_manager *input_manager,
const SDL_KeyboardEvent *event);
void void input_manager_process_mouse_motion(struct input_manager *input_manager,
input_manager_process_key(struct input_manager *input_manager, const SDL_MouseMotionEvent *event);
const SDL_KeyboardEvent *event, void input_manager_process_mouse_button(struct input_manager *input_manager,
bool control); const SDL_MouseButtonEvent *event);
void input_manager_process_mouse_wheel(struct input_manager *input_manager,
void const SDL_MouseWheelEvent *event);
input_manager_process_mouse_motion(struct input_manager *input_manager,
const SDL_MouseMotionEvent *event);
void
input_manager_process_mouse_button(struct input_manager *input_manager,
const SDL_MouseButtonEvent *event,
bool control);
void
input_manager_process_mouse_wheel(struct input_manager *input_manager,
const SDL_MouseWheelEvent *event);
#endif #endif

View File

@@ -4,32 +4,28 @@
#include "log.h" #include "log.h"
void void mutex_lock(SDL_mutex *mutex) {
mutex_lock(SDL_mutex *mutex) {
if (SDL_LockMutex(mutex)) { if (SDL_LockMutex(mutex)) {
LOGC("Could not lock mutex"); LOGC("Could not lock mutex");
abort(); abort();
} }
} }
void void mutex_unlock(SDL_mutex *mutex) {
mutex_unlock(SDL_mutex *mutex) {
if (SDL_UnlockMutex(mutex)) { if (SDL_UnlockMutex(mutex)) {
LOGC("Could not unlock mutex"); LOGC("Could not unlock mutex");
abort(); abort();
} }
} }
void void cond_wait(SDL_cond *cond, SDL_mutex *mutex) {
cond_wait(SDL_cond *cond, SDL_mutex *mutex) {
if (SDL_CondWait(cond, mutex)) { if (SDL_CondWait(cond, mutex)) {
LOGC("Could not wait on condition"); LOGC("Could not wait on condition");
abort(); abort();
} }
} }
void void cond_signal(SDL_cond *cond) {
cond_signal(SDL_cond *cond) {
if (SDL_CondSignal(cond)) { if (SDL_CondSignal(cond)) {
LOGC("Could not signal a condition"); LOGC("Could not signal a condition");
abort(); abort();

View File

@@ -5,16 +5,9 @@
typedef struct SDL_mutex SDL_mutex; typedef struct SDL_mutex SDL_mutex;
typedef struct SDL_cond SDL_cond; typedef struct SDL_cond SDL_cond;
void void mutex_lock(SDL_mutex *mutex);
mutex_lock(SDL_mutex *mutex); void mutex_unlock(SDL_mutex *mutex);
void cond_wait(SDL_cond *cond, SDL_mutex *mutex);
void void cond_signal(SDL_cond *cond);
mutex_unlock(SDL_mutex *mutex);
void
cond_wait(SDL_cond *cond, SDL_mutex *mutex);
void
cond_signal(SDL_cond *cond);
#endif #endif

View File

@@ -3,7 +3,6 @@
#include <SDL2/SDL_log.h> #include <SDL2/SDL_log.h>
#define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGW(...) SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOGW(...) SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)

View File

@@ -1,32 +1,24 @@
#include "scrcpy.h" #include "scrcpy.h"
#include <getopt.h> #include <getopt.h>
#include <stdbool.h>
#include <stdint.h>
#include <unistd.h> #include <unistd.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "compat.h"
#include "config.h" #include "config.h"
#include "log.h" #include "log.h"
#include "recorder.h"
struct args { struct args {
const char *serial; const char *serial;
const char *crop; const char *crop;
const char *record_filename; const char *record_filename;
enum recorder_format record_format; SDL_bool fullscreen;
bool fullscreen; SDL_bool help;
bool no_control; SDL_bool version;
bool no_display; SDL_bool show_touches;
bool help; Uint16 port;
bool version; Uint16 max_size;
bool show_touches; Uint32 bit_rate;
uint16_t port;
uint16_t max_size;
uint32_t bit_rate;
bool always_on_top;
}; };
static void usage(const char *arg0) { static void usage(const char *arg0) {
@@ -49,9 +41,6 @@ static void usage(const char *arg0) {
" -f, --fullscreen\n" " -f, --fullscreen\n"
" Start in fullscreen.\n" " Start in fullscreen.\n"
"\n" "\n"
" -F, --record-format\n"
" Force recording format (either mp4 or mkv).\n"
"\n"
" -h, --help\n" " -h, --help\n"
" Print this help.\n" " Print this help.\n"
"\n" "\n"
@@ -61,21 +50,12 @@ static void usage(const char *arg0) {
" is preserved.\n" " is preserved.\n"
" Default is %d%s.\n" " Default is %d%s.\n"
"\n" "\n"
" -n, --no-control\n"
" Disable device control (mirror the device in read-only).\n"
"\n"
" -N, --no-display\n"
" Do not display device (only when screen recording is\n"
" enabled).\n"
"\n"
" -p, --port port\n" " -p, --port port\n"
" Set the TCP port the client listens on.\n" " Set the TCP port the client listens on.\n"
" Default is %d.\n" " Default is %d.\n"
"\n" "\n"
" -r, --record file.mp4\n" " -r, --record file.mp4\n"
" Record screen to file.\n" " Record screen to file.\n"
" The format is determined by the -F/--record-format option if\n"
" set, or by the file extension (.mp4 or .mkv).\n"
"\n" "\n"
" -s, --serial\n" " -s, --serial\n"
" The device serial number. Mandatory only if several devices\n" " The device serial number. Mandatory only if several devices\n"
@@ -85,9 +65,6 @@ static void usage(const char *arg0) {
" Enable \"show touches\" on start, disable on quit.\n" " Enable \"show touches\" on start, disable on quit.\n"
" It only shows physical touches (not clicks from scrcpy).\n" " It only shows physical touches (not clicks from scrcpy).\n"
"\n" "\n"
" -T, --always-on-top\n"
" Make scrcpy window always on top (above other windows).\n"
"\n"
" -v, --version\n" " -v, --version\n"
" Print the version of scrcpy.\n" " Print the version of scrcpy.\n"
"\n" "\n"
@@ -131,12 +108,6 @@ static void usage(const char *arg0) {
" Right-click (when screen is off)\n" " Right-click (when screen is off)\n"
" turn screen on\n" " turn screen on\n"
"\n" "\n"
" Ctrl+n\n"
" expand notification panel\n"
"\n"
" Ctrl+Shift+n\n"
" collapse notification panel\n"
"\n"
" Ctrl+v\n" " Ctrl+v\n"
" paste computer clipboard to device\n" " paste computer clipboard to device\n"
"\n" "\n"
@@ -152,37 +123,28 @@ static void usage(const char *arg0) {
DEFAULT_LOCAL_PORT); DEFAULT_LOCAL_PORT);
} }
static void static void print_version(void) {
print_version(void) {
fprintf(stderr, "scrcpy %s\n\n", SCRCPY_VERSION); fprintf(stderr, "scrcpy %s\n\n", SCRCPY_VERSION);
fprintf(stderr, "dependencies:\n"); fprintf(stderr, "dependencies:\n");
fprintf(stderr, " - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, fprintf(stderr, " - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL);
SDL_PATCHLEVEL); fprintf(stderr, " - libavcodec %d.%d.%d\n", LIBAVCODEC_VERSION_MAJOR, LIBAVCODEC_VERSION_MINOR, LIBAVCODEC_VERSION_MICRO);
fprintf(stderr, " - libavcodec %d.%d.%d\n", LIBAVCODEC_VERSION_MAJOR, fprintf(stderr, " - libavformat %d.%d.%d\n", LIBAVFORMAT_VERSION_MAJOR, LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO);
LIBAVCODEC_VERSION_MINOR, fprintf(stderr, " - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR, LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO);
LIBAVCODEC_VERSION_MICRO);
fprintf(stderr, " - libavformat %d.%d.%d\n", LIBAVFORMAT_VERSION_MAJOR,
LIBAVFORMAT_VERSION_MINOR,
LIBAVFORMAT_VERSION_MICRO);
fprintf(stderr, " - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR,
LIBAVUTIL_VERSION_MINOR,
LIBAVUTIL_VERSION_MICRO);
} }
static bool static SDL_bool parse_bit_rate(char *optarg, Uint32 *bit_rate) {
parse_bit_rate(char *optarg, uint32_t *bit_rate) {
char *endptr; char *endptr;
if (*optarg == '\0') { if (*optarg == '\0') {
LOGE("Bit-rate parameter is empty"); LOGE("Bit-rate parameter is empty");
return false; return SDL_FALSE;
} }
long value = strtol(optarg, &endptr, 0); long value = strtol(optarg, &endptr, 0);
int mul = 1; int mul = 1;
if (*endptr != '\0') { if (*endptr != '\0') {
if (optarg == endptr) { if (optarg == endptr) {
LOGE("Invalid bit-rate: %s", optarg); LOGE("Invalid bit-rate: %s", optarg);
return false; return SDL_FALSE;
} }
if ((*endptr == 'M' || *endptr == 'm') && endptr[1] == '\0') { if ((*endptr == 'M' || *endptr == 'm') && endptr[1] == '\0') {
mul = 1000000; mul = 1000000;
@@ -190,146 +152,97 @@ parse_bit_rate(char *optarg, uint32_t *bit_rate) {
mul = 1000; mul = 1000;
} else { } else {
LOGE("Invalid bit-rate unit: %s", optarg); LOGE("Invalid bit-rate unit: %s", optarg);
return false; return SDL_FALSE;
} }
} }
if (value < 0 || ((uint32_t) -1) / mul < value) { if (value < 0 || ((Uint32) -1) / mul < value) {
LOGE("Bitrate must be positive and less than 2^32: %s", optarg); LOGE("Bitrate must be positive and less than 2^32: %s", optarg);
return false; return SDL_FALSE;
} }
*bit_rate = (uint32_t) value * mul; *bit_rate = (Uint32) value * mul;
return true; return SDL_TRUE;
} }
static bool static SDL_bool parse_max_size(char *optarg, Uint16 *max_size) {
parse_max_size(char *optarg, uint16_t *max_size) {
char *endptr; char *endptr;
if (*optarg == '\0') { if (*optarg == '\0') {
LOGE("Max size parameter is empty"); LOGE("Max size parameter is empty");
return false; return SDL_FALSE;
} }
long value = strtol(optarg, &endptr, 0); long value = strtol(optarg, &endptr, 0);
if (*endptr != '\0') { if (*endptr != '\0') {
LOGE("Invalid max size: %s", optarg); LOGE("Invalid max size: %s", optarg);
return false; return SDL_FALSE;
} }
if (value & ~0xffff) { if (value & ~0xffff) {
LOGE("Max size must be between 0 and 65535: %ld", value); LOGE("Max size must be between 0 and 65535: %ld", value);
return false; return SDL_FALSE;
} }
*max_size = (uint16_t) value; *max_size = (Uint16) value;
return true; return SDL_TRUE;
} }
static bool static SDL_bool parse_port(char *optarg, Uint16 *port) {
parse_port(char *optarg, uint16_t *port) {
char *endptr; char *endptr;
if (*optarg == '\0') { if (*optarg == '\0') {
LOGE("Invalid port parameter is empty"); LOGE("Invalid port parameter is empty");
return false; return SDL_FALSE;
} }
long value = strtol(optarg, &endptr, 0); long value = strtol(optarg, &endptr, 0);
if (*endptr != '\0') { if (*endptr != '\0') {
LOGE("Invalid port: %s", optarg); LOGE("Invalid port: %s", optarg);
return false; return SDL_FALSE;
} }
if (value & ~0xffff) { if (value & ~0xffff) {
LOGE("Port out of range: %ld", value); LOGE("Port out of range: %ld", value);
return false; return SDL_FALSE;
} }
*port = (uint16_t) value; *port = (Uint16) value;
return true; return SDL_TRUE;
} }
static bool static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
parse_record_format(const char *optarg, enum recorder_format *format) {
if (!strcmp(optarg, "mp4")) {
*format = RECORDER_FORMAT_MP4;
return true;
}
if (!strcmp(optarg, "mkv")) {
*format = RECORDER_FORMAT_MKV;
return true;
}
LOGE("Unsupported format: %s (expected mp4 or mkv)", optarg);
return false;
}
static enum recorder_format
guess_record_format(const char *filename) {
size_t len = strlen(filename);
if (len < 4) {
return 0;
}
const char *ext = &filename[len - 4];
if (!strcmp(ext, ".mp4")) {
return RECORDER_FORMAT_MP4;
}
if (!strcmp(ext, ".mkv")) {
return RECORDER_FORMAT_MKV;
}
return 0;
}
static bool
parse_args(struct args *args, int argc, char *argv[]) {
static const struct option long_options[] = { static const struct option long_options[] = {
{"always-on-top", no_argument, NULL, 'T'}, {"bit-rate", required_argument, NULL, 'b'},
{"bit-rate", required_argument, NULL, 'b'}, {"crop", required_argument, NULL, 'c'},
{"crop", required_argument, NULL, 'c'}, {"fullscreen", no_argument, NULL, 'f'},
{"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'},
{"no-control", no_argument, NULL, 'n'}, {"record", required_argument, NULL, 'r'},
{"no-display", no_argument, NULL, 'N'}, {"serial", required_argument, NULL, 's'},
{"port", required_argument, NULL, 'p'}, {"show-touches", no_argument, NULL, 't'},
{"record", required_argument, NULL, 'r'}, {"version", no_argument, NULL, 'v'},
{"record-format", required_argument, NULL, 'f'}, {NULL, 0, NULL, 0 },
{"serial", required_argument, NULL, 's'},
{"show-touches", no_argument, NULL, 't'},
{"version", no_argument, NULL, 'v'},
{NULL, 0, NULL, 0 },
}; };
int c; int c;
while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:tTv", long_options, while ((c = getopt_long(argc, argv, "b:c:fhm:p:r:s:tv", long_options, NULL)) != -1) {
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 false; return SDL_FALSE;
} }
break; break;
case 'c': case 'c':
args->crop = optarg; args->crop = optarg;
break; break;
case 'f': case 'f':
args->fullscreen = true; args->fullscreen = SDL_TRUE;
break;
case 'F':
if (!parse_record_format(optarg, &args->record_format)) {
return false;
}
break; break;
case 'h': case 'h':
args->help = true; args->help = SDL_TRUE;
break; break;
case 'm': case 'm':
if (!parse_max_size(optarg, &args->max_size)) { if (!parse_max_size(optarg, &args->max_size)) {
return false; return SDL_FALSE;
} }
break; break;
case 'n':
args->no_control = true;
break;
case 'N':
args->no_display = true;
break;
case 'p': case 'p':
if (!parse_port(optarg, &args->port)) { if (!parse_port(optarg, &args->port)) {
return false; return SDL_FALSE;
} }
break; break;
case 'r': case 'r':
@@ -339,55 +252,26 @@ parse_args(struct args *args, int argc, char *argv[]) {
args->serial = optarg; args->serial = optarg;
break; break;
case 't': case 't':
args->show_touches = true; args->show_touches = SDL_TRUE;
break;
case 'T':
args->always_on_top = true;
break; break;
case 'v': case 'v':
args->version = true; args->version = SDL_TRUE;
break; break;
default: default:
// getopt prints the error message on stderr // getopt prints the error message on stderr
return false; return SDL_FALSE;
} }
} }
if (args->no_display && !args->record_filename) {
LOGE("-N/--no-display requires screen recording (-r/--record)");
return false;
}
if (args->no_display && args->fullscreen) {
LOGE("-f/--fullscreen-window is incompatible with -N/--no-display");
return false;
}
int index = optind; int index = optind;
if (index < argc) { if (index < argc) {
LOGE("Unexpected additional argument: %s", argv[index]); LOGE("Unexpected additional argument: %s", argv[index]);
return false; return SDL_FALSE;
} }
return SDL_TRUE;
if (args->record_format && !args->record_filename) {
LOGE("Record format specified without recording");
return false;
}
if (args->record_filename && !args->record_format) {
args->record_format = guess_record_format(args->record_filename);
if (!args->record_format) {
LOGE("No format specified for \"%s\" (try with -F mkv)",
args->record_filename);
return false;
}
}
return true;
} }
int int main(int argc, char *argv[]) {
main(int argc, char *argv[]) {
#ifdef __WINDOWS__ #ifdef __WINDOWS__
// disable buffering, we want logs immediately // disable buffering, we want logs immediately
// even line buffering (setvbuf() with mode _IOLBF) is not sufficient // even line buffering (setvbuf() with mode _IOLBF) is not sufficient
@@ -398,16 +282,12 @@ main(int argc, char *argv[]) {
.serial = NULL, .serial = NULL,
.crop = NULL, .crop = NULL,
.record_filename = NULL, .record_filename = NULL,
.record_format = 0, .help = SDL_FALSE,
.help = false, .version = SDL_FALSE,
.version = false, .show_touches = SDL_FALSE,
.show_touches = false,
.port = DEFAULT_LOCAL_PORT, .port = DEFAULT_LOCAL_PORT,
.max_size = DEFAULT_MAX_SIZE, .max_size = DEFAULT_MAX_SIZE,
.bit_rate = DEFAULT_BIT_RATE, .bit_rate = DEFAULT_BIT_RATE,
.always_on_top = false,
.no_control = false,
.no_display = false,
}; };
if (!parse_args(&args, argc, argv)) { if (!parse_args(&args, argc, argv)) {
return 1; return 1;
@@ -423,7 +303,7 @@ main(int argc, char *argv[]) {
return 0; return 0;
} }
#ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
av_register_all(); av_register_all();
#endif #endif
@@ -440,14 +320,10 @@ main(int argc, char *argv[]) {
.crop = args.crop, .crop = args.crop,
.port = args.port, .port = args.port,
.record_filename = args.record_filename, .record_filename = args.record_filename,
.record_format = args.record_format,
.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, .fullscreen = args.fullscreen,
.always_on_top = args.always_on_top,
.no_control = args.no_control,
.no_display = args.no_display,
}; };
int res = scrcpy(&options) ? 0 : 1; int res = scrcpy(&options) ? 0 : 1;

View File

@@ -18,8 +18,7 @@
typedef struct in_addr IN_ADDR; typedef struct in_addr IN_ADDR;
#endif #endif
socket_t socket_t net_connect(Uint32 addr, Uint16 port) {
net_connect(uint32_t addr, uint16_t port) {
socket_t sock = socket(AF_INET, SOCK_STREAM, 0); socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) { if (sock == INVALID_SOCKET) {
perror("socket"); perror("socket");
@@ -39,8 +38,7 @@ net_connect(uint32_t addr, uint16_t port) {
return sock; return sock;
} }
socket_t socket_t net_listen(Uint32 addr, Uint16 port, int backlog) {
net_listen(uint32_t addr, uint16_t port, int backlog) {
socket_t sock = socket(AF_INET, SOCK_STREAM, 0); socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) { if (sock == INVALID_SOCKET) {
perror("socket"); perror("socket");
@@ -48,8 +46,7 @@ net_listen(uint32_t addr, uint16_t port, int backlog) {
} }
int reuse = 1; int reuse = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse, if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse, sizeof(reuse)) == -1) {
sizeof(reuse)) == -1) {
perror("setsockopt(SO_REUSEADDR)"); perror("setsockopt(SO_REUSEADDR)");
} }
@@ -71,30 +68,25 @@ net_listen(uint32_t addr, uint16_t port, int backlog) {
return sock; return sock;
} }
socket_t socket_t net_accept(socket_t server_socket) {
net_accept(socket_t server_socket) {
SOCKADDR_IN csin; SOCKADDR_IN csin;
socklen_t sinsize = sizeof(csin); socklen_t sinsize = sizeof(csin);
return accept(server_socket, (SOCKADDR *) &csin, &sinsize); return accept(server_socket, (SOCKADDR *) &csin, &sinsize);
} }
ssize_t ssize_t net_recv(socket_t socket, void *buf, size_t len) {
net_recv(socket_t socket, void *buf, size_t len) {
return recv(socket, buf, len, 0); return recv(socket, buf, len, 0);
} }
ssize_t ssize_t net_recv_all(socket_t socket, void *buf, size_t len) {
net_recv_all(socket_t socket, void *buf, size_t len) {
return recv(socket, buf, len, MSG_WAITALL); return recv(socket, buf, len, MSG_WAITALL);
} }
ssize_t ssize_t net_send(socket_t socket, const void *buf, size_t len) {
net_send(socket_t socket, const void *buf, size_t len) {
return send(socket, buf, len, 0); return send(socket, buf, len, 0);
} }
ssize_t ssize_t net_send_all(socket_t socket, const void *buf, size_t len) {
net_send_all(socket_t socket, const void *buf, size_t len) {
ssize_t w = 0; ssize_t w = 0;
while (len > 0) { while (len > 0) {
w = send(socket, buf, len, 0); w = send(socket, buf, len, 0);
@@ -107,7 +99,6 @@ net_send_all(socket_t socket, const void *buf, size_t len) {
return w; return w;
} }
bool SDL_bool net_shutdown(socket_t socket, int how) {
net_shutdown(socket_t socket, int how) {
return !shutdown(socket, how); return !shutdown(socket, how);
} }

View File

@@ -1,9 +1,8 @@
#ifndef NET_H #ifndef NET_H
#define NET_H #define NET_H
#include <stdbool.h>
#include <stdint.h>
#include <SDL2/SDL_platform.h> #include <SDL2/SDL_platform.h>
#include <SDL2/SDL_stdinc.h>
#ifdef __WINDOWS__ #ifdef __WINDOWS__
# include <winsock2.h> # include <winsock2.h>
@@ -17,39 +16,20 @@
typedef int socket_t; typedef int socket_t;
#endif #endif
bool SDL_bool net_init(void);
net_init(void); void net_cleanup(void);
void socket_t net_connect(Uint32 addr, Uint16 port);
net_cleanup(void); socket_t net_listen(Uint32 addr, Uint16 port, int backlog);
socket_t net_accept(socket_t server_socket);
socket_t
net_connect(uint32_t addr, uint16_t port);
socket_t
net_listen(uint32_t addr, uint16_t port, int backlog);
socket_t
net_accept(socket_t server_socket);
// the _all versions wait/retry until len bytes have been written/read // the _all versions wait/retry until len bytes have been written/read
ssize_t ssize_t net_recv(socket_t socket, void *buf, size_t len);
net_recv(socket_t socket, void *buf, size_t len); ssize_t net_recv_all(socket_t socket, void *buf, size_t len);
ssize_t net_send(socket_t socket, const void *buf, size_t len);
ssize_t ssize_t net_send_all(socket_t socket, const void *buf, size_t len);
net_recv_all(socket_t socket, void *buf, size_t len);
ssize_t
net_send(socket_t socket, const void *buf, size_t len);
ssize_t
net_send_all(socket_t socket, const void *buf, size_t len);
// how is SHUT_RD (read), SHUT_WR (write) or SHUT_RDWR (both) // how is SHUT_RD (read), SHUT_WR (write) or SHUT_RDWR (both)
bool SDL_bool net_shutdown(socket_t socket, int how);
net_shutdown(socket_t socket, int how); SDL_bool net_close(socket_t socket);
bool
net_close(socket_t socket);
#endif #endif

View File

@@ -1,92 +1,75 @@
#include "recorder.h" #include "recorder.h"
#include <libavutil/time.h> #include <libavutil/time.h>
#include <SDL2/SDL_assert.h>
#include "compat.h"
#include "config.h" #include "config.h"
#include "log.h" #include "log.h"
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us static const AVOutputFormat *find_mp4_muxer(void) {
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 9, 100)
static const AVOutputFormat *
find_muxer(const char *name) {
#ifdef SCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API
void *opaque = NULL; void *opaque = NULL;
#endif #endif
const AVOutputFormat *oformat = NULL; const AVOutputFormat *oformat = NULL;
do { do {
#ifdef SCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 9, 100)
oformat = av_muxer_iterate(&opaque); oformat = av_muxer_iterate(&opaque);
#else #else
oformat = av_oformat_next(oformat); oformat = av_oformat_next(oformat);
#endif #endif
// until null or with name "mp4" // until null or with name "mp4"
} while (oformat && strcmp(oformat->name, name)); } while (oformat && strcmp(oformat->name, "mp4"));
return oformat; return oformat;
} }
bool SDL_bool recorder_init(struct recorder *recorder, const char *filename,
recorder_init(struct recorder *recorder, struct size declared_frame_size) {
const char *filename,
enum recorder_format format,
struct size declared_frame_size) {
recorder->filename = SDL_strdup(filename); recorder->filename = SDL_strdup(filename);
if (!recorder->filename) { if (!recorder->filename) {
LOGE("Cannot strdup filename"); LOGE("Cannot strdup filename");
return false; return SDL_FALSE;
} }
recorder->format = format;
recorder->declared_frame_size = declared_frame_size; recorder->declared_frame_size = declared_frame_size;
recorder->header_written = false;
return true; return SDL_TRUE;
} }
void void recorder_destroy(struct recorder *recorder) {
recorder_destroy(struct recorder *recorder) {
SDL_free(recorder->filename); SDL_free(recorder->filename);
} }
static const char * SDL_bool recorder_open(struct recorder *recorder, AVCodec *input_codec) {
recorder_get_format_name(enum recorder_format format) { const AVOutputFormat *mp4 = find_mp4_muxer();
switch (format) { if (!mp4) {
case RECORDER_FORMAT_MP4: return "mp4"; LOGE("Could not find mp4 muxer");
case RECORDER_FORMAT_MKV: return "matroska"; return SDL_FALSE;
default: return NULL;
}
}
bool
recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
const char *format_name = recorder_get_format_name(recorder->format);
SDL_assert(format_name);
const AVOutputFormat *format = find_muxer(format_name);
if (!format) {
LOGE("Could not find muxer");
return false;
} }
recorder->ctx = avformat_alloc_context(); recorder->ctx = avformat_alloc_context();
if (!recorder->ctx) { if (!recorder->ctx) {
LOGE("Could not allocate output context"); LOGE("Could not allocate output context");
return false; return SDL_FALSE;
} }
// contrary to the deprecated API (av_oformat_next()), av_muxer_iterate() // contrary to the deprecated API (av_oformat_next()), av_muxer_iterate()
// returns (on purpose) a pointer-to-const, but AVFormatContext.oformat // returns (on purpose) a pointer-to-const, but AVFormatContext.oformat
// still expects a pointer-to-non-const (it has not be updated accordingly) // still expects a pointer-to-non-const (it has not be updated accordingly)
// <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd> // <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd>
recorder->ctx->oformat = (AVOutputFormat *) format; recorder->ctx->oformat = (AVOutputFormat *) mp4;
AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec); AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec);
if (!ostream) { if (!ostream) {
avformat_free_context(recorder->ctx); avformat_free_context(recorder->ctx);
return false; return SDL_FALSE;
} }
#ifdef SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API // In ffmpeg/doc/APIchanges:
// 2016-04-11 - 6f69f7a / 9200514 - lavf 57.33.100 / 57.5.0 - avformat.h
// Add AVStream.codecpar, deprecate AVStream.codec.
#if (LIBAVFORMAT_VERSION_MICRO >= 100 /* FFmpeg */ && \
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 33, 100)) \
|| (LIBAVFORMAT_VERSION_MICRO < 100 && /* Libav */ \
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 5, 0))
ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
ostream->codecpar->codec_id = input_codec->id; ostream->codecpar->codec_id = input_codec->id;
ostream->codecpar->format = AV_PIX_FMT_YUV420P; ostream->codecpar->format = AV_PIX_FMT_YUV420P;
@@ -99,6 +82,7 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
ostream->codec->width = recorder->declared_frame_size.width; ostream->codec->width = recorder->declared_frame_size.width;
ostream->codec->height = recorder->declared_frame_size.height; ostream->codec->height = recorder->declared_frame_size.height;
#endif #endif
ostream->time_base = (AVRational) {1, 1000000}; // timestamps in us
int ret = avio_open(&recorder->ctx->pb, recorder->filename, int ret = avio_open(&recorder->ctx->pb, recorder->filename,
AVIO_FLAG_WRITE); AVIO_FLAG_WRITE);
@@ -106,76 +90,29 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
LOGE("Failed to open output file: %s", recorder->filename); LOGE("Failed to open output file: %s", recorder->filename);
// ostream will be cleaned up during context cleaning // ostream will be cleaned up during context cleaning
avformat_free_context(recorder->ctx); avformat_free_context(recorder->ctx);
return false; return SDL_FALSE;
} }
LOGI("Recording started to %s file: %s", format_name, recorder->filename); ret = avformat_write_header(recorder->ctx, NULL);
if (ret < 0) {
LOGE("Failed to write header to %s", recorder->filename);
avio_closep(&recorder->ctx->pb);
avformat_free_context(recorder->ctx);
return SDL_FALSE;
}
return true; return SDL_TRUE;
} }
void void recorder_close(struct recorder *recorder) {
recorder_close(struct recorder *recorder) {
int ret = av_write_trailer(recorder->ctx); int ret = av_write_trailer(recorder->ctx);
if (ret < 0) { if (ret < 0) {
LOGE("Failed to write trailer to %s", recorder->filename); LOGE("Failed to write trailer to %s", recorder->filename);
} }
avio_close(recorder->ctx->pb); avio_close(recorder->ctx->pb);
avformat_free_context(recorder->ctx); avformat_free_context(recorder->ctx);
const char *format_name = recorder_get_format_name(recorder->format);
LOGI("Recording complete to %s file: %s", format_name, recorder->filename);
} }
static bool SDL_bool recorder_write(struct recorder *recorder, AVPacket *packet) {
recorder_write_header(struct recorder *recorder, const AVPacket *packet) {
AVStream *ostream = recorder->ctx->streams[0];
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t));
if (!extradata) {
LOGC("Cannot allocate extradata");
return false;
}
// copy the first packet to the extra data
memcpy(extradata, packet->data, packet->size);
#ifdef SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
ostream->codecpar->extradata = extradata;
ostream->codecpar->extradata_size = packet->size;
#else
ostream->codec->extradata = extradata;
ostream->codec->extradata_size = packet->size;
#endif
int ret = avformat_write_header(recorder->ctx, NULL);
if (ret < 0) {
LOGE("Failed to write header to %s", recorder->filename);
SDL_free(extradata);
avio_closep(&recorder->ctx->pb);
avformat_free_context(recorder->ctx);
return false;
}
return true;
}
static void
recorder_rescale_packet(struct recorder *recorder, AVPacket *packet) {
AVStream *ostream = recorder->ctx->streams[0];
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
}
bool
recorder_write(struct recorder *recorder, AVPacket *packet) {
if (!recorder->header_written) {
bool ok = recorder_write_header(recorder, packet);
if (!ok) {
return false;
}
recorder->header_written = true;
}
recorder_rescale_packet(recorder, packet);
return av_write_frame(recorder->ctx, packet) >= 0; return av_write_frame(recorder->ctx, packet) >= 0;
} }

View File

@@ -1,38 +1,24 @@
#ifndef RECORDER_H #ifndef RECORDER_H
#define RECORDER_H #define RECORDER_H
#include <stdbool.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <SDL2/SDL_stdinc.h>
#include "common.h" #include "common.h"
enum recorder_format {
RECORDER_FORMAT_MP4 = 1,
RECORDER_FORMAT_MKV,
};
struct recorder { struct recorder {
char *filename; char *filename;
enum recorder_format format;
AVFormatContext *ctx; AVFormatContext *ctx;
struct size declared_frame_size; struct size declared_frame_size;
bool header_written;
}; };
bool SDL_bool recorder_init(struct recorder *recoder, const char *filename,
recorder_init(struct recorder *recoder, const char *filename, struct size declared_frame_size);
enum recorder_format format, struct size declared_frame_size); void recorder_destroy(struct recorder *recorder);
void SDL_bool recorder_open(struct recorder *recorder, AVCodec *input_codec);
recorder_destroy(struct recorder *recorder); void recorder_close(struct recorder *recorder);
bool SDL_bool recorder_write(struct recorder *recorder, AVPacket *packet);
recorder_open(struct recorder *recorder, const AVCodec *input_codec);
void
recorder_close(struct recorder *recorder);
bool
recorder_write(struct recorder *recorder, AVPacket *packet);
#endif #endif

View File

@@ -14,6 +14,7 @@
#include "device.h" #include "device.h"
#include "events.h" #include "events.h"
#include "file_handler.h" #include "file_handler.h"
#include "frames.h"
#include "fps_counter.h" #include "fps_counter.h"
#include "input_manager.h" #include "input_manager.h"
#include "log.h" #include "log.h"
@@ -22,59 +23,22 @@
#include "recorder.h" #include "recorder.h"
#include "screen.h" #include "screen.h"
#include "server.h" #include "server.h"
#include "stream.h"
#include "tiny_xpm.h" #include "tiny_xpm.h"
#include "video_buffer.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 video_buffer video_buffer; static struct frames frames;
static struct stream stream;
static struct decoder decoder; static struct decoder decoder;
static struct recorder recorder;
static struct controller controller; static struct controller controller;
static struct file_handler file_handler; static struct file_handler file_handler;
static struct recorder recorder;
static struct input_manager input_manager = { static struct input_manager input_manager = {
.controller = &controller, .controller = &controller,
.video_buffer = &video_buffer, .frames = &frames,
.screen = &screen, .screen = &screen,
}; };
// init SDL and set appropriate hints
static bool
sdl_init_and_configure(bool display) {
uint32_t flags = display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS;
if (SDL_Init(flags)) {
LOGC("Could not initialize SDL: %s", SDL_GetError());
return false;
}
atexit(SDL_Quit);
if (!display) {
return true;
}
// Use the best available scale quality
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2")) {
LOGW("Could not enable bilinear filtering");
}
#ifdef SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH
// Handle a click to gain focus as any other click
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
LOGW("Could not enable mouse focus clickthrough");
}
#endif
// Do not disable the screensaver when scrcpy is running
SDL_EnableScreenSaver();
return true;
}
#if defined(__APPLE__) || defined(__WINDOWS__) #if defined(__APPLE__) || defined(__WINDOWS__)
# define CONTINUOUS_RESIZING_WORKAROUND # define CONTINUOUS_RESIZING_WORKAROUND
#endif #endif
@@ -85,10 +49,8 @@ sdl_init_and_configure(bool display) {
// //
// <https://bugzilla.libsdl.org/show_bug.cgi?id=2077> // <https://bugzilla.libsdl.org/show_bug.cgi?id=2077>
// <https://stackoverflow.com/a/40693139/1987178> // <https://stackoverflow.com/a/40693139/1987178>
static int static int event_watcher(void *data, SDL_Event *event) {
event_watcher(void *data, SDL_Event *event) { if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_RESIZED) {
if (event->type == SDL_WINDOWEVENT
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
// called from another thread, not very safe, but it's a workaround! // called from another thread, not very safe, but it's a workaround!
screen_render(&screen); screen_render(&screen);
} }
@@ -96,117 +58,75 @@ event_watcher(void *data, SDL_Event *event) {
} }
#endif #endif
static bool static SDL_bool is_apk(const char *file) {
is_apk(const char *file) {
const char *ext = strrchr(file, '.'); const char *ext = strrchr(file, '.');
return ext && !strcmp(ext, ".apk"); return ext && !strcmp(ext, ".apk");
} }
enum event_result { static SDL_bool event_loop(void) {
EVENT_RESULT_CONTINUE,
EVENT_RESULT_STOPPED_BY_USER,
EVENT_RESULT_STOPPED_BY_EOS,
};
static enum event_result
handle_event(SDL_Event *event, bool control) {
switch (event->type) {
case EVENT_STREAM_STOPPED:
LOGD("Video stream stopped");
return EVENT_RESULT_STOPPED_BY_EOS;
case SDL_QUIT:
LOGD("User requested to quit");
return EVENT_RESULT_STOPPED_BY_USER;
case EVENT_NEW_FRAME:
if (!screen.has_frame) {
screen.has_frame = true;
// this is the very first frame, show the window
screen_show_window(&screen);
}
if (!screen_update_frame(&screen, &video_buffer)) {
return false;
}
break;
case SDL_WINDOWEVENT:
switch (event->window.event) {
case SDL_WINDOWEVENT_EXPOSED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_render(&screen);
break;
}
break;
case SDL_TEXTINPUT:
if (!control) {
break;
}
input_manager_process_text_input(&input_manager, &event->text);
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
// some key events do not interact with the device, so process the
// event even if control is disabled
input_manager_process_key(&input_manager, &event->key, control);
break;
case SDL_MOUSEMOTION:
if (!control) {
break;
}
input_manager_process_mouse_motion(&input_manager, &event->motion);
break;
case SDL_MOUSEWHEEL:
if (!control) {
break;
}
input_manager_process_mouse_wheel(&input_manager, &event->wheel);
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
// some mouse events do not interact with the device, so process
// the event even if control is disabled
input_manager_process_mouse_button(&input_manager, &event->button,
control);
break;
case SDL_DROPFILE: {
if (!control) {
break;
}
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 EVENT_RESULT_CONTINUE;
}
static bool
event_loop(bool display, bool control) {
#ifdef CONTINUOUS_RESIZING_WORKAROUND #ifdef CONTINUOUS_RESIZING_WORKAROUND
if (display) { SDL_AddEventWatch(event_watcher, NULL);
SDL_AddEventWatch(event_watcher, NULL);
}
#endif #endif
SDL_Event event; SDL_Event event;
while (SDL_WaitEvent(&event)) { while (SDL_WaitEvent(&event)) {
enum event_result result = handle_event(&event, control); switch (event.type) {
switch (result) { case EVENT_DECODER_STOPPED:
case EVENT_RESULT_STOPPED_BY_USER: LOGD("Video decoder stopped");
return true; return SDL_FALSE;
case EVENT_RESULT_STOPPED_BY_EOS: case SDL_QUIT:
return false; LOGD("User requested to quit");
case EVENT_RESULT_CONTINUE: return SDL_TRUE;
case EVENT_NEW_FRAME:
if (!screen.has_frame) {
screen.has_frame = SDL_TRUE;
// this is the very first frame, show the window
screen_show_window(&screen);
}
if (!screen_update_frame(&screen, &frames)) {
return SDL_FALSE;
}
break; break;
case SDL_WINDOWEVENT:
switch (event.window.event) {
case SDL_WINDOWEVENT_EXPOSED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_render(&screen);
break;
}
break;
case SDL_TEXTINPUT:
input_manager_process_text_input(&input_manager, &event.text);
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
input_manager_process_key(&input_manager, &event.key);
break;
case SDL_MOUSEMOTION:
input_manager_process_mouse_motion(&input_manager, &event.motion);
break;
case SDL_MOUSEWHEEL:
input_manager_process_mouse_wheel(&input_manager, &event.wheel);
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
input_manager_process_mouse_button(&input_manager, &event.button);
break;
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 false; return SDL_FALSE;
} }
static process_t static process_t set_show_touches_enabled(const char *serial, SDL_bool enabled) {
set_show_touches_enabled(const char *serial, bool enabled) {
const char *value = enabled ? "1" : "0"; const char *value = enabled ? "1" : "0";
const char *const adb_cmd[] = { const char *const adb_cmd[] = {
"shell", "settings", "put", "system", "show_touches", value "shell", "settings", "put", "system", "show_touches", value
@@ -214,198 +134,134 @@ set_show_touches_enabled(const char *serial, bool enabled) {
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
static void static void wait_show_touches(process_t process) {
wait_show_touches(process_t process) {
// reap the process, ignore the result // reap the process, ignore the result
process_check_success(process, "show_touches"); process_check_success(process, "show_touches");
} }
static SDL_LogPriority SDL_bool scrcpy(const struct scrcpy_options *options) {
sdl_priority_from_av_level(int level) { SDL_bool send_frame_meta = !!options->record_filename;
switch (level) {
case AV_LOG_PANIC:
case AV_LOG_FATAL:
return SDL_LOG_PRIORITY_CRITICAL;
case AV_LOG_ERROR:
return SDL_LOG_PRIORITY_ERROR;
case AV_LOG_WARNING:
return SDL_LOG_PRIORITY_WARN;
case AV_LOG_INFO:
return SDL_LOG_PRIORITY_INFO;
}
// do not forward others, which are too verbose
return 0;
}
static void
av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
SDL_LogPriority priority = sdl_priority_from_av_level(level);
if (priority == 0) {
return;
}
char *local_fmt = SDL_malloc(strlen(fmt) + 10);
if (!local_fmt) {
LOGC("Cannot allocate string");
return;
}
// strcpy is safe here, the destination is large enough
strcpy(local_fmt, "[FFmpeg] ");
strcpy(local_fmt + 9, fmt);
SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl);
SDL_free(local_fmt);
}
bool
scrcpy(const struct scrcpy_options *options) {
bool record = !!options->record_filename;
if (!server_start(&server, options->serial, options->port, if (!server_start(&server, options->serial, options->port,
options->max_size, options->bit_rate, options->crop, options->max_size, options->bit_rate, options->crop,
record)) { send_frame_meta)) {
return false; return SDL_FALSE;
} }
process_t proc_show_touches = PROCESS_NONE; process_t proc_show_touches = PROCESS_NONE;
bool show_touches_waited; SDL_bool show_touches_waited;
if (options->show_touches) { if (options->show_touches) {
LOGI("Enable show_touches"); LOGI("Enable show_touches");
proc_show_touches = set_show_touches_enabled(options->serial, true); proc_show_touches = set_show_touches_enabled(options->serial, SDL_TRUE);
show_touches_waited = false; show_touches_waited = SDL_FALSE;
} }
bool ret = true; SDL_bool ret = SDL_TRUE;
bool display = !options->no_display; if (!sdl_init_and_configure()) {
bool control = !options->no_control; ret = SDL_FALSE;
if (!sdl_init_and_configure(display)) {
ret = false;
goto finally_destroy_server; goto finally_destroy_server;
} }
socket_t device_socket = server_connect_to(&server); socket_t device_socket = server_connect_to(&server);
if (device_socket == INVALID_SOCKET) { if (device_socket == INVALID_SOCKET) {
server_stop(&server); server_stop(&server);
ret = false; ret = SDL_FALSE;
goto finally_destroy_server; goto finally_destroy_server;
} }
char device_name[DEVICE_NAME_FIELD_LENGTH]; char device_name[DEVICE_NAME_FIELD_LENGTH];
struct size frame_size; struct size frame_size;
// screenrecord does not send frames when the screen content does not // screenrecord does not send frames when the screen content does not change
// change therefore, we transmit the screen size before the video stream, // therefore, we transmit the screen size before the video stream, to be able
// to be able to init the window immediately // to init the window immediately
if (!device_read_info(device_socket, device_name, &frame_size)) { if (!device_read_info(device_socket, device_name, &frame_size)) {
server_stop(&server); server_stop(&server);
ret = false; ret = SDL_FALSE;
goto finally_destroy_server; goto finally_destroy_server;
} }
struct decoder *dec = NULL; if (!frames_init(&frames)) {
if (display) { server_stop(&server);
if (!video_buffer_init(&video_buffer)) { ret = SDL_FALSE;
server_stop(&server); goto finally_destroy_server;
ret = false; }
goto finally_destroy_server;
}
if (control && !file_handler_init(&file_handler, server.serial)) { if (!file_handler_init(&file_handler, server.serial)) {
ret = false; ret = SDL_FALSE;
server_stop(&server); server_stop(&server);
goto finally_destroy_video_buffer; goto finally_destroy_frames;
}
decoder_init(&decoder, &video_buffer);
dec = &decoder;
} }
struct recorder *rec = NULL; struct recorder *rec = NULL;
if (record) { if (options->record_filename) {
if (!recorder_init(&recorder, if (!recorder_init(&recorder, options->record_filename, frame_size)) {
options->record_filename, ret = SDL_FALSE;
options->record_format,
frame_size)) {
ret = false;
server_stop(&server); server_stop(&server);
goto finally_destroy_file_handler; goto finally_destroy_file_handler;
} }
rec = &recorder; rec = &recorder;
} }
av_log_set_callback(av_log_callback); decoder_init(&decoder, &frames, device_socket, rec);
stream_init(&stream, device_socket, dec, rec);
// now we consumed the header values, the socket receives the video stream // now we consumed the header values, the socket receives the video stream
// start the stream // start the decoder
if (!stream_start(&stream)) { if (!decoder_start(&decoder)) {
ret = false; ret = SDL_FALSE;
server_stop(&server); server_stop(&server);
goto finally_destroy_recorder; goto finally_destroy_recorder;
} }
if (display) { if (!controller_init(&controller, device_socket)) {
if (control) { ret = SDL_FALSE;
if (!controller_init(&controller, device_socket)) { goto finally_stop_decoder;
ret = false; }
goto finally_stop_stream;
}
if (!controller_start(&controller)) { if (!controller_start(&controller)) {
ret = false; ret = SDL_FALSE;
goto finally_destroy_controller; goto finally_destroy_controller;
} }
}
if (!screen_init_rendering(&screen, device_name, frame_size, if (!screen_init_rendering(&screen, device_name, frame_size)) {
options->always_on_top)) { ret = SDL_FALSE;
ret = false; goto finally_stop_and_join_controller;
goto finally_stop_and_join_controller;
}
if (options->fullscreen) {
screen_switch_fullscreen(&screen);
}
} }
if (options->show_touches) { if (options->show_touches) {
wait_show_touches(proc_show_touches); wait_show_touches(proc_show_touches);
show_touches_waited = true; show_touches_waited = SDL_TRUE;
} }
ret = event_loop(display, control); if (options->fullscreen) {
screen_switch_fullscreen(&screen);
}
ret = event_loop();
LOGD("quit..."); LOGD("quit...");
screen_destroy(&screen); screen_destroy(&screen);
finally_stop_and_join_controller: finally_stop_and_join_controller:
if (display && control) { controller_stop(&controller);
controller_stop(&controller); controller_join(&controller);
controller_join(&controller);
}
finally_destroy_controller: finally_destroy_controller:
if (display && control) { controller_destroy(&controller);
controller_destroy(&controller); finally_stop_decoder:
} decoder_stop(&decoder);
finally_stop_stream: // stop the server before decoder_join() to wake up the decoder
stream_stop(&stream);
// stop the server before stream_join() to wake up the stream
server_stop(&server); server_stop(&server);
stream_join(&stream); decoder_join(&decoder);
finally_destroy_file_handler:
file_handler_stop(&file_handler);
file_handler_join(&file_handler);
file_handler_destroy(&file_handler);
finally_destroy_recorder: finally_destroy_recorder:
if (record) { if (options->record_filename) {
recorder_destroy(&recorder); recorder_destroy(&recorder);
} }
finally_destroy_file_handler: finally_destroy_frames:
if (display && control) { frames_destroy(&frames);
file_handler_stop(&file_handler);
file_handler_join(&file_handler);
file_handler_destroy(&file_handler);
}
finally_destroy_video_buffer:
if (display) {
video_buffer_destroy(&video_buffer);
}
finally_destroy_server: finally_destroy_server:
if (options->show_touches) { if (options->show_touches) {
if (!show_touches_waited) { if (!show_touches_waited) {
@@ -413,8 +269,7 @@ finally_destroy_server:
wait_show_touches(proc_show_touches); wait_show_touches(proc_show_touches);
} }
LOGI("Disable show_touches"); LOGI("Disable show_touches");
proc_show_touches = set_show_touches_enabled(options->serial, proc_show_touches = set_show_touches_enabled(options->serial, SDL_FALSE);
false);
wait_show_touches(proc_show_touches); wait_show_touches(proc_show_touches);
} }

View File

@@ -1,26 +1,19 @@
#ifndef SCRCPY_H #ifndef SCRCPY_H
#define SCRCPY_H #define SCRCPY_H
#include <stdbool.h> #include <SDL2/SDL_stdinc.h>
#include <stdint.h>
#include <recorder.h>
struct scrcpy_options { struct scrcpy_options {
const char *serial; const char *serial;
const char *crop; const char *crop;
const char *record_filename; const char *record_filename;
enum recorder_format record_format; Uint16 port;
uint16_t port; Uint16 max_size;
uint16_t max_size; Uint32 bit_rate;
uint32_t bit_rate; SDL_bool show_touches;
bool show_touches; SDL_bool fullscreen;
bool fullscreen;
bool always_on_top;
bool no_control;
bool no_display;
}; };
bool SDL_bool scrcpy(const struct scrcpy_options *options);
scrcpy(const struct scrcpy_options *options);
#endif #endif

View File

@@ -1,21 +1,40 @@
#include "screen.h" #include "screen.h"
#include <string.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <string.h>
#include "common.h"
#include "compat.h"
#include "icon.xpm" #include "icon.xpm"
#include "lock_util.h" #include "lock_util.h"
#include "log.h" #include "log.h"
#include "tiny_xpm.h" #include "tiny_xpm.h"
#include "video_buffer.h"
#define DISPLAY_MARGINS 96 #define DISPLAY_MARGINS 96
SDL_bool sdl_init_and_configure(void) {
if (SDL_Init(SDL_INIT_VIDEO)) {
LOGC("Could not initialize SDL: %s", SDL_GetError());
return SDL_FALSE;
}
atexit(SDL_Quit);
// Use the best available scale quality
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2")) {
LOGW("Could not enable bilinear filtering");
}
#if SDL_VERSION_ATLEAST(2, 0, 5)
// Handle a click to gain focus as any other click
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
LOGW("Could not enable mouse focus clickthrough");
}
#endif
return SDL_TRUE;
}
// get the window size in a struct size // get the window size in a struct size
static struct size static struct size get_native_window_size(SDL_Window *window) {
get_native_window_size(SDL_Window *window) {
int width; int width;
int height; int height;
SDL_GetWindowSize(window, &width, &height); SDL_GetWindowSize(window, &width, &height);
@@ -27,8 +46,7 @@ get_native_window_size(SDL_Window *window) {
} }
// get the windowed window size // get the windowed window size
static struct size static struct size get_window_size(const struct screen *screen) {
get_window_size(const struct screen *screen) {
if (screen->fullscreen) { if (screen->fullscreen) {
return screen->windowed_window_size; return screen->windowed_window_size;
} }
@@ -36,8 +54,7 @@ get_window_size(const struct screen *screen) {
} }
// set the window size to be applied when fullscreen is disabled // set the window size to be applied when fullscreen is disabled
static void static void set_window_size(struct screen *screen, struct size new_size) {
set_window_size(struct screen *screen, struct size new_size) {
// setting the window size during fullscreen is implementation defined, // setting the window size during fullscreen is implementation defined,
// so apply the resize only after fullscreen is disabled // so apply the resize only after fullscreen is disabled
if (screen->fullscreen) { if (screen->fullscreen) {
@@ -49,31 +66,28 @@ set_window_size(struct screen *screen, struct size new_size) {
} }
// get the preferred display bounds (i.e. the screen bounds with some margins) // get the preferred display bounds (i.e. the screen bounds with some margins)
static bool static SDL_bool get_preferred_display_bounds(struct size *bounds) {
get_preferred_display_bounds(struct size *bounds) {
SDL_Rect rect; SDL_Rect rect;
#ifdef SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS #if SDL_VERSION_ATLEAST(2, 0, 5)
# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayUsableBounds((i), (r)) # define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayUsableBounds((i), (r))
#else #else
# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayBounds((i), (r)) # define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayBounds((i), (r))
#endif #endif
if (GET_DISPLAY_BOUNDS(0, &rect)) { if (GET_DISPLAY_BOUNDS(0, &rect)) {
LOGW("Could not get display usable bounds: %s", SDL_GetError()); LOGW("Could not get display usable bounds: %s", SDL_GetError());
return false; return SDL_FALSE;
} }
bounds->width = MAX(0, rect.w - DISPLAY_MARGINS); bounds->width = MAX(0, rect.w - DISPLAY_MARGINS);
bounds->height = MAX(0, rect.h - DISPLAY_MARGINS); bounds->height = MAX(0, rect.h - DISPLAY_MARGINS);
return true; return SDL_TRUE;
} }
// return the optimal size of the window, with the following constraints: // return the optimal size of the window, with the following constraints:
// - it attempts to keep at least one dimension of the current_size (i.e. it // - it attempts to keep at least one dimension of the current_size (i.e. it crops the black borders)
// crops the black borders)
// - it keeps the aspect ratio // - it keeps the aspect ratio
// - it scales down to make it fit in the display_size // - it scales down to make it fit in the display_size
static struct size static struct size get_optimal_size(struct size current_size, struct size frame_size) {
get_optimal_size(struct size current_size, struct size frame_size) {
if (frame_size.width == 0 || frame_size.height == 0) { if (frame_size.width == 0 || frame_size.height == 0) {
// avoid division by 0 // avoid division by 0
return current_size; return current_size;
@@ -81,8 +95,8 @@ get_optimal_size(struct size current_size, struct size frame_size) {
struct size display_size; struct size display_size;
// 32 bits because we need to multiply two 16 bits values // 32 bits because we need to multiply two 16 bits values
uint32_t w; Uint32 w;
uint32_t h; Uint32 h;
if (!get_preferred_display_bounds(&display_size)) { if (!get_preferred_display_bounds(&display_size)) {
// cannot get display bounds, do not constraint the size // cannot get display bounds, do not constraint the size
@@ -93,13 +107,12 @@ get_optimal_size(struct size current_size, struct size frame_size) {
h = MIN(current_size.height, display_size.height); h = MIN(current_size.height, display_size.height);
} }
bool keep_width = frame_size.width * h > frame_size.height * w; SDL_bool keep_width = frame_size.width * h > frame_size.height * w;
if (keep_width) { if (keep_width) {
// remove black borders on top and bottom // remove black borders on top and bottom
h = frame_size.height * w / frame_size.width; h = frame_size.height * w / frame_size.width;
} else { } else {
// remove black borders on left and right (or none at all if it already // remove black borders on left and right (or none at all if it already fits)
// fits)
w = frame_size.width * h / frame_size.height; w = frame_size.width * h / frame_size.height;
} }
@@ -109,101 +122,78 @@ get_optimal_size(struct size current_size, struct size frame_size) {
} }
// same as get_optimal_size(), but read the current size from the window // same as get_optimal_size(), but read the current size from the window
static inline struct size static inline struct size get_optimal_window_size(const struct screen *screen, struct size frame_size) {
get_optimal_window_size(const struct screen *screen, struct size frame_size) {
struct size current_size = get_window_size(screen); struct size current_size = get_window_size(screen);
return get_optimal_size(current_size, frame_size); return get_optimal_size(current_size, frame_size);
} }
// initially, there is no current size, so use the frame size as current size // initially, there is no current size, so use the frame size as current size
static inline struct size static inline struct size get_initial_optimal_size(struct size frame_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);
} }
void void screen_init(struct screen *screen) {
screen_init(struct screen *screen) {
*screen = (struct screen) SCREEN_INITIALIZER; *screen = (struct screen) SCREEN_INITIALIZER;
} }
static inline SDL_Texture * static inline SDL_Texture *create_texture(SDL_Renderer *renderer, struct size frame_size) {
create_texture(SDL_Renderer *renderer, struct size frame_size) { return SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING,
return SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
SDL_TEXTUREACCESS_STREAMING,
frame_size.width, frame_size.height); frame_size.width, frame_size.height);
} }
bool SDL_bool screen_init_rendering(struct screen *screen, const char *device_name, struct size frame_size) {
screen_init_rendering(struct screen *screen, const char *device_name,
struct size frame_size, bool always_on_top) {
screen->frame_size = frame_size; screen->frame_size = frame_size;
struct size window_size = get_initial_optimal_size(frame_size); struct size window_size = get_initial_optimal_size(frame_size);
uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE; Uint32 window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE;
#ifdef HIDPI_SUPPORT #ifdef HIDPI_SUPPORT
window_flags |= SDL_WINDOW_ALLOW_HIGHDPI; window_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
#endif #endif
if (always_on_top) { screen->window = SDL_CreateWindow(device_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
#ifdef SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP window_size.width, window_size.height, window_flags);
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
#else
LOGW("The 'always on top' flag is not available "
"(compile with SDL >= 2.0.5 to enable it)");
#endif
}
screen->window = SDL_CreateWindow(device_name, SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
window_size.width, window_size.height,
window_flags);
if (!screen->window) { if (!screen->window) {
LOGC("Could not create window: %s", SDL_GetError()); LOGC("Could not create window: %s", SDL_GetError());
return false; return SDL_FALSE;
} }
screen->renderer = SDL_CreateRenderer(screen->window, -1, screen->renderer = SDL_CreateRenderer(screen->window, -1, SDL_RENDERER_ACCELERATED);
SDL_RENDERER_ACCELERATED);
if (!screen->renderer) { if (!screen->renderer) {
LOGC("Could not create renderer: %s", SDL_GetError()); LOGC("Could not create renderer: %s", SDL_GetError());
screen_destroy(screen); screen_destroy(screen);
return false; return SDL_FALSE;
} }
if (SDL_RenderSetLogicalSize(screen->renderer, frame_size.width, if (SDL_RenderSetLogicalSize(screen->renderer, frame_size.width, frame_size.height)) {
frame_size.height)) {
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 false; return SDL_FALSE;
} }
SDL_Surface *icon = read_xpm(icon_xpm); SDL_Surface *icon = read_xpm(icon_xpm);
if (!icon) { if (!icon) {
LOGE("Could not load icon: %s", SDL_GetError()); LOGE("Could not load icon: %s", SDL_GetError());
screen_destroy(screen); screen_destroy(screen);
return false; return SDL_FALSE;
} }
SDL_SetWindowIcon(screen->window, icon); SDL_SetWindowIcon(screen->window, icon);
SDL_FreeSurface(icon); SDL_FreeSurface(icon);
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width, LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width, frame_size.height);
frame_size.height);
screen->texture = create_texture(screen->renderer, frame_size); screen->texture = create_texture(screen->renderer, frame_size);
if (!screen->texture) { if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError()); LOGC("Could not create texture: %s", SDL_GetError());
screen_destroy(screen); screen_destroy(screen);
return false; return SDL_FALSE;
} }
return true; return SDL_TRUE;
} }
void void screen_show_window(struct screen *screen) {
screen_show_window(struct screen *screen) {
SDL_ShowWindow(screen->window); SDL_ShowWindow(screen->window);
} }
void void screen_destroy(struct screen *screen) {
screen_destroy(struct screen *screen) {
if (screen->texture) { if (screen->texture) {
SDL_DestroyTexture(screen->texture); SDL_DestroyTexture(screen->texture);
} }
@@ -216,14 +206,11 @@ 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 bool static SDL_bool prepare_for_frame(struct screen *screen, struct size new_frame_size) {
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 if (SDL_RenderSetLogicalSize(screen->renderer, new_frame_size.width, new_frame_size.height)) {
|| screen->frame_size.height != new_frame_size.height) {
if (SDL_RenderSetLogicalSize(screen->renderer, new_frame_size.width,
new_frame_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError()); LOGE("Could not set renderer logical size: %s", SDL_GetError());
return false; return SDL_FALSE;
} }
// frame dimension changed, destroy texture // frame dimension changed, destroy texture
@@ -231,67 +218,61 @@ prepare_for_frame(struct screen *screen, struct size new_frame_size) {
struct size current_size = get_window_size(screen); struct size current_size = get_window_size(screen);
struct size target_size = { struct size target_size = {
(uint32_t) current_size.width * new_frame_size.width (Uint32) current_size.width * new_frame_size.width / screen->frame_size.width,
/ screen->frame_size.width, (Uint32) current_size.height * new_frame_size.height / screen->frame_size.height,
(uint32_t) current_size.height * new_frame_size.height
/ screen->frame_size.height,
}; };
target_size = get_optimal_size(target_size, new_frame_size); target_size = get_optimal_size(target_size, new_frame_size);
set_window_size(screen, target_size); set_window_size(screen, target_size);
screen->frame_size = new_frame_size; screen->frame_size = new_frame_size;
LOGI("New texture: %" PRIu16 "x%" PRIu16, LOGD("New texture: %" PRIu16 "x%" PRIu16,
screen->frame_size.width, screen->frame_size.height); screen->frame_size.width, screen->frame_size.height);
screen->texture = create_texture(screen->renderer, new_frame_size); screen->texture = create_texture(screen->renderer, new_frame_size);
if (!screen->texture) { if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError()); LOGC("Could not create texture: %s", SDL_GetError());
return false; return SDL_FALSE;
} }
} }
return true; return SDL_TRUE;
} }
// write the frame into the texture // write the frame into the texture
static void static void update_texture(struct screen *screen, const AVFrame *frame) {
update_texture(struct screen *screen, const AVFrame *frame) {
SDL_UpdateYUVTexture(screen->texture, NULL, SDL_UpdateYUVTexture(screen->texture, NULL,
frame->data[0], frame->linesize[0], frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1], frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]); frame->data[2], frame->linesize[2]);
} }
bool SDL_bool screen_update_frame(struct screen *screen, struct frames *frames) {
screen_update_frame(struct screen *screen, struct video_buffer *vb) { mutex_lock(frames->mutex);
mutex_lock(vb->mutex); const AVFrame *frame = frames_consume_rendered_frame(frames);
const AVFrame *frame = video_buffer_consume_rendered_frame(vb);
struct size new_frame_size = {frame->width, frame->height}; struct size new_frame_size = {frame->width, frame->height};
if (!prepare_for_frame(screen, new_frame_size)) { if (!prepare_for_frame(screen, new_frame_size)) {
mutex_unlock(vb->mutex); mutex_unlock(frames->mutex);
return false; return SDL_FALSE;
} }
update_texture(screen, frame); update_texture(screen, frame);
mutex_unlock(vb->mutex); mutex_unlock(frames->mutex);
screen_render(screen); screen_render(screen);
return true; return SDL_TRUE;
} }
void void screen_render(struct screen *screen) {
screen_render(struct screen *screen) {
SDL_RenderClear(screen->renderer); SDL_RenderClear(screen->renderer);
SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL); SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL);
SDL_RenderPresent(screen->renderer); SDL_RenderPresent(screen->renderer);
} }
void void screen_switch_fullscreen(struct screen *screen) {
screen_switch_fullscreen(struct screen *screen) {
if (!screen->fullscreen) { if (!screen->fullscreen) {
// going to fullscreen, store the current windowed window size // going to fullscreen, store the current windowed window size
screen->windowed_window_size = get_native_window_size(screen->window); screen->windowed_window_size = get_native_window_size(screen->window);
} }
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP; Uint32 new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
if (SDL_SetWindowFullscreen(screen->window, new_mode)) { if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
LOGW("Could not switch fullscreen mode: %s", SDL_GetError()); LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
return; return;
@@ -300,30 +281,24 @@ screen_switch_fullscreen(struct screen *screen) {
screen->fullscreen = !screen->fullscreen; screen->fullscreen = !screen->fullscreen;
if (!screen->fullscreen) { if (!screen->fullscreen) {
// fullscreen disabled, restore expected windowed window size // fullscreen disabled, restore expected windowed window size
SDL_SetWindowSize(screen->window, screen->windowed_window_size.width, SDL_SetWindowSize(screen->window, screen->windowed_window_size.width, screen->windowed_window_size.height);
screen->windowed_window_size.height);
} }
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed"); LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
screen_render(screen); screen_render(screen);
} }
void void screen_resize_to_fit(struct screen *screen) {
screen_resize_to_fit(struct screen *screen) {
if (!screen->fullscreen) { if (!screen->fullscreen) {
struct size optimal_size = get_optimal_window_size(screen, struct size optimal_size = get_optimal_window_size(screen, screen->frame_size);
screen->frame_size); SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
SDL_SetWindowSize(screen->window, optimal_size.width,
optimal_size.height);
LOGD("Resized to optimal size"); LOGD("Resized to optimal size");
} }
} }
void void screen_resize_to_pixel_perfect(struct screen *screen) {
screen_resize_to_pixel_perfect(struct screen *screen) {
if (!screen->fullscreen) { if (!screen->fullscreen) {
SDL_SetWindowSize(screen->window, screen->frame_size.width, SDL_SetWindowSize(screen->window, screen->frame_size.width, screen->frame_size.height);
screen->frame_size.height);
LOGD("Resized to pixel-perfect"); LOGD("Resized to pixel-perfect");
} }
} }

View File

@@ -1,13 +1,11 @@
#ifndef SCREEN_H #ifndef SCREEN_H
#define SCREEN_H #define SCREEN_H
#include <stdbool.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include "common.h" #include "common.h"
#include "frames.h"
struct video_buffer;
struct screen { struct screen {
SDL_Window *window; SDL_Window *window;
@@ -16,9 +14,8 @@ struct screen {
struct size frame_size; struct size frame_size;
//used only in fullscreen mode to know the windowed window size //used only in fullscreen mode to know the windowed window size
struct size windowed_window_size; struct size windowed_window_size;
bool has_frame; SDL_bool has_frame;
bool fullscreen; SDL_bool fullscreen;
bool no_window;
}; };
#define SCREEN_INITIALIZER { \ #define SCREEN_INITIALIZER { \
@@ -33,46 +30,40 @@ struct screen {
.width = 0, \ .width = 0, \
.height = 0, \ .height = 0, \
}, \ }, \
.has_frame = false, \ .has_frame = SDL_FALSE, \
.fullscreen = false, \ .fullscreen = SDL_FALSE, \
.no_window = false, \
} }
// init SDL and set appropriate hints
SDL_bool sdl_init_and_configure(void);
// initialize default values // initialize default values
void void screen_init(struct screen *screen);
screen_init(struct screen *screen);
// initialize screen, create window, renderer and texture (window is hidden) // initialize screen, create window, renderer and texture (window is hidden)
bool SDL_bool screen_init_rendering(struct screen *screen,
screen_init_rendering(struct screen *screen, const char *device_name, const char *device_name,
struct size frame_size, bool always_on_top); struct size frame_size);
// show the window // show the window
void void screen_show_window(struct screen *screen);
screen_show_window(struct screen *screen);
// destroy window, renderer and texture (if any) // destroy window, renderer and texture (if any)
void void screen_destroy(struct screen *screen);
screen_destroy(struct screen *screen);
// resize if necessary and write the rendered frame into the texture // resize if necessary and write the rendered frame into the texture
bool SDL_bool screen_update_frame(struct screen *screen, struct frames *frames);
screen_update_frame(struct screen *screen, struct video_buffer *vb);
// render the texture to the renderer // render the texture to the renderer
void void screen_render(struct screen *screen);
screen_render(struct screen *screen);
// switch the fullscreen mode // switch the fullscreen mode
void void screen_switch_fullscreen(struct screen *screen);
screen_switch_fullscreen(struct screen *screen);
// resize window to optimal size (remove black borders) // resize window to optimal size (remove black borders)
void void screen_resize_to_fit(struct screen *screen);
screen_resize_to_fit(struct screen *screen);
// resize window to 1:1 (pixel-perfect) // resize window to 1:1 (pixel-perfect)
void void screen_resize_to_pixel_perfect(struct screen *screen);
screen_resize_to_pixel_perfect(struct screen *screen);
#endif #endif

View File

@@ -2,6 +2,7 @@
#include <errno.h> #include <errno.h>
#include <inttypes.h> #include <inttypes.h>
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <SDL2/SDL_assert.h> #include <SDL2/SDL_assert.h>
#include <SDL2/SDL_timer.h> #include <SDL2/SDL_timer.h>
@@ -20,8 +21,7 @@
#define DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar" #define DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
static const char * static const char *get_server_path(void) {
get_server_path(void) {
const char *server_path = getenv("SCRCPY_SERVER_PATH"); const char *server_path = getenv("SCRCPY_SERVER_PATH");
if (!server_path) { if (!server_path) {
server_path = DEFAULT_SERVER_PATH; server_path = DEFAULT_SERVER_PATH;
@@ -29,60 +29,58 @@ get_server_path(void) {
return server_path; return server_path;
} }
static bool static SDL_bool push_server(const char *serial) {
push_server(const char *serial) {
process_t process = adb_push(serial, get_server_path(), DEVICE_SERVER_PATH); process_t process = adb_push(serial, get_server_path(), DEVICE_SERVER_PATH);
return process_check_success(process, "adb push"); return process_check_success(process, "adb push");
} }
static bool static SDL_bool remove_server(const char *serial) {
enable_tunnel_reverse(const char *serial, uint16_t local_port) { process_t process = adb_remove_path(serial, DEVICE_SERVER_PATH);
return process_check_success(process, "adb shell rm");
}
static SDL_bool enable_tunnel_reverse(const char *serial, Uint16 local_port) {
process_t process = adb_reverse(serial, SOCKET_NAME, local_port); process_t process = adb_reverse(serial, SOCKET_NAME, local_port);
return process_check_success(process, "adb reverse"); return process_check_success(process, "adb reverse");
} }
static bool static SDL_bool disable_tunnel_reverse(const char *serial) {
disable_tunnel_reverse(const char *serial) {
process_t process = adb_reverse_remove(serial, SOCKET_NAME); process_t process = adb_reverse_remove(serial, SOCKET_NAME);
return process_check_success(process, "adb reverse --remove"); return process_check_success(process, "adb reverse --remove");
} }
static bool static SDL_bool enable_tunnel_forward(const char *serial, Uint16 local_port) {
enable_tunnel_forward(const char *serial, uint16_t local_port) {
process_t process = adb_forward(serial, local_port, SOCKET_NAME); process_t process = adb_forward(serial, local_port, SOCKET_NAME);
return process_check_success(process, "adb forward"); return process_check_success(process, "adb forward");
} }
static bool static SDL_bool disable_tunnel_forward(const char *serial, Uint16 local_port) {
disable_tunnel_forward(const char *serial, uint16_t local_port) {
process_t process = adb_forward_remove(serial, local_port); process_t process = adb_forward_remove(serial, local_port);
return process_check_success(process, "adb forward --remove"); return process_check_success(process, "adb forward --remove");
} }
static bool static SDL_bool enable_tunnel(struct server *server) {
enable_tunnel(struct server *server) {
if (enable_tunnel_reverse(server->serial, server->local_port)) { if (enable_tunnel_reverse(server->serial, server->local_port)) {
return true; return SDL_TRUE;
} }
LOGW("'adb reverse' failed, fallback to 'adb forward'"); LOGW("'adb reverse' failed, fallback to 'adb forward'");
server->tunnel_forward = true; server->tunnel_forward = SDL_TRUE;
return enable_tunnel_forward(server->serial, server->local_port); return enable_tunnel_forward(server->serial, server->local_port);
} }
static bool static SDL_bool disable_tunnel(struct server *server) {
disable_tunnel(struct server *server) {
if (server->tunnel_forward) { if (server->tunnel_forward) {
return disable_tunnel_forward(server->serial, server->local_port); return disable_tunnel_forward(server->serial, server->local_port);
} }
return disable_tunnel_reverse(server->serial); return disable_tunnel_reverse(server->serial);
} }
static process_t static process_t execute_server(const char *serial,
execute_server(const char *serial, Uint16 max_size, Uint32 bit_rate,
uint16_t max_size, uint32_t bit_rate, SDL_bool tunnel_forward, const char *crop,
bool tunnel_forward, const char *crop, SDL_bool send_frame_meta) {
bool send_frame_meta) { LOGD("tunnel_forward=%d\n", (int) 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);
@@ -96,7 +94,7 @@ 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 : "-", crop ? crop : "''",
send_frame_meta ? "true" : "false", send_frame_meta ? "true" : "false",
}; };
return adb_execute(serial, cmd, sizeof(cmd) / sizeof(cmd[0])); return adb_execute(serial, cmd, sizeof(cmd) / sizeof(cmd[0]));
@@ -104,13 +102,11 @@ execute_server(const char *serial,
#define IPV4_LOCALHOST 0x7F000001 #define IPV4_LOCALHOST 0x7F000001
static socket_t static socket_t listen_on_port(Uint16 port) {
listen_on_port(uint16_t port) {
return net_listen(IPV4_LOCALHOST, port, 1); return net_listen(IPV4_LOCALHOST, port, 1);
} }
static socket_t static socket_t connect_and_read_byte(Uint16 port) {
connect_and_read_byte(uint16_t port) {
socket_t socket = net_connect(IPV4_LOCALHOST, port); socket_t socket = net_connect(IPV4_LOCALHOST, port);
if (socket == INVALID_SOCKET) { if (socket == INVALID_SOCKET) {
return INVALID_SOCKET; return INVALID_SOCKET;
@@ -126,8 +122,7 @@ connect_and_read_byte(uint16_t port) {
return socket; return socket;
} }
static socket_t static socket_t connect_to_server(Uint16 port, Uint32 attempts, Uint32 delay) {
connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
do { do {
LOGD("Remaining connection attempts: %d", (int) attempts); LOGD("Remaining connection attempts: %d", (int) attempts);
socket_t socket = connect_and_read_byte(port); socket_t socket = connect_and_read_byte(port);
@@ -142,8 +137,7 @@ connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
return INVALID_SOCKET; return INVALID_SOCKET;
} }
static void static void close_socket(socket_t *socket) {
close_socket(socket_t *socket) {
SDL_assert(*socket != INVALID_SOCKET); SDL_assert(*socket != INVALID_SOCKET);
net_shutdown(*socket, SHUT_RDWR); net_shutdown(*socket, SHUT_RDWR);
if (!net_close(*socket)) { if (!net_close(*socket)) {
@@ -153,32 +147,32 @@ close_socket(socket_t *socket) {
*socket = INVALID_SOCKET; *socket = INVALID_SOCKET;
} }
void void server_init(struct server *server) {
server_init(struct server *server) {
*server = (struct server) SERVER_INITIALIZER; *server = (struct server) SERVER_INITIALIZER;
} }
bool SDL_bool server_start(struct server *server, const char *serial,
server_start(struct server *server, const char *serial, Uint16 local_port, Uint16 max_size, Uint32 bit_rate,
uint16_t local_port, uint16_t max_size, uint32_t bit_rate, const char *crop, SDL_bool send_frame_meta) {
const char *crop, bool send_frame_meta) {
server->local_port = local_port; server->local_port = local_port;
if (serial) { if (serial) {
server->serial = SDL_strdup(serial); server->serial = SDL_strdup(serial);
if (!server->serial) { if (!server->serial) {
return false; return SDL_FALSE;
} }
} }
if (!push_server(serial)) { if (!push_server(serial)) {
SDL_free(server->serial); SDL_free((void *) server->serial);
return false; return SDL_FALSE;
} }
server->server_copied_to_device = SDL_TRUE;
if (!enable_tunnel(server)) { if (!enable_tunnel(server)) {
SDL_free(server->serial); SDL_free((void *) server->serial);
return false; return SDL_FALSE;
} }
// if "adb reverse" does not work (e.g. over "adb connect"), it fallbacks to // if "adb reverse" does not work (e.g. over "adb connect"), it fallbacks to
@@ -187,16 +181,15 @@ server_start(struct server *server, const char *serial,
// At the application level, the device part is "the server" because it // At the application level, the device part is "the server" because it
// serves video stream and control. However, at the network level, the // serves video stream and control. However, at the network level, the
// client listens and the server connects to the client. That way, the // client listens and the server connects to the client. That way, the
// client can listen before starting the server app, so there is no // client can listen before starting the server app, so there is no need to
// need to try to connect until the server socket is listening on the // try to connect until the server socket is listening on the device.
// device.
server->server_socket = listen_on_port(local_port); server->server_socket = listen_on_port(local_port);
if (server->server_socket == INVALID_SOCKET) { if (server->server_socket == INVALID_SOCKET) {
LOGE("Could not listen on port %" PRIu16, local_port); LOGE("Could not listen on port %" PRIu16, local_port);
disable_tunnel(server); disable_tunnel(server);
SDL_free(server->serial); SDL_free((void *) server->serial);
return false; return SDL_FALSE;
} }
} }
@@ -211,23 +204,21 @@ server_start(struct server *server, const char *serial,
} }
disable_tunnel(server); disable_tunnel(server);
SDL_free((void *) server->serial); SDL_free((void *) server->serial);
return false; return SDL_FALSE;
} }
server->tunnel_enabled = true; server->tunnel_enabled = SDL_TRUE;
return true; return SDL_TRUE;
} }
socket_t socket_t server_connect_to(struct server *server) {
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_t attempts = 100; Uint32 attempts = 100;
uint32_t delay = 100; // ms Uint32 delay = 100; // ms
server->device_socket = connect_to_server(server->local_port, attempts, server->device_socket = connect_to_server(server->local_port, attempts, delay);
delay);
} }
if (server->device_socket == INVALID_SOCKET) { if (server->device_socket == INVALID_SOCKET) {
@@ -239,15 +230,18 @@ server_connect_to(struct server *server) {
close_socket(&server->server_socket); close_socket(&server->server_socket);
} }
// the server is started, we can clean up the jar from the temporary folder
remove_server(server->serial); // ignore failure
server->server_copied_to_device = SDL_FALSE;
// we don't need the adb tunnel anymore // we don't need the adb tunnel anymore
disable_tunnel(server); // ignore failure disable_tunnel(server); // ignore failure
server->tunnel_enabled = false; server->tunnel_enabled = SDL_FALSE;
return server->device_socket; return server->device_socket;
} }
void void server_stop(struct server *server) {
server_stop(struct server *server) {
SDL_assert(server->process != PROCESS_NONE); SDL_assert(server->process != PROCESS_NONE);
if (!cmd_terminate(server->process)) { if (!cmd_terminate(server->process)) {
@@ -261,15 +255,18 @@ server_stop(struct server *server) {
// ignore failure // ignore failure
disable_tunnel(server); disable_tunnel(server);
} }
if (server->server_copied_to_device) {
remove_server(server->serial); // ignore failure
}
} }
void void server_destroy(struct server *server) {
server_destroy(struct server *server) {
if (server->server_socket != INVALID_SOCKET) { if (server->server_socket != INVALID_SOCKET) {
close_socket(&server->server_socket); close_socket(&server->server_socket);
} }
if (server->device_socket != INVALID_SOCKET) { if (server->device_socket != INVALID_SOCKET) {
close_socket(&server->device_socket); close_socket(&server->device_socket);
} }
SDL_free(server->serial); SDL_free((void *) server->serial);
} }

View File

@@ -1,21 +1,19 @@
#ifndef SERVER_H #ifndef SERVER_H
#define SERVER_H #define SERVER_H
#include <stdbool.h>
#include <stdint.h>
#include "command.h" #include "command.h"
#include "net.h" #include "net.h"
struct server { struct server {
char *serial; const char *serial;
process_t process; process_t process;
socket_t server_socket; // only used if !tunnel_forward socket_t server_socket; // only used if !tunnel_forward
socket_t device_socket; socket_t device_socket;
uint16_t local_port; Uint16 local_port;
bool tunnel_enabled; SDL_bool tunnel_enabled;
bool tunnel_forward; // use "adb forward" instead of "adb reverse" SDL_bool tunnel_forward; // use "adb forward" instead of "adb reverse"
bool send_frame_meta; // request frame PTS to be able to record properly SDL_bool send_frame_meta; // request frame PTS to be able to record properly
SDL_bool server_copied_to_device;
}; };
#define SERVER_INITIALIZER { \ #define SERVER_INITIALIZER { \
@@ -24,31 +22,27 @@ struct server {
.server_socket = INVALID_SOCKET, \ .server_socket = INVALID_SOCKET, \
.device_socket = INVALID_SOCKET, \ .device_socket = INVALID_SOCKET, \
.local_port = 0, \ .local_port = 0, \
.tunnel_enabled = false, \ .tunnel_enabled = SDL_FALSE, \
.tunnel_forward = false, \ .tunnel_forward = SDL_FALSE, \
.send_frame_meta = false, \ .send_frame_meta = SDL_FALSE, \
.server_copied_to_device = SDL_FALSE, \
} }
// init default values // init default values
void void server_init(struct server *server);
server_init(struct server *server);
// push, enable tunnel et start the server // push, enable tunnel et start the server
bool SDL_bool server_start(struct server *server, const char *serial,
server_start(struct server *server, const char *serial, Uint16 local_port, Uint16 max_size, Uint32 bit_rate,
uint16_t local_port, uint16_t max_size, uint32_t bit_rate, const char *crop, SDL_bool send_frame_meta);
const char *crop, bool send_frame_meta);
// block until the communication with the server is established // block until the communication with the server is established
socket_t socket_t server_connect_to(struct server *server);
server_connect_to(struct server *server);
// disconnect and kill the server process // disconnect and kill the server process
void void server_stop(struct server *server);
server_stop(struct server *server);
// close and release sockets // close and release sockets
void void server_destroy(struct server *server);
server_destroy(struct server *server);
#endif #endif

View File

@@ -3,13 +3,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#ifdef _WIN32 size_t xstrncpy(char *dest, const char *src, size_t n) {
# include <windows.h>
# include <tchar.h>
#endif
size_t
xstrncpy(char *dest, const char *src, size_t n) {
size_t i; size_t i;
for (i = 0; i < n - 1 && src[i] != '\0'; ++i) for (i = 0; i < n - 1 && src[i] != '\0'; ++i)
dest[i] = src[i]; dest[i] = src[i];
@@ -18,8 +12,7 @@ xstrncpy(char *dest, const char *src, size_t n) {
return src[i] == '\0' ? i : n; return src[i] == '\0' ? i : n;
} }
size_t size_t xstrjoin(char *dst, const char *const tokens[], char sep, size_t n) {
xstrjoin(char *dst, const char *const tokens[], char sep, size_t n) {
const char *const *remaining = tokens; const char *const *remaining = tokens;
const char *token = *remaining++; const char *token = *remaining++;
size_t i = 0; size_t i = 0;
@@ -42,8 +35,7 @@ truncated:
return n; return n;
} }
char * char *strquote(const char *src) {
strquote(const char *src) {
size_t len = strlen(src); size_t len = strlen(src);
char *quoted = malloc(len + 3); char *quoted = malloc(len + 3);
if (!quoted) { if (!quoted) {
@@ -55,23 +47,3 @@ strquote(const char *src) {
quoted[len + 2] = '\0'; quoted[len + 2] = '\0';
return quoted; return quoted;
} }
#ifdef _WIN32
wchar_t *
utf8_to_wide_char(const char *utf8) {
int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
if (!len) {
return NULL;
}
wchar_t *wide = malloc(len * sizeof(wchar_t));
if (!wide) {
return NULL;
}
MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wide, len);
return wide;
}
#endif

View File

@@ -9,25 +9,15 @@
// - it does not write useless bytes if strlen(src) < n // - it does not write useless bytes if strlen(src) < n
// - it returns the number of chars actually written (max n-1) if src has // - it returns the number of chars actually written (max n-1) if src has
// been copied completely, or n if src has been truncated // been copied completely, or n if src has been truncated
size_t size_t xstrncpy(char *dest, const char *src, size_t n);
xstrncpy(char *dest, const char *src, size_t n);
// join tokens by sep into dst // join tokens by sep into dst
// returns the number of chars actually written (max n-1) if no trucation // returns the number of chars actually written (max n-1) if no trucation
// occurred, or n if truncated // occurred, or n if truncated
size_t size_t xstrjoin(char *dst, const char *const tokens[], char sep, size_t n);
xstrjoin(char *dst, const char *const tokens[], char sep, size_t n);
// quote a string // quote a string
// returns the new allocated string, to be freed by the caller // returns the new allocated string, to be freed by the caller
char * char *strquote(const char *src);
strquote(const char *src);
#ifdef _WIN32
// convert a UTF-8 string to a wchar_t string
// returns the new allocated string, to be freed by the caller
wchar_t *
utf8_to_wide_char(const char *utf8);
#endif
#endif #endif

View File

@@ -1,286 +0,0 @@
#include "stream.h"
#include <libavformat/avformat.h>
#include <libavutil/time.h>
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include <unistd.h>
#include "compat.h"
#include "config.h"
#include "buffer_util.h"
#include "decoder.h"
#include "events.h"
#include "lock_util.h"
#include "log.h"
#include "recorder.h"
#define BUFSIZE 0x10000
#define HEADER_SIZE 12
#define NO_PTS UINT64_C(-1)
static struct frame_meta *
frame_meta_new(uint64_t pts) {
struct frame_meta *meta = malloc(sizeof(*meta));
if (!meta) {
return meta;
}
meta->pts = pts;
meta->next = NULL;
return meta;
}
static void
frame_meta_delete(struct frame_meta *frame_meta) {
free(frame_meta);
}
static bool
receiver_state_push_meta(struct receiver_state *state, uint64_t pts) {
struct frame_meta *frame_meta = frame_meta_new(pts);
if (!frame_meta) {
return false;
}
// append to the list
// (iterate to find the last item, in practice the list should be tiny)
struct frame_meta **p = &state->frame_meta_queue;
while (*p) {
p = &(*p)->next;
}
*p = frame_meta;
return true;
}
static uint64_t
receiver_state_take_meta(struct receiver_state *state) {
struct frame_meta *frame_meta = state->frame_meta_queue; // first item
SDL_assert(frame_meta); // must not be empty
uint64_t pts = frame_meta->pts;
state->frame_meta_queue = frame_meta->next; // remove the item
frame_meta_delete(frame_meta);
return pts;
}
static int
read_packet_with_meta(void *opaque, uint8_t *buf, int buf_size) {
struct stream *stream = opaque;
struct receiver_state *state = &stream->receiver_state;
// The video stream contains raw packets, without time information. When we
// record, we retrieve the timestamps separately, from a "meta" header
// added by the server before each raw packet.
//
// The "meta" header length is 12 bytes:
// [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ...
// <-------------> <-----> <-----------------------------...
// PTS packet raw packet
// size
//
// It is followed by <packet_size> bytes containing the packet/frame.
if (!state->remaining) {
#define HEADER_SIZE 12
uint8_t header[HEADER_SIZE];
ssize_t r = net_recv_all(stream->socket, header, HEADER_SIZE);
if (r == -1) {
return AVERROR(errno);
}
if (r == 0) {
return AVERROR_EOF;
}
// no partial read (net_recv_all())
SDL_assert_release(r == HEADER_SIZE);
uint64_t pts = buffer_read64be(header);
state->remaining = buffer_read32be(&header[8]);
if (pts != NO_PTS && !receiver_state_push_meta(state, pts)) {
LOGE("Could not store PTS for recording");
// we cannot save the PTS, the recording would be broken
return AVERROR(ENOMEM);
}
}
SDL_assert(state->remaining);
if (buf_size > state->remaining) {
buf_size = state->remaining;
}
ssize_t r = net_recv(stream->socket, buf, buf_size);
if (r == -1) {
return AVERROR(errno);
}
if (r == 0) {
return AVERROR_EOF;
}
SDL_assert(state->remaining >= r);
state->remaining -= r;
return r;
}
static int
read_raw_packet(void *opaque, uint8_t *buf, int buf_size) {
struct stream *stream = opaque;
ssize_t r = net_recv(stream->socket, buf, buf_size);
if (r == -1) {
return AVERROR(errno);
}
if (r == 0) {
return AVERROR_EOF;
}
return r;
}
static void
notify_stopped(void) {
SDL_Event stop_event;
stop_event.type = EVENT_STREAM_STOPPED;
SDL_PushEvent(&stop_event);
}
static int
run_stream(void *data) {
struct stream *stream = data;
AVFormatContext *format_ctx = avformat_alloc_context();
if (!format_ctx) {
LOGC("Could not allocate format context");
goto end;
}
unsigned char *buffer = av_malloc(BUFSIZE);
if (!buffer) {
LOGC("Could not allocate buffer");
goto finally_free_format_ctx;
}
// initialize the receiver state
stream->receiver_state.frame_meta_queue = NULL;
stream->receiver_state.remaining = 0;
// if recording is enabled, a "header" is sent between raw packets
int (*read_packet)(void *, uint8_t *, int) =
stream->recorder ? read_packet_with_meta : read_raw_packet;
AVIOContext *avio_ctx = avio_alloc_context(buffer, BUFSIZE, 0, stream,
read_packet, NULL, NULL);
if (!avio_ctx) {
LOGC("Could not allocate avio context");
// avformat_open_input takes ownership of 'buffer'
// so only free the buffer before avformat_open_input()
av_free(buffer);
goto finally_free_format_ctx;
}
format_ctx->pb = avio_ctx;
if (avformat_open_input(&format_ctx, NULL, NULL, NULL) < 0) {
LOGE("Could not open video stream");
goto finally_free_avio_ctx;
}
AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!codec) {
LOGE("H.264 decoder not found");
goto end;
}
if (stream->decoder && !decoder_open(stream->decoder, codec)) {
LOGE("Could not open decoder");
goto finally_close_input;
}
if (stream->recorder && !recorder_open(stream->recorder, codec)) {
LOGE("Could not open recorder");
goto finally_close_input;
}
AVPacket packet;
av_init_packet(&packet);
packet.data = NULL;
packet.size = 0;
while (!av_read_frame(format_ctx, &packet)) {
if (stream->decoder && !decoder_push(stream->decoder, &packet)) {
av_packet_unref(&packet);
goto quit;
}
if (stream->recorder) {
// we retrieve the PTS in order they were received, so they will
// be assigned to the correct frame
uint64_t pts = receiver_state_take_meta(&stream->receiver_state);
packet.pts = pts;
packet.dts = pts;
// no need to rescale with av_packet_rescale_ts(), the timestamps
// are in microseconds both in input and output
if (!recorder_write(stream->recorder, &packet)) {
LOGE("Could not write frame to output file");
av_packet_unref(&packet);
goto quit;
}
}
av_packet_unref(&packet);
if (avio_ctx->eof_reached) {
break;
}
}
LOGD("End of frames");
quit:
if (stream->recorder) {
recorder_close(stream->recorder);
}
finally_close_input:
avformat_close_input(&format_ctx);
finally_free_avio_ctx:
av_free(avio_ctx->buffer);
av_free(avio_ctx);
finally_free_format_ctx:
avformat_free_context(format_ctx);
end:
notify_stopped();
return 0;
}
void
stream_init(struct stream *stream, socket_t socket,
struct decoder *decoder, struct recorder *recorder) {
stream->socket = socket;
stream->decoder = decoder,
stream->recorder = recorder;
}
bool
stream_start(struct stream *stream) {
LOGD("Starting stream thread");
stream->thread = SDL_CreateThread(run_stream, "stream", stream);
if (!stream->thread) {
LOGC("Could not start stream thread");
return false;
}
return true;
}
void
stream_stop(struct stream *stream) {
if (stream->decoder) {
decoder_interrupt(stream->decoder);
}
}
void
stream_join(struct stream *stream) {
SDL_WaitThread(stream->thread, NULL);
}

View File

@@ -1,43 +0,0 @@
#ifndef STREAM_H
#define STREAM_H
#include <stdbool.h>
#include <stdint.h>
#include <SDL2/SDL_thread.h>
#include "net.h"
struct video_buffer;
struct frame_meta {
uint64_t pts;
struct frame_meta *next;
};
struct stream {
socket_t socket;
struct video_buffer *video_buffer;
SDL_Thread *thread;
struct decoder *decoder;
struct recorder *recorder;
struct receiver_state {
// meta (in order) for frames not consumed yet
struct frame_meta *frame_meta_queue;
size_t remaining; // remaining bytes to receive for the current frame
} receiver_state;
};
void
stream_init(struct stream *stream, socket_t socket,
struct decoder *decoder, struct recorder *recorder);
bool
stream_start(struct stream *stream);
void
stream_stop(struct stream *stream);
void
stream_join(struct stream *stream);
#endif

View File

@@ -11,8 +11,7 @@
#include <unistd.h> #include <unistd.h>
#include "log.h" #include "log.h"
enum process_result enum process_result cmd_execute(const char *path, const char *const argv[], pid_t *pid) {
cmd_execute(const char *path, const char *const argv[], pid_t *pid) {
int fd[2]; int fd[2];
if (pipe(fd) == -1) { if (pipe(fd) == -1) {
@@ -73,18 +72,15 @@ end:
return ret; return ret;
} }
bool SDL_bool cmd_terminate(pid_t pid) {
cmd_terminate(pid_t pid) {
if (pid <= 0) { if (pid <= 0) {
LOGC("Requested to kill %d, this is an error. Please report the bug.\n", LOGC("Requested to kill %d, this is an error. Please report the bug.\n", (int) pid);
(int) pid);
abort(); abort();
} }
return kill(pid, SIGTERM) != -1; return kill(pid, SIGTERM) != -1;
} }
bool SDL_bool cmd_simple_wait(pid_t pid, int *exit_code) {
cmd_simple_wait(pid_t pid, int *exit_code) {
int status; int status;
int code; int code;
if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status)) { if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status)) {

View File

@@ -1,19 +1,16 @@
#include "net.h" #include "net.h"
#include <unistd.h> # include <unistd.h>
bool SDL_bool net_init(void) {
net_init(void) {
// do nothing // do nothing
return true; return SDL_TRUE;
} }
void void net_cleanup(void) {
net_cleanup(void) {
// do nothing // do nothing
} }
bool SDL_bool net_close(socket_t socket) {
net_close(socket_t socket) {
return !close(socket); return !close(socket);
} }

View File

@@ -4,8 +4,7 @@
#include "log.h" #include "log.h"
#include "str_util.h" #include "str_util.h"
static int static int build_cmd(char *cmd, size_t len, const char *const argv[]) {
build_cmd(char *cmd, size_t len, const char *const argv[]) {
// Windows command-line parsing is WTF: // Windows command-line parsing is WTF:
// <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS> // <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS>
// only make it work for this very specific program // only make it work for this very specific program
@@ -18,9 +17,8 @@ build_cmd(char *cmd, size_t len, const char *const argv[]) {
return 0; return 0;
} }
enum process_result enum process_result cmd_execute(const char *path, const char *const argv[], HANDLE *handle) {
cmd_execute(const char *path, const char *const argv[], HANDLE *handle) { STARTUPINFO si;
STARTUPINFOW si;
PROCESS_INFORMATION pi; PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si)); memset(&si, 0, sizeof(si));
si.cb = sizeof(si); si.cb = sizeof(si);
@@ -31,20 +29,12 @@ cmd_execute(const char *path, const char *const argv[], HANDLE *handle) {
return PROCESS_ERROR_GENERIC; return PROCESS_ERROR_GENERIC;
} }
wchar_t *wide = utf8_to_wide_char(cmd);
if (!wide) {
LOGC("Cannot allocate wide char string");
return PROCESS_ERROR_GENERIC;
}
#ifdef WINDOWS_NOCONSOLE #ifdef WINDOWS_NOCONSOLE
int flags = CREATE_NO_WINDOW; int flags = CREATE_NO_WINDOW;
#else #else
int flags = 0; int flags = 0;
#endif #endif
if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, flags, NULL, NULL, &si, if (!CreateProcess(NULL, cmd, NULL, NULL, FALSE, flags, NULL, NULL, &si, &pi)) {
&pi)) {
free(wide);
*handle = NULL; *handle = NULL;
if (GetLastError() == ERROR_FILE_NOT_FOUND) { if (GetLastError() == ERROR_FILE_NOT_FOUND) {
return PROCESS_ERROR_MISSING_BINARY; return PROCESS_ERROR_MISSING_BINARY;
@@ -52,21 +42,17 @@ cmd_execute(const char *path, const char *const argv[], HANDLE *handle) {
return PROCESS_ERROR_GENERIC; return PROCESS_ERROR_GENERIC;
} }
free(wide);
*handle = pi.hProcess; *handle = pi.hProcess;
return PROCESS_SUCCESS; return PROCESS_SUCCESS;
} }
bool SDL_bool cmd_terminate(HANDLE handle) {
cmd_terminate(HANDLE handle) {
return TerminateProcess(handle, 1) && CloseHandle(handle); return TerminateProcess(handle, 1) && CloseHandle(handle);
} }
bool SDL_bool cmd_simple_wait(HANDLE handle, DWORD *exit_code) {
cmd_simple_wait(HANDLE handle, DWORD *exit_code) {
DWORD code; DWORD code;
if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0 if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0 || !GetExitCodeProcess(handle, &code)) {
|| !GetExitCodeProcess(handle, &code)) {
// cannot wait or retrieve the exit code // cannot wait or retrieve the exit code
code = -1; // max value, it's unsigned code = -1; // max value, it's unsigned
} }

View File

@@ -2,23 +2,20 @@
#include "log.h" #include "log.h"
bool SDL_bool net_init(void) {
net_init(void) {
WSADATA wsa; WSADATA wsa;
int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0; int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0;
if (res < 0) { if (res < 0) {
LOGC("WSAStartup failed with error %d", res); LOGC("WSAStartup failed with error %d", res);
return false; return SDL_FALSE;
} }
return true; return SDL_TRUE;
} }
void void net_cleanup(void) {
net_cleanup(void) {
WSACleanup(); WSACleanup();
} }
bool SDL_bool net_close(socket_t socket) {
net_close(socket_t socket) {
return !closesocket(socket); return !closesocket(socket);
} }

View File

@@ -1,7 +1,5 @@
#include "tiny_xpm.h" #include "tiny_xpm.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -9,20 +7,19 @@
struct index { struct index {
char c; char c;
uint32_t color; Uint32 color;
}; };
static bool static SDL_bool find_color(struct index *index, int len, char c, Uint32 *color) {
find_color(struct index *index, int len, char c, uint32_t *color) {
// there are typically very few color, so it's ok to iterate over the array // there are typically very few color, so it's ok to iterate over the array
for (int i = 0; i < len; ++i) { for (int i = 0; i < len; ++i) {
if (index[i].c == c) { if (index[i].c == c) {
*color = index[i].color; *color = index[i].color;
return true; return SDL_TRUE;
} }
} }
*color = 0; *color = 0;
return false; return SDL_FALSE;
} }
// We encounter some problems with SDL2_image on MSYS2 (Windows), // We encounter some problems with SDL2_image on MSYS2 (Windows),
@@ -33,8 +30,7 @@ find_color(struct index *index, int len, char c, uint32_t *color) {
// //
// Parameter is not "const char *" because XPM formats are generally stored in a // Parameter is not "const char *" because XPM formats are generally stored in a
// (non-const) "char *" // (non-const) "char *"
SDL_Surface * SDL_Surface *read_xpm(char *xpm[]) {
read_xpm(char *xpm[]) {
#if SDL_ASSERT_LEVEL >= 2 #if SDL_ASSERT_LEVEL >= 2
// patch the XPM to change the icon color in debug mode // patch the XPM to change the icon color in debug mode
xpm[2] = ". c #CC00CC"; xpm[2] = ". c #CC00CC";
@@ -73,7 +69,7 @@ read_xpm(char *xpm[]) {
} }
// parse image // parse image
uint32_t *pixels = SDL_malloc(4 * width * height); Uint32 *pixels = SDL_malloc(4 * width * height);
if (!pixels) { if (!pixels) {
LOGE("Could not allocate icon memory"); LOGE("Could not allocate icon memory");
return NULL; return NULL;
@@ -82,23 +78,23 @@ read_xpm(char *xpm[]) {
const char *line = xpm[1 + colors + y]; const char *line = xpm[1 + colors + y];
for (int x = 0; x < width; ++x) { for (int x = 0; x < width; ++x) {
char c = line[x]; char c = line[x];
uint32_t color; Uint32 color;
bool color_found = find_color(index, colors, c, &color); SDL_bool color_found = find_color(index, colors, c, &color);
SDL_assert(color_found); SDL_assert(color_found);
pixels[y * width + x] = color; pixels[y * width + x] = color;
} }
} }
#if SDL_BYTEORDER == SDL_BIG_ENDIAN #if SDL_BYTEORDER == SDL_BIG_ENDIAN
uint32_t amask = 0x000000ff; Uint32 amask = 0x000000ff;
uint32_t rmask = 0x0000ff00; Uint32 rmask = 0x0000ff00;
uint32_t gmask = 0x00ff0000; Uint32 gmask = 0x00ff0000;
uint32_t bmask = 0xff000000; Uint32 bmask = 0xff000000;
#else // little endian, like x86 #else // little endian, like x86
uint32_t amask = 0xff000000; Uint32 amask = 0xff000000;
uint32_t rmask = 0x00ff0000; Uint32 rmask = 0x00ff0000;
uint32_t gmask = 0x0000ff00; Uint32 gmask = 0x0000ff00;
uint32_t bmask = 0x000000ff; Uint32 bmask = 0x000000ff;
#endif #endif
SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(pixels, SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(pixels,

View File

@@ -3,7 +3,6 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
SDL_Surface * SDL_Surface *read_xpm(char *xpm[]);
read_xpm(char *xpm[]);
#endif #endif

View File

@@ -1,116 +0,0 @@
#include "video_buffer.h"
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_mutex.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>
#include "config.h"
#include "lock_util.h"
#include "log.h"
bool
video_buffer_init(struct video_buffer *vb) {
if (!(vb->decoding_frame = av_frame_alloc())) {
goto error_0;
}
if (!(vb->rendering_frame = av_frame_alloc())) {
goto error_1;
}
if (!(vb->mutex = SDL_CreateMutex())) {
goto error_2;
}
#ifndef SKIP_FRAMES
if (!(vb->rendering_frame_consumed_cond = SDL_CreateCond())) {
SDL_DestroyMutex(vb->mutex);
goto error_2;
}
vb->interrupted = false;
#endif
// there is initially no rendering frame, so consider it has already been
// consumed
vb->rendering_frame_consumed = true;
fps_counter_init(&vb->fps_counter);
return true;
error_2:
av_frame_free(&vb->rendering_frame);
error_1:
av_frame_free(&vb->decoding_frame);
error_0:
return false;
}
void
video_buffer_destroy(struct video_buffer *vb) {
#ifndef SKIP_FRAMES
SDL_DestroyCond(vb->rendering_frame_consumed_cond);
#endif
SDL_DestroyMutex(vb->mutex);
av_frame_free(&vb->rendering_frame);
av_frame_free(&vb->decoding_frame);
}
static void
video_buffer_swap_frames(struct video_buffer *vb) {
AVFrame *tmp = vb->decoding_frame;
vb->decoding_frame = vb->rendering_frame;
vb->rendering_frame = tmp;
}
void
video_buffer_offer_decoded_frame(struct video_buffer *vb,
bool *previous_frame_skipped) {
mutex_lock(vb->mutex);
#ifndef SKIP_FRAMES
// if SKIP_FRAMES is disabled, then the decoder must wait for the current
// frame to be consumed
while (!vb->rendering_frame_consumed && !vb->interrupted) {
cond_wait(vb->rendering_frame_consumed_cond, vb->mutex);
}
#else
if (vb->fps_counter.started && !vb->rendering_frame_consumed) {
fps_counter_add_skipped_frame(&vb->fps_counter);
}
#endif
video_buffer_swap_frames(vb);
*previous_frame_skipped = !vb->rendering_frame_consumed;
vb->rendering_frame_consumed = false;
mutex_unlock(vb->mutex);
}
const AVFrame *
video_buffer_consume_rendered_frame(struct video_buffer *vb) {
SDL_assert(!vb->rendering_frame_consumed);
vb->rendering_frame_consumed = true;
if (vb->fps_counter.started) {
fps_counter_add_rendered_frame(&vb->fps_counter);
}
#ifndef SKIP_FRAMES
// if SKIP_FRAMES is disabled, then notify the decoder the current frame is
// consumed, so that it may push a new one
cond_signal(vb->rendering_frame_consumed_cond);
#endif
return vb->rendering_frame;
}
void
video_buffer_interrupt(struct video_buffer *vb) {
#ifdef SKIP_FRAMES
(void) vb; // unused
#else
mutex_lock(vb->mutex);
vb->interrupted = true;
mutex_unlock(vb->mutex);
// wake up blocking wait
cond_signal(vb->rendering_frame_consumed_cond);
#endif
}

View File

@@ -87,13 +87,13 @@ static void test_serialize_mouse_event(void) {
unsigned char buf[SERIALIZED_EVENT_MAX_SIZE]; unsigned char buf[SERIALIZED_EVENT_MAX_SIZE];
int size = control_event_serialize(&event, buf); int size = control_event_serialize(&event, buf);
assert(size == 18); assert(size == 14);
const unsigned char expected[] = { const unsigned char expected[] = {
0x02, // CONTROL_EVENT_TYPE_MOUSE 0x02, // CONTROL_EVENT_TYPE_MOUSE
0x00, // AKEY_EVENT_ACTION_DOWN 0x00, // AKEY_EVENT_ACTION_DOWN
0x00, 0x00, 0x00, 0x01, // AMOTION_EVENT_BUTTON_PRIMARY 0x00, 0x00, 0x00, 0x01, // AMOTION_EVENT_BUTTON_PRIMARY
0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026 0x01, 0x04, 0x04, 0x02, // 260 1026
0x04, 0x38, 0x07, 0x80, // 1080 1920 0x04, 0x38, 0x07, 0x80, // 1080 1920
}; };
assert(!memcmp(buf, expected, sizeof(expected))); assert(!memcmp(buf, expected, sizeof(expected)));
@@ -120,11 +120,11 @@ static void test_serialize_scroll_event(void) {
unsigned char buf[SERIALIZED_EVENT_MAX_SIZE]; unsigned char buf[SERIALIZED_EVENT_MAX_SIZE];
int size = control_event_serialize(&event, buf); int size = control_event_serialize(&event, buf);
assert(size == 21); assert(size == 17);
const unsigned char expected[] = { const unsigned char expected[] = {
0x03, // CONTROL_EVENT_TYPE_SCROLL 0x03, // CONTROL_EVENT_TYPE_SCROLL
0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026 0x01, 0x04, 0x04, 0x02, // 260 1026
0x04, 0x38, 0x07, 0x80, // 1080 1920 0x04, 0x38, 0x07, 0x80, // 1080 1920
0x00, 0x00, 0x00, 0x01, // 1 0x00, 0x00, 0x00, 0x01, // 1
0xFF, 0xFF, 0xFF, 0xFF, // -1 0xFF, 0xFF, 0xFF, 0xFF, // -1

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
project('scrcpy', 'c', project('scrcpy', 'c',
version: '1.8', version: '1.5',
meson_version: '>= 0.37', meson_version: '>= 0.37',
default_options: 'c_std=c11') default_options: 'c_std=c11')

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.1-win32-shared.zip \ @./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/shared/ffmpeg-4.0.2-win32-shared.zip \
e692b18c01745d262c03294b382fd64df68fabe3c66aa4546a3ad3935175cde3 \ cc190a3a4cf7bfbd4fbaa92609c1501a1de458055e6cfea8b745c1d515013aa8 \
ffmpeg-4.1-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.1-win32-dev.zip \ @./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/dev/ffmpeg-4.0.2-win32-dev.zip \
34bc5e471fb9160609abd6bc271e361050f3ff7376b1b8a0873cca02b38277c8 \ c72c74bad74ac0541f1b43090c26a50017c49041c182a703abd2057bb8cdc238 \
ffmpeg-4.1-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.1-win64-shared.zip \ @./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/shared/ffmpeg-4.0.2-win64-shared.zip \
c4908c97436c946509dc365e421159274fa4b1e66dce6fb5b63d82a6294d5357 \ ede566aca8b5348dff85570f9638c6bad33209f9419f79db7bde7daa37599bff \
ffmpeg-4.1-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.1-win64-dev.zip \ @./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/dev/ffmpeg-4.0.2-win64-dev.zip \
761ec79aa3dae66698c9791a2f0bb9da8794246f8356cadc741ddc0eabab0471 \ 23ee994161c51285cb956b98d3caa499d48083dae7b26c1fdf77f22e98df1c5f \
ffmpeg-4.1-win64-dev ffmpeg-4.0.2-win64-dev
prepare-sdl2: prepare-sdl2:
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.9-mingw.tar.gz \ @./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.9-mingw.tar.gz \

View File

@@ -55,4 +55,4 @@ get_dep() {
fi fi
} }
get_dep "$url" "$sum" "$dir" get_dep "$1" "$2" "$3"

View File

@@ -1,35 +0,0 @@
#!/bin/bash
set -e
# build and test locally
BUILDDIR=build_release
rm -rf "$BUILDDIR"
meson "$BUILDDIR" --buildtype release --strip -Db_lto=true
cd "$BUILDDIR"
ninja
ninja test
cd -
# build Windows releases
make -f Makefile.CrossWindows
# the generated server must be the same everywhere
cmp "$BUILDDIR/server/scrcpy-server.jar" dist/scrcpy-win32/scrcpy-server.jar
cmp "$BUILDDIR/server/scrcpy-server.jar" dist/scrcpy-win64/scrcpy-server.jar
# get version name
TAG=$(git describe --tags --always)
# create release directory
mkdir -p "release-$TAG"
cp "$BUILDDIR/server/scrcpy-server.jar" "release-$TAG/scrcpy-server-$TAG.jar"
cp "dist/scrcpy-win32-$TAG.zip" "release-$TAG/"
cp "dist/scrcpy-win64-$TAG.zip" "release-$TAG/"
# generate checksums
cd "release-$TAG"
sha256sum "scrcpy-server-$TAG.jar" \
"scrcpy-win32-$TAG.zip" \
"scrcpy-win64-$TAG.zip" > SHA256SUMS.txt
echo "Release generated in release-$TAG/"

View File

@@ -6,8 +6,8 @@ android {
applicationId "com.genymobile.scrcpy" applicationId "com.genymobile.scrcpy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 27 targetSdkVersion 27
versionCode 9 versionCode 6
versionName "1.8" versionName "1.5"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {

View File

@@ -10,10 +10,6 @@ if prebuilt_server == ''
install: true, install: true,
install_dir: 'share/scrcpy') install_dir: 'share/scrcpy')
else else
if not prebuilt_server.startswith('/')
# relative path needs some trick
prebuilt_server = meson.source_root() + '/' + prebuilt_server
endif
custom_target('scrcpy-server-prebuilt', custom_target('scrcpy-server-prebuilt',
input: prebuilt_server, input: prebuilt_server,
output: 'scrcpy-server.jar', output: 'scrcpy-server.jar',

View File

@@ -12,8 +12,6 @@ public final class ControlEvent {
public static final int TYPE_COMMAND = 4; public static final int TYPE_COMMAND = 4;
public static final int COMMAND_BACK_OR_SCREEN_ON = 0; public static final int COMMAND_BACK_OR_SCREEN_ON = 0;
public static final int COMMAND_EXPAND_NOTIFICATION_PANEL = 1;
public static final int COMMAND_COLLAPSE_NOTIFICATION_PANEL = 2;
private int type; private int type;
private String text; private String text;

View File

@@ -9,8 +9,8 @@ import java.nio.charset.StandardCharsets;
public class ControlEventReader { public class ControlEventReader {
private static final int KEYCODE_PAYLOAD_LENGTH = 9; private static final int KEYCODE_PAYLOAD_LENGTH = 9;
private static final int MOUSE_PAYLOAD_LENGTH = 17; private static final int MOUSE_PAYLOAD_LENGTH = 13;
private static final int SCROLL_PAYLOAD_LENGTH = 20; private static final int SCROLL_PAYLOAD_LENGTH = 16;
private static final int COMMAND_PAYLOAD_LENGTH = 1; private static final int COMMAND_PAYLOAD_LENGTH = 1;
public static final int TEXT_MAX_LENGTH = 300; public static final int TEXT_MAX_LENGTH = 300;
@@ -132,8 +132,8 @@ public class ControlEventReader {
} }
private static Position readPosition(ByteBuffer buffer) { private static Position readPosition(ByteBuffer buffer) {
int x = buffer.getInt(); int x = toUnsigned(buffer.getShort());
int y = buffer.getInt(); int y = toUnsigned(buffer.getShort());
int screenWidth = toUnsigned(buffer.getShort()); int screenWidth = toUnsigned(buffer.getShort());
int screenHeight = toUnsigned(buffer.getShort()); int screenHeight = toUnsigned(buffer.getShort());
return new Position(x, y, screenWidth, screenHeight); return new Position(x, y, screenWidth, screenHeight);

View File

@@ -132,14 +132,6 @@ public final class Device {
this.rotationListener = rotationListener; this.rotationListener = rotationListener;
} }
public void expandNotificationPanel() {
serviceManager.getStatusBarManager().expandNotificationsPanel();
}
public void collapsePanels() {
serviceManager.getStatusBarManager().collapsePanels();
}
static Rect flipRect(Rect crop) { static Rect flipRect(Rect crop) {
return new Rect(crop.top, crop.left, crop.bottom, crop.right); return new Rect(crop.top, crop.left, crop.bottom, crop.right);
} }

View File

@@ -1,7 +1,6 @@
package com.genymobile.scrcpy; package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.InputManager; import com.genymobile.scrcpy.wrappers.InputManager;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.graphics.Point; import android.graphics.Point;
import android.os.SystemClock; import android.os.SystemClock;
@@ -173,12 +172,6 @@ public class EventController {
switch (action) { switch (action) {
case ControlEvent.COMMAND_BACK_OR_SCREEN_ON: case ControlEvent.COMMAND_BACK_OR_SCREEN_ON:
return pressBackOrTurnScreenOn(); return pressBackOrTurnScreenOn();
case ControlEvent.COMMAND_EXPAND_NOTIFICATION_PANEL:
device.expandNotificationPanel();
return true;
case ControlEvent.COMMAND_COLLAPSE_NOTIFICATION_PANEL:
device.collapsePanels();
return true;
default: default:
Ln.w("Unsupported command: " + action); Ln.w("Unsupported command: " + action);
} }

View File

@@ -87,6 +87,7 @@ public class ScreenEncoder implements Device.RotationListener {
boolean eof = false; boolean eof = false;
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
while (!consumeRotationChange() && !eof) { while (!consumeRotationChange() && !eof) {
int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1); int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1);
eof = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0; eof = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
@@ -150,7 +151,7 @@ public class ScreenEncoder implements Device.RotationListener {
} }
private static IBinder createDisplay() { private static IBinder createDisplay() {
return SurfaceControl.createDisplay("scrcpy", true); return SurfaceControl.createDisplay("scrcpy", false);
} }
private static void configure(MediaCodec codec, MediaFormat format) { private static void configure(MediaCodec codec, MediaFormat format) {

View File

@@ -2,14 +2,11 @@ package com.genymobile.scrcpy;
import android.graphics.Rect; import android.graphics.Rect;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
public final class Server { public final class Server {
private static final String SERVER_PATH = "/data/local/tmp/scrcpy-server.jar";
private Server() { private Server() {
// not instantiable // not instantiable
} }
@@ -49,24 +46,36 @@ public final class Server {
@SuppressWarnings("checkstyle:MagicNumber") @SuppressWarnings("checkstyle:MagicNumber")
private static Options createOptions(String... args) { private static Options createOptions(String... args) {
if (args.length != 5)
throw new IllegalArgumentException("Expecting 5 parameters");
Options options = new Options(); Options options = new Options();
if (args.length < 1) {
return options;
}
int maxSize = Integer.parseInt(args[0]) & ~7; // multiple of 8 int maxSize = Integer.parseInt(args[0]) & ~7; // multiple of 8
options.setMaxSize(maxSize); options.setMaxSize(maxSize);
if (args.length < 2) {
return options;
}
int bitRate = Integer.parseInt(args[1]); int bitRate = Integer.parseInt(args[1]);
options.setBitRate(bitRate); options.setBitRate(bitRate);
if (args.length < 3) {
return options;
}
// use "adb forward" instead of "adb tunnel"? (so the server must listen) // use "adb forward" instead of "adb tunnel"? (so the server must listen)
boolean tunnelForward = Boolean.parseBoolean(args[2]); boolean tunnelForward = Boolean.parseBoolean(args[2]);
Ln.d("tunnelForward = " + tunnelForward);
options.setTunnelForward(tunnelForward); options.setTunnelForward(tunnelForward);
if (args.length < 4) {
return options;
}
Rect crop = parseCrop(args[3]); Rect crop = parseCrop(args[3]);
options.setCrop(crop); options.setCrop(crop);
if (args.length < 5) {
return options;
}
boolean sendFrameMeta = Boolean.parseBoolean(args[4]); boolean sendFrameMeta = Boolean.parseBoolean(args[4]);
options.setSendFrameMeta(sendFrameMeta); options.setSendFrameMeta(sendFrameMeta);
@@ -74,7 +83,7 @@ public final class Server {
} }
private static Rect parseCrop(String crop) { private static Rect parseCrop(String crop) {
if ("-".equals(crop)) { if (crop.isEmpty()) {
return null; return null;
} }
// input format: "width:height:x:y" // input format: "width:height:x:y"
@@ -89,14 +98,6 @@ public final class Server {
return new Rect(x, y, x + width, y + height); return new Rect(x, y, x + width, y + height);
} }
private static void unlinkSelf() {
try {
new File(SERVER_PATH).delete();
} catch (Exception e) {
Ln.e("Cannot unlink server", e);
}
}
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() {
@Override @Override
@@ -105,7 +106,8 @@ public final class Server {
} }
}); });
unlinkSelf(); Ln.d("args: " + Arrays.toString(args));
Options options = createOptions(args); Options options = createOptions(args);
scrcpy(options); scrcpy(options);
} }

View File

@@ -14,7 +14,6 @@ public final class ServiceManager {
private DisplayManager displayManager; private DisplayManager displayManager;
private InputManager inputManager; private InputManager inputManager;
private PowerManager powerManager; private PowerManager powerManager;
private StatusBarManager statusBarManager;
public ServiceManager() { public ServiceManager() {
try { try {
@@ -61,11 +60,4 @@ public final class ServiceManager {
} }
return powerManager; return powerManager;
} }
public StatusBarManager getStatusBarManager() {
if (statusBarManager == null) {
statusBarManager = new StatusBarManager(getService("statusbar", "com.android.internal.statusbar.IStatusBarService"));
}
return statusBarManager;
}
} }

View File

@@ -1,41 +0,0 @@
package com.genymobile.scrcpy.wrappers;
import android.annotation.SuppressLint;
import android.os.IInterface;
import android.view.InputEvent;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class StatusBarManager {
private final IInterface manager;
private final Method expandNotificationsPanelMethod;
private final Method collapsePanelsMethod;
public StatusBarManager(IInterface manager) {
this.manager = manager;
try {
expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel");
collapsePanelsMethod = manager.getClass().getMethod("collapsePanels");
} catch (NoSuchMethodException e) {
throw new AssertionError(e);
}
}
public void expandNotificationsPanel() {
try {
expandNotificationsPanelMethod.invoke(manager);
} catch (InvocationTargetException | IllegalAccessException e) {
throw new AssertionError(e);
}
}
public void collapsePanels() {
try {
collapsePanelsMethod.invoke(manager);
} catch (InvocationTargetException | IllegalAccessException e) {
throw new AssertionError(e);
}
}
}