blueman icon indicating copy to clipboard operation
blueman copied to clipboard

Applet Dbus service unavailable when turning off multiple plugins at the same time

Open infirit opened this issue 7 months ago • 5 comments

This happens (for me) when multiple plugins are disabled in quick succession due to one being a dependency. I can trigger below when disabling PowerManager which also disables KillSwitch.

Not sure what the best approach is but we need to fix eventually.

Traceback (most recent call last):
  File "/home/sander/repos/blueman/blueman/main/Manager.py", line 107, in on_applet_signal
    if "PowerManager" in self.Applet.QueryPlugins():
                         ~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/usr/lib/python3.13/site-packages/gi/overrides/Gio.py", line 380, in __call__
    result = self.dbus_proxy.call_sync(self.method_name, arg_variant,
                                       flags, timeout, None)
gi.repository.GLib.GError: g-dbus-error-quark: GDBus.Error:org.freedesktop.DBus.Error.UnknownMethod: No such interface “org.blueman.Applet” on object at path /org/blueman/Applet (19)

infirit avatar May 05 '25 22:05 infirit

I think this is eventually due to a GDBus limitation: To remove methods and signals (those provided by PowerManager in that case), we remove the object and re-register it with the new (reduced) DBusInterfaceInfo. Unfortunately, there does not seem to be a way to modify that data for introspection without that step, even though it should just be internal state purely used for handling introspection calls.

cschramm avatar May 06 '25 09:05 cschramm

I was thinking to add the plugin name and it's state in the signal signature like below. Then anything interested can check and act accordingly to the state of the plugin.

diff --git a/blueman/plugins/applet/DBusService.py b/blueman/plugins/applet/DBusService.py
index e3ed45899..2bba74b2c 100644
--- a/blueman/plugins/applet/DBusService.py
+++ b/blueman/plugins/applet/DBusService.py
@@ -10,6 +10,7 @@ from blueman.Service import Service
 from blueman.bluez.errors import BluezDBusException
 if TYPE_CHECKING:
     from blueman.main.NetworkManager import NMConnectionError
+    from blueman.main.PluginManager import PluginManager
 from blueman.plugins.AppletPlugin import AppletPlugin
 from blueman.bluez.Device import Device
 from blueman.services.Functions import get_service
@@ -56,12 +57,12 @@ class DBusService(AppletPlugin):
         self._add_dbus_method("DisconnectService", ("o", "s", "d"), "", self._disconnect_service, is_async=True)
         self._add_dbus_method("OpenPluginDialog", (), "", self._open_plugin_dialog)
 
-        self._add_dbus_signal("PluginsChanged", "")
-        self.parent.Plugins.connect("plugin-loaded", lambda *args: self._plugins_changed())
-        self.parent.Plugins.connect("plugin-unloaded", lambda *args: self._plugins_changed())
+        self._add_dbus_signal("PluginsChanged", "sb")
+        self.parent.Plugins.connect("plugin-loaded", self._plugins_changed, "loaded")
+        self.parent.Plugins.connect("plugin-unloaded", self._plugins_changed, "unloaded")
 
-    def _plugins_changed(self) -> None:
-        self._emit_dbus_signal("PluginsChanged")
+    def _plugins_changed(self, _pluginmanager: "PluginManager", name, action) -> None:
+        self._emit_dbus_signal("PluginsChanged", name, action == "loaded")
 
     def connect_service(self, object_path: ObjectPath, uuid: str, ok: Callable[[], None],
                         err: Callable[[Union[BluezDBusException, "NMConnectionError",

infirit avatar May 06 '25 19:05 infirit

You mean to avoid the QueryPlugins call at that point? Well, I'd expect dozens of possible glitches due to the object re-registration and resulting unavailability. Not sure if it's worth fixing exactly that one.

Ideally, the D-Bus object would not vanish.

Now I'm wondering... is it even necessary to add those signals and methods to introspection, i.e., does our caller side need to find them there?

cschramm avatar May 07 '25 06:05 cschramm

You mean to avoid the QueryPlugins call at that point? Well, I'd expect dozens of possible glitches due to the object re-registration and resulting unavailability. Not sure if it's worth fixing exactly that one.

That was my initial intention and you're right to question it. I do think having PluginsChanged provide the additional information would make things better in that we don't have to go and figure out what changed, it will just tell us. But that is not the solution for this.

Ideally, the D-Bus object would not vanish.

Having had this issue in the back of my mind for a few days I think we should have PowerManager (or any plugin in the future with dbus methods/signals) provide an interface under the name org.blueman.Applet with a unique interface name, like org.blueman.PowerManager or org.blueman.Applet.PowerManager. If I'm understanding dbus correctly, registering a new object like this triggers the InterfaceAdded signal, which we can then use in for example main.Manager to construct a DBusProxy and show the toggle. Like this we can always keep the main interface org.blueman.Applet available. Unloading the plugin will unregister the object and the InterfaceRemoved signal is sent and, using the same example, we tear down the proxy and hide the toggle.

Hopefully I have some time this weekend to see if this is actually a good idea :-).

infirit avatar May 07 '25 19:05 infirit

And I couldn't sleep so here is how I imagined this roughly looks like https://github.com/blueman-project/blueman/commit/0340329e47d98db8b867bb4346f56c87c4a92524. I haven't added an ObjectManager to listen for the interface signals.

Which then exposes the following interfaces under /org/blueman/Applet. Image

infirit avatar May 07 '25 23:05 infirit