Implementation overview
The first part introduced the dbus components and how to access them using Python.
The goal is to write a program that lists and stores the names of the files on a CD after it has been inserted. The basic steps are:
Initialize dbus: Starting dbus threads
In order to get the dbus communication running in the python application, the following threads have to be started before you connect to any bus:
import gobject
import dbus
import dbus.glib
...
gobject.threads_init()
dbus.glib.threads_init()
This piece of code uses Python threads in the background.
HAL implementation specifics
In the first part we've seen how callbacks are hooked into the system:
bus = dbus.SystemBus()
bus.add_signal_receiver(device_added_callback,
"DeviceAdded",
"org.freedesktop.Hal.Manager",
"org.freedesktop.Hal",
"/org/freedesktop/Hal/Manager")
bus.add_signal_receiver(device_removed_callback,
"DeviceRemoved",
"org.freedesktop.Hal.Manager",
"org.freedesktop.Hal",
"/org/freedesktop/Hal/Manager")
When a CD is inserted DeviceAdded receives a string like this:
/org/freedesktop/Hal/devices/volume_label_backupcd1________
When the CD is ejected, DeviceRemoved is executed, receiving the same information. I'll refer to this string as udi in the following code.
This string refers to an HAL object, and describes a volume i.e. the medium you inserted, not the actual hardware (i.e. the CDROM drive). To get more information about this volume you have to get the HAL object via bus.get_object (as described before):
d_object = bus.get_object('org.freedesktop.Hal',udi)
Objects starting with /org/freedesktop/Hal/devices/... implement the interface org.freedesktop.Hal.Device, which in turn exposes (among others) the function GetAllProperties which returns a dictionary that contains further useful information:
d_interface = dbus.Interface(d_object,'org.freedesktop.Hal.Device')
properties = d_interface.GetAllProperties()
Interesting keys in this dictionary are:
To get a quick overview, type
lshal | more
in a shell. (You will need the more command.)
The interface org.freedesktop.Hal.Device (implemented by all .../Hal/devices/ ) also contains the signal PropertyModified - which will be fired when properties change (in our example: the info volume.mount_point is empty when the device is added. It becomes available later when the auto-mounter of the desktop framework has mounted the device.). You can add a callback for this signal:
def device_added_hook(udi):
....
bus.add_signal_receiver(property_modified_callback,
"PropertyModified",
"org.freedesktop.Hal.Device",
"org.freedesktop.Hal",
udi)
def property_modified_callback(numupdates,updates):
...
numupdates are the number of entries in a list of updates. An "update" is a tuple with the following signature: (key_name, was_added, was_removed)
If a CD/DVD was mounted, the list of updates contains (among others) the following tuples:
('volume.mount_point', False, False)
('volume.is_mounted', False, False)
This means that the property was neither added (1st False) nor removed (2nd False), it was merely modified. We don't get any information about the new value of the property and more importantly we don't get any information about the object (i.e. the device) that triggered it.
For this reason the method add_signal_receiver can be extended:
...
bus.add_signal_receiver(property_modified_callback,
"PropertyModified",
"org.freedesktop.Hal.Device",
"org.freedesktop.Hal",
udi,
path_keyword = "sending_device" )
The parameter path_keyword defines the name of an additional parameter (in this example sending_device) for the property_modified_callback which will contain the “path” - or in my parlance “the object” - that triggered the signal. Now the property_modified_callback looks like this:
def property_modified_callback(numupdates, updates, sending_device = None):
...
Apart from path_keyword, add_signal_receiver can define even more additional parameters for the callback function:
HAL implementation: Filter out events that are not CD related
DeviceAdded is called for every new device in the system (network devices, bluetooth devices, etc.), and the property_modified_callback is invoked if any property of any device changes.
In order to filter out the CD related calls, we have to check the properties for suitable keys.
First, we have to ensure that the object describes a volume. For this we have to check if the property block.is_volume is present, and if so, that it is set to True.
The object will then have a property named info.parent, pointing back to the physical drive.
After retrieving the properties of that parent object, we first check block.is_volume, which has to be set to False, and then obtain the value of storage.drive_type, which is set to "cdrom", if the device is an optical drive.
...
# get object of the device that triggered the property_modified_callback
d_object = self.system_bus.get_object('org.freedesktop.Hal',sending_device)
d_interface = dbus.Interface(d_object,'org.freedesktop.Hal.Device')
# get their properties
d_properties = d_interface.GetAllProperties()
if not d_properties.has_key("block.is_volume"): return
if not d_properties["block.is_volume"]: return
parent_device = d_properties["info.parent"]
p_object = system_bus.get_object('org.freedesktop.Hal',parent_device)
p_interface = dbus.Interface(p_object,'org.freedesktop.Hal.Device')
p_properties = p_interface.GetAllProperties()
if not p_properties["block.is_volume"]:
if p_properties["storage.drive_type"] == "cdrom":
# further code here
Another approach is to get the list of all optical drives when the utility is launched and check the parent udi against this list:
system_bus = dbus.SystemBus()
hal_object = system_bus.get_object('org.freedesktop.Hal', '/org/freedesktop/Hal/Manager')
hal_manager = dbus.Interface(hal_object, 'org.freedesktop.Hal.Manager')
...
installed_optical_drives = hal_manager.FindDeviceStringMatch("storage.drive_type","cdrom")
...
if parent in installed_optical_drives:
...
Mount point is empty
At the time when the device is added volume.mount_point is empty. That's why the signals DeviceAdded and DeviceRemoved are not important in our application. You might want to use them anyway to display the state of your drive (e.g. disk inserted or nor) in the GUI.
The import part is the property_modified_callback. After the checks described above we know the label of the CD and its mount point. With this information we can obtain a file list and store it in any form we want.
The next part will show how DeviceKit.Disks handles these problems.
The first part introduced the dbus components and how to access them using Python.
The goal is to write a program that lists and stores the names of the files on a CD after it has been inserted. The basic steps are:
- Initialize dbus
- Wait for the notification when a CD is inserted into the drive.
- Wait for the notification when the volume is mounted by the (Gnome) automounter
- Read the file list below that mount point and store the info.
- Then perhaps eject the CD.
- Go back to step 2
Initialize dbus: Starting dbus threads
In order to get the dbus communication running in the python application, the following threads have to be started before you connect to any bus:
import gobject
import dbus
import dbus.glib
...
gobject.threads_init()
dbus.glib.threads_init()
This piece of code uses Python threads in the background.
HAL implementation specifics
In the first part we've seen how callbacks are hooked into the system:
bus = dbus.SystemBus()
bus.add_signal_receiver(device_added_callback,
"DeviceAdded",
"org.freedesktop.Hal.Manager",
"org.freedesktop.Hal",
"/org/freedesktop/Hal/Manager")
bus.add_signal_receiver(device_removed_callback,
"DeviceRemoved",
"org.freedesktop.Hal.Manager",
"org.freedesktop.Hal",
"/org/freedesktop/Hal/Manager")
When a CD is inserted DeviceAdded receives a string like this:
/org/freedesktop/Hal/devices/volume_label_backupcd1________
When the CD is ejected, DeviceRemoved is executed, receiving the same information. I'll refer to this string as udi in the following code.
This string refers to an HAL object, and describes a volume i.e. the medium you inserted, not the actual hardware (i.e. the CDROM drive). To get more information about this volume you have to get the HAL object via bus.get_object (as described before):
d_object = bus.get_object('org.freedesktop.Hal',udi)
Objects starting with /org/freedesktop/Hal/devices/... implement the interface org.freedesktop.Hal.Device, which in turn exposes (among others) the function GetAllProperties which returns a dictionary that contains further useful information:
d_interface = dbus.Interface(d_object,'org.freedesktop.Hal.Device')
properties = d_interface.GetAllProperties()
Interesting keys in this dictionary are:
- volume.label
description: CD label
typical value: empty, backup123 - volume.disc.has_data
description: data disk
typical value: 0, 1 - volume.disc.is_blank
description: blank media
typical value: 0, 1 - volume.mount_point
description: CD mount point
typical value: /media/backup123 - block.device
description: linux device descriptor
typical value: /dev/sr0 - storage.drive_type
typical value: cdrom
To get a quick overview, type
lshal | more
in a shell. (You will need the more command.)
The interface org.freedesktop.Hal.Device (implemented by all .../Hal/devices/ ) also contains the signal PropertyModified - which will be fired when properties change (in our example: the info volume.mount_point is empty when the device is added. It becomes available later when the auto-mounter of the desktop framework has mounted the device.). You can add a callback for this signal:
def device_added_hook(udi):
....
bus.add_signal_receiver(property_modified_callback,
"PropertyModified",
"org.freedesktop.Hal.Device",
"org.freedesktop.Hal",
udi)
def property_modified_callback(numupdates,updates):
...
numupdates are the number of entries in a list of updates. An "update" is a tuple with the following signature: (key_name, was_added, was_removed)
If a CD/DVD was mounted, the list of updates contains (among others) the following tuples:
('volume.mount_point', False, False)
('volume.is_mounted', False, False)
This means that the property was neither added (1st False) nor removed (2nd False), it was merely modified. We don't get any information about the new value of the property and more importantly we don't get any information about the object (i.e. the device) that triggered it.
For this reason the method add_signal_receiver can be extended:
...
bus.add_signal_receiver(property_modified_callback,
"PropertyModified",
"org.freedesktop.Hal.Device",
"org.freedesktop.Hal",
udi,
path_keyword = "sending_device" )
The parameter path_keyword defines the name of an additional parameter (in this example sending_device) for the property_modified_callback which will contain the “path” - or in my parlance “the object” - that triggered the signal. Now the property_modified_callback looks like this:
def property_modified_callback(numupdates, updates, sending_device = None):
...
Apart from path_keyword, add_signal_receiver can define even more additional parameters for the callback function:
- sender_keyword - for the connection name (usually in the internal numeric format)
- destination_keyword - None for broadcasts
- interface_keyword - interface name
- member_keyword - signal name
- path_keyword - object name
HAL implementation: Filter out events that are not CD related
DeviceAdded is called for every new device in the system (network devices, bluetooth devices, etc.), and the property_modified_callback is invoked if any property of any device changes.
In order to filter out the CD related calls, we have to check the properties for suitable keys.
First, we have to ensure that the object describes a volume. For this we have to check if the property block.is_volume is present, and if so, that it is set to True.
The object will then have a property named info.parent, pointing back to the physical drive.
After retrieving the properties of that parent object, we first check block.is_volume, which has to be set to False, and then obtain the value of storage.drive_type, which is set to "cdrom", if the device is an optical drive.
...
# get object of the device that triggered the property_modified_callback
d_object = self.system_bus.get_object('org.freedesktop.Hal',sending_device)
d_interface = dbus.Interface(d_object,'org.freedesktop.Hal.Device')
# get their properties
d_properties = d_interface.GetAllProperties()
if not d_properties.has_key("block.is_volume"): return
if not d_properties["block.is_volume"]: return
parent_device = d_properties["info.parent"]
p_object = system_bus.get_object('org.freedesktop.Hal',parent_device)
p_interface = dbus.Interface(p_object,'org.freedesktop.Hal.Device')
p_properties = p_interface.GetAllProperties()
if not p_properties["block.is_volume"]:
if p_properties["storage.drive_type"] == "cdrom":
# further code here
Another approach is to get the list of all optical drives when the utility is launched and check the parent udi against this list:
system_bus = dbus.SystemBus()
hal_object = system_bus.get_object('org.freedesktop.Hal', '/org/freedesktop/Hal/Manager')
hal_manager = dbus.Interface(hal_object, 'org.freedesktop.Hal.Manager')
...
installed_optical_drives = hal_manager.FindDeviceStringMatch("storage.drive_type","cdrom")
...
if parent in installed_optical_drives:
...
Mount point is empty
At the time when the device is added volume.mount_point is empty. That's why the signals DeviceAdded and DeviceRemoved are not important in our application. You might want to use them anyway to display the state of your drive (e.g. disk inserted or nor) in the GUI.
The import part is the property_modified_callback. After the checks described above we know the label of the CD and its mount point. With this information we can obtain a file list and store it in any form we want.
The next part will show how DeviceKit.Disks handles these problems.