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_WINRT
24 
25 /* WinRT SDL video driver implementation
26 
27    Initial work on this was done by David Ludwig (dludwig@pobox.com), and
28    was based off of SDL's "dummy" video driver.
29  */
30 
31 /* Windows includes */
32 #include <agile.h>
33 #include <windows.graphics.display.h>
34 #include <windows.system.display.h>
35 #include <dxgi.h>
36 #include <dxgi1_2.h>
37 using namespace Windows::ApplicationModel::Core;
38 using namespace Windows::Foundation;
39 using namespace Windows::Graphics::Display;
40 using namespace Windows::UI::Core;
41 using namespace Windows::UI::ViewManagement;
42 
43 
44 /* [re]declare Windows GUIDs locally, to limit the amount of external lib(s) SDL has to link to */
45 static const GUID IID_IDisplayRequest   = { 0xe5732044, 0xf49f, 0x4b60, { 0x8d, 0xd4, 0x5e, 0x7e, 0x3a, 0x63, 0x2a, 0xc0 } };
46 static const GUID IID_IDXGIFactory2     = { 0x50c83a1c, 0xe072, 0x4c48, { 0x87, 0xb0, 0x36, 0x30, 0xfa, 0x36, 0xa6, 0xd0 } };
47 
48 
49 /* SDL includes */
50 extern "C" {
51 #include "SDL_video.h"
52 #include "SDL_mouse.h"
53 #include "../SDL_sysvideo.h"
54 #include "../SDL_pixels_c.h"
55 #include "../../events/SDL_events_c.h"
56 #include "../../render/SDL_sysrender.h"
57 #include "SDL_syswm.h"
58 #include "SDL_winrtopengles.h"
59 #include "../../core/windows/SDL_windows.h"
60 }
61 
62 #include "../../core/winrt/SDL_winrtapp_direct3d.h"
63 #include "../../core/winrt/SDL_winrtapp_xaml.h"
64 #include "SDL_winrtvideo_cpp.h"
65 #include "SDL_winrtevents_c.h"
66 #include "SDL_winrtgamebar_cpp.h"
67 #include "SDL_winrtmouse_c.h"
68 #include "SDL_main.h"
69 #include "SDL_system.h"
70 
71 
72 /* Initialization/Query functions */
73 static int WINRT_VideoInit(_THIS);
74 static int WINRT_InitModes(_THIS);
75 static int WINRT_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode);
76 static void WINRT_VideoQuit(_THIS);
77 
78 
79 /* Window functions */
80 static int WINRT_CreateWindow(_THIS, SDL_Window * window);
81 static void WINRT_SetWindowSize(_THIS, SDL_Window * window);
82 static void WINRT_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDisplay * display, SDL_bool fullscreen);
83 static void WINRT_DestroyWindow(_THIS, SDL_Window * window);
84 static SDL_bool WINRT_GetWindowWMInfo(_THIS, SDL_Window * window, SDL_SysWMinfo * info);
85 
86 
87 /* Misc functions */
88 static ABI::Windows::System::Display::IDisplayRequest * WINRT_CreateDisplayRequest(_THIS);
89 extern void WINRT_SuspendScreenSaver(_THIS);
90 
91 
92 /* SDL-internal globals: */
93 SDL_Window * WINRT_GlobalSDLWindow = NULL;
94 
95 
96 /* WinRT driver bootstrap functions */
97 
98 static int
WINRT_Available(void)99 WINRT_Available(void)
100 {
101     return (1);
102 }
103 
104 static void
WINRT_DeleteDevice(SDL_VideoDevice * device)105 WINRT_DeleteDevice(SDL_VideoDevice * device)
106 {
107     if (device->driverdata) {
108         SDL_VideoData * video_data = (SDL_VideoData *)device->driverdata;
109         if (video_data->winrtEglWindow) {
110             video_data->winrtEglWindow->Release();
111         }
112         SDL_free(video_data);
113     }
114 
115     SDL_free(device);
116 }
117 
118 static SDL_VideoDevice *
WINRT_CreateDevice(int devindex)119 WINRT_CreateDevice(int devindex)
120 {
121     SDL_VideoDevice *device;
122     SDL_VideoData *data;
123 
124     /* Initialize all variables that we clean on shutdown */
125     device = (SDL_VideoDevice *) SDL_calloc(1, sizeof(SDL_VideoDevice));
126     if (!device) {
127         SDL_OutOfMemory();
128         return (0);
129     }
130 
131     data = (SDL_VideoData *) SDL_calloc(1, sizeof(SDL_VideoData));
132     if (!data) {
133         SDL_OutOfMemory();
134         SDL_free(device);
135         return (0);
136     }
137     device->driverdata = data;
138 
139     /* Set the function pointers */
140     device->VideoInit = WINRT_VideoInit;
141     device->VideoQuit = WINRT_VideoQuit;
142     device->CreateSDLWindow = WINRT_CreateWindow;
143     device->SetWindowSize = WINRT_SetWindowSize;
144     device->SetWindowFullscreen = WINRT_SetWindowFullscreen;
145     device->DestroyWindow = WINRT_DestroyWindow;
146     device->SetDisplayMode = WINRT_SetDisplayMode;
147     device->PumpEvents = WINRT_PumpEvents;
148     device->GetWindowWMInfo = WINRT_GetWindowWMInfo;
149     device->SuspendScreenSaver = WINRT_SuspendScreenSaver;
150 
151 #if NTDDI_VERSION >= NTDDI_WIN10
152     device->HasScreenKeyboardSupport = WINRT_HasScreenKeyboardSupport;
153     device->ShowScreenKeyboard = WINRT_ShowScreenKeyboard;
154     device->HideScreenKeyboard = WINRT_HideScreenKeyboard;
155     device->IsScreenKeyboardShown = WINRT_IsScreenKeyboardShown;
156 #endif
157 
158 #ifdef SDL_VIDEO_OPENGL_EGL
159     device->GL_LoadLibrary = WINRT_GLES_LoadLibrary;
160     device->GL_GetProcAddress = WINRT_GLES_GetProcAddress;
161     device->GL_UnloadLibrary = WINRT_GLES_UnloadLibrary;
162     device->GL_CreateContext = WINRT_GLES_CreateContext;
163     device->GL_MakeCurrent = WINRT_GLES_MakeCurrent;
164     device->GL_SetSwapInterval = WINRT_GLES_SetSwapInterval;
165     device->GL_GetSwapInterval = WINRT_GLES_GetSwapInterval;
166     device->GL_SwapWindow = WINRT_GLES_SwapWindow;
167     device->GL_DeleteContext = WINRT_GLES_DeleteContext;
168 #endif
169     device->free = WINRT_DeleteDevice;
170 
171     return device;
172 }
173 
174 #define WINRTVID_DRIVER_NAME "winrt"
175 VideoBootStrap WINRT_bootstrap = {
176     WINRTVID_DRIVER_NAME, "SDL WinRT video driver",
177     WINRT_Available, WINRT_CreateDevice
178 };
179 
180 int
WINRT_VideoInit(_THIS)181 WINRT_VideoInit(_THIS)
182 {
183     SDL_VideoData * driverdata = (SDL_VideoData *) _this->driverdata;
184     if (WINRT_InitModes(_this) < 0) {
185         return -1;
186     }
187     WINRT_InitMouse(_this);
188     WINRT_InitTouch(_this);
189     WINRT_InitGameBar(_this);
190     if (driverdata) {
191         /* Initialize screensaver-disabling support */
192         driverdata->displayRequest = WINRT_CreateDisplayRequest(_this);
193     }
194     return 0;
195 }
196 
197 extern "C"
198 Uint32 D3D11_DXGIFormatToSDLPixelFormat(DXGI_FORMAT dxgiFormat);
199 
200 static void
WINRT_DXGIModeToSDLDisplayMode(const DXGI_MODE_DESC * dxgiMode,SDL_DisplayMode * sdlMode)201 WINRT_DXGIModeToSDLDisplayMode(const DXGI_MODE_DESC * dxgiMode, SDL_DisplayMode * sdlMode)
202 {
203     SDL_zerop(sdlMode);
204     sdlMode->w = dxgiMode->Width;
205     sdlMode->h = dxgiMode->Height;
206     sdlMode->refresh_rate = dxgiMode->RefreshRate.Numerator / dxgiMode->RefreshRate.Denominator;
207     sdlMode->format = D3D11_DXGIFormatToSDLPixelFormat(dxgiMode->Format);
208 }
209 
210 static int
WINRT_AddDisplaysForOutput(_THIS,IDXGIAdapter1 * dxgiAdapter1,int outputIndex)211 WINRT_AddDisplaysForOutput (_THIS, IDXGIAdapter1 * dxgiAdapter1, int outputIndex)
212 {
213     HRESULT hr;
214     IDXGIOutput * dxgiOutput = NULL;
215     DXGI_OUTPUT_DESC dxgiOutputDesc;
216     SDL_VideoDisplay display;
217     char * displayName = NULL;
218     UINT numModes;
219     DXGI_MODE_DESC * dxgiModes = NULL;
220     int functionResult = -1;        /* -1 for failure, 0 for success */
221     DXGI_MODE_DESC modeToMatch, closestMatch;
222 
223     SDL_zero(display);
224 
225     hr = dxgiAdapter1->EnumOutputs(outputIndex, &dxgiOutput);
226     if (FAILED(hr)) {
227         if (hr != DXGI_ERROR_NOT_FOUND) {
228             WIN_SetErrorFromHRESULT(__FUNCTION__ ", IDXGIAdapter1::EnumOutputs failed", hr);
229         }
230         goto done;
231     }
232 
233     hr = dxgiOutput->GetDesc(&dxgiOutputDesc);
234     if (FAILED(hr)) {
235         WIN_SetErrorFromHRESULT(__FUNCTION__ ", IDXGIOutput::GetDesc failed", hr);
236         goto done;
237     }
238 
239     SDL_zero(modeToMatch);
240     modeToMatch.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
241     modeToMatch.Width = (dxgiOutputDesc.DesktopCoordinates.right - dxgiOutputDesc.DesktopCoordinates.left);
242     modeToMatch.Height = (dxgiOutputDesc.DesktopCoordinates.bottom - dxgiOutputDesc.DesktopCoordinates.top);
243     hr = dxgiOutput->FindClosestMatchingMode(&modeToMatch, &closestMatch, NULL);
244     if (hr == DXGI_ERROR_NOT_CURRENTLY_AVAILABLE) {
245         /* DXGI_ERROR_NOT_CURRENTLY_AVAILABLE gets returned by IDXGIOutput::FindClosestMatchingMode
246            when running under the Windows Simulator, which uses Remote Desktop (formerly known as Terminal
247            Services) under the hood.  According to the MSDN docs for the similar function,
248            IDXGIOutput::GetDisplayModeList, DXGI_ERROR_NOT_CURRENTLY_AVAILABLE is returned if and
249            when an app is run under a Terminal Services session, hence the assumption.
250 
251            In this case, just add an SDL display mode, with approximated values.
252         */
253         SDL_DisplayMode mode;
254         SDL_zero(mode);
255         display.name = "Windows Simulator / Terminal Services Display";
256         mode.w = (dxgiOutputDesc.DesktopCoordinates.right - dxgiOutputDesc.DesktopCoordinates.left);
257         mode.h = (dxgiOutputDesc.DesktopCoordinates.bottom - dxgiOutputDesc.DesktopCoordinates.top);
258         mode.format = DXGI_FORMAT_B8G8R8A8_UNORM;
259         mode.refresh_rate = 0;  /* Display mode is unknown, so just fill in zero, as specified by SDL's header files */
260         display.desktop_mode = mode;
261         display.current_mode = mode;
262         if ( ! SDL_AddDisplayMode(&display, &mode)) {
263             goto done;
264         }
265     } else if (FAILED(hr)) {
266         WIN_SetErrorFromHRESULT(__FUNCTION__ ", IDXGIOutput::FindClosestMatchingMode failed", hr);
267         goto done;
268     } else {
269         displayName = WIN_StringToUTF8(dxgiOutputDesc.DeviceName);
270         display.name = displayName;
271         WINRT_DXGIModeToSDLDisplayMode(&closestMatch, &display.desktop_mode);
272         display.current_mode = display.desktop_mode;
273 
274         hr = dxgiOutput->GetDisplayModeList(DXGI_FORMAT_B8G8R8A8_UNORM, 0, &numModes, NULL);
275         if (FAILED(hr)) {
276             if (hr == DXGI_ERROR_NOT_CURRENTLY_AVAILABLE) {
277                 // TODO, WinRT: make sure display mode(s) are added when using Terminal Services / Windows Simulator
278             }
279             WIN_SetErrorFromHRESULT(__FUNCTION__ ", IDXGIOutput::GetDisplayModeList [get mode list size] failed", hr);
280             goto done;
281         }
282 
283         dxgiModes = (DXGI_MODE_DESC *)SDL_calloc(numModes, sizeof(DXGI_MODE_DESC));
284         if ( ! dxgiModes) {
285             SDL_OutOfMemory();
286             goto done;
287         }
288 
289         hr = dxgiOutput->GetDisplayModeList(DXGI_FORMAT_B8G8R8A8_UNORM, 0, &numModes, dxgiModes);
290         if (FAILED(hr)) {
291             WIN_SetErrorFromHRESULT(__FUNCTION__ ", IDXGIOutput::GetDisplayModeList [get mode contents] failed", hr);
292             goto done;
293         }
294 
295         for (UINT i = 0; i < numModes; ++i) {
296             SDL_DisplayMode sdlMode;
297             WINRT_DXGIModeToSDLDisplayMode(&dxgiModes[i], &sdlMode);
298             SDL_AddDisplayMode(&display, &sdlMode);
299         }
300     }
301 
302     if (SDL_AddVideoDisplay(&display) < 0) {
303         goto done;
304     }
305 
306     functionResult = 0;     /* 0 for Success! */
307 done:
308     if (dxgiModes) {
309         SDL_free(dxgiModes);
310     }
311     if (dxgiOutput) {
312         dxgiOutput->Release();
313     }
314     if (displayName) {
315         SDL_free(displayName);
316     }
317     return functionResult;
318 }
319 
320 static int
WINRT_AddDisplaysForAdapter(_THIS,IDXGIFactory2 * dxgiFactory2,int adapterIndex)321 WINRT_AddDisplaysForAdapter (_THIS, IDXGIFactory2 * dxgiFactory2, int adapterIndex)
322 {
323     HRESULT hr;
324     IDXGIAdapter1 * dxgiAdapter1;
325 
326     hr = dxgiFactory2->EnumAdapters1(adapterIndex, &dxgiAdapter1);
327     if (FAILED(hr)) {
328         if (hr != DXGI_ERROR_NOT_FOUND) {
329             WIN_SetErrorFromHRESULT(__FUNCTION__ ", IDXGIFactory1::EnumAdapters1() failed", hr);
330         }
331         return -1;
332     }
333 
334     for (int outputIndex = 0; ; ++outputIndex) {
335         if (WINRT_AddDisplaysForOutput(_this, dxgiAdapter1, outputIndex) < 0) {
336             /* HACK: The Windows App Certification Kit 10.0 can fail, when
337                running the Store Apps' test, "Direct3D Feature Test".  The
338                certification kit's error is:
339 
340                "Application App was not running at the end of the test. It likely crashed or was terminated for having become unresponsive."
341 
342                This was caused by SDL/WinRT's DXGI failing to report any
343                outputs.  Attempts to get the 1st display-output from the
344                1st display-adapter can fail, with IDXGIAdapter::EnumOutputs
345                returning DXGI_ERROR_NOT_FOUND.  This could be a bug in Windows,
346                the Windows App Certification Kit, or possibly in SDL/WinRT's
347                display detection code.  Either way, try to detect when this
348                happens, and use a hackish means to create a reasonable-as-possible
349                'display mode'.  -- DavidL
350             */
351             if (adapterIndex == 0 && outputIndex == 0) {
352                 SDL_VideoDisplay display;
353                 SDL_DisplayMode mode;
354 #if SDL_WINRT_USE_APPLICATIONVIEW
355                 ApplicationView ^ appView = ApplicationView::GetForCurrentView();
356 #endif
357                 CoreWindow ^ coreWin = CoreWindow::GetForCurrentThread();
358                 SDL_zero(display);
359                 SDL_zero(mode);
360                 display.name = "DXGI Display-detection Workaround";
361 
362                 /* HACK: ApplicationView's VisibleBounds property, appeared, via testing, to
363                    give a better approximation of display-size, than did CoreWindow's
364                    Bounds property, insofar that ApplicationView::VisibleBounds seems like
365                    it will, at least some of the time, give the full display size (during the
366                    failing test), whereas CoreWindow might not.  -- DavidL
367                 */
368 
369 #if (NTDDI_VERSION >= NTDDI_WIN10) || (SDL_WINRT_USE_APPLICATIONVIEW && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP)
370                 mode.w = WINRT_DIPS_TO_PHYSICAL_PIXELS(appView->VisibleBounds.Width);
371                 mode.h = WINRT_DIPS_TO_PHYSICAL_PIXELS(appView->VisibleBounds.Height);
372 #else
373                 /* On platform(s) that do not support VisibleBounds, such as Windows 8.1,
374                    fall back to CoreWindow's Bounds property.
375                 */
376                 mode.w = WINRT_DIPS_TO_PHYSICAL_PIXELS(coreWin->Bounds.Width);
377                 mode.h = WINRT_DIPS_TO_PHYSICAL_PIXELS(coreWin->Bounds.Height);
378 #endif
379 
380                 mode.format = DXGI_FORMAT_B8G8R8A8_UNORM;
381                 mode.refresh_rate = 0;  /* Display mode is unknown, so just fill in zero, as specified by SDL's header files */
382                 display.desktop_mode = mode;
383                 display.current_mode = mode;
384                 if ((SDL_AddDisplayMode(&display, &mode) < 0) ||
385                     (SDL_AddVideoDisplay(&display) < 0))
386                 {
387                     return SDL_SetError("Failed to apply DXGI Display-detection workaround");
388                 }
389             }
390 
391             break;
392         }
393     }
394 
395     dxgiAdapter1->Release();
396     return 0;
397 }
398 
399 int
WINRT_InitModes(_THIS)400 WINRT_InitModes(_THIS)
401 {
402     /* HACK: Initialize a single display, for whatever screen the app's
403          CoreApplicationView is on.
404        TODO, WinRT: Try initializing multiple displays, one for each monitor.
405          Appropriate WinRT APIs for this seem elusive, though.  -- DavidL
406     */
407 
408     HRESULT hr;
409     IDXGIFactory2 * dxgiFactory2 = NULL;
410 
411     hr = CreateDXGIFactory1(IID_IDXGIFactory2, (void **)&dxgiFactory2);
412     if (FAILED(hr)) {
413         WIN_SetErrorFromHRESULT(__FUNCTION__ ", CreateDXGIFactory1() failed", hr);
414         return -1;
415     }
416 
417     for (int adapterIndex = 0; ; ++adapterIndex) {
418         if (WINRT_AddDisplaysForAdapter(_this, dxgiFactory2, adapterIndex) < 0) {
419             break;
420         }
421     }
422 
423     return 0;
424 }
425 
426 static int
WINRT_SetDisplayMode(_THIS,SDL_VideoDisplay * display,SDL_DisplayMode * mode)427 WINRT_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode)
428 {
429     return 0;
430 }
431 
432 void
WINRT_VideoQuit(_THIS)433 WINRT_VideoQuit(_THIS)
434 {
435     SDL_VideoData * driverdata = (SDL_VideoData *) _this->driverdata;
436     if (driverdata && driverdata->displayRequest) {
437         driverdata->displayRequest->Release();
438         driverdata->displayRequest = NULL;
439     }
440     WINRT_QuitGameBar(_this);
441     WINRT_QuitMouse(_this);
442 }
443 
444 static const Uint32 WINRT_DetectableFlags =
445     SDL_WINDOW_MAXIMIZED |
446     SDL_WINDOW_FULLSCREEN_DESKTOP |
447     SDL_WINDOW_SHOWN |
448     SDL_WINDOW_HIDDEN |
449     SDL_WINDOW_MOUSE_FOCUS;
450 
451 extern "C" Uint32
WINRT_DetectWindowFlags(SDL_Window * window)452 WINRT_DetectWindowFlags(SDL_Window * window)
453 {
454     Uint32 latestFlags = 0;
455     SDL_WindowData * data = (SDL_WindowData *) window->driverdata;
456     bool is_fullscreen = false;
457 
458 #if SDL_WINRT_USE_APPLICATIONVIEW
459     if (data->appView) {
460         is_fullscreen = data->appView->IsFullScreen;
461     }
462 #elif (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) || (NTDDI_VERSION == NTDDI_WIN8)
463     is_fullscreen = true;
464 #endif
465 
466     if (data->coreWindow.Get()) {
467         if (is_fullscreen) {
468             SDL_VideoDisplay * display = SDL_GetDisplayForWindow(window);
469             int w = WINRT_DIPS_TO_PHYSICAL_PIXELS(data->coreWindow->Bounds.Width);
470             int h = WINRT_DIPS_TO_PHYSICAL_PIXELS(data->coreWindow->Bounds.Height);
471 
472 #if (WINAPI_FAMILY != WINAPI_FAMILY_PHONE_APP) || (NTDDI_VERSION > NTDDI_WIN8)
473             // On all WinRT platforms, except for WinPhone 8.0, rotate the
474             // window size.  This is needed to properly calculate
475             // fullscreen vs. maximized.
476             const DisplayOrientations currentOrientation = WINRT_DISPLAY_PROPERTY(CurrentOrientation);
477             switch (currentOrientation) {
478 #if (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP)
479                 case DisplayOrientations::Landscape:
480                 case DisplayOrientations::LandscapeFlipped:
481 #else
482                 case DisplayOrientations::Portrait:
483                 case DisplayOrientations::PortraitFlipped:
484 #endif
485                 {
486                     int tmp = w;
487                     w = h;
488                     h = tmp;
489                 } break;
490             }
491 #endif
492 
493             if (display->desktop_mode.w != w || display->desktop_mode.h != h) {
494                 latestFlags |= SDL_WINDOW_MAXIMIZED;
495             } else {
496                 latestFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
497             }
498         }
499 
500         if (data->coreWindow->Visible) {
501             latestFlags |= SDL_WINDOW_SHOWN;
502         } else {
503             latestFlags |= SDL_WINDOW_HIDDEN;
504         }
505 
506 #if (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) && (NTDDI_VERSION < NTDDI_WINBLUE)
507         // data->coreWindow->PointerPosition is not supported on WinPhone 8.0
508         latestFlags |= SDL_WINDOW_MOUSE_FOCUS;
509 #else
510         if (data->coreWindow->Visible && data->coreWindow->Bounds.Contains(data->coreWindow->PointerPosition)) {
511             latestFlags |= SDL_WINDOW_MOUSE_FOCUS;
512         }
513 #endif
514     }
515 
516     return latestFlags;
517 }
518 
519 // TODO, WinRT: consider removing WINRT_UpdateWindowFlags, and just calling WINRT_DetectWindowFlags as-appropriate (with appropriate calls to SDL_SendWindowEvent)
520 void
WINRT_UpdateWindowFlags(SDL_Window * window,Uint32 mask)521 WINRT_UpdateWindowFlags(SDL_Window * window, Uint32 mask)
522 {
523     mask &= WINRT_DetectableFlags;
524     if (window) {
525         Uint32 apply = WINRT_DetectWindowFlags(window);
526         if ((apply & mask) & SDL_WINDOW_FULLSCREEN) {
527             window->last_fullscreen_flags = window->flags;  // seems necessary to programmatically un-fullscreen, via SDL APIs
528         }
529         window->flags = (window->flags & ~mask) | (apply & mask);
530     }
531 }
532 
533 static bool
534 WINRT_IsCoreWindowActive(CoreWindow ^ coreWindow)
535 {
536     /* WinRT does not appear to offer API(s) to determine window-activation state,
537        at least not that I am aware of in Win8 - Win10.  As such, SDL tracks this
538        itself, via window-activation events.
539 
540        If there *is* an API to track this, it should probably get used instead
541        of the following hack (that uses "SDLHelperWindowActivationState").
542          -- DavidL.
543     */
544     if (coreWindow->CustomProperties->HasKey("SDLHelperWindowActivationState")) {
545         CoreWindowActivationState activationState = \
546             safe_cast<CoreWindowActivationState>(coreWindow->CustomProperties->Lookup("SDLHelperWindowActivationState"));
547         return (activationState != CoreWindowActivationState::Deactivated);
548     }
549 
550     /* Assume that non-SDL tracked windows are active, although this should
551        probably be avoided, if possible.
552 
553        This might not even be possible, in normal SDL use, at least as of
554        this writing (Dec 22, 2015; via latest hg.libsdl.org/SDL clone)  -- DavidL
555     */
556     return true;
557 }
558 
559 int
WINRT_CreateWindow(_THIS,SDL_Window * window)560 WINRT_CreateWindow(_THIS, SDL_Window * window)
561 {
562     // Make sure that only one window gets created, at least until multimonitor
563     // support is added.
564     if (WINRT_GlobalSDLWindow != NULL) {
565         SDL_SetError("WinRT only supports one window");
566         return -1;
567     }
568 
569     SDL_WindowData *data = new SDL_WindowData;  /* use 'new' here as SDL_WindowData may use WinRT/C++ types */
570     if (!data) {
571         SDL_OutOfMemory();
572         return -1;
573     }
574     window->driverdata = data;
575     data->sdlWindow = window;
576 
577     /* To note, when XAML support is enabled, access to the CoreWindow will not
578        be possible, at least not via the SDL/XAML thread.  Attempts to access it
579        from there will throw exceptions.  As such, the SDL_WindowData's
580        'coreWindow' field will only be set (to a non-null value) if XAML isn't
581        enabled.
582     */
583     if (!WINRT_XAMLWasEnabled) {
584         data->coreWindow = CoreWindow::GetForCurrentThread();
585 #if SDL_WINRT_USE_APPLICATIONVIEW
586         data->appView = ApplicationView::GetForCurrentView();
587 #endif
588     }
589 
590     /* Make note of the requested window flags, before they start getting changed. */
591     const Uint32 requestedFlags = window->flags;
592 
593 #if SDL_VIDEO_OPENGL_EGL
594     /* Setup the EGL surface, but only if OpenGL ES 2 was requested. */
595     if (!(window->flags & SDL_WINDOW_OPENGL)) {
596         /* OpenGL ES 2 wasn't requested.  Don't set up an EGL surface. */
597         data->egl_surface = EGL_NO_SURFACE;
598     } else {
599         /* OpenGL ES 2 was reuqested.  Set up an EGL surface. */
600         SDL_VideoData * video_data = (SDL_VideoData *)_this->driverdata;
601 
602         /* Call SDL_EGL_ChooseConfig and eglCreateWindowSurface directly,
603          * rather than via SDL_EGL_CreateSurface, as older versions of
604          * ANGLE/WinRT may require that a C++ object, ComPtr<IUnknown>,
605          * be passed into eglCreateWindowSurface.
606          */
607         if (SDL_EGL_ChooseConfig(_this) != 0) {
608             char buf[512];
609             SDL_snprintf(buf, sizeof(buf), "SDL_EGL_ChooseConfig failed: %s", SDL_GetError());
610             return SDL_SetError("%s", buf);
611         }
612 
613         if (video_data->winrtEglWindow) {   /* ... is the 'old' version of ANGLE/WinRT being used? */
614             /* Attempt to create a window surface using older versions of
615              * ANGLE/WinRT:
616              */
617             Microsoft::WRL::ComPtr<IUnknown> cpp_winrtEglWindow = video_data->winrtEglWindow;
618             data->egl_surface = ((eglCreateWindowSurface_Old_Function)_this->egl_data->eglCreateWindowSurface)(
619                 _this->egl_data->egl_display,
620                 _this->egl_data->egl_config,
621                 cpp_winrtEglWindow, NULL);
622             if (data->egl_surface == NULL) {
623                 return SDL_EGL_SetError("unable to create EGL native-window surface", "eglCreateWindowSurface");
624             }
625         } else if (data->coreWindow.Get() != nullptr) {
626             /* Attempt to create a window surface using newer versions of
627              * ANGLE/WinRT:
628              */
629             IInspectable * coreWindowAsIInspectable = reinterpret_cast<IInspectable *>(data->coreWindow.Get());
630             data->egl_surface = _this->egl_data->eglCreateWindowSurface(
631                 _this->egl_data->egl_display,
632                 _this->egl_data->egl_config,
633                 coreWindowAsIInspectable,
634                 NULL);
635             if (data->egl_surface == NULL) {
636                 return SDL_EGL_SetError("unable to create EGL native-window surface", "eglCreateWindowSurface");
637             }
638         } else {
639             return SDL_SetError("No supported means to create an EGL window surface are available");
640         }
641     }
642 #endif
643 
644     /* Determine as many flags dynamically, as possible. */
645     window->flags =
646         SDL_WINDOW_BORDERLESS |
647         SDL_WINDOW_RESIZABLE;
648 
649 #if SDL_VIDEO_OPENGL_EGL
650     if (data->egl_surface) {
651         window->flags |= SDL_WINDOW_OPENGL;
652     }
653 #endif
654 
655     if (WINRT_XAMLWasEnabled) {
656         /* TODO, WinRT: set SDL_Window size, maybe position too, from XAML control */
657         window->x = 0;
658         window->y = 0;
659         window->flags |= SDL_WINDOW_SHOWN;
660         SDL_SetMouseFocus(NULL);        // TODO: detect this
661         SDL_SetKeyboardFocus(NULL);     // TODO: detect this
662     } else {
663         /* WinRT 8.x apps seem to live in an environment where the OS controls the
664            app's window size, with some apps being fullscreen, depending on
665            user choice of various things.  For now, just adapt the SDL_Window to
666            whatever Windows set-up as the native-window's geometry.
667         */
668         window->x = WINRT_DIPS_TO_PHYSICAL_PIXELS(data->coreWindow->Bounds.Left);
669         window->y = WINRT_DIPS_TO_PHYSICAL_PIXELS(data->coreWindow->Bounds.Top);
670 #if NTDDI_VERSION < NTDDI_WIN10
671         /* On WinRT 8.x / pre-Win10, just use the size we were given. */
672         window->w = WINRT_DIPS_TO_PHYSICAL_PIXELS(data->coreWindow->Bounds.Width);
673         window->h = WINRT_DIPS_TO_PHYSICAL_PIXELS(data->coreWindow->Bounds.Height);
674 #else
675         /* On Windows 10, we occasionally get control over window size.  For windowed
676            mode apps, try this.
677         */
678         bool didSetSize = false;
679         if (!(requestedFlags & SDL_WINDOW_FULLSCREEN)) {
680             const Windows::Foundation::Size size(WINRT_PHYSICAL_PIXELS_TO_DIPS(window->w),
681                                                  WINRT_PHYSICAL_PIXELS_TO_DIPS(window->h));
682             didSetSize = data->appView->TryResizeView(size);
683         }
684         if (!didSetSize) {
685             /* We either weren't able to set the window size, or a request for
686                fullscreen was made.  Get window-size info from the OS.
687             */
688             window->w = WINRT_DIPS_TO_PHYSICAL_PIXELS(data->coreWindow->Bounds.Width);
689             window->h = WINRT_DIPS_TO_PHYSICAL_PIXELS(data->coreWindow->Bounds.Height);
690         }
691 #endif
692 
693         WINRT_UpdateWindowFlags(
694             window,
695             0xffffffff      /* Update any window flag(s) that WINRT_UpdateWindow can handle */
696         );
697 
698         /* Try detecting if the window is active */
699         bool isWindowActive = WINRT_IsCoreWindowActive(data->coreWindow.Get());
700         if (isWindowActive) {
701             SDL_SetKeyboardFocus(window);
702         }
703     }
704 
705     /* Make sure the WinRT app's IFramworkView can post events on
706        behalf of SDL:
707     */
708     WINRT_GlobalSDLWindow = window;
709 
710     /* All done! */
711     return 0;
712 }
713 
714 void
WINRT_SetWindowSize(_THIS,SDL_Window * window)715 WINRT_SetWindowSize(_THIS, SDL_Window * window)
716 {
717 #if NTDDI_VERSION >= NTDDI_WIN10
718     SDL_WindowData * data = (SDL_WindowData *)window->driverdata;
719     const Windows::Foundation::Size size(WINRT_PHYSICAL_PIXELS_TO_DIPS(window->w),
720                                          WINRT_PHYSICAL_PIXELS_TO_DIPS(window->h));
721     data->appView->TryResizeView(size); // TODO, WinRT: return failure (to caller?) from TryResizeView()
722 #endif
723 }
724 
725 void
WINRT_SetWindowFullscreen(_THIS,SDL_Window * window,SDL_VideoDisplay * display,SDL_bool fullscreen)726 WINRT_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDisplay * display, SDL_bool fullscreen)
727 {
728 #if NTDDI_VERSION >= NTDDI_WIN10
729     SDL_WindowData * data = (SDL_WindowData *)window->driverdata;
730     bool isWindowActive = WINRT_IsCoreWindowActive(data->coreWindow.Get());
731     if (isWindowActive) {
732         if (fullscreen) {
733             if (!data->appView->IsFullScreenMode) {
734                 data->appView->TryEnterFullScreenMode();    // TODO, WinRT: return failure (to caller?) from TryEnterFullScreenMode()
735             }
736         } else {
737             if (data->appView->IsFullScreenMode) {
738                 data->appView->ExitFullScreenMode();
739             }
740         }
741     }
742 #endif
743 }
744 
745 
746 void
WINRT_DestroyWindow(_THIS,SDL_Window * window)747 WINRT_DestroyWindow(_THIS, SDL_Window * window)
748 {
749     SDL_WindowData * data = (SDL_WindowData *) window->driverdata;
750 
751     if (WINRT_GlobalSDLWindow == window) {
752         WINRT_GlobalSDLWindow = NULL;
753     }
754 
755     if (data) {
756         // Delete the internal window data:
757         delete data;
758         data = NULL;
759         window->driverdata = NULL;
760     }
761 }
762 
763 SDL_bool
WINRT_GetWindowWMInfo(_THIS,SDL_Window * window,SDL_SysWMinfo * info)764 WINRT_GetWindowWMInfo(_THIS, SDL_Window * window, SDL_SysWMinfo * info)
765 {
766     SDL_WindowData * data = (SDL_WindowData *) window->driverdata;
767 
768     if (info->version.major <= SDL_MAJOR_VERSION) {
769         info->subsystem = SDL_SYSWM_WINRT;
770         info->info.winrt.window = reinterpret_cast<IInspectable *>(data->coreWindow.Get());
771         return SDL_TRUE;
772     } else {
773         SDL_SetError("Application not compiled with SDL %d.%d",
774                      SDL_MAJOR_VERSION, SDL_MINOR_VERSION);
775         return SDL_FALSE;
776     }
777     return SDL_FALSE;
778 }
779 
780 static ABI::Windows::System::Display::IDisplayRequest *
WINRT_CreateDisplayRequest(_THIS)781 WINRT_CreateDisplayRequest(_THIS)
782 {
783     /* Setup a WinRT DisplayRequest object, usable for enabling/disabling screensaver requests */
784     wchar_t *wClassName = L"Windows.System.Display.DisplayRequest";
785     HSTRING hClassName;
786     IActivationFactory *pActivationFactory = NULL;
787     IInspectable * pDisplayRequestRaw = nullptr;
788     ABI::Windows::System::Display::IDisplayRequest * pDisplayRequest = nullptr;
789     HRESULT hr;
790 
791     hr = ::WindowsCreateString(wClassName, (UINT32)wcslen(wClassName), &hClassName);
792     if (FAILED(hr)) {
793         goto done;
794     }
795 
796     hr = Windows::Foundation::GetActivationFactory(hClassName, &pActivationFactory);
797     if (FAILED(hr)) {
798         goto done;
799     }
800 
801     hr = pActivationFactory->ActivateInstance(&pDisplayRequestRaw);
802     if (FAILED(hr)) {
803         goto done;
804     }
805 
806     hr = pDisplayRequestRaw->QueryInterface(IID_IDisplayRequest, (void **) &pDisplayRequest);
807     if (FAILED(hr)) {
808         goto done;
809     }
810 
811 done:
812     if (pDisplayRequestRaw) {
813         pDisplayRequestRaw->Release();
814     }
815     if (pActivationFactory) {
816         pActivationFactory->Release();
817     }
818     if (hClassName) {
819         ::WindowsDeleteString(hClassName);
820     }
821 
822     return pDisplayRequest;
823 }
824 
825 void
WINRT_SuspendScreenSaver(_THIS)826 WINRT_SuspendScreenSaver(_THIS)
827 {
828     SDL_VideoData *driverdata = (SDL_VideoData *)_this->driverdata;
829     if (driverdata && driverdata->displayRequest) {
830         ABI::Windows::System::Display::IDisplayRequest * displayRequest = (ABI::Windows::System::Display::IDisplayRequest *) driverdata->displayRequest;
831         if (_this->suspend_screensaver) {
832             displayRequest->RequestActive();
833         } else {
834             displayRequest->RequestRelease();
835         }
836     }
837 }
838 
839 #endif /* SDL_VIDEO_DRIVER_WINRT */
840 
841 /* vi: set ts=4 sw=4 expandtab: */
842