/** * @file readline_curses.cc */ #include "config.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_PTY_H #include #endif #ifdef HAVE_UTIL_H #include #endif #include #include "auto_mem.hh" #include "readline_curses.hh" using namespace std; static int got_line = 0; static sig_atomic_t got_timeout = 0; static sig_atomic_t got_winch = 0; static readline_curses *child_this; readline_context *readline_context::loaded_context; set *readline_context::arg_possibilities; static void sigalrm(int sig) { got_timeout = 1; } static void sigwinch(int sig) { got_winch = 1; } static void line_ready_tramp(char *line) { child_this->line_ready(line); add_history(line); got_line = 1; rl_callback_handler_remove(); } char *readline_context::completion_generator(const char *text, int state) { static vector matches; char *retval = NULL; if (state == 0) { int len = strlen(text); matches.clear(); if (arg_possibilities != NULL) { set::iterator iter; for (iter = arg_possibilities->begin(); iter != arg_possibilities->end(); iter++) { fprintf(stderr, " cmp %s %s\n", text, iter->c_str()); if (strncmp(text, iter->c_str(), len) == 0) { matches.push_back(*iter); } } } } if (!matches.empty()) { retval = strdup(matches.back().c_str()); matches.pop_back(); } return retval; } char **readline_context::attempted_completion(const char *text, int start, int end) { char **retval = NULL; if (start == 0) { arg_possibilities = &loaded_context->rc_possibilities["__command"]; rl_completion_append_character = ' '; } else { char *space; string cmd; rl_completion_append_character = 0; space = strchr(rl_line_buffer, ' '); assert(space != NULL); cmd = string(rl_line_buffer, 0, space - rl_line_buffer); fprintf(stderr, "cmd %s\n", cmd.c_str()); vector &proto = loaded_context->rc_prototypes[cmd]; if (proto.size() == 0) { arg_possibilities = NULL; } else if (proto[0] == "filename") { return NULL; // XXX } else { fprintf(stderr, "proto %s\n", proto[0].c_str()); arg_possibilities = &(loaded_context->rc_possibilities[proto[0]]); fprintf(stderr, "ag %p %d\n", arg_possibilities, arg_possibilities->size()); } } retval = rl_completion_matches(text, completion_generator); if (retval == NULL) { rl_attempted_completion_over = 1; } return retval; } readline_curses::readline_curses() : rc_active_context(-1), rc_child(-1) { struct winsize ws; int sp[2]; if (socketpair(PF_UNIX, SOCK_DGRAM, 0, sp) < 0) { throw error(errno); } this->rc_command_pipe[RCF_MASTER] = sp[RCF_MASTER]; this->rc_command_pipe[RCF_SLAVE] = sp[RCF_SLAVE]; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1) { throw error(errno); } if (openpty(this->rc_pty[RCF_MASTER].out(), this->rc_pty[RCF_SLAVE].out(), NULL, NULL, &ws) < 0) { throw error(errno); } if ((this->rc_child = fork()) == -1) { throw error(errno); } if (this->rc_child == 0) { char buffer[1024]; this->rc_command_pipe[RCF_MASTER].reset(); this->rc_pty[RCF_MASTER].reset(); signal(SIGALRM, sigalrm); signal(SIGWINCH, sigwinch); dup2(this->rc_pty[RCF_SLAVE], STDIN_FILENO); dup2(this->rc_pty[RCF_SLAVE], STDOUT_FILENO); setenv("TERM", "vt52", 1); rl_initialize(); /* * XXX Need to keep the input on a single line since the display screws * up if it wraps around. */ strcpy(buffer, "set horizontal-scroll-mode on"); rl_parse_and_bind(buffer); // NOTE: buffer is modified child_this = this; } else { this->rc_command_pipe[RCF_SLAVE].reset(); this->rc_pty[RCF_SLAVE].reset(); } } readline_curses::~readline_curses() { if (this->rc_child == 0) { exit(0); } else if (this->rc_child > 0) { int status; kill(this->rc_child, SIGTERM); this->rc_child = -1; while (wait(&status) < 0 && (errno == EINTR)) { ; } } } void readline_curses::start(void) { if (this->rc_child != 0) { return; } map::iterator current_context; fd_set rfds; int maxfd; assert(!this->rc_contexts.empty()); rl_completer_word_break_characters = (char *)" \t\n"; /* XXX */ current_context = this->rc_contexts.end(); FD_ZERO(&rfds); FD_SET(STDIN_FILENO, &rfds); FD_SET(this->rc_command_pipe[RCF_SLAVE], &rfds); maxfd = max(STDIN_FILENO, this->rc_command_pipe[RCF_SLAVE].get()); while (1) { fd_set ready_rfds = rfds; int rc; rc = select(maxfd + 1, &ready_rfds, NULL, NULL, NULL); if (rc < 0) { switch (errno) { case EINTR: break; } } else { if (FD_ISSET(STDIN_FILENO, &ready_rfds)) { struct itimerval itv; assert(current_context != this->rc_contexts.end()); itv.it_value.tv_sec = 0; itv.it_value.tv_usec = KEY_TIMEOUT; itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; setitimer(ITIMER_REAL, &itv, NULL); rl_callback_read_char(); if (RL_ISSTATE(RL_STATE_DONE) && !got_line) { got_line = 1; this->line_ready(""); rl_callback_handler_remove(); } /* fprintf(stderr, " is done: %d\n", RL_ISSTATE(RL_STATE_DONE)); */ } if (FD_ISSET(this->rc_command_pipe[RCF_SLAVE], &ready_rfds)) { char msg[1024 + 1]; /* fprintf(stderr, "rl cmd\n"); */ if ((rc = read(this->rc_command_pipe[RCF_SLAVE], msg, sizeof(msg) - 1)) < 0) { fprintf(stderr, "read\n"); } else { int context, prompt_start = 0; char type[32]; msg[rc] = '\0'; if (sscanf(msg, "f:%d:%n", &context, &prompt_start) == 1 && prompt_start != 0 && (current_context = this->rc_contexts.find(context)) != this->rc_contexts.end()) { current_context->second->load(); rl_callback_handler_install(&msg[prompt_start], line_ready_tramp); } else if (sscanf(msg, "ap:%d:%31[^:]:%n", &context, type, &prompt_start) == 2) { assert(this->rc_contexts[context] != NULL); this->rc_contexts[context]-> add_possibility(string(type), string(&msg[prompt_start])); } else if (sscanf(msg, "rp:%d:%31[^:]:%n", &context, type, &prompt_start) == 2) { assert(this->rc_contexts[context] != NULL); this->rc_contexts[context]-> rem_possibility(string(type), string(&msg[prompt_start])); } else { fprintf(stderr, "unhandled message: %s\n", msg); } } } } if (got_timeout) { char msg[1024]; fprintf(stderr, "got timeout\n"); got_timeout = 0; snprintf(msg, sizeof(msg), "t:%s", rl_line_buffer); write(this->rc_command_pipe[RCF_SLAVE], msg, strlen(msg)); } if (got_line) { struct itimerval itv; got_line = 0; itv.it_value.tv_sec = 0; itv.it_value.tv_usec = 0; itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; if (setitimer(ITIMER_REAL, &itv, NULL) < 0) { fprintf(stderr, "setitimer"); } current_context->second->save(); current_context = this->rc_contexts.end(); } if (got_winch) { struct winsize ws; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1) { throw error(errno); } fprintf(stderr, "rl winched %d %d\n", ws.ws_row, ws.ws_col); got_winch = 0; rl_set_screen_size(ws.ws_row, ws.ws_col); } } } void readline_curses::line_ready(const char *line) { auto_mem expanded; char msg[1024]; int rc; rc = history_expand(rl_line_buffer, expanded.out()); switch (rc) { #if 0 // TODO: fix clash between history and pcre metacharacters case - 1: /* XXX */ snprintf(msg, sizeof(msg), "e:unable to expand history -- %s", expanded.in()); break; #endif case -1: snprintf(msg, sizeof(msg), "d:%s", line); break; case 0: case 1: case 2: /* XXX */ snprintf(msg, sizeof(msg), "d:%s", expanded.in()); break; } write(this->rc_command_pipe[RCF_SLAVE], msg, strlen(msg)); } void readline_curses::check_fd_set(fd_set &ready_rfds) { int rc; if (FD_ISSET(this->rc_pty[RCF_MASTER], &ready_rfds)) { char buffer[128]; rc = read(this->rc_pty[RCF_MASTER], buffer, sizeof(buffer)); assert(rc != 0); assert(rc != -1 && (errno != EINTR || errno != EIO)); { int lpc; fprintf(stderr, "from child %d|", rc); for (lpc = 0; lpc < rc; lpc++) { fprintf(stderr, " %d", buffer[lpc]); } fprintf(stderr, "\n"); } this->map_output(buffer, rc); } if (FD_ISSET(this->rc_command_pipe[RCF_MASTER], &ready_rfds)) { char msg[1024 + 1]; rc = read(this->rc_command_pipe[RCF_MASTER], msg, sizeof(msg) - 1); assert(rc != 0); if (rc >= 2) { string old_value = this->rc_value; msg[rc] = '\0'; fprintf(stderr, "child command: %s\n", msg); this->rc_value = string(&msg[2]); switch (msg[0]) { case 't': if (this->rc_value != old_value) { this->rc_timeout.invoke(this); } break; case 'd': fprintf(stderr, "done!\n"); this->rc_active_context = -1; this->vc_past_lines.clear(); this->rc_perform.invoke(this); break; } } } } void readline_curses::handle_key(int ch) { const char *bch; int len; bch = this->map_input(ch, len); write(this->rc_pty[RCF_MASTER], bch, len); fprintf(stderr, "to child %d\n", bch[0]); if (ch == '\t' || ch == '\r') { this->vc_past_lines.clear(); } } void readline_curses::focus(int context, const char *prompt) { char buffer[1024]; this->rc_active_context = context; snprintf(buffer, sizeof(buffer), "f:%d:%s", context, prompt); write(this->rc_command_pipe[RCF_MASTER], buffer, strlen(buffer) + 1); wmove(this->vc_window, this->get_actual_y(), 0); wclrtoeol(this->vc_window); } void readline_curses::add_possibility(int context, string type, string value) { char buffer[1024]; snprintf(buffer, sizeof(buffer), "ap:%d:%s:%s", context, type.c_str(), value.c_str()); write(this->rc_command_pipe[RCF_MASTER], buffer, strlen(buffer) + 1); } void readline_curses::rem_possibility(int context, string type, string value) { char buffer[1024]; snprintf(buffer, sizeof(buffer), "rp:%d:%s:%s", context, type.c_str(), value.c_str()); write(this->rc_command_pipe[RCF_MASTER], buffer, strlen(buffer) + 1); } void readline_curses::do_update(void) { if (this->rc_active_context == -1) { mvwprintw(this->vc_window, this->get_actual_y(), 0, "%s", this->rc_value.c_str()); wclrtoeol(this->vc_window); this->set_x(0); } vt52_curses::do_update(); }