DBUS-reactor, or AsusOSD must die!
I love my Asus eee 701. I love it so much because I use it every day. One nice thing about it is that since it came with Linux, everything in it is well supported. Specifically, all keys do what they are supposed to do, and do it nicely.
When you click "volume up" the volume not only goes up, but you also get a graphic on screen telling you so and showing the current volume. The application that shows that is called asusosd.
However, once you turn your eee into a regular machine removing the Xandros Linux that came with it, since everything is well supported, strange things happen.
For example, when you click on "Volume Up", you get two notifications on screen: asusosd and kmix. Since kmix is much more useful than asusosd, the obvious way to fix this is removing asusosd.
But... that means other notifications are now gone. For example, "Brightness Up" does what it's supposed to do, but you get no on-screen notification (other than the screen being brighter ;-).
On other notebooks similar things happen: I have a HP Pavilion, and the "WWW" key does nothing.
So, what does a hacker do? He hacks. What do I do? I hack with python. Keep in mind, from now on, that I don't really know what heck I am doing, this is mostly a blind hack, but I think it may work as example code for the next guy stumbling into the python+dbus+hal compbination.
Here's how it works:
Whenever you press a "special key", HAL will send a DBUS message about it. You can see that using dbus-monitor. This is what happens when I press volume-down in my HP:
$ dbus-monitor --system signal sender=org.freedesktop.DBus -> dest=:1.144 serial=2 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameAcquired string ":1.144" signal sender=:1.0 -> dest=(null destination) serial=5365 path=/org/freedesktop/Hal/devices/platform_i8042_i8042_KBD_port_logicaldev_input; interface=org.freedesktop.Hal.Device; member=Condition string "ButtonPressed" string "volume-down"
You can create a python program, using python-dbus, that connects to those events, and reacts in consequence (thus the dbus-reactor name, ok?)
There is a very annoying problem here, though. This is the same event in my eee:
$ dbus-monitor --system signal sender=org.freedesktop.DBus -> dest=:1.22 serial=2 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameAcquired string ":1.22" signal sender=:1.0 -> dest=(null destination) serial=632 path=/org/freedesktop/Hal/devices/computer_logicaldev_input_3; interface=org.freedesktop.Hal.Device; member=Condition string "ButtonPressed" string "volume-down"
While the two are quite alike, the path parameter is different, so that can't be used, and every example I found of python-dbus usage ... uses path.
It would be nicer if my program worked in both notebooks, though. It turns out that it's not all that hard. The trick is in asking your system what the keyboard is called. To do that, you call the "FindByDeviceCapability" method of the HAL manager. Here's a snippet that does just that [listing1.py]:
import sys import dbus bus = dbus.SystemBus() proxy = bus.get_object('org.freedesktop.Hal', '/org/freedesktop/Hal/Manager') interface = dbus.Interface(proxy,dbus_interface='org.freedesktop.Hal.Manager') print interface.FindDeviceByCapability('input.keyboard')[0]
Try it, it should work on any modern Linux system running HAL!
$ python listing1.py /org/freedesktop/Hal/devices/platform_i8042_i8042_KBD_port_logicaldev_input
So, now, let's start with a very simple program that sets up all the DBUS stuff and connects to the 'Condition' signal [listing2.py].
# -*- coding: utf-8 -*- import sys import dbus import dbus.mainloop.qt from PyQt4 import QtCore app=QtCore.QCoreApplication(sys.argv) # You **must** set the event loop before getting the bus, # or connecting to signals will not work. Funny, isn't it? mainloop=dbus.mainloop.qt.DBusQtMainLoop(set_as_default=True) bus = dbus.SystemBus() hal_proxy = bus.get_object('org.freedesktop.Hal', '/org/freedesktop/Hal/Manager') manager = dbus.Interface(hal_proxy,dbus_interface='org.freedesktop.Hal.Manager') kbd_dev = manager.FindDeviceByCapability('input.keyboard')[0] kbd=dbus.Interface( bus.get_object('org.freedesktop.Hal', kbd_dev), dbus_interface='org.freedesktop.Hal.Device') dbus.set_default_main_loop(mainloop) def handler(*args,**kwargs): print "got signal from %s" % kwargs print kwargs['message'].get_args_list() def setHook(): kbd.connect_to_signal('Condition',handler,sender_keyword='sender', destination_keyword='dest', interface_keyword='interface', member_keyword='member', path_keyword='path', message_keyword='message') setHook() print 'Entering loop' app.exec_()
When I run it, I get this:
$ python listing2.py Entering loop got signal from {'sender': ':1.0', 'dest': None, 'member': 'Condition', 'interface': 'org.freedesktop.Hal.Device', 'path': dbus.ObjectPath('/org/freedesktop/Hal/devices/platform_i8042_i8042_KBD_port_logicaldev_input'), 'message': <dbus.lowlevel.SignalMessage object at 0xb75296a0>} [dbus.String(u'ButtonPressed'), dbus.String(u'volume-down')]
So, there you go, this program is notified of the magic keys already!
Now, how can you make pretty on-screen notifications? It turns out DBUS takes care of that, too, via the session bus and org.freedesktop.Notifications interface. Here is a snippet to show a message [listing3.py]:
import sys import dbus # This uses the session bus because it's session-specific. bus = dbus.SessionBus() proxy = bus.get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications') interface = dbus.Interface(proxy,dbus_interface='org.freedesktop.Notifications') # This should display title and message, with no icon, both in KDE and GNOME interface.Notify('test_runner', 0, '', 'title', 'message', [], {}, -1)
So, let's do another iteration of the main program, so that now we are notified when a magic key is pressed (luckily, regular keys cause no HAL events! ;-) [listing4.py]
import sys import dbus import dbus.mainloop.qt from PyQt4 import QtCore app=QtCore.QCoreApplication(sys.argv) mainloop=dbus.mainloop.qt.DBusQtMainLoop(set_as_default=True) systemBus = dbus.SystemBus() sessionBus = dbus.SessionBus() notify_proxy = sessionBus.get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications') notifier = dbus.Interface(notify_proxy,dbus_interface='org.freedesktop.Notifications') hal_proxy = systemBus.get_object('org.freedesktop.Hal', '/org/freedesktop/Hal/Manager') manager = dbus.Interface(hal_proxy,dbus_interface='org.freedesktop.Hal.Manager') kbd_dev = manager.FindDeviceByCapability('input.keyboard')[0] kbd=dbus.Interface( systemBus.get_object('org.freedesktop.Hal', kbd_dev), dbus_interface='org.freedesktop.Hal.Device') dbus.set_default_main_loop(mainloop) def handler(*args,**kwargs): print "got signal from %s" % kwargs title=kwargs['message'].get_args_list()[1].replace('-',' ').title() notifier.Notify('System',0,'',title,'',[],{},-1) print kwargs['message'].get_args_list() def setHook(): kbd.connect_to_signal('Condition',handler,sender_keyword='sender', destination_keyword='dest', interface_keyword='interface', member_keyword='member', path_keyword='path', message_keyword='message') setHook() print 'Entering loop' app.exec_()
And here's what happens when I press the special "Lock" key on my keyboard:
It's trivial to make this program much better:
Handle multiple keyboards.
Adding icon support for notifications.
The possibility to ignore specific keys (like Volume Up/Down/Mute, which are nicely handled by KMix)
Support for launching a program instead of notifying.
React to events other than keyboard (example: plugging/unplugging of the AC power)
Configuration!
Support Qt or glib event loops
I may (or may not) do it, so if anyone wants to, it's a fun beginner's project! In less than a month, you may have a nice, useful program in your hands. And then comes fame and fortune, of course.
Thanks for sharing