LUMIERA.clone/doc/technical/code/gtk/startup.txt

150 lines
9.9 KiB
Text

GTK start-up
============
:Date: 2018
//Menu: label start-up
_some insights regarding the start-up of GTK and related framework aspects_
As outlined on the link:{ldoc}/technical/code/gtk/index.html[overview page], we need to discern between
toolkit aspects, and GTK-the-framework. Moreover, we use GTK though the C++ bindings (`gtkmm`), which also
add a thin layer of software abstractions.
Initialisation
--------------
So in order to ``use GTK'' ...
- we need to build our application against the appropriate headers of GTK\--, which also implies to
install the corresponding development packages (or a source tree) of the libraries involved.
- when our main application decides to use the GTK-UI, it loads the GUI plug-in -- at this point
the dynamic loader also requires and loads the runtime libraries of GTK, Glib, GDL, Cairo, Pango,
plus the runtime libraries of the C++ wrappers (Gtkmm, Glibmm, Gdlmm)
- at this point, the various layers of software comprising ``the GTK'' will require some very
specific initialisation hooks to be invoked. Especially
** GTK itself requires the invocation of `gtk_init()` in order to attach to the windowing system.
This might also parse additional framework specific command lines and react on some environment
variables, init the gettext library and maybe activate interactive UI debugging. But nothing
beyond that
** the `gtkmm` C\++ wrappers deal with a lot of additional ceremony, required by the plain-C
implementation of the GTK core. Especially, they automatically register any ``object type''
with the GObject-system of Glib. Moreover, some virtual function tables will be populated
and several ``object functions'' and signals need to be wrapped, so client code can invoke
the more convenient C++ equivalents. All of this additional initialisation is effected
by invoking `Gtk::Main::init_gtkmm_internals()` or the (equivalent) static function within
`Gtk::Application`
** finally, any features of GTK-the-framework need to be initialised and prepared for use:
*** lifecycle events need to be issued. GTK uses ``signals'' for this purpose
footnote:[not to be confused with the signals on operating system level]
*** registration of document types with the desktop environment
*** the application instance will connect to the D-bus and install the necessary callbacks,
possibly even waiting for an ``activation event''
*** depending on the specific way of _activation_, behaviour and error response of the
application instance need to be controlled in distinct ways -- maybe just rendering
content or even silently terminating altogether after a timeout has passed
For reasons outlined link:{ldoc}/technical/code/gtk/index.html#_the_gtk_application[above],
Lumiera does not need nor use a `Gtk::Application`.footnote:[In fact, most of the framework
functionality is actually handled within the base class `Gio::Application`, which corresponds
to the plain-C type `GApplication`.] Thus, we leave out the third stage and deal with all those
application global aspects through means appropriate to our specific purpose.
- our link:{ldoc}/design/architecture/Subsystems.html[Subsystem runner] boots the UI subsystem
(`stage::GuiFacade`), which in turn loads the UI-plug-in ('target/modules/gtk_gui.lum').
- within this plug-in, the class `stage::GtkLumiera` governs the complete UI lifecycle.
- this class holds a member `stage::ctl::UiManager` -- which is our ``UI main object'' and mimics
the design of the late `Gtk::Main`.
- we inherit from our own `stage::ctrl::ApplicationBase` class, to ensure invocation of all the
initialisation and clean-up functions required by GTK
- we instantiate a class `stage::ctrl::GlobalCtx` to hold and provide all global services used
throughout the UI layer, including population of the menu and global actions
- within the global context, there is also `stage::ctrl::WindowLocator`, to keep track of all
top-level application windows, and to support direct navigation to relevant entities within
the UI, based on _abstracted UI-coordinates_.
- finally, `GtkLumiera` invokes the functions
** `UiManager::createApplicationWindow()` to create the visible interface frame
** `UiManager::performMainLoop()` to activate `gtk_main()` -- the blocking GTK event loop
Running a GTK application
-------------------------
The _event processing_ is what makes an UI application ``live''. However, event processing must
not be confused with graphical presentation. In GTK, it is perfectly valid to create and even
show a widget prior to entering the event loop. Yet it is a very fundamental design decision
within GTK to operate all of the UI concerns *synchronously*, from within a single dedicated
*UI thread*. We consider this a very wise design decision, and expand it to all of Lumiera's
UI concerns, including the UI-Bus; any ``interaction mechanics'' has to happen within this
single thread, and is completely decoupled from all other application functionality like
editing actions and rendering, which happen in separate threads and respond asynchronously
by _dispatching_ their reactions back into the UI event thread.
Due to the shifted scope between the old-style `Gtk::Main` and the corresponding `gtk_main()`
C-function on one side, and the newer, more framework-centric `Gtk::Application`, it might seem
on first sight, that both will perform different tasks. However, a closer look reveals that
during the redesign for GTK-3, the old ``main'' functionality has been _retrofitted_ to rely
on the newer, more generic GIO-Framework. More specifically
- the classical `gtk_main()` without much ado invokes `g_main_loop_run()`, which in turn
immediately starts to ``pull'' the GIO main context by repeatedly invoking `g_main_context_iterate()`
- whereas the `Gtk::Application::run()` invokes `Gio::Application::run()` and from there the new
application-level _main function_ `g_application_run(gobj(), argc, argv)`. Which in turn handles
all the above mentioned framework concerns (e.g. D-Bus registration / activation). Then it
enters `g_main_context_iteration()` which -- similar to `g_main_loop_run()` -- starts ``pulling''
the GIO main context by repeatedly invoking `g_main_context_iterate()`
Thus, once we reach the actual operative state, both paths of activating a GTK application behave
essentially the same.footnote:[This is the state of affairs during the GTK-3 lifetime cycle,
as verified in 8/2018 based on the source code of `GTK 3.22`. Note though that GTK-4 is ``around
the corner'' -- let's see what awesome innovations we have to face then...]
The application activation signal
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
However, the old-style approach seems to lack a feature offered by `Gio::Application`:
the *activation* signal, which is a feature solely present at the ``application-framework'' level
and used internally for the convenience function to ``run an ApplicationWindow object''
(-> `Gtk::Application::run(Gtk::Window&)`). In fact, this happens to be a lifecycle event,
and can be used by connecting a SigC++ slot to the application object's `signal_activation()`.
Outside the realm of GTK-the-framework this feature turns out to be not so useful; especially, the
signal is emitted _shortly before_ entering the event loop, and not from within (as you'd might expect).
A better alternative is to rely on the `Glib::signal_idle()` rsp. `Glib::signal_timeout()`. Both allow
to invoke a slot only once, and both ensure the invocation really happens from within the event loop.
footnote:[Verified as of 8/2018: uses `g_main_context_default()`, which creates the default context
on demand. This is also the default context _pulled_ when running the event loop. So indeed we're
able to attach an ``event source'' (i.e. an action or closure) even before the loop is running.
Use `Glib::PRIORITY_LOW` to schedule after any (re)drawing activities.]
SigC++ trackable
~~~~~~~~~~~~~~~~
Any closure or callback based system of wiring and activation suffers from the inherent danger of invoking
a dangling reference. Within an interactive UI environment, this problem becomes quite acid, since widgets
will be created and destroyed through arbitrary interactions, yet still need to be connected to ``live state''.
When building UI applications with Gtkmm (the C\++ wrapper of GTK), this problem is dealt with by inheriting
all widget classes from the `sigc::trackable` mix-in. This mechanism automatically detaches a signal slot
when the corresponding target widget goes out of scope. However, this solution only works reliably when
all slots are created and connected from within a single thread (the UI event thread!). Moreover, we
can not possibly ``track'' a slot created from a C\++ language lambda or functor -- which sometimes
is even the only option, unless we want to incur a dependency on the SigC++ library. In Lumiera,
we have a strict policy to prohibit any dependency on GTK libraries outside the UI layer.
Shutdown
--------
The GTK main loop terminates after invocation of `gtk_main_quit()` (or in case of serious internal errors).
Typically, this function is somehow bound to a widget interaction, like clicking on the ``close'' button.
In Lumiera, this concern is managed by the `stage::ctrl::WindowLocator`, which keeps track of all top-level
windows and terminates the UI when the last one is closed. Moreover, the UI can deliberately be closed
by sending an event over the `GuiNotification::triggerGuiShutdown(message)' call.
After leaving the main loop, the external façade interfaces are closed. By general architectonic reasoning,
no event can be processed and no signal can be invoked any more -- and thus we're free to destroy all widgets,
services and the backbone of the UI. After that, the UI subsystem signals termination, which causes all other
subsystems to shut down as well.footnote:[We are aware that there is a race between closing the façade and actually
ceasing other subsystem's activities, which might cause those other activities to fail with an exception]