summaryrefslogtreecommitdiffstats
path: root/src/bluetooth/qbluetoothdevicediscoveryagent_win.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/bluetooth/qbluetoothdevicediscoveryagent_win.cpp')
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_win.cpp557
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)