Showing posts with label HAL. Show all posts
Showing posts with label HAL. 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.
 

Scripting D-Bus

D-Bus (Desktop Bus) is a low-latency, low-overhead, easy-to-use message bus technology which supports application launch and linking.  It is primarly used on GNU/Linux desktops but has been ported to other platforms including Microsoft Windows and Apple Mac OS X.  This post provides a quick overview of D-Bus concepts, some history, and some examples of how to use D-Bus in your shell scripts.

Originally both the KDE and GNOME desktop projects used CORBA for inter-application communication.  Over time however, for various reasons, KDE migrated from CORBA to Desktop Comunications Protocol (DCOP) and GNOME migrated to Bonono.  This lead to the situation where GNU/Linux desktop distributions had to support two different inter-application lauch and linking models and many standard desktop applications could not communicate seamlessly with each other.  To ameliorate this unsatisfactory situation, D-Bus (the name was suggested by Harri Porten) was conceived and developed by Red Hat as part of the freedesktop.org project.  The design of D-Bus was heavily influenced by DCOP.  From the start, it was designed to be a replacement for the two competing technologies.   The initial source code module was created by Havoc Pennington in late 2002.  Development was quite slow with many changes to the wire protocol.  However by 2006 the specification was relatively stable.  First GNOME and then KDE made the decision to transition to D-Bus in order to support a single unified applcation linking and lauching technology on GNU/linux desktops.

In many ways D-Bus is similar to Sun Microsystems ToolTalk which is the undelying technology in Common Desktop Environment, and Microsoft's Object Linking and Embedding (OLE) technology.



The basic D-Bus protocol is a low latancy peer-to-peer or client-server binary protocol.  It is not intended for inter-machine use but rather for intra-machine use.  It works in terms of messages rather than byte streams.  A message bus is used when many-to-many communication is desired.  Normally applications communicate via such a message bus but direct application-to-application communication is also possible.

When communicating on a message bus, applications can query which other applications and services are available, as well as activate one on demand.  A daemon, or service, must be launched before any applications can connect to a message bus. This daemon is responsible for keeping track of the applications that are connected and for properly routing messages from source to destination.  The D-Bus specification defines two well-known buses called the system bus and the session bus.  These buses are special in that they have well-defined semantics and associated services.

A message bus can start (launch) an application on behalf of another application.  An application that can be started in this way is called a service and must have a well-known name.  To find an application corresponding to a particular name, the bus daemon looks for service description files which are XML files that map names to applications.  Different message buses will look for these files in different places on a system. Once the application is launched, it registers a service name.  Service names use reverse domain syntax (similar to Java), e.g org.freedesktop.Notifications.

D-Bus is object-orientated and supports the notion of named object interfaces.  As in C++, a method is an operation on an instantiated object.  Methods may have optional typed parameters and may return typed values.  Signals are broadcasts from an object to any interested observer.  All methods, method returns, signals, errors, etc. are encoded as D-Bus messages consisting of a packet header with source and destination addresses, a type signature and the body containing the parameters (for signals and method calls) or the return values (for a method return).  The type signature describes the types of the data contained within the message.  D-Bus uses interfaces to provide a namespacing mechanism for methods.  An interface is a group of related methods and signals, identified by a name which is a series of dot-separated components starting with a reversed domain name.

An application can expose one or more objects.  An object path is used to specify the address of each object.  The syntax of an object path is similar to that of the full pathname of a file on a Unix platform.  For example, /org/freedesktop/Notification is the object path to an object provided by the org.freedesktop.Notification service.  Service names, interface names and object paths do not have to be related in terms of their names but in many cases they are with the with the object path typically being a slashed version of the dotted service name as in the above example.  Messages buses can also expose interfaces, i.e. org.freedesktop.DBus.

The D-Bus specification mandates two special D-Bus interfaces - org.freedesktop.DBus.Introspect and org.freedesktop.DBus.Properties.  For our first example, let us ask the system message bus to list all it's methods.
dbus-send --system --print-reply --reply-timeout=2000 \
--type=method_call --dest=org.freedesktop.DBus \
/org/freedesktop/DBus \
org.freedesktop.DBus.Introspectable.Introspect

method return sender=org.freedesktop.DBus -> dest=:1.51 reply_serial=2
string "<!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 command lists out the names of services which can be activated (launched).
dbus-send --system --print-reply --reply-timeout=2000 \
--type=method_call --dest=org.freedesktop.DBus \
/org/freedesktop/DBus \
org.freedesktop.DBus.ListActivableNames

method return sender=org.freedesktop.DBus -> dest=:1.68 reply_serial=2
array [
string "org.freedesktop.DBus"
string "org.fedoraproject.Config.Services"
string "org.gnome.CPUFreqSelector"
string "org.freedesktop.ConsoleKit"
string "org.freedesktop.PackageKitAptBackend"
string "org.freedesktop.PackageKit"
string "org.freedesktop.NetworkManagerSystemSettings"
string "org.freedesktop.PackageKitTestBackend"
string "org.gnome.ClockApplet.Mechanism"
string "org.freedesktop.PolicyKit"
string "fi.epitest.hostap.WPASupplicant"
string "org.gnome.GConf.Defaults"
string "org.gnome.SystemMonitor.Mechanism"
string "org.freedesktop.nm_dispatcher"
]
Instead of using dbus-send which requires a number of parameters, a simpler utility to use is dbus which is part of the Google dbus-cli package.  Here is some sample output.
bash-3.2$ ./dbus
org.freedesktop.DBus
org.freedesktop.Notifications
org.freedesktop.PowerManagement
com.redhat.imsettings.IMInfo
org.gnome.SessionManager
org.gnome.GConf
org.freedesktop.PackageKit
com.redhat.imsettings
org.gnome.SettingsDaemon
com.redhat.setroubleshoot
org.gnome.ScreenSaver
org.bluez.applet
com.redhat.imsettings.GConf
Another useful utility is qdbus.
# list all GNOME-related  buses 
$ qdbus | grep gnome
org.gnome.SessionManager
org.gnome.SettingsDaemon
org.gnome.ScreenSaver
org.gnome.GConf

# list ScreenSaver interfaces
bash-3.2$ qdbus org.gnome.ScreenSaver
/

# list methods for ScreenSaver interface
$ qdbus org.gnome.ScreenSaver /ScreenSaver
method QString org.freedesktop.DBus.Introspectable.Introspect()
signal void org.gnome.ScreenSaver.ActiveChanged(bool new_value)
signal void org.gnome.ScreenSaver.AuthenticationRequestBegin()
signal void org.gnome.ScreenSaver.AuthenticationRequestEnd()
method void org.gnome.ScreenSaver.Cycle()
method bool org.gnome.ScreenSaver.GetActive()
method uint org.gnome.ScreenSaver.GetActiveTime()
method QStringList org.gnome.ScreenSaver.GetInhibitors()
method bool org.gnome.ScreenSaver.GetSessionIdle()
method uint org.gnome.ScreenSaver.GetSessionIdleTime()
method uint org.gnome.ScreenSaver.Inhibit(QString application_name, QString reason)
method void org.gnome.ScreenSaver.Lock()
signal void org.gnome.ScreenSaver.SessionIdleChanged(bool new_value)
signal void org.gnome.ScreenSaver.SessionPowerManagementIdleChanged(bool new_value)
method void org.gnome.ScreenSaver.SetActive(bool value)
method void org.gnome.ScreenSaver.SimulateUserActivity()
method uint org.gnome.ScreenSaver.Throttle(QString application_name, QString reason)
method void org.gnome.ScreenSaver.UnInhibit(uint cookie)
method void org.gnome.ScreenSaver.UnThrottle(uint cookie)

# retrieve session idle time. Zero in this case as I have been working on this post.
$ qdbus org.gnome.ScreenSaver /ScreenSaver org.gnome.ScreenSaver.GetSessionIdleTime
0

# activate the screen saver - may or may not lock depending on settings
$ qdbus org.gnome.ScreenSaver /ScreenSaver org.gnome.ScreenSaver.SetActive True

# lock screen using the screensaver
$ qdbus org.gnome.ScreenSaver /ScreenSaver org.gnome.ScreenSaver.Lock

# query whether this computer can be suspended?
$ qdbus org.freedesktop.PowerManagement /org/freedesktop/PowerManagement org.freedesktop.PowerManagement.CanSuspend
true
You can also query devices via HAL as the following examples show.
$ dbus-send --system --print-reply --dest=org.freedesktop.Hal \
/org/freedesktop/Hal/devices/computer \
org.freedesktop.DBus.Introspectable.Introspect

method return sender=:1.0 -> dest=:1.106 reply_serial=2
string "<!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.Hal.Device">
........
<interface>
<interface name="org.freedesktop.Hal.Device.SystemPowerManagement">
........
<interface>
<interface name="org.freedesktop.Hal.Device.CPUFreq">
........
<method name="GetCPUFreqAvailableGovernors">
<arg name="return_code" direction="out" type="as"/>
<method>
<interface>
<node>
"

$ dbus-send --system --print-reply --dest=org.freedesktop.Hal \
/org/freedesktop/Hal/devices/computer \
org.freedesktop.Hal.Device.CPUFreq.GetCPUFreqAvailableGovernors

method return sender=:1.0 -> dest=:1.109 reply_serial=2
array [
string "ondemand"
string "userspace"
string "performance"
]

$ dbus-send --system --print-reply --dest=org.freedesktop.Hal \
/org/freedesktop/Hal/Manager \
org.freedesktop.Hal.Manager.GetAllDevices

method return sender=:1.0 -> dest=:1.115 reply_serial=2
array [
string "/org/freedesktop/Hal/devices/volume_part_1_size_403367936"
string "/org/freedesktop/Hal/devices/net_46_b2_32_e5_29_04"
string "/org/freedesktop/Hal/devices/computer_logicaldev_input_0"
string "/org/freedesktop/Hal/devices/usb_device_45e_734_noserial_if0_logicaldev_input"
string "/org/freedesktop/Hal/devices/usb_device_45e_734_noserial_if1_logicaldev_input"
string "/org/freedesktop/Hal/devices/computer_logicaldev_input"
string "/org/freedesktop/Hal/devices/volume_uuid_9e1859de_0cf0_4048_96f5_4ef32ac904f7"
string "/org/freedesktop/Hal/devices/computer"
string "/org/freedesktop/Hal/devices/volume_part2_size_205632000"
string "/org/freedesktop/Hal/devices/volume_uuid_a332a6d6_a034_4f19_b470_bacf7ffa81e2"
string "/org/freedesktop/Hal/devices/volume_uuid_RyytQp_f73U_LzdN_6JjC_08yN_Aw3e_p3fNF1"
string "/org/freedesktop/Hal/devices/storage_model_DVD_Writer_1070d"
string "/org/freedesktop/Hal/devices/platform_serial8250_serial_platform_1"
string "/org/freedesktop/Hal/devices/storage_serial_SATA_ST3500320AS_9QM49NMQ"
string "/org/freedesktop/Hal/devices/storage_serial_SATA_ST3500320AS_9QM4GKDK"
.........
]

$ dbus-send --system --print-reply --dest=org.freedesktop.Hal \
/org/freedesktop/Hal/devices/storage_model_DVD_Writer_1070d
org.freedesktop.Hal.Device.GetProperty string:'info.vendor'

method return sender=:1.0 -> dest=:1.120 reply_serial=2
string "HP"
While D-Bus was not really designed with shell scripting in mind, it is possible to use various utilities to access system and session data via D-Bus.  I hope you have not found this post too long-winded and that you will go away and experiment with D-Bus on your own computer.  Unfortunately there is a lot of out-of-date information, terminology and examples on the Internet relating to D-Bus.  Always refer back to the specification when in doubt.  All the examples included on this port were tested on Fedora 10.

P.S.  I plan to cover scripting D-Bus signals in a future post.
 

Scripting HAL

Recent releases of Fedora and other GNU/Linux distributions include a Hardware Abstraction Layer (HAL) which is used to support device plug-and-play capabilities.  In this post I will show you how your shell scripts can use HAL to retrieve device and system information.

The term HAL is overloaded as it used to refer both to a specification and the actual software which implements the specification.  From an application developers viewpoint, HAL is way to enumerate the capabilities and features of hardware attached to a system and receive notification when something about the hardware changes.

First, a very quick overview of HAL.  Each item of physical hardware in a computer is regarded as being an device object which identified by a Unique Device Identifier (UDI).  Associated with each device object is a variable set of well-defined typed key-value pairs (or metadata) called device properties which describe what each device object represents together with its properties.  Some device properties are derived from the actual physical hardware, some are merged from XML-formatted files, known as Device Information Files, and some are derived from the actual device configuration.  Mandatory device properties are defined in the HAL specification.



A HAL daemon is used to maintain and update the list of device objects forming a hardware configuration and is notified when devices are added or removed.  HAL also provides callbacks so that the operating system and applications can react to hardware configuration changes in order to maintain system policy.  An example of such a callback and policy is when you plug in a USB memory stick HAL automatically creates a mount point and mounts the device.

For a good backgrounder on HAL, see this article Making Hardware Just Work by Havoc Pennington.  For more detailed information, you should read the HAL Specification and the HAL project page at freedesktop.org.

A number of command line utilities are provided for accessing and manipulating device objects.



















hal-disable-pollingdisable polling on drives with removable media
hal-find-by-capabilityfind device objects by capability matching
hal-find-by-propertyfind device objects by property matching
hal-get-propertyget a property from a device object
hal-is-caller-locked-out  determine if caller is locked out
hal-is-caller-privilegeddetermine if caller is privileged
hal-locklock an interface
hal-set-propertyset a property on a device object
lshallist devices objects and properties

For the purposes of this post, we are only interested in a small number of the above utilities, i.e. those utilities which locate and return information about device objects.

Here is a simple use of the hal-find-by-property utility to retrieve a list of network interfaces.
$ hal-find-by-property --key linux.subsystem --string net
/org/freedesktop/Hal/devices/net_3a_67_56_92_fb_73
/org/freedesktop/Hal/devices/net_computer_loopback
/org/freedesktop/Hal/devices/net_00_1c_c0_6d_8a_f7
The HAL specification specifies a system namespace which contains information about a system and it's currently running kernel.  The following script retrieves certain information from the system namespace.
udi=$(hal-find-by-property --key info.product --string Computer)
printf "Board Manufacturer: $(hal-get-property --udi $udi --key system.board.vendor)\n"
printf " Model: $(hal-get-property --udi $udi --key system.board.product)\n"
printf " Version: $(hal-get-property --udi $udi --key system.board.version)\n"
printf " Serial No.: $(hal-get-property --udi $udi --key system.board.serial)\n"
printf " Release Date: $(hal-get-property --udi $udi --key system.firmware.release_date)\n"
printf " Bios Version: $(hal-get-property --udi $udi --key system.firmware.version)\n"
printf " Motherboard UUID: $(hal-get-property --udi $udi --key system.hardware.uuid)\n"
Here is the output from the computer which I used to write this post.
Board Manufacturer: Intel Corporation
Model: DX48BT2
Version: AAE26191-205
Serial No.: BQBQ8280004G
Release Date: 08/07/2008
Bios Version: BTX3810J.86A.1814.2008.0807.2334
Motherboard UUID: 2237F666-4F75-11DD-894F-0007E9747DB3
The next example retrieves certain information about all the storage devices on a system using the volume capability.  The volume namespace is for device objects that represent storage devices with a filesystem that are mountable.  Such device objects have the capability volume.
for udi in $(/usr/bin/hal-find-by-capability --capability volume)
do
mount=$(hal-get-property --udi $udi --key volume.mount_point)
device=$(hal-get-property --udi $udi --key block.device)
label=$(hal-get-property --udi $udi --key volume.label)
fstype=$(hal-get-property --udi $udi --key volume.fstype)
echo "$device $mount $fstype $label"
done
Here is sample output from this script.
/dev/sda1  /boot      ext3  /boot
/dev/sdb2 /abac ext3 /abac
/dev/sdb1 /home/fpm ext3 fpm
/dev/sda2 LVM2_member
/dev/sdc1 LVM2_member
/dev/sdc2 LVM2_member
You can also drill down and return information about specific devices such as an optical drive, i.e. device objects which have a capability of cdrom.
for i in $(hal-find-by-property --key storage.drive_type --string cdrom)
do
printf "Manufacturer: $(hal-get-property --udi $i --key storage.vendor)\n"
printf " Model: $(hal-get-property --udi $i --key block.device)\n"
printf " Bus: $(hal-get-property --udi $i --key storage.model)\n"
printf " Path: $(hal-get-property --udi $i --key storage.bus)\n"
done
Here is the output for the computer which I am using to write this post.  It contains a single DVD RW drive.
Manufacturer: HP
Model: /dev/sr0
Bus: DVD Writer 1070d
Path: pci
In many cases there are multiple ways of retrieving the information you want.  For example, here is another way of retrieving the same information, together with details of the volume label and mount point if there is a disk in the drive, using the storage.cdrom capability.
for udi in $(/usr/bin/hal-find-by-capability --capability storage.cdrom)
do
device=$(hal-get-property --udi $udi --key block.device)
vendor=$(hal-get-property --udi $udi --key storage.vendor)
model=$(hal-get-property --udi $udi --key storage.model)
if [[ $(hal-get-property --udi $udi --key storage.removable.media_available) = true ]]
then
parent_udi=$(hal-find-by-property --key block.storage_device --string $udi)
mount=$(hal-get-property --udi $parent_udi --key volume.mount_point)
label=$(hal-get-property --udi $parent_udi --key volume.label)
fi
printf "$vendor $model $device $mount $label\n"
done
Here is the output from this script when there is a DVD in the drive followed by when there is not a DVD in the drive.
$ ./findodrive
HP DVD Writer 1070d /dev/sr0 /media/Kota_Kinabalu1 Kota_Kinabalu1
$ ./findodrive
HP DVD Writer 1070d /dev/sr0
Here is a script to list details about all removable USB storage drives that are currently installed on your system.
#!/bin/ksh93
#
# list attached USB storage devices
#

for udi in $(/usr/bin/hal-find-by-capability --capability storage)
do
device=$(hal-get-property --udi $udi --key block.device)
vendor=$(hal-get-property --udi $udi --key storage.vendor)
model=$(hal-get-property --udi $udi --key storage.model)
if [[ $(hal-get-property --udi $udi --key storage.bus) = "usb" ]]
then
parent_udi=$(hal-find-by-property --key block.storage_device --string $udi)
mount=$(hal-get-property --udi $parent_udi --key volume.mount_point)
label=$(hal-get-property --udi $parent_udi --key volume.label)
media_size=$(hal-get-property --udi $udi --key storage.removable.media_size)
size=$(( ceil(media_size/(1000*1000*1000)) ))
printf "$vendor $model $device $mount $label "${size}GB" \n"
fi
done
And here is the output when two USB thumb drives are present.
./listusb   
Kingston DataTraveler 2.0 /dev/sdd /media/KINGSTON KINGSTON 1GB
Kingston DataTraveler 2.0 /dev/sdc /media/USB-4GB USB-4GB 4GB
$
Well that is about all for now.  You should have gained sufficient information from this post to go away and experiment on your own system. 

Remember, however, that the HAL specification and it's implementation in GNU/Linux distributions is still somewhat in a state of flux.  The above examples worked on Fedora 10 as of the date of this post.  If an example does not work on your system, I recommend that you first check the output from the lshal utility to see if the device object and associated device properties are instantiated on your system.  You can use udevadm --monitor to see what kernel events are being pushed out to user space.  HAL monitors these events.  You can also use lshal --monitor to see what events HAL emits to user space.