Compare commits

..

8 Commits

Author SHA1 Message Date
Romain Vimont
1ad679714b wip 2021-10-31 22:29:03 +01:00
Romain Vimont
612d9722a2 server_thread 2021-10-31 22:29:03 +01:00
Romain Vimont
1f8200e295 server_cbs 2021-10-31 22:29:03 +01:00
Romain Vimont
4ae7d1e9a9 server_info 2021-10-31 22:29:03 +01:00
Romain Vimont
49357566cb stopped_cond 2021-10-31 22:29:03 +01:00
Romain Vimont
c5d962b6fd Copy server params
This is a preliminary step necessary to move the server to a separate
thread.
2021-10-31 22:29:03 +01:00
Romain Vimont
54272a931a Reorder server and server_params
This will allow to define a server_params field in server.
2021-10-31 22:29:03 +01:00
Romain Vimont
59d1b0a269 Split socket creation and connect/listen
This will allow to assign the socket to a variable before connecting or
listening, so that it can be interrupted from another thread.
2021-10-31 22:28:07 +01:00
17 changed files with 738 additions and 1212 deletions

View File

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

View File

@@ -27,7 +27,6 @@ src = [
'src/util/log.c',
'src/util/net.c',
'src/util/process.c',
'src/util/strbuf.c',
'src/util/str_util.c',
'src/util/thread.c',
'src/util/tick.c',
@@ -187,7 +186,6 @@ if get_option('buildtype') == 'debug'
'tests/test_cli.c',
'src/cli.c',
'src/options.c',
'src/util/strbuf.c',
'src/util/str_util.c',
]],
['test_clock', [
@@ -197,7 +195,6 @@ if get_option('buildtype') == 'debug'
['test_control_msg_serialize', [
'tests/test_control_msg_serialize.c',
'src/control_msg.c',
'src/util/strbuf.c',
'src/util/str_util.c',
]],
['test_device_msg_deserialize', [
@@ -207,13 +204,8 @@ if get_option('buildtype') == 'debug'
['test_queue', [
'tests/test_queue.c',
]],
['test_strbuf', [
'tests/test_strbuf.c',
'src/util/strbuf.c',
]],
['test_strutil', [
'tests/test_strutil.c',
'src/util/strbuf.c',
'src/util/str_util.c',
]],
]

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -217,6 +217,29 @@ event_loop(struct scrcpy *s, const struct scrcpy_options *options) {
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
sdl_priority_from_av_level(int level) {
switch (level) {
@@ -262,6 +285,31 @@ stream_on_eos(struct stream *stream, void *userdata) {
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
scrcpy(struct scrcpy_options *options) {
static struct scrcpy scrcpy;
@@ -275,10 +323,6 @@ scrcpy(struct scrcpy_options *options) {
atexit(SDL_Quit);
if (!server_init(&s->server)) {
return false;
}
bool ret = false;
bool server_started = false;
@@ -313,7 +357,17 @@ scrcpy(struct scrcpy_options *options) {
.force_adb_forward = options->force_adb_forward,
.power_off_on_close = options->power_off_on_close,
};
if (!server_start(&s->server, &params)) {
static const struct server_callbacks cbs = {
.on_connection_failed = server_on_connection_failed,
.on_connected = server_on_connected,
.on_disconnected = server_on_disconnected,
};
if (!server_init(&s->server, &params, &cbs, NULL)) {
return false;
}
if (!server_start(&s->server)) {
goto end;
}
@@ -331,14 +385,15 @@ scrcpy(struct scrcpy_options *options) {
sdl_configure(options->display, options->disable_screensaver);
struct server_info info;
if (!server_connect_to(&s->server, &info)) {
// Await for server without blocking Ctrl+C handling
if (!await_for_server()) {
goto end;
}
struct server_info *info = &s->server.info;
if (options->display && options->control) {
if (!file_handler_init(&s->file_handler, s->server.serial,
if (!file_handler_init(&s->file_handler, options->serial,
options->push_target)) {
goto end;
}
@@ -360,7 +415,7 @@ scrcpy(struct scrcpy_options *options) {
if (!recorder_init(&s->recorder,
options->record_filename,
options->record_format,
info.frame_size)) {
info->frame_size)) {
goto end;
}
rec = &s->recorder;
@@ -406,11 +461,11 @@ scrcpy(struct scrcpy_options *options) {
if (options->display) {
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 = {
.window_title = window_title,
.frame_size = info.frame_size,
.frame_size = info->frame_size,
.always_on_top = options->always_on_top,
.window_x = options->window_x,
.window_y = options->window_y,
@@ -434,7 +489,7 @@ scrcpy(struct scrcpy_options *options) {
#ifdef HAVE_V4L2
if (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;
}

View File

@@ -61,6 +61,44 @@ get_server_path(void) {
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
push_server(const char *serial) {
char *server_path = get_server_path();
@@ -103,24 +141,26 @@ disable_tunnel_forward(const char *serial, uint16_t local_port) {
static bool
disable_tunnel(struct server *server) {
const char *serial = server->params.serial;
if (server->tunnel_forward) {
return disable_tunnel_forward(server->serial, server->local_port);
return disable_tunnel_forward(serial, server->local_port);
}
return disable_tunnel_reverse(server->serial);
return disable_tunnel_reverse(serial);
}
static sc_socket
listen_on_port(uint16_t port) {
static bool
listen_on_port(sc_socket socket, uint16_t port) {
#define IPV4_LOCALHOST 0x7F000001
return net_listen(IPV4_LOCALHOST, port, 1);
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(server->serial, port)) {
if (!enable_tunnel_reverse(serial, port)) {
// the command itself failed, it will fail on any port
return false;
}
@@ -131,15 +171,21 @@ enable_tunnel_reverse_any_port(struct server *server,
// 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.
server->server_socket = listen_on_port(port);
if (server->server_socket != SC_INVALID_SOCKET) {
// success
server->local_port = port;
return true;
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;
return true;
}
net_close(server_socket);
}
// failure, disable tunnel and try another port
if (!disable_tunnel_reverse(server->serial)) {
if (!disable_tunnel_reverse(serial)) {
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
}
@@ -165,9 +211,11 @@ 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(server->serial, port)) {
if (enable_tunnel_forward(serial, port)) {
// success
server->local_port = port;
return true;
@@ -229,6 +277,8 @@ log_level_to_server_string(enum sc_log_level level) {
static process_t
execute_server(struct server *server, const struct server_params *params) {
const char *serial = server->params.serial;
char max_size_string[6];
char bit_rate_string[11];
char max_fps_string[6];
@@ -286,16 +336,22 @@ execute_server(struct server *server, const struct server_params *params) {
// Port: 5005
// Then click on "Debug"
#endif
return adb_execute(server->serial, cmd, ARRAY_LEN(cmd));
return adb_execute(serial, cmd, ARRAY_LEN(cmd));
}
static sc_socket
connect_and_read_byte(uint16_t port) {
sc_socket socket = net_connect(IPV4_LOCALHOST, port);
sc_socket socket = net_socket();
if (socket == SC_INVALID_SOCKET) {
return SC_INVALID_SOCKET;
}
bool ok = net_connect(socket, IPV4_LOCALHOST, port);
if (!ok) {
net_close(socket);
return SC_INVALID_SOCKET;
}
char byte;
// the connection may succeed even if the server behind the "adb tunnel"
// is not listening, so read one byte to detect a working connection
@@ -308,7 +364,8 @@ connect_and_read_byte(uint16_t port) {
}
static sc_socket
connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
connect_to_server(struct server *server, uint32_t attempts, sc_tick delay) {
uint16_t port = server->local_port;
do {
LOGD("Remaining connection attempts: %d", (int) attempts);
sc_socket socket = connect_and_read_byte(port);
@@ -317,29 +374,56 @@ connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
return socket;
}
if (attempts) {
SDL_Delay(delay);
sc_mutex_lock(&server->mutex);
// Ignore timedwait return (spurious wake ups are harmless)
sc_cond_timedwait(&server->stopped_cond, &server->mutex,
sc_tick_now() + delay);
bool stopped = server->stopped;
sc_mutex_unlock(&server->mutex);
if (stopped) {
LOGI("Connection attempt stopped");
break;
}
}
} while (--attempts > 0);
return SC_INVALID_SOCKET;
}
bool
server_init(struct server *server) {
server->serial = NULL;
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);
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) {
server_params_destroy(&server->params);
return false;
}
ok = sc_cond_init(&server->process_terminated_cond);
if (!ok) {
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;
}
server->process_terminated = false;
server->stopped = false;
server->connected = false;
server->server_socket = SC_INVALID_SOCKET;
server->video_socket = SC_INVALID_SOCKET;
@@ -350,13 +434,69 @@ server_init(struct server *server) {
server->tunnel_enabled = 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;
}
static int
run_wait_server(void *data) {
run_server_connect(void *data) {
struct server *server = data;
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);
server->process_terminated = true;
@@ -370,59 +510,22 @@ run_wait_server(void *data) {
net_interrupt(server->server_socket);
}
LOGD("Server terminated");
sc_thread_join(&connect_thread, NULL);
// Written by connect_thread, sc_thread_join() provides the necessary
// memory barrier
if (server->connected) {
server->cbs->on_disconnected(server, server->cbs_userdata);
}
// Otherwise, ->on_connection_failed() is already called
end:
return 0;
}
bool
server_start(struct server *server, const struct server_params *params) {
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 == 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) {
process_terminate(server->process);
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;
server_start(struct server *server) {
return sc_thread_create(&server->thread, run_server, "server", server);
}
static bool
@@ -466,19 +569,23 @@ server_connect_to(struct server *server, struct server_info *info) {
server->server_socket = SC_INVALID_SOCKET;
} else {
uint32_t attempts = 100;
uint32_t delay = 100; // ms
server->video_socket =
connect_to_server(server->local_port, attempts, delay);
sc_tick delay = SC_TICK_FROM_MS(100);
server->video_socket = connect_to_server(server, attempts, delay);
if (server->video_socket == SC_INVALID_SOCKET) {
return false;
}
// we know that the device is listening, we don't need several attempts
server->control_socket =
net_connect(IPV4_LOCALHOST, server->local_port);
server->control_socket = net_socket();
if (server->control_socket == SC_INVALID_SOCKET) {
return false;
}
bool ok = net_connect(server->control_socket, IPV4_LOCALHOST,
server->local_port);
if (!ok) {
net_close(server->control_socket);
return false;
}
}
// we don't need the adb tunnel anymore
@@ -491,6 +598,11 @@ server_connect_to(struct server *server, struct server_info *info) {
void
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 (!net_interrupt(server->server_socket)) {
LOGW("Could not interrupt server socket");
@@ -535,7 +647,7 @@ server_stop(struct server *server) {
process_terminate(server->process);
}
sc_thread_join(&server->wait_server_thread, NULL);
sc_thread_join(&server->thread, NULL);
process_close(server->process);
}
@@ -556,7 +668,9 @@ server_destroy(struct server *server) {
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_mutex_destroy(&server->mutex);
}

View File

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

View File

@@ -94,13 +94,18 @@ net_perror(const char *s) {
}
sc_socket
net_connect(uint32_t addr, uint16_t port) {
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) {
net_perror("socket");
return SC_INVALID_SOCKET;
}
return sock;
}
bool
net_connect(sc_socket socket, uint32_t addr, uint16_t port) {
sc_raw_socket raw_sock = unwrap(socket);
SOCKADDR_IN sin;
sin.sin_family = AF_INET;
@@ -109,21 +114,15 @@ net_connect(uint32_t addr, uint16_t port) {
if (connect(raw_sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
net_perror("connect");
net_close(sock);
return SC_INVALID_SOCKET;
return false;
}
return sock;
return true;
}
sc_socket
net_listen(uint32_t addr, uint16_t port, int backlog) {
sc_raw_socket raw_sock = socket(AF_INET, SOCK_STREAM, 0);
sc_socket sock = wrap(raw_sock);
if (sock == SC_INVALID_SOCKET) {
net_perror("socket");
return SC_INVALID_SOCKET;
}
bool
net_listen(sc_socket socket, uint32_t addr, uint16_t port, int backlog) {
sc_raw_socket raw_sock = unwrap(socket);
int reuse = 1;
if (setsockopt(raw_sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse,
@@ -138,17 +137,15 @@ net_listen(uint32_t addr, uint16_t port, int backlog) {
if (bind(raw_sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
net_perror("bind");
net_close(sock);
return SC_INVALID_SOCKET;
return false;
}
if (listen(raw_sock, backlog) == SOCKET_ERROR) {
net_perror("listen");
net_close(sock);
return SC_INVALID_SOCKET;
return false;
}
return sock;
return true;
}
sc_socket

View File

@@ -31,10 +31,13 @@ void
net_cleanup(void);
sc_socket
net_connect(uint32_t addr, uint16_t port);
net_socket(void);
sc_socket
net_listen(uint32_t addr, uint16_t port, int backlog);
bool
net_connect(sc_socket socket, uint32_t addr, uint16_t port);
bool
net_listen(sc_socket socket, uint32_t addr, uint16_t port, int backlog);
sc_socket
net_accept(sc_socket server_socket);

View File

@@ -1,11 +1,9 @@
#include "str_util.h"
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include "util/strbuf.h"
#ifdef _WIN32
# include <windows.h>
@@ -211,76 +209,3 @@ utf8_from_wide_char(const wchar_t *ws) {
}
#endif
char *wrap_lines(const char *input, unsigned columns, unsigned indent) {
struct sc_strbuf buf;
// The output string should not be a lot longer than the input string (just
// a few '\n' added), so this initial capacity should almost always avoid
// internal realloc() in string buffer
size_t cap = strlen(input) * 3 / 2;
if (!sc_strbuf_init(&buf, cap)) {
return false;
}
assert(indent < columns);
#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 ");
bool wrap = col + sep_idx > columns;
char sep = input[sep_idx];
if (sep == ' ')
sep = ' ';
if (wrap) {
APPEND_CHAR('\n');
APPEND_INDENT();
col = indent;
} else if (pending) {
APPEND_CHAR(pending);
++col;
if (pending == '\n')
{
APPEND_INDENT();
col = indent;
}
}
if (sep_idx) {
APPEND(input, sep_idx);
col += sep_idx;
}
pending = sep;
input += sep_idx;
if (*input != '\0') {
// Skip the separator
++input;
}
}
if (pending)
APPEND_CHAR(pending);
return buf.s;
error:
free(buf.s);
return NULL;
}

View File

@@ -62,8 +62,4 @@ char *
utf8_from_wide_char(const wchar_t *s);
#endif
// Wrap input lines at words boundaries (spaces) so that they fit in 'columns'
// columns, left-indented by 'indent' spaces
char *wrap_lines(const char *input, unsigned columns, unsigned indent);
#endif

View File

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

View File

@@ -1,40 +0,0 @@
#ifndef SC_STRBUF_H
#define SC_STRBUF_H
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
struct sc_strbuf {
char *s;
size_t len;
size_t cap;
};
// buf->s must be manually freed by the caller
bool
sc_strbuf_init(struct sc_strbuf *buf, size_t init_cap);
bool
sc_strbuf_append(struct sc_strbuf *buf, const char *s, size_t len);
bool
sc_strbuf_append_char(struct sc_strbuf *buf, const char c);
bool
sc_strbuf_append_n(struct sc_strbuf *buf, const char c, size_t n);
static inline bool
sc_strbuf_append_str(struct sc_strbuf *buf, const char *s) {
return sc_strbuf_append(buf, s, strlen(s));
}
// Append static string (i.e. the string 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)
void
sc_strbuf_shrink(struct sc_strbuf *buf);
#endif

View File

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

View File

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

View File

@@ -299,45 +299,6 @@ static void test_strlist_contains(void) {
assert(strlist_contains("xyz", '\0', "xyz"));
}
static void test_wrap_lines(void) {
const char *s = "This is a text to test line wrapping. The lines must be "
"wrapped either 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 either at a\n"
" space or a line\n"
" 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 = wrap_lines(s, 24, 4);
assert(formatted);
assert(!strcmp(formatted, expected));
free(formatted);
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
@@ -356,6 +317,5 @@ int main(int argc, char *argv[]) {
test_parse_integers();
test_parse_integer_with_suffix();
test_strlist_contains();
test_wrap_lines();
return 0;
}