Sometimes things aren't as simple as they should be. Most of the time I write code for embedded systems that don't come with things like screens and keyboards. That wasn't true a few days ago, when I realized that I didn't know how to make a C program sleep until a key is pressed. It sounds pretty easy, but it turns out there isn't a way to do it with ANSI C. If you don't believe me, read the FAQ.

I wanted something for a Linux system, and that can be done. After doing a lot of reading I came up with something that met my needs. Then on my way home from work, I thought about writing something more generic, and I came up with this. If you're in a hurry and just want code feel free to scroll down, copy it, and move along.

There are several websites discussing the problem and providing variations on the same theme. This web page discusses what I came up with and the design decisions that I made. If you have any interesting ideas, comments, or just happen to be board, feel free to contact me at mdipperstein@gmail.com.

If you just want my source code, click here for information on downloading it.


Objective

ANSI C provides a getchar function that basically returns the next character input from stdin. getchar is blocking, but the tread it's in sleeps. That's exactly what I wanted. The only problems are that getchar() always echos the key pressed and it normally won't recognize the input until the enter key is pressed.

I wanted to code that did not require the enter key to be pressed before the key press would be seen by stdin. I also did not want to echo the key press to stdout, and I did not need to know what key was pressed. All the key press needed to do is unblock an idle thread.

Sample Code

Below is a listing of a function that achieves all of my stated goals. It provides the option to enable/disable of echoing the key to stdout and returns the character value of the key pressed in case you have a use for it.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
int keypress(const kp_echo_t echo) { struct termios savedState; struct termios newState; unsigned char echo_bit; /* enable/disable echo. logic is reversed */ int c; if (-1 == tcgetattr(STDIN_FILENO, &savedState)) { return EOF; /* error on tcgetattr */ } newState = savedState; if (KP_ECHO_OFF == echo) { echo_bit = ECHO; /* echo bit set to disable echo */ } else { echo_bit = 0; /* echo bit cleared to enable echo */ } /* use canonical input and set echo. set minimal input to 1. */ newState.c_lflag &= ~(echo_bit | ICANON); newState.c_cc[VMIN] = 1; if (-1 == tcsetattr(STDIN_FILENO, TCSANOW, &newState)) { return EOF; /* error on tcsetattr */ } c = getchar(); /* block (without spinning) until we get a key press */ /* restore the saved state */ if (-1 == tcsetattr(STDIN_FILENO, TCSANOW, &savedState)) { return EOF; /* error on tcsetattr */ } return c; }

How it Works

All of the magic required to change the way your terminal behaves lies in the functions tcgetattr and tcsetattr. These functions are responsible for getting and setting terminal attributes. For more information see https://linux.die.net/man/3/tcgetattr for the tcgetattr and tcgetattr man page.

Line 8 of my function gets the current stdin attributes and saves it in the variable savedState.

Lines 13 through 26 compute a new set of terminal attributes for stdin that are just like the old ones, except canonical mode and possibly echo are disabled (Line 25).

Disabling canonical mode causes characters to be sent to stdin after a timeout or a minimum number have been received. Line 26 sets that minimum number to 1.

Line 28 applies the new set of attributes to stdin.

Line 33 uses getchar() to block until a character can be read from stdin. The value of the character is saved just in case it's required by the calling function.

Line 36 restores the attributes of stdin to their previous values.


Using the Code

Using the keypress() function is straight forward. I placed the keypress() function in a library that needs to be linked to the calling code. The file where keypress() function is called from should include the file keypress.h.

Library Code Interface

The keypress function is defined as follows:

int keypress(const kp_echo_t echo);

echo
Set to KP_ECHO_OFF to prevent the key pressed from being echoed to stdout.

Return Value
The value of the key pressed or EOF. EOF is returned for an End of File or error. In the event of an error, errno will also be set.

kp_echo_t is defined as follows:

typedef enum
{
KP_ECHO_OFF,
KP_ECHO_ON,
} kp_echo_t;

Portability

All the source code that I have provided is written in strict ANSI C, however the tcgetattr and tcsetattr functions are part of the POSIX specification. I would expect the source code to build correctly on any POSIX-compliant machine with an ANSI C compiler. I have tested the code compiled with gcc on Linux. I wouldn't expect it to run on a Windows machine, but it should run fine on a UNIX, like Mac OSX.


Actual Software

I am releasing my implementation of the Key Press Library under the GNU LGPL. The source code repository is available on GitHub. I recommend that you checkout the latest revision of the master branch, unless you're looking for something specific.

Repository Location https://github.com/michaeldipperstein/keypress

If you have any further questions or comments, you may contact me by e-mail. My e-mail address is: mdipperstein@gmail.com

Home

Last updated on December 27, 2018