From 6bcdacb108eb0ace0994f3448342e3fcf593a65a Mon Sep 17 00:00:00 2001 From: mglae Date: Thu, 19 Dec 2019 18:59:52 +0100 Subject: [PATCH 1/2] bluetooth-audio: Python 3 support / use separate dbus process --- .../service/bluetooth-audio/changelog.txt | 4 + .../addons/service/bluetooth-audio/package.mk | 2 +- .../bluetooth-audio/source/bin/dbusservice.py | 109 ++++++++++++++++ .../service/bluetooth-audio/source/default.py | 119 ++++-------------- 4 files changed, 139 insertions(+), 95 deletions(-) create mode 100755 packages/addons/service/bluetooth-audio/source/bin/dbusservice.py diff --git a/packages/addons/service/bluetooth-audio/changelog.txt b/packages/addons/service/bluetooth-audio/changelog.txt index 572ffaee81..4653d548a3 100644 --- a/packages/addons/service/bluetooth-audio/changelog.txt +++ b/packages/addons/service/bluetooth-audio/changelog.txt @@ -1,3 +1,7 @@ +102 +- Python 3 support +- Using separate dbus process + 101 - Fix log errors diff --git a/packages/addons/service/bluetooth-audio/package.mk b/packages/addons/service/bluetooth-audio/package.mk index 34a15ce135..a536e89143 100644 --- a/packages/addons/service/bluetooth-audio/package.mk +++ b/packages/addons/service/bluetooth-audio/package.mk @@ -3,7 +3,7 @@ PKG_NAME="bluetooth-audio" PKG_VERSION="0" -PKG_REV="101" +PKG_REV="102" PKG_ARCH="any" PKG_LICENSE="GPL" PKG_SITE="" diff --git a/packages/addons/service/bluetooth-audio/source/bin/dbusservice.py b/packages/addons/service/bluetooth-audio/source/bin/dbusservice.py new file mode 100755 index 0000000000..564c242e72 --- /dev/null +++ b/packages/addons/service/bluetooth-audio/source/bin/dbusservice.py @@ -0,0 +1,109 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2016-present Team LibreELEC (https://libreelec.tv) + +import sys +import dbus +import dbus.mainloop.glib +import gobject +import time + +gobject.threads_init() + +class BluetoothAudioClient(object): + + def __init__(self): + + self.devices = {} + self.signal_added = None + self.signal_removed = None + + self._setup_loop() + self._setup_bus() + self._setup_signals() + + def quit(self): + + self.signal_added.remove() + self.signal_removed.remove() + + self._loop.quit() + + def _setup_loop(self): + + self._loop = gobject.MainLoop() + + def run(self): + self._loop.run() + + def _setup_bus(self): + + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + self._bus = dbus.SystemBus() + + def _setup_signals(self): + + self.signal_added = self._bus.add_signal_receiver(handler_function=self.switch_audio, + signal_name='InterfacesAdded', + dbus_interface='org.freedesktop.DBus.ObjectManager', + bus_name='org.bluez', + member_keyword='signal') + + self.signal_removed = self._bus.add_signal_receiver(handler_function=self.switch_audio, + signal_name='InterfacesRemoved', + dbus_interface='org.freedesktop.DBus.ObjectManager', + bus_name='org.bluez', + member_keyword='signal') + + def switch_audio(self, *args, **kwargs): + + device_path = args[0] + + try: + if kwargs['signal'] == 'InterfacesAdded': + + self.devices[device_path] = { + 'Connected': '', + 'Device': '', + 'Class': '', + } + + device = self._bus.get_object('org.bluez', device_path) + device_iface = dbus.Interface(device, dbus.PROPERTIES_IFACE) + self.devices[device_path]['Device'] = device_iface.Get('org.bluez.MediaTransport1', 'Device') + + audio_device_path = self._bus.get_object('org.bluez', self.devices[device_path]['Device']) + audio_device_iface = dbus.Interface(audio_device_path, dbus.PROPERTIES_IFACE) + self.devices[device_path]['Class'] = audio_device_iface.Get('org.bluez.Device1', 'Class') + self.devices[device_path]['Connected'] = audio_device_iface.Get('org.bluez.Device1', 'Connected') + + if self.devices[device_path]['Class'] & (1 << 21): + print('bluetooth') + sys.stdout.flush() + + elif kwargs['signal'] == 'InterfacesRemoved': + if self.devices[device_path]['Device'] is not None and self.devices[device_path]['Class'] & (1 << 21): + audio_device_path = self._bus.get_object('org.bluez', self.devices[device_path]['Device']) + audio_device_iface = dbus.Interface(audio_device_path, dbus.PROPERTIES_IFACE) + self.devices[device_path]['Connected'] = audio_device_iface.Get('org.bluez.Device1', 'Connected') + + while self.devices[device_path]['Connected']: + self.devices[device_path]['Connected'] = audio_device_iface.Get('org.bluez.Device1', 'Connected') + time.sleep(0.1) + + for path in self.devices: + if self.devices[path]['Connected'] and self.devices[path]['Class'] & (1 << 21): + return + + print('default') + sys.stdout.flush() + + except (TypeError, KeyError, dbus.exceptions.DBusException) as e: + print('%s: ' % str(e), file=sys.stderr) + + +client = BluetoothAudioClient() + +client.run() + +del BluetoothAudioClient diff --git a/packages/addons/service/bluetooth-audio/source/default.py b/packages/addons/service/bluetooth-audio/source/default.py index d99403b7b2..75fef6d9dc 100644 --- a/packages/addons/service/bluetooth-audio/source/default.py +++ b/packages/addons/service/bluetooth-audio/source/default.py @@ -1,19 +1,15 @@ # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2016-present Team LibreELEC (https://libreelec.tv) -import dbus -import dbus.mainloop.glib -import gobject import json +import subprocess import threading -import time import xbmc import xbmcaddon __addon__ = xbmcaddon.Addon() __addonid__ = __addon__.getAddonInfo('id') - -gobject.threads_init() +__addonpath__ = xbmc.translatePath(xbmcaddon.Addon().getAddonInfo('path')) class KodiFunctions(object): @@ -63,103 +59,38 @@ class BluetoothAudioClient(object): xbmc.log('%s: starting add-on' % __addonid__, xbmc.LOGINFO) - self.devices = {} - self.signal_added = None - self.signal_removed = None - self.kodi = KodiFunctions() + self.path = __addonpath__ + '/bin/dbusservice.py' + + self.service = subprocess.Popen([self.path], stdout=subprocess.PIPE) + + self._thread = threading.Thread(target=self.loop) + self._thread.start() + + + def loop(self): + + while True: + line = self.service.stdout.readline() + if line == b'': + break + if line == b'bluetooth\n': + self.kodi.select_pulse() + continue + if line == b'default\n': + self.kodi.select_default() + continue + xbmc.log('%s: unexpected input: %s' % (__addonid__, line), xbmc.LOGERROR) - self._setup_loop() - self._setup_bus() - self._setup_signals() def quit(self): xbmc.log('%s: stopping add-on' % __addonid__, xbmc.LOGINFO) + self.service.terminate() + self._thread.join() self.kodi.select_default() - self.signal_added.remove() - self.signal_removed.remove() - - self._loop.quit() - - def _setup_loop(self): - - self._loop = gobject.MainLoop() - - self._thread = threading.Thread(target=self._loop.run) - self._thread.start() - - def _setup_bus(self): - - dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) - self._bus = dbus.SystemBus() - - def _setup_signals(self): - - self.signal_added = self._bus.add_signal_receiver(handler_function=self.switch_audio, - signal_name='InterfacesAdded', - dbus_interface='org.freedesktop.DBus.ObjectManager', - bus_name='org.bluez', - member_keyword='signal') - - self.signal_removed = self._bus.add_signal_receiver(handler_function=self.switch_audio, - signal_name='InterfacesRemoved', - dbus_interface='org.freedesktop.DBus.ObjectManager', - bus_name='org.bluez', - member_keyword='signal') - - def switch_audio(self, *args, **kwargs): - - device_path = args[0] - - try: - if kwargs['signal'] == 'InterfacesAdded': - - self.devices[device_path] = { - 'Connected': '', - 'Device': '', - 'Class': '', - } - - device = self._bus.get_object('org.bluez', device_path) - device_iface = dbus.Interface(device, dbus.PROPERTIES_IFACE) - self.devices[device_path]['Device'] = device_iface.Get('org.bluez.MediaTransport1', 'Device') - - audio_device_path = self._bus.get_object('org.bluez', self.devices[device_path]['Device']) - audio_device_iface = dbus.Interface(audio_device_path, dbus.PROPERTIES_IFACE) - self.devices[device_path]['Class'] = audio_device_iface.Get('org.bluez.Device1', 'Class') - self.devices[device_path]['Connected'] = audio_device_iface.Get('org.bluez.Device1', 'Connected') - - if self.devices[device_path]['Class'] & (1 << 21): - xbmc.log('%s: bluetooth audio device connected' % __addonid__, xbmc.LOGINFO) - xbmc.log('%s: switching to bluetooth audio device' % __addonid__, xbmc.LOGINFO) - self.kodi.select_pulse() - - elif kwargs['signal'] == 'InterfacesRemoved': - if self.devices[device_path]['Device'] is not None and self.devices[device_path]['Class'] & (1 << 21): - audio_device_path = self._bus.get_object('org.bluez', self.devices[device_path]['Device']) - audio_device_iface = dbus.Interface(audio_device_path, dbus.PROPERTIES_IFACE) - self.devices[device_path]['Connected'] = audio_device_iface.Get('org.bluez.Device1', 'Connected') - - while self.devices[device_path]['Connected']: - self.devices[device_path]['Connected'] = audio_device_iface.Get('org.bluez.Device1', 'Connected') - time.sleep(0.1) - - xbmc.log('%s: bluetooth audio device disconnected' % __addonid__, xbmc.LOGINFO) - xbmc.log('%s: checking for other connected devices' % __addonid__, xbmc.LOGINFO) - - for path in self.devices: - if self.devices[path]['Connected'] and self.devices[path]['Class'] & (1 << 21): - xbmc.log('%s: found connected bluetooth audio device' % __addonid__, xbmc.LOGINFO) - return - - xbmc.log('%s: switching to default audio device' % __addonid__, xbmc.LOGINFO) - self.kodi.select_default() - - except (TypeError, KeyError, dbus.exceptions.DBusException) as e: - xbmc.log('%s: ' % __addonid__ + unicode(e), xbmc.LOGERROR) class BluetoothMonitor(xbmc.Monitor): From 5f281c01da690a1e009186f996c396bf5e34303d Mon Sep 17 00:00:00 2001 From: mglae Date: Sat, 3 Oct 2020 15:47:47 +0200 Subject: [PATCH 2/2] bluetooth-audio: use dbussy/ravel --- .../bluetooth-audio/source/bin/dbusservice.py | 111 ++++++------------ .../service/bluetooth-audio/source/default.py | 9 +- 2 files changed, 45 insertions(+), 75 deletions(-) diff --git a/packages/addons/service/bluetooth-audio/source/bin/dbusservice.py b/packages/addons/service/bluetooth-audio/source/bin/dbusservice.py index 564c242e72..3b9035fe47 100755 --- a/packages/addons/service/bluetooth-audio/source/bin/dbusservice.py +++ b/packages/addons/service/bluetooth-audio/source/bin/dbusservice.py @@ -3,12 +3,10 @@ # Copyright (C) 2016-present Team LibreELEC (https://libreelec.tv) import sys -import dbus -import dbus.mainloop.glib -import gobject import time -gobject.threads_init() +import asyncio +import ravel class BluetoothAudioClient(object): @@ -19,87 +17,54 @@ class BluetoothAudioClient(object): self.signal_removed = None self._setup_loop() - self._setup_bus() - self._setup_signals() - - def quit(self): - - self.signal_added.remove() - self.signal_removed.remove() - - self._loop.quit() def _setup_loop(self): - self._loop = gobject.MainLoop() + self._loop = asyncio.new_event_loop() + + self.bus = ravel.system_bus() + self.bus.attach_asyncio(self._loop) + self.bus.listen_objects_added(self.object_added) + self.bus.listen_objects_removed(self.object_removed) def run(self): - self._loop.run() + self._loop.run_forever() - def _setup_bus(self): + @ravel.signal(name = "InterfacesAdded", in_signature = "oa{sa{sv}}", + arg_keys = ("device_path", "args")) + def object_added(self, device_path, args) : + if 'org.bluez.MediaTransport1' in args: + self.devices[device_path] = { + 'Connected': '', + 'Device': str(args['org.bluez.MediaTransport1']['Device'][1]), + 'Class': '', + } - dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) - self._bus = dbus.SystemBus() + audio_device_iface = self.bus['org.bluez'][self.devices[device_path]['Device']].get_interface('org.bluez.Device1') + self.devices[device_path]['Class'] = audio_device_iface.Class + self.devices[device_path]['Connected'] = audio_device_iface.Connected - def _setup_signals(self): + if self.devices[device_path]['Class'] & (1 << 21): + print('bluetooth') + sys.stdout.flush() - self.signal_added = self._bus.add_signal_receiver(handler_function=self.switch_audio, - signal_name='InterfacesAdded', - dbus_interface='org.freedesktop.DBus.ObjectManager', - bus_name='org.bluez', - member_keyword='signal') + @ravel.signal(name = "InterfacesRemoved", in_signature = "oas", + arg_keys = ("device_path", "args")) + def object_removed(self, device_path, args) : + if device_path in self.devices and self.devices[device_path]['Class'] & (1 << 21): + audio_device_iface = self.bus['org.bluez'][self.devices[device_path]['Device']].get_interface('org.bluez.Device1') + self.devices[device_path]['Connected'] = audio_device_iface.Connected - self.signal_removed = self._bus.add_signal_receiver(handler_function=self.switch_audio, - signal_name='InterfacesRemoved', - dbus_interface='org.freedesktop.DBus.ObjectManager', - bus_name='org.bluez', - member_keyword='signal') + while self.devices[device_path]['Connected']: + self.devices[device_path]['Connected'] = audio_device_iface.Connected + time.sleep(0.1) - def switch_audio(self, *args, **kwargs): + for path in self.devices: + if self.devices[path]['Connected'] and self.devices[path]['Class'] & (1 << 21): + return - device_path = args[0] - - try: - if kwargs['signal'] == 'InterfacesAdded': - - self.devices[device_path] = { - 'Connected': '', - 'Device': '', - 'Class': '', - } - - device = self._bus.get_object('org.bluez', device_path) - device_iface = dbus.Interface(device, dbus.PROPERTIES_IFACE) - self.devices[device_path]['Device'] = device_iface.Get('org.bluez.MediaTransport1', 'Device') - - audio_device_path = self._bus.get_object('org.bluez', self.devices[device_path]['Device']) - audio_device_iface = dbus.Interface(audio_device_path, dbus.PROPERTIES_IFACE) - self.devices[device_path]['Class'] = audio_device_iface.Get('org.bluez.Device1', 'Class') - self.devices[device_path]['Connected'] = audio_device_iface.Get('org.bluez.Device1', 'Connected') - - if self.devices[device_path]['Class'] & (1 << 21): - print('bluetooth') - sys.stdout.flush() - - elif kwargs['signal'] == 'InterfacesRemoved': - if self.devices[device_path]['Device'] is not None and self.devices[device_path]['Class'] & (1 << 21): - audio_device_path = self._bus.get_object('org.bluez', self.devices[device_path]['Device']) - audio_device_iface = dbus.Interface(audio_device_path, dbus.PROPERTIES_IFACE) - self.devices[device_path]['Connected'] = audio_device_iface.Get('org.bluez.Device1', 'Connected') - - while self.devices[device_path]['Connected']: - self.devices[device_path]['Connected'] = audio_device_iface.Get('org.bluez.Device1', 'Connected') - time.sleep(0.1) - - for path in self.devices: - if self.devices[path]['Connected'] and self.devices[path]['Class'] & (1 << 21): - return - - print('default') - sys.stdout.flush() - - except (TypeError, KeyError, dbus.exceptions.DBusException) as e: - print('%s: ' % str(e), file=sys.stderr) + print('default') + sys.stdout.flush() client = BluetoothAudioClient() diff --git a/packages/addons/service/bluetooth-audio/source/default.py b/packages/addons/service/bluetooth-audio/source/default.py index 75fef6d9dc..f45527591d 100644 --- a/packages/addons/service/bluetooth-audio/source/default.py +++ b/packages/addons/service/bluetooth-audio/source/default.py @@ -5,11 +5,12 @@ import json import subprocess import threading import xbmc +import xbmcvfs import xbmcaddon __addon__ = xbmcaddon.Addon() __addonid__ = __addon__.getAddonInfo('id') -__addonpath__ = xbmc.translatePath(xbmcaddon.Addon().getAddonInfo('path')) +__addonpath__ = xbmcvfs.translatePath(xbmcaddon.Addon().getAddonInfo('path')) class KodiFunctions(object): @@ -41,6 +42,7 @@ class KodiFunctions(object): self.audiodevice = __addon__.getSetting('audiodevice') self.pulsedevice = 'PULSE:Default' + xbmc.log('%s: setting default audio device "%s" on start' % (__addonid__, self.audiodevice), xbmc.LOGINFO) self.select_default() def select_default(self): @@ -60,7 +62,7 @@ class BluetoothAudioClient(object): xbmc.log('%s: starting add-on' % __addonid__, xbmc.LOGINFO) self.kodi = KodiFunctions() - self.path = __addonpath__ + '/bin/dbusservice.py' + self.path = __addonpath__ + 'bin/dbusservice.py' self.service = subprocess.Popen([self.path], stdout=subprocess.PIPE) @@ -75,9 +77,11 @@ class BluetoothAudioClient(object): if line == b'': break if line == b'bluetooth\n': + xbmc.log('%s: switching to bluetooth audio device' % __addonid__, xbmc.LOGINFO) self.kodi.select_pulse() continue if line == b'default\n': + xbmc.log('%s: switching to default audio device' % __addonid__, xbmc.LOGINFO) self.kodi.select_default() continue xbmc.log('%s: unexpected input: %s' % (__addonid__, line), xbmc.LOGERROR) @@ -89,6 +93,7 @@ class BluetoothAudioClient(object): self.service.terminate() self._thread.join() + del self.service self.kodi.select_default()