Made the linux console super awesome. The dfhack script won't LD_PRELOAD dfhack for gdb.

develop
Petr Mrázek 2011-07-15 15:55:01 +02:00
parent 0af631aaa3
commit 459d48d75a
6 changed files with 594 additions and 405 deletions

@ -80,42 +80,527 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <termios.h> #include <termios.h>
#include <errno.h> #include <errno.h>
#include <deque> #include <deque>
#include <dfhack/FakeSDL.h>
using namespace DFHack; using namespace DFHack;
#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 static int isUnsupportedTerm(void)
#define LINENOISE_MAX_LINE 4096 {
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 namespace DFHack
{ {
class Private class Private
{ {
public: public:
enum console_state
{
con_unclaimed,
con_lineedit
};
Private() Private()
{ {
dfout_C = 0; dfout_C = NULL;
stream_o = 0; stream_o = NULL;
rawmode = 0; 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"); struct termios raw;
int j;
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; void disable_raw()
for (j = 0; unsupported_term[j]; j++) {
if (!strcasecmp(term,unsupported_term[j])) return 1; /* Don't even check the return value as it's too late. */
return 0; 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; FILE * dfout_C;
duthomhas::stdiobuf * stream_o; 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 <std::string> history; std::deque <std::string> 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) Console::Console():std::ostream(0), std::ios(0)
{ {
d = 0; d = 0;
@ -132,131 +617,85 @@ bool Console::init(void)
// make our own weird streams so our IO isn't redirected // make our own weird streams so our IO isn't redirected
d->dfout_C = fopen("/dev/tty", "w"); d->dfout_C = fopen("/dev/tty", "w");
d->stream_o = new duthomhas::stdiobuf(d->dfout_C); d->stream_o = new duthomhas::stdiobuf(d->dfout_C);
d->wlock = SDL_CreateMutex();
rdbuf(d->stream_o); rdbuf(d->stream_o);
std::cin.tie(this); std::cin.tie(this);
clear(); clear();
d->supported_terminal = !isUnsupportedTerm() && isatty(STDIN_FILENO);
} }
bool Console::shutdown(void) bool Console::shutdown(void)
{ {
if(d->rawmode) if(d->rawmode)
disable_raw(); d->disable_raw();
print("\n"); print("\n");
} }
int Console::print( const char* format, ... ) int Console::print( const char* format, ... )
{ {
va_list args; va_list args;
SDL_mutexP(d->wlock);
va_start( args, format ); va_start( args, format );
int ret = vfprintf( d->dfout_C, format, args ); int ret = d->vprint(format, args);
va_end( args ); va_end(args);
SDL_mutexV(d->wlock);
return ret; return ret;
} }
int Console::printerr( const char* format, ... ) int Console::printerr( const char* format, ... )
{ {
color(12);
va_list args; va_list args;
SDL_mutexP(d->wlock);
va_start( args, format ); va_start( args, format );
int ret = vfprintf( d->dfout_C, format, args ); int ret = d->vprinterr(format, args);
va_end( args ); va_end(args);
reset_color(); SDL_mutexV(d->wlock);
return ret; return ret;
} }
int Console::get_columns(void) int Console::get_columns(void)
{ {
winsize ws; return d->get_columns();
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) return 80;
return ws.ws_col;
} }
int Console::get_rows(void) int Console::get_rows(void)
{ {
winsize ws; return d->get_rows();
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) return 25;
return ws.ws_row;
} }
void Console::clear() void Console::clear()
{ {
if(d->rawmode) SDL_mutexP(d->wlock);
{ d->clear();
const char * clr = "\033c\033[3J\033[H"; SDL_mutexV(d->wlock);
::write(STDIN_FILENO,clr,strlen(clr));
}
else
{
print("\033c\033[3J\033[H");
}
} }
void Console::gotoxy(int x, int y) void Console::gotoxy(int x, int y)
{ {
print("\033[%d;%dH", y,x); SDL_mutexP(d->wlock);
} d->gotoxy(x,y);
SDL_mutexV(d->wlock);
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 "";
}
} }
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 ) void Console::reset_color( void )
{ {
print(RESETCOLOR); SDL_mutexP(d->wlock);
fflush(d->dfout_C); d->reset_color();
SDL_mutexV(d->wlock);
} }
void Console::cursor(bool enable) void Console::cursor(bool enable)
{ {
if(enable) SDL_mutexP(d->wlock);
print("\033[?25h"); d->cursor(enable);
else SDL_mutexV(d->wlock);
print("\033[?25l");
} }
void Console::msleep (unsigned int msec) void Console::msleep (unsigned int msec)
@ -265,289 +704,18 @@ void Console::msleep (unsigned int msec)
usleep((msec % 1000000) * 1000); 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 // push to front, remove from back if we are above maximum. ignore immediate duplicates
void Console::history_add(const std::string & command) void Console::history_add(const std::string & command)
{ {
if(!d->history.empty() && d->history.front() == command) SDL_mutexP(d->wlock);
return; d->history_add(command);
d->history.push_front(command); SDL_mutexV(d->wlock);
if(d->history.size() > 100)
d->history.pop_back();
} }
int Console::lineedit(const std::string & prompt, std::string & output) int Console::lineedit(const std::string & prompt, std::string & output)
{ {
output.clear(); SDL_mutexP(d->wlock);
int count; int ret = d->lineedit(prompt,output);
if (d->isUnsupportedTerm() || !isatty(STDIN_FILENO)) SDL_mutexV(d->wlock);
{ return ret;
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();
}
} }

@ -32,12 +32,36 @@ namespace DFHack
class DFHACK_EXPORT Console : public std::ostream class DFHACK_EXPORT Console : public std::ostream
{ {
public: 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(); Console();
///dtor, NOT thread-safe
~Console(); ~Console();
/// initialize the console /// initialize the console. NOT thread-safe
bool init( void ); bool init( void );
/// shutdown the console /// shutdown the console. NOT thread-safe
bool shutdown( void ); bool shutdown( void );
/// Print a formatted string, like printf /// Print a formatted string, like printf
int print(const char * format, ...); int print(const char * format, ...);
/// Print a formatted string, like printf, in red /// Print a formatted string, like printf, in red
@ -47,7 +71,7 @@ namespace DFHack
/// Position cursor at x,y. 1,1 = top left corner /// Position cursor at x,y. 1,1 = top left corner
void gotoxy(int x, int y); void gotoxy(int x, int y);
/// Set color (ANSI color number) /// Set color (ANSI color number)
void color(int index); void color(color_value c);
/// Reset color to default /// Reset color to default
void reset_color(void); void reset_color(void);
/// Enable or disable the caret/cursor /// Enable or disable the caret/cursor
@ -64,12 +88,9 @@ namespace DFHack
int lineedit(const std::string& prompt, std::string& output); int lineedit(const std::string& prompt, std::string& output);
/// add a command to the history /// add a command to the history
void history_add(const std::string& command); void history_add(const std::string& command);
private: /// clear the command history
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();
void history_clear(); void history_clear();
private:
Private * d; Private * d;
}; };
} }

@ -10,31 +10,31 @@ cd "${DF_DIR}"
export SDL_DISABLE_LOCK_KEYS=1 # Work around for bug in Debian/Ubuntu SDL patch. 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 SDL_VIDEO_CENTERED=1 # Centre the screen. Messes up resizing.
export LD_PRELOAD=./libdfhack.so
case "$1" in case "$1" in
-g | --gdb) -g | --gdb)
shift 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=$? ret=$?
;; ;;
-h | --helgrind) -h | --helgrind)
shift 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=$? ret=$?
;; ;;
-v | --valgrind) -v | --valgrind)
shift shift
valgrind --log-file=valgrind.log ./libs/Dwarf_Fortress $* LD_PRELOAD=./libdfhack.so valgrind --log-file=valgrind.log ./libs/Dwarf_Fortress $*
ret=$? ret=$?
;; ;;
*) *)
./libs/Dwarf_Fortress $* LD_PRELOAD=./libdfhack.so ./libs/Dwarf_Fortress $*
ret=$? ret=$?
;; ;;
esac esac
# Reset terminal to sane state in case of a crash # Reset terminal to sane state in case of a crash
reset -I reset
exit $ret exit $ret

@ -50,7 +50,7 @@ DFhackCExport command_result plugin_onupdate ( Core * c )
uint64_t time2 = GetTimeMs64(); uint64_t time2 = GetTimeMs64();
uint64_t delta = time2-timeLast; uint64_t delta = time2-timeLast;
timeLast = time2; timeLast = time2;
c->con << "Time delta = " << delta << " ms" << std::endl; c->con.print("Time delta = %d ms\n", delta);
} }
return CR_OK; return CR_OK;
} }
@ -66,7 +66,7 @@ DFhackCExport command_result ktimer (Core * c, vector <string> & parameters)
c->Suspend(); c->Suspend();
c->Resume(); c->Resume();
uint64_t timeend = GetTimeMs64(); 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; timeLast = timeend;
timering = true; timering = true;
return CR_OK; return CR_OK;
@ -111,7 +111,7 @@ DFhackCExport command_result kittens (Core * c, vector <string> & parameters)
}; };
con.cursor(false); con.cursor(false);
con.clear(); con.clear();
int color = 1; Console::color_value color = Console::COLOR_BLUE;
while(1) while(1)
{ {
if(shutdown_flag) if(shutdown_flag)
@ -135,8 +135,8 @@ DFhackCExport command_result kittens (Core * c, vector <string> & parameters)
} }
con.flush(); con.flush();
con.msleep(60); con.msleep(60);
color ++; ((int&)color) ++;
if(color > 15) if(color > Console::COLOR_MAX)
color = 1; color = Console::COLOR_BLUE;
} }
} }

@ -104,7 +104,7 @@ DFhackCExport command_result reveal(DFHack::Core * c, std::vector<std::string> &
Console & con = c->con; Console & con = c->con;
if(revealed != NOT_REVEALED) 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; return CR_FAILURE;
} }
@ -115,20 +115,20 @@ DFhackCExport command_result reveal(DFHack::Core * c, std::vector<std::string> &
World->ReadGameMode(gm); World->ReadGameMode(gm);
if(gm.g_mode != GAMEMODE_DWARF) if(gm.g_mode != GAMEMODE_DWARF)
{ {
con << "Only in fortress mode." << std::endl; con.printerr("Only in fortress mode.\n");
c->Resume(); c->Resume();
return CR_FAILURE; return CR_FAILURE;
} }
if(!Maps->Start()) if(!Maps->Start())
{ {
con << "Can't init map." << std::endl; con.printerr("Can't init map.\n");
c->Resume(); c->Resume();
return CR_FAILURE; return CR_FAILURE;
} }
if(no_hell && !Maps->StartFeatures()) 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(); c->Resume();
return CR_FAILURE; return CR_FAILURE;
} }
@ -175,10 +175,10 @@ DFhackCExport command_result reveal(DFHack::Core * c, std::vector<std::string> &
World->SetPauseState(true); World->SetPauseState(true);
} }
c->Resume(); c->Resume();
con << "Map revealed." << std::endl; con.print("Map revealed.\n");
if(!no_hell) if(!no_hell)
con << "Unpausing can unleash the forces of hell, so it has been temporarily disabled." << std::endl; con.print("Unpausing can unleash the forces of hell, so it has been temporarily disabled.\n");
con << "Run 'unreveal' to revert to previous state." << std::endl; con.print("Run 'unreveal' to revert to previous state.\n");
return CR_OK; return CR_OK;
} }
@ -187,7 +187,7 @@ DFhackCExport command_result unreveal(DFHack::Core * c, std::vector<std::string>
Console & con = c->con; Console & con = c->con;
if(!revealed) if(!revealed)
{ {
con << "There's nothing to revert!" << std::endl; con.printerr("There's nothing to revert!\n");
return CR_FAILURE; return CR_FAILURE;
} }
c->Suspend(); c->Suspend();
@ -197,14 +197,14 @@ DFhackCExport command_result unreveal(DFHack::Core * c, std::vector<std::string>
World->ReadGameMode(gm); World->ReadGameMode(gm);
if(gm.g_mode != GAMEMODE_DWARF) if(gm.g_mode != GAMEMODE_DWARF)
{ {
con << "Only in fortress mode." << std::endl; con.printerr("Only in fortress mode.\n");
c->Resume(); c->Resume();
return CR_FAILURE; return CR_FAILURE;
} }
Maps = c->getMaps(); Maps = c->getMaps();
if(!Maps->Start()) if(!Maps->Start())
{ {
con << "Can't init map." << std::endl; con.printerr("Can't init map.\n");
c->Resume(); c->Resume();
return CR_FAILURE; return CR_FAILURE;
} }
@ -214,7 +214,7 @@ DFhackCExport command_result unreveal(DFHack::Core * c, std::vector<std::string>
Maps->getSize(x_max_b,y_max_b,z_max_b); 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) 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(); c->Resume();
return CR_FAILURE; return CR_FAILURE;
} }
@ -233,7 +233,7 @@ DFhackCExport command_result unreveal(DFHack::Core * c, std::vector<std::string>
// give back memory. // give back memory.
hidesaved.clear(); hidesaved.clear();
revealed = NOT_REVEALED; revealed = NOT_REVEALED;
con << "Map hidden!" << std::endl; con.print("Map hidden!\n");
c->Resume(); c->Resume();
return CR_OK; return CR_OK;
} }

@ -51,7 +51,7 @@ DFhackCExport command_result vdig (Core * c, vector <string> & parameters)
// init the map // init the map
if(!Maps->Start()) 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(); c->Resume();
return CR_FAILURE; return CR_FAILURE;
} }
@ -63,14 +63,14 @@ DFhackCExport command_result vdig (Core * c, vector <string> & parameters)
Gui->getCursorCoords(cx,cy,cz); Gui->getCursorCoords(cx,cy,cz);
while(cx == -30000) 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(); c->Resume();
return CR_FAILURE; return CR_FAILURE;
} }
DFHack::DFCoord xy ((uint32_t)cx,(uint32_t)cy,cz); 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) 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(); c->Resume();
return CR_FAILURE; return CR_FAILURE;
} }
@ -80,7 +80,7 @@ DFhackCExport command_result vdig (Core * c, vector <string> & parameters)
int16_t veinmat = MCache->veinMaterialAt(xy); int16_t veinmat = MCache->veinMaterialAt(xy);
if( veinmat == -1 ) if( veinmat == -1 )
{ {
con << "This tile is not a vein.\n"; con.printerr("This tile is not a vein.\n");
delete MCache; delete MCache;
c->Resume(); c->Resume();
return CR_FAILURE; return CR_FAILURE;