[comp.lang.c] Trouble with Curses

draper@inmet.inmet.com (10/29/90)

/* Written  1:38 pm  Oct 27, 1990 by ramsey@NCoast.ORG in inmet:comp.lang.c */
/* ---------- "Trouble with curses" ---------- */

Hi, this is Connie. I'm accessing this net with a friends account. 
I have a question about the curses package. Heres the skeletal code:

#include <stdio.h>
#include <curses.h>
#include <term.h>

main() {
  int ch;  
  /* init curses package */
  initscr();
  nonl();	
  cbreak();  
  noecho();
  for (;;) {
    ch = getch();
    switch(ch) {
    case 27: /* escape */
      do_escape();
      break;
      [ ... irrelevant stuff deleted ... ]
      }
  }
}

do_escape() {
  switch(tolower(getch())) {
  case 'e': 
    do_exit(); 
    break;
  [ ... irrelevant stuff deleted ... ]
  }
}


Basically what is happening is the the user presses ESCAPE key follow by 
'E' key to exit. But I have to press 'E' twice after pressing ESCAPE 
to get an exit. I can't figure out why. Does anybody know ?

I would prefer a post to net but if you must send mail please do not
send it to this box instead send it to me at aj398@CLEVELAND.FREENET.EDU ,

Thankyou.
/* End of text from inmet:comp.lang.c */



I made a few adjustments to the code above and then compiled it
on a Sun4. I got it to run without any problems. Below is a copy of
the code after I modified it.

It was compiled and linked using the following command:

	cc test.c -lcurses -ltermcap -o test

The modifications I made had nothing to do with the routines used
to read in characters. It almost sounds like when you press the 'E'
key the first time it is not getting read in by curses until a
subsequent keystroke causes the getch() in the switch to read the
character out of the buffer. I would modify the code to get the
character and then switch on the char. By doing this you can run it
under a debugger and see if the call to getch() is actually getting
a char when it is suppossed to.

- Dave


Internet: draper@inmet.inmet.com
UUNET: uunet!inmet!draper
-----
Intermetrics Microsystems Software, Incorporated
733 Concord Avenue	   Cambridge,  MA  02138
(617) 661-0072 x4573


------------------------------------------------------------------------
/* Modified code with comments */

#include <stdio.h>
#include <curses.h>
/* #include <term.h>  I did not need this */
#include <ctype.h>  /* needed this for the isupper, tolower macros */


main() {
  int ch;  
  /* init curses package */
  initscr();
  nonl();	
  cbreak();  
  noecho();
  for (;;) {
    ch = getch();
    switch(ch) {
    case 27: /* escape */
      do_escape();
      break;
      /* ... irrelevant stuff deleted ... */
      }
  }
}

do_escape() {

  switch(tolower(getch())) {
  /* I would watch out on the above line of code. tolower is a */
  /* macro that might have undesriable effects should the user */
  /* enter a character that is already in lower case. A good   */
  /* thing to do would be check if the char was an upper case  */
  /* using "isupper" before calling "tolower".                 */

  case 'e': 
    do_exit(); /* added a definition and bosy for this function */ 
    break;
  /* ... irrelevant stuff deleted ... */
  }
}

/* clean up and exit */

do_exit()
{
  endwin();
  exit();
}

/* end of modified code */


Internet: draper@inmet.inmet.com
UUNET: uunet!inmet!draper
-----
Intermetrics Microsystems Software, Incorporated
733 Concord Avenue	   Cambridge,  MA  02138
(617) 661-0072 x4573

akcs.dgy@vpnet.chi.il.us (Donald Yuniskis) (10/29/90)

>Basically what is happening is the the user presses ESCAPE key follow by 
>'E' key to exit. But I have to press 'E' twice after pressing ESCAPE 
>to get an exit. I can't figure out why. Does anybody know ?

most "magic buttons" (function keys, etc) send an escape sequence.  curses
tries to build these escape sequences into special "KEY_" (see curses.h)
codes to make you life easier (:->).  to do this, when an ESC is received,
curses starts a nominal one second timer.  if other keys come in before this
timer expires, curses tries to combine them into a recognized escape
sequence which is then reduced to a "KEY_" constant and propagated through
the getch() mechanism.  Presumably, this process only is acive when you
have enabled keypad() mode.  Suggestions:  try a pause after the ESC to
verify this is the mechanism which is hosing you.  A work around is to
use a "non-escape" sequence (the ESC key is frowned upon even tho' _everyone_
uses it!) or, try to find a "KEY_" code which may be supported by the
variety of terminals you intend to support and code _that_ as the "ESC e"
you are trying to use (if ((ch = getch()) == KEY_?????) ....)
also, may wish to explicitly disable keypad() mode...
(are we having fun yet??)

catfood@NCoast.ORG (Mark W. Schumann) (10/29/90)

In article <1990Oct27.173826.29771@NCoast.ORG> ramsey@NCoast.ORG
(Connie using Cedric Ramsey's account) writes:

>Hi, this is Connie. I'm accessing this net with a friends account. 
   ... and welcome.  Too bad about the Browns the other day, huh?

>I have a question about the curses package. Heres the skeletal code:
> [Much deleted for brevity]

>do_escape() {
>  switch(tolower(getch())) {
>  case 'e': 
>    do_exit(); 
>    break;
>  [ ... irrelevant stuff deleted ... ]
>  }

>Basically what is happening is the the user presses ESCAPE key follow by 
>'E' key to exit. But I have to press 'E' twice after pressing ESCAPE 
>to get an exit. I can't figure out why. Does anybody know ?

Betcha your tolower is implemented as a macro, something like this:

#define tolower(c) (isupper (c) ? c + 'a' - 'A' : c)

(Portability purists will quibble with this, but what the hey it works
in DOS.  My point is this:)  Notice that there is no way for this macro
to help but evaluate the argument 'c' twice.  The code generated in your
case would be:

  do_escape() {
    switch (isupper (getch()) ? getch() + 'a' - 'A' : getch()) {
       case 'e':
          ....dah dah dah...
    }

So your object code evaluates isupper(getch()) [gets a keystroke here],
then depending on the result it evaluates getch() + 'a' - 'A' OR
plain getch() [gets *a new* keystroke here in either case].

Your problem is the usual macro yuckies--expanding code inline causes
this type of re-evaluation.  Try this instead:

   do_escape() {
   int c;  
      c = getch();
      switch (tolower (c)) {
         .........
         }
      }

The variable 'c' of course gets evaluated only once as 'getch().'  THEN
you process its value without risking the side effects.

Alternative:
    do_escape() {
       switch (getch()) {
           case 'e':
           case 'E':
              do_exit();          /* K & R says be careful, but it works */
              break;
           ......

Hope this helps and that I wasn't over-explaining.

-- 
============================================================
Mark W. Schumann  3111 Mapledale Avenue, Cleveland 44109 USA
Domain: catfood@ncoast.org
UUCP:   ...!mailrus!usenet.ins.cwru.edu!ncoast!catfood
============================================================

ravim@gtenmc.UUCP (ravim) (10/30/90)

In article <1990Oct27.173826.29771@NCoast.ORG> ramsey@NCoast.ORG (Cedric Ramsey) writes:
 >
 >Hi, this is Connie. I'm accessing this net with a friends account. 
 >I have a question about the curses package. Heres the skeletal code:
 >
 >#include <stdio.h>
 >#include <curses.h>
 >#include <term.h>
 >
 >    ch = getch();
 >    switch(ch) {
 >    case 27: /* escape */
 >      do_escape();
 >      break;
 >      [ ... irrelevant stuff deleted ... ]
 >      }
 >  }
 >}
 >
 >do_escape() {
 >  switch(tolower(getch())) {

   If 'tolower' routine is a macro (defined in 'ctype.h') that evaluates
   its argument twice, then this could be the cause for your problem of
   having to press 'E' twice.

   One solution is :-

do_escape() {
  register char c;

  c = getch();
  switch(tolower(c)) {
    case 'e':
     ....

 >Basically what is happening is the the user presses ESCAPE key follow by 
 >'E' key to exit. But I have to press 'E' twice after pressing ESCAPE 
 >to get an exit. I can't figure out why. Does anybody know ?
 >
 >I would prefer a post to net but if you must send mail please do not
 >send it to this box instead send it to me at aj398@CLEVELAND.FREENET.EDU ,
 >
 >Thankyou.


  Good luck.

	-	Ravi Mandava

-- 
**********************   #include <stddisclaimer.h>  **************************
Ravi Mandava			e-mail :	ravim@gtenmc.gtetele.com
					  or    ravim@gtenmc.UUCP
*******************************************************************************

draper@inmet.inmet.com (10/30/90)

	I received mail today saying that I had correctly identified the
	line of code in the curses problem that was causing the problem,
	but I had missed the problem altogehter.

	These were my comments on the following line:


  	switch(tolower(getch())) {
  	/* I would watch out on the above line of code. tolower is a */
  	/* macro that might have undesriable effects should the user */
  	/* enter a character that is already in lower case. A good   */
  	/* thing to do would be check if the char was an upper case  */
  	/* using "isupper" before calling "tolower".                 */

	It was brought to my attemntion that the macro 'tolower'
	might expand out so that getch() is called twice. That
	would cause the error that was described in the posting.
	I could not beleive that I missed that. I plead guilty
	to not thinking of that, BUT I DID NOT miss it. On the
	system I am using, Sun4 , in the file ctype.h , the macro
	tolower is defined as follows:

	#define tolower(c) ((c)-'A'+'a')

	I would like to thank the indivdual who sent mail to me
	on this (sorry I mistakenly deleted your mail before
	I saved it to a file) . It is a situation that I haven't
	ran across myself.

	Moral of the story: Always make sure you know, or at least
	think you know, how your macros will expand. When in doubt
	run the code throught cpp and look at the expansions.

	- Dave


	Internet: draper@inmet.inmet.com
	UUNET: uunet!inmet!draper
	-----
	Intermetrics Microsystems Software, Incorporated
	733 Concord Avenue	   Cambridge,  MA  02138
	(617) 661-0072 x4573
		

boyd@necisa.ho.necisa.oz (Boyd Roberts) (10/30/90)

In article <20900012@inmet> draper@inmet.inmet.com writes:
>
>Hi, this is Connie. I'm accessing this net with a friends account. 
>I have a question about the curses package. Heres the skeletal code:
>
> [deleted]
>
>  switch(tolower(getch())) {
>

What's the bet that tolower() is a macro?  And is coded to evaluate its
argument _twice_?  That way you get two calls to getch() instead of one.

Eg:

    #define tolower(c)	(chartab[(c)] & C_UPPER ? (c) - 'A' + 'a' : (c))

You've got to watch those macros when their arguments have side affects.


Boyd Roberts			boyd@necisa.ho.necisa.oz.au

``When the going gets wierd, the weird turn pro...''

weimer@ssd.kodak.com (Gary Weimer) (11/08/90)

In article <20900012@inmet> draper@inmet.inmet.com writes:
>
>  switch(tolower(getch())) {
>  /* I would watch out on the above line of code. tolower is a */
>  /* macro that might have undesriable effects should the user */
>  /* enter a character that is already in lower case. A good   */
>  /* thing to do would be check if the char was an upper case  */
>  /* using "isupper" before calling "tolower".                 */

ANSI complient definition of tolower() does call islower() before
making the conversion. In this case, the code "expands" to:

    switch(islower(getch()) ? tolower(getch()) : (getch())) {

Note that you are now always making two calls to getch() (as someone
has already pointed out is a possibility).

scs@adam.mit.edu (Steve Summit) (11/08/90)

In article <1990Nov7.194154.6071@ssd.kodak.com> weimer@ssd.kodak.com (Gary Weimer) writes:
>ANSI complient definition of tolower() does call islower() before
>making the conversion. In this case, the code "expands" to:
>    switch(islower(getch()) ? tolower(getch()) : (getch())) {
>Note that you are now always making two calls to getch() (as someone
>has already pointed out is a possibility).

Yes, and the earlier pointings-out were almost as misleading as
this one.  ANSI does specify (Section 4.3.2.1) that tolower()
leaves non-upper-case letters alone, HOWEVER it also requires
(Section 4.1.6) that "Any invocation of a library function that
is implemented as a macro shall expand to code that evaluates
each of its arguments exactly once..."  (The rules in Section
4.1.6 apply unless they are explicitly retracted for a particular
library routine, but Section 4.3.2 contains no such retraction
for tolower or toupper.)  We can conclude, then, that an ANSI-
compliant tolower must be implemented as an actual function, or
perhaps as a macro with a lookup table, but certainly NOT as

	#define tolower(c) (isupper(c) ? (c) + ('a' - 'A') : (c)) /* WRONG */

To repeat: switch(tolower(getchar())) is "safe" under an ANSI
compiler.  (Whether that's the best way to write it is another
question.)  The earlier correspondent was having to hit double
characters due to some other problem, or due to an invalid
tolower() implementation.  Moving the getchar() out of the
tolower() is not required, except to work around a buggy
compiler/library, or perhaps to improve readability or error
checking.

                                            Steve Summit
                                            scs@adam.mit.edu

gwyn@smoke.brl.mil (Doug Gwyn) (11/09/90)

In article <1990Nov7.194154.6071@ssd.kodak.com> weimer@ssd.kodak.com (Gary Weimer) writes:
-ANSI complient definition of tolower() does call islower() before
-making the conversion. In this case, the code "expands" to:
-    switch(islower(getch()) ? tolower(getch()) : (getch())) {
-Note that you are now always making two calls to getch() (as someone
-has already pointed out is a possibility).

While an implementation may evaluate the argument twice, it is definitely
not conformant to the ANSI C standard if it does.

duncan@sysint.uucp (Duncan MacGregor) (11/14/90)

    In a recent posting, "draper" (sorry, I lost track of the precise article)
complained that characters read through the curses library disappeared after
an ESC (escape, octal 033) character was read.  In essence, the 'C' code was
expecting an ESC followed by the letter 'e' to indicate "exit from the system".

    The problem lies not in curses but (I believe) in termcap.  I know the
problem exists in "terminfo", which is the replacement for termcap in System V
Rel. 2 and all descendant systems;  I believe it was inherited (or should I
say inherent?) from termcap.

    The problem is caused by the necessity of dealing with "function keys",
"cursor keys", and other symbolic keys on the keyboard.  When one of these keys
is pressed, the terminal usually sends a STRING of characters to the host.  In
almost every case, the first character of such a string is ESC, to warn the host
(and thus termcap/terminfo) that a function key was pressed.  These strings are
therefore often known as "escape sequences".

    When an ESC character is received from the terminal, however, termcap or
terminfo can NOT assume that a function/cursor key was pressed;  it is perfectly
possible (as in the case of ESC+'e' above) that the user typed the ESC key by
itself.  The solution for this can only be described as kludgy (I think Henry
Spencer cited it recently with some rather uncomplementary prose).  When the
ESC is received, a timer is started.  If the timer expires (after ~1/10 sec.)
and another character has NOT been received, the ESC character is assumed to
have come from the ESC key, and not from a function key.  If a character
arrives before the expiration, it is added (as part of an escape sequence!) and
the timer restarted.  While it is possible to change the length of time before
the timer expires in the termcap/terminfo source code (it is actually using the
UNIX terminal driver for this), a fast typist can easily beat most such settings
of the timer.  The letter 'e', therefore, since it is typed right after an ESC,
may be grabbed as part of a supposed "escape sequence" string.  Even if that
does not happen, there IS a delay before the timer expires.  In terminfo, if
the characters after ESC do not correspond with a known key for the current
terminal type, it will re-interpret the string as separate characters (I think).
This certainly seems to be happening with the example given by "draper".

    Some newer terminals (eg. the DEC vt200) can use a special 8-bit character
to mark the beginning of "escape sequence" strings if you use the terminal's
"8-bit" mode.  Alas, there is NO support for 8-bit characters in terminfo or
termcap (as far as I know).  The cryptic notation used in the termcap and term-
info databases cannot support it either.  Just a suggestion:  maybe somebody
could develop a version of termcap that would allow macros to be freely used
in declaring strings or numbers?  That might make it MUCH easier to represent
8-bit characters that start "escape sequence" strings.

Hope this is helpful ...


-- 
  Duncan MacGregor              Systems Interface Inc.
  UUCP: uunet!mitel!cunews!cognos!sysint!duncan || duncan@sysint.UUCP
#include <std/disclaim.h>;
Life is a zig-zag;  too many people zig as always - they shoulda zagged. (Anon)

chris@mimsy.umd.edu (Chris Torek) (11/15/90)

In article <1990Nov14.061635.6183@sysint.uucp> duncan@sysint.uucp
(Duncan MacGregor) writes:
>... characters read through the curses library disappeared after
>an ESC (escape, octal 033) character was read. ...
>    The problem lies not in curses but (I believe) in termcap.  I know the
>problem exists in "terminfo", which is the replacement for termcap in System V
>Rel. 2 and all descendant systems;  I believe it was inherited (or should I
>say inherent?) from termcap.

No, the problem is in curses (if any particular bit of software can be
blamed), not termcap or terminfo.  Neither termcap nor terminfo provide
any input-handling routines.  They exist only to describe the terminal's
interface and handle output parameters (for, e.g., cursor motion).

>The problem is caused by the necessity of dealing with "function keys",
>"cursor keys", and other symbolic keys on the keyboard.

Right.  The timing code for curses `getch', however, is entirely in
curses (and the 4BSD termcap-based curses provides no function key
decoding at all, so it is not in all versions).

Because keystrokes can be arbitrarily delayed (e.g., by networks), the
timer hack is not such a great solution.  My own solution is never to
use function keys.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 405 2750)
Domain:	chris@cs.umd.edu	Path:	uunet!mimsy!chris