This article was originally posted on Patreon and has been brought over here to get all of the Pidgin Development/History posts into one single place.
In Purple 2 and earlier we had this concept of "sub-systems" which were responsible for object type signals and basic collection APIs. For example, the accounts sub-system in purple 2 keeps track of all accounts. It was also responsible for registering signals for accounts being enabled and disabled among others. While these sub-systems worked, they didn't come without issues.
The collection aspects of the sub-systems worked on file level global variables which made unit testing basically impossible as there was no easy way to reset everything to a known state. Also the "objects" in the collections were just pointers to allocated C structures, so there was no reference counting, and well you had to be very careful.
In Purple 3 we're moving all of these objects to GObject
s. That means they have reference counting, properties, can possibly be sub-classed, among other things. But this actually leads to a problem with the old sub-systems and all the code that uses it.
As mentioned earlier, the managers keep a collection of objects. In Purple 2 things were wired up in a way so that when an account was created the subsystem already knew about it. This is very different in Purple 3 with the managers. It is now the responsibility of the creator of the object, to add the object to the manager.
All managers have add/remove
or register/unregister
functions to add the object to the manager. The naming difference is still up for debate and we may make them all consistent in the future. The managers will then also emit signals when an object is added/registered
or removed/unregistered
. For iteration, we originally went with a foreach
function for each manager, but those are slow being replaced as we're implementing the GListModel
interface in the managers instead.
With GObject
s, signals are instance based, which means you connect to the signal for each instance. In Purple 2 it was type/class based because we want to know when any account is enabled, not just this one specific account. That's where the managers come in. Aside from the collection API of the managers, they also propagate signals from each item in their collection. This allows us to get the Purple 2 behavior while using GObject
s.
Let's look at an example in both Purple 2 and Purple 3. This example is stripped down, but in the load function for a plugin we're going to connect to the account-enabled
signal and output the account username via purple_debug_misc
Below is the Purple 2 example. In the plugin_load
function, we get a handle to the accounts subsystem and connect to the account-enabled
and account-disabled
signals. In the callback handler, we log the user name and whether or not the account is enabled.
static void
enabled_cb(PurpleAccount *account, gpointer data) {
purple_debug_misc("example", "account %s enabled:%d\n",
purple_account_get_username(account),
purple_account_get_enabled(account));
}
static gboolean
plugin_load(PurplePlugin *plugin) {
gpointer handle= purple_accounts_get_handle();
purple_signal_connect(handle, "account-enabled", plugin,
PURPLE_CALLBACK(enabled_cb), NULL);
purple_signal_connect(handle, "account-disabled", plugin,
PURPLE_CALLBACK(enabled_cb), NULL);
return TRUE;
}
In Purple 3 this looks a bit different because of GObject
s and PurpleAccountManager
, but the idea is fundamentally the same. In the plugin_load
function we use the account-changed
signal with a detail of enabled
. The account-changed signal propagates the notify
signal from the contained PurpleAccount
objects, and since the enabled
property is a registered property on a PurpleAccount
, that means the notify
signal will be emitted when the property is changed, which PurpleAccountManager
will propagate for every PurpleAccount
that it knows about!
static void
enabled_cb(GObject *obj, PurpleAccount *account,
GParamSpec *pspec, gpointer data)
{
PurpleContactInfo *info = PURPLE_CONTACT_INFO(account);
purple_debug_misc("example", "account %s enabled:%d",
purple_contact_info_get_username(info),
purple_account_get_enabled(account));
}
static gboolean
plugin_load(GPluginPlugin *plugin, GError **error) {
PurpleAccountManager *manager = NULL;
manager = purple_account_manager_get_default();
g_signal_connect(manager, "account-changed::enabled",
G_CALLBACK(enabled_cb), NULL);
return TRUE;
}
As you can see, the Purple 3 approach is going to be a lot more familiar to other GLib/GObject developers and it also completely eliminates our need for our own signaling system.
The other benefit of the manager instances is that we can now create a brand new instance of them in each unit test which means we can actually start writing unit test for all of these objects! And that's great news for everyone!!
I hope you're enjoying these posts! Remember they go live for patron's at 9AM CST on Mondays and go public at 12AM CST on Thursdays! If there's something specific you'd like to see me cover here, please comment below!
Top comments (0)