Showing posts with label Ruby. Show all posts
Showing posts with label Ruby. Show all posts

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, i.e.

$ 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-dbussource code. It is fairly compact and terse but logically laid out.
 

Ruby 1.9.1 RC Released

In his usual understated way, Yuhi Sonoda announced the first Ruby 1.9.1 release candidate earlier today on the ruby-talk mailing list.

Hi, folks

Ruby 1.9.1 has been just released.

This is a release candidate of Ruby 1.9.1, which will be
the first stable version of Ruby 1.9 series. Try it early
and have a experience of modern, faster, with clearer
syntax, multilingualized and much improved Ruby world.

We have fixed 72 bugs and implemented some features:
http://redmine.ruby-lang.org/projects/ruby-19/issues?query_id=9

If you encounter a bug or a problem, please let us know
it via the official issue tracking system
(http://redmine.ruby-lang.org ).
I am a fan of Ruby and look forward to every release.  My first introduction to Ruby was way back in early days of 1997 just after Ruby 1.0 was released by Yukihiro Matsumoto (aka Matz) whose blog, or as the Japanese prefer to term it web diary is here.  Since then I have used it from time to time for various small scripts in the same why that I also use Perl.  I recommend it to people when asked what programming language they should learn first.  My only reservations are that traditionally Ruby tends to be somewhat slow compared to other popular scripting langauges and that there now any number of Ruby implementations which leads to some portability issues.  Performance has improved with every release and there is no reason nowadays not to use Ruby in production enviroments.

This release is significant in that it is the first release candidate of Ruby 1.9.1 which is targeted to be the first stable production-grade version of the Ruby 1.9 (aka Yarv) series.  Ruby 1.9 is intended to be a major evolation of the Ruby language akin to Python 3.0 or Perl 6.  Old crud and langauge features that were, in hindsight, poorly designed have been removed.  A number of new language features are implemented.  If you want more information about the changes in Ruby 1.9, a good reference to start with is Mauricio Fernandez's Changes in Ruby 1.9.  With this release, the language features are frozen except for multiligual support is some of the standard libraries which come with Ruby.  I have played with beta releases of Ruby 1.9 for some time now and am pleased with the language changes and especially the improved threading model.

As I said previously, there is not one single implementation of the Ruby language.  The implementation released today is regarded as the official Ruby language implementation in that it is maintained by Matz, Yuhi and a number of trusted lieutenants.  It is sometimes refered to as CRuby since the underlying language is C.

If you are unfamilar with the different Ruby implementations other than CRuby, the leading contenders, in no particular order, are Ruby Enterprise Edition, JRuby, Rubinius, MagLev and finally Microsoft's IronRuby which interestingly they are developing in a semi-open model.  You can actually contribute code and examine the source code!  For a comparision of the performance of the various implementations, have a look at Antonio Cangiano's Great Ruby Shootout 2008.  To interpret these results correctly, you need to read his followup post Reflections on the Ruby Shootout.

I still have some reservationa about the speed of code execution in Ruby.  While Ruby 1.9.1 is around twice as fast as Ruby 1.8.7, Ruby is still quite slow compared to other scripting languages such as Python or Perl even if it is somewhat faster than PHP.  Work still needs to be done to improve performance.

If you want to compare the performance of Ruby to other languages for various benchmarks, one place you can do so is at Debian's Alioth which claims to be the successor to Doug Bagley's The Great Computer Language Shootout amongst other things.  Other benchmarks worth looking at are here and here.  However, keep in mind the the golden rule of benchmarking, to wit - all benchmarks are flawed benchmarks.

You can download the source code for this release at ruby-lang.org.  If you find the ruby-lang.org FTP server is too slow, Peter Cooper has made the tarball available on Amazon S3 which should result in a much faster download.

For some reason, new releases of CRuby always seem to occur in December.  Anybody know why?