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