[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
lib_getch.c
From: |
Philippe Blain |
Subject: |
lib_getch.c |
Date: |
Sun, 18 Aug 2002 05:42:56 +0200 |
>From Philippe Blain, Bordeaux, FRANCE - <address@hidden>
My old computer: P133 - 8,4 Go - 32 Mo Red Hat Linux 7.0
To maintainers of 'ncurses'.(and to Mr Dickey)
Subject: Corrections for ncurses-5.2-20020810+
Here are some problems I found:
----------------------------------------------------------------------------
----
File : ncurses/base/lib_getch.c
I've made a little program and traced manually function calls.
My purpose was to know how characters were acquired and the interaction
wgetnstr <-> wgetch.
Globally, that simple program was a loop :
while(1) { c = wgetch(mywin); } // Get chars until CTRL-C
I've understood how it runs.
I've noticed that according to 'curs_getch' man pages; erasec, backspace,
left arrow should do their job in _nc_wgetch() as in wgetstr() [cooked
mode],
but it's not the case.
I propose some improvements and a better scheme:
1) Clearly separe fifo and file-descriptors management.
On one hand we acquire bytes, on other hand we push in fifo.
2) Interpreting sequences or not according to _keypad_on.
3) Pass a delay as parameter for acquiring characters.
Conforms to nodelay, halfdelay, wtimeout and notimeout functions.
4) Encapsulate functions each other.
Under is the remplacement code for some functions :
_nc_timed_wait (int mode, int milliseconds, int *timeleft, int *active_fds)
_fifo_push (int ch) // MINOR MODIFS
_read_one_byte (int milliseconds, int *ch) // NEW
kgetch (int delay) // REWRITTEN
_nc_wgetch (WINDOW * win, unsigned long *result, int use_meta) //MINOR
MODIFS
You can modify as you will if anything bad.
**** PLEASE REMOVE ALL MY COMMENTS ****
----------------------------------------------------------------------------
----
First step is to modify _nc_timed_wait() by adding a parameter.
This permits to know when function fails (return -1).
/***************************************************************************
****
* _nc_timed_wait (mode, milliseconds, &timeleft, &active_fds)
*
*
*
* --> Wait a specified number of milliseconds, returning nonzero if the
*
* timer didn't expire before there is activity on the specified file
*
* descriptors.
*
* The file-descriptors to watch are specified by the mode:
*
* 0 - none (absolute time)
*
* 1 - normal input-descriptor
*
* 2 - mouse descriptor, if any
*
* 3 - either input or mouse.
*
* If the milliseconds given are NEGATIVE, the wait blocks until
activity *
* on the descriptors (infinite timeout).
*
* Set a mask that corresponds to the mode (e.g., 2 for mouse
activity). *
* Returns the number of file-descriptors that have changed status, or
*
* (-1) if function failed (in that case, errno can be tested).
*
****************************************************************************
***/
NCURSES_EXPORT (int)
_nc_timed_wait (int mode, int milliseconds, int *timeleft, int *active_fds)
{
int fd;
int count;
int active_desc;
int result;
#if USE_FUNC_POLL
struct pollfd fds[2];
#elif defined(__BEOS__)
#elif HAVE_SELECT
static fd_set set;
#endif
long starttime, returntime;
TR (TRACE_IEVENT, ("start twait: %d milliseconds, mode: %d",
milliseconds, mode));
#if PRECISE_GETTIME
retry:
#endif
starttime = _nc_gettime (TRUE);
count = 0;
#if USE_FUNC_POLL
memset (fds, 0, sizeof (fds));
if (mode & 1) {
fds[count].fd = SP->_ifd;
fds[count].events = POLLIN;
count++;
}
if ((mode & 2)
&& (fd = SP->_mouse_fd) >= 0) {
fds[count].fd = fd;
fds[count].events = POLLIN;
count++;
}
result = poll (fds, count, milliseconds);
#elif defined(__BEOS__)
/*
* BeOS's select() is declared in socket.h, so the configure script does
* not see it. That's just as well, since that function works only for
* sockets. This (using snooze and ioctl) was distilled from Be's patch
* for ncurses which uses a separate thread to simulate select().
*
* FIXME: the return values from the ioctl aren't very clear if we get
* interrupted.
*
* FIXME: this assumes mode&1 if milliseconds < 0 (see lib_getch.c).
*/
result = 0;
if (mode & 1) {
int step = (milliseconds < 0) ? 0 : 5000;
bigtime_t d;
bigtime_t useconds = milliseconds * 1000;
int n, howmany;
if (useconds <= 0) /* we're here to go _through_ the loop */
useconds = 1;
for (d = 0; d < useconds; d += step) {
n = 0;
howmany = ioctl (0, 'ichr', &n);
if (howmany >= 0 && n > 0) {
result = 1;
break;
}
if (useconds > 1 && step > 0) {
snooze (step);
milliseconds -= (step / 1000);
if (milliseconds <= 0) {
milliseconds = 0;
break;
}
}
}
}
else if (milliseconds > 0) {
snooze (milliseconds * 1000);
milliseconds = 0;
}
#elif HAVE_SELECT
/*
* select() modifies the fd_set arguments; do this in the
* loop.
*/
FD_ZERO (&set);
if (mode & 1) {
FD_SET (SP->_ifd, &set);
count = SP->_ifd + 1;
}
if ((mode & 2)
&& (fd = SP->_mouse_fd) >= 0) {
FD_SET (fd, &set);
count = max (fd, count) + 1;
}
if (milliseconds >= 0) {
struct timeval ntimeout;
ntimeout.tv_sec = milliseconds / 1000;
ntimeout.tv_usec = (milliseconds % 1000) * 1000;
result = select (count, &set, NULL, NULL, &ntimeout);
}
else {
result = select (count, &set, NULL, NULL, NULL);
}
#endif
returntime = _nc_gettime (FALSE);
if (milliseconds >= 0)
milliseconds -= (returntime - starttime);
if (result == -1) {
/* return approximate time left in milliseconds */
if (timeleft) *timeleft = milliseconds;
if (active_fds) *activefds = 0;
}
#if PRECISE_GETTIME
/*
* If the timeout hasn't expired, and we've gotten no data,
* this is probably a system where 'select()' needs to be left
* alone so that it can complete. Make this process sleep,
* then come back for more.
*/
if (result == 0 && milliseconds > 100) {
napms (100);
milliseconds -= 100;
goto retry;
}
#endif
/* return approximate time left in milliseconds */
if (timeleft) *timeleft = milliseconds;
TR (TRACE_IEVENT, ("end twait: returned %d (%d), remaining time %d msec",
result, errno, milliseconds));
/*
* Both 'poll()' and 'select()' return the number of file descriptors
* that are active. Translate this back to the mask that denotes which
* file-descriptors, so that we don't need all of this system-specific
* code everywhere.
*/
active_desc = 0;
if (result > 0) {
#if USE_FUNC_POLL
for (count = 0; count < 2; count++) {
if ((mode & (1 << count)) && (fds[count].revents & POLLIN)) {
active_desc |= (1 << count);
}
}
#elif defined(__BEOS__)
active_desc = 1; /* redundant, but simple */
#elif HAVE_SELECT
if ((mode & 1) && FD_ISSET (SP->_ifd, &set))
active_desc |= 1;
if ((mode & 2) && (fd = SP->_mouse_fd) >= 0 && FD_ISSET (fd, &set))
active_desc |= 2;
#endif
}
if (active_fds) *active_fds = active_desc;
return (result);
}
----------------------------------------------------------------------------
----
Second step. (lib_getch.c)
#include <fcntl.h>
/***************************************************************************
****
* _fifo_push()
*
* --> Push a character on fifo's tail.
*
****************************************************************************
***/
static inline int _fifo_push (int ch)
{
if (tail == -1) return (-1); /* fifo is full */
SP->_fifo[tail] = ch;
SP->_fifohold = 0;
if (head == -1) head = peek = tail; /* fifo was empty */
t_inc ();
TR (TRACE_IEVENT, ("pushed %s at %d", _tracechar (ch), tail));
#ifdef TRACE
if (_nc_tracing & TRACE_IEVENT)
_nc_fifo_dump ();
#endif
return (ch);
}
/***************************************************************************
****
* _read_one_byte (millis, &ch)
*
* --> Use a delay in milliseconds.
*
* If delay :
*
* < 0, blocking read is used (i.e., waits indefinitely for input).
*
* = 0, then non-blocking read is used (i.e., read returns (-1) if no
*
* input is waiting).
*
* > 0, then read blocks for delay milliseconds, and returns (-1) if
*
* there is still no input.
*
* On success, the number of characters read is returned (1 or 0).
*
* On failure, (-1) is returned, and errno is set appropriately.
*
****************************************************************************
***/
static inline int _read_one_byte (int milliseconds, int *ch)
{
int n, active_fds;
long flags;
if (milliseconds == 0) { /* set non-blocking mode for keyboard */
flags = fcntl (SP->_ifd, F_GETFL);
if (flags < 0) return (ERR);
n = fcntl (SP->_ifd, F_SETFL, flags | O_NONBLOCK);
if (n < 0) return (ERR);
}
#ifdef HIDE_EINTR
/*
* Under System V curses with non-restarting signals, getch() returns
* with value ERR when a handled signal keeps it from completing.
* If signals restart system calls, OTOH, the signal is invisible
* except to its handler.
*
* We don't want this difference to show. This piece of code
* tries to make it look like we always have restarting signals.
*/
again:
errno = 0;
#endif
active_fds = 0;
/* waiting for key or mouse activity */
n = _nc_timed_wait (3, milliseconds, (int *) 0, &active_fds);
#ifdef HIDE_EINTR
if (n < 0 && errno == EINTR) goto again;
#endif
if (n > 0) { /* some activity */
#if USE_GPM_SUPPORT || defined(USE_EMX_MOUSE)
if (active_fds & 2) { /* Prefer mouse first */
SP->_mouse_event (SP);
*ch = KEY_MOUSE;
n = 1;
}
else
#endif
if (active_fds & 1) {
int c2 = 0;
n = read (SP->_ifd, &c2, 1);
#ifdef HIDE_EINTR
if (n < 0 && errno == EINTR) goto again;
#endif
if (n > 0) {
TR (TRACE_IEVENT, ("read %d characters", n));
*ch = c2;
}
else if (n < 0) {
TR (TRACE_IEVENT,
("read(%d,&ch,1)=%d, errno=%d", SP->_ifd, n, errno));
*ch = ERR;
}
}
}
if (milliseconds == 0) { /* restore descriptor's flags */
if (fcntl (SP->_ifd, F_SETFL, flags) < 0) return (ERR);
}
return (n);
}
/***************************************************************************
****
* kgetch (delay)
*
* --> Get an input character in 'delay' milliseconds.
*
* If keypad is enabled, interpret escape sequences, returning an
*
* appropriate code when one matches the input. After each character
is *
* received, set an alarm call based on ESCDELAY. If no more of the
*
* sequence is received by the time the alarm goes off, pass through
the *
* sequence gotten so far.
*
****************************************************************************
***/
static int kgetch (int delay)
{
struct tries *ptr;
int ch = 0;
int timeleft = ESCDELAY;
bool end_sequence;
short last_being_interpreted;
TR (TRACE_IEVENT, ("kgetch() called"));
if (!raw_key_in_fifo ()) { /* character needed */
if (tail == -1) return (ERR); /* fifo full, can't read */
/* try reading first byte in 'delay' milliseconds */
if (_read_one_byte (delay, &ch) <= 0) return (ERR);
else _fifo_push (ch);
}
ptr = SP->_keytry;
end_sequence = FALSE;
/* looking if it's a recognized escape-sequence */
do {
last_being_interpreted = peek;
ch = _fifo_peek ();
TR (TRACE_IEVENT, ("ch: %s", _tracechar ((unsigned char) ch)));
/*
* COMMENT :
* I don't understand the 'if (ch >= KEY_MIN) ...' here.
* Should be explained if usefull.
*/
while ((ptr != NULL) && (ptr->ch != (unsigned char) ch))
ptr = ptr->sibling;
/* if char not found in tree, was not an escape-sequence */
if (ptr == NULL) {
TR (TRACE_IEVENT, ("ptr is null"));
end_sequence = TRUE;
}
else {
TR (TRACE_IEVENT,
("ptr=%p, ch=%d, value=%d", ptr, ptr->ch, ptr->value));
if (ptr->value != 0) { /* sequence acknoledged */
TR (TRACE_IEVENT, ("end of sequence"));
/* if keypad mode is currently on, change that sequence by
its value on head, else let sequence uninterpreted in fifo */
if (SP->_keypad_on) {
if (peek == tail) {
_fifo_clear ();
_fifo_push (ptr->value);
}
else {
head = last_being_interpreted; /* advance head */
SP->_fifo[head] = ptr->value;
}
}
end_sequence = TRUE;
}
else { /* go back for another character */
ptr = ptr->child;
if (!raw_key_in_fifo () && (tail != -1)) {
TR (TRACE_IEVENT, ("waiting for rest of sequence"));
if (_read_one_byte (timeleft, &ch) <= 0) {
TR (TRACE_IEVENT, ("ran out of time"));
end_sequence = TRUE;
}
else _fifo_push (ch);
}
}
}
} while (!end_sequence);
ch = _fifo_pull ();
peek = head; /* reset peek */
return (ch);
}
/***************************************************************************
****
* _nc_wgetch(win, &ch, meta)
*
* --> Read a character from a window.
*
* In no-delay mode (nodelay has been called), ERR is returned if no
*
* input is waiting.
*
* In half-delay mode (halfdelay has been called), waits until a
character*
* is typed or the specified timeout has been reached.
*
* In delay mode, waits until system passes text through.
*
****************************************************************************
***/
NCURSES_EXPORT (int)
_nc_wgetch (WINDOW * win, unsigned long *result, int use_meta)
{
int ch;
int delay = -1; /* Infinite timeout by default */
T ((T_CALLED ("_nc_wgetch(%p)"), win));
*result = 0;
if (!win) returnCode (ERR);
if (cooked_key_in_fifo ()) {
if (wgetch_should_refresh (win))
wrefresh (win);
*result = fifo_pull ();
returnCode (OK);
}
/*
* Handle cooked mode. Grab a string from the screen,
* stuff its contents in the FIFO queue, and pop off
* the first character to return it.
*/
if (head == -1 && !SP->_raw && !SP->_cbreak) {
char buf[MAXCOLUMNS], *sp;
TR (TRACE_IEVENT, ("filling queue in cooked mode"));
wgetnstr (win, buf, MAXCOLUMNS);
/* ungetch in reverse order */
ungetch ('\n');
for (sp = buf + strlen (buf); sp > buf; sp--)
ungetch (sp[-1]);
*result = fifo_pull ();
returnCode (OK);
}
if (win->_use_keypad != SP->_keypad_on)
_nc_keypad (win->_use_keypad);
if (wgetch_should_refresh (win))
wrefresh (win);
/* Compute delay for acquiring characters.
Has been set by nodelay, haldelay, wtimeout or notimeout. */
if ((win->_notimeout == FALSE) && (win->_delay >= 0 || SP->_cbreak > 1)) {
/*
* COMMENT :
* I don't know if 'notimeout' set a timer for input escape-sequences
* only or for all keys (see man curs_inopts) ???
*/
TR (TRACE_IEVENT, ("timed delay in wgetch()"));
if (SP->_cbreak > 1) /* half-delay mode first */
delay = (SP->_cbreak - 1) * 100;
else
delay = win->_delay;
TR (TRACE_IEVENT, ("delay is %d milliseconds", delay));
}
ch = kgetch (delay);
if (ch == KEY_MOUSE) {
/*
* This is tricky. We only want to get special-key
* events one at a time. But we want to accumulate
* mouse events until either (a) the mouse logic tells
* us it's picked up a complete gesture, or (b)
* there's a detectable time lapse after one.
*
* Note: if the mouse code starts failing to compose
* press/release events into clicks, you should probably
* increase the wait with mouseinterval().
*/
/*
* COMMENT :
* Probably something false here because I have not looked at mouse code
*/
int runcount = 0;
do {
++runcount;
if (SP->_mouse_inline (SP)) break;
if (SP->_maxclick < 0) break;
ch = kgetch (SP->_maxclick);
} while (ch == KEY_MOUSE && (!SP->_mouse_parse (runcount)));
if (runcount > 0 && ch != KEY_MOUSE) {
/* mouse event sequence ended by keystroke, push it */
ungetch (ch);
ch = KEY_MOUSE;
}
}
if (ch == ERR) {
#if USE_SIZECHANGE
if (SP->_sig_winch) {
_nc_update_screensize ();
/* resizeterm can push KEY_RESIZE */
if (cooked_key_in_fifo ()) {
*result = fifo_pull ();
returnCode (*result >= KEY_MIN ? KEY_CODE_YES : OK);
}
}
#endif
returnCode (ERR);
}
/*
* If echo() is in effect, display the printable version of the
* key on the screen. Carriage return and backspace are treated
* specially by Solaris curses:
*
* If carriage return is defined as a function key in the
* terminfo, e.g., kent, then Solaris may return either ^J (or ^M
* if nonl() is set) or KEY_ENTER depending on the echo() mode.
* We echo before translating carriage return based on nonl(),
* since the visual result simply moves the cursor to column 0.
*
* Backspace is a different matter. Solaris curses does not
* translate it to KEY_BACKSPACE if kbs=^H. This does not depend
* on the stty modes, but appears to be a hardcoded special case.
* This is a difference from ncurses, which uses the terminfo entry.
* However, we provide the same visual result as Solaris, moving the
* cursor to the left.
*/
if (SP->_echo && !(win->_flags & _ISPAD)) {
chtype backup = (ch == KEY_BACKSPACE) ? '\b' : ch;
if (backup < KEY_MIN)
wechochar (win, backup);
}
/*
* Simulate ICRNL mode
*/
if ((ch == '\r') && SP->_nl)
ch = '\n';
/* Strip 8th-bit if so desired. We do this only for characters that
* are in the range 128-255, to provide compatibility with terminals
* that display only 7-bit characters. Note that 'ch' may be a
* function key at this point, so we mustn't strip _those_.
*/
if (!use_meta)
if ((ch < KEY_MIN) && (ch & 0x80))
ch &= 0x7f;
T (("wgetch returning : %s", _tracechar (ch)));
*result = ch;
returnCode (ch >= KEY_MIN ? KEY_CODE_YES : OK);
}
----------------------------------------------------------------------------
----
- Philippe
- lib_getch.c, Philippe Blain, 2002/08/04
- lib_getch.c,
Philippe Blain <=