1 /* RetroArch - A frontend for libretro.
2 * Copyright (C) 2010-2014 - Hans-Kristian Arntzen
3 * Copyright (C) 2011-2017 - Daniel De Matteis
4 * Copyright (C) 2016-2019 - Brad Parker
5 *
6 * RetroArch is free software: you can redistribute it and/or modify it under the terms
7 * of the GNU General Public License as published by the Free Software Found-
8 * ation, either version 3 of the License, or (at your option) any later version.
9 *
10 * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
11 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12 * PURPOSE. See the GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along with RetroArch.
15 * If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 /* VC6 needs objbase included before initguid, but nothing else does */
19 #include <objbase.h>
20 #include <initguid.h>
21 #include <windows.h>
22 #include <ntverp.h>
23
24 #ifndef COBJMACROS
25 #define COBJMACROS
26 #define COBJMACROS_DEFINED
27 #endif
28 /* We really just want shobjidl.h, but there's no way to detect its existance at compile time (especially with mingw). however shlobj happens to include it for us when it's supported, which is easier. */
29 #include <shlobj.h>
30 #ifdef COBJMACROS_DEFINED
31 #undef COBJMACROS
32 #endif
33
34 #ifndef _WIN32_WINNT
35 #define _WIN32_WINNT 0x0601 /* Windows 7 */
36 #endif
37
38 #include "../video_display_server.h"
39 #include "../common/win32_common.h"
40 #include "../../retroarch.h"
41 #include "../../verbosity.h"
42
43 #ifdef __ITaskbarList3_INTERFACE_DEFINED__
44 #define HAS_TASKBAR_EXT
45
46 /* MSVC really doesn't want CINTERFACE to be used
47 * with shobjidl for some reason, but since we use C++ mode,
48 * we need a workaround... so use the names of the
49 * COBJMACROS functions instead. */
50 #if defined(__cplusplus) && !defined(CINTERFACE)
51 #define ITaskbarList3_HrInit(x) (x)->HrInit()
52 #define ITaskbarList3_Release(x) (x)->Release()
53 #define ITaskbarList3_SetProgressState(a, b, c) (a)->SetProgressState(b, c)
54 #define ITaskbarList3_SetProgressValue(a, b, c, d) (a)->SetProgressValue(b, c, d)
55 #endif
56
57 #endif
58
59 typedef struct
60 {
61 bool decorations;
62 int progress;
63 int crt_center;
64 unsigned opacity;
65 unsigned orig_width;
66 unsigned orig_height;
67 unsigned orig_refresh;
68 #ifdef HAS_TASKBAR_EXT
69 ITaskbarList3 *taskbar_list;
70 #endif
71 } dispserv_win32_t;
72
73 /*
74 NOTE: When an application displays a window, its taskbar button is created
75 by the system. When the button is in place, the taskbar sends a
76 TaskbarButtonCreated message to the window. Its value is computed by
77 calling RegisterWindowMessage(L("TaskbarButtonCreated")). That message must
78 be received by your application before it calls any ITaskbarList3 method.
79 */
80
win32_display_server_init(void)81 static void *win32_display_server_init(void)
82 {
83 dispserv_win32_t *dispserv = (dispserv_win32_t*)calloc(1, sizeof(*dispserv));
84
85 if (!dispserv)
86 return NULL;
87
88 #ifdef HAS_TASKBAR_EXT
89 #ifdef __cplusplus
90 /* When compiling in C++ mode, GUIDs are references instead of pointers */
91 if (FAILED(CoCreateInstance(CLSID_TaskbarList, NULL,
92 CLSCTX_INPROC_SERVER, IID_ITaskbarList3,
93 (void**)&dispserv->taskbar_list)))
94 #else
95 /* Mingw GUIDs are pointers instead of references since we're in C mode */
96 if (FAILED(CoCreateInstance(&CLSID_TaskbarList, NULL,
97 CLSCTX_INPROC_SERVER, &IID_ITaskbarList3,
98 (void**)&dispserv->taskbar_list)))
99 #endif
100 {
101 dispserv->taskbar_list = NULL;
102 RARCH_ERR("[dispserv]: CoCreateInstance of ITaskbarList3 failed.\n");
103 }
104 else
105 {
106 if (FAILED(ITaskbarList3_HrInit(dispserv->taskbar_list)))
107 RARCH_ERR("[dispserv]: HrInit of ITaskbarList3 failed.\n");
108 }
109 #endif
110
111 return dispserv;
112 }
113
win32_display_server_destroy(void * data)114 static void win32_display_server_destroy(void *data)
115 {
116 dispserv_win32_t *dispserv = (dispserv_win32_t*)data;
117
118 if (dispserv->orig_width > 0 && dispserv->orig_height > 0)
119 video_display_server_set_resolution(
120 dispserv->orig_width,
121 dispserv->orig_height,
122 dispserv->orig_refresh,
123 (float)dispserv->orig_refresh,
124 dispserv->crt_center, 0, 0, 0);
125
126 #ifdef HAS_TASKBAR_EXT
127 if (dispserv->taskbar_list)
128 {
129 ITaskbarList3_Release(dispserv->taskbar_list);
130 dispserv->taskbar_list = NULL;
131 }
132 #endif
133
134 if (dispserv)
135 free(dispserv);
136 }
137
win32_display_server_set_window_opacity(void * data,unsigned opacity)138 static bool win32_display_server_set_window_opacity(
139 void *data, unsigned opacity)
140 {
141 HWND hwnd = win32_get_window();
142 dispserv_win32_t *serv = (dispserv_win32_t*)data;
143
144 if (serv)
145 serv->opacity = opacity;
146
147 #if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0500
148 /* Set window transparency on Windows 2000 and above */
149 if (opacity < 100)
150 {
151 SetWindowLongPtr(hwnd,
152 GWL_EXSTYLE,
153 GetWindowLongPtr(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED);
154 return SetLayeredWindowAttributes(hwnd, 0, (255 * opacity) / 100,
155 LWA_ALPHA);
156 }
157
158 SetWindowLongPtr(hwnd,
159 GWL_EXSTYLE,
160 GetWindowLongPtr(hwnd, GWL_EXSTYLE) & ~WS_EX_LAYERED);
161 return true;
162 #else
163 return false;
164 #endif
165 }
166
win32_display_server_set_window_progress(void * data,int progress,bool finished)167 static bool win32_display_server_set_window_progress(
168 void *data, int progress, bool finished)
169 {
170 HWND hwnd = win32_get_window();
171 dispserv_win32_t *serv = (dispserv_win32_t*)data;
172
173 if (serv)
174 serv->progress = progress;
175
176 #ifdef HAS_TASKBAR_EXT
177 if (!serv->taskbar_list || !win32_taskbar_is_created())
178 return false;
179
180 if (progress == -1)
181 {
182 if (ITaskbarList3_SetProgressState(
183 serv->taskbar_list, hwnd, TBPF_INDETERMINATE) != S_OK)
184 return false;
185 }
186 else if (finished)
187 {
188 if (ITaskbarList3_SetProgressState(
189 serv->taskbar_list, hwnd, TBPF_NOPROGRESS) != S_OK)
190 return false;
191 }
192 else if (progress >= 0)
193 {
194 if (ITaskbarList3_SetProgressState(
195 serv->taskbar_list, hwnd, TBPF_NORMAL) != S_OK)
196 return false;
197
198 if (ITaskbarList3_SetProgressValue(
199 serv->taskbar_list, hwnd, progress, 100) != S_OK)
200 return false;
201 }
202 #endif
203
204 return true;
205 }
206
win32_display_server_set_window_decorations(void * data,bool on)207 static bool win32_display_server_set_window_decorations(void *data, bool on)
208 {
209 dispserv_win32_t *serv = (dispserv_win32_t*)data;
210
211 if (serv)
212 serv->decorations = on;
213
214 /* menu_setting performs a reinit instead to properly
215 * apply decoration changes */
216
217 return true;
218 }
219
win32_display_server_set_resolution(void * data,unsigned width,unsigned height,int int_hz,float hz,int center,int monitor_index,int xoffset,int padjust)220 static bool win32_display_server_set_resolution(void *data,
221 unsigned width, unsigned height, int int_hz, float hz, int center, int monitor_index, int xoffset, int padjust)
222 {
223 DEVMODE dm = {0};
224 LONG res = 0;
225 unsigned i = 0;
226 unsigned curr_bpp = 0;
227 #if _WIN32_WINNT >= 0x0500
228 unsigned curr_orientation = 0;
229 #endif
230 dispserv_win32_t *serv = (dispserv_win32_t*)data;
231
232 if (!serv)
233 return false;
234
235 win32_get_video_output(&dm, -1, sizeof(dm));
236
237 if (serv->orig_width == 0)
238 serv->orig_width = GetSystemMetrics(SM_CXSCREEN);
239 if (serv->orig_height == 0)
240 serv->orig_height = GetSystemMetrics(SM_CYSCREEN);
241 serv->orig_refresh = dm.dmDisplayFrequency;
242
243 /* Used to stop super resolution bug */
244 if (width == dm.dmPelsWidth)
245 width = 0;
246 if (width == 0)
247 width = dm.dmPelsWidth;
248 if (height == 0)
249 height = dm.dmPelsHeight;
250 if (curr_bpp == 0)
251 curr_bpp = dm.dmBitsPerPel;
252 if (int_hz == 0)
253 int_hz = dm.dmDisplayFrequency;
254 #if _WIN32_WINNT >= 0x0500
255 if (curr_orientation == 0)
256 curr_orientation = dm.dmDisplayOrientation;
257 #endif
258
259 for (i = 0; win32_get_video_output(&dm, i, sizeof(dm)); i++)
260 {
261 if (dm.dmPelsWidth != width)
262 continue;
263 if (dm.dmPelsHeight != height)
264 continue;
265 if (dm.dmBitsPerPel != curr_bpp)
266 continue;
267 if (dm.dmDisplayFrequency != int_hz)
268 continue;
269 #if _WIN32_WINNT >= 0x0500
270 if (dm.dmDisplayOrientation != curr_orientation)
271 continue;
272 if (dm.dmDisplayFixedOutput != DMDFO_DEFAULT)
273 continue;
274 #endif
275
276 dm.dmFields |= DM_PELSWIDTH | DM_PELSHEIGHT
277 | DM_BITSPERPEL | DM_DISPLAYFREQUENCY;
278 #if _WIN32_WINNT >= 0x0500
279 dm.dmFields |= DM_DISPLAYORIENTATION;
280 #endif
281
282 res = win32_change_display_settings(NULL, &dm, CDS_TEST);
283
284 switch (res)
285 {
286 case DISP_CHANGE_SUCCESSFUL:
287 res = win32_change_display_settings(NULL, &dm, 0);
288 switch (res)
289 {
290 case DISP_CHANGE_SUCCESSFUL:
291 return true;
292 case DISP_CHANGE_NOTUPDATED:
293 return true;
294 default:
295 break;
296 }
297 break;
298 case DISP_CHANGE_RESTART:
299 break;
300 default:
301 break;
302 }
303 }
304
305 return true;
306 }
307
win32_display_server_get_resolution_list(void * data,unsigned * len)308 static void *win32_display_server_get_resolution_list(
309 void *data, unsigned *len)
310 {
311 DEVMODE dm = {0};
312 unsigned i, j, count = 0;
313 unsigned curr_width = 0;
314 unsigned curr_height = 0;
315 unsigned curr_bpp = 0;
316 unsigned curr_refreshrate = 0;
317 #if _WIN32_WINNT >= 0x0500
318 unsigned curr_orientation = 0;
319 #endif
320 struct video_display_config *conf = NULL;
321
322 if (win32_get_video_output(&dm, -1, sizeof(dm)))
323 {
324 curr_width = dm.dmPelsWidth;
325 curr_height = dm.dmPelsHeight;
326 curr_bpp = dm.dmBitsPerPel;
327 curr_refreshrate = dm.dmDisplayFrequency;
328 #if _WIN32_WINNT >= 0x0500
329 curr_orientation = dm.dmDisplayOrientation;
330 #endif
331 }
332
333 for (i = 0; win32_get_video_output(&dm, i, sizeof(dm)); i++)
334 {
335 if (dm.dmBitsPerPel != curr_bpp)
336 continue;
337 #if _WIN32_WINNT >= 0x0500
338 if (dm.dmDisplayOrientation != curr_orientation)
339 continue;
340 if (dm.dmDisplayFixedOutput != DMDFO_DEFAULT)
341 continue;
342 #endif
343
344 count++;
345 }
346
347 *len = count;
348 conf = (struct video_display_config*)
349 calloc(*len, sizeof(struct video_display_config));
350
351 if (!conf)
352 return NULL;
353
354 for (i = 0, j = 0; win32_get_video_output(&dm, i, sizeof(dm)); i++)
355 {
356 if (dm.dmBitsPerPel != curr_bpp)
357 continue;
358 #if _WIN32_WINNT >= 0x0500
359 if (dm.dmDisplayOrientation != curr_orientation)
360 continue;
361 if (dm.dmDisplayFixedOutput != DMDFO_DEFAULT)
362 continue;
363 #endif
364
365 conf[j].width = dm.dmPelsWidth;
366 conf[j].height = dm.dmPelsHeight;
367 conf[j].bpp = dm.dmBitsPerPel;
368 conf[j].refreshrate = dm.dmDisplayFrequency;
369 conf[j].idx = j;
370 conf[j].current = false;
371
372 if ( (conf[j].width == curr_width)
373 && (conf[j].height == curr_height)
374 && (conf[j].bpp == curr_bpp)
375 && (conf[j].refreshrate == curr_refreshrate)
376 )
377 conf[j].current = true;
378
379 j++;
380 }
381
382 return conf;
383 }
384
385 #if _WIN32_WINNT >= 0x0500
win32_display_server_get_screen_orientation(void * data)386 enum rotation win32_display_server_get_screen_orientation(void *data)
387 {
388 DEVMODE dm = {0};
389 enum rotation rotation;
390
391 win32_get_video_output(&dm, -1, sizeof(dm));
392
393 switch (dm.dmDisplayOrientation)
394 {
395 case DMDO_DEFAULT:
396 default:
397 rotation = ORIENTATION_NORMAL;
398 break;
399 case DMDO_90:
400 rotation = ORIENTATION_FLIPPED_ROTATED;
401 break;
402 case DMDO_180:
403 rotation = ORIENTATION_FLIPPED;
404 break;
405 case DMDO_270:
406 rotation = ORIENTATION_VERTICAL;
407 break;
408 }
409
410 return rotation;
411 }
412
win32_display_server_set_screen_orientation(void * data,enum rotation rotation)413 void win32_display_server_set_screen_orientation(void *data,
414 enum rotation rotation)
415 {
416 DEVMODE dm = {0};
417
418 win32_get_video_output(&dm, -1, sizeof(dm));
419
420 switch (rotation)
421 {
422 case ORIENTATION_NORMAL:
423 default:
424 {
425 int width = dm.dmPelsWidth;
426
427 if (( dm.dmDisplayOrientation == DMDO_90
428 || dm.dmDisplayOrientation == DMDO_270)
429 && width != dm.dmPelsHeight)
430 {
431 /* device is changing orientations, swap the aspect */
432 dm.dmPelsWidth = dm.dmPelsHeight;
433 dm.dmPelsHeight = width;
434 }
435
436 dm.dmDisplayOrientation = DMDO_DEFAULT;
437 break;
438 }
439 case ORIENTATION_VERTICAL:
440 {
441 int width = dm.dmPelsWidth;
442
443 if (( dm.dmDisplayOrientation == DMDO_DEFAULT
444 || dm.dmDisplayOrientation == DMDO_180)
445 && width != dm.dmPelsHeight)
446 {
447 /* device is changing orientations, swap the aspect */
448 dm.dmPelsWidth = dm.dmPelsHeight;
449 dm.dmPelsHeight = width;
450 }
451
452 dm.dmDisplayOrientation = DMDO_270;
453 break;
454 }
455 case ORIENTATION_FLIPPED:
456 {
457 int width = dm.dmPelsWidth;
458
459 if (( dm.dmDisplayOrientation == DMDO_90
460 || dm.dmDisplayOrientation == DMDO_270)
461 && width != dm.dmPelsHeight)
462 {
463 /* device is changing orientations, swap the aspect */
464 dm.dmPelsWidth = dm.dmPelsHeight;
465 dm.dmPelsHeight = width;
466 }
467
468 dm.dmDisplayOrientation = DMDO_180;
469 break;
470 }
471 case ORIENTATION_FLIPPED_ROTATED:
472 {
473 int width = dm.dmPelsWidth;
474
475 if (( dm.dmDisplayOrientation == DMDO_DEFAULT
476 || dm.dmDisplayOrientation == DMDO_180)
477 && width != dm.dmPelsHeight)
478 {
479 /* device is changing orientations, swap the aspect */
480 dm.dmPelsWidth = dm.dmPelsHeight;
481 dm.dmPelsHeight = width;
482 }
483
484 dm.dmDisplayOrientation = DMDO_90;
485 break;
486 }
487 }
488
489 win32_change_display_settings(NULL, &dm, 0);
490 }
491 #endif
492
win32_display_server_get_flags(void * data)493 static uint32_t win32_display_server_get_flags(void *data)
494 {
495 uint32_t flags = 0;
496
497 BIT32_SET(flags, DISPSERV_CTX_CRT_SWITCHRES);
498
499 return flags;
500 }
501
502 const video_display_server_t dispserv_win32 = {
503 win32_display_server_init,
504 win32_display_server_destroy,
505 win32_display_server_set_window_opacity,
506 win32_display_server_set_window_progress,
507 win32_display_server_set_window_decorations,
508 win32_display_server_set_resolution,
509 win32_display_server_get_resolution_list,
510 NULL, /* get_output_options */
511 #if _WIN32_WINNT >= 0x0500
512 win32_display_server_set_screen_orientation,
513 win32_display_server_get_screen_orientation,
514 #else
515 NULL,
516 NULL,
517 #endif
518 win32_display_server_get_flags,
519 "win32"
520 };
521