/*
* File: cio.cc
* Summary: Platform-independent console IO functions.
* Created by: dshaligram on Wed Jun 20 19:00:52 2007 UTC
*/
#include "AppHdr.h"
#include "cio.h"
#include "externs.h"
#include "options.h"
#include "macro.h"
#include "message.h"
#include "state.h"
#include "viewgeom.h"
#include <queue>
#ifdef UNIX
extern int unixcurses_get_vi_key(int keyin);
static keycode_type _numpad2vi(keycode_type key)
{
#ifndef USE_TILE
key = unixcurses_get_vi_key(key);
#endif
switch (key)
{
case CK_UP: key = 'k'; break;
case CK_DOWN: key = 'j'; break;
case CK_LEFT: key = 'h'; break;
case CK_RIGHT: key = 'l'; break;
case 1001: key = 'b'; break;
case 1002: key = 'j'; break;
case 1003: key = 'n'; break;
case 1004: key = 'h'; break;
case 1005: key = '.'; break;
case 1006: key = 'l'; break;
case 1007: key = 'y'; break;
case 1008: key = 'k'; break;
case 1009: key = 'u'; break;
}
if (key >= '1' && key <= '9')
{
const char *vikeys = "bjnh.lyku";
return keycode_type(vikeys[key - '1']);
}
return (key);
}
#endif
int unmangle_direction_keys(int keyin, KeymapContext keymap,
bool fake_ctrl, bool fake_shift)
{
#ifdef UNIX
// Kludging running and opening as two character sequences
// for Unix systems. This is an easy way out... all the
// player has to do is find a termcap and numlock setting
// that will get curses the numbers from the keypad. This
// will hopefully be easy.
/* can we say yuck? -- haranp */
if (fake_ctrl && keyin == '*')
{
keyin = getchm(keymap);
// return control-key
keyin = CONTROL(toupper(_numpad2vi(keyin)));
}
else if (fake_shift && keyin == '/')
{
keyin = getchm(keymap);
// return shift-key
keyin = toupper(_numpad2vi(keyin));
}
#else
// Old DOS keypad support
if (keyin == 0)
{
/* FIXME haranp - hackiness */
const char DOSidiocy[10] = { "OPQKSMGHI" };
const char DOSunidiocy[10] = { "bjnh.lyku" };
const int DOScontrolidiocy[9] =
{
117, 145, 118, 115, 76, 116, 119, 141, 132
};
keyin = getchm(keymap);
for (int j = 0; j < 9; ++j )
{
if (keyin == DOSidiocy[j])
{
keyin = DOSunidiocy[j];
break;
}
if (keyin == DOScontrolidiocy[j])
{
keyin = CONTROL(toupper(DOSunidiocy[j]));
break;
}
}
}
#endif
// [dshaligram] More lovely keypad mangling.
switch (keyin)
{
#ifdef UNIX
case '1': return 'b';
case '2': return 'j';
case '3': return 'n';
case '4': return 'h';
case '6': return 'l';
case '7': return 'y';
case '8': return 'k';
case '9': return 'u';
#ifndef USE_TILE
default: return unixcurses_get_vi_key(keyin);
#endif
#else
case '1': return 'B';
case '2': return 'J';
case '3': return 'N';
case '4': return 'H';
case '6': return 'L';
case '7': return 'Y';
case '8': return 'K';
case '9': return 'U';
#endif
}
return (keyin);
}
void get_input_line( char *const buff, int len )
{
buff[0] = 0; // just in case
if (crawl_state.is_replaying_keys())
{
ASSERT(crawl_state.input_line_curr <
crawl_state.input_line_strs.size());
unsigned int curr = crawl_state.input_line_curr++;
std::string &line = crawl_state.input_line_strs[curr];
strcpy(buff, line.c_str());
return;
}
#if defined(USE_TILE)
get_input_line_gui( buff, len );
#elif defined(UNIX)
get_input_line_from_curses( buff, len ); // implemented in libunix.cc
#elif defined(TARGET_OS_WINDOWS)
getstr( buff, len );
#else
// [dshaligram] Turn on the cursor for DOS.
#ifdef TARGET_OS_DOS
_setcursortype(_NORMALCURSOR);
#endif
fgets( buff, len, stdin ); // much safer than gets()
#endif
buff[ len - 1 ] = 0; // just in case
// Removing white space from the end in order to get rid of any
// newlines or carriage returns that any of the above might have
// left there (ie fgets especially). -- bwr
const int end = strlen( buff );
int i;
for (i = end - 1; i >= 0; i++)
{
if (isspace( buff[i] ))
buff[i] = 0;
else
break;
}
crawl_state.input_line_strs.push_back(buff);
}
// Hacky wrapper around getch() that returns CK_ codes for keys
// we want to use in cancelable_get_line() and menus.
int c_getch()
{
#if defined(TARGET_OS_DOS) || defined(UNIX) || (defined(TARGET_OS_WINDOWS) && !defined(USE_TILE))
return getch_ck();
#else
return m_getch();
#endif
}
// Wrapper around cgotoxy that can draw a fake cursor for Unix terms where
// cursoring over darkgrey or black causes problems.
void cursorxy(int x, int y)
{
#if defined(USE_TILE)
coord_def ep(x, y);
coord_def gc = view2grid(ep);
tiles.place_cursor(CURSOR_MOUSE, gc);
#elif defined(UNIX)
if (Options.use_fake_cursor)
fakecursorxy(x, y);
else
cgotoxy(x, y, GOTO_DNGN);
#else
cgotoxy(x, y, GOTO_DNGN);
#endif
}
// cprintf that stops outputting when wrapped
// Conceptually very similar to wrapcprintf()
int nowrapcprintf( int wrapcol, const char *s, ... )
{
char buf[1000]; // Hard max
va_list args;
va_start(args, s);
// XXX: If snprintf isn't available, vsnprintf probably isn't, either.
const int len = vsnprintf(buf, sizeof buf, s, args);
va_end(args);
// Sanity checking to prevent buffer overflows
const int maxlen = std::min( std::max( wrapcol + 1 - wherex(), 0 ), len );
// Force the string to terminate at maxlen
buf[maxlen] = 0;
cprintf("%s", buf);
return std::min(len, maxlen);
}
// convenience wrapper (hah) for nowrapcprintf
// FIXME: should pass off to nowrapcprintf() instead of doing it manually
int nowrap_eol_cprintf( const char *s, ... )
{
const int wrapcol = get_number_of_cols() - 1;
char buf[1000]; // Hard max
va_list args;
va_start(args, s);
// XXX: If snprintf isn't available, vsnprintf probably isn't, either.
const int len = vsnprintf(buf, sizeof buf, s, args);
va_end(args);
// Sanity checking to prevent buffer overflows
const int maxlen = std::min( std::max( wrapcol + 1 - wherex(), 0 ), len );
// Force the string to terminate at maxlen
buf[maxlen] = 0;
cprintf("%s", buf);
return std::min(len, maxlen);
}
// cprintf that knows how to wrap down lines (primitive, but what the heck)
int wrapcprintf( int wrapcol, const char *s, ... )
{
char buf[1000]; // Hard max
va_list args;
va_start(args, s);
// XXX: If snprintf isn't available, vsnprintf probably isn't, either.
int len = vsnprintf(buf, sizeof buf, s, args);
int olen = len;
va_end(args);
char *run = buf;
while (len > 0)
{
int x = wherex(), y = wherey();
if (x > wrapcol) // Somebody messed up!
return 0;
int avail = wrapcol - x + 1;
int c = 0;
if (len > avail)
{
c = run[avail];
run[avail] = 0;
}
cprintf("%s", run);
if (len > avail)
run[avail] = c;
if ((len -= avail) > 0)
cgotoxy(1, y + 1);
run += avail;
}
return (olen);
}
int cancelable_get_line( char *buf, int len, int maxcol,
input_history *mh, int (*keyproc)(int &ch) )
{
flush_prev_message();
line_reader reader(buf, len, maxcol);
reader.set_input_history(mh);
reader.set_keyproc(keyproc);
return reader.read_line();
}
/////////////////////////////////////////////////////////////
// input_history
//
input_history::input_history(size_t size)
: history(), pos(), maxsize(size)
{
if (maxsize < 2)
maxsize = 2;
pos = history.end();
}
void input_history::new_input(const std::string &s)
{
history.remove(s);
if (history.size() == maxsize)
history.pop_front();
history.push_back(s);
// Force the iterator to the end (also revalidates it)
go_end();
}
const std::string *input_history::prev()
{
if (history.empty())
return NULL;
if (pos == history.begin())
pos = history.end();
return &*--pos;
}
const std::string *input_history::next()
{
if (history.empty())
return NULL;
if (pos == history.end() || ++pos == history.end())
pos = history.begin();
return &*pos;
}
void input_history::go_end()
{
pos = history.end();
}
void input_history::clear()
{
history.clear();
go_end();
}
/////////////////////////////////////////////////////////////////////////
// line_reader
line_reader::line_reader(char *buf, size_t sz, int wrap)
: buffer(buf), bufsz(sz), history(NULL), start_x(0),
start_y(0), keyfn(NULL), wrapcol(wrap), cur(NULL),
length(0), pos(-1)
{
}
line_reader::~line_reader()
{
}
std::string line_reader::get_text() const
{
return (buffer);
}
void line_reader::set_input_history(input_history *i)
{
history = i;
}
void line_reader::set_keyproc(keyproc fn)
{
keyfn = fn;
}
void line_reader::cursorto(int ncx)
{
int x = (start_x + ncx - 1) % wrapcol + 1;
int y = start_y + (start_x + ncx - 1) / wrapcol;
cgotoxy(x, y, get_cursor_region());
}
int line_reader::read_line(bool clear_previous)
{
if (bufsz <= 0)
return (false);
cursor_control con(true);
if (clear_previous)
*buffer = 0;
start_x = wherex();
start_y = wherey();
#ifndef USE_TILE
// FIXME: what exactly is going on here?
// Update the active region.
cgotoxy(start_x, start_y, GOTO_CRT);
#endif
length = strlen(buffer);
// Remember the previous cursor position, if valid.
if (pos < 0 || pos > length)
pos = length;
cur = buffer + pos;
if (length)
wrapcprintf(wrapcol, "%s", buffer);
if (pos != length)
cursorto(pos);
if (history)
history->go_end();
while (true)
{
int ch = getchm(c_getch);
#if defined(USE_UNIX_SIGNALS) && defined(SIGHUP_SAVE) && defined(USE_CURSES)
// Don't return a partial string if a HUP signal interrupted things
if (crawl_state.seen_hups)
{
buffer[0] = '\0';
return (0);
}
#endif
if (keyfn)
{
int whattodo = (*keyfn)(ch);
if (whattodo == 0)
{
buffer[length] = 0;
if (history && length)
history->new_input(buffer);
return (0);
}
else if (whattodo == -1)
{
buffer[length] = 0;
return (ch);
}
}
int ret = process_key(ch);
if (ret != -1)
return (ret);
}
}
void line_reader::backspace()
{
if (pos)
{
buffer[length] = 0;
--cur;
char *c = cur;
while (*c)
{
*c = *(c+1);
c++;
}
--pos;
--length;
cursorto(pos);
buffer[length] = 0;
wrapcprintf( wrapcol, "%s ", cur );
cursorto(pos);
}
}
bool line_reader::is_wordchar(int c)
{
return isalnum(c) || c == '_' || c == '-';
}
void line_reader::kill_to_begin()
{
if (!pos || cur == buffer)
return;
buffer[length] = 0;
cursorto(0);
wrapcprintf(wrapcol, "%s%*s", cur, cur - buffer, "");
memmove(buffer, cur, length - pos);
length -= pos;
pos = 0;
cur = buffer;
cursorto(pos);
}
void line_reader::killword()
{
if (!pos || cur == buffer)
return;
bool foundwc = false;
while (pos)
{
if (is_wordchar(cur[-1]))
foundwc = true;
else if (foundwc)
break;
backspace();
}
}
int line_reader::process_key(int ch)
{
switch (ch)
{
case CONTROL('G'):
case CK_ESCAPE:
return (CK_ESCAPE);
case CK_UP:
case CK_DOWN:
{
if (!history)
break;
const std::string *text = (ch == CK_UP) ? history->prev()
: history->next();
if (text)
{
int olen = length;
length = text->length();
if (length >= (int) bufsz)
length = bufsz - 1;
memcpy(buffer, text->c_str(), length);
buffer[length] = 0;
cursorto(0);
int clear = length < olen ? olen - length : 0;
wrapcprintf(wrapcol, "%s%*s", buffer, clear, "");
pos = length;
cur = buffer + pos;
cursorto(pos);
}
break;
}
case CK_ENTER:
buffer[length] = 0;
if (history && length)
history->new_input(buffer);
return (0);
case CONTROL('K'):
{
// Kill to end of line.
int erase = length - pos;
if (erase)
{
length = pos;
buffer[length] = 0;
wrapcprintf( wrapcol, "%*s", erase, "" );
cursorto(pos);
}
break;
}
case CK_DELETE:
if (pos < length)
{
char *c = cur;
while (c - buffer < length)
{
*c = c[1];
c++;
}
--length;
cursorto(pos);
buffer[length] = 0;
wrapcprintf( wrapcol, "%s ", cur );
cursorto(pos);
}
break;
case CK_BKSP:
backspace();
break;
case CONTROL('W'):
killword();
break;
case CONTROL('U'):
kill_to_begin();
break;
case CK_LEFT:
if (pos)
{
--pos;
cur = buffer + pos;
cursorto(pos);
}
break;
case CK_RIGHT:
if (pos < length)
{
++pos;
cur = buffer + pos;
cursorto(pos);
}
break;
case CK_HOME:
case CONTROL('A'):
pos = 0;
cur = buffer + pos;
cursorto(pos);
break;
case CK_END:
case CONTROL('E'):
pos = length;
cur = buffer + pos;
cursorto(pos);
break;
case CK_MOUSE_CLICK:
return (-1);
default:
if (isprint(ch) && length < (int) bufsz - 1)
{
if (pos < length)
{
char *c = buffer + length - 1;
while (c >= cur)
{
c[1] = *c;
c--;
}
}
*cur++ = (char) ch;
++length;
++pos;
putch(ch);
if (pos < length)
{
buffer[length] = 0;
wrapcprintf( wrapcol, "%s", cur );
}
cursorto(pos);
}
break;
}
return (-1);
}
/////////////////////////////////////////////////////////////////////////////
// Of mice and other mice.
static std::queue<c_mouse_event> mouse_events;
coord_def get_mouse_pos()
{
// lib$(OS) has to maintain mousep. This function is just the messenger.
return (crawl_view.mousep);
}
c_mouse_event get_mouse_event()
{
if (mouse_events.empty())
return c_mouse_event();
c_mouse_event ce = mouse_events.front();
mouse_events.pop();
return (ce);
}
void new_mouse_event(const c_mouse_event &ce)
{
mouse_events.push(ce);
}
void flush_mouse_events()
{
while (!mouse_events.empty())
mouse_events.pop();
}
void c_input_reset(bool enable_mouse, bool flush)
{
crawl_state.mouse_enabled = (enable_mouse && Options.mouse_input);
set_mouse_enabled(crawl_state.mouse_enabled);
if (flush)
flush_mouse_events();
}