/**************************************************************************** ** ** Copyright (C) 2018 The Qt Company Ltd. ** Copyright (C) 2014 Denis Shienkov ** 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 #include #include #include #include #include QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINDOWS) struct LeDeviceEntry { QString devicePath; QBluetoothAddress deviceAddress; }; Q_GLOBAL_STATIC(QVector, 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(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( 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 enumerateLeDevices( DWORD *systemErrorCode) { // GUID_BLUETOOTHLE_DEVICE_INTERFACE const QUuid deviceInterfaceGuid("781aee18-7733-4ce4-add0-91f41c67b592"); const HDEVINFO hDeviceInfo = ::SetupDiGetClassDevs( reinterpret_cast(&deviceInterfaceGuid), nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); if (hDeviceInfo == INVALID_HANDLE_VALUE) { *systemErrorCode = int(::GetLastError()); return QVector(); } QVector foundDevices; DWORD index = 0; QVector cachedEntries; for (;;) { SP_DEVICE_INTERFACE_DATA deviceInterfaceData = {}; deviceInterfaceData.cbSize = sizeof(deviceInterfaceData); if (!::SetupDiEnumDeviceInterfaces( hDeviceInfo, nullptr, reinterpret_cast(&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 (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 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 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(); 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(); 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 QVector uuids = deviceIt->serviceUuids(); uuids.append(foundDevice.serviceUuids()); const QSet uuidSet(uuids.begin(), uuids.end()); if (deviceIt->serviceUuids().count() != uuidSet.count()) deviceIt->setServiceUuids(uuidSet.values().toVector()); if (deviceIt->coreConfigurations() != foundDevice.coreConfigurations()) deviceIt->setCoreConfigurations( QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration); } } QT_END_NAMESPACE Q_DECLARE_METATYPE(DiscoveryResult)