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()