Raw Keyboard Input in Linux

In the course of porting my console emulators to the Raspberry Pi, I found the need to get keyboard input in such a way that I could detect exactly when a key was pressed and released, and also detect multiple keys being pressed simultaneously. This is a pretty common need for games where you often want to allow the player to run and jump at the same time, for example, but it’s not always very obvious how to do it. In this case I found myself in the barren world of the Linux command line, without any friendly libraries to do it for me. (Actually, SDL was there but for some reason I couldn’t coax it into giving me any keyboard input). I eventually found out, after a fair bit of googling, how to switch the keyboard into “raw” mode – in this mode your program gets the individual scan codes from the keyboard so you can tell exactly what’s going on.

Anyway, I didn’t find any handy ready-made code snippets to do the whole process, so here’s my one. (Actually, I did find this one in assembly language, but that’s not particularly useful if you want to code in C… and even less useful if you’re on a non-Intel platform like the Raspberry Pi!)

First the code to switch into raw mode:

#include "unistd.h"
#include "linux/kd.h"
#include "termios.h"
#include "fcntl.h"
#include "sys/ioctl.h"

static struct termios tty_attr_old;
static int old_keyboard_mode;

int setupKeyboard()
{
    struct termios tty_attr;
    int flags;

    /* make stdin non-blocking */
    flags = fcntl(0, F_GETFL);
    flags |= O_NONBLOCK;
    fcntl(0, F_SETFL, flags);

    /* save old keyboard mode */
    if (ioctl(0, KDGKBMODE, &old_keyboard_mode) < 0) {
	return 0;
    }

    tcgetattr(0, &tty_attr_old);

    /* turn off buffering, echo and key processing */
    tty_attr = tty_attr_old;
    tty_attr.c_lflag &= ~(ICANON | ECHO | ISIG);
    tty_attr.c_iflag &= ~(ISTRIP | INLCR | ICRNL | IGNCR | IXON | IXOFF);
    tcsetattr(0, TCSANOW, &tty_attr);

    ioctl(0, KDSKBMODE, K_RAW);
    return 1;
}

If the first ioctl fails, it probably means the program isn’t running under a standard console (e.g. it’s running in an xterm, or remotely over ssh), and this method won’t work, so the function returns 0.

Now the reciprocal code to switch out again. It’s very important to call this before your program exits, otherwise you leave your user’s terminal in a screwed up state where they won’t be able to type anything, or even press a magic key combination to reboot or switch terminals!

void restoreKeyboard()
{
    tcsetattr(0, TCSAFLUSH, &tty_attr_old);
    ioctl(0, KDSKBMODE, old_keyboard_mode);
}

While the keyboard’s in raw mode, you can read from the stdin file descriptor (number 0), and you will get scan codes:

void readKeyboard()
{
    char buf[1];
    int res;

    /* read scan code from stdin */
    res = read(0, &buf[0], 1);
    /* keep reading til there's no more*/
    while (res >= 0) {
	switch (buf[0]) {
	case 0x01:
            /* escape was pressed */
            break;
        case 0x81:
            /* escape was released */
            break;
        /* process more scan code possibilities here! */
	}
	res = read(0, &buf[0], 1);
    }
}

You can find lists of the scan codes online in various places. Some keys send multiple bytes in sequence when pressed. They may differ for different keyboard types and different countries, which is one of the reasons why it’s not a good idea to do this unless you really need to – you are basically disabling all the hard work that the Linux kernel normally does for you to standardise key codes and give them to your program in a nice form.

Have fun 😉

2 thoughts on “Raw Keyboard Input in Linux

  1. If you just want to detect the standard keyboard keys, you can also set it to K_MEDIUMRAW mode. That way each key is 7-bits. (High bit in the byte is 0 for down, 1 for up) and don’t have to worry about weird scancodes that use multiple bytes. While you lose some keys, you still get nearly every key on a standard 101-key keyboard.

Leave a Reply