SDL_windowsmodes.c (13993B)
1 /* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20 */ 21 #include "../../SDL_internal.h" 22 23 #if SDL_VIDEO_DRIVER_WINDOWS 24 25 #include "SDL_windowsvideo.h" 26 27 /* Windows CE compatibility */ 28 #ifndef CDS_FULLSCREEN 29 #define CDS_FULLSCREEN 0 30 #endif 31 32 /* #define DEBUG_MODES */ 33 34 static void 35 WIN_UpdateDisplayMode(_THIS, LPCTSTR deviceName, DWORD index, SDL_DisplayMode * mode) 36 { 37 SDL_DisplayModeData *data = (SDL_DisplayModeData *) mode->driverdata; 38 HDC hdc; 39 40 data->DeviceMode.dmFields = 41 (DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY | 42 DM_DISPLAYFLAGS); 43 44 if (index == ENUM_CURRENT_SETTINGS 45 && (hdc = CreateDC(deviceName, NULL, NULL, NULL)) != NULL) { 46 char bmi_data[sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)]; 47 LPBITMAPINFO bmi; 48 HBITMAP hbm; 49 int logical_width = GetDeviceCaps( hdc, HORZRES ); 50 int logical_height = GetDeviceCaps( hdc, VERTRES ); 51 52 mode->w = logical_width; 53 mode->h = logical_height; 54 55 SDL_zeroa(bmi_data); 56 bmi = (LPBITMAPINFO) bmi_data; 57 bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); 58 59 hbm = CreateCompatibleBitmap(hdc, 1, 1); 60 GetDIBits(hdc, hbm, 0, 1, NULL, bmi, DIB_RGB_COLORS); 61 GetDIBits(hdc, hbm, 0, 1, NULL, bmi, DIB_RGB_COLORS); 62 DeleteObject(hbm); 63 DeleteDC(hdc); 64 if (bmi->bmiHeader.biCompression == BI_BITFIELDS) { 65 switch (*(Uint32 *) bmi->bmiColors) { 66 case 0x00FF0000: 67 mode->format = SDL_PIXELFORMAT_RGB888; 68 break; 69 case 0x000000FF: 70 mode->format = SDL_PIXELFORMAT_BGR888; 71 break; 72 case 0xF800: 73 mode->format = SDL_PIXELFORMAT_RGB565; 74 break; 75 case 0x7C00: 76 mode->format = SDL_PIXELFORMAT_RGB555; 77 break; 78 } 79 } else if (bmi->bmiHeader.biBitCount == 8) { 80 mode->format = SDL_PIXELFORMAT_INDEX8; 81 } else if (bmi->bmiHeader.biBitCount == 4) { 82 mode->format = SDL_PIXELFORMAT_INDEX4LSB; 83 } 84 } else if (mode->format == SDL_PIXELFORMAT_UNKNOWN) { 85 /* FIXME: Can we tell what this will be? */ 86 if ((data->DeviceMode.dmFields & DM_BITSPERPEL) == DM_BITSPERPEL) { 87 switch (data->DeviceMode.dmBitsPerPel) { 88 case 32: 89 mode->format = SDL_PIXELFORMAT_RGB888; 90 break; 91 case 24: 92 mode->format = SDL_PIXELFORMAT_RGB24; 93 break; 94 case 16: 95 mode->format = SDL_PIXELFORMAT_RGB565; 96 break; 97 case 15: 98 mode->format = SDL_PIXELFORMAT_RGB555; 99 break; 100 case 8: 101 mode->format = SDL_PIXELFORMAT_INDEX8; 102 break; 103 case 4: 104 mode->format = SDL_PIXELFORMAT_INDEX4LSB; 105 break; 106 } 107 } 108 } 109 } 110 111 static SDL_bool 112 WIN_GetDisplayMode(_THIS, LPCTSTR deviceName, DWORD index, SDL_DisplayMode * mode) 113 { 114 SDL_DisplayModeData *data; 115 DEVMODE devmode; 116 117 devmode.dmSize = sizeof(devmode); 118 devmode.dmDriverExtra = 0; 119 if (!EnumDisplaySettings(deviceName, index, &devmode)) { 120 return SDL_FALSE; 121 } 122 123 data = (SDL_DisplayModeData *) SDL_malloc(sizeof(*data)); 124 if (!data) { 125 return SDL_FALSE; 126 } 127 128 mode->driverdata = data; 129 data->DeviceMode = devmode; 130 131 mode->format = SDL_PIXELFORMAT_UNKNOWN; 132 mode->w = data->DeviceMode.dmPelsWidth; 133 mode->h = data->DeviceMode.dmPelsHeight; 134 mode->refresh_rate = data->DeviceMode.dmDisplayFrequency; 135 136 /* Fill in the mode information */ 137 WIN_UpdateDisplayMode(_this, deviceName, index, mode); 138 return SDL_TRUE; 139 } 140 141 static SDL_bool 142 WIN_AddDisplay(_THIS, HMONITOR hMonitor, const MONITORINFOEXW *info, SDL_bool send_event) 143 { 144 int i; 145 SDL_VideoDisplay display; 146 SDL_DisplayData *displaydata; 147 SDL_DisplayMode mode; 148 DISPLAY_DEVICE device; 149 150 #ifdef DEBUG_MODES 151 SDL_Log("Display: %s\n", WIN_StringToUTF8(info->szDevice)); 152 #endif 153 154 if (!WIN_GetDisplayMode(_this, info->szDevice, ENUM_CURRENT_SETTINGS, &mode)) { 155 return SDL_FALSE; 156 } 157 158 // Prevent adding duplicate displays. Do this after we know the display is 159 // ready to be added to allow any displays that we can't fully query to be 160 // removed 161 for(i = 0; i < _this->num_displays; ++i) { 162 SDL_DisplayData *driverdata = (SDL_DisplayData *)_this->displays[i].driverdata; 163 if (SDL_wcscmp(driverdata->DeviceName, info->szDevice) == 0) { 164 driverdata->MonitorHandle = hMonitor; 165 driverdata->IsValid = SDL_TRUE; 166 return SDL_FALSE; 167 } 168 } 169 170 displaydata = (SDL_DisplayData *) SDL_malloc(sizeof(*displaydata)); 171 if (!displaydata) { 172 return SDL_FALSE; 173 } 174 SDL_memcpy(displaydata->DeviceName, info->szDevice, 175 sizeof(displaydata->DeviceName)); 176 displaydata->MonitorHandle = hMonitor; 177 displaydata->IsValid = SDL_TRUE; 178 179 SDL_zero(display); 180 device.cb = sizeof(device); 181 if (EnumDisplayDevices(info->szDevice, 0, &device, 0)) { 182 display.name = WIN_StringToUTF8(device.DeviceString); 183 } 184 display.desktop_mode = mode; 185 display.current_mode = mode; 186 display.driverdata = displaydata; 187 SDL_AddVideoDisplay(&display, send_event); 188 SDL_free(display.name); 189 return SDL_TRUE; 190 } 191 192 typedef struct _WIN_AddDisplaysData { 193 SDL_VideoDevice *video_device; 194 SDL_bool send_event; 195 SDL_bool want_primary; 196 } WIN_AddDisplaysData; 197 198 static BOOL CALLBACK 199 WIN_AddDisplaysCallback(HMONITOR hMonitor, 200 HDC hdcMonitor, 201 LPRECT lprcMonitor, 202 LPARAM dwData) 203 { 204 WIN_AddDisplaysData *data = (WIN_AddDisplaysData*)dwData; 205 MONITORINFOEXW info; 206 207 SDL_zero(info); 208 info.cbSize = sizeof(info); 209 210 if (GetMonitorInfoW(hMonitor, (LPMONITORINFO)&info) != 0) { 211 const SDL_bool is_primary = ((info.dwFlags & MONITORINFOF_PRIMARY) == MONITORINFOF_PRIMARY); 212 213 if (is_primary == data->want_primary) { 214 WIN_AddDisplay(data->video_device, hMonitor, &info, data->send_event); 215 } 216 } 217 218 // continue enumeration 219 return TRUE; 220 } 221 222 static void 223 WIN_AddDisplays(_THIS, SDL_bool send_event) 224 { 225 WIN_AddDisplaysData callback_data; 226 callback_data.video_device = _this; 227 callback_data.send_event = send_event; 228 229 callback_data.want_primary = SDL_TRUE; 230 EnumDisplayMonitors(NULL, NULL, WIN_AddDisplaysCallback, (LPARAM)&callback_data); 231 232 callback_data.want_primary = SDL_FALSE; 233 EnumDisplayMonitors(NULL, NULL, WIN_AddDisplaysCallback, (LPARAM)&callback_data); 234 } 235 236 int 237 WIN_InitModes(_THIS) 238 { 239 WIN_AddDisplays(_this, SDL_FALSE); 240 241 if (_this->num_displays == 0) { 242 return SDL_SetError("No displays available"); 243 } 244 return 0; 245 } 246 247 int 248 WIN_GetDisplayBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect) 249 { 250 const SDL_DisplayData *data = (const SDL_DisplayData *)display->driverdata; 251 MONITORINFO minfo; 252 BOOL rc; 253 254 SDL_zero(minfo); 255 minfo.cbSize = sizeof(MONITORINFO); 256 rc = GetMonitorInfo(data->MonitorHandle, &minfo); 257 258 if (!rc) { 259 return SDL_SetError("Couldn't find monitor data"); 260 } 261 262 rect->x = minfo.rcMonitor.left; 263 rect->y = minfo.rcMonitor.top; 264 rect->w = minfo.rcMonitor.right - minfo.rcMonitor.left; 265 rect->h = minfo.rcMonitor.bottom - minfo.rcMonitor.top; 266 267 return 0; 268 } 269 270 int 271 WIN_GetDisplayDPI(_THIS, SDL_VideoDisplay * display, float * ddpi_out, float * hdpi_out, float * vdpi_out) 272 { 273 const SDL_DisplayData *displaydata = (SDL_DisplayData *)display->driverdata; 274 const SDL_VideoData *videodata = (SDL_VideoData *)display->device->driverdata; 275 float hdpi = 0, vdpi = 0, ddpi = 0; 276 277 if (videodata->GetDpiForMonitor) { 278 UINT hdpi_uint, vdpi_uint; 279 // Windows 8.1+ codepath 280 if (videodata->GetDpiForMonitor(displaydata->MonitorHandle, MDT_EFFECTIVE_DPI, &hdpi_uint, &vdpi_uint) == S_OK) { 281 // GetDpiForMonitor docs promise to return the same hdpi/vdpi 282 hdpi = (float)hdpi_uint; 283 vdpi = (float)hdpi_uint; 284 ddpi = (float)hdpi_uint; 285 } else { 286 return SDL_SetError("GetDpiForMonitor failed"); 287 } 288 } else { 289 // Window 8.0 and below: same DPI for all monitors. 290 HDC hdc; 291 int hdpi_int, vdpi_int, hpoints, vpoints, hpix, vpix; 292 float hinches, vinches; 293 294 hdc = GetDC(NULL); 295 if (hdc == NULL) { 296 return SDL_SetError("GetDC failed"); 297 } 298 hdpi_int = GetDeviceCaps(hdc, LOGPIXELSX); 299 vdpi_int = GetDeviceCaps(hdc, LOGPIXELSY); 300 ReleaseDC(NULL, hdc); 301 302 hpoints = GetSystemMetrics(SM_CXVIRTUALSCREEN); 303 vpoints = GetSystemMetrics(SM_CYVIRTUALSCREEN); 304 305 hpix = MulDiv(hpoints, hdpi_int, 96); 306 vpix = MulDiv(vpoints, vdpi_int, 96); 307 308 hinches = (float)hpoints / 96.0f; 309 vinches = (float)vpoints / 96.0f; 310 311 hdpi = (float)hdpi_int; 312 vdpi = (float)vdpi_int; 313 ddpi = SDL_ComputeDiagonalDPI(hpix, vpix, hinches, vinches); 314 } 315 316 if (ddpi_out) { 317 *ddpi_out = ddpi; 318 } 319 if (hdpi_out) { 320 *hdpi_out = hdpi; 321 } 322 if (vdpi_out) { 323 *vdpi_out = vdpi; 324 } 325 326 return ddpi != 0.0f ? 0 : SDL_SetError("Couldn't get DPI"); 327 } 328 329 int 330 WIN_GetDisplayUsableBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect) 331 { 332 const SDL_DisplayData *data = (const SDL_DisplayData *)display->driverdata; 333 MONITORINFO minfo; 334 BOOL rc; 335 336 SDL_zero(minfo); 337 minfo.cbSize = sizeof(MONITORINFO); 338 rc = GetMonitorInfo(data->MonitorHandle, &minfo); 339 340 if (!rc) { 341 return SDL_SetError("Couldn't find monitor data"); 342 } 343 344 rect->x = minfo.rcWork.left; 345 rect->y = minfo.rcWork.top; 346 rect->w = minfo.rcWork.right - minfo.rcWork.left; 347 rect->h = minfo.rcWork.bottom - minfo.rcWork.top; 348 349 return 0; 350 } 351 352 void 353 WIN_GetDisplayModes(_THIS, SDL_VideoDisplay * display) 354 { 355 SDL_DisplayData *data = (SDL_DisplayData *) display->driverdata; 356 DWORD i; 357 SDL_DisplayMode mode; 358 359 for (i = 0;; ++i) { 360 if (!WIN_GetDisplayMode(_this, data->DeviceName, i, &mode)) { 361 break; 362 } 363 if (SDL_ISPIXELFORMAT_INDEXED(mode.format)) { 364 /* We don't support palettized modes now */ 365 SDL_free(mode.driverdata); 366 continue; 367 } 368 if (mode.format != SDL_PIXELFORMAT_UNKNOWN) { 369 if (!SDL_AddDisplayMode(display, &mode)) { 370 SDL_free(mode.driverdata); 371 } 372 } else { 373 SDL_free(mode.driverdata); 374 } 375 } 376 } 377 378 int 379 WIN_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode) 380 { 381 SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata; 382 SDL_DisplayModeData *data = (SDL_DisplayModeData *) mode->driverdata; 383 LONG status; 384 385 if (mode->driverdata == display->desktop_mode.driverdata) { 386 status = ChangeDisplaySettingsEx(displaydata->DeviceName, NULL, NULL, CDS_FULLSCREEN, NULL); 387 } else { 388 status = ChangeDisplaySettingsEx(displaydata->DeviceName, &data->DeviceMode, NULL, CDS_FULLSCREEN, NULL); 389 } 390 if (status != DISP_CHANGE_SUCCESSFUL) { 391 const char *reason = "Unknown reason"; 392 switch (status) { 393 case DISP_CHANGE_BADFLAGS: 394 reason = "DISP_CHANGE_BADFLAGS"; 395 break; 396 case DISP_CHANGE_BADMODE: 397 reason = "DISP_CHANGE_BADMODE"; 398 break; 399 case DISP_CHANGE_BADPARAM: 400 reason = "DISP_CHANGE_BADPARAM"; 401 break; 402 case DISP_CHANGE_FAILED: 403 reason = "DISP_CHANGE_FAILED"; 404 break; 405 } 406 return SDL_SetError("ChangeDisplaySettingsEx() failed: %s", reason); 407 } 408 EnumDisplaySettings(displaydata->DeviceName, ENUM_CURRENT_SETTINGS, &data->DeviceMode); 409 WIN_UpdateDisplayMode(_this, displaydata->DeviceName, ENUM_CURRENT_SETTINGS, mode); 410 return 0; 411 } 412 413 void 414 WIN_RefreshDisplays(_THIS) 415 { 416 int i; 417 418 // Mark all displays as potentially invalid to detect 419 // entries that have actually been removed 420 for (i = 0; i < _this->num_displays; ++i) { 421 SDL_DisplayData *driverdata = (SDL_DisplayData *)_this->displays[i].driverdata; 422 driverdata->IsValid = SDL_FALSE; 423 } 424 425 // Enumerate displays to add any new ones and mark still 426 // connected entries as valid 427 WIN_AddDisplays(_this, SDL_TRUE); 428 429 // Delete any entries still marked as invalid, iterate 430 // in reverse as each delete takes effect immediately 431 for (i = _this->num_displays - 1; i >= 0; --i) { 432 SDL_DisplayData *driverdata = (SDL_DisplayData *)_this->displays[i].driverdata; 433 if (driverdata->IsValid == SDL_FALSE) { 434 SDL_DelVideoDisplay(i); 435 } 436 } 437 } 438 439 void 440 WIN_QuitModes(_THIS) 441 { 442 /* All fullscreen windows should have restored modes by now */ 443 } 444 445 #endif /* SDL_VIDEO_DRIVER_WINDOWS */ 446 447 /* vi: set ts=4 sw=4 expandtab: */