#include "SDL_internal.h"
#if defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
#include "SDL_windowsvideo.h"
#include "../../events/SDL_displayevents_c.h"
#ifdef HAVE_DXGI1_6_H
#define COBJMACROS
#include <dxgi1_6.h>
#endif
#ifndef CDS_FULLSCREEN
#define CDS_FULLSCREEN 0
#endif
#ifndef USER_DEFAULT_SCREEN_DPI
#define USER_DEFAULT_SCREEN_DPI 96
#endif
static void WIN_UpdateDisplayMode(SDL_VideoDevice *_this, LPCWSTR deviceName, DWORD index, SDL_DisplayMode *mode)
{
SDL_DisplayModeData *data = (SDL_DisplayModeData *)mode->internal;
HDC hdc;
data->DeviceMode.dmFields = (DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY | DM_DISPLAYFLAGS);
if (index == ENUM_CURRENT_SETTINGS && (hdc = CreateDCW(deviceName, NULL, NULL, NULL)) != NULL) {
char bmi_data[sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)];
LPBITMAPINFO bmi;
HBITMAP hbm;
SDL_zeroa(bmi_data);
bmi = (LPBITMAPINFO)bmi_data;
bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
hbm = CreateCompatibleBitmap(hdc, 1, 1);
GetDIBits(hdc, hbm, 0, 1, NULL, bmi, DIB_RGB_COLORS);
GetDIBits(hdc, hbm, 0, 1, NULL, bmi, DIB_RGB_COLORS);
DeleteObject(hbm);
DeleteDC(hdc);
if (bmi->bmiHeader.biCompression == BI_BITFIELDS) {
switch (*(Uint32 *)bmi->bmiColors) {
case 0x00FF0000:
mode->format = SDL_PIXELFORMAT_XRGB8888;
break;
case 0x000000FF:
mode->format = SDL_PIXELFORMAT_XBGR8888;
break;
case 0xF800:
mode->format = SDL_PIXELFORMAT_RGB565;
break;
case 0x7C00:
mode->format = SDL_PIXELFORMAT_XRGB1555;
break;
}
} else if (bmi->bmiHeader.biCompression == BI_RGB) {
if (bmi->bmiHeader.biBitCount == 24) {
mode->format = SDL_PIXELFORMAT_RGB24;
} else if (bmi->bmiHeader.biBitCount == 8) {
mode->format = SDL_PIXELFORMAT_INDEX8;
} else if (bmi->bmiHeader.biBitCount == 4) {
mode->format = SDL_PIXELFORMAT_INDEX4LSB;
}
}
} else if (mode->format == SDL_PIXELFORMAT_UNKNOWN) {
if ((data->DeviceMode.dmFields & DM_BITSPERPEL) == DM_BITSPERPEL) {
switch (data->DeviceMode.dmBitsPerPel) {
case 32:
mode->format = SDL_PIXELFORMAT_XRGB8888;
break;
case 24:
mode->format = SDL_PIXELFORMAT_RGB24;
break;
case 16:
mode->format = SDL_PIXELFORMAT_RGB565;
break;
case 15:
mode->format = SDL_PIXELFORMAT_XRGB1555;
break;
case 8:
mode->format = SDL_PIXELFORMAT_INDEX8;
break;
case 4:
mode->format = SDL_PIXELFORMAT_INDEX4LSB;
break;
}
}
}
}
static void *WIN_GetDXGIOutput(SDL_VideoDevice *_this, const WCHAR *DeviceName)
{
void *result = NULL;
#ifdef HAVE_DXGI_H
const SDL_VideoData *videodata = (const SDL_VideoData *)_this->internal;
int nAdapter, nOutput;
IDXGIAdapter *pDXGIAdapter;
IDXGIOutput *pDXGIOutput;
if (!videodata->pDXGIFactory) {
return NULL;
}
nAdapter = 0;
while (!result && SUCCEEDED(IDXGIFactory_EnumAdapters(videodata->pDXGIFactory, nAdapter, &pDXGIAdapter))) {
nOutput = 0;
while (!result && SUCCEEDED(IDXGIAdapter_EnumOutputs(pDXGIAdapter, nOutput, &pDXGIOutput))) {
DXGI_OUTPUT_DESC outputDesc;
if (SUCCEEDED(IDXGIOutput_GetDesc(pDXGIOutput, &outputDesc))) {
if (SDL_wcscmp(outputDesc.DeviceName, DeviceName) == 0) {
result = pDXGIOutput;
}
}
if (pDXGIOutput != result) {
IDXGIOutput_Release(pDXGIOutput);
}
nOutput++;
}
IDXGIAdapter_Release(pDXGIAdapter);
nAdapter++;
}
#endif
return result;
}
static void WIN_ReleaseDXGIOutput(void *dxgi_output)
{
#ifdef HAVE_DXGI_H
IDXGIOutput *pDXGIOutput = (IDXGIOutput *)dxgi_output;
if (pDXGIOutput) {
IDXGIOutput_Release(pDXGIOutput);
}
#endif
}
static SDL_DisplayOrientation WIN_GetNaturalOrientation(DEVMODEW *mode)
{
int width = mode->dmPelsWidth;
int height = mode->dmPelsHeight;
if (mode->dmDisplayOrientation == DMDO_90 || mode->dmDisplayOrientation == DMDO_270) {
int temp = width;
width = height;
height = temp;
}
if (width >= height) {
return SDL_ORIENTATION_LANDSCAPE;
} else {
return SDL_ORIENTATION_PORTRAIT;
}
}
static SDL_DisplayOrientation WIN_GetDisplayOrientation(DEVMODEW *mode)
{
if (WIN_GetNaturalOrientation(mode) == SDL_ORIENTATION_LANDSCAPE) {
switch (mode->dmDisplayOrientation) {
case DMDO_DEFAULT:
return SDL_ORIENTATION_LANDSCAPE;
case DMDO_90:
return SDL_ORIENTATION_PORTRAIT;
case DMDO_180:
return SDL_ORIENTATION_LANDSCAPE_FLIPPED;
case DMDO_270:
return SDL_ORIENTATION_PORTRAIT_FLIPPED;
default:
return SDL_ORIENTATION_UNKNOWN;
}
} else {
switch (mode->dmDisplayOrientation) {
case DMDO_DEFAULT:
return SDL_ORIENTATION_PORTRAIT;
case DMDO_90:
return SDL_ORIENTATION_LANDSCAPE_FLIPPED;
case DMDO_180:
return SDL_ORIENTATION_PORTRAIT_FLIPPED;
case DMDO_270:
return SDL_ORIENTATION_LANDSCAPE;
default:
return SDL_ORIENTATION_UNKNOWN;
}
}
}
static void WIN_GetRefreshRate(void *dxgi_output, DEVMODEW *mode, int *numerator, int *denominator)
{
switch (mode->dmDisplayFrequency) {
case 119:
case 59:
case 29:
*numerator = (mode->dmDisplayFrequency + 1) * 1000;
*denominator = 1001;
break;
default:
*numerator = mode->dmDisplayFrequency;
*denominator = 1;
break;
}
#ifdef HAVE_DXGI_H
if (dxgi_output) {
IDXGIOutput *pDXGIOutput = (IDXGIOutput *)dxgi_output;
DXGI_MODE_DESC modeToMatch;
DXGI_MODE_DESC closestMatch;
SDL_zero(modeToMatch);
modeToMatch.Width = mode->dmPelsWidth;
modeToMatch.Height = mode->dmPelsHeight;
modeToMatch.RefreshRate.Numerator = *numerator;
modeToMatch.RefreshRate.Denominator = *denominator;
modeToMatch.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
if (SUCCEEDED(IDXGIOutput_FindClosestMatchingMode(pDXGIOutput, &modeToMatch, &closestMatch, NULL))) {
*numerator = closestMatch.RefreshRate.Numerator;
*denominator = closestMatch.RefreshRate.Denominator;
}
}
#endif }
static float WIN_GetContentScale(SDL_VideoDevice *_this, HMONITOR hMonitor)
{
int dpi = 0;
const SDL_VideoData *videodata = (const SDL_VideoData *)_this->internal;
if (videodata->GetDpiForMonitor) {
UINT hdpi_uint, vdpi_uint;
if (videodata->GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &hdpi_uint, &vdpi_uint) == S_OK) {
dpi = (int)hdpi_uint;
}
}
if (dpi == 0) {
HDC hdc = GetDC(NULL);
if (hdc) {
dpi = GetDeviceCaps(hdc, LOGPIXELSX);
ReleaseDC(NULL, hdc);
}
}
if (dpi == 0) {
dpi = USER_DEFAULT_SCREEN_DPI;
}
return dpi / (float)USER_DEFAULT_SCREEN_DPI;
}
static bool WIN_GetDisplayMode(SDL_VideoDevice *_this, void *dxgi_output, HMONITOR hMonitor, LPCWSTR deviceName, DWORD index, SDL_DisplayMode *mode, SDL_DisplayOrientation *natural_orientation, SDL_DisplayOrientation *current_orientation)
{
SDL_DisplayModeData *data;
DEVMODEW devmode;
devmode.dmSize = sizeof(devmode);
devmode.dmDriverExtra = 0;
if (!EnumDisplaySettingsW(deviceName, index, &devmode)) {
return false;
}
data = (SDL_DisplayModeData *)SDL_malloc(sizeof(*data));
if (!data) {
return false;
}
SDL_zerop(mode);
mode->internal = data;
data->DeviceMode = devmode;
mode->format = SDL_PIXELFORMAT_UNKNOWN;
mode->w = data->DeviceMode.dmPelsWidth;
mode->h = data->DeviceMode.dmPelsHeight;
WIN_GetRefreshRate(dxgi_output, &data->DeviceMode, &mode->refresh_rate_numerator, &mode->refresh_rate_denominator);
WIN_UpdateDisplayMode(_this, deviceName, index, mode);
if (natural_orientation) {
*natural_orientation = WIN_GetNaturalOrientation(&devmode);
}
if (current_orientation) {
*current_orientation = WIN_GetDisplayOrientation(&devmode);
}
return true;
}
static char *WIN_GetDisplayNameVista(SDL_VideoData *videodata, const WCHAR *deviceName)
{
DISPLAYCONFIG_PATH_INFO *paths = NULL;
DISPLAYCONFIG_MODE_INFO *modes = NULL;
char *result = NULL;
UINT32 pathCount = 0;
UINT32 modeCount = 0;
UINT32 i;
LONG rc;
if (!videodata->GetDisplayConfigBufferSizes || !videodata->QueryDisplayConfig || !videodata->DisplayConfigGetDeviceInfo) {
return NULL;
}
do {
rc = videodata->GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &pathCount, &modeCount);
if (rc != ERROR_SUCCESS) {
goto WIN_GetDisplayNameVista_failed;
}
SDL_free(paths);
SDL_free(modes);
paths = (DISPLAYCONFIG_PATH_INFO *)SDL_malloc(sizeof(DISPLAYCONFIG_PATH_INFO) * pathCount);
modes = (DISPLAYCONFIG_MODE_INFO *)SDL_malloc(sizeof(DISPLAYCONFIG_MODE_INFO) * modeCount);
if ((!paths) || (!modes)) {
goto WIN_GetDisplayNameVista_failed;
}
rc = videodata->QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &pathCount, paths, &modeCount, modes, 0);
} while (rc == ERROR_INSUFFICIENT_BUFFER);
if (rc == ERROR_SUCCESS) {
for (i = 0; i < pathCount; i++) {
DISPLAYCONFIG_SOURCE_DEVICE_NAME sourceName;
DISPLAYCONFIG_TARGET_DEVICE_NAME targetName;
SDL_zero(sourceName);
sourceName.header.adapterId = paths[i].targetInfo.adapterId;
sourceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
sourceName.header.size = sizeof(sourceName);
sourceName.header.id = paths[i].sourceInfo.id;
rc = videodata->DisplayConfigGetDeviceInfo(&sourceName.header);
if (rc != ERROR_SUCCESS) {
break;
} else if (SDL_wcscmp(deviceName, sourceName.viewGdiDeviceName) != 0) {
continue;
}
SDL_zero(targetName);
targetName.header.adapterId = paths[i].targetInfo.adapterId;
targetName.header.id = paths[i].targetInfo.id;
targetName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
targetName.header.size = sizeof(targetName);
rc = videodata->DisplayConfigGetDeviceInfo(&targetName.header);
if (rc == ERROR_SUCCESS) {
result = WIN_StringToUTF8W(targetName.monitorFriendlyDeviceName);
if (result && (*result == '\0')) {
SDL_free(result);
result = NULL;
}
}
break;
}
}
SDL_free(paths);
SDL_free(modes);
return result;
WIN_GetDisplayNameVista_failed:
SDL_free(result);
SDL_free(paths);
SDL_free(modes);
return NULL;
}
#ifdef HAVE_DXGI1_6_H
static bool WIN_GetMonitorDESC1(HMONITOR hMonitor, DXGI_OUTPUT_DESC1 *desc)
{
typedef HRESULT (WINAPI *pfnCreateDXGIFactory1)(REFIID riid, void **ppFactory);
pfnCreateDXGIFactory1 pCreateDXGIFactory1 = NULL;
SDL_SharedObject *hDXGIMod = NULL;
bool found = false;
hDXGIMod = SDL_LoadObject("dxgi.dll");
if (hDXGIMod) {
pCreateDXGIFactory1 = (pfnCreateDXGIFactory1)SDL_LoadFunction(hDXGIMod, "CreateDXGIFactory1");
}
if (pCreateDXGIFactory1) {
static const GUID SDL_IID_IDXGIFactory1 = { 0x770aae78, 0xf26f, 0x4dba, { 0xa8, 0x29, 0x25, 0x3c, 0x83, 0xd1, 0xb3, 0x87 } };
static const GUID SDL_IID_IDXGIOutput6 = { 0x068346e8, 0xaaec, 0x4b84, { 0xad, 0xd7, 0x13, 0x7f, 0x51, 0x3f, 0x77, 0xa1 } };
IDXGIFactory1 *dxgiFactory;
if (SUCCEEDED(pCreateDXGIFactory1(&SDL_IID_IDXGIFactory1, (void **)&dxgiFactory))) {
IDXGIAdapter1 *dxgiAdapter;
UINT adapter = 0;
while (!found && SUCCEEDED(IDXGIFactory1_EnumAdapters1(dxgiFactory, adapter, &dxgiAdapter))) {
IDXGIOutput *dxgiOutput;
UINT output = 0;
while (!found && SUCCEEDED(IDXGIAdapter1_EnumOutputs(dxgiAdapter, output, &dxgiOutput))) {
IDXGIOutput6 *dxgiOutput6;
if (SUCCEEDED(IDXGIOutput_QueryInterface(dxgiOutput, &SDL_IID_IDXGIOutput6, (void **)&dxgiOutput6))) {
if (SUCCEEDED(IDXGIOutput6_GetDesc1(dxgiOutput6, desc))) {
if (desc->Monitor == hMonitor) {
found = true;
}
}
IDXGIOutput6_Release(dxgiOutput6);
}
IDXGIOutput_Release(dxgiOutput);
++output;
}
IDXGIAdapter1_Release(dxgiAdapter);
++adapter;
}
IDXGIFactory2_Release(dxgiFactory);
}
}
if (hDXGIMod) {
SDL_UnloadObject(hDXGIMod);
}
return found;
}
static bool WIN_GetMonitorPathInfo(SDL_VideoData *videodata, HMONITOR hMonitor, DISPLAYCONFIG_PATH_INFO *path_info)
{
LONG result;
MONITORINFOEXW view_info;
UINT32 i;
UINT32 num_path_array_elements = 0;
UINT32 num_mode_info_array_elements = 0;
DISPLAYCONFIG_PATH_INFO *path_infos = NULL, *new_path_infos;
DISPLAYCONFIG_MODE_INFO *mode_infos = NULL, *new_mode_infos;
bool found = false;
if (!videodata->GetDisplayConfigBufferSizes || !videodata->QueryDisplayConfig || !videodata->DisplayConfigGetDeviceInfo) {
return false;
}
SDL_zero(view_info);
view_info.cbSize = sizeof(view_info);
if (!GetMonitorInfoW(hMonitor, (MONITORINFO *)&view_info)) {
goto done;
}
do {
if (videodata->GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &num_path_array_elements, &num_mode_info_array_elements) != ERROR_SUCCESS) {
SDL_free(path_infos);
SDL_free(mode_infos);
return false;
}
new_path_infos = (DISPLAYCONFIG_PATH_INFO *)SDL_realloc(path_infos, num_path_array_elements * sizeof(*path_infos));
if (!new_path_infos) {
goto done;
}
path_infos = new_path_infos;
new_mode_infos = (DISPLAYCONFIG_MODE_INFO *)SDL_realloc(mode_infos, num_mode_info_array_elements * sizeof(*mode_infos));
if (!new_mode_infos) {
goto done;
}
mode_infos = new_mode_infos;
result = videodata->QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &num_path_array_elements, path_infos, &num_mode_info_array_elements, mode_infos, NULL);
} while (result == ERROR_INSUFFICIENT_BUFFER);
if (result == ERROR_SUCCESS) {
for (i = 0; i < num_path_array_elements; ++i) {
DISPLAYCONFIG_SOURCE_DEVICE_NAME device_name;
SDL_zero(device_name);
device_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
device_name.header.size = sizeof(device_name);
device_name.header.adapterId = path_infos[i].sourceInfo.adapterId;
device_name.header.id = path_infos[i].sourceInfo.id;
if (videodata->DisplayConfigGetDeviceInfo(&device_name.header) == ERROR_SUCCESS) {
if (SDL_wcscmp(view_info.szDevice, device_name.viewGdiDeviceName) == 0) {
SDL_copyp(path_info, &path_infos[i]);
found = true;
break;
}
}
}
}
done:
SDL_free(path_infos);
SDL_free(mode_infos);
return found;
}
static float WIN_GetSDRWhitePoint(SDL_VideoDevice *_this, HMONITOR hMonitor)
{
DISPLAYCONFIG_PATH_INFO path_info;
SDL_VideoData *videodata = _this->internal;
float SDR_white_level = 1.0f;
if (WIN_GetMonitorPathInfo(videodata, hMonitor, &path_info)) {
struct SDL_DISPLAYCONFIG_SDR_WHITE_LEVEL {
DISPLAYCONFIG_DEVICE_INFO_HEADER header;
ULONG SDRWhiteLevel;
} white_level;
#define DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL 11
SDL_zero(white_level);
white_level.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL;
white_level.header.size = sizeof(white_level);
white_level.header.adapterId = path_info.targetInfo.adapterId;
white_level.header.id = path_info.targetInfo.id;
if (videodata->DisplayConfigGetDeviceInfo(&white_level.header) == ERROR_SUCCESS &&
white_level.SDRWhiteLevel > 0) {
SDR_white_level = (white_level.SDRWhiteLevel / 1000.0f);
}
}
return SDR_white_level;
}
static void WIN_GetHDRProperties(SDL_VideoDevice *_this, HMONITOR hMonitor, SDL_HDROutputProperties *HDR)
{
DXGI_OUTPUT_DESC1 desc;
SDL_zerop(HDR);
if (WIN_GetMonitorDESC1(hMonitor, &desc)) {
if (desc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) {
HDR->SDR_white_level = WIN_GetSDRWhitePoint(_this, hMonitor);
HDR->HDR_headroom = (desc.MaxLuminance / 80.0f) / HDR->SDR_white_level;
}
}
}
#endif
static void WIN_AddDisplay(SDL_VideoDevice *_this, HMONITOR hMonitor, const MONITORINFOEXW *info, int *display_index)
{
int i, index = *display_index;
SDL_VideoDisplay display;
SDL_DisplayData *displaydata;
void *dxgi_output = NULL;
SDL_DisplayMode mode;
SDL_DisplayOrientation natural_orientation;
SDL_DisplayOrientation current_orientation;
float content_scale = WIN_GetContentScale(_this, hMonitor);
#ifdef DEBUG_MODES
SDL_Log("Display: %s", WIN_StringToUTF8W(info->szDevice));
#endif
dxgi_output = WIN_GetDXGIOutput(_this, info->szDevice);
bool found = WIN_GetDisplayMode(_this, dxgi_output, hMonitor, info->szDevice, ENUM_CURRENT_SETTINGS, &mode, &natural_orientation, ¤t_orientation);
WIN_ReleaseDXGIOutput(dxgi_output);
if (!found) {
return;
}
for (i = 0; i < _this->num_displays; ++i) {
SDL_DisplayData *internal = _this->displays[i]->internal;
if (SDL_wcscmp(internal->DeviceName, info->szDevice) == 0) {
bool moved = (index != i);
bool changed_bounds = false;
if (internal->state != DisplayRemoved) {
goto cleanup;
}
if (index >= _this->num_displays) {
goto cleanup;
}
if (moved) {
SDL_VideoDisplay *tmp;
tmp = _this->displays[index];
_this->displays[index] = _this->displays[i];
_this->displays[i] = tmp;
i = index;
}
internal->MonitorHandle = hMonitor;
internal->state = DisplayUnchanged;
SDL_PropertiesID props = SDL_GetDisplayProperties(_this->displays[i]->id);
SDL_SetPointerProperty(props, SDL_PROP_DISPLAY_WINDOWS_HMONITOR_POINTER, hMonitor);
if (!_this->setting_display_mode) {
SDL_VideoDisplay *existing_display = _this->displays[i];
SDL_Rect bounds;
SDL_ResetFullscreenDisplayModes(existing_display);
SDL_SetDesktopDisplayMode(existing_display, &mode);
mode.internal = NULL;
if (WIN_GetDisplayBounds(_this, existing_display, &bounds) &&
SDL_memcmp(&internal->bounds, &bounds, sizeof(bounds)) != 0) {
changed_bounds = true;
SDL_copyp(&internal->bounds, &bounds);
}
if (moved || changed_bounds) {
SDL_SendDisplayEvent(existing_display, SDL_EVENT_DISPLAY_MOVED, 0, 0);
}
SDL_SendDisplayEvent(existing_display, SDL_EVENT_DISPLAY_ORIENTATION, current_orientation, 0);
SDL_SetDisplayContentScale(existing_display, content_scale);
#ifdef HAVE_DXGI1_6_H
SDL_HDROutputProperties HDR;
WIN_GetHDRProperties(_this, hMonitor, &HDR);
SDL_SetDisplayHDRProperties(existing_display, &HDR);
#endif
}
goto done;
}
}
displaydata = (SDL_DisplayData *)SDL_calloc(1, sizeof(*displaydata));
if (!displaydata) {
goto cleanup;
}
SDL_memcpy(displaydata->DeviceName, info->szDevice, sizeof(displaydata->DeviceName));
displaydata->MonitorHandle = hMonitor;
displaydata->state = DisplayAdded;
SDL_zero(display);
display.name = WIN_GetDisplayNameVista(_this->internal, info->szDevice);
if (!display.name) {
DISPLAY_DEVICEW device;
SDL_zero(device);
device.cb = sizeof(device);
if (EnumDisplayDevicesW(info->szDevice, 0, &device, 0)) {
display.name = WIN_StringToUTF8W(device.DeviceString);
}
}
display.desktop_mode = mode;
display.natural_orientation = natural_orientation;
display.current_orientation = current_orientation;
display.content_scale = content_scale;
display.device = _this;
display.internal = displaydata;
WIN_GetDisplayBounds(_this, &display, &displaydata->bounds);
#ifdef HAVE_DXGI1_6_H
WIN_GetHDRProperties(_this, hMonitor, &display.HDR);
#endif
SDL_DisplayID displayID = SDL_AddVideoDisplay(&display, false);
if (displayID) {
mode.internal = NULL;
SDL_PropertiesID props = SDL_GetDisplayProperties(displayID);
SDL_SetPointerProperty(props, SDL_PROP_DISPLAY_WINDOWS_HMONITOR_POINTER, hMonitor);
} else {
SDL_free(displaydata);
}
SDL_free(display.name);
done:
*display_index += 1;
cleanup:
SDL_free(mode.internal);
}
typedef struct _WIN_AddDisplaysData
{
SDL_VideoDevice *video_device;
int display_index;
bool want_primary;
} WIN_AddDisplaysData;
static BOOL CALLBACK WIN_AddDisplaysCallback(HMONITOR hMonitor,
HDC hdcMonitor,
LPRECT lprcMonitor,
LPARAM dwData)
{
WIN_AddDisplaysData *data = (WIN_AddDisplaysData *)dwData;
MONITORINFOEXW info;
SDL_zero(info);
info.cbSize = sizeof(info);
if (GetMonitorInfoW(hMonitor, (LPMONITORINFO)&info) != 0) {
const bool is_primary = ((info.dwFlags & MONITORINFOF_PRIMARY) == MONITORINFOF_PRIMARY);
if (is_primary == data->want_primary) {
WIN_AddDisplay(data->video_device, hMonitor, &info, &data->display_index);
}
}
return TRUE;
}
static void WIN_AddDisplays(SDL_VideoDevice *_this)
{
WIN_AddDisplaysData callback_data;
callback_data.video_device = _this;
callback_data.display_index = 0;
callback_data.want_primary = true;
EnumDisplayMonitors(NULL, NULL, WIN_AddDisplaysCallback, (LPARAM)&callback_data);
callback_data.want_primary = false;
EnumDisplayMonitors(NULL, NULL, WIN_AddDisplaysCallback, (LPARAM)&callback_data);
}
bool WIN_InitModes(SDL_VideoDevice *_this)
{
WIN_AddDisplays(_this);
if (_this->num_displays == 0) {
return SDL_SetError("No displays available");
}
return true;
}
bool WIN_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect)
{
const SDL_DisplayData *data = display->internal;
MONITORINFO minfo;
BOOL rc;
SDL_zero(minfo);
minfo.cbSize = sizeof(MONITORINFO);
rc = GetMonitorInfo(data->MonitorHandle, &minfo);
if (!rc) {
return SDL_SetError("Couldn't find monitor data");
}
rect->x = minfo.rcMonitor.left;
rect->y = minfo.rcMonitor.top;
rect->w = minfo.rcMonitor.right - minfo.rcMonitor.left;
rect->h = minfo.rcMonitor.bottom - minfo.rcMonitor.top;
return true;
}
bool WIN_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect)
{
const SDL_DisplayData *data = display->internal;
MONITORINFO minfo;
BOOL rc;
SDL_zero(minfo);
minfo.cbSize = sizeof(MONITORINFO);
rc = GetMonitorInfo(data->MonitorHandle, &minfo);
if (!rc) {
return SDL_SetError("Couldn't find monitor data");
}
rect->x = minfo.rcWork.left;
rect->y = minfo.rcWork.top;
rect->w = minfo.rcWork.right - minfo.rcWork.left;
rect->h = minfo.rcWork.bottom - minfo.rcWork.top;
return true;
}
bool WIN_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display)
{
SDL_DisplayData *data = display->internal;
void *dxgi_output;
DWORD i;
SDL_DisplayMode mode;
dxgi_output = WIN_GetDXGIOutput(_this, data->DeviceName);
for (i = ENUM_CURRENT_SETTINGS; ; ++i) {
if (!WIN_GetDisplayMode(_this, dxgi_output, data->MonitorHandle, data->DeviceName, i, &mode, NULL, NULL)) {
break;
}
if (SDL_ISPIXELFORMAT_INDEXED(mode.format)) {
SDL_free(mode.internal);
continue;
}
if (mode.format != SDL_PIXELFORMAT_UNKNOWN) {
if (!SDL_AddFullscreenDisplayMode(display, &mode)) {
SDL_free(mode.internal);
}
} else {
SDL_free(mode.internal);
}
}
WIN_ReleaseDXGIOutput(dxgi_output);
return true;
}
#ifdef DEBUG_MODES
static void WIN_LogMonitor(SDL_VideoDevice *_this, HMONITOR mon)
{
const SDL_VideoData *vid_data = (const SDL_VideoData *)_this->internal;
MONITORINFOEX minfo;
UINT xdpi = 0, ydpi = 0;
char *name_utf8;
if (vid_data->GetDpiForMonitor) {
vid_data->GetDpiForMonitor(mon, MDT_EFFECTIVE_DPI, &xdpi, &ydpi);
}
SDL_zero(minfo);
minfo.cbSize = sizeof(minfo);
GetMonitorInfo(mon, (LPMONITORINFO)&minfo);
name_utf8 = WIN_StringToUTF8(minfo.szDevice);
SDL_Log("WIN_LogMonitor: monitor \"%s\": dpi: %d windows screen coordinates: %d, %d, %dx%d",
name_utf8,
xdpi,
minfo.rcMonitor.left,
minfo.rcMonitor.top,
minfo.rcMonitor.right - minfo.rcMonitor.left,
minfo.rcMonitor.bottom - minfo.rcMonitor.top);
SDL_free(name_utf8);
}
#endif
bool WIN_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode)
{
SDL_DisplayData *displaydata = display->internal;
SDL_DisplayModeData *data = (SDL_DisplayModeData *)mode->internal;
LONG status;
#ifdef DEBUG_MODES
SDL_Log("WIN_SetDisplayMode: monitor state before mode change:");
WIN_LogMonitor(_this, displaydata->MonitorHandle);
#endif
if (mode->internal == display->desktop_mode.internal) {
#ifdef DEBUG_MODES
SDL_Log("WIN_SetDisplayMode: resetting to original resolution");
#endif
status = ChangeDisplaySettingsExW(displaydata->DeviceName, NULL, NULL, CDS_FULLSCREEN, NULL);
} else {
#ifdef DEBUG_MODES
SDL_Log("WIN_SetDisplayMode: changing to %dx%d pixels", data->DeviceMode.dmPelsWidth, data->DeviceMode.dmPelsHeight);
#endif
status = ChangeDisplaySettingsExW(displaydata->DeviceName, &data->DeviceMode, NULL, CDS_FULLSCREEN, NULL);
}
if (status != DISP_CHANGE_SUCCESSFUL) {
const char *reason = "Unknown reason";
switch (status) {
case DISP_CHANGE_BADFLAGS:
reason = "DISP_CHANGE_BADFLAGS";
break;
case DISP_CHANGE_BADMODE:
reason = "DISP_CHANGE_BADMODE";
break;
case DISP_CHANGE_BADPARAM:
reason = "DISP_CHANGE_BADPARAM";
break;
case DISP_CHANGE_FAILED:
reason = "DISP_CHANGE_FAILED";
break;
}
return SDL_SetError("ChangeDisplaySettingsEx() failed: %s", reason);
}
#ifdef DEBUG_MODES
SDL_Log("WIN_SetDisplayMode: monitor state after mode change:");
WIN_LogMonitor(_this, displaydata->MonitorHandle);
#endif
EnumDisplaySettingsW(displaydata->DeviceName, ENUM_CURRENT_SETTINGS, &data->DeviceMode);
WIN_UpdateDisplayMode(_this, displaydata->DeviceName, ENUM_CURRENT_SETTINGS, mode);
return true;
}
void WIN_RefreshDisplays(SDL_VideoDevice *_this)
{
int i;
for (i = 0; i < _this->num_displays; ++i) {
SDL_DisplayData *internal = _this->displays[i]->internal;
internal->state = DisplayRemoved;
}
WIN_AddDisplays(_this);
for (i = _this->num_displays - 1; i >= 0; --i) {
SDL_VideoDisplay *display = _this->displays[i];
SDL_DisplayData *internal = display->internal;
if (internal->state == DisplayRemoved) {
SDL_DelVideoDisplay(display->id, true);
}
}
for (i = 0; i < _this->num_displays; ++i) {
SDL_VideoDisplay *display = _this->displays[i];
SDL_DisplayData *internal = display->internal;
if (internal->state == DisplayAdded) {
SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_ADDED, 0, 0);
}
}
}
void WIN_UpdateDisplayUsableBounds(SDL_VideoDevice *_this)
{
for (int i = 0; i < _this->num_displays; ++i) {
SDL_SendDisplayEvent(_this->displays[i], SDL_EVENT_DISPLAY_USABLE_BOUNDS_CHANGED, 0, 0);
}
}
void WIN_QuitModes(SDL_VideoDevice *_this)
{
}
#endif