diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 4121ff9e3..e0ad3606b 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -85,14 +85,14 @@ IF(WIN32) ENDIF() SET(MAIN_SOURCES_LINUX -Console-linux.cpp +Console-posix.cpp Hooks-linux.cpp PlugLoad-linux.cpp Process-linux.cpp ) SET(MAIN_SOURCES_DARWIN -Console-darwin.cpp +Console-posix.cpp PlugLoad-darwin.cpp Process-darwin.cpp Hooks-darwin.cpp diff --git a/library/Console-linux.cpp b/library/Console-linux.cpp deleted file mode 100644 index d4005af3c..000000000 --- a/library/Console-linux.cpp +++ /dev/null @@ -1,838 +0,0 @@ -/* -https://github.com/peterix/dfhack - -A thread-safe logging console with a line editor. - -Based on linenoise: -linenoise -- guerrilla line editing library against the idea that a -line editing lib needs to be 20,000 lines of C code. - -You can find the latest source code at: - - http://github.com/antirez/linenoise - -Does a number of crazy assumptions that happen to be true in 99.9999% of -the 2010 UNIX computers around. - ------------------------------------------------------------------------- - -Copyright (c) 2010, Salvatore Sanfilippo -Copyright (c) 2010, Pieter Noordhuis -Copyright (c) 2011, Petr Mrázek - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// George Vulov for MacOSX -#ifndef __LINUX__ -#define TMP_FAILURE_RETRY(expr) \ - ({ long int _res; \ - do _res = (long int) (expr); \ - while (_res == -1L && errno == EINTR); \ - _res; }) -#endif - -#include "Console.h" -#include "Hooks.h" -using namespace DFHack; - -#include "tinythread.h" -using namespace tthread; - -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 ""; - } -} - -namespace DFHack -{ - class Private - { - public: - Private() - { - dfout_C = NULL; - rawmode = false; - in_batch = false; - supported_terminal = false; - state = con_unclaimed; - }; - virtual ~Private() - { - //sync(); - } - private: - bool read_char(unsigned char & out) - { - FD_ZERO(&descriptor_set); - FD_SET(STDIN_FILENO, &descriptor_set); - FD_SET(exit_pipe[0], &descriptor_set); - int ret = TMP_FAILURE_RETRY( - select (FD_SETSIZE,&descriptor_set, NULL, NULL, NULL) - ); - if(ret == -1) - return false; - if (FD_ISSET(exit_pipe[0], &descriptor_set)) - return false; - if (FD_ISSET(STDIN_FILENO, &descriptor_set)) - { - // read byte from stdin - ret = TMP_FAILURE_RETRY( - read(STDIN_FILENO, &out, 1) - ); - if(ret == -1) - return false; - return true; - } - return false; - } - - public: - void print(const char *data) - { - fputs(data, dfout_C); - } - - void print_text(color_ostream::color_value clr, const std::string &chunk) - { - if(!in_batch && state == con_lineedit) - { - disable_raw(); - fprintf(dfout_C,"\x1b[1G"); - fprintf(dfout_C,"\x1b[0K"); - - color(clr); - print(chunk.c_str()); - - reset_color(); - enable_raw(); - prompt_refresh(); - } - else - { - color(clr); - print(chunk.c_str()); - } - } - - void begin_batch() - { - assert(!in_batch); - - in_batch = true; - - if (state == con_lineedit) - { - disable_raw(); - fprintf(dfout_C,"\x1b[1G"); - fprintf(dfout_C,"\x1b[0K"); - } - } - - void end_batch() - { - assert(in_batch); - - flush(); - - in_batch = false; - - if (state == con_lineedit) - { - reset_color(); - enable_raw(); - prompt_refresh(); - } - } - - void flush() - { - if (!rawmode) - fflush(dfout_C); - } - - /// Clear the console, along with its scrollback - void clear() - { - if(rawmode) - { - const char * clr = "\033c\033[3J\033[H"; - if (::write(STDIN_FILENO,clr,strlen(clr)) == -1) - ; - } - else - { - print("\033c\033[3J\033[H"); - fflush(dfout_C); - } - } - /// Position cursor at x,y. 1,1 = top left corner - void gotoxy(int x, int y) - { - char tmp[64]; - sprintf(tmp,"\033[%d;%dH", y,x); - print(tmp); - } - /// Set color (ANSI color number) - void color(Console::color_value index) - { - if(!rawmode) - fprintf(dfout_C, "%s", getANSIColor(index)); - else - { - const char * colstr = getANSIColor(index); - int lstr = strlen(colstr); - if (::write(STDIN_FILENO,colstr,lstr) == -1) - ; - } - } - /// Reset color to default - void reset_color(void) - { - color(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); - void back_word() - { - if (raw_cursor == 0) - return; - raw_cursor--; - while (raw_cursor > 0 && !isalnum(raw_buffer[raw_cursor])) - raw_cursor--; - while (raw_cursor > 0 && isalnum(raw_buffer[raw_cursor])) - raw_cursor--; - if (!isalnum(raw_buffer[raw_cursor]) && raw_cursor != 0) - raw_cursor++; - prompt_refresh(); - } - void forward_word() - { - int len = raw_buffer.size(); - if (raw_cursor == len) - return; - raw_cursor++; - while (raw_cursor <= len && !isalnum(raw_buffer[raw_cursor])) - raw_cursor++; - while (raw_cursor <= len && isalnum(raw_buffer[raw_cursor])) - raw_cursor++; - if (raw_cursor > len) - raw_cursor = len; - prompt_refresh(); - } - /// A simple line edit (raw mode) - int lineedit(const std::string& prompt, std::string& output, recursive_mutex * lock, CommandHistory & ch) - { - output.clear(); - reset_color(); - this->prompt = prompt; - if (!supported_terminal) - { - print(prompt.c_str()); - fflush(dfout_C); - // FIXME: what do we do here??? - //SDL_recursive_mutexV(lock); - std::getline(std::cin, output); - //SDL_recursive_mutexP(lock); - return output.size(); - } - else - { - int count; - if (enable_raw() == -1) return 0; - if(state == con_lineedit) - return -1; - state = con_lineedit; - count = prompt_loop(lock,ch); - state = con_unclaimed; - disable_raw(); - print("\n"); - if(count != -1) - { - output = raw_buffer; - } - return count; - } - } - int enable_raw() - { - 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) -#ifdef CONSOLE_NO_CATCH - raw.c_lflag &= ~( ECHO | ICANON | IEXTEN ); -#else - raw.c_lflag &= ~( ECHO | ICANON | IEXTEN | ISIG ); -#endif - // 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; - } - - 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(recursive_mutex * lock, CommandHistory & history) - { - 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) - { - unsigned char c; - int isok; - unsigned char seq[2], seq2; - lock->unlock(); - if(!read_char(c)) - { - lock->lock(); - return -2; - } - lock->lock(); - /* 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.remove(); - 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 - lock->unlock(); - if (!read_char(seq[0])) - { - lock->lock(); - return -2; - } - lock->lock(); - if (seq[0] == 'b') - { - back_word(); - } - else if (seq[0] == 'f') - { - forward_word(); - } - else if(seq[0] == '[') - { - if (!read_char(seq[1])) - { - lock->lock(); - return -2; - } - if (seq[1] == 'D') - { - left_arrow: - if (raw_cursor > 0) - { - raw_cursor--; - prompt_refresh(); - } - } - else if ( seq[1] == 'C') - { - right_arrow: - /* right arrow */ - if (size_t(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 (size_t(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 - unsigned char seq3[3]; - lock->unlock(); - if(!read_char(seq2)) - { - lock->lock(); - return -2; - } - lock->lock(); - if (seq[1] == '3' && seq2 == '~' ) - { - // delete - if (raw_buffer.size() > 0 && size_t(raw_cursor) < raw_buffer.size()) - { - raw_buffer.erase(raw_cursor,1); - prompt_refresh(); - } - } - if (!read_char(seq3[0]) || !read_char(seq3[1])) - { - lock->lock(); - return -2; - } - if (seq2 == ';') - { - // Format: esc [ n ; n DIRECTION - // Ignore first character (second "n") - if (seq3[1] == 'C') - { - forward_word(); - } - else if (seq3[1] == 'D') - { - back_word(); - } - } - } - } - break; - default: - if (raw_buffer.size() == size_t(raw_cursor)) - { - raw_buffer.append(1,c); - raw_cursor++; - if (plen+raw_buffer.size() < size_t(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; - bool supported_terminal; - // state variables - bool rawmode; // is raw mode active? - termios orig_termios; // saved/restored by raw mode - // current state - enum console_state - { - con_unclaimed, - con_lineedit - } state; - bool in_batch; - std::string prompt; // current prompt string - std::string raw_buffer; // current raw mode buffer - int raw_cursor; // cursor position in the buffer - // thread exit mechanism - int exit_pipe[2]; - fd_set descriptor_set; - }; -} - -Console::Console() -{ - d = 0; - inited = false; - // we can't create the mutex at this time. the SDL functions aren't hooked yet. - wlock = new recursive_mutex(); -} -Console::~Console() -{ - if(inited) - shutdown(); - if(wlock) - delete wlock; - if(d) - delete d; -} - -bool Console::init(bool sharing) -{ - if(sharing) - { - inited = false; - return false; - } - if (!freopen("stdout.log", "w", stdout)) - ; - d = new Private(); - // make our own weird streams so our IO isn't redirected - d->dfout_C = fopen("/dev/tty", "w"); - std::cin.tie(this); - clear(); - d->supported_terminal = !isUnsupportedTerm() && isatty(STDIN_FILENO); - // init the exit mechanism - if (pipe(d->exit_pipe) == -1) - ; - FD_ZERO(&d->descriptor_set); - FD_SET(STDIN_FILENO, &d->descriptor_set); - FD_SET(d->exit_pipe[0], &d->descriptor_set); - inited = true; - return true; -} - -bool Console::shutdown(void) -{ - if(!d) - return true; - lock_guard g(*wlock); - if(d->rawmode) - d->disable_raw(); - d->print("\n"); - inited = false; - // kill the thing - close(d->exit_pipe[1]); - return true; -} - -void Console::begin_batch() -{ - //color_ostream::begin_batch(); - - wlock->lock(); - - if (inited) - d->begin_batch(); -} - -void Console::end_batch() -{ - if (inited) - d->end_batch(); - - wlock->unlock(); -} - -void Console::flush_proxy() -{ - lock_guard g(*wlock); - if (inited) - d->flush(); -} - -void Console::add_text(color_value color, const std::string &text) -{ - lock_guard g(*wlock); - if (inited) - d->print_text(color, text); - else - fwrite(text.data(), 1, text.size(), stderr); -} - -int Console::get_columns(void) -{ - lock_guard g(*wlock); - int ret = -1; - if(inited) - ret = d->get_columns(); - return ret; -} - -int Console::get_rows(void) -{ - lock_guard g(*wlock); - int ret = -1; - if(inited) - ret = d->get_rows(); - return ret; -} - -void Console::clear() -{ - lock_guard g(*wlock); - if(inited) - d->clear(); -} - -void Console::gotoxy(int x, int y) -{ - lock_guard g(*wlock); - if(inited) - d->gotoxy(x,y); -} - -void Console::cursor(bool enable) -{ - lock_guard g(*wlock); - if(inited) - d->cursor(enable); -} - -int Console::lineedit(const std::string & prompt, std::string & output, CommandHistory & ch) -{ - lock_guard g(*wlock); - int ret = -2; - if(inited) - ret = d->lineedit(prompt,output,wlock,ch); - return ret; -} - -void Console::msleep (unsigned int msec) -{ - if (msec > 1000) sleep(msec/1000000); - usleep((msec % 1000000) * 1000); -} diff --git a/library/Console-darwin.cpp b/library/Console-posix.cpp similarity index 99% rename from library/Console-darwin.cpp rename to library/Console-posix.cpp index f36973d5c..50cebdde7 100644 --- a/library/Console-darwin.cpp +++ b/library/Console-posix.cpp @@ -775,6 +775,8 @@ void Console::add_text(color_value color, const std::string &text) lock_guard g(*wlock); if (inited) d->print_text(color, text); + else + fwrite(text.data(), 1, text.size(), stderr); } int Console::get_columns(void) @@ -829,4 +831,4 @@ void Console::msleep (unsigned int msec) { if (msec > 1000) sleep(msec/1000000); usleep((msec % 1000000) * 1000); -} \ No newline at end of file +}