Understanding Keyboard State

Learn how to detect and handle keyboard input in SDL3 using both event-driven and polling methods. This lesson covers obtaining and interpreting the keyboard state array.

Ryan McCombe
Updated

In previous lessons, we introduced how we can detect and react to keyboard input through the event loop:

#include <SDL3/SDL.h>
#include "Window.h"

void HandleKeyboardEvent(const SDL_KeyboardEvent& E) {
  // ...
}

int main(int, char**) {
  SDL_Init(SDL_INIT_VIDEO);
  Window GameWindow;
  SDL_Event Event;

  while (true) {
    while (SDL_PollEvent(&Event)) {
      if (Event.type == SDL_EVENT_KEY_DOWN)
        HandleKeyboardEvent(Event.key);
    }
  }

  SDL_Quit();
  return 0;
}

However, there is another option. At any time within our application, we can query SDL to find out which keys are currently pressed.

Starting Point

This lesson builds on our earlier work. If you want to follow along, a minimalist project containing a Window class that initializes SDL and creates a window, and a main function with a basic application loop is available below.

Files

src
Select a file to view its content

Getting Keyboard State

SDL maintains an array of bool values to represent the state of the user's keyboard. This array has an entry for every key on the user's keyboard, and we can receive a const pointer to it using the SDL_GetKeyboardState() function.

This function receives a pointer to an integer as an argument, which it updates with the size of the array:

src/main.cpp

#include <iostream>
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include "Window.h"

void HandleKeyboard() {
  int Size;
  SDL_GetKeyboardState(&Size);

  std::cout << "Array Size: " << Size;
  
  // Alternatively:
  std::cout << "\nSDL_SCANCODE_COUNT: "
    << SDL_SCANCODE_COUNT;
}

int main(int, char**) {
  SDL_Init(SDL_INIT_VIDEO);
  Window GameWindow;
  SDL_Event Event;

  HandleKeyboard();

  bool IsRunning = true;
  while (IsRunning) {
    while (SDL_PollEvent(&Event)) {
      if (Event.type == SDL_EVENT_QUIT) {
        IsRunning = false;
      }
    }
    GameWindow.Render();
    GameWindow.Update();
  }

  SDL_Quit();
  return 0;
}
Array Size: 512
SDL_SCANCODE_COUNT: 512

As of SDL3, we can also get the this value via the SDL_SCANCODE_COUNT variable.

Note that the array's size will be larger than the number of keys on our keyboard, meaning not every position is assigned to a key. The main reason the size is provided is to communicate the upper bound of the array, which we may need to know if we were iterating through it, for example.

If we don't need to know the size, we can pass a nullptr:

void HandleKeyboard() {
  SDL_GetKeyboardState(nullptr);
}

C-Style Arrays

The array returned by SDL_GetKeyboardState() is an example of a C-style array. These are much more primitive than standard library containers such as a std::vector. A c-style array has two components:

  • A pointer to the first element in the collection
  • The size of the collection

We already saw how SDL_GetKeyboardState() provides the size - it updates the int* we pass to it or, equivalently, we can just read it from the SDL_SCANCODE_COUNT variable.

The pointer to the first element in the collection is the value returned by SDL_GetKeyboardState(). The function returns a collection of bool values, representing whether each key is currently pressed.

As such, it returns a bool* and, because we're not supposed to modify this array, it's additionally qualified as const:

void HandleKeyboard() {
  const bool* State {
    SDL_GetKeyboardState(nullptr)
  };
}

We cover C-style arrays in more detail in our lesson on .

Reusing the Keyboard State Array

Once we acquire the array returned by SDL_GetKeyboardState(), we can store it in a variable and reuse it as needed.

The array is kept updated at all times by SDL, so we don't need to call the SDL_GetKeyboardState() function repeatedly to get the latest state.

Determining Which Keys are Held

Once we have the keyboard array from SDL_GetKeyboardState(), we can determine if a key is currently held down by investigating the corresponding entry within that array. The index we need for each key is the scan code of that key, which SDL provides variables for.

For example, to determine if the spacebar is currently held down, we would check the index represented by SDL_SCANCODE_SPACE:

void HandleKeyboard() {
  const bool* State {
    SDL_GetKeyboardState(nullptr)};

  State[SDL_SCANCODE_SPACE];
}

The bool value at each array position is true if the corresponding key is pressed, and false otherwise. We can directly use these values in conditionals.

Below, we check on every frame whether the spacebar is held or not:

src/main.cpp

#include <iostream>
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include "Window.h"

void HandleKeyboard() {
  const bool* State {
    SDL_GetKeyboardState(nullptr)};

  if (State[SDL_SCANCODE_SPACE]) {
    std::cout << "Space is held\n";
  } else {
    std::cout << "Space is not held\n";
  }
}

int main(int, char**) {
  SDL_Init(SDL_INIT_VIDEO);
  Window GameWindow;
  SDL_Event Event;

  bool IsRunning = true;
  while (IsRunning) {
    while (SDL_PollEvent(&Event)) {
      if (Event.type == SDL_EVENT_QUIT) {
        IsRunning = false;
      }
    }
    
    GameWindow.Render();
    HandleKeyboard();
    GameWindow.Update();
  }

  SDL_Quit();
  return 0;
}
Space is not held
Space is not held
Space is not held
Space is held
Space is held

All the scan codes we need to index into this array are available in the SDL_scancode.h header file, documented here.

Keyboard State and Input Focus

When our application doesn't have input focus, the keyboard state array will be false in every position. That is, no key will be considered to be held down.

Iterating over All Keys

As a final example, this program logs out all keys that are held down on each frame by iterating over the keyboard state array.

We can get the friendly name of any key by passing its scan code to the SDL_GetScancodeName() function:

src/main.cpp

#include <iostream>
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include "Window.h"

void HandleKeyboard() {
  const bool* State{SDL_GetKeyboardState(nullptr)};

  for (int i{0}; i < SDL_SCANCODE_COUNT; ++i) {
    if (State[i]) {
      std::cout << "Key pressed: "
        << SDL_GetScancodeName(SDL_Scancode(i))
        << " (scancode: " << i << ")\n";
    }
  }
}

int main(int, char**) {/*...*/}
Key pressed: Space (scancode: 44)
Key pressed: Space (scancode: 44)
Key pressed: Space (scancode: 44)

Keyboard State and the Event Queue

For SDL to update its keyboard state array, we must instruct it to process its event queue. In the previous examples, and in most applications, we've been doing that within our application loops.

Specifically, the continuous calls to SDL_PollEvent() prompt SDL to keep its various components up to date, including the keyboard state array:

SDL_Event Event;

while (true) {
  while (SDL_PollEvent(&Event)) {
    // React to events
    // ...
  }

  HandleKeyboard();
}

Even if we don't need to react to the events, we still need to prompt SDL to process them at the appropriate time within the application loop.

However, if we don't need to inspect the events, we can simplify our loop by using SDL_PumpEvents() instead of SDL_PollEvent():

while (true) {
  SDL_PumpEvents(); 
  HandleKeyboard();
}

SDL_PumpEvents() and SDL_PollEvent() have a similar purpose, but are different in two key ways:

  • SDL_PumpEvents() processes all the outstanding events on the queue, whilst SDL_PollEvent() only handles one event per invocation. As such, we can remove the inner loop - we only need to call SDL_PumpEvents() once per frame.
  • SDL_PumpEvents() doesn't give us visibility of each individual event, so we don't need an SDL_Event variable to store the details.

Events vs Polling

We've now seen two different approaches we can take to handle keyboard input. These designs are typically referred to as event-driven and polling:

  • Event-based designs handle input through the event loop. Our application loop detects the keyboard events we're interested in and notifies the interested objects.
  • Polling-based designs instead have those objects directly examine the keyboard state. This is referred to as polling as it generally needs to happen continuously - typically every frame.

Each design has its advantages and disadvantages and, in a more complex application, both techniques tend to be used.

Advantages of Events

Event-based designs are typically preferred for interactions that are discrete rather than continuous. Examples of discrete actions include the user pressing a key to open up a menu, or clicking on a button in the UI.

Event implementations also tend to be more performant than polling, especially when the event is infrequent. Polling every frame to detect a state that rarely happens is usually a waste of resources, and is better implemented through the event queue.

Advantages of Polling

Polling designs are more appropriate for interactions that tend to be continuous. For example, movement input is often implemented by the user holding down one of several designated keys.

In that scenario, having the movement system implement frame-by-frame polling to determine which movement keys are held down is typically cleaner than reacting to keydown and keyup actions coming from the event queue.

Summary

In this lesson, we explored how to handle keyboard input in SDL3 using both event-driven and polling methods. We learned how to obtain and interpret the keyboard state array to detect key presses.

  • SDL maintains an array to represent the keyboard state.
  • The SDL_GetKeyboardState() function provides access to this array.
  • The array is of type const bool*.
  • We can check if a key is pressed by indexing the array with the key's scan code.
  • The keyboard state array is updated when SDL processes its event queue via SDL_PollEvent() or SDL_PumpEvents().
  • Event-driven and polling methods have different use cases and can be used together in complex applications.
Next Lesson
Lesson 49 of 52

Window Configuration

Explore window creation, configuration, and event handling using SDL3's windowing system

Have a question about this lesson?
Answers are generated by AI models and may not be accurate