Handling Keyboard Input in ncurses

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 blocksif 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
qis 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_UPKEY_DOWNKEY_LEFTKEY_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.




