I think it would be useful to step back and think about all the moving parts that exist in the runtime of your program.
, . (, - , ssh). .
( ), . , , , , , , .
, , . API- C, , termios.
, : TTY demystified. TTY. termios , , stty, termios.
( , , , Linux, Unix- , Mac OS X.)
, "" , , .
, , - ECHO ICANON. .
ECHO, , , . , , , .
ICANON, , , . read(). , , , .
:
1: , ECHO ICANON.
2: , .
3: , , ECHO ICANON.
4: .
5: 1. .
3 , . , stdin read() , . , , , -. .
, . , termios fcntl() ( O_NONBLOCK, ), , read() errno, EAGAIN, . - poll() select(), , ; , read().
, select(), :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <termios.h>
struct termios g_terminalSettings;
void disableInput(void);
void enableInput(void);
void discardInputBuffer(void);
void discardInputLine(void);
void setTermiosBit(int fd, tcflag_t bit, int onElseOff );
void turnEchoOff(void);
void turnEchoOn(void);
void turnCanonOff(void);
void turnCanonOn(void);
int main(void) {
disableInput();
printf("welcome to the game\n");
int line = 1;
int quit = 0;
while (1) {
for (int i = 0; i < 3; ++i) {
printf("line of dialogue %d\n",line++);
sleep(1);
}
enableInput();
int input;
while (1) {
printf("choose a number in 1:3 (-1 to quit)\n");
int ret = scanf("%d",&input);
discardInputLine();
if (ret == EOF) {
if (ferror(stdin)) { fprintf(stderr, "[error] scanf() failed: %s", strerror(errno) ); exit(1); }
printf("end of input\n");
quit = 1;
break;
} else if (ret == 0) {
printf("invalid input\n");
} else if (input == -1) {
quit = 1;
break;
} else if (!(input >= 1 && input <= 3)) {
printf("number is out-of-range\n");
} else {
printf("you entered %d\n",input);
break;
}
}
if (quit) break;
disableInput();
}
printf("goodbye\n");
return 0;
}
void disableInput(void) {
turnEchoOff();
turnCanonOff();
}
void enableInput(void) {
discardInputBuffer();
turnCanonOn();
turnEchoOn();
}
void turnEchoOff(void) { setTermiosBit(0,ECHO,0); }
void turnEchoOn(void) { setTermiosBit(0,ECHO,1); }
void turnCanonOff(void) { setTermiosBit(0,ICANON,0); }
void turnCanonOn(void) { setTermiosBit(0,ICANON,1); }
void setTermiosBit(int fd, tcflag_t bit, int onElseOff ) {
static int first = 1;
if (first) {
first = 0;
tcgetattr(fd,&g_terminalSettings);
}
if (onElseOff)
g_terminalSettings.c_lflag |= bit;
else
g_terminalSettings.c_lflag &= ~bit;
tcsetattr(fd,TCSANOW,&g_terminalSettings);
}
void discardInputBuffer(void) {
struct timeval tv;
fd_set rfds;
while (1) {
FD_ZERO(&rfds);
FD_SET(0,&rfds);
tv.tv_sec = 0;
tv.tv_usec = 0;
if (select(1,&rfds,0,0,&tv) == -1) { fprintf(stderr, "[error] select() failed: %s", strerror(errno) ); exit(1); }
if (!FD_ISSET(0,&rfds)) break;
char buf[500];
ssize_t numRead = read(0,buf,500);
if (numRead == -1) { fprintf(stderr, "[error] read() failed: %s", strerror(errno) ); exit(1); }
printf("[debug] cleared %d chars\n",numRead);
}
}
void discardInputLine(void) {
int c;
while ((c = getchar()) != EOF && c != '\n');
}
, discardInputLine() , discardInputBuffer() enableInput(). , , scanf(), . , . , , , , .
, :
welcome to the game
line of dialogue 1
line of dialogue 2
line of dialogue 3
[debug] cleared 12 chars
choose a number in 1:3 (-1 to quit)
0
number is out-of-range
choose a number in 1:3 (-1 to quit)
4
number is out-of-range
choose a number in 1:3 (-1 to quit)
asdf
invalid input
choose a number in 1:3 (-1 to quit)
asdf 1 2 3
invalid input
choose a number in 1:3 (-1 to quit)
0 1
number is out-of-range
choose a number in 1:3 (-1 to quit)
1 4
you entered 1
line of dialogue 4
line of dialogue 5
line of dialogue 6
choose a number in 1:3 (-1 to quit)
2
you entered 2
line of dialogue 7
line of dialogue 8
line of dialogue 9
[debug] cleared 256 chars
[debug] cleared 256 chars
[debug] cleared 256 chars
[debug] cleared 256 chars
[debug] cleared 256 chars
[debug] cleared 256 chars
[debug] cleared 256 chars
[debug] cleared 238 chars
choose a number in 1:3 (-1 to quit)
-1
goodbye
12 , . . , . ( , ), , , select()/read().