Change std:vector to eina_array
[platform/upstream/SDL.git] / src / video / windows / SDL_windowsmodes.c
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: */