Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions blueman/gui/manager/ManagerDeviceMenu.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"] = []
Expand Down Expand Up @@ -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"])

Expand Down
27 changes: 18 additions & 9 deletions blueman/main/Applet.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)
37 changes: 7 additions & 30 deletions blueman/main/PluginManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -15,10 +15,6 @@
from blueman.typing import GSignals


class StopException(Exception):
pass


class LoadException(Exception):
pass

Expand Down Expand Up @@ -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]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the name because it does a bit more than just get loaded plugins, it get loaded plugins with a certain class. But I can't think of a better one...

for name in self.__loaded:
plugin = self.__plugins[name]
if isinstance(plugin, protocol):
yield plugin


class PersistentPluginManager(PluginManager):
Expand Down
21 changes: 1 addition & 20 deletions blueman/plugins/BasePlugin.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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"),
Expand All @@ -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):
Expand Down
4 changes: 0 additions & 4 deletions blueman/plugins/ManagerPlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 4 additions & 1 deletion blueman/plugins/applet/AppIndicator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
97 changes: 45 additions & 52 deletions blueman/plugins/applet/DBusService.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -17,20 +16,37 @@
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:
Comment thread
infirit marked this conversation as resolved.
...

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
__description__ = _("Provides DBus API for other Blueman components")
__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)
Expand All @@ -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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For alls these any() I find this very hard to read because the if continues on a new line.

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,
Expand All @@ -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
6 changes: 3 additions & 3 deletions blueman/plugins/applet/KillSwitch.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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; "
Expand Down
Loading