From c2243092d7a5248bcdd8624c6b7fc224e67e04c4 Mon Sep 17 00:00:00 2001 From: David Ludwig Date: Sun, 23 Mar 2014 11:04:47 -0400 Subject: [PATCH] WinRT: Miscellaneous app-backgrounding/restoring event fixes and additions SDL_WINDOWEVENT_FOCUS_LOST is now sent when an app's native window is hidden. Likewise, SDL_WINDOWEVENT_FOCUS_GAINED is sent when an app's window is shown. This mimicks behavior seen on iOS and Android. SDL_WINDOWEVENT_MINIMIZED and SDL_WINDOWEVENT_RESTORED are now sent when the app's native window is hidden and shown. Previously, these were sent when an app was suspended and resumed. On Windows 8.x/RT, an app may be sent to the background without being suspended, which previously meant that SDL_WINDOWEVENT_MINIMIZED might never have been sent. (On Windows Phone 8, however, this seems to be different, whereby apps sent to the background appear to always get suspended.) --- src/core/winrt/SDL_winrtapp_direct3d.cpp | 116 ++++++++++++----------- src/core/winrt/SDL_winrtapp_direct3d.h | 2 + 2 files changed, 63 insertions(+), 55 deletions(-) diff --git a/src/core/winrt/SDL_winrtapp_direct3d.cpp b/src/core/winrt/SDL_winrtapp_direct3d.cpp index 49880f2b76b2d..96b539f35efd3 100644 --- a/src/core/winrt/SDL_winrtapp_direct3d.cpp +++ b/src/core/winrt/SDL_winrtapp_direct3d.cpp @@ -418,16 +418,62 @@ void SDL_WinRTApp::Run() } } +static bool IsSDLWindowEventPending(SDL_WindowEventID windowEventID) +{ + SDL_Event events[128]; + const int count = SDL_PeepEvents(events, sizeof(events)/sizeof(SDL_Event), SDL_PEEKEVENT, SDL_WINDOWEVENT, SDL_WINDOWEVENT); + for (int i = 0; i < count; ++i) { + if (events[i].window.event == windowEventID) { + return true; + } + } + return false; +} + +bool SDL_WinRTApp::ShouldWaitForAppResumeEvents() +{ + /* Don't wait if the app is visible: */ + if (m_windowVisible) { + return false; + } + + /* Don't wait until the window-hide events finish processing. + * Do note that if an app-suspend event is sent (as indicated + * by SDL_APP_WILLENTERBACKGROUND and SDL_APP_DIDENTERBACKGROUND + * events), then this code may be a moot point, as WinRT's + * own event pump (aka ProcessEvents()) will pause regardless + * of what we do here. This happens on Windows Phone 8, to note. + * Windows 8.x apps, on the other hand, may get a chance to run + * these. + */ + if (IsSDLWindowEventPending(SDL_WINDOWEVENT_HIDDEN)) { + return false; + } else if (IsSDLWindowEventPending(SDL_WINDOWEVENT_FOCUS_LOST)) { + return false; + } else if (IsSDLWindowEventPending(SDL_WINDOWEVENT_MINIMIZED)) { + return false; + } + + return true; +} + void SDL_WinRTApp::PumpEvents() { - if (!m_windowClosed) - { - if (m_windowVisible) - { + if (!m_windowClosed) { + if (!ShouldWaitForAppResumeEvents()) { + /* This is the normal way in which events should be pumped. + * 'ProcessAllIfPresent' will make ProcessEvents() process anywhere + * from zero to N events, and will then return. + */ CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent); - } - else - { + } else { + /* This style of event-pumping, with 'ProcessOneAndAllPending', + * will cause anywhere from one to N events to be processed. If + * at least one event is processed, the call will return. If + * no events are pending, then the call will wait until one is + * available, and will not return (to the caller) until this + * happens! This should only occur when the app is hidden. + */ CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending); } } @@ -511,8 +557,12 @@ void SDL_WinRTApp::OnVisibilityChanged(CoreWindow^ sender, VisibilityChangedEven if (args->Visible) { SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_SHOWN, 0, 0); + SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0); + SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_RESTORED, 0, 0); } else { SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_HIDDEN, 0, 0); + SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0); + SDL_SendWindowEvent(WINRT_GlobalSDLWindow, SDL_WINDOWEVENT_MINIMIZED, 0, 0); } // HACK: Prevent SDL's window-hide handling code, which currently @@ -538,26 +588,6 @@ void SDL_WinRTApp::OnActivated(CoreApplicationView^ applicationView, IActivatedE 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 @@ -577,24 +607,13 @@ void SDL_WinRTApp::OnSuspending(Platform::Object^ sender, SuspendingEventArgs^ a SuspendingDeferral^ deferral = args->SuspendingOperation->GetDeferral(); create_task([this, deferral]() { - // Send a window-minimized event immediately to observers. + // Send an app did-enter-background 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); - } - SDL_SendAppEvent(SDL_APP_DIDENTERBACKGROUND); deferral->Complete(); @@ -603,24 +622,11 @@ void SDL_WinRTApp::OnSuspending(Platform::Object^ sender, SuspendingEventArgs^ a 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 these events + // do not occur if the app was previously terminated. SDL_SendAppEvent(SDL_APP_WILLENTERFOREGROUND); SDL_SendAppEvent(SDL_APP_DIDENTERFOREGROUND); - - // 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); - } } void SDL_WinRTApp::OnExiting(Platform::Object^ sender, Platform::Object^ args) diff --git a/src/core/winrt/SDL_winrtapp_direct3d.h b/src/core/winrt/SDL_winrtapp_direct3d.h index 6dc9a6c85fc23..714d41c7e2272 100644 --- a/src/core/winrt/SDL_winrtapp_direct3d.h +++ b/src/core/winrt/SDL_winrtapp_direct3d.h @@ -39,6 +39,8 @@ ref class SDL_WinRTApp sealed : public Windows::ApplicationModel::Core::IFramewo void PumpEvents(); protected: + bool ShouldWaitForAppResumeEvents(); + // Event Handlers. #if WINAPI_FAMILY == WINAPI_FAMILY_APP // for Windows 8/8.1/RT apps... (and not Phone apps)