From 556eacab120d556f05b7b6c57d64cfc0086b882f Mon Sep 17 00:00:00 2001 From: Sander Sweers Date: Sun, 15 Feb 2026 11:33:51 +0100 Subject: [PATCH 1/5] ManagerDeviceMenu: Use pattern matching in _handle_error_message --- blueman/gui/manager/ManagerDeviceMenu.py | 31 ++++++++++++------------ 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/blueman/gui/manager/ManagerDeviceMenu.py b/blueman/gui/manager/ManagerDeviceMenu.py index 7e892e866..8625ca7fd 100644 --- a/blueman/gui/manager/ManagerDeviceMenu.py +++ b/blueman/gui/manager/ManagerDeviceMenu.py @@ -196,21 +196,22 @@ def on_device_property_changed(self, lst: "ManagerDeviceList", _device: Device, def _handle_error_message(self, error: GLib.Error) -> None: err = self._BLUEZ_ERROR_MAP.get(error.message.split(":", 3)[-1].strip()) - if err == self._BluezError.PROFILE_UNAVAILABLE: - logging.warning("No audio endpoints registered to bluetoothd. " - "Pulseaudio Bluetooth module, bluez-alsa, PipeWire or other audio support missing.") - msg = _("No audio endpoints registered") - elif err == self._BluezError.CREATE_SOCKET: - logging.warning("bluetoothd reported input/output error. Check its logs for context.") - msg = _("Input/output error") - elif err == self._BluezError.PAGE_TIMEOUT: - msg = _("Device did not respond") - elif err == self._BluezError.UNKNOWN: - logging.warning("bluetoothd reported an unknown error. " - "Retry or check its logs for context.") - msg = _("Unknown error") - else: - msg = error.message.split(":", 3)[-1].strip() + match err: + case self._BluezError.PROFILE_UNAVAILABLE: + logging.warning("No audio endpoints registered to bluetoothd. " + "Pulseaudio Bluetooth module, bluez-alsa, PipeWire or other audio support missing.") + msg = _("No audio endpoints registered") + case self._BluezError.CREATE_SOCKET: + logging.warning("bluetoothd reported input/output error. Check its logs for context.") + msg = _("Input/output error") + case self._BluezError.PAGE_TIMEOUT: + msg = _("Device did not respond") + case self._BluezError.UNKNOWN: + logging.warning("bluetoothd reported an unknown error. " + "Retry or check its logs for context.") + msg = _("Unknown error") + case _: + msg = error.message.split(":", 3)[-1].strip() if err != self._BluezError.CANCELED: self.Blueman.infobar_update(_("Connection Failed: ") + msg) From 197ae70cfa8d13ae51d3b583419bc98f416cacf6 Mon Sep 17 00:00:00 2001 From: Sander Sweers Date: Sun, 15 Feb 2026 13:33:19 +0100 Subject: [PATCH 2/5] ManagerDeviceMenu: Add bluez errors I see them often enough in the issues and locally. --- blueman/gui/manager/ManagerDeviceMenu.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/blueman/gui/manager/ManagerDeviceMenu.py b/blueman/gui/manager/ManagerDeviceMenu.py index 8625ca7fd..8f23e8f20 100644 --- a/blueman/gui/manager/ManagerDeviceMenu.py +++ b/blueman/gui/manager/ManagerDeviceMenu.py @@ -206,6 +206,10 @@ def _handle_error_message(self, error: GLib.Error) -> None: msg = _("Input/output error") case self._BluezError.PAGE_TIMEOUT: msg = _("Device did not respond") + case self._BluezError.ABORTED: + msg = _("Aborted") + case self._BluezError.RESET: + msg = _("Reset") case self._BluezError.UNKNOWN: logging.warning("bluetoothd reported an unknown error. " "Retry or check its logs for context.") @@ -221,6 +225,8 @@ class _BluezError(Enum): PROFILE_UNAVAILABLE = auto() CREATE_SOCKET = auto() CANCELED = auto() + ABORTED = auto() + RESET = auto() UNKNOWN = auto() # BlueZ 5.62 introduced machine-readable error strings while earlier versions @@ -240,6 +246,10 @@ class _BluezError(Enum): "br-connection-unknown": _BluezError.UNKNOWN, "Cancelled": _BluezError.CANCELED, "br-connection-canceled": _BluezError.CANCELED, + "br-connection-abort-by-local": _BluezError.ABORTED, + "le-connection-abort-by-local": _BluezError.ABORTED, + "br-connection-abort-by-remote": _BluezError.RESET, + "le-connection-abort-by-remote": _BluezError.RESET, } def show_generic_connect_calc(self, device_uuids: Iterable[str]) -> bool: From 295066dcbf3ee6672447a48655febf20aa868fea Mon Sep 17 00:00:00 2001 From: Sander Sweers Date: Sun, 15 Feb 2026 13:16:34 +0100 Subject: [PATCH 3/5] Infobar: Add better feedback for the user Todo: make these translatable (probably will change). --- blueman/gui/manager/ManagerDeviceMenu.py | 20 +++++++++++++------- blueman/main/Manager.py | 7 ++++++- data/ui/manager-main.ui | 5 +++++ 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/blueman/gui/manager/ManagerDeviceMenu.py b/blueman/gui/manager/ManagerDeviceMenu.py index 8f23e8f20..67639f656 100644 --- a/blueman/gui/manager/ManagerDeviceMenu.py +++ b/blueman/gui/manager/ManagerDeviceMenu.py @@ -195,30 +195,36 @@ def on_device_property_changed(self, lst: "ManagerDeviceList", _device: Device, def _handle_error_message(self, error: GLib.Error) -> None: err = self._BLUEZ_ERROR_MAP.get(error.message.split(":", 3)[-1].strip()) - + info: str | None = None match err: case self._BluezError.PROFILE_UNAVAILABLE: - logging.warning("No audio endpoints registered to bluetoothd. " - "Pulseaudio Bluetooth module, bluez-alsa, PipeWire or other audio support missing.") + info = \ + "No audio endpoints registered to bluetoothd. " + "Pulseaudio Bluetooth module, bluez-alsa, PipeWire or other audio support missing." msg = _("No audio endpoints registered") case self._BluezError.CREATE_SOCKET: - logging.warning("bluetoothd reported input/output error. Check its logs for context.") + info = "bluetoothd reported input/output error. Check its logs for context." msg = _("Input/output error") case self._BluezError.PAGE_TIMEOUT: msg = _("Device did not respond") + info = "Is the device turned on?" case self._BluezError.ABORTED: msg = _("Aborted") + info = "Is the device turned on?" case self._BluezError.RESET: msg = _("Reset") + info = "Check the device for more info." case self._BluezError.UNKNOWN: - logging.warning("bluetoothd reported an unknown error. " - "Retry or check its logs for context.") + info = "bluetoothd reported an unknown error. \nRetry or check its logs for context." msg = _("Unknown error") case _: msg = error.message.split(":", 3)[-1].strip() + if info is not None: + logging.warning(info) + if err != self._BluezError.CANCELED: - self.Blueman.infobar_update(_("Connection Failed: ") + msg) + self.Blueman.infobar_update(_("Connection Failed: ") + msg, info=info) class _BluezError(Enum): PAGE_TIMEOUT = auto() diff --git a/blueman/main/Manager.py b/blueman/main/Manager.py index 113a79b7a..01f870b24 100644 --- a/blueman/main/Manager.py +++ b/blueman/main/Manager.py @@ -289,7 +289,8 @@ def on_error(e: Exception) -> None: s1 = self.List.connect("discovery-progress", on_progress) s2 = self.List.connect("adapter-property-changed", prop_changed) - def infobar_update(self, message: str, bt: str | None = None, icon_name: str = "dialog-warning") -> None: + def infobar_update(self, message: str, bt: str | None = None, info: str | None = None, icon_name: str = "dialog-warning") -> None: + more_button = self.builder.get_widget("ib_backtrace_button", Gtk.Button) if icon_name == "dialog-warning": self._infobar.set_message_type(Gtk.MessageType.WARNING) else: @@ -298,6 +299,10 @@ def infobar_update(self, message: str, bt: str | None = None, icon_name: str = " more_button = self.builder.get_widget("ib_more_button", Gtk.Button) image = self.builder.get_widget("ib_icon", Gtk.Image) msg_lbl = self.builder.get_widget("ib_message", Gtk.Label) + info_image = self.builder.get_widget("ib_info_image", Gtk.Image) + + info_image.set_visible(False if info is None else True) + info_image.set_tooltip_text("" if info is None else info) image.set_from_icon_name(icon_name, 16) if bt is not None: diff --git a/data/ui/manager-main.ui b/data/ui/manager-main.ui index 9314fb052..4a1ea2b52 100644 --- a/data/ui/manager-main.ui +++ b/data/ui/manager-main.ui @@ -694,6 +694,11 @@ 1 + + + dialog-question + + False From 3e3bb12025f22c2889e676ff51cbad883ef007c1 Mon Sep 17 00:00:00 2001 From: Sander Sweers Date: Sun, 15 Feb 2026 13:24:36 +0100 Subject: [PATCH 4/5] infobar: drop icon and rename more button * Remove the dialog-warning icon. It doesn't much. * AFAICS blueman only uses the More button for backtraces so rename it. * Infobar should be an error when there is a backtrace. --- blueman/main/Manager.py | 18 ++++++------------ data/ui/manager-main.ui | 25 ++++++------------------- 2 files changed, 12 insertions(+), 31 deletions(-) diff --git a/blueman/main/Manager.py b/blueman/main/Manager.py index 01f870b24..87b9efcc0 100644 --- a/blueman/main/Manager.py +++ b/blueman/main/Manager.py @@ -289,29 +289,23 @@ def on_error(e: Exception) -> None: s1 = self.List.connect("discovery-progress", on_progress) s2 = self.List.connect("adapter-property-changed", prop_changed) - def infobar_update(self, message: str, bt: str | None = None, info: str | None = None, icon_name: str = "dialog-warning") -> None: - more_button = self.builder.get_widget("ib_backtrace_button", Gtk.Button) - if icon_name == "dialog-warning": - self._infobar.set_message_type(Gtk.MessageType.WARNING) - else: - self._infobar.set_message_type(Gtk.MessageType.INFO) - - more_button = self.builder.get_widget("ib_more_button", Gtk.Button) - image = self.builder.get_widget("ib_icon", Gtk.Image) + def infobar_update(self, message: str, bt: str | None = None, info: str | None = None) -> None: + backtace_button = self.builder.get_widget("ib_backtrace_button", Gtk.Button) msg_lbl = self.builder.get_widget("ib_message", Gtk.Label) info_image = self.builder.get_widget("ib_info_image", Gtk.Image) info_image.set_visible(False if info is None else True) info_image.set_tooltip_text("" if info is None else info) - image.set_from_icon_name(icon_name, 16) if bt is not None: msg_lbl.set_text(f"{message}…") self._infobar_bt = f"{message}\n{bt}" - more_button.show() + backtace_button.show() + self._infobar.set_message_type(Gtk.MessageType.ERROR) else: - more_button.hide() + backtace_button.hide() msg_lbl.set_text(f"{message}") + self._infobar.set_message_type(Gtk.MessageType.INFO) self._infobar.set_visible(True) self._infobar.set_revealed(True) diff --git a/data/ui/manager-main.ui b/data/ui/manager-main.ui index 4a1ea2b52..d15c70a30 100644 --- a/data/ui/manager-main.ui +++ b/data/ui/manager-main.ui @@ -1,9 +1,9 @@ - + - + False dialog-information True @@ -644,10 +644,10 @@ False 8 - + True - ib_more_icon - More + ib_backtrace_icon + Backtrace True none @@ -668,19 +668,6 @@ False 16 - - - False - dialog-warning - 16 - True - - - False - True - 0 - - False @@ -708,7 +695,7 @@ - ib_more_button + ib_backtrace_button From 23d95d395bb2bb0584a0f7e7cc6cf5b09ed7318e Mon Sep 17 00:00:00 2001 From: Sander Sweers Date: Sun, 15 Feb 2026 14:40:23 +0100 Subject: [PATCH 5/5] ManagerDeviceMenu: handle error when connecting PANU/DUN DUN untested as I don't have a working phone that supports it. NMConnectionBase now uses a dedicated exception for unsupported connection types to avoid confusion. --- blueman/gui/manager/ManagerDeviceMenu.py | 4 ++++ blueman/main/NetworkManager.py | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/blueman/gui/manager/ManagerDeviceMenu.py b/blueman/gui/manager/ManagerDeviceMenu.py index 67639f656..21355b5a2 100644 --- a/blueman/gui/manager/ManagerDeviceMenu.py +++ b/blueman/gui/manager/ManagerDeviceMenu.py @@ -217,6 +217,8 @@ def _handle_error_message(self, error: GLib.Error) -> None: case self._BluezError.UNKNOWN: info = "bluetoothd reported an unknown error. \nRetry or check its logs for context." msg = _("Unknown error") + case self._BluezError.NMBTFAILED: + msg = _("could not create bluetooth connection for NetworkManager") case _: msg = error.message.split(":", 3)[-1].strip() @@ -234,6 +236,7 @@ class _BluezError(Enum): ABORTED = auto() RESET = auto() UNKNOWN = auto() + NMBTFAILED = auto() # BlueZ 5.62 introduced machine-readable error strings while earlier versions # used strerror() so that the messages depend on the libc implementation: @@ -256,6 +259,7 @@ class _BluezError(Enum): "le-connection-abort-by-local": _BluezError.ABORTED, "br-connection-abort-by-remote": _BluezError.RESET, "le-connection-abort-by-remote": _BluezError.RESET, + "Connection failed with reason: bt-failed": _BluezError.NMBTFAILED, } def show_generic_connect_calc(self, device_uuids: Iterable[str]) -> bool: diff --git a/blueman/main/NetworkManager.py b/blueman/main/NetworkManager.py index 903980f07..7148453a2 100644 --- a/blueman/main/NetworkManager.py +++ b/blueman/main/NetworkManager.py @@ -18,6 +18,10 @@ class NMConnectionError(Exception): pass +class NMConnectionNotSupported(Exception): + pass + + class NMConnectionBase: conntype: str @@ -25,7 +29,7 @@ def __init__(self, service: Service, reply_handler: Callable[[], None], error_handler: Callable[[NMConnectionError | GLib.Error], None]): if self.conntype not in ('dun', 'panu'): error_handler( - NMConnectionError(f"Invalid connection type {self.conntype}, should be panu or dun") + NMConnectionNotSupported(f"Invalid connection type {self.conntype}, should be panu or dun") ) self.device = service.device self.bdaddr: BtAddress = self.device['Address']