Skip to main content

Command Palette

Search for a command to run...

Handling Keyboard Input in ncurses

Published
3 min read
Handling Keyboard Input in ncurses
D

An engineer with a curious mind and a love for systems that go beyond the surface.I enjoy exploring how things work under the hood — whether it's rendering pixels, routing audio, or handling low-level protocols. My work spans across game development, system programming, and occasionally web tech when the problem is worth solving. I’m driven by the desire to build things that are fast, efficient, and just a bit unconventional. This blog is my space to document experiments, challenges, and the weirdly satisfying bugs that teach you more than tutorials ever could.

Outside of tech, I enjoy observing how people use technology, and occasionally breaking things just to fix them better.

In the previous part, our program could print text, but it couldn’t really do anything.

In this part, we’ll change that.

We’ll learn how to:

  • read keyboard input using ncurses

  • detect special keys like arrow keys

  • avoid blocking the program while waiting for input

This is the first step toward real-time behavior.

Reading Input in ncurses

ncurses provides the getch() function to read keyboard input.

By default, getch() blocks.
This means the program stops and waits until a key is pressed.

Let’s see a simple example.

#include <ncurses.h>

int main(){
    initscr();
    int ch = getch();  /* Waits for user input */
    endwin();
}

If a key is pressed, getch() returns its value.
If not, the program waits.

This is fine for simple programs, but not for games.

Why Blocking Input Is a Problem

Games do not wait for input.

A game keeps running, updating logic and rendering frames, whether the player presses a key or not.

If input blocks execution:

  • animations freeze

  • logic stops

  • the program feels unresponsive

So we need a way to check for input without stopping the program.

Making Input Non-Blocking

ncurses gives us two useful ways to handle non-blocking input:

  • nodelay(win, bool)

  • timeout(delay)

The timeout() function lets you specify how long an input function should wait for a key press. The value is given in milliseconds.

The nodelay() function is equivalent to setting the timeout to 0.

nodelay(stdscr, TRUE);

Once this is enabled:

  • getch() no longer blocks

  • if no key is pressed, it returns ERR

This allows the program to keep running even when there is no input, which is exactly what we need for real-time programs and games.

Note - calling timeout(-1) restores the blocking behavior

Handling Input Safely

Here’s a simple example that reacts to key presses without blocking.

#include <ncurses.h>

int main() {
  initscr();
  cbreak();                  // Disable line buffering
  nodelay(stdscr, TRUE);     // Make getch() non-blocking
  noecho();                  // Do not echo input characters
  curs_set(0);               // Hide the cursor

  bool running = true;

  while (running) {
    int ch = getch();

    if (ch == 'q' || ch == 'Q') {
      running = false;
    }

    clear();
    printw("Press 'q' to quit");
    refresh();
  }

  endwin();
  return 0;
}

Now the program:

  • keeps running

  • checks for input every loop

  • exits when q is pressed

Now we are getting somewhere.


Special Keys and keypad()

Arrow keys and function keys do not behave like normal characters.

To detect them correctly, you must enable keypad mode.

keypad(stdscr, TRUE);

Once enabled, arrow keys are returned as constants like:

  • KEY_UP

  • KEY_DOWN

  • KEY_LEFT

  • KEY_RIGHT

Example:

if (ch == KEY_UP) {
  // move up
}

This will be essential later when we control a player or object.

A Few Small but Important Details

cbreak()

By default, terminals use line buffering, meaning input is only sent to the program after pressing Enter.

cbreak();

This disables line buffering so key presses are sent to the program immediately.


noecho()

By default, terminals echo typed characters to the screen.

noecho();

This prevents pressed keys from appearing automatically and gives you full control over rendering.


curs_set(0)

Terminals normally show a blinking cursor.

curs_set(0);

This hides the cursor, which is usually desirable for games and real-time applications.
You can re-enable it later using curs_set(1) or curs_set(2).

What We’ve Achieved

At this point, we can:

  • read keyboard input

  • detect special keys

  • keep the program running without blocking

  • control when the program exits

This is the foundation of real-time interaction.

What’s Next

In the next part, we’ll formalize what we’ve already started doing here.

We’ll build a proper game loop with:

  • fixed structure

  • frame updates

  • and timing control

That’s when things start feeling like an actual game.

More from this blog

Code. Chaos. Curiosity. | Devesh's Devlog

12 posts