Compare commits
12 Commits
Author | SHA1 | Date |
---|---|---|
Romain Vimont | a859d4cea6 | |
Romain Vimont | 802afce455 | |
Romain Vimont | 56b58d0908 | |
Romain Vimont | 86203c691d | |
Romain Vimont | 6aa4114cd8 | |
Romain Vimont | ff1ddc11e4 | |
Romain Vimont | d39f8c76b4 | |
Romain Vimont | 3bc00accde | |
Romain Vimont | b85fcb5e8e | |
Romain Vimont | eb10e3510a | |
Romain Vimont | 6725d80967 | |
Romain Vimont | 2186616584 |
29
README.md
29
README.md
|
@ -1,26 +1,37 @@
|
|||
# scrcpy (v1.19)
|
||||
|
||||
![icon](data/icon.png)
|
||||
<img src="data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
|
||||
|
||||
[Read in another language](#translations)
|
||||
|
||||
This application provides display and control of Android devices connected on
|
||||
USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access.
|
||||
This application provides display and control of Android devices connected via
|
||||
USB (or [over TCP/IP](#wireless)). It does not require any _root_ access.
|
||||
It works on _GNU/Linux_, _Windows_ and _macOS_.
|
||||
|
||||
![screenshot](assets/screenshot-debian-600.jpg)
|
||||
|
||||
It focuses on:
|
||||
|
||||
- **lightness** (native, displays only the device screen)
|
||||
- **performance** (30~60fps)
|
||||
- **quality** (1920×1080 or above)
|
||||
- **low latency** ([35~70ms][lowlatency])
|
||||
- **low startup time** (~1 second to display the first image)
|
||||
- **non-intrusiveness** (nothing is left installed on the device)
|
||||
- **lightness**: native, displays only the device screen
|
||||
- **performance**: 30~120fps, depending on the device
|
||||
- **quality**: 1920×1080 or above
|
||||
- **low latency**: [35~70ms][lowlatency]
|
||||
- **low startup time**: ~1 second to display the first image
|
||||
- **non-intrusiveness**: nothing is left installed on the device
|
||||
- **user benefits**: no account, no ads, no internet required
|
||||
- **freedom**: free and open source software
|
||||
|
||||
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
|
||||
|
||||
Its features include:
|
||||
- [recording](#recording)
|
||||
- mirroring with [device screen off](#turn-screen-off)
|
||||
- [copy-paste](#copy-paste) in both directions
|
||||
- [configurable quality](#capture-configuration)
|
||||
- device screen [as a webcam (V4L2)](#v4l2loopback) (Linux-only)
|
||||
- [physical keyboard simulation (HID)](#physical-keyboard-simulation-hid)
|
||||
(Linux-only)
|
||||
- and more…
|
||||
|
||||
## Requirements
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
src = [
|
||||
'src/main.c',
|
||||
'src/adb.c',
|
||||
'src/adb_tunnel.c',
|
||||
'src/cli.c',
|
||||
'src/clock.c',
|
||||
'src/compat.c',
|
||||
|
@ -32,7 +33,7 @@ src = [
|
|||
'src/util/process.c',
|
||||
'src/util/process_intr.c',
|
||||
'src/util/strbuf.c',
|
||||
'src/util/str_util.c',
|
||||
'src/util/str.c',
|
||||
'src/util/term.c',
|
||||
'src/util/thread.c',
|
||||
'src/util/tick.c',
|
||||
|
@ -198,8 +199,8 @@ if get_option('buildtype') == 'debug'
|
|||
'tests/test_cli.c',
|
||||
'src/cli.c',
|
||||
'src/options.c',
|
||||
'src/util/str.c',
|
||||
'src/util/strbuf.c',
|
||||
'src/util/str_util.c',
|
||||
'src/util/term.c',
|
||||
]],
|
||||
['test_clock', [
|
||||
|
@ -209,8 +210,8 @@ if get_option('buildtype') == 'debug'
|
|||
['test_control_msg_serialize', [
|
||||
'tests/test_control_msg_serialize.c',
|
||||
'src/control_msg.c',
|
||||
'src/util/str.c',
|
||||
'src/util/strbuf.c',
|
||||
'src/util/str_util.c',
|
||||
]],
|
||||
['test_device_msg_deserialize', [
|
||||
'tests/test_device_msg_deserialize.c',
|
||||
|
@ -223,10 +224,10 @@ if get_option('buildtype') == 'debug'
|
|||
'tests/test_strbuf.c',
|
||||
'src/util/strbuf.c',
|
||||
]],
|
||||
['test_strutil', [
|
||||
'tests/test_strutil.c',
|
||||
['test_str', [
|
||||
'tests/test_str.c',
|
||||
'src/util/str.c',
|
||||
'src/util/strbuf.c',
|
||||
'src/util/str_util.c',
|
||||
]],
|
||||
]
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
#include "util/file.h"
|
||||
#include "util/log.h"
|
||||
#include "util/str_util.h"
|
||||
#include "util/str.h"
|
||||
|
||||
static const char *adb_command;
|
||||
|
||||
|
@ -190,11 +190,11 @@ adb_push(const char *serial, const char *local, const char *remote) {
|
|||
#ifdef __WINDOWS__
|
||||
// Windows will parse the string, so the paths must be quoted
|
||||
// (see sys/win/command.c)
|
||||
local = strquote(local);
|
||||
local = sc_str_quote(local);
|
||||
if (!local) {
|
||||
return SC_PROCESS_NONE;
|
||||
}
|
||||
remote = strquote(remote);
|
||||
remote = sc_str_quote(remote);
|
||||
if (!remote) {
|
||||
free((void *) local);
|
||||
return SC_PROCESS_NONE;
|
||||
|
@ -217,7 +217,7 @@ adb_install(const char *serial, const char *local) {
|
|||
#ifdef __WINDOWS__
|
||||
// Windows will parse the string, so the local name must be quoted
|
||||
// (see sys/win/command.c)
|
||||
local = strquote(local);
|
||||
local = sc_str_quote(local);
|
||||
if (!local) {
|
||||
return SC_PROCESS_NONE;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
#include "adb_tunnel.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "adb.h"
|
||||
#include "util/log.h"
|
||||
#include "util/net_intr.h"
|
||||
#include "util/process_intr.h"
|
||||
|
||||
#define SC_SOCKET_NAME "scrcpy"
|
||||
|
||||
static bool
|
||||
enable_tunnel_reverse(struct sc_intr *intr, const char *serial,
|
||||
uint16_t local_port) {
|
||||
sc_pid pid = adb_reverse(serial, SC_SOCKET_NAME, local_port);
|
||||
return sc_process_check_success_intr(intr, pid, "adb reverse");
|
||||
}
|
||||
|
||||
static bool
|
||||
disable_tunnel_reverse(struct sc_intr *intr, const char *serial) {
|
||||
sc_pid pid = adb_reverse_remove(serial, SC_SOCKET_NAME);
|
||||
return sc_process_check_success_intr(intr, pid, "adb reverse --remove");
|
||||
}
|
||||
|
||||
static bool
|
||||
enable_tunnel_forward(struct sc_intr *intr, const char *serial,
|
||||
uint16_t local_port) {
|
||||
sc_pid pid = adb_forward(serial, local_port, SC_SOCKET_NAME);
|
||||
return sc_process_check_success_intr(intr, pid, "adb forward");
|
||||
}
|
||||
|
||||
static bool
|
||||
disable_tunnel_forward(struct sc_intr *intr, const char *serial,
|
||||
uint16_t local_port) {
|
||||
sc_pid pid = adb_forward_remove(serial, local_port);
|
||||
return sc_process_check_success_intr(intr, pid, "adb forward --remove");
|
||||
}
|
||||
|
||||
static bool
|
||||
listen_on_port(struct sc_intr *intr, sc_socket socket, uint16_t port) {
|
||||
return net_listen_intr(intr, socket, IPV4_LOCALHOST, port, 1);
|
||||
}
|
||||
|
||||
static bool
|
||||
enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
|
||||
struct sc_intr *intr, const char *serial,
|
||||
struct sc_port_range port_range) {
|
||||
uint16_t port = port_range.first;
|
||||
for (;;) {
|
||||
if (!enable_tunnel_reverse(intr, serial, port)) {
|
||||
// the command itself failed, it will fail on any port
|
||||
return false;
|
||||
}
|
||||
|
||||
// At the application level, the device part is "the server" because it
|
||||
// serves video stream and control. However, at the network level, the
|
||||
// client listens and the server connects to the client. That way, the
|
||||
// client can listen before starting the server app, so there is no
|
||||
// need to try to connect until the server socket is listening on the
|
||||
// device.
|
||||
sc_socket server_socket = net_socket();
|
||||
if (server_socket != SC_SOCKET_NONE) {
|
||||
bool ok = listen_on_port(intr, server_socket, port);
|
||||
if (ok) {
|
||||
// success
|
||||
tunnel->server_socket = server_socket;
|
||||
tunnel->local_port = port;
|
||||
tunnel->enabled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
net_close(server_socket);
|
||||
}
|
||||
|
||||
if (sc_intr_is_interrupted(intr)) {
|
||||
// Stop immediately
|
||||
return false;
|
||||
}
|
||||
|
||||
// failure, disable tunnel and try another port
|
||||
if (!disable_tunnel_reverse(intr, serial)) {
|
||||
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
|
||||
}
|
||||
|
||||
// check before incrementing to avoid overflow on port 65535
|
||||
if (port < port_range.last) {
|
||||
LOGW("Could not listen on port %" PRIu16", retrying on %" PRIu16,
|
||||
port, (uint16_t) (port + 1));
|
||||
port++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (port_range.first == port_range.last) {
|
||||
LOGE("Could not listen on port %" PRIu16, port_range.first);
|
||||
} else {
|
||||
LOGE("Could not listen on any port in range %" PRIu16 ":%" PRIu16,
|
||||
port_range.first, port_range.last);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
enable_tunnel_forward_any_port(struct sc_adb_tunnel *tunnel,
|
||||
struct sc_intr *intr, const char *serial,
|
||||
struct sc_port_range port_range) {
|
||||
tunnel->forward = true;
|
||||
|
||||
uint16_t port = port_range.first;
|
||||
for (;;) {
|
||||
if (enable_tunnel_forward(intr, serial, port)) {
|
||||
// success
|
||||
tunnel->local_port = port;
|
||||
tunnel->enabled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sc_intr_is_interrupted(intr)) {
|
||||
// Stop immediately
|
||||
return false;
|
||||
}
|
||||
|
||||
if (port < port_range.last) {
|
||||
LOGW("Could not forward port %" PRIu16", retrying on %" PRIu16,
|
||||
port, (uint16_t) (port + 1));
|
||||
port++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (port_range.first == port_range.last) {
|
||||
LOGE("Could not forward port %" PRIu16, port_range.first);
|
||||
} else {
|
||||
LOGE("Could not forward any port in range %" PRIu16 ":%" PRIu16,
|
||||
port_range.first, port_range.last);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sc_adb_tunnel_init(struct sc_adb_tunnel *tunnel) {
|
||||
tunnel->enabled = false;
|
||||
tunnel->forward = false;
|
||||
tunnel->server_socket = SC_SOCKET_NONE;
|
||||
tunnel->local_port = 0;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
|
||||
const char *serial, struct sc_port_range port_range,
|
||||
bool force_adb_forward) {
|
||||
assert(!tunnel->enabled);
|
||||
|
||||
if (!force_adb_forward) {
|
||||
// Attempt to use "adb reverse"
|
||||
if (enable_tunnel_reverse_any_port(tunnel, intr, serial, port_range)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if "adb reverse" does not work (e.g. over "adb connect"), it
|
||||
// fallbacks to "adb forward", so the app socket is the client
|
||||
|
||||
LOGW("'adb reverse' failed, fallback to 'adb forward'");
|
||||
}
|
||||
|
||||
return enable_tunnel_forward_any_port(tunnel, intr, serial, port_range);
|
||||
}
|
||||
|
||||
bool
|
||||
sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
|
||||
const char *serial) {
|
||||
assert(tunnel->enabled);
|
||||
|
||||
bool ret;
|
||||
if (tunnel->forward) {
|
||||
ret = disable_tunnel_forward(intr, serial, tunnel->local_port);
|
||||
} else {
|
||||
ret = disable_tunnel_reverse(intr, serial);
|
||||
|
||||
assert(tunnel->server_socket != SC_SOCKET_NONE);
|
||||
if (!net_close(tunnel->server_socket)) {
|
||||
LOGW("Could not close server socket");
|
||||
}
|
||||
|
||||
// server_socket is never used anymore
|
||||
}
|
||||
|
||||
// Consider tunnel disabled even if the command failed
|
||||
tunnel->enabled = false;
|
||||
|
||||
return ret;
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
#ifndef SC_ADB_TUNNEL_H
|
||||
#define SC_ADB_TUNNEL_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "options.h"
|
||||
#include "util/intr.h"
|
||||
#include "util/net.h"
|
||||
|
||||
struct sc_adb_tunnel {
|
||||
bool enabled;
|
||||
bool forward; // use "adb forward" instead of "adb reverse"
|
||||
sc_socket server_socket; // only used if !forward
|
||||
uint16_t local_port;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the adb tunnel struct to default values
|
||||
*/
|
||||
void
|
||||
sc_adb_tunnel_init(struct sc_adb_tunnel *tunnel);
|
||||
|
||||
/**
|
||||
* Open a tunnel
|
||||
*
|
||||
* Blocking calls may be interrupted asynchronously via `intr`.
|
||||
*
|
||||
* If `force_adb_forward` is not set, then attempts to set up an "adb reverse"
|
||||
* tunnel first. Only if it fails (typical on old Android version connected via
|
||||
* TCP/IP), use "adb forward".
|
||||
*/
|
||||
bool
|
||||
sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
|
||||
const char *serial, struct sc_port_range port_range,
|
||||
bool force_adb_forward);
|
||||
|
||||
/**
|
||||
* Close the tunnel
|
||||
*/
|
||||
bool
|
||||
sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
|
||||
const char *serial);
|
||||
|
||||
#endif
|
|
@ -9,8 +9,8 @@
|
|||
|
||||
#include "options.h"
|
||||
#include "util/log.h"
|
||||
#include "util/str.h"
|
||||
#include "util/strbuf.h"
|
||||
#include "util/str_util.h"
|
||||
#include "util/term.h"
|
||||
|
||||
#define STR_IMPL_(x) #x
|
||||
|
@ -779,9 +779,9 @@ parse_integer_arg(const char *s, long *out, bool accept_suffix, long min,
|
|||
long value;
|
||||
bool ok;
|
||||
if (accept_suffix) {
|
||||
ok = parse_integer_with_suffix(s, &value);
|
||||
ok = sc_str_parse_integer_with_suffix(s, &value);
|
||||
} else {
|
||||
ok = parse_integer(s, &value);
|
||||
ok = sc_str_parse_integer(s, &value);
|
||||
}
|
||||
if (!ok) {
|
||||
LOGE("Could not parse %s: %s", name, s);
|
||||
|
@ -801,7 +801,7 @@ parse_integer_arg(const char *s, long *out, bool accept_suffix, long min,
|
|||
static size_t
|
||||
parse_integers_arg(const char *s, size_t max_items, long *out, long min,
|
||||
long max, const char *name) {
|
||||
size_t count = parse_integers(s, ':', max_items, out);
|
||||
size_t count = sc_str_parse_integers(s, ':', max_items, out);
|
||||
if (!count) {
|
||||
LOGE("Could not parse %s: %s", name, s);
|
||||
return 0;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
#include "util/buffer_util.h"
|
||||
#include "util/log.h"
|
||||
#include "util/str_util.h"
|
||||
#include "util/str.h"
|
||||
|
||||
/**
|
||||
* Map an enum value to a string based on an array, without crashing on an
|
||||
|
@ -66,7 +66,7 @@ write_position(uint8_t *buf, const struct sc_position *position) {
|
|||
// write length (2 bytes) + string (non nul-terminated)
|
||||
static size_t
|
||||
write_string(const char *utf8, size_t max_len, unsigned char *buf) {
|
||||
size_t len = utf8_truncation_index(utf8, max_len);
|
||||
size_t len = sc_str_utf8_truncation_index(utf8, max_len);
|
||||
buffer_write32be(buf, len);
|
||||
memcpy(&buf[4], utf8, len);
|
||||
return 4 + len;
|
||||
|
|
|
@ -307,7 +307,7 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
|
|||
// requested. Wait a bit so that the clipboard is set before
|
||||
// injecting Ctrl+v via HID, otherwise it would paste the old
|
||||
// clipboard content.
|
||||
hid_event.delay = SC_TICK_FROM_MS(2);
|
||||
hid_event.delay = SC_TICK_FROM_MS(5);
|
||||
}
|
||||
|
||||
if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) {
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#include "compat.h"
|
||||
#include "util/file.h"
|
||||
#include "util/log.h"
|
||||
#include "util/str_util.h"
|
||||
#include "util/str.h"
|
||||
|
||||
#define SCRCPY_PORTABLE_ICON_FILENAME "icon.png"
|
||||
#define SCRCPY_DEFAULT_ICON_PATH \
|
||||
|
@ -26,7 +26,7 @@ get_icon_path(void) {
|
|||
if (icon_path_env) {
|
||||
// if the envvar is set, use it
|
||||
#ifdef __WINDOWS__
|
||||
char *icon_path = utf8_from_wide_char(icon_path_env);
|
||||
char *icon_path = sc_str_from_wchars(icon_path_env);
|
||||
#else
|
||||
char *icon_path = strdup(icon_path_env);
|
||||
#endif
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include <libavutil/time.h>
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/str_util.h"
|
||||
#include "util/str.h"
|
||||
|
||||
/** Downcast packet_sink to recorder */
|
||||
#define DOWNCAST(SINK) container_of(SINK, struct recorder, packet_sink)
|
||||
|
@ -26,7 +26,7 @@ find_muxer(const char *name) {
|
|||
oformat = av_oformat_next(oformat);
|
||||
#endif
|
||||
// until null or containing the requested name
|
||||
} while (oformat && !strlist_contains(oformat->name, ',', name));
|
||||
} while (oformat && !sc_str_list_contains(oformat->name, ',', name));
|
||||
return oformat;
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
#endif
|
||||
|
||||
struct scrcpy {
|
||||
struct server server;
|
||||
struct sc_server server;
|
||||
struct screen screen;
|
||||
struct stream stream;
|
||||
struct decoder decoder;
|
||||
|
@ -286,7 +286,7 @@ stream_on_eos(struct stream *stream, void *userdata) {
|
|||
}
|
||||
|
||||
static void
|
||||
server_on_connection_failed(struct server *server, void *userdata) {
|
||||
sc_server_on_connection_failed(struct sc_server *server, void *userdata) {
|
||||
(void) server;
|
||||
(void) userdata;
|
||||
|
||||
|
@ -294,7 +294,7 @@ server_on_connection_failed(struct server *server, void *userdata) {
|
|||
}
|
||||
|
||||
static void
|
||||
server_on_connected(struct server *server, void *userdata) {
|
||||
sc_server_on_connected(struct sc_server *server, void *userdata) {
|
||||
(void) server;
|
||||
(void) userdata;
|
||||
|
||||
|
@ -302,7 +302,7 @@ server_on_connected(struct server *server, void *userdata) {
|
|||
}
|
||||
|
||||
static void
|
||||
server_on_disconnected(struct server *server, void *userdata) {
|
||||
sc_server_on_disconnected(struct sc_server *server, void *userdata) {
|
||||
(void) server;
|
||||
(void) userdata;
|
||||
|
||||
|
@ -340,7 +340,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||
bool controller_started = false;
|
||||
bool screen_initialized = false;
|
||||
|
||||
struct server_params params = {
|
||||
struct sc_server_params params = {
|
||||
.serial = options->serial,
|
||||
.log_level = options->log_level,
|
||||
.crop = options->crop,
|
||||
|
@ -359,16 +359,16 @@ scrcpy(struct scrcpy_options *options) {
|
|||
.power_off_on_close = options->power_off_on_close,
|
||||
};
|
||||
|
||||
static const struct server_callbacks cbs = {
|
||||
.on_connection_failed = server_on_connection_failed,
|
||||
.on_connected = server_on_connected,
|
||||
.on_disconnected = server_on_disconnected,
|
||||
static const struct sc_server_callbacks cbs = {
|
||||
.on_connection_failed = sc_server_on_connection_failed,
|
||||
.on_connected = sc_server_on_connected,
|
||||
.on_disconnected = sc_server_on_disconnected,
|
||||
};
|
||||
if (!server_init(&s->server, ¶ms, &cbs, NULL)) {
|
||||
if (!sc_server_init(&s->server, ¶ms, &cbs, NULL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!server_start(&s->server)) {
|
||||
if (!sc_server_start(&s->server)) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
|
@ -392,7 +392,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||
}
|
||||
|
||||
// It is necessarily initialized here, since the device is connected
|
||||
struct server_info *info = &s->server.info;
|
||||
struct sc_server_info *info = &s->server.info;
|
||||
|
||||
if (options->display && options->control) {
|
||||
if (!file_handler_init(&s->file_handler, options->serial,
|
||||
|
@ -608,7 +608,7 @@ end:
|
|||
|
||||
if (server_started) {
|
||||
// shutdown the sockets and kill the server
|
||||
server_stop(&s->server);
|
||||
sc_server_stop(&s->server);
|
||||
}
|
||||
|
||||
// now that the sockets are shutdown, the stream and controller are
|
||||
|
@ -653,7 +653,7 @@ end:
|
|||
file_handler_destroy(&s->file_handler);
|
||||
}
|
||||
|
||||
server_destroy(&s->server);
|
||||
sc_server_destroy(&s->server);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
361
app/src/server.c
361
app/src/server.c
|
@ -10,14 +10,14 @@
|
|||
#include "adb.h"
|
||||
#include "util/file.h"
|
||||
#include "util/log.h"
|
||||
#include "util/net.h"
|
||||
#include "util/str_util.h"
|
||||
#include "util/net_intr.h"
|
||||
#include "util/process_intr.h"
|
||||
#include "util/str.h"
|
||||
|
||||
#define SOCKET_NAME "scrcpy"
|
||||
#define SERVER_FILENAME "scrcpy-server"
|
||||
#define SC_SERVER_FILENAME "scrcpy-server"
|
||||
|
||||
#define DEFAULT_SERVER_PATH PREFIX "/share/scrcpy/" SERVER_FILENAME
|
||||
#define DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
|
||||
#define SC_SERVER_PATH_DEFAULT PREFIX "/share/scrcpy/" SC_SERVER_FILENAME
|
||||
#define SC_DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
|
||||
|
||||
static char *
|
||||
get_server_path(void) {
|
||||
|
@ -29,7 +29,7 @@ get_server_path(void) {
|
|||
if (server_path_env) {
|
||||
// if the envvar is set, use it
|
||||
#ifdef __WINDOWS__
|
||||
char *server_path = utf8_from_wide_char(server_path_env);
|
||||
char *server_path = sc_str_from_wchars(server_path_env);
|
||||
#else
|
||||
char *server_path = strdup(server_path_env);
|
||||
#endif
|
||||
|
@ -42,18 +42,18 @@ get_server_path(void) {
|
|||
}
|
||||
|
||||
#ifndef PORTABLE
|
||||
LOGD("Using server: " DEFAULT_SERVER_PATH);
|
||||
char *server_path = strdup(DEFAULT_SERVER_PATH);
|
||||
LOGD("Using server: " SC_SERVER_PATH_DEFAULT);
|
||||
char *server_path = strdup(SC_SERVER_PATH_DEFAULT);
|
||||
if (!server_path) {
|
||||
LOGE("Could not allocate memory");
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
char *server_path = sc_file_get_local_path(SERVER_FILENAME);
|
||||
char *server_path = sc_file_get_local_path(SC_SERVER_FILENAME);
|
||||
if (!server_path) {
|
||||
LOGE("Could not get local file path, "
|
||||
"using " SERVER_FILENAME " from current directory");
|
||||
return strdup(SERVER_FILENAME);
|
||||
"using " SC_SERVER_FILENAME " from current directory");
|
||||
return strdup(SC_SERVER_FILENAME);
|
||||
}
|
||||
|
||||
LOGD("Using server (portable): %s", server_path);
|
||||
|
@ -63,7 +63,7 @@ get_server_path(void) {
|
|||
}
|
||||
|
||||
static void
|
||||
server_params_destroy(struct server_params *params) {
|
||||
sc_server_params_destroy(struct sc_server_params *params) {
|
||||
// The server stores a copy of the params provided by the user
|
||||
free((char *) params->serial);
|
||||
free((char *) params->crop);
|
||||
|
@ -72,7 +72,8 @@ server_params_destroy(struct server_params *params) {
|
|||
}
|
||||
|
||||
static bool
|
||||
server_params_copy(struct server_params *dst, const struct server_params *src) {
|
||||
sc_server_params_copy(struct sc_server_params *dst,
|
||||
const struct sc_server_params *src) {
|
||||
*dst = *src;
|
||||
|
||||
// The params reference user-allocated memory, so we must copy them to
|
||||
|
@ -96,12 +97,12 @@ server_params_copy(struct server_params *dst, const struct server_params *src) {
|
|||
return true;
|
||||
|
||||
error:
|
||||
server_params_destroy(dst);
|
||||
sc_server_params_destroy(dst);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
push_server(const char *serial) {
|
||||
push_server(struct sc_intr *intr, const char *serial) {
|
||||
char *server_path = get_server_path();
|
||||
if (!server_path) {
|
||||
return false;
|
||||
|
@ -111,158 +112,9 @@ push_server(const char *serial) {
|
|||
free(server_path);
|
||||
return false;
|
||||
}
|
||||
sc_pid pid = adb_push(serial, server_path, DEVICE_SERVER_PATH);
|
||||
sc_pid pid = adb_push(serial, server_path, SC_DEVICE_SERVER_PATH);
|
||||
free(server_path);
|
||||
return sc_process_check_success(pid, "adb push", true);
|
||||
}
|
||||
|
||||
static bool
|
||||
enable_tunnel_reverse(const char *serial, uint16_t local_port) {
|
||||
sc_pid pid = adb_reverse(serial, SOCKET_NAME, local_port);
|
||||
return sc_process_check_success(pid, "adb reverse", true);
|
||||
}
|
||||
|
||||
static bool
|
||||
disable_tunnel_reverse(const char *serial) {
|
||||
sc_pid pid = adb_reverse_remove(serial, SOCKET_NAME);
|
||||
return sc_process_check_success(pid, "adb reverse --remove", true);
|
||||
}
|
||||
|
||||
static bool
|
||||
enable_tunnel_forward(const char *serial, uint16_t local_port) {
|
||||
sc_pid pid = adb_forward(serial, local_port, SOCKET_NAME);
|
||||
return sc_process_check_success(pid, "adb forward", true);
|
||||
}
|
||||
|
||||
static bool
|
||||
disable_tunnel_forward(const char *serial, uint16_t local_port) {
|
||||
sc_pid pid = adb_forward_remove(serial, local_port);
|
||||
return sc_process_check_success(pid, "adb forward --remove", true);
|
||||
}
|
||||
|
||||
static bool
|
||||
disable_tunnel(struct server *server) {
|
||||
assert(server->tunnel_enabled);
|
||||
|
||||
const char *serial = server->params.serial;
|
||||
bool ok = server->tunnel_forward
|
||||
? disable_tunnel_forward(serial, server->local_port)
|
||||
: disable_tunnel_reverse(serial);
|
||||
|
||||
// Consider tunnel disabled even if the command failed
|
||||
server->tunnel_enabled = false;
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool
|
||||
listen_on_port(sc_socket socket, uint16_t port) {
|
||||
#define IPV4_LOCALHOST 0x7F000001
|
||||
return net_listen(socket, IPV4_LOCALHOST, port, 1);
|
||||
}
|
||||
|
||||
static bool
|
||||
enable_tunnel_reverse_any_port(struct server *server,
|
||||
struct sc_port_range port_range) {
|
||||
const char *serial = server->params.serial;
|
||||
uint16_t port = port_range.first;
|
||||
for (;;) {
|
||||
if (!enable_tunnel_reverse(serial, port)) {
|
||||
// the command itself failed, it will fail on any port
|
||||
return false;
|
||||
}
|
||||
|
||||
// At the application level, the device part is "the server" because it
|
||||
// serves video stream and control. However, at the network level, the
|
||||
// client listens and the server connects to the client. That way, the
|
||||
// client can listen before starting the server app, so there is no
|
||||
// need to try to connect until the server socket is listening on the
|
||||
// device.
|
||||
sc_socket server_socket = net_socket();
|
||||
if (server_socket != SC_INVALID_SOCKET) {
|
||||
bool ok = listen_on_port(server_socket, port);
|
||||
if (ok) {
|
||||
// success
|
||||
server->server_socket = server_socket;
|
||||
server->local_port = port;
|
||||
server->tunnel_enabled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
net_close(server_socket);
|
||||
}
|
||||
|
||||
// failure, disable tunnel and try another port
|
||||
if (!disable_tunnel_reverse(serial)) {
|
||||
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
|
||||
}
|
||||
|
||||
// check before incrementing to avoid overflow on port 65535
|
||||
if (port < port_range.last) {
|
||||
LOGW("Could not listen on port %" PRIu16", retrying on %" PRIu16,
|
||||
port, (uint16_t) (port + 1));
|
||||
port++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (port_range.first == port_range.last) {
|
||||
LOGE("Could not listen on port %" PRIu16, port_range.first);
|
||||
} else {
|
||||
LOGE("Could not listen on any port in range %" PRIu16 ":%" PRIu16,
|
||||
port_range.first, port_range.last);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
enable_tunnel_forward_any_port(struct server *server,
|
||||
struct sc_port_range port_range) {
|
||||
server->tunnel_forward = true;
|
||||
|
||||
const char *serial = server->params.serial;
|
||||
uint16_t port = port_range.first;
|
||||
for (;;) {
|
||||
if (enable_tunnel_forward(serial, port)) {
|
||||
// success
|
||||
server->local_port = port;
|
||||
server->tunnel_enabled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (port < port_range.last) {
|
||||
LOGW("Could not forward port %" PRIu16", retrying on %" PRIu16,
|
||||
port, (uint16_t) (port + 1));
|
||||
port++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (port_range.first == port_range.last) {
|
||||
LOGE("Could not forward port %" PRIu16, port_range.first);
|
||||
} else {
|
||||
LOGE("Could not forward any port in range %" PRIu16 ":%" PRIu16,
|
||||
port_range.first, port_range.last);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
enable_tunnel_any_port(struct server *server, struct sc_port_range port_range,
|
||||
bool force_adb_forward) {
|
||||
if (!force_adb_forward) {
|
||||
// Attempt to use "adb reverse"
|
||||
if (enable_tunnel_reverse_any_port(server, port_range)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if "adb reverse" does not work (e.g. over "adb connect"), it
|
||||
// fallbacks to "adb forward", so the app socket is the client
|
||||
|
||||
LOGW("'adb reverse' failed, fallback to 'adb forward'");
|
||||
}
|
||||
|
||||
return enable_tunnel_forward_any_port(server, port_range);
|
||||
return sc_process_check_success_intr(intr, pid, "adb push");
|
||||
}
|
||||
|
||||
static const char *
|
||||
|
@ -285,7 +137,8 @@ log_level_to_server_string(enum sc_log_level level) {
|
|||
}
|
||||
|
||||
static sc_pid
|
||||
execute_server(struct server *server, const struct server_params *params) {
|
||||
execute_server(struct sc_server *server,
|
||||
const struct sc_server_params *params) {
|
||||
const char *serial = server->params.serial;
|
||||
|
||||
char max_size_string[6];
|
||||
|
@ -301,7 +154,7 @@ execute_server(struct server *server, const struct server_params *params) {
|
|||
sprintf(display_id_string, "%"PRIu32, params->display_id);
|
||||
const char *const cmd[] = {
|
||||
"shell",
|
||||
"CLASSPATH=" DEVICE_SERVER_PATH,
|
||||
"CLASSPATH=" SC_DEVICE_SERVER_PATH,
|
||||
"app_process",
|
||||
#ifdef SERVER_DEBUGGER
|
||||
# define SERVER_DEBUGGER_PORT "5005"
|
||||
|
@ -323,7 +176,7 @@ execute_server(struct server *server, const struct server_params *params) {
|
|||
bit_rate_string,
|
||||
max_fps_string,
|
||||
lock_video_orientation_string,
|
||||
server->tunnel_forward ? "true" : "false",
|
||||
server->tunnel.forward ? "true" : "false",
|
||||
params->crop ? params->crop : "-",
|
||||
"true", // always send frame meta (packet boundaries + timestamp)
|
||||
params->control ? "true" : "false",
|
||||
|
@ -349,8 +202,8 @@ execute_server(struct server *server, const struct server_params *params) {
|
|||
}
|
||||
|
||||
static bool
|
||||
connect_and_read_byte(sc_socket socket, uint16_t port) {
|
||||
bool ok = net_connect(socket, IPV4_LOCALHOST, port);
|
||||
connect_and_read_byte(struct sc_intr *intr, sc_socket socket, uint16_t port) {
|
||||
bool ok = net_connect_intr(intr, socket, IPV4_LOCALHOST, port);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
@ -367,13 +220,13 @@ connect_and_read_byte(sc_socket socket, uint16_t port) {
|
|||
}
|
||||
|
||||
static sc_socket
|
||||
connect_to_server(struct server *server, uint32_t attempts, sc_tick delay) {
|
||||
uint16_t port = server->local_port;
|
||||
connect_to_server(struct sc_server *server, uint32_t attempts, sc_tick delay) {
|
||||
uint16_t port = server->tunnel.local_port;
|
||||
do {
|
||||
LOGD("Remaining connection attempts: %d", (int) attempts);
|
||||
sc_socket socket = net_socket();
|
||||
if (socket != SC_INVALID_SOCKET) {
|
||||
bool ok = connect_and_read_byte(socket, port);
|
||||
if (socket != SC_SOCKET_NONE) {
|
||||
bool ok = connect_and_read_byte(&server->intr, socket, port);
|
||||
if (ok) {
|
||||
// it worked!
|
||||
return socket;
|
||||
|
@ -398,13 +251,13 @@ connect_to_server(struct server *server, uint32_t attempts, sc_tick delay) {
|
|||
}
|
||||
}
|
||||
} while (--attempts > 0);
|
||||
return SC_INVALID_SOCKET;
|
||||
return SC_SOCKET_NONE;
|
||||
}
|
||||
|
||||
bool
|
||||
server_init(struct server *server, const struct server_params *params,
|
||||
const struct server_callbacks *cbs, void *cbs_userdata) {
|
||||
bool ok = server_params_copy(&server->params, params);
|
||||
sc_server_init(struct sc_server *server, const struct sc_server_params *params,
|
||||
const struct sc_server_callbacks *cbs, void *cbs_userdata) {
|
||||
bool ok = sc_server_params_copy(&server->params, params);
|
||||
if (!ok) {
|
||||
LOGE("Could not copy server params");
|
||||
return false;
|
||||
|
@ -413,7 +266,7 @@ server_init(struct server *server, const struct server_params *params,
|
|||
ok = sc_mutex_init(&server->mutex);
|
||||
if (!ok) {
|
||||
LOGE("Could not create server mutex");
|
||||
server_params_destroy(&server->params);
|
||||
sc_server_params_destroy(&server->params);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -421,20 +274,25 @@ server_init(struct server *server, const struct server_params *params,
|
|||
if (!ok) {
|
||||
LOGE("Could not create server cond_stopped");
|
||||
sc_mutex_destroy(&server->mutex);
|
||||
server_params_destroy(&server->params);
|
||||
sc_server_params_destroy(&server->params);
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = sc_intr_init(&server->intr);
|
||||
if (!ok) {
|
||||
LOGE("Could not create intr");
|
||||
sc_cond_destroy(&server->cond_stopped);
|
||||
sc_mutex_destroy(&server->mutex);
|
||||
sc_server_params_destroy(&server->params);
|
||||
return false;
|
||||
}
|
||||
|
||||
server->stopped = false;
|
||||
|
||||
server->server_socket = SC_INVALID_SOCKET;
|
||||
server->video_socket = SC_INVALID_SOCKET;
|
||||
server->control_socket = SC_INVALID_SOCKET;
|
||||
server->video_socket = SC_SOCKET_NONE;
|
||||
server->control_socket = SC_SOCKET_NONE;
|
||||
|
||||
server->local_port = 0;
|
||||
|
||||
server->tunnel_enabled = false;
|
||||
server->tunnel_forward = false;
|
||||
sc_adb_tunnel_init(&server->tunnel);
|
||||
|
||||
assert(cbs);
|
||||
assert(cbs->on_connection_failed);
|
||||
|
@ -448,69 +306,66 @@ server_init(struct server *server, const struct server_params *params,
|
|||
}
|
||||
|
||||
static bool
|
||||
device_read_info(sc_socket device_socket, struct server_info *info) {
|
||||
unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4];
|
||||
device_read_info(sc_socket device_socket, struct sc_server_info *info) {
|
||||
unsigned char buf[SC_DEVICE_NAME_FIELD_LENGTH + 4];
|
||||
ssize_t r = net_recv_all(device_socket, buf, sizeof(buf));
|
||||
if (r < DEVICE_NAME_FIELD_LENGTH + 4) {
|
||||
if (r < SC_DEVICE_NAME_FIELD_LENGTH + 4) {
|
||||
LOGE("Could not retrieve device information");
|
||||
return false;
|
||||
}
|
||||
// in case the client sends garbage
|
||||
buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0';
|
||||
buf[SC_DEVICE_NAME_FIELD_LENGTH - 1] = '\0';
|
||||
memcpy(info->device_name, (char *) buf, sizeof(info->device_name));
|
||||
|
||||
info->frame_size.width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8)
|
||||
| buf[DEVICE_NAME_FIELD_LENGTH + 1];
|
||||
info->frame_size.height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8)
|
||||
| buf[DEVICE_NAME_FIELD_LENGTH + 3];
|
||||
info->frame_size.width = (buf[SC_DEVICE_NAME_FIELD_LENGTH] << 8)
|
||||
| buf[SC_DEVICE_NAME_FIELD_LENGTH + 1];
|
||||
info->frame_size.height = (buf[SC_DEVICE_NAME_FIELD_LENGTH + 2] << 8)
|
||||
| buf[SC_DEVICE_NAME_FIELD_LENGTH + 3];
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
server_connect_to(struct server *server, struct server_info *info) {
|
||||
assert(server->tunnel_enabled);
|
||||
sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
|
||||
struct sc_adb_tunnel *tunnel = &server->tunnel;
|
||||
|
||||
sc_socket video_socket = SC_INVALID_SOCKET;
|
||||
sc_socket control_socket = SC_INVALID_SOCKET;
|
||||
if (!server->tunnel_forward) {
|
||||
video_socket = net_accept(server->server_socket);
|
||||
if (video_socket == SC_INVALID_SOCKET) {
|
||||
assert(tunnel->enabled);
|
||||
|
||||
const char *serial = server->params.serial;
|
||||
|
||||
sc_socket video_socket = SC_SOCKET_NONE;
|
||||
sc_socket control_socket = SC_SOCKET_NONE;
|
||||
if (!tunnel->forward) {
|
||||
video_socket = net_accept(tunnel->server_socket);
|
||||
if (video_socket == SC_SOCKET_NONE) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
control_socket = net_accept(server->server_socket);
|
||||
if (control_socket == SC_INVALID_SOCKET) {
|
||||
control_socket = net_accept(tunnel->server_socket);
|
||||
if (control_socket == SC_SOCKET_NONE) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// we don't need the server socket anymore
|
||||
if (!net_close(server->server_socket)) {
|
||||
LOGW("Could not close server socket on connect");
|
||||
}
|
||||
// Do not attempt to close it again on server_destroy()
|
||||
server->server_socket = SC_INVALID_SOCKET;
|
||||
} else {
|
||||
uint32_t attempts = 100;
|
||||
sc_tick delay = SC_TICK_FROM_MS(100);
|
||||
video_socket = connect_to_server(server, attempts, delay);
|
||||
if (video_socket == SC_INVALID_SOCKET) {
|
||||
if (video_socket == SC_SOCKET_NONE) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// we know that the device is listening, we don't need several attempts
|
||||
control_socket = net_socket();
|
||||
if (control_socket == SC_INVALID_SOCKET) {
|
||||
if (control_socket == SC_SOCKET_NONE) {
|
||||
goto fail;
|
||||
}
|
||||
bool ok = net_connect(control_socket, IPV4_LOCALHOST,
|
||||
server->local_port);
|
||||
tunnel->local_port);
|
||||
if (!ok) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
// we don't need the adb tunnel anymore
|
||||
disable_tunnel(server); // ignore failure
|
||||
sc_adb_tunnel_close(tunnel, &server->intr, serial);
|
||||
|
||||
// The sockets will be closed on stop if device_read_info() fails
|
||||
bool ok = device_read_info(video_socket, info);
|
||||
|
@ -518,8 +373,8 @@ server_connect_to(struct server *server, struct server_info *info) {
|
|||
goto fail;
|
||||
}
|
||||
|
||||
assert(video_socket != SC_INVALID_SOCKET);
|
||||
assert(control_socket != SC_INVALID_SOCKET);
|
||||
assert(video_socket != SC_SOCKET_NONE);
|
||||
assert(control_socket != SC_SOCKET_NONE);
|
||||
|
||||
server->video_socket = video_socket;
|
||||
server->control_socket = control_socket;
|
||||
|
@ -527,36 +382,33 @@ server_connect_to(struct server *server, struct server_info *info) {
|
|||
return true;
|
||||
|
||||
fail:
|
||||
if (video_socket != SC_INVALID_SOCKET) {
|
||||
if (video_socket != SC_SOCKET_NONE) {
|
||||
if (!net_close(video_socket)) {
|
||||
LOGW("Could not close video socket");
|
||||
}
|
||||
}
|
||||
|
||||
if (control_socket != SC_INVALID_SOCKET) {
|
||||
if (control_socket != SC_SOCKET_NONE) {
|
||||
if (!net_close(control_socket)) {
|
||||
LOGW("Could not close control socket");
|
||||
}
|
||||
}
|
||||
|
||||
// Always leave this function with tunnel disabled
|
||||
disable_tunnel(server);
|
||||
sc_adb_tunnel_close(tunnel, &server->intr, serial);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
server_on_terminated(void *userdata) {
|
||||
struct server *server = userdata;
|
||||
sc_server_on_terminated(void *userdata) {
|
||||
struct sc_server *server = userdata;
|
||||
|
||||
// No need for synchronization, server_socket is initialized before the
|
||||
// observer thread is created.
|
||||
if (server->server_socket != SC_INVALID_SOCKET) {
|
||||
// If the server process dies before connecting to the server socket,
|
||||
// then the client will be stuck forever on accept(). To avoid the
|
||||
// problem, wake up the accept() call when the server dies.
|
||||
net_interrupt(server->server_socket);
|
||||
}
|
||||
// If the server process dies before connecting to the server socket,
|
||||
// then the client will be stuck forever on accept(). To avoid the problem,
|
||||
// wake up the accept() call (or any other) when the server dies, like on
|
||||
// stop() (it is safe to call interrupt() twice).
|
||||
sc_intr_interrupt(&server->intr);
|
||||
|
||||
server->cbs->on_disconnected(server, server->cbs_userdata);
|
||||
|
||||
|
@ -565,17 +417,17 @@ server_on_terminated(void *userdata) {
|
|||
|
||||
static int
|
||||
run_server(void *data) {
|
||||
struct server *server = data;
|
||||
struct sc_server *server = data;
|
||||
|
||||
const struct server_params *params = &server->params;
|
||||
const struct sc_server_params *params = &server->params;
|
||||
|
||||
bool ok = push_server(params->serial);
|
||||
bool ok = push_server(&server->intr, params->serial);
|
||||
if (!ok) {
|
||||
goto error_connection_failed;
|
||||
}
|
||||
|
||||
ok = enable_tunnel_any_port(server, params->port_range,
|
||||
params->force_adb_forward);
|
||||
ok = sc_adb_tunnel_open(&server->tunnel, &server->intr, params->serial,
|
||||
params->port_range, params->force_adb_forward);
|
||||
if (!ok) {
|
||||
goto error_connection_failed;
|
||||
}
|
||||
|
@ -583,23 +435,23 @@ run_server(void *data) {
|
|||
// server will connect to our server socket
|
||||
sc_pid pid = execute_server(server, params);
|
||||
if (pid == SC_PROCESS_NONE) {
|
||||
disable_tunnel(server);
|
||||
sc_adb_tunnel_close(&server->tunnel, &server->intr, params->serial);
|
||||
goto error_connection_failed;
|
||||
}
|
||||
|
||||
static const struct sc_process_listener listener = {
|
||||
.on_terminated = server_on_terminated,
|
||||
.on_terminated = sc_server_on_terminated,
|
||||
};
|
||||
struct sc_process_observer observer;
|
||||
ok = sc_process_observer_init(&observer, pid, &listener, server);
|
||||
if (!ok) {
|
||||
sc_process_terminate(pid);
|
||||
sc_process_wait(pid, true); // ignore exit code
|
||||
disable_tunnel(server);
|
||||
sc_adb_tunnel_close(&server->tunnel, &server->intr, params->serial);
|
||||
goto error_connection_failed;
|
||||
}
|
||||
|
||||
ok = server_connect_to(server, &server->info);
|
||||
ok = sc_server_connect_to(server, &server->info);
|
||||
// The tunnel is always closed by server_connect_to()
|
||||
if (!ok) {
|
||||
sc_process_terminate(pid);
|
||||
|
@ -619,23 +471,6 @@ run_server(void *data) {
|
|||
}
|
||||
sc_mutex_unlock(&server->mutex);
|
||||
|
||||
// Server stop has been requested
|
||||
if (server->server_socket != SC_INVALID_SOCKET) {
|
||||
if (!net_interrupt(server->server_socket)) {
|
||||
LOGW("Could not interrupt server socket");
|
||||
}
|
||||
}
|
||||
if (server->video_socket != SC_INVALID_SOCKET) {
|
||||
if (!net_interrupt(server->video_socket)) {
|
||||
LOGW("Could not interrupt video socket");
|
||||
}
|
||||
}
|
||||
if (server->control_socket != SC_INVALID_SOCKET) {
|
||||
if (!net_interrupt(server->control_socket)) {
|
||||
LOGW("Could not interrupt control socket");
|
||||
}
|
||||
}
|
||||
|
||||
// Give some delay for the server to terminate properly
|
||||
#define WATCHDOG_DELAY SC_TICK_FROM_SEC(1)
|
||||
sc_tick deadline = sc_tick_now() + WATCHDOG_DELAY;
|
||||
|
@ -665,7 +500,7 @@ error_connection_failed:
|
|||
}
|
||||
|
||||
bool
|
||||
server_start(struct server *server) {
|
||||
sc_server_start(struct sc_server *server) {
|
||||
bool ok = sc_thread_create(&server->thread, run_server, "server", server);
|
||||
if (!ok) {
|
||||
LOGE("Could not create server thread");
|
||||
|
@ -676,18 +511,20 @@ server_start(struct server *server) {
|
|||
}
|
||||
|
||||
void
|
||||
server_stop(struct server *server) {
|
||||
sc_server_stop(struct sc_server *server) {
|
||||
sc_mutex_lock(&server->mutex);
|
||||
server->stopped = true;
|
||||
sc_cond_signal(&server->cond_stopped);
|
||||
sc_intr_interrupt(&server->intr);
|
||||
sc_mutex_unlock(&server->mutex);
|
||||
|
||||
sc_thread_join(&server->thread, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
server_destroy(struct server *server) {
|
||||
server_params_destroy(&server->params);
|
||||
sc_server_destroy(struct sc_server *server) {
|
||||
sc_server_params_destroy(&server->params);
|
||||
sc_intr_destroy(&server->intr);
|
||||
sc_cond_destroy(&server->cond_stopped);
|
||||
sc_mutex_destroy(&server->mutex);
|
||||
}
|
||||
|
|
|
@ -8,19 +8,21 @@
|
|||
#include <stdint.h>
|
||||
|
||||
#include "adb.h"
|
||||
#include "adb_tunnel.h"
|
||||
#include "coords.h"
|
||||
#include "options.h"
|
||||
#include "util/intr.h"
|
||||
#include "util/log.h"
|
||||
#include "util/net.h"
|
||||
#include "util/thread.h"
|
||||
|
||||
#define DEVICE_NAME_FIELD_LENGTH 64
|
||||
struct server_info {
|
||||
char device_name[DEVICE_NAME_FIELD_LENGTH];
|
||||
#define SC_DEVICE_NAME_FIELD_LENGTH 64
|
||||
struct sc_server_info {
|
||||
char device_name[SC_DEVICE_NAME_FIELD_LENGTH];
|
||||
struct sc_size frame_size;
|
||||
};
|
||||
|
||||
struct server_params {
|
||||
struct sc_server_params {
|
||||
const char *serial;
|
||||
enum sc_log_level log_level;
|
||||
const char *crop;
|
||||
|
@ -39,63 +41,62 @@ struct server_params {
|
|||
bool power_off_on_close;
|
||||
};
|
||||
|
||||
struct server {
|
||||
struct sc_server {
|
||||
// The internal allocated strings are copies owned by the server
|
||||
struct server_params params;
|
||||
struct sc_server_params params;
|
||||
|
||||
sc_thread thread;
|
||||
struct server_info info; // initialized once connected
|
||||
struct sc_server_info info; // initialized once connected
|
||||
|
||||
sc_mutex mutex;
|
||||
sc_cond cond_stopped;
|
||||
bool stopped;
|
||||
|
||||
sc_socket server_socket; // only used if !tunnel_forward
|
||||
struct sc_intr intr;
|
||||
struct sc_adb_tunnel tunnel;
|
||||
|
||||
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;
|
||||
const struct sc_server_callbacks *cbs;
|
||||
void *cbs_userdata;
|
||||
};
|
||||
|
||||
struct server_callbacks {
|
||||
struct sc_server_callbacks {
|
||||
/**
|
||||
* Called when the server failed to connect
|
||||
*
|
||||
* If it is called, then on_connected() and on_disconnected() will never be
|
||||
* called.
|
||||
*/
|
||||
void (*on_connection_failed)(struct server *server, void *userdata);
|
||||
void (*on_connection_failed)(struct sc_server *server, void *userdata);
|
||||
|
||||
/**
|
||||
* Called on server connection
|
||||
*/
|
||||
void (*on_connected)(struct server *server, void *userdata);
|
||||
void (*on_connected)(struct sc_server *server, void *userdata);
|
||||
|
||||
/**
|
||||
* Called on server disconnection (after it has been connected)
|
||||
*/
|
||||
void (*on_disconnected)(struct server *server, void *userdata);
|
||||
void (*on_disconnected)(struct sc_server *server, void *userdata);
|
||||
};
|
||||
|
||||
// init the server with the given params
|
||||
bool
|
||||
server_init(struct server *server, const struct server_params *params,
|
||||
const struct server_callbacks *cbs, void *cbs_userdata);
|
||||
sc_server_init(struct sc_server *server, const struct sc_server_params *params,
|
||||
const struct sc_server_callbacks *cbs, void *cbs_userdata);
|
||||
|
||||
// start the server asynchronously
|
||||
bool
|
||||
server_start(struct server *server);
|
||||
sc_server_start(struct sc_server *server);
|
||||
|
||||
// disconnect and kill the server process
|
||||
void
|
||||
server_stop(struct server *server);
|
||||
sc_server_stop(struct sc_server *server);
|
||||
|
||||
// close and release sockets
|
||||
void
|
||||
server_destroy(struct server *server);
|
||||
sc_server_destroy(struct sc_server *server);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
#include <sys/stat.h>
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/str_util.h"
|
||||
#include "util/str.h"
|
||||
|
||||
char *
|
||||
sc_file_get_executable_path(void) {
|
||||
|
@ -19,12 +19,12 @@ sc_file_get_executable_path(void) {
|
|||
return NULL;
|
||||
}
|
||||
buf[len] = '\0';
|
||||
return utf8_from_wide_char(buf);
|
||||
return sc_str_from_wchars(buf);
|
||||
}
|
||||
|
||||
bool
|
||||
sc_file_is_regular(const char *path) {
|
||||
wchar_t *wide_path = utf8_to_wide_char(path);
|
||||
wchar_t *wide_path = sc_str_to_wchars(path);
|
||||
if (!wide_path) {
|
||||
LOGC("Could not allocate wide char string");
|
||||
return false;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#include <assert.h>
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/str_util.h"
|
||||
#include "util/str.h"
|
||||
|
||||
#define CMD_MAX_LEN 8192
|
||||
|
||||
|
@ -13,7 +13,7 @@ build_cmd(char *cmd, size_t len, const char *const argv[]) {
|
|||
// <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS>
|
||||
// only make it work for this very specific program
|
||||
// (don't handle escaping nor quotes)
|
||||
size_t ret = xstrjoin(cmd, argv, ' ', len);
|
||||
size_t ret = sc_str_join(cmd, argv, ' ', len);
|
||||
if (ret >= len) {
|
||||
LOGE("Command too long (%" SC_PRIsizet " chars)", len - 1);
|
||||
return false;
|
||||
|
@ -88,7 +88,7 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle,
|
|||
goto error_close_stderr;
|
||||
}
|
||||
|
||||
wchar_t *wide = utf8_to_wide_char(cmd);
|
||||
wchar_t *wide = sc_str_to_wchars(cmd);
|
||||
free(cmd);
|
||||
if (!wide) {
|
||||
LOGC("Could not allocate wide char string");
|
||||
|
|
|
@ -12,7 +12,7 @@ sc_intr_init(struct sc_intr *intr) {
|
|||
return false;
|
||||
}
|
||||
|
||||
intr->socket = SC_INVALID_SOCKET;
|
||||
intr->socket = SC_SOCKET_NONE;
|
||||
intr->process = SC_PROCESS_NONE;
|
||||
|
||||
atomic_store_explicit(&intr->interrupted, false, memory_order_relaxed);
|
||||
|
@ -37,7 +37,7 @@ sc_intr_set_socket(struct sc_intr *intr, sc_socket socket) {
|
|||
|
||||
bool
|
||||
sc_intr_set_process(struct sc_intr *intr, sc_pid pid) {
|
||||
assert(intr->socket == SC_INVALID_SOCKET);
|
||||
assert(intr->socket == SC_SOCKET_NONE);
|
||||
|
||||
sc_mutex_lock(&intr->mutex);
|
||||
bool interrupted =
|
||||
|
@ -57,13 +57,13 @@ sc_intr_interrupt(struct sc_intr *intr) {
|
|||
atomic_store_explicit(&intr->interrupted, true, memory_order_relaxed);
|
||||
|
||||
// No more than one component to interrupt
|
||||
assert(intr->socket == SC_INVALID_SOCKET ||
|
||||
assert(intr->socket == SC_SOCKET_NONE ||
|
||||
intr->process == SC_PROCESS_NONE);
|
||||
|
||||
if (intr->socket != SC_INVALID_SOCKET) {
|
||||
if (intr->socket != SC_SOCKET_NONE) {
|
||||
LOGD("Interrupting socket");
|
||||
net_interrupt(intr->socket);
|
||||
intr->socket = SC_INVALID_SOCKET;
|
||||
intr->socket = SC_SOCKET_NONE;
|
||||
}
|
||||
if (intr->process != SC_PROCESS_NONE) {
|
||||
LOGD("Interrupting process");
|
||||
|
@ -76,7 +76,7 @@ sc_intr_interrupt(struct sc_intr *intr) {
|
|||
|
||||
void
|
||||
sc_intr_destroy(struct sc_intr *intr) {
|
||||
assert(intr->socket == SC_INVALID_SOCKET);
|
||||
assert(intr->socket == SC_SOCKET_NONE);
|
||||
assert(intr->process == SC_PROCESS_NONE);
|
||||
|
||||
sc_mutex_destroy(&intr->mutex);
|
||||
|
|
|
@ -37,7 +37,7 @@ sc_intr_init(struct sc_intr *intr);
|
|||
/**
|
||||
* Set a socket as the interruptible component
|
||||
*
|
||||
* Call with SC_INVALID_SOCKET to unset.
|
||||
* Call with SC_SOCKET_NONE to unset.
|
||||
*/
|
||||
bool
|
||||
sc_intr_set_socket(struct sc_intr *intr, sc_socket socket);
|
||||
|
|
|
@ -46,13 +46,13 @@ static inline sc_socket
|
|||
wrap(sc_raw_socket sock) {
|
||||
#ifdef __WINDOWS__
|
||||
if (sock == INVALID_SOCKET) {
|
||||
return SC_INVALID_SOCKET;
|
||||
return SC_SOCKET_NONE;
|
||||
}
|
||||
|
||||
struct sc_socket_windows *socket = malloc(sizeof(*socket));
|
||||
if (!socket) {
|
||||
closesocket(sock);
|
||||
return SC_INVALID_SOCKET;
|
||||
return SC_SOCKET_NONE;
|
||||
}
|
||||
|
||||
socket->socket = sock;
|
||||
|
@ -67,7 +67,7 @@ wrap(sc_raw_socket sock) {
|
|||
static inline sc_raw_socket
|
||||
unwrap(sc_socket socket) {
|
||||
#ifdef __WINDOWS__
|
||||
if (socket == SC_INVALID_SOCKET) {
|
||||
if (socket == SC_SOCKET_NONE) {
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
|
@ -97,7 +97,7 @@ sc_socket
|
|||
net_socket(void) {
|
||||
sc_raw_socket raw_sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
sc_socket sock = wrap(raw_sock);
|
||||
if (sock == SC_INVALID_SOCKET) {
|
||||
if (sock == SC_SOCKET_NONE) {
|
||||
net_perror("socket");
|
||||
}
|
||||
return sock;
|
||||
|
@ -195,7 +195,7 @@ net_send_all(sc_socket socket, const void *buf, size_t len) {
|
|||
|
||||
bool
|
||||
net_interrupt(sc_socket socket) {
|
||||
assert(socket != SC_INVALID_SOCKET);
|
||||
assert(socket != SC_SOCKET_NONE);
|
||||
|
||||
sc_raw_socket raw_sock = unwrap(socket);
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
# include <winsock2.h>
|
||||
# include <stdatomic.h>
|
||||
# define SC_INVALID_SOCKET NULL
|
||||
# define SC_SOCKET_NONE NULL
|
||||
typedef struct sc_socket_windows {
|
||||
SOCKET socket;
|
||||
atomic_flag closed;
|
||||
|
@ -20,10 +20,13 @@
|
|||
#else // not __WINDOWS__
|
||||
|
||||
# include <sys/socket.h>
|
||||
# define SC_INVALID_SOCKET -1
|
||||
# define SC_SOCKET_NONE -1
|
||||
typedef int sc_socket;
|
||||
|
||||
#endif
|
||||
|
||||
#define IPV4_LOCALHOST 0x7F000001
|
||||
|
||||
bool
|
||||
net_init(void);
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ net_connect_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr,
|
|||
|
||||
bool ret = net_connect(socket, addr, port);
|
||||
|
||||
sc_intr_set_socket(intr, SC_INVALID_SOCKET);
|
||||
sc_intr_set_socket(intr, SC_SOCKET_NONE);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ net_listen_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr,
|
|||
|
||||
bool ret = net_listen(socket, addr, port, backlog);
|
||||
|
||||
sc_intr_set_socket(intr, SC_INVALID_SOCKET);
|
||||
sc_intr_set_socket(intr, SC_SOCKET_NONE);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -32,12 +32,12 @@ sc_socket
|
|||
net_accept_intr(struct sc_intr *intr, sc_socket server_socket) {
|
||||
if (!sc_intr_set_socket(intr, server_socket)) {
|
||||
// Already interrupted
|
||||
return SC_INVALID_SOCKET;
|
||||
return SC_SOCKET_NONE;
|
||||
}
|
||||
|
||||
sc_socket socket = net_accept(server_socket);
|
||||
|
||||
sc_intr_set_socket(intr, SC_INVALID_SOCKET);
|
||||
sc_intr_set_socket(intr, SC_SOCKET_NONE);
|
||||
return socket;
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ net_recv_intr(struct sc_intr *intr, sc_socket socket, void *buf, size_t len) {
|
|||
|
||||
ssize_t r = net_recv(socket, buf, len);
|
||||
|
||||
sc_intr_set_socket(intr, SC_INVALID_SOCKET);
|
||||
sc_intr_set_socket(intr, SC_SOCKET_NONE);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ net_recv_all_intr(struct sc_intr *intr, sc_socket socket, void *buf,
|
|||
|
||||
ssize_t r = net_recv_all(socket, buf, len);
|
||||
|
||||
sc_intr_set_socket(intr, SC_INVALID_SOCKET);
|
||||
sc_intr_set_socket(intr, SC_SOCKET_NONE);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,7 @@ net_send_intr(struct sc_intr *intr, sc_socket socket, const void *buf,
|
|||
|
||||
ssize_t w = net_send(socket, buf, len);
|
||||
|
||||
sc_intr_set_socket(intr, SC_INVALID_SOCKET);
|
||||
sc_intr_set_socket(intr, SC_SOCKET_NONE);
|
||||
return w;
|
||||
}
|
||||
|
||||
|
@ -92,6 +92,6 @@ net_send_all_intr(struct sc_intr *intr, sc_socket socket, const void *buf,
|
|||
|
||||
ssize_t w = net_send_all(socket, buf, len);
|
||||
|
||||
sc_intr_set_socket(intr, SC_INVALID_SOCKET);
|
||||
sc_intr_set_socket(intr, SC_SOCKET_NONE);
|
||||
return w;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include "str_util.h"
|
||||
#include "str.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
@ -13,7 +13,7 @@
|
|||
#endif
|
||||
|
||||
size_t
|
||||
xstrncpy(char *dest, const char *src, size_t n) {
|
||||
sc_strncpy(char *dest, const char *src, size_t n) {
|
||||
size_t i;
|
||||
for (i = 0; i < n - 1 && src[i] != '\0'; ++i)
|
||||
dest[i] = src[i];
|
||||
|
@ -23,7 +23,7 @@ xstrncpy(char *dest, const char *src, size_t n) {
|
|||
}
|
||||
|
||||
size_t
|
||||
xstrjoin(char *dst, const char *const tokens[], char sep, size_t n) {
|
||||
sc_str_join(char *dst, const char *const tokens[], char sep, size_t n) {
|
||||
const char *const *remaining = tokens;
|
||||
const char *token = *remaining++;
|
||||
size_t i = 0;
|
||||
|
@ -33,7 +33,7 @@ xstrjoin(char *dst, const char *const tokens[], char sep, size_t n) {
|
|||
if (i == n)
|
||||
goto truncated;
|
||||
}
|
||||
size_t w = xstrncpy(dst + i, token, n - i);
|
||||
size_t w = sc_strncpy(dst + i, token, n - i);
|
||||
if (w >= n - i)
|
||||
goto truncated;
|
||||
i += w;
|
||||
|
@ -47,7 +47,7 @@ truncated:
|
|||
}
|
||||
|
||||
char *
|
||||
strquote(const char *src) {
|
||||
sc_str_quote(const char *src) {
|
||||
size_t len = strlen(src);
|
||||
char *quoted = malloc(len + 3);
|
||||
if (!quoted) {
|
||||
|
@ -61,7 +61,7 @@ strquote(const char *src) {
|
|||
}
|
||||
|
||||
bool
|
||||
parse_integer(const char *s, long *out) {
|
||||
sc_str_parse_integer(const char *s, long *out) {
|
||||
char *endptr;
|
||||
if (*s == '\0') {
|
||||
return false;
|
||||
|
@ -80,7 +80,8 @@ parse_integer(const char *s, long *out) {
|
|||
}
|
||||
|
||||
size_t
|
||||
parse_integers(const char *s, const char sep, size_t max_items, long *out) {
|
||||
sc_str_parse_integers(const char *s, const char sep, size_t max_items,
|
||||
long *out) {
|
||||
size_t count = 0;
|
||||
char *endptr;
|
||||
do {
|
||||
|
@ -109,7 +110,7 @@ parse_integers(const char *s, const char sep, size_t max_items, long *out) {
|
|||
}
|
||||
|
||||
bool
|
||||
parse_integer_with_suffix(const char *s, long *out) {
|
||||
sc_str_parse_integer_with_suffix(const char *s, long *out) {
|
||||
char *endptr;
|
||||
if (*s == '\0') {
|
||||
return false;
|
||||
|
@ -143,7 +144,7 @@ parse_integer_with_suffix(const char *s, long *out) {
|
|||
}
|
||||
|
||||
bool
|
||||
strlist_contains(const char *list, char sep, const char *s) {
|
||||
sc_str_list_contains(const char *list, char sep, const char *s) {
|
||||
char *p;
|
||||
do {
|
||||
p = strchr(list, sep);
|
||||
|
@ -161,7 +162,7 @@ strlist_contains(const char *list, char sep, const char *s) {
|
|||
}
|
||||
|
||||
size_t
|
||||
utf8_truncation_index(const char *utf8, size_t max_len) {
|
||||
sc_str_utf8_truncation_index(const char *utf8, size_t max_len) {
|
||||
size_t len = strlen(utf8);
|
||||
if (len <= max_len) {
|
||||
return len;
|
||||
|
@ -179,7 +180,7 @@ utf8_truncation_index(const char *utf8, size_t max_len) {
|
|||
#ifdef _WIN32
|
||||
|
||||
wchar_t *
|
||||
utf8_to_wide_char(const char *utf8) {
|
||||
sc_str_to_wchars(const char *utf8) {
|
||||
int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
|
||||
if (!len) {
|
||||
return NULL;
|
||||
|
@ -195,7 +196,7 @@ utf8_to_wide_char(const char *utf8) {
|
|||
}
|
||||
|
||||
char *
|
||||
utf8_from_wide_char(const wchar_t *ws) {
|
||||
sc_str_from_wchars(const wchar_t *ws) {
|
||||
int len = WideCharToMultiByte(CP_UTF8, 0, ws, -1, NULL, 0, NULL, NULL);
|
||||
if (!len) {
|
||||
return NULL;
|
||||
|
@ -212,7 +213,8 @@ utf8_from_wide_char(const wchar_t *ws) {
|
|||
|
||||
#endif
|
||||
|
||||
char *sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent) {
|
||||
char *
|
||||
sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent) {
|
||||
assert(indent < columns);
|
||||
|
||||
struct sc_strbuf buf;
|
|
@ -0,0 +1,106 @@
|
|||
#ifndef SC_STR_H
|
||||
#define SC_STR_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/**
|
||||
* Like strncpy(), except:
|
||||
* - it copies at most n-1 chars
|
||||
* - the dest string is nul-terminated
|
||||
* - it does not write useless bytes if strlen(src) < n
|
||||
* - it returns the number of chars actually written (max n-1) if src has
|
||||
* been copied completely, or n if src has been truncated
|
||||
*/
|
||||
size_t
|
||||
sc_strncpy(char *dest, const char *src, size_t n);
|
||||
|
||||
/**
|
||||
* Join tokens by separator `sep` into `dst`
|
||||
*
|
||||
* Return the number of chars actually written (max n-1) if no truncation
|
||||
* occurred, or n if truncated.
|
||||
*/
|
||||
size_t
|
||||
sc_str_join(char *dst, const char *const tokens[], char sep, size_t n);
|
||||
|
||||
/**
|
||||
* Quote a string
|
||||
*
|
||||
* Return a new allocated string, surrounded with quotes (`"`).
|
||||
*/
|
||||
char *
|
||||
sc_str_quote(const char *src);
|
||||
|
||||
/**
|
||||
* Parse `s` as an integer into `out`
|
||||
*
|
||||
* Return true if the conversion succeeded, false otherwise.
|
||||
*/
|
||||
bool
|
||||
sc_str_parse_integer(const char *s, long *out);
|
||||
|
||||
/**
|
||||
* Parse `s` as integers separated by `sep` (for example `1234:2000`) into `out`
|
||||
*
|
||||
* Returns the number of integers on success, 0 on failure.
|
||||
*/
|
||||
size_t
|
||||
sc_str_parse_integers(const char *s, const char sep, size_t max_items,
|
||||
long *out);
|
||||
|
||||
/**
|
||||
* Parse `s` as an integer into `out`
|
||||
*
|
||||
* Like `sc_str_parse_integer()`, but accept 'k'/'K' (x1000) and 'm'/'M'
|
||||
* (x1000000) as suffixes.
|
||||
*
|
||||
* Return true if the conversion succeeded, false otherwise.
|
||||
*/
|
||||
bool
|
||||
sc_str_parse_integer_with_suffix(const char *s, long *out);
|
||||
|
||||
/**
|
||||
* Search `s` in the list separated by `sep`
|
||||
*
|
||||
* For example, sc_str_list_contains("a,bc,def", ',', "bc") returns true.
|
||||
*/
|
||||
bool
|
||||
sc_str_list_contains(const char *list, char sep, const char *s);
|
||||
|
||||
/**
|
||||
* Return the index to truncate a UTF-8 string at a valid position
|
||||
*/
|
||||
size_t
|
||||
sc_str_utf8_truncation_index(const char *utf8, size_t max_len);
|
||||
|
||||
#ifdef _WIN32
|
||||
/**
|
||||
* Convert a UTF-8 string to a wchar_t string
|
||||
*
|
||||
* Return the new allocated string, to be freed by the caller.
|
||||
*/
|
||||
wchar_t *
|
||||
sc_str_to_wchars(const char *utf8);
|
||||
|
||||
/**
|
||||
* Convert a wchar_t string to a UTF-8 string
|
||||
*
|
||||
* Return the new allocated string, to be freed by the caller.
|
||||
*/
|
||||
char *
|
||||
sc_str_from_wchars(const wchar_t *s);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Wrap input lines to fit in `columns` columns
|
||||
*
|
||||
* Break input lines at word boundaries (spaces) so that they fit in `columns`
|
||||
* columns, left-indented by `indent` spaces.
|
||||
*/
|
||||
char *
|
||||
sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent);
|
||||
|
||||
#endif
|
|
@ -1,73 +0,0 @@
|
|||
#ifndef STRUTIL_H
|
||||
#define STRUTIL_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// like strncpy, except:
|
||||
// - it copies at most n-1 chars
|
||||
// - the dest string is nul-terminated
|
||||
// - it does not write useless bytes if strlen(src) < n
|
||||
// - it returns the number of chars actually written (max n-1) if src has
|
||||
// been copied completely, or n if src has been truncated
|
||||
size_t
|
||||
xstrncpy(char *dest, const char *src, size_t n);
|
||||
|
||||
// join tokens by sep into dst
|
||||
// returns the number of chars actually written (max n-1) if no truncation
|
||||
// occurred, or n if truncated
|
||||
size_t
|
||||
xstrjoin(char *dst, const char *const tokens[], char sep, size_t n);
|
||||
|
||||
// quote a string
|
||||
// returns the new allocated string, to be freed by the caller
|
||||
char *
|
||||
strquote(const char *src);
|
||||
|
||||
// parse s as an integer into value
|
||||
// returns true if the conversion succeeded, false otherwise
|
||||
bool
|
||||
parse_integer(const char *s, long *out);
|
||||
|
||||
// parse s as integers separated by sep (for example '1234:2000')
|
||||
// returns the number of integers on success, 0 on failure
|
||||
size_t
|
||||
parse_integers(const char *s, const char sep, size_t max_items, long *out);
|
||||
|
||||
// parse s as an integer into value
|
||||
// like parse_integer(), but accept 'k'/'K' (x1000) and 'm'/'M' (x1000000) as
|
||||
// suffix
|
||||
// returns true if the conversion succeeded, false otherwise
|
||||
bool
|
||||
parse_integer_with_suffix(const char *s, long *out);
|
||||
|
||||
// search s in the list separated by sep
|
||||
// for example, strlist_contains("a,bc,def", ',', "bc") returns true
|
||||
bool
|
||||
strlist_contains(const char *list, char sep, const char *s);
|
||||
|
||||
// return the index to truncate a UTF-8 string at a valid position
|
||||
size_t
|
||||
utf8_truncation_index(const char *utf8, size_t max_len);
|
||||
|
||||
#ifdef _WIN32
|
||||
// convert a UTF-8 string to a wchar_t string
|
||||
// returns the new allocated string, to be freed by the caller
|
||||
wchar_t *
|
||||
utf8_to_wide_char(const char *utf8);
|
||||
|
||||
char *
|
||||
utf8_from_wide_char(const wchar_t *s);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Wrap input lines to fit in `columns` columns
|
||||
*
|
||||
* Break input lines at word boundaries (spaces) so that they fit in `columns`
|
||||
* columns, left-indented by `indent` spaces.
|
||||
*/
|
||||
char *sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent);
|
||||
|
||||
#endif
|
|
@ -1,7 +1,7 @@
|
|||
#include "v4l2_sink.h"
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/str_util.h"
|
||||
#include "util/str.h"
|
||||
|
||||
/** Downcast frame_sink to sc_v4l2_sink */
|
||||
#define DOWNCAST(SINK) container_of(SINK, struct sc_v4l2_sink, frame_sink)
|
||||
|
@ -21,7 +21,7 @@ find_muxer(const char *name) {
|
|||
oformat = av_oformat_next(oformat);
|
||||
#endif
|
||||
// until null or containing the requested name
|
||||
} while (oformat && !strlist_contains(oformat->name, ',', name));
|
||||
} while (oformat && !sc_str_list_contains(oformat->name, ',', name));
|
||||
return oformat;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "util/str_util.h"
|
||||
#include "util/str.h"
|
||||
|
||||
static void test_xstrncpy_simple(void) {
|
||||
static void test_strncpy_simple(void) {
|
||||
char s[] = "xxxxxxxxxx";
|
||||
size_t w = xstrncpy(s, "abcdef", sizeof(s));
|
||||
size_t w = sc_strncpy(s, "abcdef", sizeof(s));
|
||||
|
||||
// returns strlen of copied string
|
||||
assert(w == 6);
|
||||
|
@ -24,9 +24,9 @@ static void test_xstrncpy_simple(void) {
|
|||
assert(!strcmp("abcdef", s));
|
||||
}
|
||||
|
||||
static void test_xstrncpy_just_fit(void) {
|
||||
static void test_strncpy_just_fit(void) {
|
||||
char s[] = "xxxxxx";
|
||||
size_t w = xstrncpy(s, "abcdef", sizeof(s));
|
||||
size_t w = sc_strncpy(s, "abcdef", sizeof(s));
|
||||
|
||||
// returns strlen of copied string
|
||||
assert(w == 6);
|
||||
|
@ -38,9 +38,9 @@ static void test_xstrncpy_just_fit(void) {
|
|||
assert(!strcmp("abcdef", s));
|
||||
}
|
||||
|
||||
static void test_xstrncpy_truncated(void) {
|
||||
static void test_strncpy_truncated(void) {
|
||||
char s[] = "xxx";
|
||||
size_t w = xstrncpy(s, "abcdef", sizeof(s));
|
||||
size_t w = sc_strncpy(s, "abcdef", sizeof(s));
|
||||
|
||||
// returns 'n' (sizeof(s))
|
||||
assert(w == 4);
|
||||
|
@ -52,10 +52,10 @@ static void test_xstrncpy_truncated(void) {
|
|||
assert(!strncmp("abcdef", s, 3));
|
||||
}
|
||||
|
||||
static void test_xstrjoin_simple(void) {
|
||||
static void test_join_simple(void) {
|
||||
const char *const tokens[] = { "abc", "de", "fghi", NULL };
|
||||
char s[] = "xxxxxxxxxxxxxx";
|
||||
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
|
||||
size_t w = sc_str_join(s, tokens, ' ', sizeof(s));
|
||||
|
||||
// returns strlen of concatenation
|
||||
assert(w == 11);
|
||||
|
@ -70,10 +70,10 @@ static void test_xstrjoin_simple(void) {
|
|||
assert(!strcmp("abc de fghi", s));
|
||||
}
|
||||
|
||||
static void test_xstrjoin_just_fit(void) {
|
||||
static void test_join_just_fit(void) {
|
||||
const char *const tokens[] = { "abc", "de", "fghi", NULL };
|
||||
char s[] = "xxxxxxxxxxx";
|
||||
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
|
||||
size_t w = sc_str_join(s, tokens, ' ', sizeof(s));
|
||||
|
||||
// returns strlen of concatenation
|
||||
assert(w == 11);
|
||||
|
@ -85,10 +85,10 @@ static void test_xstrjoin_just_fit(void) {
|
|||
assert(!strcmp("abc de fghi", s));
|
||||
}
|
||||
|
||||
static void test_xstrjoin_truncated_in_token(void) {
|
||||
static void test_join_truncated_in_token(void) {
|
||||
const char *const tokens[] = { "abc", "de", "fghi", NULL };
|
||||
char s[] = "xxxxx";
|
||||
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
|
||||
size_t w = sc_str_join(s, tokens, ' ', sizeof(s));
|
||||
|
||||
// returns 'n' (sizeof(s))
|
||||
assert(w == 6);
|
||||
|
@ -100,10 +100,10 @@ static void test_xstrjoin_truncated_in_token(void) {
|
|||
assert(!strcmp("abc d", s));
|
||||
}
|
||||
|
||||
static void test_xstrjoin_truncated_before_sep(void) {
|
||||
static void test_join_truncated_before_sep(void) {
|
||||
const char *const tokens[] = { "abc", "de", "fghi", NULL };
|
||||
char s[] = "xxxxxx";
|
||||
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
|
||||
size_t w = sc_str_join(s, tokens, ' ', sizeof(s));
|
||||
|
||||
// returns 'n' (sizeof(s))
|
||||
assert(w == 7);
|
||||
|
@ -115,10 +115,10 @@ static void test_xstrjoin_truncated_before_sep(void) {
|
|||
assert(!strcmp("abc de", s));
|
||||
}
|
||||
|
||||
static void test_xstrjoin_truncated_after_sep(void) {
|
||||
static void test_join_truncated_after_sep(void) {
|
||||
const char *const tokens[] = { "abc", "de", "fghi", NULL };
|
||||
char s[] = "xxxxxxx";
|
||||
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
|
||||
size_t w = sc_str_join(s, tokens, ' ', sizeof(s));
|
||||
|
||||
// returns 'n' (sizeof(s))
|
||||
assert(w == 8);
|
||||
|
@ -130,9 +130,9 @@ static void test_xstrjoin_truncated_after_sep(void) {
|
|||
assert(!strcmp("abc de ", s));
|
||||
}
|
||||
|
||||
static void test_strquote(void) {
|
||||
static void test_quote(void) {
|
||||
const char *s = "abcde";
|
||||
char *out = strquote(s);
|
||||
char *out = sc_str_quote(s);
|
||||
|
||||
// add '"' at the beginning and the end
|
||||
assert(!strcmp("\"abcde\"", out));
|
||||
|
@ -146,71 +146,71 @@ static void test_utf8_truncate(void) {
|
|||
|
||||
size_t count;
|
||||
|
||||
count = utf8_truncation_index(s, 1);
|
||||
count = sc_str_utf8_truncation_index(s, 1);
|
||||
assert(count == 1);
|
||||
|
||||
count = utf8_truncation_index(s, 2);
|
||||
count = sc_str_utf8_truncation_index(s, 2);
|
||||
assert(count == 1); // É is 2 bytes-wide
|
||||
|
||||
count = utf8_truncation_index(s, 3);
|
||||
count = sc_str_utf8_truncation_index(s, 3);
|
||||
assert(count == 3);
|
||||
|
||||
count = utf8_truncation_index(s, 4);
|
||||
count = sc_str_utf8_truncation_index(s, 4);
|
||||
assert(count == 4);
|
||||
|
||||
count = utf8_truncation_index(s, 5);
|
||||
count = sc_str_utf8_truncation_index(s, 5);
|
||||
assert(count == 4); // Ô is 2 bytes-wide
|
||||
|
||||
count = utf8_truncation_index(s, 6);
|
||||
count = sc_str_utf8_truncation_index(s, 6);
|
||||
assert(count == 6);
|
||||
|
||||
count = utf8_truncation_index(s, 7);
|
||||
count = sc_str_utf8_truncation_index(s, 7);
|
||||
assert(count == 7);
|
||||
|
||||
count = utf8_truncation_index(s, 8);
|
||||
count = sc_str_utf8_truncation_index(s, 8);
|
||||
assert(count == 7); // no more chars
|
||||
}
|
||||
|
||||
static void test_parse_integer(void) {
|
||||
long value;
|
||||
bool ok = parse_integer("1234", &value);
|
||||
bool ok = sc_str_parse_integer("1234", &value);
|
||||
assert(ok);
|
||||
assert(value == 1234);
|
||||
|
||||
ok = parse_integer("-1234", &value);
|
||||
ok = sc_str_parse_integer("-1234", &value);
|
||||
assert(ok);
|
||||
assert(value == -1234);
|
||||
|
||||
ok = parse_integer("1234k", &value);
|
||||
ok = sc_str_parse_integer("1234k", &value);
|
||||
assert(!ok);
|
||||
|
||||
ok = parse_integer("123456789876543212345678987654321", &value);
|
||||
ok = sc_str_parse_integer("123456789876543212345678987654321", &value);
|
||||
assert(!ok); // out-of-range
|
||||
}
|
||||
|
||||
static void test_parse_integers(void) {
|
||||
long values[5];
|
||||
|
||||
size_t count = parse_integers("1234", ':', 5, values);
|
||||
size_t count = sc_str_parse_integers("1234", ':', 5, values);
|
||||
assert(count == 1);
|
||||
assert(values[0] == 1234);
|
||||
|
||||
count = parse_integers("1234:5678", ':', 5, values);
|
||||
count = sc_str_parse_integers("1234:5678", ':', 5, values);
|
||||
assert(count == 2);
|
||||
assert(values[0] == 1234);
|
||||
assert(values[1] == 5678);
|
||||
|
||||
count = parse_integers("1234:5678", ':', 2, values);
|
||||
count = sc_str_parse_integers("1234:5678", ':', 2, values);
|
||||
assert(count == 2);
|
||||
assert(values[0] == 1234);
|
||||
assert(values[1] == 5678);
|
||||
|
||||
count = parse_integers("1234:-5678", ':', 2, values);
|
||||
count = sc_str_parse_integers("1234:-5678", ':', 2, values);
|
||||
assert(count == 2);
|
||||
assert(values[0] == 1234);
|
||||
assert(values[1] == -5678);
|
||||
|
||||
count = parse_integers("1:2:3:4:5", ':', 5, values);
|
||||
count = sc_str_parse_integers("1:2:3:4:5", ':', 5, values);
|
||||
assert(count == 5);
|
||||
assert(values[0] == 1);
|
||||
assert(values[1] == 2);
|
||||
|
@ -218,85 +218,85 @@ static void test_parse_integers(void) {
|
|||
assert(values[3] == 4);
|
||||
assert(values[4] == 5);
|
||||
|
||||
count = parse_integers("1234:5678", ':', 1, values);
|
||||
count = sc_str_parse_integers("1234:5678", ':', 1, values);
|
||||
assert(count == 0); // max_items == 1
|
||||
|
||||
count = parse_integers("1:2:3:4:5", ':', 3, values);
|
||||
count = sc_str_parse_integers("1:2:3:4:5", ':', 3, values);
|
||||
assert(count == 0); // max_items == 3
|
||||
|
||||
count = parse_integers(":1234", ':', 5, values);
|
||||
count = sc_str_parse_integers(":1234", ':', 5, values);
|
||||
assert(count == 0); // invalid
|
||||
|
||||
count = parse_integers("1234:", ':', 5, values);
|
||||
count = sc_str_parse_integers("1234:", ':', 5, values);
|
||||
assert(count == 0); // invalid
|
||||
|
||||
count = parse_integers("1234:", ':', 1, values);
|
||||
count = sc_str_parse_integers("1234:", ':', 1, values);
|
||||
assert(count == 0); // invalid, even when max_items == 1
|
||||
|
||||
count = parse_integers("1234::5678", ':', 5, values);
|
||||
count = sc_str_parse_integers("1234::5678", ':', 5, values);
|
||||
assert(count == 0); // invalid
|
||||
}
|
||||
|
||||
static void test_parse_integer_with_suffix(void) {
|
||||
long value;
|
||||
bool ok = parse_integer_with_suffix("1234", &value);
|
||||
bool ok = sc_str_parse_integer_with_suffix("1234", &value);
|
||||
assert(ok);
|
||||
assert(value == 1234);
|
||||
|
||||
ok = parse_integer_with_suffix("-1234", &value);
|
||||
ok = sc_str_parse_integer_with_suffix("-1234", &value);
|
||||
assert(ok);
|
||||
assert(value == -1234);
|
||||
|
||||
ok = parse_integer_with_suffix("1234k", &value);
|
||||
ok = sc_str_parse_integer_with_suffix("1234k", &value);
|
||||
assert(ok);
|
||||
assert(value == 1234000);
|
||||
|
||||
ok = parse_integer_with_suffix("1234m", &value);
|
||||
ok = sc_str_parse_integer_with_suffix("1234m", &value);
|
||||
assert(ok);
|
||||
assert(value == 1234000000);
|
||||
|
||||
ok = parse_integer_with_suffix("-1234k", &value);
|
||||
ok = sc_str_parse_integer_with_suffix("-1234k", &value);
|
||||
assert(ok);
|
||||
assert(value == -1234000);
|
||||
|
||||
ok = parse_integer_with_suffix("-1234m", &value);
|
||||
ok = sc_str_parse_integer_with_suffix("-1234m", &value);
|
||||
assert(ok);
|
||||
assert(value == -1234000000);
|
||||
|
||||
ok = parse_integer_with_suffix("123456789876543212345678987654321", &value);
|
||||
ok = sc_str_parse_integer_with_suffix("123456789876543212345678987654321", &value);
|
||||
assert(!ok); // out-of-range
|
||||
|
||||
char buf[32];
|
||||
|
||||
sprintf(buf, "%ldk", LONG_MAX / 2000);
|
||||
ok = parse_integer_with_suffix(buf, &value);
|
||||
ok = sc_str_parse_integer_with_suffix(buf, &value);
|
||||
assert(ok);
|
||||
assert(value == LONG_MAX / 2000 * 1000);
|
||||
|
||||
sprintf(buf, "%ldm", LONG_MAX / 2000);
|
||||
ok = parse_integer_with_suffix(buf, &value);
|
||||
ok = sc_str_parse_integer_with_suffix(buf, &value);
|
||||
assert(!ok);
|
||||
|
||||
sprintf(buf, "%ldk", LONG_MIN / 2000);
|
||||
ok = parse_integer_with_suffix(buf, &value);
|
||||
ok = sc_str_parse_integer_with_suffix(buf, &value);
|
||||
assert(ok);
|
||||
assert(value == LONG_MIN / 2000 * 1000);
|
||||
|
||||
sprintf(buf, "%ldm", LONG_MIN / 2000);
|
||||
ok = parse_integer_with_suffix(buf, &value);
|
||||
ok = sc_str_parse_integer_with_suffix(buf, &value);
|
||||
assert(!ok);
|
||||
}
|
||||
|
||||
static void test_strlist_contains(void) {
|
||||
assert(strlist_contains("a,bc,def", ',', "bc"));
|
||||
assert(!strlist_contains("a,bc,def", ',', "b"));
|
||||
assert(strlist_contains("", ',', ""));
|
||||
assert(strlist_contains("abc,", ',', ""));
|
||||
assert(strlist_contains(",abc", ',', ""));
|
||||
assert(strlist_contains("abc,,def", ',', ""));
|
||||
assert(!strlist_contains("abc", ',', ""));
|
||||
assert(strlist_contains(",,|x", '|', ",,"));
|
||||
assert(strlist_contains("xyz", '\0', "xyz"));
|
||||
assert(sc_str_list_contains("a,bc,def", ',', "bc"));
|
||||
assert(!sc_str_list_contains("a,bc,def", ',', "b"));
|
||||
assert(sc_str_list_contains("", ',', ""));
|
||||
assert(sc_str_list_contains("abc,", ',', ""));
|
||||
assert(sc_str_list_contains(",abc", ',', ""));
|
||||
assert(sc_str_list_contains("abc,,def", ',', ""));
|
||||
assert(!sc_str_list_contains("abc", ',', ""));
|
||||
assert(sc_str_list_contains(",,|x", '|', ",,"));
|
||||
assert(sc_str_list_contains("xyz", '\0', "xyz"));
|
||||
}
|
||||
|
||||
static void test_wrap_lines(void) {
|
||||
|
@ -341,15 +341,15 @@ int main(int argc, char *argv[]) {
|
|||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
test_xstrncpy_simple();
|
||||
test_xstrncpy_just_fit();
|
||||
test_xstrncpy_truncated();
|
||||
test_xstrjoin_simple();
|
||||
test_xstrjoin_just_fit();
|
||||
test_xstrjoin_truncated_in_token();
|
||||
test_xstrjoin_truncated_before_sep();
|
||||
test_xstrjoin_truncated_after_sep();
|
||||
test_strquote();
|
||||
test_strncpy_simple();
|
||||
test_strncpy_just_fit();
|
||||
test_strncpy_truncated();
|
||||
test_join_simple();
|
||||
test_join_just_fit();
|
||||
test_join_truncated_in_token();
|
||||
test_join_truncated_before_sep();
|
||||
test_join_truncated_after_sep();
|
||||
test_quote();
|
||||
test_utf8_truncate();
|
||||
test_parse_integer();
|
||||
test_parse_integers();
|
Loading…
Reference in New Issue