degesch: some more progress
Whoa, this thing is huge. Started implementing the basis for IRC and user command handlers.
This commit is contained in:
parent
08c0027397
commit
c421532e6e
208
degesch.c
208
degesch.c
|
@ -78,6 +78,8 @@ static struct config_item g_config_table[] =
|
||||||
{ "socks_username", NULL, "SOCKS auth. username" },
|
{ "socks_username", NULL, "SOCKS auth. username" },
|
||||||
{ "socks_password", NULL, "SOCKS auth. password" },
|
{ "socks_password", NULL, "SOCKS auth. password" },
|
||||||
|
|
||||||
|
{ "isolate_buffers", "off", "Isolate global/server buffers" },
|
||||||
|
|
||||||
{ ATTR_PROMPT, NULL, "Terminal attributes for the prompt" },
|
{ ATTR_PROMPT, NULL, "Terminal attributes for the prompt" },
|
||||||
{ ATTR_RESET, NULL, "String to reset terminal attributes" },
|
{ ATTR_RESET, NULL, "String to reset terminal attributes" },
|
||||||
{ ATTR_WARNING, NULL, "Terminal attributes for warnings" },
|
{ ATTR_WARNING, NULL, "Terminal attributes for warnings" },
|
||||||
|
@ -244,6 +246,7 @@ struct app_context
|
||||||
enum color_mode color_mode; ///< Colour output mode
|
enum color_mode color_mode; ///< Colour output mode
|
||||||
bool reconnect; ///< Whether to reconnect on conn. fail.
|
bool reconnect; ///< Whether to reconnect on conn. fail.
|
||||||
unsigned long reconnect_delay; ///< Reconnect delay in seconds
|
unsigned long reconnect_delay; ///< Reconnect delay in seconds
|
||||||
|
bool isolate_buffers; ///< Isolate global/server buffers
|
||||||
|
|
||||||
// Server connection:
|
// 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);
|
str_map_set (&ctx->buffers_by_name, buffer->name, buffer);
|
||||||
LIST_APPEND_WITH_TAIL (ctx->buffers, ctx->buffers_tail, 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
|
static void
|
||||||
|
@ -848,7 +852,7 @@ buffer_remove (struct app_context *ctx, struct buffer *buffer)
|
||||||
if (buffer == ctx->server_buffer)
|
if (buffer == ctx->server_buffer)
|
||||||
ctx->server_buffer = NULL;
|
ctx->server_buffer = NULL;
|
||||||
|
|
||||||
// TODO: refresh the prompt? Or caller?
|
refresh_prompt (ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -931,7 +935,7 @@ buffer_activate (struct app_context *ctx, struct buffer *buffer)
|
||||||
rl_redisplay ();
|
rl_redisplay ();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: refresh the prompt? Or caller?
|
refresh_prompt (ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -1395,6 +1399,38 @@ init_readline (void)
|
||||||
|
|
||||||
// --- Input handling ----------------------------------------------------------
|
// --- 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
|
static void
|
||||||
irc_process_message (const struct irc_message *msg,
|
irc_process_message (const struct irc_message *msg,
|
||||||
const char *raw, void *user_data)
|
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);
|
app_readline_restore (&state, ctx->readline_prompt);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool show_to_user = true;
|
if (!ctx->irc_ready && (!strcasecmp (msg->command, "MODE")
|
||||||
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")
|
|
||||||
|| !strcasecmp (msg->command, "376") // RPL_ENDOFMOTD
|
|| !strcasecmp (msg->command, "376") // RPL_ENDOFMOTD
|
||||||
|| !strcasecmp (msg->command, "422"))) // ERR_NOMOTD
|
|| !strcasecmp (msg->command, "422"))) // ERR_NOMOTD
|
||||||
{
|
{
|
||||||
|
@ -1435,29 +1462,56 @@ irc_process_message (const struct irc_message *msg,
|
||||||
if (autojoin)
|
if (autojoin)
|
||||||
irc_send (ctx, "JOIN :%s", autojoin);
|
irc_send (ctx, "JOIN :%s", autojoin);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// TODO: whatever processing we need
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is going to be a lot more complicated
|
struct irc_handler key = { .name = msg->command };
|
||||||
if (show_to_user)
|
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
|
// TODO: ensure proper encoding
|
||||||
// FIXME: print to the server buffer
|
// FIXME: print to the server buffer
|
||||||
print_status ("%s", raw);
|
print_status ("%s", raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: load and preprocess this table so that shortcuts are accepted
|
// --- User input handling -----------------------------------------------------
|
||||||
struct command_handler
|
|
||||||
|
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;
|
char *name;
|
||||||
void (*handler) (struct app_context *ctx, const char *arguments);
|
void (*handler) (struct app_context *ctx, const char *arguments);
|
||||||
|
// TODO: probably also a usage string
|
||||||
}
|
}
|
||||||
g_handlers[] =
|
g_command_handlers[] =
|
||||||
{
|
{
|
||||||
{ "buffer", NULL },
|
{ "help", handle_command_help },
|
||||||
{ "help", NULL },
|
{ "quit", handle_command_quit },
|
||||||
|
{ "buffer", handle_command_buffer },
|
||||||
|
#if 0
|
||||||
{ "msg", NULL },
|
{ "msg", NULL },
|
||||||
{ "query", NULL },
|
{ "query", NULL },
|
||||||
{ "notice", NULL },
|
{ "notice", NULL },
|
||||||
|
@ -1483,17 +1537,93 @@ g_handlers[] =
|
||||||
{ "motd", NULL },
|
{ "motd", NULL },
|
||||||
{ "away", NULL },
|
{ "away", NULL },
|
||||||
{ "quote", NULL },
|
{ "quote", NULL },
|
||||||
{ "quit", NULL },
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
static void
|
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
|
// TODO: if it's a number, switch to the given buffer
|
||||||
|
|
||||||
if (!strcmp (command, "quit"))
|
struct command_handler *handler = str_map_find (&partial, command);
|
||||||
initiate_quit (ctx);
|
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
|
static void
|
||||||
|
@ -1503,19 +1633,12 @@ process_input (struct app_context *ctx, char *user_input)
|
||||||
size_t len;
|
size_t len;
|
||||||
|
|
||||||
if (!(input = iconv_xstrdup (ctx->term_to_utf8, user_input, -1, &len)))
|
if (!(input = iconv_xstrdup (ctx->term_to_utf8, user_input, -1, &len)))
|
||||||
{
|
|
||||||
print_error ("character conversion failed for `%s'", "user input");
|
print_error ("character conversion failed for `%s'", "user input");
|
||||||
goto fail;
|
else if (input[0] == '/' && input[1] != '/')
|
||||||
}
|
process_user_command (ctx, input + 1);
|
||||||
|
|
||||||
if (*input == '/')
|
|
||||||
process_internal_command (ctx, input + 1);
|
|
||||||
else
|
else
|
||||||
{
|
send_message_to_current_buffer (ctx, input);
|
||||||
// TODO: send a message to the current buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
fail:
|
|
||||||
free (input);
|
free (input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2048,7 +2171,10 @@ load_config (struct app_context *ctx, struct error **e)
|
||||||
if (!success)
|
if (!success)
|
||||||
return false;
|
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;
|
return false;
|
||||||
|
|
||||||
const char *delay_str = str_map_find (&ctx->config, "reconnect_delay");
|
const char *delay_str = str_map_find (&ctx->config, "reconnect_delay");
|
||||||
|
|
Loading…
Reference in New Issue