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