diff options
Diffstat (limited to 'src/bluetooth/qbluetoothdevicediscoveryagent_win.cpp')
-rw-r--r-- | src/bluetooth/qbluetoothdevicediscoveryagent_win.cpp | 557 |
1 files changed, 557 insertions, 0 deletions
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_win.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_win.cpp new file mode 100644 index 00000000..159428d4 --- /dev/null +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_win.cpp @@ -0,0 +1,557 @@ +/**************************************************************************** +** +** 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) |