You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
PDCursesMod/pdcurses/getch.c

830 lines
21 KiB
C

/* PDCurses */
#include <curspriv.h>
#include <assert.h>
/*man-start**************************************************************
getch
-----
### Synopsis
int getch(void);
int wgetch(WINDOW *win);
int mvgetch(int y, int x);
int mvwgetch(WINDOW *win, int y, int x);
int ungetch(int ch);
int flushinp(void);
int get_wch(wint_t *wch);
int wget_wch(WINDOW *win, wint_t *wch);
int mvget_wch(int y, int x, wint_t *wch);
int mvwget_wch(WINDOW *win, int y, int x, wint_t *wch);
int unget_wch(const wchar_t wch);
unsigned long PDC_get_key_modifiers(void);
int PDC_return_key_modifiers(bool flag);
### Description
With the getch(), wgetch(), mvgetch(), and mvwgetch() functions, a
character is read from the terminal associated with the window. In
nodelay mode, if there is no input waiting, the value ERR is
returned. In delay mode, the program will hang until the system
passes text through to the program. Depending on the setting of
cbreak(), this will be after one character or after the first
newline. Unless noecho() has been set, the character will also be
echoed into the designated window.
If keypad() is TRUE, and a function key is pressed, the token for
that function key will be returned instead of the raw characters.
Possible function keys are defined in <curses.h> with integers
starting at KEY_OFFSET, whose names begin with KEY_.
If nodelay(win, TRUE) has been called on the window and no input is
waiting, the value ERR is returned.
ungetch() places ch back onto the input queue to be returned by the
next call to wgetch().
flushinp() throws away any type-ahead that has been typed by the user
and has not yet been read by the program.
wget_wch() is the wide-character version of wgetch(), available when
PDCurses is built with the PDC_WIDE option. It takes a pointer to a
wint_t rather than returning the key as an int, and instead returns
KEY_CODE_YES if the key is a function key. Otherwise, it returns OK
or ERR. It's important to check for KEY_CODE_YES; on most Curses
implementations (not PDCursesMod), regular wide characters can have
the same values as function key codes.
unget_wch() puts a wide character on the input queue.
PDC_get_key_modifiers() returns the keyboard modifiers (shift,
control, alt, numlock) effective at the time of the last getch()
call. Use the macros PDC_KEY_MODIFIER_* to determine which
modifier(s) were set. PDC_return_key_modifiers() tells getch() to
return modifier keys pressed alone as keystrokes (KEY_ALT_L, etc.).
These may not work on all platforms.
NOTE: getch() and ungetch() are implemented as macros, to avoid
conflict with many DOS compiler's runtime libraries.
### Return Value
These functions return ERR or the value of the character, meta
character or function key token.
### Portability
X/Open ncurses NetBSD
getch Y Y Y
wgetch Y Y Y
mvgetch Y Y Y
mvwgetch Y Y Y
ungetch Y Y Y
flushinp Y Y Y
get_wch Y Y Y
wget_wch Y Y Y
mvget_wch Y Y Y
mvwget_wch Y Y Y
unget_wch Y Y Y
PDC_get_key_modifiers - - -
**man-end****************************************************************/
#include <stdlib.h>
/* By default, the PDC_function_key[] array contains 0 */
/* (i.e., there's no key that's supposed to be returned for */
/* exit handling), and 22 = Ctrl-V (i.e., hit Ctrl-V to */
/* paste text from the clipboard into the key queue); then */
/* no key by default to enlarge/decrease font size or to */
/* select a font from the font dialog; then Ctrl-C for copy. */
static int PDC_function_key[PDC_MAX_FUNCTION_KEYS] = { 0, 22, 0, 0, 0, 0, 3 };
/*man-start**************************************************************
Function keys
-------------
### Synopsis
int PDC_set_function_key( const unsigned function, const int new_key);
int PDC_get_function_key( const unsigned function);
### Description
Allows one to set a 'shut down' key, and reassign hotkeys used for
copying to/pasting from the clipboard and enlarging and decreasing the
font size, and for using the font selection dialog (on platforms where
these things are possible and implemented). For example, calling
PDC_set_function_key( FUNCTION_KEY_SHUT_DOWN, ALT_Q);
would reset PDCursesMod such that, if the user clicks on the 'close'
box, Alt-Q would be added to the key queue. This would give the app the
opportunity to shut things down gracefully, perhaps asking "are you
sure", and/or "save changes or discard or cancel", rather than just
having the window close (the default behavior).
Similarly, one can set FUNCTION_KEY_ABORT to a key which, when pressed,
will cause the program to abort gracelessly (no key returned to the
application). One would normally use this to enable/disable Ctrl-C or
Ctrl-Break, or to set a different 'abort' key so that Ctrl-C can be
used for copying.
### Return Value
Returns key code previously set for that function, or -1 if the
function does not actually exist.
### Portability
PDCursesMod-only function.
**man-end****************************************************************/
int PDC_set_function_key( const unsigned function, const int new_key)
{
int old_key = -1;
assert( function < PDC_MAX_FUNCTION_KEYS);
if( function < PDC_MAX_FUNCTION_KEYS)
{
old_key = PDC_function_key[function];
PDC_function_key[function] = new_key;
}
return( old_key);
}
int PDC_get_function_key( const unsigned function)
{
assert( function < PDC_MAX_FUNCTION_KEYS);
return( PDC_function_key[function]);
}
static int _get_box(int *y_start, int *y_end, int *x_start, int *x_end)
{
int start, end;
if (SP->sel_start < SP->sel_end)
{
start = SP->sel_start;
end = SP->sel_end;
}
else
{
start = SP->sel_end;
end = SP->sel_start;
}
*y_start = start / COLS;
*x_start = start % COLS;
*y_end = end / COLS;
*x_end = end % COLS;
return (end - start) + (*y_end - *y_start);
}
static bool _highlight(void)
{
int i, j, y_start, y_end, x_start, x_end;
if (-1 == SP->sel_start || SP->sel_start == SP->sel_end)
return( FALSE);
_get_box(&y_start, &y_end, &x_start, &x_end);
for (j = y_start; j <= y_end; j++)
for (i = (j == y_start ? x_start : 0);
i < (j == y_end ? x_end : COLS); i++)
curscr->_y[j][i] ^= A_REVERSE;
return( TRUE);
}
static void _copy(void)
{
#ifdef PDC_WIDE
wchar_t *wtmp;
# define TMP wtmp
# define MASK A_CHARTEXT
#else
# define TMP tmp
# define MASK 0xff
#endif
char *tmp;
long pos;
int i, j, y_start, y_end, x_start, x_end, len;
if (-1 == SP->sel_start)
return;
len = _get_box(&y_start, &y_end, &x_start, &x_end);
if (!len)
return;
#ifdef PDC_WIDE
wtmp = (wchar_t *)malloc((len + 1) * sizeof(wchar_t));
len *= 4;
#endif
tmp = (char *)malloc(len + 1);
for (j = y_start, pos = 0; j <= y_end; j++)
{
for (i = (j == y_start ? x_start : 0);
i < (j == y_end ? x_end : COLS); i++)
#ifdef PDC_WIDE
wtmp[pos++] = (wchar_t)( curscr->_y[j][i] & MASK);
#else
tmp[pos++] = (char)( curscr->_y[j][i] & MASK);
#endif
while (y_start != y_end && pos > 0 && TMP[pos - 1] == 32)
pos--;
if (j < y_end)
TMP[pos++] = 10;
}
TMP[pos] = 0;
#ifdef PDC_WIDE
pos = (long)PDC_wcstombs(tmp, wtmp, len);
#endif
PDC_setclipboard(tmp, pos);
free(tmp);
#ifdef PDC_WIDE
free(wtmp);
#endif
}
static int _paste(void)
{
#ifdef PDC_WIDE
wchar_t *wpaste;
# define PASTE wpaste
#else
# define PASTE paste
#endif
char *paste;
long len, newmax;
int key;
key = PDC_getclipboard(&paste, &len);
if (PDC_CLIP_SUCCESS != key || !len)
return -1;
#ifdef PDC_WIDE
wpaste = (wchar_t *)malloc((len + 1) * sizeof(wchar_t));
len = (long)PDC_mbstowcs(wpaste, paste, len + 1);
#endif
newmax = len + SP->c_ungind;
if (newmax > SP->c_ungmax)
{
SP->c_ungch = (int *)realloc(SP->c_ungch, newmax * sizeof(int));
if (!SP->c_ungch)
return -1;
SP->c_ungmax = newmax;
}
while (len > 1)
PDC_ungetch(PASTE[--len]);
key = *PASTE;
#ifdef PDC_WIDE
free(wpaste);
#endif
PDC_freeclipboard(paste);
SP->key_modifiers = 0;
return key;
}
#define WHEEL_EVENTS (PDC_MOUSE_WHEEL_UP|PDC_MOUSE_WHEEL_DOWN|PDC_MOUSE_WHEEL_RIGHT | PDC_MOUSE_WHEEL_LEFT)
static int _mouse_key(void)
{
int i, key = (int)KEY_MOUSE;
const int changes = SP->mouse_status.changes;
const mmask_t mbe = SP->_trap_mbe;
bool can_select = !(mbe & (BUTTON1_MOVED | BUTTON1_PRESSED | BUTTON1_RELEASED));
bool can_paste = !(mbe & BUTTON2_CLICKED);
/* really means 'can do these things without shift' */
/* Selection highlighting? */
if ((can_select || SP->mouse_status.button[0] & BUTTON_SHIFT) && changes & 1)
{
i = SP->mouse_status.y * COLS + SP->mouse_status.x;
switch (SP->mouse_status.button[0] & BUTTON_ACTION_MASK)
{
case BUTTON_PRESSED:
if( _highlight())
wrefresh(curscr);
SP->sel_start = SP->sel_end = i;
return -1;
case BUTTON_MOVED:
{
const bool refresh_needed = _highlight();
SP->sel_end = i;
if( _highlight() || refresh_needed)
wrefresh(curscr);
return -1;
}
case BUTTON_RELEASED:
_copy();
return -1;
}
}
else if ((can_paste || SP->mouse_status.button[1] & BUTTON_SHIFT) &&
changes & 2 && (SP->mouse_status.button[1] &
BUTTON_ACTION_MASK) == BUTTON_CLICKED)
{
return _paste();
}
/* Filter unwanted mouse events */
for (i = 0; i < 3; i++)
{
if (changes & (1 << i))
{
int shf = i * PDC_BITS_PER_BUTTON;
short button = SP->mouse_status.button[i] & BUTTON_ACTION_MASK;
if ( (!(mbe & (BUTTON1_PRESSED << shf)) &&
(button == BUTTON_PRESSED))
|| (!(mbe & (BUTTON1_CLICKED << shf)) &&
(button == BUTTON_CLICKED))
|| (!(mbe & (BUTTON1_DOUBLE_CLICKED << shf)) &&
(button == BUTTON_DOUBLE_CLICKED))
|| (!(mbe & (BUTTON1_TRIPLE_CLICKED << shf)) &&
(button == BUTTON_TRIPLE_CLICKED))
|| (!(mbe & (BUTTON1_MOVED << shf)) &&
(button == BUTTON_MOVED))
|| (!(mbe & (BUTTON1_RELEASED << shf)) &&
(button == BUTTON_RELEASED))
)
SP->mouse_status.changes ^= (1 << i);
}
}
if (changes & PDC_MOUSE_MOVED)
{
if (!(mbe & (BUTTON1_MOVED|BUTTON2_MOVED|BUTTON3_MOVED | REPORT_MOUSE_POSITION)))
SP->mouse_status.changes ^= PDC_MOUSE_MOVED;
}
if (changes & WHEEL_EVENTS)
{
if (!(mbe & MOUSE_WHEEL_SCROLL))
SP->mouse_status.changes &= ~WHEEL_EVENTS;
}
if (!SP->mouse_status.changes)
return -1;
/* Check for click in slk area */
i = PDC_mouse_in_slk(SP->mouse_status.y, SP->mouse_status.x);
if (i)
{
if (SP->mouse_status.button[0] & (BUTTON_PRESSED|BUTTON_CLICKED))
key = KEY_F(i);
else
key = -1;
}
return key;
}
/* ftime() is consided obsolete. But it's all we have for
millisecond precision on older compilers/systems. We'll
use clock_gettime() or gettimeofday() when available. */
#if defined( _POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 199309L)
#define CLOCK_GETTIME_AVAILABLE 1
#endif
#if defined( _DEFAULT_SOURCE) || defined( _BSD_SOURCE) \
|| defined(HAVE_GETTIMEOFDAY) || defined( __FreeBSD__)
#define GETTIMEOFDAY_AVAILABLE 1
#endif
#if defined( GETTIMEOFDAY_AVAILABLE)
#include <sys/time.h>
long PDC_millisecs( void)
{
struct timeval t;
gettimeofday( &t, NULL);
return( t.tv_sec * 1000 + t.tv_usec / 1000);
}
#elif defined( CLOCK_GETTIME_AVAILABLE)
#include <time.h>
long PDC_millisecs( void)
{
struct timespec t;
clock_gettime( CLOCK_REALTIME, &t);
return( t.tv_sec * 1000 + t.tv_nsec / 1000000);
}
#else /* neither gettimeofday() or clock_gettime() available */
#include <sys/timeb.h>
long PDC_millisecs( void)
{
struct timeb t;
ftime( &t);
return( (long)t.time * 1000L + (long)t.millitm);
}
#endif
/* On many systems, checking for a key hit is quite slow. If
PDC_check_key( ) returns FALSE, we can safely stop checking for
a key hit for a millisecond. This ensures we won't call it more
than 1000 times per second.
On DOS, it appears that checking the time is so slow that we're
better off (by a small margin) not using this scheme. */
static bool _fast_check_key( void)
{
#if defined( __DMC__) && !defined( _WIN32)
return( PDC_check_key( ));
#else
static long prev_millisecond;
const long curr_ms = PDC_millisecs( );
bool rval;
if( prev_millisecond == curr_ms)
return( FALSE);
rval = PDC_check_key( );
if( !rval)
prev_millisecond = curr_ms;
return( rval);
#endif
}
bool PDC_is_function_key( const int key)
{
return( key >= KEY_MIN && key < KEY_MAX);
}
#define WAIT_FOREVER -1
static int _raw_wgetch_no_surrogate_pairs( WINDOW *win)
{
int key = ERR, remaining_millisecs;
PDC_LOG(("_raw_wgetch_no_surrogate_pairs() - called\n"));
assert( SP);
assert( win);
if (!win || !SP)
return ERR;
if (SP->delaytenths)
remaining_millisecs = 100 * SP->delaytenths;
else
remaining_millisecs = win->_delayms;
if( !remaining_millisecs && !win->_nodelay)
remaining_millisecs = WAIT_FOREVER;
/* refresh window when wgetch is called if there have been changes
to it and it is not a pad */
if (!(win->_flags & _PAD) && ((!win->_leaveit &&
(win->_begx + win->_curx != SP->curscol ||
win->_begy + win->_cury != SP->cursrow)) || is_wintouched(win)))
wrefresh(win);
/* if ungotten char exists, remove and return it */
if (SP->c_ungind)
key = SP->c_ungch[--(SP->c_ungind)];
/* if normal and data in buffer */
else if ((!SP->raw_inp && !SP->cbreak) && (SP->c_gindex < SP->c_pindex))
key = SP->c_buffer[SP->c_gindex++];
if( key != ERR)
{
if( key == KEY_RESIZE)
resize_term( 0, 0);
return( key);
}
/* prepare to buffer data */
SP->c_pindex = 0;
SP->c_gindex = 0;
/* to get here, no keys are buffered. go and get one. */
for (;;) /* loop for any buffering */
{
/* is there a keystroke ready? */
while( !_fast_check_key())
{
/* if not, handle timeout() and halfdelay() */
int nap_time = 50;
if (remaining_millisecs != WAIT_FOREVER)
{
if (!remaining_millisecs)
return ERR;
if( nap_time > remaining_millisecs)
nap_time = remaining_millisecs;
remaining_millisecs -= nap_time;
}
napms( nap_time);
}
/* if there is, fetch it */
key = PDC_get_key();
/* loop back if we did not get a key yet */
if (key == -1)
continue;
/* filter mouse events; translate mouse clicks in the slk
area to function keys (especially copy + pase) */
if( key == KEY_MOUSE)
{
key = _mouse_key();
}
else
{
/* copy or paste? */
#ifndef _WIN32
if (SP->key_modifiers & PDC_KEY_MODIFIER_SHIFT)
#endif
{
if (PDC_function_key[FUNCTION_KEY_COPY] == key)
{
_copy();
continue;
}
else if (PDC_function_key[FUNCTION_KEY_PASTE] == key)
key = _paste();
}
}
/* filter special keys if not in keypad mode */
if( key != KEY_RESIZE && PDC_is_function_key( key) && !win->_use_keypad)
key = -1;
/* unwanted key? loop back */
if (key == -1)
continue;
if( _highlight())
wrefresh(curscr);
SP->sel_start = SP->sel_end = -1;
/* translate CR */
if (key == '\r' && SP->autocr && !SP->raw_inp)
key = '\n';
/* if echo is enabled */
if (SP->echo && !PDC_is_function_key( key))
{
waddch(win, key);
wrefresh(win);
}
/* if no buffering */
if (SP->raw_inp || SP->cbreak)
{
if( key == KEY_RESIZE)
resize_term( 0, 0);
return key;
}
/* if no overflow, put data in buffer */
if (key == '\b')
{
if (SP->c_pindex > SP->c_gindex)
SP->c_pindex--;
}
else
if (SP->c_pindex < _INBUFSIZ - 2)
SP->c_buffer[SP->c_pindex++] = key;
/* if we got a line */
if (key == '\n' || key == '\r')
{
if( SP->c_buffer[SP->c_gindex] == KEY_RESIZE)
resize_term( 0, 0);
return SP->c_buffer[SP->c_gindex++];
}
}
}
#define IS_HIGH_SURROGATE( x) ((x) >= 0xd800 && (x) < 0xdc00)
#define IS_LOW_SURROGATE( x) ((x) >= 0xdc00 && (x) < 0xe000)
static int _raw_wgetch( WINDOW *win)
{
int rval = _raw_wgetch_no_surrogate_pairs( win);
if( IS_HIGH_SURROGATE( rval))
{
const int c = _raw_wgetch_no_surrogate_pairs( win);
if( IS_LOW_SURROGATE( c))
rval = ((rval - 0xd800) << 10) + 0x10000 + c - 0xdc00;
}
return( rval);
}
int mvgetch(int y, int x)
{
PDC_LOG(("mvgetch() - called\n"));
if (move(y, x) == ERR)
return ERR;
return wgetch(stdscr);
}
int mvwgetch(WINDOW *win, int y, int x)
{
PDC_LOG(("mvwgetch() - called\n"));
if (wmove(win, y, x) == ERR)
return ERR;
return wgetch(win);
}
int PDC_ungetch(int ch)
{
PDC_LOG(("ungetch() - called\n"));
if (SP->c_ungind >= SP->c_ungmax) /* pushback stack full */
return ERR;
SP->c_ungch[SP->c_ungind++] = ch;
return OK;
}
int flushinp(void)
{
PDC_LOG(("flushinp() - called\n"));
assert( SP);
if (!SP)
return ERR;
PDC_flushinp();
SP->c_gindex = 1; /* set indices to kill buffer */
SP->c_pindex = 0;
SP->c_ungind = 0; /* clear SP->c_ungch array */
return OK;
}
unsigned long PDC_get_key_modifiers(void)
{
PDC_LOG(("PDC_get_key_modifiers() - called\n"));
assert( SP);
if (!SP)
return (unsigned long)ERR;
return SP->key_modifiers;
}
int PDC_return_key_modifiers(bool flag)
{
PDC_LOG(("PDC_return_key_modifiers() - called\n"));
assert( SP);
if (!SP)
return ERR;
SP->return_key_modifiers = flag;
return PDC_modifiers_set();
}
int wgetch(WINDOW *win)
{
#ifndef PDC_WIDE
return( _raw_wgetch( win));
#else
static unsigned char buffered[8];
static size_t n_buff = 0;
int rval;
if( n_buff)
{
size_t i;
rval = buffered[0];
n_buff--;
for( i = 0; i < n_buff; i++)
buffered[i] = buffered[i + 1];
}
else
{
rval = _raw_wgetch(win);
if( rval != ERR && (rval < 0 || rval > 127)
&& !PDC_is_function_key( rval))
{
wchar_t c = (wchar_t)rval;
n_buff = PDC_wcstombs( (char *)buffered, &c, 1);
if( (int)n_buff <= 0)
{
n_buff = 0;
rval = ERR;
}
else /* successfully converted to multi-byte string */
rval = wgetch( win);
}
}
return( rval);
#endif
}
#ifdef PDC_WIDE
int wget_wch(WINDOW *win, wint_t *wch)
{
int key;
PDC_LOG(("wget_wch() - called\n"));
assert( wch);
if (!wch)
return ERR;
key = _raw_wgetch(win);
if (key == ERR)
return ERR;
*wch = (wint_t)key;
return PDC_is_function_key( key) ? KEY_CODE_YES : OK;
}
int get_wch(wint_t *wch)
{
PDC_LOG(("get_wch() - called\n"));
return wget_wch(stdscr, wch);
}
int mvget_wch(int y, int x, wint_t *wch)
{
PDC_LOG(("mvget_wch() - called\n"));
if (move(y, x) == ERR)
return ERR;
return wget_wch(stdscr, wch);
}
int mvwget_wch(WINDOW *win, int y, int x, wint_t *wch)
{
PDC_LOG(("mvwget_wch() - called\n"));
if (wmove(win, y, x) == ERR)
return ERR;
return wget_wch(win, wch);
}
int unget_wch(const wchar_t wch)
{
return PDC_ungetch(wch);
}
#endif