Fix: SDL Window Not Moving or Responding on Ubuntu / Linux
When getting started with SDL3 (or SDL2) on Linux distributions like Ubuntu, a very common stumbling block is creating a window that appears fine at first glance, but freezes immediately—refusing to move, minimize, or close. If your SDL window is unresponsive and dragging the title bar does nothing, you are not alone. Here is why this happens and how to fix it with proper event handling.
Why Is the SDL Window Freezing?
The issue is not actually an Ubuntu-specific bug; it is directly related to how window managers (such as GNOME or Wayland/X11) interact with graphical applications. In your game_run function, the program simply calls SDL_Delay() without running an event loop:
void game_run(Game *game) {
SDL_Delay(1000);
SDL_SetRenderDrawColor(game->renderer, 255, 255, 255, 255);
SDL_RenderClear(game->renderer);
SDL_RenderPresent(game->renderer);
SDL_Delay(2500);
}Calling SDL_Delay() blocks the main thread completely. During this time, your application stops polling the operating system's event queue. As a result:
- The operating system sends window events (drag, minimize, resize, focus, close).
- Because your application never handles or drains these events using
SDL_PollEvent()(orSDL_PumpEvents()), the OS assumes the window is unresponsive. - The desktop environment freezes window movement and interactions.
The Solution: Implement an Event Loop
To make your window interactive and responsive, replace blocking delays with a standard game loop that continuously polls events using SDL_PollEvent() until the user requests to quit (e.g., clicking the 'X' button or pressing a close shortcut).
Updated game_run Implementation
void game_run(Game *game) {
bool running = true;
SDL_Event event;
while (running) {
// 1. Process all pending window and OS events
while (SDL_PollEvent(&event)) {
if (event.type == SDL_EVENT_QUIT) {
running = false;
}
}
// 2. Render graphics
SDL_SetRenderDrawColor(game->renderer, 255, 255, 255, 255);
SDL_RenderClear(game->renderer);
SDL_RenderPresent(game->renderer);
// 3. Small delay to prevent running at 100% CPU usage (if VSync isn't enabled)
SDL_Delay(16); // roughly ~60 FPS
}
}Complete Working SDL3 Example
Here is the full refactored C program incorporating proper resource management and a non-blocking event loop:
#include <SDL3/SDL.h>
#include <SDL3/SDL_error.h>
#include <SDL3/SDL_render.h>
#include <SDL3/SDL_video.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#define SDL_FLAGS SDL_INIT_VIDEO
#define WINDOW_TITLE "SAMPLE"
#define WINDOW_WIDTH 500
#define WINDOW_HEIGHT 800
typedef struct {
SDL_Renderer *renderer;
SDL_Window *window;
} Game;
bool game_init_sdl(Game *game);
void game_run(Game *game);
void game_free(Game *g);
bool game_init_sdl(Game *game) {
if (!SDL_Init(SDL_FLAGS)) {
fprintf(stderr, "Error initializing SDL: %s\n", SDL_GetError());
return false;
}
game->window = SDL_CreateWindow(WINDOW_TITLE, WINDOW_WIDTH, WINDOW_HEIGHT, 0);
if (!game->window) {
fprintf(stderr, "Error creating window: %s\n", SDL_GetError());
return false;
}
game->renderer = SDL_CreateRenderer(game->window, NULL);
if (!game->renderer) {
fprintf(stderr, "Error creating renderer: %s\n", SDL_GetError());
return false;
}
return true;
}
void game_run(Game *game) {
bool is_running = true;
SDL_Event event;
while (is_running) {
// Draining the event queue keeps the window responsive
while (SDL_PollEvent(&event)) {
if (event.type == SDL_EVENT_QUIT) {
is_running = false;
}
}
// Draw frame
SDL_SetRenderDrawColor(game->renderer, 255, 255, 255, 255);
SDL_RenderClear(game->renderer);
SDL_RenderPresent(game->renderer);
// Cap frame timing
SDL_Delay(16);
}
}
void game_free(Game *g) {
if (g->renderer) {
SDL_DestroyRenderer(g->renderer);
g->renderer = NULL;
}
if (g->window) {
SDL_DestroyWindow(g->window);
g->window = NULL;
}
SDL_Quit();
}
int main(void) {
Game game = {0};
int exit_status = EXIT_FAILURE;
if (game_init_sdl(&game)) {
exit_status = EXIT_SUCCESS;
game_run(&game);
}
game_free(&game);
return exit_status;
}Key Takeaways
- Never rely on long
SDL_Delaycalls without pumping the event loop. - Every GUI framework requires regular polling to communicate with the OS display server (Wayland or X11 on Ubuntu).
- Always listen for
SDL_EVENT_QUIT(orSDL_QUITin SDL2) to ensure clean exits when the user closes the window.