/* Standard C++11 includes */ #include #include #include using namespace std; /* Windows includes */ #include "ppltasks.h" using namespace concurrency; using namespace Windows::ApplicationModel; using namespace Windows::ApplicationModel::Core; using namespace Windows::ApplicationModel::Activation; using namespace Windows::Devices::Input; using namespace Windows::Graphics::Display; using namespace Windows::Foundation; using namespace Windows::System; using namespace Windows::UI::Core; using namespace Windows::UI::Input; /* SDL includes */ extern "C" { #include "SDL_assert.h" #include "SDL_events.h" #include "SDL_hints.h" #include "SDL_log.h" #include "SDL_main.h" #include "SDL_stdinc.h" #include "SDL_render.h" #include "../../video/SDL_sysvideo.h" //#include "../../SDL_hints_c.h" #include "../../events/SDL_mouse_c.h" #include "../../events/SDL_windowevents_c.h" #include "../../render/SDL_sysrender.h" } #include "../../video/winrt/SDL_winrtevents_c.h" #include "../../video/winrt/SDL_winrtvideo_cpp.h" #include "SDL_winrtapp_common.h" #include "SDL_winrtapp_direct3d.h" // Compile-time debugging options: // To enable, uncomment; to disable, comment them out. //#define LOG_POINTER_EVENTS 1 //#define LOG_WINDOW_EVENTS 1 //#define LOG_ORIENTATION_EVENTS 1 // HACK, DLudwig: record a reference to the global, WinRT 'app'/view. // SDL/WinRT will use this throughout its code. // // TODO, WinRT: consider replacing SDL_WinRTGlobalApp with something // non-global, such as something created inside // SDL_InitSubSystem(SDL_INIT_VIDEO), or something inside // SDL_CreateWindow(). SDL_WinRTApp ^ SDL_WinRTGlobalApp = nullptr; ref class SDLApplicationSource sealed : Windows::ApplicationModel::Core::IFrameworkViewSource { public: virtual Windows::ApplicationModel::Core::IFrameworkView^ CreateView(); }; IFrameworkView^ SDLApplicationSource::CreateView() { // TODO, WinRT: see if this function (CreateView) can ever get called // more than once. For now, just prevent it from ever assigning // SDL_WinRTGlobalApp more than once. SDL_assert(!SDL_WinRTGlobalApp); SDL_WinRTApp ^ app = ref new SDL_WinRTApp(); if (!SDL_WinRTGlobalApp) { SDL_WinRTGlobalApp = app; } return app; } int SDL_WinRTInitNonXAMLApp(int (*mainFunction)(int, char **)) { WINRT_SDLAppEntryPoint = mainFunction; auto direct3DApplicationSource = ref new SDLApplicationSource(); CoreApplication::Run(direct3DApplicationSource); return 0; } static void WINRT_SetDisplayOrientationsPreference(void *userdata, const char *name, const char *oldValue, const char *newValue) { SDL_assert(SDL_strcmp(name, SDL_HINT_ORIENTATIONS) == 0); // Start with no orientation flags, then add each in as they're parsed // from newValue. unsigned int orientationFlags = 0; if (newValue) { std::istringstream tokenizer(newValue); while (!tokenizer.eof()) { std::string orientationName; std::getline(tokenizer, orientationName, ' '); if (orientationName == "LandscapeLeft") { orientationFlags |= (unsigned int) DisplayOrientations::LandscapeFlipped; } else if (orientationName == "LandscapeRight") { orientationFlags |= (unsigned int) DisplayOrientations::Landscape; } else if (orientationName == "Portrait") { orientationFlags |= (unsigned int) DisplayOrientations::Portrait; } else if (orientationName == "PortraitUpsideDown") { orientationFlags |= (unsigned int) DisplayOrientations::PortraitFlipped; } } } // If no valid orientation flags were specified, use a reasonable set of defaults: if (!orientationFlags) { // TODO, WinRT: consider seeing if an app's default orientation flags can be found out via some API call(s). orientationFlags = (unsigned int) ( \ DisplayOrientations::Landscape | DisplayOrientations::LandscapeFlipped | DisplayOrientations::Portrait | DisplayOrientations::PortraitFlipped); } // Set the orientation/rotation preferences. Please note that this does // not constitute a 100%-certain lock of a given set of possible // orientations. According to Microsoft's documentation on WinRT [1] // when a device is not capable of being rotated, Windows may ignore // the orientation preferences, and stick to what the device is capable of // displaying. // // [1] Documentation on the 'InitialRotationPreference' setting for a // Windows app's manifest file describes how some orientation/rotation // preferences may be ignored. See // http://msdn.microsoft.com/en-us/library/windows/apps/hh700343.aspx // for details. Microsoft's "Display orientation sample" also gives an // outline of how Windows treats device rotation // (http://code.msdn.microsoft.com/Display-Orientation-Sample-19a58e93). DisplayProperties::AutoRotationPreferences = (DisplayOrientations) orientationFlags; } static void WINRT_ProcessWindowSizeChange() { // Make the new window size be the one true fullscreen mode. // This change was initially done, in part, to allow the Direct3D 11.1 // renderer to receive window-resize events as a device rotates. // Before, rotating a device from landscape, to portrait, and then // back to landscape would cause the Direct3D 11.1 swap buffer to // not get resized appropriately. SDL would, on the rotation from // landscape to portrait, re-resize the SDL window to it's initial // size (landscape). On the subsequent rotation, SDL would drop the // window-resize event as it appeared the SDL window didn't change // size, and the Direct3D 11.1 renderer wouldn't resize its swap // chain. SDL_DisplayMode resizedDisplayMode = WINRT_CalcDisplayModeUsingNativeWindow(); if (resizedDisplayMode.w == 0 || resizedDisplayMode.h == 0) { return; } SDL_DisplayMode oldDisplayMode; SDL_zero(oldDisplayMode); if (WINRT_GlobalSDLVideoDevice) { oldDisplayMode = WINRT_GlobalSDLVideoDevice->displays[0].desktop_mode; WINRT_GlobalSDLVideoDevice->displays[0].current_mode = resizedDisplayMode; WINRT_GlobalSDLVideoDevice->displays[0].desktop_mode = resizedDisplayMode; WINRT_GlobalSDLVideoDevice->displays[0].display_modes[0] = resizedDisplayMode; } if (WINRT_GlobalSDLWindow) { // Send a window-resize event to the rest of SDL, and to apps: SDL_SendWindowEvent( WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_RESIZED, resizedDisplayMode.w, resizedDisplayMode.h); #if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP // HACK: On Windows Phone, make sure that orientation changes from // Landscape to LandscapeFlipped, Portrait to PortraitFlipped, // or vice-versa on either of those two, lead to the Direct3D renderer // getting updated. const DisplayOrientations oldOrientation = (DisplayOrientations) (unsigned int) oldDisplayMode.driverdata; const DisplayOrientations newOrientation = (DisplayOrientations) (unsigned int) resizedDisplayMode.driverdata; if ((oldOrientation == DisplayOrientations::Landscape && newOrientation == DisplayOrientations::LandscapeFlipped) || (oldOrientation == DisplayOrientations::LandscapeFlipped && newOrientation == DisplayOrientations::Landscape) || (oldOrientation == DisplayOrientations::Portrait && newOrientation == DisplayOrientations::PortraitFlipped) || (oldOrientation == DisplayOrientations::PortraitFlipped && newOrientation == DisplayOrientations::Portrait)) { // One of the reasons this event is getting sent out is because SDL // will ignore requests to send out SDL_WINDOWEVENT_RESIZED events // if and when the event size doesn't change (and the Direct3D 11.1 // renderer doesn't get the memo). // // Make sure that the display/window size really didn't change. If // it did, then a SDL_WINDOWEVENT_SIZE_CHANGED event got sent, and // the Direct3D 11.1 renderer picked it up, presumably. if (oldDisplayMode.w == resizedDisplayMode.w && oldDisplayMode.h == resizedDisplayMode.h) { SDL_SendWindowEvent( WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_SIZE_CHANGED, resizedDisplayMode.w, resizedDisplayMode.h); } } #endif } } SDL_WinRTApp::SDL_WinRTApp() : m_windowClosed(false), m_windowVisible(true) { } void SDL_WinRTApp::Initialize(CoreApplicationView^ applicationView) { applicationView->Activated += ref new TypedEventHandler(this, &SDL_WinRTApp::OnActivated); CoreApplication::Suspending += ref new EventHandler(this, &SDL_WinRTApp::OnSuspending); CoreApplication::Resuming += ref new EventHandler(this, &SDL_WinRTApp::OnResuming); DisplayProperties::OrientationChanged += ref new DisplayPropertiesEventHandler(this, &SDL_WinRTApp::OnOrientationChanged); // Register the hint, SDL_HINT_ORIENTATIONS, with SDL. This needs to be // done before the hint's callback is registered (as of Feb 22, 2013), // otherwise the hint callback won't get registered. // // 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. //SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight Portrait PortraitUpsideDown"); // DavidL: this is no longer needed (for SDL_AddHintCallback) SDL_AddHintCallback(SDL_HINT_ORIENTATIONS, WINRT_SetDisplayOrientationsPreference, NULL); } void SDL_WinRTApp::OnOrientationChanged(Object^ sender) { #if LOG_ORIENTATION_EVENTS==1 CoreWindow^ window = CoreWindow::GetForCurrentThread(); if (window) { SDL_Log("%s, current orientation=%d, native orientation=%d, auto rot. pref=%d, CoreWindow Size={%f,%f}\n", __FUNCTION__, (int)DisplayProperties::CurrentOrientation, (int)DisplayProperties::NativeOrientation, (int)DisplayProperties::AutoRotationPreferences, window->Bounds.Width, window->Bounds.Height); } else { SDL_Log("%s, current orientation=%d, native orientation=%d, auto rot. pref=%d\n", __FUNCTION__, (int)DisplayProperties::CurrentOrientation, (int)DisplayProperties::NativeOrientation, (int)DisplayProperties::AutoRotationPreferences); } #endif #if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP // On Windows Phone, treat an orientation change as a change in window size. // The native window's size doesn't seem to change, however SDL will simulate // a window size change. WINRT_ProcessWindowSizeChange(); #endif } void SDL_WinRTApp::SetWindow(CoreWindow^ window) { #if LOG_WINDOW_EVENTS==1 SDL_Log("%s, current orientation=%d, native orientation=%d, auto rot. pref=%d, window Size={%f,%f}\n", __FUNCTION__, (int)DisplayProperties::CurrentOrientation, (int)DisplayProperties::NativeOrientation, (int)DisplayProperties::AutoRotationPreferences, window->Bounds.Width, window->Bounds.Height); #endif window->SizeChanged += ref new TypedEventHandler(this, &SDL_WinRTApp::OnWindowSizeChanged); window->VisibilityChanged += ref new TypedEventHandler(this, &SDL_WinRTApp::OnVisibilityChanged); window->Closed += ref new TypedEventHandler(this, &SDL_WinRTApp::OnWindowClosed); #if WINAPI_FAMILY != WINAPI_FAMILY_PHONE_APP window->PointerCursor = ref new CoreCursor(CoreCursorType::Arrow, 0); #endif window->PointerPressed += ref new TypedEventHandler(this, &SDL_WinRTApp::OnPointerPressed); window->PointerMoved += ref new TypedEventHandler(this, &SDL_WinRTApp::OnPointerMoved); window->PointerReleased += ref new TypedEventHandler(this, &SDL_WinRTApp::OnPointerReleased); window->PointerWheelChanged += ref new TypedEventHandler(this, &SDL_WinRTApp::OnPointerWheelChanged); #if WINAPI_FAMILY != WINAPI_FAMILY_PHONE_APP // Retrieves relative-only mouse movements: Windows::Devices::Input::MouseDevice::GetForCurrentView()->MouseMoved += ref new TypedEventHandler(this, &SDL_WinRTApp::OnMouseMoved); #endif window->KeyDown += ref new TypedEventHandler(this, &SDL_WinRTApp::OnKeyDown); window->KeyUp += ref new TypedEventHandler(this, &SDL_WinRTApp::OnKeyUp); } void SDL_WinRTApp::Load(Platform::String^ entryPoint) { } void SDL_WinRTApp::Run() { SDL_SetMainReady(); if (WINRT_SDLAppEntryPoint) { // TODO, WinRT: pass the C-style main() a reasonably realistic // representation of command line arguments. int argc = 0; char **argv = NULL; WINRT_SDLAppEntryPoint(argc, argv); } } void SDL_WinRTApp::PumpEvents() { if (!m_windowClosed) { if (m_windowVisible) { CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent); } else { CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending); } } } void SDL_WinRTApp::Uninitialize() { } void SDL_WinRTApp::OnWindowSizeChanged(CoreWindow^ sender, WindowSizeChangedEventArgs^ args) { #if LOG_WINDOW_EVENTS==1 SDL_Log("%s, size={%f,%f}, current orientation=%d, native orientation=%d, auto rot. pref=%d, WINRT_GlobalSDLWindow?=%s\n", __FUNCTION__, args->Size.Width, args->Size.Height, (int)DisplayProperties::CurrentOrientation, (int)DisplayProperties::NativeOrientation, (int)DisplayProperties::AutoRotationPreferences, (WINRT_GlobalSDLWindow ? "yes" : "no")); #endif WINRT_ProcessWindowSizeChange(); } void SDL_WinRTApp::OnVisibilityChanged(CoreWindow^ sender, VisibilityChangedEventArgs^ args) { #if LOG_WINDOW_EVENTS==1 SDL_Log("%s, visible?=%s, WINRT_GlobalSDLWindow?=%s\n", __FUNCTION__, (args->Visible ? "yes" : "no"), (WINRT_GlobalSDLWindow ? "yes" : "no")); #endif m_windowVisible = args->Visible; if (WINRT_GlobalSDLWindow) { SDL_bool wasSDLWindowSurfaceValid = WINRT_GlobalSDLWindow->surface_valid; if (args->Visible) { SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_SHOWN, 0, 0); } else { SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_HIDDEN, 0, 0); } // HACK: Prevent SDL's window-hide handling code, which currently // triggers a fake window resize (possibly erronously), from // marking the SDL window's surface as invalid. // // A better solution to this probably involves figuring out if the // fake window resize can be prevented. WINRT_GlobalSDLWindow->surface_valid = wasSDLWindowSurfaceValid; } } void SDL_WinRTApp::OnWindowClosed(CoreWindow^ sender, CoreWindowEventArgs^ args) { #if LOG_WINDOW_EVENTS==1 SDL_Log("%s\n", __FUNCTION__); #endif m_windowClosed = true; } void SDL_WinRTApp::OnActivated(CoreApplicationView^ applicationView, IActivatedEventArgs^ args) { CoreWindow::GetForCurrentThread()->Activate(); } static int SDLCALL RemoveAppSuspendAndResumeEvents(void * userdata, SDL_Event * event) { if (event->type == SDL_WINDOWEVENT) { switch (event->window.event) { case SDL_WINDOWEVENT_MINIMIZED: case SDL_WINDOWEVENT_RESTORED: // Return 0 to indicate that the event should be removed from the // event queue: return 0; default: break; } } // Return 1 to indicate that the event should stay in the event queue: return 1; } void SDL_WinRTApp::OnSuspending(Platform::Object^ sender, SuspendingEventArgs^ args) { // Save app state asynchronously after requesting a deferral. Holding a deferral // indicates that the application is busy performing suspending operations. Be // aware that a deferral may not be held indefinitely. After about five seconds, // the app will be forced to exit. SuspendingDeferral^ deferral = args->SuspendingOperation->GetDeferral(); create_task([this, deferral]() { // Send a window-minimized event immediately to observers. // CoreDispatcher::ProcessEvents, which is the backbone on which // SDL_WinRTApp::PumpEvents is built, will not return to its caller // once it sends out a suspend event. Any events posted to SDL's // event queue won't get received until the WinRT app is resumed. // SDL_AddEventWatch() may be used to receive app-suspend events on // WinRT. // // In order to prevent app-suspend events from being received twice: // first via a callback passed to SDL_AddEventWatch, and second via // SDL's event queue, the event will be sent to SDL, then immediately // removed from the queue. if (WINRT_GlobalSDLWindow) { SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_MINIMIZED, 0, 0); // TODO: see if SDL_WINDOWEVENT_SIZE_CHANGED should be getting triggered here (it is, currently) SDL_FilterEvents(RemoveAppSuspendAndResumeEvents, 0); } deferral->Complete(); }); } void SDL_WinRTApp::OnResuming(Platform::Object^ sender, Platform::Object^ args) { // Restore any data or state that was unloaded on suspend. By default, data // and state are persisted when resuming from suspend. Note that this event // does not occur if the app was previously terminated. if (WINRT_GlobalSDLWindow) { SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_RESTORED, 0, 0); // TODO: see if SDL_WINDOWEVENT_SIZE_CHANGED should be getting triggered here (it is, currently) // Remove the app-resume event from the queue, as is done with the // app-suspend event. // // TODO, WinRT: consider posting this event to the queue even though // its counterpart, the app-suspend event, effectively has to be // processed immediately. SDL_FilterEvents(RemoveAppSuspendAndResumeEvents, 0); } } static void WINRT_LogPointerEvent(const char * header, Windows::UI::Core::PointerEventArgs ^ args, Windows::Foundation::Point transformedPoint) { Windows::UI::Input::PointerPoint ^ pt = args->CurrentPoint; SDL_Log("%s: Position={%f,%f}, Transformed Pos={%f, %f}, MouseWheelDelta=%d, FrameId=%d, PointerId=%d, SDL button=%d\n", header, pt->Position.X, pt->Position.Y, transformedPoint.X, transformedPoint.Y, pt->Properties->MouseWheelDelta, pt->FrameId, pt->PointerId, WINRT_GetSDLButtonForPointerPoint(pt)); } void SDL_WinRTApp::OnPointerPressed(CoreWindow^ sender, PointerEventArgs^ args) { #if LOG_POINTER_EVENTS WINRT_LogPointerEvent("pointer pressed", args, WINRT_TransformCursorPosition(WINRT_GlobalSDLWindow, args->CurrentPoint->Position)); #endif WINRT_ProcessPointerPressedEvent(WINRT_GlobalSDLWindow, args->CurrentPoint); } void SDL_WinRTApp::OnPointerMoved(CoreWindow^ sender, PointerEventArgs^ args) { #if LOG_POINTER_EVENTS WINRT_LogPointerEvent("pointer moved", args, WINRT_TransformCursorPosition(WINRT_GlobalSDLWindow, args->CurrentPoint->Position)); #endif WINRT_ProcessPointerMovedEvent(WINRT_GlobalSDLWindow, args->CurrentPoint); } void SDL_WinRTApp::OnPointerReleased(CoreWindow^ sender, PointerEventArgs^ args) { #if LOG_POINTER_EVENTS WINRT_LogPointerEvent("pointer released", args, WINRT_TransformCursorPosition(WINRT_GlobalSDLWindow, args->CurrentPoint->Position)); #endif WINRT_ProcessPointerReleasedEvent(WINRT_GlobalSDLWindow, args->CurrentPoint); } void SDL_WinRTApp::OnPointerWheelChanged(CoreWindow^ sender, PointerEventArgs^ args) { #if LOG_POINTER_EVENTS WINRT_LogPointerEvent("pointer wheel changed", args, WINRT_TransformCursorPosition(WINRT_GlobalSDLWindow, args->CurrentPoint->Position)); #endif WINRT_ProcessPointerWheelChangedEvent(WINRT_GlobalSDLWindow, args->CurrentPoint); } void SDL_WinRTApp::OnMouseMoved(MouseDevice^ mouseDevice, MouseEventArgs^ args) { WINRT_ProcessMouseMovedEvent(WINRT_GlobalSDLWindow, args); } void SDL_WinRTApp::OnKeyDown(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::KeyEventArgs^ args) { WINRT_ProcessKeyDownEvent(args); } void SDL_WinRTApp::OnKeyUp(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::KeyEventArgs^ args) { WINRT_ProcessKeyUpEvent(args); }