A few weeks ago I was chatting with Dan about my one great annoyance with Pidgin: it takes up to 15 minutes to realize that it’s network connection isn’t valid any more and to automatically reconnect. With NetworkManager and dbus, this should be a reasonably simple feat. Dan got curious about this and wrote himself a python program that sets pidgin status in this way.
D-What?
A bit of background. One of the Freedesktop.org standards is d-bus:
D-Bus is a message bus system, a simple way for applications to talk to one another. In addition to interprocess communication, D-Bus helps coordinate process lifecycle; it makes it simple and reliable to code a “single instance” application or daemon, and to launch applications and daemons on demand when their services are needed.
The basic idea is that d-bus allows applications to publish interfaces that other applications on the desktop can interface with. Right now, only a few applications really support d-bus in a significant way. Fortunately, one of those is pidgin, which has an extremely rich d-bus interface.
Ruby & DBUS
Here’s a place where I know I’ll draw Dan‘s ire. I’ve been on a Ruby kick recently, so my first reaction to Dan’s post was “great, I’ll have to figure out how to do this in ruby now.” It sounds like typical language bias, but I’ll try to justify it at least a little.
Right now I’m writing code on a weekly basis in 3 languages: C# (OpenSim), Java (Grad School Project), and Ruby (side web projects done in Rails). 3 languages is a lot to remain fluent in, and causes some interesting syntax errors when jumping back and forth between them. One less context switch seemed like a good idea. At some point I’ll bother writing up why ruby has seduced me, but that is for another day.
While finding python-dbus bindings are extremely straight forward, the ruby-dbus front is a little more of a wandering path. After finding a couple of abandoned efforts, I finally came to the active ruby-dbus project. Make sure to grab the latest and greatest code from there before proceeding.
Connecting to DBUS
Before I get into actually using dbus, I need to set up connections to it. There are 2 different buses, the system bus (which is shared for all users), and the session bus, which is unique per login session. Pidgin uses the session bus: it’s a user application. Network Manager uses the system bus: it’s events are system wide and affect all users.
#!/usr/bin/ruby require "dbus" bus = DBus::SystemBus.instance session_bus = DBus::SessionBus.instance # Get the Pidgin Service pidgin_dbus = session_bus.service("im.pidgin.purple.PurpleService") # Get the object from this service pidgin = pidgin_dbus.object("/im/pidgin/purple/PurpleObject") # Introspect it pidgin.introspect if pidgin.has_iface? "im.pidgin.purple.PurpleInterface" pidgin.default_iface = "im.pidgin.purple.PurpleInterface" puts "We have Pidgin interface" end n_dbus = bus.service("org.freedesktop.NetworkManager") netman = n_dbus.object("/org/freedesktop/NetworkManager") # Establish a proxy interface object for NetworkManager as it doesn't support introspection poi = DBus::ProxyObjectInterface.new(netman, "org.freedesktop.NetworkManager")
Basically the pattern is clear. Get a service handle, get an object definition from that service handle, then get an interface from that object. There are 2 flavors for this, one where we’ve got introspection information, and one where we’ve got to go blind because introspection isn’t supported.
DBUS Introspection
DBUS interfaces come in 2 flavors, those that support introspection, and those that don’t. If an interface supports introspection you can get an interface definition off the dbus wire itself, otherwise you need to know the interface a priori. Pidgin supports introspection, Network Manager does not. The major short coming of the ars technica article on Pidgin and DBUS was the lack of information on using introspection to show all the other pidgin interface functions (of which there are > 600). Python dbus introspection throws an exception on my Ubuntu 7.10 environment, so it wasn’t helpful here. However, ruby, as usual, came to the rescue.
The ruby-dbus code contains an example application called gd-bus, which performs introspection on all dbus interfaces it can find, and prints them out nicely. The cheat sheet goes something like so:
M PurpleAccountsFind(in name:s, in protocol:s, out RESULT:i)
- M – DBUS Method, aka Function (might also be S meaning it’s a Signal you can register to listen to)
- PurpleAccountsFind – the method name
- in / out – whether this is an input or output parameter
- NAME:type – the name and type of the parameter. Types can be s – string, i – integer, u – unsigned int, ai – array of integers
Note: the ruby interface seems to always wrap arrays around the output parameters. I have no idea why, but it’s consistent, so a few extra “[0]”s get you a long ways. If you know why, please comment.
The translation of this interface specification into ruby gives you something like this:
def recycle_pidgin(pidgin) accounts = pidgin.PurpleAccountsGetAll for account in accounts[0] if pidgin.PurpleAccountIsConnected(account)[0] > 0 pidgin.PurpleAccountDisconnect(account) end pidgin.PurpleAccountConnect(account) end end
This cycles through all pidgin accounts, disconnects all the connected ones, and then attempts to connect all accounts. This is effectively what I end up doing by hand every time I switch networks with my laptop.
Bringing it all together
I now had connections to the 2 buses, and code to cycle the pidgin accounts. Last bit is actually watching for the Network Manager signal that I’ve got a new active network device.
poi.on_signal(bus, "DeviceNowActive") { recycle_pidgin(pidgin) } main = DBus::Main.new main << bus main.run
Network manager doesn’t support introspection. However, it does have pretty decent docs to figure out what the interface is. I’m still sad it doesn’t show up nicely in gd-bus though.
The proxy interface object sets up a signal using 2 parameters and a code block. Every time there is a DeviceNowActive signal on the system bus, I recycle pidgin. Pretty straight forward.
The last little bit is making this thing go into a loop. Ruby dbus contains it’s own main loop for just this task. I created a new main loop, tell it to watch the system bus, and then start it. And, we’re done.
The future’s so bright…
DBUS has been on my list of “I need to go figure this out” for a while. A morning of reading docs, hacking a bit, and crashing network manager a few times, and voila, you’ve got this blog post.
I’ve got lots of ideas floating around in my head now for other things that I can do with dbus to make my applications work better for me. As I bang a few of those out into code, expect to see more here about it. Pidgin is an especially target rich environment given how rich and interface they expose (nice job guys!).
very interesting for a dbus starter like me 🙂
LikeLike