dbus-next

repository·master·Indexed 42 days ago

https://github.com/altdesktop/python-dbus-next

A high-level, pure-Python 3 DBus library for integrating applications into Linux desktop and mobile environments. It features zero dependencies and supports multiple IO backends, including asyncio and GLib. The library provides high-level client interfaces via ProxyObject and ProxyInterface, a service definition system using ServiceInterface, and a low-level interface for direct Message handling.

Tokens
10.8K
Snippets
30
Records
45
Agent score
50%

What's inside dbus-next

  1. Overview of python-dbus-next interfaces

    master

    python-dbus-next provides three distinct ways to interact with the DBus message bus system, depending on your use case:

    1. High Level Client: Use this to communicate with an existing interface exported on the bus by another client. You interact with the interface through a proxy object.
    2. High Level Service: Use this to export a service interface from your own application, allowing other clients to connect and interact with your application at runtime.
    3. Low Level Interface: Use this to work with DBus messages directly. This is intended for applications that interact with the DBus daemon directly or for developers building their own high-level abstractions.
  2. How the high-level client and service handle D-Bus types

    master

    In python-dbus-next, D-Bus values are associated with a signature that specifies their types. The library uses introspection XML to automate type handling:

    • High-level Client: Dynamically creates Python classes based on introspection data. Methods and signals are automatically generated with arguments that match the type signatures defined in the XML.
    • High-level Service: Performs the inverse; it introspects your Python classes to generate the introspection XML data that is then advertised on the D-Bus for clients to consume.
  3. Choose a MessageBus implementation based on your main loop

    master

    The MessageBus manages the connection to the DBus daemon, handles sending/receiving messages, and wires up high-level interfaces. You must choose an implementation that matches your application's event loop:

    • dbus_next.aio.MessageBus: Use this if your application uses an asyncio main loop. This is recommended for standalone applications as it provides a modern async/await API.
    • dbus_next.glib.MessageBus: Use this if your application uses a GLib main loop (for example, if you are building a GTK application). This implementation uses a callback/synchronous interface.
  4. How ProxyObjects and ProxyInterfaces work together

    master

    In python-dbus-next, D-Bus nodes and interfaces are represented by two main abstractions:

    1. ProxyObject (BaseProxyObject): Represents a D-Bus node at a specific path. A node can contain multiple interfaces and child nodes.
    2. ProxyInterface (BaseProxyInterface): Represents a specific interface within a ProxyObject. It exposes the methods, signals, and properties defined by that interface.

    To interact with a D-Bus service, you first obtain a ProxyObject from a MessageBus, and then use that object to retrieve a ProxyInterface for the specific interface you want to call.

  5. Use the introspection module to model D-Bus objects

    master

    The dbus_next.introspection module provides classes to programmatically represent the structure of D-Bus objects based on XML introspection data. This is useful for building custom interfaces or understanding the schema of an existing service.

    Key classes include:

    • Node: Represents a D-Bus object node.
    • Interface: Represents a D-Bus interface containing methods, signals, and properties.
    • Property: Represents a D-Bus property.
    • Method: Represents a D-Bus method.
    • Signal: Represents a D-Bus signal.
    • Arg: Represents an argument within a method or signal.
  6. Implement a D-Bus service using ServiceInterface

    master

    To create a D-Bus service, you must subclass dbus_next.service.ServiceInterface. This class allows you to define the interface that your service will expose to the D-Bus system. You use specific decorators to define the members of this interface:

    • @dbus_next.service.method: Marks a function as a D-Bus method that can be called by clients.
    • @dbus_next.service.signal: Marks a function as a D-Bus signal that your service can emit to notify listeners.
    • @dbus_next.service.dbus_property: Marks a function or property as a D-Bus property (supporting Get and Set operations).
  7. Use the Low-Level Interface to work with DBus messages directly

    master

    The low-level interface allows you to interact with DBus using the Message class and MessageBus methods directly, bypassing the high-level proxy/interface abstractions. This is useful for building tools like dbus-send, implementing custom BaseMessageBus versions, or working with non-standard DBus interfaces.

    Warning: Mixed use of the low-level and high-level interfaces on the same bus connection is not recommended.

    from dbus_next import Message, MessageBus
    
    bus = await MessageBus().connect()
    msg = Message(destination='org.freedesktop.DBus',
                  path='/org/freedesktop/DBus',
                  interface='org.freedesktop.DBus',
                  member='ListNames',
                  serial=bus.next_serial())
    
    reply = await bus.call(msg)
  8. Implement D-Bus authentication protocols

    master

    To implement the D-Bus authentication protocol within a dbus_next.message_bus.BaseMessageBus implementation, use the classes provided in the dbus_next.auth module. This allows you to define how the bus handles identity and connection security.

    Available authentication classes:

    • dbus_next.auth.Authenticator: The base class for implementing custom authentication logic.
    • dbus_next.auth.AuthExternal: Implements the EXTERNAL authentication mechanism (typically relies on Unix credentials).
    • dbus_next.auth.AuthAnnonymous: Implements anonymous authentication.
  9. How to export a High Level Service

    master

    To expose an interface on the D-Bus, you must subclass ServiceInterface and define your members using specific decorators. Once defined, you use MessageBus.export() to make the interface available at a specific object path on a connected bus.

    Core Components

    • ServiceInterface: The base class for all exported services. You must call super().__init__('interface.name') in your constructor.
    • @method(): Defines a D-Bus method. Parameters and return values must be annotated with DBus type signature strings (e.g., 'i' for integer, 's' for string). If using aio.MessageBus, methods can be coroutines.
    • @dbus_property(): Defines a D-Bus property getter. Use the standard Python @<property_name>.setter syntax to define a setter. Property getters/setters can be coroutines in aio.MessageBus.
    • @signal(): Defines a D-Bus signal. The value returned by the decorated method is broadcast to listeners. If the signal has multiple arguments, return them as a list.
    • DBusError: You can raise DBusError within methods or properties to send detailed error information back to the D-Bus client.

    Requesting a Well-Known Name

    If you want clients to find your service using a predictable name rather than a connection-specific one, call MessageBus.request_name('your.well-known.name') after connecting.

    from dbus_next.aio import MessageBus
    from dbus_next.service import ServiceInterface, method, dbus_property, signal
    
    class ExampleInterface(ServiceInterface):
        def __init__(self):
            super().__init__('com.example.SampleInterface0')
            self._bar = 105
    
        @method()
        def Frobate(self, foo: 'i', bar: 's') -> 'a{us}':
            return {1: 'one', 2: 'two'}
    
        @dbus_property()
        def Bar(self) -> 'y':
            return self._bar
    
        @Bar.setter
        def Bar(self, val: 'y'):
            self._bar = val
    
        @signal()
        def Changed(self) -> 'b':
            return True
    
    async def main():
        bus = await MessageBus().connect()
        interface = ExampleInterface()
        bus.export('/com/example/sample0', interface)
        await bus.request_name('com.example.name')
        await bus.wait_for_disconnect()
  10. Use the High-Level Client Interface

    master

    To consume a service on the DBus, use the MessageBus to connect and create a proxy object. The proxy object allows you to call methods, access properties, and listen to signals on a specific interface.

    Common workflow:

    1. Connect to the bus using await MessageBus().connect().
    2. Introspect the object to get its structure using await bus.introspect(service_name, object_path).
    3. Get a proxy object via bus.get_proxy_object(service_name, object_path, introspection).
    4. Access specific interfaces using obj.get_interface(interface_name).
    5. Call methods with await interface.method_name() or listen to signals using interface.on_signal_name(callback).
    from dbus_next.aio import MessageBus
    import asyncio
    
    async def main():
        bus = await MessageBus().connect()
        # Introspect the service
        introspection = await bus.introspect('org.mpris.MediaPlayer2.vlc', '/org/mpris/MediaPlayer2')
    
        # Get proxy object and interfaces
        obj = bus.get_proxy_object('org.mpris.MediaPlayer2.vlc', '/org/mpris/MediaPlayer2', introspection)
        player = obj.get_interface('org.mpris.MediaPlayer2.Player')
        properties = obj.get_interface('org.freedesktop.DBus.Properties')
    
        # Call a method
        await player.call_play()
    
        # Listen to a signal
        def on_properties_changed(interface_name, changed_properties, invalidated_properties):
            for changed, variant in changed_properties.items():
                print(f'property changed: {changed} - {variant.value}')
    
        properties.on_properties_changed(on_properties_changed)
    
    asyncio.run(main())
  11. Define a Service using ServiceInterface

    master

    To export a service on the DBus, inherit from ServiceInterface. Use decorators to define the DBus interface members:

    • @method(): Defines a DBus method. You can use Python type hints to specify DBus type signatures (e.g., what: 's' for a string).
    • @dbus_property(): Defines a DBus property. Use a corresponding .setter decorator to implement property updates.
    • @signal(): Defines a DBus signal.

    To make the service available:

    1. Instantiate your class with a name.
    2. Export it to a path using bus.export(path, interface_instance).
    3. Request the service name on the bus using await bus.request_name(name).
    from dbus_next.service import ServiceInterface, method, dbus_property, signal, Variant
    from dbus_next.aio import MessageBus
    import asyncio
    
    class ExampleInterface(ServiceInterface):
        def __init__(self, name):
            super().__init__(name)
            self._string_prop = 'kevin'
    
        @method()
        def Echo(self, what: 's') -> 's':
            return what
    
        @dbus_property()
        def string_prop(self) -> 's':
            return self._string_prop
    
        @string_prop.setter
        def string_prop_setter(self, val: 's'):
            self._string_prop = val
    
        @signal()
        def signal_simple(self) -> 's':
            return 'hello'
    
    async def main():
        bus = await MessageBus().connect()
        interface = ExampleInterface('test.interface')
        bus.export('/test/path', interface)
        await bus.request_name('test.name')
        await asyncio.get_event_loop().create_future()
    
    asyncio.run(main())