blob: 159428d49ef555c02b7e339c59c257eeb63bed6d [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Copyright (C) 2014 Denis Shienkov <denis.shienkov@gmail.com>
** 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 "qbluetoothdevicediscoveryagent.h"
#include "qbluetoothuuid.h"
#include "qbluetoothdevicediscoveryagent_p.h"
#include "qbluetoothlocaldevice_p.h"
#include <QtCore/qmutex.h>
#include <QtCore/QThread>
#include <QtCore/QLoggingCategory>
#include <qt_windows.h>
#include <setupapi.h>
#include <bluetoothapis.h>
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINDOWS)
struct LeDeviceEntry {
QString devicePath;
QBluetoothAddress deviceAddress;
};
Q_GLOBAL_STATIC(QVector<LeDeviceEntry>, cachedLeDeviceEntries)
Q_GLOBAL_STATIC(QMutex, cachedLeDeviceEntriesGuard)
static QString devicePropertyString(
HDEVINFO hDeviceInfo,
const PSP_DEVINFO_DATA deviceInfoData,
DWORD registryProperty)
{
DWORD propertyRegDataType = 0;
DWORD propertyBufferSize = 0;
QByteArray propertyBuffer;
while (!::SetupDiGetDeviceRegistryProperty(
hDeviceInfo,
deviceInfoData,
registryProperty,
&propertyRegDataType,
propertyBuffer.isEmpty() ? nullptr : reinterpret_cast<PBYTE>(propertyBuffer.data()),
propertyBuffer.size(),
&propertyBufferSize)) {
const DWORD error = ::GetLastError();
if (error != ERROR_INSUFFICIENT_BUFFER
|| (propertyRegDataType != REG_SZ
&& propertyRegDataType != REG_EXPAND_SZ)) {
return QString();
}
// add +2 byte to allow to successfully convert to qstring
propertyBuffer.fill(0, propertyBufferSize + sizeof(wchar_t));
}
return QString::fromWCharArray(reinterpret_cast<const wchar_t *>(
propertyBuffer.constData()));
}
static QString deviceName(HDEVINFO hDeviceInfo, PSP_DEVINFO_DATA deviceInfoData)
{
return devicePropertyString(hDeviceInfo, deviceInfoData, SPDRP_FRIENDLYNAME);
}
static QString deviceSystemPath(const PSP_INTERFACE_DEVICE_DETAIL_DATA detailData)
{
return QString::fromWCharArray(detailData->DevicePath);
}
static QBluetoothAddress deviceAddress(const QString &devicePath)
{
const int firstbound = devicePath.indexOf(QStringLiteral("dev_"));
const int lastbound = devicePath.indexOf(QLatin1Char('#'), firstbound);
const QString hex = devicePath.mid(firstbound + 4, lastbound - firstbound - 4);
bool ok = false;
return QBluetoothAddress(hex.toULongLong(&ok, 16));
}
static QBluetoothDeviceInfo createClassicDeviceInfo(const BLUETOOTH_DEVICE_INFO &foundDevice)
{
QBluetoothDeviceInfo deviceInfo(
QBluetoothAddress(foundDevice.Address.ullLong),
QString::fromWCharArray(foundDevice.szName),
foundDevice.ulClassofDevice);
deviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::BaseRateCoreConfiguration);
if (foundDevice.fRemembered)
deviceInfo.setCached(true);
return deviceInfo;
}
static QBluetoothDeviceInfo findFirstClassicDevice(
DWORD *systemErrorCode, HBLUETOOTH_DEVICE_FIND *hSearch)
{
BLUETOOTH_DEVICE_SEARCH_PARAMS searchParams = {};
searchParams.dwSize = sizeof(searchParams);
searchParams.cTimeoutMultiplier = 10; // 12.8 sec
searchParams.fIssueInquiry = TRUE;
searchParams.fReturnAuthenticated = TRUE;
searchParams.fReturnConnected = TRUE;
searchParams.fReturnRemembered = TRUE;
searchParams.fReturnUnknown = TRUE;
searchParams.hRadio = nullptr;
BLUETOOTH_DEVICE_INFO deviceInfo = {};
deviceInfo.dwSize = sizeof(deviceInfo);
const HBLUETOOTH_DEVICE_FIND hFind = ::BluetoothFindFirstDevice(
&searchParams, &deviceInfo);
QBluetoothDeviceInfo foundDevice;
if (hFind) {
*hSearch = hFind;
*systemErrorCode = NO_ERROR;
foundDevice = createClassicDeviceInfo(deviceInfo);
} else {
*systemErrorCode = int(::GetLastError());
}
return foundDevice;
}
static QBluetoothDeviceInfo findNextClassicDevice(
DWORD *systemErrorCode, HBLUETOOTH_DEVICE_FIND hSearch)
{
BLUETOOTH_DEVICE_INFO deviceInfo = {};
deviceInfo.dwSize = sizeof(deviceInfo);
QBluetoothDeviceInfo foundDevice;
if (!::BluetoothFindNextDevice(hSearch, &deviceInfo)) {
*systemErrorCode = int(::GetLastError());
} else {
*systemErrorCode = NO_ERROR;
foundDevice = createClassicDeviceInfo(deviceInfo);
}
return foundDevice;
}
static void closeClassicSearch(HBLUETOOTH_DEVICE_FIND hSearch)
{
if (hSearch)
::BluetoothFindDeviceClose(hSearch);
}
static QVector<QBluetoothDeviceInfo> enumerateLeDevices(
DWORD *systemErrorCode)
{
// GUID_BLUETOOTHLE_DEVICE_INTERFACE
const QUuid deviceInterfaceGuid("781aee18-7733-4ce4-add0-91f41c67b592");
const HDEVINFO hDeviceInfo = ::SetupDiGetClassDevs(
reinterpret_cast<const GUID *>(&deviceInterfaceGuid),
nullptr,
nullptr,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (hDeviceInfo == INVALID_HANDLE_VALUE) {
*systemErrorCode = int(::GetLastError());
return QVector<QBluetoothDeviceInfo>();
}
QVector<QBluetoothDeviceInfo> foundDevices;
DWORD index = 0;
QVector<LeDeviceEntry> cachedEntries;
for (;;) {
SP_DEVICE_INTERFACE_DATA deviceInterfaceData = {};
deviceInterfaceData.cbSize = sizeof(deviceInterfaceData);
if (!::SetupDiEnumDeviceInterfaces(
hDeviceInfo,
nullptr,
reinterpret_cast<const GUID *>(&deviceInterfaceGuid),
index++,
&deviceInterfaceData)) {
*systemErrorCode = int(::GetLastError());
break;
}
DWORD deviceInterfaceDetailDataSize = 0;
if (!::SetupDiGetDeviceInterfaceDetail(
hDeviceInfo,
&deviceInterfaceData,
nullptr,
deviceInterfaceDetailDataSize,
&deviceInterfaceDetailDataSize,
nullptr)) {
if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
*systemErrorCode = int(::GetLastError());
break;
}
}
SP_DEVINFO_DATA deviceInfoData = {};
deviceInfoData.cbSize = sizeof(deviceInfoData);
QByteArray deviceInterfaceDetailDataBuffer(
deviceInterfaceDetailDataSize, 0);
PSP_INTERFACE_DEVICE_DETAIL_DATA deviceInterfaceDetailData =
reinterpret_cast<PSP_INTERFACE_DEVICE_DETAIL_DATA>
(deviceInterfaceDetailDataBuffer.data());
deviceInterfaceDetailData->cbSize =
sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);
if (!::SetupDiGetDeviceInterfaceDetail(
hDeviceInfo,
&deviceInterfaceData,
deviceInterfaceDetailData,
deviceInterfaceDetailDataBuffer.size(),
&deviceInterfaceDetailDataSize,
&deviceInfoData)) {
*systemErrorCode = int(::GetLastError());
break;
}
const QString systemPath = deviceSystemPath(deviceInterfaceDetailData);
const QBluetoothAddress address = deviceAddress(systemPath);
if (address.isNull())
continue;
const QString name = deviceName(hDeviceInfo, &deviceInfoData);
QBluetoothDeviceInfo deviceInfo(address, name, QBluetoothDeviceInfo::MiscellaneousDevice);
deviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration);
deviceInfo.setCached(true);
foundDevices << deviceInfo;
cachedEntries << LeDeviceEntry{systemPath, address};
}
QMutexLocker locker(cachedLeDeviceEntriesGuard());
cachedLeDeviceEntries()->swap(cachedEntries);
::SetupDiDestroyDeviceInfoList(hDeviceInfo);
return foundDevices;
}
struct DiscoveryResult {
QVector<QBluetoothDeviceInfo> devices;
DWORD systemErrorCode;
HBLUETOOTH_DEVICE_FIND hSearch; // Used only for classic devices
};
QString QBluetoothDeviceDiscoveryAgentPrivate::discoveredLeDeviceSystemPath(
const QBluetoothAddress &deviceAddress)
{
// update LE devices cache
DWORD dummyErrorCode;
enumerateLeDevices(&dummyErrorCode);
QMutexLocker locker(cachedLeDeviceEntriesGuard());
for (const LeDeviceEntry &e: *cachedLeDeviceEntries) {
if (e.deviceAddress == deviceAddress)
return e.devicePath;
}
return QString();
}
QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(
const QBluetoothAddress &deviceAdapter,
QBluetoothDeviceDiscoveryAgent *parent)
: inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry)
, lastError(QBluetoothDeviceDiscoveryAgent::NoError)
, adapterAddress(deviceAdapter)
, pendingCancel(false)
, pendingStart(false)
, active(false)
, lowEnergySearchTimeout(-1) // remains -1 -> timeout not supported
, q_ptr(parent)
{
threadLE = new QThread;
threadWorkerLE = new ThreadWorkerDeviceDiscovery;
threadWorkerLE->moveToThread(threadLE);
connect(threadWorkerLE, &ThreadWorkerDeviceDiscovery::discoveryCompleted, this, &QBluetoothDeviceDiscoveryAgentPrivate::completeLeDevicesDiscovery);
connect(threadLE, &QThread::finished, threadWorkerLE, &ThreadWorkerDeviceDiscovery::deleteLater);
connect(threadLE, &QThread::finished, threadLE, &QThread::deleteLater);
threadLE->start();
threadClassic = new QThread;
threadWorkerClassic = new ThreadWorkerDeviceDiscovery;
threadWorkerClassic->moveToThread(threadClassic);
connect(threadWorkerClassic, &ThreadWorkerDeviceDiscovery::discoveryCompleted, this, &QBluetoothDeviceDiscoveryAgentPrivate::completeClassicDevicesDiscovery);
connect(threadClassic, &QThread::finished, threadWorkerClassic, &ThreadWorkerDeviceDiscovery::deleteLater);
connect(threadClassic, &QThread::finished, threadClassic, &QThread::deleteLater);
threadClassic->start();
}
QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate()
{
if (active)
stop();
if (threadLE)
threadLE->quit();
if (threadClassic)
threadClassic->quit();
}
bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const
{
if (pendingStart)
return true;
if (pendingCancel)
return false;
return active;
}
QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods()
{
return (LowEnergyMethod | ClassicMethod);
}
void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)
{
requestedMethods = methods;
if (pendingCancel == true) {
pendingStart = true;
return;
}
const QList<QBluetoothHostInfo> foundLocalAdapters =
QBluetoothLocalDevicePrivate::localAdapters();
Q_Q(QBluetoothDeviceDiscoveryAgent);
if (foundLocalAdapters.isEmpty()) {
qCWarning(QT_BT_WINDOWS) << "Device does not support Bluetooth";
lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError;
errorString = QBluetoothDeviceDiscoveryAgent::tr("Device does not support Bluetooth");
emit q->error(lastError);
return;
}
// Check for the local adapter address.
auto equals = [this](const QBluetoothHostInfo &adapterInfo) {
return adapterAddress == QBluetoothAddress()
|| adapterAddress == adapterInfo.address();
};
const auto end = foundLocalAdapters.cend();
const auto it = std::find_if(foundLocalAdapters.cbegin(), end, equals);
if (it == end) {
qCWarning(QT_BT_WINDOWS) << "Incorrect local adapter passed.";
lastError = QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError;
errorString = QBluetoothDeviceDiscoveryAgent::tr("Passed address is not a local device.");
emit q->error(lastError);
return;
}
discoveredDevices.clear();
active = true;
// We run LE search first, as it is fast on windows.
if (requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)
startLeDevicesDiscovery();
else if (requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod)
startClassicDevicesDiscovery();
}
void QBluetoothDeviceDiscoveryAgentPrivate::stop()
{
if (!active)
return;
pendingCancel = true;
pendingStart = false;
}
void QBluetoothDeviceDiscoveryAgentPrivate::cancelDiscovery()
{
Q_Q(QBluetoothDeviceDiscoveryAgent);
active = false;
pendingCancel = false;
emit q->canceled();
}
void QBluetoothDeviceDiscoveryAgentPrivate::restartDiscovery()
{
pendingStart = false;
pendingCancel = false;
start(requestedMethods);
}
void QBluetoothDeviceDiscoveryAgentPrivate::finishDiscovery(QBluetoothDeviceDiscoveryAgent::Error errorCode, const QString &errorText)
{
active = false;
pendingStart = false;
pendingCancel = false;
lastError = errorCode;
errorString = errorText;
Q_Q(QBluetoothDeviceDiscoveryAgent);
if (errorCode == QBluetoothDeviceDiscoveryAgent::NoError)
emit q->finished();
else
emit q->error(lastError);
}
void QBluetoothDeviceDiscoveryAgentPrivate::startLeDevicesDiscovery()
{
const auto threadWorker = threadWorkerLE;
QMetaObject::invokeMethod(threadWorkerLE, [threadWorker]()
{
DiscoveryResult result; // Do not use hSearch here!
result.systemErrorCode = NO_ERROR;
result.devices = enumerateLeDevices(&result.systemErrorCode);
emit threadWorker->discoveryCompleted(QVariant::fromValue(result));
}, Qt::QueuedConnection);
}
void QBluetoothDeviceDiscoveryAgentPrivate::completeLeDevicesDiscovery(const QVariant &res)
{
if (pendingCancel && !pendingStart) {
cancelDiscovery();
} else if (pendingStart) {
restartDiscovery();
} else {
const DiscoveryResult result = res.value<DiscoveryResult>();
if (result.systemErrorCode == NO_ERROR || result.systemErrorCode == ERROR_NO_MORE_ITEMS) {
for (const QBluetoothDeviceInfo &device : result.devices)
processDiscoveredDevice(device);
// We run classic search at second, as it is slow on windows.
if (requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod)
startClassicDevicesDiscovery();
else
finishDiscovery(QBluetoothDeviceDiscoveryAgent::NoError, qt_error_string(NO_ERROR));
} else {
const QBluetoothDeviceDiscoveryAgent::Error error = (result.systemErrorCode == ERROR_INVALID_HANDLE)
? QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError
: QBluetoothDeviceDiscoveryAgent::InputOutputError;
finishDiscovery(error, qt_error_string(result.systemErrorCode));
}
}
}
void QBluetoothDeviceDiscoveryAgentPrivate::startClassicDevicesDiscovery(Qt::HANDLE hSearch)
{
const auto threadWorker = threadWorkerClassic;
QMetaObject::invokeMethod(threadWorker, [threadWorker, hSearch]()
{
DiscoveryResult result;
result.hSearch = hSearch;
result.systemErrorCode = NO_ERROR;
const QBluetoothDeviceInfo device = hSearch
? findNextClassicDevice(&result.systemErrorCode, result.hSearch)
: findFirstClassicDevice(&result.systemErrorCode, &result.hSearch);
result.devices.append(device);
emit threadWorker->discoveryCompleted(QVariant::fromValue(result));
}, Qt::QueuedConnection);
}
void QBluetoothDeviceDiscoveryAgentPrivate::completeClassicDevicesDiscovery(const QVariant &res)
{
const DiscoveryResult result = res.value<DiscoveryResult>();
if (pendingCancel && !pendingStart) {
closeClassicSearch(result.hSearch);
cancelDiscovery();
} else if (pendingStart) {
closeClassicSearch(result.hSearch);
restartDiscovery();
} else {
if (result.systemErrorCode == ERROR_NO_MORE_ITEMS) {
closeClassicSearch(result.hSearch);
finishDiscovery(QBluetoothDeviceDiscoveryAgent::NoError, QString());
} else if (result.systemErrorCode == NO_ERROR) {
if (result.hSearch) {
for (const QBluetoothDeviceInfo &device : result.devices)
processDiscoveredDevice(device);
startClassicDevicesDiscovery(result.hSearch);
} else {
finishDiscovery(QBluetoothDeviceDiscoveryAgent::NoError, QString());
}
} else {
closeClassicSearch(result.hSearch);
const QBluetoothDeviceDiscoveryAgent::Error error = (result.systemErrorCode == ERROR_INVALID_HANDLE)
? QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError
: QBluetoothDeviceDiscoveryAgent::InputOutputError;
finishDiscovery(error, qt_error_string(result.systemErrorCode));
}
}
}
void QBluetoothDeviceDiscoveryAgentPrivate::processDiscoveredDevice(
const QBluetoothDeviceInfo &foundDevice)
{
Q_Q(QBluetoothDeviceDiscoveryAgent);
auto equalAddress = [foundDevice](const QBluetoothDeviceInfo &targetDevice) {
return foundDevice.address() == targetDevice.address(); };
auto end = discoveredDevices.end();
auto deviceIt = std::find_if(discoveredDevices.begin(), end, equalAddress);
if (deviceIt == end) {
qCDebug(QT_BT_WINDOWS) << "Found device: " << foundDevice.name() << foundDevice.address();
discoveredDevices.append(foundDevice);
emit q->deviceDiscovered(foundDevice);
} else {
qCDebug(QT_BT_WINDOWS) << "Updating device:" << deviceIt->name() << deviceIt->address();
// merge service uuids
QList<QBluetoothUuid> uuids = deviceIt->serviceUuids();
uuids.append(foundDevice.serviceUuids());
const QSet<QBluetoothUuid> uuidSet = uuids.toSet();
if (deviceIt->serviceUuids().count() != uuidSet.count())
deviceIt->setServiceUuids(uuidSet.toList().toVector());
if (deviceIt->coreConfigurations() != foundDevice.coreConfigurations())
deviceIt->setCoreConfigurations(
QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration);
}
}
QT_END_NAMESPACE
Q_DECLARE_METATYPE(DiscoveryResult)