From 3ca13dbc1ba54b497f52b86c9ac38bfa6533dc79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 15 Dec 2018 21:45:36 +0100 Subject: [PATCH] Platform: implement {Glfw,Sdl2}Application::exitEvent(). SDL has to be special and behave weirdly, of course, as always. --- doc/changelog.dox | 6 +- src/Magnum/Platform/GlfwApplication.cpp | 9 +++ src/Magnum/Platform/GlfwApplication.h | 51 ++++++++++++ src/Magnum/Platform/Sdl2Application.cpp | 29 +++++-- src/Magnum/Platform/Sdl2Application.h | 79 ++++++++++++++++++- .../Platform/Test/GlfwApplicationTest.cpp | 6 ++ .../Platform/Test/Sdl2ApplicationTest.cpp | 5 ++ 7 files changed, 176 insertions(+), 9 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 0d52d3c5a..4fe681949 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -80,13 +80,17 @@ See also: @ref Platform::AndroidApplication::ViewportEvent::dpiScaling(), @ref Platform::GlfwApplication::ViewportEvent::framebufferSize() and @ref Platform::GlfwApplication::ViewportEvent::dpiScaling() to ensure - feature parity with @ref Sdl2Application + feature parity with @ref Platform::Sdl2Application - Clarified HiDPI behavior of Android apps and mentioning @ref platforms-android-apps-manifest-screen-compatibility-mode "the necessary steps" in the docs - Implemented handling of @ref Platform::AndroidApplication::viewportEvent() and documenting how to get Android to call it instead of relaunching the app from scratch +- New @ref Platform::Sdl2Application::exitEvent() and + @ref Platform::GlfwApplication::exitEvent() events for responding to + application window close and possibility to cancel it (for example to + show an exit confirmation dialog) @subsection changelog-latest-changes Changes and improvements diff --git a/src/Magnum/Platform/GlfwApplication.cpp b/src/Magnum/Platform/GlfwApplication.cpp index 17c4de39d..7698e25b5 100644 --- a/src/Magnum/Platform/GlfwApplication.cpp +++ b/src/Magnum/Platform/GlfwApplication.cpp @@ -454,6 +454,11 @@ bool GlfwApplication::tryCreate(const Configuration& configuration, const GLConf void GlfwApplication::setupCallbacks() { glfwSetWindowUserPointer(_window, this); + glfwSetWindowCloseCallback(_window, [](GLFWwindow* const window){ + ExitEvent e; + static_cast(glfwGetWindowUserPointer(window))->exitEvent(e); + if(!e.isAccepted()) glfwSetWindowShouldClose(window, false); + }); glfwSetWindowRefreshCallback(_window, [](GLFWwindow* const window){ /* Properly redraw after the window is restored from minimized state */ static_cast(glfwGetWindowUserPointer(window))->drawEvent(); @@ -577,6 +582,10 @@ auto GlfwApplication::MouseScrollEvent::modifiers() -> Modifiers { return *_modifiers; } +void GlfwApplication::exitEvent(ExitEvent& event) { + event.setAccepted(); +} + void GlfwApplication::viewportEvent(ViewportEvent& event) { #ifdef MAGNUM_BUILD_DEPRECATED CORRADE_IGNORE_DEPRECATED_PUSH diff --git a/src/Magnum/Platform/GlfwApplication.h b/src/Magnum/Platform/GlfwApplication.h index 29e0b4764..ea1132d76 100644 --- a/src/Magnum/Platform/GlfwApplication.h +++ b/src/Magnum/Platform/GlfwApplication.h @@ -149,6 +149,7 @@ class GlfwApplication { #ifdef MAGNUM_TARGET_GL class GLConfiguration; #endif + class ExitEvent; class ViewportEvent; class InputEvent; class KeyEvent; @@ -426,6 +427,17 @@ class GlfwApplication { #else private: #endif + /** + * @brief Exit event + * + * If implemented, it allows the application to react to an application + * exit (for example to save its internal state) and suppress it as + * well (for example to show a exit confirmation dialog). The default + * implementation calls @ref ExitEvent::setAccepted() on @p event, + * which tells the application that it's safe to exit. + */ + virtual void exitEvent(ExitEvent& event); + /** * @brief Viewport event * @@ -1130,6 +1142,45 @@ class GlfwApplication::Configuration { CORRADE_ENUMSET_OPERATORS(GlfwApplication::Configuration::WindowFlags) +/** +@brief Exit event + +@see @ref exitEvent() +*/ +class GlfwApplication::ExitEvent { + public: + /** @brief Copying is not allowed */ + ExitEvent(const ExitEvent&) = delete; + + /** @brief Moving is not allowed */ + ExitEvent(ExitEvent&&) = delete; + + /** @brief Copying is not allowed */ + ExitEvent& operator=(const ExitEvent&) = delete; + + /** @brief Moving is not allowed */ + ExitEvent& operator=(ExitEvent&&) = delete; + + /** @brief Whether the event is accepted */ + bool isAccepted() const { return _accepted; } + + /** + * @brief Set event as accepted + * + * If the event is ignored (i.e., not set as accepted) in + * @ref exitEvent(), the application won't exit. Default implementation + * of @ref exitEvent() accepts the event. + */ + void setAccepted(bool accepted = true) { _accepted = accepted; } + + private: + friend GlfwApplication; + + explicit ExitEvent(): _accepted(false) {} + + bool _accepted; +}; + /** @brief Viewport event diff --git a/src/Magnum/Platform/Sdl2Application.cpp b/src/Magnum/Platform/Sdl2Application.cpp index ea51f69d3..320eef02b 100644 --- a/src/Magnum/Platform/Sdl2Application.cpp +++ b/src/Magnum/Platform/Sdl2Application.cpp @@ -91,6 +91,12 @@ Sdl2Application::Sdl2Application(const Arguments& arguments, NoCreateT): args.parse(arguments.argc, arguments.argv); #endif + /* Available since 2.0.4, disables interception of SIGINT and SIGTERM so + it's possible to Ctrl-C the application even if exitEvent() doesn't set + event.setAccepted(). */ + #ifdef SDL_HINT_NO_SIGNAL_HANDLERS + SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1"); + #endif /* Available since 2.0.8, disables compositor bypass on X11, which causes flickering on KWin as the compositor gets shut down on every startup */ #ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR @@ -713,13 +719,18 @@ void Sdl2Application::mainLoopIteration() { textEditingEvent(e); } break; - case SDL_QUIT: - #ifndef CORRADE_TARGET_EMSCRIPTEN - _flags |= Flag::Exit; - #else - emscripten_cancel_main_loop(); - #endif - return; + case SDL_QUIT: { + ExitEvent e; + exitEvent(e); + if(e.isAccepted()) { + #ifndef CORRADE_TARGET_EMSCRIPTEN + _flags |= Flag::Exit; + #else + emscripten_cancel_main_loop(); + #endif + return; + } + } break; } } @@ -795,6 +806,10 @@ void Sdl2Application::setTextInputRect(const Range2Di& rect) { SDL_SetTextInputRect(&r); } +void Sdl2Application::exitEvent(ExitEvent& event) { + event.setAccepted(); +} + void Sdl2Application::tickEvent() { /* If this got called, the tick event is not implemented by user and thus we don't need to call it ever again */ diff --git a/src/Magnum/Platform/Sdl2Application.h b/src/Magnum/Platform/Sdl2Application.h index 89e5bf8b8..11b73313b 100644 --- a/src/Magnum/Platform/Sdl2Application.h +++ b/src/Magnum/Platform/Sdl2Application.h @@ -239,13 +239,35 @@ If no other application header is included, this class is also aliased to @cpp Platform::Application @ce and the macro is aliased to @cpp MAGNUM_APPLICATION_MAIN() @ce to simplify porting. +@subsection Platform-Sdl2Application-usage-posix POSIX specifics + +On POSIX systems, SDL by default intercepts the `SIGTERM` signal and generates +an exit event for it, instead of doing the usual application exit. This would +mean that if the application fails to set @ref ExitEvent::setAccepted() in an +@ref exitEvent() override for some reason, pressing +@m_class{m-label m-warning} **Ctrl** @m_class{m-label m-default} **C** would +not terminate it either and you'd have to forcibly kill it instead. When using +SDL >= 2.0.4, @ref Sdl2Application turns this behavior off, making +@ref exitEvent() behave consistently with other application implementations +such as @ref GlfwApplication. You can turn this behavior back on by enabling +the [corresponding SDL hint](https://wiki.libsdl.org/SDL_HINT_NO_SIGNAL_HANDLERS) +through an environment variable: + +@code{.sh} +SDL_NO_SIGNAL_HANDLERS=1 ./your-app +@endcode + +See also the [SDL Wiki](https://wiki.libsdl.org/SDL_EventType#SDL_QUIT) for +details. + @subsection Platform-Sdl2Application-usage-linux Linux specifics SDL by default attempts to disable compositing, which may cause ugly flickering for non-fullscreen apps (KWin, among others, is known to respect this setting). When using SDL >= 2.0.8, @ref Sdl2Application turns this behavior off, keeping the compositor running to avoid the flicker. You can turn this behavior back on -by enabling the [corresponding SDL hint](https://wiki.libsdl.org/CategoryHints) through an environment variable: +by enabling the [corresponding SDL hint](https://wiki.libsdl.org/CategoryHints) +through an environment variable: @code{.sh} SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR=1 ./your-app @@ -381,6 +403,7 @@ class Sdl2Application { #ifdef MAGNUM_TARGET_GL class GLConfiguration; #endif + class ExitEvent; class ViewportEvent; class InputEvent; class KeyEvent; @@ -715,6 +738,21 @@ class Sdl2Application { #else private: #endif + /** + * @brief Exit event + * + * If implemented, it allows the application to react to an application + * exit (for example to save its internal state) and suppress it as + * well (for example to show a exit confirmation dialog). The default + * implementation calls @ref ExitEvent::setAccepted() on @p event, + * which tells the application that it's safe to exit. + * + * SDL has special behavior on POSIX systems regarding `SIGINT` and + * `SIGTERM` handling, see @ref Platform-Sdl2Application-usage-posix + * for more information. + */ + virtual void exitEvent(ExitEvent& event); + /** * @brief Tick event * @@ -1606,6 +1644,45 @@ class Sdl2Application::Configuration { #endif }; +/** +@brief Exit event + +@see @ref exitEvent() +*/ +class Sdl2Application::ExitEvent { + public: + /** @brief Copying is not allowed */ + ExitEvent(const ExitEvent&) = delete; + + /** @brief Moving is not allowed */ + ExitEvent(ExitEvent&&) = delete; + + /** @brief Copying is not allowed */ + ExitEvent& operator=(const ExitEvent&) = delete; + + /** @brief Moving is not allowed */ + ExitEvent& operator=(ExitEvent&&) = delete; + + /** @brief Whether the event is accepted */ + bool isAccepted() const { return _accepted; } + + /** + * @brief Set event as accepted + * + * If the event is ignored (i.e., not set as accepted) in + * @ref exitEvent(), the application won't exit. Default implementation + * of @ref exitEvent() accepts the event. + */ + void setAccepted(bool accepted = true) { _accepted = accepted; } + + private: + friend Sdl2Application; + + explicit ExitEvent(): _accepted(false) {} + + bool _accepted; +}; + /** @brief Viewport event diff --git a/src/Magnum/Platform/Test/GlfwApplicationTest.cpp b/src/Magnum/Platform/Test/GlfwApplicationTest.cpp index 4529a843b..b00925530 100644 --- a/src/Magnum/Platform/Test/GlfwApplicationTest.cpp +++ b/src/Magnum/Platform/Test/GlfwApplicationTest.cpp @@ -29,6 +29,12 @@ namespace Magnum { namespace Platform { namespace Test { struct GlfwApplicationTest: Platform::Application { explicit GlfwApplicationTest(const Arguments& arguments): Platform::Application{arguments} {} + + void exitEvent(ExitEvent& event) override { + Debug{} << "application exiting"; + event.setAccepted(); /* Comment-out to test app exit suppression */ + } + void drawEvent() override {} }; diff --git a/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp b/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp index 0618bde32..5b0d5b4ca 100644 --- a/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp +++ b/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp @@ -31,6 +31,11 @@ struct Sdl2ApplicationTest: Platform::Application { /* For testing resize events */ explicit Sdl2ApplicationTest(const Arguments& arguments): Platform::Application{arguments, Configuration{}.setWindowFlags(Configuration::WindowFlag::Resizable)} {} + void exitEvent(ExitEvent& event) override { + Debug{} << "application exiting"; + event.setAccepted(); /* Comment-out to test app exit suppression */ + } + void drawEvent() override {} /* For testing HiDPI resize events */