From 80bea1f32b92c70c21c96762e31c726cb49e180f Mon Sep 17 00:00:00 2001 From: Alex Blasche Date: Wed, 25 Jul 2018 14:28:22 +0200 Subject: Implement QBluetoothSocket Client support for Bluez5 DBus The new code is not yet enabled in QBluetoothSocket because the server side implementation is still missing. Task-number: QTBUG-68550 Change-Id: I2f94dac9f7665c8d4ba5d675e91c5ab81af8504a Reviewed-by: Oliver Wolff Reviewed-by: Alex Blasche --- src/bluetooth/bluetooth.pro | 2 +- src/bluetooth/bluez/bluez.pri | 2 + src/bluetooth/bluez/profile1context.cpp | 72 +++++ src/bluetooth/bluez/profile1context_p.h | 96 ++++++ src/bluetooth/qbluetoothsocket.cpp | 6 +- src/bluetooth/qbluetoothsocket_bluezdbus.cpp | 451 ++++++++++++++++++++++++++- src/bluetooth/qbluetoothsocket_bluezdbus_p.h | 26 ++ 7 files changed, 640 insertions(+), 15 deletions(-) create mode 100644 src/bluetooth/bluez/profile1context.cpp create mode 100644 src/bluetooth/bluez/profile1context_p.h (limited to 'src') diff --git a/src/bluetooth/bluetooth.pro b/src/bluetooth/bluetooth.pro index 884dbebf..72208a87 100644 --- a/src/bluetooth/bluetooth.pro +++ b/src/bluetooth/bluetooth.pro @@ -87,7 +87,7 @@ win32 { qtConfig(bluez) { QT_PRIVATE = concurrent - QT_FOR_PRIVATE += dbus + QT_FOR_PRIVATE += dbus network # do not link against QtNetwork but use inline qt_safe_* functions INCLUDEPATH += $$QT.network_private.includes diff --git a/src/bluetooth/bluez/bluez.pri b/src/bluetooth/bluez/bluez.pri index 9fb9242e..4201f104 100644 --- a/src/bluetooth/bluez/bluez.pri +++ b/src/bluetooth/bluez/bluez.pri @@ -15,6 +15,7 @@ HEADERS += bluez/manager_p.h \ bluez/device1_bluez5_p.h \ bluez/profilemanager1_p.h \ bluez/profile1_p.h \ + bluez/profile1context_p.h \ bluez/obex_client1_bluez5_p.h \ bluez/obex_objectpush1_bluez5_p.h \ bluez/obex_transfer1_bluez5_p.h \ @@ -43,6 +44,7 @@ SOURCES += bluez/manager.cpp \ bluez/bluez5_helper.cpp \ bluez/profilemanager1.cpp \ bluez/profile1.cpp \ + bluez/profile1context.cpp \ bluez/obex_client1_bluez5.cpp \ bluez/obex_objectpush1_bluez5.cpp \ bluez/obex_transfer1_bluez5.cpp \ diff --git a/src/bluetooth/bluez/profile1context.cpp b/src/bluetooth/bluez/profile1context.cpp new file mode 100644 index 00000000..5eeec56a --- /dev/null +++ b/src/bluetooth/bluez/profile1context.cpp @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "profile1context_p.h" + +#include + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ) + +OrgBluezProfile1ContextInterface::OrgBluezProfile1ContextInterface(QObject *parent) : QObject(parent) +{ +} + +void OrgBluezProfile1ContextInterface::NewConnection(const QDBusObjectPath &/*remotePath*/, + const QDBusUnixFileDescriptor &descriptor, + const QVariantMap &/*properties*/) +{ + qCDebug(QT_BT_BLUEZ) << "Profile Context: New Connection"; + emit newConnection(descriptor); + setDelayedReply(false); +} + +void OrgBluezProfile1ContextInterface::RequestDisconnection(const QDBusObjectPath &/*remotePath*/) +{ + qCDebug(QT_BT_BLUEZ) << "Profile Context: Request Disconnection"; + setDelayedReply(false); +} + +void OrgBluezProfile1ContextInterface::Release() +{ + qCDebug(QT_BT_BLUEZ) << "Profile Context: Release"; +} + +QT_END_NAMESPACE diff --git a/src/bluetooth/bluez/profile1context_p.h b/src/bluetooth/bluez/profile1context_p.h new file mode 100644 index 00000000..bd7b7df3 --- /dev/null +++ b/src/bluetooth/bluez/profile1context_p.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PROFILECONTEXT_P_H +#define PROFILECONTEXT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class OrgBluezProfile1ContextInterface : public QObject, protected QDBusContext +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.bluez.Profile1") + Q_CLASSINFO("D-Bus Introspection", "" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" + "") +public: + explicit OrgBluezProfile1ContextInterface(QObject *parent = nullptr); + +Q_SIGNALS: + void newConnection(const QDBusUnixFileDescriptor &fd); + +public Q_SLOTS: + void NewConnection(const QDBusObjectPath &, const QDBusUnixFileDescriptor &, + const QVariantMap &); + void RequestDisconnection(const QDBusObjectPath &); + Q_NOREPLY void Release(); +}; + +QT_END_NAMESPACE + +#endif // PROFILECONTEXT_P_H diff --git a/src/bluetooth/qbluetoothsocket.cpp b/src/bluetooth/qbluetoothsocket.cpp index dae844d7..4bd59095 100644 --- a/src/bluetooth/qbluetoothsocket.cpp +++ b/src/bluetooth/qbluetoothsocket.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2018 The Qt Company Ltd. ** Copyright (C) 2016 BlackBerry Limited. All rights reserved. ** Contact: https://www.qt.io/licensing/ ** @@ -41,6 +41,7 @@ #include "qbluetoothsocket.h" #if QT_CONFIG(bluez) #include "qbluetoothsocket_bluez_p.h" +#include "qbluetoothsocket_bluezdbus_p.h" #elif defined(QT_ANDROID_BLUETOOTH) #include "qbluetoothsocket_android_p.h" #elif defined(QT_WINRT_BLUETOOTH) @@ -259,6 +260,7 @@ QBluetoothSocket::QBluetoothSocket(QBluetoothServiceInfo::Protocol socketType, Q { #if QT_CONFIG(bluez) d_ptr = new QBluetoothSocketPrivateBluez(); + //d_ptr = new QBluetoothSocketPrivateBluezDBus(); #elif defined(QT_ANDROID_BLUETOOTH) d_ptr = new QBluetoothSocketPrivateAndroid(); #elif defined(QT_WINRT_BLUETOOTH) @@ -282,6 +284,7 @@ QBluetoothSocket::QBluetoothSocket(QObject *parent) { #if QT_CONFIG(bluez) d_ptr = new QBluetoothSocketPrivateBluez(); + //d_ptr = new QBluetoothSocketPrivateBluezDBus(); #elif defined(QT_ANDROID_BLUETOOTH) d_ptr = new QBluetoothSocketPrivateAndroid(); #elif defined(QT_WINRT_BLUETOOTH) @@ -724,6 +727,7 @@ void QBluetoothSocket::close() d->close(); #ifndef QT_ANDROID_BLUETOOTH + // TODO Add return type to d->close() & d->abort() to detect when to emit below signals //Android closes when the Java event loop comes around setSocketState(UnconnectedState); emit readChannelFinished(); diff --git a/src/bluetooth/qbluetoothsocket_bluezdbus.cpp b/src/bluetooth/qbluetoothsocket_bluezdbus.cpp index bdcc89ef..52193a1a 100644 --- a/src/bluetooth/qbluetoothsocket_bluezdbus.cpp +++ b/src/bluetooth/qbluetoothsocket_bluezdbus.cpp @@ -40,10 +40,40 @@ #include "qbluetoothsocket.h" #include "qbluetoothsocket_bluezdbus_p.h" +#include "bluez/bluez_data_p.h" +#include "bluez/bluez5_helper_p.h" +#include "bluez/adapter1_bluez5_p.h" +#include "bluez/device1_bluez5_p.h" +#include "bluez/objectmanager_p.h" +#include "bluez/profile1_p.h" +#include "bluez/profile1context_p.h" +#include "bluez/profilemanager1_p.h" + +#include +#include + +#include +#include + +#include + +#include + QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ) + +static const QLatin1String profilePathTemplate("/qt/btsocket/%1%2/%3"); + QBluetoothSocketPrivateBluezDBus::QBluetoothSocketPrivateBluezDBus() { secFlags = QBluetooth::NoSecurity; + + profileManager = new OrgBluezProfileManager1Interface( + QStringLiteral("org.bluez"), + QStringLiteral("/org/bluez"), + QDBusConnection::systemBus(), + this); } QBluetoothSocketPrivateBluezDBus::~QBluetoothSocketPrivateBluezDBus() @@ -52,71 +82,361 @@ QBluetoothSocketPrivateBluezDBus::~QBluetoothSocketPrivateBluezDBus() bool QBluetoothSocketPrivateBluezDBus::ensureNativeSocket(QBluetoothServiceInfo::Protocol type) { - socketType = type; + switch (type) { + case QBluetoothServiceInfo::UnknownProtocol: + break; + case QBluetoothServiceInfo::RfcommProtocol: + case QBluetoothServiceInfo::L2capProtocol: + socketType = type; + return true; + } + return false; } -void QBluetoothSocketPrivateBluezDBus::connectToServiceHelper(const QBluetoothAddress &address, quint16 port, QIODevice::OpenMode openMode) +void QBluetoothSocketPrivateBluezDBus::connectToServiceHelper( + const QBluetoothAddress &address, quint16 port, QIODevice::OpenMode openMode) { + // TODO Remove when Bluez4 support dropped + // Only used by QBluetoothSocketPrivateBluez Q_UNUSED(openMode); Q_UNUSED(address); Q_UNUSED(port); } +static QString findRemoteDevicePath(const QBluetoothAddress &address) +{ + OrgFreedesktopDBusObjectManagerInterface manager(QStringLiteral("org.bluez"), + QStringLiteral("/"), + QDBusConnection::systemBus()); + + bool ok = false; + const QString adapterPath = findAdapterForAddress(QBluetoothAddress(), &ok); + if (!ok) + return QString(); + + auto reply = manager.GetManagedObjects(); + reply.waitForFinished(); + if (reply.isError()) + return QString(); + + QString remoteDevicePath; + + ManagedObjectList objectList = reply.value(); + for (ManagedObjectList::const_iterator it = objectList.constBegin(); + it != objectList.constEnd(); ++it) { + const QDBusObjectPath &path = it.key(); + const InterfaceList &ifaceList = it.value(); + + for (InterfaceList::const_iterator ifaceIter = ifaceList.constBegin(); + ifaceIter != ifaceList.constEnd(); ++ifaceIter) { + if (ifaceIter.key() == QStringLiteral("org.bluez.Device1")) { + if (path.path().indexOf(adapterPath) != 0) + continue; // devices whose path does not start with same path we skip + + OrgBluezDevice1Interface device(QStringLiteral("org.bluez"), + path.path(), QDBusConnection::systemBus()); + if (device.adapter().path() != adapterPath) + continue; + + const QBluetoothAddress btAddress(device.address()); + if (btAddress.isNull() || btAddress != address) + continue; + + return path.path(); + } + } + } + + return QString(); +} + +void QBluetoothSocketPrivateBluezDBus::connectToServiceHelper( + const QBluetoothAddress &address, const QBluetoothUuid &uuid, + QIODevice::OpenMode openMode) +{ + Q_Q(QBluetoothSocket); + + int i = 0; + bool success = false; + profileUuid = uuid.toString(QUuid::WithoutBraces); + + if (profileContext) { + qCDebug(QT_BT_BLUEZ) << "Profile context still active. close socket first."; + q->setSocketError(QBluetoothSocket::UnknownSocketError); + return; + } + + + profileContext = new OrgBluezProfile1ContextInterface(this); + connect(profileContext, &OrgBluezProfile1ContextInterface::newConnection, + this, &QBluetoothSocketPrivateBluezDBus::remoteConnected); + + for (i = 0; i < 10 && !success; i++) { + // profile registration might fail in case other service uses same path + // try 10 times and otherwise abort + + profilePath = QString(profilePathTemplate). + arg(sanitizeNameForDBus(QCoreApplication::applicationName())). + arg(QCoreApplication::applicationPid()). + arg(QRandomGenerator::global()->generate()); + + success = QDBusConnection::systemBus().registerObject( + profilePath, profileContext, QDBusConnection::ExportAllSlots); + } + + if (!success) { + // we could not register the profile + qCWarning(QT_BT_BLUEZ) << "Cannot export serial client profile on DBus"; + + delete profileContext; + profileContext = nullptr; + + errorString = QBluetoothSocket::tr("Cannot export profile on DBus"); + q->setSocketError(QBluetoothSocket::UnknownSocketError); + + return; + } + + QVariantMap profileOptions; + profileOptions.insert(QStringLiteral("Role"), QStringLiteral("client")); + profileOptions.insert(QStringLiteral("Service"), profileUuid); + profileOptions.insert(QStringLiteral("Name"), + QStringLiteral("QBluetoothSocket-%1").arg(QCoreApplication::applicationPid())); + + // TODO support more profile parameter + // profileOptions.insert(QStringLiteral("Channel"), 0); + + qCDebug(QT_BT_BLUEZ) << "Registering client profile on" << profilePath << "with options:"; + qCDebug(QT_BT_BLUEZ) << profileOptions; + QDBusPendingReply<> reply = profileManager->RegisterProfile( + QDBusObjectPath(profilePath), + profileUuid, + profileOptions); + reply.waitForFinished(); + if (reply.isError()) { + qCWarning(QT_BT_BLUEZ) << "Client profile registration failed:" + << reply.error().message(); + + QDBusConnection::systemBus().unregisterObject(profilePath); + errorString = QBluetoothSocket::tr("Cannot register profile on DBus"); + q->setSocketError(QBluetoothSocket::UnknownSocketError); + return; + } + + remoteDevicePath = findRemoteDevicePath(address); + if (remoteDevicePath.isEmpty()) { + qCWarning(QT_BT_BLUEZ) << "Unknown remote device:" << address + << "Try device discovery first"; + clearSocket(); + + errorString = QBluetoothSocket::tr("Cannot find remote device"); + q->setSocketError(QBluetoothSocket::HostNotFoundError); + return; + } + + OrgBluezDevice1Interface device(QStringLiteral("org.bluez"), remoteDevicePath, + QDBusConnection::systemBus()); + reply = device.ConnectProfile(profileUuid); + if (reply.isError()) { + qCWarning(QT_BT_BLUEZ) << "Cannot connect to profile/service:" << uuid; + + clearSocket(); + + errorString = QBluetoothSocket::tr("Cannot connect to remote profile"); + q->setSocketError(QBluetoothSocket::HostNotFoundError); + return; + } + + q->setOpenMode(openMode); + q->setSocketState(QBluetoothSocket::ConnectingState); +} + void QBluetoothSocketPrivateBluezDBus::connectToService( const QBluetoothServiceInfo &service, QIODevice::OpenMode openMode) { - Q_UNUSED(openMode); - Q_UNUSED(service); + Q_Q(QBluetoothSocket); + QBluetoothUuid targetService; + + targetService = service.serviceUuid(); + if (targetService.isNull()) { + // Do we have serialport service class? + if (service.serviceClassUuids().contains(QBluetoothUuid::SerialPort)) + targetService = QBluetoothUuid::SerialPort; + } + + if (targetService.isNull()) { + qCWarning(QT_BT_BLUEZ) << "Cannot find appropriate serviceUuid" + << "or SerialPort service class uuid"; + errorString = QBluetoothSocket::tr("Missing serviceUuid or Serial Port service class uuid"); + q->setSocketError(QBluetoothSocket::OperationError); + return; + } + + connectToService(service.device().address(), targetService, openMode); } void QBluetoothSocketPrivateBluezDBus::connectToService( const QBluetoothAddress &address, const QBluetoothUuid &uuid, QIODevice::OpenMode openMode) { - Q_UNUSED(openMode); - Q_UNUSED(address); - Q_UNUSED(uuid); + Q_Q(QBluetoothSocket); + + if (address.isNull()) { + qCWarning(QT_BT_BLUEZ) << "Invalid address to remote address passed."; + errorString = QBluetoothSocket::tr("Invalid Bluetooth address passed to connectToService()"); + q->setSocketError(QBluetoothSocket::OperationError); + return; + } + + if (uuid.isNull()) { + qCWarning(QT_BT_BLUEZ) << "Cannot find appropriate serviceUuid" + << "or SerialPort service class uuid"; + errorString = QBluetoothSocket::tr("Missing serviceUuid or Serial Port service class uuid"); + q->setSocketError(QBluetoothSocket::OperationError); + return; + } + + if (q->state() != QBluetoothSocket::UnconnectedState) { + qCWarning(QT_BT_BLUEZ) << "QBluetoothSocketPrivateBluezDBus::connectToService called on busy socket"; + errorString = QBluetoothSocket::tr("Trying to connect while connection is in progress"); + q->setSocketError(QBluetoothSocket::OperationError); + return; + } + + if (q->socketType() == QBluetoothServiceInfo::UnknownProtocol) { + qCWarning(QT_BT_BLUEZ) << "QBluetoothSocketPrivateBluezDBus::connectToService cannot " + "connect with 'UnknownProtocol' (type provided by given service)"; + errorString = QBluetoothSocket::tr("Socket type not supported"); + q->setSocketError(QBluetoothSocket::UnsupportedProtocolError); + return; + } + + if (!ensureNativeSocket(q->socketType())) { + errorString = QBluetoothSocket::tr("Socket type not supported"); + q->setSocketError(QBluetoothSocket::UnsupportedProtocolError); + return; + } + connectToServiceHelper(address, uuid, openMode); } void QBluetoothSocketPrivateBluezDBus::connectToService( const QBluetoothAddress &address, quint16 port, QIODevice::OpenMode openMode) { - Q_UNUSED(openMode); - Q_UNUSED(address); + Q_UNUSED(port); + Q_UNUSED(address); + Q_UNUSED(openMode); + Q_Q(QBluetoothSocket); + + errorString = tr("Connecting to port is not supported via Bluez DBus"); + q->setSocketError(QBluetoothSocket::ServiceNotFoundError); + qCWarning(QT_BT_BLUEZ) << "Connecting to port is not supported (Uuid required)"; } void QBluetoothSocketPrivateBluezDBus::abort() { + if (localSocket) { + localSocket->close(); + //TODO delayed disconnected() not yet implemented + } else { + // delayed disconnected not needed + clearSocket(); + } } QString QBluetoothSocketPrivateBluezDBus::localName() const { - return QString(); + bool ok = false; + const QString adapterPath = findAdapterForAddress(QBluetoothAddress(), &ok); + if (!ok) + return QString(); + + OrgBluezAdapter1Interface adapter(QStringLiteral("org.bluez"), adapterPath, + QDBusConnection::systemBus()); + return QString(adapter.alias()); } QBluetoothAddress QBluetoothSocketPrivateBluezDBus::localAddress() const { - return QBluetoothAddress(); + bool ok = false; + const QString adapterPath = findAdapterForAddress(QBluetoothAddress(), &ok); + if (!ok) + return QBluetoothAddress(); + + OrgBluezAdapter1Interface adapter(QStringLiteral("org.bluez"), adapterPath, + QDBusConnection::systemBus()); + return QBluetoothAddress(adapter.address()); } quint16 QBluetoothSocketPrivateBluezDBus::localPort() const { + int descriptor = -1; + + if (localSocket) + descriptor = int(localSocket->socketDescriptor()); + if (descriptor == -1) + return 0; + + if (socketType == QBluetoothServiceInfo::RfcommProtocol) { + sockaddr_rc addr; + socklen_t addrLength = sizeof(addr); + + if (::getsockname(descriptor, reinterpret_cast(&addr), &addrLength) == 0) + return (addr.rc_channel); + } else if (socketType == QBluetoothServiceInfo::L2capProtocol) { + sockaddr_l2 addr; + socklen_t addrLength = sizeof(addr); + + if (::getsockname(descriptor, reinterpret_cast(&addr), &addrLength) == 0) + return addr.l2_psm; + } + return 0; } QString QBluetoothSocketPrivateBluezDBus::peerName() const { - return QString(); + if (remoteDevicePath.isEmpty()) + return QString(); + + OrgBluezDevice1Interface device(QStringLiteral("org.bluez"), remoteDevicePath, + QDBusConnection::systemBus()); + return device.alias(); } QBluetoothAddress QBluetoothSocketPrivateBluezDBus::peerAddress() const { - return QBluetoothAddress(); + if (remoteDevicePath.isEmpty()) + return QBluetoothAddress(); + + OrgBluezDevice1Interface device(QStringLiteral("org.bluez"), remoteDevicePath, + QDBusConnection::systemBus()); + return QBluetoothAddress(device.address()); } quint16 QBluetoothSocketPrivateBluezDBus::peerPort() const { + int descriptor = -1; + + if (localSocket) + descriptor = int(localSocket->socketDescriptor()); + if (descriptor == -1) + return 0; + + if (socketType == QBluetoothServiceInfo::RfcommProtocol) { + sockaddr_rc addr; + socklen_t addrLength = sizeof(addr); + + if (::getpeername(descriptor, reinterpret_cast(&addr), &addrLength) == 0) + return addr.rc_channel; + } else if (socketType == QBluetoothServiceInfo::L2capProtocol) { + sockaddr_l2 addr; + socklen_t addrLength = sizeof(addr); + + if (::getpeername(descriptor, reinterpret_cast(&addr), &addrLength) == 0) + return addr.l2_psm; + } + return 0; } @@ -132,6 +452,10 @@ qint64 QBluetoothSocketPrivateBluezDBus::writeData(const char *data, qint64 maxS q->setSocketError(QBluetoothSocket::OperationError); return -1; } + + if (localSocket) + return localSocket->write(data, maxSize); + return -1; } @@ -148,11 +472,15 @@ qint64 QBluetoothSocketPrivateBluezDBus::readData(char *data, qint64 maxSize) return -1; } + if (localSocket) + return localSocket->read(data, maxSize); + return -1; } void QBluetoothSocketPrivateBluezDBus::close() { + abort(); } bool QBluetoothSocketPrivateBluezDBus::setSocketDescriptor(int socketDescriptor, QBluetoothServiceInfo::Protocol socketType, @@ -167,16 +495,113 @@ bool QBluetoothSocketPrivateBluezDBus::setSocketDescriptor(int socketDescriptor, qint64 QBluetoothSocketPrivateBluezDBus::bytesAvailable() const { + if (localSocket) + return localSocket->bytesAvailable(); + return 0; } bool QBluetoothSocketPrivateBluezDBus::canReadLine() const { + if (localSocket) + return localSocket->canReadLine(); + return false; } qint64 QBluetoothSocketPrivateBluezDBus::bytesToWrite() const { + if (localSocket) + return localSocket->bytesToWrite(); + return 0; } + +void QBluetoothSocketPrivateBluezDBus::remoteConnected(const QDBusUnixFileDescriptor &fd) +{ + Q_Q(QBluetoothSocket); + + int descriptor = ::dup(fd.fileDescriptor()); + localSocket = new QLocalSocket(this); + bool success = localSocket->setSocketDescriptor( + descriptor, QLocalSocket::ConnectedState, q->openMode()); + if (!success || !localSocket->isValid()) { + q->setSocketState(QBluetoothSocket::UnconnectedState); + delete localSocket; + localSocket = nullptr; + } else { + connect(localSocket, &QLocalSocket::readyRead, + q, &QBluetoothSocket::readyRead); + connect(localSocket, &QLocalSocket::stateChanged, + this, &QBluetoothSocketPrivateBluezDBus::socketStateChanged); + + q->setSocketState(QBluetoothSocket::ConnectedState); + emit q->connected(); + } +} + +void QBluetoothSocketPrivateBluezDBus::socketStateChanged(QLocalSocket::LocalSocketState newState) +{ + Q_Q(QBluetoothSocket); + + switch (newState) { + case QLocalSocket::ClosingState: + q->setSocketState(QBluetoothSocket::ClosingState); + break; + case QLocalSocket::UnconnectedState: + clearSocket(); + q->setSocketState(QBluetoothSocket::UnconnectedState); + + emit q->readChannelFinished(); + emit q->disconnected(); + break; + default: + // ConnectingState and ConnectedState not mapped + // (already set at the time when the socket is created) + break; + } +} + +void QBluetoothSocketPrivateBluezDBus::clearSocket() +{ + Q_Q(QBluetoothSocket); + + if (profilePath.isEmpty()) + return; + + qCDebug(QT_BT_BLUEZ) << "Clearing profile called for" << profilePath; + + if (localSocket) { + localSocket->close(); + localSocket->deleteLater(); + localSocket = nullptr; + } + + if (q->state() == QBluetoothSocket::ConnectedState) { + OrgBluezDevice1Interface device(QStringLiteral("org.bluez"), remoteDevicePath, + QDBusConnection::systemBus()); + auto reply = device.DisconnectProfile(profileUuid); + reply.waitForFinished(); + if (reply.isError()) { + qCWarning(QT_BT_BLUEZ) << "Disconnect profile failed:" + << reply.error().message(); + } + } + + QDBusPendingReply<> reply = profileManager->UnregisterProfile(QDBusObjectPath(profilePath)); + reply.waitForFinished(); + if (reply.isError()) + qCWarning(QT_BT_BLUEZ) << "Unregister profile:" << reply.error().message(); + + QDBusConnection::systemBus().unregisterObject(profilePath); + + if (profileContext) { + delete profileContext; + profileContext = nullptr; + } + + remoteDevicePath.clear(); + profileUuid.clear(); + profilePath.clear(); +} QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothsocket_bluezdbus_p.h b/src/bluetooth/qbluetoothsocket_bluezdbus_p.h index c7279555..4d1a272e 100644 --- a/src/bluetooth/qbluetoothsocket_bluezdbus_p.h +++ b/src/bluetooth/qbluetoothsocket_bluezdbus_p.h @@ -53,8 +53,17 @@ #include "qbluetoothsocketbase_p.h" +#include + +#include + +class OrgBluezProfileManager1Interface; + QT_BEGIN_NAMESPACE +class QLocalSocket; +class OrgBluezProfile1ContextInterface; + class QBluetoothSocketPrivateBluezDBus final: public QBluetoothSocketBasePrivate { Q_OBJECT @@ -66,6 +75,9 @@ public: void connectToServiceHelper(const QBluetoothAddress &address, quint16 port, QIODevice::OpenMode openMode) override; + void connectToServiceHelper(const QBluetoothAddress &address, + const QBluetoothUuid &uuid, + QIODevice::OpenMode openMode); void connectToService(const QBluetoothServiceInfo &service, QIODevice::OpenMode openMode) override; @@ -97,6 +109,20 @@ public: qint64 bytesAvailable() const override; bool canReadLine() const override; qint64 bytesToWrite() const override; + +private: + void remoteConnected(const QDBusUnixFileDescriptor &fd); + void socketStateChanged(QLocalSocket::LocalSocketState newState); + + void clearSocket(); + +private: + OrgBluezProfileManager1Interface *profileManager = nullptr; + OrgBluezProfile1ContextInterface *profileContext = nullptr; + QString remoteDevicePath; + QString profileUuid; + QString profilePath; + QLocalSocket *localSocket = nullptr; }; QT_END_NAMESPACE -- cgit v1.2.3