How to make gpg-agent forget the passphrase when the screensaver starts…

…or: how to listen for signals on D-Bus in python.

Recently, I was stuck trying to get KDE to run a simple script whenever I leave the room (i.e. when the screen saver starts). I’m a fan of the KDE desktop environment and generally value its versatility. Still, it seems there is no way to run programs on desktop events other than login and logout. Searching the web and asking around I found two different workarounds to my particular problem, but both are rather intrusive and complex given the simplicity of the task.

As it turns out, the KDE screen saver/locker program emits a signal on D-Bus to announce its activity. A little python is all that is needed to turn this into a working solution for my problem…

D-Bus

Many modern Linux systems use the freedesktop.org D-Bus as an interprocess communication method. D-Bus allows programs to expose internal functionality (e.g. “start the screen saver”), as well as internal state changes (e.g. “network state changed”) to other programs. D-Bus can even be used to automatically launch services when their D-Bus interface is called.

If you have installed Qt4 development tools (Debian package qt4-dev-tools), there is a handy program called qdbusviewer, which allows you to browse the D-Bus interfaces of all running programs, call methods via D-Bus, and listen on D-Bus signals. This is quite handy when you have no idea what exactly you are looking for. If you don’t want to install qdbusviewer, one can also display activity on the D-Bus using dbus-monitor. If you don’t already know what you are looking for, though, the unfiltered output of dbus-monitor can be quite noisy.

Screenshot of qdbusviewer

In my case, I simply entered “screen” into the search field and quickly discovered that there is a D-Bus service called org.freedesktop.ScreenSaver which exposes the object ScreenSaver which has an interface org.freedesktop.ScreenSaver that emits the signal ActiveChanged.

With this knowledge, one can filter the output of dmus-monitor to only show the interesting stuff:

dbus-monitor "type='signal',sender='org.freedesktop.ScreenSaver',path='/ScreenSaver',interface='org.freedesktop.ScreenSaver',member='ActiveChanged'"

Listening in on the signal is already quite nice. But how to make a nice script out of this without lots of fragile string parsing?

Python saves the day

Since I’m mostly a C++ guy, my first idea was to create a small daemon that listens on D-Bus and calls script files accordingly. I was already reading the QDbus documentation when I noticed that there are python bindings for D-Bus as well. Since python is not terribly hard to learn, I figured I’d just try the python interface first and revert to the C++ plan if that didn’t work out. Fortunately, python is quite apt for the job.

[python]# mainloop:
import gobject
# dbus:
import dbus
from dbus.mainloop.glib import DBusGMainLoop
from os import system

# callback:
def signal_handler(state):
print "Signal received. State: " + str(state)
return

# create session bus object:
DBusGMainLoop(set_as_default=True)
bus = dbus.SessionBus()

try:
busobject = bus.get_object("org.freedesktop.ScreenSaver","/ScreenSaver")
busobject.connect_to_signal("ActiveChanged", signal_handler, dbus_interface="org.freedesktop.ScreenSaver")
except dbus.DBusException:
traceback.print_exc()
print usage
sys.exit(1)

# set up event loop:
loop = gobject.MainLoop()
loop.run()[/python]

Flushing gpg passphrase and pin cache

With the above python program, all that was left for me was to get gpg to flush its passphrase and pin cache. For the passphrase, that’s well documented in gpg-agent’s manpage – just send a SIGHUP to the gpg-agent process. After asking on gnupg-user for help, flushing the cached pin (for my OpenPGP card) was only slightly more complicated, calling the gpg-agent command “SCD RESET”.

Changing the signal handle function in the python method above to do the necessary steps:
[python]
def signal_handler(state):
if (state):
print "Flushing gpg-agent passphrases…"
system("killall -SIGHUP gpg-agent")
print "Flushing gpg-agent PINs…"
system("gpg-connect-agent ‘SCD RESET’ /bye")
return
[/python]
Just put the script into your autostart directory, and you can be sure that gpg flushes the credential cache as soon as you lock the screen.