Translate

Archives

Ruby D-Bus and Fedora 11

Earlier this year I wrote a number of posts about monitoring and interacting with D-Bus using shell scripts. In this post I use Ruby to monitor and interact with D-Bus enabled applications. If you are unfamiliar with D-Bus, a good starting point is this Freedesktop.org tutorial by the authors of the D-Bus specification.

I used the standard out-of-the-box version of Ruby which comes with Fedora 11.

$ ruby --version
ruby 1.8.6 (2009-06-08 patchlevel 369) [x86_64-linux] 


Those readers who are familiar with Ruby will recognize that this version of Ruby is quite old by Ruby standards and is in maintenance mode with Kirk Haines of Engine Yard being the lead maintainer. Hopefully a 1.9 version of Ruby will be included in the official repositories for Fedora 12.

I also installed the latest version (0.2.9 “I’m not dead”) of ruby-dbus which comes as a compressed tarball rather than as a gem. This project was recently taken over by Martin Vidner, who works for Novell in their Prague development group, after a long period of no activity by the original developers. An introduction to and a tutorial on ruby-dbus is available here. It needs to be updated and expanded to include all the interfaces and methods but it is a good introduction to the general concepts.

Introspection is core to D-Bus scripting. Here is one way to introspect the D-Bus system bus and output the resulting introspection data in XML format. The introspection data format is specified by the D-Bus Specification. The introspect_data method does all the heavy lifting.

#!/usr/bin/ruby

require 'dbus'

bus = DBus::SystemBus.instance
xml = bus.introspect_data("org.freedesktop.DBus", "/org/freedesktop/DBus/Introspectable")
puts xml


Here is the output from my computer which is running Fedora 11:

<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <interface name="org.freedesktop.DBus.Introspectable">
    <method name="Introspect">
      <arg name="data" direction="out" type="s"/>
    </method>
  </interface>
  <interface name="org.freedesktop.DBus">
    <method name="Hello">
      <arg direction="out" type="s"/>
    </method>
    <method name="RequestName">
      <arg direction="in" type="s"/>
      <arg direction="in" type="u"/>
      <arg direction="out" type="u"/>
    </method>
    <method name="ReleaseName">
      <arg direction="in" type="s"/>
      <arg direction="out" type="u"/>
    </method>
    <method name="StartServiceByName">
      <arg direction="in" type="s"/>
      <arg direction="in" type="u"/>
      <arg direction="out" type="u"/>
    </method>
    <method name="UpdateActivationEnvironment">
      <arg direction="in" type="a{ss}"/>
    </method>
    <method name="NameHasOwner">
      <arg direction="in" type="s"/>
      <arg direction="out" type="b"/>
    </method>
    <method name="ListNames">
      <arg direction="out" type="as"/>
    </method>
    <method name="ListActivatableNames">
      <arg direction="out" type="as"/>
    </method>
    <method name="AddMatch">
      <arg direction="in" type="s"/>
    </method>
    <method name="RemoveMatch">
      <arg direction="in" type="s"/>
    </method>
    <method name="GetNameOwner">
      <arg direction="in" type="s"/>
      <arg direction="out" type="s"/>
    </method>
    <method name="ListQueuedOwners">
      <arg direction="in" type="s"/>
      <arg direction="out" type="as"/>
    </method>
    <method name="GetConnectionUnixUser">
      <arg direction="in" type="s"/>
      <arg direction="out" type="u"/>
    </method>
    <method name="GetConnectionUnixProcessID">
      <arg direction="in" type="s"/>
      <arg direction="out" type="u"/>
    </method>
    <method name="GetAdtAuditSessionData">
      <arg direction="in" type="s"/>
      <arg direction="out" type="ay"/>
    </method>
    <method name="GetConnectionSELinuxSecurityContext">
      <arg direction="in" type="s"/>
      <arg direction="out" type="ay"/>
    </method>
    <method name="ReloadConfig">
    </method>
    <method name="GetId">
      <arg direction="out" type="s"/>
    </method>
    <signal name="NameOwnerChanged">
      <arg type="s"/>
      <arg type="s"/>
      <arg type="s"/>
    </signal>
    <signal name="NameLost">
      <arg type="s"/>
    </signal>
    <signal name="NameAcquired">
      <arg type="s"/>
    </signal>
  </interface>
</node>


The following example uses the introspect method instead of the introspect_data method to enumerate a list of the available services using the ListActivatableNames method. I am going to assume that you are familiar with the Ruby language.so concepts such as looping and arrays do not need explanation.

require 'dbus'  
  
bus = DBus::SystemBus.instance  
proxy = bus.introspect("org.freedesktop.DBus", "/org/freedesktop/DBus/ListActivatableNames")  

bus.proxy.ListActivatableNames[0].each do |service|  
   puts "#{service}"  
end  


This is the output:

org.freedesktop.DBus
org.freedesktop.DeviceKit.Disks
org.fedoraproject.Setroubleshootd
com.hp.hplip
org.fedoraproject.Config.Services
org.freedesktop.ConsoleKit
org.gnome.CPUFreqSelector
net.reactivated.Fprint
org.freedesktop.PackageKit
org.freedesktop.DeviceKit
org.freedesktop.NetworkManagerSystemSettings
org.gnome.ClockApplet.Mechanism
org.kerneloops.submit
org.freedesktop.PolicyKit
org.freedesktop.Gypsy
org.gnome.GConf.Defaults
fi.epitest.hostap.WPASupplicant
org.gnome.SystemMonitor.Mechanism
org.freedesktop.DeviceKit.Power
org.freedesktop.nm_dispatcher
org.opensuse.CupsPkHelper.Mechanism


You can query devices via the D-Bus interface to HAL as the following examples demonstrate.

#!/usr/bin/ruby

require 'dbus'  
  
bus = DBus::SystemBus.instance  
xml = bus.introspect_data("org.freedesktop.Hal", "/org/freedesktop/Hal/devices/computer")  
puts xml


Here is what is outputted when this script is executed:

<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <interface name="org.freedesktop.Hal.Device">
    <method name="GetAllProperties">
      <arg name="properties" direction="out" type="a{sv}"/>
    </method>
    <method name="SetMultipleProperties">
      <arg name="properties" direction="in" type="a{sv}"/>
    </method>
    <method name="GetProperty">
      <arg name="key" direction="in" type="s"/>
      <arg name="value" direction="out" type="v"/>
    </method>
    ......
    <signal name="InterfaceLockAcquired">
      <arg name="interface_name" type="s"/>
      <arg name="lock_holder" type="s"/>
      <arg name="num_locks" type="i"/>
    </signal>
    <signal name="InterfaceLockReleased">
      <arg name="interface_name" type="s"/>
      <arg name="lock_holder" type="s"/>
      <arg name="num_locks" type="i"/>
    </signal>
  </interface>
  .......
  <interface name="org.freedesktop.Hal.Device.CPUFreq">
    <method name="SetCPUFreqGovernor">
      <arg name="governor_string" direction="in" type="s"/>
    </method>
    <method name="SetCPUFreqPerformance">
      <arg name="value" direction="in" type="i"/>
    </method>
    <method name="SetCPUFreqConsiderNice">
      <arg name="value" direction="in" type="b"/>
    </method>
    <method name="GetCPUFreqGovernor">
      <arg name="return_code" direction="out" type="s"/>
    </method>
    <method name="GetCPUFreqPerformance">
      <arg name="return_code" direction="out" type="i"/>
    </method>
    <method name="GetCPUFreqConsiderNice">
      <arg name="return_code" direction="out" type="b"/>
    </method>
    <method name="GetCPUFreqAvailableGovernors">
      <arg name="return_code" direction="out" type="as"/>
    </method>
  </interface>
</node>


Here is an example of how to output the previous data in a more readable form using the interfaces method..

#!/usr/bin/ruby

require 'dbus'

bus = DBus::SystemBus.instance
proxy = bus.introspect("org.freedesktop.Hal", "/org/freedesktop/Hal/devices/computer")

proxy.interfaces.each do |interface|
   puts "Interface:  #{interface}"
   proxy[interface].methods.each do |key,value|
      puts "     Method: #{key}"
   end
   proxy[interface].signals.each do |key,value|
      puts "     Signal: #{key}"
   end


Here is an portion of the output: from executing this script.

Interface:  org.freedesktop.Hal.Device.CPUFreq
     Method: GetCPUFreqConsiderNice
     Method: GetCPUFreqGovernor
     Method: SetCPUFreqPerformance
     Method: GetCPUFreqAvailableGovernors
     Method: SetCPUFreqGovernor
     Method: GetCPUFreqPerformance
     Method: SetCPUFreqConsiderNice
Interface:  org.freedesktop.Hal.Device.SystemPowerManagement
     Method: Shutdown
     Method: SuspendHybrid
     Method: SetPowerSave
     Method: Hibernate
     Method: Reboot
     Method: Suspend
Interface:  org.freedesktop.Hal.Device
     Method: ReleaseInterfaceLock
     Method: ClaimInterface
     Method: Reprobe
     ........
     Method: SetPropertyBoolean
     Signal: InterfaceLockAcquired
     Signal: InterfaceLockReleased
     Signal: PropertyModified
     Signal: Condition
Interface:  org.freedesktop.DBus.Introspectable
     Method: Introspect


You can easily drill down to individual interfaces and methods. For example, here is one way to list the set of available CPU frequency governors.

#!/usr/bin/ruby
#
#  <interface name="org.freedesktop.Hal.Device.CPUFreq">
#    <method name="SetCPUFreqGovernor">
#      <arg name="governor_string" direction="in" type="s"/>
#    </method>
#    <method name="SetCPUFreqPerformance">
#      <arg name="value" direction="in" type="i"/>
#    </method>
#    <method name="SetCPUFreqConsiderNice">
#      <arg name="value" direction="in" type="b"/>
#    </method>
#    <method name="GetCPUFreqGovernor">
#      <arg name="return_code" direction="out" type="s"/>
#    </method>
#    <method name="GetCPUFreqPerformance">
#      <arg name="return_code" direction="out" type="i"/>
#    </method>
#    <method name="GetCPUFreqConsiderNice">
#      <arg name="return_code" direction="out" type="b"/>
#    </method>
#    <method name="GetCPUFreqAvailableGovernors">
#      <arg name="return_code" direction="out" type="as"/>
#    </method>
#  </interface>
#

require 'dbus'

bus = DBus::SystemBus.instance
proxy = bus.introspect("org.freedesktop.Hal", "/org/freedesktop/Hal/devices/computer")

interface = proxy["org.freedesktop.Hal.Device.CPUFreq"]
governors = interface.GetCPUFreqAvailableGovernors
puts "Available CPU Freq Governors:"
governors[0].each do |gov|
   puts "  #{gov}"
end


which produces the following output:

Available CPU Freq Governors:
  ondemand
  userspace
  performance


Lots of useful information can be be obtained from the HAL manager interface. The following script lists all the methods supported by the HAL manager interface and then uses the GetAllDevices method to enumerate the devices on my computer.

require 'dbus'

bus = DBus::SystemBus.instance
proxy = bus.introspect("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager")

puts "HAL manager reports following methods:"
proxy.interfaces[0].each do |interface|
  proxy[interface].methods.each do |key, value|
    puts "   #{key}"
  end
end

interface = proxy["org.freedesktop.Hal.Manager"]
devices = interface.GetAllDevices
puts "\nHAL manager reports following devices:"
devices[0].each do |device|
   puts "  #{device}"
end


Here is a portion of the output when this script is executed

HAL manager reports following methods:
   AcquireGlobalInterfaceLock
   DeviceExists
   CommitToGdl
   GetAllDevicesWithProperties
   FindDeviceByCapability
   NewDevice
   SingletonAddonIsReady
   FindDeviceStringMatch
   GetAllDevices
   ReleaseGlobalInterfaceLock
   Remove

HAL manager reports following devices:
  /org/freedesktop/Hal/devices/net_ba_cf_03_4e_14_ca
  /org/freedesktop/Hal/devices/volume_part7_size_115326976
  /org/freedesktop/Hal/devices/computer
  /org/freedesktop/Hal/devices/storage_model_DVD_Writer_1070d
  ...............
  /org/freedesktop/Hal/devices/acpi_CPU0
  /org/freedesktop/Hal/devices/acpi_CPU1
  /org/freedesktop/Hal/devices/acpi_CPU2
  /org/freedesktop/Hal/devices/acpi_CPU3
  /org/freedesktop/Hal/devices/pci_8086_2940
  /org/freedesktop/Hal/devices/pci_8086_293e
  /org/freedesktop/Hal/devices/usb_device_1d6b_2_0000_00_1a_7_if0
  /org/freedesktop/Hal/devices/usb_device_1d6b_2_0000_00_1a_7
  /org/freedesktop/Hal/devices/pci_8086_293c
  /org/freedesktop/Hal/devices/usb_device_1d6b_1_0000_00_1a_2_if0
  /org/freedesktop/Hal/devices/usb_device_1d6b_1_0000_00_1a_2
  /org/freedesktop/Hal/devices/pci_8086_2939
  /org/freedesktop/Hal/devices/usb_device_1d6b_1_0000_00_1a_1_if0
  /org/freedesktop/Hal/devices/usb_device_1d6b_1_0000_00_1a_1
  /org/freedesktop/Hal/devices/pci_8086_2938
  /org/freedesktop/Hal/devices/usb_device_1d6b_1_0000_00_1a_0_if0
  /org/freedesktop/Hal/devices/usb_device_1d6b_1_0000_00_1a_0
  /org/freedesktop/Hal/devices/pci_8086_2937
  /org/freedesktop/Hal/devices/pci_8086_294c
  /org/freedesktop/Hal/devices/pci_10de_640
  /org/freedesktop/Hal/devices/pci_8086_29e1
  /org/freedesktop/Hal/devices/pci_8086_29e0


The next example uses the D-Bus interface to Tomboy, a desktop note-taking application, to create a simple note, display it for 5 seconds and then delete the note.

#!/usr/bin/ruby

require 'dbus'

bus = DBus::SessionBus.instance
service = bus.service("org.gnome.Tomboy")

tomboy = service.object("/org/gnome/Tomboy/RemoteControl")
tomboy.introspect
tomboy.default_iface = "org.gnome.Tomboy.RemoteControl"
note = tomboy.CreateNamedNote("My Note")[0]
tomboy.SetNoteContents(note, "Hello World")
tomboy.DisplayNote(note)
sleep 5
tomboy.DeleteNote(note)


Note the need to introspect the tomboy object and set the default interface using the default_iface method.

You can also add one or more tags to a Tomboy note and extract comprehensive metadata in XML format as shown in the following example.

#!/usr/bin/ruby

require 'dbus'

bus = DBus::SessionBus.instance
service = bus.service("org.gnome.Tomboy")

tomboy = service.object("/org/gnome/Tomboy/RemoteControl")
tomboy.introspect
tomboy.default_iface = "org.gnome.Tomboy.RemoteControl"
note = tomboy.CreateNamedNote("My Note")[0]
tomboy.SetNoteContents(note, "Hello World")
tomboy.AddTagToNote(note, "blog example")
tomboy.DisplayNote(note)
sleep 5
xml = tomboy.GetNoteCompleteXml(note)
tomboy.DeleteNote(note)

puts xml


Here is the XML that is outputted when this script is executed:

<?xml version="1.0" encoding="utf-16"?>
<note version="0.3" xmlns:link="http://beatniksoftware.com/tomboy/link" xmlns:size="http://beatniksoftware.com/tomboy/size" xmlns="http://beatniksoftware.com/tomboy">
  <title>My Note</title>
  <text xml:space="preserve"><note-content version="0.1">Hello World</note-content></text>
  <last-change-date>2009-09-07T15:44:10.2178650-04:00</last-change-date>
  <last-metadata-change-date>2009-09-07T15:44:10.2212100-04:00</last-metadata-change-date>
  <create-date>2009-09-07T15:44:10.2142940-04:00</create-date>
  <cursor-position>37</cursor-position>
  <width>450</width>
  <height>360</height>
  <x>1440</x>
  <y>0</y>
  <tags>
    <tag>blog example</tag>
  </tags>
  <open-on-startup>False</open-on-startup>
</note>


The next example show you how to monitor D-Bus messages and act upon certain messages. You can do this using D-Bus signals. Suppose, for example, you want to monitor activity relating to Tomboy notes such as when Tomboy notes are created. First of all you need to find out what D-Bus signals Tomboy supports. This is easy to do using introspection as shown in the following script.

#!/usr/bin/ruby

require 'dbus'

bus = DBus::SessionBus.instance
service = bus.service("org.gnome.Tomboy")

tomboy = service.object("/org/gnome/Tomboy/RemoteControl")
tomboy.introspect
tomboy.default_iface = "org.gnome.Tomboy.RemoteControl"
puts "List of supported signals:"
tomboy.signals.each do |key,value|
   puts "  #{key}"
end


Here is the output:

List of supported signals:
  NoteSaved
  NoteAdded
  NoteDeleted


As you can see the version of Tomboy on my computer emits three D-Bus signals – NoteAdded when a note is added, NoteSaved when a note is saved, and NoteDeleted when a note is deleted.

The following script outputs a message each time a Tomboy note is created, saved or deleted.

#!/usr/bin/ruby

require 'dbus'

bus = DBus::SessionBus.instance

match = DBus::MatchRule.new
match.type = "signal"
match.interface = "org.gnome.Tomboy.RemoteControl"
match.path = "/org/gnome/Tomboy/RemoteControl"

bus.add_match(match) do |msg, misc|
    puts "#{msg.member}  #{msg.params[0]}"
end

main = DBus::Main.new
main << bus
main.run


Here is what is outputted when a single Tomboy note is created, saved and then deleted.

NoteAdded  note://tomboy/56587548-7535-4e21-a4b3-3ee85ffa7756
NoteSaved  note://tomboy/56587548-7535-4e21-a4b3-3ee85ffa7756
NoteDeleted  note://tomboy/56587548-7535-4e21-a4b3-3ee85ffa7756


Well that's all for now. There is a lot more that you can do using ruby-dbus but I will leave it up to you to go explore the possibilities. The best place to discover what is possible is to study the ruby-dbus source code. It is fairly compact and terse but logically laid out.
 

Comments are closed.