diff options
Diffstat (limited to 'autistdraw.c')
-rw-r--r-- | autistdraw.c | 419 |
1 files changed, 387 insertions, 32 deletions
diff --git a/autistdraw.c b/autistdraw.c index 3e7b6c4..f6dd2ec 100644 --- a/autistdraw.c +++ b/autistdraw.c @@ -34,10 +34,21 @@ #include "config.h" #include "utils.c" -#define PALETTE_WIDTH 9 ///< Width of the palette -#define TOP_BAR_CUTOFF 3 ///< Height of the top bar +#define PALETTE_WIDTH 9 ///< Width of the palette +#define TOP_BAR_CUTOFF 3 ///< Height of the top bar -#define BITMAP_BLOCK_SIZE 50 ///< Step for extending bitmap size +#define BITMAP_BLOCK_SIZE 50 ///< Step for extending bitmap size + +#define PROTOCOL_VERSION 1 ///< Network protocol version + +enum +{ + MESSAGE_HELLO, ///< Server/client hello + MESSAGE_GET_BITMAP, ///< Request bitmap data + MESSAGE_PUT_POINT, ///< Request to place a point + + MESSAGE_COUNT ///< Total number of messages +}; typedef enum network_mode network_mode_t; enum network_mode @@ -47,12 +58,20 @@ 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 + struct msg_reader msg_reader; ///< Client message reader }; typedef struct app_context app_context_t; @@ -70,6 +89,7 @@ struct app_context // Client: uv_tcp_t server_fd; ///< Connection to the server + struct msg_reader msg_reader; ///< Server message reader // Server: uv_tcp_t listen_fd; ///< Listening FD @@ -98,20 +118,14 @@ struct app_context uint8_t current_color_right; ///< Right mouse button color }; +static void remove_client (app_context_t *app, client_t *client); +static void on_server_disconnected (app_context_t *app); + static void app_init (app_context_t *self) { memset (self, 0, sizeof *self); -} - -static void -remove_client (app_context_t *app, client_t *client) -{ - // TODO: cancel any write requests? - // XXX: should we unref it? - uv_close ((uv_handle_t *) &client->handle, NULL); - LIST_UNLINK (app->clients, client); - free (client); + msg_reader_init (&self->msg_reader); } static void @@ -120,12 +134,151 @@ app_free (app_context_t *self) if (self->tk) termo_destroy (self->tk); while (self->clients) + // XXX: we probably shouldn't do this from here remove_client (self, self->clients); free (self->bitmap); + msg_reader_free (&self->msg_reader); +} + +// --- Server-client messaging ------------------------------------------------- + +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); + 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); +} + +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? +} + +static void +send_draw_point_response (client_t *client, int x, int y, uint8_t color) +{ + struct msg_writer writer; + msg_writer_init (&writer); + msg_writer_u8 (&writer, MESSAGE_PUT_POINT); + msg_writer_i32 (&writer, x); + msg_writer_i32 (&writer, y); + msg_writer_u8 (&writer, color); + + flush_writer_to_client (&writer, client); +} + +static void +send_draw_point_request (app_context_t *app, int x, int y, uint8_t color) +{ + struct msg_writer writer; + msg_writer_init (&writer); + msg_writer_u8 (&writer, MESSAGE_PUT_POINT); + msg_writer_i32 (&writer, x); + msg_writer_i32 (&writer, y); + msg_writer_u8 (&writer, color); + + flush_writer_to_server (&writer, app); +} + +static void +send_hello_request (app_context_t *app) +{ + struct msg_writer writer; + msg_writer_init (&writer); + msg_writer_u8 (&writer, MESSAGE_HELLO); + msg_writer_u8 (&writer, PROTOCOL_VERSION); + + flush_writer_to_server (&writer, app); +} + +static void +send_hello_response (client_t *client) +{ + struct msg_writer writer; + msg_writer_init (&writer); + msg_writer_u8 (&writer, MESSAGE_HELLO); + msg_writer_u8 (&writer, PROTOCOL_VERSION); + + flush_writer_to_client (&writer, client); +} + +static void +send_get_bitmap_request (app_context_t *app) +{ + struct msg_writer writer; + msg_writer_init (&writer); + msg_writer_u8 (&writer, MESSAGE_GET_BITMAP); + + flush_writer_to_server (&writer, app); +} + +static void +send_get_bitmap_response (client_t *client, app_context_t *app) +{ + struct msg_writer writer; + msg_writer_init (&writer); + msg_writer_u8 (&writer, MESSAGE_GET_BITMAP); + msg_writer_i32 (&writer, app->bitmap_x); + msg_writer_i32 (&writer, app->bitmap_y); + msg_writer_u64 (&writer, app->bitmap_w); + msg_writer_u64 (&writer, app->bitmap_h); + + msg_writer_blob (&writer, app->bitmap, + app->bitmap_w * app->bitmap_h * sizeof *app->bitmap); + + flush_writer_to_client (&writer, client); +} + +// --- Server-client messaging ------------------------------------------------- + +static void display (const char *format, ...) { va_list ap; @@ -306,6 +459,11 @@ draw_point (app_context_t *app, int x, int y, uint8_t color) addch (app->palette[color]); refresh (); } + + // Broadcast the clients about the event + if (app->mode == NETWORK_MODE_SERVER) + for (client_t *iter = app->clients; iter; iter = iter->next) + send_draw_point_response (iter, x, y, color); } // --- Exports ----------------------------------------------------------------- @@ -538,7 +696,8 @@ on_key (app_context_t *app, termo_key_t *key) if (event != TERMO_MOUSE_PRESS && event != TERMO_MOUSE_DRAG) return true; - if (button == 2) + // Middle mouse button, or Ctrl + left mouse button, moves the canvas + if (button == 2 || (button == 1 && key->modifiers == TERMO_KEYMOD_CTRL)) { if (event == TERMO_MOUSE_DRAG) { @@ -568,7 +727,12 @@ on_key (app_context_t *app, termo_key_t *key) int canvas_y = app->corner_y + screen_y - TOP_BAR_CUTOFF; if (screen_y >= TOP_BAR_CUTOFF) - draw_point (app, canvas_x, canvas_y, *color); + { + if (app->mode == NETWORK_MODE_CLIENT) + send_draw_point_request (app, canvas_x, canvas_y, *color); + else + draw_point (app, canvas_x, canvas_y, *color); + } else if (screen_y > 0 && event != TERMO_MOUSE_DRAG) { int pair = (float) screen_x / COLS * PALETTE_WIDTH; @@ -650,44 +814,231 @@ app_uv_allocator (uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) buf->len = sizeof app->read_buf; } +// --- Client-specific stuff --------------------------------------------------- + +typedef void (*server_handler_fn) (app_context_t *, struct msg_unpacker *); + static void -on_server_data (uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) +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); + + display ("Disconnected!"); + beep (); // Beep beep! Made a boo-boo. + + // Let the user save the picture at least. + // Also prevents us from trying to use the dead server handle. + app->mode = NETWORK_MODE_STANDALONE; +} + +static void +on_server_hello (app_context_t *app, struct msg_unpacker *unpacker) +{ + (void) app; + + uint8_t version; + if (!msg_unpacker_u8 (unpacker, &version)) + return; + + if (version != PROTOCOL_VERSION) + // XXX: possibly incompatible version, disconnect? + return; +} + +static void +on_server_get_bitmap (app_context_t *app, struct msg_unpacker *unpacker) +{ + int32_t x, y; + uint64_t w, h; + if (!msg_unpacker_i32 (unpacker, &x) + || !msg_unpacker_i32 (unpacker, &y) + || !msg_unpacker_u64 (unpacker, &w) + || !msg_unpacker_u64 (unpacker, &h)) + return; + + // TODO: employ at least some RLE encoding + size_t size = w * h * sizeof *app->bitmap; + if ((h && w > SIZE_MAX / h) || w > SIZE_MAX || h > SIZE_MAX) + return; + + const void *data; + if (!msg_unpacker_blob (unpacker, &data, size)) + return; + + free (app->bitmap); + app->bitmap = memcpy (xcalloc (1, size), data, size); + app->bitmap_x = x; + app->bitmap_y = y; + app->bitmap_w = w; + app->bitmap_h = h; + + redraw_canvas (app); +} + +static void +on_server_put_point (app_context_t *app, struct msg_unpacker *unpacker) { - (void) buf; + int32_t x, y; + uint8_t color; + + if (!msg_unpacker_i32 (unpacker, &x) + || !msg_unpacker_i32 (unpacker, &y) + || !msg_unpacker_u8 (unpacker, &color)) + return; + draw_point (app, x, y, color); +} + +static void +on_server_data (uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) +{ app_context_t *app = stream->loop->data; if (nread == UV_EOF || nread < 0) { - // TODO: cancel any write requests? - // XXX: should we unref it? - uv_close ((uv_handle_t *) &app->server_fd, NULL); + on_server_disconnected (app); + return; + } - display ("Disconnected!"); - beep (); // Beep beep! Made a boo-boo. + msg_reader_feed (&app->msg_reader, buf->base, nread); - // Let the user save the picture at least. - // Also prevents us from trying to use the dead server handle. - app->mode = NETWORK_MODE_STANDALONE; - return; + static const server_handler_fn handlers[MESSAGE_COUNT] = + { + [MESSAGE_HELLO] = on_server_hello, + [MESSAGE_GET_BITMAP] = on_server_get_bitmap, + [MESSAGE_PUT_POINT] = on_server_put_point, + }; + + void *msg; + size_t len; + while ((msg = msg_reader_get (&app->msg_reader, &len))) + { + struct msg_unpacker unpacker; + msg_unpacker_init (&unpacker, msg, len); + + uint8_t type; + if (!msg_unpacker_u8 (&unpacker, &type) + || type >= MESSAGE_COUNT) + // XXX: unknown message, disconnect? + continue; + + server_handler_fn handler = handlers[type]; + if (!handler) + // XXX: unknown message, disconnect? + continue; + + handler (app, &unpacker); + + if (msg_unpacker_get_available (&unpacker) > 0) + // XXX: overlong message, disconnect? + continue; } +} + +// --- Server-specific stuff --------------------------------------------------- + +typedef bool (*client_handler_fn) + (app_context_t *, client_t *, struct msg_unpacker *); + +static void +remove_client (app_context_t *app, client_t *client) +{ + // TODO: cancel any write requests? + // XXX: should we unref it? + uv_close ((uv_handle_t *) &client->handle, NULL); + LIST_UNLINK (app->clients, client); + msg_reader_free (&client->msg_reader); + free (client); +} + +static bool +on_client_hello (app_context_t *app, client_t *client, + struct msg_unpacker *unpacker) +{ + (void) app; - // TODO: process the data + uint8_t version; + if (!msg_unpacker_u8 (unpacker, &version) + || version != PROTOCOL_VERSION) + // Nope, I don't like you + return false; + + send_hello_response (client); + return true; +} + +static bool +on_client_get_bitmap (app_context_t *app, client_t *client, + struct msg_unpacker *unpacker) +{ + (void) unpacker; + + send_get_bitmap_response (client, app); + return true; +} + +static bool +on_client_put_point (app_context_t *app, client_t *client, + struct msg_unpacker *unpacker) +{ + (void) client; + + int32_t x, y; + uint8_t color; + if (!msg_unpacker_i32 (unpacker, &x) + || !msg_unpacker_i32 (unpacker, &y) + || !msg_unpacker_u8 (unpacker, &color)) + return false; + + // The function takes care of broadcasting to all the other clients, + // as well as back to the original sender + draw_point (app, x, y, color); + return true; } static void on_client_data (uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { - (void) buf; - app_context_t *app = stream->loop->data; client_t *client = stream->data; if (nread == UV_EOF || nread < 0) + goto disconnect; // Connection closed or error + + msg_reader_feed (&client->msg_reader, buf->base, nread); + + static const client_handler_fn handlers[MESSAGE_COUNT] = { - remove_client (app, client); - return; + [MESSAGE_HELLO] = on_client_hello, + [MESSAGE_GET_BITMAP] = on_client_get_bitmap, + [MESSAGE_PUT_POINT] = on_client_put_point, + }; + + void *msg; + size_t len; + while ((msg = msg_reader_get (&client->msg_reader, &len))) + { + struct msg_unpacker unpacker; + msg_unpacker_init (&unpacker, msg, len); + + uint8_t type; + if (!msg_unpacker_u8 (&unpacker, &type)) + goto disconnect; // Invalid message + if (type >= MESSAGE_COUNT) + goto disconnect; // Unknown message + + client_handler_fn handler = handlers[type]; + if (!handler) + goto disconnect; // Unknown message + if (!handler (app, client, &unpacker)) + goto disconnect; // Invalid message + if (msg_unpacker_get_available (&unpacker) > 0) + goto disconnect; // Overlong message data } + return; - // TODO: process the data +disconnect: + remove_client (app, client); } static void @@ -708,6 +1059,7 @@ on_new_client (uv_stream_t *server, int status) goto free_handle; client->handle.data = client; + msg_reader_init (&client->msg_reader); LIST_PREPEND (app->clients, client); return; @@ -907,6 +1259,9 @@ initialize_client (app_context_t *app, struct addrinfo *address) "error", "initialization failed", uv_strerror (err)); exit (EXIT_FAILURE); } + + send_hello_request (app); + send_get_bitmap_request (app); } static void |