bug-ncurses
[Top][All Lists]
Advanced

[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






reply via email to

[Prev in Thread] Current Thread [Next in Thread]