2014-10-24 22:58:18 +02:00
|
|
|
/*
|
2014-10-26 18:55:25 +01:00
|
|
|
* autistdraw.c: terminal drawing for NEET autists^Wartists
|
2014-10-24 22:58:18 +02:00
|
|
|
*
|
|
|
|
* Copyright (c) 2014, Přemysl Janouch <p.janouch@gmail.com>
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
|
|
* copyright notice and this permission notice appear in all copies.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
|
|
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
|
|
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
|
|
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
*
|
|
|
|
*/
|
2014-10-22 23:58:25 +02:00
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <locale.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include <signal.h>
|
|
|
|
|
2014-10-24 22:59:24 +02:00
|
|
|
#include <uv.h>
|
2014-10-22 23:58:25 +02:00
|
|
|
#include <curses.h>
|
|
|
|
#include "termo.h"
|
|
|
|
|
|
|
|
#include "config.h"
|
2014-10-25 00:46:26 +02:00
|
|
|
#include "utils.c"
|
2014-10-22 23:58:25 +02:00
|
|
|
|
2014-10-27 13:54:53 +01:00
|
|
|
#define PALETTE_WIDTH 9 ///< Width of the palette
|
|
|
|
#define TOP_BAR_CUTOFF 3 ///< Height of the top bar
|
2014-10-23 23:23:09 +02:00
|
|
|
|
2014-10-27 13:54:53 +01:00
|
|
|
#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
|
|
|
|
};
|
2014-10-23 09:22:17 +02:00
|
|
|
|
2014-10-26 18:57:28 +01:00
|
|
|
typedef enum network_mode network_mode_t;
|
|
|
|
enum network_mode
|
|
|
|
{
|
|
|
|
NETWORK_MODE_STANDALONE, ///< No networking taking place
|
|
|
|
NETWORK_MODE_SERVER, ///< We're the server
|
|
|
|
NETWORK_MODE_CLIENT ///< We're a client
|
|
|
|
};
|
|
|
|
|
2014-10-27 13:54:53 +01:00
|
|
|
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
|
|
|
|
};
|
|
|
|
|
2014-10-26 18:57:28 +01:00
|
|
|
typedef struct client client_t;
|
|
|
|
struct client
|
|
|
|
{
|
|
|
|
LIST_HEADER (client_t)
|
|
|
|
|
|
|
|
uv_tcp_t handle; ///< TCP connection handle
|
2014-10-27 13:54:53 +01:00
|
|
|
struct msg_reader msg_reader; ///< Client message reader
|
2014-10-26 18:57:28 +01:00
|
|
|
};
|
|
|
|
|
2014-10-27 16:48:06 +01:00
|
|
|
#define BITMAP_PIXEL(app, x, y) (app)->bitmap[(y) * (app)->bitmap_w + (x)]
|
|
|
|
|
2014-10-23 09:22:17 +02:00
|
|
|
typedef struct app_context app_context_t;
|
|
|
|
struct app_context
|
2014-10-22 23:58:25 +02:00
|
|
|
{
|
2014-10-23 09:22:17 +02:00
|
|
|
termo_t *tk; ///< Termo instance
|
|
|
|
|
2014-10-24 22:59:24 +02:00
|
|
|
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
|
|
|
|
|
2014-10-26 18:57:28 +01:00
|
|
|
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
|
2014-10-27 13:54:53 +01:00
|
|
|
struct msg_reader msg_reader; ///< Server message reader
|
2014-10-26 18:57:28 +01:00
|
|
|
|
|
|
|
// Server:
|
|
|
|
uv_tcp_t listen_fd; ///< Listening FD
|
|
|
|
client_t *clients; ///< Client connections
|
|
|
|
|
2014-10-23 09:22:17 +02:00
|
|
|
chtype palette[2 * 9]; ///< Attribute palette
|
2014-10-22 23:58:25 +02:00
|
|
|
|
2014-10-23 23:23:09 +02:00
|
|
|
uint8_t *bitmap; ///< Canvas data for drawing
|
|
|
|
int bitmap_x; ///< X coord. of left top bitmap corner
|
|
|
|
int bitmap_y; ///< Y coord. of left top bitmap corner
|
|
|
|
size_t bitmap_w; ///< Canvas data width
|
|
|
|
size_t bitmap_h; ///< Canvas data height
|
|
|
|
|
|
|
|
int center_x; ///< X coordinate at center
|
|
|
|
int center_y; ///< Y coordinate at center
|
|
|
|
|
|
|
|
// These two are computed from `center_x' and `center_y':
|
|
|
|
|
|
|
|
int corner_x; ///< X coordinate of LT screen corner
|
|
|
|
int corner_y; ///< Y coordinate of LT screen corner
|
|
|
|
|
|
|
|
int move_saved_x; ///< Saved X coord. for moving
|
|
|
|
int move_saved_y; ///< Saved Y coord. for moving
|
|
|
|
|
2014-10-23 09:22:17 +02:00
|
|
|
uint8_t current_color_left; ///< Left mouse button color
|
|
|
|
uint8_t current_color_right; ///< Right mouse button color
|
2014-10-22 23:58:25 +02:00
|
|
|
};
|
|
|
|
|
2014-10-27 13:54:53 +01:00
|
|
|
static void remove_client (app_context_t *app, client_t *client);
|
|
|
|
static void on_server_disconnected (app_context_t *app);
|
|
|
|
|
2014-10-23 09:22:17 +02:00
|
|
|
static void
|
|
|
|
app_init (app_context_t *self)
|
|
|
|
{
|
|
|
|
memset (self, 0, sizeof *self);
|
2014-10-27 13:54:53 +01:00
|
|
|
msg_reader_init (&self->msg_reader);
|
2014-10-26 18:57:28 +01:00
|
|
|
}
|
|
|
|
|
2014-10-24 22:59:24 +02:00
|
|
|
static void
|
|
|
|
app_free (app_context_t *self)
|
|
|
|
{
|
|
|
|
if (self->tk)
|
|
|
|
termo_destroy (self->tk);
|
2014-10-26 18:57:28 +01:00
|
|
|
while (self->clients)
|
2014-10-27 13:54:53 +01:00
|
|
|
// XXX: we probably shouldn't do this from here
|
2014-10-26 18:57:28 +01:00
|
|
|
remove_client (self, self->clients);
|
2014-10-24 22:59:24 +02:00
|
|
|
|
|
|
|
free (self->bitmap);
|
2014-10-27 13:54:53 +01:00
|
|
|
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?
|
2014-10-24 22:59:24 +02:00
|
|
|
}
|
2014-10-22 23:58:25 +02:00
|
|
|
|
2014-10-27 13:54:53 +01:00
|
|
|
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 -------------------------------------------------
|
|
|
|
|
2014-10-22 23:58:25 +02:00
|
|
|
static void
|
|
|
|
display (const char *format, ...)
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
mvwhline (stdscr, 0, 0, A_REVERSE, COLS);
|
|
|
|
attron (A_REVERSE);
|
|
|
|
|
|
|
|
va_start (ap, format);
|
|
|
|
vw_printw (stdscr, format, ap);
|
|
|
|
va_end (ap);
|
|
|
|
|
|
|
|
attroff (A_REVERSE);
|
|
|
|
refresh ();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2014-10-23 09:22:17 +02:00
|
|
|
init_palette (app_context_t *app)
|
2014-10-22 23:58:25 +02:00
|
|
|
{
|
|
|
|
start_color ();
|
|
|
|
|
|
|
|
// Also does init_pair (0, -1, -1);
|
|
|
|
use_default_colors ();
|
|
|
|
// Duplicate it for convenience.
|
|
|
|
init_pair (9, -1, -1);
|
|
|
|
|
|
|
|
// Add the basic 8 colors to the default pair. Once normally, once
|
|
|
|
// inverted to workaround VTE's inability to set a bright background.
|
|
|
|
for (int i = 0; i < 8; i++)
|
|
|
|
{
|
|
|
|
init_pair (1 + i, COLOR_WHITE, COLOR_BLACK + i);
|
|
|
|
init_pair (10 + i, COLOR_BLACK + i, COLOR_WHITE);
|
|
|
|
}
|
|
|
|
|
2014-10-23 09:22:17 +02:00
|
|
|
// Initialize the palette of characters with attributes
|
|
|
|
for (int i = 0; i < PALETTE_WIDTH; i++)
|
|
|
|
{
|
|
|
|
app->palette[i] = ' ' | COLOR_PAIR (i);
|
|
|
|
app->palette[i + 9] = ' ' | COLOR_PAIR (i + 9) | A_REVERSE | A_BOLD;
|
|
|
|
}
|
|
|
|
|
2014-10-22 23:58:25 +02:00
|
|
|
// This usually creates a solid black or white.
|
2014-10-23 09:22:17 +02:00
|
|
|
app->current_color_left = app->current_color_right = 9;
|
2014-10-22 23:58:25 +02:00
|
|
|
}
|
|
|
|
|
2014-10-23 23:23:09 +02:00
|
|
|
static void
|
|
|
|
update_canvas_for_screen (app_context_t *app)
|
|
|
|
{
|
|
|
|
app->corner_x = app->center_x - COLS / 2;
|
|
|
|
app->corner_y = app->center_y - (LINES - TOP_BAR_CUTOFF) / 2;
|
|
|
|
}
|
|
|
|
|
2014-10-22 23:58:25 +02:00
|
|
|
static void
|
2014-10-23 09:22:17 +02:00
|
|
|
redraw (app_context_t *app)
|
2014-10-22 23:58:25 +02:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
mvwhline (stdscr, 1, 0, A_REVERSE, COLS);
|
|
|
|
mvwhline (stdscr, 2, 0, A_REVERSE, COLS);
|
|
|
|
|
|
|
|
for (i = 0; i < COLS; i++)
|
|
|
|
{
|
2014-10-23 09:22:17 +02:00
|
|
|
int pair = (float) i / COLS * PALETTE_WIDTH;
|
|
|
|
mvaddch (1, i, app->palette[pair]);
|
|
|
|
mvaddch (2, i, app->palette[pair + PALETTE_WIDTH]);
|
2014-10-22 23:58:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
display ("Choose a color from the palette and draw. "
|
|
|
|
"Press Escape or ^C to quit.");
|
|
|
|
refresh ();
|
|
|
|
}
|
|
|
|
|
2014-10-23 23:23:09 +02:00
|
|
|
static bool
|
|
|
|
is_in_bitmap_data (app_context_t *app, int x, int y)
|
|
|
|
{
|
|
|
|
return x >= app->bitmap_x
|
|
|
|
&& y >= app->bitmap_y
|
|
|
|
&& x < app->bitmap_x + (int) app->bitmap_w
|
|
|
|
&& y < app->bitmap_y + (int) app->bitmap_h;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
redraw_canvas (app_context_t *app)
|
|
|
|
{
|
|
|
|
int y = app->corner_y;
|
|
|
|
for (int screen_y = TOP_BAR_CUTOFF; screen_y < LINES; screen_y++, y++)
|
|
|
|
{
|
|
|
|
move (screen_y, 0);
|
|
|
|
|
|
|
|
int x = app->corner_x;
|
|
|
|
for (int screen_x = 0; screen_x < COLS; screen_x++, x++)
|
|
|
|
{
|
2014-10-27 16:48:06 +01:00
|
|
|
uint8_t color = 0;
|
|
|
|
if (is_in_bitmap_data (app, x, y))
|
|
|
|
color = BITMAP_PIXEL (app,
|
|
|
|
x - app->bitmap_x, y - app->bitmap_y);
|
2014-10-23 23:23:09 +02:00
|
|
|
|
|
|
|
addch (app->palette[color]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
refresh ();
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
is_visible (app_context_t *app, int x, int y)
|
|
|
|
{
|
|
|
|
return x >= app->corner_x
|
|
|
|
&& y >= app->corner_y
|
|
|
|
&& x < app->corner_x + COLS
|
|
|
|
&& y < app->corner_y + LINES - TOP_BAR_CUTOFF;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
make_place_for_point (app_context_t *app, int x, int y)
|
|
|
|
{
|
|
|
|
if (is_in_bitmap_data (app, x, y))
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Make sure the point has some place to go
|
|
|
|
int new_bitmap_x = app->bitmap_x;
|
|
|
|
int new_bitmap_y = app->bitmap_y;
|
|
|
|
|
|
|
|
while (new_bitmap_x > x)
|
|
|
|
new_bitmap_x -= BITMAP_BLOCK_SIZE;
|
|
|
|
while (new_bitmap_y > y)
|
|
|
|
new_bitmap_y -= BITMAP_BLOCK_SIZE;
|
|
|
|
|
|
|
|
int new_bitmap_w = app->bitmap_w + (app->bitmap_x - new_bitmap_x);
|
|
|
|
int new_bitmap_h = app->bitmap_h + (app->bitmap_y - new_bitmap_y);
|
|
|
|
|
|
|
|
while (new_bitmap_x + new_bitmap_w <= x)
|
|
|
|
new_bitmap_w += BITMAP_BLOCK_SIZE;
|
|
|
|
while (new_bitmap_y + new_bitmap_h <= y)
|
|
|
|
new_bitmap_h += BITMAP_BLOCK_SIZE;
|
|
|
|
|
2014-10-25 00:46:26 +02:00
|
|
|
uint8_t *new_bitmap = xcalloc (new_bitmap_w * new_bitmap_h,
|
2014-10-23 23:23:09 +02:00
|
|
|
sizeof *new_bitmap);
|
|
|
|
if (app->bitmap)
|
|
|
|
{
|
|
|
|
// Copy data, assuming that the area can only get larger
|
|
|
|
for (size_t data_y = 0; data_y < app->bitmap_h; data_y++)
|
|
|
|
memcpy (new_bitmap
|
|
|
|
+ ((data_y + app->bitmap_y - new_bitmap_y) * new_bitmap_w)
|
|
|
|
+ (app->bitmap_x - new_bitmap_x),
|
|
|
|
app->bitmap + (data_y * app->bitmap_w),
|
|
|
|
app->bitmap_w * sizeof *new_bitmap);
|
|
|
|
|
|
|
|
free (app->bitmap);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Replace the bitmap with the reallocated version
|
|
|
|
app->bitmap_x = new_bitmap_x;
|
|
|
|
app->bitmap_y = new_bitmap_y;
|
|
|
|
app->bitmap_w = new_bitmap_w;
|
|
|
|
app->bitmap_h = new_bitmap_h;
|
|
|
|
app->bitmap = new_bitmap;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
draw_point (app_context_t *app, int x, int y, uint8_t color)
|
|
|
|
{
|
|
|
|
make_place_for_point (app, x, y);
|
2014-10-27 16:48:06 +01:00
|
|
|
BITMAP_PIXEL (app, x - app->bitmap_x, y - app->bitmap_y) = color;
|
2014-10-23 23:23:09 +02:00
|
|
|
|
|
|
|
if (is_visible (app, x, y))
|
|
|
|
{
|
|
|
|
int screen_x = x - app->corner_x;
|
|
|
|
int screen_y = y - app->corner_y + TOP_BAR_CUTOFF;
|
|
|
|
|
|
|
|
move (screen_y, screen_x);
|
|
|
|
addch (app->palette[color]);
|
|
|
|
refresh ();
|
|
|
|
}
|
2014-10-27 13:54:53 +01:00
|
|
|
|
|
|
|
// 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);
|
2014-10-23 23:23:09 +02:00
|
|
|
}
|
|
|
|
|
2014-10-24 08:27:13 +02:00
|
|
|
// --- Exports -----------------------------------------------------------------
|
|
|
|
|
|
|
|
static bool
|
|
|
|
is_data_row_empty (app_context_t *app, int y)
|
|
|
|
{
|
|
|
|
for (size_t x = 0; x < app->bitmap_w; x++)
|
|
|
|
if (app->bitmap[y * app->bitmap_w + x])
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
is_data_column_empty (app_context_t *app, int x)
|
|
|
|
{
|
|
|
|
for (size_t y = 0; y < app->bitmap_h; y++)
|
|
|
|
if (app->bitmap[y * app->bitmap_w + x])
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
find_data_bounding_rect (app_context_t *app,
|
|
|
|
size_t *x, size_t *y, size_t *w, size_t *h)
|
|
|
|
{
|
|
|
|
size_t my_x = 0, my_y = 0;
|
|
|
|
size_t my_w = app->bitmap_w, my_h = app->bitmap_h;
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
while (i < app->bitmap_h && is_data_row_empty (app, i++))
|
|
|
|
my_y++;
|
|
|
|
|
|
|
|
// Special case: the whole canvas is empty
|
|
|
|
if (my_y == my_h)
|
|
|
|
{
|
|
|
|
my_x = my_w;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
i = app->bitmap_h;
|
|
|
|
while (i-- && is_data_row_empty (app, i))
|
|
|
|
my_h--;
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
while (i < app->bitmap_w && is_data_column_empty (app, i++))
|
|
|
|
my_x++;
|
|
|
|
|
|
|
|
i = app->bitmap_w;
|
|
|
|
while (i-- && is_data_column_empty (app, i))
|
|
|
|
my_w--;
|
|
|
|
|
|
|
|
end:
|
|
|
|
*x = my_x;
|
|
|
|
*y = my_y;
|
|
|
|
*w = my_w - my_x;
|
|
|
|
*h = my_h - my_y;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *
|
|
|
|
color_to_ansi (uint8_t color)
|
|
|
|
{
|
|
|
|
static const char *table[2 * PALETTE_WIDTH] =
|
|
|
|
{
|
|
|
|
"\033[0m",
|
|
|
|
"\033[0;40m",
|
|
|
|
"\033[0;41m",
|
|
|
|
"\033[0;42m",
|
|
|
|
"\033[0;43m",
|
|
|
|
"\033[0;44m",
|
|
|
|
"\033[0;45m",
|
|
|
|
"\033[0;46m",
|
|
|
|
"\033[0;47m",
|
|
|
|
"\033[0;1;7m",
|
|
|
|
"\033[0;1;7;30m",
|
|
|
|
"\033[0;1;7;31m",
|
|
|
|
"\033[0;1;7;32m",
|
|
|
|
"\033[0;1;7;33m",
|
|
|
|
"\033[0;1;7;34m",
|
|
|
|
"\033[0;1;7;35m",
|
|
|
|
"\033[0;1;7;36m",
|
|
|
|
"\033[0;1;7;37m",
|
|
|
|
};
|
|
|
|
|
|
|
|
if (color > sizeof table / sizeof table[0])
|
|
|
|
return NULL;
|
|
|
|
return table[color];
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
export_ansi (app_context_t *app)
|
|
|
|
{
|
|
|
|
FILE *fp = fopen ("export-ansi.asc", "wb");
|
|
|
|
if (!fp)
|
|
|
|
{
|
|
|
|
display ("Error opening file for writing.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t x, y, w, h;
|
|
|
|
find_data_bounding_rect (app, &x, &y, &w, &h);
|
|
|
|
|
|
|
|
for (size_t row = 0; row < h; row++)
|
|
|
|
{
|
|
|
|
const char *color = NULL;
|
|
|
|
for (size_t column = 0; column < w; column++)
|
|
|
|
{
|
2014-10-27 16:48:06 +01:00
|
|
|
const char *new_color = color_to_ansi
|
|
|
|
(BITMAP_PIXEL (app, x + column, y + row));
|
2014-10-24 08:27:13 +02:00
|
|
|
if (color != new_color)
|
|
|
|
fputs (new_color, fp);
|
|
|
|
color = new_color;
|
|
|
|
fputc (' ', fp);
|
|
|
|
}
|
2014-10-24 23:01:22 +02:00
|
|
|
|
|
|
|
// We need to reset the attributes
|
|
|
|
fputs (color_to_ansi (0), fp);
|
2014-10-24 08:27:13 +02:00
|
|
|
fputc ('\n', fp);
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose (fp);
|
|
|
|
}
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
MIRC_NONE = -1,
|
|
|
|
|
|
|
|
MIRC_WHITE = 0,
|
|
|
|
MIRC_BLACK = 1,
|
|
|
|
MIRC_BLUE = 2,
|
|
|
|
MIRC_GREEN = 3,
|
|
|
|
MIRC_L_RED = 4,
|
|
|
|
MIRC_RED = 5,
|
|
|
|
MIRC_PURPLE = 6,
|
|
|
|
MIRC_ORANGE = 7,
|
|
|
|
MIRC_YELLOW = 8,
|
|
|
|
MIRC_L_GREEN = 9,
|
|
|
|
MIRC_CYAN = 10,
|
|
|
|
MIRC_L_CYAN = 11,
|
|
|
|
MIRC_L_BLUE = 12,
|
|
|
|
MIRC_L_PURPLE = 13,
|
|
|
|
MIRC_GRAY = 14,
|
|
|
|
MIRC_L_GRAY = 15,
|
|
|
|
|
|
|
|
MIRC_TRANSPARENT = 99
|
|
|
|
};
|
|
|
|
|
|
|
|
static int
|
|
|
|
color_to_mirc (uint8_t color)
|
|
|
|
{
|
|
|
|
static const int table[2 * PALETTE_WIDTH] =
|
|
|
|
{
|
|
|
|
// XXX: not sure what to map the default color pair to;
|
|
|
|
// the mIRC code for reverse colours seems to not be well supported
|
|
|
|
MIRC_TRANSPARENT, MIRC_BLACK, MIRC_RED, MIRC_GREEN, MIRC_YELLOW,
|
|
|
|
MIRC_BLUE, MIRC_PURPLE, MIRC_CYAN, MIRC_L_GRAY,
|
|
|
|
MIRC_BLACK, MIRC_GRAY, MIRC_L_RED, MIRC_L_GREEN, MIRC_YELLOW,
|
|
|
|
MIRC_L_BLUE, MIRC_L_PURPLE, MIRC_L_CYAN, MIRC_WHITE
|
|
|
|
};
|
|
|
|
|
|
|
|
if (color > sizeof table / sizeof table[0])
|
|
|
|
return MIRC_NONE;
|
|
|
|
return table[color];
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
export_irc (app_context_t *app)
|
|
|
|
{
|
|
|
|
FILE *fp = fopen ("export-irc.asc", "wb");
|
|
|
|
if (!fp)
|
|
|
|
{
|
|
|
|
display ("Error opening file for writing.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t x, y, w, h;
|
|
|
|
find_data_bounding_rect (app, &x, &y, &w, &h);
|
|
|
|
|
|
|
|
for (size_t row = 0; row < h; row++)
|
|
|
|
{
|
|
|
|
int color = MIRC_NONE;
|
|
|
|
for (size_t column = 0; column < w; column++)
|
|
|
|
{
|
2014-10-27 16:48:06 +01:00
|
|
|
int new_color = color_to_mirc
|
|
|
|
(BITMAP_PIXEL (app, x + column, y + row));
|
2014-10-24 08:27:13 +02:00
|
|
|
if (color != new_color)
|
|
|
|
fprintf (fp, "\x03%02d,%02d", new_color, new_color);
|
|
|
|
color = new_color;
|
|
|
|
fputc ('_', fp);
|
|
|
|
}
|
|
|
|
fputc ('\n', fp);
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose (fp);
|
|
|
|
}
|
|
|
|
|
2014-10-26 18:57:28 +01:00
|
|
|
// --- Event handlers ----------------------------------------------------------
|
2014-10-24 08:27:13 +02:00
|
|
|
|
2014-10-22 23:58:25 +02:00
|
|
|
static bool
|
2014-10-23 09:22:17 +02:00
|
|
|
on_key (app_context_t *app, termo_key_t *key)
|
2014-10-22 23:58:25 +02:00
|
|
|
{
|
2014-10-24 08:27:13 +02:00
|
|
|
if (key->type == TERMO_TYPE_KEYSYM && key->code.sym == TERMO_SYM_ESCAPE)
|
2014-10-22 23:58:25 +02:00
|
|
|
return false;
|
|
|
|
|
|
|
|
if (key->type == TERMO_TYPE_KEY
|
|
|
|
&& (key->modifiers & TERMO_KEYMOD_CTRL)
|
|
|
|
&& (key->code.codepoint == 'C' || key->code.codepoint == 'c'))
|
|
|
|
return false;
|
|
|
|
|
2014-10-24 08:27:13 +02:00
|
|
|
if (key->type == TERMO_TYPE_KEY && key->code.codepoint == 'e')
|
|
|
|
{
|
|
|
|
export_ansi (app);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (key->type == TERMO_TYPE_KEY && key->code.codepoint == 'E')
|
|
|
|
{
|
|
|
|
export_irc (app);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2014-10-22 23:58:25 +02:00
|
|
|
if (key->type != TERMO_TYPE_MOUSE)
|
|
|
|
return true;
|
|
|
|
|
2014-10-23 23:23:09 +02:00
|
|
|
int screen_y, screen_x, button;
|
2014-10-22 23:58:25 +02:00
|
|
|
termo_mouse_event_t event;
|
|
|
|
|
2014-10-23 23:23:09 +02:00
|
|
|
termo_interpret_mouse (app->tk, key, &event, &button, &screen_y, &screen_x);
|
2014-10-22 23:58:25 +02:00
|
|
|
if (event != TERMO_MOUSE_PRESS && event != TERMO_MOUSE_DRAG)
|
|
|
|
return true;
|
|
|
|
|
2014-10-27 13:54:53 +01:00
|
|
|
// Middle mouse button, or Ctrl + left mouse button, moves the canvas
|
|
|
|
if (button == 2 || (button == 1 && key->modifiers == TERMO_KEYMOD_CTRL))
|
2014-10-23 23:23:09 +02:00
|
|
|
{
|
|
|
|
if (event == TERMO_MOUSE_DRAG)
|
|
|
|
{
|
|
|
|
app->corner_x += app->move_saved_x - screen_x;
|
|
|
|
app->corner_y += app->move_saved_y - screen_y;
|
|
|
|
|
|
|
|
app->center_x += app->move_saved_x - screen_x;
|
|
|
|
app->center_y += app->move_saved_y - screen_y;
|
|
|
|
|
|
|
|
redraw_canvas (app);
|
|
|
|
}
|
|
|
|
|
|
|
|
app->move_saved_x = screen_x;
|
|
|
|
app->move_saved_y = screen_y;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2014-10-23 09:22:17 +02:00
|
|
|
uint8_t *color;
|
2014-10-22 23:58:25 +02:00
|
|
|
if (button == 1)
|
2014-10-23 09:22:17 +02:00
|
|
|
color = &app->current_color_left;
|
2014-10-22 23:58:25 +02:00
|
|
|
else if (button == 3)
|
2014-10-23 09:22:17 +02:00
|
|
|
color = &app->current_color_right;
|
2014-10-22 23:58:25 +02:00
|
|
|
else
|
|
|
|
return true;
|
|
|
|
|
2014-10-23 23:23:09 +02:00
|
|
|
int canvas_x = app->corner_x + screen_x;
|
|
|
|
int canvas_y = app->corner_y + screen_y - TOP_BAR_CUTOFF;
|
|
|
|
|
|
|
|
if (screen_y >= TOP_BAR_CUTOFF)
|
2014-10-27 13:54:53 +01:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
2014-10-23 23:23:09 +02:00
|
|
|
else if (screen_y > 0 && event != TERMO_MOUSE_DRAG)
|
2014-10-23 09:22:17 +02:00
|
|
|
{
|
2014-10-23 23:23:09 +02:00
|
|
|
int pair = (float) screen_x / COLS * PALETTE_WIDTH;
|
|
|
|
*color = pair + (screen_y - 1) * PALETTE_WIDTH;
|
2014-10-23 09:22:17 +02:00
|
|
|
}
|
2014-10-22 23:58:25 +02:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2014-10-24 22:59:24 +02:00
|
|
|
on_winch (uv_signal_t *handle, int signum)
|
2014-10-22 23:58:25 +02:00
|
|
|
{
|
2014-10-24 22:59:24 +02:00
|
|
|
app_context_t *app = handle->loop->data;
|
2014-10-22 23:58:25 +02:00
|
|
|
(void) signum;
|
2014-10-24 22:59:24 +02:00
|
|
|
|
|
|
|
#ifdef HAVE_RESIZETERM
|
|
|
|
int w, h;
|
|
|
|
if (!uv_tty_get_winsize (&app->tty, &w, &h))
|
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
#else // ! HAVE_RESIZETERM
|
|
|
|
endwin ();
|
|
|
|
refresh ();
|
|
|
|
#endif // ! HAVE_RESIZETERM
|
|
|
|
|
|
|
|
update_canvas_for_screen (app);
|
|
|
|
redraw (app);
|
|
|
|
redraw_canvas (app);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
on_key_timer (uv_timer_t *handle)
|
|
|
|
{
|
|
|
|
app_context_t *app = handle->loop->data;
|
|
|
|
|
|
|
|
termo_key_t key;
|
|
|
|
if (termo_getkey_force (app->tk, &key) == TERMO_RES_KEY)
|
|
|
|
if (!on_key (app, &key))
|
|
|
|
uv_stop (handle->loop);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
on_tty_readable (uv_poll_t *handle, int status, int events)
|
|
|
|
{
|
|
|
|
// Ignoring and hoping for the best
|
|
|
|
(void) status;
|
|
|
|
(void) events;
|
|
|
|
|
|
|
|
app_context_t *app = handle->loop->data;
|
|
|
|
|
|
|
|
uv_timer_stop (&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);
|
|
|
|
|
|
|
|
if (ret == TERMO_RES_AGAIN)
|
|
|
|
uv_timer_start (&app->tty_timer,
|
|
|
|
on_key_timer, termo_get_waittime (app->tk), 0);
|
2014-10-22 23:58:25 +02:00
|
|
|
}
|
|
|
|
|
2014-10-25 00:46:26 +02:00
|
|
|
static void
|
2014-10-26 18:57:28 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2014-10-27 13:54:53 +01:00
|
|
|
// --- Client-specific stuff ---------------------------------------------------
|
|
|
|
|
|
|
|
typedef void (*server_handler_fn) (app_context_t *, struct msg_unpacker *);
|
|
|
|
|
2014-10-26 18:57:28 +01:00
|
|
|
static void
|
2014-10-27 13:54:53 +01:00
|
|
|
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)
|
2014-10-26 18:57:28 +01:00
|
|
|
{
|
2014-10-27 13:54:53 +01:00
|
|
|
int32_t x, y;
|
|
|
|
uint8_t color;
|
|
|
|
|
|
|
|
if (!msg_unpacker_i32 (unpacker, &x)
|
|
|
|
|| !msg_unpacker_i32 (unpacker, &y)
|
|
|
|
|| !msg_unpacker_u8 (unpacker, &color))
|
|
|
|
return;
|
2014-10-26 18:57:28 +01:00
|
|
|
|
2014-10-27 13:54:53 +01:00
|
|
|
draw_point (app, x, y, color);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
on_server_data (uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf)
|
|
|
|
{
|
2014-10-26 18:57:28 +01:00
|
|
|
app_context_t *app = stream->loop->data;
|
|
|
|
if (nread == UV_EOF || nread < 0)
|
|
|
|
{
|
2014-10-27 13:54:53 +01:00
|
|
|
on_server_disconnected (app);
|
|
|
|
return;
|
|
|
|
}
|
2014-10-26 18:57:28 +01:00
|
|
|
|
2014-10-27 13:54:53 +01:00
|
|
|
msg_reader_feed (&app->msg_reader, buf->base, nread);
|
2014-10-26 18:57:28 +01:00
|
|
|
|
2014-10-27 13:54:53 +01:00
|
|
|
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;
|
2014-10-26 18:57:28 +01:00
|
|
|
}
|
2014-10-27 13:54:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// --- 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;
|
2014-10-26 18:57:28 +01:00
|
|
|
|
2014-10-27 13:54:53 +01:00
|
|
|
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;
|
2014-10-26 18:57:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
on_client_data (uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf)
|
|
|
|
{
|
|
|
|
app_context_t *app = stream->loop->data;
|
|
|
|
client_t *client = stream->data;
|
|
|
|
if (nread == UV_EOF || nread < 0)
|
2014-10-27 13:54:53 +01:00
|
|
|
goto disconnect; // Connection closed or error
|
|
|
|
|
|
|
|
msg_reader_feed (&client->msg_reader, buf->base, nread);
|
|
|
|
|
|
|
|
static const client_handler_fn handlers[MESSAGE_COUNT] =
|
2014-10-26 18:57:28 +01:00
|
|
|
{
|
2014-10-27 13:54:53 +01:00
|
|
|
[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
|
2014-10-26 18:57:28 +01:00
|
|
|
}
|
2014-10-27 13:54:53 +01:00
|
|
|
return;
|
2014-10-26 18:57:28 +01:00
|
|
|
|
2014-10-27 13:54:53 +01:00
|
|
|
disconnect:
|
|
|
|
remove_client (app, client);
|
2014-10-26 18:57:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
on_new_client (uv_stream_t *server, int status)
|
|
|
|
{
|
|
|
|
app_context_t *app = server->loop->data;
|
|
|
|
if (status)
|
|
|
|
return;
|
|
|
|
|
|
|
|
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;
|
2014-10-27 13:54:53 +01:00
|
|
|
msg_reader_init (&client->msg_reader);
|
2014-10-26 18:57:28 +01:00
|
|
|
LIST_PREPEND (app->clients, client);
|
|
|
|
return;
|
|
|
|
|
|
|
|
free_handle:
|
|
|
|
uv_close ((uv_handle_t *) &client->handle, NULL);
|
|
|
|
// XXX: should we unref it?
|
|
|
|
free_client:
|
|
|
|
free (client);
|
|
|
|
}
|
|
|
|
|
|
|
|
// --- Program startup ---------------------------------------------------------
|
|
|
|
|
|
|
|
typedef struct app_options app_options_t;
|
|
|
|
struct app_options
|
|
|
|
{
|
|
|
|
struct addrinfo *client_address; ///< Address to connect to
|
|
|
|
struct addrinfo *server_address; ///< Address to listen at
|
|
|
|
};
|
|
|
|
|
|
|
|
static void
|
|
|
|
app_options_init (app_options_t *self)
|
2014-10-25 00:46:26 +02:00
|
|
|
{
|
2014-10-26 18:57:28 +01:00
|
|
|
memset (self, 0, sizeof *self);
|
|
|
|
}
|
2014-10-25 00:46:26 +02:00
|
|
|
|
2014-10-26 18:57:28 +01:00
|
|
|
static void
|
|
|
|
app_options_free (app_options_t *self)
|
|
|
|
{
|
|
|
|
if (self->client_address) freeaddrinfo (self->client_address);
|
|
|
|
if (self->server_address) freeaddrinfo (self->server_address);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct addrinfo *
|
|
|
|
parse_address (const char *address, int flags)
|
|
|
|
{
|
|
|
|
char address_copy[strlen (address) + 1];
|
|
|
|
strcpy (address_copy, address);
|
|
|
|
|
|
|
|
char *colon = strrchr (address_copy, ':');
|
|
|
|
if (!colon)
|
|
|
|
{
|
|
|
|
fprintf (stderr, "error: no port number specified in `%s'\n", address);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
char *host = address_copy, *service = colon + 1;
|
|
|
|
|
|
|
|
if (host == colon)
|
|
|
|
host = NULL;
|
|
|
|
else if (host < colon && *host == '[' && colon[-1] == ']')
|
|
|
|
{
|
|
|
|
// Remove IPv6 RFC 2732-style [] brackets from the host, if present.
|
|
|
|
// This also makes it possible to take the usage string literally. :))
|
|
|
|
host++;
|
|
|
|
colon[-1] = '\0';
|
|
|
|
}
|
|
|
|
else
|
|
|
|
*colon = '\0';
|
|
|
|
|
|
|
|
struct addrinfo *result, hints =
|
|
|
|
{
|
|
|
|
.ai_socktype = SOCK_STREAM,
|
|
|
|
.ai_protocol = IPPROTO_TCP,
|
|
|
|
.ai_flags = flags,
|
|
|
|
};
|
|
|
|
int err = getaddrinfo (host, service, &hints, &result);
|
|
|
|
if (err)
|
|
|
|
{
|
|
|
|
fprintf (stderr, "error: cannot resolve `%s', port `%s': %s\n",
|
|
|
|
host, service, gai_strerror (err));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
parse_program_arguments (app_options_t *options, int argc, char **argv)
|
|
|
|
{
|
2014-10-25 00:46:26 +02:00
|
|
|
static const struct opt opts[] =
|
|
|
|
{
|
|
|
|
{ 'h', "help", NULL, 0, "display this help and exit" },
|
|
|
|
{ 'V', "version", NULL, 0, "output version information and exit" },
|
2014-10-26 18:57:28 +01:00
|
|
|
{ 's', "server", "[ADDRESS]:PORT", 0, "start a server" },
|
|
|
|
{ 'c', "client", "[ADDRESS]:PORT", 0, "connect to a server" },
|
2014-10-25 00:46:26 +02:00
|
|
|
{ 0, NULL, NULL, 0, NULL }
|
|
|
|
};
|
|
|
|
|
|
|
|
struct opt_handler oh;
|
|
|
|
opt_handler_init (&oh, argc, argv, opts,
|
2014-10-26 18:55:25 +01:00
|
|
|
NULL, "Terminal drawing for NEET autists^Wartists");
|
2014-10-25 00:46:26 +02:00
|
|
|
|
|
|
|
int c;
|
|
|
|
while ((c = opt_handler_get (&oh)) != -1)
|
|
|
|
switch (c)
|
|
|
|
{
|
|
|
|
case 'h':
|
|
|
|
opt_handler_usage (&oh, stdout);
|
|
|
|
exit (EXIT_SUCCESS);
|
|
|
|
case 'V':
|
|
|
|
printf (PROJECT_NAME " " PROJECT_VERSION "\n");
|
|
|
|
exit (EXIT_SUCCESS);
|
2014-10-26 18:57:28 +01:00
|
|
|
case 's':
|
|
|
|
if (options->server_address)
|
|
|
|
{
|
|
|
|
fprintf (stderr, "%s: %s\n",
|
|
|
|
"error", "cannot specify multiple listening addresses");
|
|
|
|
exit (EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
if (!(options->server_address = parse_address (optarg, AI_PASSIVE)))
|
|
|
|
exit (EXIT_FAILURE);
|
|
|
|
break;
|
|
|
|
case 'c':
|
|
|
|
if (options->client_address)
|
|
|
|
{
|
|
|
|
fprintf (stderr, "%s: %s\n",
|
|
|
|
"error", "cannot specify multiple addresses to connect to");
|
|
|
|
exit (EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
if (!(options->client_address = parse_address (optarg, 0)))
|
|
|
|
exit (EXIT_FAILURE);
|
|
|
|
break;
|
2014-10-25 00:46:26 +02:00
|
|
|
default:
|
2014-10-26 18:57:28 +01:00
|
|
|
fprintf (stderr, "%s: %s\n", "error", "wrong options");
|
2014-10-25 00:46:26 +02:00
|
|
|
opt_handler_usage (&oh, stderr);
|
|
|
|
exit (EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
2014-10-26 18:57:28 +01:00
|
|
|
if (options->client_address && options->server_address)
|
|
|
|
{
|
|
|
|
fprintf (stderr, "%s: %s\n",
|
|
|
|
"error", "cannot be both a server and a client");
|
|
|
|
exit (EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
2014-10-25 00:46:26 +02:00
|
|
|
argc -= optind;
|
|
|
|
argv += optind;
|
|
|
|
|
|
|
|
if (argc)
|
|
|
|
{
|
|
|
|
opt_handler_usage (&oh, stderr);
|
|
|
|
exit (EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
opt_handler_free (&oh);
|
|
|
|
}
|
|
|
|
|
2014-10-26 18:57:28 +01:00
|
|
|
static void
|
|
|
|
initialize_client (app_context_t *app, struct addrinfo *address)
|
|
|
|
{
|
|
|
|
app->mode = NETWORK_MODE_CLIENT;
|
|
|
|
|
|
|
|
int sock_fd, err;
|
|
|
|
for (; address; address = address->ai_next)
|
|
|
|
{
|
|
|
|
sock_fd = socket (address->ai_family,
|
|
|
|
address->ai_socktype, address->ai_protocol);
|
|
|
|
if (sock_fd == -1)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
char host_buf[NI_MAXHOST], serv_buf[NI_MAXSERV];
|
|
|
|
err = getnameinfo (address->ai_addr, address->ai_addrlen,
|
|
|
|
host_buf, sizeof host_buf, serv_buf, sizeof serv_buf,
|
|
|
|
NI_NUMERICHOST | NI_NUMERICSERV);
|
|
|
|
if (err)
|
|
|
|
{
|
|
|
|
fprintf (stderr, "%s: %s: %s\n",
|
|
|
|
"error", "getnameinfo", gai_strerror (err));
|
|
|
|
fprintf (stderr, "connecting...\n");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
char *x = format_host_port_pair (host_buf, serv_buf);
|
|
|
|
fprintf (stderr, "connecting to %s...\n", x);
|
|
|
|
free (x);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!connect (sock_fd, address->ai_addr, address->ai_addrlen))
|
|
|
|
break;
|
|
|
|
|
|
|
|
xclose (sock_fd);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!address)
|
|
|
|
{
|
|
|
|
fprintf (stderr, "%s: %s\n", "error", "connection failed");
|
|
|
|
exit (EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2014-10-27 13:54:53 +01:00
|
|
|
|
|
|
|
send_hello_request (app);
|
|
|
|
send_get_bitmap_request (app);
|
2014-10-26 18:57:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-22 23:58:25 +02:00
|
|
|
int
|
|
|
|
main (int argc, char *argv[])
|
|
|
|
{
|
|
|
|
TERMO_CHECK_VERSION;
|
|
|
|
setlocale (LC_CTYPE, "");
|
|
|
|
|
2014-10-25 00:46:26 +02:00
|
|
|
app_context_t app;
|
|
|
|
app_init (&app);
|
2014-10-26 18:57:28 +01:00
|
|
|
|
|
|
|
app_options_t options;
|
|
|
|
app_options_init (&options);
|
|
|
|
parse_program_arguments (&options, argc, argv);
|
|
|
|
|
|
|
|
if (options.client_address)
|
|
|
|
initialize_client (&app, options.client_address);
|
|
|
|
else if (options.server_address)
|
|
|
|
initialize_server (&app, options.server_address);
|
|
|
|
else
|
|
|
|
app.mode = NETWORK_MODE_STANDALONE;
|
|
|
|
|
|
|
|
app_options_free (&options);
|
2014-10-25 00:46:26 +02:00
|
|
|
|
2014-10-22 23:58:25 +02:00
|
|
|
termo_t *tk = termo_new (STDIN_FILENO, NULL, 0);
|
|
|
|
if (!tk)
|
|
|
|
{
|
2014-10-26 18:57:28 +01:00
|
|
|
fprintf (stderr, "%s: %s\n",
|
|
|
|
"error", "cannot allocate termo instance\n");
|
2014-10-22 23:58:25 +02:00
|
|
|
exit (EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
2014-10-25 00:46:26 +02:00
|
|
|
app.tk = tk;
|
2014-10-22 23:58:25 +02:00
|
|
|
termo_set_mouse_proto (tk, termo_guess_mouse_proto (tk));
|
|
|
|
termo_set_mouse_tracking_mode (tk, TERMO_MOUSE_TRACKING_DRAG);
|
|
|
|
|
|
|
|
// Set up curses for our drawing needs
|
|
|
|
if (!initscr () || nonl () == ERR || curs_set (0) == ERR)
|
|
|
|
{
|
2014-10-26 18:57:28 +01:00
|
|
|
fprintf (stderr, "%s: %s\n", "error", "cannot initialize curses");
|
2014-10-22 23:58:25 +02:00
|
|
|
exit (EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
2014-10-24 22:59:24 +02:00
|
|
|
uv_loop_t *loop = uv_default_loop ();
|
|
|
|
loop->data = &app;
|
2014-10-22 23:58:25 +02:00
|
|
|
|
2014-10-24 22:59:24 +02:00
|
|
|
uv_signal_init (loop, &app.winch_watcher);
|
|
|
|
uv_signal_start (&app.winch_watcher, on_winch, SIGWINCH);
|
2014-10-22 23:58:25 +02:00
|
|
|
|
2014-10-24 22:59:24 +02:00
|
|
|
uv_tty_init (loop, &app.tty, STDOUT_FILENO, false);
|
2014-10-22 23:58:25 +02:00
|
|
|
|
2014-10-24 22:59:24 +02:00
|
|
|
uv_poll_init (loop, &app.tty_watcher, STDIN_FILENO);
|
|
|
|
uv_poll_start (&app.tty_watcher, UV_READABLE, on_tty_readable);
|
2014-10-22 23:58:25 +02:00
|
|
|
|
2014-10-24 22:59:24 +02:00
|
|
|
uv_timer_init (loop, &app.tty_timer);
|
2014-10-22 23:58:25 +02:00
|
|
|
|
2014-10-24 22:59:24 +02:00
|
|
|
init_palette (&app);
|
|
|
|
update_canvas_for_screen (&app);
|
|
|
|
redraw (&app);
|
|
|
|
redraw_canvas (&app);
|
2014-10-22 23:58:25 +02:00
|
|
|
|
2014-10-24 22:59:24 +02:00
|
|
|
uv_run (loop, UV_RUN_DEFAULT);
|
2014-10-22 23:58:25 +02:00
|
|
|
endwin ();
|
2014-10-24 22:59:24 +02:00
|
|
|
|
|
|
|
app_free (&app);
|
2014-10-26 18:57:28 +01:00
|
|
|
uv_loop_close (loop);
|
2014-10-24 22:59:24 +02:00
|
|
|
return 0;
|
2014-10-22 23:58:25 +02:00
|
|
|
}
|
|
|
|
|