src/lib/console.c
author Tero Marttila <terom@fixme.fi>
Thu, 28 May 2009 00:35:02 +0300
branchnew-lib-errors
changeset 218 5229a5d098b2
parent 217 src/console.c@7728d6ec3abf
permissions -rw-r--r--
some of spbot and lib compiles
#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;
}