diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index cbd27a5..888978f 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -1,40 +1,41 @@ add_subdirectory(fakebluez) include(ECMMarkAsTest) set(bluezqt_autotests_SRCS autotests.cpp ) qt5_add_dbus_interface(bluezqt_autotests_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.freedesktop.DBus.Properties.xml dbusproperties_tst) qt5_add_dbus_interface(bluezqt_autotests_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.bluez.Adapter1.xml bluezadapter1_tst) qt5_add_dbus_interface(bluezqt_autotests_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.bluez.Device1.xml bluezdevice1_tst) qt5_add_dbus_interface(bluezqt_autotests_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.bluez.MediaPlayer1.xml bluezmediaplayer1_tst) qt5_add_dbus_interface(bluezqt_autotests_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.bluez.Input1.xml bluezinput1_tst) macro(bluezqt_tests) foreach(_testname ${ARGN}) add_executable(${_testname} ${_testname}.cpp ${bluezqt_autotests_SRCS}) target_link_libraries(${_testname} Qt5::DBus Qt5::Test KF5BluezQt) add_test(NAME bluezqt-${_testname} COMMAND ${_testname}) ecm_mark_as_test(${_testname}) set_tests_properties(bluezqt-${_testname} PROPERTIES RUN_SERIAL TRUE) endforeach(_testname) endmacro() bluezqt_tests( managertest agentmanagertest obexmanagertest adaptertest devicetest inputtest mediaplayertest jobstest + mediatest ) if(Qt5Qml_FOUND AND Qt5QuickTest_FOUND) bluezqt_tests(qmltests) target_link_libraries(qmltests Qt5::Qml Qt5::QuickTest) add_definitions(-DBLUEZQT_QML_IMPORT_PATH="${CMAKE_CURRENT_BINARY_DIR}/../src/imports") endif() diff --git a/autotests/fakebluez/CMakeLists.txt b/autotests/fakebluez/CMakeLists.txt index 5e29d57..ae8049f 100644 --- a/autotests/fakebluez/CMakeLists.txt +++ b/autotests/fakebluez/CMakeLists.txt @@ -1,23 +1,24 @@ set(fakebluez_SRCS main.cpp fakebluez.cpp testinterface.cpp objectmanager.cpp object.cpp agentmanager.cpp profilemanager.cpp devicemanager.cpp adapterinterface.cpp deviceinterface.cpp inputinterface.cpp mediaplayerinterface.cpp obexagentmanager.cpp obexclient.cpp + media.cpp ) add_executable(fakebluez ${fakebluez_SRCS}) target_link_libraries(fakebluez Qt5::Core Qt5::DBus ) diff --git a/autotests/fakebluez/fakebluez.cpp b/autotests/fakebluez/fakebluez.cpp index dc7c487..f415424 100644 --- a/autotests/fakebluez/fakebluez.cpp +++ b/autotests/fakebluez/fakebluez.cpp @@ -1,216 +1,228 @@ /* * Copyright (C) 2014-2015 David Rosca * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "fakebluez.h" #include "testinterface.h" #include "objectmanager.h" #include "agentmanager.h" #include "profilemanager.h" #include "devicemanager.h" +#include "media.h" #include "obexagentmanager.h" #include "obexclient.h" #include #include #include // ObexObject class ObexObject : public QObject { public: explicit ObexObject(QObject *parent = nullptr) : QObject(parent) { QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/bluez/obex"), this); } }; // FakeBluez FakeBluez::FakeBluez(QObject *parent) : QObject(parent) , m_testInterface(new TestInterface(this)) , m_objectManager(nullptr) , m_agentManager(nullptr) , m_profileManager(nullptr) , m_deviceManager(nullptr) + , m_media(nullptr) , m_obexObject(nullptr) , m_obexAgentManager(nullptr) , m_obexClient(nullptr) { QDBusConnection::sessionBus().registerObject(QStringLiteral("/"), this); } void FakeBluez::runTest(const QString &testName) { if (testName == QLatin1String("bluez-not-exporting-interfaces")) { runBluezNotExportingInterfacesTest(); } else if (testName == QLatin1String("bluez-empty-managed-objects")) { runBluezEmptyManagedObjectsTest(); } else if (testName == QLatin1String("bluez-no-adapters")) { runBluezNoAdaptersTest(); } else if (testName == QLatin1String("bluez-standard")) { runBluezStandardTest(); } if (testName == QLatin1String("obex-not-exporting-interfaces")) { runObexNotExportingInterfacesTest(); } else if (testName == QLatin1String("obex-empty-managed-objects")) { runObexEmptyManagedObjectsTest(); } else if (testName == QLatin1String("obex-no-client")) { runObexNoClientTest(); } else if (testName == QLatin1String("obex-no-agentmanager")) { runObexNoAgentManagerTest(); } else if (testName == QLatin1String("obex-standard")) { runObexStandardTest(); } if (!QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.bluezqt.fakebluez"))) { qWarning() << "Cannot register org.kde.bluezqt.fakebluez service!"; } } void FakeBluez::runAction(const QString &object, const QString &actionName, const QVariantMap &properties) { m_actionObject = object; m_actionName = actionName; m_actionProperties = properties; QTimer::singleShot(0, this, SLOT(doRunAction())); } void FakeBluez::doRunAction() { if (m_actionObject == QLatin1String("agentmanager")) { m_agentManager->runAction(m_actionName, m_actionProperties); } else if (m_actionObject == QLatin1String("devicemanager")) { m_deviceManager->runAction(m_actionName, m_actionProperties); + } else if (m_actionObject == QLatin1String("media")) { + m_media->runAction(m_actionName, m_actionProperties); } QTimer::singleShot(0, m_testInterface, SLOT(emitActionFinished())); } void FakeBluez::clear() { // Everything is parented to ObjectManager delete m_objectManager; m_objectManager = nullptr; } void FakeBluez::createObjectManager() { m_objectManager = new ObjectManager(this); } void FakeBluez::createAgentManager() { m_agentManager = new AgentManager(m_objectManager); m_objectManager->addObject(m_agentManager); } void FakeBluez::createProfileManager() { m_profileManager = new ProfileManager(m_objectManager); m_objectManager->addObject(m_profileManager); } void FakeBluez::createDeviceManager() { m_deviceManager = new DeviceManager(m_objectManager); } +void FakeBluez::createMedia() +{ + m_media = new Media(m_objectManager); + m_objectManager->addObject(m_media); +} + void FakeBluez::createObexObjectManager() { createObjectManager(); m_obexObject = new ObexObject(this); m_objectManager->addAutoDeleteObject(m_obexObject); } void FakeBluez::createObexAgentManager() { m_obexAgentManager = new ObexAgentManager(m_obexObject); m_objectManager->addObject(m_obexAgentManager); } void FakeBluez::createObexClient() { m_obexClient = new ObexClient(m_obexObject); m_objectManager->addObject(m_obexClient); } void FakeBluez::runBluezNotExportingInterfacesTest() { clear(); } void FakeBluez::runBluezEmptyManagedObjectsTest() { clear(); createObjectManager(); } void FakeBluez::runBluezNoAdaptersTest() { clear(); createObjectManager(); createAgentManager(); createProfileManager(); + createMedia(); } void FakeBluez::runBluezStandardTest() { clear(); createObjectManager(); createDeviceManager(); createAgentManager(); createProfileManager(); + createMedia(); } void FakeBluez::runObexNotExportingInterfacesTest() { clear(); } void FakeBluez::runObexEmptyManagedObjectsTest() { clear(); createObexObjectManager(); } void FakeBluez::runObexNoClientTest() { clear(); createObexObjectManager(); createObexAgentManager(); } void FakeBluez::runObexNoAgentManagerTest() { clear(); createObexObjectManager(); createObexClient(); } void FakeBluez::runObexStandardTest() { clear(); createObexObjectManager(); createObexClient(); createObexAgentManager(); } diff --git a/autotests/fakebluez/fakebluez.h b/autotests/fakebluez/fakebluez.h index b6e53c6..8c76b2a 100644 --- a/autotests/fakebluez/fakebluez.h +++ b/autotests/fakebluez/fakebluez.h @@ -1,85 +1,88 @@ /* * Copyright (C) 2014-2015 David Rosca * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef FAKEBLUEZ_H #define FAKEBLUEZ_H #include #include class TestInterface; class ObjectManager; class AgentManager; class ProfileManager; class DeviceManager; +class Media; class ObexObject; class ObexAgentManager; class ObexClient; class FakeBluez : public QObject { Q_OBJECT public: explicit FakeBluez(QObject *parent = nullptr); void runTest(const QString &testName); void runAction(const QString &object, const QString &actionName, const QVariantMap &properties); private Q_SLOTS: void doRunAction(); private: void clear(); void createObjectManager(); void createAgentManager(); void createProfileManager(); void createDeviceManager(); + void createMedia(); void createObexObjectManager(); void createObexAgentManager(); void createObexClient(); void runBluezNotExportingInterfacesTest(); void runBluezEmptyManagedObjectsTest(); void runBluezNoAdaptersTest(); void runBluezStandardTest(); void runObexNotExportingInterfacesTest(); void runObexEmptyManagedObjectsTest(); void runObexNoClientTest(); void runObexNoAgentManagerTest(); void runObexStandardTest(); TestInterface *m_testInterface; ObjectManager *m_objectManager; AgentManager *m_agentManager; ProfileManager *m_profileManager; DeviceManager *m_deviceManager; + Media *m_media; ObexObject *m_obexObject; ObexAgentManager *m_obexAgentManager; ObexClient *m_obexClient; QString m_actionObject; QString m_actionName; QVariantMap m_actionProperties; }; #endif // FAKEBLUEZ_H diff --git a/autotests/fakebluez/media.cpp b/autotests/fakebluez/media.cpp new file mode 100644 index 0000000..6fd386b --- /dev/null +++ b/autotests/fakebluez/media.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "media.h" + +#include +#include +#include +#include + +Media::Media(QObject *parent) + : QDBusAbstractAdaptor(parent) +{ + setName(QStringLiteral("org.bluez.Media1")); + setPath(QDBusObjectPath(QStringLiteral("/org/bluez"))); +} + +void Media::runAction(const QString &actionName, const QVariantMap &properties) +{ + if (actionName == QLatin1String("set-configuration")) { + runSetConfigurationAction(properties); + } else if (actionName == QLatin1String("select-configuration")) { + runSelectConfigurationAction(properties); + } else if (actionName == QLatin1String("clear-configuration")) { + runClearConfigurationAction(properties); + } else if (actionName == QLatin1String("release")) { + runReleaseAction(); + } +} + +void Media::RegisterEndpoint(const QDBusObjectPath &path, const QVariantMap &properties, const QDBusMessage &msg) +{ + m_endpoint = path; + m_service = msg.service(); + m_properties = properties; +} + +void Media::UnregisterEndpoint(const QDBusObjectPath &path, const QDBusMessage &msg) +{ + if (m_endpoint == path && m_service == msg.service()) { + m_endpoint = QDBusObjectPath(); + m_service.clear(); + m_properties.clear(); + } +} + +void Media::runSetConfigurationAction(const QVariantMap &properties) +{ + QDBusMessage call = QDBusMessage::createMethodCall(m_service, + m_endpoint.path(), + QStringLiteral("org.bluez.MediaEndpoint1"), + QStringLiteral("SetConfiguration")); + call << QVariant::fromValue(properties.value(QStringLiteral("Transport")).value()); + call << properties.value(QStringLiteral("Properties")); + QDBusConnection::sessionBus().asyncCall(call); +} + +void Media::runSelectConfigurationAction(const QVariantMap &properties) +{ + QDBusMessage call = QDBusMessage::createMethodCall(m_service, + m_endpoint.path(), + QStringLiteral("org.bluez.MediaEndpoint1"), + QStringLiteral("SelectConfiguration")); + call << properties.value(QStringLiteral("Capabilities")); + QDBusConnection::sessionBus().asyncCall(call); +} + +void Media::runClearConfigurationAction(const QVariantMap &properties) +{ + QDBusMessage call = QDBusMessage::createMethodCall(m_service, + m_endpoint.path(), + QStringLiteral("org.bluez.MediaEndpoint1"), + QStringLiteral("ClearConfiguration")); + call << QVariant::fromValue(properties.value(QStringLiteral("Transport")).value()); + QDBusConnection::sessionBus().asyncCall(call); +} + +void Media::runReleaseAction() +{ + QDBusMessage call = QDBusMessage::createMethodCall(m_service, + m_endpoint.path(), + QStringLiteral("org.bluez.MediaEndpoint1"), + QStringLiteral("Release")); + QDBusConnection::sessionBus().asyncCall(call); +} diff --git a/autotests/fakebluez/media.h b/autotests/fakebluez/media.h new file mode 100644 index 0000000..52d7dbc --- /dev/null +++ b/autotests/fakebluez/media.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef MEDIA_H +#define MEDIA_H + +#include "object.h" + +#include + +class QDBusMessage; + +class Media : public QDBusAbstractAdaptor, public Object +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.bluez.Media1") + +public: + explicit Media(QObject *parent = nullptr); + + void runAction(const QString &actionName, const QVariantMap &properties); + +public Q_SLOTS: + void RegisterEndpoint(const QDBusObjectPath &path, const QVariantMap &properties, const QDBusMessage &msg); + void UnregisterEndpoint(const QDBusObjectPath &path, const QDBusMessage &msg); + +private: + void runSetConfigurationAction(const QVariantMap &properties); + void runSelectConfigurationAction(const QVariantMap &properties); + void runClearConfigurationAction(const QVariantMap &properties); + void runReleaseAction(); + + QDBusObjectPath m_endpoint; + QString m_service; + QVariantMap m_properties; +}; + +#endif // MEDIA_H diff --git a/autotests/mediatest.cpp b/autotests/mediatest.cpp new file mode 100644 index 0000000..8cb2d4d --- /dev/null +++ b/autotests/mediatest.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "mediatest.h" +#include "a2dp-codecs.h" +#include "autotests.h" +#include "initmanagerjob.h" +#include "manager.h" +#include "media.h" +#include "mediaendpoint.h" +#include "pendingcall.h" + +#include +#include +#include + +namespace BluezQt +{ +extern void bluezqt_initFakeBluezTestRun(); +} + +using namespace BluezQt; + +void TestEndpoint::release() +{ + m_releaseCalled = true; +} + +void MediaTest::initTestCase() +{ + bluezqt_initFakeBluezTestRun(); + FakeBluez::start(); + FakeBluez::runTest(QStringLiteral("bluez-standard")); + + Manager *manager = new Manager(this); + InitManagerJob *initJob = manager->init(); + initJob->exec(); + QVERIFY(!initJob->error()); + QVERIFY(manager->media()); + + m_endpoint = new TestEndpoint({MediaEndpoint::Role::AudioSink, MediaEndpoint::Codec::Sbc}, this); + manager->media()->registerEndpoint(m_endpoint)->waitForFinished(); +} + +void MediaTest::cleanupTestCase() +{ + FakeBluez::stop(); +} + +void MediaTest::setConfigurationTest() +{ + QSignalSpy endpointSpy(m_endpoint, SIGNAL(configurationSet(QString,QVariantMap))); + + QVariantMap props; + props.insert(QStringLiteral("Key"), QVariant::fromValue(int(123))); + + QVariantMap params; + params.insert(QStringLiteral("Transport"), QVariant::fromValue(m_endpoint->objectPath())); + params.insert(QStringLiteral("Properties"), props); + FakeBluez::runAction(QStringLiteral("media"), QStringLiteral("set-configuration"), params); + endpointSpy.wait(); + auto args = endpointSpy.takeFirst(); + QCOMPARE(args.at(0).toString(), m_endpoint->objectPath().path()); + QCOMPARE(args.at(1).value(), props); +} + +void MediaTest::selectConfigurationTest() +{ + QSignalSpy endpointSpy(m_endpoint, SIGNAL(configurationSelected(QByteArray,QByteArray))); + + a2dp_sbc_t sbcConfiguration; + sbcConfiguration.frequency = SBC_SAMPLING_FREQ_44100; + sbcConfiguration.channel_mode = SBC_CHANNEL_MODE_STEREO; + sbcConfiguration.block_length = SBC_BLOCK_LENGTH_16; + sbcConfiguration.subbands = SBC_SUBBANDS_8; + sbcConfiguration.allocation_method = SBC_ALLOCATION_LOUDNESS; + sbcConfiguration.min_bitpool = 2; + sbcConfiguration.max_bitpool = 53; + + QVariantMap params; + params.insert(QStringLiteral("Capabilities"), QByteArray(reinterpret_cast(&sbcCapabilities), sizeof(sbcCapabilities))); + FakeBluez::runAction(QStringLiteral("media"), QStringLiteral("select-configuration"), params); + endpointSpy.wait(); + auto args = endpointSpy.takeFirst(); + QCOMPARE(args.at(0).toByteArray(), QByteArray(reinterpret_cast(&sbcCapabilities), sizeof(sbcCapabilities))); + QCOMPARE(args.at(1).toByteArray(), QByteArray(reinterpret_cast(&sbcConfiguration), sizeof(sbcConfiguration))); + + params.insert(QStringLiteral("Capabilities"), QByteArray()); + FakeBluez::runAction(QStringLiteral("media"), QStringLiteral("select-configuration"), params); + endpointSpy.wait(); + args = endpointSpy.takeFirst(); + QCOMPARE(args.at(0).toByteArray(), QByteArray()); + QCOMPARE(args.at(1).toByteArray(), QByteArray()); +} + +void MediaTest::clearConfigurationTest() +{ + QSignalSpy endpointSpy(m_endpoint, SIGNAL(configurationCleared(QString))); + + QVariantMap params; + params.insert(QStringLiteral("Transport"), QVariant::fromValue(m_endpoint->objectPath())); + FakeBluez::runAction(QStringLiteral("media"), QStringLiteral("clear-configuration"), params); + endpointSpy.wait(); + auto args = endpointSpy.takeFirst(); + QCOMPARE(args.at(0).toString(), m_endpoint->objectPath().path()); +} + +void MediaTest::releaseTest() +{ + QCOMPARE(m_endpoint->m_releaseCalled, false); + + FakeBluez::runAction(QStringLiteral("media"), QStringLiteral("release")); + + QTRY_COMPARE(m_endpoint->m_releaseCalled, true); +} + +QTEST_MAIN(MediaTest) diff --git a/autotests/mediatest.h b/autotests/mediatest.h new file mode 100644 index 0000000..8912d5c --- /dev/null +++ b/autotests/mediatest.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef MEDIATEST_H +#define MEDIATEST_H + +#include "mediaendpoint.h" + +class TestEndpoint : public BluezQt::MediaEndpoint +{ + Q_OBJECT + +public: + using BluezQt::MediaEndpoint::MediaEndpoint; + void release() override; + + // release + bool m_releaseCalled = false; +}; + +class MediaTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + + void setConfigurationTest(); + void selectConfigurationTest(); + void clearConfigurationTest(); + void releaseTest(); + +private: + TestEndpoint* m_endpoint; +}; + +#endif // MEDIATEST_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 88ecf38..2a1bd58 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,155 +1,162 @@ set(bluezqt_SRCS + a2dp-codecs.c manager.cpp manager_p.cpp adapter.cpp adapter_p.cpp device.cpp device_p.cpp input.cpp + media.cpp + mediaendpoint.cpp + mediaendpoint_p.cpp + mediaendpointadaptor.cpp mediaplayer.cpp mediaplayer_p.cpp mediaplayertrack.cpp devicesmodel.cpp job.cpp initmanagerjob.cpp initobexmanagerjob.cpp utils.cpp agent.cpp agentadaptor.cpp profile.cpp profileadaptor.cpp pendingcall.cpp request.cpp rfkill.cpp obexmanager.cpp obexmanager_p.cpp obexagent.cpp obexagentadaptor.cpp obextransfer.cpp obexsession.cpp obexobjectpush.cpp obexfiletransfer.cpp obexfiletransferentry.cpp ) ecm_qt_declare_logging_category(bluezqt_SRCS HEADER debug.h IDENTIFIER BLUEZQT CATEGORY_NAME org.kde.bluez) set(dbusobjectmanager_xml ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.freedesktop.DBus.ObjectManager.xml) set_source_files_properties(${dbusobjectmanager_xml} PROPERTIES INCLUDE "bluezqt_dbustypes.h") qt5_add_dbus_interface(bluezqt_SRCS ${dbusobjectmanager_xml} dbusobjectmanager) set(obexfiletransfer1_xml ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.bluez.obex.FileTransfer1.xml) set_source_files_properties(${obexfiletransfer1_xml} PROPERTIES INCLUDE "bluezqt_dbustypes.h") qt5_add_dbus_interface(bluezqt_SRCS ${obexfiletransfer1_xml} obexfiletransfer1) qt5_add_dbus_interface(bluezqt_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.freedesktop.DBus.Properties.xml dbusproperties) qt5_add_dbus_interface(bluezqt_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.bluez.Adapter1.xml bluezadapter1) qt5_add_dbus_interface(bluezqt_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.bluez.AgentManager1.xml bluezagentmanager1) qt5_add_dbus_interface(bluezqt_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.bluez.ProfileManager1.xml bluezprofilemanager1) qt5_add_dbus_interface(bluezqt_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.bluez.Device1.xml bluezdevice1) +qt5_add_dbus_interface(bluezqt_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.bluez.Media1.xml bluezmedia1) +qt5_add_dbus_interface(bluezqt_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.bluez.MediaEndpoint1.xml bluezmediaendpoint1) qt5_add_dbus_interface(bluezqt_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.bluez.MediaPlayer1.xml bluezmediaplayer1) qt5_add_dbus_interface(bluezqt_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.bluez.obex.AgentManager1.xml obexagentmanager1) qt5_add_dbus_interface(bluezqt_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.bluez.obex.Client1.xml obexclient1) qt5_add_dbus_interface(bluezqt_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.bluez.obex.Transfer1.xml obextransfer1) qt5_add_dbus_interface(bluezqt_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.bluez.obex.Session1.xml obexsession1) qt5_add_dbus_interface(bluezqt_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.bluez.obex.ObjectPush1.xml obexobjectpush1) add_library(KF5BluezQt ${bluezqt_SRCS}) generate_export_header(KF5BluezQt BASE_NAME BluezQt) add_library(KF5::BluezQt ALIAS KF5BluezQt) target_include_directories(KF5BluezQt INTERFACE "$") target_link_libraries(KF5BluezQt PUBLIC Qt5::Core PRIVATE Qt5::DBus Qt5::Network ) set_target_properties(KF5BluezQt PROPERTIES VERSION ${BLUEZQT_VERSION_STRING} SOVERSION ${BLUEZQT_SOVERSION} EXPORT_NAME "BluezQt" ) ecm_generate_headers(BluezQt_CamelCase_HEADERS HEADER_NAMES Types Manager Adapter Device Input MediaPlayer MediaPlayerTrack DevicesModel Job InitManagerJob InitObexManagerJob Services Agent Profile PendingCall Request ObexManager ObexAgent ObexTransfer ObexSession ObexObjectPush ObexFileTransfer ObexFileTransferEntry REQUIRED_HEADERS BluezQt_HEADERS PREFIX BluezQt ) # Install files install(TARGETS KF5BluezQt EXPORT KF5BluezQtTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES ${BluezQt_CamelCase_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/BluezQt/BluezQt COMPONENT Devel) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/bluezqt_export.h ${BluezQt_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/BluezQt/bluezqt COMPONENT Devel) if(BUILD_QCH) ecm_add_qch( KF5BluezQt_QCH NAME BluezQt BASE_NAME KF5BluezQt VERSION ${KF5_VERSION} ORG_DOMAIN org.kde SOURCES # using only public headers, to cover only public API ${BluezQt_HEADERS} MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md" LINK_QCHS Qt5Core_QCH BLANK_MACROS BLUEZQT_EXPORT BLUEZQT_DEPRECATED BLUEZQT_DEPRECATED_EXPORT TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} COMPONENT Devel ) endif() include(ECMGeneratePriFile) ecm_generate_pri_file( BASE_NAME BluezQt LIB_NAME KF5BluezQt DEPS "core" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/BluezQt ) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) diff --git a/src/a2dp-codecs.c b/src/a2dp-codecs.c new file mode 100644 index 0000000..e27cd03 --- /dev/null +++ b/src/a2dp-codecs.c @@ -0,0 +1,77 @@ +/* + * BluezQt - Asynchronous Bluez wrapper library + * + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "a2dp-codecs.h" + +/* Currently our capabilites are limited to the most common use cases. + iOS has a fixed stream configuration anyway (for SBC and AAC). */ +const a2dp_sbc_t sbcCapabilities = { + .frequency = + /*SBC_SAMPLING_FREQ_16000 | + SBC_SAMPLING_FREQ_32000 |*/ + SBC_SAMPLING_FREQ_44100 | + SBC_SAMPLING_FREQ_48000, + .channel_mode = + /*SBC_CHANNEL_MODE_MONO | + SBC_CHANNEL_MODE_DUAL_CHANNEL |*/ + SBC_CHANNEL_MODE_STEREO | + SBC_CHANNEL_MODE_JOINT_STEREO, + .block_length = + SBC_BLOCK_LENGTH_4 | + SBC_BLOCK_LENGTH_8 | + SBC_BLOCK_LENGTH_12 | + SBC_BLOCK_LENGTH_16, + .subbands = + SBC_SUBBANDS_4 | + SBC_SUBBANDS_8, + .allocation_method = + SBC_ALLOCATION_SNR | + SBC_ALLOCATION_LOUDNESS, + .min_bitpool = MIN_BITPOOL, + .max_bitpool = MAX_BITPOOL, +}; + +const a2dp_aac_t aacCapabilities = { + .object_type = + AAC_OBJECT_TYPE_MPEG2_AAC_LC | + AAC_OBJECT_TYPE_MPEG4_AAC_LC, + /*AAC_OBJECT_TYPE_MPEG4_AAC_LTP | + AAC_OBJECT_TYPE_MPEG4_AAC_SCA, */ + AAC_INIT_FREQUENCY( + /*AAC_SAMPLING_FREQ_8000 | + AAC_SAMPLING_FREQ_11025 | + AAC_SAMPLING_FREQ_12000 | + AAC_SAMPLING_FREQ_16000 | + AAC_SAMPLING_FREQ_22050 | + AAC_SAMPLING_FREQ_24000 | + AAC_SAMPLING_FREQ_32000 |*/ + AAC_SAMPLING_FREQ_44100 | + AAC_SAMPLING_FREQ_48000) + /*AAC_SAMPLING_FREQ_64000 | + AAC_SAMPLING_FREQ_88200 | + AAC_SAMPLING_FREQ_96000)*/ + .channels = + /*AAC_CHANNELS_1 |*/ + AAC_CHANNELS_2, + .vbr = 1, + AAC_INIT_BITRATE(0xFFFF) +}; diff --git a/src/a2dp-codecs.h b/src/a2dp-codecs.h new file mode 100644 index 0000000..adbf46d --- /dev/null +++ b/src/a2dp-codecs.h @@ -0,0 +1,264 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef BLUEZQT_A2DPCODECS_H +#define BLUEZQT_A2DPCODECS_H + +#include + +#define A2DP_CODEC_SBC 0x00 +#define A2DP_CODEC_MPEG12 0x01 +#define A2DP_CODEC_MPEG24 0x02 +#define A2DP_CODEC_ATRAC 0x03 +#define A2DP_CODEC_VENDOR 0xFF + +#define SBC_SAMPLING_FREQ_16000 (1 << 3) +#define SBC_SAMPLING_FREQ_32000 (1 << 2) +#define SBC_SAMPLING_FREQ_44100 (1 << 1) +#define SBC_SAMPLING_FREQ_48000 1 + +#define SBC_CHANNEL_MODE_MONO (1 << 3) +#define SBC_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) +#define SBC_CHANNEL_MODE_STEREO (1 << 1) +#define SBC_CHANNEL_MODE_JOINT_STEREO 1 + +#define SBC_BLOCK_LENGTH_4 (1 << 3) +#define SBC_BLOCK_LENGTH_8 (1 << 2) +#define SBC_BLOCK_LENGTH_12 (1 << 1) +#define SBC_BLOCK_LENGTH_16 1 + +#define SBC_SUBBANDS_4 (1 << 1) +#define SBC_SUBBANDS_8 1 + +#define SBC_ALLOCATION_SNR (1 << 1) +#define SBC_ALLOCATION_LOUDNESS 1 + +#define MAX_BITPOOL 64 +#define MIN_BITPOOL 2 + +#define MPEG_CHANNEL_MODE_MONO (1 << 3) +#define MPEG_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) +#define MPEG_CHANNEL_MODE_STEREO (1 << 1) +#define MPEG_CHANNEL_MODE_JOINT_STEREO 1 + +#define MPEG_LAYER_MP1 (1 << 2) +#define MPEG_LAYER_MP2 (1 << 1) +#define MPEG_LAYER_MP3 1 + +#define MPEG_SAMPLING_FREQ_16000 (1 << 5) +#define MPEG_SAMPLING_FREQ_22050 (1 << 4) +#define MPEG_SAMPLING_FREQ_24000 (1 << 3) +#define MPEG_SAMPLING_FREQ_32000 (1 << 2) +#define MPEG_SAMPLING_FREQ_44100 (1 << 1) +#define MPEG_SAMPLING_FREQ_48000 1 + +#define MPEG_BIT_RATE_VBR 0x8000 +#define MPEG_BIT_RATE_320000 0x4000 +#define MPEG_BIT_RATE_256000 0x2000 +#define MPEG_BIT_RATE_224000 0x1000 +#define MPEG_BIT_RATE_192000 0x0800 +#define MPEG_BIT_RATE_160000 0x0400 +#define MPEG_BIT_RATE_128000 0x0200 +#define MPEG_BIT_RATE_112000 0x0100 +#define MPEG_BIT_RATE_96000 0x0080 +#define MPEG_BIT_RATE_80000 0x0040 +#define MPEG_BIT_RATE_64000 0x0020 +#define MPEG_BIT_RATE_56000 0x0010 +#define MPEG_BIT_RATE_48000 0x0008 +#define MPEG_BIT_RATE_40000 0x0004 +#define MPEG_BIT_RATE_32000 0x0002 +#define MPEG_BIT_RATE_FREE 0x0001 + +#define AAC_OBJECT_TYPE_MPEG2_AAC_LC 0x80 +#define AAC_OBJECT_TYPE_MPEG4_AAC_LC 0x40 +#define AAC_OBJECT_TYPE_MPEG4_AAC_LTP 0x20 +#define AAC_OBJECT_TYPE_MPEG4_AAC_SCA 0x10 + +#define AAC_SAMPLING_FREQ_8000 0x0800 +#define AAC_SAMPLING_FREQ_11025 0x0400 +#define AAC_SAMPLING_FREQ_12000 0x0200 +#define AAC_SAMPLING_FREQ_16000 0x0100 +#define AAC_SAMPLING_FREQ_22050 0x0080 +#define AAC_SAMPLING_FREQ_24000 0x0040 +#define AAC_SAMPLING_FREQ_32000 0x0020 +#define AAC_SAMPLING_FREQ_44100 0x0010 +#define AAC_SAMPLING_FREQ_48000 0x0008 +#define AAC_SAMPLING_FREQ_64000 0x0004 +#define AAC_SAMPLING_FREQ_88200 0x0002 +#define AAC_SAMPLING_FREQ_96000 0x0001 + +#define AAC_CHANNELS_1 0x02 +#define AAC_CHANNELS_2 0x01 + +#define AAC_GET_BITRATE(a) ((a).bitrate1 << 16 | \ + (a).bitrate2 << 8 | (a).bitrate3) +#define AAC_GET_FREQUENCY(a) ((a).frequency1 << 4 | (a).frequency2) + +#define AAC_SET_BITRATE(a, b) \ + do { \ + (a).bitrate1 = (b >> 16) & 0x7f; \ + (a).bitrate2 = (b >> 8) & 0xff; \ + (a).bitrate3 = b & 0xff; \ + } while (0) +#define AAC_SET_FREQUENCY(a, f) \ + do { \ + (a).frequency1 = (f >> 4) & 0xff; \ + (a).frequency2 = f & 0x0f; \ + } while (0) + +#define AAC_INIT_BITRATE(b) \ + .bitrate1 = (b >> 16) & 0x7f, \ + .bitrate2 = (b >> 8) & 0xff, \ + .bitrate3 = b & 0xff, +#define AAC_INIT_FREQUENCY(f) \ + .frequency1 = (f >> 4) & 0xff, \ + .frequency2 = f & 0x0f, + +#define APTX_VENDOR_ID 0x0000004f +#define APTX_CODEC_ID 0x0001 + +#define APTX_CHANNEL_MODE_MONO 0x01 +#define APTX_CHANNEL_MODE_STEREO 0x02 + +#define APTX_SAMPLING_FREQ_16000 0x08 +#define APTX_SAMPLING_FREQ_32000 0x04 +#define APTX_SAMPLING_FREQ_44100 0x02 +#define APTX_SAMPLING_FREQ_48000 0x01 + +#define LDAC_VENDOR_ID 0x0000012d +#define LDAC_CODEC_ID 0x00aa + +typedef struct { + uint32_t vendor_id; + uint16_t codec_id; +} __attribute__ ((packed)) a2dp_vendor_codec_t; + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +typedef struct { + uint8_t channel_mode:4; + uint8_t frequency:4; + uint8_t allocation_method:2; + uint8_t subbands:2; + uint8_t block_length:4; + uint8_t min_bitpool; + uint8_t max_bitpool; +} __attribute__ ((packed)) a2dp_sbc_t; + +typedef struct { + uint8_t channel_mode:4; + uint8_t crc:1; + uint8_t layer:3; + uint8_t frequency:6; + uint8_t mpf:1; + uint8_t rfa:1; + uint16_t bitrate; +} __attribute__ ((packed)) a2dp_mpeg_t; + +typedef struct { + uint8_t object_type; + uint8_t frequency1; + uint8_t rfa:2; + uint8_t channels:2; + uint8_t frequency2:4; + uint8_t bitrate1:7; + uint8_t vbr:1; + uint8_t bitrate2; + uint8_t bitrate3; +} __attribute__ ((packed)) a2dp_aac_t; + +typedef struct { + a2dp_vendor_codec_t info; + uint8_t channel_mode:4; + uint8_t frequency:4; +} __attribute__ ((packed)) a2dp_aptx_t; + +typedef struct { + a2dp_vendor_codec_t info; + uint8_t unknown[2]; +} __attribute__ ((packed)) a2dp_ldac_t; + +#elif __BYTE_ORDER == __BIG_ENDIAN + +typedef struct { + uint8_t frequency:4; + uint8_t channel_mode:4; + uint8_t block_length:4; + uint8_t subbands:2; + uint8_t allocation_method:2; + uint8_t min_bitpool; + uint8_t max_bitpool; +} __attribute__ ((packed)) a2dp_sbc_t; + +typedef struct { + uint8_t layer:3; + uint8_t crc:1; + uint8_t channel_mode:4; + uint8_t rfa:1; + uint8_t mpf:1; + uint8_t frequency:6; + uint16_t bitrate; +} __attribute__ ((packed)) a2dp_mpeg_t; + +typedef struct { + uint8_t object_type; + uint8_t frequency1; + uint8_t frequency2:4; + uint8_t channels:2; + uint8_t rfa:2; + uint8_t vbr:1; + uint8_t bitrate1:7; + uint8_t bitrate2; + uint8_t bitrate3; +} __attribute__ ((packed)) a2dp_aac_t; + +typedef struct { + a2dp_vendor_codec_t info; + uint8_t frequency:4; + uint8_t channel_mode:4; +} __attribute__ ((packed)) a2dp_aptx_t; + +typedef struct { + a2dp_vendor_codec_t info; + uint8_t unknown[2]; +} __attribute__ ((packed)) a2dp_ldac_t; + +#else +#error "Unknown byte order" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bluezqt_export.h" + +BLUEZQT_EXPORT extern const a2dp_sbc_t sbcCapabilities; +BLUEZQT_EXPORT extern const a2dp_aac_t aacCapabilities; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/interfaces/org.bluez.Media1.xml b/src/interfaces/org.bluez.Media1.xml new file mode 100644 index 0000000..2b32522 --- /dev/null +++ b/src/interfaces/org.bluez.Media1.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/interfaces/org.bluez.MediaEndpoint1.xml b/src/interfaces/org.bluez.MediaEndpoint1.xml new file mode 100644 index 0000000..1149ea7 --- /dev/null +++ b/src/interfaces/org.bluez.MediaEndpoint1.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/manager.cpp b/src/manager.cpp index 4bd0068..b3bbbc6 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -1,249 +1,254 @@ /* * BluezQt - Asynchronous Bluez wrapper library * * Copyright (C) 2014 David Rosca * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "manager.h" #include "manager_p.h" #include "adapter.h" #include "agent.h" #include "agentadaptor.h" #include "profile.h" #include "profile_p.h" #include "profileadaptor.h" #include "pendingcall.h" #include "initmanagerjob.h" #include "utils.h" #include "debug.h" namespace BluezQt { Manager::Manager(QObject *parent) : QObject(parent) , d(new ManagerPrivate(this)) { Instance::setManager(this); } Manager::~Manager() { delete d; } InitManagerJob *Manager::init() { return new InitManagerJob(this); } bool Manager::isInitialized() const { return d->m_initialized; } bool Manager::isOperational() const { return d->m_initialized && d->m_bluezRunning && d->m_loaded; } bool Manager::isBluetoothOperational() const { return !d->m_bluetoothBlocked && d->m_bluezRunning && d->m_loaded && d->m_usableAdapter; } bool Manager::isBluetoothBlocked() const { return d->m_bluetoothBlocked; } bool Manager::setBluetoothBlocked(bool blocked) { if (blocked) { return d->m_rfkill->block(); } else { return d->m_rfkill->unblock(); } } AdapterPtr Manager::usableAdapter() const { return d->m_usableAdapter; } QList Manager::adapters() const { return d->m_adapters.values(); } QList Manager::devices() const { return d->m_devices.values(); } PendingCall *Manager::startService() { QDBusMessage msg = QDBusMessage::createMethodCall(Strings::orgFreedesktopDBus(), QStringLiteral("/org/freedesktop/DBus"), Strings::orgFreedesktopDBus(), QStringLiteral("StartServiceByName")); msg << Strings::orgBluez(); msg << quint32(0); return new PendingCall(DBusConnection::orgBluez().asyncCall(msg), PendingCall::ReturnUint32); } AdapterPtr Manager::adapterForAddress(const QString &address) const { Q_FOREACH (AdapterPtr adapter, d->m_adapters) { if (adapter->address() == address) { return adapter; } } return AdapterPtr(); } AdapterPtr Manager::adapterForUbi(const QString &ubi) const { return d->m_adapters.value(ubi); } DevicePtr Manager::deviceForAddress(const QString &address) const { DevicePtr device; Q_FOREACH (AdapterPtr adapter, d->m_adapters) { DevicePtr d = adapter->deviceForAddress(address); if (!d) { continue; } // Prefer powered adapter if (!device) { device = d; } else if (adapter->isPowered()) { device = d; } } return device; } DevicePtr Manager::deviceForUbi(const QString &ubi) const { return d->m_devices.value(ubi); } PendingCall *Manager::registerAgent(Agent *agent) { Q_ASSERT(agent); if (!d->m_bluezAgentManager) { return new PendingCall(PendingCall::InternalError, QStringLiteral("Manager not operational!")); } QString capability; switch (agent->capability()) { case Agent::DisplayOnly: capability = QStringLiteral("DisplayOnly"); break; case Agent::DisplayYesNo: capability = QStringLiteral("DisplayYesNo"); break; case Agent::KeyboardOnly: capability = QStringLiteral("KeyboardOnly"); break; case Agent::NoInputNoOutput: capability = QStringLiteral("NoInputNoOutput"); break; default: capability = QStringLiteral("DisplayYesNo"); break; } new AgentAdaptor(agent, this); if (!DBusConnection::orgBluez().registerObject(agent->objectPath().path(), agent)) { qCDebug(BLUEZQT) << "Cannot register object" << agent->objectPath().path(); } return new PendingCall(d->m_bluezAgentManager->RegisterAgent(agent->objectPath(), capability), PendingCall::ReturnVoid, this); } PendingCall *Manager::unregisterAgent(Agent *agent) { Q_ASSERT(agent); if (!d->m_bluezAgentManager) { return new PendingCall(PendingCall::InternalError, QStringLiteral("Manager not operational!")); } DBusConnection::orgBluez().unregisterObject(agent->objectPath().path()); return new PendingCall(d->m_bluezAgentManager->UnregisterAgent(agent->objectPath()), PendingCall::ReturnVoid, this); } PendingCall *Manager::requestDefaultAgent(Agent *agent) { Q_ASSERT(agent); if (!d->m_bluezAgentManager) { return new PendingCall(PendingCall::InternalError, QStringLiteral("Manager not operational!")); } return new PendingCall(d->m_bluezAgentManager->RequestDefaultAgent(agent->objectPath()), PendingCall::ReturnVoid, this); } PendingCall *Manager::registerProfile(Profile *profile) { Q_ASSERT(profile); if (!d->m_bluezProfileManager) { return new PendingCall(PendingCall::InternalError, QStringLiteral("Manager not operational!")); } new ProfileAdaptor(profile, this); if (!DBusConnection::orgBluez().registerObject(profile->objectPath().path(), profile)) { qCDebug(BLUEZQT) << "Cannot register object" << profile->objectPath().path(); } return new PendingCall(d->m_bluezProfileManager->RegisterProfile(profile->objectPath(), profile->uuid(), profile->d->options), PendingCall::ReturnVoid, this); } PendingCall *Manager::unregisterProfile(Profile *profile) { Q_ASSERT(profile); if (!d->m_bluezProfileManager) { return new PendingCall(PendingCall::InternalError, QStringLiteral("Manager not operational!")); } DBusConnection::orgBluez().unregisterObject(profile->objectPath().path()); return new PendingCall(d->m_bluezProfileManager->UnregisterProfile(profile->objectPath()), PendingCall::ReturnVoid, this); } +MediaPtr Manager::media() const +{ + return d->m_media; +} + } // namespace BluezQt diff --git a/src/manager.h b/src/manager.h index b33719a..8540eff 100644 --- a/src/manager.h +++ b/src/manager.h @@ -1,365 +1,372 @@ /* * BluezQt - Asynchronous BlueZ wrapper library * * Copyright (C) 2014-2015 David Rosca * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef BLUEZQT_MANAGER_H #define BLUEZQT_MANAGER_H #include #include "types.h" #include "bluezqt_export.h" namespace BluezQt { class Device; class Adapter; class Agent; class Profile; class PendingCall; class InitManagerJob; /** * @class BluezQt::Manager manager.h * * Bluetooth manager. * * The entry point to communicate with system BlueZ daemon. * * The typical usecase is to work with usableAdapter() (any powered adapter), * but it is also possible to use specific adapter. * * You must call init() before other functions can be used. * * The only functions that can be used before initialization are two rfkill-related * functions: isBluetoothBlocked() and setBluetoothBlocked(). * * Example use in C++ code: * @code * BluezQt::Manager *manager = new BluezQt::Manager(); * BluezQt::InitManagerJob *job = manager->init(); * job->start(); * connect(job, &BluezQt::InitManagerJob::result, ...); * @endcode * * In QML, manager is a singleton and initialization is started when first using * the manager. You don't need to track initialized state, just use property binding. * * Example use in QML code: * @code * import QtQuick 2.2 * import org.kde.bluezqt 1.0 as BluezQt * * Item { * property QtObject manager : BluezQt.Manager * property devicesCount : manager.devices.length * property adaptersCount : manager.adapters.length * * Component.onCompleted: { * console.log("Manager operational:", manager.operational) * } * } * @endcode * * @note All communication with BlueZ daemon happens asynchronously. Almost all methods * returns PendingCall to help track the call progress and to check for any errors. * * @note If manager is not operational, all methods that returns a PendingCall will fail * with PendingCall::InternalError. * * @see InitManagerJob */ class BLUEZQT_EXPORT Manager : public QObject { Q_OBJECT Q_PROPERTY(bool initialized READ isInitialized) Q_PROPERTY(bool operational READ isOperational NOTIFY operationalChanged) Q_PROPERTY(bool bluetoothOperational READ isBluetoothOperational NOTIFY bluetoothOperationalChanged) Q_PROPERTY(bool bluetoothBlocked READ isBluetoothBlocked WRITE setBluetoothBlocked NOTIFY bluetoothBlockedChanged) Q_PROPERTY(AdapterPtr usableAdapter READ usableAdapter NOTIFY usableAdapterChanged) Q_PROPERTY(QList adapters READ adapters) Q_PROPERTY(QList devices READ devices) public: /** * Creates a new Manager object. * * @param parent */ explicit Manager(QObject *parent = nullptr); /** * Destroys a Manager object. */ ~Manager(); /** * Creates a new init job. * * @return init manager job */ InitManagerJob *init(); /** * Returns whether the manager is initialized. * * @return true if manager is initialized */ bool isInitialized() const; /** * Returns whether the manager is operational. * * The manager is operational when initialization was successful and * BlueZ system daemon is running. * * @return true if manager is operational */ bool isOperational() const; /** * Returns whether Bluetooth is operational. * * Bluetooth is operational when manager is operational and there is * a valid usable adapter. * * @return true if Bluetooth is operational */ bool isBluetoothOperational() const; /** * Returns whether Bluetooth is blocked. * * Bluetooth is blocked if rfkill state for Bluetooth is either * SOFT_BLOCKED or HARD_BLOCKED. * * @note This requires read access to /dev/rfkill. * * @return true if Bluetooth is blocked */ bool isBluetoothBlocked() const; /** * Sets a Bluetooth blocked state. * * This may fail either due to insufficent permissions or * because rfkill state is HARD_BLOCKED. In that case, * this function returns false. * * @note This requires write access to /dev/rfkill. * * @return true if unblocking rfkill devices succeeded */ bool setBluetoothBlocked(bool blocked); /** * Returns a usable adapter. * * Usable adapter is any adapter that is currently powered on. * * @return usable adapter */ AdapterPtr usableAdapter() const; /** * Returns a list of all adapters. * * @return list of adapters */ QList adapters() const; /** * Returns a list of all devices. * * @return list of devices */ QList devices() const; /** * Attempts to start org.bluez service by D-Bus activation. * * Possible return values are 1 if the service was started, * 2 if the service is already running or error if the service * could not be started. * * @return quint32 pending call */ static PendingCall *startService(); /** * Returns an adapter for specified address. * * @param address address of adapter (eg. "1C:E5:C3:BC:94:7E") * @return null if there is no adapter with specified address */ AdapterPtr adapterForAddress(const QString &address) const; /** * Returns an adapter for specified UBI. * * @param ubi UBI of adapter (eg. "/org/bluez/hci0") * @return null if there is no adapter with specified UBI */ AdapterPtr adapterForUbi(const QString &ubi) const; /** * Returns a device for specified address. * * @note There may be more devices with the same address (same device * in multiple adapters). In this case, the first found device will * be returned while preferring powered adapters in search. * * @param address address of device (eg. "40:79:6A:0C:39:75") * @return null if there is no device with specified address */ DevicePtr deviceForAddress(const QString &address) const; /** * Returns a device for specified UBI. * * @param ubi UBI of device (eg. "/org/bluez/hci0/dev_40_79_6A_0C_39_75") * @return null if there is no device with specified UBI */ DevicePtr deviceForUbi(const QString &ubi) const; /** * Registers agent. * * This agent will be used for for all actions triggered by the application. * Eg. show a PIN code in pairing process. * * Possible errors: PendingCall::InvalidArguments, PendingCall::AlreadyExists * * @param agent agent to be registered * @return void pending call */ PendingCall *registerAgent(Agent *agent); /** * Unregisters agent. * * Possible errors: PendingCall::DoesNotExist * * @param agent agent to be unregistered * @return void pending call */ PendingCall *unregisterAgent(Agent *agent); /** * Requests default agent. * * This requests to make the application agent the default agent. * * Possible errors: PendingCall::DoesNotExist * * @param agent registered agent * @return void pending call */ PendingCall *requestDefaultAgent(Agent *agent); /** * Registers profile. * * Possible errors: PendingCall::InvalidArguments, PendingCall::AlreadyExists * * @param profile profile to be registered * @return void pending call */ PendingCall *registerProfile(Profile *profile); /** * Unregisters profile. * * Possible errors: PendingCall::DoesNotExist * * @param profile profile to be unregistered * @return void pending call */ PendingCall *unregisterProfile(Profile *profile); + /** + * Returns the media interface. + * + * @return media + */ + MediaPtr media() const; + Q_SIGNALS: /** * Indicates that operational state have changed. */ void operationalChanged(bool operational); /** * Indicates that Bluetooth operational state have changed. */ void bluetoothOperationalChanged(bool operational); /** * Indicates that Bluetooth blocked state have changed. */ void bluetoothBlockedChanged(bool blocked); /** * Indicates that adapter was added. */ void adapterAdded(AdapterPtr adapter); /** * Indicates that adapter was removed. */ void adapterRemoved(AdapterPtr adapter); /** * Indicates that at least one of the adapter's properties have changed. */ void adapterChanged(AdapterPtr adapter); /** * Indicates that a new device was added (eg. found by discovery). */ void deviceAdded(DevicePtr device); /** * Indicates that a device was removed. */ void deviceRemoved(DevicePtr device); /** * Indicates that at least one of the device's properties have changed. */ void deviceChanged(DevicePtr device); /** * Indicates that usable adapter have changed. */ void usableAdapterChanged(AdapterPtr adapter); /** * Indicates that all adapters were removed. */ void allAdaptersRemoved(); private: class ManagerPrivate *const d; friend class ManagerPrivate; friend class InitManagerJobPrivate; }; } // namespace BluezQt #endif // BLUEZQT_MANAGER_H diff --git a/src/manager_p.cpp b/src/manager_p.cpp index 1fd8125..bb990c1 100644 --- a/src/manager_p.cpp +++ b/src/manager_p.cpp @@ -1,457 +1,466 @@ /* * BluezQt - Asynchronous Bluez wrapper library * * Copyright (C) 2014-2015 David Rosca * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "manager_p.h" #include "manager.h" #include "device.h" #include "device_p.h" #include "adapter.h" #include "adapter_p.h" #include "debug.h" #include "utils.h" +#include "media.h" #include #include #include namespace BluezQt { ManagerPrivate::ManagerPrivate(Manager *parent) : QObject(parent) , q(parent) , m_dbusObjectManager(nullptr) , m_bluezAgentManager(nullptr) , m_bluezProfileManager(nullptr) , m_initialized(false) , m_bluezRunning(false) , m_loaded(false) , m_adaptersLoaded(false) { qDBusRegisterMetaType(); qDBusRegisterMetaType(); m_rfkill = new Rfkill(this); m_bluetoothBlocked = rfkillBlocked(); connect(m_rfkill, &Rfkill::stateChanged, this, &ManagerPrivate::rfkillStateChanged); connect(q, &Manager::adapterRemoved, this, &ManagerPrivate::adapterRemoved); } void ManagerPrivate::init() { // Keep an eye on org.bluez service QDBusServiceWatcher *serviceWatcher = new QDBusServiceWatcher(Strings::orgBluez(), DBusConnection::orgBluez(), QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration, this); connect(serviceWatcher, &QDBusServiceWatcher::serviceRegistered, this, &ManagerPrivate::serviceRegistered); connect(serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &ManagerPrivate::serviceUnregistered); // Update the current state of org.bluez service if (!DBusConnection::orgBluez().isConnected()) { Q_EMIT initError(QStringLiteral("DBus system bus is not connected!")); return; } QDBusMessage call = QDBusMessage::createMethodCall(Strings::orgFreedesktopDBus(), QStringLiteral("/"), Strings::orgFreedesktopDBus(), QStringLiteral("NameHasOwner")); call << Strings::orgBluez(); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(DBusConnection::orgBluez().asyncCall(call)); connect(watcher, &QDBusPendingCallWatcher::finished, this, &ManagerPrivate::nameHasOwnerFinished); DBusConnection::orgBluez().connect(Strings::orgBluez(), QString(), Strings::orgFreedesktopDBusProperties(), QStringLiteral("PropertiesChanged"), this, SLOT(propertiesChanged(QString,QVariantMap,QStringList))); } void ManagerPrivate::nameHasOwnerFinished(QDBusPendingCallWatcher *watcher) { const QDBusPendingReply &reply = *watcher; watcher->deleteLater(); if (reply.isError()) { Q_EMIT initError(reply.error().message()); return; } m_bluezRunning = reply.value(); if (m_bluezRunning) { load(); } else { m_initialized = true; Q_EMIT initFinished(); } } void ManagerPrivate::load() { if (!m_bluezRunning || m_loaded) { return; } // Force QDBus to cache owner of org.bluez - this will be the only blocking call on system connection DBusConnection::orgBluez().connect(Strings::orgBluez(), QStringLiteral("/"), Strings::orgFreedesktopDBus(), QStringLiteral("Dummy"), this, SLOT(dummy())); m_dbusObjectManager = new DBusObjectManager(Strings::orgBluez(), QStringLiteral("/"), DBusConnection::orgBluez(), this); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(m_dbusObjectManager->GetManagedObjects(), this); connect(watcher, &QDBusPendingCallWatcher::finished, this, &ManagerPrivate::getManagedObjectsFinished); } void ManagerPrivate::getManagedObjectsFinished(QDBusPendingCallWatcher *watcher) { const QDBusPendingReply &reply = *watcher; watcher->deleteLater(); if (reply.isError()) { Q_EMIT initError(reply.error().message()); return; } DBusManagerStruct::const_iterator it; const DBusManagerStruct &managedObjects = reply.value(); for (it = managedObjects.constBegin(); it != managedObjects.constEnd(); ++it) { const QString &path = it.key().path(); const QVariantMapMap &interfaces = it.value(); interfacesAdded(it.key(), interfaces); if (interfaces.contains(Strings::orgBluezAgentManager1())) { m_bluezAgentManager = new BluezAgentManager(Strings::orgBluez(), path, DBusConnection::orgBluez(), this); } if (interfaces.contains(Strings::orgBluezProfileManager1())) { m_bluezProfileManager = new BluezProfileManager(Strings::orgBluez(), path, DBusConnection::orgBluez(), this); } + if (interfaces.contains(Strings::orgBluezMedia1())) { + m_media = MediaPtr(new Media(path)); + } } if (!m_bluezAgentManager) { Q_EMIT initError(QStringLiteral("Cannot find org.bluez.AgentManager1 object!")); return; } if (!m_bluezProfileManager) { Q_EMIT initError(QStringLiteral("Cannot find org.bluez.ProfileManager1 object!")); return; } + if (!m_media) { + Q_EMIT initError(QStringLiteral("Cannot find org.bluez.Media1 object!")); + return; + } + connect(m_dbusObjectManager, &DBusObjectManager::InterfacesAdded, this, &ManagerPrivate::interfacesAdded); connect(m_dbusObjectManager, &DBusObjectManager::InterfacesRemoved, this, &ManagerPrivate::interfacesRemoved); m_loaded = true; m_initialized = true; Q_EMIT q->operationalChanged(true); if (q->isBluetoothOperational()) { Q_EMIT q->bluetoothOperationalChanged(true); } Q_EMIT initFinished(); } void ManagerPrivate::clear() { m_loaded = false; // Delete all devices first while (!m_devices.isEmpty()) { DevicePtr device = m_devices.begin().value(); m_devices.remove(m_devices.begin().key()); device->adapter()->d->removeDevice(device); } // Delete all adapters while (!m_adapters.isEmpty()) { AdapterPtr adapter = m_adapters.begin().value(); m_adapters.remove(m_adapters.begin().key()); Q_EMIT adapter->adapterRemoved(adapter); if (m_adapters.isEmpty()) { Q_EMIT q->allAdaptersRemoved(); } } // Delete all other objects m_usableAdapter.clear(); if (m_dbusObjectManager) { m_dbusObjectManager->deleteLater(); m_dbusObjectManager = nullptr; } if (m_bluezAgentManager) { m_bluezAgentManager->deleteLater(); m_bluezAgentManager = nullptr; } } AdapterPtr ManagerPrivate::findUsableAdapter() const { Q_FOREACH (AdapterPtr adapter, m_adapters) { if (adapter->isPowered()) { return adapter; } } return AdapterPtr(); } void ManagerPrivate::serviceRegistered() { qCDebug(BLUEZQT) << "BlueZ service registered"; m_bluezRunning = true; load(); } void ManagerPrivate::serviceUnregistered() { qCDebug(BLUEZQT) << "BlueZ service unregistered"; bool wasBtOperational = q->isBluetoothOperational(); m_bluezRunning = false; if (wasBtOperational) { Q_EMIT q->bluetoothOperationalChanged(false); } clear(); Q_EMIT q->operationalChanged(false); } void ManagerPrivate::interfacesAdded(const QDBusObjectPath &objectPath, const QVariantMapMap &interfaces) { const QString &path = objectPath.path(); QVariantMapMap::const_iterator it; for (it = interfaces.constBegin(); it != interfaces.constEnd(); ++it) { if (it.key() == Strings::orgBluezAdapter1()) { addAdapter(path, it.value()); } else if (it.key() == Strings::orgBluezDevice1()) { addDevice(path, it.value()); } } Q_FOREACH (DevicePtr device, m_devices.values()) { if (path.startsWith(device->ubi())) { device->d->interfacesAdded(path, interfaces); break; } } } void ManagerPrivate::interfacesRemoved(const QDBusObjectPath &objectPath, const QStringList &interfaces) { const QString &path = objectPath.path(); Q_FOREACH (const QString &interface, interfaces) { if (interface == Strings::orgBluezAdapter1()) { removeAdapter(path); } else if (interface == Strings::orgBluezDevice1()) { removeDevice(path); } } Q_FOREACH (DevicePtr device, m_devices.values()) { if (path.startsWith(device->ubi())) { device->d->interfacesRemoved(path, interfaces); break; } } } void ManagerPrivate::adapterRemoved(const AdapterPtr &adapter) { disconnect(adapter.data(), &Adapter::poweredChanged, this, &ManagerPrivate::adapterPoweredChanged); // Current usable adapter was removed if (adapter == m_usableAdapter) { setUsableAdapter(findUsableAdapter()); } } void ManagerPrivate::adapterPoweredChanged(bool powered) { Q_ASSERT(qobject_cast(sender())); AdapterPtr adapter = static_cast(sender())->toSharedPtr(); // Current usable adapter was powered off if (m_usableAdapter == adapter && !powered) { setUsableAdapter(findUsableAdapter()); } // Adapter was powered on, set it as usable if (!m_usableAdapter && powered) { setUsableAdapter(adapter); } } void ManagerPrivate::rfkillStateChanged(Rfkill::State state) { Q_UNUSED(state) bool blocked = rfkillBlocked(); bool wasBtOperational = q->isBluetoothOperational(); if (m_bluetoothBlocked != blocked) { m_bluetoothBlocked = blocked; Q_EMIT q->bluetoothBlockedChanged(m_bluetoothBlocked); if (wasBtOperational != q->isBluetoothOperational()) { Q_EMIT q->bluetoothOperationalChanged(q->isBluetoothOperational()); } } } void ManagerPrivate::addAdapter(const QString &adapterPath, const QVariantMap &properties) { AdapterPtr adapter = AdapterPtr(new Adapter(adapterPath, properties)); adapter->d->q = adapter.toWeakRef(); m_adapters.insert(adapterPath, adapter); Q_EMIT q->adapterAdded(adapter); // Powered adapter was added, set it as usable if (!m_usableAdapter && adapter->isPowered()) { setUsableAdapter(adapter); } connect(adapter.data(), &Adapter::deviceAdded, q, &Manager::deviceAdded); connect(adapter.data(), &Adapter::adapterRemoved, q, &Manager::adapterRemoved); connect(adapter.data(), &Adapter::adapterChanged, q, &Manager::adapterChanged); connect(adapter.data(), &Adapter::poweredChanged, this, &ManagerPrivate::adapterPoweredChanged); } void ManagerPrivate::addDevice(const QString &devicePath, const QVariantMap &properties) { AdapterPtr adapter = m_adapters.value(properties.value(QStringLiteral("Adapter")).value().path()); if (!adapter) { return; } DevicePtr device = DevicePtr(new Device(devicePath, properties, adapter)); device->d->q = device.toWeakRef(); m_devices.insert(devicePath, device); adapter->d->addDevice(device); connect(device.data(), &Device::deviceRemoved, q, &Manager::deviceRemoved); connect(device.data(), &Device::deviceChanged, q, &Manager::deviceChanged); } void ManagerPrivate::removeAdapter(const QString &adapterPath) { AdapterPtr adapter = m_adapters.value(adapterPath); if (!adapter) { return; } // Make sure we always remove all devices before removing the adapter Q_FOREACH (const DevicePtr &device, adapter->devices()) { removeDevice(device->ubi()); } m_adapters.remove(adapterPath); Q_EMIT adapter->adapterRemoved(adapter); if (m_adapters.isEmpty()) { Q_EMIT q->allAdaptersRemoved(); } disconnect(adapter.data(), &Adapter::adapterChanged, q, &Manager::adapterChanged); disconnect(adapter.data(), &Adapter::poweredChanged, this, &ManagerPrivate::adapterPoweredChanged); } void ManagerPrivate::removeDevice(const QString &devicePath) { DevicePtr device = m_devices.take(devicePath); if (!device) { return; } device->adapter()->d->removeDevice(device); disconnect(device.data(), &Device::deviceChanged, q, &Manager::deviceChanged); } bool ManagerPrivate::rfkillBlocked() const { return m_rfkill->state() == Rfkill::SoftBlocked || m_rfkill->state() == Rfkill::HardBlocked; } void ManagerPrivate::setUsableAdapter(const AdapterPtr &adapter) { if (m_usableAdapter == adapter) { return; } qCDebug(BLUEZQT) << "Setting usable adapter" << adapter; bool wasBtOperational = q->isBluetoothOperational(); m_usableAdapter = adapter; Q_EMIT q->usableAdapterChanged(m_usableAdapter); if (wasBtOperational != q->isBluetoothOperational()) { Q_EMIT q->bluetoothOperationalChanged(q->isBluetoothOperational()); } } void ManagerPrivate::propertiesChanged(const QString &interface, const QVariantMap &changed, const QStringList &invalidated) { // Cut anything after device path to forward it to Device to handle const QString path = message().path().section(QLatin1Char('/'), 0, 4); QTimer::singleShot(0, this, [=]() { AdapterPtr adapter = m_adapters.value(path); if (adapter) { adapter->d->propertiesChanged(interface, changed, invalidated); return; } DevicePtr device = m_devices.value(path); if (device) { device->d->propertiesChanged(interface, changed, invalidated); return; } qCDebug(BLUEZQT) << "Unhandled property change" << interface << changed << invalidated; }); } void ManagerPrivate::dummy() { } } // namespace BluezQt diff --git a/src/manager_p.h b/src/manager_p.h index cc3276d..ae5630c 100644 --- a/src/manager_p.h +++ b/src/manager_p.h @@ -1,107 +1,108 @@ /* * BluezQt - Asynchronous Bluez wrapper library * * Copyright (C) 2014 David Rosca * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef BLUEZQT_MANAGER_P_H #define BLUEZQT_MANAGER_P_H #include #include #include #include "types.h" #include "rfkill.h" #include "dbusobjectmanager.h" #include "bluezagentmanager1.h" #include "bluezprofilemanager1.h" namespace BluezQt { typedef org::freedesktop::DBus::ObjectManager DBusObjectManager; typedef org::bluez::AgentManager1 BluezAgentManager; typedef org::bluez::ProfileManager1 BluezProfileManager; class Manager; class Adapter; class Device; class AdapterPrivate; class ManagerPrivate : public QObject, protected QDBusContext { Q_OBJECT public: explicit ManagerPrivate(Manager *parent); void init(); void nameHasOwnerFinished(QDBusPendingCallWatcher *watcher); void load(); void getManagedObjectsFinished(QDBusPendingCallWatcher *watcher); void clear(); AdapterPtr findUsableAdapter() const; void serviceRegistered(); void serviceUnregistered(); void interfacesAdded(const QDBusObjectPath &objectPath, const QVariantMapMap &interfaces); void interfacesRemoved(const QDBusObjectPath &objectPath, const QStringList &interfaces); void adapterRemoved(const AdapterPtr &adapter); void adapterPoweredChanged(bool powered); void rfkillStateChanged(Rfkill::State state); void addAdapter(const QString &adapterPath, const QVariantMap &properties); void addDevice(const QString &devicePath, const QVariantMap &properties); void removeAdapter(const QString &adapterPath); void removeDevice(const QString &devicePath); bool rfkillBlocked() const; void setUsableAdapter(const AdapterPtr &adapter); Manager *q; Rfkill *m_rfkill; DBusObjectManager *m_dbusObjectManager; BluezAgentManager *m_bluezAgentManager; BluezProfileManager *m_bluezProfileManager; + MediaPtr m_media; QHash m_adapters; QHash m_devices; AdapterPtr m_usableAdapter; bool m_initialized; bool m_bluezRunning; bool m_loaded; bool m_adaptersLoaded; bool m_bluetoothBlocked; Q_SIGNALS: void initError(const QString &errorText); void initFinished(); private Q_SLOTS: void propertiesChanged(const QString &interface, const QVariantMap &changed, const QStringList &invalidated); void dummy(); }; } // namespace BluezQt #endif // BLUEZQT_MANAGER_P_H diff --git a/src/media.cpp b/src/media.cpp new file mode 100644 index 0000000..9ce451e --- /dev/null +++ b/src/media.cpp @@ -0,0 +1,78 @@ +/* + * BluezQt - Asynchronous Bluez wrapper library + * + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "media.h" +#include "media_p.h" +#include "mediaendpoint.h" +#include "mediaendpointadaptor.h" +#include "pendingcall.h" +#include "utils.h" +#include "debug.h" + +namespace BluezQt +{ + +Media::Media(const QString &path, QObject *parent) + : QObject(parent) + , d(new MediaPrivate()) +{ + d->m_bluezMedia = new BluezMedia(Strings::orgBluez(), path, DBusConnection::orgBluez(), this); +} + +Media::~Media() +{ + delete d; +} + +PendingCall *Media::registerEndpoint(MediaEndpoint *endpoint) +{ + Q_ASSERT(endpoint); + + if (!d->m_bluezMedia) { + return new PendingCall(PendingCall::InternalError, QStringLiteral("Media not operational!")); + } + + new MediaEndpointAdaptor(endpoint); + + if (!DBusConnection::orgBluez().registerObject(endpoint->objectPath().path(), endpoint)) { + qCDebug(BLUEZQT) << "Cannot register object" << endpoint->objectPath().path(); + } + + return new PendingCall(d->m_bluezMedia->RegisterEndpoint(endpoint->objectPath(), endpoint->properties()), + PendingCall::ReturnVoid, this); +} + +PendingCall *Media::unregisterEndpoint(MediaEndpoint *endpoint) +{ + Q_ASSERT(endpoint); + + if (!d->m_bluezMedia) { + return new PendingCall(PendingCall::InternalError, QStringLiteral("Media not operational!")); + } + + DBusConnection::orgBluez().unregisterObject(endpoint->objectPath().path()); + + return new PendingCall(d->m_bluezMedia->UnregisterEndpoint(endpoint->objectPath()), + PendingCall::ReturnVoid, this); +} + +} // namespace BluezQt diff --git a/src/media.h b/src/media.h new file mode 100644 index 0000000..98e43ae --- /dev/null +++ b/src/media.h @@ -0,0 +1,95 @@ +/* + * BluezQt - Asynchronous BlueZ wrapper library + * + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef BLUEZQT_MEDIA_H +#define BLUEZQT_MEDIA_H + +#include + +#include "bluezqt_export.h" + +namespace BluezQt +{ + +class MediaEndpoint; +class PendingCall; + +/** + * @class BluezQt::Media Media.h + * + * Bluetooth Media. + * + * This allows media endpoints to be established in accordance with the + * capabilities of a specific media service profile. + * + * For example, an A2DP media endpoint could be created allowing data from a + * remote device to be streamed to/from the sender. + * + * Each media endpoint is associated with a service object instance that + * implements the required behaviours of the endpoint. The service object + * must be created at a given path before it is registered. + * + * @see MediaEndpoint + */ +class BLUEZQT_EXPORT Media : public QObject +{ + Q_OBJECT + +public: + /** + * Destroys a Media object. + */ + ~Media(); + + /** + * Registers endpoint. + * + * Register a local end point to sender, the sender can register as many end points as it likes. + * + * Note: If the sender disconnects the end points are automatically unregistered. + * + * Possible errors: PendingCall::InvalidArguments, PendingCall::NotSupported + * + * @param endpoint endpoint to be registered + * @return void pending call + */ + PendingCall *registerEndpoint(MediaEndpoint *endpoint); + + /** + * Unregisters endpoint. + * + * @param endpoint endpoint to be unregistered + * @return void pending call + */ + PendingCall *unregisterEndpoint(MediaEndpoint *endpoint); + +private: + explicit Media(const QString &path, QObject *parent = nullptr); + + class MediaPrivate *const d; + + friend class ManagerPrivate; +}; + +} // namespace BluezQt + +#endif // BLUEZQT_MEDIA_H diff --git a/src/media_p.h b/src/media_p.h new file mode 100644 index 0000000..5f101b8 --- /dev/null +++ b/src/media_p.h @@ -0,0 +1,41 @@ +/* + * BluezQt - Asynchronous Bluez wrapper library + * + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef BLUEZQT_MEDIA_P_H +#define BLUEZQT_MEDIA_P_H + +#include "bluezmedia1.h" + +namespace BluezQt +{ + +typedef org::bluez::Media1 BluezMedia; + +class MediaPrivate +{ +public: + BluezMedia *m_bluezMedia = nullptr; +}; + +} // namespace BluezQt + +#endif // BLUEZQT_MEDIA_P_H diff --git a/src/mediaendpoint.cpp b/src/mediaendpoint.cpp new file mode 100644 index 0000000..4b00b62 --- /dev/null +++ b/src/mediaendpoint.cpp @@ -0,0 +1,150 @@ +/* + * BluezQt - Asynchronous Bluez wrapper library + * + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "mediaendpoint.h" +#include "mediaendpoint_p.h" +#include "a2dp-codecs.h" + +namespace BluezQt +{ + +MediaEndpoint::MediaEndpoint(const Configuration& configuration, QObject *parent) + : QObject(parent), + d(new MediaEndpointPrivate(configuration)) +{ +} + +MediaEndpoint::~MediaEndpoint() +{ + delete d; +} + +QDBusObjectPath MediaEndpoint::objectPath() const +{ + return d->m_objectPath; +} + +const QVariantMap &MediaEndpoint::properties() const +{ + return d->m_properties; +} + +void MediaEndpoint::setConfiguration(const QString &transportObjectPath, const QVariantMap &properties) +{ + emit configurationSet(transportObjectPath, properties); +} + +void MediaEndpoint::selectConfiguration(const QByteArray &capabilities, const Request &request) +{ + switch (d->m_configuration.codec) { + case MediaEndpoint::Codec::Sbc: + { + if (capabilities.size() != sizeof(a2dp_sbc_t)) { + emit configurationSelected(capabilities, QByteArray()); + request.reject(); + return; + } + + a2dp_sbc_t caps = *reinterpret_cast(capabilities.constData()); + if (caps.frequency & SBC_SAMPLING_FREQ_44100) { + caps.frequency = SBC_SAMPLING_FREQ_44100; + } else if (caps.frequency & SBC_SAMPLING_FREQ_48000) { + caps.frequency = SBC_SAMPLING_FREQ_48000; + } else { + break; + } + + if (caps.channel_mode & SBC_CHANNEL_MODE_STEREO) { + caps.channel_mode = SBC_CHANNEL_MODE_STEREO; + } else if (caps.channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) { + caps.channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO; + } else { + break; + } + + if (caps.block_length & SBC_BLOCK_LENGTH_16) { + caps.block_length = SBC_BLOCK_LENGTH_16; + } else if (caps.block_length & SBC_BLOCK_LENGTH_12) { + caps.block_length = SBC_BLOCK_LENGTH_12; + } else if (caps.block_length & SBC_BLOCK_LENGTH_8) { + caps.block_length = SBC_BLOCK_LENGTH_8; + } else if (caps.block_length & SBC_BLOCK_LENGTH_4) { + caps.block_length = SBC_BLOCK_LENGTH_4; + } else { + break; + } + + if (caps.subbands & SBC_SUBBANDS_8) { + caps.subbands = SBC_SUBBANDS_8; + } else if (caps.subbands & SBC_SUBBANDS_4) { + caps.subbands = SBC_SUBBANDS_4; + } else { + break; + } + + if (caps.allocation_method & SBC_ALLOCATION_LOUDNESS) { + caps.allocation_method = SBC_ALLOCATION_LOUDNESS; + } else if (caps.allocation_method & SBC_ALLOCATION_SNR) { + caps.allocation_method = SBC_ALLOCATION_SNR; + } else { + break; + } + + caps.min_bitpool = 2; + caps.max_bitpool = 53; + + const QByteArray configuration(reinterpret_cast(&caps), sizeof(caps)); + emit configurationSelected(capabilities, configuration); + request.accept(configuration); + return; + + break; + } + case MediaEndpoint::Codec::Aac: + if (capabilities.size() != sizeof(a2dp_aac_t)) { + emit configurationSelected(capabilities, QByteArray()); + request.reject(); + return; + } + + // TODO: implement AAC. However selectConfiguration seems not to be used by iOS nor Android. + emit configurationSelected(capabilities, QByteArray()); + request.reject(); + return; + + break; + } + + emit configurationSelected(capabilities, QByteArray()); + request.reject(); +} + +void MediaEndpoint::clearConfiguration(const QString &transportObjectPath) +{ + emit configurationCleared(transportObjectPath); +} + +void MediaEndpoint::release() +{ +} + +} // namespace BluezQt diff --git a/src/mediaendpoint.h b/src/mediaendpoint.h new file mode 100644 index 0000000..b32fc2c --- /dev/null +++ b/src/mediaendpoint.h @@ -0,0 +1,154 @@ +/* + * BluezQt - Asynchronous BlueZ wrapper library + * + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef BLUEZQT_MEDIAENDPOINT_H +#define BLUEZQT_MEDIAENDPOINT_H + +#include + +#include "bluezqt_export.h" +#include "request.h" + +class QDBusObjectPath; + +namespace BluezQt +{ + +/** + * @class BluezQt::MediaEndpoint MediaEndpoint.h + * + * Bluetooth MediaEndpoint. + * + * This class represents a Bluetooth MediaEndpoint. + */ +class BLUEZQT_EXPORT MediaEndpoint : public QObject +{ + Q_OBJECT + +public: + /** Role which this MediaEndpoint acts as. */ + enum class Role { + AudioSource, + AudioSink + }; + + /** Codec which this MediaEndpoint supports. */ + enum class Codec { + Sbc, + Aac + }; + + /** Configuration for MediaEndpoint construction. */ + struct Configuration { + Role role; + Codec codec; + }; + + /** + * Creates a new MediaEndpoint object. + * + * @param parent + */ + explicit MediaEndpoint(const Configuration &configuration, QObject *parent = nullptr); + + /** + * Destroys a MediaEndpoint object. + */ + ~MediaEndpoint(); + + /** + * D-Bus object path of the MediaEndpoint. + * + * The path where the MediaEndpoint will be registered. + * + * @note You must provide valid object path! + * + * @return object path of MediaEndpoint + */ + virtual QDBusObjectPath objectPath() const; + + /** + * Properties of the endpoint. + * + * @return Properties of the endpoint + */ + virtual const QVariantMap &properties() const; + + /** + * Set configuration for the transport. + * + * @param transport transport to be configured + * @param properties properties to be set for transport + */ + virtual void setConfiguration(const QString &transportObjectPath, const QVariantMap &properties); + + /** + * Select preferable configuration from the supported capabilities. + * + * @note There is no need to cache the selected configuration since on success + * the configuration is send back as parameter of SetConfiguration. + * + * @param capabilities supported capabilities + * @param request request to be used for sending reply + */ + virtual void selectConfiguration(const QByteArray &capabilities, const Request &request); + + /** + * Clear transport configuration. + */ + virtual void clearConfiguration(const QString &transportObjectPath); + + /** + * Indicates that the MediaEndpoint was unregistered. + * + * This method gets called when the Bluetooth daemon + * unregisters the MediaEndpoint. + * + * An MediaEndpoint can use it to do cleanup tasks. There is no need + * to unregister the MediaEndpoint, because when this method gets called + * it has already been unregistered. + */ + virtual void release(); + +Q_SIGNALS: + /** + * Indicates that configuration was selected. + */ + void configurationSelected(const QByteArray &capabilities, const QByteArray &configuration); + + /** + * Indicates that configuration was set for transport. + */ + void configurationSet(const QString &transportObjectPath, const QVariantMap &properties); + + /** + * Indicates that configuration was cleared for transport. + */ + void configurationCleared(const QString &transportObjectPath); + +private: + class MediaEndpointPrivate *const d; +}; + +} // namespace BluezQt + +#endif // BLUEZQT_MEDIAENDPOINT_H diff --git a/src/mediaendpoint_p.cpp b/src/mediaendpoint_p.cpp new file mode 100644 index 0000000..eca5508 --- /dev/null +++ b/src/mediaendpoint_p.cpp @@ -0,0 +1,72 @@ +/* + * BluezQt - Asynchronous Bluez wrapper library + * + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "mediaendpoint_p.h" + +#include "a2dp-codecs.h" +#include "services.h" + +namespace BluezQt +{ + +MediaEndpointPrivate::MediaEndpointPrivate(const MediaEndpoint::Configuration &configuration) + : m_configuration(configuration) +{ + init(configuration); +} + +void MediaEndpointPrivate::init(const MediaEndpoint::Configuration &configuration) +{ + const QString uuid = QStringLiteral("UUID"); + const QString codec = QStringLiteral("Codec"); + const QString capabilities = QStringLiteral("Capabilities"); + + QString objectPath = QStringLiteral("/MediaEndpoint"); + + switch (configuration.role) { + case MediaEndpoint::Role::AudioSource: + m_properties[uuid] = Services::AudioSource; + objectPath += QStringLiteral("/Source"); + break; + case MediaEndpoint::Role::AudioSink: + m_properties[uuid] = Services::AudioSink; + objectPath += QStringLiteral("/Sink"); + break; + } + + switch (configuration.codec) { + case MediaEndpoint::Codec::Sbc: + m_properties[codec] = QVariant::fromValue(uchar(A2DP_CODEC_SBC)); + m_properties[capabilities] = QByteArray(reinterpret_cast(&sbcCapabilities), sizeof(sbcCapabilities)); + objectPath += QStringLiteral("/Sbc"); + break; + case MediaEndpoint::Codec::Aac: + m_properties[codec] = QVariant::fromValue(uchar(A2DP_CODEC_MPEG24)); + m_properties[capabilities] = QByteArray(reinterpret_cast(&aacCapabilities), sizeof(aacCapabilities)); + objectPath += QStringLiteral("/Aac"); + break; + } + + m_objectPath.setPath(objectPath); +} + +} // namespace BluezQt diff --git a/src/mediaendpoint_p.h b/src/mediaendpoint_p.h new file mode 100644 index 0000000..676c8c4 --- /dev/null +++ b/src/mediaendpoint_p.h @@ -0,0 +1,48 @@ +/* + * BluezQt - Asynchronous Bluez wrapper library + * + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef BLUEZQT_MEDIAENDPOINT_P_H +#define BLUEZQT_MEDIAENDPOINT_P_H + +#include +#include + +#include "mediaendpoint.h" + +namespace BluezQt +{ + +class MediaEndpointPrivate +{ +public: + explicit MediaEndpointPrivate(const MediaEndpoint::Configuration &configuration); + + void init(const MediaEndpoint::Configuration &configuration); + + QVariantMap m_properties; + MediaEndpoint::Configuration m_configuration; + QDBusObjectPath m_objectPath; +}; + +} // namespace BluezQt + +#endif // BLUEZQT_MEDIAENDPOINT_P_H diff --git a/src/mediaendpointadaptor.cpp b/src/mediaendpointadaptor.cpp new file mode 100644 index 0000000..20ac25e --- /dev/null +++ b/src/mediaendpointadaptor.cpp @@ -0,0 +1,63 @@ +/* + * BluezQt - Asynchronous Bluez wrapper library + * + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "mediaendpointadaptor.h" +#include "mediaendpoint.h" +#include "request.h" + +#include +#include + +namespace BluezQt +{ + +MediaEndpointAdaptor::MediaEndpointAdaptor(MediaEndpoint *parent) + : QDBusAbstractAdaptor(parent) + , m_endpoint(parent) +{ +} + +void MediaEndpointAdaptor::SetConfiguration(const QDBusObjectPath &transport, const QVariantMap &properties) +{ + m_endpoint->setConfiguration(transport.path(), properties); +} + +QByteArray MediaEndpointAdaptor::SelectConfiguration(const QByteArray &capabilities, const QDBusMessage &msg) +{ + msg.setDelayedReply(true); + Request req(OrgBluezMediaEndpoint, msg); + + m_endpoint->selectConfiguration(capabilities, req); + return QByteArray(); +} + +void MediaEndpointAdaptor::ClearConfiguration(const QDBusObjectPath &transport) +{ + m_endpoint->clearConfiguration(transport.path()); +} + +void MediaEndpointAdaptor::Release() +{ + m_endpoint->release(); +} + +} // namespace BluezQt diff --git a/src/mediaendpointadaptor.h b/src/mediaendpointadaptor.h new file mode 100644 index 0000000..090f020 --- /dev/null +++ b/src/mediaendpointadaptor.h @@ -0,0 +1,56 @@ +/* + * BluezQt - Asynchronous Bluez wrapper library + * + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef BLUEZQT_MEDIAENDPOINTADAPTOR_H +#define BLUEZQT_MEDIAENDPOINTADAPTOR_H + +#include + +class QDBusMessage; +class QDBusObjectPath; + +namespace BluezQt +{ + +class MediaEndpoint; + +class MediaEndpointAdaptor : public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.bluez.MediaEndpoint1") + +public: + explicit MediaEndpointAdaptor(MediaEndpoint *parent); + +public Q_SLOTS: + void SetConfiguration(const QDBusObjectPath &transport, const QVariantMap &properties); + QByteArray SelectConfiguration(const QByteArray &capabilities, const QDBusMessage &msg); + void ClearConfiguration(const QDBusObjectPath &transport); + Q_NOREPLY void Release(); + +private: + MediaEndpoint *m_endpoint; +}; + +} // namespace BluezQt + +#endif // BLUEZQT_MEDIAENDPOINTADAPTOR_H diff --git a/src/pendingcall.h b/src/pendingcall.h index 2f42160..7accf1e 100644 --- a/src/pendingcall.h +++ b/src/pendingcall.h @@ -1,205 +1,206 @@ /* * BluezQt - Asynchronous BlueZ wrapper library * * Copyright (C) 2014-2015 David Rosca * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef BLUEZQT_PENDINGCALL_H #define BLUEZQT_PENDINGCALL_H #include #include "bluezqt_export.h" class QDBusPendingCall; namespace BluezQt { /** * @class BluezQt::PendingCall pendingcall.h * * Pending method call. * * This class represents a pending method call. It is a convenient wrapper * around QDBusPendingReply and QDBusPendingCallWatcher. */ class BLUEZQT_EXPORT PendingCall : public QObject { Q_OBJECT Q_PROPERTY(QVariant value READ value) Q_PROPERTY(QVariantList values READ values) Q_PROPERTY(int error READ error) Q_PROPERTY(QString errorText READ errorText) Q_PROPERTY(bool isFinished READ isFinished) Q_PROPERTY(QVariant userData READ userData WRITE setUserData) public: /** * Known error types. */ enum Error { /** Indicates there is no error. */ NoError = 0, /** Indicates that the device is not ready. */ NotReady = 1, /** Indicates that the action have failed. */ Failed = 2, /** Indicates that the action was rejected. */ Rejected = 3, /** Indicates that the action was canceled. */ Canceled = 4, /** Indicates that invalid arguments were passed. */ InvalidArguments = 5, /** Indicates that an agent or pairing record already exists. */ AlreadyExists = 6, /** Indicates that an agent, service or pairing operation does not exists. */ DoesNotExist = 7, /** Indicates that the action is already in progress. */ InProgress = 8, /** Indicates that the action is not in progress. */ NotInProgress = 9, /** Indicates that the device is already connected. */ AlreadyConnected = 10, /** Indicates that the connection to the device have failed. */ ConnectFailed = 11, /** Indicates that the device is not connected. */ NotConnected = 12, /** Indicates that the action is not supported. */ NotSupported = 13, /** Indicates that the caller is not authorized to do the action. */ NotAuthorized = 14, /** Indicates that the authentication was canceled. */ AuthenticationCanceled = 15, /** Indicates that the authentication have failed. */ AuthenticationFailed = 16, /** Indicates that the authentication was rejected. */ AuthenticationRejected = 17, /** Indicates that the authentication timed out. */ AuthenticationTimeout = 18, /** Indicates that the connection attempt have failed. */ ConnectionAttemptFailed = 19, /** Indicates an error with D-Bus. */ DBusError = 98, /** Indicates an internal error. */ InternalError = 99, /** Indicates an unknown error. */ UnknownError = 100 }; Q_ENUM(Error) /** * Destroys a PendingCall object. */ ~PendingCall(); /** * Returns a first return value of the call. * * @return first return value */ QVariant value() const; /** * Returns all values of the call. * * @return all return values */ QVariantList values() const; /** * Returns an error code. * * @return error code * @see Error */ int error() const; /** * Returns an error text. * * @return error text */ QString errorText() const; /** * Returns whether the call is finished. * * @return true if call is finished */ bool isFinished() const; /** * Waits for the call to finish. * * @warning This method blocks until the call finishes! */ void waitForFinished(); /** * Returns the user data of the call. * * @return user data of call */ QVariant userData() const; /** * Sets the user data of the call. * * @param userData user data */ void setUserData(const QVariant &userData); Q_SIGNALS: /** * Indicates that the call have finished. */ void finished(PendingCall *call); private: enum ReturnType { ReturnVoid, ReturnUint32, ReturnString, ReturnObjectPath, ReturnFileTransferList, ReturnTransferWithProperties }; explicit PendingCall(const QDBusPendingCall &call, ReturnType type, QObject *parent = nullptr); explicit PendingCall(Error error, const QString &errorText, QObject *parent = nullptr); class PendingCallPrivate *const d; friend class PendingCallPrivate; friend class Manager; friend class Adapter; friend class Device; + friend class Media; friend class MediaPlayer; friend class ObexManager; friend class ObexTransfer; friend class ObexSession; friend class ObexObjectPush; friend class ObexFileTransfer; }; } // namespace BluezQt #endif // BLUEZQT_PENDINGCALL_H diff --git a/src/request.cpp b/src/request.cpp index 0a36cf1..a8870ea 100644 --- a/src/request.cpp +++ b/src/request.cpp @@ -1,211 +1,212 @@ /* * BluezQt - Asynchronous Bluez wrapper library * * Copyright (C) 2014 David Rosca * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "request.h" #include "debug.h" #include "utils.h" #include #include #include namespace BluezQt { class RequestPrivate { public: RequestOriginatingType m_type; QDBusMessage m_message; bool sendMessage(const QDBusMessage &msg); QString interfaceName(); void acceptRequest(const QVariant &val); void rejectRequest(); void cancelRequest(); }; bool RequestPrivate::sendMessage(const QDBusMessage &msg) { switch (m_type) { case OrgBluezAgent: case OrgBluezProfile: return DBusConnection::orgBluez().send(msg); case OrgBluezObexAgent: return DBusConnection::orgBluezObex().send(msg); default: return false; } } QString RequestPrivate::interfaceName() { switch (m_type) { case OrgBluezAgent: return QStringLiteral("org.bluez.Agent1"); case OrgBluezProfile: return QStringLiteral("org.bluez.Profile1"); case OrgBluezObexAgent: return QStringLiteral("org.bluez.obex.Agent1"); default: return QString(); } } void RequestPrivate::acceptRequest(const QVariant &val) { QDBusMessage reply; if (val.isValid()) { reply = m_message.createReply(val); } else { reply = m_message.createReply(); } if (!sendMessage(reply)) { qCWarning(BLUEZQT) << "Request: Failed to put reply on DBus queue"; } } void RequestPrivate::rejectRequest() { const QDBusMessage &reply = m_message.createErrorReply(interfaceName() % QStringLiteral(".Rejected"), QStringLiteral("Rejected")); if (!sendMessage(reply)) { qCWarning(BLUEZQT) << "Request: Failed to put reply on DBus queue"; } } void RequestPrivate::cancelRequest() { const QDBusMessage &reply = m_message.createErrorReply(interfaceName() % QStringLiteral(".Canceled"), QStringLiteral("Canceled")); if (!sendMessage(reply)) { qCWarning(BLUEZQT) << "Request: Failed to put reply on DBus queue"; } } // T template Request::Request() : d(new RequestPrivate) { } template Request::Request(RequestOriginatingType type, const QDBusMessage &message) : d(new RequestPrivate) { d->m_type = type; d->m_message = message; } template Request::~Request() { } template Request::Request(const Request &other) : d(other.d) { } template Request &Request::operator=(const Request &other) { if (d != other.d) { d = other.d; } return *this; } template void Request::accept(T returnValue) const { d->acceptRequest(returnValue); } template void Request::reject() const { d->rejectRequest(); } template void Request::cancel() const { d->cancelRequest(); } // void Request::Request() : d(new RequestPrivate) { } Request::Request(RequestOriginatingType type, const QDBusMessage &message) : d(new RequestPrivate) { d->m_type = type; d->m_message = message; } Request::~Request() { } Request::Request(const Request &other) : d(other.d) { } Request &Request::operator=(const Request &other) { if (d != other.d) { d = other.d; } return *this; } void Request::accept() const { d->acceptRequest(QVariant()); } void Request::reject() const { d->rejectRequest(); } void Request::cancel() const { d->cancelRequest(); } // Generate classes template class Request; template class Request; template class Request; +template class Request; } // namespace BluezQt diff --git a/src/request.h b/src/request.h index 3946378..acf34aa 100644 --- a/src/request.h +++ b/src/request.h @@ -1,145 +1,147 @@ /* * BluezQt - Asynchronous BlueZ wrapper library * * Copyright (C) 2014-2015 David Rosca * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef BLUEZQT_REQUEST_H #define BLUEZQT_REQUEST_H #include #include "bluezqt_export.h" class QDBusMessage; namespace BluezQt { enum RequestOriginatingType { OrgBluezAgent, OrgBluezProfile, - OrgBluezObexAgent + OrgBluezObexAgent, + OrgBluezMediaEndpoint }; /** * @class BluezQt::Request request.h * * D-Bus request. * * This class represents a request from a Bluetooth daemon. It is a convenient * wrapper around QDBusMessage and easily allows sending replies and handling errors. * * @see Agent, ObexAgent, Profile */ template class BLUEZQT_EXPORT Request { public: /** * Creates a new Request object. */ explicit Request(); /** * Destroys a Request object. */ virtual ~Request(); /** * Copy constructor. * * @param other */ Request(const Request &other); /** * Copy assignment operator. * * @param other */ Request &operator=(const Request &other); /** * Accepts the request. * * This method should be called to send a reply to indicate * the request was accepted. * * In case the request is of type void, this method does not * take any parameter. * * @param returnValue return value of request */ void accept(T returnValue) const; /** * Rejects the request. * * This method should be called to send an error reply to * indicate the request was rejected. */ void reject() const; /** * Cancels the request. * * This method should be called to send an error reply to * indicate the request was canceled. */ void cancel() const; private: explicit Request(RequestOriginatingType type, const QDBusMessage &message); QSharedPointer d; friend class AgentAdaptor; friend class ObexAgentAdaptor; friend class ProfileAdaptor; + friend class MediaEndpointAdaptor; }; // void template<> class BLUEZQT_EXPORT Request { public: explicit Request(); virtual ~Request(); Request(const Request &other); Request &operator=(const Request &other); void accept() const; void reject() const; void cancel() const; private: explicit Request(RequestOriginatingType type, const QDBusMessage &message); QSharedPointer d; friend class AgentAdaptor; friend class ObexAgentAdaptor; friend class ProfileAdaptor; }; } // namespace BluezQt #endif // BLUEZQT_REQUEST_H diff --git a/src/services.h b/src/services.h index 60179ec..2a4144c 100644 --- a/src/services.h +++ b/src/services.h @@ -1,68 +1,69 @@ /* * BluezQt - Asynchronous BlueZ wrapper library * * Copyright (C) 2014-2015 David Rosca * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef BLUEZQT_SERVICES_H #define BLUEZQT_SERVICES_H #include namespace BluezQt { /** Service UUIDs. */ namespace Services { const QString ServiceDiscoveryServer = QLatin1String("00001000-0000-1000-8000-00805F9B34FB"); const QString SerialPort = QLatin1String("00001101-0000-1000-8000-00805F9B34FB"); const QString DialupNetworking = QLatin1String("00001103-0000-1000-8000-00805F9B34FB"); const QString ObexObjectPush = QLatin1String("00001105-0000-1000-8000-00805F9B34FB"); const QString ObexFileTransfer = QLatin1String("00001106-0000-1000-8000-00805F9B34FB"); const QString Headset = QLatin1String("00001108-0000-1000-8000-00805F9B34FB"); const QString AudioSource = QLatin1String("0000110A-0000-1000-8000-00805F9B34FB"); +const QString AudioSink = QLatin1String("0000110B-0000-1000-8000-00805F9B34FB"); const QString AudioVideoRemoteControlTarget = QLatin1String("0000110C-0000-1000-8000-00805F9B34FB"); const QString AdvancedAudioDistribution = QLatin1String("0000110D-0000-1000-8000-00805F9B34FB"); const QString AudioVideoRemoteControl = QLatin1String("0000110E-0000-1000-8000-00805F9B34FB"); const QString HeadsetAudioGateway = QLatin1String("00001112-0000-1000-8000-00805F9B34FB"); const QString Panu = QLatin1String("00001115-0000-1000-8000-00805F9B34FB"); const QString Nap = QLatin1String("00001116-0000-1000-8000-00805F9B34FB"); const QString Handsfree = QLatin1String("0000111E-0000-1000-8000-00805F9B34FB"); const QString HandsfreeAudioGateway = QLatin1String("0000111F-0000-1000-8000-00805F9B34FB"); const QString HumanInterfaceDevice = QLatin1String("00001124-0000-1000-8000-00805F9B34FB"); const QString SimAccess = QLatin1String("0000112D-0000-1000-8000-00805F9B34FB"); const QString PhonebookAccessServer = QLatin1String("0000112F-0000-1000-8000-00805F9B34FB"); const QString MessageAccessServer = QLatin1String("00001132-0000-1000-8000-00805F9B34FB"); const QString PnpInformation = QLatin1String("00001200-0000-1000-8000-00805F9B34FB"); // Bluetooth Low Energy const QString GenericAcces = QLatin1String("00001800-0000-1000-8000-00805f9b34fb"); const QString GenericAttribute = QLatin1String("00001801-0000-1000-8000-00805f9b34fb"); const QString ImmediateAlert = QLatin1String("00001802-0000-1000-8000-00805f9b34fb"); const QString LinkLoss = QLatin1String("00001803-0000-1000-8000-00805f9b34fb"); const QString TxPower = QLatin1String("00001804-0000-1000-8000-00805f9b34fb"); const QString HeartRate = QLatin1String("0000180d-0000-1000-8000-00805f9b34fb"); } } // namespace BluezQt #endif // BLUEZQT_SERVICES_H diff --git a/src/types.h b/src/types.h index 8afcf67..97a254e 100644 --- a/src/types.h +++ b/src/types.h @@ -1,62 +1,64 @@ /* * BluezQt - Asynchronous BlueZ wrapper library * * Copyright (C) 2015 David Rosca * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef BLUEZQT_TYPES_H #define BLUEZQT_TYPES_H #include namespace BluezQt { class Manager; class Adapter; class Device; class Input; +class Media; class MediaPlayer; class MediaPlayerTrack; class Agent; class DevicesModel; class InitManagerJob; class Profile; class PendingCall; class ObexManager; class ObexSession; class ObexTransfer; class InitObexManagerJob; class ObexAgent; class ObexFileTransfer; class ObexFileTransferEntry; class ObexObjectPush; typedef QSharedPointer ManagerPtr; typedef QSharedPointer AdapterPtr; typedef QSharedPointer DevicePtr; typedef QSharedPointer InputPtr; +typedef QSharedPointer MediaPtr; typedef QSharedPointer MediaPlayerPtr; typedef QSharedPointer ObexManagerPtr; typedef QSharedPointer ObexSessionPtr; typedef QSharedPointer ObexTransferPtr; } // namespace BluezQt #endif // BLUEZQT_TYPES_H diff --git a/src/utils.cpp b/src/utils.cpp index 9ca3d6d..35452ae 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1,297 +1,304 @@ /* * BluezQt - Asynchronous Bluez wrapper library * * Copyright (C) 2014-2015 David Rosca * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "utils.h" #include "manager.h" #include "obexmanager.h" #include "bluezqt_export.h" #include #include #include namespace BluezQt { class GlobalData { public: explicit GlobalData(); bool testRun; QString orgFreedesktopDBus; QString orgFreedesktopDBusProperties; QString orgBluez; QString orgBluezAdapter1; QString orgBluezDevice1; QString orgBluezInput1; + QString orgBluezMedia1; QString orgBluezMediaPlayer1; QString orgBluezAgentManager1; QString orgBluezProfileManager1; QString orgBluezObex; QString orgBluezObexClient1; QString orgBluezObexAgentManager1; QString orgBluezObexSession1; QString orgBluezObexTransfer1; QPointer manager; QPointer obexManager; }; GlobalData::GlobalData() { testRun = false; orgFreedesktopDBus = QStringLiteral("org.freedesktop.DBus"); orgFreedesktopDBusProperties = QStringLiteral("org.freedesktop.DBus.Properties"); orgBluez = QStringLiteral("org.bluez"); orgBluezAdapter1 = QStringLiteral("org.bluez.Adapter1"); orgBluezDevice1 = QStringLiteral("org.bluez.Device1"); orgBluezInput1 = QStringLiteral("org.bluez.Input1"); + orgBluezMedia1 = QStringLiteral("org.bluez.Media1"); orgBluezMediaPlayer1 = QStringLiteral("org.bluez.MediaPlayer1"); orgBluezAgentManager1 = QStringLiteral("org.bluez.AgentManager1"); orgBluezProfileManager1 = QStringLiteral("org.bluez.ProfileManager1"); orgBluezObex = QStringLiteral("org.bluez.obex"); orgBluezObexClient1 = QStringLiteral("org.bluez.obex.Client1"); orgBluezObexAgentManager1 = QStringLiteral("org.bluez.obex.AgentManager1"); orgBluezObexSession1 = QStringLiteral("org.bluez.obex.Session1"); orgBluezObexTransfer1 = QStringLiteral("org.bluez.obex.Transfer1"); } Q_GLOBAL_STATIC(GlobalData, globalData) // For fakebluez tests BLUEZQT_EXPORT void bluezqt_initFakeBluezTestRun() { globalData->testRun = true; globalData->orgBluez = QStringLiteral("org.kde.bluezqt.fakebluez"); globalData->orgBluezObex = QStringLiteral("org.kde.bluezqt.fakebluez"); } QString Strings::orgFreedesktopDBus() { return globalData->orgFreedesktopDBus; } QString Strings::orgFreedesktopDBusProperties() { return globalData->orgFreedesktopDBusProperties; } QString Strings::orgBluez() { return globalData->orgBluez; } QString Strings::orgBluezAdapter1() { return globalData->orgBluezAdapter1; } QString Strings::orgBluezDevice1() { return globalData->orgBluezDevice1; } QString Strings::orgBluezInput1() { return globalData->orgBluezInput1; } +QString Strings::orgBluezMedia1() +{ + return globalData->orgBluezMedia1; +} + QString Strings::orgBluezMediaPlayer1() { return globalData->orgBluezMediaPlayer1; } QString Strings::orgBluezAgentManager1() { return globalData->orgBluezAgentManager1; } QString Strings::orgBluezProfileManager1() { return globalData->orgBluezProfileManager1; } QString Strings::orgBluezObex() { return globalData->orgBluezObex; } QString Strings::orgBluezObexClient1() { return globalData->orgBluezObexClient1; } QString Strings::orgBluezObexAgentManager1() { return globalData->orgBluezObexAgentManager1; } QString Strings::orgBluezObexSession1() { return globalData->orgBluezObexSession1; } QString Strings::orgBluezObexTransfer1() { return globalData->orgBluezObexTransfer1; } QDBusConnection DBusConnection::orgBluez() { if (globalData->testRun) { return QDBusConnection::sessionBus(); } return QDBusConnection::systemBus(); } QDBusConnection DBusConnection::orgBluezObex() { return QDBusConnection::sessionBus(); } Manager *Instance::manager() { return globalData->manager; } void Instance::setManager(Manager *manager) { globalData->manager = manager; } ObexManager *Instance::obexManager() { return globalData->obexManager; } void Instance::setObexManager(ObexManager *obexManager) { globalData->obexManager = obexManager; } QStringList stringListToUpper(const QStringList &list) { QStringList converted; converted.reserve(list.size()); Q_FOREACH (const QString &str, list) { converted.append(str.toUpper()); } return converted; } Device::Type classToType(quint32 classNum) { switch ((classNum & 0x1f00) >> 8) { case 0x01: return Device::Computer; case 0x02: switch ((classNum & 0xfc) >> 2) { case 0x04: return Device::Modem; default: return Device::Phone; } case 0x03: return Device::Network; case 0x04: switch ((classNum & 0xfc) >> 2) { case 0x01: case 0x02: return Device::Headset; case 0x06: return Device::Headphones; default: return Device::AudioVideo; } case 0x05: switch ((classNum & 0xc0) >> 6) { case 0x00: switch ((classNum & 0x1e) >> 2) { case 0x01: case 0x02: return Device::Joypad; } break; case 0x01: return Device::Keyboard; case 0x02: switch ((classNum & 0x1e) >> 2) { case 0x05: return Device::Tablet; default: return Device::Mouse; } } return Device::Peripheral; case 0x06: if (classNum & 0x80) { return Device::Printer; } else if (classNum & 0x20) { return Device::Camera; } return Device::Imaging; case 0x07: return Device::Wearable; case 0x08: return Device::Toy; case 0x09: return Device::Health; default: return Device::Uncategorized; } } Device::Type appearanceToType(quint16 appearance) { switch ((appearance & 0xffc0) >> 6) { case 0x00: return Device::Uncategorized; case 0x01: // Generic Phone return Device::Phone; case 0x02: // Generic Computer return Device::Computer; case 0x05: // Generic Display return Device::AudioVideo; case 0x0a: // Generic Media Player return Device::AudioVideo; case 0x0b: // Generic Barcode Scanner return Device::Peripheral; case 0x0f: // Generic HID switch (appearance & 0x3f) { case 0x01: // Keyboard return Device::Keyboard; case 0x02: // Mouse return Device::Mouse; case 0x03: // Joystick case 0x04: // Gamepad return Device::Joypad; case 0x05: // Digitizer Tablet return Device::Tablet; case 0x08: // Barcode Scanner return Device::Peripheral; } default: return Device::Uncategorized; } } } // namespace BluezQt diff --git a/src/utils.h b/src/utils.h index 08cb6b9..3e017b5 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,80 +1,81 @@ /* * BluezQt - Asynchronous Bluez wrapper library * * Copyright (C) 2014-2015 David Rosca * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef BLUEZQT_UTILS_H #define BLUEZQT_UTILS_H #include "device.h" class QString; class QStringList; class QDBusConnection; namespace BluezQt { namespace Strings { QString orgFreedesktopDBus(); QString orgFreedesktopDBusProperties(); QString orgBluez(); QString orgBluezAdapter1(); QString orgBluezDevice1(); QString orgBluezInput1(); +QString orgBluezMedia1(); QString orgBluezMediaPlayer1(); QString orgBluezAgentManager1(); QString orgBluezProfileManager1(); QString orgBluezObex(); QString orgBluezObexClient1(); QString orgBluezObexAgentManager1(); QString orgBluezObexSession1(); QString orgBluezObexTransfer1(); } namespace DBusConnection { QDBusConnection orgBluez(); QDBusConnection orgBluezObex(); } namespace Instance { Manager *manager(); void setManager(Manager *manager); ObexManager *obexManager(); void setObexManager(ObexManager *obexManager); } QStringList stringListToUpper(const QStringList &list); Device::Type classToType(quint32 classNum); Device::Type appearanceToType(quint16 appearance); } // namespace BluezQt #endif // BLUEZQT_UTILS_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a448df0..66e4985 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,15 +1,16 @@ include(ECMMarkAsTest) macro(bluezqt_executable_tests) foreach(_testname ${ARGN}) add_executable(${_testname} ${_testname}.cpp) target_link_libraries(${_testname} Qt5::DBus Qt5::Network Qt5::Test KF5BluezQt) ecm_mark_as_test(${_testname}) endforeach(_testname) endmacro() bluezqt_executable_tests( adaptersreceiver devicereceiver chatprofile + mediaendpointconnector ) diff --git a/tests/mediaendpointconnector.cpp b/tests/mediaendpointconnector.cpp new file mode 100644 index 0000000..fd78b06 --- /dev/null +++ b/tests/mediaendpointconnector.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "mediaendpointconnector.h" + +#include +#include +#include + +#include "device.h" +#include "initmanagerjob.h" +#include "media.h" +#include "mediaendpoint.h" +#include "services.h" + +using namespace BluezQt; + +class NoInputNoOutputAgentPrivate +{ +public: + QStringList allowedUuids; +}; + +NoInputNoOutputAgent::NoInputNoOutputAgent(const QStringList &uuids, QObject *parent) + : Agent(parent) + , d(new NoInputNoOutputAgentPrivate) +{ + d->allowedUuids = uuids; +} + +NoInputNoOutputAgent::~NoInputNoOutputAgent() +{ + delete d; +} + +QDBusObjectPath NoInputNoOutputAgent::objectPath() const +{ + return QDBusObjectPath("/Agent/NoInputNoOutput"); +} + +Agent::Capability NoInputNoOutputAgent::capability() const +{ + return Agent::NoInputNoOutput; +} + +void NoInputNoOutputAgent::authorizeService(DevicePtr device, const QString &uuid, const Request<> &request) +{ + Q_UNUSED(device) + + d->allowedUuids.contains(uuid) ? request.accept() : request.reject(); + emit serviceAuthorized(device, uuid, d->allowedUuids.contains(uuid)); +} + +MediaEndpointConnector::MediaEndpointConnector(Manager *manager, QObject *parent) + : QObject(parent) + , m_manager(manager) +{ + NoInputNoOutputAgent *agent = new NoInputNoOutputAgent({Services::AdvancedAudioDistribution, Services::AudioVideoRemoteControl}); + connect(agent, &NoInputNoOutputAgent::serviceAuthorized, this, &MediaEndpointConnector::onServiceAuthorized); + manager->registerAgent(agent); + manager->requestDefaultAgent(agent); + + MediaEndpoint *sbcSink = new MediaEndpoint({MediaEndpoint::Role::AudioSink, MediaEndpoint::Codec::Sbc}, manager); + MediaEndpoint *aacSink = new MediaEndpoint({MediaEndpoint::Role::AudioSink, MediaEndpoint::Codec::Aac}, manager); + connect(sbcSink, &MediaEndpoint::configurationSelected, this, &MediaEndpointConnector::onConfigurationSelected); + connect(aacSink, &MediaEndpoint::configurationSelected, this, &MediaEndpointConnector::onConfigurationSelected); + connect(sbcSink, &MediaEndpoint::configurationSet, this, &MediaEndpointConnector::onConfigurationSet); + connect(aacSink, &MediaEndpoint::configurationSet, this, &MediaEndpointConnector::onConfigurationSet); + connect(sbcSink, &MediaEndpoint::configurationCleared, this, &MediaEndpointConnector::onConfigurationCleared); + connect(aacSink, &MediaEndpoint::configurationCleared, this, &MediaEndpointConnector::onConfigurationCleared); + manager->media()->registerEndpoint(sbcSink); + manager->media()->registerEndpoint(aacSink); +} + +void MediaEndpointConnector::onServiceAuthorized(BluezQt::DevicePtr device, const QString &uuid, bool allowed) +{ + qDebug() << (allowed ? "Accepted" : "Rejected") << "service:" << uuid << "from" << device->friendlyName(); +} + +void MediaEndpointConnector::onConfigurationSelected(const QByteArray &capabilities, const QByteArray &configuration) +{ + if (configuration.isEmpty()) { + qDebug() << "No useable configuration found for capabilities:" << capabilities; + } else { + qDebug() << "Selected configuration:" << configuration << "for capabilities:" << capabilities; + } +} + +void MediaEndpointConnector::onConfigurationSet(const QString& transportObjectPath, const QVariantMap& properties) +{ + qDebug() << "Set configuration for transport:" << transportObjectPath << "to:" << properties; +} + +void MediaEndpointConnector::onConfigurationCleared(const QString& transportObjectPath) +{ + qDebug() << "Cleared configuration for transport:" << transportObjectPath; +} + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + + qDebug() << "Waiting for bluetooth audio source to connect. Ctrl + C to cancel..."; + + Manager *manager = new Manager(); + InitManagerJob *initJob = manager->init(); + initJob->exec(); + if (initJob->error()) { + qWarning() << "Error initializing manager:" << initJob->errorText(); + return 1; + } + + new MediaEndpointConnector(manager); + + return app.exec(); +} diff --git a/tests/mediaendpointconnector.h b/tests/mediaendpointconnector.h new file mode 100644 index 0000000..f4d8f9f --- /dev/null +++ b/tests/mediaendpointconnector.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef MEDIAENDPOINTCONNECTOR_H +#define MEDIAENDPOINTCONNECTOR_H + +#include + +#include "agent.h" +#include "manager.h" +#include "types.h" + +class NoInputNoOutputAgent : public BluezQt::Agent +{ + Q_OBJECT + +public: + /** + * Creates a new NoInputNoOutputAgent object. + * + * @param parent + */ + explicit NoInputNoOutputAgent(const QStringList &uuids, QObject *parent = nullptr); + + /** + * Destroys a NoInputNoOutputAgent object. + */ + ~NoInputNoOutputAgent() override; + +Q_SIGNALS: + /** + * Indicates that a service was authorized. + */ + void serviceAuthorized(BluezQt::DevicePtr device, const QString &uuid, bool allowed); + +private: + QDBusObjectPath objectPath() const override; + Capability capability() const override; + void authorizeService(BluezQt::DevicePtr device, const QString &uuid, const BluezQt::Request<> &request) override; + + class NoInputNoOutputAgentPrivate *const d; +}; + +class MediaEndpointConnector : public QObject +{ + Q_OBJECT + +public: + explicit MediaEndpointConnector(BluezQt::Manager *manager, QObject *parent = nullptr); + +private: + void onServiceAuthorized(BluezQt::DevicePtr device, const QString &uuid, bool allowed); + void onConfigurationSelected(const QByteArray &capabilities, const QByteArray &configuration); + void onConfigurationSet(const QString& transportObjectPath, const QVariantMap& properties); + void onConfigurationCleared(const QString& transportObjectPath); + + BluezQt::Manager *m_manager; +}; + +#endif // MEDIAENDPOINTCONNECTOR_H