What seems to be a very simple problem is actually pretty complicated. The root of the problem is that terminals operate in two different modes: raw and cooked. Cooked mode, which is the default, means that the terminal does not read characters, it reads lines. So, your program never receives any input at all unless a whole line is entered (or an end of file character is received). The way the terminal recognizes an end of line is by receiving a newline character (0x0A) which can be caused by pressing the Enter key. To make it even more confusing, on a Windows machine pressing Enter causes TWO characters to be generated, (0x0D and 0x0A).
So, your basic problem is that you want a single-character interface, but your terminal is operating in a line-oriented (cooked) mode.
The correct solution is to switch the terminal to raw mode so your program can receive characters as the user types them. Also, I would recommend the use of getchar() rather than getc() in this usage. The difference is that getc() takes a file descriptor as an argument, so it can read from any stream. The getchar() function only reads from standard input, which is what you want. Therefore, it is a more specific choice. After your program is done it should switch the terminal back to the way it was, so it needs to save the current terminal state before modifying it.
Also, you should handle the case that the EOF (0x04) is received by the terminal which the user can do by pressing CTRL-D.
Here is the complete program that does these things:
#include <stdio.h>
#include <termios.h>
main(){
tty_mode(0); /* save current terminal mode */
set_terminal_raw(); /* set -icanon, -echo */
interact(); /* interact with user */
tty_mode(1); /* restore terminal to the way it was */
return 0; /* 0 means the program exited normally */
}
void interact(){
while(1){
printf( "\nPlease enter a choice: \n1)quit\n2)Something\n" );
switch( getchar() ){
case 'q': return;
case '2': {
printf( "Hi\n" );
break;
}
case EOF: return;
}
}
}
/* put file descriptor 0 into chr-by-chr mode and noecho mode */
set_terminal_raw(){
struct termios ttystate;
tcgetattr( 0, &ttystate); /* read current setting */
ttystate.c_lflag &= ~ICANON; /* no buffering */
ttystate.c_lflag &= ~ECHO; /* no echo either */
ttystate.c_cc[VMIN] = 1; /* get 1 char at a time */
tcsetattr( 0 , TCSANOW, &ttystate); /* install settings */
}
/* 0 => save current mode 1 => restore mode */
tty_mode( int operation ){
static struct termios original_mode;
if ( operation == 0 )
tcgetattr( 0, &original_mode );
else
return tcsetattr( 0, TCSANOW, &original_mode );
}
As you can see, what seems to be a pretty simple problem is quite tricky to do properly.
A book I can highly recommend to navigate these matters is "Understanding Unix/Linux Programming" by Bruce Molay. Chapter 6 explains all the things above in detail.
if (*choice == '2') printf("Hi\n"); else printf("Oops: %x\n", *choice & 0xff );fgetswith a larger buffer, say 80 chars or so. Or stick another loop in there that reads until the next newline.while(1)why not just saywhile(choice != 'q'). Then eliminate the first if statement all togetherfflushthough), you can'tgetcreliably on a dirty buffer2<enter>, the 2 and a newline are both queued for reading onstdin. So yourgetcloop consumes the 2, runs again to consume the newline. This produces the extra prompt, but since you don't have a switch case for\n, that's all it does. Then it finally prints the prompt again and waits for the next input. The main idea to take away is thatgetcrequires you to manage the newlines in the input buffer yourself, which can be painful. Thefgetsapproach is generally simpler.