/* GuiFacade - access point for communicating with the Lumiera GTK GUI Copyright (C) Lumiera.org 2008, Hermann Vosseler This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *****************************************************/ /** @file guifacade.cpp ** Implementation of the GUI loader. Code generated from this ** translation unit is linked into the core application, where it implements ** the [Subsystem descriptor](\ref subsys.hpp) for the _UI Subsystem._ When main() ** activates and starts this subsystem, an instance of stage::GuiRunner will be ** created, which causes the Lumiera UI plug-in to be loaded and a new thread ** to be spawned, which launches the UI and performs the event loop. */ #include "stage/guifacade.hpp" #include "include/gui-notification-facade.h" #include "lib/sync.hpp" #include "lib/error.hpp" #include "lib/depend.hpp" #include "common/instancehandle.hpp" #include "common/option.hpp" #include #include namespace stage { using std::string; using std::unique_ptr; using lumiera::Subsys; using lumiera::InstanceHandle; using lib::Sync; using lib::RecursiveLock_NoWait; /** load and start the GUI as a plugin */ struct GuiRunner : util::NonCopyable { typedef InstanceHandle GuiHandle; GuiHandle theGUI_; GuiRunner (Subsys::SigTerm terminationHandle) : theGUI_("lumieraorg_Gui", 1, 1, "lumieraorg_GuiStarterPlugin") // load GuiStarterPlugin { ASSERT (theGUI_); bool res = this->launchUI (terminationHandle); if (!res || lumiera_error_peek()) throw lumiera::error::Fatal{"failed to bring up the GUI", lumiera_error()}; } /* ===== control interface for the GuiStarterPlugin ======= */ /** start the actual GUI thread(s), after successfully loading * the GuiStarterPlugin, that is. The implementation of this function * must ensure to invoke the given termination signal reliably after * shutting down the GUI, otherwise the application will hang on exit. * @internal this function is invoked automatically during the GUI * loading and startup process. Don't call it manually. */ bool launchUI (Subsys::SigTerm& terminationHandle) { return theGUI_->launchUI (reinterpret_cast (&terminationHandle)); } }; namespace { // implementation of GUI-"Subsystem" : start GUI through GuiStarterPlugin unique_ptr facade; class GuiSubsysDescriptor : public lumiera::Subsys, public Sync { operator string() const { return "Lumiera GTK GUI"; } bool shouldStart (lumiera::Option& opts) override { if (opts.isHeadless() or 0 < opts.getPort()) { INFO (guifacade, "*not* starting the GUI..."); return false; } else return true; } bool start (lumiera::Option&, Subsys::SigTerm termNotification) override { Lock guard (this); if (facade) return false; // already started facade.reset ( // trigger loading of the GuiStarterPlugin... new GuiRunner ( [=] (string* problemMessage) { // will be invoked when the UI thread exits closeGuiModule(); termNotification(problemMessage); })); return true; } /** * @warning there is a possible race here, * when shutdown is triggered before the GUI was able to open the GuiNotification interface. * However, the Lumiera thread handling wrapper/framework ensures that a new thread actually * starts to execute (and picks up the arguments), prior to returning from the thread starting * function. For this reason, it is rather unlikely this race actually happens in practice, * since opening the GuiNotification interface is done early, while starting the UI-Bus. */ void triggerShutdown () noexcept override { try { GuiNotification::facade().triggerGuiShutdown ("Application shutdown"); } ERROR_LOG_AND_IGNORE (guifacade, "trigger shutdown of the GUI"); } bool checkRunningState () noexcept override { return bool(facade); } void closeGuiModule () { Lock guard (this); if (!facade) { WARN (guifacade, "Termination signal invoked, but GUI is currently closed. " "Probably this is due to some broken startup logic and should be fixed."); } else facade.reset (nullptr); } public: GuiSubsysDescriptor() { } ~GuiSubsysDescriptor() { if (facade) { WARN (guifacade, "GUI subsystem terminates, but GuiFacade isn't properly closed. " "Closing it forcedly; this indicates broken startup logic and should be fixed."); try { facade.reset (0); } ERROR_LOG_AND_IGNORE (guifacade, "forcibly closing the GUI"); ENSURE (not lumiera_error_peek()); } } }; lib::Depend theDescriptor; } // (End) impl details /** @internal intended for use by main(). */ lumiera::Subsys& GuiFacade::getDescriptor() { return theDescriptor(); } bool GuiFacade::isUp () { return bool(facade); } } // namespace stage