diff --git a/LICENSE b/LICENSE index 7998532ab..68b70dcf3 100644 --- a/LICENSE +++ b/LICENSE @@ -139,4 +139,46 @@ DAMAGE. * IN NO EVENT SHALL TONI RONKKO BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file + * OTHER DEALINGS IN THE SOFTWARE. + +------------------------------------------------------------------ +Parts of dfhack are based on linenoise: +linenoise.c -- 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 + +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. diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 25e4bb2ab..803ade583 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -151,6 +151,9 @@ IF(UNIX) COMMAND ${CMAKE_COMMAND} -E make_directory ${DFHACK_OUTPUT_DIR} COMMAND ${CMAKE_COMMAND} -E copy ${dfhack_SOURCE_DIR}/package/linux/dfhack ${DFHACK_OUTPUT_DIR}) ADD_DEPENDENCIES(dfhack prepare_UNIX) + install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack + DESTINATION ${DFHACK_LIBRARY_DESTINATION}) #linux: share/dfhack + ENDIF() install(TARGETS dfhack diff --git a/library/Console-linux.cpp b/library/Console-linux.cpp index 4313d4d8e..6a846828c 100644 --- a/library/Console-linux.cpp +++ b/library/Console-linux.cpp @@ -22,42 +22,178 @@ must not be misrepresented as being the original software. distribution. */ +/* +Parts of this code are based on linenoise: +linenoise.c -- 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 + +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 "dfhack/Console.h" #include #include #include +#include +#include +#include +#include +#include +#include +#include #include +#include using namespace DFHack; -duthomhas::stdiostream dfout; -FILE * dfout_C = 0; -duthomhas::stdiobuf * stream_o = 0; +#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 +#define LINENOISE_MAX_LINE 4096 + +static const char *unsupported_term[] = {"dumb","cons25",NULL}; +namespace DFHack +{ + class Private + { + public: + Private() + { + dfout_C = 0; + stream_o = 0; + rawmode = 0; + history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; + history_len = 0; + history = NULL; + }; + int get_columns(void) + { + winsize ws; + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) return 80; + return ws.ws_col; + } + int get_rows(void) + { + winsize ws; + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) return 25; + return ws.ws_row; + } + void clear() + { + const char * clr = "\033c\033[3J\033[H"; + write(STDIN_FILENO,clr,strlen(clr)); + } + + int isUnsupportedTerm(void) + { + 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; + } + + 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*/ + int history_max_len; + int history_len; + char **history; + }; +} // FIXME: prime candidate for being a singleton... Console::Console() +{ + d = new Private(); +} +Console::~Console() +{ + delete d; +} + +bool Console::init(void) { // make our own weird streams so our IO isn't redirected - dfout_C = fopen("/dev/tty", "w"); - stream_o = new duthomhas::stdiobuf(dfout_C); - dfout.rdbuf(stream_o); - std::cin.tie(&dfout); + d->dfout_C = fopen("/dev/tty", "w"); + d->stream_o = new duthomhas::stdiobuf(d->dfout_C); + rdbuf(d->stream_o); + std::cin.tie(this); clear(); - // result is a terminal controlled by the parasitic code! } -Console::~Console() + +bool Console::shutdown(void) { - + if(d->rawmode) + disable_raw(); + *this << std::endl; } + +int Console::print( const char* format, ... ) +{ + va_list args; + va_start( args, format ); + int ret = vfprintf( d->dfout_C, format, args ); + va_end( args ); + return ret; +} + +int Console::get_columns(void) +{ + return d->get_columns(); +} + +int Console::get_rows(void) +{ + return d->get_rows(); +} + void Console::clear() { - dfout << "\033c"; - dfout << "\033[3J\033[H"; + *this << "\033c"; + *this << "\033[3J\033[H"; } + void Console::gotoxy(int x, int y) { std::ostringstream oss; oss << "\033[" << y << ";" << x << "H"; - dfout << oss.str(); + *this << oss.str(); } const char * ANSI_CLS = "\033[2J"; @@ -105,12 +241,12 @@ const char * getANSIColor(const int c) void Console::color(int index) { - dfout << getANSIColor(index); + *this << getANSIColor(index); } void Console::reset_color( void ) { - dfout << RESETCOLOR; + *this << RESETCOLOR; } @@ -118,11 +254,11 @@ void Console::cursor(bool enable) { if(enable) { - dfout <<"\033[?25h"; + *this <<"\033[?25h"; } else { - dfout <<"\033[?25l"; + *this <<"\033[?25l"; } } @@ -130,4 +266,327 @@ void Console::msleep (unsigned int msec) { if (msec > 1000) sleep(msec/1000000); 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 = d->get_columns(); + int history_index = 0; + + /* The latest history entry is always our current buffer, that + * initially is just an empty string. */ + history_add(""); + + 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 */ + 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; + // I fail to see how is this useful to anyone but hardcore emacs users + /* + case 4: // ctrl-d, remove char at right of cursor + if (len > 1 && pos < (len-1)) { + memmove(buf+pos,buf+pos+1,len-pos); + len--; + buf[len] = '\0'; + prompt_refresh(prompt,buffer,pos); + } else if (len == 0) { + history_len--; + free(history[history_len]); + return -1; + } + break; + case 20: // ctrl-t + if (pos > 0 && pos < len) { + int aux = buf[pos-1]; + buf[pos-1] = buf[pos]; + buf[pos] = aux; + if (pos != len-1) pos++; + prompt_refresh(prompt,buffer,pos); + } + break; + case 2: // ctrl-b + goto left_arrow; + case 6: // ctrl-f + goto right_arrow; + case 16: // ctrl-p + seq[1] = 65; + goto up_down_arrow; + case 14: // ctrl-n + seq[1] = 66; + goto up_down_arrow; + 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_down_arrow: + /* 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] = 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; + } + buffer = 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(history.front() == command) + return; + history.push_front(command); + if(history.size() > 100) + history.pop_back(); +} + +int Console::lineedit(const std::string & prompt, std::string & output) +{ + output.clear(); + int count; + + if (d->isUnsupportedTerm() || !isatty(STDIN_FILENO)) + { + *this << prompt; + flush(); + std::getline(std::cin, output); + return output.size(); + } + else + { + if (enable_raw() == -1) return 0; + count = prompt_loop(prompt, output); + disable_raw(); + *this << std::endl; + return output.size(); + } } \ No newline at end of file diff --git a/library/Core.cpp b/library/Core.cpp index ea8d26084..72e607c54 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -125,46 +125,44 @@ int fIOthread(void * iodata) PluginManager * plug_mgr = ((IODATA*) iodata)->plug_mgr; if(plug_mgr == 0 || core == 0) { - dfout << "Something horrible happened to the plugin manager in Core's constructor..." << std::endl; + core->con << "Something horrible happened to the plugin manager in Core's constructor..." << std::endl; return 0; } - fprintf(dfout_C,"DFHack is ready. Have a nice day! Type in '?' or 'help' for help.\n"); + core->con.print("DFHack is ready. Have a nice day! Type in '?' or 'help' for help.\n"); //dfterm << << endl; int clueless_counter = 0; while (true) { string command = ""; - //dfout <<"[DFHack]# "; - char * line = linenoise("[DFHack]# ", dfout_C); + core->con.lineedit("[DFHack]# ",command); + core->con.history_add(command); + //core->con <<"[DFHack]# "; + //char * line = linenoise("[DFHack]# ", core->con.dfout_C); // dfout <<"[DFHack]# "; - if(line) + /* + if (line) { command=line; linenoiseHistoryAdd(line); free(line); - } + }*/ //getline(cin, command); - if (cin.eof()) - { - command = "q"; - dfout << std::endl; // No newline from the user here! - } if(command=="help" || command == "?") { - dfout << "Available commands" << endl; - dfout << "------------------" << endl; + core->con << "Available commands" << endl; + core->con << "------------------" << endl; for(int i = 0; i < plug_mgr->size();i++) { const Plugin * plug = (plug_mgr->operator[](i)); if(!plug->size()) continue; - dfout << "Plugin " << plug->getName() << " :" << std::endl; + core->con << "Plugin " << plug->getName() << " :" << std::endl; for (int j = 0; j < plug->size();j++) { const PluginCommand & pcmd = (plug->operator[](j)); - dfout << setw(12) << pcmd.name << "| " << pcmd.description << endl; + core->con << setw(12) << pcmd.name << "| " << pcmd.description << endl; } - dfout << endl; + core->con << endl; } } else if( command == "" ) @@ -186,18 +184,18 @@ int fIOthread(void * iodata) command_result res = plug_mgr->InvokeCommand(first, parts); if(res == CR_NOT_IMPLEMENTED) { - dfout << "Invalid command." << endl; + core->con << "Invalid command." << endl; clueless_counter ++; } else if(res == CR_FAILURE) { - dfout << "ERROR!" << endl; + core->con << "ERROR!" << endl; } } } if(clueless_counter == 3) { - dfout << "Do 'help' or '?' for the list of available commands." << endl; + core->con << "Do 'help' or '?' for the list of available commands." << endl; clueless_counter = 0; } } @@ -206,7 +204,6 @@ int fIOthread(void * iodata) Core::Core() { // init the console. This must be always the first step! - con = 0; plug_mgr = 0; vif = 0; p = 0; @@ -228,13 +225,13 @@ Core::Core() bool Core::Init() { // init the console. This must be always the first step! - con = new Console(); + con.init(); // find out what we are... vif = new DFHack::VersionInfoFactory("Memory.xml"); p = new DFHack::Process(vif); if (!p->isIdentified()) { - dfout << "Couldn't identify this version of DF." << std::endl; + con << "Couldn't identify this version of DF." << std::endl; errorstate = true; delete p; p = NULL; @@ -246,7 +243,7 @@ bool Core::Init() AccessMutex = SDL_CreateMutex(); if(!AccessMutex) { - dfout << "Mutex creation failed." << std::endl; + con << "Mutex creation failed." << std::endl; errorstate = true; return false; } @@ -255,7 +252,7 @@ bool Core::Init() plug_mgr = new PluginManager(this); if(!plug_mgr) { - dfout << "Failed to create the Plugin Manager." << std::endl; + con << "Failed to create the Plugin Manager." << std::endl; errorstate = true; return false; } @@ -367,11 +364,7 @@ int Core::Shutdown ( void ) } allModules.clear(); memset(&(s_mods), 0, sizeof(s_mods)); - dfout << std::endl; - // kill the console object - if(con) - delete con; - con = 0; + con.shutdown(); return -1; } diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index be57ca1bd..77515f891 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -47,7 +47,6 @@ static int getdir (string dir, vector &files) struct dirent *dirp; if((dp = opendir(dir.c_str())) == NULL) { - dfout << "Error(" << errno << ") opening " << dir << endl; return errno; } while ((dirp = readdir(dp)) != NULL) { @@ -81,20 +80,20 @@ Plugin::Plugin(Core * core, const std::string & file) DFLibrary * plug = OpenPlugin(file.c_str()); if(!plug) { - dfout << "Can't load plugin " << filename << endl; + core->con << "Can't load plugin " << filename << endl; return; } const char * (*_PlugName)() =(const char * (*)()) LookupPlugin(plug, "plugin_name"); if(!_PlugName) { - dfout << "Plugin " << filename << " has no name." << endl; + core->con << "Plugin " << filename << " has no name." << endl; ClosePlugin(plug); return; } plugin_init = (command_result (*)(Core *, std::vector &)) LookupPlugin(plug, "plugin_init"); if(!plugin_init) { - dfout << "Plugin " << filename << " has no init function." << endl; + core->con << "Plugin " << filename << " has no init function." << endl; ClosePlugin(plug); return; } diff --git a/library/depends/libnoise/linenoise.cpp b/library/depends/libnoise/linenoise.cpp index 863590f53..5cbf48256 100644 --- a/library/depends/libnoise/linenoise.cpp +++ b/library/depends/libnoise/linenoise.cpp @@ -112,7 +112,8 @@ char **history = NULL; static void linenoiseAtExit(void); int linenoiseHistoryAdd(const char *line); -static int isUnsupportedTerm(void) { +static int isUnsupportedTerm(void) +{ char *term = getenv("TERM"); int j; @@ -286,7 +287,47 @@ void linenoiseClearScreen(void) { /* nothing to do, just to avoid warning. */ } } - +/* +static int selread(int fd, char *buf, size_t buflen) +{ + int n; + int start = 0; + fd_set input; + struct timeval timeout; + // Initialize the input set + FD_ZERO(&input); + FD_SET(fd, &input); + + repeat_select: + timeout.tv_sec = 1; + timeout.tv_usec = 0; + n = select(fd+1, &input, NULL, NULL, &timeout); + if(n == 0 || n == -1 && errno == EINTR) + { + // lock mutex + // copy end variable + // unlock mutex + // if end, return -2 + // else + goto repeat_select; + } + else if (n > 0) + { + int nread = read(fd,buf + start,buflen); + if (nread == -1) + return -1; // it's an ugly error + else if(nread < buflen) + { + start += nread; + buflen -= nread; + goto repeat_select; + } + else return nread + start; + } + else + return -1; +} +*/ static int linenoisePrompt(int fd, char *buf, size_t buflen, const char *prompt) { size_t plen = strlen(prompt); size_t pos = 0; @@ -538,7 +579,8 @@ static int linenoiseRaw(char *buf, size_t buflen, const char *prompt, FILE * out return count; } -char *linenoise(const char *prompt, FILE * out) { +char *linenoise(const char *prompt, FILE * out) +{ char buf[LINENOISE_MAX_LINE]; int count; @@ -575,11 +617,13 @@ void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { } /* Using a circular buffer is smarter, but a bit more complex to handle. */ -int linenoiseHistoryAdd(const char *line) { +int linenoiseHistoryAdd(const char *line) +{ char *linecopy; if (history_max_len == 0) return 0; - if (history == NULL) { + if (history == NULL) + { history = (char**)malloc(sizeof(char*)*history_max_len); if (history == NULL) return 0; memset(history,0,(sizeof(char*)*history_max_len)); @@ -596,7 +640,8 @@ int linenoiseHistoryAdd(const char *line) { return 1; } -int linenoiseHistorySetMaxLen(int len) { +int linenoiseHistorySetMaxLen(int len) +{ char **newHistory; if (len < 1) return 0; diff --git a/library/include/dfhack/Console.h b/library/include/dfhack/Console.h index faa29c866..de8cfdca9 100644 --- a/library/include/dfhack/Console.h +++ b/library/include/dfhack/Console.h @@ -25,19 +25,23 @@ distribution. #pragma once #include "dfhack/Pragma.h" #include "dfhack/Export.h" -#include "dfhack/Core.h" #include "dfhack/extra/stdiostream.h" - -extern DFHACK_EXPORT duthomhas::stdiostream dfout; -extern DFHACK_EXPORT FILE * dfout_C; +#include namespace DFHack { - class DFHACK_EXPORT Console + class Private; + class DFHACK_EXPORT Console : public duthomhas::stdiostream { public: Console(); ~Console(); + /// initialize the console + bool init( void ); + /// shutdown the console + bool shutdown( void ); + /// Print a formatted string, like printf + int print(const char * format, ...); /// Clear the console, along with its scrollback void clear(); /// Position cursor at x,y. 1,1 = top left corner @@ -50,5 +54,23 @@ namespace DFHack void cursor(bool enable = true); /// Waits given number of milliseconds before continuing. void msleep(unsigned int msec); + /// get the current number of columns + int get_columns(void); + /// get the current number of rows + int get_rows(void); + /// beep. maybe? + //void beep (void); + /// A simple line edit (raw mode) + 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(); + std::deque history; + void history_clear(); + Private * d; }; } \ No newline at end of file diff --git a/library/include/dfhack/Core.h b/library/include/dfhack/Core.h index 1f9615d9e..381d68194 100644 --- a/library/include/dfhack/Core.h +++ b/library/include/dfhack/Core.h @@ -31,7 +31,7 @@ distribution. #include #include #include - +#include "dfhack/Console.h" namespace DFHack { @@ -112,7 +112,7 @@ namespace DFHack DFHack::Process * p; DFHack::VersionInfo * vinfo; - DFHack::Console * con; + DFHack::Console con; private: Core(); bool Init(); diff --git a/plugins/cleanmap.cpp b/plugins/cleanmap.cpp index 01cb530e5..132b8bd4f 100644 --- a/plugins/cleanmap.cpp +++ b/plugins/cleanmap.cpp @@ -51,7 +51,7 @@ DFhackCExport command_result cleanmap (Core * c, vector & parameters) // init the map if(!Mapz->Start()) { - dfout << "Can't init map." << std::endl; + c->con << "Can't init map." << std::endl; c->Resume(); return CR_FAILURE; } diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index 639f1900d..9c78264d4 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -41,7 +41,7 @@ struct compare_pair_second }; -void printMats(MatMap &mat, std::vector &materials) +void printMats(DFHack::Console & con, MatMap &mat, std::vector &materials) { unsigned int total = 0; MatSorter sorting_vector; @@ -54,15 +54,15 @@ void printMats(MatMap &mat, std::vector &materials) { if(it->first >= materials.size()) { - dfout << "Bad index: " << it->first << " out of " << materials.size() << endl; + con << "Bad index: " << it->first << " out of " << materials.size() << endl; continue; } DFHack::t_matgloss mat = materials[it->first]; - dfout << std::setw(25) << mat.id << " : " << it->second << std::endl; + con << std::setw(25) << mat.id << " : " << it->second << std::endl; total += it->second; } - dfout << ">>> TOTAL = " << total << std::endl << std::endl; + con << ">>> TOTAL = " << total << std::endl << std::endl; } DFhackCExport command_result prospector (Core * c, vector & parameters); @@ -98,7 +98,7 @@ DFhackCExport command_result prospector (DFHack::Core * c, vector & par DFHack::Maps *maps = c->getMaps(); if (!maps->Start()) { - dfout << "Cannot get map info!" << std::endl; + c->con << "Cannot get map info!" << std::endl; c->Resume(); return CR_FAILURE; } @@ -108,13 +108,13 @@ DFhackCExport command_result prospector (DFHack::Core * c, vector & par DFHack::Materials *mats = c->getMaterials(); if (!mats->ReadInorganicMaterials()) { - dfout << "Unable to read inorganic material definitons!" << std::endl; + c->con << "Unable to read inorganic material definitons!" << std::endl; c->Resume(); return CR_FAILURE; } if (showPlants && !mats->ReadOrganicMaterials()) { - dfout << "Unable to read organic material definitons; plants won't be listed!" << std::endl; + c->con << "Unable to read organic material definitons; plants won't be listed!" << std::endl; showPlants = false; } @@ -134,21 +134,21 @@ DFhackCExport command_result prospector (DFHack::Core * c, vector & par if (!(showSlade && maps->ReadGlobalFeatures(globalFeatures))) { - dfout << "Unable to read global features; slade won't be listed!" << std::endl; + c->con << "Unable to read global features; slade won't be listed!" << std::endl; } if (!maps->ReadLocalFeatures(localFeatures)) { - dfout << "Unable to read local features; adamantine " - << (showTemple ? "and demon temples " : "") - << "won't be listed!" << std::endl; + c->con << "Unable to read local features; adamantine " + << (showTemple ? "and demon temples " : "") + << "won't be listed!" << std::endl; } uint32_t vegCount = 0; DFHack::Vegetation *veg = c->getVegetation(); if (showPlants && !veg->Start()) { - dfout << "Unable to read vegetation; plants won't be listed!" << std::endl; + c->con << "Unable to read vegetation; plants won't be listed!" << std::endl; } for(uint32_t z = 0; z < z_max; z++) @@ -217,7 +217,7 @@ DFhackCExport command_result prospector (DFHack::Core * c, vector & par if (!info) { - dfout << "Bad type: " << type << std::endl; + c->con << "Bad type: " << type << std::endl; continue; } @@ -306,39 +306,39 @@ DFhackCExport command_result prospector (DFHack::Core * c, vector & par MatMap::const_iterator it; - dfout << "Base materials:" << std::endl; + c->con << "Base materials:" << std::endl; for (it = baseMats.begin(); it != baseMats.end(); ++it) { - dfout << std::setw(25) << DFHack::TileMaterialString[it->first] << " : " << it->second << std::endl; + c->con << std::setw(25) << DFHack::TileMaterialString[it->first] << " : " << it->second << std::endl; } - dfout << std::endl << "Layer materials:" << std::endl; - printMats(layerMats, mats->inorganic); + c->con << std::endl << "Layer materials:" << std::endl; + printMats(c->con, layerMats, mats->inorganic); - dfout << "Vein materials:" << std::endl; - printMats(veinMats, mats->inorganic); + c->con << "Vein materials:" << std::endl; + printMats(c->con, veinMats, mats->inorganic); if (showPlants) { - dfout << "Shrubs:" << std::endl; - printMats(plantMats, mats->organic); - dfout << "Wood in trees:" << std::endl; - printMats(treeMats, mats->organic); + c->con << "Shrubs:" << std::endl; + printMats(c->con, plantMats, mats->organic); + c->con << "Wood in trees:" << std::endl; + printMats(c->con, treeMats, mats->organic); } if (hasAquifer) { - dfout << "Has aquifer" << std::endl; + c->con << "Has aquifer" << std::endl; } if (hasDemonTemple) { - dfout << "Has demon temple" << std::endl; + c->con << "Has demon temple" << std::endl; } if (hasLair) { - dfout << "Has lair" << std::endl; + c->con << "Has lair" << std::endl; } // Cleanup @@ -349,6 +349,6 @@ DFhackCExport command_result prospector (DFHack::Core * c, vector & par mats->Finish(); maps->Finish(); c->Resume(); - dfout << std::endl; + c->con << std::endl; return CR_OK; } diff --git a/plugins/reveal.cpp b/plugins/reveal.cpp index 01cefb0de..474cdee50 100644 --- a/plugins/reveal.cpp +++ b/plugins/reveal.cpp @@ -104,7 +104,7 @@ DFhackCExport command_result reveal(DFHack::Core * c, std::vector & if(revealed != NOT_REVEALED) { - dfout << "Map is already revealed or this is a different map." << std::endl; + c->con << "Map is already revealed or this is a different map." << std::endl; 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) { - dfout << "Only in fortress mode." << std::endl; + c->con << "Only in fortress mode." << std::endl; c->Resume(); return CR_FAILURE; } if(!Maps->Start()) { - dfout << "Can't init map." << std::endl; + c->con << "Can't init map." << std::endl; c->Resume(); return CR_FAILURE; } if(no_hell && !Maps->StartFeatures()) { - dfout << "Unable to read local features; can't reveal map safely" << std::endl; + c->con << "Unable to read local features; can't reveal map safely" << std::endl; c->Resume(); return CR_FAILURE; } @@ -175,10 +175,10 @@ DFhackCExport command_result reveal(DFHack::Core * c, std::vector & World->SetPauseState(true); } c->Resume(); - dfout << "Map revealed." << std::endl; + c->con << "Map revealed." << std::endl; if(!no_hell) - dfout << "Unpausing can unleash the forces of hell, so it has been temporarily disabled." << std::endl; - dfout << "Run 'unreveal' to revert to previous state." << std::endl; + c->con << "Unpausing can unleash the forces of hell, so it has been temporarily disabled." << std::endl; + c->con << "Run 'unreveal' to revert to previous state." << std::endl; return CR_OK; } @@ -187,7 +187,7 @@ DFhackCExport command_result unreveal(DFHack::Core * c, std::vector DFHack::designations40d designations; if(!revealed) { - dfout << "There's nothing to revert!" << std::endl; + c->con << "There's nothing to revert!" << std::endl; 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) { - dfout << "Only in fortress mode." << std::endl; + c->con << "Only in fortress mode." << std::endl; c->Resume(); return CR_FAILURE; } Maps = c->getMaps(); if(!Maps->Start()) { - dfout << "Can't init map." << std::endl; + c->con << "Can't init map." << std::endl; 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) { - dfout << "The map is not of the same size..." << std::endl; + c->con << "The map is not of the same size..." << std::endl; 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; - dfout << "Map hidden!" << std::endl; + c->con << "Map hidden!" << std::endl; c->Resume(); return CR_OK; } diff --git a/plugins/vdig.cpp b/plugins/vdig.cpp index 934825fbc..5fcbed96c 100644 --- a/plugins/vdig.cpp +++ b/plugins/vdig.cpp @@ -49,7 +49,7 @@ DFhackCExport command_result vdig (Core * c, vector & parameters) // init the map if(!Maps->Start()) { - dfout << "Can't init map. Make sure you have a map loaded in DF." << std::endl; + c->con << "Can't init map. Make sure you have a map loaded in DF.\n"; c->Resume(); return CR_FAILURE; } @@ -61,14 +61,14 @@ DFhackCExport command_result vdig (Core * c, vector & parameters) Gui->getCursorCoords(cx,cy,cz); while(cx == -30000) { - dfout << "Cursor is not active. Point the cursor at a vein." << std::endl; + c->con << "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) { - dfout << "I won't dig the borders. That would be cheating!" << std::endl; + c->con << "I won't dig the borders. That would be cheating!\n"; c->Resume(); return CR_FAILURE; } @@ -78,12 +78,12 @@ DFhackCExport command_result vdig (Core * c, vector & parameters) int16_t veinmat = MCache->veinMaterialAt(xy); if( veinmat == -1 ) { - dfout << "This tile is not a vein." << std::endl; + c->con << "This tile is not a vein.\n"; delete MCache; c->Resume(); return CR_FAILURE; } - fprintf(dfout_C,"%d/%d/%d tiletype: %d, veinmat: %d, designation: 0x%x ... DIGGING!\n", cx,cy,cz, tt, veinmat, des.whole); + c->con.print("%d/%d/%d tiletype: %d, veinmat: %d, designation: 0x%x ... DIGGING!\n", cx,cy,cz, tt, veinmat, des.whole); stack flood; flood.push(xy); diff --git a/plugins/weather.cpp b/plugins/weather.cpp index cf2833ab4..0e2ed180b 100644 --- a/plugins/weather.cpp +++ b/plugins/weather.cpp @@ -58,7 +58,7 @@ DFhackCExport command_result weather (Core * c, vector & parameters) } if(lock && unlock) { - dfout << "Lock or unlock? DECIDE!" << std::endl; + c->con << "Lock or unlock? DECIDE!" << std::endl; return CR_FAILURE; } int cnt = 0; @@ -67,7 +67,7 @@ DFhackCExport command_result weather (Core * c, vector & parameters) cnt += clear; if(cnt > 1) { - dfout << "Rain, snow or clear sky? DECIDE!" << std::endl; + c->con << "Rain, snow or clear sky? DECIDE!" << std::endl; return CR_FAILURE; } bool something = lock || unlock || rain || snow || clear; @@ -75,14 +75,14 @@ DFhackCExport command_result weather (Core * c, vector & parameters) DFHack::World * w = c->getWorld(); if(!w->wmap) { - dfout << "Weather support seems broken :(" << std::endl; + c->con << "Weather support seems broken :(" << std::endl; c->Resume(); return CR_FAILURE; } if(!something) { // paint weather map - dfout << "Weather map (C = clear, R = rain, S = snow):" << std::endl; + c->con << "Weather map (C = clear, R = rain, S = snow):" << std::endl; for(int y = 0; y<5;y++) { for(int x = 0; x<5;x++) @@ -90,20 +90,20 @@ DFhackCExport command_result weather (Core * c, vector & parameters) switch((*w->wmap)[x][y]) { case DFHack::CLEAR: - dfout << "C "; + c->con << "C "; break; case DFHack::RAINING: - dfout << "R "; + c->con << "R "; break; case DFHack::SNOWING: - dfout << "S "; + c->con << "S "; break; default: - dfout << (int) (*w->wmap)[x][y] << " "; + c->con << (int) (*w->wmap)[x][y] << " "; break; } } - dfout << std::endl; + c->con << std::endl; } } else @@ -111,17 +111,17 @@ DFhackCExport command_result weather (Core * c, vector & parameters) // weather changing action! if(rain) { - dfout << "Here comes the rain." << std::endl; + c->con << "Here comes the rain." << std::endl; w->SetCurrentWeather(RAINING); } if(snow) { - dfout << "Snow everywhere!" << std::endl; + c->con << "Snow everywhere!" << std::endl; w->SetCurrentWeather(SNOWING); } if(clear) { - dfout << "Suddenly, sunny weather!" << std::endl; + c->con << "Suddenly, sunny weather!" << std::endl; w->SetCurrentWeather(CLEAR); } // FIXME: weather lock needs map ID to work reliably... needs to be implemented.