#!/usr/bin/env python3 # Libervia D-Bus communication bridge # Copyright (C) 2009-2026 Jérôme Poisson (goffi@goffi.org) # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . import asyncio from functools import partial from twisted.internet import defer, reactor from txdbus import client from .bridge_frontend import BridgeException from libervia.backend.core.exceptions import BridgeExceptionNoService, BridgeInitError from libervia.backend.core.i18n import _ from libervia.backend.core.log import getLogger from libervia.backend.tools import config log = getLogger(__name__) # Interface prefix conf = config.parse_main_conf() bridge_conf = conf.get_bridge_conf(config.DBusBridgeSettings) const_INT_PREFIX = bridge_conf.int_prefix const_ERROR_PREFIX = const_INT_PREFIX + ".error" const_OBJ_PATH = "/org/libervia/Libervia/bridge" const_CORE_SUFFIX = ".core" const_PLUGIN_SUFFIX = ".plugin" const_TIMEOUT = 120 const_CONNECT_TIMEOUT = 5 def _connect_dbus(): """Connect to the D-Bus session bus with a timeout. @return: Deferred to the D-Bus connection """ d = client.connect(reactor) reactor.callLater(const_CONNECT_TIMEOUT, d.cancel) def on_cancel(failure): if failure.check(defer.CancelledError): log.error( f"D-Bus connection timed out after {const_CONNECT_TIMEOUT} s, " "please check the D-Bus service" ) return failure d.addErrback(on_cancel) return d def dbus_to_bridge_exception(failure): """Convert a txdbus Failure to a BridgeException. @param failure (Failure or RemoteError) @return: BridgeException """ import ast from twisted.python.failure import Failure if isinstance(failure, Failure): err = failure.value message = failure.getErrorMessage() else: err = failure message = str(failure) full_name = getattr(err, "dbusErrorName", None) or getattr(err, "errName", None) if full_name is None: full_name = ".".join( (const_ERROR_PREFIX, err.__class__.__module__, err.__class__.__name__) ) if full_name and full_name.startswith(const_ERROR_PREFIX): name = full_name[len(const_ERROR_PREFIX) + 1 :] else: name = full_name # The backend GenericException serializes (message, condition) as a tuple string. # Try to unpack it or fall back to using the raw message with no condition. condition = "" try: data = ast.literal_eval(message) if isinstance(data, tuple) and len(data) >= 2: message = data[0] condition = data[1] except (ValueError, SyntaxError): pass return BridgeException(name, message, condition) class SignalsHandler: def __init__(self, db_object, iface_name): self.db_object = db_object self._iface_names = { "core": const_INT_PREFIX + const_CORE_SUFFIX, "plugin": iface_name, } def __getattr__(self, name): if name.startswith("remote_"): log.debug("calling an unregistered signal: {name}".format(name=name[7:])) return lambda *args, **kwargs: None else: raise AttributeError(name) def register_signal(self, name, handler, iface="core"): log.debug("registering signal {name}".format(name=name)) method_name = "remote_" + name try: self.__getattribute__(method_name) except AttributeError: pass else: from libervia.backend.core import exceptions raise exceptions.InternalError( "{name} signal handler has been registered twice".format(name=method_name) ) def on_signal(*args): handler(*args) d = self.db_object.notifyOnSignal( name, on_signal, interface=self._iface_names[iface] ) d.addErrback( lambda failure_: log.error( f"Failed to register signal {name}: {failure_.getErrorMessage()}" ) ) setattr(self, method_name, on_signal) class bridge: def __init__(self): self.conn = None self.db_object = None self.db_core_iface = None self.db_plugin_iface = None self.signals_handler = None def __getattr__(self, name): return partial(self.call, name) @staticmethod def _unwrap_txdbus_result(d, return_sig): """Unwrap txdbus result.""" # txdbus seems to wrongly wrap result in a list, this is a workaround. # FIXME: check txdbus behaviour, and report upstream if it's confirmed to be a # bug. if return_sig and return_sig.startswith("("): d.addCallback( lambda result: ( result[0] if isinstance(result, list) and len(result) == 1 else result ) ) return d def _errback(self, failure_): """Convert Failure to BridgeException""" raise dbus_to_bridge_exception(failure_) def call(self, name: str, *args, **kwargs): """Call a remote method. @param name: name of the bridge method @param args: method arguments @param kwargs: keyword arguments (callback, errback, and methods arguments) """ # Determine which interface to use by checking where the method is defined for iface in self.db_object.interfaces: if name in iface.methods: interface_name = iface.name return_sig = iface.methods[name].sigOut break else: # Default to core interface if method not found (will raise AttributeError) interface_name = const_INT_PREFIX + const_CORE_SUFFIX return_sig = None # Call the remote method d = self.db_object.callRemote( name, *args, interface=interface_name, timeout=const_TIMEOUT, ) self._unwrap_txdbus_result(d, return_sig) return d async def bridge_connect(self) -> None: """Connect to the D-Bus bridge.""" try: self.conn = await _connect_dbus() self.db_object = await self.conn.getRemoteObject( const_INT_PREFIX, const_OBJ_PATH ) # Find the core and plugin interfaces for iface in self.db_object.interfaces: if iface.name == const_INT_PREFIX + const_CORE_SUFFIX: self.db_core_iface = iface elif iface.name == const_INT_PREFIX + const_PLUGIN_SUFFIX: self.db_plugin_iface = iface if self.db_core_iface is None: raise BridgeInitError( f"Core interface {const_INT_PREFIX + const_CORE_SUFFIX} not found" ) if self.db_plugin_iface is None: raise BridgeInitError( f"Plugin interface {const_INT_PREFIX + const_PLUGIN_SUFFIX} not found" ) # Initialize signals handler self.signals_handler = SignalsHandler( self.db_object, const_INT_PREFIX + const_PLUGIN_SUFFIX ) except BridgeInitError as e: raise e except defer.CancelledError: # The connect deferred was cancelled by the timeout, we raise BridgeInitError # so that the "auto" bridge can fall back to the next one. raise BridgeInitError from None except Exception as e: msg = str(e) if ( "org.freedesktop.DBus.Error.ServiceUnknown" in msg or "org.freedesktop.DBus.Error.Spawn.ExecFailed" in msg ): raise BridgeExceptionNoService from e elif "org.freedesktop.DBus.Error.NotSupported" in msg: log.error( _( "D-Bus is not launched, please see README to see instructions on" " how to launch it" ) ) raise BridgeInitError from e def register_signal(self, functionName, handler, iface="core"): if self.signals_handler is None: log.error("Bridge not initialized") return self.signals_handler.register_signal(functionName, handler, iface) def action_launch(self, callback_id, data, profile_key="@DEFAULT@"): return self.call("action_launch", callback_id, data, profile_key) def actions_get(self, profile_key="@DEFAULT@"): return self.call("actions_get", profile_key) def config_get(self, section, name): return self.call("config_get", section, name) def connect(self, profile_key="@DEFAULT@", password="", options={}): return self.call("connect", profile_key, password, options) def contact_add(self, entity_jid, profile_key="@DEFAULT@"): return self.call("contact_add", entity_jid, profile_key) def contact_del(self, entity_jid, profile_key="@DEFAULT@"): return self.call("contact_del", entity_jid, profile_key) def contact_get(self, arg_0, profile_key="@DEFAULT@"): return self.call("contact_get", arg_0, profile_key) def contact_update(self, entity_jid, name, groups, profile_key="@DEFAULT@"): return self.call("contact_update", entity_jid, name, groups, profile_key) def contacts_get(self, profile_key="@DEFAULT@"): return self.call("contacts_get", profile_key) def contacts_get_from_group(self, group, profile_key="@DEFAULT@"): return self.call("contacts_get_from_group", group, profile_key) def devices_infos_get(self, bare_jid, profile_key): return self.call("devices_infos_get", bare_jid, profile_key) def disco_find_by_features( self, namespaces, identities, bare_jid=False, service=True, roster=True, own_jid=True, local_device=False, profile_key="@DEFAULT@", ): return self.call( "disco_find_by_features", namespaces, identities, bare_jid, service, roster, own_jid, local_device, profile_key, ) def disco_infos(self, entity_jid, node="", use_cache=True, profile_key="@DEFAULT@"): return self.call("disco_infos", entity_jid, node, use_cache, profile_key) def disco_items(self, entity_jid, node="", use_cache=True, profile_key="@DEFAULT@"): return self.call("disco_items", entity_jid, node, use_cache, profile_key) def disconnect(self, profile_key="@DEFAULT@"): return self.call("disconnect", profile_key) def encryption_namespace_get(self, arg_0): return self.call("encryption_namespace_get", arg_0) def encryption_plugins_get( self, ): return self.call( "encryption_plugins_get", ) def encryption_trust_ui_get(self, to_jid, namespace, profile_key): return self.call("encryption_trust_ui_get", to_jid, namespace, profile_key) def entities_data_get(self, jids, keys, profile): return self.call("entities_data_get", jids, keys, profile) def entity_data_get(self, jid, keys, profile): return self.call("entity_data_get", jid, keys, profile) def features_get(self, profile_key): return self.call("features_get", profile_key) def history_get( self, from_jid, to_jid, limit, between=True, filters="", profile="@NONE@" ): return self.call( "history_get", from_jid, to_jid, limit, between, filters, profile ) def history_last_messages_metadata_get(self, options, profile_key="@NONE@"): return self.call("history_last_messages_metadata_get", options, profile_key) def image_check(self, arg_0): return self.call("image_check", arg_0) def image_convert(self, source, dest, arg_2, extra): return self.call("image_convert", source, dest, arg_2, extra) def image_generate_preview(self, image_path, profile_key): return self.call("image_generate_preview", image_path, profile_key) def image_resize(self, image_path, width, height): return self.call("image_resize", image_path, width, height) def init_pre_script( self, ): return self.call( "init_pre_script", ) def is_connected(self, profile_key="@DEFAULT@"): return self.call("is_connected", profile_key) def main_resource_get(self, contact_jid, profile_key="@DEFAULT@"): return self.call("main_resource_get", contact_jid, profile_key) def menu_help_get(self, menu_id, language): return self.call("menu_help_get", menu_id, language) def menu_launch(self, menu_type, path, data, security_limit, profile_key): return self.call( "menu_launch", menu_type, path, data, security_limit, profile_key ) def menus_get(self, language, security_limit): return self.call("menus_get", language, security_limit) def message_encryption_get(self, to_jid, profile_key): return self.call("message_encryption_get", to_jid, profile_key) def message_encryption_start( self, to_jid, namespace="", replace=False, profile_key="@NONE@" ): return self.call( "message_encryption_start", to_jid, namespace, replace, profile_key ) def message_encryption_stop(self, to_jid, profile_key): return self.call("message_encryption_stop", to_jid, profile_key) def message_send( self, to_jid, message, subject={}, mess_type="auto", extra={}, profile_key="@NONE@", ): return self.call( "message_send", to_jid, message, subject, mess_type, extra, profile_key ) def messages_display_state_get(self, jids, profile_key): return self.call("messages_display_state_get", jids, profile_key) def messages_display_state_set(self, peer_jid, message_uid, profile_key): return self.call("messages_display_state_set", peer_jid, message_uid, profile_key) def namespaces_get( self, ): return self.call( "namespaces_get", ) def notification_add( self, type_, body_plain, body_rich, title, is_global, requires_action, arg_6, priority, expire_at, extra, ): return self.call( "notification_add", type_, body_plain, body_rich, title, is_global, requires_action, arg_6, priority, expire_at, extra, ) def notification_delete(self, id_, is_global, profile_key): return self.call("notification_delete", id_, is_global, profile_key) def notifications_expired_clean(self, limit_timestamp, profile_key): return self.call("notifications_expired_clean", limit_timestamp, profile_key) def notifications_get(self, filters, profile_key): return self.call("notifications_get", filters, profile_key) def param_get_a(self, name, category, attribute="value", profile_key="@DEFAULT@"): return self.call("param_get_a", name, category, attribute, profile_key) def param_get_a_async( self, name, category, attribute="value", security_limit=-1, profile_key="@DEFAULT@", ): return self.call( "param_get_a_async", name, category, attribute, security_limit, profile_key ) def param_set( self, name, value, category, security_limit=-1, profile_key="@DEFAULT@" ): return self.call("param_set", name, value, category, security_limit, profile_key) def param_ui_get(self, security_limit=-1, app="", extra="", profile_key="@DEFAULT@"): return self.call("param_ui_get", security_limit, app, extra, profile_key) def params_categories_get( self, ): return self.call( "params_categories_get", ) def params_register_app(self, xml, security_limit=-1, app=""): return self.call("params_register_app", xml, security_limit, app) def params_template_load(self, filename): return self.call("params_template_load", filename) def params_template_save(self, filename): return self.call("params_template_save", filename) def params_values_from_category_get_async( self, category, security_limit=-1, app="", extra="", profile_key="@DEFAULT@" ): return self.call( "params_values_from_category_get_async", category, security_limit, app, extra, profile_key, ) def presence_set(self, to_jid="", show="", statuses={}, profile_key="@DEFAULT@"): return self.call("presence_set", to_jid, show, statuses, profile_key) def presence_statuses_get(self, profile_key="@DEFAULT@"): return self.call("presence_statuses_get", profile_key) def private_data_delete(self, namespace, key, arg_2): return self.call("private_data_delete", namespace, key, arg_2) def private_data_get(self, namespace, key, profile_key): return self.call("private_data_get", namespace, key, profile_key) def private_data_set(self, namespace, key, data, profile_key): return self.call("private_data_set", namespace, key, data, profile_key) def profile_create(self, profile, password="", component=""): return self.call("profile_create", profile, password, component) def profile_delete_async(self, profile): return self.call("profile_delete_async", profile) def profile_is_session_started(self, profile_key="@DEFAULT@"): return self.call("profile_is_session_started", profile_key) def profile_name_get(self, profile_key="@DEFAULT@"): return self.call("profile_name_get", profile_key) def profile_set_default(self, profile): return self.call("profile_set_default", profile) def profile_start_session(self, password="", profile_key="@DEFAULT@"): return self.call("profile_start_session", password, profile_key) def profiles_list_get(self, clients=True, components=False): return self.call("profiles_list_get", clients, components) def progress_get(self, id, profile): return self.call("progress_get", id, profile) def progress_get_all(self, profile): return self.call("progress_get_all", profile) def progress_get_all_metadata(self, profile): return self.call("progress_get_all_metadata", profile) def ready_get( self, ): return self.call( "ready_get", ) def roster_resync(self, profile_key="@DEFAULT@"): return self.call("roster_resync", profile_key) def session_infos_get(self, profile_key): return self.call("session_infos_get", profile_key) def sub_waiting_get(self, profile_key="@DEFAULT@"): return self.call("sub_waiting_get", profile_key) def subscription(self, sub_type, entity, profile_key="@DEFAULT@"): return self.call("subscription", sub_type, entity, profile_key) def tor_infos_get(self, extra, profile_key): return self.call("tor_infos_get", extra, profile_key) def tor_mode_get(self, profile_key): return self.call("tor_mode_get", profile_key) def version_get( self, ): return self.call( "version_get", ) class AIOSignalsHandler(SignalsHandler): def register_signal(self, name, handler, iface="core"): """Register an async signal handler.""" async_handler = lambda *args: asyncio.ensure_future(handler(*args)) return super().register_signal(name, async_handler, iface) async def action_launch(self, callback_id, data, profile_key="@DEFAULT@"): return await self.call("action_launch", callback_id, data, profile_key) async def actions_get(self, profile_key="@DEFAULT@"): return await self.call("actions_get", profile_key) async def config_get(self, section, name): return await self.call("config_get", section, name) async def connect(self, profile_key="@DEFAULT@", password="", options={}): return await self.call("connect", profile_key, password, options) async def contact_add(self, entity_jid, profile_key="@DEFAULT@"): return await self.call("contact_add", entity_jid, profile_key) async def contact_del(self, entity_jid, profile_key="@DEFAULT@"): return await self.call("contact_del", entity_jid, profile_key) async def contact_get(self, arg_0, profile_key="@DEFAULT@"): return await self.call("contact_get", arg_0, profile_key) async def contact_update(self, entity_jid, name, groups, profile_key="@DEFAULT@"): return await self.call("contact_update", entity_jid, name, groups, profile_key) async def contacts_get(self, profile_key="@DEFAULT@"): return await self.call("contacts_get", profile_key) async def contacts_get_from_group(self, group, profile_key="@DEFAULT@"): return await self.call("contacts_get_from_group", group, profile_key) async def devices_infos_get(self, bare_jid, profile_key): return await self.call("devices_infos_get", bare_jid, profile_key) async def disco_find_by_features( self, namespaces, identities, bare_jid=False, service=True, roster=True, own_jid=True, local_device=False, profile_key="@DEFAULT@", ): return await self.call( "disco_find_by_features", namespaces, identities, bare_jid, service, roster, own_jid, local_device, profile_key, ) async def disco_infos( self, entity_jid, node="", use_cache=True, profile_key="@DEFAULT@" ): return await self.call("disco_infos", entity_jid, node, use_cache, profile_key) async def disco_items( self, entity_jid, node="", use_cache=True, profile_key="@DEFAULT@" ): return await self.call("disco_items", entity_jid, node, use_cache, profile_key) async def disconnect(self, profile_key="@DEFAULT@"): return await self.call("disconnect", profile_key) async def encryption_namespace_get(self, arg_0): return await self.call("encryption_namespace_get", arg_0) async def encryption_plugins_get( self, ): return await self.call( "encryption_plugins_get", ) async def encryption_trust_ui_get(self, to_jid, namespace, profile_key): return await self.call("encryption_trust_ui_get", to_jid, namespace, profile_key) async def entities_data_get(self, jids, keys, profile): return await self.call("entities_data_get", jids, keys, profile) async def entity_data_get(self, jid, keys, profile): return await self.call("entity_data_get", jid, keys, profile) async def features_get(self, profile_key): return await self.call("features_get", profile_key) async def history_get( self, from_jid, to_jid, limit, between=True, filters="", profile="@NONE@" ): return await self.call( "history_get", from_jid, to_jid, limit, between, filters, profile ) async def history_last_messages_metadata_get(self, options, profile_key="@NONE@"): return await self.call("history_last_messages_metadata_get", options, profile_key) async def image_check(self, arg_0): return await self.call("image_check", arg_0) async def image_convert(self, source, dest, arg_2, extra): return await self.call("image_convert", source, dest, arg_2, extra) async def image_generate_preview(self, image_path, profile_key): return await self.call("image_generate_preview", image_path, profile_key) async def image_resize(self, image_path, width, height): return await self.call("image_resize", image_path, width, height) async def init_pre_script( self, ): return await self.call( "init_pre_script", ) async def is_connected(self, profile_key="@DEFAULT@"): return await self.call("is_connected", profile_key) async def main_resource_get(self, contact_jid, profile_key="@DEFAULT@"): return await self.call("main_resource_get", contact_jid, profile_key) async def menu_help_get(self, menu_id, language): return await self.call("menu_help_get", menu_id, language) async def menu_launch(self, menu_type, path, data, security_limit, profile_key): return await self.call( "menu_launch", menu_type, path, data, security_limit, profile_key ) async def menus_get(self, language, security_limit): return await self.call("menus_get", language, security_limit) async def message_encryption_get(self, to_jid, profile_key): return await self.call("message_encryption_get", to_jid, profile_key) async def message_encryption_start( self, to_jid, namespace="", replace=False, profile_key="@NONE@" ): return await self.call( "message_encryption_start", to_jid, namespace, replace, profile_key ) async def message_encryption_stop(self, to_jid, profile_key): return await self.call("message_encryption_stop", to_jid, profile_key) async def message_send( self, to_jid, message, subject={}, mess_type="auto", extra={}, profile_key="@NONE@", ): return await self.call( "message_send", to_jid, message, subject, mess_type, extra, profile_key ) async def messages_display_state_get(self, jids, profile_key): return await self.call("messages_display_state_get", jids, profile_key) async def messages_display_state_set(self, peer_jid, message_uid, profile_key): return await self.call( "messages_display_state_set", peer_jid, message_uid, profile_key ) async def namespaces_get( self, ): return await self.call( "namespaces_get", ) async def notification_add( self, type_, body_plain, body_rich, title, is_global, requires_action, arg_6, priority, expire_at, extra, ): return await self.call( "notification_add", type_, body_plain, body_rich, title, is_global, requires_action, arg_6, priority, expire_at, extra, ) async def notification_delete(self, id_, is_global, profile_key): return await self.call("notification_delete", id_, is_global, profile_key) async def notifications_expired_clean(self, limit_timestamp, profile_key): return await self.call( "notifications_expired_clean", limit_timestamp, profile_key ) async def notifications_get(self, filters, profile_key): return await self.call("notifications_get", filters, profile_key) async def param_get_a( self, name, category, attribute="value", profile_key="@DEFAULT@" ): return await self.call("param_get_a", name, category, attribute, profile_key) async def param_get_a_async( self, name, category, attribute="value", security_limit=-1, profile_key="@DEFAULT@", ): return await self.call( "param_get_a_async", name, category, attribute, security_limit, profile_key ) async def param_set( self, name, value, category, security_limit=-1, profile_key="@DEFAULT@" ): return await self.call( "param_set", name, value, category, security_limit, profile_key ) async def param_ui_get( self, security_limit=-1, app="", extra="", profile_key="@DEFAULT@" ): return await self.call("param_ui_get", security_limit, app, extra, profile_key) async def params_categories_get( self, ): return await self.call( "params_categories_get", ) async def params_register_app(self, xml, security_limit=-1, app=""): return await self.call("params_register_app", xml, security_limit, app) async def params_template_load(self, filename): return await self.call("params_template_load", filename) async def params_template_save(self, filename): return await self.call("params_template_save", filename) async def params_values_from_category_get_async( self, category, security_limit=-1, app="", extra="", profile_key="@DEFAULT@" ): return await self.call( "params_values_from_category_get_async", category, security_limit, app, extra, profile_key, ) async def presence_set( self, to_jid="", show="", statuses={}, profile_key="@DEFAULT@" ): return await self.call("presence_set", to_jid, show, statuses, profile_key) async def presence_statuses_get(self, profile_key="@DEFAULT@"): return await self.call("presence_statuses_get", profile_key) async def private_data_delete(self, namespace, key, arg_2): return await self.call("private_data_delete", namespace, key, arg_2) async def private_data_get(self, namespace, key, profile_key): return await self.call("private_data_get", namespace, key, profile_key) async def private_data_set(self, namespace, key, data, profile_key): return await self.call("private_data_set", namespace, key, data, profile_key) async def profile_create(self, profile, password="", component=""): return await self.call("profile_create", profile, password, component) async def profile_delete_async(self, profile): return await self.call("profile_delete_async", profile) async def profile_is_session_started(self, profile_key="@DEFAULT@"): return await self.call("profile_is_session_started", profile_key) async def profile_name_get(self, profile_key="@DEFAULT@"): return await self.call("profile_name_get", profile_key) async def profile_set_default(self, profile): return await self.call("profile_set_default", profile) async def profile_start_session(self, password="", profile_key="@DEFAULT@"): return await self.call("profile_start_session", password, profile_key) async def profiles_list_get(self, clients=True, components=False): return await self.call("profiles_list_get", clients, components) async def progress_get(self, id, profile): return await self.call("progress_get", id, profile) async def progress_get_all(self, profile): return await self.call("progress_get_all", profile) async def progress_get_all_metadata(self, profile): return await self.call("progress_get_all_metadata", profile) async def ready_get( self, ): return await self.call( "ready_get", ) async def roster_resync(self, profile_key="@DEFAULT@"): return await self.call("roster_resync", profile_key) async def session_infos_get(self, profile_key): return await self.call("session_infos_get", profile_key) async def sub_waiting_get(self, profile_key="@DEFAULT@"): return await self.call("sub_waiting_get", profile_key) async def subscription(self, sub_type, entity, profile_key="@DEFAULT@"): return await self.call("subscription", sub_type, entity, profile_key) async def tor_infos_get(self, extra, profile_key): return await self.call("tor_infos_get", extra, profile_key) async def tor_mode_get(self, profile_key): return await self.call("tor_mode_get", profile_key) async def version_get( self, ): return await self.call( "version_get", ) class AIOBridge(bridge): def __init__(self): super().__init__() self.signals_handler = None async def post_init(self): """Post-initialization hook. Actually no post init is needed, but the "__getattr__" return "partial" for unknown methods, and this causes "dynamic_import.load_and_init_bridge" to fail. """ pass def call(self, name: str, *args, **kwargs): """Call a remote method and return an awaitable. @param name: name of the bridge method """ d = super().call(name, *args, **kwargs) d.addErrback(self._errback) return d.asFuture(asyncio.get_event_loop()) async def bridge_connect(self): """Connect to the D-Bus bridge (async version).""" try: self.conn = await _connect_dbus().asFuture(asyncio.get_event_loop()) self.db_object = await self.conn.getRemoteObject( const_INT_PREFIX, const_OBJ_PATH ).asFuture(asyncio.get_event_loop()) # Find the core and plugin interfaces for iface in self.db_object.interfaces: if iface.name == const_INT_PREFIX + const_CORE_SUFFIX: self.db_core_iface = iface elif iface.name == const_INT_PREFIX + const_PLUGIN_SUFFIX: self.db_plugin_iface = iface if self.db_core_iface is None: raise BridgeInitError( f"Core interface {const_INT_PREFIX + const_CORE_SUFFIX} not found" ) if self.db_plugin_iface is None: raise BridgeInitError( f"Plugin interface {const_INT_PREFIX + const_PLUGIN_SUFFIX} not found" ) # Initialize signals handler self.signals_handler = AIOSignalsHandler( self.db_object, const_INT_PREFIX + const_PLUGIN_SUFFIX ) except BridgeInitError as e: raise e except defer.CancelledError: # The connect deferred was cancelled by the timeout, we raise BridgeInitError # so that the "auto" bridge can fall back to the next one. raise BridgeInitError from None except Exception as e: msg = str(e) if ( "org.freedesktop.DBus.Error.ServiceUnknown" in msg or "org.freedesktop.DBus.Error.Spawn.ExecFailed" in msg ): raise BridgeExceptionNoService from e elif "org.freedesktop.DBus.Error.NotSupported" in msg: log.error( _( "D-Bus is not launched, please see README to see instructions on" " how to launch it" ) ) raise BridgeInitError from e def register_signal(self, functionName, handler, iface="core"): if self.signals_handler is None: log.error("Bridge not initialized") return self.signals_handler.register_signal(functionName, handler, iface) class TwistedBridge(bridge): async def post_init(self): """Post-initialization hook. Actually no post init is needed, but the "__getattr__" return "partial" for unknown methods, and this causes "dynamic_import.load_and_init_bridge" to fail. """ pass def call(self, name, *args, **kwargs): """Call a remote method and return a Deferred. @param name(str): name of the bridge method @param args(list): arguments @param kwargs(dict): keyword arguments (profile, etc.) """ d = super().call(name, *args, **kwargs) d.addErrback(self._errback) return d async def bridge_connect(self) -> None: """Connect to the D-Bus bridge.""" return await super().bridge_connect() def register_signal(self, functionName, handler, iface="core"): super().register_signal(functionName, handler, iface) def action_launch(self, callback_id, data, profile_key="@DEFAULT@"): return self.call("action_launch", callback_id, data, profile_key) def actions_get(self, profile_key="@DEFAULT@"): return self.call("actions_get", profile_key) def config_get(self, section, name): return self.call("config_get", section, name) def connect(self, profile_key="@DEFAULT@", password="", options={}): return self.call("connect", profile_key, password, options) def contact_add(self, entity_jid, profile_key="@DEFAULT@"): return self.call("contact_add", entity_jid, profile_key) def contact_del(self, entity_jid, profile_key="@DEFAULT@"): return self.call("contact_del", entity_jid, profile_key) def contact_get(self, arg_0, profile_key="@DEFAULT@"): return self.call("contact_get", arg_0, profile_key) def contact_update(self, entity_jid, name, groups, profile_key="@DEFAULT@"): return self.call("contact_update", entity_jid, name, groups, profile_key) def contacts_get(self, profile_key="@DEFAULT@"): return self.call("contacts_get", profile_key) def contacts_get_from_group(self, group, profile_key="@DEFAULT@"): return self.call("contacts_get_from_group", group, profile_key) def devices_infos_get(self, bare_jid, profile_key): return self.call("devices_infos_get", bare_jid, profile_key) def disco_find_by_features( self, namespaces, identities, bare_jid=False, service=True, roster=True, own_jid=True, local_device=False, profile_key="@DEFAULT@", ): return self.call( "disco_find_by_features", namespaces, identities, bare_jid, service, roster, own_jid, local_device, profile_key, ) def disco_infos(self, entity_jid, node="", use_cache=True, profile_key="@DEFAULT@"): return self.call("disco_infos", entity_jid, node, use_cache, profile_key) def disco_items(self, entity_jid, node="", use_cache=True, profile_key="@DEFAULT@"): return self.call("disco_items", entity_jid, node, use_cache, profile_key) def disconnect(self, profile_key="@DEFAULT@"): return self.call("disconnect", profile_key) def encryption_namespace_get(self, arg_0): return self.call("encryption_namespace_get", arg_0) def encryption_plugins_get( self, ): return self.call( "encryption_plugins_get", ) def encryption_trust_ui_get(self, to_jid, namespace, profile_key): return self.call("encryption_trust_ui_get", to_jid, namespace, profile_key) def entities_data_get(self, jids, keys, profile): return self.call("entities_data_get", jids, keys, profile) def entity_data_get(self, jid, keys, profile): return self.call("entity_data_get", jid, keys, profile) def features_get(self, profile_key): return self.call("features_get", profile_key) def history_get( self, from_jid, to_jid, limit, between=True, filters="", profile="@NONE@" ): return self.call( "history_get", from_jid, to_jid, limit, between, filters, profile ) def history_last_messages_metadata_get(self, options, profile_key="@NONE@"): return self.call("history_last_messages_metadata_get", options, profile_key) def image_check(self, arg_0): return self.call("image_check", arg_0) def image_convert(self, source, dest, arg_2, extra): return self.call("image_convert", source, dest, arg_2, extra) def image_generate_preview(self, image_path, profile_key): return self.call("image_generate_preview", image_path, profile_key) def image_resize(self, image_path, width, height): return self.call("image_resize", image_path, width, height) def init_pre_script( self, ): return self.call( "init_pre_script", ) def is_connected(self, profile_key="@DEFAULT@"): return self.call("is_connected", profile_key) def main_resource_get(self, contact_jid, profile_key="@DEFAULT@"): return self.call("main_resource_get", contact_jid, profile_key) def menu_help_get(self, menu_id, language): return self.call("menu_help_get", menu_id, language) def menu_launch(self, menu_type, path, data, security_limit, profile_key): return self.call( "menu_launch", menu_type, path, data, security_limit, profile_key ) def menus_get(self, language, security_limit): return self.call("menus_get", language, security_limit) def message_encryption_get(self, to_jid, profile_key): return self.call("message_encryption_get", to_jid, profile_key) def message_encryption_start( self, to_jid, namespace="", replace=False, profile_key="@NONE@" ): return self.call( "message_encryption_start", to_jid, namespace, replace, profile_key ) def message_encryption_stop(self, to_jid, profile_key): return self.call("message_encryption_stop", to_jid, profile_key) def message_send( self, to_jid, message, subject={}, mess_type="auto", extra={}, profile_key="@NONE@", ): return self.call( "message_send", to_jid, message, subject, mess_type, extra, profile_key ) def messages_display_state_get(self, jids, profile_key): return self.call("messages_display_state_get", jids, profile_key) def messages_display_state_set(self, peer_jid, message_uid, profile_key): return self.call("messages_display_state_set", peer_jid, message_uid, profile_key) def namespaces_get( self, ): return self.call( "namespaces_get", ) def notification_add( self, type_, body_plain, body_rich, title, is_global, requires_action, arg_6, priority, expire_at, extra, ): return self.call( "notification_add", type_, body_plain, body_rich, title, is_global, requires_action, arg_6, priority, expire_at, extra, ) def notification_delete(self, id_, is_global, profile_key): return self.call("notification_delete", id_, is_global, profile_key) def notifications_expired_clean(self, limit_timestamp, profile_key): return self.call("notifications_expired_clean", limit_timestamp, profile_key) def notifications_get(self, filters, profile_key): return self.call("notifications_get", filters, profile_key) def param_get_a(self, name, category, attribute="value", profile_key="@DEFAULT@"): return self.call("param_get_a", name, category, attribute, profile_key) def param_get_a_async( self, name, category, attribute="value", security_limit=-1, profile_key="@DEFAULT@", ): return self.call( "param_get_a_async", name, category, attribute, security_limit, profile_key ) def param_set( self, name, value, category, security_limit=-1, profile_key="@DEFAULT@" ): return self.call("param_set", name, value, category, security_limit, profile_key) def param_ui_get(self, security_limit=-1, app="", extra="", profile_key="@DEFAULT@"): return self.call("param_ui_get", security_limit, app, extra, profile_key) def params_categories_get( self, ): return self.call( "params_categories_get", ) def params_register_app(self, xml, security_limit=-1, app=""): return self.call("params_register_app", xml, security_limit, app) def params_template_load(self, filename): return self.call("params_template_load", filename) def params_template_save(self, filename): return self.call("params_template_save", filename) def params_values_from_category_get_async( self, category, security_limit=-1, app="", extra="", profile_key="@DEFAULT@" ): return self.call( "params_values_from_category_get_async", category, security_limit, app, extra, profile_key, ) def presence_set(self, to_jid="", show="", statuses={}, profile_key="@DEFAULT@"): return self.call("presence_set", to_jid, show, statuses, profile_key) def presence_statuses_get(self, profile_key="@DEFAULT@"): return self.call("presence_statuses_get", profile_key) def private_data_delete(self, namespace, key, arg_2): return self.call("private_data_delete", namespace, key, arg_2) def private_data_get(self, namespace, key, profile_key): return self.call("private_data_get", namespace, key, profile_key) def private_data_set(self, namespace, key, data, profile_key): return self.call("private_data_set", namespace, key, data, profile_key) def profile_create(self, profile, password="", component=""): return self.call("profile_create", profile, password, component) def profile_delete_async(self, profile): return self.call("profile_delete_async", profile) def profile_is_session_started(self, profile_key="@DEFAULT@"): return self.call("profile_is_session_started", profile_key) def profile_name_get(self, profile_key="@DEFAULT@"): return self.call("profile_name_get", profile_key) def profile_set_default(self, profile): return self.call("profile_set_default", profile) def profile_start_session(self, password="", profile_key="@DEFAULT@"): return self.call("profile_start_session", password, profile_key) def profiles_list_get(self, clients=True, components=False): return self.call("profiles_list_get", clients, components) def progress_get(self, id, profile): return self.call("progress_get", id, profile) def progress_get_all(self, profile): return self.call("progress_get_all", profile) def progress_get_all_metadata(self, profile): return self.call("progress_get_all_metadata", profile) def ready_get( self, ): return self.call( "ready_get", ) def roster_resync(self, profile_key="@DEFAULT@"): return self.call("roster_resync", profile_key) def session_infos_get(self, profile_key): return self.call("session_infos_get", profile_key) def sub_waiting_get(self, profile_key="@DEFAULT@"): return self.call("sub_waiting_get", profile_key) def subscription(self, sub_type, entity, profile_key="@DEFAULT@"): return self.call("subscription", sub_type, entity, profile_key) def tor_infos_get(self, extra, profile_key): return self.call("tor_infos_get", extra, profile_key) def tor_mode_get(self, profile_key): return self.call("tor_mode_get", profile_key) def version_get( self, ): return self.call( "version_get", )