Change std:vector to eina_array
[platform/upstream/SDL.git] / src / core / winrt / SDL_winrtapp_direct3d.cpp
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 /* Standard C++11 includes */
24 #include <functional>
25 #include <string>
26 #include <sstream>
27 using namespace std;
28
29
30 /* Windows includes */
31 #include "ppltasks.h"
32 using namespace concurrency;
33 using namespace Windows::ApplicationModel;
34 using namespace Windows::ApplicationModel::Core;
35 using namespace Windows::ApplicationModel::Activation;
36 using namespace Windows::Devices::Input;
37 using namespace Windows::Graphics::Display;
38 using namespace Windows::Foundation;
39 using namespace Windows::System;
40 using namespace Windows::UI::Core;
41 using namespace Windows::UI::Input;
42
43 #if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
44 using namespace Windows::Phone::UI::Input;
45 #endif
46
47
48 /* SDL includes */
49 extern "C" {
50 #include "SDL_events.h"
51 #include "SDL_hints.h"
52 #include "SDL_main.h"
53 #include "SDL_stdinc.h"
54 #include "SDL_render.h"
55 #include "../../video/SDL_sysvideo.h"
56 //#include "../../SDL_hints_c.h"
57 #include "../../events/SDL_events_c.h"
58 #include "../../events/SDL_keyboard_c.h"
59 #include "../../events/SDL_mouse_c.h"
60 #include "../../events/SDL_windowevents_c.h"
61 #include "../../render/SDL_sysrender.h"
62 #include "../windows/SDL_windows.h"
63 }
64
65 #include "../../video/winrt/SDL_winrtevents_c.h"
66 #include "../../video/winrt/SDL_winrtvideo_cpp.h"
67 #include "SDL_winrtapp_common.h"
68 #include "SDL_winrtapp_direct3d.h"
69
70 #if SDL_VIDEO_RENDER_D3D11 && !SDL_RENDER_DISABLED
71 /* Calling IDXGIDevice3::Trim on the active Direct3D 11.x device is necessary
72  * when Windows 8.1 apps are about to get suspended.
73  */
74 extern "C" void D3D11_Trim(SDL_Renderer *);
75 #endif
76
77
78 // Compile-time debugging options:
79 // To enable, uncomment; to disable, comment them out.
80 //#define LOG_POINTER_EVENTS 1
81 //#define LOG_WINDOW_EVENTS 1
82 //#define LOG_ORIENTATION_EVENTS 1
83
84
85 // HACK, DLudwig: record a reference to the global, WinRT 'app'/view.
86 // SDL/WinRT will use this throughout its code.
87 //
88 // TODO, WinRT: consider replacing SDL_WinRTGlobalApp with something
89 // non-global, such as something created inside
90 // SDL_InitSubSystem(SDL_INIT_VIDEO), or something inside
91 // SDL_CreateWindow().
92 SDL_WinRTApp ^ SDL_WinRTGlobalApp = nullptr;
93
94 ref class SDLApplicationSource sealed : Windows::ApplicationModel::Core::IFrameworkViewSource
95 {
96 public:
97     virtual Windows::ApplicationModel::Core::IFrameworkView^ CreateView();
98 };
99
100 IFrameworkView^ SDLApplicationSource::CreateView()
101 {
102     // TODO, WinRT: see if this function (CreateView) can ever get called
103     // more than once.  For now, just prevent it from ever assigning
104     // SDL_WinRTGlobalApp more than once.
105     SDL_assert(!SDL_WinRTGlobalApp);
106     SDL_WinRTApp ^ app = ref new SDL_WinRTApp();
107     if (!SDL_WinRTGlobalApp)
108     {
109         SDL_WinRTGlobalApp = app;
110     }
111     return app;
112 }
113
114 int SDL_WinRTInitNonXAMLApp(int (*mainFunction)(int, char **))
115 {
116     WINRT_SDLAppEntryPoint = mainFunction;
117     auto direct3DApplicationSource = ref new SDLApplicationSource();
118     CoreApplication::Run(direct3DApplicationSource);
119     return 0;
120 }
121
122 static void SDLCALL
123 WINRT_SetDisplayOrientationsPreference(void *userdata, const char *name, const char *oldValue, const char *newValue)
124 {
125     SDL_assert(SDL_strcmp(name, SDL_HINT_ORIENTATIONS) == 0);
126
127     /* HACK: prevent SDL from altering an app's .appxmanifest-set orientation
128      * from being changed on startup, by detecting when SDL_HINT_ORIENTATIONS
129      * is getting registered.
130      *
131      * TODO, WinRT: consider reading in an app's .appxmanifest file, and apply its orientation when 'newValue == NULL'.
132      */
133     if ((oldValue == NULL) && (newValue == NULL)) {
134         return;
135     }
136
137     // Start with no orientation flags, then add each in as they're parsed
138     // from newValue.
139     unsigned int orientationFlags = 0;
140     if (newValue) {
141         std::istringstream tokenizer(newValue);
142         while (!tokenizer.eof()) {
143             std::string orientationName;
144             std::getline(tokenizer, orientationName, ' ');
145             if (orientationName == "LandscapeLeft") {
146                 orientationFlags |= (unsigned int) DisplayOrientations::LandscapeFlipped;
147             } else if (orientationName == "LandscapeRight") {
148                 orientationFlags |= (unsigned int) DisplayOrientations::Landscape;
149             } else if (orientationName == "Portrait") {
150                 orientationFlags |= (unsigned int) DisplayOrientations::Portrait;
151             } else if (orientationName == "PortraitUpsideDown") {
152                 orientationFlags |= (unsigned int) DisplayOrientations::PortraitFlipped;
153             }
154         }
155     }
156
157     // If no valid orientation flags were specified, use a reasonable set of defaults:
158     if (!orientationFlags) {
159         // TODO, WinRT: consider seeing if an app's default orientation flags can be found out via some API call(s).
160         orientationFlags = (unsigned int) ( \
161             DisplayOrientations::Landscape |
162             DisplayOrientations::LandscapeFlipped |
163             DisplayOrientations::Portrait |
164             DisplayOrientations::PortraitFlipped);
165     }
166
167     // Set the orientation/rotation preferences.  Please note that this does
168     // not constitute a 100%-certain lock of a given set of possible
169     // orientations.  According to Microsoft's documentation on WinRT [1]
170     // when a device is not capable of being rotated, Windows may ignore
171     // the orientation preferences, and stick to what the device is capable of
172     // displaying.
173     //
174     // [1] Documentation on the 'InitialRotationPreference' setting for a
175     // Windows app's manifest file describes how some orientation/rotation
176     // preferences may be ignored.  See
177     // http://msdn.microsoft.com/en-us/library/windows/apps/hh700343.aspx
178     // for details.  Microsoft's "Display orientation sample" also gives an
179     // outline of how Windows treats device rotation
180     // (http://code.msdn.microsoft.com/Display-Orientation-Sample-19a58e93).
181     WINRT_DISPLAY_PROPERTY(AutoRotationPreferences) = (DisplayOrientations) orientationFlags;
182 }
183
184 static void
185 WINRT_ProcessWindowSizeChange() // TODO: Pass an SDL_Window-identifying thing into WINRT_ProcessWindowSizeChange()
186 {
187     CoreWindow ^ coreWindow = CoreWindow::GetForCurrentThread();
188     if (coreWindow) {
189         if (WINRT_GlobalSDLWindow) {
190             SDL_Window * window = WINRT_GlobalSDLWindow;
191             SDL_WindowData * data = (SDL_WindowData *) window->driverdata;
192
193             int x = WINRT_DIPS_TO_PHYSICAL_PIXELS(data->coreWindow->Bounds.Left);
194             int y = WINRT_DIPS_TO_PHYSICAL_PIXELS(data->coreWindow->Bounds.Top);
195             int w = WINRT_DIPS_TO_PHYSICAL_PIXELS(data->coreWindow->Bounds.Width);
196             int h = WINRT_DIPS_TO_PHYSICAL_PIXELS(data->coreWindow->Bounds.Height);
197
198 #if (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) && (NTDDI_VERSION == NTDDI_WIN8)
199             /* WinPhone 8.0 always keeps its native window size in portrait,
200                regardless of orientation.  This changes in WinPhone 8.1,
201                in which the native window's size changes along with
202                orientation.
203
204                Attempt to emulate WinPhone 8.1's behavior on WinPhone 8.0, with
205                regards to window size.  This fixes a rendering bug that occurs
206                when a WinPhone 8.0 app is rotated to either 90 or 270 degrees.
207             */
208             const DisplayOrientations currentOrientation = WINRT_DISPLAY_PROPERTY(CurrentOrientation);
209             switch (currentOrientation) {
210                 case DisplayOrientations::Landscape:
211                 case DisplayOrientations::LandscapeFlipped: {
212                     int tmp = w;
213                     w = h;
214                     h = tmp;
215                 } break;
216             }
217 #endif
218
219             const Uint32 latestFlags = WINRT_DetectWindowFlags(window);
220             if (latestFlags & SDL_WINDOW_MAXIMIZED) {
221                 SDL_SendWindowEvent(window, SDL_WINDOWEVENT_MAXIMIZED, 0, 0);
222             } else {
223                 SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESTORED, 0, 0);
224             }
225
226             WINRT_UpdateWindowFlags(window, SDL_WINDOW_FULLSCREEN_DESKTOP);
227
228             /* The window can move during a resize event, such as when maximizing
229                or resizing from a corner */
230             SDL_SendWindowEvent(window, SDL_WINDOWEVENT_MOVED, x, y);
231             SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESIZED, w, h);
232         }
233     }
234 }
235
236 SDL_WinRTApp::SDL_WinRTApp() :
237     m_windowClosed(false),
238     m_windowVisible(true)
239 {
240 }
241
242 void SDL_WinRTApp::Initialize(CoreApplicationView^ applicationView)
243 {
244     applicationView->Activated +=
245         ref new TypedEventHandler<CoreApplicationView^, IActivatedEventArgs^>(this, &SDL_WinRTApp::OnAppActivated);
246
247     CoreApplication::Suspending +=
248         ref new EventHandler<SuspendingEventArgs^>(this, &SDL_WinRTApp::OnSuspending);
249
250     CoreApplication::Resuming +=
251         ref new EventHandler<Platform::Object^>(this, &SDL_WinRTApp::OnResuming);
252
253     CoreApplication::Exiting +=
254         ref new EventHandler<Platform::Object^>(this, &SDL_WinRTApp::OnExiting);
255
256 #if NTDDI_VERSION >= NTDDI_WIN10
257     /* HACK ALERT!  Xbox One doesn't seem to detect gamepads unless something
258        gets registered to receive Win10's Windows.Gaming.Input.Gamepad.GamepadAdded
259        events.  We'll register an event handler for these events here, to make
260        sure that gamepad detection works later on, if requested.
261     */
262     Windows::Gaming::Input::Gamepad::GamepadAdded +=
263         ref new Windows::Foundation::EventHandler<Windows::Gaming::Input::Gamepad^>(
264             this, &SDL_WinRTApp::OnGamepadAdded
265         );
266 #endif
267 }
268
269 #if NTDDI_VERSION > NTDDI_WIN8
270 void SDL_WinRTApp::OnOrientationChanged(DisplayInformation^ sender, Object^ args)
271 #else
272 void SDL_WinRTApp::OnOrientationChanged(Object^ sender)
273 #endif
274 {
275 #if LOG_ORIENTATION_EVENTS==1
276     {
277         CoreWindow^ window = CoreWindow::GetForCurrentThread();
278         if (window) {
279             SDL_Log("%s, current orientation=%d, native orientation=%d, auto rot. pref=%d, CoreWindow Bounds={%f,%f,%f,%f}\n",
280                 __FUNCTION__,
281                 WINRT_DISPLAY_PROPERTY(CurrentOrientation),
282                 WINRT_DISPLAY_PROPERTY(NativeOrientation),
283                 WINRT_DISPLAY_PROPERTY(AutoRotationPreferences),
284                 window->Bounds.X,
285                 window->Bounds.Y,
286                 window->Bounds.Width,
287                 window->Bounds.Height);
288         } else {
289             SDL_Log("%s, current orientation=%d, native orientation=%d, auto rot. pref=%d\n",
290                 __FUNCTION__,
291                 WINRT_DISPLAY_PROPERTY(CurrentOrientation),
292                 WINRT_DISPLAY_PROPERTY(NativeOrientation),
293                 WINRT_DISPLAY_PROPERTY(AutoRotationPreferences));
294         }
295     }
296 #endif
297
298     WINRT_ProcessWindowSizeChange();
299
300 #if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
301     // HACK: Make sure that orientation changes
302     // lead to the Direct3D renderer's viewport getting updated:
303     //
304     // For some reason, this doesn't seem to need to be done on Windows 8.x,
305     // even when going from Landscape to LandscapeFlipped.  It only seems to
306     // be needed on Windows Phone, at least when I tested on my devices.
307     // I'm not currently sure why this is, but it seems to work fine. -- David L.
308     //
309     // TODO, WinRT: do more extensive research into why orientation changes on Win 8.x don't need D3D changes, or if they might, in some cases
310     SDL_Window * window = WINRT_GlobalSDLWindow;
311     if (window) {
312         SDL_WindowData * data = (SDL_WindowData *)window->driverdata;
313         int w = WINRT_DIPS_TO_PHYSICAL_PIXELS(data->coreWindow->Bounds.Width);
314         int h = WINRT_DIPS_TO_PHYSICAL_PIXELS(data->coreWindow->Bounds.Height);
315         SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_SIZE_CHANGED, w, h);
316     }
317 #endif
318
319 }
320
321 void SDL_WinRTApp::SetWindow(CoreWindow^ window)
322 {
323 #if LOG_WINDOW_EVENTS==1
324     SDL_Log("%s, current orientation=%d, native orientation=%d, auto rot. pref=%d, window bounds={%f, %f, %f,%f}\n",
325         __FUNCTION__,
326         WINRT_DISPLAY_PROPERTY(CurrentOrientation),
327         WINRT_DISPLAY_PROPERTY(NativeOrientation),
328         WINRT_DISPLAY_PROPERTY(AutoRotationPreferences),
329         window->Bounds.X,
330         window->Bounds.Y,
331         window->Bounds.Width,
332         window->Bounds.Height);
333 #endif
334
335     window->SizeChanged += 
336         ref new TypedEventHandler<CoreWindow^, WindowSizeChangedEventArgs^>(this, &SDL_WinRTApp::OnWindowSizeChanged);
337
338     window->VisibilityChanged +=
339         ref new TypedEventHandler<CoreWindow^, VisibilityChangedEventArgs^>(this, &SDL_WinRTApp::OnVisibilityChanged);
340
341     window->Activated +=
342         ref new TypedEventHandler<CoreWindow^, WindowActivatedEventArgs^>(this, &SDL_WinRTApp::OnWindowActivated);
343
344     window->Closed += 
345         ref new TypedEventHandler<CoreWindow^, CoreWindowEventArgs^>(this, &SDL_WinRTApp::OnWindowClosed);
346
347 #if WINAPI_FAMILY != WINAPI_FAMILY_PHONE_APP
348     window->PointerCursor = ref new CoreCursor(CoreCursorType::Arrow, 0);
349 #endif
350
351     window->PointerPressed +=
352         ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &SDL_WinRTApp::OnPointerPressed);
353
354     window->PointerMoved +=
355         ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &SDL_WinRTApp::OnPointerMoved);
356
357     window->PointerReleased +=
358         ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &SDL_WinRTApp::OnPointerReleased);
359
360     window->PointerEntered +=
361         ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &SDL_WinRTApp::OnPointerEntered);
362
363     window->PointerExited +=
364         ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &SDL_WinRTApp::OnPointerExited);
365
366     window->PointerWheelChanged +=
367         ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &SDL_WinRTApp::OnPointerWheelChanged);
368
369 #if WINAPI_FAMILY != WINAPI_FAMILY_PHONE_APP
370     // Retrieves relative-only mouse movements:
371     Windows::Devices::Input::MouseDevice::GetForCurrentView()->MouseMoved +=
372         ref new TypedEventHandler<MouseDevice^, MouseEventArgs^>(this, &SDL_WinRTApp::OnMouseMoved);
373 #endif
374
375     window->KeyDown +=
376         ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>(this, &SDL_WinRTApp::OnKeyDown);
377
378     window->KeyUp +=
379         ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>(this, &SDL_WinRTApp::OnKeyUp);
380
381     window->CharacterReceived +=
382         ref new TypedEventHandler<CoreWindow^, CharacterReceivedEventArgs^>(this, &SDL_WinRTApp::OnCharacterReceived);
383
384 #if NTDDI_VERSION >= NTDDI_WIN10
385     Windows::UI::Core::SystemNavigationManager::GetForCurrentView()->BackRequested +=
386         ref new EventHandler<BackRequestedEventArgs^>(this, &SDL_WinRTApp::OnBackButtonPressed);
387 #elif WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
388     HardwareButtons::BackPressed +=
389         ref new EventHandler<BackPressedEventArgs^>(this, &SDL_WinRTApp::OnBackButtonPressed);
390 #endif
391
392 #if NTDDI_VERSION > NTDDI_WIN8
393     DisplayInformation::GetForCurrentView()->OrientationChanged +=
394         ref new TypedEventHandler<Windows::Graphics::Display::DisplayInformation^, Object^>(this, &SDL_WinRTApp::OnOrientationChanged);
395 #else
396     DisplayProperties::OrientationChanged +=
397         ref new DisplayPropertiesEventHandler(this, &SDL_WinRTApp::OnOrientationChanged);
398 #endif
399
400     // Register the hint, SDL_HINT_ORIENTATIONS, with SDL.
401     // 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.
402     SDL_AddHintCallback(SDL_HINT_ORIENTATIONS, WINRT_SetDisplayOrientationsPreference, NULL);
403
404 #if (WINAPI_FAMILY == WINAPI_FAMILY_APP) && (NTDDI_VERSION < NTDDI_WIN10)  // for Windows 8/8.1/RT apps... (and not Phone apps)
405     // Make sure we know when a user has opened the app's settings pane.
406     // This is needed in order to display a privacy policy, which needs
407     // to be done for network-enabled apps, as per Windows Store requirements.
408     using namespace Windows::UI::ApplicationSettings;
409     SettingsPane::GetForCurrentView()->CommandsRequested +=
410         ref new TypedEventHandler<SettingsPane^, SettingsPaneCommandsRequestedEventArgs^>
411             (this, &SDL_WinRTApp::OnSettingsPaneCommandsRequested);
412 #endif
413 }
414
415 void SDL_WinRTApp::Load(Platform::String^ entryPoint)
416 {
417 }
418
419 void SDL_WinRTApp::Run()
420 {
421     SDL_SetMainReady();
422     if (WINRT_SDLAppEntryPoint)
423     {
424         // TODO, WinRT: pass the C-style main() a reasonably realistic
425         // representation of command line arguments.
426         int argc = 0;
427         char **argv = NULL;
428         WINRT_SDLAppEntryPoint(argc, argv);
429     }
430 }
431
432 static bool IsSDLWindowEventPending(SDL_WindowEventID windowEventID)
433 {
434     SDL_Event events[128];
435     const int count = SDL_PeepEvents(events, sizeof(events)/sizeof(SDL_Event), SDL_PEEKEVENT, SDL_WINDOWEVENT, SDL_WINDOWEVENT);
436     for (int i = 0; i < count; ++i) {
437         if (events[i].window.event == windowEventID) {
438             return true;
439         }
440     }
441     return false;
442 }
443
444 bool SDL_WinRTApp::ShouldWaitForAppResumeEvents()
445 {
446     /* Don't wait if the app is visible: */
447     if (m_windowVisible) {
448         return false;
449     }
450     
451     /* Don't wait until the window-hide events finish processing.
452      * Do note that if an app-suspend event is sent (as indicated
453      * by SDL_APP_WILLENTERBACKGROUND and SDL_APP_DIDENTERBACKGROUND
454      * events), then this code may be a moot point, as WinRT's
455      * own event pump (aka ProcessEvents()) will pause regardless
456      * of what we do here.  This happens on Windows Phone 8, to note.
457      * Windows 8.x apps, on the other hand, may get a chance to run
458      * these.
459      */
460     if (IsSDLWindowEventPending(SDL_WINDOWEVENT_HIDDEN)) {
461         return false;
462     } else if (IsSDLWindowEventPending(SDL_WINDOWEVENT_FOCUS_LOST)) {
463         return false;
464     } else if (IsSDLWindowEventPending(SDL_WINDOWEVENT_MINIMIZED)) {
465         return false;
466     }
467
468     return true;
469 }
470
471 void SDL_WinRTApp::PumpEvents()
472 {
473     if (!m_windowClosed) {
474         if (!ShouldWaitForAppResumeEvents()) {
475             /* This is the normal way in which events should be pumped.
476              * 'ProcessAllIfPresent' will make ProcessEvents() process anywhere
477              * from zero to N events, and will then return.
478              */
479             CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);
480         } else {
481             /* This style of event-pumping, with 'ProcessOneAndAllPending',
482              * will cause anywhere from one to N events to be processed.  If
483              * at least one event is processed, the call will return.  If
484              * no events are pending, then the call will wait until one is
485              * available, and will not return (to the caller) until this
486              * happens!  This should only occur when the app is hidden.
487              */
488             CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
489         }
490     }
491 }
492
493 void SDL_WinRTApp::Uninitialize()
494 {
495 }
496
497 #if (WINAPI_FAMILY == WINAPI_FAMILY_APP) && (NTDDI_VERSION < NTDDI_WIN10)
498 void SDL_WinRTApp::OnSettingsPaneCommandsRequested(
499     Windows::UI::ApplicationSettings::SettingsPane ^p,
500     Windows::UI::ApplicationSettings::SettingsPaneCommandsRequestedEventArgs ^args)
501 {
502     using namespace Platform;
503     using namespace Windows::UI::ApplicationSettings;
504     using namespace Windows::UI::Popups;
505
506     String ^privacyPolicyURL = nullptr;     // a URL to an app's Privacy Policy
507     String ^privacyPolicyLabel = nullptr;   // label/link text
508     const char *tmpHintValue = NULL;        // SDL_GetHint-retrieved value, used immediately
509     wchar_t *tmpStr = NULL;                 // used for UTF8 to UCS2 conversion
510
511     // Setup a 'Privacy Policy' link, if one is available (via SDL_GetHint):
512     tmpHintValue = SDL_GetHint(SDL_HINT_WINRT_PRIVACY_POLICY_URL);
513     if (tmpHintValue && tmpHintValue[0] != '\0') {
514         // Convert the privacy policy's URL to UCS2:
515         tmpStr = WIN_UTF8ToString(tmpHintValue);
516         privacyPolicyURL = ref new String(tmpStr);
517         SDL_free(tmpStr);
518
519         // Optionally retrieve custom label-text for the link.  If this isn't
520         // available, a default value will be used instead.
521         tmpHintValue = SDL_GetHint(SDL_HINT_WINRT_PRIVACY_POLICY_LABEL);
522         if (tmpHintValue && tmpHintValue[0] != '\0') {
523             tmpStr = WIN_UTF8ToString(tmpHintValue);
524             privacyPolicyLabel = ref new String(tmpStr);
525             SDL_free(tmpStr);
526         } else {
527             privacyPolicyLabel = ref new String(L"Privacy Policy");
528         }
529
530         // Register the link, along with a handler to be called if and when it is
531         // clicked:
532         auto cmd = ref new SettingsCommand(L"privacyPolicy", privacyPolicyLabel,
533             ref new UICommandInvokedHandler([=](IUICommand ^) {
534                 Windows::System::Launcher::LaunchUriAsync(ref new Uri(privacyPolicyURL));
535         }));
536         args->Request->ApplicationCommands->Append(cmd);
537     }
538 }
539 #endif // if (WINAPI_FAMILY == WINAPI_FAMILY_APP) && (NTDDI_VERSION < NTDDI_WIN10)
540
541 void SDL_WinRTApp::OnWindowSizeChanged(CoreWindow^ sender, WindowSizeChangedEventArgs^ args)
542 {
543 #if LOG_WINDOW_EVENTS==1
544     SDL_Log("%s, size={%f,%f}, bounds={%f,%f,%f,%f}, current orientation=%d, native orientation=%d, auto rot. pref=%d, WINRT_GlobalSDLWindow?=%s\n",
545         __FUNCTION__,
546         args->Size.Width, args->Size.Height,
547         sender->Bounds.X, sender->Bounds.Y, sender->Bounds.Width, sender->Bounds.Height,
548         WINRT_DISPLAY_PROPERTY(CurrentOrientation),
549         WINRT_DISPLAY_PROPERTY(NativeOrientation),
550         WINRT_DISPLAY_PROPERTY(AutoRotationPreferences),
551         (WINRT_GlobalSDLWindow ? "yes" : "no"));
552 #endif
553
554     WINRT_ProcessWindowSizeChange();
555 }
556
557 void SDL_WinRTApp::OnVisibilityChanged(CoreWindow^ sender, VisibilityChangedEventArgs^ args)
558 {
559 #if LOG_WINDOW_EVENTS==1
560     SDL_Log("%s, visible?=%s, bounds={%f,%f,%f,%f}, WINRT_GlobalSDLWindow?=%s\n",
561         __FUNCTION__,
562         (args->Visible ? "yes" : "no"),
563         sender->Bounds.X, sender->Bounds.Y,
564         sender->Bounds.Width, sender->Bounds.Height,
565         (WINRT_GlobalSDLWindow ? "yes" : "no"));
566 #endif
567
568     m_windowVisible = args->Visible;
569     if (WINRT_GlobalSDLWindow) {
570         SDL_bool wasSDLWindowSurfaceValid = WINRT_GlobalSDLWindow->surface_valid;
571         Uint32 latestWindowFlags = WINRT_DetectWindowFlags(WINRT_GlobalSDLWindow);
572         if (args->Visible) {
573             SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_SHOWN, 0, 0);
574             SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
575             if (latestWindowFlags & SDL_WINDOW_MAXIMIZED) {
576                 SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_MAXIMIZED, 0, 0);
577             } else {
578                 SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_RESTORED, 0, 0);
579             }
580         } else {
581             SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_HIDDEN, 0, 0);
582             SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
583             SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
584         }
585
586         // HACK: Prevent SDL's window-hide handling code, which currently
587         // triggers a fake window resize (possibly erronously), from
588         // marking the SDL window's surface as invalid.
589         //
590         // A better solution to this probably involves figuring out if the
591         // fake window resize can be prevented.
592         WINRT_GlobalSDLWindow->surface_valid = wasSDLWindowSurfaceValid;
593     }
594 }
595
596 void SDL_WinRTApp::OnWindowActivated(CoreWindow^ sender, WindowActivatedEventArgs^ args)
597 {
598 #if LOG_WINDOW_EVENTS==1
599     SDL_Log("%s, WINRT_GlobalSDLWindow?=%s\n\n",
600         __FUNCTION__,
601         (WINRT_GlobalSDLWindow ? "yes" : "no"));
602 #endif
603
604     /* There's no property in Win 8.x to tell whether a window is active or
605        not.  [De]activation events are, however, sent to the app.  We'll just
606        record those, in case the CoreWindow gets wrapped by an SDL_Window at
607        some future time.
608     */
609     sender->CustomProperties->Insert("SDLHelperWindowActivationState", args->WindowActivationState);
610
611     SDL_Window * window = WINRT_GlobalSDLWindow;
612     if (window) {
613         if (args->WindowActivationState != CoreWindowActivationState::Deactivated) {
614             SDL_SendWindowEvent(window, SDL_WINDOWEVENT_SHOWN, 0, 0);
615             if (SDL_GetKeyboardFocus() != window) {
616                 SDL_SetKeyboardFocus(window);
617             }
618         
619             /* Send a mouse-motion event as appropriate.
620                This doesn't work when called from OnPointerEntered, at least
621                not in WinRT CoreWindow apps (as OnPointerEntered doesn't
622                appear to be called after window-reactivation, at least not
623                in Windows 10, Build 10586.3 (November 2015 update, non-beta).
624
625                Don't do it on WinPhone 8.0 though, as CoreWindow's 'PointerPosition'
626                property isn't available.
627              */
628 #if (WINAPI_FAMILY != WINAPI_FAMILY_PHONE_APP) || (NTDDI_VERSION >= NTDDI_WINBLUE)
629             Point cursorPos = WINRT_TransformCursorPosition(window, sender->PointerPosition, TransformToSDLWindowSize);
630             SDL_SendMouseMotion(window, 0, 0, (int)cursorPos.X, (int)cursorPos.Y);
631 #endif
632
633             /* TODO, WinRT: see if the Win32 bugfix from https://hg.libsdl.org/SDL/rev/d278747da408 needs to be applied (on window activation) */
634             //WIN_CheckAsyncMouseRelease(data);
635
636             /* TODO, WinRT: implement clipboard support, if possible */
637             ///*
638             // * FIXME: Update keyboard state
639             // */
640             //WIN_CheckClipboardUpdate(data->videodata);
641
642             // HACK: Resetting the mouse-cursor here seems to fix
643             // https://bugzilla.libsdl.org/show_bug.cgi?id=3217, whereby a
644             // WinRT app's mouse cursor may switch to Windows' 'wait' cursor,
645             // after a user alt-tabs back into a full-screened SDL app.
646             // This bug does not appear to reproduce 100% of the time.
647             // It may be a bug in Windows itself (v.10.0.586.36, as tested,
648             // and the most-recent as of this writing).
649             SDL_SetCursor(NULL);
650         } else {
651             if (SDL_GetKeyboardFocus() == window) {
652                 SDL_SetKeyboardFocus(NULL);
653             }
654         }
655     }
656 }
657
658 void SDL_WinRTApp::OnWindowClosed(CoreWindow^ sender, CoreWindowEventArgs^ args)
659 {
660 #if LOG_WINDOW_EVENTS==1
661     SDL_Log("%s\n", __FUNCTION__);
662 #endif
663     m_windowClosed = true;
664 }
665
666 void SDL_WinRTApp::OnAppActivated(CoreApplicationView^ applicationView, IActivatedEventArgs^ args)
667 {
668     CoreWindow::GetForCurrentThread()->Activate();
669 }
670
671 void SDL_WinRTApp::OnSuspending(Platform::Object^ sender, SuspendingEventArgs^ args)
672 {
673     // Save app state asynchronously after requesting a deferral. Holding a deferral
674     // indicates that the application is busy performing suspending operations. Be
675     // aware that a deferral may not be held indefinitely. After about five seconds,
676     // the app will be forced to exit.
677
678     // ... but first, let the app know it's about to go to the background.
679     // The separation of events may be important, given that the deferral
680     // runs in a separate thread.  This'll make SDL_APP_WILLENTERBACKGROUND
681     // the only event among the two that runs in the main thread.  Given
682     // that a few WinRT operations can only be done from the main thread
683     // (things that access the WinRT CoreWindow are one example of this),
684     // this could be important.
685     SDL_SendAppEvent(SDL_APP_WILLENTERBACKGROUND);
686
687     SuspendingDeferral^ deferral = args->SuspendingOperation->GetDeferral();
688     create_task([this, deferral]()
689     {
690         // Send an app did-enter-background event immediately to observers.
691         // CoreDispatcher::ProcessEvents, which is the backbone on which
692         // SDL_WinRTApp::PumpEvents is built, will not return to its caller
693         // once it sends out a suspend event.  Any events posted to SDL's
694         // event queue won't get received until the WinRT app is resumed.
695         // SDL_AddEventWatch() may be used to receive app-suspend events on
696         // WinRT.
697         SDL_SendAppEvent(SDL_APP_DIDENTERBACKGROUND);
698
699         // Let the Direct3D 11 renderer prepare for the app to be backgrounded.
700         // This is necessary for Windows 8.1, possibly elsewhere in the future.
701         // More details at: http://msdn.microsoft.com/en-us/library/windows/apps/Hh994929.aspx
702 #if SDL_VIDEO_RENDER_D3D11 && !SDL_RENDER_DISABLED
703         if (WINRT_GlobalSDLWindow) {
704             SDL_Renderer * renderer = SDL_GetRenderer(WINRT_GlobalSDLWindow);
705             if (renderer && (SDL_strcmp(renderer->info.name, "direct3d11") == 0)) {
706                 D3D11_Trim(renderer);
707             }
708         }
709 #endif
710
711         deferral->Complete();
712     });
713 }
714
715 void SDL_WinRTApp::OnResuming(Platform::Object^ sender, Platform::Object^ args)
716 {
717     // Restore any data or state that was unloaded on suspend. By default, data
718     // and state are persisted when resuming from suspend. Note that these events
719     // do not occur if the app was previously terminated.
720     SDL_SendAppEvent(SDL_APP_WILLENTERFOREGROUND);
721     SDL_SendAppEvent(SDL_APP_DIDENTERFOREGROUND);
722 }
723
724 void SDL_WinRTApp::OnExiting(Platform::Object^ sender, Platform::Object^ args)
725 {
726     SDL_SendAppEvent(SDL_APP_TERMINATING);
727 }
728
729 static void
730 WINRT_LogPointerEvent(const char * header, Windows::UI::Core::PointerEventArgs ^ args, Windows::Foundation::Point transformedPoint)
731 {
732     Windows::UI::Input::PointerPoint ^ pt = args->CurrentPoint;
733     SDL_Log("%s: Position={%f,%f}, Transformed Pos={%f, %f}, MouseWheelDelta=%d, FrameId=%d, PointerId=%d, SDL button=%d\n",
734         header,
735         pt->Position.X, pt->Position.Y,
736         transformedPoint.X, transformedPoint.Y,
737         pt->Properties->MouseWheelDelta,
738         pt->FrameId,
739         pt->PointerId,
740         WINRT_GetSDLButtonForPointerPoint(pt));
741 }
742
743 void SDL_WinRTApp::OnPointerPressed(CoreWindow^ sender, PointerEventArgs^ args)
744 {
745 #if LOG_POINTER_EVENTS
746     WINRT_LogPointerEvent("pointer pressed", args, WINRT_TransformCursorPosition(WINRT_GlobalSDLWindow, args->CurrentPoint->Position, TransformToSDLWindowSize));
747 #endif
748
749     WINRT_ProcessPointerPressedEvent(WINRT_GlobalSDLWindow, args->CurrentPoint);
750 }
751
752 void SDL_WinRTApp::OnPointerMoved(CoreWindow^ sender, PointerEventArgs^ args)
753 {
754 #if LOG_POINTER_EVENTS
755     WINRT_LogPointerEvent("pointer moved", args, WINRT_TransformCursorPosition(WINRT_GlobalSDLWindow, args->CurrentPoint->Position, TransformToSDLWindowSize));
756 #endif
757
758     WINRT_ProcessPointerMovedEvent(WINRT_GlobalSDLWindow, args->CurrentPoint);
759 }
760
761 void SDL_WinRTApp::OnPointerReleased(CoreWindow^ sender, PointerEventArgs^ args)
762 {
763 #if LOG_POINTER_EVENTS
764     WINRT_LogPointerEvent("pointer released", args, WINRT_TransformCursorPosition(WINRT_GlobalSDLWindow, args->CurrentPoint->Position, TransformToSDLWindowSize));
765 #endif
766     
767     WINRT_ProcessPointerReleasedEvent(WINRT_GlobalSDLWindow, args->CurrentPoint);
768 }
769
770 void SDL_WinRTApp::OnPointerEntered(CoreWindow^ sender, PointerEventArgs^ args)
771 {
772 #if LOG_POINTER_EVENTS
773     WINRT_LogPointerEvent("pointer entered", args, WINRT_TransformCursorPosition(WINRT_GlobalSDLWindow, args->CurrentPoint->Position, TransformToSDLWindowSize));
774 #endif
775
776     WINRT_ProcessPointerEnteredEvent(WINRT_GlobalSDLWindow, args->CurrentPoint);
777 }
778
779 void SDL_WinRTApp::OnPointerExited(CoreWindow^ sender, PointerEventArgs^ args)
780 {
781 #if LOG_POINTER_EVENTS
782     WINRT_LogPointerEvent("pointer exited", args, WINRT_TransformCursorPosition(WINRT_GlobalSDLWindow, args->CurrentPoint->Position, TransformToSDLWindowSize));
783 #endif
784
785     WINRT_ProcessPointerExitedEvent(WINRT_GlobalSDLWindow, args->CurrentPoint);
786 }
787
788 void SDL_WinRTApp::OnPointerWheelChanged(CoreWindow^ sender, PointerEventArgs^ args)
789 {
790 #if LOG_POINTER_EVENTS
791     WINRT_LogPointerEvent("pointer wheel changed", args, WINRT_TransformCursorPosition(WINRT_GlobalSDLWindow, args->CurrentPoint->Position, TransformToSDLWindowSize));
792 #endif
793
794     WINRT_ProcessPointerWheelChangedEvent(WINRT_GlobalSDLWindow, args->CurrentPoint);
795 }
796
797 void SDL_WinRTApp::OnMouseMoved(MouseDevice^ mouseDevice, MouseEventArgs^ args)
798 {
799     WINRT_ProcessMouseMovedEvent(WINRT_GlobalSDLWindow, args);
800 }
801
802 void SDL_WinRTApp::OnKeyDown(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::KeyEventArgs^ args)
803 {
804     WINRT_ProcessKeyDownEvent(args);
805 }
806
807 void SDL_WinRTApp::OnKeyUp(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::KeyEventArgs^ args)
808 {
809     WINRT_ProcessKeyUpEvent(args);
810 }
811
812 void SDL_WinRTApp::OnCharacterReceived(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::CharacterReceivedEventArgs^ args)
813 {
814     WINRT_ProcessCharacterReceivedEvent(args);
815 }
816
817 template <typename BackButtonEventArgs>
818 static void WINRT_OnBackButtonPressed(BackButtonEventArgs ^ args)
819 {
820     SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_AC_BACK);
821     SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_AC_BACK);
822
823     if (SDL_GetHintBoolean(SDL_HINT_WINRT_HANDLE_BACK_BUTTON, SDL_FALSE)) {
824         args->Handled = true;
825     }
826 }
827
828 #if NTDDI_VERSION >= NTDDI_WIN10
829 void SDL_WinRTApp::OnBackButtonPressed(Platform::Object^ sender, Windows::UI::Core::BackRequestedEventArgs^ args)
830
831 {
832     WINRT_OnBackButtonPressed(args);
833 }
834 #elif WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
835 void SDL_WinRTApp::OnBackButtonPressed(Platform::Object^ sender, Windows::Phone::UI::Input::BackPressedEventArgs^ args)
836
837 {
838     WINRT_OnBackButtonPressed(args);
839 }
840 #endif
841
842 #if NTDDI_VERSION >= NTDDI_WIN10
843 void SDL_WinRTApp::OnGamepadAdded(Platform::Object ^sender, Windows::Gaming::Input::Gamepad ^gamepad)
844 {
845     /* HACK ALERT: Nothing needs to be done here, as this method currently
846        only exists to allow something to be registered with Win10's
847        GamepadAdded event, an operation that seems to be necessary to get
848        Xinput-based detection to work on Xbox One.
849     */
850 }
851 #endif
852
853 /* vi: set ts=4 sw=4 expandtab: */