As a fan of multi-protocol IM clients, I have been using Miranda NG for quite some time. But the crooked support of some modern protocols like Discord made it difficult to use only it, although it has very wide customization possibilities. In the end, practicality prevailed over perfectionism, and I installed Pidgin 2.14. Despite being a bit scary, the program turned out to be very practical. However, there was also a fly in the ointment.
As it turned out, the three-level hierarchy of Discord (server – category – channel) does not fit well with the two-level hierarchy of the contact list (group – contact), and the corresponding plugin solved this collision simply – each category was a group whose name contained the server prefix. Of course, this led to the fact that even with my modest social circle, 30+ groups were formed in the contact list – in addition to groups from other protocols. It was not very convenient to navigate in this – I wanted to be able to arrange some kind of hierarchy, for example, to make over-groups.
However, a quick search brought up the old bug tracker Pidginwhere such a possibility was mentioned … and marked as wont-fix. Oops. Well, where ours did not disappear – I will make an imitation myself!
Pidgin mainly supports plugins written in C. There are Perl plugin loaders and a mechanism for interacting with DBus, but C is the main development tool. In addition, of all the languages affected, it is the least unfamiliar to me.
Further, the key idea of the plugin was formulated as follows: hide or combine groups in the contact list according to a certain set of rules. In this case, the sets of rules must be specified by the user, have a readable name and, preferably, an icon. Since the plugin for Discord generated groups of the form “Server name: Category”, the idea of using wildcards in plugins suggested itself, so that you did not have to list all the server categories separately. It also seemed worthwhile to provide for the possibility of specifying several rules in the set, as well as creating negative rules (excluding the group from the display).
Thus, the task was decomposed into components:
Understand how the Pidgin plugin works.
Figure out which means to use for wildcard matching, and implement the logic for applying rules to the group.
Understand how the libpurple (Pidgin client backend) contact list works and how to get group information.
Understand how the display of the contact list in Pidgin works, and how to hide or show a contact in the list.
Understand how the Pidgin GUI works in general, and how to add your own toolbar to it.
Deal with storing settings, and creating a settings dialog for your plugin.
A simple plugin for Pidgin
Fortunately, the repository contains a fairly understandable hello-world.cwhich describes the basic plugin structure for Pidgin. All plugins can be divided into several categories:
core – plugins that work only with libpurple and do not affect the UI in any way.
prpl – plugins that implement messenger protocols.
lopl – loaders for plugins in scripting languages.
gtk, gtk-x11, and gtk-win32 are GUI plugins – generic, Linux-specific, and Windows-specific.
gnt – plugin for the console version of the client (Finch).
Obviously, we are interested in the gtk plugin, at worst gtk-win32.
Both Pidgin itself and the underlying libpurple library are based on GLib and a graphical toolkit GTK – however, quite old versions. Several conclusions follow from this. First, the reference counting mechanism is often used to manage memory, which somewhat simplifies the solution of issues related to the lifetime of objects. Second, GLib provides a signaling mechanism that allows you to fairly transparently implement event handling, including interface events. libpurple actively uses signals to notify client code about events from IM networks, as well as about operations on stored data such as a contact list. Therefore, the question of the reaction to this or that event will be reduced to finding the desired signal and subscribing to it.
Along the way, the task of implementing windcard-matching was solved – GLib presents the corresponding tools, over which only a small logical superstructure was required.
BuddyList and all-all-all
After a thoughtful reading of the documentation (and careful questioning on the semi-official Discord server of the project), the following was revealed. Technically, libpurple’s buddy list is a tree of disparate nodes, similar to the well-known “linker” pattern. This tree is wrapped in an object PurpleBuddyList…
In practice, however, this tree forms a fixed three-level hierarchical structure. At the top level are contact groups, represented by objects PurpleGroup, at the bottom – individual participants (Purplebuddy), and on the intermediate – chats (Purplechat) and contacts (PurpleContact) as such. Items at each level of the tree are linked in a doubly linked list – so the parent only stores a reference to its first child.
In fact, contacts in libpurple are analogous to meta-contacts in other clients – they allow you to combine several accounts of one person into one entity, which is quite convenient. However, this did not help in any way in solving the problem of grouping contact groups. I had to abandon the idea of grouping, and stop at the option with the hiding of “unnecessary” groups.
For this, I had to go through the source code responsible for displaying a contact in the Pidgin graphical interface. And then the so-called contact flags surfaced, among which was the INVISIBLE flag. Items with this flag were not displayed in the contact list regardless of their type. Bingo?
Almost. As it quickly became clear, hiding a contact in a group could result in hiding the entire group. This behavior was a consequence of the logic in the function pidgin_blist_update_group (), which hid the entire group if a hidden contact in it was updated. Why so, I still do not understand. However, this did not mean that this mechanism was unusable – it only limited the plugin to hiding entire groups.
The graphical element of the contact list itself turned out to be simpler and more complicated than I expected. Easier, because it turned out to be just one component – GtkTreeView… More difficult because this component deals with a separate model object (hidden behind the interface GtkTreeModel), and has an intricate mechanism for customizing the rendering of model elements on the screen. As a consequence, to interfere with it enough, I would have to re-implement a solid chunk of Pidgin’s contact list – which would be long, difficult, and poorly compatible with other plugins. As a result, to implement hiding, we had to stop at the INVISIBLE flag described above.
Nevertheless, GtkTreeView came in handy during the implementation of the dialog for editing rules.
As for the rest of the graphical interface, everything turned out to be quite simple. The TreeView is wrapped in several containers, the links to which are stored in publicly available data structure. Thus, by creating your own container, you can add it to the existing ones. But this approach is seriously limited in its capabilities. You can only add a static-sized pane above or below the contact list, but not to the left or right. Plugin mystatusbox from the purple plugin pack adds a resizable panel, but for that it has to shovel half the interface. I was not so confident in my abilities as to do this and not break compatibility with mystatusbox (which I myself used), so I limited myself to a simple panel.
To store settings libpurple implements its own mechanism – a kind of hierarchical database (in practice, it is saved to an XML document). Fortunately, I found a good one example work with her. Although the approach is quite straightforward – there are operations to create, read, set and delete settings, as well as a few basic data types – there are some pitfalls.
First of all, it takes quite cumbersome code to check a situation where a setting exists but is of the wrong type. Adding the missing customization is much easier. Also, the question remains about migration from one configuration scheme to another, for example, when updating a plugin.
Also, the most complex standard data type is a list of strings. Since in my case I needed to deal with a list of data structures, I had to write moderately complex code to deserialize this structure.
Finally, Pidgin provides three mechanisms for changing plugin settings.
First, simplified, allows you to specify a list of records containing the parameter name and its desired representation. Based on this list, Pidgin will automatically generate a settings dialog and bind them to controls.
Second, a little more complex, allows you to set a custom dialog as the settings dialog, and show it instead of the standard one.
The third, most versatile, is to use the mechanism action, i.e. additional menu items added by plugins. Within actions, you can perform almost any operation, including displaying the desired dialog boxes. The only limitation is that these windows will work in parallel with the rest of the program.
As a result, the plugin settings were split into two sections. One describes only the rules for hiding / showing groups, and is placed in the plugin’s action menu – since the user will use it more often. The second describes the appearance and placement of the rule selector, and is hidden in the standard plugin settings dialog. In theory, it is enough to use it once, and then you can forget about it.
The interface for selecting the displayed groups looks like this:
Below the main menu is a panel for selecting rule sets. Each set is associated with a name and an icon for easier understanding. As an example, there are three sets – one shows everything, the second – only the General group, the third – everything except General.
The rules are configured through the settings dialog shown below. Not too pretty, but it does its job.
The plugin itself can be found at Github…
Results of the project
Do I find the plugin useful? It came in handy for me. If the plugin itself, or the information provided in the article is useful to someone else, it will be great.
How difficult was it to enter the topic? Surprisingly simple, despite the fact that I have not previously dealt with development in pure C or GTK. The pidgin codebase is fairly well organized, although the documentation is sometimes poor.
What was the most difficult part of the development? Oddly enough, set up an environment for building a project on Windows. It turned out to be much easier on Linux, so almost all development was done on the Debian home server. Assembly instructions is, but it requires adaptation. As a result, I had to ask for help on the Pidgin Discord server.
Why did I find it necessary to write this article? Because I had to tinker, collecting grains of documentation and wading through dead links, and I wanted to preserve the knowledge I gained.
And although Pidgin is now going through a transition to version 3.0 with the accompanying reworking of the code base, version 2.xy will be relevant for a long time. In general, the situation looks as if multi-protocol clients are in some kind of decline. If this is not the case, write it down in the comments – I will be glad to be mistaken.