1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409
|
===============
pydbus tutorial
===============
:Author: `Linus Lewandowski`_
:Based on: python-dbus tutorial by Simon McVittie, `Collabora Ltd.`_ (2006-06-14)
:Date: 2016-09-26
.. _`Collabora Ltd.`: http://www.collabora.co.uk/
.. _`Linus Lewandowski`: http://lew21.net/
This tutorial requires Python 2.7 or up, and ``pydbus`` 0.6 or up.
.. contents::
.. --------------------------------------------------------------------
.. _Bus object:
.. _Bus objects:
Connecting to the Bus
=====================
Applications that use D-Bus typically connect to a *bus daemon*, which
forwards messages between the applications. To use D-Bus, you need to create a
``Bus`` object representing the connection to the bus daemon.
There are generally two bus daemons you may be interested in. Each user
login session should have a *session bus*, which is local to that
session. It's used to communicate between desktop applications. Connect
to the session bus by creating a ``SessionBus`` object::
from pydbus import SessionBus
session_bus = SessionBus()
The *system bus* is global and usually started during boot; it's used to
communicate with system services like systemd_, udev_ and NetworkManager_.
To connect to the system bus, create a ``SystemBus`` object::
from pydbus import SystemBus
system_bus = SystemBus()
Of course, you can connect to both in the same application.
For special purposes, you might use a non-default Bus using the Bus class.
.. _systemd:
https://www.freedesktop.org/wiki/Software/systemd/
.. _udev:
https://www.kernel.org/pub/linux/utils/kernel/hotplug/udev/udev.html
.. _NetworkManager:
https://wiki.gnome.org/Projects/NetworkManager
.. --------------------------------------------------------------------
D-Bus objects
=============
D-Bus applications can export objects for other applications' use. To
start working with an object in another application, you need to know:
* The *bus name*. This identifies which application you want to
communicate with. You'll usually identify applications by a
*well-known name*, which is a dot-separated string starting with a
reversed domain name, such as ``org.freedesktop.NetworkManager``
or ``com.example.WordProcessor``.
* The *object path*. Applications can export many objects - for
instance, example.com's word processor might provide an object
representing the word processor application itself and an object for
each document window opened, or it might also provide an object for
each paragraph within a document.
To identify which one you want to interact with, you use an object path,
a slash-separated string resembling a filename. For instance, example.com's
word processor might provide an object at ``/`` representing the word
processor itself, and objects at ``/documents/123`` and
``/documents/345`` representing opened document windows.
All objects have methods, properties and signals.
Setting up an event loop
========================
To handle signals emitted by exported objects, or to export your own objects, you need to setup an event loop.
The only main loop supported by ``pydbus`` is GLib.MainLoop.
GLib.MainLoop
-------------
To create the loop object use::
from gi.repository import GLib
loop = GLib.MainLoop()
To execute the loop use::
loop.run()
While ``loop.run()`` is executing, GLib will watch for signals you're subscribed to, or accesses to objects you exported, and execute correct callbacks when appropriate. To stop, call ``loop.quit()``.
.. _proxy object:
Accessing exported objects
==========================
To interact with a remote object, you use a *proxy object*. This is a
Python object which acts as a proxy or "stand-in" for the remote object -
when you call a method on a proxy object, this causes dbus-python to make
a method call on the remote object, passing back any return values from
the remote object's method as the return values of the proxy method call.
Obtaining proxy objects
-----------------------
.. _bus.get:
To obtain a proxy object, call the ``get`` method on the ``Bus``.
For example, NetworkManager_ has the well-known name
``org.freedesktop.NetworkManager`` and exports an object whose object
path is ``/org/freedesktop/NetworkManager``, plus an object per network
interface at object paths like
``/org/freedesktop/NetworkManager/Devices/eth0``. You can get a proxy
for the object representing eth0 like this::
from pydbus import SystemBus
bus = SystemBus()
proxy = bus.get('org.freedesktop.NetworkManager',
'/org/freedesktop/NetworkManager/Devices/0')
pydbus has implemented shortcuts for the most common cases. If you
start the bus name with "." (".NetworkManager"), "org.freedesktop" will
become automatically prepended. If you specify a relative object path
(without the leading "/"), the bus name transformed to a path format
will get prepended ("/org/freedesktop/NetworkManager/"). If you don't
specify the object path at all, the transformed bus name will be used
automatically ("/org/freedesktop/NetworkManager"). Therefore, you may
rewrite the above code as::
from pydbus import SystemBus
bus = SystemBus()
dev = bus.get('.NetworkManager', 'Devices/0')
Object API
-----------
To see the API of a specific proxy object, use help()::
help(dev)
To call a method::
dev.Disconnect()
To read a property::
print(dev.Autoconnect)
To set a property::
dev.Autoconnect = True
.. _signal.connect:
To subscribe to a signal::
dev.StateChanged.connect(print)
loop.run()
connect() returns a Subscription object with a disconnect() method, that can be used to stop watching the signal. Also, it can be used as a context manager (with the ''with'' statement), to automatically disconnect at the end of the scope.
.. _onSignal:
Alternatively, you can set the on`Signal` property::
dev.onStateChanged = print
loop.run()
This way, you can unsubscribe from the signal by setting the property to None.
However, don't mix subscriptions in one of those ways with unsubscribtions
in another, it won't work.
See also
~~~~~~~~
See the examples in ``examples/systemctl.py`` and ``tests/gnome_music.py``.
Interfaces
----------
D-Bus uses *interfaces* to provide a namespacing mechanism for methods,
signals and properties. An interface is a group of related methods, signals
and properties, identified by a name which is a series of dot-separated components
starting with a reversed domain name. For instance, each NetworkManager_
object representing a network interface implements the interface
``org.freedesktop.NetworkManager.Device``, which has methods like
``Disconnect``.
An object may have multiple interfaces. They may be incompatible, for example
when using some sort of API versioning. By default, pydbus merges all the
interfaces to offer a single proxy object's API, but it's possible to obtain
a view providing only a single interface::
dev = bus.get('.NetworkManager', 'Devices/0')
dev_api = dev['org.freedesktop.NetworkManager.Device']
You may use all of the proxy object members described in the previous chapter
on the dev_api too.
.. _bus.subscribe:
Signal matching
---------------
You may also match the signals using a pattern.
See ``help(bus.subscribe)`` for more details.
.. --------------------------------------------------------------------
Exporting own objects
=====================
Objects made available to other applications over D-Bus are said to be
*exported*.
To export objects, the Bus needs to be connected to an event loop - see
section `Setting up an event loop`_. Exported methods will only be called,
and queued signals will only be sent, while the event loop is running.
Class preparation
-----------------
To prepare a class for exporting on the Bus, provide the dbus introspection XML
in a ''dbus'' class property or in its ''docstring''. For example::
from pydbus.generic import signal
class Example(object):
"""
<node>
<interface name='net.lew21.pydbus.TutorialExample'>
<method name='EchoString'>
<arg type='s' name='a' direction='in'/>
<arg type='s' name='response' direction='out'/>
</method>
<property name="SomeProperty" type="s" access="readwrite">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
</property>
</interface>
</node>
"""
def EchoString(self, s):
"""returns whatever is passed to it"""
return s
def __init__(self):
self._someProperty = "initial value"
@property
def SomeProperty(self):
return self._someProperty
@SomeProperty.setter
def SomeProperty(self, value):
self._someProperty = value
self.PropertiesChanged("net.lew21.pydbus.TutorialExample", {"SomeProperty": self.SomeProperty}, [])
PropertiesChanged = signal()
If you don't want to put XML in a Python file, you can add XML files to your Python package and use them this way::
import pkg_resources
ifaces = ["org.mpris.MediaPlayer2", "org.mpris.MediaPlayer2.Player", "org.mpris.MediaPlayer2.Playlists", "org.mpris.MediaPlayer2.TrackList"]
MediaPlayer2.dbus = [pkg_resources.resource_string(__name__, "mpris/" + iface + ".xml").decode("utf-8") for iface in ifaces]
.. _bus.publish:
Object publication
------------------
To publish an object, use the ``bus.publish`` method::
bus.publish("net.lew21.pydbus.TutorialExample", Example())
loop.run()
Here, publish() both binds the service to the net.lew21.pydbus.TutorialExample
bus name, and exports the object as /net/lew21/pydbus/TutorialExample.
Note, that you can use the publish() method only once per a bus name
that you want to bind. However, you can use it to export multiple objects
- by passing them in additional parameters to the method::
bus.publish("net.lew21.pydbus.TutorialExample",
Example(),
("Subdir1", Example()),
("Subdir2", Example()),
("Subdir2/Whatever", Example())
)
loop.run()
The 2nd, 3rd, ... arguments can be objects or tuples of a path and a object.
``bus.publish()`` uses the same path-deducing (and bus-name-deducing) logic that's
used in ``bus.get()``, so you may use relative paths or absolute paths, depending
on your needs.
Like ``signal.connect()``, ``bus.publish()`` returns an object with an ``unpublish()``
method, that can be used as a context manager.
See also
~~~~~~~~
See the example in ``examples/clientserver/server.py``.
.. _bus.request_name:
.. _bus.register_object:
Lower level API
---------------
Sometimes, you can't just publish everything in one call, you need more control
over the process of binding a name and exporting single objects.
In this case, you can use ``bus.request_name()`` and ``bus.register_object()`` yourself.
See ``help(bus.request_name)`` and ``help(bus.register_object)`` for details.
.. --------------------------------------------------------------------
Data types
==========
Unlike Python, D-Bus is statically typed. Each method and signal takes arguments of predefined types; each method returns value(s) of predefined types; and each property has a predefined type. You can't dynamically change those types.
D-Bus has an introspection mechanism, which ``pydbus`` uses to discover
the correct argument types. Python types are converted into the right
D-Bus data types automatically, if possible; ``TypeError`` is raised
if the type is inappropriate.
Container types
---------------
D-Bus supports four container types: array (a variable-length sequence of the
same type), struct (a fixed-length sequence whose members may have
different types), dictionary (a mapping from values of the same basic type to
values of the same type), and variant (a container which may hold any
D-Bus type, including another variant).
Arrays are represented by Python lists. The signature of an array is 'ax'
where 'x' represents the signature of one item. For instance, you could
also have 'as' (array of strings) or 'a(ii)' (array of structs each
containing two 32-bit integers).
Structs are represented by Python tuples. The signature of a struct
consists of the signatures of the contents, in parentheses - for instance
'(is)' is the signature of a struct containing a 32-bit integer and a string.
Dictionaries are represented by Python dictionaries.
The signature of a dictionary is 'a{xy}' where 'x' represents the
signature of the keys (which may not be a container type) and 'y'
represents the signature of the values. For instance,
'a{s(ii)}' is a dictionary where the keys are strings and the values are
structs containing two 32-bit integers.
Return values
-------------
If a D-Bus method returns no value, the Python proxy method will return ``None``.
If a D-Bus method returns a single value, it will be returned directly.
Otherwise, Python proxy method will return a tuple containing all the values.
.. --------------------------------------------------------------------
License for this document
=========================
Copyright 2006-2007 `Collabora Ltd.`_
Copyright 2016 `Linus Lewandowski`_
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
..
vim:set ft=rst sw=4 sts=4 et tw=72:
|