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_MINOR}")
set (project_VERSION "${project_VERSION}.${project_VERSION_PATCH}") set (project_VERSION "${project_VERSION}.${project_VERSION_PATCH}")
# For custom modules
set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
# Dependencies # Dependencies
find_package (PkgConfig REQUIRED) 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) if (USE_SYSTEM_TERMO)
find_package (Termo REQUIRED) find_package (Termo REQUIRED)
@ -38,11 +42,12 @@ else (USE_SYSTEM_TERMO)
set (Termo_LIBRARIES termo-static) set (Termo_LIBRARIES termo-static)
endif (USE_SYSTEM_TERMO) endif (USE_SYSTEM_TERMO)
include_directories (${dependencies_INCLUDE_DIRS} ${Termo_INCLUDE_DIRS}) include_directories (${ncursesw_INCLUDE_DIRS}
${LIBEV_INCLUDE_DIRS} ${Termo_INCLUDE_DIRS})
# Configuration # Configuration
include (CheckFunctionExists) include (CheckFunctionExists)
set (CMAKE_REQUIRED_LIBRARIES ${dependencies_LIBRARIES}) set (CMAKE_REQUIRED_LIBRARIES ${ncursesw_LIBRARIES})
CHECK_FUNCTION_EXISTS ("resizeterm" HAVE_RESIZETERM) CHECK_FUNCTION_EXISTS ("resizeterm" HAVE_RESIZETERM)
# Project source files # Project source files
@ -50,7 +55,7 @@ set (project_sources ${PROJECT_NAME}.c)
set (project_headers ${PROJECT_BINARY_DIR}/config.h) set (project_headers ${PROJECT_BINARY_DIR}/config.h)
# Project libraries # Project libraries
set (project_libraries ${dependencies_LIBRARIES} termo-static) set (project_libraries ${ncursesw_LIBRARIES} ${LIBEV_LIBRARIES} termo-static)
# Generate a configuration file # Generate a configuration file
configure_file (${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h) 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 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 clone https://github.com/pjanouch/autistdraw.git
$ git submodule init $ git submodule init

View File

@ -19,15 +19,37 @@
*/ */
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <stdlib.h>
#include <locale.h> #include <locale.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <string.h> #include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.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 <curses.h>
#include "termo.h" #include "termo.h"
@ -58,20 +80,15 @@ enum network_mode
NETWORK_MODE_CLIENT ///< We're a client 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; typedef struct client client_t;
struct client struct client
{ {
LIST_HEADER (client_t) 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 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)] #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 termo_t *tk; ///< Termo instance
uv_tty_t tty; ///< TTY ev_io tty_watcher; ///< TTY input watcher
uv_poll_t tty_watcher; ///< TTY input watcher ev_timer tty_timer; ///< TTY timeout timer
uv_timer_t tty_timer; ///< TTY timeout timer ev_signal winch_watcher; ///< SIGWINCH watcher
uv_signal_t winch_watcher; ///< SIGWINCH watcher
network_mode_t mode; ///< Networking mode network_mode_t mode; ///< Networking mode
char read_buf[8192]; ///< Global read buffer for libuv
// Client: // 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 struct msg_reader msg_reader; ///< Server message reader
write_queue_t write_queue; ///< Server write queue
// Server: // 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 client_t *clients; ///< Client connections
chtype palette[2 * 9]; ///< Attribute palette chtype palette[2 * 9]; ///< Attribute palette
@ -128,6 +146,7 @@ app_init (app_context_t *self)
{ {
memset (self, 0, sizeof *self); memset (self, 0, sizeof *self);
msg_reader_init (&self->msg_reader); msg_reader_init (&self->msg_reader);
write_queue_init (&self->write_queue);
} }
static void static void
@ -141,66 +160,88 @@ app_free (app_context_t *self)
free (self->bitmap); free (self->bitmap);
msg_reader_free (&self->msg_reader); msg_reader_free (&self->msg_reader);
write_queue_free (&self->write_queue);
} }
// --- Server-client messaging ------------------------------------------------- // --- 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 * static write_req_t *
flush_writer (struct msg_writer *writer) flush_writer (struct msg_writer *writer)
{ {
size_t len;
void *data = msg_writer_flush (writer, &len);
write_req_t *req = xcalloc (1, sizeof *req); 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; 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 static void
flush_writer_to_client (struct msg_writer *writer, client_t *client) flush_writer_to_client (struct msg_writer *writer, client_t *client)
{ {
write_req_t *wr = flush_writer (writer); write_queue_add (&client->write_queue, flush_writer (writer));
wr->req.data = client; ev_io_stop (EV_DEFAULT_ &client->watcher);
(void) uv_write (&wr->req, (uv_stream_t *) &client->handle, ev_io_set (&client->watcher, client->fd, EV_READ | EV_WRITE);
&wr->buf, 1, on_data_written_to_client); ev_io_start (EV_DEFAULT_ &client->watcher);
// 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);
} }
static void static void
flush_writer_to_server (struct msg_writer *writer, app_context_t *app) flush_writer_to_server (struct msg_writer *writer, app_context_t *app)
{ {
write_req_t *wr = flush_writer (writer); write_queue_add (&app->write_queue, flush_writer (writer));
(void) uv_write (&wr->req, (uv_stream_t *) &app->server_fd, ev_io_stop (EV_DEFAULT_ &app->server_watcher);
&wr->buf, 1, on_data_written_to_server); ev_io_set (&app->server_watcher, app->server_fd, EV_READ | EV_WRITE);
// XXX: should we put the request on a list so that we can get rid of it? ev_io_start (EV_DEFAULT_ &app->server_watcher);
} }
static void static void
@ -873,26 +914,27 @@ on_key (app_context_t *app, termo_key_t *key)
} }
static void 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; app_context_t *app = ev_userdata (loop);
(void) signum; (void) handle;
(void) revents;
#ifdef HAVE_RESIZETERM #if defined (HAVE_RESIZETERM) && defined (TIOCGWINSZ)
int w, h; struct winsize size;
if (!uv_tty_get_winsize (&app->tty, &w, &h)) if (!ioctl (STDOUT_FILENO, TIOCGWINSZ, (char *) &size))
{ {
char *row = getenv ("LINES"); char *row = getenv ("LINES");
char *col = getenv ("COLUMNS"); char *col = getenv ("COLUMNS");
unsigned long tmp; unsigned long tmp;
resizeterm ( resizeterm (
(row && xstrtoul (&tmp, row, 10)) ? (int) tmp : h, (row && xstrtoul (&tmp, row, 10)) ? (int) tmp : size.ws_row,
(col && xstrtoul (&tmp, col, 10)) ? (int) tmp : w); (col && xstrtoul (&tmp, col, 10)) ? (int) tmp : size.ws_col);
} }
#else // ! HAVE_RESIZETERM #else // ! HAVE_RESIZE_TERM || ! TIOCGWINSZ
endwin (); endwin ();
refresh (); refresh ();
#endif // ! HAVE_RESIZETERM #endif // ! HAVE_RESIZE_TERM || ! TIOCGWINSZ
update_canvas_for_screen (app); update_canvas_for_screen (app);
redraw (app); redraw (app);
@ -900,60 +942,51 @@ on_winch (uv_signal_t *handle, int signum)
} }
static void 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; termo_key_t key;
if (termo_getkey_force (app->tk, &key) == TERMO_RES_KEY) if (termo_getkey_force (app->tk, &key) == TERMO_RES_KEY)
if (!on_key (app, &key)) if (!on_key (app, &key))
uv_stop (handle->loop); ev_break (EV_A_ EVBREAK_ONE);
} }
static void 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 // Ignoring and hoping for the best
(void) status; (void) handle;
(void) events; (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_advisereadable (app->tk);
termo_key_t key; termo_key_t key;
termo_result_t ret; termo_result_t ret;
while ((ret = termo_getkey (app->tk, &key)) == TERMO_RES_KEY) while ((ret = termo_getkey (app->tk, &key)) == TERMO_RES_KEY)
if (!on_key (app, &key)) if (!on_key (app, &key))
uv_stop (handle->loop); ev_break (EV_A_ EVBREAK_ONE);
if (ret == TERMO_RES_AGAIN) if (ret == TERMO_RES_AGAIN)
uv_timer_start (&app->tty_timer, ev_timer_start (EV_A_ &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;
} }
// --- Client-specific stuff --------------------------------------------------- // --- 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 static void
on_server_disconnected (app_context_t *app) on_server_disconnected (app_context_t *app)
{ {
// TODO: cancel any write requests? // TODO: cancel any write requests?
// XXX: should we unref it? // 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!"); display ("Disconnected!");
beep (); // Beep beep! Made a boo-boo. beep (); // Beep beep! Made a boo-boo.
@ -963,21 +996,20 @@ on_server_disconnected (app_context_t *app)
app->mode = NETWORK_MODE_STANDALONE; app->mode = NETWORK_MODE_STANDALONE;
} }
static void static bool
on_server_hello (app_context_t *app, struct msg_unpacker *unpacker) on_server_hello (app_context_t *app, struct msg_unpacker *unpacker)
{ {
(void) app; (void) app;
uint8_t version; uint8_t version;
if (!msg_unpacker_u8 (unpacker, &version)) if (!msg_unpacker_u8 (unpacker, &version))
return; return false; // Not enough data
if (version != PROTOCOL_VERSION) if (version != PROTOCOL_VERSION)
// XXX: possibly incompatible version, disconnect? return false; // Incompatible version
return; return true;
} }
static void static bool
on_server_get_bitmap (app_context_t *app, struct msg_unpacker *unpacker) on_server_get_bitmap (app_context_t *app, struct msg_unpacker *unpacker)
{ {
int32_t x, y; 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_i32 (unpacker, &y)
|| !msg_unpacker_u64 (unpacker, &w) || !msg_unpacker_u64 (unpacker, &w)
|| !msg_unpacker_u64 (unpacker, &h)) || !msg_unpacker_u64 (unpacker, &h))
return; return false; // Not enough data
size_t size = w * h; size_t size = w * h;
if ((h && w > SIZE_MAX / h) || w > SIZE_MAX || h > SIZE_MAX) 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); 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; app->bitmap_h = h;
redraw_canvas (app); redraw_canvas (app);
return true;
} }
static void static bool
on_server_put_point (app_context_t *app, struct msg_unpacker *unpacker) on_server_put_point (app_context_t *app, struct msg_unpacker *unpacker)
{ {
int32_t x, y; 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) if (!msg_unpacker_i32 (unpacker, &x)
|| !msg_unpacker_i32 (unpacker, &y) || !msg_unpacker_i32 (unpacker, &y)
|| !msg_unpacker_u8 (unpacker, &color)) || !msg_unpacker_u8 (unpacker, &color))
return; return false; // Not enough data
draw_point (app, x, y, color); draw_point (app, x, y, color);
return true;
} }
static void static bool
on_server_data (uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) on_server_data (EV_P_ ev_io *watcher, const void *buf, ssize_t n_read)
{ {
app_context_t *app = stream->loop->data; app_context_t *app = ev_userdata (loop);
if (nread == UV_EOF || nread < 0) (void) watcher;
{
on_server_disconnected (app);
return;
}
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] = 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; uint8_t type;
if (!msg_unpacker_u8 (&unpacker, &type) if (!msg_unpacker_u8 (&unpacker, &type)
|| type >= MESSAGE_COUNT) || type >= MESSAGE_COUNT)
// XXX: unknown message, disconnect? return false; // Unknown message
continue;
server_handler_fn handler = handlers[type]; server_handler_fn handler = handlers[type];
if (!handler) if (!handler)
// XXX: unknown message, disconnect? return false; // Unknown message
continue; if (!handler (app, &unpacker))
return false; // Invalid message
handler (app, &unpacker);
if (msg_unpacker_get_available (&unpacker) > 0) if (msg_unpacker_get_available (&unpacker) > 0)
// XXX: overlong message, disconnect? return false; // Overlong message
continue;
} }
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 --------------------------------------------------- // --- Server-specific stuff ---------------------------------------------------
@ -1084,11 +1128,13 @@ typedef bool (*client_handler_fn)
static void static void
remove_client (app_context_t *app, client_t *client) remove_client (app_context_t *app, client_t *client)
{ {
// TODO: stop any watchers?
// TODO: cancel any write requests? // TODO: cancel any write requests?
// XXX: should we unref it? // XXX: should we unref it?
uv_close ((uv_handle_t *) &client->handle, NULL); xclose (client->fd);
LIST_UNLINK (app->clients, client); LIST_UNLINK (app->clients, client);
msg_reader_free (&client->msg_reader); msg_reader_free (&client->msg_reader);
write_queue_free (&client->write_queue);
free (client); free (client);
} }
@ -1137,15 +1183,13 @@ on_client_put_point (app_context_t *app, client_t *client,
return true; return true;
} }
static void static bool
on_client_data (uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) on_client_data (EV_P_ ev_io *watcher, const void *buf, ssize_t n_read)
{ {
app_context_t *app = stream->loop->data; app_context_t *app = ev_userdata (loop);
client_t *client = stream->data; client_t *client = watcher->data;
if (nread == UV_EOF || nread < 0)
goto disconnect; // Connection closed or error
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] = 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; uint8_t type;
if (!msg_unpacker_u8 (&unpacker, &type)) if (!msg_unpacker_u8 (&unpacker, &type))
goto disconnect; // Invalid message return false; // Invalid message
if (type >= MESSAGE_COUNT) if (type >= MESSAGE_COUNT)
goto disconnect; // Unknown message return false; // Unknown message
client_handler_fn handler = handlers[type]; client_handler_fn handler = handlers[type];
if (!handler) if (!handler)
goto disconnect; // Unknown message return false; // Unknown message
if (!handler (app, client, &unpacker)) if (!handler (app, client, &unpacker))
goto disconnect; // Invalid message return false; // Invalid message
if (msg_unpacker_get_available (&unpacker) > 0) 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; return;
disconnect: error:
remove_client (app, client); remove_client (app, client);
} }
static void 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; app_context_t *app = ev_userdata (loop);
if (status) (void) revents;
return;
while (true)
{
int sock_fd = accept (watcher->fd, NULL, NULL);
if (sock_fd == -1)
{
if (errno == EAGAIN)
break;
if (errno == EINTR
|| errno == ECONNABORTED)
continue;
// Stop accepting connections to prevent busy looping
// TODO: indicate the error to the user
ev_io_stop (EV_A_ watcher);
break;
}
int err;
client_t *client = xcalloc (1, sizeof *client); client_t *client = xcalloc (1, sizeof *client);
if ((err = uv_tcp_init (server->loop, &client->handle))) client->fd = sock_fd;
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;
client->handle.data = client;
msg_reader_init (&client->msg_reader); msg_reader_init (&client->msg_reader);
LIST_PREPEND (app->clients, client); write_queue_init (&client->write_queue);
return;
free_handle: set_blocking (sock_fd, false);
uv_close ((uv_handle_t *) &client->handle, NULL); ev_io_init (&client->watcher, on_client_ready, sock_fd, EV_READ);
// XXX: should we unref it? client->watcher.data = client;
free_client: ev_io_start (EV_A_ &client->watcher);
free (client);
LIST_PREPEND (app->clients, client);
}
} }
// --- Program startup --------------------------------------------------------- // --- Program startup ---------------------------------------------------------
@ -1388,17 +1455,13 @@ initialize_client (app_context_t *app, struct addrinfo *address)
exit (EXIT_FAILURE); exit (EXIT_FAILURE);
} }
int yes = 1;
(void) setsockopt (sock_fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof yes);
set_blocking (sock_fd, false); set_blocking (sock_fd, false);
if ((err = uv_tcp_init (uv_default_loop (), &app->server_fd)) app->server_fd = sock_fd;
|| (err = uv_tcp_open (&app->server_fd, sock_fd)) ev_io_init (&app->server_watcher, on_server_ready, sock_fd, EV_READ);
|| (err = uv_tcp_keepalive (&app->server_fd, true, 30)) ev_io_start (EV_DEFAULT_ &app->server_watcher);
|| (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);
}
send_hello_request (app); send_hello_request (app);
send_get_bitmap_request (app); send_get_bitmap_request (app);
@ -1409,16 +1472,30 @@ initialize_server (app_context_t *app, struct addrinfo *address)
{ {
app->mode = NETWORK_MODE_SERVER; app->mode = NETWORK_MODE_SERVER;
int err; int sock_fd = socket (address->ai_family,
if ((err = uv_tcp_init (uv_default_loop (), &app->listen_fd)) address->ai_socktype, address->ai_protocol);
|| (err = uv_tcp_bind (&app->listen_fd, address->ai_addr, 0)) if (sock_fd == -1)
|| (err = uv_listen ((uv_stream_t *) &app->listen_fd, 10, on_new_client))) 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", fprintf (stderr, "%s: %s: %s\n",
"error", "initialization failed", uv_strerror (err)); "error", "initialization failed", strerror (errno));
exit (EXIT_FAILURE); exit (EXIT_FAILURE);
} }
}
int int
main (int argc, char *argv[]) main (int argc, char *argv[])
@ -1429,6 +1506,13 @@ main (int argc, char *argv[])
app_context_t app; app_context_t app;
app_init (&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_t options;
app_options_init (&options); app_options_init (&options);
parse_program_arguments (&options, argc, argv); parse_program_arguments (&options, argc, argv);
@ -1461,29 +1545,27 @@ main (int argc, char *argv[])
exit (EXIT_FAILURE); exit (EXIT_FAILURE);
} }
uv_loop_t *loop = uv_default_loop (); ev_set_userdata (loop, &app);
loop->data = &app;
uv_signal_init (loop, &app.winch_watcher); ev_signal_init (&app.winch_watcher, on_winch, SIGWINCH);
uv_signal_start (&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); ev_timer_init (&app.tty_timer, on_key_timer,
uv_poll_start (&app.tty_watcher, UV_READABLE, on_tty_readable); termo_get_waittime (app.tk) / 1000., 0);
uv_timer_init (loop, &app.tty_timer);
init_palette (&app); init_palette (&app);
update_canvas_for_screen (&app); update_canvas_for_screen (&app);
redraw (&app); redraw (&app);
redraw_canvas (&app); redraw_canvas (&app);
uv_run (loop, UV_RUN_DEFAULT); ev_run (loop, 0);
endwin (); endwin ();
app_free (&app); app_free (&app);
uv_loop_close (loop); ev_loop_destroy (loop);
return 0; 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; \ (link)->next->prev = (link)->prev; \
BLOCK_END 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 ------------------------------------------- // --- Dynamically allocated strings -------------------------------------------
// Basically a string builder to abstract away manual memory management. // Basically a string builder to abstract away manual memory management.
@ -236,11 +254,14 @@ static bool
set_blocking (int fd, bool blocking) set_blocking (int fd, bool blocking)
{ {
int flags = fcntl (fd, F_GETFL); int flags = fcntl (fd, F_GETFL);
bool prev = !(flags & O_NONBLOCK); bool prev = !(flags & O_NONBLOCK);
if (blocking) if (blocking)
flags &= ~O_NONBLOCK; flags &= ~O_NONBLOCK;
else else
flags |= O_NONBLOCK; flags |= O_NONBLOCK;
(void) fcntl (fd, F_SETFL, flags);
return prev; return prev;
} }
@ -284,6 +305,76 @@ xstrtoul (unsigned long *out, const char *s, int base)
return errno == 0 && !*end && end != s; 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 ---------------------------------------------------------- // --- Message reader ----------------------------------------------------------
struct msg_reader struct msg_reader