diff --git a/library/Console-linux.cpp b/library/Console-linux.cpp index c17860efd..e97cc9d30 100644 --- a/library/Console-linux.cpp +++ b/library/Console-linux.cpp @@ -80,42 +80,527 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include using namespace DFHack; -#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 -#define LINENOISE_MAX_LINE 4096 +static int isUnsupportedTerm(void) +{ + static const char *unsupported_term[] = {"dumb","cons25",NULL}; + char *term = getenv("TERM"); + int j; + + if (term == NULL) return 0; + for (j = 0; unsupported_term[j]; j++) + if (!strcasecmp(term,unsupported_term[j])) return 1; + return 0; +} + +const char * ANSI_CLS = "\033[2J"; +const char * ANSI_BLACK = "\033[22;30m"; +const char * ANSI_RED = "\033[22;31m"; +const char * ANSI_GREEN = "\033[22;32m"; +const char * ANSI_BROWN = "\033[22;33m"; +const char * ANSI_BLUE = "\033[22;34m"; +const char * ANSI_MAGENTA = "\033[22;35m"; +const char * ANSI_CYAN = "\033[22;36m"; +const char * ANSI_GREY = "\033[22;37m"; +const char * ANSI_DARKGREY = "\033[01;30m"; +const char * ANSI_LIGHTRED = "\033[01;31m"; +const char * ANSI_LIGHTGREEN = "\033[01;32m"; +const char * ANSI_YELLOW = "\033[01;33m"; +const char * ANSI_LIGHTBLUE = "\033[01;34m"; +const char * ANSI_LIGHTMAGENTA = "\033[01;35m"; +const char * ANSI_LIGHTCYAN = "\033[01;36m"; +const char * ANSI_WHITE = "\033[01;37m"; +const char * RESETCOLOR = "\033[0m"; + +const char * getANSIColor(const int c) +{ + switch (c) + { + case -1: return RESETCOLOR; // HACK! :P + case 0 : return ANSI_BLACK; + case 1 : return ANSI_BLUE; // non-ANSI + case 2 : return ANSI_GREEN; + case 3 : return ANSI_CYAN; // non-ANSI + case 4 : return ANSI_RED; // non-ANSI + case 5 : return ANSI_MAGENTA; + case 6 : return ANSI_BROWN; + case 7 : return ANSI_GREY; + case 8 : return ANSI_DARKGREY; + case 9 : return ANSI_LIGHTBLUE; // non-ANSI + case 10: return ANSI_LIGHTGREEN; + case 11: return ANSI_LIGHTCYAN; // non-ANSI; + case 12: return ANSI_LIGHTRED; // non-ANSI; + case 13: return ANSI_LIGHTMAGENTA; + case 14: return ANSI_YELLOW; // non-ANSI + case 15: return ANSI_WHITE; + default: return ""; + } +} -static const char *unsupported_term[] = {"dumb","cons25",NULL}; namespace DFHack { class Private { public: + enum console_state + { + con_unclaimed, + con_lineedit + }; Private() { - dfout_C = 0; - stream_o = 0; - rawmode = 0; + dfout_C = NULL; + stream_o = NULL; + rawmode = false; + supported_terminal = false; + state = con_unclaimed; }; + /// Print a formatted string, like printf + int print(const char * format, ...) + { + va_list args; + va_start( args, format ); + int ret = vprint( format, args ); + va_end( args ); + return ret; + } + int vprint(const char * format, va_list vl) + { + if(state == con_lineedit) + { + disable_raw(); + fprintf(dfout_C,"\x1b[1G"); + fprintf(dfout_C,"\x1b[0K"); + int ret = vfprintf( dfout_C, format, vl ); + enable_raw(); + prompt_refresh(); + return ret; + } + else return vfprintf( dfout_C, format, vl ); + } + int vprinterr(const char * format, va_list vl) + { + if(state == con_lineedit) + { + disable_raw(); + color(Console::COLOR_LIGHTRED); + fprintf(dfout_C,"\x1b[1G"); + fprintf(dfout_C,"\x1b[0K"); + int ret = vfprintf( dfout_C, format, vl ); + reset_color(); + enable_raw(); + prompt_refresh(); + return ret; + } + else + { + color(Console::COLOR_LIGHTRED); + int ret = vfprintf( dfout_C, format, vl ); + reset_color(); + } + } + /// Print a formatted string, like printf, in red + int printerr(const char * format, ...) + { + va_list args; + va_start( args, format ); + int ret = vprinterr( format, args ); + va_end( args ); + return ret; + } + /// Clear the console, along with its scrollback + void clear() + { + if(rawmode) + { + const char * clr = "\033c\033[3J\033[H"; + ::write(STDIN_FILENO,clr,strlen(clr)); + } + else + { + print("\033c\033[3J\033[H"); + } + } + /// Position cursor at x,y. 1,1 = top left corner + void gotoxy(int x, int y) + { + print("\033[%d;%dH", y,x); + } + /// Set color (ANSI color number) + void color(Console::color_value index) + { + if(!rawmode) + fprintf(dfout_C,getANSIColor(index)); + else + { + const char * colstr = getANSIColor(index); + int lstr = strlen(colstr); + ::write(STDIN_FILENO,colstr,lstr); + } + } + /// Reset color to default + void reset_color(void) + { + color(Console::COLOR_RESET); + if(!rawmode) + fflush(dfout_C); + } + /// Enable or disable the caret/cursor + void cursor(bool enable = true) + { + if(enable) + print("\033[?25h"); + else + print("\033[?25l"); + } + /// Waits given number of milliseconds before continuing. + void msleep(unsigned int msec); + /// get the current number of columns + int get_columns(void) + { + winsize ws; + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) return 80; + return ws.ws_col; + } + /// get the current number of rows + int get_rows(void) + { + winsize ws; + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) return 25; + return ws.ws_row; + } + /// beep. maybe? + //void beep (void); + /// A simple line edit (raw mode) + int lineedit(const std::string& prompt, std::string& output) + { + output.clear(); + this->prompt = prompt; + if (!supported_terminal) + { + print(prompt.c_str()); + fflush(dfout_C); + // FIXME: what do we do here??? + //SDL_mutexV(wlock); + std::getline(std::cin, output); + //SDL_mutexP(wlock); + return output.size(); + } + else + { + int count; + if (enable_raw() == -1) return 0; + if(state == con_lineedit) + return -1; + state = con_lineedit; + count = prompt_loop(); + state = con_unclaimed; + disable_raw(); + print("\n"); + if(count != -1) + { + output = raw_buffer; + } + return count; + } + } + /// add a command to the history + void history_add(const std::string& command) + { + // if current command = last in history -> do not add. Always add if history is empty. + if(!history.empty() && history.front() == command) + return; + history.push_front(command); + if(history.size() > 100) + history.pop_back(); + } + /// clear the command history + void history_clear(); - int isUnsupportedTerm(void) + int enable_raw() { - char *term = getenv("TERM"); - int j; + struct termios raw; + + if (!supported_terminal) + return -1; + if (tcgetattr(STDIN_FILENO,&orig_termios) == -1) + return -1; + + raw = orig_termios; //modify the original mode + // input modes: no break, no CR to NL, no parity check, no strip char, + // no start/stop output control. + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + // output modes - disable post processing + raw.c_oflag &= ~(OPOST); + // control modes - set 8 bit chars + raw.c_cflag |= (CS8); + // local modes - choing off, canonical off, no extended functions, + // no signal chars (^Z,^C) + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + // control chars - set return condition: min number of bytes and timer. + // We want read to return every single byte, without timeout. + raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0;// 1 byte, no timer + // put terminal in raw mode after flushing + if (tcsetattr(STDIN_FILENO,TCSAFLUSH,&raw) < 0) + return -1; + rawmode = 1; + return 0; + } - if (term == NULL) return 0; - for (j = 0; unsupported_term[j]; j++) - if (!strcasecmp(term,unsupported_term[j])) return 1; - return 0; + void disable_raw() + { + /* Don't even check the return value as it's too late. */ + if (rawmode && tcsetattr(STDIN_FILENO,TCSAFLUSH,&orig_termios) != -1) + rawmode = 0; + } + void prompt_refresh() + { + char seq[64]; + int cols = get_columns(); + int plen = prompt.size(); + const char * buf = raw_buffer.c_str(); + int len = raw_buffer.size(); + int cooked_cursor = raw_cursor; + // Use math! This is silly. + while((plen+cooked_cursor) >= cols) + { + buf++; + len--; + cooked_cursor--; + } + while (plen+len > cols) + { + len--; + } + /* Cursor to left edge */ + snprintf(seq,64,"\x1b[1G"); + if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return; + /* Write the prompt and the current buffer content */ + if (::write(STDIN_FILENO,prompt.c_str(),plen) == -1) return; + if (::write(STDIN_FILENO,buf,len) == -1) return; + /* Erase to right */ + snprintf(seq,64,"\x1b[0K"); + if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return; + /* Move cursor to original position. */ + snprintf(seq,64,"\x1b[1G\x1b[%dC", (int)(cooked_cursor+plen)); + if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return; } + int prompt_loop() + { + int fd = STDIN_FILENO; + size_t plen = prompt.size(); + int history_index = 0; + raw_buffer.clear(); + raw_cursor = 0; + /* The latest history entry is always our current buffer, that + * initially is just an empty string. */ + const std::string empty; + history_add(empty); + if (::write(fd,prompt.c_str(),prompt.size()) == -1) return -1; + while(1) + { + char c; + int nread; + char seq[2], seq2[2]; + SDL_mutexV(wlock); + nread = ::read(fd,&c,1); + SDL_mutexP(wlock); + if (nread <= 0) return raw_buffer.size(); + + /* Only autocomplete when the callback is set. It returns < 0 when + * there was an error reading from fd. Otherwise it will return the + * character that should be handled next. */ + if (c == 9) + { + /* + if( completionCallback != NULL) { + c = completeLine(fd,prompt,buf,buflen,&len,&pos,cols); + // Return on errors + if (c < 0) return len; + // Read next character when 0 + if (c == 0) continue; + } + else + { + // ignore tab + continue; + } + */ + // just ignore tabs + continue; + } + + switch(c) + { + case 13: // enter + history.pop_front(); + return raw_buffer.size(); + case 3: // ctrl-c + errno = EAGAIN; + return -1; + case 127: // backspace + case 8: // ctrl-h + if (raw_cursor > 0 && raw_buffer.size() > 0) + { + raw_buffer.erase(raw_cursor-1,1); + raw_cursor--; + prompt_refresh(); + } + break; + case 27: // escape sequence + SDL_mutexV(wlock); + if (::read(fd,seq,2) == -1) + { + SDL_mutexP(wlock); + break; + } + SDL_mutexP(wlock); + if(seq[0] == '[') + { + if (seq[1] == 'D') + { + left_arrow: + if (raw_cursor > 0) + { + raw_cursor--; + prompt_refresh(); + } + } + else if ( seq[1] == 'C') + { + right_arrow: + /* right arrow */ + if (raw_cursor != raw_buffer.size()) + { + raw_cursor++; + prompt_refresh(); + } + } + else if (seq[1] == 'A' || seq[1] == 'B') + { + /* up and down arrow: history */ + if (history.size() > 1) + { + /* Update the current history entry before to + * overwrite it with tne next one. */ + history[history_index] = raw_buffer; + /* Show the new entry */ + history_index += (seq[1] == 'A') ? 1 : -1; + if (history_index < 0) + { + history_index = 0; + break; + } + else if (history_index >= history.size()) + { + history_index = history.size()-1; + break; + } + raw_buffer = history[history_index]; + raw_cursor = raw_buffer.size(); + prompt_refresh(); + } + } + else if(seq[1] == 'H') + { + // home + raw_cursor = 0; + prompt_refresh(); + } + else if(seq[1] == 'F') + { + // end + raw_cursor = raw_buffer.size(); + prompt_refresh(); + } + else if (seq[1] > '0' && seq[1] < '7') + { + // extended escape + SDL_mutexV(wlock); + if (::read(fd,seq2,2) == -1) + { + SDL_mutexP(wlock); + break; + } + SDL_mutexP(wlock); + if (seq2[0] == '~' && seq[1] == '3') + { + // delete + if (raw_buffer.size() > 0 && raw_cursor < raw_buffer.size()) + { + raw_buffer.erase(raw_cursor,1); + prompt_refresh(); + } + } + } + } + break; + default: + if (raw_buffer.size() == raw_cursor) + { + raw_buffer.append(1,c); + raw_cursor++; + if (plen+raw_buffer.size() < get_columns()) + { + /* Avoid a full update of the line in the + * trivial case. */ + if (::write(fd,&c,1) == -1) return -1; + } + else + { + prompt_refresh(); + } + } + else + { + raw_buffer.insert(raw_cursor,1,c); + raw_cursor++; + prompt_refresh(); + } + break; + case 21: // Ctrl+u, delete the whole line. + raw_buffer.clear(); + raw_cursor = 0; + prompt_refresh(); + break; + case 11: // Ctrl+k, delete from current to end of line. + raw_buffer.erase(raw_cursor); + prompt_refresh(); + break; + case 1: // Ctrl+a, go to the start of the line + raw_cursor = 0; + prompt_refresh(); + break; + case 5: // ctrl+e, go to the end of the line + raw_cursor = raw_buffer.size(); + prompt_refresh(); + break; + case 12: // ctrl+l, clear screen + clear(); + prompt_refresh(); + } + } + return raw_buffer.size(); + } FILE * dfout_C; duthomhas::stdiobuf * stream_o; - termios orig_termios; /* in order to restore at exit */ - int rawmode; /* for atexit() function to check if restore is needed*/ std::deque history; + bool supported_terminal; + // state variables + bool rawmode; // is raw mode active? + termios orig_termios; // saved/restored by raw mode + int state; // current state + std::string prompt; // current prompt string + std::string raw_buffer; // current raw mode buffer + int raw_cursor; // cursor position in the buffer + // locks + SDL::Mutex *wlock; }; } + Console::Console():std::ostream(0), std::ios(0) { d = 0; @@ -132,131 +617,85 @@ bool Console::init(void) // make our own weird streams so our IO isn't redirected d->dfout_C = fopen("/dev/tty", "w"); d->stream_o = new duthomhas::stdiobuf(d->dfout_C); + d->wlock = SDL_CreateMutex(); rdbuf(d->stream_o); std::cin.tie(this); clear(); + d->supported_terminal = !isUnsupportedTerm() && isatty(STDIN_FILENO); } bool Console::shutdown(void) { if(d->rawmode) - disable_raw(); + d->disable_raw(); print("\n"); } int Console::print( const char* format, ... ) { va_list args; + SDL_mutexP(d->wlock); va_start( args, format ); - int ret = vfprintf( d->dfout_C, format, args ); - va_end( args ); + int ret = d->vprint(format, args); + va_end(args); + SDL_mutexV(d->wlock); return ret; } - int Console::printerr( const char* format, ... ) { - color(12); va_list args; + SDL_mutexP(d->wlock); va_start( args, format ); - int ret = vfprintf( d->dfout_C, format, args ); - va_end( args ); - reset_color(); + int ret = d->vprinterr(format, args); + va_end(args); + SDL_mutexV(d->wlock); return ret; } int Console::get_columns(void) { - winsize ws; - if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) return 80; - return ws.ws_col; + return d->get_columns(); } int Console::get_rows(void) { - winsize ws; - if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) return 25; - return ws.ws_row; + return d->get_rows(); } void Console::clear() { - if(d->rawmode) - { - const char * clr = "\033c\033[3J\033[H"; - ::write(STDIN_FILENO,clr,strlen(clr)); - } - else - { - print("\033c\033[3J\033[H"); - } + SDL_mutexP(d->wlock); + d->clear(); + SDL_mutexV(d->wlock); } void Console::gotoxy(int x, int y) { - print("\033[%d;%dH", y,x); -} - -const char * ANSI_CLS = "\033[2J"; -const char * ANSI_BLACK = "\033[22;30m"; -const char * ANSI_RED = "\033[22;31m"; -const char * ANSI_GREEN = "\033[22;32m"; -const char * ANSI_BROWN = "\033[22;33m"; -const char * ANSI_BLUE = "\033[22;34m"; -const char * ANSI_MAGENTA = "\033[22;35m"; -const char * ANSI_CYAN = "\033[22;36m"; -const char * ANSI_GREY = "\033[22;37m"; -const char * ANSI_DARKGREY = "\033[01;30m"; -const char * ANSI_LIGHTRED = "\033[01;31m"; -const char * ANSI_LIGHTGREEN = "\033[01;32m"; -const char * ANSI_YELLOW = "\033[01;33m"; -const char * ANSI_LIGHTBLUE = "\033[01;34m"; -const char * ANSI_LIGHTMAGENTA = "\033[01;35m"; -const char * ANSI_LIGHTCYAN = "\033[01;36m"; -const char * ANSI_WHITE = "\033[01;37m"; -const char * RESETCOLOR = "\033[0m"; - -const char * getANSIColor(const int c) -{ - switch (c) - { - case 0 : return ANSI_BLACK; - case 1 : return ANSI_BLUE; // non-ANSI - case 2 : return ANSI_GREEN; - case 3 : return ANSI_CYAN; // non-ANSI - case 4 : return ANSI_RED; // non-ANSI - case 5 : return ANSI_MAGENTA; - case 6 : return ANSI_BROWN; - case 7 : return ANSI_GREY; - case 8 : return ANSI_DARKGREY; - case 9 : return ANSI_LIGHTBLUE; // non-ANSI - case 10: return ANSI_LIGHTGREEN; - case 11: return ANSI_LIGHTCYAN; // non-ANSI; - case 12: return ANSI_LIGHTRED; // non-ANSI; - case 13: return ANSI_LIGHTMAGENTA; - case 14: return ANSI_YELLOW; // non-ANSI - case 15: return ANSI_WHITE; - default: return ""; - } + SDL_mutexP(d->wlock); + d->gotoxy(x,y); + SDL_mutexV(d->wlock); } -void Console::color(int index) +void Console::color(color_value index) { - print(getANSIColor(index)); + SDL_mutexP(d->wlock); + d->color(index); + SDL_mutexV(d->wlock); } void Console::reset_color( void ) { - print(RESETCOLOR); - fflush(d->dfout_C); + SDL_mutexP(d->wlock); + d->reset_color(); + SDL_mutexV(d->wlock); } void Console::cursor(bool enable) { - if(enable) - print("\033[?25h"); - else - print("\033[?25l"); + SDL_mutexP(d->wlock); + d->cursor(enable); + SDL_mutexV(d->wlock); } void Console::msleep (unsigned int msec) @@ -265,289 +704,18 @@ void Console::msleep (unsigned int msec) usleep((msec % 1000000) * 1000); } -int Console::enable_raw() -{ - struct termios raw; - - if (!isatty(STDIN_FILENO)) goto fatal; - if (tcgetattr(STDIN_FILENO,&d->orig_termios) == -1) goto fatal; - - raw = d->orig_termios; /* modify the original mode */ - /* input modes: no break, no CR to NL, no parity check, no strip char, - * no start/stop output control. */ - raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); - /* output modes - disable post processing */ - raw.c_oflag &= ~(OPOST); - /* control modes - set 8 bit chars */ - raw.c_cflag |= (CS8); - /* local modes - choing off, canonical off, no extended functions, - * no signal chars (^Z,^C) */ - raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); - /* control chars - set return condition: min number of bytes and timer. - * We want read to return every single byte, without timeout. */ - raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ - - /* put terminal in raw mode after flushing */ - if (tcsetattr(STDIN_FILENO,TCSAFLUSH,&raw) < 0) goto fatal; - d->rawmode = 1; - return 0; - -fatal: - errno = ENOTTY; - return -1; -} - -void Console::disable_raw() -{ - /* Don't even check the return value as it's too late. */ - if (d->rawmode && tcsetattr(STDIN_FILENO,TCSAFLUSH,&d->orig_termios) != -1) - d->rawmode = 0; -} - -void Console::prompt_refresh( const std::string& prompt, const std::string& buffer, size_t pos) -{ - char seq[64]; - int cols = get_columns(); - int plen = prompt.size(); - const char * buf = buffer.c_str(); - int len = buffer.size(); - // Use math! This is silly. - while((plen+pos) >= cols) - { - buf++; - len--; - pos--; - } - while (plen+len > cols) - { - len--; - } - /* Cursor to left edge */ - snprintf(seq,64,"\x1b[1G"); - if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return; - /* Write the prompt and the current buffer content */ - if (::write(STDIN_FILENO,prompt.c_str(),plen) == -1) return; - if (::write(STDIN_FILENO,buf,len) == -1) return; - /* Erase to right */ - snprintf(seq,64,"\x1b[0K"); - if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return; - /* Move cursor to original position. */ - snprintf(seq,64,"\x1b[1G\x1b[%dC", (int)(pos+plen)); - if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return; -} - -int Console::prompt_loop(const std::string & prompt, std::string & buffer) -{ - int fd = STDIN_FILENO; - size_t plen = prompt.size(); - size_t pos = 0; - size_t cols = get_columns(); - int history_index = 0; - - /* The latest history entry is always our current buffer, that - * initially is just an empty string. */ - const std::string empty; - history_add(empty); - if (::write(fd,prompt.c_str(),plen) == -1) return -1; - while(1) - { - char c; - int nread; - char seq[2], seq2[2]; - - nread = ::read(fd,&c,1); - if (nread <= 0) return buffer.size(); - - /* Only autocomplete when the callback is set. It returns < 0 when - * there was an error reading from fd. Otherwise it will return the - * character that should be handled next. */ - if (c == 9) - { - /* - if( completionCallback != NULL) { - c = completeLine(fd,prompt,buf,buflen,&len,&pos,cols); - // Return on errors - if (c < 0) return len; - // Read next character when 0 - if (c == 0) continue; - } - else - { - // ignore tab - continue; - } - */ - // just ignore tabs - continue; - } - - switch(c) - { - case 13: /* enter */ - d->history.pop_front(); - return buffer.size(); - case 3: /* ctrl-c */ - errno = EAGAIN; - return -1; - case 127: /* backspace */ - case 8: /* ctrl-h */ - if (pos > 0 && buffer.size() > 0) - { - buffer.erase(pos-1,1); - pos--; - prompt_refresh(prompt,buffer,pos); - } - break; - case 27: /* escape sequence */ - if (::read(fd,seq,2) == -1) break; - if(seq[0] == '[') - { - if (seq[1] == 'D') - { - left_arrow: - if (pos > 0) - { - pos--; - prompt_refresh(prompt,buffer,pos); - } - } - else if ( seq[1] == 'C') - { - right_arrow: - /* right arrow */ - if (pos != buffer.size()) - { - pos++; - prompt_refresh(prompt,buffer,pos); - } - } - else if (seq[1] == 'A' || seq[1] == 'B') - { - /* up and down arrow: history */ - if (d->history.size() > 1) - { - /* Update the current history entry before to - * overwrite it with tne next one. */ - d->history[history_index] = buffer; - /* Show the new entry */ - history_index += (seq[1] == 'A') ? 1 : -1; - if (history_index < 0) - { - history_index = 0; - break; - } - else if (history_index >= d->history.size()) - { - history_index = d->history.size()-1; - break; - } - buffer = d->history[history_index]; - pos = buffer.size(); - prompt_refresh(prompt,buffer,pos); - } - } - else if(seq[1] == 'H') - { - // home - pos = 0; - prompt_refresh(prompt,buffer,pos); - } - else if(seq[1] == 'F') - { - // end - pos = buffer.size(); - prompt_refresh(prompt,buffer,pos); - } - else if (seq[1] > '0' && seq[1] < '7') - { - // extended escape - if (::read(fd,seq2,2) == -1) break; - if (seq2[0] == '~' && seq[1] == '3') - { - // delete - if (buffer.size() > 0 && pos < buffer.size()) - { - buffer.erase(pos,1); - prompt_refresh(prompt,buffer,pos); - } - } - } - } - break; - default: - if (buffer.size() == pos) - { - buffer.append(1,c); - pos++; - if (plen+buffer.size() < cols) - { - /* Avoid a full update of the line in the - * trivial case. */ - if (::write(fd,&c,1) == -1) return -1; - } - else - { - prompt_refresh(prompt,buffer,pos); - } - } - else - { - buffer.insert(pos,1,c); - pos++; - prompt_refresh(prompt,buffer,pos); - } - break; - case 21: // Ctrl+u, delete the whole line. - buffer.clear(); - pos = 0; - prompt_refresh(prompt,buffer,pos); - break; - case 11: // Ctrl+k, delete from current to end of line. - buffer.erase(pos); - prompt_refresh(prompt,buffer,pos); - break; - case 1: // Ctrl+a, go to the start of the line - pos = 0; - prompt_refresh(prompt,buffer,pos); - break; - case 5: // ctrl+e, go to the end of the line - pos = buffer.size(); - prompt_refresh(prompt,buffer,pos); - break; - case 12: // ctrl+l, clear screen - clear(); - prompt_refresh(prompt,buffer,pos); - } - } - return buffer.size(); -} // push to front, remove from back if we are above maximum. ignore immediate duplicates void Console::history_add(const std::string & command) { - if(!d->history.empty() && d->history.front() == command) - return; - d->history.push_front(command); - if(d->history.size() > 100) - d->history.pop_back(); + SDL_mutexP(d->wlock); + d->history_add(command); + SDL_mutexV(d->wlock); } int Console::lineedit(const std::string & prompt, std::string & output) { - output.clear(); - int count; - if (d->isUnsupportedTerm() || !isatty(STDIN_FILENO)) - { - print(prompt.c_str()); - fflush(d->dfout_C); - std::getline(std::cin, output); - return output.size(); - } - else - { - if (enable_raw() == -1) return 0; - count = prompt_loop(prompt, output); - disable_raw(); - print("\n"); - return output.size(); - } + SDL_mutexP(d->wlock); + int ret = d->lineedit(prompt,output); + SDL_mutexV(d->wlock); + return ret; } \ No newline at end of file diff --git a/library/include/dfhack/Console.h b/library/include/dfhack/Console.h index 628cc8eca..5c9cf07e4 100644 --- a/library/include/dfhack/Console.h +++ b/library/include/dfhack/Console.h @@ -32,12 +32,36 @@ namespace DFHack class DFHACK_EXPORT Console : public std::ostream { public: + enum color_value + { + COLOR_RESET = -1, + COLOR_BLACK = 0, + COLOR_BLUE, + COLOR_GREEN, + COLOR_CYAN, + COLOR_RED, + COLOR_MAGENTA, + COLOR_BROWN, + COLOR_GREY, + COLOR_DARKGREY, + COLOR_LIGHTBLUE, + COLOR_LIGHTGREEN, + COLOR_LIGHTCYAN, + COLOR_LIGHTRED, + COLOR_LIGHTMAGENTA, + COLOR_YELLOW, + COLOR_WHITE, + COLOR_MAX = COLOR_WHITE + }; + ///ctor, NOT thread-safe Console(); + ///dtor, NOT thread-safe ~Console(); - /// initialize the console + /// initialize the console. NOT thread-safe bool init( void ); - /// shutdown the console + /// shutdown the console. NOT thread-safe bool shutdown( void ); + /// Print a formatted string, like printf int print(const char * format, ...); /// Print a formatted string, like printf, in red @@ -47,7 +71,7 @@ namespace DFHack /// Position cursor at x,y. 1,1 = top left corner void gotoxy(int x, int y); /// Set color (ANSI color number) - void color(int index); + void color(color_value c); /// Reset color to default void reset_color(void); /// Enable or disable the caret/cursor @@ -64,12 +88,9 @@ namespace DFHack int lineedit(const std::string& prompt, std::string& output); /// add a command to the history void history_add(const std::string& command); - private: - int prompt_loop(const std::string & prompt, std::string & buffer); - void prompt_refresh( const std::string & prompt, const std::string & buffer, size_t pos); - int enable_raw(); - void disable_raw(); + /// clear the command history void history_clear(); + private: Private * d; }; } \ No newline at end of file diff --git a/package/linux/dfhack b/package/linux/dfhack index 3b6708f95..d5d221879 100755 --- a/package/linux/dfhack +++ b/package/linux/dfhack @@ -10,31 +10,31 @@ cd "${DF_DIR}" export SDL_DISABLE_LOCK_KEYS=1 # Work around for bug in Debian/Ubuntu SDL patch. #export SDL_VIDEO_CENTERED=1 # Centre the screen. Messes up resizing. -export LD_PRELOAD=./libdfhack.so - case "$1" in -g | --gdb) shift - gdb ./libs/Dwarf_Fortress $* + echo "set environment LD_PRELOAD=./libdfhack.so" > gdbcmd.tmp + gdb -x gdbcmd.tmp ./libs/Dwarf_Fortress $* + rm gdbcmd.tmp ret=$? ;; -h | --helgrind) shift - valgrind --tool=helgrind --log-file=helgrind.log ./libs/Dwarf_Fortress $* + LD_PRELOAD=./libdfhack.so valgrind --tool=helgrind --log-file=helgrind.log ./libs/Dwarf_Fortress $* ret=$? ;; -v | --valgrind) shift - valgrind --log-file=valgrind.log ./libs/Dwarf_Fortress $* + LD_PRELOAD=./libdfhack.so valgrind --log-file=valgrind.log ./libs/Dwarf_Fortress $* ret=$? ;; *) - ./libs/Dwarf_Fortress $* + LD_PRELOAD=./libdfhack.so ./libs/Dwarf_Fortress $* ret=$? ;; esac # Reset terminal to sane state in case of a crash -reset -I +reset exit $ret diff --git a/plugins/kittens.cpp b/plugins/kittens.cpp index 0c8dffb0d..c6c4728a7 100644 --- a/plugins/kittens.cpp +++ b/plugins/kittens.cpp @@ -50,7 +50,7 @@ DFhackCExport command_result plugin_onupdate ( Core * c ) uint64_t time2 = GetTimeMs64(); uint64_t delta = time2-timeLast; timeLast = time2; - c->con << "Time delta = " << delta << " ms" << std::endl; + c->con.print("Time delta = %d ms\n", delta); } return CR_OK; } @@ -66,7 +66,7 @@ DFhackCExport command_result ktimer (Core * c, vector & parameters) c->Suspend(); c->Resume(); uint64_t timeend = GetTimeMs64(); - c->con << "Time to suspend = " << timeend - timestart << " ms" << std::endl; + c->con.print("Time to suspend = %d ms\n",timeend - timestart); timeLast = timeend; timering = true; return CR_OK; @@ -111,7 +111,7 @@ DFhackCExport command_result kittens (Core * c, vector & parameters) }; con.cursor(false); con.clear(); - int color = 1; + Console::color_value color = Console::COLOR_BLUE; while(1) { if(shutdown_flag) @@ -135,8 +135,8 @@ DFhackCExport command_result kittens (Core * c, vector & parameters) } con.flush(); con.msleep(60); - color ++; - if(color > 15) - color = 1; + ((int&)color) ++; + if(color > Console::COLOR_MAX) + color = Console::COLOR_BLUE; } } diff --git a/plugins/reveal.cpp b/plugins/reveal.cpp index efdf61339..d387b0997 100644 --- a/plugins/reveal.cpp +++ b/plugins/reveal.cpp @@ -104,7 +104,7 @@ DFhackCExport command_result reveal(DFHack::Core * c, std::vector & Console & con = c->con; if(revealed != NOT_REVEALED) { - con << "Map is already revealed or this is a different map." << std::endl; + con.printerr("Map is already revealed or this is a different map.\n"); return CR_FAILURE; } @@ -115,20 +115,20 @@ DFhackCExport command_result reveal(DFHack::Core * c, std::vector & World->ReadGameMode(gm); if(gm.g_mode != GAMEMODE_DWARF) { - con << "Only in fortress mode." << std::endl; + con.printerr("Only in fortress mode.\n"); c->Resume(); return CR_FAILURE; } if(!Maps->Start()) { - con << "Can't init map." << std::endl; + con.printerr("Can't init map.\n"); c->Resume(); return CR_FAILURE; } if(no_hell && !Maps->StartFeatures()) { - con << "Unable to read local features; can't reveal map safely" << std::endl; + con.printerr("Unable to read local features; can't reveal map safely.\n"); c->Resume(); return CR_FAILURE; } @@ -175,10 +175,10 @@ DFhackCExport command_result reveal(DFHack::Core * c, std::vector & World->SetPauseState(true); } c->Resume(); - con << "Map revealed." << std::endl; + con.print("Map revealed.\n"); if(!no_hell) - con << "Unpausing can unleash the forces of hell, so it has been temporarily disabled." << std::endl; - con << "Run 'unreveal' to revert to previous state." << std::endl; + con.print("Unpausing can unleash the forces of hell, so it has been temporarily disabled.\n"); + con.print("Run 'unreveal' to revert to previous state.\n"); return CR_OK; } @@ -187,7 +187,7 @@ DFhackCExport command_result unreveal(DFHack::Core * c, std::vector Console & con = c->con; if(!revealed) { - con << "There's nothing to revert!" << std::endl; + con.printerr("There's nothing to revert!\n"); return CR_FAILURE; } c->Suspend(); @@ -197,14 +197,14 @@ DFhackCExport command_result unreveal(DFHack::Core * c, std::vector World->ReadGameMode(gm); if(gm.g_mode != GAMEMODE_DWARF) { - con << "Only in fortress mode." << std::endl; + con.printerr("Only in fortress mode.\n"); c->Resume(); return CR_FAILURE; } Maps = c->getMaps(); if(!Maps->Start()) { - con << "Can't init map." << std::endl; + con.printerr("Can't init map.\n"); c->Resume(); return CR_FAILURE; } @@ -214,7 +214,7 @@ DFhackCExport command_result unreveal(DFHack::Core * c, std::vector Maps->getSize(x_max_b,y_max_b,z_max_b); if(x_max != x_max_b || y_max != y_max_b || z_max != z_max_b) { - con << "The map is not of the same size..." << std::endl; + con.printerr("The map is not of the same size...\n"); c->Resume(); return CR_FAILURE; } @@ -233,7 +233,7 @@ DFhackCExport command_result unreveal(DFHack::Core * c, std::vector // give back memory. hidesaved.clear(); revealed = NOT_REVEALED; - con << "Map hidden!" << std::endl; + con.print("Map hidden!\n"); c->Resume(); return CR_OK; } diff --git a/plugins/vdig.cpp b/plugins/vdig.cpp index 459bf2240..eed39c875 100644 --- a/plugins/vdig.cpp +++ b/plugins/vdig.cpp @@ -51,7 +51,7 @@ DFhackCExport command_result vdig (Core * c, vector & parameters) // init the map if(!Maps->Start()) { - con << "Can't init map. Make sure you have a map loaded in DF.\n"; + con.printerr("Can't init map. Make sure you have a map loaded in DF.\n"); c->Resume(); return CR_FAILURE; } @@ -63,14 +63,14 @@ DFhackCExport command_result vdig (Core * c, vector & parameters) Gui->getCursorCoords(cx,cy,cz); while(cx == -30000) { - con << "Cursor is not active. Point the cursor at a vein.\n"; + con.printerr("Cursor is not active. Point the cursor at a vein.\n"); c->Resume(); return CR_FAILURE; } DFHack::DFCoord xy ((uint32_t)cx,(uint32_t)cy,cz); if(xy.x == 0 || xy.x == tx_max - 1 || xy.y == 0 || xy.y == ty_max - 1) { - con << "I won't dig the borders. That would be cheating!\n"; + con.printerr("I won't dig the borders. That would be cheating!\n"); c->Resume(); return CR_FAILURE; } @@ -80,7 +80,7 @@ DFhackCExport command_result vdig (Core * c, vector & parameters) int16_t veinmat = MCache->veinMaterialAt(xy); if( veinmat == -1 ) { - con << "This tile is not a vein.\n"; + con.printerr("This tile is not a vein.\n"); delete MCache; c->Resume(); return CR_FAILURE;