src/lib/console.c
branchnew-lib-errors
changeset 218 5229a5d098b2
parent 217 7728d6ec3abf
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/console.c	Thu May 28 00:35:02 2009 +0300
@@ -0,0 +1,277 @@
+#include "console.h"
+#include "log.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <readline/readline.h>
+#include <readline/history.h>
+
+#include <signal.h>
+#include <assert.h>
+
+struct console {
+    /** Configuration */
+    struct console_config config;
+
+    /** Input event */
+    struct event *ev;
+
+    /** Callback functions */
+    const struct console_callbacks *callbacks;
+
+    /** Callback context argument */
+    void *cb_arg;
+
+    /** Old SIGINT handler */
+    struct sigaction old_sigint;
+
+    /** Already initialized? */
+    bool initialized;
+
+    /** Set as log output function? */
+    bool log_output;
+
+    /** Prompt displayed? */
+    bool have_prompt;
+
+    /** Waiting for line to be processed */
+    bool waiting;
+};
+
+/** The global console state */
+static struct console _console;
+
+/**
+ * Our stdin input handler
+ */
+static void console_input (int fd, short what, void *arg)
+{
+    struct console *console = arg;
+
+    (void) fd;
+    (void) what;
+
+    if (console->waiting)
+        // can't feed readline input while it's disabled
+        return log_warn("discrding input while waiting");
+
+    else
+        // tell readline to process it
+        rl_callback_read_char();
+}
+
+/**
+ * Our readline line handler
+ */
+static void console_line (char *line)
+{
+    struct console *console = &_console;
+    enum console_line_status status = CONSOLE_CONTINUE;
+    
+    // special-case EOF
+    if (!line) {
+        // prettify
+        rl_crlf();
+        rl_on_new_line();
+
+        if (console->callbacks->on_eof)
+            console->callbacks->on_eof(console->cb_arg);
+        
+        return;
+    }
+
+    // update state for console_print during processing
+    console->have_prompt = false;
+
+    // invoke the console callback
+    if (console->callbacks && console->callbacks->on_line)
+        status = console->callbacks->on_line(line, console->cb_arg);
+    
+    // add to history mechanism
+    add_history(line);
+
+    // release the line
+    free(line);
+    
+    switch (status) {
+        case CONSOLE_CONTINUE:
+            // the prompt will be displayed again
+            console->have_prompt = true;
+
+            break;
+
+        case CONSOLE_WAIT:
+            // deactivate our read event
+            if (event_del(console->ev))
+                log_warn("unable to deactivate console read event");
+
+            // remove the readline stuff to stop it from displaying the prompt
+            rl_callback_handler_remove();
+
+            // ignore input
+            console->waiting = true;
+
+            break;
+    }
+}
+static void on_sigint (int sig)
+{
+    struct console *console = &_console;
+
+    (void) sig;
+
+    if (console->waiting) {
+        // notify user
+        if (console->callbacks->on_interrupt)
+            console->callbacks->on_interrupt(console->cb_arg);
+
+    } else {
+        // interrupt the input line
+        // XXX: is this the right function to call?
+        rl_free_line_state();
+
+        // redisplay on new line
+        rl_crlf();
+        rl_callback_handler_install(console->config.prompt, console_line);
+    }
+}
+
+err_t console_init (struct console **console_ptr, struct event_base *ev_base, const struct console_config *config,
+        const struct console_callbacks *callbacks, void *cb_arg, error_t *err)
+{
+    struct console *console = &_console;
+
+    // check it's not already initialized
+    assert(!console->initialized);
+
+    // store
+    console->config = *config;
+
+    // store callbacks?
+    if (callbacks)
+        console_set_callbacks(console, callbacks, cb_arg);
+
+    // setup the input event
+    if ((console->ev = event_new(ev_base, STDIN_FILENO, EV_READ | EV_PERSIST, &console_input, console)) == NULL)
+        JUMP_SET_ERROR(err, ERR_EVENT_NEW);
+    
+    // set our SIGINT handler
+    struct sigaction sigact;
+    
+    memset(&sigact, 0, sizeof(sigact));
+    sigact.sa_handler = on_sigint;
+    sigaction(SIGINT, &sigact, &console->old_sigint);
+
+    // setup state for initial readline prompt
+    console->have_prompt = true;
+
+    // setup readline
+    rl_callback_handler_install(config->prompt, console_line);
+    
+    // mark it as initialized
+    console->initialized = true;
+
+    // enable input
+    if (event_add(console->ev, NULL))
+        JUMP_SET_ERROR(err, ERR_EVENT_ADD);
+
+    // ok
+    *console_ptr = console;
+
+    return SUCCESS;
+
+error:
+    console_destroy(console);
+
+    return ERROR_CODE(err);
+}
+
+void console_set_callbacks (struct console *console, const struct console_callbacks *callbacks, void *cb_arg)
+{
+    console->callbacks = callbacks;
+    console->cb_arg = cb_arg;
+}
+
+void console_continue (struct console *console)
+{
+    if (!console->waiting)
+        return;
+
+    // re-enable input
+    console->waiting = false;
+
+    if (event_add(console->ev, NULL))
+        log_fatal("unable to re-enable console read event");
+    
+    // the prompt will be displayed again
+    console->have_prompt = true;
+
+    // re-setup readline with prompt
+    rl_callback_handler_install(console->config.prompt, console_line);
+}
+
+err_t console_print (struct console *console, const char *line)
+{
+    if (console->have_prompt)
+        // don't interrupt current input line
+        rl_crlf();
+
+    // output the line
+    if (printf("%s\n", line) < 0)
+        return _ERR_GENERAL;
+    
+    if (console->have_prompt) {
+        // restore input
+        rl_on_new_line();
+        rl_redisplay();
+    }
+
+    // ok
+    return SUCCESS;
+}
+
+/**
+ * Our log_output_func implementation
+ */
+static void console_log_output_func (const char *line, void *_console)
+{
+    struct console *console = _console;
+
+    console_print(console, line);
+}
+
+void console_set_log_output (struct console *console)
+{
+    // replace old one
+    log_set_func(console_log_output_func, console);
+
+    // mark
+    console->log_output = true;
+}
+
+void console_destroy (struct console *console)
+{
+    if (console->log_output)
+        // unset ourselves as the log handler
+        log_set_func(NULL, NULL);
+
+    // remove the input event
+    if (console->ev)
+        event_free(console->ev); 
+    
+    console->ev = NULL;
+
+    // de-init rl?
+    if (console->initialized)
+        rl_callback_handler_remove();
+    
+    console->initialized = false;
+
+    // restore signal handler
+    sigaction(SIGINT, &console->old_sigint, NULL);
+
+    // remove stored stuff
+    console->callbacks = console->cb_arg = NULL;
+}
+