diff --git a/CHANGELOG.md b/CHANGELOG.md index 708f4227e..2bfff3e66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,14 @@ * Fix Rfcom plugin dbus signature * Set an initial selected device in blueman-sendto * AutoConnect: Store bluetooth address instead of object path +* Applet: Handle UnknownObject DBus error (@tommie) +* Make search button available after device list becomes empty (@astcri) ### Changes +* Terminate applet on manager termination if it was started by manager * Add Galic and Esperanto translations * AutoConnect: Automatically convert path to address +* Add toggle to force symbolic statusicon ## 2.4.3 diff --git a/blueman/bluez/errors.py b/blueman/bluez/errors.py index 6f31b1c77..dfe3200a1 100644 --- a/blueman/bluez/errors.py +++ b/blueman/bluez/errors.py @@ -85,6 +85,10 @@ class DBusUnsupportedMajorClassError(BluezDBusException): pass +class DBusUnknownObjectError(BluezDBusException): + pass + + class DBusServiceUnknownError(BluezDBusException): pass @@ -121,6 +125,7 @@ class BluezUnavailableAgentMethodError(BluezDBusException): 'org.bluez.Error.AuthenticationCanceled': DBusAuthenticationCanceledError, 'org.bluez.serial.Error.NotSupported': DBusNotSupportedError, 'org.bluez.Error.UnsupportedMajorClass': DBusUnsupportedMajorClassError, + 'org.freedesktop.DBus.Error.UnknownObject': DBusUnknownObjectError, 'org.freedesktop.DBus.Error.ServiceUnknown': DBusServiceUnknownError} diff --git a/blueman/gui/manager/ManagerToolbar.py b/blueman/gui/manager/ManagerToolbar.py index bf0e17f35..c87f5fdb8 100644 --- a/blueman/gui/manager/ManagerToolbar.py +++ b/blueman/gui/manager/ManagerToolbar.py @@ -72,7 +72,7 @@ def on_device_selected( device: Optional[Device], _tree_iter: Gtk.TreeIter, ) -> None: - self._update_buttons(None if device is None else Adapter(obj_path=device["Adapter"])) + self._update_buttons(dev_list.Adapter) def _update_buttons(self, adapter: Optional[Adapter]) -> None: powered = adapter is not None and adapter["Powered"] diff --git a/blueman/main/Applet.py b/blueman/main/Applet.py index 9e51f9d9f..9986c6494 100644 --- a/blueman/main/Applet.py +++ b/blueman/main/Applet.py @@ -65,6 +65,11 @@ def do_quit(_: object) -> bool: def do_startup(self) -> None: Gtk.Application.do_startup(self) + + quit_action = Gio.SimpleAction.new("Quit", None) + quit_action.connect("activate", lambda _action, _param: self.quit()) + self.add_action(quit_action) + self.set_accels_for_action("win.close", ["w", "Escape"]) def do_activate(self) -> None: diff --git a/blueman/main/DBusProxies.py b/blueman/main/DBusProxies.py index 599ff1665..5f259b085 100644 --- a/blueman/main/DBusProxies.py +++ b/blueman/main/DBusProxies.py @@ -43,6 +43,12 @@ def call_finish(proxy: ProxyBase, response: Gio.AsyncResult) -> None: self.call(name, params, Gio.DBusCallFlags.NONE, -1, None, call_finish) +class DBus(ProxyBase): + def __init__(self) -> None: + super().__init__(name="org.freedesktop.DBus", interface_name="org.freedesktop.DBus", + object_path="/org/freedesktop/DBus") + + class Mechanism(ProxyBase): def __init__(self) -> None: super().__init__(name='org.blueman.Mechanism', interface_name='org.blueman.Mechanism', @@ -50,11 +56,22 @@ def __init__(self) -> None: class AppletService(ProxyBase): + NAME = "org.blueman.Applet" + def __init__(self) -> None: - super().__init__(name='org.blueman.Applet', interface_name='org.blueman.Applet', + super().__init__(name=self.NAME, interface_name='org.blueman.Applet', object_path="/org/blueman/Applet") +class AppletServiceApplication(ProxyBase): + def __init__(self) -> None: + super().__init__(name=AppletService.NAME, interface_name="org.freedesktop.Application", + object_path="/org/blueman/Applet") + + def stop(self) -> None: + self.ActivateAction('(sava{sv})', "Quit", [], {}) + + class ManagerService(ProxyBase): def __init__(self) -> None: super().__init__(name="org.blueman.Manager", interface_name="org.freedesktop.Application", diff --git a/blueman/main/Manager.py b/blueman/main/Manager.py index 8ec24fa6a..bf1bcc2db 100644 --- a/blueman/main/Manager.py +++ b/blueman/main/Manager.py @@ -13,7 +13,7 @@ from blueman.gui.manager.ManagerStats import ManagerStats from blueman.gui.manager.ManagerProgressbar import ManagerProgressbar from blueman.main.Builder import Builder -from blueman.main.DBusProxies import AppletService, DBusProxyFailed +from blueman.main.DBusProxies import AppletService, DBusProxyFailed, DBus, AppletServiceApplication from blueman.gui.CommonUi import ErrorDialog from blueman.gui.Notification import Notification from blueman.main.PluginManager import PluginManager @@ -29,6 +29,7 @@ class Blueman(Gtk.Application): def __init__(self) -> None: super().__init__(application_id="org.blueman.Manager") + self._applet_was_running = DBus().NameHasOwner("(s)", AppletService.NAME) def do_quit(_: object) -> bool: self.quit() @@ -60,6 +61,12 @@ def doquit(_a: Gio.SimpleAction, _param: None) -> None: bt_status_action.connect("change-state", self._on_bt_state_changed) self.add_action(bt_status_action) + def do_shutdown(self) -> None: + Gtk.Application.do_shutdown(self) + + if not self._applet_was_running: + AppletServiceApplication().stop() + def do_activate(self) -> None: if not self.window: self.window = self.builder.get_widget("manager_window", Gtk.ApplicationWindow) diff --git a/blueman/plugins/BasePlugin.py b/blueman/plugins/BasePlugin.py index 5206b058c..5b2a88018 100644 --- a/blueman/plugins/BasePlugin.py +++ b/blueman/plugins/BasePlugin.py @@ -45,7 +45,7 @@ class BasePlugin: def __init__(self, *_args: object) -> None: if self.__options__: - self.__config = Gio.Settings( + self._config = Gio.Settings( schema_id=self.__class__.__gsettings__["schema"], path=self.__class__.__gsettings__["path"] ) @@ -99,7 +99,7 @@ def on_delete(self) -> None: def get_option(self, key: str) -> Any: if key not in self.__class__.__options__: raise KeyError("No such option") - return self.__config[key] + return self._config[key] def set_option(self, key: str, value: Any) -> None: if key not in self.__class__.__options__: @@ -107,7 +107,7 @@ def set_option(self, key: str, value: Any) -> None: opt = self.__class__.__options__[key] if type(value) is opt["type"]: - self.__config[key] = value + self._config[key] = value self.option_changed(key, value) else: raise TypeError(f"Wrong type, must be {repr(opt['type'])}") diff --git a/blueman/plugins/applet/RecentConns.py b/blueman/plugins/applet/RecentConns.py index df23b3d40..9f95fd35c 100644 --- a/blueman/plugins/applet/RecentConns.py +++ b/blueman/plugins/applet/RecentConns.py @@ -6,7 +6,7 @@ from typing import List, TYPE_CHECKING, Optional, Callable, cast, Union from blueman.bluez.Device import Device -from blueman.bluez.errors import DBusNoSuchAdapterError +from blueman.bluez.errors import DBusNoSuchAdapterError, DBusUnknownObjectError from blueman.gui.Notification import Notification from blueman.Sdp import ServiceUUID from blueman.plugins.AppletPlugin import AppletPlugin @@ -199,7 +199,11 @@ def _get_device_path(self, adapter_path: str, address: str) -> Optional[str]: except DBusNoSuchAdapterError: return None - device = self.parent.Manager.find_device(address, adapter.get_object_path()) + try: + device = self.parent.Manager.find_device(address, adapter.get_object_path()) + except DBusUnknownObjectError: + return None + return device.get_object_path() if device is not None else None def _get_items(self) -> List["Item"]: diff --git a/blueman/plugins/applet/StatusIcon.py b/blueman/plugins/applet/StatusIcon.py index cb01e80e3..951dba2a8 100644 --- a/blueman/plugins/applet/StatusIcon.py +++ b/blueman/plugins/applet/StatusIcon.py @@ -27,6 +27,19 @@ class StatusIcon(AppletPlugin, GObject.GObject): __icon__ = "bluetooth-symbolic" __depends__ = ["StandardItems", "Menu"] + __gsettings__ = { + "schema": "org.blueman.general", + "path": None + } + __options__ = { + "symbolic-status-icons": { + "type": bool, + "default": False, + "name": _("Force symbolic status icons"), + "desc": _("When enabled this will force the use of a symbolic version of the blueman statusicon"), + } + } + visible = None visibility_timeout: Optional[int] = None @@ -38,9 +51,7 @@ def on_load(self) -> None: self._tooltip_title = _("Bluetooth Enabled") self._tooltip_text = "" - self.general_config = Gio.Settings(schema_id="org.blueman.general") - self.general_config.connect("changed::symbolic-status-icons", self.on_symbolic_config_change) - + self._config.connect("changed::symbolic-status-icons", self.on_symbolic_config_change) self.query_visibility(emit=False) self.parent.Plugins.connect('plugin-loaded', self._on_plugins_changed) @@ -133,7 +144,7 @@ def _get_icon_name(self) -> str: # depending on configuration, ensure fullcolor icons.. name = name.replace("-symbolic", "") - if self.general_config.get_boolean("symbolic-status-icons"): + if self.get_option("symbolic-status-icons"): # or symbolic name = f"{name}-symbolic" diff --git a/configure.ac b/configure.ac index 2ef518b15..66e35ac31 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ AC_PREREQ(2.61) -AC_INIT([blueman], [2.4.3], [https://github.com/blueman-project/blueman/issues]) +AC_INIT([blueman], [2.4.4], [https://github.com/blueman-project/blueman/issues]) AC_CONFIG_HEADERS(config.h) AC_CONFIG_MACRO_DIRS([m4]) AM_INIT_AUTOMAKE([1.16.3 foreign dist-xz]) diff --git a/meson.build b/meson.build index 40275ec55..9c1004bb8 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project( 'blueman', 'c', - version: '2.4.3', + version: '2.4.4', license: 'GPL3', meson_version: '>=0.56.0', default_options: 'b_lundef=false'