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:
parent
3864cca21d
commit
3df841f088
295
degesch.c
295
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
|
||||
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, ¤t))
|
||||
{
|
||||
// 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", ¤t)))
|
||||
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, ¤t))
|
||||
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)
|
||||
|
Loading…
Reference in New Issue
Block a user