Hi, I am writing, done actually, a littl app that carries on a few things that I ask it to do. There is a point where I need my app to wait till I press any key so it can keep moving. I tried with getchar, even doing fflush(stdin) first, but the app goes right throught it, it seems like something forces a bypass over getchar, so I decided to try read() and wait for 1 character. It works great.. but is it elegant and convenient to use a system call for a usermode app ? Shouldnt I use a library wrapper? What glibc function should I use to wait for a return(enter) or any other key to be pressed? This is C. Thanks, Raul
Raúl Gutiérrez Segalés wrote:
Hi, Hi
I am writing, done actually, a littl app that carries on a few things that I ask it to do. There is a point where I need my app to wait till I press any key so it can keep moving.
I tried with getchar, even doing fflush(stdin) first, but the app goes right throught it, it seems like something forces a bypass over getchar, so I decided to try read() and wait for 1 character.
fflush can only be used on writeable streams, because, actually, when you fflush a stream, glibc calls write() on its descriptor. So, fflush(stdin) must *not* be used (on some systems, it may even cause an error). What are the characters you receive after having called getchar() ?? Don't forget also that, by default, the Linux terminal is in cooked mode. That is, you have to press <enter> before receiving any character. For example, execute the simple program : #include <stdio.h> int main() { int i; while ((i = fgetc(stdin)) != EOF) fprintf(stdout, " %02X\n", i); return (0); } If you only press a key, without <enter>, you will actually see nothing. But, when you press enter, you will see : $ ./example_fgetc a<enter> 61 0A b<enter> 62 0A abc<enter> 61 62 63 0A <CTRL-D> $ Note that the enter key is also processed. --> The _first_ time you call getchar(), does it react as you expected ? Does the problem only occur the following times ? You may try to run the program after a 'stty -icanon'. This will put your terminal in raw mode --> you won't have to press the enter key to make your application to react at user inputs. (to put your terminal back in normal mode, do a 'stty sane')
It works great.. but is it elegant and convenient to use a system call for a usermode app ? Shouldnt I use a library wrapper?
Well, IMHO it is elegant not to mix system calls with glibc functions...
What glibc function should I use to wait for a return(enter) or any other key to be pressed?
You can simply ;) put your terminal in raw mode. man cfmakeraw will tell you what to do. You may also find some tips in the Serial Programming HOW-TO (available at http://www.linuxdocs.org/HOWTOs/Serial-Programming-HOWTO/ ) Don't forget to put your terminal back in its previous state when exiting your app. It will make the user a *lot* more happy ;)
This is C.
Thanks, Raul
I hope this was helpful, Nicolas
Over the years I've had to do this on various Unix systems. The following C code will work under SuSE Linux and most POSIX compliant systems. --------------------------------------------------------------------- #include <stdio.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <termios.h> void GetOne() { int rv; int fd = fileno(stdin); char ch; struct termios oldflags, newflags; /*********** Reset the terminal to accept unbuffered input ***/ /* get the current oldflags */ tcgetattr(fd, &oldflags); newflags = oldflags; /* set raw input, 1 second timeout */ newflags.c_cflag |= (CLOCAL | CREAD); newflags.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /* set the terminal to the newflags settings */ tcsetattr(fd, TCSANOW, &newflags); rv = read(fd, &ch, 1); /* restore the oldflags -- This is important otherwise * your terminal will no longer echo characters */ tcsetattr(fd, TCSANOW, &oldflags); } int main() { printf("Wait for a char: "); fflush(stdout); GetOne(); /* do this after resetting the terminal because * stdin and stdout should be the same pseudo terminal */ printf("\n"); /* simply return back to beginning of line */ return 0; } ---------------------------------- -- Jerry Feldman <gaf@blu.org> Boston Linux and Unix user group http://www.blu.org PGP key id:C5061EA9 PGP Key fingerprint:053C 73EC 3AC1 5C44 3E14 9245 FB00 3ED5 C506 1EA9
Jerry Feldman <gaf@blu.org> [Fri, 30 May 2003 22:15:29 -0400]:
void GetOne()
<nitpick> This isn't a valid Prototype in ISO C, because the compiler can't distinguish between an empty parameter list and K&R definitions. So it should be void GetOne(void) </nitpick>
int main()
<nitpick> Same here, this should be int main (void) </nitpick> And you should have added that on Linux this needs to be linked against libncurses to get the lowlevel termio functions. cheers Philipp -- Philipp Thomas work: pthomas@suse.de Development, SuSE Linux AG private: philipp.thomas@t-link.de
Jerry Feldman <gaf@blu.org> [Fri, 30 May 2003 22:15:29 -0400]:
void GetOne()
<nitpick> This isn't a valid Prototype in ISO C, because the compiler can't distinguish between an empty parameter list and K&R definitions. So it should be void GetOne(void) </nitpick> I would agree with your nitpick here, but C89 very specifically allows
On Sat, 31 May 2003 05:49:51 +0200 Philipp Thomas <philipp.thomas@t-link.de> wrote: this. However, this was a quick hack. My copy of the ANSI specification, section 3.7.1 defines this. Also look at footnote 79 that has a specific example of an empty parameter list. (Note that the ISO version of the spec may have a different section number. My copy of C98 is a draft copy and is not at my fingertips).
int main()
<nitpick> Same here, this should be int main (void) </nitpick> Main is rarely used with a void argument list, but I guess it is more correct to specify void.
And you should have added that on Linux this needs to be linked against libncurses to get the lowlevel termio functions. Not true. Termios(3) is not dependent upon libncurses at all. I think you are confusing termio with termios. Check the man page for termios. The termios functions are in libc. gaf@gaf:~/src> nm /usr/lib/libncurses.a | grep tcgetattr U tcgetattr Note that this is an external reference. gaf@gaf:~/src> nm /usr/lib/libc.a | grep tcgetattr U __tcgetattr tcgetattr.o: 00000000 T __tcgetattr 00000000 W tcgetattr U __tcgetattr
In this case, in libc, tcgetattr is a weak symbol and __tcgetattr is the actual function name. -- Jerry Feldman <gaf@blu.org> Boston Linux and Unix user group http://www.blu.org PGP key id:C5061EA9 PGP Key fingerprint:053C 73EC 3AC1 5C44 3E14 9245 FB00 3ED5 C506 1EA9
Jerry Feldman <gaf@blu.org> [Sat, 31 May 2003 07:54:05 -0400]:
void GetOne()
<nitpick> This isn't a valid Prototype in ISO C, because the compiler can't distinguish between an empty parameter list and K&R definitions. So it should be void GetOne(void) </nitpick>
I would agree with your nitpick here, but C89 very specifically allows this.
I should have been more precise, sorry. At least C89 allows it (can't check ISO C98 as it's at work). It's the compiler that can't distinguish between empty parameter list and K&R definition and thus won't flag passing parameters as an error. That's why I *always* change code to use void and have caught a few bugs in packages where functions first took a parameter and later none but as there was only a K&R prototype the compiler didn't catch it.
Not true. Termios(3) is not dependent upon libncurses at all. I think you are confusing termio with termios. Check the man page for termios.
Yepp, you're right, I confused them. Shows how often I use that stuff myself :) Philipp
On Sat, 31 May 2003 16:08:49 +0200 Philipp Thomas <philipp.thomas@t-link.de> wrote:
I should have been more precise, sorry. At least C89 allows it (can't check ISO C98 as it's at work). It's the compiler that can't distinguish between empty parameter list and K&R definition and thus won't flag passing parameters as an error. This is true to some extent. A good compiler should be able to catch the error in some cases. Lint on Tru64 Unix was able to do it, and it used a YACC parser. It is relatively easy for the compiler if the function is defined first. But, I don't think that gcc has that capability.
That's why I *always* change code to use void and have caught a few bugs in packages where functions first took a parameter and later none but as there was only a K&R prototype the compiler didn't catch it. It is a good practice to always use ANSI prototypes and ANSI function definitions. The only exception might be main() which is a special case in that it takes either 0 or 2 parameters (or 3 in archaic cases).
In C++, an empty parameter list is equivalent to using void as the parameter list since C++ does not permit K&R function types. But, other than the howto, in answer to the original person's question, "s read() evil in usermode", the answer is no. It is appropriate to use standard Unix/Linux system calls in any code that is intended for use in a Unix-like system. My only comment is that it is better to use the more portable interfaces, which is why I used termios(3) and not ioctl(2). Additionally, read(2), write(2), open(2) and close(2) are normally available in non-Unix systems. -- Jerry Feldman <gaf@blu.org> Boston Linux and Unix user group http://www.blu.org PGP key id:C5061EA9 PGP Key fingerprint:053C 73EC 3AC1 5C44 3E14 9245 FB00 3ED5 C506 1EA9
On Sat, 31 May 2003, Jerry Feldman wrote:
On Sat, 31 May 2003 16:08:49 +0200 Philipp Thomas <philipp.thomas@t-link.de> wrote:
I should have been more precise, sorry. At least C89 allows it (can't check ISO C98 as it's at work). It's the compiler that can't distinguish between empty parameter list and K&R definition and thus won't flag passing parameters as an error. This is true to some extent. A good compiler should be able to catch the error in some cases. Lint on Tru64 Unix was able to do it, and it used a YACC parser. It is relatively easy for the compiler if the function is defined first. But, I don't think that gcc has that capability.
That's why I *always* change code to use void and have caught a few bugs in packages where functions first took a parameter and later none but as there was only a K&R prototype the compiler didn't catch it. It is a good practice to always use ANSI prototypes and ANSI function definitions. The only exception might be main() which is a special case in that it takes either 0 or 2 parameters (or 3 in archaic cases).
In C++, an empty parameter list is equivalent to using void as the parameter list since C++ does not permit K&R function types.
But, other than the howto, in answer to the original person's question, "s read() evil in usermode", the answer is no. It is appropriate to use standard Unix/Linux system calls in any code that is intended for use in a Unix-like system. My only comment is that it is better to use the more portable interfaces, which is why I used termios(3) and not ioctl(2). Additionally, read(2), write(2), open(2) and close(2) are normally available in non-Unix systems.
Finally, thanks! This is arrises a new question, since both using read() or using termio to put the terminal out of cooked mode solve the problem, using which of this builds up a better coding style, or better said: the correct coding style.
-- Jerry Feldman <gaf@blu.org> Boston Linux and Unix user group http://www.blu.org PGP key id:C5061EA9 PGP Key fingerprint:053C 73EC 3AC1 5C44 3E14 9245 FB00 3ED5 C506 1EA9
This is arrises a new question, since both using read() or using termio to put the terminal out of cooked mode solve the problem, using which of this builds up a better coding style, or better said: the correct coding style. First, these function make for somewhat unportable code. The code that I
On Sun, 1 Jun 2003 11:03:20 -0400 (PYT) Raúl Gutiérrez Segalés <rau@campoalto.edu.py> wrote: presented should work on most Unix and Linux systems (except maybe SCO), but on Windows, you might want to use the conio functions. As I created a separate function to wait for a keypress, it is best to make non-portable code into separate entities such that you can easily add that functionality on another platform. (by separate entity, I mean a separate function or macro). -- Jerry Feldman <gaf@blu.org> Boston Linux and Unix user group http://www.blu.org PGP key id:C5061EA9 PGP Key fingerprint:053C 73EC 3AC1 5C44 3E14 9245 FB00 3ED5 C506 1EA9
On 1 Jun 2003 at 12:37, Jerry Feldman wrote: Date sent: Sun, 1 Jun 2003 12:37:09 -0400 From: Jerry Feldman <gaf@blu.org> To: suse-programming-e@suse.com Organization: Boston Linux and Unix Subject: Re: [suse-programming-e] is read() evil in usermode? [snip...]
First, these function make for somewhat unportable code. The code that I presented should work on most Unix and Linux systems (except maybe SCO), but on Windows, you might want to use the conio functions. As I created a separate function to wait for a keypress, it is best to make non-portable code into separate entities such that you can easily add that functionality on another platform. (by separate entity, I mean a separate function or macro).
-- Jerry Feldman <gaf@blu.org>
read() is pretty portable - it's POSIX compliant, so it should be available on all Unixes, and it's also available on Win32 (Ugh!). I agree with the point about keeping potentially non-portable code seperate. I/O code often falls into this category, so it's as well to get into the habit of doing it anyway, even if you don't think there are going to be any problems. alan -- http://www.ibgames.net/alan Registered Linux user #6822 http://counter.li.org Winding Down - Weekly Tech Newsletter - subscribe at http://www.ibgames.net/alan/winding/mailing.html
<nitpick> This isn't a valid Prototype in ISO C, because the compiler can't distinguish between an empty parameter list and K&R definitions. So it should be void GetOne(void) </nitpick> In any case, I decided to make a slight change to the function. It would be more useful for the function to take the file descriptor as the
On Sat, 31 May 2003 05:49:51 +0200 Philipp Thomas <philipp.thomas@t-link.de> wrote: parameter where the original function assumed that stdin was the input device. This function can be applied to any arbitrary input terminal device. Note I added the tcflush(3) function. Also, note that there is no error checking. ------------------------------------------ #include <stdio.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <termios.h> void GetOne(int fd) { int rv; char ch; struct termios oldflags, newflags; /*********** Reset the terminal to accept unbuffered input ***/ /* get the current oldflags */ tcgetattr(fd, &oldflags); /* make a copy of the flags so we can easily restore them */ newflags = oldflags; /* set raw input */ newflags.c_cflag |= (CLOCAL | CREAD); newflags.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /* set the newflags */ tcsetattr(fd, TCSANOW, &newflags); rv = read(fd, &ch, 1); /* Flush the input queue. This takes into account the case * where an F button is pressed */ tcflush(fd, TCIFLUSH); /* restore the oldflags -- This is important otherwise * your terminal will no longer echo characters */ tcsetattr(fd, TCSANOW, &oldflags); } int main(void) { printf("Wait for a char: "); fflush(stdout); GetOne(fileno(stdin)); /* do this after resetting the terminal because * stdin and stdout should be the same pseudo terminal */ printf("\n"); /* simply return back to beginning of line */ return 0; } --------------------------------------------------- -- Jerry Feldman <gaf@blu.org> Boston Linux and Unix user group http://www.blu.org PGP key id:C5061EA9 PGP Key fingerprint:053C 73EC 3AC1 5C44 3E14 9245 FB00 3ED5 C506 1EA9
participants (5)
-
alan@ibgames.com
-
Jerry Feldman
-
Nicolas Bellido
-
Philipp Thomas
-
Raúl Gutiérrez Segalés