degesch: steady progress

I'm sorry, I can't do reasonable commit messages in this stage of development.

It's all a total mess slowly converging to an alpha version.
This commit is contained in:
Přemysl Eric Janouch 2015-04-13 00:06:08 +02:00
parent 3864cca21d
commit 3df841f088
1 changed files with 270 additions and 25 deletions

295
degesch.c
View File

@ -44,11 +44,18 @@
#include <curses.h>
#include <term.h>
// Literally cancer
#undef lines
#undef columns
#include <readline/readline.h>
#include <readline/history.h>
// --- Configuration (application-specific) ------------------------------------
// TODO: reject all junk present in the configuration; there can be newlines
static struct config_item g_config_table[] =
{
{ ATTR_PROMPT, NULL, "Terminal attributes for the prompt" },
@ -81,6 +88,9 @@ static struct config_item g_config_table[] =
// --- Application data --------------------------------------------------------
// All text stored in our data structures is encoded in UTF-8.
// Or at least should be.
/// Shorthand to set an error and return failure from the function
#define FAIL(...) \
BLOCK_START \
@ -97,23 +107,47 @@ enum buffer_line_flags
enum buffer_line_type
{
BUFFER_LINE_TEXT, ///< PRIVMSG
BUFFER_LINE_PRIVMSG, ///< PRIVMSG
BUFFER_LINE_ACTION, ///< PRIVMSG ACTION
BUFFER_LINE_NOTICE, ///< NOTICE
BUFFER_LINE_STATUS ///< JOIN, PART, QUIT
BUFFER_LINE_JOIN, ///< JOIN
BUFFER_LINE_PART, ///< PART
BUFFER_LINE_KICK, ///< KICK
BUFFER_LINE_QUIT, ///< QUIT
BUFFER_LINE_STATUS ///< Whatever status messages
};
struct buffer_line
{
LIST_HEADER (struct buffer_line)
// We use the "type" and "flags" mostly just as formatting hints
enum buffer_line_type type; ///< Type of the event
int flags; ///< Flags
time_t when; ///< Time of the event
char *origin; ///< Name of the origin
char *text; ///< The text of the message
char *who; ///< Name of the origin or NULL (user)
char *object; ///< Text of message, object of action
char *reason; ///< Reason for PART, KICK, QUIT
};
struct buffer_line *
buffer_line_new (void)
{
struct buffer_line *self = xcalloc (1, sizeof *self);
return self;
}
static void
buffer_line_destroy (struct buffer_line *self)
{
free (self->who);
free (self->object);
free (self->reason);
free (self);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
struct nick_info
@ -143,6 +177,9 @@ enum buffer_type
BUFFER_PM ///< Private messages (query)
};
// TODO: now I can't just print messages with print_status() etc.,
// all that stuff has to go in a buffer now
struct buffer
{
LIST_HEADER (struct buffer)
@ -150,12 +187,15 @@ struct buffer
enum buffer_type type; ///< Type of the buffer
char *name; ///< The name of the buffer
unsigned unseen_messages; ///< # messages since last visited
// Buffer contents:
// TODO: now I can't just print messages with print_status() etc.,
// all that stuff has to go into a buffer now
struct buffer_line *lines; ///< All lines in this buffer
struct buffer_line *lines_tail; ///< The tail of buffer lines
unsigned lines_count; ///< How many lines we have
// Channels:
unsigned unseen_messages_count; ///< # messages since last visited
// Channel information:
char *topic; ///< Channel topic
struct str_map nicks; ///< Maps nicks to "nick_info"
@ -191,29 +231,41 @@ enum color_mode
struct app_context
{
// Configuration:
struct str_map config; ///< User configuration
enum color_mode color_mode; ///< Colour output mode
bool reconnect; ///< Whether to reconnect on conn. fail.
unsigned long reconnect_delay; ///< Reconnect delay in seconds
// Server connection:
int irc_fd; ///< Socket FD of the server
struct str read_buffer; ///< Input yet to be processed
struct poller_fd irc_event; ///< IRC FD event
bool irc_ready; ///< Whether we may send messages now
SSL_CTX *ssl_ctx; ///< SSL context
SSL *ssl; ///< SSL connection
// Events:
struct poller_fd tty_event; ///< Terminal input event
struct poller_fd signal_event; ///< Signal FD event
struct poller_timer ping_tmr; ///< We should send a ping
struct poller_timer timeout_tmr; ///< Connection seems to be dead
struct poller_timer reconnect_tmr; ///< We should reconnect now
SSL_CTX *ssl_ctx; ///< SSL context
SSL *ssl; ///< SSL connection
struct poller poller; ///< Manages polled descriptors
bool quitting; ///< User requested quitting
bool polling; ///< The event loop is running
// Buffers:
struct buffer *buffers; ///< All our buffers in order
struct buffer *buffers_tail; ///< The tail of our buffers
// TODO: a name -> buffer map that excludes GLOBAL and SERVER
struct str_map buffers_by_name; ///< Excludes GLOBAL and SERVER
struct buffer *global_buffer; ///< The global buffer
struct buffer *server_buffer; ///< The server buffer
@ -222,14 +274,16 @@ struct app_context
// TODO: So that we always output proper date change messages
time_t last_displayed_msg_time; ///< Time of last displayed message
struct poller poller; ///< Manages polled descriptors
bool quitting; ///< User requested quitting
bool polling; ///< The event loop is running
// Terminal:
iconv_t term_to_utf8; ///< Terminal encoding to UTF-8
iconv_t term_from_utf8; ///< UTF-8 to terminal encoding
// XXX: shouldn't it be rather UTF-8 from Latin 1?
iconv_t term_from_latin1; ///< ISO Latin 1 to terminal encoding
int lines; ///< Current terminal height
int columns; ///< Current ternimal width
char *readline_prompt; ///< The prompt we use for readline
bool readline_prompt_shown; ///< Whether the prompt is shown now
}
@ -252,6 +306,9 @@ app_context_init (struct app_context *self)
str_init (&self->read_buffer);
self->irc_ready = false;
str_map_init (&self->buffers_by_name);
self->buffers_by_name.key_xfrm = irc_strxfrm;
self->last_displayed_msg_time = time (NULL);
poller_init (&self->poller);
@ -291,12 +348,17 @@ app_context_free (struct app_context *self)
if (self->ssl_ctx)
SSL_CTX_free (self->ssl_ctx);
LIST_FOR_EACH (struct buffer, iter, self->buffers)
buffer_destroy (iter);
str_map_free (&self->buffers_by_name);
poller_free (&self->poller);
free (self->readline_prompt);
iconv_close (self->term_from_latin1);
iconv_close (self->term_from_utf8);
iconv_close (self->term_to_utf8);
free (self->readline_prompt);
}
// --- Attributed output -------------------------------------------------------
@ -566,14 +628,192 @@ setup_signal_handlers (void)
// --- Buffers -----------------------------------------------------------------
static void
send_to_buffer (struct app_context *ctx, struct buffer *buffer,
enum buffer_line_type type, int flags,
const char *origin, const char *format, ...);
static void buffer_send (struct app_context *ctx, struct buffer *buffer,
enum buffer_line_type type, int flags, const char *origin,
const char *reason, const char *format, ...) ATTRIBUTE_PRINTF (7, 8);
static void
prepare_buffers (struct app_context *ctx)
buffer_update_time (struct app_context *ctx, time_t now)
{
struct tm last, current;
if (!localtime_r (&ctx->last_displayed_msg_time, &last)
|| !localtime_r (&now, &current))
{
// Strange but nonfatal
print_error ("%s: %s", "localtime_r", strerror (errno));
return;
}
ctx->last_displayed_msg_time = now;
if (last.tm_year == current.tm_year
&& last.tm_mon == current.tm_mon
&& last.tm_mday == current.tm_mday)
return;
char buf[32] = "";
if (soft_assert (strftime (buf, sizeof buf, "%F", &current)))
print_status ("%s", buf);
// Else the buffer was too small, which is pretty weird
}
static void
buffer_line_display (struct app_context *ctx, struct buffer_line *line)
{
// Normal timestamps don't include the date, this way the user won't be
// confused as to when an event has happened
buffer_update_time (ctx, line->when);
struct str text;
str_init (&text);
struct tm current;
if (!localtime_r (&line->when, &current))
print_error ("%s: %s", "localtime_r", strerror (errno));
else
str_append_printf (&text, "%02d:%02d:%02d ",
current.tm_hour, current.tm_min, current.tm_sec);
char *who = iconv_xstrdup (ctx->term_from_utf8, line->who, -1, NULL);
char *object = iconv_xstrdup (ctx->term_from_utf8, line->object, -1, NULL);
char *reason = iconv_xstrdup (ctx->term_from_utf8, line->reason, -1, NULL);
switch (line->type)
{
case BUFFER_LINE_PRIVMSG:
str_append_printf (&text, "<%s> %s", who, object);
break;
case BUFFER_LINE_ACTION:
str_append_printf (&text, " * %s %s", who, object);
break;
case BUFFER_LINE_NOTICE:
str_append_printf (&text, " - Notice(%s): %s", who, object);
break;
case BUFFER_LINE_JOIN:
if (who)
str_append_printf (&text, "--> %s has joined %s", who, object);
else
str_append_printf (&text, "--> You have joined %s", object);
break;
case BUFFER_LINE_PART:
if (who)
str_append_printf (&text, "<-- %s has left %s (%s)",
who, object, reason);
else
str_append_printf (&text, "<-- You have left %s (%s)",
object, reason);
break;
case BUFFER_LINE_KICK:
if (who)
str_append_printf (&text, "<-- %s has kicked %s (%s)",
who, object, reason);
else
str_append_printf (&text, "<-- You have kicked %s (%s)",
object, reason);
break;
case BUFFER_LINE_QUIT:
if (who)
str_append_printf (&text, "<-- %s has quit (%s)", who, reason);
else
str_append_printf (&text, "<-- You have quit (%s)", reason);
break;
case BUFFER_LINE_STATUS:
str_append_printf (&text, " - %s", object);
}
free (who);
free (object);
free (reason);
// TODO: hide readline if needed
printf ("%s\n", text.str);
str_free (&text);
// TODO: unhide readline if hidden
}
static void
buffer_send (struct app_context *ctx, struct buffer *buffer,
enum buffer_line_type type, int flags,
const char *origin, const char *reason, const char *format, ...)
{
va_list ap;
va_start (ap, format);
struct str text;
str_init (&text);
str_append_vprintf (&text, format, ap);
va_end (ap);
struct buffer_line *line = buffer_line_new ();
line->type = type;
line->flags = flags;
line->when = time (NULL);
line->who = xstrdup (origin);
line->object = str_steal (&text);
line->reason = xstrdup (reason);
LIST_APPEND_WITH_TAIL (buffer->lines, buffer->lines_tail, line);
buffer->lines_count++;
if (buffer == ctx->current_buffer)
buffer_line_display (ctx, line);
}
static struct buffer *
buffer_by_name (struct app_context *ctx, const char *name)
{
return str_map_find (&ctx->buffers_by_name, name);
}
static void
buffer_add (struct app_context *ctx, struct buffer *buffer)
{
hard_assert (!buffer_by_name (ctx, buffer->name));
str_map_set (&ctx->buffers_by_name, buffer->name, buffer);
LIST_APPEND_WITH_TAIL (ctx->buffers, ctx->buffers_tail, buffer);
// TODO: refresh the prompt?
}
static void
buffer_remove (struct app_context *ctx, struct buffer *buffer)
{
hard_assert (buffer != ctx->current_buffer);
str_map_set (&ctx->buffers_by_name, buffer->name, NULL);
LIST_UNLINK_WITH_TAIL (ctx->buffers, ctx->buffers_tail, buffer);
buffer_destroy (buffer);
// It's not a good idea to remove these buffers, but it's even a worse
// one to leave the pointers point to invalid memory
if (buffer == ctx->global_buffer)
ctx->global_buffer = NULL;
if (buffer == ctx->server_buffer)
ctx->server_buffer = NULL;
// TODO: refresh the prompt?
}
static void
buffer_activate (struct app_context *ctx, struct buffer *buffer)
{
ctx->current_buffer = buffer;
print_status ("%s", buffer->name);
// That is, minus the buffer switch line and the readline prompt
int to_display = ctx->lines - 2;
// TODO: find the to_display-th line from the back
// TODO: print all the lines in order
// TODO: switch readline history stack
// TODO: refresh the prompt?
}
static void
init_buffers (struct app_context *ctx)
{
// At the moment we have only two global everpresent buffers
struct buffer *global = ctx->global_buffer = buffer_new ();
struct buffer *server = ctx->server_buffer = buffer_new ();
@ -1348,10 +1588,13 @@ on_signal_pipe_readable (const struct pollfd *fd, struct app_context *ctx)
}
if (g_winch_received)
{
// This fucks up big time on terminals with automatic wrapping such as
// rxvt-unicode or newer VTE when the current line overflows, however we
// can't do much about that
rl_resize_terminal ();
rl_get_screen_size (&ctx->lines, &ctx->columns);
}
}
static void
@ -1657,9 +1900,14 @@ main (int argc, char *argv[])
using_history ();
stifle_history (HISTORY_LIMIT);
setup_signal_handlers ();
init_colors (&ctx);
init_poller_events (&ctx);
init_buffers (&ctx);
refresh_prompt (&ctx);
// TODO: connect asynchronously (first step towards multiple servers)
struct error *e = NULL;
if (!load_config (&ctx, &e)
|| !irc_connect (&ctx, &e))
@ -1669,10 +1917,6 @@ main (int argc, char *argv[])
exit (EXIT_FAILURE);
}
setup_signal_handlers ();
prepare_buffers (&ctx);
refresh_prompt (&ctx);
// TODO: maybe use rl_make_bare_keymap() and start from there
// XXX: Since readline() installs a set of default key bindings the first
@ -1690,8 +1934,9 @@ main (int argc, char *argv[])
rl_bind_keyseq ("M-n", rl_named_function ("next-history"));
rl_catch_sigwinch = false;
ctx.readline_prompt_shown = true;
rl_callback_handler_install (ctx.readline_prompt, on_readline_input);
rl_get_screen_size (&ctx.lines, &ctx.columns);
ctx.readline_prompt_shown = true;
ctx.polling = true;
while (ctx.polling)