Why getch() returns before press any key?
Asked Answered
H

2

10
int main(int argc, char *argv[], char *env[])
{
    printf("Press any key to exit.\n");
    getch();
    return 0;
}

According to the man page,

getch should wait until any key is pressed

...but in fact it returns directly before press any key. (The value returned is -1).

Why?


Update

I'm on Linux. How can I implement Press any key to exit., if not using getch()?

getchar() will only return after press Enter, it's not what I want.

Huneycutt answered 14/9, 2011 at 1:52 Comment(0)
A
13

On Linux, getch() is probably the curses function, which is quite different from the Windows-specific function of the same name. curses (ncurses) is probably overkill for what you want to do.

The simplest approach would be to wait until the user presses Enter, which you can do like this:

int c;
printf("Press <enter> to quit: ");
fflush(stdout);
while ((c = getchar()) != '\n' && c != EOF) {
    /* nothing */
}

If you really want the user to be able to press any key, not just Enter, you can do something like this:

system("stty cbreak -echo");
getchar();
system("stty cooked echo");

The second stty is intended to restore the tty to reasonable settings (it should really restore them to whatever they were, but saving and restoring the state is a little more complicated). There are probably cleaner ways to do that (using whatever library functions the stty program itself uses).

EDIT: Reading a single character without waiting for Enter is a frequently asked question. In fact, it's question 19.1 in the comp.lang.c FAQ.

EDIT2: I haven't done much work with curses recently, but I just did some playing around with it. It appears that getch() won't work unless you first call initscr() -- and initscr() clears the screen. curses is intended for use with applications like text editors that need full control of the display. There may be a way to use getch() without taking control of the screen, but I haven't found it.

The system("stty ...") kludge might actually be the best approach.

EDIT3: The termios solution in the other answer is probably the best (system("stty ...") is simpler, but invoking an external program feels like overkill.

As for the OP's comment "I can't believe Press any key to exit. is so troublesome to do in c", yes, that does seem odd -- but on further thought there are valid reasons for it.

Take a look at the programs installed on a typical Unix or Linux system. I think you'll find that very few of them require this kind of input (waiting for a single keypress).

A lot of programs work with command line arguments and data read from files or from stdin. Anything the user types is input data, not commands or responses to prompts.

Some programs do ask for confirmation for some actions (installers like apt-get and cpan often do this) -- but they usually read a line of input and check the first character. Or, for some drastic actions, they might require you to type the whole word "yes" followed by Enter (you don't want to reformat your hard drive because you accidentally hit a key).

Of course a lot of programs (text editors, file viewers) read single-character non-echoing input, but such programs tend to be curses-based; they take control of the entire terminal window.

Finally, a number of programs have GUI interfaces (web browsers, etc.); they probably don't even read from stdin.

Most production Unix programs don't use or need Press any key to exit prompts. They just do their jobs, often silently, and then terminate so you can do the next thing. The need exists largely for relatively elementary programs, such as homework assignments. Not that there's anything wrong with homework assignments, but the system as a whole isn't primarily designed to support such usage.

Allahabad answered 14/9, 2011 at 2:29 Comment(11)
thanks,but I've found out it's a bug of ncurse: www.cpan.org/authors/id/WPS/Curses-a8.readmeHuneycutt
@new_perl: Really? That seems unlikely. The document you referenced refers to a 16-year-old Perl binding to ncurses. If there was a bug in ncurses at the time, it shouldn't affect you now unless you have a very old system.Allahabad
/lib64/libncurses.so.5.6, do you know how I can know the version of this ncurses library?Huneycutt
Looks like it's version 5.6 (I have 5.7 on my system).Allahabad
Is it older or newer than ncurses-1.8.5?Huneycutt
Much newer. According to the document you cited, ncurses-1.8.5 was current in 1995. Do the math.Allahabad
I think your expectations are in error. In your original program, it's behaving exactly as it's documented to behave. As I said, curses is probably overkill for what you're trying to do -- but if you want to use it, you'll need to read the documentation. There's a fair amount of setup you need to do before calling getch(). And see my latest edit to my answer.Allahabad
I tried your last edit, it's still the same: exit directly with a return value -1. Trust me it should be bug of my version of ncurses.Huneycutt
What is miserable is that that FAQ is something like 30 years old and people still keeping asking the same damned questions. :(Truncation
@new_perl: It should return -1 in those circumstances. Your ncurses is almost certainly working correctly. You just need to use it correctly. Did you read question 19.1 of the FAQ? What exactly did you try? If you show us the code you tried, we can probably tell you what you're doing wrong.Allahabad
@new_perl: Then you didn't read the FAQ I cited, or didn't pay sufficient attention to it.Allahabad
D
6

Here's a minimal example

#include <curses.h>

int main(){
    initscr();
    cbreak();
    noecho();
    printw("Press any key to continue.");
    refresh();
    getch();
    endwin();
    return 0;
}

But there doesn't seem to be any way to use ncurses without clearing the screen. The Stain of Overkill, perhaps?!

Edit

Here's another way I got working. This does not use curses, nor does it clear the screen.

#include <stdio.h>
#include <termios.h>
#include <unistd.h>

int main() {
    struct termios old,new;

    tcgetattr(fileno(stdin),&old);
    tcgetattr(fileno(stdin),&new);
    cfmakeraw(&new);
    tcsetattr(fileno(stdin),TCSANOW,&new);
    fputs("Press any key to continue.",stdout);
    fflush(NULL);
    fgetc(stdin);
    tcsetattr(fileno(stdin),TCSANOW,&old);

    return 0;
}

The manpage says cfmakeraw() might not be fully portable. But it's just a shorthand for setting a whole mess of flags:

Raw mode
    cfmakeraw() sets the terminal to something like the "raw" mode of the old  Version  7  terminal
    driver:  input  is  available character by character, echoing is disabled, and all special pro-
    cessing of terminal input and output characters is disabled.  The terminal attributes  are  set
    as follows:

       termios_p->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
                       | INLCR | IGNCR | ICRNL | IXON);
       termios_p->c_oflag &= ~OPOST;
       termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
       termios_p->c_cflag &= ~(CSIZE | PARENB);
       termios_p->c_cflag |= CS8;
Dagan answered 14/9, 2011 at 5:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.