Gajim's Plug-in system
Any suggestions, questions or requests? Ways you can contact [Mateusz Biliński (aka vArDo)]:
- e-mail me: email@example.com
- jabber me: firstname.lastname@example.org
- poke me at email@example.com
- post on Gajim's development list
Ad-hoc written tasks list for plugin system:
- plugin init failure (check plugin runtime requirements, eg. import errors when module is specific only to one OS) - proposed solution: raise custom exception which error info (will be catched by plugin system and displayed to user)
- outgoing messages hook
- replacing incoming messages stream (rewrite core to use events system in common with plugin system - no events leakage); probably the hardest thing to do as this is operation on live body; would be great to have a detailed schedule on this and and regression tests (this would be perfect)
- merge with trunk; possible important point to consider since branch was created: 1) sessions 2) mods in xmpppy 3) BOSH
- available plugins installation from repository hosted by Gajim project; Capuchin would a nice solution but it looks like it's DBUS dependent, which makes it pretty useless under Win32, where no mature DBUS implementation is available; we'll probably end up with writing something own, as it does not seem to be very difficult
- consider egg format for plugins; optional or mandatory? on one hand it would make plugin system more standardized on the other it will probably make harder for beginners to write plugins
- draw diagrams for flows (hand drawn should be OK as temporary solution)
- side task: evaluate plugin system performance (compare speed of old and new approach to events)
- side task: tickets dependencies on Trac
- more plugin ideas/demos
- documentation: 1) events documentation (what arguments go where) 2) examples of GUI manipulation 3) ...
- divide tasks for 0.13 and 0.14/later
- rewrite above list to Trac system
Reports on project's progress. Apart from changesets these represent the most up-to-date state of implementation and issues I'm currently working on.
- GSoC 2008 - So, I’m accepted. Quick report and initial thoughts.
- GSoC 2008 - Gajim loads plug-ins. Report #1
- GSoC 2008 - Gajim has GUI for plug-ins management. Report #2
- GSoC 2008 - Configs, refactoring and more examples. Report #3
Notes and thoughts
Concept of plug-in system in Gajim (proposal)
Plug-in system should consist of elements that will give plug-in developer ability to:
- parse particular type of message when it occurs in Gajim. This called event handling, also referred to as data flow hooks (in some parts of this page).
- show plug-in in GUI, i.e. chat window, context menu of contacts in roster. These places in GUI are known as GUI extension points.
- take any action that Gajim's core currently can take: pop-up notifications, play sound, modify roster, modify conversation history, etc. Also use-case of long-running handlers should be included (generating new thread).
- save and load configuration of plug-in. Also presenting configuration dialogs to end-user.
Above elements should meet requirements of example plug-ins.
From Gajim's user side, there should be:
- tool for managing plug-ins. This includes (de)activating plug-ins and installing new ones. Using auto-updated list of available plug-ins (centralized repository) is considered as very useful enhancement.
Event handling (proposal)
Event classes hierarchy
First of all: incoming and outgoing messages are separated in hierarchy, as it is a rare case to react on particular type of messages and not give concern whether it is coming in or out of Gajim (in short: react to message type not message direction). Also, in current concept, outgoing and incoming messages will be handled by semi-separate mechanism (as shown on overview diagram).
Question: how to handle messages of type that is not in 'hardcoded hierachy' (i.e. PEP messages)?
- plug-in registers for handling IncomingEvent, and checks it self whether the message is PEP event. Pros: easy to implement in core (all work left to plug-in dev). Cons: big performance load (plug-in must parse each incoming message for his own, probably only small percentage will accepted for further parsing; if we have more plug-ins that want to handle PEP then plug-ins are repeating same operation unnecessarily)
- mechanism for plug-ins to add new Event subclasses into hierarchy and later register for handling it. These subclasses would have to detect such event. Pros: huge flexibility. Cons: harder to implement than the above solution. However, Observer Pattern, proposed by Steve-e, would generally deal with it.
- mechanism for plug-ins to register for handling messages from particular namespace (proposed by Steve-e). Pros: easier to implement than (2) and solves (at least in high percentage) performance issues from (1). Cons: namespace name would be the only filtering criteria, so it's less flexible than (2). There may also be a hierarchy problem - do namespaces in XMPP have clear hierarchy? Would registering for handling base class (in sense of 'base namespace') also register for handling subclasses (here: 'more specialized namespaces').
Diagrams showing event handling concepts:
GUI extension points (notes)
There's lot's of places to plug-in into GUI. Most of them should be available to plug-in developers.
Proposed GUI extension points:
- contact's context menu (in roster)- possible place for user defined actions, i.e. sending SMS or some Outlook/Evolution? actions.
- group's context menu (in roster)
- context menu of text part in chat/MUC window (invoking actions on text selected by user)
- chat/MUC window toolbar (next to Emoticons menu, Action menu and send button; also in Action menu)
- context menu of tray icon
- account's context menu (in roster)
- main menu at the top of roster (Actions? Edit? View? Help?)
- tooltips (in roster) - displaying some data calculated by plug-in
- tooltip of tray icon
Big question: what about keyboard shortcuts? should plug-ins be able to register for using one? or maybe plug-ins should expose some actions to end-users so that they can assign these to key-sequences of their choice (i.e. such action could be toggling visibility of some elements in GUI)? This can be probably solved giving plug-ins ability to connect to GTK+ events.
This is related to elements of GUI generated on-the-fly (i.e. some context menus) but does not describe modifications of static widgets (i.e. roster window).
This pragraph describes proposed way of defining GUI extension points and handling them by plug-ins.
Let's say plug-in wants to add some action menuitem in context menu when multiple contacts are selected in roster. In this case RosterWindow.make_multiple_contact_menu(self, event, iters) is called.
The idea is to add in this method (in example here, just before showing context menu) a code that will iterate through all plug-ins that want to modify this menu in some way. This could look like this:
plugin_manager.set_gui_extension_point("make_multiple_contact_menu", menu, account, list_)
- "make_multiple_contact_menu" is GUI extension point name
- menu is a gtk.Menu object that is our context menu, that will be displayed
- account is a string that holds account name" #. If selected contacts belong to different accounts this is None.
- list_ is list of tuples (contact, account) where account is the same as above and contact is Contact class object
On plug-in side, developer would have to add method that is named exactly as GUI extension point. In our case make_multiple_contact_menu. Each extension point will be clearly defined in API, so our method must have exactly the same arguments as passed to extension point.
If we would like to add new menuitem with action, we could do this by defining such method in our plug-in:
def make_multiple_contact_menu(menu, account, list_) our_new_action_item = gtk.ImageMenuItem(_('Our New A_ction')) # line below is just to show how to add icon to menuitem icon = gtk.image_new_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_MENU) send_group_message_item.set_image(icon) menu.append(our_new_action_item) our_new_action_item.connect('activate', self.on_our_new_action_activate, account, list_)
As arguments in Python are passed using references this should work like a charm. This assumes that in our plug-in class on_our_new_action_activate method is defined.
The above approach is pretty flexible and requires minimal work in current Gajim's source code - all work would be done in some kind of plugin_manager class. In our case such manger would have to:
- detect which plug-ins have appropriate methods (for each defined extension point) and keep that in mind (for quick iterations)
- iterate through plug-ins that want to use specific GUI extension point
Extending static widgets
If the widget (window) is not generated on-the-fly every time it's showed another approach should be used. Plug-in that wants to modify, i.e. roster window (which is 'static') it should provide method for modifying roster GUI (attach), but also for removing these modifications (detach). This is needed for dynamic plug-in loading and unloading.
Similar approach is used in Epiphany.
Plug-in configuration handling (notes)
to be added - how to integrate configuration per-plugin with global configuration so that data is not overwritten (also between plug-ins). Some kind of configuration sandbox?
Example plug-ins (notes)
Below is a list of example plug-ins that could be implemented. The idea is to break down each plug-in into pieces that it needs from plug-in system to work. This is a bottom-up approach to generating architecture that will be useful for Gajim but not too overloaded (for simplicity and performance) - let's call it a "feature driven development". All those small requirement will be then generalized to create plug-in system architecture. Of course there will have to be decisions made on how deep plug-in system should go (bigger or smaller core?).
List of plug-ins:
- Advanced Notification Control (ANC) - TODO: add desc here and ticket number
- acronyms expanding - needs to: modify each incoming (or outgoing) text message; show configuration dialog to user and save/load configuration; optionally: add checkbox to chat window toolbar whether to use acronyms expaner at the moment or not; optionally: change content of message field in chat window (for 'live' acronyms expanding) (ability to register for handling GUI events)
- message archiving (XEP-0136) - TODO: add desc here and ticket number
- XMPP over BOSH - TODO: add desc here and XEP number
- whiteboard - TODO: add desc here
- PEP GeoIP and Google maps - TODO: add desc here. By steve-e at mailing list: "This could also show people how to modify the GUI: You might render a small globe in the roster (next to the avatar) when a contact has published his current position and activity."
- SMS Plug-in - TODO: add desc here
- LaTeX formula rendering - TODO: add desc here, #4176
- notes on each contact in roster - TODO: add desc here
- Python console - TODO: add desc here
Plug-in system from user side (notes)
I think that Exaile's plug-ins system is pretty nice from user side (of course there are many other applications but Exaile is written in Python so it's closer to Gajim).
There are two tabbed panes used to manage plug-ins (screenshots below):
- Installed plug-ins
- Available plug-ins (these are automatically fetched from Exaile's site)
This may seem pretty obvious but Pidgin for example doesn't automatically fetch plug-ins list from external resource. I don't think this is a must-be-done feature for GSoC period but I think we should take this into account while developing plug-ins system (and check whether this implies any changes in design - currently I think that it won't).
Possible solution for managing plug-ins could be Capuchin -- program that downloads and installs plugins in background. It has DBUS interface which applications use to get downloaded plugins. Written in C# (prototype written in Python). It is claimed to be easy in use. I'll have to check this out. The question is whether we need another dependency and how independently from OS this works. They have 'providing Python wrapper' in roadmap'. Currently only Deskbar-Applet uses it (they hope that apps like gedit, Banshee or Rhythmbox will support Capuchin in future).
session_centric branch (notes)
session_centric branch coded by bct should be taken into account when creating plug-in system. Main issue is merging this branch with possible modifications that plug-in system will make in Gajim's core.
Notes from bct on this branch:
moved the chat message handler from the gajim interface object (ie. src/gajim.py) into the session object. The idea is for session objects to handle everything after a message is received. The standard session object prints messages to a chat window and sends messages that it receives from the chat window. Another kind of session object could handle whiteboarding, another kind could handle GPG or E2E encryption, another kind could handle a chess game, etc.
From a quick look, my plans are very similar to what's on the PluginSystem wiki page for message flow hooks.
Events, signals and handling them (notes, old)
There's a ticket #1005 related to Refactoring the whole event handling system. This should be probably handled withing plug-in system architecture. Common way to implement this is use a dispatcher to which object sends signals and the dispatcher sends them further to registered signal handlers.
There is a mechanism in GTK+ that drives whole GUI events handling but it's probably not a good idea to use this built-in dispatcher, because we would get too dependent on GTK+ (Glib) library in part of Gajim that does not need it really. From a low level side - every object that emits signal must subclass PyGObject class. In fact this is glib class not PyGTK class. Gajim already has this in it's dependencies, so on second thought maybe it would be good (easier?) to use dispatching mechanism bundled in PyGObject.
PyDispatcher is another solution to learn from - this is based on a simple recipe in Python Cookbook. There's also a variation of this solution called Louie. Personally I don't think that dispatching mechanism is worth another dependency.
Of course we can code our own simple dispatching mechanism.
Big question: which way to go: PyGOject, use some external library for dispatching or write simple dispatching mechanism for Gajim? We will implement our own events dispatcher in both ways (network-->user, user-->network).
Data flow hooks (notes, old)
Notes on how and where modify messages.
Proposed message flow hooks (aka filters?):
- [when sending] just after reading message text from text field (this would be a string object to modify)
- [when sending] just before putting message to network interface (an xmpppy object to modify)
- [when receiving] just after receiving package from network interface (just after creation of xmpppy object that holds message?)
- [when receiving] just before putting message text info chat/MUC window and history (question: history and chat window output are separate? how is it implemented currently? where does the message stream divide between history database and current chat window?)
Source analysis (notes)
My source analysis of how messages flow in both directions:
- from user entering message in chat window to sending it through network interface
- from receiving message through network interface to displaying it in chat window (and archiving in history)
User --> network:
ChatControlBase.__init__ connects send_button clicked-event to method in ChatControlBase class called _on_send_button_clicked. In this method a message text is taken from self.msg_textview which is a MessageTextView object. Then self.send_message is called. There is also a ChatControl.send_message method (TODO: where ChatControl class is used and where ChatControlBase is used?)
In short (compelete this with links): ChatControl?.send_message -> ChatControlBase?.send_message -> MessageControl?.send_message -> gajim.connections[self.account].send_message (question: where gajim.connections is initialized/modified) -> Connection.send_message -> self.connection.send (self.connection is according to commnents a 'xmpppy ClientCommon? instance'. it fact there is only class common.xmpp.client.CommonClient? - question: is this an error in comment or ClientCommon? is somewhere else? maybe external xmpppy?)
Links to articles/documents/software
- Extending Sametime 7.5: Building Plug-ins for Sametime - IBM's RedBook (pretty detailed - almost 600 pages) on writing plug-ins for their collaboration software called Lotus Sametime. As part of it there's an instant messaging solution called Sametime Connect. Although this RedBook describes whole collaboration system, there are nice parts about IM client and how plug-ins can be written for it. This can act as a basic checklist of what we should implement in Gajim's plug-in system.
- Extending IBM Lotus Sametime Connect V7.5 - article at DeveloperWorks that shows (in short) Sametime Connect GUI extensions-points and architecture. I think this is a really nice overview of how IM clients can be extended.
- A Simple Plugin Framework - pretty nice article describing how to implement very simple plugin framework in Python using metaclasses. This article strongly refers to Django and Zope but these parts are irrelevant to plug-ins system in Gajim.
- Entry points in setuptools - notes on dynamic discovery of services and plugins. This mechanism is used in PasterScript used by Pylons. And probably in other software (TODO: where?).
- Plugins using Eggs - presentation slides from PyCon2006
- Plugin development in Exaile - article describing how plug-in system works in Exaile player. Mostly from plug-in developer side. Exaile is written in Python so this simplifies analysis.
- Epiphany extensions - another example of plug-in system that uses Python and PyGTK. There's a documentation on Writing Epiphany Extensions and some site with Unofficial Epiphany Extensions (from simple to complex examples).
- How to write plugins for Pidgin in C - this one describes plug-ins system in Pidgin. However examples are in C so this one is probably harder to analyze. There are also bindings for Perl and Tcl - these are probably easier to relate to Gajim.
- Martian - a library that allows the embedding of configuration information in Python code. Another approach to write framework. It is used by Grok project and is in fact an independent spin-off from it. The page includes pretty detailed example of how to build a framework using Martian.
- Short post on implementing plug-in system in Crunchy
(40.6 KB) -
added by vArDo 8 years ago.
Exaile's plug-in manager - Installed Plug-ins tab
(63.6 KB) -
added by vArDo 8 years ago.
Exaile's plug-in manager - Available Plug-ins tab
(62.5 KB) -
added by vArDo 8 years ago.
General overview of proposed events handling architecture in Gajim
(20.0 KB) -
added by vArDo 8 years ago.
Proposed event classes hierarchy for Gajim (general idea)
(51.3 KB) -
added by vArDo 8 years ago.
Event handling example of incoming message (proposed way). Includes possible actions that plug-in can invoke.
Download all attachments as: .zip