Modern games and applications often require precise control over display management. In this lesson, you'll learn to retrieve monitor counts, display names, and manage window placement across different displays.
In the context of games, the concepts we cover in this lesson are primarily useful for letting players choose which monitor they want our game to run on:
We can retrieve the total number of displays available to our program using the SDL_GetNumVideoDisplays()
function. Below, we invoke this function on a computer with 4 monitors:
#include <SDL.h>
#include <iostream>
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
int DisplayCount{SDL_GetNumVideoDisplays()};
std::cout << "Displays: " << DisplayCount;
return 0;
}
Displays: 4
The SDL_GetNumVideoDisplays()
function can return 0
, or a negative error code. This typically indicates an error, which can be further investigated using SDL_GetError()
:
#include <SDL.h>
#include <iostream>
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
int DisplayCount{SDL_GetNumVideoDisplays()};
if (DisplayCount > 0) {
std::cout << "Displays: " << DisplayCount;
} else {
std::cout << "Could not get displays: "
<< SDL_GetError();
}
return 0;
}
Could not get displays: Video subsystem has not been initialized
When presenting options for our player to select a monitor, it can be helpful to identify the displays by name. We can do this by passing a display index to the SDL_GetDisplayName()
function.
This will return a char*
string containing the name of the display. The first display on our system will have a display index of 0
:
#include <SDL.h>
#include <iostream>
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
std::cout << "First Display Name: "
<< SDL_GetDisplayName(0);
return 0;
}
First Display Name: DELL S2721DGF
Beyond the first monitor, whose display index is 0
, additional monitors will have incrementing display indices. For example, if our system has four displays, their display indices will be 0
, 1
, 2
, and 3
.
Knowing the names of all connected displays can be useful for games or applications that allow players to select a monitor. To achieve this, we combine the SDL_GetNumVideoDisplays()
and SDL_GetDisplayName()
functions.
We can use SDL_GetNumVideoDisplays()
to get the number of displays, and then use a for
loop to iterate over all of the display indices in that range:
#include <SDL.h>
#include <iostream>
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
int displayCount = SDL_GetNumVideoDisplays();
if (displayCount < 1) {
std::cout << "Error: " << SDL_GetError() << '\n';
return 1;
}
for (int i = 0; i < displayCount; ++i) {
const char* displayName = SDL_GetDisplayName(i);
if (displayName) {
std::cout << "Display " << i << ": "
<< displayName << '\n';
} else {
std::cout << "Display " << i
<< ": Unknown (" << SDL_GetError() << ")\n";
}
}
return 0;
}
Display 0: DELL S2721DGF
Display 1: DELL S2721DGF
Display 2: DELL U2515H
Display 3: DELL U2515H
The SDL_GetDisplayName()
function will return a nullptr
if it is unable to get the name of the display with the index we provide. We can call SDL_GetError()
for an explanation of this failure. Below, we provide a display index of 100
, when our system only has 4 displays:
#include <SDL.h>
#include <iostream>
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
const char* Name{SDL_GetDisplayName(100)};
if (Name) {
std::cout << "Display 100: " << Name;
} else {
std::cout << "Cannot get display name:\n"
<< SDL_GetError();
}
return 0;
}
Cannot get display name:
displayIndex must be in the range 0 - 3
As we’ve seen, we can set the horizontal and vertical positions of a window when creating it. We specify these coordinates using the second and third arguments to SDL_CreateWindow()
, representing the horizontal and vertical position of the window:
SDL_CreateWindow(
"First Monitor Window",
100, // Horizontal position
200, // Vertical position
400,400, 0)};
We’ll cover how to precisely control position on a targeted display later in this lesson. For now, let’s introduce some SDL helpers that cover most use cases.
SDL_WINDOWPOS_UNDEFINED_DISPLAY(n)
Previously, we saw how we could use the SDL_WINDOWPOS_UNDEFINED
macro to let the platform decide where to position our window:
SDL_CreateWindow(
"First Monitor Window",
SDL_WINDOWPOS_UNDEFINED, // Horizontal position
SDL_WINDOWPOS_UNDEFINED, // Vertical position
400,400, 0)};
We have a similar SDL_WINDOWPOS_UNDEFINED_DISPLAY()
macro that lets us specify which display our window should be on, but let the platform decide the position within that display.
We provide the display index as an argument to SDL_WINDOWPOS_UNDEFINED_DISPLAY()
to specify the monitor for our window. Below, we create a window on the display with index 0
, and a second window on display with index 1
:
#include <SDL.h>
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
SDL_Window* Window1{
SDL_CreateWindow(
"First Monitor Window",
SDL_WINDOWPOS_UNDEFINED_DISPLAY(0),
SDL_WINDOWPOS_UNDEFINED_DISPLAY(0),
400,400, 0)};
SDL_Window* Window2{
SDL_CreateWindow(
"Second Monitor Window",
SDL_WINDOWPOS_UNDEFINED_DISPLAY(1),
SDL_WINDOWPOS_UNDEFINED_DISPLAY(1),
400, 400, 0)};
SDL_Event E;
while (true) {/*...*/}
}
SDL_WINDOWPOS_CENTERED_DISPLAY(n)
A display-targetting variation of the SDL_WINDOWPOS_CENTERED
macro is also available, in the form of SDL_WINDOWPOS_CENTERED_DISPLAY()
Below, we create a window centered on the display with index 0
, and a second window centered on the display with index 1
:
#include <SDL.h>
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
SDL_Window* Window1{
SDL_CreateWindow(
"First Monitor Centered",
SDL_WINDOWPOS_CENTERED_DISPLAY(0),
SDL_WINDOWPOS_CENTERED_DISPLAY(0),
400,400, 0)};
SDL_Window* Window2{
SDL_CreateWindow(
"Second Monitor Centered",
SDL_WINDOWPOS_CENTERED_DISPLAY(1),
SDL_WINDOWPOS_CENTERED_DISPLAY(1),
400, 400, 0)};
SDL_Event E;
while (true) {/*...*/}
}
To understand the size and layout of our user’s monitors, we can get their display bounds. This involves passing the display ID and a pointer to an SDL_Rect
to the SDL_GetDisplayBounds()
function:
SDL_Rect Bounds;
SDL_GetDisplayBounds(0, &Bounds);
As we’ve covered in the past, an SDL_Rect
combines 4 integers to represent a rectangle. The member variables are:
x
- The left edge of the rectangley
- The top edge of the rectanglew
- The width of the rectangleh
- The height of the rectangleSDL_GetDisplayBounds()
updates these members of our SDL_Rect
with values representing the position and size of a given display. These values are useful if we want some operations, such as moving a window, to target a specific monitor. We’ll demonstrate this use case in the next section.
Below, we iterate through all of our displays, and log out their bounds:
#include <iostream>
#include <SDL.h>
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
int DisplayCount{SDL_GetNumVideoDisplays()};
SDL_Rect Bounds;
for (int i{0}; i < DisplayCount; ++i) {
SDL_GetDisplayBounds(i, &Bounds);
std::cout << "[Display " << i
<< "] Left: " << Bounds.x
<< ", Top: " << Bounds.y
<< ", Width: " << Bounds.w
<< ", Height: " << Bounds.h << '\n';
}
SDL_Event E;
while (true) {/*...*/}
}
[Display 0] Left: 0, Top: 0, Width: 2560, Height: 1440
[Display 1] Left: 2560, Top: 0, Width: 2560, Height: 1440
[Display 2] Left: 14, Top: -1440, Width: 2560, Height: 1440
[Display 3] Left: 2574, Top: -1440, Width: 2560, Height: 1440
Whilst the SDL_Rect
only includes where the top left corner of a display is within the overall monitor layout, we can calculate the right and bottom edges using some basic arithmetic.
The right edge of a display can be calculated by adding its left edge to it’s width. In this example, the right edge of display 2
is $14 + 2560 = 2574$
The bottom edge can be calculated by adding its top edge to its height. The bottom edge of display 2
is $-1440 + 1440 = 0$
Once we’ve used SDL_GetDisplayBounds()
to understand the size and position of a monitor, we can move a window onto that monitor by setting its position to within those bounds.
Below, we move the window to the top left of display 0
if the user presses 0
on their keyboard, and to the top left of display 1
if the user presses 1
:
#include <iostream>
#include <SDL.h>
#include "Window.h"
void HandleKeydownEvent(SDL_KeyboardEvent& E) {
SDL_Window* Window{
SDL_GetWindowFromID(E.windowID)};
SDL_Rect Bounds;
if (E.keysym.sym == SDLK_0) {
SDL_GetDisplayBounds(0, &Bounds);
SDL_SetWindowPosition(
Window, Bounds.x, Bounds.y);
} else if (E.keysym.sym == SDLK_1) {
SDL_GetDisplayBounds(1, &Bounds);
SDL_SetWindowPosition(
Window, Bounds.x, Bounds.y);
}
}
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
SDL_Event E;
while (true) {
while (SDL_PollEvent(&E)) {
if (E.type == SDL_KEYDOWN) {
HandleKeydownEvent(E.key);
} else if (E.type == SDL_QUIT) {
SDL_Quit();
return 0;
}
}
GameWindow.Update();
GameWindow.Render();
}
}
In the previous chapter, we introduced the notion of window decorations, which SDL refers to as borders. These can include elements like the title bar. Below, we show a decorated window, and the equivalent undecorated window:
When setting a window position, we are setting the position of the window's top-left corner, excluding decorations like borders and the title bar. The borders are added outside this area, so we typically want to ensure we leave enough room for them.
In the previous chapter, we covered how to do this using the SDL_GetWindowBorderSize()
function.
Below, we update our previous example where we move our window to the top left of a targetted monitor. But this time, we use SDL_GetWindowBorderSize()
and some arithmetic to ensure the position we choose leaves enough room for the decorations:
void HandleKeydownEvent(SDL_KeyboardEvent& E) {
SDL_Window* Window{
SDL_GetWindowFromID(E.windowID)};
SDL_Rect Bounds;
int Top, Left;
SDL_GetWindowBordersSize(
Window, &Top, &Left, nullptr, nullptr);
if (E.keysym.sym == SDLK_0) {
SDL_GetDisplayBounds(0, &Bounds);
SDL_SetWindowPosition(
Window, Bounds.x + Left, Bounds.y + Top);
} else if (E.keysym.sym == SDLK_1) {
SDL_GetDisplayBounds(1, &Bounds);
SDL_SetWindowPosition(
Window, Bounds.x + Left, Bounds.y + Top);
}
}
If we have a window and need to find out which display it is on, we can use the SDL_GetWindowDisplayIndex()
function. We pass it an SDL_Window
pointer, and retrieve an integer display index as the return value:
SDL_Window* Window(SDL_CreateWindow(
"Example Window",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
300, 300, 0));
std::cout << "Display Index: "
<< SDL_GetWindowDisplayIndex(Window);
Display Index: 0
In this example, we log out the name of the display the window is currently on when the player presses their spacebar:
#include <SDL.h>
#include <iostream>
#include "Window.h"
void HandleKeydownEvent(SDL_KeyboardEvent& E) {
if (E.keysym.sym != SDLK_SPACE) return;
SDL_Window* Window{
SDL_GetWindowFromID(E.windowID)};
int DisplayIndex{
SDL_GetWindowDisplayIndex(Window)};
std::cout << "\nWindow is on "
<< SDL_GetDisplayName(DisplayIndex);
}
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
SDL_Event E;
while (true) {
while (SDL_PollEvent(&E)) {
if (E.type == SDL_KEYDOWN) {
HandleKeydownEvent(E.key);
} else if (E.type == SDL_QUIT) {
SDL_Quit();
return 0;
}
}
GameWindow.Update();
GameWindow.Render();
}
}
Window is on DELL S2721DGF
Window is on DELL U2515H
This lesson covers detecting displays, fetching their properties, and dynamically creating and positioning windows across multiple screens. Key takeaways:
SDL_GetNumVideoDisplays()
.SDL_GetDisplayName()
to display monitor names for user-friendly interfaces.SDL_WINDOWPOS_UNDEFINED_DISPLAY()
and SDL_WINDOWPOS_CENTERED_DISPLAY()
.SDL_GetDisplayBounds()
.SDL_GetWindowBordersSize()
for better placement accuracy.Learn how to handle multiple monitors in SDL, including creating windows on specific displays.
Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games