Read Key presses in C; ex.: Arrow keys, Enter key
Asked Answered
A

2

2

I know how to use events to test when a key is pressed or not, but in C I never found out how to do that.

What I want exactly is a "KeyListener" that listens for the Up, Down, Left and Right arrow keys. I need it to work in Linux, so no Windows libraries. And, if possible, using no 3rd party libraries is the best option for me.

Pseudocode of what I want:

int main() {
 
    // key listener {
    // if(key == up) { // do something }
    // if(key == down) { // do something }
    // if(key == left) { // do something }
    // if(key == right) { // do something }
    // }
}
Algor answered 3/10, 2012 at 13:12 Comment(5)
do you want to receive keypresses in a) terminal, b) in a window, c) in the background of X windowing system?Srinagar
I am Using SDL library to show the program visually, but i just want to have the capability of reading the key pressesAlgor
Okay, just a normal keyboard, whatever it is. In C# you just use KeyValue()Algor
SDL is a 3rd party library :)Sidonius
SolutionBekelja
R
5

How about using SDL to read the keyboard too.

  SDL_Event event;
  .
  .
  /* Poll for events. SDL_PollEvent() returns 0 when there are no  */
  /* more events on the event queue, our while loop will exit when */
  /* that occurs.                                                  */
  while( SDL_PollEvent( &event ) ){
    /* We are only worried about SDL_KEYDOWN and SDL_KEYUP events */
    switch( event.type ){
      case SDL_KEYDOWN:
        printf( "Key press detected\n" );

        if (event.key.keysym.sym==SDLK_UP) 
            printf( "It was the UP key\n" );

        break;
      case SDL_KEYUP:
        printf( "Key release detected\n" );
        break;

      default:
        break;
    }
  }
  .
  .

Source: http://www.libsdl.org/docs/html/guideinputkeyboard.html

Reyna answered 3/10, 2012 at 13:19 Comment(6)
Nice one, how do i capture for example the up key inside SDL_KEYDOWN ?Algor
Huh? The up key is SDL_KEYUP.Atheroma
the keysymbol you can get in event.key.keysym.sym; for up-key it is SDLK_UP. Also I updated the example.Srinagar
The source link is dead. Maybe this is it? Even this is way old though I think: libsdl.org/release/SDL-1.2.15/docs/html/guideinputkeyboard.htmlBeget
As of 2023, here is the solutionBekelja
@Bekelja the question was about when using SDL. That linked answer is not.Srinagar
B
0

Here's a hack based on my other answer here: Capture characters from standard input without waiting for enter to be pressed. I use a bash system call in C or C++ to read and parse arrow key presses. Arrow key presses come in as 3 chars, so I parse 3 chars at a time and figure out which arrow key it was.

There is no need to ever press Enter (which is unlike when using the getc() command, which will only return when you press Enter). The arrow key presses are detected immediately after you press them.

read_system_call_via_pipe__arrow_keypresses.c:

#include <stdbool.h> // For `true` (`1`) and `false` (`0`) macros in C
#include <stdint.h>  // For `uint8_t`, `int8_t`, etc.
#include <stdio.h>   // For `printf()`
#include <stdlib.h>

#define BUFSIZE 32

typedef enum arrowKey_e
{
    ARROWKEY_UNKNOWN = 0,
    ARROWKEY_UP,
    ARROWKEY_DOWN,
    ARROWKEY_LEFT,
    ARROWKEY_RIGHT,
} arrowKey_t;

// Modeled after my answer here: https://mcmap.net/q/17738/-stm32-how-to-get-last-reset-status
const char* arrowKeyGetName(arrowKey_t arrowKey)
{
    const char* arrowKeyName = "TBD";

    switch (arrowKey)
    {
        case ARROWKEY_UNKNOWN:
            arrowKeyName = "ARROWKEY_UNKNOWN";
            break;
        case ARROWKEY_UP:
            arrowKeyName = "ARROWKEY_UP";
            break;
        case ARROWKEY_DOWN:
            arrowKeyName = "ARROWKEY_DOWN";
            break;
        case ARROWKEY_LEFT:
            arrowKeyName = "ARROWKEY_LEFT";
            break;
        case ARROWKEY_RIGHT:
            arrowKeyName = "ARROWKEY_RIGHT";
            break;
    }

    return arrowKeyName;
}

// Read a keyboard key press and return the character pressed, or a negative
// number in the event of an error.
// NB: for help reading output from system calls, see here:
//  1. https://mcmap.net/q/65640/-how-can-i-run-an-external-program-from-c-and-parse-its-output
//  2. https://mcmap.net/q/66518/-is-there-a-way-to-obtain-the-output-of-a-linux-command-like-ifconfig-on-a-txt-file-using-a-c-program-duplicate
arrowKey_t readArrowKeyPress()
{
    arrowKey_t arrowKeyPressed = ARROWKEY_UNKNOWN;

    // This bash cmd is from my answer here:
    // https://mcmap.net/q/119459/-shell-script-respond-to-keypress
    // `-n3` here means to read 3 chars at once, since an arrow key press
    // comes in as 3 chars.
    // The `-t .01` forces a timeout of that many seconds, which means the loop
    // interval will be this fast.
    const char* cmd = "bash -c 'read -s -t .1 -n3 c && printf \"%s\" \"$c\"'";
    FILE *fp = popen(cmd, "r");
    if (fp == NULL)
    {
        printf("\nError opening pipe!\n");
        return arrowKeyPressed;
    }

    char buf[BUFSIZE] = {0};
    char* retval1 = fgets(buf, BUFSIZE, fp);
    if (retval1 == NULL)
    {
        // printf("\nFailed to read cmd response.\n");
        // Timeout occured; just exit.
        return arrowKeyPressed;
    }

    // See meaning of this return value here:
    // https://mcmap.net/q/65640/-how-can-i-run-an-external-program-from-c-and-parse-its-output/28971647#comment60311936_28971647
    int retval2 = pclose(fp);
    if (retval2 == -1)
    {
        printf("\nError obtaining the cmd's exit status code.\n");
        return arrowKeyPressed;
    }
    else if (retval2 != 0)
    {
        printf("\nCommand exited with exit status code %i.\n", retval2);
        return arrowKeyPressed;
    }

    // Map the readings to arrow keys
    if ((buf[0] == 27) && (buf[1] == 91) && (buf[2] == 65))
    {
        arrowKeyPressed = ARROWKEY_UP;
    }
    else if ((buf[0] == 27) && (buf[1] == 91) && (buf[2] == 66))
    {
        arrowKeyPressed = ARROWKEY_DOWN;
    }
    else if ((buf[0] == 27) && (buf[1] == 91) && (buf[2] == 67))
    {
        arrowKeyPressed = ARROWKEY_RIGHT;
    }
    else if ((buf[0] == 27) && (buf[1] == 91) && (buf[2] == 68))
    {
        arrowKeyPressed = ARROWKEY_LEFT;
    }

    return arrowKeyPressed;
}

// int main(int argc, char *argv[])  // alternative prototype
int main()
{
    printf("Press any arrow key. Press Ctrl + C to quit.\n");
    fflush(stdout);

    while (true)
    {
        arrowKey_t arrowKeyPressed = readArrowKeyPress();
        if (arrowKeyPressed == ARROWKEY_UNKNOWN)
        {
            continue;
        }
        printf("Key pressed = %s\n", arrowKeyGetName(arrowKeyPressed));
    }

    return 0;
}

Sample build and run output:

eRCaGuy_hello_world/c$ gcc -Wall -Wextra -Werror -O3 -std=gnu17 read_arrow_keypresses_system_call.c -o bin/a && bin/a
Press any arrow key. Press Ctrl + C to quit.
Key pressed = ARROWKEY_DOWN
Key pressed = ARROWKEY_UP
Key pressed = ARROWKEY_LEFT
Key pressed = ARROWKEY_RIGHT
Key pressed = ARROWKEY_UP
Key pressed = ARROWKEY_DOWN
Key pressed = ARROWKEY_LEFT
Key pressed = ARROWKEY_RIGHT
Key pressed = ARROWKEY_UP
Key pressed = ARROWKEY_DOWN
Key pressed = ARROWKEY_LEFT
Key pressed = ARROWKEY_RIGHT
Beget answered 4/2, 2022 at 0:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.