summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--degesch.c291
1 files changed, 268 insertions, 23 deletions
diff --git a/degesch.c b/degesch.c
index 02af1d9..7b9d141 100644
--- a/degesch.c
+++ b/degesch.c
@@ -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 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
-send_to_buffer (struct app_context *ctx, struct buffer *buffer,
+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 *format, ...);
+ 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
-prepare_buffers (struct app_context *ctx)
+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)