Basically, it depends heavily on how you define immediately.
There are two tasks here. The first is to disable the regular key echoing that is built into most C input libraries. The second is to print out the new character instead of the old one.
In pseudo code.
echo(off);
while (capturing && charIsAvailable()) {
c = readOneChar();
if (c == '\n') {
capturing = false;
}
printf("%c", c++);
}
echo(on);
There are a number of systems communicating to capture a key press.
- The keyboard
- (possibly) a USB bus
- The CPU interrupt handler
- The operating system
- The X window server process
- The X "window" that has focus.
The last step is done with a program that runs a continuous loop that captures events from the X server and processes them. If you wanted to expand this program in certain ways (get the length of time the key was pressed) you need to tell the other programs that you want "raw" keyboard events, which means that you won't really be receiving fully "cooked" characters. As a result, you will have to keep track of which keys are up and down, and how long, and handle all the odd meta key behavior in your program (that's not an 'a' it's a 'A' because shift is down, etc).
There are also other processing modes to consider, like canonical and non-canonical, which will control whether you wish the events to be received in line oriented chunks (line events) or character oriented chunks (character events). Again this is somewhat complicated by the need to make the upstream programs aware of the requirements of the downstream client.
Now that you have some idea of your environment, let's revisit the actual code needed to suppress character output.
// define a terminal configuration data structure
struct termios term;
// copy the stdin terminal configuration into term
tcgetattr( fileno(stdin), &term );
// turn off Canonical processing in term
term.c_lflag &= ~ICANON;
// turn off screen echo in term
term.c_lflag &= ~ECHO;
// set the terminal configuration for stdin according to term, now
tcsetattr( fileno(stdin), TCSANOW, &term);
(fetch characters here, use printf to show whatever you like)
// turn on Canonical processing in term
term.c_lflag |= ICANON;
// turn on screen echo in term
term.c_lflag |= ECHO;
// set the terminal configuration for stdin according to term, now
tcsetattr( fileno(stdin), TCSANOW, &term);
Even this is not immediate. To get immediate, you need to get closer to the source, which eventually means a kernel module (which still isn't as immediate as the keyboard micro-controller, which isn't as immediate as the moment the switch actually closes). With enough items in between the source and the destination, eventually it becomes possible to notice the difference, however, in practice this code has been worked on a lot by people who are seeking the best tradeoff between performance and flexibility.
getch()
? – Aesop