From 74965b0f66f1907346df49c1eebb07169d26129d Mon Sep 17 00:00:00 2001 From: Přemysl Janouch Date: Tue, 28 Oct 2014 02:39:37 +0100 Subject: 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. --- CMakeLists.txt | 13 +- README | 2 +- autistdraw.c | 450 +++++++++++++++++++++++++++++--------------------- cmake/FindLibEV.cmake | 18 ++ utils.c | 91 ++++++++++ 5 files changed, 385 insertions(+), 189 deletions(-) create mode 100644 cmake/FindLibEV.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 88a01c8..ca80edf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/README b/README index 2ee09f8..c1956aa 100644 --- a/README +++ b/README @@ -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 diff --git a/autistdraw.c b/autistdraw.c index 41a2942..ce07540 100644 --- a/autistdraw.c +++ b/autistdraw.c @@ -19,15 +19,37 @@ */ #include -#include +#include #include #include +#include #include #include +#include +#include +#include #include -#include +#include +#include +#include +#include + +#ifndef NI_MAXHOST +#define NI_MAXHOST 1025 +#endif // ! NI_MAXHOST + +#ifndef NI_MAXSERV +#define NI_MAXSERV 32 +#endif // ! NI_MAXSERV + +#include +#ifndef TIOCGWINSZ +#include +#endif // ! TIOCGWINSZ + +#include #include #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 write_req_t * -flush_writer (struct msg_writer *writer) +static bool +read_loop (EV_P_ ev_io *watcher, + bool (*cb) (EV_P_ ev_io *, const void *, ssize_t)) { - 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); - return req; + 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 void -on_data_written_to_client (uv_write_t *req, int status) +static bool +flush_queue (write_queue_t *queue, ev_io *watcher) { - write_req_t *wr = (write_req_t *) req; - app_context_t *app = req->handle->loop->data; - client_t *client = req->data; + 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; + } - if (status) - // Write failed - remove_client (app, client); + write_queue_processed (queue, written); - free (wr->buf.base); - free (wr); +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 void -flush_writer_to_client (struct msg_writer *writer, client_t *client) +static write_req_t * +flush_writer (struct msg_writer *writer) { - 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? + write_req_t *req = xcalloc (1, sizeof *req); + req->data.iov_base = msg_writer_flush (writer, &req->data.iov_len); + return req; } static void -on_data_written_to_server (uv_write_t *req, int status) +flush_writer_to_client (struct msg_writer *writer, client_t *client) { - 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; - - client->handle.data = client; - msg_reader_init (&client->msg_reader); - LIST_PREPEND (app->clients, client); - 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; + } -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; - - uv_signal_init (loop, &app.winch_watcher); - uv_signal_start (&app.winch_watcher, on_winch, SIGWINCH); + ev_set_userdata (loop, &app); - uv_tty_init (loop, &app.tty, STDOUT_FILENO, false); + ev_signal_init (&app.winch_watcher, on_winch, SIGWINCH); + ev_signal_start (EV_DEFAULT_ &app.winch_watcher); - uv_poll_init (loop, &app.tty_watcher, STDIN_FILENO); - uv_poll_start (&app.tty_watcher, UV_READABLE, on_tty_readable); + ev_io_init (&app.tty_watcher, on_tty_readable, STDIN_FILENO, EV_READ); + ev_io_start (EV_DEFAULT_ &app.tty_watcher); - 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; } diff --git a/cmake/FindLibEV.cmake b/cmake/FindLibEV.cmake new file mode 100644 index 0000000..cdc67dc --- /dev/null +++ b/cmake/FindLibEV.cmake @@ -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) + diff --git a/utils.c b/utils.c index df16559..23b95a4 100644 --- a/utils.c +++ b/utils.c @@ -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 -- cgit v1.2.3-70-g09d2