from typing import Union from warnings import warn from .low_level import * __all__ = [ 'DBusAddress', 'new_method_call', 'new_method_return', 'new_error', 'new_signal', 'MessageGenerator', 'Properties', 'Introspectable', 'DBusErrorResponse', ] class DBusAddress: """This identifies the object and interface a message is for. e.g. messages to display desktop notifications would have this address:: DBusAddress('/org/freedesktop/Notifications', bus_name='org.freedesktop.Notifications', interface='org.freedesktop.Notifications') """ def __init__(self, object_path, bus_name=None, interface=None): self.object_path = object_path self.bus_name = bus_name self.interface = interface def __repr__(self): return '{}({!r}, bus_name={!r}, interface={!r})'.format(type(self).__name__, self.object_path, self.bus_name, self.interface) def with_interface(self, interface): return type(self)(self.object_path, self.bus_name, interface) class DBusObject(DBusAddress): def __init__(self, object_path, bus_name=None, interface=None): super().__init__(object_path, bus_name, interface) warn('Deprecated alias, use DBusAddress instead', stacklevel=2) def new_header(msg_type): return Header(Endianness.little, msg_type, flags=0, protocol_version=1, body_length=-1, serial=-1, fields={}) def new_method_call(remote_obj, method, signature=None, body=()): """Construct a new method call message This is a relatively low-level method. In many cases, this will be called from a :class:`MessageGenerator` subclass which provides a more convenient API. :param DBusAddress remote_obj: The object to call a method on :param str method: The name of the method to call :param str signature: The DBus signature of the body data :param tuple body: Body data (i.e. method parameters) """ header = new_header(MessageType.method_call) header.fields[HeaderFields.path] = remote_obj.object_path if remote_obj.bus_name is None: raise ValueError("remote_obj.bus_name cannot be None for method calls") header.fields[HeaderFields.destination] = remote_obj.bus_name if remote_obj.interface is not None: header.fields[HeaderFields.interface] = remote_obj.interface header.fields[HeaderFields.member] = method if signature is not None: header.fields[HeaderFields.signature] = signature return Message(header, body) def new_method_return(parent_msg, signature=None, body=()): """Construct a new response message :param Message parent_msg: The method call this is a reply to :param str signature: The DBus signature of the body data :param tuple body: Body data """ header = new_header(MessageType.method_return) header.fields[HeaderFields.reply_serial] = parent_msg.header.serial sender = parent_msg.header.fields.get(HeaderFields.sender, None) if sender is not None: header.fields[HeaderFields.destination] = sender if signature is not None: header.fields[HeaderFields.signature] = signature return Message(header, body) def new_error(parent_msg, error_name, signature=None, body=()): """Construct a new error response message :param Message parent_msg: The method call this is a reply to :param str error_name: The name of the error :param str signature: The DBus signature of the body data :param tuple body: Body data """ header = new_header(MessageType.error) header.fields[HeaderFields.reply_serial] = parent_msg.header.serial header.fields[HeaderFields.error_name] = error_name sender = parent_msg.header.fields.get(HeaderFields.sender, None) if sender is not None: header.fields[HeaderFields.destination] = sender if signature is not None: header.fields[HeaderFields.signature] = signature return Message(header, body) def new_signal(emitter, signal, signature=None, body=()): """Construct a new signal message :param DBusAddress emitter: The object sending the signal :param str signal: The name of the signal :param str signature: The DBus signature of the body data :param tuple body: Body data """ header = new_header(MessageType.signal) header.fields[HeaderFields.path] = emitter.object_path if emitter.interface is None: raise ValueError("emitter.interface cannot be None for signals") header.fields[HeaderFields.interface] = emitter.interface header.fields[HeaderFields.member] = signal if signature is not None: header.fields[HeaderFields.signature] = signature return Message(header, body) class MessageGenerator: """Subclass this to define the methods available on a DBus interface. jeepney.bindgen can automatically create subclasses using introspection. """ def __init__(self, object_path, bus_name): self.object_path = object_path self.bus_name = bus_name def __repr__(self): return "{}({!r}, bus_name={!r})".format(type(self).__name__, self.object_path, self.bus_name) class ProxyBase: """A proxy is an IO-aware wrapper around a MessageGenerator Calling methods on a proxy object will send a message and wait for the reply. This is a base class for proxy implementations in jeepney.io. """ def __init__(self, msggen): self._msggen = msggen def __getattr__(self, item): if item.startswith('__'): raise AttributeError(item) make_msg = getattr(self._msggen, item, None) if callable(make_msg): return self._method_call(make_msg) raise AttributeError(item) def _method_call(self, make_msg): raise NotImplementedError("Needs to be implemented in subclass") class Properties: """Build messages for accessing object properties If a D-Bus object has multiple interfaces, each interface has its own set of properties. This uses the standard DBus interface ``org.freedesktop.DBus.Properties`` """ def __init__(self, obj: Union[DBusAddress, MessageGenerator]): self.obj = obj self.props_if = DBusAddress(obj.object_path, bus_name=obj.bus_name, interface='org.freedesktop.DBus.Properties') def get(self, name): """Get the value of the property *name*""" return new_method_call(self.props_if, 'Get', 'ss', (self.obj.interface, name)) def get_all(self): """Get all property values for this interface""" return new_method_call(self.props_if, 'GetAll', 's', (self.obj.interface,)) def set(self, name, signature, value): """Set the property *name* to *value* (with appropriate signature)""" return new_method_call(self.props_if, 'Set', 'ssv', (self.obj.interface, name, (signature, value))) class Introspectable(MessageGenerator): interface = 'org.freedesktop.DBus.Introspectable' def Introspect(self): """Request D-Bus introspection XML for a remote object""" return new_method_call(self, 'Introspect') class DBusErrorResponse(Exception): """Raised by proxy method calls when the reply is an error message""" def __init__(self, msg): self.name = msg.header.fields.get(HeaderFields.error_name) self.data = msg.body def __str__(self): return '[{}] {}'.format(self.name, self.data) def unwrap_msg(msg: Message): """Get the body of a message, raising DBusErrorResponse for error messages This is to be used with replies to method_call messages, which may be method_return or error. """ if msg.header.message_type == MessageType.error: raise DBusErrorResponse(msg) return msg.body