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 #include "../../../include/SDL_assert.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,LPCTSTR deviceName,DWORD index,SDL_DisplayMode * mode)36 WIN_UpdateDisplayMode(_THIS, LPCTSTR 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_bool
WIN_GetDisplayMode(_THIS,LPCTSTR deviceName,DWORD index,SDL_DisplayMode * mode)113 WIN_GetDisplayMode(_THIS, LPCTSTR deviceName, DWORD index, SDL_DisplayMode * mode)
114 {
115     SDL_DisplayModeData *data;
116     DEVMODE devmode;
117 
118     devmode.dmSize = sizeof(devmode);
119     devmode.dmDriverExtra = 0;
120     if (!EnumDisplaySettings(deviceName, index, &devmode)) {
121         return SDL_FALSE;
122     }
123 
124     data = (SDL_DisplayModeData *) SDL_malloc(sizeof(*data));
125     if (!data) {
126         return SDL_FALSE;
127     }
128 
129     mode->driverdata = data;
130     data->DeviceMode = devmode;
131 
132     mode->format = SDL_PIXELFORMAT_UNKNOWN;
133     mode->w = data->DeviceMode.dmPelsWidth;
134     mode->h = data->DeviceMode.dmPelsHeight;
135     mode->refresh_rate = data->DeviceMode.dmDisplayFrequency;
136 
137     /* Fill in the mode information */
138     WIN_UpdateDisplayMode(_this, deviceName, index, mode);
139     return SDL_TRUE;
140 }
141 
142 static SDL_bool
WIN_AddDisplay(_THIS,HMONITOR hMonitor,const MONITORINFOEX * info)143 WIN_AddDisplay(_THIS, HMONITOR hMonitor, const MONITORINFOEX *info)
144 {
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     displaydata = (SDL_DisplayData *) SDL_malloc(sizeof(*displaydata));
159     if (!displaydata) {
160         return SDL_FALSE;
161     }
162     SDL_memcpy(displaydata->DeviceName, info->szDevice,
163                sizeof(displaydata->DeviceName));
164     displaydata->MonitorHandle = hMonitor;
165 
166     SDL_zero(display);
167     device.cb = sizeof(device);
168     if (EnumDisplayDevices(info->szDevice, 0, &device, 0)) {
169         display.name = WIN_StringToUTF8(device.DeviceString);
170     }
171     display.desktop_mode = mode;
172     display.current_mode = mode;
173     display.driverdata = displaydata;
174     SDL_AddVideoDisplay(&display);
175     SDL_free(display.name);
176     return SDL_TRUE;
177 }
178 
179 typedef struct _WIN_AddDisplaysData {
180     SDL_VideoDevice *video_device;
181     SDL_bool want_primary;
182 } WIN_AddDisplaysData;
183 
184 static BOOL CALLBACK
WIN_AddDisplaysCallback(HMONITOR hMonitor,HDC hdcMonitor,LPRECT lprcMonitor,LPARAM dwData)185 WIN_AddDisplaysCallback(HMONITOR hMonitor,
186                         HDC      hdcMonitor,
187                         LPRECT   lprcMonitor,
188                         LPARAM   dwData)
189 {
190     WIN_AddDisplaysData *data = (WIN_AddDisplaysData*)dwData;
191     MONITORINFOEX info;
192 
193     SDL_zero(info);
194     info.cbSize = sizeof(info);
195 
196     if (GetMonitorInfo(hMonitor, (LPMONITORINFO)&info) != 0) {
197         const SDL_bool is_primary = ((info.dwFlags & MONITORINFOF_PRIMARY) == MONITORINFOF_PRIMARY);
198 
199         if (is_primary == data->want_primary) {
200             WIN_AddDisplay(data->video_device, hMonitor, &info);
201         }
202     }
203 
204     // continue enumeration
205     return TRUE;
206 }
207 
208 static void
WIN_AddDisplays(_THIS)209 WIN_AddDisplays(_THIS)
210 {
211     WIN_AddDisplaysData callback_data;
212     callback_data.video_device = _this;
213 
214     callback_data.want_primary = SDL_TRUE;
215     EnumDisplayMonitors(NULL, NULL, WIN_AddDisplaysCallback, (LPARAM)&callback_data);
216 
217     callback_data.want_primary = SDL_FALSE;
218     EnumDisplayMonitors(NULL, NULL, WIN_AddDisplaysCallback, (LPARAM)&callback_data);
219 }
220 
221 int
WIN_InitModes(_THIS)222 WIN_InitModes(_THIS)
223 {
224     WIN_AddDisplays(_this);
225 
226     if (_this->num_displays == 0) {
227         return SDL_SetError("No displays available");
228     }
229     return 0;
230 }
231 
232 int
WIN_GetDisplayBounds(_THIS,SDL_VideoDisplay * display,SDL_Rect * rect)233 WIN_GetDisplayBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect)
234 {
235     const SDL_DisplayData *data = (const SDL_DisplayData *)display->driverdata;
236     MONITORINFO minfo;
237     BOOL rc;
238 
239     SDL_zero(minfo);
240     minfo.cbSize = sizeof(MONITORINFO);
241     rc = GetMonitorInfo(data->MonitorHandle, &minfo);
242 
243     if (!rc) {
244         return SDL_SetError("Couldn't find monitor data");
245     }
246 
247     rect->x = minfo.rcMonitor.left;
248     rect->y = minfo.rcMonitor.top;
249     rect->w = minfo.rcMonitor.right - minfo.rcMonitor.left;
250     rect->h = minfo.rcMonitor.bottom - minfo.rcMonitor.top;
251 
252     return 0;
253 }
254 
255 int
WIN_GetDisplayDPI(_THIS,SDL_VideoDisplay * display,float * ddpi_out,float * hdpi_out,float * vdpi_out)256 WIN_GetDisplayDPI(_THIS, SDL_VideoDisplay * display, float * ddpi_out, float * hdpi_out, float * vdpi_out)
257 {
258     const SDL_DisplayData *displaydata = (SDL_DisplayData *)display->driverdata;
259     const SDL_VideoData *videodata = (SDL_VideoData *)display->device->driverdata;
260     float hdpi = 0, vdpi = 0, ddpi = 0;
261 
262     if (videodata->GetDpiForMonitor) {
263         UINT hdpi_uint, vdpi_uint;
264         // Windows 8.1+ codepath
265         if (videodata->GetDpiForMonitor(displaydata->MonitorHandle, MDT_EFFECTIVE_DPI, &hdpi_uint, &vdpi_uint) == S_OK) {
266             // GetDpiForMonitor docs promise to return the same hdpi/vdpi
267             hdpi = (float)hdpi_uint;
268             vdpi = (float)hdpi_uint;
269             ddpi = (float)hdpi_uint;
270         } else {
271             return SDL_SetError("GetDpiForMonitor failed");
272         }
273     } else {
274         // Window 8.0 and below: same DPI for all monitors.
275         HDC hdc;
276         int hdpi_int, vdpi_int, hpoints, vpoints, hpix, vpix;
277         float hinches, vinches;
278 
279         hdc = GetDC(NULL);
280         if (hdc == NULL) {
281             return SDL_SetError("GetDC failed");
282         }
283         hdpi_int = GetDeviceCaps(hdc, LOGPIXELSX);
284         vdpi_int = GetDeviceCaps(hdc, LOGPIXELSY);
285         ReleaseDC(NULL, hdc);
286 
287         hpoints = GetSystemMetrics(SM_CXVIRTUALSCREEN);
288         vpoints = GetSystemMetrics(SM_CYVIRTUALSCREEN);
289 
290         hpix = MulDiv(hpoints, hdpi_int, 96);
291         vpix = MulDiv(vpoints, vdpi_int, 96);
292 
293         hinches = (float)hpoints / 96.0f;
294         vinches = (float)vpoints / 96.0f;
295 
296         hdpi = (float)hdpi_int;
297         vdpi = (float)vdpi_int;
298         ddpi = SDL_ComputeDiagonalDPI(hpix, vpix, hinches, vinches);
299     }
300 
301     if (ddpi_out) {
302         *ddpi_out = ddpi;
303     }
304     if (hdpi_out) {
305         *hdpi_out = hdpi;
306     }
307     if (vdpi_out) {
308         *vdpi_out = vdpi;
309     }
310 
311     return ddpi != 0.0f ? 0 : SDL_SetError("Couldn't get DPI");
312 }
313 
314 int
WIN_GetDisplayUsableBounds(_THIS,SDL_VideoDisplay * display,SDL_Rect * rect)315 WIN_GetDisplayUsableBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect)
316 {
317     const SDL_DisplayData *data = (const SDL_DisplayData *)display->driverdata;
318     MONITORINFO minfo;
319     BOOL rc;
320 
321     SDL_zero(minfo);
322     minfo.cbSize = sizeof(MONITORINFO);
323     rc = GetMonitorInfo(data->MonitorHandle, &minfo);
324 
325     if (!rc) {
326         return SDL_SetError("Couldn't find monitor data");
327     }
328 
329     rect->x = minfo.rcWork.left;
330     rect->y = minfo.rcWork.top;
331     rect->w = minfo.rcWork.right - minfo.rcWork.left;
332     rect->h = minfo.rcWork.bottom - minfo.rcWork.top;
333 
334     return 0;
335 }
336 
337 void
WIN_GetDisplayModes(_THIS,SDL_VideoDisplay * display)338 WIN_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
339 {
340     SDL_DisplayData *data = (SDL_DisplayData *) display->driverdata;
341     DWORD i;
342     SDL_DisplayMode mode;
343 
344     for (i = 0;; ++i) {
345         if (!WIN_GetDisplayMode(_this, data->DeviceName, i, &mode)) {
346             break;
347         }
348         if (SDL_ISPIXELFORMAT_INDEXED(mode.format)) {
349             /* We don't support palettized modes now */
350             SDL_free(mode.driverdata);
351             continue;
352         }
353         if (mode.format != SDL_PIXELFORMAT_UNKNOWN) {
354             if (!SDL_AddDisplayMode(display, &mode)) {
355                 SDL_free(mode.driverdata);
356             }
357         } else {
358             SDL_free(mode.driverdata);
359         }
360     }
361 }
362 
363 int
WIN_SetDisplayMode(_THIS,SDL_VideoDisplay * display,SDL_DisplayMode * mode)364 WIN_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode)
365 {
366     SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata;
367     SDL_DisplayModeData *data = (SDL_DisplayModeData *) mode->driverdata;
368     LONG status;
369 
370     if (mode->driverdata == display->desktop_mode.driverdata) {
371         status = ChangeDisplaySettingsEx(displaydata->DeviceName, NULL, NULL, CDS_FULLSCREEN, NULL);
372     } else {
373         status = ChangeDisplaySettingsEx(displaydata->DeviceName, &data->DeviceMode, NULL, CDS_FULLSCREEN, NULL);
374     }
375     if (status != DISP_CHANGE_SUCCESSFUL) {
376         const char *reason = "Unknown reason";
377         switch (status) {
378         case DISP_CHANGE_BADFLAGS:
379             reason = "DISP_CHANGE_BADFLAGS";
380             break;
381         case DISP_CHANGE_BADMODE:
382             reason = "DISP_CHANGE_BADMODE";
383             break;
384         case DISP_CHANGE_BADPARAM:
385             reason = "DISP_CHANGE_BADPARAM";
386             break;
387         case DISP_CHANGE_FAILED:
388             reason = "DISP_CHANGE_FAILED";
389             break;
390         }
391         return SDL_SetError("ChangeDisplaySettingsEx() failed: %s", reason);
392     }
393     EnumDisplaySettings(displaydata->DeviceName, ENUM_CURRENT_SETTINGS, &data->DeviceMode);
394     WIN_UpdateDisplayMode(_this, displaydata->DeviceName, ENUM_CURRENT_SETTINGS, mode);
395     return 0;
396 }
397 
398 void
WIN_QuitModes(_THIS)399 WIN_QuitModes(_THIS)
400 {
401     /* All fullscreen windows should have restored modes by now */
402 }
403 
404 #endif /* SDL_VIDEO_DRIVER_WINDOWS */
405 
406 /* vi: set ts=4 sw=4 expandtab: */
407