Originally Posted by
Paul Omans
I'm using GCC on Redhat 6.3, it doesn't seem to include conio.h.
No, conio.h is a Windows/DOS interface.
In Linux and Unix land (including Mac OS and *BSDs, basically everywhere except in Windows), this requires terminal control. The most common and recommended way is to use a Curses library -- at least one is available for Windows, too; they're all inter-compatible -- but you can also use the basic termios interface. When using stdio.h, you also need to tell the C library not to buffer inputs, using setvbuf().
Terminals have two basic states: canonical, and raw. Canonical mode is the one where you can edit each line, and only when you press Enter, it is supplied to the program (shell or your own program). These editing facilities are provided by the kernel itself; it's not just some program that is helping you.
Raw mode, as you can probably guess, is the raw mode, where the kernel does not interfere; it lets the data flow through. (The kernel can still do a lot of interesting stuff to the data, like converting newline conventions on the fly -- but all that is another topic, for another time.)
When you change the terminal to raw mode, you also need to make sure you restore it back to the original mode before your program exits, because most shells and terminal emulators do not automatically reset back to canonical mode! In fact, if you've ever had your terminal window go wonky, producing strange output, and not responding correctly to your keypresses, it is because it's in raw mode. Fortunately, there is a command called reset, which resets the terminal back to normal mode. If your terminal window goes wonky, just type "reset"+enter a couple of times, and it should recover.
Here is an example implementation with lots of comments, on how to modify the state of a terminal (where the terminal is related to the FILE stream specified as a parameter). The first one sets the terminal to raw state, and the second one restores the original state.
Code:
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
/* Call this to change the terminal related to the stream to "raw" state.
* (Usually you call this with stdin).
* This means you get all keystrokes, and special keypresses like CTRL-C
* no longer generate interrupts.
*
* You must restore the state before your program exits, or your user will
* frantically have to figure out how to type 'reset' blind, to get their terminal
* back to a sane state.
*
* The function returns 0 if success, errno error code otherwise.
*/
static int stream_makeraw(FILE *const stream, struct termios *const state)
{
struct termios old, raw, actual;
int fd;
if (!stream)
return errno = EINVAL;
/* Tell the C library not to buffer any data from/to the stream. */
if (setvbuf(stream, NULL, _IONBF, 0))
return errno = EIO;
/* Write/discard already buffered data in the stream. */
fflush(stream);
/* Termios functions use the file descriptor. */
fd = fileno(stream);
if (fd == -1)
return errno = EINVAL;
/* Discard all unread input and untransmitted output. */
tcflush(fd, TCIOFLUSH);
/* Get current terminal settings. */
if (tcgetattr(fd, &old))
return errno;
/* Store state, if requested. */
if (state)
*state = old; /* Structures can be assigned! */
/* New terminal settings are based on current ones. */
raw = old;
/* Because the terminal needs to be restored to the original state,
* you want to ignore CTRL-C (break). */
raw.c_iflag |= IGNBRK; /* do ignore break, */
raw.c_iflag &= ~BRKINT; /* do not generate INT signal at break. */
/* Make sure we are enabled to receive data. */
raw.c_cflag |= CREAD;
/* Do not generate signals from special keypresses. */
raw.c_lflag &= ~ISIG;
/* Do not echo characters. */
raw.c_lflag &= ~ECHO;
/* Most importantly, disable "canonical" mode. */
raw.c_lflag &= ~ICANON;
/* In non-canonical mode, we can set whether getc() returns immediately
* when there is no data, or whether it waits until there is data.
* You can even set the wait timeout in tenths of a second.
* This sets indefinite wait mode. */
raw.c_cc[VMIN] = 1; /* Wait until at least one character available, */
raw.c_cc[VTIME] = 0; /* Wait indefinitely. */
/* Set the new terminal settings. */
if (tcsetattr(fd, TCSAFLUSH, &raw))
return errno;
/* tcsetattr() is happy even if it did not set *all* settings.
* We need to verify. */
if (tcgetattr(fd, &actual)) {
const int saved_errno = errno;
/* Try restoring the old settings! */
tcsetattr(fd, TCSANOW, &old);
return errno = saved_errno;
}
if (actual.c_iflag != raw.c_iflag ||
actual.c_oflag != raw.c_oflag ||
actual.c_cflag != raw.c_cflag ||
actual.c_lflag != raw.c_lflag) {
/* Try restoring the old settings! */
tcsetattr(fd, TCSANOW, &old);
return errno = EIO;
}
/* Success! */
return 0;
}
/* Call this to restore the saved state.
*
* The function returns 0 if success, errno error code otherwise.
*/
static int stream_restore(FILE *const stream, const struct termios *const state)
{
int fd, result;
if (!stream || !state)
return errno = EINVAL;
/* Write/discard all buffered data in the stream. Ignores errors. */
fflush(stream);
/* Termios functions use the file descriptor. */
fd = fileno(stream);
if (fd == -1)
return errno = EINVAL;
/* Discard all unread input and untransmitted output. */
do {
result = tcflush(fd, TCIOFLUSH);
} while (result == -1 && errno == EINTR);
/* Restore terminal state. */
do {
result = tcsetattr(fd, TCSAFLUSH, state);
} while (result == -1 && errno == EINTR);
if (result == -1)
return errno;
/* Success. */
return 0;
}
The ctype.h is used for the example main() below; it's not needed by the two above functions.
The first function changes the terminal related to the file descriptor, and the second function restores the original state. You normally use standard input, stdin.
Here is an example program using the above. You need to type Q (uppercase Q, not lowercase q) to exit from the program:
Code:
int main(void)
{
struct termios saved;
int c;
/* Make terminal at standard input unbuffered and raw. */
if (stream_makeraw(stdin, &saved)) {
fprintf(stderr, "Cannot make standard input raw: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
fprintf(stdout, "Press Q to quit.\n");
fflush(stdout);
do {
c = getchar(); /* Or c = getc(stdin); */
if (isprint(c))
fprintf(stdout, "Received character '%c', code %d = 0%03o = 0x%02x\n", c, c, c, c);
else
fprintf(stdout, "Received code %d = 0%03o = 0x%02x\n", c, c, c);
fflush(stdout);
} while (c != 'Q');
/* Restore terminal to original state. */
if (stream_restore(stdin, &saved)) {
fprintf(stderr, "Cannot restore standard input state: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
fprintf(stdout, "All done.\n");
return EXIT_SUCCESS;
}
If you experiment with special keys, like function keys and arrow keys, you'll see they produce a sequence of input characters, all beginning with code 27 and '['.
These are standard CSI sequences ("ANSI escape sequences", standardized in ECMA-48 and ISO/IEC 6429). For example, the ANSI escape code Wikipedia page lists some of them in a table under the "Some ANSI escape sequences" heading.
For example, the "Up" cursor key produces 27 91 65 = \033[A, which is the CSI sequence for cursor up.