Handling Keyboard Input

Learn how to detect and respond to keyboard input events in your SDL-based applications. This lesson covers key events, key codes, and modifier keys.
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, Unlimited Access
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated

In this lesson, we will cover in detail how we can detect and react to the user providing input to our application using their keyboard. Similar to other forms of input, when SDL detects keyboard interaction, it pushes events into the event queue. We can capture these events through our event loop, and handle them as needed.

This lesson builds on our earlier work, where we have a Window class that initializes SDL and creates a window, and an application loop set up in our main function:

#include <SDL.h>

class Window {/*...*/}; int main(int argc, char** argv) { Window GameWindow; SDL_Event Event; while(true) { while(SDL_PollEvent(&Event)) { // Detect and handle input events // ... } GameWindow.RenderFrame(); } SDL_Quit(); return 0; }

Our SDL_PollEvent(&Event) statement will update our Event object with any keyboard action that the user performs. We’ll then detect and react to those actions within the body of the loop.

Keyboard Button Events

The two most common event types for handling the keyboard are SDL_KEYDOWN, which is created when a button is pressed, and SDL_KEYUP, for when a button is released.

while (SDL_PollEvent(&Event)) {
  if (Event.type == SDL_KEYDOWN) {
    // ...
  } else if (Event.type == SDL_KEYUP) {
    // ...
  }
}

To find out which button was pressed or released, we need to check the key code. If we have an object called Event whose type is SDL_Event, and the Event.type member variable is SDL_KEYUP or SDL_KEYDOWN, we can access the key code using Event.key.keysym.sym:

while (SDL_PollEvent(&Event)) {
  if (Event.type == SDL_KEYDOWN) {
    std::cout << "Key: " << Event.key.keysym.sym;
  }
}
Key: 32

Key codes are basic integers, but SDL provides variables to help us understand which keys these integers represent. These variables are identified by the SDLK_ prefix. For example, the integer that represents the spacebar is available as SDLK_SPACE

while (SDL_PollEvent(&Event)) {
  if (Event.type == SDL_KEYDOWN) {
    if (Event.key.keysym.sym == SDLK_SPACE) {
      std::cout << "Spacebar Pressed";
    }
  }
}
Spacebar Pressed

In this example, we react to the arrow keys being pressed:

while (SDL_PollEvent(&Event)) {
  if (Event.type == SDL_KEYDOWN) {
    if (Event.key.keysym.sym == SDLK_UP) {
      // Up Arrow
    } else if (Event.key.keysym.sym == SDLK_DOWN) {
      // Down Arrow
    } else if (Event.key.keysym.sym == SDLK_LEFT) {
      // Left Arrow
    } else if (Event.key.keysym.sym == SDLK_RIGHT) {
      // Right Arrow
    }
  }
}

A list of all the key codes is available in the SDL_keycode header file.

SDL_KeyboardEvent

When our SDL_Event object has a type of SDL_KEYDOWN or SDL_KEYUP, most of the information we care about is within the key struct of that event. This struct has a type of SDL_KeyboardEvent:

while (SDL_PollEvent(&Event)) {
  if (Event.type == SDL_KEYDOWN
    || Event.type == SDL_KEYUP) {
    SDL_KeyboardEvent KeyEvent = Event.key;
  }
}

Our main event loop can get long and complex, so we want to proactively mitigate this growth in our design.

One of the main ways of doing this is restricting our main event loop to just determining the high-level nature of each event (typically by examining the type variable) and then handing it off to a function to implement the required reaction.

A handler for events whose type is SDL_KEYDOWN or SDL_KEYUP will be most interested in the SDL_KeyboardEvent subobject stored in the key variable, so that tends to be what we provide from our main loop:

// Event Loop
while (SDL_PollEvent(&Event)) {
  if (Event.type == SDL_KEYDOWN
    || Event.type == SDL_KEYUP) {
    HandleKeyboard(Event.key);
  }
}
// Handler
void HandleKeyboard(SDL_KeyboardEvent& E) {
  if (E.keysym.sym == SDLK_UP) {
    // Up Arrow
  } else if (E.keysym.sym == SDLK_DOWN) {
    // Down Arrow
  } else if (E.keysym.sym == SDLK_LEFT) {
    // Left Arrow
  } else if (E.keysym.sym == SDLK_RIGHT) {
    // Right Arrow
  }
}

To understand whether the key was pressed or released, the SDL_KeyboardEvent subobject also has a type field, which mirrors the type of the original SDL_Event. As such, we can compare it to SDL_KEYDOWN or SDL_KEYUP:

void HandleKeyboard(SDL_KeyboardEvent& E) {
  if (E.type == SDL_KEYDOWN) {
    // Key Pressed
  } else if (E.type == SDL_KEYUP) {
    // Key Released
  }
}

Alternatively, we can access the state variable of the SDL_KeyboardEvent, and compare it to SDL_PRESSED or SDL_RELEASED:

void HandleKeyboard(SDL_KeyboardEvent& E) {
  if (E.state == SDL_PRESSED) {
    // Key Pressed
  } else if (E.state == SDL_RELEASED) {
    // Key Released
  }
}

Converting Key Codes to Key Names

If we have a keycode, and we want to understand what key was pressed, the SDL_GetKeyName() function can help us. It converts a key code to a more understandable name:

void HandleKeyboard(SDL_KeyboardEvent& E) {
  std::cout
    << "Key Pressed! Key Code: "
    << E.keysym.sym
    << ", Key Name: "
    << SDL_GetKeyName(E.keysym.sym)
    << '\n';
}
Key Pressed! Key Code: 113, Key Name: Q
Key Pressed! Key Code: 119, Key Name: W
Key Pressed! Key Code: 101, Key Name: E
Key Pressed! Key Code: 114, Key Name: R
Key Pressed! Key Code: 116, Key Name: T
Key Pressed! Key Code: 121, Key Name: Y

Modifiers - Ctrl, Alt, Shift, etc

A small subset of keys on our keyboard are considered modifier keys as, by convention, applications them to modify the intent of other inputs. For example, in word processing applications, pressing a letter key whilst the shift key is held down is interpreted as requesting the uppercase form of that letter.

We can detect keydown and keyup events for these keys like any other:

void HandleKeyboard(SDL_KeyboardEvent& E) {
  if (E.state == SDL_PRESSED) {
    std::cout << SDL_GetKeyName(E.keysym.sym)
      << " pressed\n";
  } else if (E.state == SDL_RELEASED) {
    std::cout << SDL_GetKeyName(E.keysym.sym)
      << " released\n";
  }
}
Left Ctrl pressed
Left Ctrl released
Left Shift pressed
Left Shift released

However, for convenience, the keysym struct also has a mod member variable, which tells us which (if any) modifier keys were active on any keyboard button event.

This variable is an integer that acts as a bit set, so we use the bitwise operators to understand which modifiers were active

SDL provides variables to use with this bit set. For example, we can use KMOD_LSHIFT as the right operand to the & operator, which will return true if the left shift button was active.

Below, we detect presses of the space bar, with different behaviors depending on whether or not left shift was active:

void HandleKeyboard(SDL_KeyboardEvent& E) {
  if (E.state == SDL_PRESSED &&
    E.keysym.sym == SDLK_SPACE) {
    std::cout << "Space bar was pressed\n";
    if (E.keysym.mod & KMOD_LSHIFT) {
      std::cout << "  with left shift active\n";
    }
  }
}
Space bar was pressed
Space bar was pressed
  with left shift active

We can build more elaborate logic by combining boolean or bitwise operators. Below, we use the boolean && operator to determine if multiple modifiers are simultaneously active, and the bitwise | operator to detect if any of a range of modifiers are active:

void HandleKeyboard(SDL_KeyboardEvent& E) {
  if (E.state == SDL_PRESSED &&
    E.keysym.sym == SDLK_SPACE) {
    std::cout << "Space bar was pressed\n";
    if ((E.keysym.mod & KMOD_LSHIFT)
      && (E.keysym.mod & KMOD_LCTRL)) {
      std::cout << "  with left shift AND "
        "left ctrl active\n";
    } else if (E.keysym.mod &
      (KMOD_LSHIFT | KMOD_LCTRL)) {
      std::cout << "  with left shift OR "
        "left ctrl active\n";
    }
  }
}
Space bar was pressed
Space bar was pressed
  with left shift AND left ctrl active
Space bar was pressed
  with left shift OR left ctrl active

The range of KMOD_ variables provided by SDL are defined in the SDL_Keymod enum within the keycode header file.

Key Repeats

On many platforms, holding a key down sends a continuous stream of input events after a brief pause. SDL reports these as repeated keydown events.

void HandleKeyboard(SDL_KeyboardEvent& E) {
  if (E.state == SDL_PRESSED &&
    E.keysym.sym == SDLK_SPACE) {
    std::cout << "Space bar was pressed\n";
  } else if (E.state == SDL_RELEASED &&
    E.keysym.sym == SDLK_SPACE) {
    std::cout << "Space bar was released\n";
  }
}

Holding our spacebar down for a few seconds generates output similar to the following:

Space bar was pressed
Space bar was pressed
Space bar was pressed
Space bar was pressed
Space bar was released

We can distinguish between initial and repeat events by inspecting the repeat variable of the SDL_KeyboardEvent (or the key.repeat variable of the SDL_Event)

The repeat value will be 0 for the initial keydown, and a non-zero value if it was caused by the user continuing to hold the key down:

void HandleKeyboard(SDL_KeyboardEvent& E) {
  if (E.state == SDL_PRESSED &&
    E.keysym.sym == SDLK_SPACE) {
    std::cout << "\nSpace bar was pressed - "
      << (E.repeat ? "Repeat" : "Initial");
  } else if (E.state == SDL_RELEASED &&
    E.keysym.sym == SDLK_SPACE) {
    std::cout << "Space bar was released\n";
  }
}
Space bar was pressed - Initial
Space bar was pressed - Repeat
Space bar was pressed - Repeat
Space bar was pressed - Repeat
Space bar was released

Preview: Polling and Keyboard States

If our interaction model needs to detect if the user is currently holding down a key, directly querying the keyboard state is often easier than keeping track of keyup and keydown events.

We can determine which buttons are being held down at any time, from anywhere in our application. We introduce how to do this later in the chapter, and its advantages and disadvantages over using the event loop to track inputs.

Key Codes and Scan Codes

Keyboards can have different layouts. Much of the world uses the QWERTY layout, but others, such as AZERTY, are also common.

This variation means there are two different ways to represent keys.

Key codes, sometimes referred to as virtual key codes, are the simplest to understand. If the user presses the button labeled "Q` on their keyboard, a key code representing Q will be reported. This assumes the user configured their system correctly - that is, the layout they selected in their system settings matches the layout of their physical keyboard.

Scan codes, sometimes referred to as physical key codes, ignore the configured layout and instead represent what physical button was pressed. In SDL2, the scan code is reported based on the QWERTY layout. For example, if the user presses the button that would be labeled "Q" on a QWERTY keyboard, a scan code representing Q will be reported.

This means that if the user is on an AZERTY keyboard for example, and they press the button labeled A, the scan code we receive will be Q, as that would be the Q button on a QWERTY keyboard:

AZERTY Keyboard

Scan codes are primarily useful when we care about the position of the buttons, rather than their alphabetic representations. For example, the W, A, S, and D keys are commonly used for movement in video games, due to their position and proximity to each other on a QWERTY keyboard.

On an AZERTY layout, those same physical buttons are labeled Z, Q, S, and D. But, if we’re using scan codes, we don’t need to care about that difference. Z will be reported as W, and Q will be reported as A, so players can use the same physical controls regardless of their keyboard layout.

Within SDL’s keysym struct, the key code is available as the sym member variable. whilst the scan code is available as scancode

void HandleKeyboard(SDL_KeyboardEvent& E) {
  SDL_Keycode KeyCode{E.keysym.sym};
  SDL_Scancode ScanCode{E.keysym.scancode};
}

Summary

This lesson introduced how to handle keyboard input using SDL. The key takeaways include:

  • Detecting and handling keyboard events with SDL
  • Using SDL_KEYDOWN and SDL_KEYUP events
  • Understanding key codes and scan codes
  • Utilizing the SDL_GetKeyName() function
  • Handling modifier keys such as Ctrl, Alt, and Shift
  • Distinguishing between initial and repeated key events

Was this lesson useful?

Next Lesson

Managing Window Input Focus

Learn how to manage and control window input focus in SDL applications, including how to create, detect, and manipulate window focus states.
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated
Lesson Contents

Handling Keyboard Input

Learn how to detect and respond to keyboard input events in your SDL-based applications. This lesson covers key events, key codes, and modifier keys.

sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, Unlimited Access
Keyboard Input
  • 49.GPUs and Rasterization
  • 50.SDL Renderers
sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, unlimited access

This course includes:

  • 51 Lessons
  • 100+ Code Samples
  • 91% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Managing Window Input Focus

Learn how to manage and control window input focus in SDL applications, including how to create, detect, and manipulate window focus states.
Abstract art representing computer programming
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved