Compare commits
21 Commits
server_thr
...
process
Author | SHA1 | Date | |
---|---|---|---|
c7e46583b8 | |||
848db078a5 | |||
b197372dd3 | |||
66f21cf680 | |||
f197c1181e | |||
7a733328bc | |||
38332f683c | |||
3f51a2ab43 | |||
74ab0a4df8 | |||
f59e9e3cb0 | |||
9ec3406568 | |||
6dba1922c1 | |||
570a003c39 | |||
b62df7ee91 | |||
30d40f4e78 | |||
f489f7fcad | |||
d72c7076f7 | |||
fc64445555 | |||
f65c3fde69 | |||
48fcfa96ab | |||
676fa73d2c |
12
README.md
12
README.md
@ -582,6 +582,14 @@ 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
|
||||||
|
|
||||||
@ -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>
|
| 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>
|
||||||
@ -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>
|
| 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._
|
||||||
|
@ -24,18 +24,27 @@ 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/sys/win/process.c' ]
|
src += [
|
||||||
|
'src/sys/win/file.c',
|
||||||
|
'src/sys/win/process.c',
|
||||||
|
]
|
||||||
else
|
else
|
||||||
src += [ 'src/sys/unix/process.c' ]
|
src += [
|
||||||
|
'src/sys/unix/file.c',
|
||||||
|
'src/sys/unix/process.c',
|
||||||
|
]
|
||||||
endif
|
endif
|
||||||
|
|
||||||
v4l2_support = host_machine.system() == 'linux'
|
v4l2_support = host_machine.system() == 'linux'
|
||||||
@ -186,7 +195,9 @@ 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',
|
||||||
@ -195,6 +206,7 @@ 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', [
|
||||||
@ -204,8 +216,13 @@ 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',
|
||||||
]],
|
]],
|
||||||
]
|
]
|
||||||
|
@ -136,6 +136,10 @@ 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.
|
||||||
@ -364,6 +368,10 @@ 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
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#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"
|
||||||
|
|
||||||
@ -68,7 +69,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 (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);
|
LOGI("You may install 'adb' by \"%s\"", pkg_managers[i].command);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -80,7 +81,7 @@ show_adb_installation_msg() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
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
|
#define MAX_COMMAND_STRING_LEN 1024
|
||||||
char *buf = malloc(MAX_COMMAND_STRING_LEN);
|
char *buf = malloc(MAX_COMMAND_STRING_LEN);
|
||||||
if (!buf) {
|
if (!buf) {
|
||||||
@ -89,18 +90,18 @@ show_adb_err_msg(enum process_result err, const char *const argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (err) {
|
switch (err) {
|
||||||
case PROCESS_ERROR_GENERIC:
|
case SC_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 PROCESS_ERROR_MISSING_BINARY:
|
case SC_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 PROCESS_SUCCESS:
|
case SC_PROCESS_SUCCESS:
|
||||||
// do nothing
|
// do nothing
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -108,16 +109,15 @@ show_adb_err_msg(enum process_result err, const char *const argv[]) {
|
|||||||
free(buf);
|
free(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
process_t
|
sc_pid
|
||||||
adb_execute_redirect(const char *serial, const char *const adb_cmd[],
|
adb_execute_p(const char *serial, const char *const adb_cmd[],
|
||||||
size_t len, pipe_t *pipe_stdin, pipe_t *pipe_stdout,
|
size_t len, sc_pipe *pin, sc_pipe *pout, sc_pipe *perr) {
|
||||||
pipe_t *pipe_stderr) {
|
|
||||||
int i;
|
int i;
|
||||||
process_t process;
|
sc_pid pid;
|
||||||
|
|
||||||
const char **argv = malloc((len + 4) * sizeof(*argv));
|
const char **argv = malloc((len + 4) * sizeof(*argv));
|
||||||
if (!argv) {
|
if (!argv) {
|
||||||
return PROCESS_NONE;
|
return SC_PROCESS_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
argv[0] = get_adb_command();
|
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 *));
|
memcpy(&argv[i], adb_cmd, len * sizeof(const char *));
|
||||||
argv[len + i] = NULL;
|
argv[len + i] = NULL;
|
||||||
enum process_result r =
|
enum sc_process_result r =
|
||||||
process_execute_redirect(argv, &process, pipe_stdin, pipe_stdout,
|
sc_process_execute_p(argv, &pid, pin, pout, perr);
|
||||||
pipe_stderr);
|
if (r != SC_PROCESS_SUCCESS) {
|
||||||
if (r != PROCESS_SUCCESS) {
|
|
||||||
show_adb_err_msg(r, argv);
|
show_adb_err_msg(r, argv);
|
||||||
process = PROCESS_NONE;
|
pid = SC_PROCESS_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
free(argv);
|
free(argv);
|
||||||
return process;
|
return pid;
|
||||||
}
|
}
|
||||||
|
|
||||||
process_t
|
sc_pid
|
||||||
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_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,
|
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
|
||||||
@ -159,7 +158,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));
|
||||||
}
|
}
|
||||||
|
|
||||||
process_t
|
sc_pid
|
||||||
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);
|
||||||
@ -167,7 +166,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));
|
||||||
}
|
}
|
||||||
|
|
||||||
process_t
|
sc_pid
|
||||||
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
|
||||||
@ -178,7 +177,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));
|
||||||
}
|
}
|
||||||
|
|
||||||
process_t
|
sc_pid
|
||||||
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);
|
||||||
@ -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));
|
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) {
|
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 PROCESS_NONE;
|
return SC_PROCESS_NONE;
|
||||||
}
|
}
|
||||||
remote = strquote(remote);
|
remote = strquote(remote);
|
||||||
if (!remote) {
|
if (!remote) {
|
||||||
free((void *) local);
|
free((void *) local);
|
||||||
return PROCESS_NONE;
|
return SC_PROCESS_NONE;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const char *const adb_cmd[] = {"push", local, remote};
|
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__
|
#ifdef __WINDOWS__
|
||||||
free((void *) remote);
|
free((void *) remote);
|
||||||
free((void *) local);
|
free((void *) local);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return proc;
|
return pid;
|
||||||
}
|
}
|
||||||
|
|
||||||
process_t
|
sc_pid
|
||||||
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 PROCESS_NONE;
|
return SC_PROCESS_NONE;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const char *const adb_cmd[] = {"install", "-r", local};
|
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__
|
#ifdef __WINDOWS__
|
||||||
free((void *) local);
|
free((void *) local);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return proc;
|
return pid;
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
pipe_t pipe_stdout;
|
sc_pipe pout;
|
||||||
process_t proc = adb_execute_redirect(serial, adb_cmd, adb_cmd_len, NULL,
|
sc_pid pid = adb_execute_p(serial, adb_cmd, adb_cmd_len, NULL, &pout, NULL);
|
||||||
&pipe_stdout, NULL);
|
|
||||||
|
|
||||||
ssize_t r = read_pipe_all(pipe_stdout, buf, buf_len);
|
ssize_t r = sc_pipe_read_all(pout, buf, buf_len);
|
||||||
close_pipe(pipe_stdout);
|
sc_pipe_close(pout);
|
||||||
|
|
||||||
if (!process_check_success(proc, name, true)) {
|
if (!sc_process_check_success(pid, name, true)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,32 +8,31 @@
|
|||||||
|
|
||||||
#include "util/process.h"
|
#include "util/process.h"
|
||||||
|
|
||||||
process_t
|
sc_pid
|
||||||
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);
|
||||||
|
|
||||||
process_t
|
sc_pid
|
||||||
adb_execute_redirect(const char *serial, const char *const adb_cmd[],
|
adb_execute_p(const char *serial, const char *const adb_cmd[],
|
||||||
size_t len, pipe_t *pipe_stdin, pipe_t *pipe_stdout,
|
size_t len, sc_pipe *pin, sc_pipe *pout, sc_pipe *perr);
|
||||||
pipe_t *pipe_stderr);
|
|
||||||
|
|
||||||
process_t
|
sc_pid
|
||||||
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);
|
||||||
|
|
||||||
process_t
|
sc_pid
|
||||||
adb_forward_remove(const char *serial, uint16_t local_port);
|
adb_forward_remove(const char *serial, uint16_t local_port);
|
||||||
|
|
||||||
process_t
|
sc_pid
|
||||||
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);
|
||||||
|
|
||||||
process_t
|
sc_pid
|
||||||
adb_reverse_remove(const char *serial, const char *device_socket_name);
|
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);
|
adb_push(const char *serial, const char *local, const char *remote);
|
||||||
|
|
||||||
process_t
|
sc_pid
|
||||||
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".
|
||||||
|
1189
app/src/cli.c
1189
app/src/cli.c
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,2 @@
|
|||||||
#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)
|
|
||||||
|
@ -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 = PROCESS_NONE;
|
file_handler->current_process = SC_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 process_t
|
static sc_pid
|
||||||
install_apk(const char *serial, const char *file) {
|
install_apk(const char *serial, const char *file) {
|
||||||
return adb_install(serial, file);
|
return adb_install(serial, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
static process_t
|
static sc_pid
|
||||||
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 = PROCESS_NONE;
|
file_handler->current_process = SC_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;
|
||||||
|
|
||||||
process_t process;
|
sc_pid pid;
|
||||||
if (req.action == ACTION_INSTALL_APK) {
|
if (req.action == ACTION_INSTALL_APK) {
|
||||||
LOGI("Installing %s...", req.file);
|
LOGI("Installing %s...", req.file);
|
||||||
process = install_apk(file_handler->serial, req.file);
|
pid = install_apk(file_handler->serial, req.file);
|
||||||
} else {
|
} else {
|
||||||
LOGI("Pushing %s...", req.file);
|
LOGI("Pushing %s...", req.file);
|
||||||
process = push_file(file_handler->serial, req.file,
|
pid = push_file(file_handler->serial, req.file,
|
||||||
file_handler->push_target);
|
file_handler->push_target);
|
||||||
}
|
}
|
||||||
file_handler->current_process = process;
|
file_handler->current_process = pid;
|
||||||
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 (process_check_success(process, "adb install", false)) {
|
if (sc_process_check_success(pid, "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 (process_check_success(process, "adb push", false)) {
|
if (sc_process_check_success(pid, "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 necessary already terminated)
|
// Close the process (it is necessarily 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()
|
||||||
process_close(file_handler->current_process);
|
sc_process_close(file_handler->current_process);
|
||||||
file_handler->current_process = PROCESS_NONE;
|
file_handler->current_process = SC_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 != PROCESS_NONE) {
|
if (file_handler->current_process != SC_PROCESS_NONE) {
|
||||||
if (!process_terminate(file_handler->current_process)) {
|
if (!sc_process_terminate(file_handler->current_process)) {
|
||||||
LOGW("Could not terminate push/install process");
|
LOGW("Could not terminate push/install process");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ struct file_handler {
|
|||||||
sc_cond event_cond;
|
sc_cond event_cond;
|
||||||
bool stopped;
|
bool stopped;
|
||||||
bool initialized;
|
bool initialized;
|
||||||
process_t current_process;
|
sc_pid current_process;
|
||||||
struct file_handler_request_queue queue;
|
struct file_handler_request_queue queue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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 = get_local_file_path(SCRCPY_PORTABLE_ICON_FILENAME);
|
char *icon_path = sc_file_get_local_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;
|
||||||
|
@ -217,29 +217,6 @@ 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) {
|
||||||
@ -285,31 +262,6 @@ 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;
|
||||||
@ -323,6 +275,10 @@ 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;
|
||||||
@ -357,17 +313,7 @@ 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, ¶ms)) {
|
||||||
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, ¶ms, &cbs, NULL)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!server_start(&s->server)) {
|
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -385,15 +331,14 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
|
|
||||||
sdl_configure(options->display, options->disable_screensaver);
|
sdl_configure(options->display, options->disable_screensaver);
|
||||||
|
|
||||||
// Await for server without blocking Ctrl+C handling
|
struct server_info info;
|
||||||
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, options->serial,
|
if (!file_handler_init(&s->file_handler, s->server.serial,
|
||||||
options->push_target)) {
|
options->push_target)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
@ -415,7 +360,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;
|
||||||
@ -461,11 +406,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,
|
||||||
@ -489,7 +434,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
267
app/src/server.c
267
app/src/server.c
@ -8,6 +8,7 @@
|
|||||||
#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"
|
||||||
@ -48,7 +49,7 @@ get_server_path(void) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
char *server_path = get_local_file_path(SERVER_FILENAME);
|
char *server_path = sc_file_get_local_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");
|
||||||
@ -61,91 +62,52 @@ 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 (!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);
|
LOGE("'%s' does not exist or is not a regular file\n", server_path);
|
||||||
free(server_path);
|
free(server_path);
|
||||||
return false;
|
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);
|
free(server_path);
|
||||||
return process_check_success(process, "adb push", true);
|
return sc_process_check_success(pid, "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) {
|
||||||
process_t process = adb_reverse(serial, SOCKET_NAME, local_port);
|
sc_pid pid = adb_reverse(serial, SOCKET_NAME, local_port);
|
||||||
return process_check_success(process, "adb reverse", true);
|
return sc_process_check_success(pid, "adb reverse", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
disable_tunnel_reverse(const char *serial) {
|
disable_tunnel_reverse(const char *serial) {
|
||||||
process_t process = adb_reverse_remove(serial, SOCKET_NAME);
|
sc_pid pid = adb_reverse_remove(serial, SOCKET_NAME);
|
||||||
return process_check_success(process, "adb reverse --remove", true);
|
return sc_process_check_success(pid, "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) {
|
||||||
process_t process = adb_forward(serial, local_port, SOCKET_NAME);
|
sc_pid pid = adb_forward(serial, local_port, SOCKET_NAME);
|
||||||
return process_check_success(process, "adb forward", true);
|
return sc_process_check_success(pid, "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) {
|
||||||
process_t process = adb_forward_remove(serial, local_port);
|
sc_pid pid = adb_forward_remove(serial, local_port);
|
||||||
return process_check_success(process, "adb forward --remove", true);
|
return sc_process_check_success(pid, "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(serial, server->local_port);
|
return disable_tunnel_forward(server->serial, server->local_port);
|
||||||
}
|
}
|
||||||
return disable_tunnel_reverse(serial);
|
return disable_tunnel_reverse(server->serial);
|
||||||
}
|
}
|
||||||
|
|
||||||
static sc_socket
|
static sc_socket
|
||||||
@ -157,10 +119,9 @@ 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(serial, port)) {
|
if (!enable_tunnel_reverse(server->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;
|
||||||
}
|
}
|
||||||
@ -179,7 +140,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(serial)) {
|
if (!disable_tunnel_reverse(server->serial)) {
|
||||||
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
|
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,11 +166,9 @@ 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(serial, port)) {
|
if (enable_tunnel_forward(server->serial, port)) {
|
||||||
// success
|
// success
|
||||||
server->local_port = port;
|
server->local_port = port;
|
||||||
return true;
|
return true;
|
||||||
@ -269,10 +228,8 @@ 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) {
|
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];
|
||||||
@ -330,7 +287,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(serial, cmd, ARRAY_LEN(cmd));
|
return adb_execute(server->serial, cmd, ARRAY_LEN(cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
static sc_socket
|
static sc_socket
|
||||||
@ -352,8 +309,7 @@ connect_and_read_byte(uint16_t port) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static sc_socket
|
static sc_socket
|
||||||
connect_to_server(struct server *server, uint32_t attempts, sc_tick delay) {
|
connect_to_server(uint16_t port, uint32_t attempts, uint32_t 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);
|
||||||
@ -361,58 +317,30 @@ connect_to_server(struct server *server, uint32_t attempts, sc_tick delay) {
|
|||||||
// it worked!
|
// it worked!
|
||||||
return socket;
|
return socket;
|
||||||
}
|
}
|
||||||
// TODO use mutex + condvar + bool stopped
|
|
||||||
if (attempts) {
|
if (attempts) {
|
||||||
sc_mutex_lock(&server->mutex);
|
SDL_Delay(delay);
|
||||||
// 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, const struct server_params *params,
|
server_init(struct server *server) {
|
||||||
const struct server_callbacks *cbs, void *cbs_userdata) {
|
server->serial = NULL;
|
||||||
bool ok = server_params_copy(&server->params, params);
|
server->process = SC_PROCESS_NONE;
|
||||||
if (!ok) {
|
|
||||||
LOGE("Could not copy server params");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
server->process = PROCESS_NONE;
|
bool ok = sc_mutex_init(&server->mutex);
|
||||||
|
|
||||||
ok = sc_mutex_init(&server->mutex);
|
|
||||||
if (!ok) {
|
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;
|
||||||
@ -423,69 +351,13 @@ server_init(struct server *server, const struct server_params *params,
|
|||||||
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_server_connect(void *data) {
|
run_wait_server(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;
|
||||||
@ -499,22 +371,59 @@ run_server(void *data) {
|
|||||||
net_interrupt(server->server_socket);
|
net_interrupt(server->server_socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_thread_join(&connect_thread, NULL);
|
LOGD("Server terminated");
|
||||||
|
|
||||||
// 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) {
|
server_start(struct server *server, const struct server_params *params) {
|
||||||
return sc_thread_create(&server->thread, run_server, "server", server);
|
if (params->serial) {
|
||||||
|
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
|
||||||
@ -558,8 +467,9 @@ 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;
|
||||||
sc_tick delay = SC_TICK_FROM_MS(100);
|
uint32_t delay = 100; // ms
|
||||||
server->video_socket = connect_to_server(server, attempts, delay);
|
server->video_socket =
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
@ -582,11 +492,6 @@ 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");
|
||||||
@ -603,7 +508,7 @@ server_stop(struct server *server) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(server->process != PROCESS_NONE);
|
assert(server->process != SC_PROCESS_NONE);
|
||||||
|
|
||||||
if (server->tunnel_enabled) {
|
if (server->tunnel_enabled) {
|
||||||
// ignore failure
|
// ignore failure
|
||||||
@ -628,11 +533,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...");
|
||||||
process_terminate(server->process);
|
sc_process_terminate(server->process);
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_thread_join(&server->thread, NULL);
|
sc_thread_join(&server->wait_server_thread, NULL);
|
||||||
process_close(server->process);
|
sc_process_close(server->process);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -652,9 +557,7 @@ 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);
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,23 @@ 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;
|
||||||
@ -39,52 +56,15 @@ struct server_params {
|
|||||||
bool power_off_on_close;
|
bool power_off_on_close;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct server {
|
// init default values
|
||||||
// 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, const struct server_params *params,
|
server_init(struct server *server);
|
||||||
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);
|
server_start(struct server *server, const struct server_params *params);
|
||||||
|
|
||||||
// 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);
|
||||||
|
|
||||||
|
75
app/src/sys/unix/file.c
Normal file
75
app/src/sys/unix/file.c
Normal 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);
|
||||||
|
}
|
||||||
|
|
@ -3,56 +3,16 @@
|
|||||||
#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"
|
||||||
|
|
||||||
bool
|
enum sc_process_result
|
||||||
search_executable(const char *file) {
|
sc_process_execute_p(const char *const argv[], sc_pid *pid,
|
||||||
char *path = getenv("PATH");
|
int *pin, int *pout, int *perr) {
|
||||||
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];
|
||||||
@ -60,44 +20,44 @@ process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin,
|
|||||||
|
|
||||||
if (pipe(internal) == -1) {
|
if (pipe(internal) == -1) {
|
||||||
perror("pipe");
|
perror("pipe");
|
||||||
return PROCESS_ERROR_GENERIC;
|
return SC_PROCESS_ERROR_GENERIC;
|
||||||
}
|
}
|
||||||
if (pipe_stdin) {
|
if (pin) {
|
||||||
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 PROCESS_ERROR_GENERIC;
|
return SC_PROCESS_ERROR_GENERIC;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pipe_stdout) {
|
if (pout) {
|
||||||
if (pipe(out) == -1) {
|
if (pipe(out) == -1) {
|
||||||
perror("pipe");
|
perror("pipe");
|
||||||
// clean up
|
// clean up
|
||||||
if (pipe_stdin) {
|
if (pin) {
|
||||||
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 PROCESS_ERROR_GENERIC;
|
return SC_PROCESS_ERROR_GENERIC;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pipe_stderr) {
|
if (perr) {
|
||||||
if (pipe(err) == -1) {
|
if (pipe(err) == -1) {
|
||||||
perror("pipe");
|
perror("pipe");
|
||||||
// clean up
|
// clean up
|
||||||
if (pipe_stdout) {
|
if (pout) {
|
||||||
close(out[0]);
|
close(out[0]);
|
||||||
close(out[1]);
|
close(out[1]);
|
||||||
}
|
}
|
||||||
if (pipe_stdin) {
|
if (pin) {
|
||||||
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 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) {
|
if (*pid == -1) {
|
||||||
perror("fork");
|
perror("fork");
|
||||||
// clean up
|
// clean up
|
||||||
if (pipe_stderr) {
|
if (perr) {
|
||||||
close(err[0]);
|
close(err[0]);
|
||||||
close(err[1]);
|
close(err[1]);
|
||||||
}
|
}
|
||||||
if (pipe_stdout) {
|
if (pout) {
|
||||||
close(out[0]);
|
close(out[0]);
|
||||||
close(out[1]);
|
close(out[1]);
|
||||||
}
|
}
|
||||||
if (pipe_stdin) {
|
if (pin) {
|
||||||
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 PROCESS_ERROR_GENERIC;
|
return SC_PROCESS_ERROR_GENERIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*pid == 0) {
|
if (*pid == 0) {
|
||||||
if (pipe_stdin) {
|
if (pin) {
|
||||||
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 (pipe_stdout) {
|
if (pout) {
|
||||||
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 (pipe_stderr) {
|
if (perr) {
|
||||||
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]);
|
||||||
@ -145,15 +105,15 @@ process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin,
|
|||||||
close(err[0]);
|
close(err[0]);
|
||||||
}
|
}
|
||||||
close(internal[0]);
|
close(internal[0]);
|
||||||
enum process_result err;
|
enum sc_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 ? PROCESS_ERROR_MISSING_BINARY
|
err = errno == ENOENT ? SC_PROCESS_ERROR_MISSING_BINARY
|
||||||
: PROCESS_ERROR_GENERIC;
|
: SC_PROCESS_ERROR_GENERIC;
|
||||||
} else {
|
} else {
|
||||||
perror("fcntl");
|
perror("fcntl");
|
||||||
err = PROCESS_ERROR_GENERIC;
|
err = SC_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) {
|
||||||
@ -168,38 +128,33 @@ process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin,
|
|||||||
|
|
||||||
close(internal[1]);
|
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
|
// 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 = PROCESS_ERROR_GENERIC;
|
res = SC_PROCESS_ERROR_GENERIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
close(internal[0]);
|
close(internal[0]);
|
||||||
|
|
||||||
if (pipe_stdin) {
|
if (pin) {
|
||||||
close(in[0]);
|
close(in[0]);
|
||||||
*pipe_stdin = in[1];
|
*pin = in[1];
|
||||||
}
|
}
|
||||||
if (pipe_stdout) {
|
if (pout) {
|
||||||
*pipe_stdout = out[0];
|
*pout = out[0];
|
||||||
close(out[1]);
|
close(out[1]);
|
||||||
}
|
}
|
||||||
if (pipe_stderr) {
|
if (perr) {
|
||||||
*pipe_stderr = err[0];
|
*perr = 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
|
||||||
process_terminate(pid_t pid) {
|
sc_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);
|
||||||
@ -208,8 +163,8 @@ process_terminate(pid_t pid) {
|
|||||||
return kill(pid, SIGKILL) != -1;
|
return kill(pid, SIGKILL) != -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
exit_code_t
|
sc_exit_code
|
||||||
process_wait(pid_t pid, bool close) {
|
sc_process_wait(pid_t pid, bool close) {
|
||||||
int code;
|
int code;
|
||||||
int options = WEXITED;
|
int options = WEXITED;
|
||||||
if (!close) {
|
if (!close) {
|
||||||
@ -220,7 +175,7 @@ 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 = NO_EXIT_CODE;
|
code = SC_EXIT_CODE_NONE;
|
||||||
} else {
|
} else {
|
||||||
code = info.si_status;
|
code = info.si_status;
|
||||||
}
|
}
|
||||||
@ -228,48 +183,17 @@ process_wait(pid_t pid, bool close) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
process_close(pid_t pid) {
|
sc_process_close(pid_t pid) {
|
||||||
process_wait(pid, true); // ignore exit code
|
sc_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
|
||||||
read_pipe(int pipe, char *data, size_t len) {
|
sc_pipe_read(int pipe, char *data, size_t len) {
|
||||||
return read(pipe, data, len);
|
return read(pipe, data, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
close_pipe(int pipe) {
|
sc_pipe_close(int pipe) {
|
||||||
if (close(pipe)) {
|
if (close(pipe)) {
|
||||||
perror("close pipe");
|
perror("close pipe");
|
||||||
}
|
}
|
||||||
|
43
app/src/sys/win/file.c
Normal file
43
app/src/sys/win/file.c
Normal 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);
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
|||||||
#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"
|
||||||
@ -16,17 +15,16 @@ 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 (%" PRIsizet " chars)", len - 1);
|
LOGE("Command too long (%" SC_PRIsizet " chars)", len - 1);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum process_result
|
enum sc_process_result
|
||||||
process_execute_redirect(const char *const argv[], HANDLE *handle,
|
sc_process_execute_p(const char *const argv[], HANDLE *handle,
|
||||||
HANDLE *pipe_stdin, HANDLE *pipe_stdout,
|
HANDLE *pin, HANDLE *pout, HANDLE *perr) {
|
||||||
HANDLE *pipe_stderr) {
|
enum sc_process_result ret = SC_PROCESS_ERROR_GENERIC;
|
||||||
enum process_result ret = PROCESS_ERROR_GENERIC;
|
|
||||||
|
|
||||||
SECURITY_ATTRIBUTES sa;
|
SECURITY_ATTRIBUTES sa;
|
||||||
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||||
@ -36,32 +34,32 @@ process_execute_redirect(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 (pipe_stdin) {
|
if (pin) {
|
||||||
if (!CreatePipe(&stdin_read_handle, pipe_stdin, &sa, 0)) {
|
if (!CreatePipe(&stdin_read_handle, pin, &sa, 0)) {
|
||||||
perror("pipe");
|
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");
|
LOGE("SetHandleInformation stdin failed");
|
||||||
goto error_close_stdin;
|
goto error_close_stdin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pipe_stdout) {
|
if (pout) {
|
||||||
if (!CreatePipe(pipe_stdout, &stdout_write_handle, &sa, 0)) {
|
if (!CreatePipe(pout, &stdout_write_handle, &sa, 0)) {
|
||||||
perror("pipe");
|
perror("pipe");
|
||||||
goto error_close_stdin;
|
goto error_close_stdin;
|
||||||
}
|
}
|
||||||
if (!SetHandleInformation(*pipe_stdout, HANDLE_FLAG_INHERIT, 0)) {
|
if (!SetHandleInformation(*pout, HANDLE_FLAG_INHERIT, 0)) {
|
||||||
LOGE("SetHandleInformation stdout failed");
|
LOGE("SetHandleInformation stdout failed");
|
||||||
goto error_close_stdout;
|
goto error_close_stdout;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pipe_stderr) {
|
if (perr) {
|
||||||
if (!CreatePipe(pipe_stderr, &stderr_write_handle, &sa, 0)) {
|
if (!CreatePipe(perr, &stderr_write_handle, &sa, 0)) {
|
||||||
perror("pipe");
|
perror("pipe");
|
||||||
goto error_close_stdout;
|
goto error_close_stdout;
|
||||||
}
|
}
|
||||||
if (!SetHandleInformation(*pipe_stderr, HANDLE_FLAG_INHERIT, 0)) {
|
if (!SetHandleInformation(*perr, HANDLE_FLAG_INHERIT, 0)) {
|
||||||
LOGE("SetHandleInformation stderr failed");
|
LOGE("SetHandleInformation stderr failed");
|
||||||
goto error_close_stderr;
|
goto error_close_stderr;
|
||||||
}
|
}
|
||||||
@ -71,15 +69,15 @@ process_execute_redirect(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 (pipe_stdin || pipe_stdout || pipe_stderr) {
|
if (pin || pout || perr) {
|
||||||
si.dwFlags = STARTF_USESTDHANDLES;
|
si.dwFlags = STARTF_USESTDHANDLES;
|
||||||
if (pipe_stdin) {
|
if (pin) {
|
||||||
si.hStdInput = stdin_read_handle;
|
si.hStdInput = stdin_read_handle;
|
||||||
}
|
}
|
||||||
if (pipe_stdout) {
|
if (pout) {
|
||||||
si.hStdOutput = stdout_write_handle;
|
si.hStdOutput = stdout_write_handle;
|
||||||
}
|
}
|
||||||
if (pipe_stderr) {
|
if (perr) {
|
||||||
si.hStdError = stderr_write_handle;
|
si.hStdError = stderr_write_handle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -103,63 +101,58 @@ process_execute_redirect(const char *const argv[], HANDLE *handle,
|
|||||||
*handle = NULL;
|
*handle = NULL;
|
||||||
|
|
||||||
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
|
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
|
||||||
ret = PROCESS_ERROR_MISSING_BINARY;
|
ret = SC_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 (pipe_stdin) {
|
if (pin) {
|
||||||
CloseHandle(stdin_read_handle);
|
CloseHandle(stdin_read_handle);
|
||||||
}
|
}
|
||||||
if (pipe_stdout) {
|
if (pout) {
|
||||||
CloseHandle(stdout_write_handle);
|
CloseHandle(stdout_write_handle);
|
||||||
}
|
}
|
||||||
if (pipe_stderr) {
|
if (perr) {
|
||||||
CloseHandle(stderr_write_handle);
|
CloseHandle(stderr_write_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
free(wide);
|
free(wide);
|
||||||
*handle = pi.hProcess;
|
*handle = pi.hProcess;
|
||||||
|
|
||||||
return PROCESS_SUCCESS;
|
return SC_PROCESS_SUCCESS;
|
||||||
|
|
||||||
error_close_stderr:
|
error_close_stderr:
|
||||||
if (pipe_stderr) {
|
if (perr) {
|
||||||
CloseHandle(*pipe_stderr);
|
CloseHandle(*perr);
|
||||||
CloseHandle(stderr_write_handle);
|
CloseHandle(stderr_write_handle);
|
||||||
}
|
}
|
||||||
error_close_stdout:
|
error_close_stdout:
|
||||||
if (pipe_stdout) {
|
if (pout) {
|
||||||
CloseHandle(*pipe_stdout);
|
CloseHandle(*pout);
|
||||||
CloseHandle(stdout_write_handle);
|
CloseHandle(stdout_write_handle);
|
||||||
}
|
}
|
||||||
error_close_stdin:
|
error_close_stdin:
|
||||||
if (pipe_stdin) {
|
if (pin) {
|
||||||
CloseHandle(*pipe_stdin);
|
CloseHandle(*pin);
|
||||||
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
|
||||||
process_terminate(HANDLE handle) {
|
sc_process_terminate(HANDLE handle) {
|
||||||
return TerminateProcess(handle, 1);
|
return TerminateProcess(handle, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
exit_code_t
|
sc_exit_code
|
||||||
process_wait(HANDLE handle, bool close) {
|
sc_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 = NO_EXIT_CODE; // max value, it's unsigned
|
code = SC_EXIT_CODE_NONE;
|
||||||
}
|
}
|
||||||
if (close) {
|
if (close) {
|
||||||
CloseHandle(handle);
|
CloseHandle(handle);
|
||||||
@ -168,48 +161,14 @@ process_wait(HANDLE handle, bool close) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
process_close(HANDLE handle) {
|
sc_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
|
||||||
read_pipe(HANDLE pipe, char *data, size_t len) {
|
sc_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;
|
||||||
@ -218,7 +177,7 @@ read_pipe(HANDLE pipe, char *data, size_t len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
close_pipe(HANDLE pipe) {
|
sc_close_pipe(HANDLE pipe) {
|
||||||
if (!CloseHandle(pipe)) {
|
if (!CloseHandle(pipe)) {
|
||||||
LOGW("Cannot close pipe");
|
LOGW("Cannot close pipe");
|
||||||
}
|
}
|
||||||
|
48
app/src/util/file.c
Normal file
48
app/src/util/file.c
Normal 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
49
app/src/util/file.h
Normal 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
|
@ -3,16 +3,22 @@
|
|||||||
#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
|
||||||
process_check_success(process_t proc, const char *name, bool close) {
|
sc_process_check_success(sc_pid pid, const char *name, bool close) {
|
||||||
if (proc == PROCESS_NONE) {
|
if (pid == SC_PROCESS_NONE) {
|
||||||
LOGE("Could not execute \"%s\"", name);
|
LOGE("Could not execute \"%s\"", name);
|
||||||
return false;
|
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) {
|
||||||
if (exit_code != NO_EXIT_CODE) {
|
if (exit_code != SC_EXIT_CODE_NONE) {
|
||||||
LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code);
|
LOGE("\"%s\" returned with value %" SC_PRIexitcode, name,
|
||||||
|
exit_code);
|
||||||
} else {
|
} else {
|
||||||
LOGE("\"%s\" exited unexpectedly", name);
|
LOGE("\"%s\" exited unexpectedly", name);
|
||||||
}
|
}
|
||||||
@ -21,52 +27,11 @@ process_check_success(process_t proc, 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
|
||||||
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;
|
size_t copied = 0;
|
||||||
while (len > 0) {
|
while (len > 0) {
|
||||||
ssize_t r = read_pipe(pipe, data, len);
|
ssize_t r = sc_pipe_read(pipe, data, len);
|
||||||
if (r <= 0) {
|
if (r <= 0) {
|
||||||
return copied ? (ssize_t) copied : r;
|
return copied ? (ssize_t) copied : r;
|
||||||
}
|
}
|
||||||
|
@ -10,96 +10,100 @@
|
|||||||
// 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 PATH_SEPARATOR '\\'
|
# define SC_PRIexitcode "lu"
|
||||||
# define PRIexitcode "lu"
|
|
||||||
// <https://stackoverflow.com/a/44383330/1987178>
|
// <https://stackoverflow.com/a/44383330/1987178>
|
||||||
# define PRIsizet "Iu"
|
# define SC_PRIsizet "Iu"
|
||||||
# define PROCESS_NONE NULL
|
# define SC_PROCESS_NONE NULL
|
||||||
# define NO_EXIT_CODE -1u // max value as unsigned
|
# define SC_EXIT_CODE_NONE -1u // max value as unsigned
|
||||||
typedef HANDLE process_t;
|
typedef HANDLE sc_pid;
|
||||||
typedef DWORD exit_code_t;
|
typedef DWORD sc_exit_code;
|
||||||
typedef HANDLE pipe_t;
|
typedef HANDLE sc_pipe;
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
# include <sys/types.h>
|
# include <sys/types.h>
|
||||||
# define PATH_SEPARATOR '/'
|
# define SC_PRIsizet "zu"
|
||||||
# define PRIsizet "zu"
|
# define SC_PRIexitcode "d"
|
||||||
# define PRIexitcode "d"
|
# define SC_PROCESS_NONE -1
|
||||||
# define PROCESS_NONE -1
|
# define SC_EXIT_CODE_NONE -1
|
||||||
# define NO_EXIT_CODE -1
|
typedef pid_t sc_pid;
|
||||||
typedef pid_t process_t;
|
typedef int sc_exit_code;
|
||||||
typedef int exit_code_t;
|
typedef int sc_pipe;
|
||||||
typedef int pipe_t;
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
enum process_result {
|
enum sc_process_result {
|
||||||
PROCESS_SUCCESS,
|
SC_PROCESS_SUCCESS,
|
||||||
PROCESS_ERROR_GENERIC,
|
SC_PROCESS_ERROR_GENERIC,
|
||||||
PROCESS_ERROR_MISSING_BINARY,
|
SC_PROCESS_ERROR_MISSING_BINARY,
|
||||||
};
|
};
|
||||||
|
|
||||||
// execute the command and write the result to the output parameter "process"
|
/**
|
||||||
enum process_result
|
* Execute the command and write the process id to `pid`
|
||||||
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
|
/**
|
||||||
process_execute_redirect(const char *const argv[], process_t *process,
|
* Execute the command and write the process id to `pid`
|
||||||
pipe_t *pipe_stdin, pipe_t *pipe_stdout,
|
*
|
||||||
pipe_t *pipe_stderr);
|
* 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
|
bool
|
||||||
process_terminate(process_t pid);
|
sc_process_terminate(sc_pid pid);
|
||||||
|
|
||||||
// kill the process
|
/**
|
||||||
bool
|
* Wait and close the process (similar to waitpid())
|
||||||
process_terminate(process_t pid);
|
*
|
||||||
|
* 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)
|
* Close (reap) the process
|
||||||
// (passing false is equivalent to enable WNOWAIT in waitid())
|
*
|
||||||
exit_code_t
|
* Semantically:
|
||||||
process_wait(process_t pid, bool close);
|
* sc_process_wait(close) = sc_process_wait(noclose) + sc_process_close()
|
||||||
|
*/
|
||||||
// close the process
|
|
||||||
//
|
|
||||||
// Semantically, process_wait(close) = process_wait(noclose) + process_close
|
|
||||||
void
|
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
|
bool
|
||||||
process_check_success(process_t proc, const char *name, bool close);
|
sc_process_check_success(sc_pid pid, 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);
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read from the pipe
|
||||||
|
*
|
||||||
|
* Same semantic as read().
|
||||||
|
*/
|
||||||
ssize_t
|
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
|
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
|
void
|
||||||
close_pipe(pipe_t pipe);
|
sc_pipe_close(sc_pipe pipe);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
#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>
|
||||||
@ -209,3 +211,81 @@ 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;
|
||||||
|
}
|
||||||
|
@ -62,4 +62,12 @@ 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
|
||||||
|
87
app/src/util/strbuf.c
Normal file
87
app/src/util/strbuf.c
Normal 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
73
app/src/util/strbuf.h
Normal 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
51
app/src/util/term.c
Normal 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
21
app/src/util/term.h
Normal 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
|
@ -1,6 +1,8 @@
|
|||||||
#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;
|
||||||
|
47
app/tests/test_strbuf.c
Normal file
47
app/tests/test_strbuf.c
Normal 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;
|
||||||
|
}
|
@ -299,6 +299,44 @@ 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;
|
||||||
@ -317,5 +355,6 @@ 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;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user