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