aboutsummaryrefslogtreecommitdiff
path: root/autistdraw.c
diff options
context:
space:
mode:
Diffstat (limited to 'autistdraw.c')
-rw-r--r--autistdraw.c419
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