From 8ac26d6c26ecbdd9c39d94ca29474fdc64f7bc80 Mon Sep 17 00:00:00 2001 From: S4NKALP Date: Tue, 21 Jul 2026 10:24:39 +0545 Subject: [PATCH] fix(mpris): guard GLib.Variant type check before get_type_string feat(bluetooth): add rfkill soft-block check and BluetoothState enum --- src/services/bluetooth.py | 44 ++++++++++++++++++++++++++++++++++++++- src/services/mpris.py | 2 +- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/services/bluetooth.py b/src/services/bluetooth.py index d86f101..3def3ce 100644 --- a/src/services/bluetooth.py +++ b/src/services/bluetooth.py @@ -1,3 +1,5 @@ +import enum +import subprocess from collections.abc import Callable from typing import Any, Concatenate, ParamSpec @@ -13,6 +15,14 @@ BLUEZ_BATTERY_IFACE = "org.bluez.Battery1" DBUS_OM_IFACE = "org.freedesktop.DBus.ObjectManager" DBUS_PROPS_IFACE = "org.freedesktop.DBus.Properties" +RFKILL_PATH = "/dev/rfkill" + + +class BluetoothState(enum.Enum): + UNAVAILABLE = "unavailable" + ACTIVE = "active" + INACTIVE = "inactive" + def _unpack_variant(v): if v is None: @@ -38,6 +48,23 @@ def _make_proxy(bus: Gio.DBusConnection, path: str, iface: str) -> Gio.DBusProxy ) +def _rfkill_soft_blocked() -> bool: + try: + result = subprocess.run( + ["rfkill", "list", "bluetooth"], + capture_output=True, + text=True, + timeout=5, + ) + return "Soft blocked: yes" in result.stdout + except FileNotFoundError: + logger.warning("[Bluetooth] rfkill not found, assuming not soft blocked") + return False + except Exception as e: + logger.warning(f"[Bluetooth] rfkill check failed: {e}") + return False + + class BluetoothDevice(Service): @Signal def changed(self) -> None: ... @@ -570,10 +597,14 @@ class BluetoothClient(Service): @Property(str, "readable") def state(self) -> str: + if not self._adapters: + return BluetoothState.UNAVAILABLE.value + if _rfkill_soft_blocked(): + return BluetoothState.INACTIVE.value for a in self._adapters.values(): if a.powered: return a.state - return "absent" if not self._adapters else "off" + return BluetoothState.INACTIVE.value @Property(bool, "read-write", default_value=False) def enabled(self) -> bool: @@ -639,8 +670,19 @@ class BluetoothClient(Service): None, ) + self._cached_rfkill_blocked = _rfkill_soft_blocked() + self._rfkill_timeout_id = GLib.timeout_add_seconds(10, self._check_rfkill) + self._populate_from_object_manager() + def _check_rfkill(self) -> bool: + blocked = _rfkill_soft_blocked() + if blocked != self._cached_rfkill_blocked: + self._cached_rfkill_blocked = blocked + self.notify("state") + self.emit("changed") + return True + def _populate_from_object_manager(self): try: result = self._om_proxy.call_sync( diff --git a/src/services/mpris.py b/src/services/mpris.py index 1b11170..6fab333 100644 --- a/src/services/mpris.py +++ b/src/services/mpris.py @@ -375,7 +375,7 @@ class PlayerService(Service): if v is not None: dur = ( v.get_uint64() / 1_000_000 - if v.get_type_string() == "t" + if isinstance(v, GLib.Variant) and v.get_type_string() == "t" else int(v) / 1_000_000 ) self._handle_artwork(metadata)