Version 10 (modified by 11 years ago) (diff) | ,
---|
Who's working?
This exciting project is done by gillux and mentored by elb.
What's this?
Story
Let's start with a little story. You're at home on your desktop and you have Pidgin running. Then you do a little break, and you go to your bed with your laptop, which is also running Pidgin to keep chatting with your friends. Then it's time to go to work, and you go to your office, where you keep chatting discreetly using Finch.
You have 3 clients running, and each have a separate account or handle. Wouldn't it be great if all of these clients were synchronized using the same connections, displayed you the same buddy list and chat logs? Well, that's what detachable sessions allows you to get :)
The idea
The goal of detachable libpurple sessions is to allow a user to have several running libpurple based applications simultaneously, all sharing the same the same data and connections. This involves having libpurple acting as a daemon and clients like Pidgin and Finch connecting to it. All the connected clients will access and display the buddy list, conversations, and chats. Existing ongoing conversations' history will be visible in Pidgin or Finch when they connect to the daemon.
Goals
This project will be managed in two steps. A cool thing would be to allow the clients to be running on different machines rather than on the same, but this requires to "networkify" all the UI <-> libpurple communication. Such a thing is not simple to do and that's why I will first make it to work with clients on the same machine. This is a minimal goal called minimal version, and having clients on separate machines is an extra goal called remote version. I'll do it if I get the time to. Note that the previous story is still possible with the minimal version if you run the client remotely, using ssh or an export display.
Current status
Mercurial
Current state of this work can be followed in the detachablepurple repo (during GSoC, work happened in the gsoc branch, which is outdated now). You can check it out following the mercurial guide and using my repo instead of the main repo.
GSOC and after
I didn't manage to complete this project before the end of the 2010 summer of code. There are two main reasons: lack of time and lack of gobjectification. About the latter, I used the gobjectification branch as a base for my project, because the combination of gobjects and dbus makes a powerful IPC mechanism. As a result, the detachable session project entirely depends on the gobjectification status. At the moment, only the gobjectified parts of the code (that is, accounts and buddy list) are getting synchronized between daemon and clients. The gobjectification branch is currently pretty much stalled. And so the detachable session project is.
End-user usage
This is not working yet.
But if you really want to see something and you know how to use a command line, you can play with what's available:
- Follow this to get the source.
- Type
./autogen.sh --enable-dbus && make
to compile the detachable branch. Note that you shouldn't type make install because you probably have an existing pidgin installation.
- Run the daemon typing
./libpurple/purpled/purpled
.
- On the same host as the daemon, run pidgin typing
./libpurple/pidgin/pidgin -r -m
- Pidgin should says that no accounts are activated. Activate one if you want.
- If you want, run as many finchs (again on the same host) as you want too typing
./finch/finch -r
.
- Run as many other pidgin sessions as you want repeating step #4.
Now you can try to manipulate the Accounts window of all the running pidgins and finchs, and see that they are synchronized. Awesome, isn't it?
Applications that uses libpurple
To make your fancy libpurple-based GUI to work in the so-called detachable (or remote) mode, first make sure the libpurple you are used have dbus support enabled. Then just add the following code before the purple_core_init()
call.
purple_core_set_running_mode(PURPLE_RUN_REMOTE_MODE); if (!purple_core_is_remote_mode()) { /* Setting remote mode failed and an error * message were printed on stdout. */ /* Handle this as you can... */ }
Then use the libpurple API normally. Your libpurple should contact the local dbus server and get synchronised with the purpled daemon. Note that if your have core plugins, you'll probably have to add the dbus cflags to their makfiles to make them compile.
Details of the hack
Drawing
+------------------+ +---------------------------+ IM servers<-->[libpurple] Daemon |<--DBus-->/ libpurple in \ | +------------------+ \detachable mode/ UI Client | +---------------------------+
The idea is to have libpurple acting as a daemon on one side, and as a client on the other side. DBus is used as the RPC mechanism. Because I care about security, we may forward the data over a custom tunnel as described in this drawing.
Technical details about the current implementation
We just forward API calls, object properties and signals between daemon and clients through dbus. The daemon “UI” is located in purpled/, and the dbus code reside in libpurple/dbus/.
Let’s imagine we have a PurpleFoo gobject in libpurple/foo.c. First, libpurple/dbus/foo.xml contains what parts of PurpleFoo are exported on dbus. A python script generates libpurple/dbus/foo.xml.h from this XML, which is basically a C struct version of the XML. This XML data is called “introspection data” in dbus terminology. Just call purple_object_class_register_dbus_iface() giving this C struct data as argument, and all the properties and signals listed in the XML get automatically synchronized between clients and daemon.
The usual initialization code of PurpleFoo from libpurple/foo.c gets extended by calling two functions from libpurple/dbus/foo.c: one for the class and one for the objects. For instance we have purple_account_class_dbus_init() called from purple_account_class_init() and purple_object_dbus_init() called from purple_account_new(). This extra initialization code will do different things depending on the running mode flag.
The running mode flag is a boolean field of PurpleCore which says whether the libpurple acts as a daemon, a client, or in normal mode. Typical code is if(purple_core_is_daemon_mode()) {...}. The idea is that the detachable session functions are common between client and daemon, but what gets actually executed internally is different depending on the context.
To export a method, we just call purple_object_bind_dbus_callback() in the extra dbus initialization part. Simple functions such as purple_account_connect() can be directly binded to a dbus method, whereas more complex ones requires an extra wrapper function, such as purple_account_register().
What needs to be done
First, gobjectification.
Then, export the gobjectified parts on dbus so that everything is nicely synchronized between clients and daemon. This should be easy as it mostly consists of adding wrapper code.
Other than that, the initialization part of libpurple will need to be adapted for the clients. Currently, libpurple gather data from ~/.purple/ during its initialization part only. However we need to be able to load this data remotely and anytime after libpurple has been initialized.
There are two approaches to get the whole thing working. Quick and dirty way and clean and right way. Quick and dirty way is e.g. to simply replace the call purple_blist_load() by a purple_blist_load_RPC() one that grabs the buddy list from the daemon instead of the local blist.xml. This is obviously wrong because it freezes the UI and cannot handle network errors neither do retries. But it works with very minimal effort. And this is how things are currently done.
The clean and right way is to first initialize libpurple without loading anything from ~/.purple/, and then to get the data from the daemon. I can imagine having a separate Pidgin launcher for detachable sessions, which would prompt the user for the daemon’s IP first, properly show any network errors etc.
And finally, many extra things that don’t prevent detachable sessions from basically working, such as handling of downloads, searching through logs, getting logs of what happened while a client was disconnected etc. This is probably a lot of work.
Also, we need to encrypt and authenticate the data between clients and daemon through a tunnel. Since gdbus allows to run a custom dbus server, and to connect to a particular server, it should be possible to forward the dbus connection through a custom tunnel.