Signals
GObject signals are a system1 for registering handlers for specific events.
In peel, any type of callable can be a signal handler. The first argument of a handler is a pointer to the object emitting the signal. The return type and any other arguments of the handler are defined by the signal.
Note
The type of the handler’s first argument is the type that defined the signal. For example, if the signal was defined in a base class and you connect to an instance of a derived class, the first argument of the handler is a pointer to a base class instance. This pointer can be
castto the derived class.For example, the
clickedsignal is defined onGtk::Button. Even thoughGtk::LinkButtoninherits the signal, the first argument of any handler toclickedis aGtk::Button *.#include <peel/Gtk/Gtk.h> #include <peel/signal.h> using namespace peel; RefPtr<Gtk::LinkButton> link_button = Object::create<Gtk::LinkButton> (); link_button->set_uri ("https://bugaevc.pages.gitlab.gnome.org/peel/"); link_button->set_label ("peel documentation"); link_button->connect_clicked( [] (Gtk::Button *button) { g_print ("The link was visited.\n"); button->cast<Gtk::LinkButton> ()->set_visited (true); });
Handlers are connected to signals by calling
.connect_signal_name () on the object emitting the signal.
There are two different ways to call .connect_signal_name ():
- Pass a GObject and member function.
- Pass a C++ callable (like a lambda or function pointer).
Using a member function is the recommended way because your handler will automatically be disconnected when it is finalized.
The result of .connect_signal_name () can be assigned to a
SignalConnection which can be used to disconnect or block the handler.
You can define your own signals in a custom GObject. Read the page on defining signals for details.
Connecting GObject member functions
If your signal is handled by a custom GObject class, this is the recommended way to connect to the signal.
Define a member function on your class, and then call
.connect_signal_name () passing in
- a pointer to the class instance,
- a pointer to the member function that handles the signal.
This example of a window with a button and a label, shows how to use a member function as a signal handler:
#include <peel/Gtk/Gtk.h>
#include <peel/GLib/MainContext.h>
#include <peel/class.h>
#include <cstring>
using namespace peel;
class MyWindow final : public Gtk::Window
{
PEEL_SIMPLE_CLASS (MyWindow, Gtk::Window)
RefPtr<Gtk::Button> m_button;
RefPtr<Gtk::Label> m_label;
int m_times_clicked;
void
init (Class *);
void
vfunc_dispose ();
void
on_button_clicked (Gtk::Button *);
};
/* ... */
PEEL_CLASS_IMPL (MyWindow, "MyWindow", Gtk::Window)
void
MyWindow::Class::init ()
{
override_vfunc_dispose<MyWindow> ();
}
void
MyWindow::vfunc_dispose ()
{
m_button = nullptr;
m_label = nullptr;
parent_vfunc_dispose<MyWindow> ();
}
void
MyWindow::on_button_clicked (Gtk::Button *)
{
char label_text[64];
snprintf (label_text, sizeof (label_text),
"The button was clicked %d times", ++m_times_clicked);
m_label->set_label (label_text);
}
void
MyWindow::init (Class *)
{
FloatPtr<Gtk::Box> box = Gtk::Box::create (Gtk::Orientation::VERTICAL, 6);
m_button = Gtk::Button::create_with_label ("Click me");
m_label = Gtk::Label::create ("The button was clicked 0 times.");
box->append (m_label);
box->append (m_button);
set_child (std::move (box));
/* Allocate the button and label ... */
m_button->connect_clicked (this, &MyWindow::on_button_clicked);
}
int
main ()
{
Gtk::init ();
bool should_stop = false;
MyWindow *window = Object::create<MyWindow> ();
window->connect_destroy (
[&should_stop] (Gtk::Widget *)
{
should_stop = true;
});
window->present ();
GLib::MainContext *main_context = GLib::MainContext::default_ ();
while (!should_stop)
main_context->iteration (true);
}
This method only works when the class is derived from GObject::Object.
Ordinary C++ classes and classes derived from other types in the GObject type
system such as Dex::Object won’t work.
Connecting using member functions is recommended for two lifetime safety reasons:
- The signal runtime will keep the object alive while the handler is running by adding an additional reference before the handler is called.
- The signal handler is automatically disconnected when the object is destructed, preventing use-after-frees.
Connecting C++ callables
A signal handler can be any C++ callable (any object with an
operator ()). This includes lambdas, plain functions, std::function, etc.
Although handlers in peel do not include gpointer user_data as their last
argument, a lambda can capture any variables you want the handler to access.
The lambda can capture a variable of any size and can capture multiple variables.
Here is the same example from previous section using a lambda instead:
#include <peel/Gtk/Gtk.h>
#include <peel/GLib/MainContext.h>
#include <peel/class.h>
#include <cstring>
using namespace peel;
int
main ()
{
Gtk::init ();
FloatPtr<Gtk::Box> box = Gtk::Box::create (Gtk::Orientation::VERTICAL, 6);
RefPtr<Gtk::Button> button = Gtk::Button::create_with_label ("Click me");
RefPtr<Gtk::Label> label = Gtk::Label::create ("The button was clicked 0 times.");
box->append (label);
box->append (button);
bool should_stop = false;
Gtk::Window *window = Gtk::Window::create ();
window->set_child (std::move (box));
button->connect_clicked (
[label, count = 0] (Gtk::Button *) mutable
{
char label_text[64];
snprintf (label_text, sizeof(label_text),
"The button was clicked %d times", ++count);
label->set_label (label_text);
});
window->connect_destroy (
[&should_stop] (Gtk::Widget *)
{
should_stop = true;
});
window->present ();
GLib::MainContext *main_context = GLib::MainContext::default_ ();
while (!should_stop)
main_context->iteration (true);
}
Warning
Signal handlers connected this way do not automatically disconnect when a capture variable is destructed. This can cause use-after-free vulnerabilities if the object emitting the signal outlasts the lambda capture 2.
To avoid the possibility of a use-after-free you can either:
- Use a member function as the handler instead.
- Manually disconnect the signal handler using a
SignalConnection.
SignalConnection
A SignalConnection is used to disconnect a signal handler
either manually or automatically when it is destructed.
Before a SignalConnection can be used, it must be assigned
to the result of a .connect_signal_name () call.
SignalConnection connection = button->connect_clicked (some_function);
Disconnecting a handler
The SignalConnection will automatically disconnect the signal handler
when the SignalConnection is destructed. This makes it very convenient to
have a SignalConnection as a member variable of a custom GObject. When
the object is finalized, the handler is disconnected.
You can also manually disconnect the handler by calling .disconnect ()
on the SignalConnection.
Warning
If you both:
- connect to a signal using a GObject and member function,
- save that connection to a
SignalConnectionmember variable,then, there are two mechanisms to disconnect the handler when the object destructs. One will disconnect the handler, and the other will try to disconnect an already disconnected handler. This is an error, so a critical warning is emitted reading:
g_signal_handler_disconnect: assertion 'G_TYPE_CHECK_INSTANCE (instance)' failedTo work around this issue, manually disconnect the handler in the object’s
disposefunction.class MyObject final : public Object { PEEL_SIMPLE_CLASS (MyObject, Object) SignalConnection m_connection; RefPtr<Gtk::Button> m_button; inline void on_clicked (Gtk::Button *); inline void init (Class *); } inline void MyObject::init (Class *) { m_button = Object::create<Gtk::Button> (); m_connection = m_button->connect_clicked (this, &MyObject::on_clicked); } inline void MyObject::vfunc_dispose () { /* Disconnecting manually avoids a double disconnect. */ m_connection.disconnect (); parent_vfunc_dispose<MyObject> (); }
Blocking a handler
Blocking temporarily stops a handler from running. This is commonly useful when you programmatically want to set a property on an object, but you don’t want your handler to run because of the change.
To block a handler, call .block () on your SignalConnection.
This returns a SignalBlockGuard.
When the SignalBlockGuard destructs, or when .unblock () is called on it,
the handler is unblocked.
It is recommended to block the handler in a syntatic block (inside a { })
so that the handler is automatically unblocked at the end of the block.
The SignalBlockGuard returned from .block () must be stored in a variable.
Otherwise, it will immediately destruct and unblock.
This example shows how to change the value of a spin button without invoking a value-changed handler:
#include <peel/Gtk/Gtk.h>
#include <peel/signal.h>
using namespace peel;
inline void
some_function (Gtk::SpinButton *spin_button)
{
g_print ("The value of the spin button is %d.\n",
spin_button->get_value_as_int ());
}
RefPtr<Gtk::SpinButton> spin_button = /* ... */;
SignalConnection connection = spin_button->connect_value_changed (some_function);
{
SignalBlockGuard guard = connection.block ();
spin_button->set_value (42.0); /* some_function is not called. */
} /* The handler is unblocked when guard is destructed. */
Detailed signals
Detailed signals allow handlers to selectively respond to some events but not
others. When connecting to a detailed signal, a handler can optionally be
associated with a detail. When the signal is emitted, a detail
is also emitted, and the handler will only run if the two details match.
In peel, detailed signals support passing in the detail as the first argument
to .connect_signal_name (). The type of the detail
depends on the signal.
The most common detailed signal is GObject::notify. It is emitted when a
property changes on a GObject. The detail argument is a Property<T>
representing the property which changed.
This example shows how to track when the selected entry of a Gtk::DropDown changes:
#include <peel/Gtk/Gtk.h>
#include <peel/signal.h>
#include <peel/GObject/Object.h>
#include <peel/GObject/ParamSpec.h>
using namespace peel;
RefPtr<Gtk::DropDown> dropdown = /* ... */;
dropdown->connect_notify (
Gtk::DropDown::prop_selected (), /* Only run the callback when the */
/* property named "selected" changes. */
[] (Object *obj, peel::GObject::ParamSpec *pspec)
{
int selected = obj->cast<Gtk::DropDown> ()->get_selected ();
g_print ("The index of the selected item is %d.\n", selected);
});
Dynamic signal connection
Dynamic signal connection allows you to connect to a signal by its name.
You call .connect_signal () passing in the name
of the signal as a string in kebab-case and the handler.
#include <peel/Gtk/Gtk.h>
#include <peel/GLib/MainContext.h>
#include <peel/class.h>
RefPtr<Gtk::Button> button = /* ... */;
button->connect_signal ("clicked",
[] (Gtk::Button *)
{
g_print ("The button was clicked.\n");
});
Passing a misspelled signal name or a handler with an incorrect signature
will cause runtime errors rather than compile time errors. Unless you have a
specific reason to use .connect_signal (), you should use the static
.connect_signal_name ()functions instead.
-
GObject signals are similar to Qt signals and slots. ↩
-
Michael Catanzaro’s blog post titled “Common GLib Programming Errors” explains more about this problem. ↩