blob: 05a540daeb69b58f69f37c1aa08ce34394c460eb [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 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 <QtCore/QLoggingCategory>
#include <QtCore/QRandomGenerator>
#include <QtDBus/QDBusContext>
#include "qbluetoothlocaldevice.h"
#include "qbluetoothaddress.h"
#include "qbluetoothlocaldevice_p.h"
#include "bluez/manager_p.h"
#include "bluez/adapter_p.h"
#include "bluez/agent_p.h"
#include "bluez/device_p.h"
#include "bluez/bluez5_helper_p.h"
#include "bluez/objectmanager_p.h"
#include "bluez/properties_p.h"
#include "bluez/adapter1_bluez5_p.h"
#include "bluez/device1_bluez5_p.h"
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
static const QLatin1String agentPath("/qt/agent");
inline uint qHash(const QBluetoothAddress &address)
{
return ::qHash(address.toUInt64());
}
QBluetoothLocalDevice::QBluetoothLocalDevice(QObject *parent) :
QObject(parent),
d_ptr(new QBluetoothLocalDevicePrivate(this))
{
}
QBluetoothLocalDevice::QBluetoothLocalDevice(const QBluetoothAddress &address, QObject *parent) :
QObject(parent),
d_ptr(new QBluetoothLocalDevicePrivate(this, address))
{
}
QString QBluetoothLocalDevice::name() const
{
if (d_ptr->adapter) {
QDBusPendingReply<QVariantMap> reply = d_ptr->adapter->GetProperties();
reply.waitForFinished();
if (reply.isError())
return QString();
return reply.value().value(QStringLiteral("Name")).toString();
} else if (d_ptr->adapterBluez5) {
return d_ptr->adapterBluez5->alias();
}
return QString();
}
QBluetoothAddress QBluetoothLocalDevice::address() const
{
if (d_ptr->adapter) {
QDBusPendingReply<QVariantMap> reply = d_ptr->adapter->GetProperties();
reply.waitForFinished();
if (reply.isError())
return QBluetoothAddress();
return QBluetoothAddress(reply.value().value(QStringLiteral("Address")).toString());
} else if (d_ptr->adapterBluez5) {
return QBluetoothAddress(d_ptr->adapterBluez5->address());
}
return QBluetoothAddress();
}
void QBluetoothLocalDevice::powerOn()
{
if (d_ptr->adapter)
d_ptr->adapter->SetProperty(QStringLiteral("Powered"), QDBusVariant(QVariant::fromValue(true)));
else if (d_ptr->adapterBluez5)
d_ptr->adapterBluez5->setPowered(true);
}
void QBluetoothLocalDevice::setHostMode(QBluetoothLocalDevice::HostMode mode)
{
if (!isValid())
return;
Q_D(QBluetoothLocalDevice);
switch (mode) {
case HostDiscoverableLimitedInquiry:
case HostDiscoverable:
if (hostMode() == HostPoweredOff) {
// We first have to wait for BT to be powered on,
// then we can set the host mode correctly
d->pendingHostModeChange = static_cast<int>(HostDiscoverable);
if (d->adapter) {
d->adapter->SetProperty(QStringLiteral("Powered"),
QDBusVariant(QVariant::fromValue(true)));
} else {
d->adapterBluez5->setPowered(true);
}
} else {
if (d->adapter) {
d->adapter->SetProperty(QStringLiteral("Discoverable"),
QDBusVariant(QVariant::fromValue(true)));
} else {
d->adapterBluez5->setDiscoverable(true);
}
}
break;
case HostConnectable:
if (hostMode() == HostPoweredOff) {
d->pendingHostModeChange = static_cast<int>(HostConnectable);
if (d->adapter) {
d->adapter->SetProperty(QStringLiteral("Powered"),
QDBusVariant(QVariant::fromValue(true)));
} else {
d->adapterBluez5->setPowered(true);
}
} else {
if (d->adapter) {
d->adapter->SetProperty(QStringLiteral("Discoverable"),
QDBusVariant(QVariant::fromValue(false)));
} else {
d->adapterBluez5->setDiscoverable(false);
}
}
break;
case HostPoweredOff:
if (d->adapter) {
d->adapter->SetProperty(QStringLiteral("Powered"),
QDBusVariant(QVariant::fromValue(false)));
} else {
d->adapterBluez5->setPowered(false);
}
break;
}
}
QBluetoothLocalDevice::HostMode QBluetoothLocalDevice::hostMode() const
{
if (d_ptr->adapter) {
QDBusPendingReply<QVariantMap> reply = d_ptr->adapter->GetProperties();
reply.waitForFinished();
if (reply.isError())
return HostPoweredOff;
if (!reply.value().value(QStringLiteral("Powered")).toBool())
return HostPoweredOff;
else if (reply.value().value(QStringLiteral("Discoverable")).toBool())
return HostDiscoverable;
else if (reply.value().value(QStringLiteral("Powered")).toBool())
return HostConnectable;
} else if (d_ptr->adapterBluez5) {
if (!d_ptr->adapterBluez5->powered())
return HostPoweredOff;
else if (d_ptr->adapterBluez5->discoverable())
return HostDiscoverable;
else if (d_ptr->adapterBluez5->powered())
return HostConnectable;
}
return HostPoweredOff;
}
QList<QBluetoothAddress> QBluetoothLocalDevice::connectedDevices() const
{
return d_ptr->connectedDevices();
}
QList<QBluetoothHostInfo> QBluetoothLocalDevice::allDevices()
{
QList<QBluetoothHostInfo> localDevices;
if (isBluez5()) {
OrgFreedesktopDBusObjectManagerInterface manager(QStringLiteral("org.bluez"),
QStringLiteral("/"),
QDBusConnection::systemBus());
QDBusPendingReply<ManagedObjectList> reply = manager.GetManagedObjects();
reply.waitForFinished();
if (reply.isError())
return localDevices;
ManagedObjectList managedObjectList = reply.value();
for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
const InterfaceList &ifaceList = it.value();
for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
const QString &iface = jt.key();
const QVariantMap &ifaceValues = jt.value();
if (iface == QStringLiteral("org.bluez.Adapter1")) {
QBluetoothHostInfo hostInfo;
const QString temp = ifaceValues.value(QStringLiteral("Address")).toString();
hostInfo.setAddress(QBluetoothAddress(temp));
if (hostInfo.address().isNull())
continue;
hostInfo.setName(ifaceValues.value(QStringLiteral("Name")).toString());
localDevices.append(hostInfo);
}
}
}
} else {
OrgBluezManagerInterface manager(QStringLiteral("org.bluez"), QStringLiteral("/"),
QDBusConnection::systemBus());
QDBusPendingReply<QList<QDBusObjectPath> > reply = manager.ListAdapters();
reply.waitForFinished();
if (reply.isError())
return localDevices;
const QList<QDBusObjectPath> paths = reply.value();
for (const QDBusObjectPath &path : paths) {
QBluetoothHostInfo hostinfo;
OrgBluezAdapterInterface adapter(QStringLiteral("org.bluez"), path.path(),
QDBusConnection::systemBus());
QDBusPendingReply<QVariantMap> reply = adapter.GetProperties();
reply.waitForFinished();
if (reply.isError())
continue;
hostinfo.setAddress(QBluetoothAddress(
reply.value().value(QStringLiteral("Address")).toString()));
hostinfo.setName(reply.value().value(QStringLiteral("Name")).toString());
localDevices.append(hostinfo);
}
}
return localDevices;
}
static inline OrgBluezDeviceInterface *getDevice(const QBluetoothAddress &address,
QBluetoothLocalDevicePrivate *d_ptr)
{
if (!d_ptr || !d_ptr->adapter)
return nullptr;
QDBusPendingReply<QDBusObjectPath> reply = d_ptr->adapter->FindDevice(address.toString());
reply.waitForFinished();
if (reply.isError()) {
qCWarning(QT_BT_BLUEZ) << Q_FUNC_INFO << "reply failed" << reply.error();
return nullptr;
}
QDBusObjectPath path = reply.value();
return new OrgBluezDeviceInterface(QStringLiteral("org.bluez"), path.path(),
QDBusConnection::systemBus());
}
void QBluetoothLocalDevice::requestPairing(const QBluetoothAddress &address, Pairing pairing)
{
if (!isValid() || address.isNull()) {
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
Q_ARG(QBluetoothLocalDevice::Error,
QBluetoothLocalDevice::PairingError));
return;
}
const Pairing current_pairing = pairingStatus(address);
if (current_pairing == pairing) {
if (d_ptr->adapterBluez5) {
// A possibly running discovery or pending pairing request should be canceled
if (d_ptr->pairingDiscoveryTimer && d_ptr->pairingDiscoveryTimer->isActive()) {
d_ptr->pairingDiscoveryTimer->stop();
}
if (d_ptr->pairingTarget) {
qCDebug(QT_BT_BLUEZ) << "Cancelling pending pairing request to" << d_ptr->pairingTarget->address();
QDBusPendingReply<> cancelReply = d_ptr->pairingTarget->CancelPairing();
cancelReply.waitForFinished();
delete d_ptr->pairingTarget;
d_ptr->pairingTarget = nullptr;
}
}
QMetaObject::invokeMethod(this, "pairingFinished", Qt::QueuedConnection,
Q_ARG(QBluetoothAddress, address),
Q_ARG(QBluetoothLocalDevice::Pairing, pairing));
return;
}
if (d_ptr->adapterBluez5) {
d_ptr->requestPairingBluez5(address, pairing);
return;
}
if (pairing == Paired || pairing == AuthorizedPaired) {
d_ptr->address = address;
d_ptr->pairing = pairing;
if (!d_ptr->agent) {
d_ptr->agent = new OrgBluezAgentAdaptor(d_ptr);
bool res = QDBusConnection::systemBus().registerObject(d_ptr->agent_path, d_ptr);
if (!res) {
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
Q_ARG(QBluetoothLocalDevice::Error,
QBluetoothLocalDevice::PairingError));
qCWarning(QT_BT_BLUEZ) << "Failed to register agent";
return;
}
}
if (current_pairing == Paired && pairing == AuthorizedPaired) {
OrgBluezDeviceInterface *device = getDevice(address, d_ptr);
if (!device) {
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
Q_ARG(QBluetoothLocalDevice::Error,
QBluetoothLocalDevice::PairingError));
return;
}
QDBusPendingReply<> deviceReply
= device->SetProperty(QStringLiteral("Trusted"), QDBusVariant(true));
deviceReply.waitForFinished();
if (deviceReply.isError()) {
qCWarning(QT_BT_BLUEZ) << Q_FUNC_INFO << "reply failed" << deviceReply.error();
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
Q_ARG(QBluetoothLocalDevice::Error,
QBluetoothLocalDevice::PairingError));
delete device;
return;
}
delete device;
QMetaObject::invokeMethod(this, "pairingFinished", Qt::QueuedConnection,
Q_ARG(QBluetoothAddress, address),
Q_ARG(QBluetoothLocalDevice::Pairing,
QBluetoothLocalDevice::AuthorizedPaired));
} else if (current_pairing == AuthorizedPaired && pairing == Paired) {
OrgBluezDeviceInterface *device = getDevice(address, d_ptr);
if (!device) {
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
Q_ARG(QBluetoothLocalDevice::Error,
QBluetoothLocalDevice::PairingError));
return;
}
QDBusPendingReply<> deviceReply
= device->SetProperty(QStringLiteral("Trusted"), QDBusVariant(false));
deviceReply.waitForFinished();
if (deviceReply.isError()) {
qCWarning(QT_BT_BLUEZ) << Q_FUNC_INFO << "reply failed" << deviceReply.error();
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
Q_ARG(QBluetoothLocalDevice::Error,
QBluetoothLocalDevice::PairingError));
delete device;
return;
}
delete device;
QMetaObject::invokeMethod(this, "pairingFinished", Qt::QueuedConnection,
Q_ARG(QBluetoothAddress, address),
Q_ARG(QBluetoothLocalDevice::Pairing,
QBluetoothLocalDevice::Paired));
} else {
QDBusPendingReply<QDBusObjectPath> reply
= d_ptr->adapter->CreatePairedDevice(address.toString(),
QDBusObjectPath(d_ptr->agent_path),
QStringLiteral("NoInputNoOutput"));
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
connect(watcher, &QDBusPendingCallWatcher::finished,
d_ptr, &QBluetoothLocalDevicePrivate::pairingCompleted);
if (reply.isError())
qCWarning(QT_BT_BLUEZ) << Q_FUNC_INFO << reply.error() << d_ptr->agent_path;
}
} else if (pairing == Unpaired) {
QDBusPendingReply<QDBusObjectPath> reply = this->d_ptr->adapter->FindDevice(
address.toString());
reply.waitForFinished();
if (reply.isError()) {
qCWarning(QT_BT_BLUEZ) << Q_FUNC_INFO << "failed to find device" << reply.error();
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
Q_ARG(QBluetoothLocalDevice::Error,
QBluetoothLocalDevice::PairingError));
return;
}
QDBusPendingReply<> removeReply = this->d_ptr->adapter->RemoveDevice(reply.value());
removeReply.waitForFinished();
if (removeReply.isError()) {
qCWarning(QT_BT_BLUEZ) << Q_FUNC_INFO << "failed to remove device"
<< removeReply.error();
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
Q_ARG(QBluetoothLocalDevice::Error,
QBluetoothLocalDevice::PairingError));
} else {
QMetaObject::invokeMethod(this, "pairingFinished", Qt::QueuedConnection,
Q_ARG(QBluetoothAddress, address),
Q_ARG(QBluetoothLocalDevice::Pairing,
QBluetoothLocalDevice::Unpaired));
}
}
}
void QBluetoothLocalDevicePrivate::requestPairingBluez5(const QBluetoothAddress &targetAddress,
QBluetoothLocalDevice::Pairing targetPairing)
{
if (!managerBluez5)
return;
//are we already discovering something? -> abort those attempts
if (pairingDiscoveryTimer && pairingDiscoveryTimer->isActive()) {
pairingDiscoveryTimer->stop();
QtBluezDiscoveryManager::instance()->unregisterDiscoveryInterest(
adapterBluez5->path());
}
if (pairingTarget) {
delete pairingTarget;
pairingTarget = nullptr;
}
// pairing implies that the device was found
// if we cannot find it we may have to turn on Discovery mode for a limited amount of time
// check device doesn't already exist
QDBusPendingReply<ManagedObjectList> reply = managerBluez5->GetManagedObjects();
reply.waitForFinished();
if (reply.isError()) {
emit q_ptr->error(QBluetoothLocalDevice::PairingError);
return;
}
ManagedObjectList managedObjectList = reply.value();
for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
const QDBusObjectPath &path = it.key();
const InterfaceList &ifaceList = it.value();
for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
const QString &iface = jt.key();
if (iface == QStringLiteral("org.bluez.Device1")) {
OrgBluezDevice1Interface device(QStringLiteral("org.bluez"),
path.path(),
QDBusConnection::systemBus());
if (targetAddress == QBluetoothAddress(device.address())) {
qCDebug(QT_BT_BLUEZ) << "Initiating direct pair to" << targetAddress.toString();
//device exist -> directly work with it
processPairingBluez5(path.path(), targetPairing);
return;
}
}
}
}
//no device matching -> turn on discovery
QtBluezDiscoveryManager::instance()->registerDiscoveryInterest(adapterBluez5->path());
address = targetAddress;
pairing = targetPairing;
if (!pairingDiscoveryTimer) {
pairingDiscoveryTimer = new QTimer(this);
pairingDiscoveryTimer->setSingleShot(true);
pairingDiscoveryTimer->setInterval(20000); //20s
connect(pairingDiscoveryTimer, &QTimer::timeout,
this, &QBluetoothLocalDevicePrivate::pairingDiscoveryTimedOut);
}
qCDebug(QT_BT_BLUEZ) << "Initiating discovery for pairing on" << targetAddress.toString();
pairingDiscoveryTimer->start();
}
/*!
* \internal
*
* Found a matching device. Now we must ensure its pairing/trusted state is as desired.
* If it has to be paired then we need another roundtrip through the event loop
* while we wait for the user to accept the pairing dialogs.
*/
void QBluetoothLocalDevicePrivate::processPairingBluez5(const QString &objectPath,
QBluetoothLocalDevice::Pairing target)
{
if (pairingTarget)
delete pairingTarget;
//stop possibly running discovery
if (pairingDiscoveryTimer && pairingDiscoveryTimer->isActive()) {
pairingDiscoveryTimer->stop();
QtBluezDiscoveryManager::instance()->unregisterDiscoveryInterest(
adapterBluez5->path());
}
pairingTarget = new OrgBluezDevice1Interface(QStringLiteral("org.bluez"), objectPath,
QDBusConnection::systemBus(), this);
const QBluetoothAddress targetAddress(pairingTarget->address());
Q_Q(QBluetoothLocalDevice);
switch (target) {
case QBluetoothLocalDevice::Unpaired: {
delete pairingTarget;
pairingTarget = nullptr;
QDBusPendingReply<> removeReply = adapterBluez5->RemoveDevice(QDBusObjectPath(objectPath));
auto watcher = new QDBusPendingCallWatcher(removeReply, this);
connect(watcher, &QDBusPendingCallWatcher::finished,
this, [q, targetAddress](QDBusPendingCallWatcher* watcher){
QDBusPendingReply<> reply = *watcher;
if (reply.isError())
emit q->error(QBluetoothLocalDevice::PairingError);
else
emit q->pairingFinished(targetAddress, QBluetoothLocalDevice::Unpaired);
watcher->deleteLater();
});
break;
}
case QBluetoothLocalDevice::Paired:
case QBluetoothLocalDevice::AuthorizedPaired:
pairing = target;
if (!pairingTarget->paired()) {
qCDebug(QT_BT_BLUEZ) << "Sending pairing request to" << pairingTarget->address();
//initiate the pairing
QDBusPendingReply<> pairReply = pairingTarget->Pair();
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pairReply, this);
connect(watcher, &QDBusPendingCallWatcher::finished,
this, &QBluetoothLocalDevicePrivate::pairingCompleted);
return;
}
//already paired but Trust level must be adjusted
if (target == QBluetoothLocalDevice::AuthorizedPaired && !pairingTarget->trusted())
pairingTarget->setTrusted(true);
else if (target == QBluetoothLocalDevice::Paired && pairingTarget->trusted())
pairingTarget->setTrusted(false);
delete pairingTarget;
pairingTarget = nullptr;
emit q->pairingFinished(targetAddress, target);
break;
default:
break;
}
}
void QBluetoothLocalDevicePrivate::pairingDiscoveryTimedOut()
{
qCWarning(QT_BT_BLUEZ) << "Discovery for pairing purposes failed. Cannot find parable device.";
QtBluezDiscoveryManager::instance()->unregisterDiscoveryInterest(
adapterBluez5->path());
emit q_ptr->error(QBluetoothLocalDevice::PairingError);
}
QBluetoothLocalDevice::Pairing QBluetoothLocalDevice::pairingStatus(
const QBluetoothAddress &address) const
{
if (address.isNull())
return Unpaired;
if (d_ptr->adapter) {
OrgBluezDeviceInterface *device = getDevice(address, d_ptr);
if (!device)
return Unpaired;
QDBusPendingReply<QVariantMap> deviceReply = device->GetProperties();
deviceReply.waitForFinished();
if (deviceReply.isError()) {
delete device;
return Unpaired;
}
QVariantMap map = deviceReply.value();
if (map.value(QStringLiteral("Trusted")).toBool() && map.value(QStringLiteral("Paired")).toBool()) {
delete device;
return AuthorizedPaired;
} else if (map.value(QStringLiteral("Paired")).toBool()) {
delete device;
return Paired;
}
delete device;
} else if (d_ptr->adapterBluez5) {
QDBusPendingReply<ManagedObjectList> reply = d_ptr->managerBluez5->GetManagedObjects();
reply.waitForFinished();
if (reply.isError())
return Unpaired;
ManagedObjectList managedObjectList = reply.value();
for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
const QDBusObjectPath &path = it.key();
const InterfaceList &ifaceList = it.value();
for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
const QString &iface = jt.key();
if (iface == QStringLiteral("org.bluez.Device1")) {
OrgBluezDevice1Interface device(QStringLiteral("org.bluez"),
path.path(),
QDBusConnection::systemBus());
if (address == QBluetoothAddress(device.address())) {
if (device.trusted() && device.paired())
return AuthorizedPaired;
else if (device.paired())
return Paired;
else
return Unpaired;
}
}
}
}
}
return Unpaired;
}
QBluetoothLocalDevicePrivate::QBluetoothLocalDevicePrivate(QBluetoothLocalDevice *q,
QBluetoothAddress address) :
localAddress(address),
pendingHostModeChange(-1),
q_ptr(q)
{
registerQBluetoothLocalDeviceMetaType();
if (isBluez5())
initializeAdapterBluez5();
else
initializeAdapter();
connectDeviceChanges();
}
bool objectPathIsForThisDevice(const QString &adapterPath, const QString &objectPath)
{
return (!adapterPath.isEmpty() && objectPath.startsWith(adapterPath));
}
void QBluetoothLocalDevicePrivate::connectDeviceChanges()
{
if (adapter) { // invalid QBluetoothLocalDevice due to wrong local adapter address
createCache();
connect(adapter, &OrgBluezAdapterInterface::PropertyChanged,
this, &QBluetoothLocalDevicePrivate::PropertyChanged);
connect(adapter, &OrgBluezAdapterInterface::DeviceCreated,
this, &QBluetoothLocalDevicePrivate::_q_deviceCreated);
connect(adapter, &OrgBluezAdapterInterface::DeviceRemoved,
this, &QBluetoothLocalDevicePrivate::_q_deviceRemoved);
} else if (adapterBluez5 && managerBluez5) {
//setup property change notifications for all existing devices
QDBusPendingReply<ManagedObjectList> reply = managerBluez5->GetManagedObjects();
reply.waitForFinished();
if (reply.isError())
return;
OrgFreedesktopDBusPropertiesInterface *monitor = nullptr;
ManagedObjectList managedObjectList = reply.value();
for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
const QDBusObjectPath &path = it.key();
const InterfaceList &ifaceList = it.value();
// don't track connected devices from other adapters but the current
if (!objectPathIsForThisDevice(deviceAdapterPath, path.path()))
continue;
for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
const QString &iface = jt.key();
const QVariantMap &ifaceValues = jt.value();
if (iface == QStringLiteral("org.bluez.Device1")) {
monitor = new OrgFreedesktopDBusPropertiesInterface(QStringLiteral("org.bluez"),
path.path(),
QDBusConnection::systemBus(), this);
connect(monitor, &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged,
this, &QBluetoothLocalDevicePrivate::PropertiesChanged);
deviceChangeMonitors.insert(path.path(), monitor);
if (ifaceValues.value(QStringLiteral("Connected"), false).toBool()) {
QBluetoothAddress address(ifaceValues.value(QStringLiteral("Address")).toString());
connectedDevicesSet.insert(address);
}
}
}
}
}
}
QBluetoothLocalDevicePrivate::~QBluetoothLocalDevicePrivate()
{
delete msgConnection;
delete adapter;
delete adapterBluez5;
delete adapterProperties;
delete managerBluez5;
delete agent;
delete pairingTarget;
delete manager;
qDeleteAll(devices);
qDeleteAll(deviceChangeMonitors);
}
void QBluetoothLocalDevicePrivate::initializeAdapter()
{
if (adapter)
return;
QScopedPointer<OrgBluezManagerInterface> man(new OrgBluezManagerInterface(
QStringLiteral("org.bluez"), QStringLiteral("/"),
QDBusConnection::systemBus()));
if (localAddress == QBluetoothAddress()) {
QDBusPendingReply<QDBusObjectPath> reply = man->DefaultAdapter();
reply.waitForFinished();
if (reply.isError())
return;
adapter = new OrgBluezAdapterInterface(QStringLiteral("org.bluez"),
reply.value().path(), QDBusConnection::systemBus());
} else {
QDBusPendingReply<QList<QDBusObjectPath> > reply = man->ListAdapters();
reply.waitForFinished();
if (reply.isError())
return;
const QList<QDBusObjectPath> paths = reply.value();
for (const QDBusObjectPath &path : paths) {
OrgBluezAdapterInterface *tmpAdapter
= new OrgBluezAdapterInterface(QStringLiteral("org.bluez"),
path.path(), QDBusConnection::systemBus());
QDBusPendingReply<QVariantMap> reply = tmpAdapter->GetProperties();
reply.waitForFinished();
if (reply.isError()) {
delete tmpAdapter;
continue;
}
QBluetoothAddress path_address(reply.value().value(QStringLiteral("Address")).toString());
if (path_address == localAddress) {
adapter = tmpAdapter;
break;
} else {
delete tmpAdapter;
}
}
}
// monitor case when local adapter is removed
manager = man.take();
connect(manager, &OrgBluezManagerInterface::AdapterRemoved,
this, &QBluetoothLocalDevicePrivate::adapterRemoved);
currentMode = static_cast<QBluetoothLocalDevice::HostMode>(-1);
if (adapter) {
connect(adapter, &OrgBluezAdapterInterface::PropertyChanged,
this, &QBluetoothLocalDevicePrivate::PropertyChanged);
agent_path = agentPath;
agent_path.append(QString::fromLatin1("/%1").arg(QRandomGenerator::global()->generate()));
}
}
void QBluetoothLocalDevicePrivate::initializeAdapterBluez5()
{
if (adapterBluez5)
return;
//get all local adapters
if (!managerBluez5)
managerBluez5 = new OrgFreedesktopDBusObjectManagerInterface(
QStringLiteral("org.bluez"),
QStringLiteral("/"),
QDBusConnection::systemBus(), this);
connect(managerBluez5, &OrgFreedesktopDBusObjectManagerInterface::InterfacesAdded,
this, &QBluetoothLocalDevicePrivate::InterfacesAdded);
connect(managerBluez5, &OrgFreedesktopDBusObjectManagerInterface::InterfacesRemoved,
this, &QBluetoothLocalDevicePrivate::InterfacesRemoved);
bool ok = true;
const QString adapterPath = findAdapterForAddress(localAddress, &ok);
if (!ok || adapterPath.isEmpty())
return;
deviceAdapterPath = adapterPath;
adapterBluez5 = new OrgBluezAdapter1Interface(QStringLiteral("org.bluez"),
adapterPath,
QDBusConnection::systemBus(), this);
if (adapterBluez5) {
//hook up propertiesChanged for current adapter
adapterProperties = new OrgFreedesktopDBusPropertiesInterface(
QStringLiteral("org.bluez"), adapterBluez5->path(),
QDBusConnection::systemBus(), this);
connect(adapterProperties, &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged,
this, &QBluetoothLocalDevicePrivate::PropertiesChanged);
}
currentMode = static_cast<QBluetoothLocalDevice::HostMode>(-1);
}
// Bluez 5
void QBluetoothLocalDevicePrivate::PropertiesChanged(const QString &interface,
const QVariantMap &changed_properties,
const QStringList &/*invalidated_properties*/,
const QDBusMessage &)
{
//qDebug() << "Change" << interface << changed_properties;
if (interface == QStringLiteral("org.bluez.Adapter1")) {
//update host mode
if (changed_properties.contains(QStringLiteral("Discoverable"))
|| changed_properties.contains(QStringLiteral("Powered"))) {
QBluetoothLocalDevice::HostMode mode;
if (!adapterBluez5->powered()) {
mode = QBluetoothLocalDevice::HostPoweredOff;
} else {
if (adapterBluez5->discoverable())
mode = QBluetoothLocalDevice::HostDiscoverable;
else
mode = QBluetoothLocalDevice::HostConnectable;
if (pendingHostModeChange != -1) {
if (static_cast<int>(mode) != pendingHostModeChange) {
adapterBluez5->setDiscoverable(
pendingHostModeChange
== static_cast<int>(QBluetoothLocalDevice::HostDiscoverable));
pendingHostModeChange = -1;
return;
}
pendingHostModeChange = -1;
}
}
if (mode != currentMode)
emit q_ptr->hostModeStateChanged(mode);
currentMode = mode;
}
} else if (interface == QStringLiteral("org.bluez.Device1")
&& changed_properties.contains(QStringLiteral("Connected"))) {
// update list of connected devices
OrgFreedesktopDBusPropertiesInterface *senderIface =
qobject_cast<OrgFreedesktopDBusPropertiesInterface*>(sender());
if (!senderIface)
return;
const QString currentPath = senderIface->path();
bool isConnected = changed_properties.value(QStringLiteral("Connected"), false).toBool();
OrgBluezDevice1Interface device(QStringLiteral("org.bluez"), currentPath,
QDBusConnection::systemBus());
const QBluetoothAddress changedAddress(device.address());
bool isInSet = connectedDevicesSet.contains(changedAddress);
if (isConnected && !isInSet) {
connectedDevicesSet.insert(changedAddress);
emit q_ptr->deviceConnected(changedAddress);
} else if (!isConnected && isInSet) {
connectedDevicesSet.remove(changedAddress);
emit q_ptr->deviceDisconnected(changedAddress);
}
}
}
void QBluetoothLocalDevicePrivate::InterfacesAdded(const QDBusObjectPath &object_path, InterfaceList interfaces_and_properties)
{
if (interfaces_and_properties.contains(QStringLiteral("org.bluez.Device1"))
&& !deviceChangeMonitors.contains(object_path.path())) {
// a new device was added which we need to add to list of known devices
if (objectPathIsForThisDevice(deviceAdapterPath, object_path.path())) {
OrgFreedesktopDBusPropertiesInterface *monitor = new OrgFreedesktopDBusPropertiesInterface(
QStringLiteral("org.bluez"),
object_path.path(),
QDBusConnection::systemBus());
connect(monitor, &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged,
this, &QBluetoothLocalDevicePrivate::PropertiesChanged);
deviceChangeMonitors.insert(object_path.path(), monitor);
const QVariantMap ifaceValues = interfaces_and_properties.value(QStringLiteral("org.bluez.Device1"));
if (ifaceValues.value(QStringLiteral("Connected"), false).toBool()) {
QBluetoothAddress address(ifaceValues.value(QStringLiteral("Address")).toString());
connectedDevicesSet.insert(address);
emit q_ptr->deviceConnected(address);
}
}
}
if (pairingDiscoveryTimer && pairingDiscoveryTimer->isActive()
&& interfaces_and_properties.contains(QStringLiteral("org.bluez.Device1"))) {
//device discovery for pairing found new remote device
OrgBluezDevice1Interface device(QStringLiteral("org.bluez"),
object_path.path(), QDBusConnection::systemBus());
if (!address.isNull() && address == QBluetoothAddress(device.address()))
processPairingBluez5(object_path.path(), pairing);
}
}
void QBluetoothLocalDevicePrivate::InterfacesRemoved(const QDBusObjectPath &object_path,
const QStringList &interfaces)
{
if (deviceChangeMonitors.contains(object_path.path())
&& interfaces.contains(QLatin1String("org.bluez.Device1"))) {
if (objectPathIsForThisDevice(deviceAdapterPath, object_path.path())) {
//a device was removed
delete deviceChangeMonitors.take(object_path.path());
//the path contains the address (e.g.: /org/bluez/hci0/dev_XX_XX_XX_XX_XX_XX)
//-> use it to update current list of connected devices
QString addressString = object_path.path().right(17);
addressString.replace(QStringLiteral("_"), QStringLiteral(":"));
const QBluetoothAddress address(addressString);
bool found = connectedDevicesSet.remove(address);
if (found)
emit q_ptr->deviceDisconnected(address);
}
}
if (adapterBluez5
&& object_path.path() == adapterBluez5->path()
&& interfaces.contains(QLatin1String("org.bluez.Adapter1"))) {
qCDebug(QT_BT_BLUEZ) << "Adapter" << adapterBluez5->path() << "was removed";
// current adapter was removed -> invalidate the instance
delete adapterBluez5;
adapterBluez5 = nullptr;
managerBluez5->deleteLater();
managerBluez5 = nullptr;
delete adapterProperties;
adapterProperties = nullptr;
delete pairingTarget;
pairingTarget = nullptr;
// turn off connectivity monitoring
qDeleteAll(deviceChangeMonitors);
deviceChangeMonitors.clear();
connectedDevicesSet.clear();
}
}
bool QBluetoothLocalDevicePrivate::isValid() const
{
return adapter || adapterBluez5;
}
// Bluez 4
void QBluetoothLocalDevicePrivate::adapterRemoved(const QDBusObjectPath &devicePath)
{
if (!adapter )
return;
if (adapter->path() != devicePath.path())
return;
qCDebug(QT_BT_BLUEZ) << "Adapter" << devicePath.path()
<< "was removed. Invalidating object.";
// the current adapter was removed
delete adapter;
adapter = nullptr;
manager->deleteLater();
manager = nullptr;
// stop all pairing related activities
if (agent) {
QDBusConnection::systemBus().unregisterObject(agent_path);
delete agent;
agent = nullptr;
}
delete msgConnection;
msgConnection = nullptr;
// stop all connectivity monitoring
qDeleteAll(devices);
devices.clear();
connectedDevicesSet.clear();
}
void QBluetoothLocalDevicePrivate::RequestConfirmation(const QDBusObjectPath &in0, uint in1)
{
Q_UNUSED(in0);
Q_Q(QBluetoothLocalDevice);
setDelayedReply(true);
msgConfirmation = message();
msgConnection = new QDBusConnection(connection());
emit q->pairingDisplayConfirmation(address, QString::fromLatin1("%1").arg(in1));
}
void QBluetoothLocalDevicePrivate::_q_deviceCreated(const QDBusObjectPath &device)
{
OrgBluezDeviceInterface *deviceInterface
= new OrgBluezDeviceInterface(QStringLiteral("org.bluez"),
device.path(),
QDBusConnection::systemBus(), this);
connect(deviceInterface, &OrgBluezDeviceInterface::PropertyChanged,
this, &QBluetoothLocalDevicePrivate::_q_devicePropertyChanged);
devices << deviceInterface;
QDBusPendingReply<QVariantMap> properties
= deviceInterface->asyncCall(QStringLiteral("GetProperties"));
properties.waitForFinished();
if (!properties.isValid()) {
qCritical() << "Unable to get device properties from: " << device.path();
return;
}
const QBluetoothAddress address
= QBluetoothAddress(properties.value().value(QStringLiteral("Address")).toString());
const bool connected = properties.value().value(QStringLiteral("Connected")).toBool();
if (connected) {
connectedDevicesSet.insert(address);
emit q_ptr->deviceConnected(address);
} else {
connectedDevicesSet.remove(address);
emit q_ptr->deviceDisconnected(address);
}
}
void QBluetoothLocalDevicePrivate::_q_deviceRemoved(const QDBusObjectPath &device)
{
for (OrgBluezDeviceInterface *deviceInterface : qAsConst(devices)) {
if (deviceInterface->path() == device.path()) {
devices.remove(deviceInterface);
delete deviceInterface; // deviceDisconnected is already emitted by _q_devicePropertyChanged
break;
}
}
}
void QBluetoothLocalDevicePrivate::_q_devicePropertyChanged(const QString &property,
const QDBusVariant &value)
{
OrgBluezDeviceInterface *deviceInterface = qobject_cast<OrgBluezDeviceInterface *>(sender());
if (deviceInterface && property == QLatin1String("Connected")) {
QDBusPendingReply<QVariantMap> propertiesReply = deviceInterface->GetProperties();
propertiesReply.waitForFinished();
if (propertiesReply.isError()) {
qCWarning(QT_BT_BLUEZ) << propertiesReply.error().message();
return;
}
const QVariantMap properties = propertiesReply.value();
const QBluetoothAddress address
= QBluetoothAddress(properties.value(QStringLiteral("Address")).toString());
const bool connected = value.variant().toBool();
if (connected) {
connectedDevicesSet.insert(address);
emit q_ptr->deviceConnected(address);
} else {
connectedDevicesSet.remove(address);
emit q_ptr->deviceDisconnected(address);
}
}
}
void QBluetoothLocalDevicePrivate::createCache()
{
if (!adapter)
return;
QDBusPendingReply<QList<QDBusObjectPath> > reply = adapter->ListDevices();
reply.waitForFinished();
if (reply.isError()) {
qCWarning(QT_BT_BLUEZ) << reply.error().message();
return;
}
const QList<QDBusObjectPath> knownDevices = reply.value();
for (const QDBusObjectPath &device : knownDevices) {
OrgBluezDeviceInterface *deviceInterface =
new OrgBluezDeviceInterface(QStringLiteral("org.bluez"),
device.path(),
QDBusConnection::systemBus(), this);
connect(deviceInterface, &OrgBluezDeviceInterface::PropertyChanged,
this, &QBluetoothLocalDevicePrivate::_q_devicePropertyChanged);
devices << deviceInterface;
QDBusPendingReply<QVariantMap> properties
= deviceInterface->asyncCall(QStringLiteral("GetProperties"));
properties.waitForFinished();
if (!properties.isValid()) {
qCWarning(QT_BT_BLUEZ) << "Unable to get properties for device " << device.path();
return;
}
if (properties.value().value(QStringLiteral("Connected")).toBool()) {
connectedDevicesSet.insert(
QBluetoothAddress(properties.value().value(QStringLiteral("Address")).toString()));
}
}
}
QList<QBluetoothAddress> QBluetoothLocalDevicePrivate::connectedDevices() const
{
return connectedDevicesSet.values();
}
void QBluetoothLocalDevice::pairingConfirmation(bool confirmation)
{
if (!d_ptr
|| !d_ptr->msgConfirmation.isReplyRequired()
|| !d_ptr->msgConnection)
return;
if (confirmation) {
QDBusMessage msg = d_ptr->msgConfirmation.createReply(QVariant(true));
d_ptr->msgConnection->send(msg);
} else {
QDBusMessage error
= d_ptr->msgConfirmation.createErrorReply(QDBusError::AccessDenied,
QStringLiteral("Pairing rejected"));
d_ptr->msgConnection->send(error);
}
delete d_ptr->msgConnection;
d_ptr->msgConnection = nullptr;
}
QString QBluetoothLocalDevicePrivate::RequestPinCode(const QDBusObjectPath &in0)
{
Q_UNUSED(in0)
Q_Q(QBluetoothLocalDevice);
qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << in0.path();
// seeded in constructor, 6 digit pin
QString pin = QString::fromLatin1("%1").arg(QRandomGenerator::global()->bounded(1000000));
pin = QString::fromLatin1("%1").arg(pin, 6, QLatin1Char('0'));
emit q->pairingDisplayPinCode(address, pin);
return pin;
}
void QBluetoothLocalDevicePrivate::pairingCompleted(QDBusPendingCallWatcher *watcher)
{
Q_Q(QBluetoothLocalDevice);
QDBusPendingReply<> reply = *watcher;
if (reply.isError()) {
qCWarning(QT_BT_BLUEZ) << "Failed to create pairing" << reply.error().name();
if (reply.error().name() != QStringLiteral("org.bluez.Error.AuthenticationCanceled"))
emit q->error(QBluetoothLocalDevice::PairingError);
watcher->deleteLater();
return;
}
if (adapter) {
QDBusPendingReply<QDBusObjectPath> findReply = adapter->FindDevice(address.toString());
findReply.waitForFinished();
if (findReply.isError()) {
qCWarning(QT_BT_BLUEZ) << Q_FUNC_INFO << "failed to find device" << findReply.error();
emit q->error(QBluetoothLocalDevice::PairingError);
watcher->deleteLater();
return;
}
OrgBluezDeviceInterface device(QStringLiteral("org.bluez"), findReply.value().path(),
QDBusConnection::systemBus());
if (pairing == QBluetoothLocalDevice::AuthorizedPaired) {
device.SetProperty(QStringLiteral("Trusted"), QDBusVariant(QVariant(true)));
emit q->pairingFinished(address, QBluetoothLocalDevice::AuthorizedPaired);
}
else {
device.SetProperty(QStringLiteral("Trusted"), QDBusVariant(QVariant(false)));
emit q->pairingFinished(address, QBluetoothLocalDevice::Paired);
}
} else if (adapterBluez5) {
if (!pairingTarget) {
qCWarning(QT_BT_BLUEZ) << "Pairing target expected but found null pointer.";
emit q->error(QBluetoothLocalDevice::PairingError);
watcher->deleteLater();
return;
}
if (!pairingTarget->paired()) {
qCWarning(QT_BT_BLUEZ) << "Device was not paired as requested";
emit q->error(QBluetoothLocalDevice::PairingError);
watcher->deleteLater();
return;
}
const QBluetoothAddress targetAddress(pairingTarget->address());
if (pairing == QBluetoothLocalDevice::AuthorizedPaired && !pairingTarget->trusted())
pairingTarget->setTrusted(true);
else if (pairing == QBluetoothLocalDevice::Paired && pairingTarget->trusted())
pairingTarget->setTrusted(false);
delete pairingTarget;
pairingTarget = nullptr;
emit q->pairingFinished(targetAddress, pairing);
}
watcher->deleteLater();
}
void QBluetoothLocalDevicePrivate::Authorize(const QDBusObjectPath &in0, const QString &in1)
{
Q_UNUSED(in0)
Q_UNUSED(in1)
// TODO implement this
qCDebug(QT_BT_BLUEZ) << "Got authorize for" << in0.path() << in1;
}
void QBluetoothLocalDevicePrivate::Cancel()
{
// TODO implement this
qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO;
}
void QBluetoothLocalDevicePrivate::Release()
{
// TODO implement this
qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO;
}
void QBluetoothLocalDevicePrivate::ConfirmModeChange(const QString &in0)
{
Q_UNUSED(in0)
// TODO implement this
qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << in0;
}
void QBluetoothLocalDevicePrivate::DisplayPasskey(const QDBusObjectPath &in0, uint in1, uchar in2)
{
Q_UNUSED(in0)
Q_UNUSED(in1)
Q_UNUSED(in2)
// TODO implement this
qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << in0.path() << in1 << in2;
}
uint QBluetoothLocalDevicePrivate::RequestPasskey(const QDBusObjectPath &in0)
{
Q_UNUSED(in0);
qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO;
return (QRandomGenerator::global()->bounded(1000000));
}
// Bluez 4
void QBluetoothLocalDevicePrivate::PropertyChanged(QString property, QDBusVariant value)
{
Q_UNUSED(value);
if (property != QLatin1String("Powered")
&& property != QLatin1String("Discoverable"))
return;
Q_Q(QBluetoothLocalDevice);
QBluetoothLocalDevice::HostMode mode;
QDBusPendingReply<QVariantMap> reply = adapter->GetProperties();
reply.waitForFinished();
if (reply.isError()) {
qCWarning(QT_BT_BLUEZ) << "Failed to get bluetooth properties for mode change";
return;
}
QVariantMap map = reply.value();
if (!map.value(QStringLiteral("Powered")).toBool()) {
mode = QBluetoothLocalDevice::HostPoweredOff;
} else {
if (map.value(QStringLiteral("Discoverable")).toBool())
mode = QBluetoothLocalDevice::HostDiscoverable;
else
mode = QBluetoothLocalDevice::HostConnectable;
if (pendingHostModeChange != -1) {
if ((int)mode != pendingHostModeChange) {
if (property == QStringLiteral("Powered"))
return;
if (pendingHostModeChange == (int)QBluetoothLocalDevice::HostDiscoverable) {
adapter->SetProperty(QStringLiteral("Discoverable"),
QDBusVariant(QVariant::fromValue(true)));
} else {
adapter->SetProperty(QStringLiteral("Discoverable"),
QDBusVariant(QVariant::fromValue(false)));
}
pendingHostModeChange = -1;
return;
}
}
}
if (mode != currentMode)
emit q->hostModeStateChanged(mode);
currentMode = mode;
}
#include "moc_qbluetoothlocaldevice_p.cpp"
QT_END_NAMESPACE