From 1cd00ae21b7f8a1adc17d61ca427519fd782c6f7 Mon Sep 17 00:00:00 2001 From: jsahn Date: Mon, 12 Oct 2026 22:01:54 +0900 Subject: [PATCH] Show Bluetooth inputs when BlueZ objects can't be read as JSON inputs() still used GetManagedObjects on BlueZ to look up names and silently dropped every Bluetooth input when an Apple device's advertisement data broke the JSON conversion. Read only Alias per device and fall back to the address. Co-Authored-By: Claude Opus 5.5 --- scripts/input_controls.py | 7 ++++--- tests/test_web.py | 8 ++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/scripts/input_controls.py b/scripts/input_controls.py index aefb238..4ca5503 100644 --- a/scripts/input_controls.py +++ b/scripts/input_controls.py @@ -114,13 +114,14 @@ def inputs(): result.append({'id':'airplay','name':'AirPlay','kind':'airplay','volume':0, 'muted':False,'playing':False,'available':False}) try: - devices = objects('org.bluez', '/') for path, props in pcms().items(): if not is_input(props): continue dev = props['Device']; key = address(dev) - d = devices.get(dev, {}).get('org.bluez.Device1', {}) + # A missing name must not hide a working input. + try: name = properties('org.bluez', dev, 'org.bluez.Device1', ['Alias'])['Alias'] + except (ValueError, KeyError, subprocess.TimeoutExpired): name = key volume, muted = decode_volume(int(props['Volume'])) - result.append({'id':key,'name':d.get('Alias', key),'kind':'bluetooth','volume':volume, + result.append({'id':key,'name':name,'kind':'bluetooth','volume':volume, 'muted':muted,'playing':bool(props.get('Running')),'available':True}) except (ValueError, KeyError, subprocess.TimeoutExpired): pass return result diff --git a/tests/test_web.py b/tests/test_web.py index 417c5a7..cc47144 100644 --- a/tests/test_web.py +++ b/tests/test_web.py @@ -58,6 +58,14 @@ class WebTests(unittest.TestCase): {'action':'discoverable','on':'yes','seconds':300},{'action':'discoverable','on':True,'seconds':True}]: with self.subTest(data=data), self.assertRaises(ValueError): w.validate_action(data) + def test_bluetooth_input_listed_even_if_name_unreadable(self): + source={'Device':'/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF','Mode':'source','Transport':'A2DP-sink','Volume':0x4040,'Running':True} + with patch.object(a,'airplay',side_effect=ValueError), patch.object(a,'pcms',return_value={'/input':source}), \ + patch.object(a,'properties',side_effect=ValueError('json')): + items=a.inputs() + self.assertEqual([(i['id'],i['name'],i['playing']) for i in items if i['kind']=='bluetooth'], + [('AA:BB:CC:DD:EE:FF','AA:BB:CC:DD:EE:FF',True)]) + def test_devices_skip_unreadable_and_tolerate_missing_uuids(self): out={'/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF':'{"type":"s","data":"AA:BB:CC:DD:EE:FF"}\n{"type":"s","data":"QCY"}\n' '{"type":"b","data":true}\n{"type":"b","data":true}\n{"type":"b","data":false}'}