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