diff --git a/blueman/gui/manager/ManagerDeviceMenu.py b/blueman/gui/manager/ManagerDeviceMenu.py
index b5889bdc7..c0c721ac2 100644
--- a/blueman/gui/manager/ManagerDeviceMenu.py
+++ b/blueman/gui/manager/ManagerDeviceMenu.py
@@ -26,6 +26,12 @@
from gi.repository import GLib
+class MenuItemsProvider:
+ def on_request_menu_items(self, manager_menu: "ManagerDeviceMenu",
+ device: Device) -> List[Tuple[Gtk.MenuItem, int]]:
+ ...
+
+
class ManagerDeviceMenu(Gtk.Menu):
__ops__: Dict[str, str] = {}
__instances__: List["ManagerDeviceMenu"] = []
@@ -248,12 +254,9 @@ def generate(self):
connect_item.show()
self.append(connect_item)
- rets = self.Blueman.Plugins.run("on_request_menu_items", self, self.SelectedDevice)
-
- for ret in rets:
- if ret:
- for (item, pos) in ret:
- items.append((pos, item))
+ for plugin in self.Blueman.Plugins.get_loaded_plugins(MenuItemsProvider):
+ for item, pos in plugin.on_request_menu_items(self, self.SelectedDevice):
+ items.append((pos, item))
logging.debug(row["alias"])
diff --git a/blueman/main/Applet.py b/blueman/main/Applet.py
index 6a5a0dc9c..a718e4a8e 100644
--- a/blueman/main/Applet.py
+++ b/blueman/main/Applet.py
@@ -32,7 +32,8 @@ def __init__(self):
self.Plugins = PersistentPluginManager(AppletPlugin, blueman.plugins.applet, self)
self.Plugins.load_plugin()
- self.Plugins.run("on_plugins_loaded")
+ for plugin in self.Plugins.get_loaded_plugins(AppletPlugin):
+ plugin.on_plugins_loaded()
self.Manager.watch_name_owner(self._on_dbus_name_appeared, self._on_dbus_name_vanished)
@@ -51,32 +52,40 @@ def _on_dbus_name_appeared(self, _connection, name, owner):
logging.info(f"{name} {owner}")
self.manager_state = True
self.plugin_run_state_changed = True
- self.Plugins.run("on_manager_state_changed", self.manager_state)
+ for plugin in self.Plugins.get_loaded_plugins(AppletPlugin):
+ plugin.on_manager_state_changed(self.manager_state)
def _on_dbus_name_vanished(self, _connection, name):
logging.info(name)
self.manager_state = False
self.plugin_run_state_changed = True
- self.Plugins.run("on_manager_state_changed", self.manager_state)
+ for plugin in self.Plugins.get_loaded_plugins(AppletPlugin):
+ plugin.on_manager_state_changed(self.manager_state)
def _on_adapter_property_changed(self, _adapter, key, value, path):
- self.Plugins.run("on_adapter_property_changed", path, key, value)
+ for plugin in self.Plugins.get_loaded_plugins(AppletPlugin):
+ plugin.on_adapter_property_changed(path, key, value)
def _on_device_property_changed(self, _device, key, value, path):
- self.Plugins.run("on_device_property_changed", path, key, value)
+ for plugin in self.Plugins.get_loaded_plugins(AppletPlugin):
+ plugin.on_device_property_changed(path, key, value)
def on_adapter_added(self, _manager, path):
logging.info(f"Adapter added {path}")
- self.Plugins.run("on_adapter_added", path)
+ for plugin in self.Plugins.get_loaded_plugins(AppletPlugin):
+ plugin.on_adapter_added(path)
def on_adapter_removed(self, _manager, path):
logging.info(f"Adapter removed {path}")
- self.Plugins.run("on_adapter_removed", path)
+ for plugin in self.Plugins.get_loaded_plugins(AppletPlugin):
+ plugin.on_adapter_removed(path)
def on_device_created(self, _manager, path):
logging.info(f"Device created {path}")
- self.Plugins.run("on_device_created", path)
+ for plugin in self.Plugins.get_loaded_plugins(AppletPlugin):
+ plugin.on_device_created(path)
def on_device_removed(self, _manager, path):
logging.info(f"Device removed {path}")
- self.Plugins.run("on_device_removed", path)
+ for plugin in self.Plugins.get_loaded_plugins(AppletPlugin):
+ plugin.on_device_removed(path)
diff --git a/blueman/main/PluginManager.py b/blueman/main/PluginManager.py
index 6ac6efaf4..39a548e1b 100644
--- a/blueman/main/PluginManager.py
+++ b/blueman/main/PluginManager.py
@@ -3,7 +3,7 @@
import logging
import traceback
import importlib
-from typing import Dict, List, Type, Union
+from typing import Dict, List, Type, TypeVar, Iterable, Union
from gi.repository import GObject
@@ -15,10 +15,6 @@
from blueman.typing import GSignals
-class StopException(Exception):
- pass
-
-
class LoadException(Exception):
pass
@@ -200,32 +196,13 @@ def unload_plugin(self, name):
def get_plugins(self):
return self.__plugins
- # executes a function on all plugin instances
- def run(self, func, *args, **kwargs):
- rets = []
- for inst in self.__plugins.values():
- try:
- ret = getattr(inst, func)(*args, **kwargs)
- rets.append(ret)
- except Exception:
- logging.error(f"Function {func} on {inst.__class__.__name__} failed", exc_info=True)
-
- return rets
-
- # executes a function on all plugin instances, runs a callback after each plugin returns something
- def run_ex(self, func, callback, *args, **kwargs):
- for inst in self.__plugins.values():
- ret = getattr(inst, func)(*args, **kwargs)
- try:
- ret = callback(inst, ret)
- except StopException:
- return ret
- except Exception:
- logging.error(f"Function {func} on {inst.__class__.__name__} failed", exc_info=True)
- return
+ _U = TypeVar("_U")
- if ret is not None:
- args = ret
+ def get_loaded_plugins(self, protocol: Type[_U]) -> Iterable[_U]:
+ for name in self.__loaded:
+ plugin = self.__plugins[name]
+ if isinstance(plugin, protocol):
+ yield plugin
class PersistentPluginManager(PluginManager):
diff --git a/blueman/plugins/BasePlugin.py b/blueman/plugins/BasePlugin.py
index a66f89a73..0e61328ea 100644
--- a/blueman/plugins/BasePlugin.py
+++ b/blueman/plugins/BasePlugin.py
@@ -1,15 +1,11 @@
import logging
import weakref
from gettext import gettext as _
-from typing import List, TYPE_CHECKING, Dict, Tuple, Any, Type
+from typing import List, TYPE_CHECKING, Dict, Tuple, Any
from blueman.main.Config import Config
-class MethodAlreadyExists(Exception):
- pass
-
-
if TYPE_CHECKING:
from typing_extensions import TypedDict
@@ -48,8 +44,6 @@ class BasePlugin:
def __init__(self, parent):
self.parent = parent
- self.__methods: List[Tuple[Type[BasePlugin], str]] = []
-
if self.__options__:
self.__config = Config(
self.__class__.__gsettings__.get("schema"),
@@ -67,22 +61,9 @@ def is_configurable(cls):
res = map(lambda x: (len(x) > 2), cls.__options__.values())
return True in res
- @classmethod
- def add_method(cls, func):
- """Add a new method that can be used by other plugins to listen for changes, query state, etc"""
- func.__self__.__methods.append((cls, func.__name__))
-
- if func.__name__ in cls.__dict__:
- raise MethodAlreadyExists
- else:
- setattr(cls, func.__name__, func)
-
def _unload(self):
self.on_unload()
- for cls, met in self.__methods:
- delattr(cls, met)
-
self.__class__.__instance__ = None
def _load(self):
diff --git a/blueman/plugins/ManagerPlugin.py b/blueman/plugins/ManagerPlugin.py
index 91dac1478..cb4d376c5 100644
--- a/blueman/plugins/ManagerPlugin.py
+++ b/blueman/plugins/ManagerPlugin.py
@@ -7,7 +7,3 @@ def __init__(self, parent):
def on_unload(self):
pass
-
- # return list of (GtkMenuItem, position) tuples
- def on_request_menu_items(self, manager_menu, device):
- pass
diff --git a/blueman/plugins/applet/AppIndicator.py b/blueman/plugins/applet/AppIndicator.py
index 990e33b93..65dc6ca83 100644
--- a/blueman/plugins/applet/AppIndicator.py
+++ b/blueman/plugins/applet/AppIndicator.py
@@ -4,13 +4,16 @@
# Check if Appindicator is available and raise ImportError
from gi import require_version
+
+from blueman.plugins.applet.StatusIcon import StatusIconImplementationProvider
+
try:
require_version('AppIndicator3', '0.1')
except ValueError:
raise ImportError("AppIndicator3 not found")
-class AppIndicator(AppletPlugin):
+class AppIndicator(AppletPlugin, StatusIconImplementationProvider):
__description__ = _("Uses libappindicator to show a statusicon")
__icon__ = "blueman-tray"
__author__ = "Walmis"
diff --git a/blueman/plugins/applet/DBusService.py b/blueman/plugins/applet/DBusService.py
index 5e20e1ee3..ca09445a2 100644
--- a/blueman/plugins/applet/DBusService.py
+++ b/blueman/plugins/applet/DBusService.py
@@ -7,7 +7,6 @@
from blueman.Service import Service
from blueman.bluez.errors import BluezDBusException
from blueman.main.NetworkManager import NMConnectionError
-from blueman.main.PluginManager import StopException
from blueman.plugins.AppletPlugin import AppletPlugin
from blueman.bluez.Device import Device
from blueman.services.Functions import get_service
@@ -17,6 +16,30 @@
from blueman.services.meta import SerialService, NetworkService
+class RFCOMMConnectedListener:
+ def on_rfcomm_connected(self, service: Service, port: str) -> None:
+ ...
+
+ def on_rfcomm_disconnect(self, port: int) -> None:
+ ...
+
+
+class RFCOMMConnectHandler:
+ def rfcomm_connect_handler(self, service: Service, reply: Callable[[str], None],
+ err: Callable[[Exception], None]) -> bool:
+ ...
+
+
+class ServiceConnectHandler:
+ def service_connect_handler(self, service: Service, ok: Callable[[], None],
+ err: Callable[[Union[NMConnectionError, GLib.Error]], None]) -> bool:
+ ...
+
+ def service_disconnect_handler(self, service: Service, ok: Callable[[], None],
+ err: Callable[[Union[NMConnectionError, GLib.Error]], None]) -> bool:
+ ...
+
+
class DBusService(AppletPlugin):
__depends__ = ["StatusIcon"]
__unloadable__ = False
@@ -24,13 +47,6 @@ class DBusService(AppletPlugin):
__author__ = "Walmis"
def on_load(self):
-
- AppletPlugin.add_method(self.on_rfcomm_connected)
- AppletPlugin.add_method(self.on_rfcomm_disconnect)
- AppletPlugin.add_method(self.rfcomm_connect_handler)
- AppletPlugin.add_method(self.service_connect_handler)
- AppletPlugin.add_method(self.service_disconnect_handler)
-
self._add_dbus_method("QueryPlugins", (), "as", self.parent.Plugins.get_loaded)
self._add_dbus_method("QueryAvailablePlugins", (), "as", lambda: list(self.parent.Plugins.get_classes()))
self._add_dbus_method("SetPluginConfig", ("s", "b"), "", self.parent.Plugins.set_config)
@@ -55,30 +71,26 @@ def connect_service(self, object_path: str, uuid: str, ok: Callable[[], None],
device = Device(obj_path=object_path)
device.connect(reply_handler=ok, error_handler=err)
else:
- def cb(_inst, ret):
- if ret:
- raise StopException
-
service = get_service(Device(obj_path=object_path), uuid)
assert service is not None
- if isinstance(service, SerialService) and 'NMDUNSupport' in self.parent.Plugins.get_loaded():
- self.parent.Plugins.run_ex("service_connect_handler", cb, service, ok, err)
- elif isinstance(service, SerialService) and 'PPPSupport' in self.parent.Plugins.get_loaded():
+ if any(plugin.service_connect_handler(service, ok, err)
+ for plugin in self.parent.Plugins.get_loaded_plugins(ServiceConnectHandler)):
+ pass
+ elif isinstance(service, SerialService):
def reply(rfcomm):
- self.parent.Plugins.run("on_rfcomm_connected", service, rfcomm)
+ for plugin in self.parent.Plugins.get_loaded_plugins(RFCOMMConnectedListener):
+ plugin.on_rfcomm_connected(service, rfcomm)
ok()
- rets = self.parent.Plugins.run("rfcomm_connect_handler", service, reply, err)
- if True in rets:
- pass
- else:
- logging.info("No handler registered")
- err("Service not supported\nPossibly the plugin that handles this service is not loaded")
+ if not any(plugin.rfcomm_connect_handler(service, reply, err)
+ for plugin in self.parent.Plugins.get_loaded_plugins(RFCOMMConnectHandler)):
+ service.connect(reply_handler=lambda port: ok(), error_handler=err)
+ elif isinstance(service, NetworkService):
+ service.connect(reply_handler=lambda interface: ok(), error_handler=err)
else:
- if not self.parent.Plugins.run_ex("service_connect_handler", cb, service, ok, err) \
- and isinstance(service, (SerialService, NetworkService)):
- service.connect(reply_handler=lambda *args: ok(), error_handler=err)
+ logging.info("No handler registered")
+ err("Service not supported\nPossibly the plugin that handles this service is not loaded")
def _disconnect_service(self, object_path: str, uuid: str, port: int, ok: Callable[[], None],
err: Callable[[Union[BluezDBusException, NMConnectionError,
@@ -87,40 +99,21 @@ def _disconnect_service(self, object_path: str, uuid: str, port: int, ok: Callab
device = Device(obj_path=object_path)
device.disconnect(reply_handler=ok, error_handler=err)
else:
- def cb(_inst, ret):
- if ret:
- raise StopException
-
service = get_service(Device(obj_path=object_path), uuid)
assert service is not None
- if isinstance(service, SerialService) and 'NMDUNSupport' in self.parent.Plugins.get_loaded():
- self.parent.Plugins.run_ex("service_disconnect_handler", cb, service, ok, err)
- elif isinstance(service, SerialService) and 'PPPSupport' in self.parent.Plugins.get_loaded():
+ if any(plugin.service_disconnect_handler(service, ok, err)
+ for plugin in self.parent.Plugins.get_loaded_plugins(ServiceConnectHandler)):
+ pass
+ elif isinstance(service, SerialService):
service.disconnect(port, reply_handler=ok, error_handler=err)
- self.parent.Plugins.run("on_rfcomm_disconnect", port)
+ for plugin in self.parent.Plugins.get_loaded_plugins(RFCOMMConnectedListener):
+ plugin.on_rfcomm_disconnect(port)
logging.info("Disconnecting rfcomm device")
- else:
- if not self.parent.Plugins.run_ex("service_disconnect_handler", cb, service, ok, err) \
- and isinstance(service, NetworkService):
- service.disconnect(reply_handler=ok, error_handler=err)
-
- def service_connect_handler(self, service: Service, ok: Callable[..., None], err: Callable[..., None]) -> bool:
- return False
-
- def service_disconnect_handler(self, service: Service, ok: Callable[..., None], err: Callable[..., None]) -> bool:
- return False
+ elif isinstance(service, NetworkService):
+ service.disconnect(reply_handler=ok, error_handler=err)
def _open_plugin_dialog(self):
self.parent.Plugins.StandardItems.on_plugins()
-
- def rfcomm_connect_handler(self, service: Service, reply: Callable[..., None], err: Callable[..., None]) -> bool:
- return False
-
- def on_rfcomm_connected(self, service, port):
- pass
-
- def on_rfcomm_disconnect(self, port):
- pass
diff --git a/blueman/plugins/applet/KillSwitch.py b/blueman/plugins/applet/KillSwitch.py
index 67ea8f10f..54ae05658 100644
--- a/blueman/plugins/applet/KillSwitch.py
+++ b/blueman/plugins/applet/KillSwitch.py
@@ -8,8 +8,8 @@
from blueman.main.DBusProxies import Mechanism
from blueman.plugins.AppletPlugin import AppletPlugin
-from blueman.plugins.applet.StatusIcon import StatusIcon
-
+from blueman.plugins.applet.PowerManager import PowerStateHandler
+from blueman.plugins.applet.StatusIcon import StatusIcon, StatusIconVisibilityHandler
RFKILL_TYPE_BLUETOOTH = 2
@@ -32,7 +32,7 @@ def __init__(self, idx, switch_type, soft, hard):
self.hard = hard
-class KillSwitch(AppletPlugin):
+class KillSwitch(AppletPlugin, PowerStateHandler, StatusIconVisibilityHandler):
__author__ = "Walmis"
__description__ = _("Switches Bluetooth killswitch status to match Bluetooth power state."
"Allows turning Bluetooth back on from an icon that shows its status; "
diff --git a/blueman/plugins/applet/NMDUNSupport.py b/blueman/plugins/applet/NMDUNSupport.py
index 422cb070a..db16b4333 100644
--- a/blueman/plugins/applet/NMDUNSupport.py
+++ b/blueman/plugins/applet/NMDUNSupport.py
@@ -7,9 +7,10 @@
from blueman.plugins.AppletPlugin import AppletPlugin
from blueman.main.NetworkManager import NMDUNConnection, NMConnectionError
from blueman.Sdp import DIALUP_NET_SVCLASS_ID
+from blueman.plugins.applet.DBusService import ServiceConnectHandler
-class NMDUNSupport(AppletPlugin):
+class NMDUNSupport(AppletPlugin, ServiceConnectHandler):
__depends__ = ["StatusIcon", "DBusService"]
__conflicts__ = ["PPPSupport"]
__icon__ = "modem"
@@ -20,8 +21,7 @@ class NMDUNSupport(AppletPlugin):
def on_load(self):
pass
- @staticmethod
- def service_connect_handler(service: Service, ok: Callable[[], None],
+ def service_connect_handler(self, service: Service, ok: Callable[[], None],
err: Callable[[Union[NMConnectionError, GLib.Error]], None]) -> bool:
if DIALUP_NET_SVCLASS_ID != service.short_uuid:
return False
@@ -31,8 +31,7 @@ def service_connect_handler(service: Service, ok: Callable[[], None],
return True
- @staticmethod
- def service_disconnect_handler(service: Service, ok: Callable[[], None],
+ def service_disconnect_handler(self, service: Service, ok: Callable[[], None],
err: Callable[[Union[NMConnectionError, GLib.Error]], None]) -> bool:
if DIALUP_NET_SVCLASS_ID != service.short_uuid:
return False
diff --git a/blueman/plugins/applet/NMPANSupport.py b/blueman/plugins/applet/NMPANSupport.py
index 22f27f107..fde2f4241 100644
--- a/blueman/plugins/applet/NMPANSupport.py
+++ b/blueman/plugins/applet/NMPANSupport.py
@@ -6,10 +6,11 @@
from blueman.Service import Service
from blueman.plugins.AppletPlugin import AppletPlugin
from blueman.main.NetworkManager import NMPANConnection, NMConnectionError
+from blueman.plugins.applet.DBusService import ServiceConnectHandler
from blueman.services.meta import NetworkService
-class NMPANSupport(AppletPlugin):
+class NMPANSupport(AppletPlugin, ServiceConnectHandler):
__depends__ = ["DBusService"]
__conflicts__ = ["DhcpClient"]
__icon__ = "network-workgroup"
@@ -20,8 +21,7 @@ class NMPANSupport(AppletPlugin):
def on_load(self):
pass
- @staticmethod
- def service_connect_handler(service: Service, ok: Callable[[], None],
+ def service_connect_handler(self, service: Service, ok: Callable[[], None],
err: Callable[[Union[NMConnectionError, GLib.Error]], None]) -> bool:
if not isinstance(service, NetworkService):
return False
@@ -31,8 +31,7 @@ def service_connect_handler(service: Service, ok: Callable[[], None],
return True
- @staticmethod
- def service_disconnect_handler(service: Service, ok: Callable[[], None],
+ def service_disconnect_handler(self, service: Service, ok: Callable[[], None],
err: Callable[[Union[NMConnectionError, GLib.Error]], None]) -> bool:
if not isinstance(service, NetworkService):
return False
diff --git a/blueman/plugins/applet/NetUsage.py b/blueman/plugins/applet/NetUsage.py
index 3e1c5d8b1..fdb278968 100644
--- a/blueman/plugins/applet/NetUsage.py
+++ b/blueman/plugins/applet/NetUsage.py
@@ -16,6 +16,7 @@
import gi
+from blueman.plugins.applet.PPPSupport import PPPConnectedListener
from blueman.typing import GSignals
gi.require_version("Gtk", "3.0")
@@ -281,7 +282,7 @@ def monitor_removed(self, parent, monitor):
return
-class NetUsage(AppletPlugin, GObject.GObject):
+class NetUsage(AppletPlugin, GObject.GObject, PPPConnectedListener):
__depends__ = ["Menu"]
__icon__ = "network-wireless"
__description__ = _("Allows you to monitor your (mobile broadband) network traffic usage. Useful for limited "
diff --git a/blueman/plugins/applet/PPPSupport.py b/blueman/plugins/applet/PPPSupport.py
index 3b1547710..83a082103 100644
--- a/blueman/plugins/applet/PPPSupport.py
+++ b/blueman/plugins/applet/PPPSupport.py
@@ -4,6 +4,7 @@
from _blueman import RFCOMMError
from blueman.Service import Service
+from blueman.bluez.Device import Device
from blueman.plugins.AppletPlugin import AppletPlugin
from blueman.gui.Notification import Notification
from blueman.main.DBusProxies import Mechanism
@@ -14,12 +15,18 @@
import subprocess
import logging
+from blueman.plugins.applet.DBusService import RFCOMMConnectHandler
from blueman.services import DialupNetwork
if TYPE_CHECKING:
from blueman.main.Applet import BluemanApplet
+class PPPConnectedListener:
+ def on_ppp_connected(self, device: Device, rfcomm: str, ppp_port: str) -> None:
+ ...
+
+
class Connection:
def __init__(self, applet: "BluemanApplet", service: DialupNetwork, port: str,
ok: Callable[[str], None], err: Callable[[GLib.Error], None]):
@@ -52,7 +59,8 @@ def on_error(self, _obj: Mechanism, result: GLib.Error, _user_data: None) -> Non
def on_connected(self, _obj: Mechanism, result: str, _user_data: None) -> None:
self.reply_handler(self.port)
- self.parent.Plugins.run("on_ppp_connected", self.service.device, self.port, result)
+ for plugin in self.parent.Plugins.get_loaded_plugins(PPPConnectedListener):
+ plugin.on_ppp_connected(self.service.device, self.port, result)
msg = _("Successfully connected to DUN service on %(0)s.\n"
"Network is now available through %(1)s") % {"0": self.service.device['Alias'], "1": result}
@@ -60,25 +68,16 @@ def on_connected(self, _obj: Mechanism, result: str, _user_data: None) -> None:
Notification(_("Connected"), msg, icon_name="network-wireless").show()
-class PPPSupport(AppletPlugin):
+class PPPSupport(AppletPlugin, RFCOMMConnectHandler):
__depends__ = ["DBusService"]
__description__ = _("Provides basic support for connecting to the internet via DUN profile.")
__author__ = "Walmis"
__icon__ = "modem"
__priority__ = 0
- def on_load(self):
- AppletPlugin.add_method(self.on_ppp_connected)
-
def on_unload(self):
pass
- def on_ppp_connected(self, device, rfcomm, ppp_port):
- pass
-
- def on_rfcomm_connected(self, service, port):
- pass
-
def rfcomm_connect_handler(self, service: Service, reply: Callable[[str], None],
err: Callable[[Union[RFCOMMError, GLib.Error]], None]) -> bool:
if isinstance(service, DialupNetwork):
diff --git a/blueman/plugins/applet/PowerManager.py b/blueman/plugins/applet/PowerManager.py
index f1306c77d..3f3e36b6f 100644
--- a/blueman/plugins/applet/PowerManager.py
+++ b/blueman/plugins/applet/PowerManager.py
@@ -1,13 +1,30 @@
from gettext import gettext as _
import logging
+from typing import Callable
from blueman.plugins.AppletPlugin import AppletPlugin
from blueman.bluez.Adapter import Adapter
from gi.repository import GLib
+from blueman.plugins.applet.StatusIcon import StatusIconProvider
-class PowerManager(AppletPlugin):
+
+class PowerStateListener:
+ def on_power_state_changed(self, manager: "PowerManager", state: bool) -> None:
+ ...
+
+
+class PowerStateHandler:
+ def on_power_state_query(self, manager: "PowerManager") -> int:
+ ...
+
+ def on_power_state_change_requested(self, manager: "PowerManager", state: bool,
+ cb: Callable[[bool], None]) -> None:
+ ...
+
+
+class PowerManager(AppletPlugin, StatusIconProvider):
__depends__ = ["StatusIcon", "Menu"]
__unloadable__ = True
__description__ = _("Controls Bluetooth adapter power states")
@@ -29,10 +46,6 @@ class PowerManager(AppletPlugin):
}
def on_load(self):
- AppletPlugin.add_method(self.on_power_state_query)
- AppletPlugin.add_method(self.on_power_state_change_requested)
- AppletPlugin.add_method(self.on_power_state_changed)
-
self.item = self.parent.Plugins.Menu.add(self, 0, text=_("Turn Bluetooth _Off"), markup=True,
icon_name="blueman-disabled", tooltip=_("Turn off all adapters"),
callback=self.on_bluetooth_toggled)
@@ -123,31 +136,22 @@ def request_power_state(self, state, force=False):
logging.info(f"Requesting {state}")
cb = PowerManager.Callback(self, state)
- rets = self.parent.Plugins.run("on_power_state_change_requested", self, state, cb)
- cb.num_cb = len(rets)
+ handlers = list(self.parent.Plugins.get_loaded_plugins(PowerStateHandler))
+ for handler in handlers:
+ handler.on_power_state_change_requested(self, state, cb)
+ cb.num_cb = len(handlers)
cb.check()
else:
logging.info("Another request in progress")
- def on_power_state_change_requested(self, pm, state, cb):
- cb(None)
-
- def on_power_state_query(self, pm):
- if self.adapter_state:
- return self.STATE_ON
- else:
- return self.STATE_OFF
-
- def on_power_state_changed(self, manager, state):
- pass
-
# queries other plugins to determine the current power state
def update_power_state(self):
- rets = self.parent.Plugins.run("on_power_state_query", self)
+ rets = [plugin.on_power_state_query(self)
+ for plugin in self.parent.Plugins.get_loaded_plugins(PowerStateHandler)]
- off = any(x < self.STATE_ON for x in rets)
+ off = any(x < self.STATE_ON for x in rets) or not self.adapter_state
foff = self.STATE_OFF_FORCED in rets
- on = self.STATE_ON in rets
+ on = self.STATE_ON in rets or self.adapter_state
new_state = True
if foff or off:
@@ -175,7 +179,15 @@ def update_power_state(self):
self.current_state = new_state
self._emit_dbus_signal("BluetoothStatusChanged", new_state)
- self.parent.Plugins.run("on_power_state_changed", self, new_state)
+ for plugin in self.parent.Plugins.get_loaded_plugins(PowerStateListener):
+ plugin.on_power_state_changed(self, new_state)
+
+ if new_state:
+ self.parent.Plugins.StatusIcon.set_text_line(0, _("Bluetooth Enabled"))
+ self.parent.Plugins.StatusIcon.query_visibility(delay_hiding=True)
+ else:
+ self.parent.Plugins.StatusIcon.set_text_line(0, _("Bluetooth Disabled"))
+ self.parent.Plugins.StatusIcon.query_visibility()
self.parent.Plugins.StatusIcon.icon_should_change()
def get_bluetooth_status(self):
@@ -195,7 +207,7 @@ def on_bluetooth_toggled(self):
def on_status_icon_query_icon(self):
if not self.get_bluetooth_status():
- return "blueman-disabled", "blueman-disabled"
+ return "blueman-disabled"
def on_adapter_added(self, path):
adapter = Adapter(obj_path=path)
diff --git a/blueman/plugins/applet/RecentConns.py b/blueman/plugins/applet/RecentConns.py
index b5cd28eec..afe574c5d 100644
--- a/blueman/plugins/applet/RecentConns.py
+++ b/blueman/plugins/applet/RecentConns.py
@@ -9,8 +9,7 @@
from blueman.gui.Notification import Notification
from blueman.Sdp import ServiceUUID
from blueman.plugins.AppletPlugin import AppletPlugin
-from blueman.plugins.applet.PowerManager import PowerManager
-
+from blueman.plugins.applet.PowerManager import PowerManager, PowerStateListener
if TYPE_CHECKING:
from typing_extensions import TypedDict
@@ -44,7 +43,7 @@ class StoredIcon(_ItemBase):
REGISTRY_VERSION = 0
-class RecentConns(AppletPlugin):
+class RecentConns(AppletPlugin, PowerStateListener):
__depends__ = ["DBusService", "Menu"]
__icon__ = "document-open-recent"
__description__ = _("Provides a menu item that contains last used connections for quick access")
diff --git a/blueman/plugins/applet/SerialManager.py b/blueman/plugins/applet/SerialManager.py
index 225f6eb60..7f61a4c95 100644
--- a/blueman/plugins/applet/SerialManager.py
+++ b/blueman/plugins/applet/SerialManager.py
@@ -1,12 +1,12 @@
from gettext import gettext as _
from typing import Dict, Any, Callable # noqa: F401
-from blueman.Service import Service
from blueman.plugins.AppletPlugin import AppletPlugin
from blueman.gui.Notification import Notification
from blueman.Sdp import SERIAL_PORT_SVCLASS_ID
+from blueman.plugins.applet.DBusService import RFCOMMConnectedListener
from blueman.services.Functions import get_services
-from _blueman import rfcomm_list, RFCOMMError
+from _blueman import rfcomm_list
from subprocess import Popen
import logging
import os
@@ -17,7 +17,7 @@
from blueman.services.meta import SerialService
-class SerialManager(AppletPlugin):
+class SerialManager(AppletPlugin, RFCOMMConnectedListener):
__icon__ = "blueman-serial"
__description__ = _("Standard SPP profile connection handler, allows executing custom actions")
__author__ = "walmis"
@@ -124,14 +124,6 @@ def on_rfcomm_disconnect(self, port):
logging.info(f"Sending HUP to {process.pid}")
os.killpg(process.pid, signal.SIGHUP)
- def rfcomm_connect_handler(self, service: Service, reply: Callable[[str], None],
- err: Callable[[RFCOMMError], None]) -> bool:
- if isinstance(service, SerialService):
- service.connect(reply_handler=reply, error_handler=err)
- return True
- else:
- return False
-
def on_device_disconnect(self, device):
serial_services = [service for service in get_services(device) if isinstance(service, SerialService)]
diff --git a/blueman/plugins/applet/ShowConnected.py b/blueman/plugins/applet/ShowConnected.py
index 36aa0927e..d0044ec04 100644
--- a/blueman/plugins/applet/ShowConnected.py
+++ b/blueman/plugins/applet/ShowConnected.py
@@ -4,8 +4,10 @@
from blueman.plugins.AppletPlugin import AppletPlugin
from gettext import ngettext
+from blueman.plugins.applet.StatusIcon import StatusIconProvider
-class ShowConnected(AppletPlugin):
+
+class ShowConnected(AppletPlugin, StatusIconProvider):
__author__ = "Walmis"
__depends__ = ["StatusIcon"]
__icon__ = "blueman-active"
@@ -25,7 +27,7 @@ def on_unload(self):
def on_status_icon_query_icon(self):
if self.num_connections > 0:
self.active = True
- return "blueman-active",
+ return "blueman-active"
else:
self.active = False
diff --git a/blueman/plugins/applet/StandardItems.py b/blueman/plugins/applet/StandardItems.py
index 015014464..9279685f0 100644
--- a/blueman/plugins/applet/StandardItems.py
+++ b/blueman/plugins/applet/StandardItems.py
@@ -7,11 +7,14 @@
from blueman.gui.applet.PluginDialog import PluginDialog
import gi
+
+from blueman.plugins.applet.PowerManager import PowerStateListener
+
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
-class StandardItems(AppletPlugin):
+class StandardItems(AppletPlugin, PowerStateListener):
__depends__ = ["StatusIcon", "Menu"]
__unloadable__ = False
__description__ = _("Adds standard menu items to the status icon menu")
diff --git a/blueman/plugins/applet/StatusIcon.py b/blueman/plugins/applet/StatusIcon.py
index 3a69912d4..53a91aa90 100644
--- a/blueman/plugins/applet/StatusIcon.py
+++ b/blueman/plugins/applet/StatusIcon.py
@@ -1,13 +1,28 @@
from gettext import gettext as _
+from typing import Optional
from gi.repository import GObject, GLib
from blueman.Functions import launch
-from blueman.main.PluginManager import StopException
from blueman.plugins.AppletPlugin import AppletPlugin
from blueman.typing import GSignals
+class StatusIconImplementationProvider:
+ def on_query_status_icon_implementation(self) -> Optional[str]:
+ ...
+
+
+class StatusIconVisibilityHandler:
+ def on_query_status_icon_visibility(self) -> int:
+ ...
+
+
+class StatusIconProvider:
+ def on_status_icon_query_icon(self) -> Optional[str]:
+ ...
+
+
class StatusIcon(AppletPlugin, GObject.GObject):
__gsignals__: GSignals = {'activate': (GObject.SignalFlags.NO_HOOKS, None, ())}
@@ -29,10 +44,6 @@ def on_load(self):
GObject.GObject.__init__(self)
self.lines = {0: _("Bluetooth Enabled")}
- AppletPlugin.add_method(self.on_query_status_icon_implementation)
- AppletPlugin.add_method(self.on_query_status_icon_visibility)
- AppletPlugin.add_method(self.on_status_icon_query_icon)
-
self.query_visibility(emit=False)
self.parent.Plugins.connect('plugin-loaded', self._on_plugins_changed)
@@ -47,16 +58,9 @@ def on_load(self):
self._add_dbus_method("GetIconName", (), "s", self._get_icon_name)
self._add_dbus_method("Activate", (), "", lambda: self.emit("activate"))
- def on_power_state_changed(self, _manager, state):
- if state:
- self.set_text_line(0, _("Bluetooth Enabled"))
- self.query_visibility(delay_hiding=True)
- else:
- self.set_text_line(0, _("Bluetooth Disabled"))
- self.query_visibility()
-
def query_visibility(self, delay_hiding=False, emit=True):
- rets = self.parent.Plugins.run("on_query_status_icon_visibility")
+ rets = [plugin.on_query_status_icon_visibility()
+ for plugin in self.parent.Plugins.get_loaded_plugins(StatusIconVisibilityHandler)]
if StatusIcon.FORCE_HIDE not in rets:
if StatusIcon.FORCE_SHOW in rets:
self.set_visible(True, emit)
@@ -120,27 +124,15 @@ def _on_plugins_changed(self, _plugins, _name):
launch('blueman-tray', icon_name='blueman', sn=False)
def _get_status_icon_implementation(self):
- implementations = self.parent.Plugins.run("on_query_status_icon_implementation")
- return next((implementation for implementation in implementations if implementation), 'GtkStatusIcon')
+ for plugin in self.parent.Plugins.get_loaded_plugins(StatusIconImplementationProvider):
+ implementation = plugin.on_query_status_icon_implementation()
+ if implementation:
+ return implementation
+ return "GtkStatusIcon"
def _get_icon_name(self):
- icon = "blueman-tray"
-
- def callback(inst, ret):
- if ret is not None:
- for i in ret:
- nonlocal icon
- icon = i
- raise StopException
-
- self.parent.Plugins.run_ex("on_status_icon_query_icon", callback)
- return icon
-
- def on_query_status_icon_implementation(self):
- return None
-
- def on_query_status_icon_visibility(self):
- return StatusIcon.SHOW
-
- def on_status_icon_query_icon(self):
- return None
+ for plugin in self.parent.Plugins.get_loaded_plugins(StatusIconProvider):
+ icon = plugin.on_status_icon_query_icon()
+ if icon is not None:
+ return icon
+ return "blueman-tray"
diff --git a/blueman/plugins/manager/Info.py b/blueman/plugins/manager/Info.py
index 00ac27dc4..e4a904785 100644
--- a/blueman/plugins/manager/Info.py
+++ b/blueman/plugins/manager/Info.py
@@ -6,6 +6,7 @@
from blueman.Functions import create_menuitem
from blueman.Sdp import ServiceUUID
from blueman.bluez.errors import BluezDBusException
+from blueman.gui.manager.ManagerDeviceMenu import MenuItemsProvider
from blueman.plugins.ManagerPlugin import ManagerPlugin
@@ -107,7 +108,7 @@ def on_accel_activated(group, dialog, key, flags):
dialog.destroy()
-class Info(ManagerPlugin):
+class Info(ManagerPlugin, MenuItemsProvider):
def on_unload(self):
pass
diff --git a/blueman/plugins/manager/Notes.py b/blueman/plugins/manager/Notes.py
index ce2844c64..166c5ea86 100644
--- a/blueman/plugins/manager/Notes.py
+++ b/blueman/plugins/manager/Notes.py
@@ -4,6 +4,7 @@
from blueman.Constants import UI_PATH
from blueman.Functions import create_menuitem, launch
+from blueman.gui.manager.ManagerDeviceMenu import MenuItemsProvider
from blueman.plugins.ManagerPlugin import ManagerPlugin
import gi
@@ -44,7 +45,7 @@ def send_note(device, parent):
dialog.present()
-class Notes(ManagerPlugin):
+class Notes(ManagerPlugin, MenuItemsProvider):
def on_unload(self):
pass
diff --git a/blueman/plugins/manager/PulseAudioProfile.py b/blueman/plugins/manager/PulseAudioProfile.py
index 55dd5eddd..6dc63ad24 100644
--- a/blueman/plugins/manager/PulseAudioProfile.py
+++ b/blueman/plugins/manager/PulseAudioProfile.py
@@ -5,7 +5,7 @@
from blueman.bluez.Device import Device
from blueman.plugins.ManagerPlugin import ManagerPlugin
from blueman.main.PulseAudioUtils import PulseAudioUtils, EventType
-from blueman.gui.manager.ManagerDeviceMenu import ManagerDeviceMenu
+from blueman.gui.manager.ManagerDeviceMenu import ManagerDeviceMenu, MenuItemsProvider
from blueman.gui.MessageArea import MessageArea
from blueman.Functions import create_menuitem
from blueman.Sdp import AUDIO_SOURCE_SVCLASS_ID, AUDIO_SINK_SVCLASS_ID, ServiceUUID
@@ -19,7 +19,7 @@
from blueman.main.PulseAudioUtils import CardInfo # noqa: F401
-class PulseAudioProfile(ManagerPlugin):
+class PulseAudioProfile(ManagerPlugin, MenuItemsProvider):
def on_load(self):
self.devices: Dict[str, "CardInfo"] = {}
diff --git a/blueman/plugins/manager/Services.py b/blueman/plugins/manager/Services.py
index ff8f5f949..829e569dd 100644
--- a/blueman/plugins/manager/Services.py
+++ b/blueman/plugins/manager/Services.py
@@ -2,6 +2,7 @@
import cairo
from blueman.bluez.Network import Network
+from blueman.gui.manager.ManagerDeviceMenu import MenuItemsProvider
from blueman.plugins.ManagerPlugin import ManagerPlugin
from blueman.Functions import create_menuitem
from blueman.main.DBusProxies import AppletService
@@ -16,7 +17,7 @@
from gi.repository import Gtk
-class Services(ManagerPlugin):
+class Services(ManagerPlugin, MenuItemsProvider):
def on_load(self):
self.icon_theme = Gtk.IconTheme.get_default()