27

This is a follow-up question to A list of available DBus services.

The following python code will list all available DBus services.

import dbus
for service in dbus.SystemBus().list_names():
    print(service)

How do we list out the object paths under the services in python? It is ok if the answer does not involve python although it is preferred.

I am using Ubuntu 14.04

8 Answers8

20

QT setups provide the most convenient way to do it, via qdbus:

qdbus --system org.freedesktop.UPower

prints

/
/org
/org/freedesktop
/org/freedesktop/UPower
/org/freedesktop/UPower/Wakeups
/org/freedesktop/UPower/devices
/org/freedesktop/UPower/devices/line_power_ADP0
/org/freedesktop/UPower/devices/DisplayDevice
/org/freedesktop/UPower/devices/battery_BAT0

As to the python way... per the official docs (under standard interfaces):

There are some standard interfaces that may be useful across various D-Bus applications.

org.freedesktop.DBus.Introspectable

This interface has one method:

org.freedesktop.DBus.Introspectable.Introspect (out STRING xml_data)

Objects instances may implement Introspect which returns an XML description of the object, including its interfaces (with signals and methods), objects below it in the object path tree, and its properties.

So here's a very simplistic example that should get you started. It uses xml.etree.ElementTree and dbus:

#!/usr/bin/env python

import dbus
from xml.etree import ElementTree

def rec_intro(bus, service, object_path):
    print(object_path)
    obj = bus.get_object(service, object_path)
    iface = dbus.Interface(obj, 'org.freedesktop.DBus.Introspectable')
    xml_string = iface.Introspect()
    for child in ElementTree.fromstring(xml_string):
        if child.tag == 'node':
            if object_path == '/':
                object_path = ''
            new_path = '/'.join((object_path, child.attrib['name']))
            rec_intro(bus, service, new_path)

bus = dbus.SystemBus()
rec_intro(bus, 'org.freedesktop.UPower', '/org/freedesktop/UPower')

It recursively introspects org.freedesktop.UPower starting from e.g. /org/freedesktop/UPower and prints all object paths (node names):

/org/freedesktop/UPower
/org/freedesktop/UPower/Wakeups
/org/freedesktop/UPower/devices
/org/freedesktop/UPower/devices/DisplayDevice
/org/freedesktop/UPower/devices/battery_BAT0
/org/freedesktop/UPower/devices/line_power_ADP0

which is pretty much what you'd get if you used d-feet (not that you'd need it):

enter image description here

don_crissti
  • 82,805
  • How do I create a list of object paths from rec_intro(bus, 'org.freedesktop.UPower', '/org/freedesktop/UPower') ? – Khurshid Alam Apr 28 '17 at 10:03
  • No, I mean creating a python list of object paths, so that I can check (in my script) if particular object-path exists in the list. It prints the objectpath alright., But I want something like k = rec_intro(bus, 'org.freedesktop.UPower', '/org/freedesktop/UPower'). I suppose it is possible by modifying the function little bit. – Khurshid Alam Apr 28 '17 at 10:39
  • Example code with qbus: bus = dbus.SessionBus()..... obj_path = '/org/gnome/Gnote/RemoteControl'.......... cmd = 'qdbus org.gnome.Gnote'......... while obj_path not in ((subprocess.check_output(cmd, shell=True)).decode("utf-8")).split("\n"): ........pass – Khurshid Alam Apr 28 '17 at 10:41
  • @KhurshidAlam - initialize a list before the function e.g. mylist=[] then replace print with mylist.append and then as the last command in that function block return mylist - that's pretty much what there is... you can then iterate over the list or whatever e.g. add at the bottom of the script for x in mylist: print("OBJ_PATH", x) to have them printed with a OBJ_PATH prefix... – don_crissti Apr 28 '17 at 13:32
8

I am not sure you can do this programmatically in Python. You might but it will be a huge headache to figure out how. I tried to do it before and ended up hating Dbus. Anyhow I recommend to use d-feet if you want to investigate things. Below is a screenshot that I stole from my blog.

enter image description here

Once you know the program name, object path, etc. you can then use Python to access those things.

Example

progname = 'org.freedesktop.NetworkManager'
objpath  = '/org/freedesktop/NetworkManager'
intfname = 'org.freedesktop.NetworkManager'
methname = 'GetDevices'

bus = dbus.SystemBus()

obj = bus.get_object(progname, objpath) interface = dbus.Interface(obj, intfname) # Get the interface to obj method = interface.get_dbus_method(methname) # The method on that interface

method() # And finally calling the method

As you see, it's a pain in the ass to get a simple thing done. But this is the easiest workflow you can get with Dbus!

So use a GUI tool to find out the object paths, interfaces, etc. Then use the code snippet above as a template to access those things. Also I suggest you do this via IPython's interpreter to see what methods, properties, etc. each object has (by hitting tab).

FeRD
  • 921
Pithikos
  • 3,294
4

If the service has an object implementing org.freedesktop.DBus.ObjectManager, its method GetManagedObjects returns “all objects, interfaces and properties in a single method call.” For example, UDisks2 has such an object.

beroal
  • 201
3

Here's another way using Python and GDBus. The python-dbus module is I believe being deprecated (very slowly), and its not the best API. There are a few other promising projects for Python which can be found by googling.

For the Gtk/GLib/GObject world, the simplest is to use GDBus which is built in to Gio library (which comes with GLib). This is the recommended way for new Gtk-based code (rather than dbus module). If you are writing a Gtk app or trying to script a Gtk-based desktop environment (Gnome, Xfce, Cinnamon, Mate, Pantheon etc.), these libraries are probably already available. You can use in Python through Gobject Introspection (python gi module). Python gi API Docs here.

Here is an example introspect function which returns a single DBusNodeInfo.

from gi.repository import Gio, GLib

def introspect(bus, name, object_path): res = bus.call_sync( name, # bus_name object_path, # object_path 'org.freedesktop.DBus.Introspectable', # interface_name 'Introspect', # method_name None, # parameters GLib.VariantType("(s)"), # reply_type Gio.DBusCallFlags.NONE, # flags -1, # timeout_msecs None # cancellable )

if not res:
    return None

return Gio.DBusNodeInfo.new_for_xml(res[0])

(API docs link: Gio.DBusConnection.call_sync.)

Note that you will need to get the bus doing something like

bus = Gio.bus_get_sync(Gio.BusType.SYSTEM)

or

bus = Gio.bus_get_sync(Gio.BusType.SESSION)

(See Gio.bus_get_sync docs)

DBusNodeInfo has nodes, interfaces and path properties. The path property contains just the last segment of the actual path (e.g. "bar", not "/foo/bar"). nodes is a list of DBusNodeInfo, but note that these are not recursively introspected, you must iterate over them, build the absolute path by joining to the parent path with a slash, and call introspect again.

As you can see, the library includes XML parser and parse tree API, unlike python-dbus, so there is no need to use Python's xml.etree etc.

Recursive introspection

Building on the above introspect function you could do something like (Python 3 code):

def introspect_tree(bus, name, object_path):
    node = introspect(bus, name, object_path)
if node:
    yield object_path, node

    if object_path == '/':
        object_path = ''

    for child in node.nodes:
        yield from introspect_tree(bus, name, f"{object_path}/{child.path}")

This is a generator of (object_path, node) pairs, where object_path is the absolute path and node is the DBusNodeInfo object.

If you just the want paths:

bus = Gio.bus_get_sync(Gio.BusType.SYSTEM)
for path, node in introspect_tree(bus, 'org.freedesktop.UPower', '/org/freedesktop/UPower'):
    print(path)

Prints:

/org/freedesktop/UPower
/org/freedesktop/UPower/Wakeups
/org/freedesktop/UPower/devices
/org/freedesktop/UPower/devices/DisplayDevice
/org/freedesktop/UPower/devices/battery_BAT0
/org/freedesktop/UPower/devices/line_power_ADP0

Introspecting interfaces

The interfaces property of DBusNodeInfo objects contains a list of Gio.DBusInterfaceInfo objects of the interfaces the object has. From there, you have name, methods, properties and signals properties.

Asynchronous API

Note that the above code is all synchronous, which is fine for command line apps and simple tools. However, there is also an asynchronous API which you will definitely want to use for GUI apps. All the functions that end with _sync have async versions e.g. call_sync has call and call_finish. There's also support for timeouts and cancellation (see timeout_msec and cancellable parameters of call/call_sync for example). Pretty easy to use once you figure it out, can be hard to find the docs though. Reading existing source code is a good way, especially the d-feet app source code which demonstrates asynchronous use of the APIs mentioned here.

gdbus CLI tool

Another fantastic part of GDBus is the gdbus command line tool which comes with the library (should be available in your Gtk-based desktop environments). Like qdbus (mentioned above), it has tab completion and excellent introspection functionality built in and is very useful for quick exploration and discovering names and paths.

Try:

gdbus introspect -r --system --dest org.freedesktop.UPower --object-path /org/freedesktop/UPower
ejm
  • 601
2

What I know from my experience to get the object paths of a bus name (service) it is possible to introspect with object path '/' i.e. (using the above example)

introspectfunc('org.freedesktop.UPower', '/') 

this should return:

<node name="/"> 
<node name="org"/>
<node name="org"/>
<node name="org"/>
<node name="org"/>
<node name="org"/>
<node name="org"/></node>

then introspect with path '/org'

introspectfunc('org.freedesktop.UPower', '/org')

this should return:

<node name="/org"> 
<node name="freedesktop"/>
<node name="freedesktop"/>
<node name="freedesktop"/>
<node name="freedesktop"/>
<node name="freedesktop"/>
<node name="freedesktop"/></node>

and so on:

introspectfunc('org.freedesktop.UPower', '/org/freedesktop')
introspectfunc('org.freedesktop.UPower', '/org/freedesktop/UPower')
etc.

It is like going through the folder structure of the hard drive where the object path '/' is the root and every node is subfolder. This seems the best way to retrieve the object paths of a particular bus name (service) and to construct a collection containing the object paths

qnbibiqn
  • 21
  • 2
2

As per #don_crissti answers, I implemented, This solution gives to interface name and method and signals information

import dbus
from xml.etree import ElementTree
bus = dbus.SystemBus()

def busNames():
    return [ name for name in  bus.list_names() if not name.startswith(":") ]


def pathNames(service,object_path="/",paths=None,serviceDict=None):
    if paths == None:
        paths = {}
    paths[object_path] = {}
    obj = bus.get_object(service, object_path)
    iface = dbus.Interface(obj, 'org.freedesktop.DBus.Introspectable')
    xml_string = iface.Introspect()
    root = ElementTree.fromstring(xml_string)
    for child in root:
        if child.tag == 'node':
            if object_path == '/':
                    object_path = ''
            new_path = '/'.join((object_path, child.attrib['name']))
            pathNames(service, new_path,paths)
        else:
            if object_path == "":
                object_path = "/"
            functiondict = {}
            paths[object_path][child.attrib["name"]] = functiondict
            for func in child.getchildren():
                if func.tag not in functiondict.keys():
                    functiondict[func.tag] =[]
                functiondict[func.tag].append(func.attrib["name"])
    if serviceDict == None:
        serviceDict = {}
    serviceDict[service] = paths
    return serviceDict



import json
import random
service=random.sample(busNames(),1).pop()
print service
print json.dumps(pathNames(service),indent=3)
2

python with dasbus

With dasbus I wrote this POC:

from xml.etree import ElementTree

from dasbus.connection import SystemMessageBus from dasbus.error import DBusError

def tree(bus, bus_name, path=None): obj = bus.get_proxy(bus_name, path or "/") try: xml = obj.Introspect() except DBusError as exc: # implement here your exception handling raise Exception(str(exc)) if path: yield path root = ElementTree.fromstring(xml) for node in root.findall("node[@name]"): yield from tree(bus, bus_name, (path or "") + "/" + node.attrib["name"])

if name == "main": bus = SystemMessageBus() for pth in tree(bus, "org.freedesktop.login1"): print(pth)

which outputs

/org
/org/freedesktop
/org/freedesktop/LogControl1
/org/freedesktop/login1
/org/freedesktop/login1/user
/org/freedesktop/login1/user/_1000
/org/freedesktop/login1/user/self
/org/freedesktop/login1/session
/org/freedesktop/login1/session/_326
/org/freedesktop/login1/session/_324
/org/freedesktop/login1/session/_327
/org/freedesktop/login1/session/self
/org/freedesktop/login1/session/auto
/org/freedesktop/login1/seat
/org/freedesktop/login1/seat/seat0

shell with busctl

From the shell, with systemd busctl you can use the tree command:

$ busctl tree org.freedesktop.login1
└─/org
  └─/org/freedesktop
    ├─/org/freedesktop/LogControl1
    └─/org/freedesktop/login1
      ├─/org/freedesktop/login1/seat
      │ └─/org/freedesktop/login1/seat/seat0
      ├─/org/freedesktop/login1/session
      │ ├─/org/freedesktop/login1/session/_324
      │ ├─/org/freedesktop/login1/session/_326
      │ ├─/org/freedesktop/login1/session/_327
      │ ├─/org/freedesktop/login1/session/auto
      │ └─/org/freedesktop/login1/session/self
      └─/org/freedesktop/login1/user
        ├─/org/freedesktop/login1/user/_1000
        └─/org/freedesktop/login1/user/self
don_crissti
  • 82,805
Stefano M
  • 131
1

According to the partly complete answer of Reegan Miranda I ported the code to python 3.9 and made it introspect the whole bus system by regex filter.

#!/bin/python
#> usage: ./query-dbus.py --pattern "^.*activit.*$"
import argparse
parser = argparse.ArgumentParser(description='query dbus tree')
parser.add_argument('-s','--system', help='query SystemBus', required=False, default=False)
parser.add_argument('-S','--session', help='query SessionBus (default)', required=False, default=True)
parser.add_argument('-p','--pattern', help='search pattern', required=False, default=None)
args = vars(parser.parse_args())

import re pattern = None if args['pattern']: pattern = re.compile(args['pattern'], re.IGNORECASE)

import dbus from xml.etree import ElementTree

if args['session']: bus = dbus.SessionBus() else: bus = dbus.SystemBus()

def busNames(): return [ name for name in bus.list_names() if not name.startswith(":") ]

def objectTree(service, object_path="/", paths=None, serviceDict=None): if str(service).startswith(':') == True: return

if paths == None:
    paths = {}
pathdict = {}
try:
  try:
      obj = bus.get_object(service, object_path)
  except dbus.exceptions.DBusException:
      print(&quot;[ERROR] service \&quot;%s\&quot; doesn't exist or isn't running&quot; \
            % service)
      return

  try:
    iface = dbus.Interface(obj, 'org.freedesktop.DBus.Introspectable')
    xml_string = iface.Introspect()
  except dbus.exceptions.DBusException:
    print(&quot;[ERROR] service \&quot;%s\&quot; doesn't reply request. timeout.&quot; \
            % service)
    return
  root = ElementTree.fromstring(xml_string)
  for child in root:
      if child.tag == 'node':
          if object_path == '/':
                  object_path = ''
          new_path = '/'.join((object_path, child.attrib['name']))
          objectTree(service, new_path, paths)
      else:
          if object_path == &quot;&quot;:
              object_path = &quot;/&quot;
          functiondict = {}

          for func in child:
            if pattern is None or pattern.match(func.attrib[&quot;name&quot;]):
              if func.tag not in functiondict.keys():
                  functiondict[func.tag] = []
              functiondict[func.tag].append(func.attrib[&quot;name&quot;])

          if functiondict:
            pathdict[child.attrib[&quot;name&quot;]] = functiondict
  if serviceDict == None:
      serviceDict = {}
  if pathdict:
    paths[object_path] = pathdict
  if paths:
    serviceDict[service] = paths
  return serviceDict
except:
  raise

import json import random

for service in bus.list_names(): object_tree = objectTree(service) if object_tree: print(json.dumps(object_tree,indent=3))

domson
  • 341