Compare commits

...

38 Commits

Author SHA1 Message Date
Romain Vimont
c7e46583b8 Improve process API
Prefix symbols and constants names and improve documentation.
2021-11-11 18:08:47 +01:00
Romain Vimont
848db078a5 Factorize common impl of process_execute()
Both implementations are the same. Move them to the common process.c.
2021-11-11 18:08:41 +01:00
Romain Vimont
b197372dd3 Remove duplicate function declaration
The function process_terminate() was declared twice.
2021-11-11 18:08:41 +01:00
Romain Vimont
66f21cf680 Improve file API
Prefix symbols and constants names and improve documentation.
2021-11-11 18:08:35 +01:00
Romain Vimont
f197c1181e Move functions from process to file
Move filesystem-related functions from process.[ch] to file.[ch].
2021-11-11 16:27:12 +01:00
Romain Vimont
7a733328bc Adapt help to terminal size
If the error stream is a terminal, and we can retrieve the terminal
size, wrap the help content according to the terminal width.
2021-11-11 15:22:39 +01:00
Romain Vimont
38332f683c Add util function to get terminal size 2021-11-11 15:22:39 +01:00
Romain Vimont
3f51a2ab43 Generate getopt params from option structures
Use the option descriptions to generate the optstring and longopts
parameters for the getopt_long() command.

That way, the options are completely described in a single place.
2021-11-11 14:59:34 +01:00
Romain Vimont
74ab0a4df8 Structure shortcuts help 2021-11-11 14:59:34 +01:00
Romain Vimont
f59e9e3cb0 Structure command line options help
It will allow to correctly print the help for the current terminal size
(even if for now it is hardcoded to 80 columns).
2021-11-11 14:59:34 +01:00
Romain Vimont
9ec3406568 Add line wrapper
Add a tool to wrap lines at words boundaries (spaces) to fit in a
specified number of columns, left-indented by a specified number of
spaces.
2021-11-11 14:55:53 +01:00
Romain Vimont
6dba1922c1 Add string buffer util
This will help to build strings incrementally.
2021-11-11 14:55:52 +01:00
Romain Vimont
570a003c39 Remove deprecated -T option
The short option -T is deprecated since v1.11. Only the long version
(--always-on-top) remains.
2021-11-07 21:35:57 +01:00
Romain Vimont
b62df7ee91 Remove deprecated -c option
The short option -c is deprecated since v1.11. Only the long version
(--crop) remains.
2021-11-07 21:35:53 +01:00
Romain Vimont
30d40f4e78 Document --power-off-on-close
The option was not documented.

Refs #824 <https://github.com/Genymobile/scrcpy/pull/824>
2021-11-07 19:27:53 +01:00
Romain Vimont
f489f7fcad Mention drag & drop for non-APK files in help 2021-11-07 18:51:22 +01:00
Romain Vimont
d72c7076f7 Mention drag & drop APK in README
For consistency with --help and manpage.
2021-11-07 18:50:29 +01:00
Romain Vimont
fc64445555 Remove extra space in README 2021-11-07 18:50:24 +01:00
Romain Vimont
f65c3fde69 Fix typos in help 2021-11-07 18:50:24 +01:00
Romain Vimont
48fcfa96ab Add missing include "common.h" 2021-11-06 19:26:29 +01:00
Romain Vimont
676fa73d2c Wrap device name and size in a struct
As a benefit, this avoids to take care of the device name length on the
caller side.
2021-11-02 11:08:58 +01:00
Romain Vimont
58ea238fb2 Remove unnecessary variable
Test directly if a record filename is provided, without an intermediate
boolean variable.
2021-10-31 12:45:59 +01:00
Romain Vimont
13c4aa1a3b Disable synthetic mouse events from touch events
Touch events with id SDL_TOUCH_MOUSEID are ignored anyway, but it is
better not to generate them in the first place.
2021-10-31 12:45:59 +01:00
Romain Vimont
caf594c90e Split SDL initialization
Initialize SDL_INIT_EVENTS first (very quick) and SDL_INIT_VIDEO after
the server (quite long).
2021-10-31 12:45:59 +01:00
Romain Vimont
688477ff65 Move SDL initialization
Inline SDL initialization in scrcpy(). This will allow to split
minimal initialization and video initialization.
2021-10-31 12:45:59 +01:00
Romain Vimont
ac539e1312 Set SDL hints before initialization
In theory, some SDL hints should be initialized before calling
SDL_Init().
2021-10-31 12:45:59 +01:00
Romain Vimont
a57c7d3a2b Extract SDL hints
Set all SDL hints in a separate function.
2021-10-31 12:45:56 +01:00
Romain Vimont
d2d18466d4 Factorize SDL event push
Add a macro and inline function to call SDL_PushEvent() and log on error
(without actually handling the error, because there is nothing we could
do).
2021-10-30 22:36:56 +02:00
Romain Vimont
dae091e3ab Handle SDL_PushEvent() errors
Pushing an event to the main thread may fail. If this happens, log an
error, and try to recover when possible.
2021-10-30 20:25:20 +02:00
Romain Vimont
4c4381de4c Use sc_ prefix for size, position and point 2021-10-30 15:20:39 +02:00
Romain Vimont
db484d82db Upgrade gradle build tools to 7.0.2 2021-10-30 11:32:48 +02:00
Romain Vimont
0c9666b733 Upgrade Android checkstyle to 9.0.1
Adapt checkstyle.xml to match the latest version, and remove a line
break between imports which trigger a checkstyle volation.
2021-10-30 11:23:51 +02:00
Romain Vimont
8bf28e9f53 Upgrade Android SDK to 31 2021-10-30 11:23:51 +02:00
Romain Vimont
06131ef634 Fix typo in clock comments 2021-10-29 12:21:34 +02:00
Romain Vimont
34eb10ea0b Define v4l2 option field only if HAVE_V4L2 2021-10-27 18:43:47 +02:00
Romain Vimont
27fa23846d Define default options as const struct
This is more readable than a macro, and we could ifdef some fields.
2021-10-27 18:43:47 +02:00
Romain Vimont
e4d5c1ce36 Move scrcpy option structs to options.h
This will allow to define symbols in options.c without all the
dependencies of scrcpy.c.
2021-10-27 18:43:47 +02:00
Romain Vimont
ac23bec144 Expose socket interruption
On Linux, socket functions are unblocked by shutdown(), but on Windows
they are unblocked by closesocket().

Expose net_interrupt() and net_close() to abstract these differences:
 - net_interrupt() calls shutdown() on Linux and closesocket() on
   Windows (if not already called);
 - net_close() calls close() on Linux and closesocket() on Windows (if
   not already called).

This simplifies the server code, and prevents a data race on close
(reported by TSAN) on Linux (but does not fix it on Windows):

    WARNING: ThreadSanitizer: data race (pid=836124)
      Write of size 8 at 0x7ba0000000d0 by main thread:
        #0 close ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1690 (libtsan.so.0+0x359d8)
        #1 net_close ../app/src/util/net.c:211 (scrcpy+0x1c76b)
        #2 close_socket ../app/src/server.c:330 (scrcpy+0x19442)
        #3 server_stop ../app/src/server.c:522 (scrcpy+0x19e33)
        #4 scrcpy ../app/src/scrcpy.c:532 (scrcpy+0x156fc)
        #5 main ../app/src/main.c:92 (scrcpy+0x622a)

      Previous read of size 8 at 0x7ba0000000d0 by thread T6:
        #0 recv ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:6603 (libtsan.so.0+0x4f4a6)
        #1 net_recv ../app/src/util/net.c:167 (scrcpy+0x1c5a7)
        #2 run_receiver ../app/src/receiver.c:76 (scrcpy+0x12819)
        #3 <null> <null> (libSDL2-2.0.so.0+0x84f40)
2021-10-27 18:41:58 +02:00
61 changed files with 2124 additions and 1189 deletions

View File

@@ -582,6 +582,14 @@ scrcpy --turn-screen-off --stay-awake
scrcpy -Sw
```
#### Power off on close
To turn the device screen off when closing scrcpy:
```bash
scrcpy --power-off-on-close
```
#### Show touches
@@ -830,7 +838,7 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
| Turn device screen on | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
| Rotate device screen | <kbd>MOD</kbd>+<kbd>r</kbd>
| Expand notification panel | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5th-click³_
| Expand settings panel | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Double-5th-click³_
| Expand settings panel | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Double-5th-click³_
| Collapse panels | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| Copy to clipboard⁴ | <kbd>MOD</kbd>+<kbd>c</kbd>
| Cut to clipboard⁴ | <kbd>MOD</kbd>+<kbd>x</kbd>
@@ -838,6 +846,8 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
| Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_
| Drag & drop APK file | Install APK from computer
| Drag & drop non-APK file | [Push file to device](#push-file-to-device)
_¹Double-click on black borders to remove them._
_²Right-click turns the screen on if it was off, presses BACK otherwise._

View File

@@ -16,6 +16,7 @@ src = [
'src/keyboard_inject.c',
'src/mouse_inject.c',
'src/opengl.c',
'src/options.c',
'src/receiver.c',
'src/recorder.c',
'src/scrcpy.c',
@@ -23,18 +24,27 @@ src = [
'src/server.c',
'src/stream.c',
'src/video_buffer.c',
'src/util/file.c',
'src/util/log.c',
'src/util/net.c',
'src/util/process.c',
'src/util/strbuf.c',
'src/util/str_util.c',
'src/util/term.c',
'src/util/thread.c',
'src/util/tick.c',
]
if host_machine.system() == 'windows'
src += [ 'src/sys/win/process.c' ]
src += [
'src/sys/win/file.c',
'src/sys/win/process.c',
]
else
src += [ 'src/sys/unix/process.c' ]
src += [
'src/sys/unix/file.c',
'src/sys/unix/process.c',
]
endif
v4l2_support = host_machine.system() == 'linux'
@@ -184,7 +194,10 @@ if get_option('buildtype') == 'debug'
['test_cli', [
'tests/test_cli.c',
'src/cli.c',
'src/options.c',
'src/util/strbuf.c',
'src/util/str_util.c',
'src/util/term.c',
]],
['test_clock', [
'tests/test_clock.c',
@@ -193,6 +206,7 @@ if get_option('buildtype') == 'debug'
['test_control_msg_serialize', [
'tests/test_control_msg_serialize.c',
'src/control_msg.c',
'src/util/strbuf.c',
'src/util/str_util.c',
]],
['test_device_msg_deserialize', [
@@ -202,8 +216,13 @@ if get_option('buildtype') == 'debug'
['test_queue', [
'tests/test_queue.c',
]],
['test_strbuf', [
'tests/test_strbuf.c',
'src/util/strbuf.c',
]],
['test_strutil', [
'tests/test_strutil.c',
'src/util/strbuf.c',
'src/util/str_util.c',
]],
]

View File

@@ -136,6 +136,10 @@ Set the TCP port (range) used by the client to listen.
Default is 27183:27199.
.TP
.B \-\-power\-off\-on\-close
Turn the device screen off when closing scrcpy.
.TP
.B \-\-prefer\-text
Inject alpha characters and space as text events instead of key events.
@@ -364,6 +368,10 @@ Pinch-to-zoom from the center of the screen
.B Drag & drop APK file
Install APK from computer
.TP
.B Drag & drop non-APK file
Push file to device (see \fB\-\-push\-target\fR)
.SH Environment variables

View File

@@ -5,6 +5,7 @@
#include <stdlib.h>
#include <string.h>
#include "util/file.h"
#include "util/log.h"
#include "util/str_util.h"
@@ -68,7 +69,7 @@ show_adb_installation_msg() {
{"pacman", "pacman -S android-tools"},
};
for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) {
if (search_executable(pkg_managers[i].binary)) {
if (sc_file_executable_exists(pkg_managers[i].binary)) {
LOGI("You may install 'adb' by \"%s\"", pkg_managers[i].command);
return;
}
@@ -80,7 +81,7 @@ show_adb_installation_msg() {
}
static void
show_adb_err_msg(enum process_result err, const char *const argv[]) {
show_adb_err_msg(enum sc_process_result err, const char *const argv[]) {
#define MAX_COMMAND_STRING_LEN 1024
char *buf = malloc(MAX_COMMAND_STRING_LEN);
if (!buf) {
@@ -89,18 +90,18 @@ show_adb_err_msg(enum process_result err, const char *const argv[]) {
}
switch (err) {
case PROCESS_ERROR_GENERIC:
case SC_PROCESS_ERROR_GENERIC:
argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN);
LOGE("Failed to execute: %s", buf);
break;
case PROCESS_ERROR_MISSING_BINARY:
case SC_PROCESS_ERROR_MISSING_BINARY:
argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN);
LOGE("Command not found: %s", buf);
LOGE("(make 'adb' accessible from your PATH or define its full"
"path in the ADB environment variable)");
show_adb_installation_msg();
break;
case PROCESS_SUCCESS:
case SC_PROCESS_SUCCESS:
// do nothing
break;
}
@@ -108,16 +109,15 @@ show_adb_err_msg(enum process_result err, const char *const argv[]) {
free(buf);
}
process_t
adb_execute_redirect(const char *serial, const char *const adb_cmd[],
size_t len, pipe_t *pipe_stdin, pipe_t *pipe_stdout,
pipe_t *pipe_stderr) {
sc_pid
adb_execute_p(const char *serial, const char *const adb_cmd[],
size_t len, sc_pipe *pin, sc_pipe *pout, sc_pipe *perr) {
int i;
process_t process;
sc_pid pid;
const char **argv = malloc((len + 4) * sizeof(*argv));
if (!argv) {
return PROCESS_NONE;
return SC_PROCESS_NONE;
}
argv[0] = get_adb_command();
@@ -131,24 +131,23 @@ adb_execute_redirect(const char *serial, const char *const adb_cmd[],
memcpy(&argv[i], adb_cmd, len * sizeof(const char *));
argv[len + i] = NULL;
enum process_result r =
process_execute_redirect(argv, &process, pipe_stdin, pipe_stdout,
pipe_stderr);
if (r != PROCESS_SUCCESS) {
enum sc_process_result r =
sc_process_execute_p(argv, &pid, pin, pout, perr);
if (r != SC_PROCESS_SUCCESS) {
show_adb_err_msg(r, argv);
process = PROCESS_NONE;
pid = SC_PROCESS_NONE;
}
free(argv);
return process;
return pid;
}
process_t
sc_pid
adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
return adb_execute_redirect(serial, adb_cmd, len, NULL, NULL, NULL);
return adb_execute_p(serial, adb_cmd, len, NULL, NULL, NULL);
}
process_t
sc_pid
adb_forward(const char *serial, uint16_t local_port,
const char *device_socket_name) {
char local[4 + 5 + 1]; // tcp:PORT
@@ -159,7 +158,7 @@ adb_forward(const char *serial, uint16_t local_port,
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
process_t
sc_pid
adb_forward_remove(const char *serial, uint16_t local_port) {
char local[4 + 5 + 1]; // tcp:PORT
sprintf(local, "tcp:%" PRIu16, local_port);
@@ -167,7 +166,7 @@ adb_forward_remove(const char *serial, uint16_t local_port) {
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
process_t
sc_pid
adb_reverse(const char *serial, const char *device_socket_name,
uint16_t local_port) {
char local[4 + 5 + 1]; // tcp:PORT
@@ -178,7 +177,7 @@ adb_reverse(const char *serial, const char *device_socket_name,
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
process_t
sc_pid
adb_reverse_remove(const char *serial, const char *device_socket_name) {
char remote[108 + 14 + 1]; // localabstract:NAME
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
@@ -186,66 +185,65 @@ adb_reverse_remove(const char *serial, const char *device_socket_name) {
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
process_t
sc_pid
adb_push(const char *serial, const char *local, const char *remote) {
#ifdef __WINDOWS__
// Windows will parse the string, so the paths must be quoted
// (see sys/win/command.c)
local = strquote(local);
if (!local) {
return PROCESS_NONE;
return SC_PROCESS_NONE;
}
remote = strquote(remote);
if (!remote) {
free((void *) local);
return PROCESS_NONE;
return SC_PROCESS_NONE;
}
#endif
const char *const adb_cmd[] = {"push", local, remote};
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__
free((void *) remote);
free((void *) local);
#endif
return proc;
return pid;
}
process_t
sc_pid
adb_install(const char *serial, const char *local) {
#ifdef __WINDOWS__
// Windows will parse the string, so the local name must be quoted
// (see sys/win/command.c)
local = strquote(local);
if (!local) {
return PROCESS_NONE;
return SC_PROCESS_NONE;
}
#endif
const char *const adb_cmd[] = {"install", "-r", local};
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__
free((void *) local);
#endif
return proc;
return pid;
}
static ssize_t
adb_execute_for_output(const char *serial, const char *const adb_cmd[],
size_t adb_cmd_len, char *buf, size_t buf_len,
const char *name) {
pipe_t pipe_stdout;
process_t proc = adb_execute_redirect(serial, adb_cmd, adb_cmd_len, NULL,
&pipe_stdout, NULL);
sc_pipe pout;
sc_pid pid = adb_execute_p(serial, adb_cmd, adb_cmd_len, NULL, &pout, NULL);
ssize_t r = read_pipe_all(pipe_stdout, buf, buf_len);
close_pipe(pipe_stdout);
ssize_t r = sc_pipe_read_all(pout, buf, buf_len);
sc_pipe_close(pout);
if (!process_check_success(proc, name, true)) {
if (!sc_process_check_success(pid, name, true)) {
return -1;
}

View File

@@ -8,32 +8,31 @@
#include "util/process.h"
process_t
sc_pid
adb_execute(const char *serial, const char *const adb_cmd[], size_t len);
process_t
adb_execute_redirect(const char *serial, const char *const adb_cmd[],
size_t len, pipe_t *pipe_stdin, pipe_t *pipe_stdout,
pipe_t *pipe_stderr);
sc_pid
adb_execute_p(const char *serial, const char *const adb_cmd[],
size_t len, sc_pipe *pin, sc_pipe *pout, sc_pipe *perr);
process_t
sc_pid
adb_forward(const char *serial, uint16_t local_port,
const char *device_socket_name);
process_t
sc_pid
adb_forward_remove(const char *serial, uint16_t local_port);
process_t
sc_pid
adb_reverse(const char *serial, const char *device_socket_name,
uint16_t local_port);
process_t
sc_pid
adb_reverse_remove(const char *serial, const char *device_socket_name);
process_t
sc_pid
adb_push(const char *serial, const char *local, const char *remote);
process_t
sc_pid
adb_install(const char *serial, const char *local);
// Return the result of "adb get-serialno".

View File

@@ -6,7 +6,6 @@
#include <libusb-1.0/libusb.h>
#include "scrcpy.h"
#include "util/cbuf.h"
#include "util/thread.h"
#include "util/tick.h"

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
#include <stdbool.h>
#include "scrcpy.h"
#include "options.h"
struct scrcpy_cli_args {
struct scrcpy_options opts;

View File

@@ -26,7 +26,7 @@ struct sc_clock_point {
* array.
*
* To estimate the slope, it splits the last SC_CLOCK_RANGE points into two
* sets of SC_CLOCK_RANGE/2 points, and compute their centroid ("average
* sets of SC_CLOCK_RANGE/2 points, and computes their centroid ("average
* point"). The slope of the estimated affine function is that of the line
* passing through these two points.
*

View File

@@ -43,6 +43,11 @@
# define SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
#endif
#if SDL_VERSION_ATLEAST(2, 0, 6)
// <https://github.com/libsdl-org/SDL/commit/d7a318de563125e5bb465b1000d6bc9576fbc6fc>
# define SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
#endif
#if SDL_VERSION_ATLEAST(2, 0, 8)
// <https://hg.libsdl.org/SDL/rev/dfde5d3f9781>
# define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR

View File

@@ -56,7 +56,7 @@ static const char *const screen_power_mode_labels[] = {
};
static void
write_position(uint8_t *buf, const struct position *position) {
write_position(uint8_t *buf, const struct sc_position *position) {
buffer_write32be(&buf[0], position->point.x);
buffer_write32be(&buf[4], position->point.y);
buffer_write16be(&buf[8], position->screen_size.width);

View File

@@ -57,11 +57,11 @@ struct control_msg {
enum android_motionevent_action action;
enum android_motionevent_buttons buttons;
uint64_t pointer_id;
struct position position;
struct sc_position position;
float pressure;
} inject_touch_event;
struct {
struct position position;
struct sc_position position;
int32_t hscroll;
int32_t vscroll;
} inject_scroll_event;

View File

@@ -3,22 +3,22 @@
#include <stdint.h>
struct size {
struct sc_size {
uint16_t width;
uint16_t height;
};
struct point {
struct sc_point {
int32_t x;
int32_t y;
};
struct position {
struct sc_position {
// 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
// accordingly.
struct size screen_size;
struct point point;
struct sc_size screen_size;
struct sc_point point;
};
#endif

View File

@@ -46,7 +46,7 @@ file_handler_init(struct file_handler *file_handler, const char *serial,
file_handler->initialized = false;
file_handler->stopped = false;
file_handler->current_process = PROCESS_NONE;
file_handler->current_process = SC_PROCESS_NONE;
file_handler->push_target = push_target ? push_target : DEFAULT_PUSH_TARGET;
@@ -65,12 +65,12 @@ file_handler_destroy(struct file_handler *file_handler) {
}
}
static process_t
static sc_pid
install_apk(const char *serial, const char *file) {
return adb_install(serial, file);
}
static process_t
static sc_pid
push_file(const char *serial, const char *file, const char *push_target) {
return adb_push(serial, file, push_target);
}
@@ -109,7 +109,7 @@ run_file_handler(void *data) {
for (;;) {
sc_mutex_lock(&file_handler->mutex);
file_handler->current_process = PROCESS_NONE;
file_handler->current_process = SC_PROCESS_NONE;
while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) {
sc_cond_wait(&file_handler->event_cond, &file_handler->mutex);
}
@@ -123,26 +123,26 @@ run_file_handler(void *data) {
assert(non_empty);
(void) non_empty;
process_t process;
sc_pid pid;
if (req.action == ACTION_INSTALL_APK) {
LOGI("Installing %s...", req.file);
process = install_apk(file_handler->serial, req.file);
pid = install_apk(file_handler->serial, req.file);
} else {
LOGI("Pushing %s...", req.file);
process = push_file(file_handler->serial, req.file,
file_handler->push_target);
pid = push_file(file_handler->serial, req.file,
file_handler->push_target);
}
file_handler->current_process = process;
file_handler->current_process = pid;
sc_mutex_unlock(&file_handler->mutex);
if (req.action == ACTION_INSTALL_APK) {
if (process_check_success(process, "adb install", false)) {
if (sc_process_check_success(pid, "adb install", false)) {
LOGI("%s successfully installed", req.file);
} else {
LOGE("Failed to install %s", req.file);
}
} else {
if (process_check_success(process, "adb push", false)) {
if (sc_process_check_success(pid, "adb push", false)) {
LOGI("%s successfully pushed to %s", req.file,
file_handler->push_target);
} else {
@@ -152,11 +152,11 @@ run_file_handler(void *data) {
}
sc_mutex_lock(&file_handler->mutex);
// Close the process (it is necessary already terminated)
// Close the process (it is necessarily already terminated)
// Execute this call with mutex locked to avoid race conditions with
// file_handler_stop()
process_close(file_handler->current_process);
file_handler->current_process = PROCESS_NONE;
sc_process_close(file_handler->current_process);
file_handler->current_process = SC_PROCESS_NONE;
sc_mutex_unlock(&file_handler->mutex);
file_handler_request_destroy(&req);
@@ -183,8 +183,8 @@ file_handler_stop(struct file_handler *file_handler) {
sc_mutex_lock(&file_handler->mutex);
file_handler->stopped = true;
sc_cond_signal(&file_handler->event_cond);
if (file_handler->current_process != PROCESS_NONE) {
if (!process_terminate(file_handler->current_process)) {
if (file_handler->current_process != SC_PROCESS_NONE) {
if (!sc_process_terminate(file_handler->current_process)) {
LOGW("Could not terminate push/install process");
}
}

View File

@@ -29,7 +29,7 @@ struct file_handler {
sc_cond event_cond;
bool stopped;
bool initialized;
process_t current_process;
sc_pid current_process;
struct file_handler_request_queue queue;
};

View File

@@ -8,8 +8,8 @@
#include "config.h"
#include "compat.h"
#include "util/file.h"
#include "util/log.h"
#include "util/process.h"
#include "util/str_util.h"
#define SCRCPY_PORTABLE_ICON_FILENAME "icon.png"
@@ -46,7 +46,7 @@ get_icon_path(void) {
return NULL;
}
#else
char *icon_path = get_local_file_path(SCRCPY_PORTABLE_ICON_FILENAME);
char *icon_path = sc_file_get_local_path(SCRCPY_PORTABLE_ICON_FILENAME);
if (!icon_path) {
LOGE("Could not get icon path");
return NULL;

View File

@@ -332,7 +332,7 @@ input_manager_process_text_input(struct input_manager *im,
static bool
simulate_virtual_finger(struct input_manager *im,
enum android_motionevent_action action,
struct point point) {
struct sc_point point) {
bool up = action == AMOTION_EVENT_ACTION_UP;
struct control_msg msg;
@@ -352,8 +352,8 @@ simulate_virtual_finger(struct input_manager *im,
return true;
}
static struct point
inverse_point(struct point point, struct size size) {
static struct sc_point
inverse_point(struct sc_point point, struct sc_size size) {
point.x = size.width - point.x;
point.y = size.height - point.y;
return point;
@@ -545,10 +545,10 @@ input_manager_process_mouse_motion(struct input_manager *im,
im->mp->ops->process_mouse_motion(im->mp, event);
if (im->vfinger_down) {
struct point mouse =
struct sc_point mouse =
screen_convert_window_to_frame_coords(im->screen, event->x,
event->y);
struct point vfinger = inverse_point(mouse, im->screen->frame_size);
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size);
simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger);
}
}
@@ -630,10 +630,10 @@ input_manager_process_mouse_button(struct input_manager *im,
#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL))
if ((down && !im->vfinger_down && CTRL_PRESSED)
|| (!down && im->vfinger_down)) {
struct point mouse =
struct sc_point mouse =
screen_convert_window_to_frame_coords(im->screen, event->x,
event->y);
struct point vfinger = inverse_point(mouse, im->screen->frame_size);
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size);
enum android_motionevent_action action = down
? AMOTION_EVENT_ACTION_DOWN
: AMOTION_EVENT_ACTION_UP;

View File

@@ -9,7 +9,7 @@
#include "controller.h"
#include "fps_counter.h"
#include "scrcpy.h"
#include "options.h"
#include "screen.h"
#include "trait/key_processor.h"
#include "trait/mouse_processor.h"

View File

@@ -6,7 +6,7 @@
#include <stdbool.h>
#include "controller.h"
#include "scrcpy.h"
#include "options.h"
#include "trait/key_processor.h"
struct sc_keyboard_inject {

View File

@@ -1,5 +1,3 @@
#include "scrcpy.h"
#include "common.h"
#include <assert.h>
@@ -13,6 +11,8 @@
#include <SDL2/SDL.h>
#include "cli.h"
#include "options.h"
#include "scrcpy.h"
#include "util/log.h"
static void
@@ -48,7 +48,7 @@ main(int argc, char *argv[]) {
#endif
struct scrcpy_cli_args args = {
.opts = SCRCPY_OPTIONS_DEFAULT,
.opts = scrcpy_options_default,
.help = false,
.version = false,
};

View File

@@ -125,7 +125,7 @@ convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
int mouse_y;
SDL_GetMouseState(&mouse_x, &mouse_y);
struct position position = {
struct sc_position position = {
.screen_size = screen->frame_size,
.point = screen_convert_window_to_frame_coords(screen,
mouse_x, mouse_y),

View File

@@ -6,7 +6,6 @@
#include <stdbool.h>
#include "controller.h"
#include "scrcpy.h"
#include "screen.h"
#include "trait/mouse_processor.h"

54
app/src/options.c Normal file
View File

@@ -0,0 +1,54 @@
#include "options.h"
const struct scrcpy_options scrcpy_options_default = {
.serial = NULL,
.crop = NULL,
.record_filename = NULL,
.window_title = NULL,
.push_target = NULL,
.render_driver = NULL,
.codec_options = NULL,
.encoder_name = NULL,
#ifdef HAVE_V4L2
.v4l2_device = NULL,
#endif
.log_level = SC_LOG_LEVEL_INFO,
.record_format = SC_RECORD_FORMAT_AUTO,
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT,
.port_range = {
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST,
.last = DEFAULT_LOCAL_PORT_RANGE_LAST,
},
.shortcut_mods = {
.data = {SC_MOD_LALT, SC_MOD_LSUPER},
.count = 2,
},
.max_size = 0,
.bit_rate = DEFAULT_BIT_RATE,
.max_fps = 0,
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED,
.rotation = 0,
.window_x = SC_WINDOW_POSITION_UNDEFINED,
.window_y = SC_WINDOW_POSITION_UNDEFINED,
.window_width = 0,
.window_height = 0,
.display_id = 0,
.display_buffer = 0,
.v4l2_buffer = 0,
.show_touches = false,
.fullscreen = false,
.always_on_top = false,
.control = true,
.display = true,
.turn_screen_off = false,
.prefer_text = false,
.window_borderless = false,
.mipmaps = true,
.stay_awake = false,
.force_adb_forward = false,
.disable_screensaver = false,
.forward_key_repeat = true,
.forward_all_clicks = false,
.legacy_paste = false,
.power_off_on_close = false,
};

113
app/src/options.h Normal file
View File

@@ -0,0 +1,113 @@
#ifndef SCRCPY_OPTIONS_H
#define SCRCPY_OPTIONS_H
#include "common.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "util/tick.h"
enum sc_log_level {
SC_LOG_LEVEL_VERBOSE,
SC_LOG_LEVEL_DEBUG,
SC_LOG_LEVEL_INFO,
SC_LOG_LEVEL_WARN,
SC_LOG_LEVEL_ERROR,
};
enum sc_record_format {
SC_RECORD_FORMAT_AUTO,
SC_RECORD_FORMAT_MP4,
SC_RECORD_FORMAT_MKV,
};
enum sc_lock_video_orientation {
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
// lock the current orientation when scrcpy starts
SC_LOCK_VIDEO_ORIENTATION_INITIAL = -2,
SC_LOCK_VIDEO_ORIENTATION_0 = 0,
SC_LOCK_VIDEO_ORIENTATION_1,
SC_LOCK_VIDEO_ORIENTATION_2,
SC_LOCK_VIDEO_ORIENTATION_3,
};
enum sc_keyboard_input_mode {
SC_KEYBOARD_INPUT_MODE_INJECT,
SC_KEYBOARD_INPUT_MODE_HID,
};
#define SC_MAX_SHORTCUT_MODS 8
enum sc_shortcut_mod {
SC_MOD_LCTRL = 1 << 0,
SC_MOD_RCTRL = 1 << 1,
SC_MOD_LALT = 1 << 2,
SC_MOD_RALT = 1 << 3,
SC_MOD_LSUPER = 1 << 4,
SC_MOD_RSUPER = 1 << 5,
};
struct sc_shortcut_mods {
unsigned data[SC_MAX_SHORTCUT_MODS];
unsigned count;
};
struct sc_port_range {
uint16_t first;
uint16_t last;
};
#define SC_WINDOW_POSITION_UNDEFINED (-0x8000)
struct scrcpy_options {
const char *serial;
const char *crop;
const char *record_filename;
const char *window_title;
const char *push_target;
const char *render_driver;
const char *codec_options;
const char *encoder_name;
#ifdef HAVE_V4L2
const char *v4l2_device;
#endif
enum sc_log_level log_level;
enum sc_record_format record_format;
enum sc_keyboard_input_mode keyboard_input_mode;
struct sc_port_range port_range;
struct sc_shortcut_mods shortcut_mods;
uint16_t max_size;
uint32_t bit_rate;
uint16_t max_fps;
enum sc_lock_video_orientation lock_video_orientation;
uint8_t rotation;
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
uint16_t window_width;
uint16_t window_height;
uint32_t display_id;
sc_tick display_buffer;
sc_tick v4l2_buffer;
bool show_touches;
bool fullscreen;
bool always_on_top;
bool control;
bool display;
bool turn_screen_off;
bool prefer_text;
bool window_borderless;
bool mipmaps;
bool stay_awake;
bool force_adb_forward;
bool disable_screensaver;
bool forward_key_repeat;
bool forward_all_clicks;
bool legacy_paste;
bool power_off_on_close;
};
extern const struct scrcpy_options scrcpy_options_default;
#endif

View File

@@ -372,7 +372,7 @@ bool
recorder_init(struct recorder *recorder,
const char *filename,
enum sc_record_format format,
struct size declared_frame_size) {
struct sc_size declared_frame_size) {
recorder->filename = strdup(filename);
if (!recorder->filename) {
LOGE("Could not strdup filename");

View File

@@ -7,7 +7,7 @@
#include <libavformat/avformat.h>
#include "coords.h"
#include "scrcpy.h"
#include "options.h"
#include "trait/packet_sink.h"
#include "util/queue.h"
#include "util/thread.h"
@@ -25,7 +25,7 @@ struct recorder {
char *filename;
enum sc_record_format format;
AVFormatContext *ctx;
struct size declared_frame_size;
struct sc_size declared_frame_size;
bool header_written;
sc_thread thread;
@@ -44,7 +44,7 @@ struct recorder {
bool
recorder_init(struct recorder *recorder, const char *filename,
enum sc_record_format format, struct size declared_frame_size);
enum sc_record_format format, struct sc_size declared_frame_size);
void
recorder_destroy(struct recorder *recorder);

View File

@@ -57,41 +57,30 @@ struct scrcpy {
struct input_manager input_manager;
};
static inline void
push_event(uint32_t type, const char *name) {
SDL_Event event;
event.type = type;
int ret = SDL_PushEvent(&event);
if (ret < 0) {
LOGE("Could not post %s event: %s", name, SDL_GetError());
// What could we do?
}
}
#define PUSH_EVENT(TYPE) push_event(TYPE, # TYPE)
#ifdef _WIN32
BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
if (ctrl_type == CTRL_C_EVENT) {
SDL_Event event;
event.type = SDL_QUIT;
SDL_PushEvent(&event);
PUSH_EVENT(SDL_QUIT);
return TRUE;
}
return FALSE;
}
#endif // _WIN32
// init SDL and set appropriate hints
static bool
sdl_init_and_configure(bool display, const char *render_driver,
bool disable_screensaver) {
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);
#ifdef _WIN32
// Clean up properly on Ctrl+C on Windows
bool ok = SetConsoleCtrlHandler(windows_ctrl_handler, TRUE);
if (!ok) {
LOGW("Could not set Ctrl+C handler");
}
#endif // _WIN32
if (!display) {
return true;
}
static void
sdl_set_hints(const char *render_driver) {
if (render_driver && !SDL_SetHint(SDL_HINT_RENDER_DRIVER, render_driver)) {
LOGW("Could not set render driver");
@@ -109,6 +98,15 @@ sdl_init_and_configure(bool display, const char *render_driver,
}
#endif
#ifdef SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
// Disable synthetic mouse events from touch events
// Touch events with id SDL_TOUCH_MOUSEID are ignored anyway, but it is
// better not to generate them in the first place.
if (!SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0")) {
LOGW("Could not disable synthetic mouse events");
}
#endif
#ifdef SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
// Disable compositor bypassing on X11
if (!SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0")) {
@@ -120,6 +118,21 @@ sdl_init_and_configure(bool display, const char *render_driver,
if (!SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0")) {
LOGW("Could not disable minimize on focus loss");
}
}
static void
sdl_configure(bool display, bool disable_screensaver) {
#ifdef _WIN32
// Clean up properly on Ctrl+C on Windows
bool ok = SetConsoleCtrlHandler(windows_ctrl_handler, TRUE);
if (!ok) {
LOGW("Could not set Ctrl+C handler");
}
#endif // _WIN32
if (!display) {
return;
}
if (disable_screensaver) {
LOGD("Screensaver disabled");
@@ -128,8 +141,6 @@ sdl_init_and_configure(bool display, const char *render_driver,
LOGD("Screensaver enabled");
SDL_EnableScreenSaver();
}
return true;
}
static bool
@@ -248,9 +259,7 @@ stream_on_eos(struct stream *stream, void *userdata) {
(void) stream;
(void) userdata;
SDL_Event stop_event;
stop_event.type = EVENT_STREAM_STOPPED;
SDL_PushEvent(&stop_event);
PUSH_EVENT(EVENT_STREAM_STOPPED);
}
bool
@@ -258,6 +267,14 @@ scrcpy(struct scrcpy_options *options) {
static struct scrcpy scrcpy;
struct scrcpy *s = &scrcpy;
// Minimal SDL initialization
if (SDL_Init(SDL_INIT_EVENTS)) {
LOGC("Could not initialize SDL: %s", SDL_GetError());
return false;
}
atexit(SDL_Quit);
if (!server_init(&s->server)) {
return false;
}
@@ -278,7 +295,6 @@ scrcpy(struct scrcpy_options *options) {
bool controller_started = false;
bool screen_initialized = false;
bool record = !!options->record_filename;
struct server_params params = {
.serial = options->serial,
.log_level = options->log_level,
@@ -303,15 +319,21 @@ scrcpy(struct scrcpy_options *options) {
server_started = true;
if (!sdl_init_and_configure(options->display, options->render_driver,
options->disable_screensaver)) {
if (options->display) {
sdl_set_hints(options->render_driver);
}
// Initialize SDL video in addition if display is enabled
if (options->display && SDL_Init(SDL_INIT_VIDEO)) {
LOGC("Could not initialize SDL: %s", SDL_GetError());
goto end;
}
char device_name[DEVICE_NAME_FIELD_LENGTH];
struct size frame_size;
sdl_configure(options->display, options->disable_screensaver);
if (!server_connect_to(&s->server, device_name, &frame_size)) {
struct server_info info;
if (!server_connect_to(&s->server, &info)) {
goto end;
}
@@ -334,11 +356,11 @@ scrcpy(struct scrcpy_options *options) {
}
struct recorder *rec = NULL;
if (record) {
if (options->record_filename) {
if (!recorder_init(&s->recorder,
options->record_filename,
options->record_format,
frame_size)) {
info.frame_size)) {
goto end;
}
rec = &s->recorder;
@@ -384,11 +406,11 @@ scrcpy(struct scrcpy_options *options) {
if (options->display) {
const char *window_title =
options->window_title ? options->window_title : device_name;
options->window_title ? options->window_title : info.device_name;
struct screen_params screen_params = {
.window_title = window_title,
.frame_size = frame_size,
.frame_size = info.frame_size,
.always_on_top = options->always_on_top,
.window_x = options->window_x,
.window_y = options->window_y,
@@ -411,8 +433,8 @@ scrcpy(struct scrcpy_options *options) {
#ifdef HAVE_V4L2
if (options->v4l2_device) {
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device, frame_size,
options->v4l2_buffer)) {
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device,
info.frame_size, options->v4l2_buffer)) {
goto end;
}

View File

@@ -4,158 +4,7 @@
#include "common.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "util/tick.h"
enum sc_log_level {
SC_LOG_LEVEL_VERBOSE,
SC_LOG_LEVEL_DEBUG,
SC_LOG_LEVEL_INFO,
SC_LOG_LEVEL_WARN,
SC_LOG_LEVEL_ERROR,
};
enum sc_record_format {
SC_RECORD_FORMAT_AUTO,
SC_RECORD_FORMAT_MP4,
SC_RECORD_FORMAT_MKV,
};
enum sc_lock_video_orientation {
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
// lock the current orientation when scrcpy starts
SC_LOCK_VIDEO_ORIENTATION_INITIAL = -2,
SC_LOCK_VIDEO_ORIENTATION_0 = 0,
SC_LOCK_VIDEO_ORIENTATION_1,
SC_LOCK_VIDEO_ORIENTATION_2,
SC_LOCK_VIDEO_ORIENTATION_3,
};
enum sc_keyboard_input_mode {
SC_KEYBOARD_INPUT_MODE_INJECT,
SC_KEYBOARD_INPUT_MODE_HID,
};
#define SC_MAX_SHORTCUT_MODS 8
enum sc_shortcut_mod {
SC_MOD_LCTRL = 1 << 0,
SC_MOD_RCTRL = 1 << 1,
SC_MOD_LALT = 1 << 2,
SC_MOD_RALT = 1 << 3,
SC_MOD_LSUPER = 1 << 4,
SC_MOD_RSUPER = 1 << 5,
};
struct sc_shortcut_mods {
unsigned data[SC_MAX_SHORTCUT_MODS];
unsigned count;
};
struct sc_port_range {
uint16_t first;
uint16_t last;
};
#define SC_WINDOW_POSITION_UNDEFINED (-0x8000)
struct scrcpy_options {
const char *serial;
const char *crop;
const char *record_filename;
const char *window_title;
const char *push_target;
const char *render_driver;
const char *codec_options;
const char *encoder_name;
const char *v4l2_device;
enum sc_log_level log_level;
enum sc_record_format record_format;
enum sc_keyboard_input_mode keyboard_input_mode;
struct sc_port_range port_range;
struct sc_shortcut_mods shortcut_mods;
uint16_t max_size;
uint32_t bit_rate;
uint16_t max_fps;
enum sc_lock_video_orientation lock_video_orientation;
uint8_t rotation;
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
uint16_t window_width;
uint16_t window_height;
uint32_t display_id;
sc_tick display_buffer;
sc_tick v4l2_buffer;
bool show_touches;
bool fullscreen;
bool always_on_top;
bool control;
bool display;
bool turn_screen_off;
bool prefer_text;
bool window_borderless;
bool mipmaps;
bool stay_awake;
bool force_adb_forward;
bool disable_screensaver;
bool forward_key_repeat;
bool forward_all_clicks;
bool legacy_paste;
bool power_off_on_close;
};
#define SCRCPY_OPTIONS_DEFAULT { \
.serial = NULL, \
.crop = NULL, \
.record_filename = NULL, \
.window_title = NULL, \
.push_target = NULL, \
.render_driver = NULL, \
.codec_options = NULL, \
.encoder_name = NULL, \
.v4l2_device = NULL, \
.log_level = SC_LOG_LEVEL_INFO, \
.record_format = SC_RECORD_FORMAT_AUTO, \
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT, \
.port_range = { \
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \
.last = DEFAULT_LOCAL_PORT_RANGE_LAST, \
}, \
.shortcut_mods = { \
.data = {SC_MOD_LALT, SC_MOD_LSUPER}, \
.count = 2, \
}, \
.max_size = 0, \
.bit_rate = DEFAULT_BIT_RATE, \
.max_fps = 0, \
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED, \
.rotation = 0, \
.window_x = SC_WINDOW_POSITION_UNDEFINED, \
.window_y = SC_WINDOW_POSITION_UNDEFINED, \
.window_width = 0, \
.window_height = 0, \
.display_id = 0, \
.display_buffer = 0, \
.v4l2_buffer = 0, \
.show_touches = false, \
.fullscreen = false, \
.always_on_top = false, \
.control = true, \
.display = true, \
.turn_screen_off = false, \
.prefer_text = false, \
.window_borderless = false, \
.mipmaps = true, \
.stay_awake = false, \
.force_adb_forward = false, \
.disable_screensaver = false, \
.forward_key_repeat = true, \
.forward_all_clicks = false, \
.legacy_paste = false, \
.power_off_on_close = false, \
}
#include "options.h"
bool
scrcpy(struct scrcpy_options *options);

View File

@@ -6,7 +6,7 @@
#include "events.h"
#include "icon.h"
#include "scrcpy.h"
#include "options.h"
#include "video_buffer.h"
#include "util/log.h"
@@ -14,9 +14,9 @@
#define DOWNCAST(SINK) container_of(SINK, struct screen, frame_sink)
static inline struct size
get_rotated_size(struct size size, int rotation) {
struct size rotated_size;
static inline struct sc_size
get_rotated_size(struct sc_size size, int rotation) {
struct sc_size rotated_size;
if (rotation & 1) {
rotated_size.width = size.height;
rotated_size.height = size.width;
@@ -27,26 +27,26 @@ get_rotated_size(struct size size, int rotation) {
return rotated_size;
}
// get the window size in a struct size
static struct size
// get the window size in a struct sc_size
static struct sc_size
get_window_size(const struct screen *screen) {
int width;
int height;
SDL_GetWindowSize(screen->window, &width, &height);
struct size size;
struct sc_size size;
size.width = width;
size.height = height;
return size;
}
static struct point
static struct sc_point
get_window_position(const struct screen *screen) {
int x;
int y;
SDL_GetWindowPosition(screen->window, &x, &y);
struct point point;
struct sc_point point;
point.x = x;
point.y = y;
return point;
@@ -54,7 +54,7 @@ get_window_position(const struct screen *screen) {
// set the window size to be applied when fullscreen is disabled
static void
set_window_size(struct screen *screen, struct size new_size) {
set_window_size(struct screen *screen, struct sc_size new_size) {
assert(!screen->fullscreen);
assert(!screen->maximized);
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
@@ -62,7 +62,7 @@ set_window_size(struct screen *screen, struct size new_size) {
// get the preferred display bounds (i.e. the screen bounds with some margins)
static bool
get_preferred_display_bounds(struct size *bounds) {
get_preferred_display_bounds(struct sc_size *bounds) {
SDL_Rect rect;
#ifdef SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS
# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayUsableBounds((i), (r))
@@ -80,7 +80,7 @@ get_preferred_display_bounds(struct size *bounds) {
}
static bool
is_optimal_size(struct size current_size, struct size content_size) {
is_optimal_size(struct sc_size current_size, struct sc_size content_size) {
// The size is optimal if we can recompute one dimension of the current
// size from the other
return current_size.height == current_size.width * content_size.height
@@ -94,16 +94,16 @@ is_optimal_size(struct size current_size, struct size content_size) {
// crops the black borders)
// - it keeps the aspect ratio
// - it scales down to make it fit in the display_size
static struct size
get_optimal_size(struct size current_size, struct size content_size) {
static struct sc_size
get_optimal_size(struct sc_size current_size, struct sc_size content_size) {
if (content_size.width == 0 || content_size.height == 0) {
// avoid division by 0
return current_size;
}
struct size window_size;
struct sc_size window_size;
struct size display_size;
struct sc_size display_size;
if (!get_preferred_display_bounds(&display_size)) {
// could not get display bounds, do not constraint the size
window_size.width = current_size.width;
@@ -135,10 +135,10 @@ get_optimal_size(struct size current_size, struct size content_size) {
// initially, there is no current size, so use the frame size as current size
// req_width and req_height, if not 0, are the sizes requested by the user
static inline struct size
get_initial_optimal_size(struct size content_size, uint16_t req_width,
static inline struct sc_size
get_initial_optimal_size(struct sc_size content_size, uint16_t req_width,
uint16_t req_height) {
struct size window_size;
struct sc_size window_size;
if (!req_width && !req_height) {
window_size = get_optimal_size(content_size, content_size);
} else {
@@ -166,9 +166,9 @@ screen_update_content_rect(struct screen *screen) {
int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
struct size content_size = screen->content_size;
struct sc_size content_size = screen->content_size;
// The drawable size is the window size * the HiDPI scale
struct size drawable_size = {dw, dh};
struct sc_size drawable_size = {dw, dh};
SDL_Rect *rect = &screen->rect;
@@ -200,7 +200,7 @@ screen_update_content_rect(struct screen *screen) {
static inline SDL_Texture *
create_texture(struct screen *screen) {
SDL_Renderer *renderer = screen->renderer;
struct size size = screen->frame_size;
struct sc_size size = screen->frame_size;
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
SDL_TEXTUREACCESS_STREAMING,
size.width, size.height);
@@ -282,17 +282,33 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
(void) vb;
struct screen *screen = userdata;
// event_failed implies previous_skipped (the previous frame may not have
// been consumed if the event was not sent)
assert(!screen->event_failed || previous_skipped);
bool need_new_event;
if (previous_skipped) {
fps_counter_add_skipped_frame(&screen->fps_counter);
// The EVENT_NEW_FRAME triggered for the previous frame will consume
// this new frame instead
// this new frame instead, unless the previous event failed
need_new_event = screen->event_failed;
} else {
need_new_event = true;
}
if (need_new_event) {
static SDL_Event new_frame_event = {
.type = EVENT_NEW_FRAME,
};
// Post the event on the UI thread
SDL_PushEvent(&new_frame_event);
int ret = SDL_PushEvent(&new_frame_event);
if (ret < 0) {
LOGW("Could not post new frame event: %s", SDL_GetError());
screen->event_failed = true;
} else {
screen->event_failed = false;
}
}
}
@@ -302,6 +318,7 @@ screen_init(struct screen *screen, const struct screen_params *params) {
screen->has_frame = false;
screen->fullscreen = false;
screen->maximized = false;
screen->event_failed = false;
static const struct sc_video_buffer_callbacks cbs = {
.on_new_frame = sc_video_buffer_on_new_frame,
@@ -330,13 +347,13 @@ screen_init(struct screen *screen, const struct screen_params *params) {
if (screen->rotation) {
LOGI("Initial display rotation set to %u", screen->rotation);
}
struct size content_size =
struct sc_size content_size =
get_rotated_size(screen->frame_size, screen->rotation);
screen->content_size = content_size;
struct size window_size = get_initial_optimal_size(content_size,
params->window_width,
params->window_height);
struct sc_size window_size =
get_initial_optimal_size(content_size,params->window_width,
params->window_height);
uint32_t window_flags = SDL_WINDOW_HIDDEN
| SDL_WINDOW_RESIZABLE
| SDL_WINDOW_ALLOW_HIGHDPI;
@@ -508,10 +525,10 @@ screen_destroy(struct screen *screen) {
}
static void
resize_for_content(struct screen *screen, struct size old_content_size,
struct size new_content_size) {
struct size window_size = get_window_size(screen);
struct size target_size = {
resize_for_content(struct screen *screen, struct sc_size old_content_size,
struct sc_size new_content_size) {
struct sc_size window_size = get_window_size(screen);
struct sc_size target_size = {
.width = (uint32_t) window_size.width * new_content_size.width
/ old_content_size.width,
.height = (uint32_t) window_size.height * new_content_size.height
@@ -522,7 +539,7 @@ resize_for_content(struct screen *screen, struct size old_content_size,
}
static void
set_content_size(struct screen *screen, struct size new_content_size) {
set_content_size(struct screen *screen, struct sc_size new_content_size) {
if (!screen->fullscreen && !screen->maximized) {
resize_for_content(screen, screen->content_size, new_content_size);
} else if (!screen->resize_pending) {
@@ -553,7 +570,7 @@ screen_set_rotation(struct screen *screen, unsigned rotation) {
return;
}
struct size new_content_size =
struct sc_size new_content_size =
get_rotated_size(screen->frame_size, rotation);
set_content_size(screen, new_content_size);
@@ -566,7 +583,7 @@ screen_set_rotation(struct screen *screen, unsigned rotation) {
// recreate the texture and resize the window if the frame size has changed
static bool
prepare_for_frame(struct screen *screen, struct size new_frame_size) {
prepare_for_frame(struct screen *screen, struct sc_size new_frame_size) {
if (screen->frame_size.width != new_frame_size.width
|| screen->frame_size.height != new_frame_size.height) {
// frame dimension changed, destroy texture
@@ -574,7 +591,7 @@ prepare_for_frame(struct screen *screen, struct size new_frame_size) {
screen->frame_size = new_frame_size;
struct size new_content_size =
struct sc_size new_content_size =
get_rotated_size(new_frame_size, screen->rotation);
set_content_size(screen, new_content_size);
@@ -615,7 +632,7 @@ screen_update_frame(struct screen *screen) {
fps_counter_add_rendered_frame(&screen->fps_counter);
struct size new_frame_size = {frame->width, frame->height};
struct sc_size new_frame_size = {frame->width, frame->height};
if (!prepare_for_frame(screen, new_frame_size)) {
return false;
}
@@ -682,10 +699,10 @@ screen_resize_to_fit(struct screen *screen) {
return;
}
struct point point = get_window_position(screen);
struct size window_size = get_window_size(screen);
struct sc_point point = get_window_position(screen);
struct sc_size window_size = get_window_size(screen);
struct size optimal_size =
struct sc_size optimal_size =
get_optimal_size(window_size, screen->content_size);
// Center the window related to the device screen
@@ -711,7 +728,7 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
screen->maximized = false;
}
struct size content_size = screen->content_size;
struct sc_size content_size = screen->content_size;
SDL_SetWindowSize(screen->window, content_size.width, content_size.height);
LOGD("Resized to pixel-perfect: %ux%u", content_size.width,
content_size.height);
@@ -766,7 +783,7 @@ screen_handle_event(struct screen *screen, SDL_Event *event) {
return false;
}
struct point
struct sc_point
screen_convert_drawable_to_frame_coords(struct screen *screen,
int32_t x, int32_t y) {
unsigned rotation = screen->rotation;
@@ -780,7 +797,7 @@ screen_convert_drawable_to_frame_coords(struct screen *screen,
y = (int64_t) (y - screen->rect.y) * h / screen->rect.h;
// rotate
struct point result;
struct sc_point result;
switch (rotation) {
case 0:
result.x = x;
@@ -803,7 +820,7 @@ screen_convert_drawable_to_frame_coords(struct screen *screen,
return result;
}
struct point
struct sc_point
screen_convert_window_to_frame_coords(struct screen *screen,
int32_t x, int32_t y) {
screen_hidpi_scale_coords(screen, &x, &y);

View File

@@ -27,13 +27,13 @@ struct screen {
SDL_Renderer *renderer;
SDL_Texture *texture;
struct sc_opengl gl;
struct size frame_size;
struct size content_size; // rotated frame_size
struct sc_size frame_size;
struct sc_size content_size; // rotated frame_size
bool resize_pending; // resize requested while fullscreen or maximized
// The content size the last time the window was not maximized or
// fullscreen (meaningful only when resize_pending is true)
struct size windowed_content_size;
struct sc_size windowed_content_size;
// client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise)
unsigned rotation;
@@ -44,12 +44,14 @@ struct screen {
bool maximized;
bool mipmaps;
bool event_failed; // in case SDL_PushEvent() returned an error
AVFrame *frame;
};
struct screen_params {
const char *window_title;
struct size frame_size;
struct sc_size frame_size;
bool always_on_top;
int16_t window_x;
@@ -120,13 +122,13 @@ screen_handle_event(struct screen *screen, SDL_Event *event);
// convert point from window coordinates to frame coordinates
// x and y are expressed in pixels
struct point
struct sc_point
screen_convert_window_to_frame_coords(struct screen *screen,
int32_t x, int32_t y);
// convert point from drawable coordinates to frame coordinates
// x and y are expressed in pixels
struct point
struct sc_point
screen_convert_drawable_to_frame_coords(struct screen *screen,
int32_t x, int32_t y);

View File

@@ -8,6 +8,7 @@
#include <SDL2/SDL_platform.h>
#include "adb.h"
#include "util/file.h"
#include "util/log.h"
#include "util/net.h"
#include "util/str_util.h"
@@ -48,7 +49,7 @@ get_server_path(void) {
return NULL;
}
#else
char *server_path = get_local_file_path(SERVER_FILENAME);
char *server_path = sc_file_get_local_path(SERVER_FILENAME);
if (!server_path) {
LOGE("Could not get local file path, "
"using " SERVER_FILENAME " from current directory");
@@ -67,38 +68,38 @@ push_server(const char *serial) {
if (!server_path) {
return false;
}
if (!is_regular_file(server_path)) {
if (!sc_file_is_regular(server_path)) {
LOGE("'%s' does not exist or is not a regular file\n", server_path);
free(server_path);
return false;
}
process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH);
sc_pid pid = adb_push(serial, server_path, DEVICE_SERVER_PATH);
free(server_path);
return process_check_success(process, "adb push", true);
return sc_process_check_success(pid, "adb push", true);
}
static bool
enable_tunnel_reverse(const char *serial, uint16_t local_port) {
process_t process = adb_reverse(serial, SOCKET_NAME, local_port);
return process_check_success(process, "adb reverse", true);
sc_pid pid = adb_reverse(serial, SOCKET_NAME, local_port);
return sc_process_check_success(pid, "adb reverse", true);
}
static bool
disable_tunnel_reverse(const char *serial) {
process_t process = adb_reverse_remove(serial, SOCKET_NAME);
return process_check_success(process, "adb reverse --remove", true);
sc_pid pid = adb_reverse_remove(serial, SOCKET_NAME);
return sc_process_check_success(pid, "adb reverse --remove", true);
}
static bool
enable_tunnel_forward(const char *serial, uint16_t local_port) {
process_t process = adb_forward(serial, local_port, SOCKET_NAME);
return process_check_success(process, "adb forward", true);
sc_pid pid = adb_forward(serial, local_port, SOCKET_NAME);
return sc_process_check_success(pid, "adb forward", true);
}
static bool
disable_tunnel_forward(const char *serial, uint16_t local_port) {
process_t process = adb_forward_remove(serial, local_port);
return process_check_success(process, "adb forward --remove", true);
sc_pid pid = adb_forward_remove(serial, local_port);
return sc_process_check_success(pid, "adb forward --remove", true);
}
static bool
@@ -227,7 +228,7 @@ log_level_to_server_string(enum sc_log_level level) {
}
}
static process_t
static sc_pid
execute_server(struct server *server, const struct server_params *params) {
char max_size_string[6];
char bit_rate_string[11];
@@ -323,21 +324,10 @@ connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
return SC_INVALID_SOCKET;
}
static void
close_socket(sc_socket socket) {
assert(socket != SC_INVALID_SOCKET);
net_shutdown(socket, SHUT_RDWR);
if (!net_close(socket)) {
LOGW("Could not close socket");
}
}
bool
server_init(struct server *server) {
server->serial = NULL;
server->process = PROCESS_NONE;
atomic_flag_clear_explicit(&server->server_socket_closed,
memory_order_relaxed);
server->process = SC_PROCESS_NONE;
bool ok = sc_mutex_init(&server->mutex);
if (!ok) {
@@ -367,7 +357,7 @@ server_init(struct server *server) {
static int
run_wait_server(void *data) {
struct server *server = data;
process_wait(server->process, false); // ignore exit code
sc_process_wait(server->process, false); // ignore exit code
sc_mutex_lock(&server->mutex);
server->process_terminated = true;
@@ -376,12 +366,11 @@ run_wait_server(void *data) {
// no need for synchronization, server_socket is initialized before this
// thread was created
if (server->server_socket != SC_INVALID_SOCKET
&& !atomic_flag_test_and_set(&server->server_socket_closed)) {
// On Linux, accept() is unblocked by shutdown(), but on Windows, it is
// unblocked by closesocket(). Therefore, call both (close_socket()).
close_socket(server->server_socket);
if (server->server_socket != SC_INVALID_SOCKET) {
// Unblock any accept()
net_interrupt(server->server_socket);
}
LOGD("Server terminated");
return 0;
}
@@ -407,7 +396,7 @@ server_start(struct server *server, const struct server_params *params) {
// server will connect to our server socket
server->process = execute_server(server, params);
if (server->process == PROCESS_NONE) {
if (server->process == SC_PROCESS_NONE) {
goto error;
}
@@ -420,8 +409,8 @@ server_start(struct server *server, const struct server_params *params) {
bool ok = sc_thread_create(&server->wait_server_thread, run_wait_server,
"wait-server", server);
if (!ok) {
process_terminate(server->process);
process_wait(server->process, true); // ignore exit code
sc_process_terminate(server->process);
sc_process_wait(server->process, true); // ignore exit code
goto error;
}
@@ -430,22 +419,15 @@ server_start(struct server *server, const struct server_params *params) {
return true;
error:
if (!server->tunnel_forward) {
bool was_closed =
atomic_flag_test_and_set(&server->server_socket_closed);
// the thread is not started, the flag could not be already set
assert(!was_closed);
(void) was_closed;
close_socket(server->server_socket);
}
// The server socket (if any) will be closed on server_destroy()
disable_tunnel(server);
return false;
}
static bool
device_read_info(sc_socket device_socket, char *device_name,
struct size *size) {
device_read_info(sc_socket device_socket, struct server_info *info) {
unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4];
ssize_t r = net_recv_all(device_socket, buf, sizeof(buf));
if (r < DEVICE_NAME_FIELD_LENGTH + 4) {
@@ -454,18 +436,17 @@ device_read_info(sc_socket device_socket, char *device_name,
}
// 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 and strlen(buf) < DEVICE_NAME_FIELD_LENGTH
strcpy(device_name, (char *) buf);
size->width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8)
| buf[DEVICE_NAME_FIELD_LENGTH + 1];
size->height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8)
| buf[DEVICE_NAME_FIELD_LENGTH + 3];
memcpy(info->device_name, (char *) buf, sizeof(info->device_name));
info->frame_size.width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8)
| buf[DEVICE_NAME_FIELD_LENGTH + 1];
info->frame_size.height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8)
| buf[DEVICE_NAME_FIELD_LENGTH + 3];
return true;
}
bool
server_connect_to(struct server *server, char *device_name, struct size *size) {
server_connect_to(struct server *server, struct server_info *info) {
if (!server->tunnel_forward) {
server->video_socket = net_accept(server->server_socket);
if (server->video_socket == SC_INVALID_SOCKET) {
@@ -479,11 +460,11 @@ server_connect_to(struct server *server, char *device_name, struct size *size) {
}
// we don't need the server socket anymore
if (!atomic_flag_test_and_set(&server->server_socket_closed)) {
// close it from here
close_socket(server->server_socket);
// otherwise, it is closed by run_wait_server()
if (!net_close(server->server_socket)) {
LOGW("Could not close server socket on connect");
}
// Do not attempt to close it again on server_destroy()
server->server_socket = SC_INVALID_SOCKET;
} else {
uint32_t attempts = 100;
uint32_t delay = 100; // ms
@@ -506,23 +487,28 @@ server_connect_to(struct server *server, char *device_name, struct size *size) {
server->tunnel_enabled = false;
// The sockets will be closed on stop if device_read_info() fails
return device_read_info(server->video_socket, device_name, size);
return device_read_info(server->video_socket, info);
}
void
server_stop(struct server *server) {
if (server->server_socket != SC_INVALID_SOCKET
&& !atomic_flag_test_and_set(&server->server_socket_closed)) {
close_socket(server->server_socket);
if (server->server_socket != SC_INVALID_SOCKET) {
if (!net_interrupt(server->server_socket)) {
LOGW("Could not interrupt server socket");
}
}
if (server->video_socket != SC_INVALID_SOCKET) {
close_socket(server->video_socket);
if (!net_interrupt(server->video_socket)) {
LOGW("Could not interrupt video socket");
}
}
if (server->control_socket != SC_INVALID_SOCKET) {
close_socket(server->control_socket);
if (!net_interrupt(server->control_socket)) {
LOGW("Could not interrupt control socket");
}
}
assert(server->process != PROCESS_NONE);
assert(server->process != SC_PROCESS_NONE);
if (server->tunnel_enabled) {
// ignore failure
@@ -547,15 +533,30 @@ server_stop(struct server *server) {
// The process is terminated, but not reaped (closed) yet, so its PID
// is still valid.
LOGW("Killing the server...");
process_terminate(server->process);
sc_process_terminate(server->process);
}
sc_thread_join(&server->wait_server_thread, NULL);
process_close(server->process);
sc_process_close(server->process);
}
void
server_destroy(struct server *server) {
if (server->server_socket != SC_INVALID_SOCKET) {
if (!net_close(server->server_socket)) {
LOGW("Could not close server socket");
}
}
if (server->video_socket != SC_INVALID_SOCKET) {
if (!net_close(server->video_socket)) {
LOGW("Could not close video socket");
}
}
if (server->control_socket != SC_INVALID_SOCKET) {
if (!net_close(server->control_socket)) {
LOGW("Could not close control socket");
}
}
free(server->serial);
sc_cond_destroy(&server->process_terminated_cond);
sc_mutex_destroy(&server->mutex);

View File

@@ -9,16 +9,21 @@
#include "adb.h"
#include "coords.h"
#include "scrcpy.h"
#include "options.h"
#include "util/log.h"
#include "util/net.h"
#include "util/thread.h"
#define DEVICE_NAME_FIELD_LENGTH 64
struct server_info {
char device_name[DEVICE_NAME_FIELD_LENGTH];
struct sc_size frame_size;
};
struct server {
char *serial;
process_t process;
sc_pid process;
sc_thread wait_server_thread;
atomic_flag server_socket_closed;
sc_mutex mutex;
sc_cond process_terminated_cond;
@@ -59,11 +64,9 @@ server_init(struct server *server);
bool
server_start(struct server *server, const struct server_params *params);
#define DEVICE_NAME_FIELD_LENGTH 64
// block until the communication with the server is established
// device_name must point to a buffer of at least DEVICE_NAME_FIELD_LENGTH bytes
bool
server_connect_to(struct server *server, char *device_name, struct size *size);
server_connect_to(struct server *server, struct server_info *info);
// disconnect and kill the server process
void

75
app/src/sys/unix/file.c Normal file
View File

@@ -0,0 +1,75 @@
#include "util/file.h"
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
bool
sc_file_executable_exists(const char *file) {
char *path = getenv("PATH");
if (!path)
return false;
path = strdup(path);
if (!path)
return false;
bool ret = false;
size_t file_len = strlen(file);
char *saveptr;
for (char *dir = strtok_r(path, ":", &saveptr); dir;
dir = strtok_r(NULL, ":", &saveptr)) {
size_t dir_len = strlen(dir);
char *fullpath = malloc(dir_len + file_len + 2);
if (!fullpath)
continue;
memcpy(fullpath, dir, dir_len);
fullpath[dir_len] = '/';
memcpy(fullpath + dir_len + 1, file, file_len + 1);
struct stat sb;
bool fullpath_executable = stat(fullpath, &sb) == 0 &&
sb.st_mode & S_IXUSR;
free(fullpath);
if (fullpath_executable) {
ret = true;
break;
}
}
free(path);
return ret;
}
char *
sc_file_get_executable_path(void) {
// <https://stackoverflow.com/a/1024937/1987178>
#ifdef __linux__
char buf[PATH_MAX + 1]; // +1 for the null byte
ssize_t len = readlink("/proc/self/exe", buf, PATH_MAX);
if (len == -1) {
perror("readlink");
return NULL;
}
buf[len] = '\0';
return strdup(buf);
#else
// in practice, we only need this feature for portable builds, only used on
// Windows, so we don't care implementing it for every platform
// (it's useful to have a working version on Linux for debugging though)
return NULL;
#endif
}
bool
sc_file_is_regular(const char *path) {
struct stat path_stat;
if (stat(path, &path_stat)) {
perror("stat");
return false;
}
return S_ISREG(path_stat.st_mode);
}

View File

@@ -3,56 +3,16 @@
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "util/log.h"
bool
search_executable(const char *file) {
char *path = getenv("PATH");
if (!path)
return false;
path = strdup(path);
if (!path)
return false;
bool ret = false;
size_t file_len = strlen(file);
char *saveptr;
for (char *dir = strtok_r(path, ":", &saveptr); dir;
dir = strtok_r(NULL, ":", &saveptr)) {
size_t dir_len = strlen(dir);
char *fullpath = malloc(dir_len + file_len + 2);
if (!fullpath)
continue;
memcpy(fullpath, dir, dir_len);
fullpath[dir_len] = '/';
memcpy(fullpath + dir_len + 1, file, file_len + 1);
struct stat sb;
bool fullpath_executable = stat(fullpath, &sb) == 0 &&
sb.st_mode & S_IXUSR;
free(fullpath);
if (fullpath_executable) {
ret = true;
break;
}
}
free(path);
return ret;
}
enum process_result
process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin,
int *pipe_stdout, int *pipe_stderr) {
enum sc_process_result
sc_process_execute_p(const char *const argv[], sc_pid *pid,
int *pin, int *pout, int *perr) {
int in[2];
int out[2];
int err[2];
@@ -60,44 +20,44 @@ process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin,
if (pipe(internal) == -1) {
perror("pipe");
return PROCESS_ERROR_GENERIC;
return SC_PROCESS_ERROR_GENERIC;
}
if (pipe_stdin) {
if (pin) {
if (pipe(in) == -1) {
perror("pipe");
close(internal[0]);
close(internal[1]);
return PROCESS_ERROR_GENERIC;
return SC_PROCESS_ERROR_GENERIC;
}
}
if (pipe_stdout) {
if (pout) {
if (pipe(out) == -1) {
perror("pipe");
// clean up
if (pipe_stdin) {
if (pin) {
close(in[0]);
close(in[1]);
}
close(internal[0]);
close(internal[1]);
return PROCESS_ERROR_GENERIC;
return SC_PROCESS_ERROR_GENERIC;
}
}
if (pipe_stderr) {
if (perr) {
if (pipe(err) == -1) {
perror("pipe");
// clean up
if (pipe_stdout) {
if (pout) {
close(out[0]);
close(out[1]);
}
if (pipe_stdin) {
if (pin) {
close(in[0]);
close(in[1]);
}
close(internal[0]);
close(internal[1]);
return PROCESS_ERROR_GENERIC;
return SC_PROCESS_ERROR_GENERIC;
}
}
@@ -105,39 +65,39 @@ process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin,
if (*pid == -1) {
perror("fork");
// clean up
if (pipe_stderr) {
if (perr) {
close(err[0]);
close(err[1]);
}
if (pipe_stdout) {
if (pout) {
close(out[0]);
close(out[1]);
}
if (pipe_stdin) {
if (pin) {
close(in[0]);
close(in[1]);
}
close(internal[0]);
close(internal[1]);
return PROCESS_ERROR_GENERIC;
return SC_PROCESS_ERROR_GENERIC;
}
if (*pid == 0) {
if (pipe_stdin) {
if (pin) {
if (in[0] != STDIN_FILENO) {
dup2(in[0], STDIN_FILENO);
close(in[0]);
}
close(in[1]);
}
if (pipe_stdout) {
if (pout) {
if (out[1] != STDOUT_FILENO) {
dup2(out[1], STDOUT_FILENO);
close(out[1]);
}
close(out[0]);
}
if (pipe_stderr) {
if (perr) {
if (err[1] != STDERR_FILENO) {
dup2(err[1], STDERR_FILENO);
close(err[1]);
@@ -145,15 +105,15 @@ process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin,
close(err[0]);
}
close(internal[0]);
enum process_result err;
enum sc_process_result err;
if (fcntl(internal[1], F_SETFD, FD_CLOEXEC) == 0) {
execvp(argv[0], (char *const *) argv);
perror("exec");
err = errno == ENOENT ? PROCESS_ERROR_MISSING_BINARY
: PROCESS_ERROR_GENERIC;
err = errno == ENOENT ? SC_PROCESS_ERROR_MISSING_BINARY
: SC_PROCESS_ERROR_GENERIC;
} else {
perror("fcntl");
err = PROCESS_ERROR_GENERIC;
err = SC_PROCESS_ERROR_GENERIC;
}
// send err to the parent
if (write(internal[1], &err, sizeof(err)) == -1) {
@@ -168,38 +128,33 @@ process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin,
close(internal[1]);
enum process_result res = PROCESS_SUCCESS;
enum sc_process_result res = SC_PROCESS_SUCCESS;
// wait for EOF or receive err from child
if (read(internal[0], &res, sizeof(res)) == -1) {
perror("read");
res = PROCESS_ERROR_GENERIC;
res = SC_PROCESS_ERROR_GENERIC;
}
close(internal[0]);
if (pipe_stdin) {
if (pin) {
close(in[0]);
*pipe_stdin = in[1];
*pin = in[1];
}
if (pipe_stdout) {
*pipe_stdout = out[0];
if (pout) {
*pout = out[0];
close(out[1]);
}
if (pipe_stderr) {
*pipe_stderr = err[0];
if (perr) {
*perr = err[0];
close(err[1]);
}
return res;
}
enum process_result
process_execute(const char *const argv[], pid_t *pid) {
return process_execute_redirect(argv, pid, NULL, NULL, NULL);
}
bool
process_terminate(pid_t pid) {
sc_process_terminate(pid_t pid) {
if (pid <= 0) {
LOGC("Requested to kill %d, this is an error. Please report the bug.\n",
(int) pid);
@@ -208,8 +163,8 @@ process_terminate(pid_t pid) {
return kill(pid, SIGKILL) != -1;
}
exit_code_t
process_wait(pid_t pid, bool close) {
sc_exit_code
sc_process_wait(pid_t pid, bool close) {
int code;
int options = WEXITED;
if (!close) {
@@ -220,7 +175,7 @@ process_wait(pid_t pid, bool close) {
int r = waitid(P_PID, pid, &info, options);
if (r == -1 || info.si_code != CLD_EXITED) {
// could not wait, or exited unexpectedly, probably by a signal
code = NO_EXIT_CODE;
code = SC_EXIT_CODE_NONE;
} else {
code = info.si_status;
}
@@ -228,48 +183,17 @@ process_wait(pid_t pid, bool close) {
}
void
process_close(pid_t pid) {
process_wait(pid, true); // ignore exit code
}
char *
get_executable_path(void) {
// <https://stackoverflow.com/a/1024937/1987178>
#ifdef __linux__
char buf[PATH_MAX + 1]; // +1 for the null byte
ssize_t len = readlink("/proc/self/exe", buf, PATH_MAX);
if (len == -1) {
perror("readlink");
return NULL;
}
buf[len] = '\0';
return strdup(buf);
#else
// in practice, we only need this feature for portable builds, only used on
// Windows, so we don't care implementing it for every platform
// (it's useful to have a working version on Linux for debugging though)
return NULL;
#endif
}
bool
is_regular_file(const char *path) {
struct stat path_stat;
if (stat(path, &path_stat)) {
perror("stat");
return false;
}
return S_ISREG(path_stat.st_mode);
sc_process_close(pid_t pid) {
sc_process_wait(pid, true); // ignore exit code
}
ssize_t
read_pipe(int pipe, char *data, size_t len) {
sc_pipe_read(int pipe, char *data, size_t len) {
return read(pipe, data, len);
}
void
close_pipe(int pipe) {
sc_pipe_close(int pipe) {
if (close(pipe)) {
perror("close pipe");
}

43
app/src/sys/win/file.c Normal file
View File

@@ -0,0 +1,43 @@
#include "util/file.h"
#include <windows.h>
#include <sys/stat.h>
#include "util/log.h"
#include "util/str_util.h"
char *
sc_file_get_executable_path(void) {
HMODULE hModule = GetModuleHandleW(NULL);
if (!hModule) {
return NULL;
}
WCHAR buf[MAX_PATH + 1]; // +1 for the null byte
int len = GetModuleFileNameW(hModule, buf, MAX_PATH);
if (!len) {
return NULL;
}
buf[len] = '\0';
return utf8_from_wide_char(buf);
}
bool
sc_file_is_regular(const char *path) {
wchar_t *wide_path = utf8_to_wide_char(path);
if (!wide_path) {
LOGC("Could not allocate wide char string");
return false;
}
struct _stat path_stat;
int r = _wstat(wide_path, &path_stat);
free(wide_path);
if (r) {
perror("stat");
return false;
}
return S_ISREG(path_stat.st_mode);
}

View File

@@ -1,7 +1,6 @@
#include "util/process.h"
#include <assert.h>
#include <sys/stat.h>
#include "util/log.h"
#include "util/str_util.h"
@@ -16,17 +15,16 @@ build_cmd(char *cmd, size_t len, const char *const argv[]) {
// (don't handle escaping nor quotes)
size_t ret = xstrjoin(cmd, argv, ' ', len);
if (ret >= len) {
LOGE("Command too long (%" PRIsizet " chars)", len - 1);
LOGE("Command too long (%" SC_PRIsizet " chars)", len - 1);
return false;
}
return true;
}
enum process_result
process_execute_redirect(const char *const argv[], HANDLE *handle,
HANDLE *pipe_stdin, HANDLE *pipe_stdout,
HANDLE *pipe_stderr) {
enum process_result ret = PROCESS_ERROR_GENERIC;
enum sc_process_result
sc_process_execute_p(const char *const argv[], HANDLE *handle,
HANDLE *pin, HANDLE *pout, HANDLE *perr) {
enum sc_process_result ret = SC_PROCESS_ERROR_GENERIC;
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
@@ -36,32 +34,32 @@ process_execute_redirect(const char *const argv[], HANDLE *handle,
HANDLE stdin_read_handle;
HANDLE stdout_write_handle;
HANDLE stderr_write_handle;
if (pipe_stdin) {
if (!CreatePipe(&stdin_read_handle, pipe_stdin, &sa, 0)) {
if (pin) {
if (!CreatePipe(&stdin_read_handle, pin, &sa, 0)) {
perror("pipe");
return PROCESS_ERROR_GENERIC;
return SC_PROCESS_ERROR_GENERIC;
}
if (!SetHandleInformation(*pipe_stdin, HANDLE_FLAG_INHERIT, 0)) {
if (!SetHandleInformation(*pin, HANDLE_FLAG_INHERIT, 0)) {
LOGE("SetHandleInformation stdin failed");
goto error_close_stdin;
}
}
if (pipe_stdout) {
if (!CreatePipe(pipe_stdout, &stdout_write_handle, &sa, 0)) {
if (pout) {
if (!CreatePipe(pout, &stdout_write_handle, &sa, 0)) {
perror("pipe");
goto error_close_stdin;
}
if (!SetHandleInformation(*pipe_stdout, HANDLE_FLAG_INHERIT, 0)) {
if (!SetHandleInformation(*pout, HANDLE_FLAG_INHERIT, 0)) {
LOGE("SetHandleInformation stdout failed");
goto error_close_stdout;
}
}
if (pipe_stderr) {
if (!CreatePipe(pipe_stderr, &stderr_write_handle, &sa, 0)) {
if (perr) {
if (!CreatePipe(perr, &stderr_write_handle, &sa, 0)) {
perror("pipe");
goto error_close_stdout;
}
if (!SetHandleInformation(*pipe_stderr, HANDLE_FLAG_INHERIT, 0)) {
if (!SetHandleInformation(*perr, HANDLE_FLAG_INHERIT, 0)) {
LOGE("SetHandleInformation stderr failed");
goto error_close_stderr;
}
@@ -71,15 +69,15 @@ process_execute_redirect(const char *const argv[], HANDLE *handle,
PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
if (pipe_stdin || pipe_stdout || pipe_stderr) {
if (pin || pout || perr) {
si.dwFlags = STARTF_USESTDHANDLES;
if (pipe_stdin) {
if (pin) {
si.hStdInput = stdin_read_handle;
}
if (pipe_stdout) {
if (pout) {
si.hStdOutput = stdout_write_handle;
}
if (pipe_stderr) {
if (perr) {
si.hStdError = stderr_write_handle;
}
}
@@ -103,63 +101,58 @@ process_execute_redirect(const char *const argv[], HANDLE *handle,
*handle = NULL;
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
ret = PROCESS_ERROR_MISSING_BINARY;
ret = SC_PROCESS_ERROR_MISSING_BINARY;
}
goto error_close_stderr;
}
// These handles are used by the child process, close them for this process
if (pipe_stdin) {
if (pin) {
CloseHandle(stdin_read_handle);
}
if (pipe_stdout) {
if (pout) {
CloseHandle(stdout_write_handle);
}
if (pipe_stderr) {
if (perr) {
CloseHandle(stderr_write_handle);
}
free(wide);
*handle = pi.hProcess;
return PROCESS_SUCCESS;
return SC_PROCESS_SUCCESS;
error_close_stderr:
if (pipe_stderr) {
CloseHandle(*pipe_stderr);
if (perr) {
CloseHandle(*perr);
CloseHandle(stderr_write_handle);
}
error_close_stdout:
if (pipe_stdout) {
CloseHandle(*pipe_stdout);
if (pout) {
CloseHandle(*pout);
CloseHandle(stdout_write_handle);
}
error_close_stdin:
if (pipe_stdin) {
CloseHandle(*pipe_stdin);
if (pin) {
CloseHandle(*pin);
CloseHandle(stdin_read_handle);
}
return ret;
}
enum process_result
process_execute(const char *const argv[], HANDLE *handle) {
return process_execute_redirect(argv, handle, NULL, NULL, NULL);
}
bool
process_terminate(HANDLE handle) {
sc_process_terminate(HANDLE handle) {
return TerminateProcess(handle, 1);
}
exit_code_t
process_wait(HANDLE handle, bool close) {
sc_exit_code
sc_process_wait(HANDLE handle, bool close) {
DWORD code;
if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0
|| !GetExitCodeProcess(handle, &code)) {
// could not wait or retrieve the exit code
code = NO_EXIT_CODE; // max value, it's unsigned
code = SC_EXIT_CODE_NONE;
}
if (close) {
CloseHandle(handle);
@@ -168,48 +161,14 @@ process_wait(HANDLE handle, bool close) {
}
void
process_close(HANDLE handle) {
sc_process_close(HANDLE handle) {
bool closed = CloseHandle(handle);
assert(closed);
(void) closed;
}
char *
get_executable_path(void) {
HMODULE hModule = GetModuleHandleW(NULL);
if (!hModule) {
return NULL;
}
WCHAR buf[MAX_PATH + 1]; // +1 for the null byte
int len = GetModuleFileNameW(hModule, buf, MAX_PATH);
if (!len) {
return NULL;
}
buf[len] = '\0';
return utf8_from_wide_char(buf);
}
bool
is_regular_file(const char *path) {
wchar_t *wide_path = utf8_to_wide_char(path);
if (!wide_path) {
LOGC("Could not allocate wide char string");
return false;
}
struct _stat path_stat;
int r = _wstat(wide_path, &path_stat);
free(wide_path);
if (r) {
perror("stat");
return false;
}
return S_ISREG(path_stat.st_mode);
}
ssize_t
read_pipe(HANDLE pipe, char *data, size_t len) {
sc_read_pipe(HANDLE pipe, char *data, size_t len) {
DWORD r;
if (!ReadFile(pipe, data, len, &r, NULL)) {
return -1;
@@ -218,7 +177,7 @@ read_pipe(HANDLE pipe, char *data, size_t len) {
}
void
close_pipe(HANDLE pipe) {
sc_close_pipe(HANDLE pipe) {
if (!CloseHandle(pipe)) {
LOGW("Cannot close pipe");
}

48
app/src/util/file.c Normal file
View File

@@ -0,0 +1,48 @@
#include "file.h"
#include <stdlib.h>
#include <string.h>
#include "util/log.h"
char *
sc_file_get_local_path(const char *name) {
char *executable_path = sc_file_get_executable_path();
if (!executable_path) {
return NULL;
}
// dirname() does not work correctly everywhere, so get the parent
// directory manually.
// See <https://github.com/Genymobile/scrcpy/issues/2619>
char *p = strrchr(executable_path, SC_PATH_SEPARATOR);
if (!p) {
LOGE("Unexpected executable path: \"%s\" (it should contain a '%c')",
executable_path, SC_PATH_SEPARATOR);
free(executable_path);
return NULL;
}
*p = '\0'; // modify executable_path in place
char *dir = executable_path;
size_t dirlen = strlen(dir);
size_t namelen = strlen(name);
size_t len = dirlen + namelen + 2; // +2: '/' and '\0'
char *file_path = malloc(len);
if (!file_path) {
LOGE("Could not alloc path");
free(executable_path);
return NULL;
}
memcpy(file_path, dir, dirlen);
file_path[dirlen] = SC_PATH_SEPARATOR;
// namelen + 1 to copy the final '\0'
memcpy(&file_path[dirlen + 1], name, namelen + 1);
free(executable_path);
return file_path;
}

49
app/src/util/file.h Normal file
View File

@@ -0,0 +1,49 @@
#ifndef SC_FILE_H
#define SC_FILE_H
#include "common.h"
#include <stdbool.h>
#ifdef _WIN32
# define SC_PATH_SEPARATOR '\\'
#else
# define SC_PATH_SEPARATOR '/'
#endif
#ifndef _WIN32
/**
* Indicate if an executable exists using $PATH
*
* In practice, it is only used to know if a package manager is available on
* the system. It is only implemented on Linux.
*/
bool
sc_file_executable_exists(const char *file);
#endif
/**
* Return the absolute path of the executable (the scrcpy binary)
*
* The result must be freed by the caller using free(). It may return NULL on
* error.
*/
char *
sc_file_get_executable_path(void);
/**
* Return the absolute path of a file in the same directory as the executable
*
* The result must be freed by the caller using free(). It may return NULL on
* error.
*/
char *
sc_file_get_local_path(const char *name);
/**
* Indicate if the file exists and is not a directory
*/
bool
sc_file_is_regular(const char *path);
#endif

View File

@@ -5,7 +5,7 @@
#include <SDL2/SDL_log.h>
#include "scrcpy.h"
#include "options.h"
#define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)

View File

@@ -1,5 +1,6 @@
#include "net.h"
#include <assert.h>
#include <stdio.h>
#include <SDL2/SDL_platform.h>
@@ -55,6 +56,7 @@ wrap(sc_raw_socket sock) {
}
socket->socket = sock;
socket->closed = (atomic_flag) ATOMIC_FLAG_INIT;
return socket;
#else
@@ -195,18 +197,33 @@ net_send_all(sc_socket socket, const void *buf, size_t len) {
}
bool
net_shutdown(sc_socket socket, int how) {
net_interrupt(sc_socket socket) {
assert(socket != SC_INVALID_SOCKET);
sc_raw_socket raw_sock = unwrap(socket);
return !shutdown(raw_sock, how);
#ifdef __WINDOWS__
if (!atomic_flag_test_and_set(&socket->closed)) {
return !closesocket(raw_sock);
}
return true;
#else
return !shutdown(raw_sock, SHUT_RDWR);
#endif
}
#include <errno.h>
bool
net_close(sc_socket socket) {
sc_raw_socket raw_sock = unwrap(socket);
#ifdef __WINDOWS__
bool ret = true;
if (!atomic_flag_test_and_set(&socket->closed)) {
ret = !closesocket(raw_sock);
}
free(socket);
return !closesocket(raw_sock);
return ret;
#else
return !close(raw_sock);
#endif

View File

@@ -10,12 +10,11 @@
#ifdef __WINDOWS__
# include <winsock2.h>
# define SHUT_RD SD_RECEIVE
# define SHUT_WR SD_SEND
# define SHUT_RDWR SD_BOTH
# include <stdatomic.h>
# define SC_INVALID_SOCKET NULL
typedef struct sc_socket_windows {
SOCKET socket;
atomic_flag closed;
} *sc_socket;
#else // not __WINDOWS__
@@ -53,10 +52,13 @@ net_send(sc_socket socket, const void *buf, size_t len);
ssize_t
net_send_all(sc_socket socket, const void *buf, size_t len);
// how is SHUT_RD (read), SHUT_WR (write) or SHUT_RDWR (both)
// Shutdown the socket (or close on Windows) so that any blocking send() or
// recv() are interrupted.
bool
net_shutdown(sc_socket socket, int how);
net_interrupt(sc_socket socket);
// Close the socket.
// A socket must always be closed, even if net_interrupt() has been called.
bool
net_close(sc_socket socket);

View File

@@ -3,16 +3,22 @@
#include <libgen.h>
#include "log.h"
enum sc_process_result
sc_process_execute(const char *const argv[], sc_pid *pid) {
return sc_process_execute_p(argv, pid, NULL, NULL, NULL);
}
bool
process_check_success(process_t proc, const char *name, bool close) {
if (proc == PROCESS_NONE) {
sc_process_check_success(sc_pid pid, const char *name, bool close) {
if (pid == SC_PROCESS_NONE) {
LOGE("Could not execute \"%s\"", name);
return false;
}
exit_code_t exit_code = process_wait(proc, close);
sc_exit_code exit_code = sc_process_wait(pid, close);
if (exit_code) {
if (exit_code != NO_EXIT_CODE) {
LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code);
if (exit_code != SC_EXIT_CODE_NONE) {
LOGE("\"%s\" returned with value %" SC_PRIexitcode, name,
exit_code);
} else {
LOGE("\"%s\" exited unexpectedly", name);
}
@@ -21,52 +27,11 @@ process_check_success(process_t proc, const char *name, bool close) {
return true;
}
char *
get_local_file_path(const char *name) {
char *executable_path = get_executable_path();
if (!executable_path) {
return NULL;
}
// dirname() does not work correctly everywhere, so get the parent
// directory manually.
// See <https://github.com/Genymobile/scrcpy/issues/2619>
char *p = strrchr(executable_path, PATH_SEPARATOR);
if (!p) {
LOGE("Unexpected executable path: \"%s\" (it should contain a '%c')",
executable_path, PATH_SEPARATOR);
free(executable_path);
return NULL;
}
*p = '\0'; // modify executable_path in place
char *dir = executable_path;
size_t dirlen = strlen(dir);
size_t namelen = strlen(name);
size_t len = dirlen + namelen + 2; // +2: '/' and '\0'
char *file_path = malloc(len);
if (!file_path) {
LOGE("Could not alloc path");
free(executable_path);
return NULL;
}
memcpy(file_path, dir, dirlen);
file_path[dirlen] = PATH_SEPARATOR;
// namelen + 1 to copy the final '\0'
memcpy(&file_path[dirlen + 1], name, namelen + 1);
free(executable_path);
return file_path;
}
ssize_t
read_pipe_all(pipe_t pipe, char *data, size_t len) {
sc_pipe_read_all(sc_pipe pipe, char *data, size_t len) {
size_t copied = 0;
while (len > 0) {
ssize_t r = read_pipe(pipe, data, len);
ssize_t r = sc_pipe_read(pipe, data, len);
if (r <= 0) {
return copied ? (ssize_t) copied : r;
}

View File

@@ -10,96 +10,100 @@
// not needed here, but winsock2.h must never be included AFTER windows.h
# include <winsock2.h>
# include <windows.h>
# define PATH_SEPARATOR '\\'
# define PRIexitcode "lu"
# define SC_PRIexitcode "lu"
// <https://stackoverflow.com/a/44383330/1987178>
# define PRIsizet "Iu"
# define PROCESS_NONE NULL
# define NO_EXIT_CODE -1u // max value as unsigned
typedef HANDLE process_t;
typedef DWORD exit_code_t;
typedef HANDLE pipe_t;
# define SC_PRIsizet "Iu"
# define SC_PROCESS_NONE NULL
# define SC_EXIT_CODE_NONE -1u // max value as unsigned
typedef HANDLE sc_pid;
typedef DWORD sc_exit_code;
typedef HANDLE sc_pipe;
#else
# include <sys/types.h>
# define PATH_SEPARATOR '/'
# define PRIsizet "zu"
# define PRIexitcode "d"
# define PROCESS_NONE -1
# define NO_EXIT_CODE -1
typedef pid_t process_t;
typedef int exit_code_t;
typedef int pipe_t;
# define SC_PRIsizet "zu"
# define SC_PRIexitcode "d"
# define SC_PROCESS_NONE -1
# define SC_EXIT_CODE_NONE -1
typedef pid_t sc_pid;
typedef int sc_exit_code;
typedef int sc_pipe;
#endif
enum process_result {
PROCESS_SUCCESS,
PROCESS_ERROR_GENERIC,
PROCESS_ERROR_MISSING_BINARY,
enum sc_process_result {
SC_PROCESS_SUCCESS,
SC_PROCESS_ERROR_GENERIC,
SC_PROCESS_ERROR_MISSING_BINARY,
};
// execute the command and write the result to the output parameter "process"
enum process_result
process_execute(const char *const argv[], process_t *process);
/**
* Execute the command and write the process id to `pid`
*/
enum sc_process_result
sc_process_execute(const char *const argv[], sc_pid *pid);
enum process_result
process_execute_redirect(const char *const argv[], process_t *process,
pipe_t *pipe_stdin, pipe_t *pipe_stdout,
pipe_t *pipe_stderr);
/**
* Execute the command and write the process id to `pid`
*
* If not NULL, provide a pipe for stdin (`pin`), stdout (`pout`) and stderr
* (`perr`).
*/
enum sc_process_result
sc_process_execute_p(const char *const argv[], sc_pid *pid,
sc_pipe *pin, sc_pipe *pout, sc_pipe *perr);
/**
* Kill the process
*/
bool
process_terminate(process_t pid);
sc_process_terminate(sc_pid pid);
// kill the process
bool
process_terminate(process_t pid);
/**
* Wait and close the process (similar to waitpid())
*
* The `close` flag indicates if the process must be _closed_ (reaped) (passing
* false is equivalent to enable WNOWAIT in waitid()).
*/
sc_exit_code
sc_process_wait(sc_pid pid, bool close);
// wait and close the process (like waitpid())
// the "close" flag indicates if the process must be "closed" (reaped)
// (passing false is equivalent to enable WNOWAIT in waitid())
exit_code_t
process_wait(process_t pid, bool close);
// close the process
//
// Semantically, process_wait(close) = process_wait(noclose) + process_close
/**
* Close (reap) the process
*
* Semantically:
* sc_process_wait(close) = sc_process_wait(noclose) + sc_process_close()
*/
void
process_close(process_t pid);
sc_process_close(sc_pid pid);
// convenience function to wait for a successful process execution
// automatically log process errors with the provided process name
/**
* Convenience function to wait for a successful process execution
*
* Automatically log process errors with the provided process name.
*/
bool
process_check_success(process_t proc, const char *name, bool close);
#ifndef _WIN32
// only used to find package manager, not implemented for Windows
bool
search_executable(const char *file);
#endif
// return the absolute path of the executable (the scrcpy binary)
// may be NULL on error; to be freed by free()
char *
get_executable_path(void);
// Return the absolute path of a file in the same directory as he executable.
// May be NULL on error. To be freed by free().
char *
get_local_file_path(const char *name);
// returns true if the file exists and is not a directory
bool
is_regular_file(const char *path);
sc_process_check_success(sc_pid pid, const char *name, bool close);
/**
* Read from the pipe
*
* Same semantic as read().
*/
ssize_t
read_pipe(pipe_t pipe, char *data, size_t len);
sc_pipe_read(sc_pipe pipe, char *data, size_t len);
/**
* Read exactly `len` chars from a pipe (unless EOF)
*/
ssize_t
read_pipe_all(pipe_t pipe, char *data, size_t len);
sc_pipe_read_all(sc_pipe pipe, char *data, size_t len);
/**
* Close the pipe
*/
void
close_pipe(pipe_t pipe);
sc_pipe_close(sc_pipe pipe);
#endif

View File

@@ -1,9 +1,11 @@
#include "str_util.h"
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include "util/strbuf.h"
#ifdef _WIN32
# include <windows.h>
@@ -209,3 +211,81 @@ utf8_from_wide_char(const wchar_t *ws) {
}
#endif
char *sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent) {
assert(indent < columns);
struct sc_strbuf buf;
// The output string should not be much longer than the input string (just
// a few '\n' added), so this initial capacity should hopefully almost
// always avoid internal realloc() in string buffer
size_t cap = strlen(input) * 3 / 2;
if (!sc_strbuf_init(&buf, cap)) {
return false;
}
#define APPEND(S,N) if (!sc_strbuf_append(&buf, S, N)) goto error
#define APPEND_CHAR(C) if (!sc_strbuf_append_char(&buf, C)) goto error
#define APPEND_N(C,N) if (!sc_strbuf_append_n(&buf, C, N)) goto error
#define APPEND_INDENT() if (indent) APPEND_N(' ', indent)
APPEND_INDENT();
// The last separator encountered, it must be inserted only conditionnaly,
// depending on the next token
char pending = 0;
// col tracks the current column in the current line
size_t col = indent;
while (*input) {
size_t sep_idx = strcspn(input, "\n ");
size_t new_col = col + sep_idx;
if (pending == ' ') {
// The pending space counts
++new_col;
}
bool wrap = new_col > columns;
char sep = input[sep_idx];
if (sep == ' ')
sep = ' ';
if (wrap) {
APPEND_CHAR('\n');
APPEND_INDENT();
col = indent;
} else if (pending) {
APPEND_CHAR(pending);
++col;
if (pending == '\n')
{
APPEND_INDENT();
col = indent;
}
}
if (sep_idx) {
APPEND(input, sep_idx);
col += sep_idx;
}
pending = sep;
input += sep_idx;
if (*input != '\0') {
// Skip the separator
++input;
}
}
if (pending)
APPEND_CHAR(pending);
return buf.s;
error:
free(buf.s);
return NULL;
}

View File

@@ -62,4 +62,12 @@ char *
utf8_from_wide_char(const wchar_t *s);
#endif
/**
* Wrap input lines to fit in `columns` columns
*
* Break input lines at word boundaries (spaces) so that they fit in `columns`
* columns, left-indented by `indent` spaces.
*/
char *sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent);
#endif

87
app/src/util/strbuf.c Normal file
View File

@@ -0,0 +1,87 @@
#include "strbuf.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
bool
sc_strbuf_init(struct sc_strbuf *buf, size_t init_cap) {
buf->s = malloc(init_cap + 1); // +1 for '\0'
if (!buf->s) {
return false;
}
buf->len = 0;
buf->cap = init_cap;
return true;
}
static bool
sc_strbuf_reserve(struct sc_strbuf *buf, size_t len) {
if (buf->len + len > buf->cap) {
size_t new_cap = buf->cap * 3 / 2 + len;
char *s = realloc(buf->s, new_cap + 1); // +1 for '\0'
if (!s) {
// Leave the old buf->s
return false;
}
buf->s = s;
buf->cap = new_cap;
}
return true;
}
bool
sc_strbuf_append(struct sc_strbuf *buf, const char *s, size_t len) {
assert(s);
assert(*s);
assert(strlen(s) >= len);
if (!sc_strbuf_reserve(buf, len)) {
return false;
}
memcpy(&buf->s[buf->len], s, len);
buf->len += len;
buf->s[buf->len] = '\0';
return true;
}
bool
sc_strbuf_append_char(struct sc_strbuf *buf, const char c) {
if (!sc_strbuf_reserve(buf, 1)) {
return false;
}
buf->s[buf->len] = c;
buf->len ++;
buf->s[buf->len] = '\0';
return true;
}
bool
sc_strbuf_append_n(struct sc_strbuf *buf, const char c, size_t n) {
if (!sc_strbuf_reserve(buf, n)) {
return false;
}
memset(&buf->s[buf->len], c, n);
buf->len += n;
buf->s[buf->len] = '\0';
return true;
}
void
sc_strbuf_shrink(struct sc_strbuf *buf) {
assert(buf->len <= buf->cap);
if (buf->len != buf->cap) {
char *s = realloc(buf->s, buf->len + 1); // +1 for '\0'
assert(s); // decreasing the size may not fail
buf->s = s;
buf->cap = buf->len;
}
}

73
app/src/util/strbuf.h Normal file
View File

@@ -0,0 +1,73 @@
#ifndef SC_STRBUF_H
#define SC_STRBUF_H
#include "common.h"
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
struct sc_strbuf {
char *s;
size_t len;
size_t cap;
};
/**
* Initialize the string buffer
*
* `buf->s` must be manually freed by the caller.
*/
bool
sc_strbuf_init(struct sc_strbuf *buf, size_t init_cap);
/**
* Append a string
*
* Append `len` characters from `s` to the buffer.
*/
bool
sc_strbuf_append(struct sc_strbuf *buf, const char *s, size_t len);
/**
* Append a char
*
* Append a single character to the buffer.
*/
bool
sc_strbuf_append_char(struct sc_strbuf *buf, const char c);
/**
* Append a char `n` times
*
* Append the same characters `n` times to the buffer.
*/
bool
sc_strbuf_append_n(struct sc_strbuf *buf, const char c, size_t n);
/**
* Append a NUL-terminated string
*/
static inline bool
sc_strbuf_append_str(struct sc_strbuf *buf, const char *s) {
return sc_strbuf_append(buf, s, strlen(s));
}
/**
* Append a static string
*
* Append a string whose size is known at compile time (for
* example a string literal).
*/
#define sc_strbuf_append_staticstr(BUF, S) \
sc_strbuf_append(BUF, S, sizeof(S) - 1)
/**
* Shrink the buffer capacity to its current length
*
* This resizes `buf->s` to fit the content.
*/
void
sc_strbuf_shrink(struct sc_strbuf *buf);
#endif

51
app/src/util/term.c Normal file
View File

@@ -0,0 +1,51 @@
#include "term.h"
#include <assert.h>
#ifdef _WIN32
# include <windows.h>
#else
# include <unistd.h>
# include <sys/ioctl.h>
#endif
bool
sc_term_get_size(unsigned *rows, unsigned *cols) {
#ifdef _WIN32
CONSOLE_SCREEN_BUFFER_INFO csbi;
bool ok =
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
if (!ok) {
return false;
}
if (rows) {
assert(csbi.srWindow.Bottom >= csbi.srWindow.Top);
*rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
}
if (cols) {
assert(csbi.srWindow.Right >= csbi.srWindow.Left);
*cols = csbi.srWindow.Right - csbi.srWindow.Left + 1;
}
return true;
#else
struct winsize ws;
int r = ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
if (r == -1) {
return false;
}
if (rows) {
*rows = ws.ws_row;
}
if (cols) {
*cols = ws.ws_col;
}
return true;
#endif
}

21
app/src/util/term.h Normal file
View File

@@ -0,0 +1,21 @@
#ifndef SC_TERM_H
#define SC_TERM_H
#include "common.h"
#include <stdbool.h>
/**
* Return the terminal dimensions
*
* Return false if the dimensions could not be retrieved.
*
* Otherwise, return true, and:
* - if `rows` is not NULL, then the number of rows is written to `*rows`.
* - if `columns` is not NULL, then the number of columns is written to
* `*columns`.
*/
bool
sc_term_get_size(unsigned *rows, unsigned *cols);
#endif

View File

@@ -1,6 +1,8 @@
#ifndef SC_TICK_H
#define SC_TICK_H
#include "common.h"
#include <stdint.h>
typedef int64_t sc_tick;

View File

@@ -359,7 +359,7 @@ sc_v4l2_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
bool
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
struct size frame_size, sc_tick buffering_time) {
struct sc_size frame_size, sc_tick buffering_time) {
vs->device_name = strdup(device_name);
if (!vs->device_name) {
LOGE("Could not strdup v4l2 device name");

View File

@@ -18,7 +18,7 @@ struct sc_v4l2_sink {
AVCodecContext *encoder_ctx;
char *device_name;
struct size frame_size;
struct sc_size frame_size;
sc_tick buffering_time;
sc_thread thread;
@@ -34,7 +34,7 @@ struct sc_v4l2_sink {
bool
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
struct size frame_size, sc_tick buffering_time);
struct sc_size frame_size, sc_tick buffering_time);
void
sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs);

View File

@@ -4,11 +4,11 @@
#include <string.h>
#include "cli.h"
#include "scrcpy.h"
#include "options.h"
static void test_flag_version(void) {
struct scrcpy_cli_args args = {
.opts = SCRCPY_OPTIONS_DEFAULT,
.opts = scrcpy_options_default,
.help = false,
.version = false,
};
@@ -23,7 +23,7 @@ static void test_flag_version(void) {
static void test_flag_help(void) {
struct scrcpy_cli_args args = {
.opts = SCRCPY_OPTIONS_DEFAULT,
.opts = scrcpy_options_default,
.help = false,
.version = false,
};
@@ -38,7 +38,7 @@ static void test_flag_help(void) {
static void test_options(void) {
struct scrcpy_cli_args args = {
.opts = SCRCPY_OPTIONS_DEFAULT,
.opts = scrcpy_options_default,
.help = false,
.version = false,
};
@@ -100,7 +100,7 @@ static void test_options(void) {
static void test_options2(void) {
struct scrcpy_cli_args args = {
.opts = SCRCPY_OPTIONS_DEFAULT,
.opts = scrcpy_options_default,
.help = false,
.version = false,
};

47
app/tests/test_strbuf.c Normal file
View File

@@ -0,0 +1,47 @@
#include "common.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "util/strbuf.h"
static void test_strbuf_simple(void) {
struct sc_strbuf buf;
bool ok = sc_strbuf_init(&buf, 10);
assert(ok);
ok = sc_strbuf_append_staticstr(&buf, "Hello");
assert(ok);
ok = sc_strbuf_append_char(&buf, ' ');
assert(ok);
ok = sc_strbuf_append_staticstr(&buf, "world");
assert(ok);
ok = sc_strbuf_append_staticstr(&buf, "!\n");
assert(ok);
ok = sc_strbuf_append_staticstr(&buf, "This is a test");
assert(ok);
ok = sc_strbuf_append_n(&buf, '.', 3);
assert(ok);
assert(!strcmp(buf.s, "Hello world!\nThis is a test..."));
sc_strbuf_shrink(&buf);
assert(buf.len == buf.cap);
assert(!strcmp(buf.s, "Hello world!\nThis is a test..."));
free(buf.s);
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_strbuf_simple();
return 0;
}

View File

@@ -299,6 +299,44 @@ static void test_strlist_contains(void) {
assert(strlist_contains("xyz", '\0', "xyz"));
}
static void test_wrap_lines(void) {
const char *s = "This is a text to test line wrapping. The lines must be "
"wrapped at a space or a line break.\n"
"\n"
"This rectangle must remains a rectangle because it is "
"drawn in lines having lengths lower than the specified "
"number of columns:\n"
" +----+\n"
" | |\n"
" +----+\n";
// |---- 1 1 2 2|
// |0 5 0 5 0 3| <-- 24 columns
const char *expected = " This is a text to\n"
" test line wrapping.\n"
" The lines must be\n"
" wrapped at a space\n"
" or a line break.\n"
" \n"
" This rectangle must\n"
" remains a rectangle\n"
" because it is drawn\n"
" in lines having\n"
" lengths lower than\n"
" the specified number\n"
" of columns:\n"
" +----+\n"
" | |\n"
" +----+\n";
char *formatted = sc_str_wrap_lines(s, 24, 4);
assert(formatted);
assert(!strcmp(formatted, expected));
free(formatted);
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
@@ -317,5 +355,6 @@ int main(int argc, char *argv[]) {
test_parse_integers();
test_parse_integer_with_suffix();
test_strlist_contains();
test_wrap_lines();
return 0;
}

View File

@@ -7,7 +7,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.1'
classpath 'com.android.tools.build:gradle:7.0.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View File

@@ -2,7 +2,7 @@ apply plugin: 'checkstyle'
check.dependsOn 'checkstyle'
checkstyle {
toolVersion = '6.19'
toolVersion = '9.0.1'
}
task checkstyle(type: Checkstyle) {

View File

@@ -37,6 +37,14 @@ page at http://checkstyle.sourceforge.net/config.html -->
<module name="SuppressWarningsFilter"/>
<module name="LineLength">
<!-- what is a good max value? -->
<property name="max" value="150" />
<!-- ignore lines like "$File: //depot/... $" -->
<property name="ignorePattern" value="\$File.*\$" />
<property name="severity" value="info" />
</module>
<module name="TreeWalker">
<!-- Checks for Naming Conventions. -->
@@ -72,13 +80,6 @@ page at http://checkstyle.sourceforge.net/config.html -->
<!-- Checks for Size Violations. -->
<!-- See http://checkstyle.sf.net/config_sizes.html -->
<module name="LineLength">
<!-- what is a good max value? -->
<property name="max" value="150" />
<!-- ignore lines like "$File: //depot/... $" -->
<property name="ignorePattern" value="\$File.*\$" />
<property name="severity" value="info" />
</module>
<module name="MethodLength" />
<module name="ParameterNumber">
<property name="ignoreOverriddenMethods" value="true"/>
@@ -152,26 +153,6 @@ page at http://checkstyle.sourceforge.net/config.html -->
</module>
<module name="UpperEll" />
<module name="FileContentsHolder" />
<!-- Required by comment suppression filters -->
</module>
<module name="SuppressionFilter">
<!--<property name="file" value="team-props/checkstyle/checkstyle-suppressions.xml" />-->
</module>
<!-- Enable suppression comments -->
<module name="SuppressionCommentFilter">
<property name="offCommentFormat" value="CHECKSTYLE IGNORE\s+(\S+)" />
<property name="onCommentFormat" value="CHECKSTYLE END IGNORE\s+(\S+)" />
<property name="checkFormat" value="$1" />
</module>
<module name="SuppressWithNearbyCommentFilter">
<!-- Syntax is "SUPPRESS CHECKSTYLE name" -->
<property name="commentFormat" value="SUPPRESS CHECKSTYLE (\w+)" />
<property name="checkFormat" value="$1" />
<property name="influenceFormat" value="1" />
</module>
</module>

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,11 +1,11 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 30
compileSdkVersion 31
defaultConfig {
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
targetSdkVersion 30
targetSdkVersion 31
versionCode 11900
versionName "1.19"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

View File

@@ -2,7 +2,6 @@ package com.genymobile.scrcpy;
import android.view.KeyEvent;
import android.view.MotionEvent;
import org.junit.Assert;
import org.junit.Test;