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