From c421532e6e984776ad798d5b8216629e7c73c203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?= Date: Thu, 16 Apr 2015 00:56:05 +0200 Subject: [PATCH] degesch: some more progress Whoa, this thing is huge. Started implementing the basis for IRC and user command handlers. --- degesch.c | 208 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 167 insertions(+), 41 deletions(-) diff --git a/degesch.c b/degesch.c index af2ffac..ede96a8 100644 --- a/degesch.c +++ b/degesch.c @@ -78,6 +78,8 @@ static struct config_item g_config_table[] = { "socks_username", NULL, "SOCKS auth. username" }, { "socks_password", NULL, "SOCKS auth. password" }, + { "isolate_buffers", "off", "Isolate global/server buffers" }, + { ATTR_PROMPT, NULL, "Terminal attributes for the prompt" }, { ATTR_RESET, NULL, "String to reset terminal attributes" }, { ATTR_WARNING, NULL, "Terminal attributes for warnings" }, @@ -244,6 +246,7 @@ struct app_context enum color_mode color_mode; ///< Colour output mode bool reconnect; ///< Whether to reconnect on conn. fail. unsigned long reconnect_delay; ///< Reconnect delay in seconds + bool isolate_buffers; ///< Isolate global/server buffers // Server connection: @@ -810,7 +813,8 @@ buffer_add (struct app_context *ctx, struct buffer *buffer) str_map_set (&ctx->buffers_by_name, buffer->name, buffer); LIST_APPEND_WITH_TAIL (ctx->buffers, ctx->buffers_tail, buffer); - // TODO: refresh the prompt? Or caller? + // In theory this can't cause changes in the prompt + refresh_prompt (ctx); } static void @@ -848,7 +852,7 @@ buffer_remove (struct app_context *ctx, struct buffer *buffer) if (buffer == ctx->server_buffer) ctx->server_buffer = NULL; - // TODO: refresh the prompt? Or caller? + refresh_prompt (ctx); } static void @@ -931,7 +935,7 @@ buffer_activate (struct app_context *ctx, struct buffer *buffer) rl_redisplay (); } - // TODO: refresh the prompt? Or caller? + refresh_prompt (ctx); } static void @@ -1395,6 +1399,38 @@ init_readline (void) // --- Input handling ---------------------------------------------------------- +// TODO: we will need a proper mode parser; to be shared with kike +// TODO: we alse definitely need to parse server capability messages + +static void +irc_handle_ping (struct app_context *ctx, const struct irc_message *msg) +{ + if (msg->params.len) + irc_send (ctx, "PONG :%s", msg->params.vector[0]); + else + irc_send (ctx, "PONG"); +} + +static struct irc_handler +{ + char *name; + void (*handler) (struct app_context *ctx, const struct irc_message *msg); +} +g_irc_handlers[] = +{ + // This list needs to stay sorted + // TODO: handle as much as we can + { "PING", irc_handle_ping }, +}; + +static int +irc_handler_cmp_by_name (const void *a, const void *b) +{ + const struct irc_handler *first = a; + const struct irc_handler *second = b; + return strcasecmp_ascii (first->name, second->name); +} + static void irc_process_message (const struct irc_message *msg, const char *raw, void *user_data) @@ -1414,16 +1450,7 @@ irc_process_message (const struct irc_message *msg, app_readline_restore (&state, ctx->readline_prompt); } - bool show_to_user = true; - if (!strcasecmp (msg->command, "PING")) - { - show_to_user = false; - if (msg->params.len) - irc_send (ctx, "PONG :%s", msg->params.vector[0]); - else - irc_send (ctx, "PONG"); - } - else if (!ctx->irc_ready && (!strcasecmp (msg->command, "MODE") + if (!ctx->irc_ready && (!strcasecmp (msg->command, "MODE") || !strcasecmp (msg->command, "376") // RPL_ENDOFMOTD || !strcasecmp (msg->command, "422"))) // ERR_NOMOTD { @@ -1435,29 +1462,56 @@ irc_process_message (const struct irc_message *msg, if (autojoin) irc_send (ctx, "JOIN :%s", autojoin); } - else - { - // TODO: whatever processing we need - } - // This is going to be a lot more complicated - if (show_to_user) + struct irc_handler key = { .name = msg->command }; + struct irc_handler *handler = bsearch (&key, g_irc_handlers, + N_ELEMENTS (g_irc_handlers), sizeof key, irc_handler_cmp_by_name); + if (handler) + handler->handler (ctx, msg); + + // Numerics typically have human-readable information + unsigned long dummy; + if (xstrtoul (&dummy, msg->command, 10)) // TODO: ensure proper encoding // FIXME: print to the server buffer print_status ("%s", raw); } -// TODO: load and preprocess this table so that shortcuts are accepted -struct command_handler +// --- User input handling ----------------------------------------------------- + +static void handle_command_help (struct app_context *, const char *); + +static void +handle_command_buffer (struct app_context *ctx, const char *arguments) +{ + // TODO: parse the arguments +} + +static void +handle_command_quit (struct app_context *ctx, const char *arguments) +{ + if (ctx->irc_fd != -1) + { + if (*arguments) + irc_send (ctx, "QUIT :%s", arguments); + else + irc_send (ctx, "QUIT :%s", PROGRAM_NAME " " PROGRAM_VERSION); + } + initiate_quit (ctx); +} + +static struct command_handler { char *name; void (*handler) (struct app_context *ctx, const char *arguments); + // TODO: probably also a usage string } -g_handlers[] = +g_command_handlers[] = { - { "buffer", NULL }, - { "help", NULL }, - + { "help", handle_command_help }, + { "quit", handle_command_quit }, + { "buffer", handle_command_buffer }, +#if 0 { "msg", NULL }, { "query", NULL }, { "notice", NULL }, @@ -1483,17 +1537,93 @@ g_handlers[] = { "motd", NULL }, { "away", NULL }, { "quote", NULL }, - { "quit", NULL }, +#endif }; static void -process_internal_command (struct app_context *ctx, const char *command) +handle_command_help (struct app_context *ctx, const char *arguments) { - // TODO: resolve commands from a map + // TODO: show a list of all user commands +} + +static int +command_handler_cmp_by_length (const void *a, const void *b) +{ + const struct command_handler *first = a; + const struct command_handler *second = b; + return strlen (first->name) - strlen (second->name); +} + +static void +init_partial_matching_user_command_map (struct str_map *partial) +{ + str_map_init (partial); + partial->key_xfrm = tolower_ascii_strxfrm; + + // We process them from the longest to the shortest one, + // so that common prefixes favor shorter entries + struct command_handler *by_length[N_ELEMENTS (g_command_handlers)]; + for (size_t i = 0; i < N_ELEMENTS (by_length); i++) + by_length[i] = &g_command_handlers[i]; + qsort (by_length, N_ELEMENTS (by_length), sizeof *by_length, + command_handler_cmp_by_length); + + for (size_t i = N_ELEMENTS (by_length); i--; ) + { + char *copy = xstrdup (by_length[i]->name); + for (size_t part = strlen (copy); part; part--) + { + copy[part] = '\0'; + str_map_set (partial, copy, by_length[i]); + } + free (copy); + } +} + +static void +process_user_command (struct app_context *ctx, char *command) +{ + // Trivially create a partial matching map + static bool initialized = false; + struct str_map partial; + if (!initialized) + { + init_partial_matching_user_command_map (&partial); + initialized = true; + } + + // TODO: cut a single word (strtok_r()?) // TODO: if it's a number, switch to the given buffer - if (!strcmp (command, "quit")) - initiate_quit (ctx); + struct command_handler *handler = str_map_find (&partial, command); + if (handler) + // FIXME: pass arguments correctly + handler->handler (ctx, ""); +} + +static void +send_message_to_current_buffer (struct app_context *ctx, char *message) +{ + struct buffer *buffer = ctx->current_buffer; + if (!buffer) + { + // TODO: print an error message to the global buffer + return; + } + + switch (buffer->type) + { + case BUFFER_GLOBAL: + case BUFFER_SERVER: + // TODO: print a message to the buffer that it's not a channel + break; + case BUFFER_CHANNEL: + // TODO: send an IRC message to the channel + break; + case BUFFER_PM: + // TODO: send an IRC message to the user + break; + } } static void @@ -1503,19 +1633,12 @@ process_input (struct app_context *ctx, char *user_input) size_t len; if (!(input = iconv_xstrdup (ctx->term_to_utf8, user_input, -1, &len))) - { print_error ("character conversion failed for `%s'", "user input"); - goto fail; - } - - if (*input == '/') - process_internal_command (ctx, input + 1); + else if (input[0] == '/' && input[1] != '/') + process_user_command (ctx, input + 1); else - { - // TODO: send a message to the current buffer - } + send_message_to_current_buffer (ctx, input); -fail: free (input); } @@ -2048,7 +2171,10 @@ load_config (struct app_context *ctx, struct error **e) if (!success) return false; - if (!irc_get_boolean_from_config (ctx, "reconnect", &ctx->reconnect, e)) + if (!irc_get_boolean_from_config (ctx, + "reconnect", &ctx->reconnect, e) + || !irc_get_boolean_from_config (ctx, + "isolate_buffers", &ctx->isolate_buffers, e)) return false; const char *delay_str = str_map_find (&ctx->config, "reconnect_delay");