Rewrite to use libev

libuv is too immature so far and I'm not in the mood to try and
link it statically via some horrible hack (no CMake support).

Also libev is much closer to my understanding of event loops.

The messaging model stays for when/if I want to return to libuv.
This commit is contained in:
Přemysl Eric Janouch 2014-10-28 02:39:37 +01:00
parent 606c5f43af
commit 74965b0f66
5 changed files with 387 additions and 191 deletions

View File

@ -20,9 +20,13 @@ set (project_VERSION "${project_VERSION_MAJOR}")
set (project_VERSION "${project_VERSION}.${project_VERSION_MINOR}")
set (project_VERSION "${project_VERSION}.${project_VERSION_PATCH}")
# For custom modules
set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
# Dependencies
find_package (PkgConfig REQUIRED)
pkg_check_modules (dependencies REQUIRED ncursesw libuv>=0.11)
pkg_check_modules (ncursesw REQUIRED ncursesw)
find_package (LibEV REQUIRED)
if (USE_SYSTEM_TERMO)
find_package (Termo REQUIRED)
@ -38,11 +42,12 @@ else (USE_SYSTEM_TERMO)
set (Termo_LIBRARIES termo-static)
endif (USE_SYSTEM_TERMO)
include_directories (${dependencies_INCLUDE_DIRS} ${Termo_INCLUDE_DIRS})
include_directories (${ncursesw_INCLUDE_DIRS}
${LIBEV_INCLUDE_DIRS} ${Termo_INCLUDE_DIRS})
# Configuration
include (CheckFunctionExists)
set (CMAKE_REQUIRED_LIBRARIES ${dependencies_LIBRARIES})
set (CMAKE_REQUIRED_LIBRARIES ${ncursesw_LIBRARIES})
CHECK_FUNCTION_EXISTS ("resizeterm" HAVE_RESIZETERM)
# Project source files
@ -50,7 +55,7 @@ set (project_sources ${PROJECT_NAME}.c)
set (project_headers ${PROJECT_BINARY_DIR}/config.h)
# Project libraries
set (project_libraries ${dependencies_LIBRARIES} termo-static)
set (project_libraries ${ncursesw_LIBRARIES} ${LIBEV_LIBRARIES} termo-static)
# Generate a configuration file
configure_file (${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h)

2
README
View File

@ -5,7 +5,7 @@ autistdraw
Building and Running
--------------------
Build dependencies: CMake, pkg-config, ncursesw, libuv>=0.11.x, termo (included)
Build dependencies: CMake, pkg-config, ncursesw, libev, termo (included)
$ git clone https://github.com/pjanouch/autistdraw.git
$ git submodule init

View File

@ -19,15 +19,37 @@
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <locale.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <uv.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#ifndef NI_MAXHOST
#define NI_MAXHOST 1025
#endif // ! NI_MAXHOST
#ifndef NI_MAXSERV
#define NI_MAXSERV 32
#endif // ! NI_MAXSERV
#include <termios.h>
#ifndef TIOCGWINSZ
#include <sys/ioctl.h>
#endif // ! TIOCGWINSZ
#include <ev.h>
#include <curses.h>
#include "termo.h"
@ -58,20 +80,15 @@ enum network_mode
NETWORK_MODE_CLIENT ///< We're a client
};
typedef struct write_req write_req_t;
struct write_req
{
uv_write_t req; ///< libuv write request
uv_buf_t buf; ///< The data to be written
};
typedef struct client client_t;
struct client
{
LIST_HEADER (client_t)
uv_tcp_t handle; ///< TCP connection handle
int fd; ///< Client connection
ev_io watcher; ///< Client connection watcher
struct msg_reader msg_reader; ///< Client message reader
write_queue_t write_queue; ///< Write queue
};
#define BITMAP_PIXEL(app, x, y) (app)->bitmap[(y) * (app)->bitmap_w + (x)]
@ -81,20 +98,21 @@ struct app_context
{
termo_t *tk; ///< Termo instance
uv_tty_t tty; ///< TTY
uv_poll_t tty_watcher; ///< TTY input watcher
uv_timer_t tty_timer; ///< TTY timeout timer
uv_signal_t winch_watcher; ///< SIGWINCH watcher
ev_io tty_watcher; ///< TTY input watcher
ev_timer tty_timer; ///< TTY timeout timer
ev_signal winch_watcher; ///< SIGWINCH watcher
network_mode_t mode; ///< Networking mode
char read_buf[8192]; ///< Global read buffer for libuv
// Client:
uv_tcp_t server_fd; ///< Connection to the server
int server_fd; ///< Server connection
ev_io server_watcher; ///< Server connection watcher
struct msg_reader msg_reader; ///< Server message reader
write_queue_t write_queue; ///< Server write queue
// Server:
uv_tcp_t listen_fd; ///< Listening FD
int listen_fd; ///< Listening FD
ev_io listen_watcher; ///< Listening FD watcher
client_t *clients; ///< Client connections
chtype palette[2 * 9]; ///< Attribute palette
@ -128,6 +146,7 @@ app_init (app_context_t *self)
{
memset (self, 0, sizeof *self);
msg_reader_init (&self->msg_reader);
write_queue_init (&self->write_queue);
}
static void
@ -141,66 +160,88 @@ app_free (app_context_t *self)
free (self->bitmap);
msg_reader_free (&self->msg_reader);
write_queue_free (&self->write_queue);
}
// --- Server-client messaging -------------------------------------------------
static bool
read_loop (EV_P_ ev_io *watcher,
bool (*cb) (EV_P_ ev_io *, const void *, ssize_t))
{
char buf[8192];
while (true)
{
ssize_t n_read = recv (watcher->fd, buf, sizeof buf, 0);
if (n_read < 0)
{
if (errno == EAGAIN)
break;
if (errno == EINTR)
continue;
}
if (n_read <= 0 || !cb (EV_A_ watcher, buf, n_read))
return false;
}
return true;
}
static bool
flush_queue (write_queue_t *queue, ev_io *watcher)
{
struct iovec vec[queue->len], *vec_iter = vec;
for (write_req_t *iter = queue->head; iter; iter = iter->next)
*vec_iter++ = iter->data;
int new_events = EV_READ;
ssize_t written;
again:
written = writev (watcher->fd, vec, N_ELEMENTS (vec));
if (written < 0)
{
if (errno == EAGAIN)
goto skip;
if (errno == EINTR)
goto again;
return false;
}
write_queue_processed (queue, written);
skip:
if (!write_queue_is_empty (queue))
new_events |= EV_WRITE;
ev_io_stop (EV_DEFAULT_ watcher);
ev_io_set (watcher, watcher->fd, new_events);
ev_io_start (EV_DEFAULT_ watcher);
return true;
}
static write_req_t *
flush_writer (struct msg_writer *writer)
{
size_t len;
void *data = msg_writer_flush (writer, &len);
write_req_t *req = xcalloc (1, sizeof *req);
req->buf = uv_buf_init (data, len);
req->data.iov_base = msg_writer_flush (writer, &req->data.iov_len);
return req;
}
static void
on_data_written_to_client (uv_write_t *req, int status)
{
write_req_t *wr = (write_req_t *) req;
app_context_t *app = req->handle->loop->data;
client_t *client = req->data;
if (status)
// Write failed
remove_client (app, client);
free (wr->buf.base);
free (wr);
}
static void
flush_writer_to_client (struct msg_writer *writer, client_t *client)
{
write_req_t *wr = flush_writer (writer);
wr->req.data = client;
(void) uv_write (&wr->req, (uv_stream_t *) &client->handle,
&wr->buf, 1, on_data_written_to_client);
// XXX: should we put the request on a list so that we can get rid of it?
}
static void
on_data_written_to_server (uv_write_t *req, int status)
{
write_req_t *wr = (write_req_t *) req;
app_context_t *app = req->handle->loop->data;
if (status)
// Write failed
on_server_disconnected (app);
free (wr->buf.base);
free (wr);
write_queue_add (&client->write_queue, flush_writer (writer));
ev_io_stop (EV_DEFAULT_ &client->watcher);
ev_io_set (&client->watcher, client->fd, EV_READ | EV_WRITE);
ev_io_start (EV_DEFAULT_ &client->watcher);
}
static void
flush_writer_to_server (struct msg_writer *writer, app_context_t *app)
{
write_req_t *wr = flush_writer (writer);
(void) uv_write (&wr->req, (uv_stream_t *) &app->server_fd,
&wr->buf, 1, on_data_written_to_server);
// XXX: should we put the request on a list so that we can get rid of it?
write_queue_add (&app->write_queue, flush_writer (writer));
ev_io_stop (EV_DEFAULT_ &app->server_watcher);
ev_io_set (&app->server_watcher, app->server_fd, EV_READ | EV_WRITE);
ev_io_start (EV_DEFAULT_ &app->server_watcher);
}
static void
@ -873,26 +914,27 @@ on_key (app_context_t *app, termo_key_t *key)
}
static void
on_winch (uv_signal_t *handle, int signum)
on_winch (EV_P_ ev_signal *handle, int revents)
{
app_context_t *app = handle->loop->data;
(void) signum;
app_context_t *app = ev_userdata (loop);
(void) handle;
(void) revents;
#ifdef HAVE_RESIZETERM
int w, h;
if (!uv_tty_get_winsize (&app->tty, &w, &h))
#if defined (HAVE_RESIZETERM) && defined (TIOCGWINSZ)
struct winsize size;
if (!ioctl (STDOUT_FILENO, TIOCGWINSZ, (char *) &size))
{
char *row = getenv ("LINES");
char *col = getenv ("COLUMNS");
unsigned long tmp;
resizeterm (
(row && xstrtoul (&tmp, row, 10)) ? (int) tmp : h,
(col && xstrtoul (&tmp, col, 10)) ? (int) tmp : w);
(row && xstrtoul (&tmp, row, 10)) ? (int) tmp : size.ws_row,
(col && xstrtoul (&tmp, col, 10)) ? (int) tmp : size.ws_col);
}
#else // ! HAVE_RESIZETERM
#else // ! HAVE_RESIZE_TERM || ! TIOCGWINSZ
endwin ();
refresh ();
#endif // ! HAVE_RESIZETERM
#endif // ! HAVE_RESIZE_TERM || ! TIOCGWINSZ
update_canvas_for_screen (app);
redraw (app);
@ -900,60 +942,51 @@ on_winch (uv_signal_t *handle, int signum)
}
static void
on_key_timer (uv_timer_t *handle)
on_key_timer (EV_P_ ev_timer *handle, int revents)
{
app_context_t *app = handle->loop->data;
app_context_t *app = ev_userdata (loop);
(void) handle;
(void) revents;
termo_key_t key;
if (termo_getkey_force (app->tk, &key) == TERMO_RES_KEY)
if (!on_key (app, &key))
uv_stop (handle->loop);
ev_break (EV_A_ EVBREAK_ONE);
}
static void
on_tty_readable (uv_poll_t *handle, int status, int events)
on_tty_readable (EV_P_ ev_io *handle, int revents)
{
// Ignoring and hoping for the best
(void) status;
(void) events;
(void) handle;
(void) revents;
app_context_t *app = handle->loop->data;
app_context_t *app = ev_userdata (loop);
uv_timer_stop (&app->tty_timer);
ev_timer_stop (EV_A_ &app->tty_timer);
termo_advisereadable (app->tk);
termo_key_t key;
termo_result_t ret;
while ((ret = termo_getkey (app->tk, &key)) == TERMO_RES_KEY)
if (!on_key (app, &key))
uv_stop (handle->loop);
ev_break (EV_A_ EVBREAK_ONE);
if (ret == TERMO_RES_AGAIN)
uv_timer_start (&app->tty_timer,
on_key_timer, termo_get_waittime (app->tk), 0);
}
static void
app_uv_allocator (uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf)
{
// Let's just use a single "global" buffer
(void) suggested_size;
app_context_t *app = handle->loop->data;
buf->base = app->read_buf;
buf->len = sizeof app->read_buf;
ev_timer_start (EV_A_ &app->tty_timer);
}
// --- Client-specific stuff ---------------------------------------------------
typedef void (*server_handler_fn) (app_context_t *, struct msg_unpacker *);
typedef bool (*server_handler_fn) (app_context_t *, struct msg_unpacker *);
static void
on_server_disconnected (app_context_t *app)
{
// TODO: cancel any write requests?
// XXX: should we unref it?
uv_close ((uv_handle_t *) &app->server_fd, NULL);
xclose (app->server_fd);
ev_io_stop (EV_DEFAULT_ &app->server_watcher);
display ("Disconnected!");
beep (); // Beep beep! Made a boo-boo.
@ -963,21 +996,20 @@ on_server_disconnected (app_context_t *app)
app->mode = NETWORK_MODE_STANDALONE;
}
static void
static bool
on_server_hello (app_context_t *app, struct msg_unpacker *unpacker)
{
(void) app;
uint8_t version;
if (!msg_unpacker_u8 (unpacker, &version))
return;
return false; // Not enough data
if (version != PROTOCOL_VERSION)
// XXX: possibly incompatible version, disconnect?
return;
return false; // Incompatible version
return true;
}
static void
static bool
on_server_get_bitmap (app_context_t *app, struct msg_unpacker *unpacker)
{
int32_t x, y;
@ -986,11 +1018,11 @@ on_server_get_bitmap (app_context_t *app, struct msg_unpacker *unpacker)
|| !msg_unpacker_i32 (unpacker, &y)
|| !msg_unpacker_u64 (unpacker, &w)
|| !msg_unpacker_u64 (unpacker, &h))
return;
return false; // Not enough data
size_t size = w * h;
if ((h && w > SIZE_MAX / h) || w > SIZE_MAX || h > SIZE_MAX)
return;
return false; // The server is flooding us
uint8_t *bitmap = xcalloc (size, sizeof *app->bitmap);
@ -1015,9 +1047,10 @@ on_server_get_bitmap (app_context_t *app, struct msg_unpacker *unpacker)
app->bitmap_h = h;
redraw_canvas (app);
return true;
}
static void
static bool
on_server_put_point (app_context_t *app, struct msg_unpacker *unpacker)
{
int32_t x, y;
@ -1026,22 +1059,19 @@ on_server_put_point (app_context_t *app, struct msg_unpacker *unpacker)
if (!msg_unpacker_i32 (unpacker, &x)
|| !msg_unpacker_i32 (unpacker, &y)
|| !msg_unpacker_u8 (unpacker, &color))
return;
return false; // Not enough data
draw_point (app, x, y, color);
return true;
}
static void
on_server_data (uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf)
static bool
on_server_data (EV_P_ ev_io *watcher, const void *buf, ssize_t n_read)
{
app_context_t *app = stream->loop->data;
if (nread == UV_EOF || nread < 0)
{
on_server_disconnected (app);
return;
}
app_context_t *app = ev_userdata (loop);
(void) watcher;
msg_reader_feed (&app->msg_reader, buf->base, nread);
msg_reader_feed (&app->msg_reader, buf, n_read);
static const server_handler_fn handlers[MESSAGE_COUNT] =
{
@ -1060,20 +1090,34 @@ on_server_data (uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf)
uint8_t type;
if (!msg_unpacker_u8 (&unpacker, &type)
|| type >= MESSAGE_COUNT)
// XXX: unknown message, disconnect?
continue;
return false; // Unknown message
server_handler_fn handler = handlers[type];
if (!handler)
// XXX: unknown message, disconnect?
continue;
handler (app, &unpacker);
return false; // Unknown message
if (!handler (app, &unpacker))
return false; // Invalid message
if (msg_unpacker_get_available (&unpacker) > 0)
// XXX: overlong message, disconnect?
continue;
return false; // Overlong message
}
return true;
}
static void
on_server_ready (EV_P_ ev_io *watcher, int revents)
{
app_context_t *app = ev_userdata (loop);
if (revents & EV_READ)
if (!read_loop (EV_A_ watcher, on_server_data))
goto error;
if (revents & EV_WRITE)
if (!flush_queue (&app->write_queue, watcher))
goto error;
return;
error:
on_server_disconnected (app);
}
// --- Server-specific stuff ---------------------------------------------------
@ -1084,11 +1128,13 @@ typedef bool (*client_handler_fn)
static void
remove_client (app_context_t *app, client_t *client)
{
// TODO: stop any watchers?
// TODO: cancel any write requests?
// XXX: should we unref it?
uv_close ((uv_handle_t *) &client->handle, NULL);
xclose (client->fd);
LIST_UNLINK (app->clients, client);
msg_reader_free (&client->msg_reader);
write_queue_free (&client->write_queue);
free (client);
}
@ -1137,15 +1183,13 @@ on_client_put_point (app_context_t *app, client_t *client,
return true;
}
static void
on_client_data (uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf)
static bool
on_client_data (EV_P_ ev_io *watcher, const void *buf, ssize_t n_read)
{
app_context_t *app = stream->loop->data;
client_t *client = stream->data;
if (nread == UV_EOF || nread < 0)
goto disconnect; // Connection closed or error
app_context_t *app = ev_userdata (loop);
client_t *client = watcher->data;
msg_reader_feed (&client->msg_reader, buf->base, nread);
msg_reader_feed (&client->msg_reader, buf, n_read);
static const client_handler_fn handlers[MESSAGE_COUNT] =
{
@ -1163,51 +1207,74 @@ on_client_data (uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf)
uint8_t type;
if (!msg_unpacker_u8 (&unpacker, &type))
goto disconnect; // Invalid message
return false; // Invalid message
if (type >= MESSAGE_COUNT)
goto disconnect; // Unknown message
return false; // Unknown message
client_handler_fn handler = handlers[type];
if (!handler)
goto disconnect; // Unknown message
return false; // Unknown message
if (!handler (app, client, &unpacker))
goto disconnect; // Invalid message
return false; // Invalid message
if (msg_unpacker_get_available (&unpacker) > 0)
goto disconnect; // Overlong message data
return false; // Overlong message data
}
return true;
}
static void
on_client_ready (EV_P_ ev_io *watcher, int revents)
{
app_context_t *app = ev_userdata (loop);
client_t *client = watcher->data;
if (revents & EV_READ)
if (!read_loop (EV_A_ watcher, on_client_data))
goto error;
if (revents & EV_WRITE)
if (!flush_queue (&client->write_queue, watcher))
goto error;
return;
disconnect:
error:
remove_client (app, client);
}
static void
on_new_client (uv_stream_t *server, int status)
on_new_client (EV_P_ ev_io *watcher, int revents)
{
app_context_t *app = server->loop->data;
if (status)
return;
app_context_t *app = ev_userdata (loop);
(void) revents;
int err;
client_t *client = xcalloc (1, sizeof *client);
if ((err = uv_tcp_init (server->loop, &client->handle)))
goto free_client;
if ((err = uv_accept (server, (uv_stream_t *) &client->handle))
|| (err = uv_read_start ((uv_stream_t *) &client->handle,
app_uv_allocator, on_client_data)))
// XXX: do we need to un-accept?
goto free_handle;
while (true)
{
int sock_fd = accept (watcher->fd, NULL, NULL);
if (sock_fd == -1)
{
if (errno == EAGAIN)
break;
if (errno == EINTR
|| errno == ECONNABORTED)
continue;
client->handle.data = client;
msg_reader_init (&client->msg_reader);
LIST_PREPEND (app->clients, client);
return;
// Stop accepting connections to prevent busy looping
// TODO: indicate the error to the user
ev_io_stop (EV_A_ watcher);
break;
}
free_handle:
uv_close ((uv_handle_t *) &client->handle, NULL);
// XXX: should we unref it?
free_client:
free (client);
client_t *client = xcalloc (1, sizeof *client);
client->fd = sock_fd;
msg_reader_init (&client->msg_reader);
write_queue_init (&client->write_queue);
set_blocking (sock_fd, false);
ev_io_init (&client->watcher, on_client_ready, sock_fd, EV_READ);
client->watcher.data = client;
ev_io_start (EV_A_ &client->watcher);
LIST_PREPEND (app->clients, client);
}
}
// --- Program startup ---------------------------------------------------------
@ -1388,17 +1455,13 @@ initialize_client (app_context_t *app, struct addrinfo *address)
exit (EXIT_FAILURE);
}
int yes = 1;
(void) setsockopt (sock_fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof yes);
set_blocking (sock_fd, false);
if ((err = uv_tcp_init (uv_default_loop (), &app->server_fd))
|| (err = uv_tcp_open (&app->server_fd, sock_fd))
|| (err = uv_tcp_keepalive (&app->server_fd, true, 30))
|| (err = uv_read_start ((uv_stream_t *) &app->server_fd,
app_uv_allocator, on_server_data)))
{
fprintf (stderr, "%s: %s: %s\n",
"error", "initialization failed", uv_strerror (err));
exit (EXIT_FAILURE);
}
app->server_fd = sock_fd;
ev_io_init (&app->server_watcher, on_server_ready, sock_fd, EV_READ);
ev_io_start (EV_DEFAULT_ &app->server_watcher);
send_hello_request (app);
send_get_bitmap_request (app);
@ -1409,15 +1472,29 @@ initialize_server (app_context_t *app, struct addrinfo *address)
{
app->mode = NETWORK_MODE_SERVER;
int err;
if ((err = uv_tcp_init (uv_default_loop (), &app->listen_fd))
|| (err = uv_tcp_bind (&app->listen_fd, address->ai_addr, 0))
|| (err = uv_listen ((uv_stream_t *) &app->listen_fd, 10, on_new_client)))
{
fprintf (stderr, "%s: %s: %s\n",
"error", "initialization failed", uv_strerror (err));
exit (EXIT_FAILURE);
}
int sock_fd = socket (address->ai_family,
address->ai_socktype, address->ai_protocol);
if (sock_fd == -1)
goto fail;
if (bind (sock_fd, address->ai_addr, address->ai_addrlen)
|| listen (sock_fd, 10))
goto fail;
int yes = 1;
(void) setsockopt (sock_fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof yes);
(void) setsockopt (sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes);
set_blocking (sock_fd, false);
app->listen_fd = sock_fd;
ev_io_init (&app->listen_watcher, on_new_client, sock_fd, EV_READ);
ev_io_start (EV_DEFAULT_ &app->listen_watcher);
return;
fail:
fprintf (stderr, "%s: %s: %s\n",
"error", "initialization failed", strerror (errno));
exit (EXIT_FAILURE);
}
int
@ -1429,6 +1506,13 @@ main (int argc, char *argv[])
app_context_t app;
app_init (&app);
struct ev_loop *loop = EV_DEFAULT;
if (!loop)
{
fprintf (stderr, "%s: %s\n", "error", "cannot initialize libev");
exit (EXIT_FAILURE);
}
app_options_t options;
app_options_init (&options);
parse_program_arguments (&options, argc, argv);
@ -1461,29 +1545,27 @@ main (int argc, char *argv[])
exit (EXIT_FAILURE);
}
uv_loop_t *loop = uv_default_loop ();
loop->data = &app;
ev_set_userdata (loop, &app);
uv_signal_init (loop, &app.winch_watcher);
uv_signal_start (&app.winch_watcher, on_winch, SIGWINCH);
ev_signal_init (&app.winch_watcher, on_winch, SIGWINCH);
ev_signal_start (EV_DEFAULT_ &app.winch_watcher);
uv_tty_init (loop, &app.tty, STDOUT_FILENO, false);
ev_io_init (&app.tty_watcher, on_tty_readable, STDIN_FILENO, EV_READ);
ev_io_start (EV_DEFAULT_ &app.tty_watcher);
uv_poll_init (loop, &app.tty_watcher, STDIN_FILENO);
uv_poll_start (&app.tty_watcher, UV_READABLE, on_tty_readable);
uv_timer_init (loop, &app.tty_timer);
ev_timer_init (&app.tty_timer, on_key_timer,
termo_get_waittime (app.tk) / 1000., 0);
init_palette (&app);
update_canvas_for_screen (&app);
redraw (&app);
redraw_canvas (&app);
uv_run (loop, UV_RUN_DEFAULT);
ev_run (loop, 0);
endwin ();
app_free (&app);
uv_loop_close (loop);
ev_loop_destroy (loop);
return 0;
}

18
cmake/FindLibEV.cmake Normal file
View File

@ -0,0 +1,18 @@
# Public Domain
# The author of libev is a dick and doesn't want to add support for pkg-config,
# forcing us to include this pointless file in the distribution.
# Some distributions do add it, though
find_package (PkgConfig REQUIRED)
pkg_check_modules (LIBEV libev)
if (NOT LIBEV_FOUND)
find_path (LIBEV_INCLUDE_DIRS ev.h)
find_library (LIBEV_LIBRARIES NAMES ev)
if (LIBEV_INCLUDE_DIRS AND LIBEV_LIBRARIES)
set (LIBEV_FOUND TRUE)
endif (LIBEV_INCLUDE_DIRS AND LIBEV_LIBRARIES)
endif (NOT LIBEV_FOUND)

91
utils.c
View File

@ -104,6 +104,24 @@ xrealloc (void *o, size_t n)
(link)->next->prev = (link)->prev; \
BLOCK_END
#define LIST_APPEND_WITH_TAIL(head, tail, link) \
BLOCK_START \
(link)->prev = (tail); \
(link)->next = NULL; \
if ((link)->prev) \
(link)->prev->next = (link); \
else \
(head) = (link); \
(tail) = (link); \
BLOCK_END
#define LIST_UNLINK_WITH_TAIL(head, tail, link) \
BLOCK_START \
if ((tail) == (link)) \
(tail) = (link)->prev; \
LIST_UNLINK ((head), (link)); \
BLOCK_END
// --- Dynamically allocated strings -------------------------------------------
// Basically a string builder to abstract away manual memory management.
@ -236,11 +254,14 @@ static bool
set_blocking (int fd, bool blocking)
{
int flags = fcntl (fd, F_GETFL);
bool prev = !(flags & O_NONBLOCK);
if (blocking)
flags &= ~O_NONBLOCK;
else
flags |= O_NONBLOCK;
(void) fcntl (fd, F_SETFL, flags);
return prev;
}
@ -284,6 +305,76 @@ xstrtoul (unsigned long *out, const char *s, int base)
return errno == 0 && !*end && end != s;
}
// --- libuv-style write adaptor -----------------------------------------------
// Makes it possible to use iovec to write multiple data chunks at once.
typedef struct write_req write_req_t;
struct write_req
{
LIST_HEADER (write_req_t)
struct iovec data; ///< Data to be written
};
typedef struct write_queue write_queue_t;
struct write_queue
{
write_req_t *head; ///< The head of the queue
write_req_t *tail; ///< The tail of the queue
size_t head_offset; ///< Offset into the head
size_t len;
};
static void
write_queue_init (struct write_queue *self)
{
self->head = self->tail = NULL;
self->head_offset = 0;
self->len = 0;
}
static void
write_queue_free (struct write_queue *self)
{
for (write_req_t *iter = self->head, *next; iter; iter = next)
{
next = iter->next;
free (iter->data.iov_base);
free (iter);
}
}
static void
write_queue_add (struct write_queue *self, write_req_t *req)
{
LIST_APPEND_WITH_TAIL (self->head, self->tail, req);
self->len++;
}
static void
write_queue_processed (struct write_queue *self, size_t len)
{
while (self->head
&& self->head_offset + len >= self->head->data.iov_len)
{
write_req_t *head = self->head;
len -= (head->data.iov_len - self->head_offset);
self->head_offset = 0;
LIST_UNLINK_WITH_TAIL (self->head, self->tail, head);
self->len--;
free (head->data.iov_base);
free (head);
}
self->head_offset += len;
}
static bool
write_queue_is_empty (struct write_queue *self)
{
return self->head == NULL;
}
// --- Message reader ----------------------------------------------------------
struct msg_reader