Compare commits

..

7 Commits

Author SHA1 Message Date
2f816fdfc2 wip 2021-10-31 15:08:01 +01:00
91e5e68582 server_thread 2021-10-31 15:08:01 +01:00
0bc1882179 server_cbs 2021-10-31 15:08:01 +01:00
52eb960dd0 server_info 2021-10-31 15:08:01 +01:00
34b92440e2 stopped_cond 2021-10-31 15:08:01 +01:00
e23daebe06 Copy server params
This is a preliminary step necessary to move the server to a separate
thread.
2021-10-31 12:45:59 +01:00
0b616634c3 Reorder server and server_params
This will allow to define a server_params field in server.
2021-10-31 12:45:59 +01:00
30 changed files with 1094 additions and 1790 deletions

View File

@ -582,14 +582,6 @@ scrcpy --turn-screen-off --stay-awake
scrcpy -Sw scrcpy -Sw
``` ```
#### Power off on close
To turn the device screen off when closing scrcpy:
```bash
scrcpy --power-off-on-close
```
#### Show touches #### Show touches
@ -838,7 +830,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> | Turn device screen on | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
| Rotate device screen | <kbd>MOD</kbd>+<kbd>r</kbd> | Rotate device screen | <kbd>MOD</kbd>+<kbd>r</kbd>
| Expand notification panel | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5th-click³_ | 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> | Collapse panels | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| Copy to clipboard⁴ | <kbd>MOD</kbd>+<kbd>c</kbd> | Copy to clipboard⁴ | <kbd>MOD</kbd>+<kbd>c</kbd>
| Cut to clipboard⁴ | <kbd>MOD</kbd>+<kbd>x</kbd> | Cut to clipboard⁴ | <kbd>MOD</kbd>+<kbd>x</kbd>
@ -846,8 +838,6 @@ _<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> | 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> | Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_ | 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._ _¹Double-click on black borders to remove them._
_²Right-click turns the screen on if it was off, presses BACK otherwise._ _²Right-click turns the screen on if it was off, presses BACK otherwise._

View File

@ -24,27 +24,18 @@ src = [
'src/server.c', 'src/server.c',
'src/stream.c', 'src/stream.c',
'src/video_buffer.c', 'src/video_buffer.c',
'src/util/file.c',
'src/util/log.c', 'src/util/log.c',
'src/util/net.c', 'src/util/net.c',
'src/util/process.c', 'src/util/process.c',
'src/util/strbuf.c',
'src/util/str_util.c', 'src/util/str_util.c',
'src/util/term.c',
'src/util/thread.c', 'src/util/thread.c',
'src/util/tick.c', 'src/util/tick.c',
] ]
if host_machine.system() == 'windows' if host_machine.system() == 'windows'
src += [ src += [ 'src/sys/win/process.c' ]
'src/sys/win/file.c',
'src/sys/win/process.c',
]
else else
src += [ src += [ 'src/sys/unix/process.c' ]
'src/sys/unix/file.c',
'src/sys/unix/process.c',
]
endif endif
v4l2_support = host_machine.system() == 'linux' v4l2_support = host_machine.system() == 'linux'
@ -195,9 +186,7 @@ if get_option('buildtype') == 'debug'
'tests/test_cli.c', 'tests/test_cli.c',
'src/cli.c', 'src/cli.c',
'src/options.c', 'src/options.c',
'src/util/strbuf.c',
'src/util/str_util.c', 'src/util/str_util.c',
'src/util/term.c',
]], ]],
['test_clock', [ ['test_clock', [
'tests/test_clock.c', 'tests/test_clock.c',
@ -206,7 +195,6 @@ if get_option('buildtype') == 'debug'
['test_control_msg_serialize', [ ['test_control_msg_serialize', [
'tests/test_control_msg_serialize.c', 'tests/test_control_msg_serialize.c',
'src/control_msg.c', 'src/control_msg.c',
'src/util/strbuf.c',
'src/util/str_util.c', 'src/util/str_util.c',
]], ]],
['test_device_msg_deserialize', [ ['test_device_msg_deserialize', [
@ -216,13 +204,8 @@ if get_option('buildtype') == 'debug'
['test_queue', [ ['test_queue', [
'tests/test_queue.c', 'tests/test_queue.c',
]], ]],
['test_strbuf', [
'tests/test_strbuf.c',
'src/util/strbuf.c',
]],
['test_strutil', [ ['test_strutil', [
'tests/test_strutil.c', 'tests/test_strutil.c',
'src/util/strbuf.c',
'src/util/str_util.c', 'src/util/str_util.c',
]], ]],
] ]

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +1,4 @@
#define EVENT_NEW_FRAME SDL_USEREVENT #define EVENT_NEW_FRAME SDL_USEREVENT
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1) #define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1)
#define EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2)
#define EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3)

View File

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

View File

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

View File

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

View File

@ -217,6 +217,29 @@ event_loop(struct scrcpy *s, const struct scrcpy_options *options) {
return false; return false;
} }
static bool
await_for_server(void) {
SDL_Event event;
while (SDL_WaitEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
LOGD("User requested to quit");
return false;
case EVENT_SERVER_CONNECTION_FAILED:
LOGE("Server connection failed");
return false;
case EVENT_SERVER_CONNECTED:
LOGD("Server connected");
return true;
default:
break;
}
}
LOGE("SDL_WaitEvent() error: %s", SDL_GetError());
return false;
}
static SDL_LogPriority static SDL_LogPriority
sdl_priority_from_av_level(int level) { sdl_priority_from_av_level(int level) {
switch (level) { switch (level) {
@ -262,6 +285,31 @@ stream_on_eos(struct stream *stream, void *userdata) {
PUSH_EVENT(EVENT_STREAM_STOPPED); PUSH_EVENT(EVENT_STREAM_STOPPED);
} }
static void
server_on_connection_failed(struct server *server, void *userdata) {
(void) server;
(void) userdata;
PUSH_EVENT(EVENT_SERVER_CONNECTION_FAILED);
}
static void
server_on_connected(struct server *server, void *userdata) {
(void) server;
(void) userdata;
PUSH_EVENT(EVENT_SERVER_CONNECTED);
}
static void
server_on_disconnected(struct server *server, void *userdata) {
(void) server;
(void) userdata;
LOGD("Server disconnected");
// Do nothing, will be managed by the "stream stopped" event
}
bool bool
scrcpy(struct scrcpy_options *options) { scrcpy(struct scrcpy_options *options) {
static struct scrcpy scrcpy; static struct scrcpy scrcpy;
@ -275,10 +323,6 @@ scrcpy(struct scrcpy_options *options) {
atexit(SDL_Quit); atexit(SDL_Quit);
if (!server_init(&s->server)) {
return false;
}
bool ret = false; bool ret = false;
bool server_started = false; bool server_started = false;
@ -313,7 +357,17 @@ scrcpy(struct scrcpy_options *options) {
.force_adb_forward = options->force_adb_forward, .force_adb_forward = options->force_adb_forward,
.power_off_on_close = options->power_off_on_close, .power_off_on_close = options->power_off_on_close,
}; };
if (!server_start(&s->server, &params)) {
static const struct server_callbacks cbs = {
.on_connection_failed = server_on_connection_failed,
.on_connected = server_on_connected,
.on_disconnected = server_on_disconnected,
};
if (!server_init(&s->server, &params, &cbs, NULL)) {
return false;
}
if (!server_start(&s->server)) {
goto end; goto end;
} }
@ -331,14 +385,15 @@ scrcpy(struct scrcpy_options *options) {
sdl_configure(options->display, options->disable_screensaver); sdl_configure(options->display, options->disable_screensaver);
struct server_info info; // Await for server without blocking Ctrl+C handling
if (!await_for_server()) {
if (!server_connect_to(&s->server, &info)) {
goto end; goto end;
} }
struct server_info *info = &s->server.info;
if (options->display && options->control) { if (options->display && options->control) {
if (!file_handler_init(&s->file_handler, s->server.serial, if (!file_handler_init(&s->file_handler, options->serial,
options->push_target)) { options->push_target)) {
goto end; goto end;
} }
@ -360,7 +415,7 @@ scrcpy(struct scrcpy_options *options) {
if (!recorder_init(&s->recorder, if (!recorder_init(&s->recorder,
options->record_filename, options->record_filename,
options->record_format, options->record_format,
info.frame_size)) { info->frame_size)) {
goto end; goto end;
} }
rec = &s->recorder; rec = &s->recorder;
@ -406,11 +461,11 @@ scrcpy(struct scrcpy_options *options) {
if (options->display) { if (options->display) {
const char *window_title = const char *window_title =
options->window_title ? options->window_title : info.device_name; options->window_title ? options->window_title : info->device_name;
struct screen_params screen_params = { struct screen_params screen_params = {
.window_title = window_title, .window_title = window_title,
.frame_size = info.frame_size, .frame_size = info->frame_size,
.always_on_top = options->always_on_top, .always_on_top = options->always_on_top,
.window_x = options->window_x, .window_x = options->window_x,
.window_y = options->window_y, .window_y = options->window_y,
@ -434,7 +489,7 @@ scrcpy(struct scrcpy_options *options) {
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
if (options->v4l2_device) { if (options->v4l2_device) {
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device, if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device,
info.frame_size, options->v4l2_buffer)) { info->frame_size, options->v4l2_buffer)) {
goto end; goto end;
} }

View File

@ -8,7 +8,6 @@
#include <SDL2/SDL_platform.h> #include <SDL2/SDL_platform.h>
#include "adb.h" #include "adb.h"
#include "util/file.h"
#include "util/log.h" #include "util/log.h"
#include "util/net.h" #include "util/net.h"
#include "util/str_util.h" #include "util/str_util.h"
@ -49,7 +48,7 @@ get_server_path(void) {
return NULL; return NULL;
} }
#else #else
char *server_path = sc_file_get_local_path(SERVER_FILENAME); char *server_path = get_local_file_path(SERVER_FILENAME);
if (!server_path) { if (!server_path) {
LOGE("Could not get local file path, " LOGE("Could not get local file path, "
"using " SERVER_FILENAME " from current directory"); "using " SERVER_FILENAME " from current directory");
@ -62,52 +61,91 @@ get_server_path(void) {
return server_path; return server_path;
} }
static void
server_params_destroy(struct server_params *params) {
// The server stores a copy of the params provided by the user
free((char *) params->serial);
free((char *) params->crop);
free((char *) params->codec_options);
free((char *) params->encoder_name);
}
static bool
server_params_copy(struct server_params *dst, const struct server_params *src) {
*dst = *src;
// The params reference user-allocated memory, so we must copy them to
// handle them from another thread
#define COPY(FIELD) \
dst->FIELD = NULL; \
if (src->FIELD) { \
dst->FIELD = strdup(src->FIELD); \
if (!dst->FIELD) { \
goto error; \
} \
}
COPY(serial);
COPY(crop);
COPY(codec_options);
COPY(encoder_name);
#undef COPY
return true;
error:
server_params_destroy(dst);
return false;
}
static bool static bool
push_server(const char *serial) { push_server(const char *serial) {
char *server_path = get_server_path(); char *server_path = get_server_path();
if (!server_path) { if (!server_path) {
return false; return false;
} }
if (!sc_file_is_regular(server_path)) { if (!is_regular_file(server_path)) {
LOGE("'%s' does not exist or is not a regular file\n", server_path); LOGE("'%s' does not exist or is not a regular file\n", server_path);
free(server_path); free(server_path);
return false; return false;
} }
sc_pid pid = adb_push(serial, server_path, DEVICE_SERVER_PATH); process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH);
free(server_path); free(server_path);
return sc_process_check_success(pid, "adb push", true); return process_check_success(process, "adb push", true);
} }
static bool static bool
enable_tunnel_reverse(const char *serial, uint16_t local_port) { enable_tunnel_reverse(const char *serial, uint16_t local_port) {
sc_pid pid = adb_reverse(serial, SOCKET_NAME, local_port); process_t process = adb_reverse(serial, SOCKET_NAME, local_port);
return sc_process_check_success(pid, "adb reverse", true); return process_check_success(process, "adb reverse", true);
} }
static bool static bool
disable_tunnel_reverse(const char *serial) { disable_tunnel_reverse(const char *serial) {
sc_pid pid = adb_reverse_remove(serial, SOCKET_NAME); process_t process = adb_reverse_remove(serial, SOCKET_NAME);
return sc_process_check_success(pid, "adb reverse --remove", true); return process_check_success(process, "adb reverse --remove", true);
} }
static bool static bool
enable_tunnel_forward(const char *serial, uint16_t local_port) { enable_tunnel_forward(const char *serial, uint16_t local_port) {
sc_pid pid = adb_forward(serial, local_port, SOCKET_NAME); process_t process = adb_forward(serial, local_port, SOCKET_NAME);
return sc_process_check_success(pid, "adb forward", true); return process_check_success(process, "adb forward", true);
} }
static bool static bool
disable_tunnel_forward(const char *serial, uint16_t local_port) { disable_tunnel_forward(const char *serial, uint16_t local_port) {
sc_pid pid = adb_forward_remove(serial, local_port); process_t process = adb_forward_remove(serial, local_port);
return sc_process_check_success(pid, "adb forward --remove", true); return process_check_success(process, "adb forward --remove", true);
} }
static bool static bool
disable_tunnel(struct server *server) { disable_tunnel(struct server *server) {
const char *serial = server->params.serial;
if (server->tunnel_forward) { if (server->tunnel_forward) {
return disable_tunnel_forward(server->serial, server->local_port); return disable_tunnel_forward(serial, server->local_port);
} }
return disable_tunnel_reverse(server->serial); return disable_tunnel_reverse(serial);
} }
static sc_socket static sc_socket
@ -119,9 +157,10 @@ listen_on_port(uint16_t port) {
static bool static bool
enable_tunnel_reverse_any_port(struct server *server, enable_tunnel_reverse_any_port(struct server *server,
struct sc_port_range port_range) { struct sc_port_range port_range) {
const char *serial = server->params.serial;
uint16_t port = port_range.first; uint16_t port = port_range.first;
for (;;) { for (;;) {
if (!enable_tunnel_reverse(server->serial, port)) { if (!enable_tunnel_reverse(serial, port)) {
// the command itself failed, it will fail on any port // the command itself failed, it will fail on any port
return false; return false;
} }
@ -140,7 +179,7 @@ enable_tunnel_reverse_any_port(struct server *server,
} }
// failure, disable tunnel and try another port // failure, disable tunnel and try another port
if (!disable_tunnel_reverse(server->serial)) { if (!disable_tunnel_reverse(serial)) {
LOGW("Could not remove reverse tunnel on port %" PRIu16, port); LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
} }
@ -166,9 +205,11 @@ static bool
enable_tunnel_forward_any_port(struct server *server, enable_tunnel_forward_any_port(struct server *server,
struct sc_port_range port_range) { struct sc_port_range port_range) {
server->tunnel_forward = true; server->tunnel_forward = true;
const char *serial = server->params.serial;
uint16_t port = port_range.first; uint16_t port = port_range.first;
for (;;) { for (;;) {
if (enable_tunnel_forward(server->serial, port)) { if (enable_tunnel_forward(serial, port)) {
// success // success
server->local_port = port; server->local_port = port;
return true; return true;
@ -228,8 +269,10 @@ log_level_to_server_string(enum sc_log_level level) {
} }
} }
static sc_pid static process_t
execute_server(struct server *server, const struct server_params *params) { execute_server(struct server *server, const struct server_params *params) {
const char *serial = server->params.serial;
char max_size_string[6]; char max_size_string[6];
char bit_rate_string[11]; char bit_rate_string[11];
char max_fps_string[6]; char max_fps_string[6];
@ -287,7 +330,7 @@ execute_server(struct server *server, const struct server_params *params) {
// Port: 5005 // Port: 5005
// Then click on "Debug" // Then click on "Debug"
#endif #endif
return adb_execute(server->serial, cmd, ARRAY_LEN(cmd)); return adb_execute(serial, cmd, ARRAY_LEN(cmd));
} }
static sc_socket static sc_socket
@ -309,7 +352,8 @@ connect_and_read_byte(uint16_t port) {
} }
static sc_socket static sc_socket
connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) { connect_to_server(struct server *server, uint32_t attempts, sc_tick delay) {
uint16_t port = server->local_port;
do { do {
LOGD("Remaining connection attempts: %d", (int) attempts); LOGD("Remaining connection attempts: %d", (int) attempts);
sc_socket socket = connect_and_read_byte(port); sc_socket socket = connect_and_read_byte(port);
@ -317,30 +361,58 @@ connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
// it worked! // it worked!
return socket; return socket;
} }
// TODO use mutex + condvar + bool stopped
if (attempts) { if (attempts) {
SDL_Delay(delay); sc_mutex_lock(&server->mutex);
// Ignore timedwait return (spurious wake ups are harmless)
sc_cond_timedwait(&server->stopped_cond, &server->mutex,
sc_tick_now() + delay);
bool stopped = server->stopped;
sc_mutex_unlock(&server->mutex);
if (stopped) {
LOGI("Connection attempt stopped");
break;
}
} }
} while (--attempts > 0); } while (--attempts > 0);
return SC_INVALID_SOCKET; return SC_INVALID_SOCKET;
} }
bool bool
server_init(struct server *server) { server_init(struct server *server, const struct server_params *params,
server->serial = NULL; const struct server_callbacks *cbs, void *cbs_userdata) {
server->process = SC_PROCESS_NONE; bool ok = server_params_copy(&server->params, params);
bool ok = sc_mutex_init(&server->mutex);
if (!ok) { if (!ok) {
LOGE("Could not copy server params");
return false;
}
server->process = PROCESS_NONE;
ok = sc_mutex_init(&server->mutex);
if (!ok) {
server_params_destroy(&server->params);
return false; return false;
} }
ok = sc_cond_init(&server->process_terminated_cond); ok = sc_cond_init(&server->process_terminated_cond);
if (!ok) { if (!ok) {
sc_mutex_destroy(&server->mutex); sc_mutex_destroy(&server->mutex);
server_params_destroy(&server->params);
return false;
}
ok = sc_cond_init(&server->stopped_cond);
if (!ok) {
sc_cond_destroy(&server->process_terminated_cond);
sc_mutex_destroy(&server->mutex);
server_params_destroy(&server->params);
return false; return false;
} }
server->process_terminated = false; server->process_terminated = false;
server->stopped = false;
server->connected = false;
server->server_socket = SC_INVALID_SOCKET; server->server_socket = SC_INVALID_SOCKET;
server->video_socket = SC_INVALID_SOCKET; server->video_socket = SC_INVALID_SOCKET;
@ -351,13 +423,69 @@ server_init(struct server *server) {
server->tunnel_enabled = false; server->tunnel_enabled = false;
server->tunnel_forward = false; server->tunnel_forward = false;
assert(cbs);
assert(cbs->on_connection_failed);
assert(cbs->on_connected);
assert(cbs->on_disconnected);
server->cbs = cbs;
server->cbs_userdata = cbs_userdata;
return true; return true;
} }
static int static int
run_wait_server(void *data) { run_server_connect(void *data) {
struct server *server = data; struct server *server = data;
sc_process_wait(server->process, false); // ignore exit code
if (!server_connect_to(server, &server->info)) {
server->cbs->on_connection_failed(server, server->cbs_userdata);
goto end;
}
server->connected = true;
server->cbs->on_connected(server, server->cbs_userdata);
end:
return 0;
}
static int
run_server(void *data) {
struct server *server = data;
const struct server_params *params = &server->params;
if (!push_server(params->serial)) {
server->cbs->on_connection_failed(server, server->cbs_userdata);
goto end;
}
if (!enable_tunnel_any_port(server, params->port_range,
params->force_adb_forward)) {
server->cbs->on_connection_failed(server, server->cbs_userdata);
goto end;
}
server->process = execute_server(server, params);
if (server->process == PROCESS_NONE) {
server->cbs->on_connection_failed(server, server->cbs_userdata);
goto end;
}
sc_thread connect_thread;
bool ok = sc_thread_create(&connect_thread, run_server_connect,
"server-connect", server);
if (!ok) {
LOGW("Could not create thread, killing the server...");
process_terminate(server->process);
server->cbs->on_connection_failed(server, server->cbs_userdata);
process_wait(server->process, false); // ignore exit code
goto end;
}
process_wait(server->process, false); // ignore exit code
LOGD("Server terminated");
sc_mutex_lock(&server->mutex); sc_mutex_lock(&server->mutex);
server->process_terminated = true; server->process_terminated = true;
@ -371,59 +499,22 @@ run_wait_server(void *data) {
net_interrupt(server->server_socket); net_interrupt(server->server_socket);
} }
LOGD("Server terminated"); sc_thread_join(&connect_thread, NULL);
// Written by connect_thread, sc_thread_join() provides the necessary
// memory barrier
if (server->connected) {
server->cbs->on_disconnected(server, server->cbs_userdata);
}
// Otherwise, ->on_connection_failed() is already called
end:
return 0; return 0;
} }
bool bool
server_start(struct server *server, const struct server_params *params) { server_start(struct server *server) {
if (params->serial) { return sc_thread_create(&server->thread, run_server, "server", server);
server->serial = strdup(params->serial);
if (!server->serial) {
return false;
}
}
if (!push_server(params->serial)) {
/* server->serial will be freed on server_destroy() */
return false;
}
if (!enable_tunnel_any_port(server, params->port_range,
params->force_adb_forward)) {
return false;
}
// server will connect to our server socket
server->process = execute_server(server, params);
if (server->process == SC_PROCESS_NONE) {
goto error;
}
// If the server process dies before connecting to the server socket, then
// the client will be stuck forever on accept(). To avoid the problem, we
// must be able to wake up the accept() call when the server dies. To keep
// things simple and multiplatform, just spawn a new thread waiting for the
// server process and calling shutdown()/close() on the server socket if
// necessary to wake up any accept() blocking call.
bool ok = sc_thread_create(&server->wait_server_thread, run_wait_server,
"wait-server", server);
if (!ok) {
sc_process_terminate(server->process);
sc_process_wait(server->process, true); // ignore exit code
goto error;
}
server->tunnel_enabled = true;
return true;
error:
// The server socket (if any) will be closed on server_destroy()
disable_tunnel(server);
return false;
} }
static bool static bool
@ -467,9 +558,8 @@ server_connect_to(struct server *server, struct server_info *info) {
server->server_socket = SC_INVALID_SOCKET; server->server_socket = SC_INVALID_SOCKET;
} else { } else {
uint32_t attempts = 100; uint32_t attempts = 100;
uint32_t delay = 100; // ms sc_tick delay = SC_TICK_FROM_MS(100);
server->video_socket = server->video_socket = connect_to_server(server, attempts, delay);
connect_to_server(server->local_port, attempts, delay);
if (server->video_socket == SC_INVALID_SOCKET) { if (server->video_socket == SC_INVALID_SOCKET) {
return false; return false;
} }
@ -492,6 +582,11 @@ server_connect_to(struct server *server, struct server_info *info) {
void void
server_stop(struct server *server) { server_stop(struct server *server) {
sc_mutex_lock(&server->mutex);
server->stopped = true;
sc_cond_signal(&server->stopped_cond);
sc_mutex_unlock(&server->mutex);
if (server->server_socket != SC_INVALID_SOCKET) { if (server->server_socket != SC_INVALID_SOCKET) {
if (!net_interrupt(server->server_socket)) { if (!net_interrupt(server->server_socket)) {
LOGW("Could not interrupt server socket"); LOGW("Could not interrupt server socket");
@ -508,7 +603,7 @@ server_stop(struct server *server) {
} }
} }
assert(server->process != SC_PROCESS_NONE); assert(server->process != PROCESS_NONE);
if (server->tunnel_enabled) { if (server->tunnel_enabled) {
// ignore failure // ignore failure
@ -533,11 +628,11 @@ server_stop(struct server *server) {
// The process is terminated, but not reaped (closed) yet, so its PID // The process is terminated, but not reaped (closed) yet, so its PID
// is still valid. // is still valid.
LOGW("Killing the server..."); LOGW("Killing the server...");
sc_process_terminate(server->process); process_terminate(server->process);
} }
sc_thread_join(&server->wait_server_thread, NULL); sc_thread_join(&server->thread, NULL);
sc_process_close(server->process); process_close(server->process);
} }
void void
@ -557,7 +652,9 @@ server_destroy(struct server *server) {
LOGW("Could not close control socket"); LOGW("Could not close control socket");
} }
} }
free(server->serial);
server_params_destroy(&server->params);
sc_cond_destroy(&server->stopped_cond);
sc_cond_destroy(&server->process_terminated_cond); sc_cond_destroy(&server->process_terminated_cond);
sc_mutex_destroy(&server->mutex); sc_mutex_destroy(&server->mutex);
} }

View File

@ -20,23 +20,6 @@ struct server_info {
struct sc_size frame_size; struct sc_size frame_size;
}; };
struct server {
char *serial;
sc_pid process;
sc_thread wait_server_thread;
sc_mutex mutex;
sc_cond process_terminated_cond;
bool process_terminated;
sc_socket server_socket; // only used if !tunnel_forward
sc_socket video_socket;
sc_socket control_socket;
uint16_t local_port; // selected from port_range
bool tunnel_enabled;
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
};
struct server_params { struct server_params {
const char *serial; const char *serial;
enum sc_log_level log_level; enum sc_log_level log_level;
@ -56,15 +39,52 @@ struct server_params {
bool power_off_on_close; bool power_off_on_close;
}; };
// init default values struct server {
// The internal allocated strings are copies owned by the server
struct server_params params;
process_t process;
sc_thread thread;
sc_mutex mutex;
sc_cond process_terminated_cond;
bool process_terminated;
sc_cond stopped_cond;
bool stopped;
bool connected; // written by connect_thread
struct server_info info; // initialized once connected
sc_socket server_socket; // only used if !tunnel_forward
sc_socket video_socket;
sc_socket control_socket;
uint16_t local_port; // selected from port_range
bool tunnel_enabled;
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
const struct server_callbacks *cbs;
void *cbs_userdata;
};
struct server_callbacks {
void (*on_connection_failed)(struct server *server, void *userdata);
void (*on_connected)(struct server *server, void *userdata);
void (*on_disconnected)(struct server *server, void *userdata);
};
// init the server with the given params
bool bool
server_init(struct server *server); server_init(struct server *server, const struct server_params *params,
const struct server_callbacks *cbs, void *cbs_userdata);
// push, enable tunnel et start the server // push, enable tunnel et start the server
bool bool
server_start(struct server *server, const struct server_params *params); server_start(struct server *server);
// block until the communication with the server is established // 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 bool
server_connect_to(struct server *server, struct server_info *info); server_connect_to(struct server *server, struct server_info *info);

View File

@ -1,75 +0,0 @@
#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,16 +3,56 @@
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <limits.h>
#include <signal.h> #include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
#include "util/log.h" #include "util/log.h"
enum sc_process_result bool
sc_process_execute_p(const char *const argv[], sc_pid *pid, search_executable(const char *file) {
int *pin, int *pout, int *perr) { 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) {
int in[2]; int in[2];
int out[2]; int out[2];
int err[2]; int err[2];
@ -20,44 +60,44 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid,
if (pipe(internal) == -1) { if (pipe(internal) == -1) {
perror("pipe"); perror("pipe");
return SC_PROCESS_ERROR_GENERIC; return PROCESS_ERROR_GENERIC;
} }
if (pin) { if (pipe_stdin) {
if (pipe(in) == -1) { if (pipe(in) == -1) {
perror("pipe"); perror("pipe");
close(internal[0]); close(internal[0]);
close(internal[1]); close(internal[1]);
return SC_PROCESS_ERROR_GENERIC; return PROCESS_ERROR_GENERIC;
} }
} }
if (pout) { if (pipe_stdout) {
if (pipe(out) == -1) { if (pipe(out) == -1) {
perror("pipe"); perror("pipe");
// clean up // clean up
if (pin) { if (pipe_stdin) {
close(in[0]); close(in[0]);
close(in[1]); close(in[1]);
} }
close(internal[0]); close(internal[0]);
close(internal[1]); close(internal[1]);
return SC_PROCESS_ERROR_GENERIC; return PROCESS_ERROR_GENERIC;
} }
} }
if (perr) { if (pipe_stderr) {
if (pipe(err) == -1) { if (pipe(err) == -1) {
perror("pipe"); perror("pipe");
// clean up // clean up
if (pout) { if (pipe_stdout) {
close(out[0]); close(out[0]);
close(out[1]); close(out[1]);
} }
if (pin) { if (pipe_stdin) {
close(in[0]); close(in[0]);
close(in[1]); close(in[1]);
} }
close(internal[0]); close(internal[0]);
close(internal[1]); close(internal[1]);
return SC_PROCESS_ERROR_GENERIC; return PROCESS_ERROR_GENERIC;
} }
} }
@ -65,39 +105,39 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid,
if (*pid == -1) { if (*pid == -1) {
perror("fork"); perror("fork");
// clean up // clean up
if (perr) { if (pipe_stderr) {
close(err[0]); close(err[0]);
close(err[1]); close(err[1]);
} }
if (pout) { if (pipe_stdout) {
close(out[0]); close(out[0]);
close(out[1]); close(out[1]);
} }
if (pin) { if (pipe_stdin) {
close(in[0]); close(in[0]);
close(in[1]); close(in[1]);
} }
close(internal[0]); close(internal[0]);
close(internal[1]); close(internal[1]);
return SC_PROCESS_ERROR_GENERIC; return PROCESS_ERROR_GENERIC;
} }
if (*pid == 0) { if (*pid == 0) {
if (pin) { if (pipe_stdin) {
if (in[0] != STDIN_FILENO) { if (in[0] != STDIN_FILENO) {
dup2(in[0], STDIN_FILENO); dup2(in[0], STDIN_FILENO);
close(in[0]); close(in[0]);
} }
close(in[1]); close(in[1]);
} }
if (pout) { if (pipe_stdout) {
if (out[1] != STDOUT_FILENO) { if (out[1] != STDOUT_FILENO) {
dup2(out[1], STDOUT_FILENO); dup2(out[1], STDOUT_FILENO);
close(out[1]); close(out[1]);
} }
close(out[0]); close(out[0]);
} }
if (perr) { if (pipe_stderr) {
if (err[1] != STDERR_FILENO) { if (err[1] != STDERR_FILENO) {
dup2(err[1], STDERR_FILENO); dup2(err[1], STDERR_FILENO);
close(err[1]); close(err[1]);
@ -105,15 +145,15 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid,
close(err[0]); close(err[0]);
} }
close(internal[0]); close(internal[0]);
enum sc_process_result err; enum process_result err;
if (fcntl(internal[1], F_SETFD, FD_CLOEXEC) == 0) { if (fcntl(internal[1], F_SETFD, FD_CLOEXEC) == 0) {
execvp(argv[0], (char *const *) argv); execvp(argv[0], (char *const *) argv);
perror("exec"); perror("exec");
err = errno == ENOENT ? SC_PROCESS_ERROR_MISSING_BINARY err = errno == ENOENT ? PROCESS_ERROR_MISSING_BINARY
: SC_PROCESS_ERROR_GENERIC; : PROCESS_ERROR_GENERIC;
} else { } else {
perror("fcntl"); perror("fcntl");
err = SC_PROCESS_ERROR_GENERIC; err = PROCESS_ERROR_GENERIC;
} }
// send err to the parent // send err to the parent
if (write(internal[1], &err, sizeof(err)) == -1) { if (write(internal[1], &err, sizeof(err)) == -1) {
@ -128,33 +168,38 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid,
close(internal[1]); close(internal[1]);
enum sc_process_result res = SC_PROCESS_SUCCESS; enum process_result res = PROCESS_SUCCESS;
// wait for EOF or receive err from child // wait for EOF or receive err from child
if (read(internal[0], &res, sizeof(res)) == -1) { if (read(internal[0], &res, sizeof(res)) == -1) {
perror("read"); perror("read");
res = SC_PROCESS_ERROR_GENERIC; res = PROCESS_ERROR_GENERIC;
} }
close(internal[0]); close(internal[0]);
if (pin) { if (pipe_stdin) {
close(in[0]); close(in[0]);
*pin = in[1]; *pipe_stdin = in[1];
} }
if (pout) { if (pipe_stdout) {
*pout = out[0]; *pipe_stdout = out[0];
close(out[1]); close(out[1]);
} }
if (perr) { if (pipe_stderr) {
*perr = err[0]; *pipe_stderr = err[0];
close(err[1]); close(err[1]);
} }
return res; return res;
} }
enum process_result
process_execute(const char *const argv[], pid_t *pid) {
return process_execute_redirect(argv, pid, NULL, NULL, NULL);
}
bool bool
sc_process_terminate(pid_t pid) { process_terminate(pid_t pid) {
if (pid <= 0) { if (pid <= 0) {
LOGC("Requested to kill %d, this is an error. Please report the bug.\n", LOGC("Requested to kill %d, this is an error. Please report the bug.\n",
(int) pid); (int) pid);
@ -163,8 +208,8 @@ sc_process_terminate(pid_t pid) {
return kill(pid, SIGKILL) != -1; return kill(pid, SIGKILL) != -1;
} }
sc_exit_code exit_code_t
sc_process_wait(pid_t pid, bool close) { process_wait(pid_t pid, bool close) {
int code; int code;
int options = WEXITED; int options = WEXITED;
if (!close) { if (!close) {
@ -175,7 +220,7 @@ sc_process_wait(pid_t pid, bool close) {
int r = waitid(P_PID, pid, &info, options); int r = waitid(P_PID, pid, &info, options);
if (r == -1 || info.si_code != CLD_EXITED) { if (r == -1 || info.si_code != CLD_EXITED) {
// could not wait, or exited unexpectedly, probably by a signal // could not wait, or exited unexpectedly, probably by a signal
code = SC_EXIT_CODE_NONE; code = NO_EXIT_CODE;
} else { } else {
code = info.si_status; code = info.si_status;
} }
@ -183,17 +228,48 @@ sc_process_wait(pid_t pid, bool close) {
} }
void void
sc_process_close(pid_t pid) { process_close(pid_t pid) {
sc_process_wait(pid, true); // ignore exit code 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);
} }
ssize_t ssize_t
sc_pipe_read(int pipe, char *data, size_t len) { read_pipe(int pipe, char *data, size_t len) {
return read(pipe, data, len); return read(pipe, data, len);
} }
void void
sc_pipe_close(int pipe) { close_pipe(int pipe) {
if (close(pipe)) { if (close(pipe)) {
perror("close pipe"); perror("close pipe");
} }

View File

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

View File

@ -1,48 +0,0 @@
#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;
}

View File

@ -1,49 +0,0 @@
#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

@ -3,22 +3,16 @@
#include <libgen.h> #include <libgen.h>
#include "log.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 bool
sc_process_check_success(sc_pid pid, const char *name, bool close) { process_check_success(process_t proc, const char *name, bool close) {
if (pid == SC_PROCESS_NONE) { if (proc == PROCESS_NONE) {
LOGE("Could not execute \"%s\"", name); LOGE("Could not execute \"%s\"", name);
return false; return false;
} }
sc_exit_code exit_code = sc_process_wait(pid, close); exit_code_t exit_code = process_wait(proc, close);
if (exit_code) { if (exit_code) {
if (exit_code != SC_EXIT_CODE_NONE) { if (exit_code != NO_EXIT_CODE) {
LOGE("\"%s\" returned with value %" SC_PRIexitcode, name, LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code);
exit_code);
} else { } else {
LOGE("\"%s\" exited unexpectedly", name); LOGE("\"%s\" exited unexpectedly", name);
} }
@ -27,11 +21,52 @@ sc_process_check_success(sc_pid pid, const char *name, bool close) {
return true; 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 ssize_t
sc_pipe_read_all(sc_pipe pipe, char *data, size_t len) { read_pipe_all(pipe_t pipe, char *data, size_t len) {
size_t copied = 0; size_t copied = 0;
while (len > 0) { while (len > 0) {
ssize_t r = sc_pipe_read(pipe, data, len); ssize_t r = read_pipe(pipe, data, len);
if (r <= 0) { if (r <= 0) {
return copied ? (ssize_t) copied : r; return copied ? (ssize_t) copied : r;
} }

View File

@ -10,100 +10,96 @@
// not needed here, but winsock2.h must never be included AFTER windows.h // not needed here, but winsock2.h must never be included AFTER windows.h
# include <winsock2.h> # include <winsock2.h>
# include <windows.h> # include <windows.h>
# define SC_PRIexitcode "lu" # define PATH_SEPARATOR '\\'
# define PRIexitcode "lu"
// <https://stackoverflow.com/a/44383330/1987178> // <https://stackoverflow.com/a/44383330/1987178>
# define SC_PRIsizet "Iu" # define PRIsizet "Iu"
# define SC_PROCESS_NONE NULL # define PROCESS_NONE NULL
# define SC_EXIT_CODE_NONE -1u // max value as unsigned # define NO_EXIT_CODE -1u // max value as unsigned
typedef HANDLE sc_pid; typedef HANDLE process_t;
typedef DWORD sc_exit_code; typedef DWORD exit_code_t;
typedef HANDLE sc_pipe; typedef HANDLE pipe_t;
#else #else
# include <sys/types.h> # include <sys/types.h>
# define SC_PRIsizet "zu" # define PATH_SEPARATOR '/'
# define SC_PRIexitcode "d" # define PRIsizet "zu"
# define SC_PROCESS_NONE -1 # define PRIexitcode "d"
# define SC_EXIT_CODE_NONE -1 # define PROCESS_NONE -1
typedef pid_t sc_pid; # define NO_EXIT_CODE -1
typedef int sc_exit_code; typedef pid_t process_t;
typedef int sc_pipe; typedef int exit_code_t;
typedef int pipe_t;
#endif #endif
enum sc_process_result { enum process_result {
SC_PROCESS_SUCCESS, PROCESS_SUCCESS,
SC_PROCESS_ERROR_GENERIC, PROCESS_ERROR_GENERIC,
SC_PROCESS_ERROR_MISSING_BINARY, PROCESS_ERROR_MISSING_BINARY,
}; };
/** // execute the command and write the result to the output parameter "process"
* Execute the command and write the process id to `pid` enum process_result
*/ process_execute(const char *const argv[], process_t *process);
enum sc_process_result
sc_process_execute(const char *const argv[], sc_pid *pid);
/** enum process_result
* Execute the command and write the process id to `pid` process_execute_redirect(const char *const argv[], process_t *process,
* pipe_t *pipe_stdin, pipe_t *pipe_stdout,
* If not NULL, provide a pipe for stdin (`pin`), stdout (`pout`) and stderr pipe_t *pipe_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 bool
sc_process_terminate(sc_pid pid); process_terminate(process_t pid);
/** // kill the process
* 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);
/**
* Close (reap) the process
*
* Semantically:
* sc_process_wait(close) = sc_process_wait(noclose) + sc_process_close()
*/
void
sc_process_close(sc_pid pid);
/**
* Convenience function to wait for a successful process execution
*
* Automatically log process errors with the provided process name.
*/
bool bool
sc_process_check_success(sc_pid pid, const char *name, bool close); process_terminate(process_t pid);
/** // wait and close the process (like waitpid())
* Read from the pipe // the "close" flag indicates if the process must be "closed" (reaped)
* // (passing false is equivalent to enable WNOWAIT in waitid())
* Same semantic as read(). exit_code_t
*/ process_wait(process_t pid, bool close);
ssize_t
sc_pipe_read(sc_pipe pipe, char *data, size_t len);
/** // close the process
* Read exactly `len` chars from a pipe (unless EOF) //
*/ // Semantically, process_wait(close) = process_wait(noclose) + process_close
ssize_t
sc_pipe_read_all(sc_pipe pipe, char *data, size_t len);
/**
* Close the pipe
*/
void void
sc_pipe_close(sc_pipe pipe); process_close(process_t pid);
// 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);
ssize_t
read_pipe(pipe_t pipe, char *data, size_t len);
ssize_t
read_pipe_all(pipe_t pipe, char *data, size_t len);
void
close_pipe(pipe_t pipe);
#endif #endif

View File

@ -1,11 +1,9 @@
#include "str_util.h" #include "str_util.h"
#include <assert.h>
#include <errno.h> #include <errno.h>
#include <limits.h> #include <limits.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "util/strbuf.h"
#ifdef _WIN32 #ifdef _WIN32
# include <windows.h> # include <windows.h>
@ -211,81 +209,3 @@ utf8_from_wide_char(const wchar_t *ws) {
} }
#endif #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,12 +62,4 @@ char *
utf8_from_wide_char(const wchar_t *s); utf8_from_wide_char(const wchar_t *s);
#endif #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 #endif

View File

@ -1,87 +0,0 @@
#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;
}
}

View File

@ -1,73 +0,0 @@
#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

View File

@ -1,51 +0,0 @@
#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
}

View File

@ -1,21 +0,0 @@
#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,8 +1,6 @@
#ifndef SC_TICK_H #ifndef SC_TICK_H
#define SC_TICK_H #define SC_TICK_H
#include "common.h"
#include <stdint.h> #include <stdint.h>
typedef int64_t sc_tick; typedef int64_t sc_tick;

View File

@ -1,47 +0,0 @@
#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,44 +299,6 @@ static void test_strlist_contains(void) {
assert(strlist_contains("xyz", '\0', "xyz")); 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[]) { int main(int argc, char *argv[]) {
(void) argc; (void) argc;
(void) argv; (void) argv;
@ -355,6 +317,5 @@ int main(int argc, char *argv[]) {
test_parse_integers(); test_parse_integers();
test_parse_integer_with_suffix(); test_parse_integer_with_suffix();
test_strlist_contains(); test_strlist_contains();
test_wrap_lines();
return 0; return 0;
} }