Revision control
++ // Copyright (C) 2022 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "qbluetoothdevicediscoveryagent.h"
++ #include "qbluetoothdevicediscoveryagent_p.h"
++ #include "qbluetoothaddress.h"
++ #include "qbluetoothuuid.h"
++
++ #include <QtBluetooth/private/qbluetoothdevicewatcher_winrt_p.h>
++ #include <QtBluetooth/private/qbluetoothutils_winrt_p.h>
++ #include <QtBluetooth/private/qtbluetoothglobal_p.h>
++
++ #include <QtCore/QLoggingCategory>
++ #include <QtCore/QMutex>
++ #include <QtCore/qendian.h>
++
++ #include <winrt/Windows.Devices.Bluetooth.h>
++ #include <winrt/Windows.Devices.Bluetooth.Advertisement.h>
++ #include <winrt/Windows.Devices.Bluetooth.Rfcomm.h>
++ #include <winrt/Windows.Devices.Bluetooth.GenericAttributeProfile.h>
++ #include <winrt/Windows.Devices.Enumeration.h>
++ #include <winrt/Windows.Foundation.h>
++ #include <winrt/Windows.Foundation.Collections.h>
++ #include <winrt/Windows.Storage.Streams.h>
++
++ using namespace winrt::Windows::Devices::Bluetooth;
++ using namespace winrt::Windows::Devices::Bluetooth::Advertisement;
++ using namespace winrt::Windows::Devices::Bluetooth::GenericAttributeProfile;
++ using namespace winrt::Windows::Devices::Bluetooth::Rfcomm;
++ using namespace winrt::Windows::Devices::Enumeration;
++ using namespace winrt::Windows::Foundation;
++ using namespace winrt::Windows::Storage::Streams;
++
++ QT_BEGIN_NAMESPACE
++
++ QT_IMPL_METATYPE_EXTERN(ManufacturerData)
++ QT_IMPL_METATYPE_EXTERN(ServiceData)
++
++ Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINDOWS)
++
++ static QByteArray byteArrayFromBuffer(const IBuffer &buffer)
++ {
++ const uint8_t *data = buffer.data();
++ return QByteArray(reinterpret_cast<const char *>(data),
++ static_cast<qsizetype>(buffer.Length()));
++ }
++
++ static ManufacturerData extractManufacturerData(const BluetoothLEAdvertisement &ad)
++ {
++ ManufacturerData ret;
++ const auto data = ad.ManufacturerData();
++ for (const auto &item : data) {
++ const uint16_t id = item.CompanyId();
++ const QByteArray bufferData = byteArrayFromBuffer(item.Data());
++ if (ret.contains(id))
++ qCWarning(QT_BT_WINDOWS) << "Company ID already present in manufacturer data.";
++ ret.insert(id, bufferData);
++ }
++ return ret;
++ }
++
++ static ServiceData extractServiceData(const BluetoothLEAdvertisement &ad)
++ {
++ static constexpr int serviceDataTypes[3] = { 0x16, 0x20, 0x21 };
++
++ ServiceData ret;
++
++ for (const auto &serviceDataType : serviceDataTypes) {
++ const auto dataSections = ad.GetSectionsByType(serviceDataType);
++ for (const auto §ion : dataSections) {
++ const unsigned char dataType = section.DataType();
++ const QByteArray bufferData = byteArrayFromBuffer(section.Data());
++ if (dataType == 0x16) {
++ Q_ASSERT(bufferData.size() >= 2);
++ ret.insert(QBluetoothUuid(qFromLittleEndian<quint16>(bufferData.constData())),
++ bufferData.right(bufferData.length() - 2));
++ } else if (dataType == 0x20) {
++ Q_ASSERT(bufferData.size() >= 4);
++ ret.insert(QBluetoothUuid(qFromLittleEndian<quint32>(bufferData.constData())),
++ bufferData.right(bufferData.length() - 4));
++ } else if (dataType == 0x21) {
++ Q_ASSERT(bufferData.size() >= 16);
++ ret.insert(QBluetoothUuid(qToBigEndian<QUuid::Id128Bytes>(
++ qFromLittleEndian<QUuid::Id128Bytes>(bufferData.constData()))),
++ bufferData.right(bufferData.length() - 16));
++ }
++ }
++ }
++
++ return ret;
++ }
++
++ // Needed because there is no explicit conversion
++ static GUID fromWinRtGuid(const winrt::guid &guid)
++ {
++ const GUID uuid {
++ guid.Data1,
++ guid.Data2,
++ guid.Data3,
++ { guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3],
++ guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7] }
++ };
++ return uuid;
++ }
++
++ class AdvertisementWatcherWrapper : public QObject,
++ public std::enable_shared_from_this<AdvertisementWatcherWrapper>
++ {
++ Q_OBJECT
++ public:
++ AdvertisementWatcherWrapper() {}
++ ~AdvertisementWatcherWrapper()
++ {
++ stop();
++ }
++ void init()
++ {
++ m_watcher.AllowExtendedAdvertisements(true);
++ m_watcher.ScanningMode(BluetoothLEScanningMode::Active);
++ }
++ void start() {
++ subscribeToEvents();
++ m_watcher.Start();
++ }
++ void stop()
++ {
++ if (canStop()) {
++ unsubscribeFromEvents();
++ m_watcher.Stop();
++ }
++ }
++
++ signals:
++ // The signal will be emitted from a separate thread,
++ // so we need to use Qt::QueuedConnection
++ void advertisementDataReceived(quint64 address, qint16 rssi,
++ const ManufacturerData &manufacturerData,
++ const ServiceData &serviceData,
++ const QList<QBluetoothUuid> &uuids);
++ private:
++ void subscribeToEvents()
++ {
++ // The callbacks are triggered from separate threads. So we capture
++ // thisPtr to make sure that the object is valid.
++ auto thisPtr = shared_from_this();
++ m_receivedToken = m_watcher.Received(
++ [thisPtr](BluetoothLEAdvertisementWatcher,
++ BluetoothLEAdvertisementReceivedEventArgs args) {
++ const uint64_t address = args.BluetoothAddress();
++ const short rssi = args.RawSignalStrengthInDBm();
++ const BluetoothLEAdvertisement ad = args.Advertisement();
++
++ const ManufacturerData manufacturerData = extractManufacturerData(ad);
++ const ServiceData serviceData = extractServiceData(ad);
++
++ QList<QBluetoothUuid> serviceUuids;
++ const auto guids = ad.ServiceUuids();
++ for (const auto &guid : guids) {
++ const GUID uuid = fromWinRtGuid(guid);
++ serviceUuids.append(QBluetoothUuid(uuid));
++ }
++
++ emit thisPtr->advertisementDataReceived(address, rssi, manufacturerData,
++ serviceData, serviceUuids);
++ });
++ }
++ void unsubscribeFromEvents()
++ {
++ m_watcher.Received(m_receivedToken);
++ }
++ bool canStop() const
++ {
++ const auto status = m_watcher.Status();
++ return status == BluetoothLEAdvertisementWatcherStatus::Started
++ || status == BluetoothLEAdvertisementWatcherStatus::Aborted;
++ }
++
++ BluetoothLEAdvertisementWatcher m_watcher;
++ winrt::event_token m_receivedToken;
++ };
++
++ // Both constants are taken from Microsoft's docs:
++ // Alternatively we could create separate watchers for paired and unpaired devices.
++ static const winrt::hstring ClassicDeviceSelector =
++ L"System.Devices.Aep.ProtocolId:=\"{e0cbf06c-cd8b-4647-bb8a-263b43f0f974}\"";
++ // Do not use it for now, so comment out. Do not delete in case we want to reuse it.
++ //static const winrt::hstring LowEnergyDeviceSelector =
++ // L"System.Devices.Aep.ProtocolId:=\"{bb7bb05e-5972-42b5-94fc-76eaa7084d49}\"";
++
++ class QWinRTBluetoothDeviceDiscoveryWorker : public QObject,
++ public std::enable_shared_from_this<QWinRTBluetoothDeviceDiscoveryWorker>
++ {
++ Q_OBJECT
++ public:
++ QWinRTBluetoothDeviceDiscoveryWorker(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods,
++ int interval);
++ ~QWinRTBluetoothDeviceDiscoveryWorker();
++ void start();
++ void stop();
++
++ private:
++ void startDeviceDiscovery(QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode);
++
++ std::shared_ptr<QBluetoothDeviceWatcherWinRT> createDeviceWatcher(winrt::hstring selector,
++ int watcherId);
++ void generateError(QBluetoothDeviceDiscoveryAgent::Error error, const char *msg = nullptr);
++ void invokeDeviceFoundWithDebug(const QBluetoothDeviceInfo &info);
++ void finishDiscovery();
++ bool isFinished() const;
++
++ // Bluetooth Classic handlers
++ void getClassicDeviceFromId(const winrt::hstring &id);
++ void handleClassicDevice(const BluetoothDevice &device);
++ void handleRfcommServices(const RfcommDeviceServicesResult &servicesResult,
++ uint64_t address, const QString &name, uint32_t classOfDeviceInt,
++ bool isCached);
++
++ // Bluetooth Low Energy handlers
++ void getLowEnergyDeviceFromId(const winrt::hstring &id);
++ void handleLowEnergyDevice(const BluetoothLEDevice &device);
++ void handleGattServices(const GattDeviceServicesResult &servicesResult,
++ QBluetoothDeviceInfo &info);
++
++ // Bluetooth Low Energy Advertising handlers
++ std::shared_ptr<AdvertisementWatcherWrapper> createAdvertisementWatcher();
++
++ // invokable methods for handling finish conditions
++ Q_INVOKABLE void decrementPendingDevicesCountAndCheckFinished(
++ std::shared_ptr<QWinRTBluetoothDeviceDiscoveryWorker> worker);
++
++ Q_SIGNALS:
++ void deviceFound(const QBluetoothDeviceInfo &info);
++ void deviceDataChanged(const QBluetoothAddress &address, QBluetoothDeviceInfo::Fields,
++ qint16 rssi, ManufacturerData manufacturerData, ServiceData serviceData);
++ void errorOccured(QBluetoothDeviceDiscoveryAgent::Error error);
++ void scanFinished();
++
++ private slots:
++ void onBluetoothDeviceFound(winrt::hstring deviceId, int watcherId);
++ void onDeviceEnumerationCompleted(int watcherId);
++
++ void onAdvertisementDataReceived(quint64 address, qint16 rssi,
++ const ManufacturerData &manufacturerData,
++ const ServiceData &serviceData,
++ const QList<QBluetoothUuid> &uuids);
++
++ void stopAdvertisementWatcher();
++
++ private:
++ struct LEAdvertisingInfo {
++ QList<QBluetoothUuid> services;
++ ManufacturerData manufacturerData;
++ ServiceData serviceData;
++ qint16 rssi = 0;
++ };
++
++ quint8 requestedModes = 0;
++ QMutex m_leDevicesMutex;
++ QMap<quint64, LEAdvertisingInfo> m_foundLEDevicesMap;
++ int m_pendingDevices = 0;
++
++ static constexpr int ClassicWatcherId = 1;
++ static constexpr int LowEnergyWatcherId = 2;
++
++ std::shared_ptr<QBluetoothDeviceWatcherWinRT> m_classicWatcher;
++ std::shared_ptr<QBluetoothDeviceWatcherWinRT> m_lowEnergyWatcher;
++ std::shared_ptr<AdvertisementWatcherWrapper> m_advertisementWatcher;
++ bool m_classicScanStarted = false;
++ bool m_lowEnergyScanStarted = false;
++ QTimer *m_leScanTimer = nullptr;
++ };
++
++ static void invokeDecrementPendingDevicesCountAndCheckFinished(
++ std::shared_ptr<QWinRTBluetoothDeviceDiscoveryWorker> worker)
++ {
++ QMetaObject::invokeMethod(worker.get(), "decrementPendingDevicesCountAndCheckFinished",
++ Qt::QueuedConnection,
++ Q_ARG(std::shared_ptr<QWinRTBluetoothDeviceDiscoveryWorker>,
++ worker));
++ }
++
++ QWinRTBluetoothDeviceDiscoveryWorker::QWinRTBluetoothDeviceDiscoveryWorker(
++ QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods, int interval)
++ : requestedModes(methods)
++ {
++ qRegisterMetaType<QBluetoothDeviceInfo>();
++ qRegisterMetaType<QBluetoothDeviceInfo::Fields>();
++ qRegisterMetaType<ManufacturerData>();
++ qRegisterMetaType<std::shared_ptr<QWinRTBluetoothDeviceDiscoveryWorker>>();
++
++ m_classicWatcher = createDeviceWatcher(ClassicDeviceSelector, ClassicWatcherId);
++ // For LE scan use DeviceWatcher to handle only paired devices.
++ // Non-paired devices will be found using BluetoothLEAdvertisementWatcher.
++ const auto leSelector = BluetoothLEDevice::GetDeviceSelectorFromPairingState(true);
++ m_lowEnergyWatcher = createDeviceWatcher(leSelector, LowEnergyWatcherId);
++ m_advertisementWatcher = createAdvertisementWatcher();
++
++ // Docs claim that a negative interval means that the backend handles it on its own
++ if (interval < 0)
++ interval = 40000;
++
++ if (interval != 0) {
++ m_leScanTimer = new QTimer(this);
++ m_leScanTimer->setSingleShot(true);
++ m_leScanTimer->setInterval(interval);
++ connect(m_leScanTimer, &QTimer::timeout, this,
++ &QWinRTBluetoothDeviceDiscoveryWorker::stopAdvertisementWatcher);
++ }
++ }
++
++ QWinRTBluetoothDeviceDiscoveryWorker::~QWinRTBluetoothDeviceDiscoveryWorker()
++ {
++ stop();
++ }
++
++ void QWinRTBluetoothDeviceDiscoveryWorker::start()
++ {
++ if (requestedModes & QBluetoothDeviceDiscoveryAgent::ClassicMethod) {
++ if (m_classicWatcher && m_classicWatcher->init()) {
++ m_classicWatcher->start();
++ m_classicScanStarted = true;
++ } else {
++ generateError(QBluetoothDeviceDiscoveryAgent::Error::UnknownError,
++ "Could not start classic device watcher");
++ }
++ }
++ if (requestedModes & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) {
++ if (m_lowEnergyWatcher && m_lowEnergyWatcher->init()) {
++ m_lowEnergyWatcher->start();
++ m_lowEnergyScanStarted = true;
++ } else {
++ generateError(QBluetoothDeviceDiscoveryAgent::Error::UnknownError,
++ "Could not start low energy device watcher");
++ }
++ if (m_advertisementWatcher) {
++ m_advertisementWatcher->init();
++ m_advertisementWatcher->start();
++ if (m_leScanTimer)
++ m_leScanTimer->start();
++ } else {
++ generateError(QBluetoothDeviceDiscoveryAgent::Error::UnknownError,
++ "Could not start low energy advertisement watcher");
++ }
++ }
++
++ qCDebug(QT_BT_WINDOWS) << "Worker started";
++ }
++
++ void QWinRTBluetoothDeviceDiscoveryWorker::stop()
++ {
++ if (m_leScanTimer && m_leScanTimer->isActive())
++ m_leScanTimer->stop();
++ m_classicWatcher->stop();
++ m_lowEnergyWatcher->stop();
++ m_advertisementWatcher->stop();
++ }
++
++ void QWinRTBluetoothDeviceDiscoveryWorker::finishDiscovery()
++ {
++ stop();
++ emit scanFinished();
++ }
++
++ bool QWinRTBluetoothDeviceDiscoveryWorker::isFinished() const
++ {
++ // If the interval is set to 0, we do not start a timer, and that means
++ // that we need to wait for the user to explicitly call stop()
++ return (m_pendingDevices == 0) && !m_lowEnergyScanStarted && !m_classicScanStarted
++ && (m_leScanTimer && !m_leScanTimer->isActive());
++ }
++
++ void QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothDeviceFound(winrt::hstring deviceId, int watcherId)
++ {
++ if (watcherId == ClassicWatcherId)
++ getClassicDeviceFromId(deviceId);
++ else if (watcherId == LowEnergyWatcherId)
++ getLowEnergyDeviceFromId(deviceId);
++ }
++
++ void QWinRTBluetoothDeviceDiscoveryWorker::onDeviceEnumerationCompleted(int watcherId)
++ {
++ qCDebug(QT_BT_WINDOWS) << (watcherId == ClassicWatcherId ? "BT" : "BTLE")
++ << "enumeration completed";
++ if (watcherId == ClassicWatcherId) {
++ m_classicWatcher->stop();
++ m_classicScanStarted = false;
++ } else if (watcherId == LowEnergyWatcherId) {
++ m_lowEnergyWatcher->stop();
++ m_lowEnergyScanStarted = false;
++ }
++ if (isFinished())
++ finishDiscovery();
++ }
++
++ // this function executes in main worker thread
++ void QWinRTBluetoothDeviceDiscoveryWorker::onAdvertisementDataReceived(
++ quint64 address, qint16 rssi, const ManufacturerData &manufacturerData,
++ const ServiceData &serviceData, const QList<QBluetoothUuid> &uuids)
++ {
++ // Merge newly found services with list of currently found ones
++ bool needDiscoverServices = false;
++ {
++ QMutexLocker locker(&m_leDevicesMutex);
++ if (m_foundLEDevicesMap.contains(address)) {
++ QBluetoothDeviceInfo::Fields changedFields = QBluetoothDeviceInfo::Field::None;
++ const LEAdvertisingInfo adInfo = m_foundLEDevicesMap.value(address);
++ QList<QBluetoothUuid> foundServices = adInfo.services;
++ if (adInfo.rssi != rssi) {
++ m_foundLEDevicesMap[address].rssi = rssi;
++ changedFields.setFlag(QBluetoothDeviceInfo::Field::RSSI);
++ }
++ if (adInfo.manufacturerData != manufacturerData) {
++ m_foundLEDevicesMap[address].manufacturerData.insert(manufacturerData);
++ if (adInfo.manufacturerData != m_foundLEDevicesMap[address].manufacturerData)
++ changedFields.setFlag(QBluetoothDeviceInfo::Field::ManufacturerData);
++ }
++ if (adInfo.serviceData != serviceData) {
++ m_foundLEDevicesMap[address].serviceData.insert(serviceData);
++ if (adInfo.serviceData != m_foundLEDevicesMap[address].serviceData)
++ changedFields.setFlag((QBluetoothDeviceInfo::Field::ServiceData));
++ }
++ for (const QBluetoothUuid &uuid : std::as_const(uuids)) {
++ if (!foundServices.contains(uuid)) {
++ foundServices.append(uuid);
++ needDiscoverServices = true;
++ }
++ }
++ if (!needDiscoverServices) {
++ if (!changedFields.testFlag(QBluetoothDeviceInfo::Field::None)) {
++ QMetaObject::invokeMethod(this, "deviceDataChanged", Qt::AutoConnection,
++ Q_ARG(QBluetoothAddress, QBluetoothAddress(address)),
++ Q_ARG(QBluetoothDeviceInfo::Fields, changedFields),
++ Q_ARG(qint16, rssi),
++ Q_ARG(ManufacturerData, manufacturerData),
++ Q_ARG(ServiceData, serviceData));
++ }
++ }
++ m_foundLEDevicesMap[address].services = foundServices;
++ } else {
++ needDiscoverServices = true;
++ LEAdvertisingInfo info;
++ info.services = std::move(uuids);
++ info.manufacturerData = std::move(manufacturerData);
++ info.serviceData = std::move(serviceData);
++ info.rssi = rssi;
++ m_foundLEDevicesMap.insert(address, info);
++ }
++ }
++ if (needDiscoverServices) {
++ ++m_pendingDevices; // as if we discovered a new LE device
++ auto thisPtr = shared_from_this();
++ auto asyncOp = BluetoothLEDevice::FromBluetoothAddressAsync(address);
++ asyncOp.Completed([thisPtr, address](auto &&op, AsyncStatus status) {
++ if (thisPtr) {
++ if (status == AsyncStatus::Completed) {
++ BluetoothLEDevice device = op.GetResults();
++ if (device) {
++ thisPtr->handleLowEnergyDevice(device);
++ return;
++ }
++ }
++ // status != Completed or failed to extract result
++ qCDebug(QT_BT_WINDOWS) << "Failed to get LE device from address"
++ << QBluetoothAddress(address);
++ invokeDecrementPendingDevicesCountAndCheckFinished(thisPtr);
++ }
++ });
++ }
++ }
++
++ void QWinRTBluetoothDeviceDiscoveryWorker::stopAdvertisementWatcher()
++ {
++ m_advertisementWatcher->stop();
++ if (isFinished())
++ finishDiscovery();
++ }
++
++ std::shared_ptr<QBluetoothDeviceWatcherWinRT>
++ QWinRTBluetoothDeviceDiscoveryWorker::createDeviceWatcher(winrt::hstring selector, int watcherId)
++ {
++ auto watcher = std::make_shared<QBluetoothDeviceWatcherWinRT>(
++ watcherId, selector, DeviceInformationKind::AssociationEndpoint);
++ if (watcher) {
++ connect(watcher.get(), &QBluetoothDeviceWatcherWinRT::deviceAdded,
++ this, &QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothDeviceFound,
++ Qt::QueuedConnection);
++ connect(watcher.get(), &QBluetoothDeviceWatcherWinRT::enumerationCompleted,
++ this, &QWinRTBluetoothDeviceDiscoveryWorker::onDeviceEnumerationCompleted,
++ Qt::QueuedConnection);
++ }
++ return watcher;
++ }
++
++ void QWinRTBluetoothDeviceDiscoveryWorker::generateError(
++ QBluetoothDeviceDiscoveryAgent::Error error, const char *msg)
++ {
++ emit errorOccured(error);
++ qCWarning(QT_BT_WINDOWS) << msg;
++ }
++
++ void QWinRTBluetoothDeviceDiscoveryWorker::invokeDeviceFoundWithDebug(const QBluetoothDeviceInfo &info)
++ {
++ qCDebug(QT_BT_WINDOWS) << "Discovered BTLE device: " << info.address() << info.name()
++ << "Num UUIDs" << info.serviceUuids().size() << "RSSI:" << info.rssi()
++ << "Num manufacturer data" << info.manufacturerData().size()
++ << "Num service data" << info.serviceData().size();
++
++ QMetaObject::invokeMethod(this, "deviceFound", Qt::AutoConnection,
++ Q_ARG(QBluetoothDeviceInfo, info));
++ }
++
++ // this function executes in main worker thread
++ void QWinRTBluetoothDeviceDiscoveryWorker::getClassicDeviceFromId(const winrt::hstring &id)
++ {
++ ++m_pendingDevices;
++ auto thisPtr = shared_from_this();
++ auto asyncOp = BluetoothDevice::FromIdAsync(id);
++ asyncOp.Completed([thisPtr](auto &&op, AsyncStatus status) {
++ if (thisPtr) {
++ if (status == AsyncStatus::Completed) {
++ BluetoothDevice device = op.GetResults();
++ if (device) {
++ thisPtr->handleClassicDevice(device);
++ return;
++ }
++ }
++ // status != Completed or failed to extract result
++ qCDebug(QT_BT_WINDOWS) << "Failed to get Classic device from id";
++ invokeDecrementPendingDevicesCountAndCheckFinished(thisPtr);
++ }
++ });
++ }
++
++ // this is a callback - executes in a new thread
++ void QWinRTBluetoothDeviceDiscoveryWorker::handleClassicDevice(const BluetoothDevice &device)
++ {
++ const uint64_t address = device.BluetoothAddress();
++ const std::wstring name { device.Name() }; // via operator std::wstring_view()
++ const QString btName = QString::fromStdWString(name);
++ const uint32_t deviceClass = device.ClassOfDevice().RawValue();
++
++ // Use IsPaired() to determine if the device is cached or not.
++ // That is because Windows will always report paired devices, even if
++ // they are not physically available. This is a sub-optimal approach,
++ // but looks like we cannot do better...
++ const auto deviceInfo = SAFE(device.DeviceInformation());
++ if (!deviceInfo) {
++ qCDebug(QT_BT_WINDOWS) << "Failed to obtain DeviceInformation for Classic device";
++ invokeDecrementPendingDevicesCountAndCheckFinished(shared_from_this());
++ return;
++ }
++ const bool isCached = SAFE(deviceInfo.Pairing().IsPaired());
++
++ auto thisPtr = shared_from_this();
++ auto asyncOp = device.GetRfcommServicesAsync();
++ asyncOp.Completed([thisPtr, address, btName, deviceClass, isCached]
++ (auto &&op, AsyncStatus status) {
++ if (thisPtr) {
++ if (status == AsyncStatus::Completed) {
++ auto servicesResult = op.GetResults();
++ if (servicesResult) {
++ thisPtr->handleRfcommServices(servicesResult, address, btName,
++ deviceClass, isCached);
++ return;
++ }
++ }
++ // Failed to get services
++ qCDebug(QT_BT_WINDOWS) << "Failed to get RFCOMM services for device" << btName;
++ invokeDecrementPendingDevicesCountAndCheckFinished(thisPtr);
++ }
++ });
++ }
++
++ // this is a callback - executes in a new thread
++ void QWinRTBluetoothDeviceDiscoveryWorker::handleRfcommServices(
++ const RfcommDeviceServicesResult &servicesResult, uint64_t address,
++ const QString &name, uint32_t classOfDeviceInt, bool isCached)
++ {
++ // need to perform the check even if some of the operations fails
++ auto shared = shared_from_this();
++ auto guard = qScopeGuard([shared]() {
++ invokeDecrementPendingDevicesCountAndCheckFinished(shared);
++ });
++ Q_UNUSED(guard); // to suppress warning
++
++ const auto error = servicesResult.Error();
++ if (error != BluetoothError::Success) {
++ qCWarning(QT_BT_WINDOWS) << "Obtain device services completed with BluetoothError"
++ << static_cast<int>(error);
++ return;
++ }
++
++ const auto services = servicesResult.Services();
++ QList<QBluetoothUuid> uuids;
++ for (const auto &service : services) {
++ const auto serviceId = service.ServiceId();
++ const GUID uuid = fromWinRtGuid(serviceId.Uuid());
++ uuids.append(QBluetoothUuid(uuid));
++ }
++
++ const QBluetoothAddress btAddress(address);
++
++ qCDebug(QT_BT_WINDOWS) << "Discovered BT device: " << btAddress << name
++ << "Num UUIDs" << uuids.size();
++
++ QBluetoothDeviceInfo info(btAddress, name, classOfDeviceInt);
++ info.setCoreConfigurations(QBluetoothDeviceInfo::BaseRateCoreConfiguration);
++ info.setServiceUuids(uuids);
++ info.setCached(isCached);
++
++ QMetaObject::invokeMethod(this, "deviceFound", Qt::AutoConnection,
++ Q_ARG(QBluetoothDeviceInfo, info));
++ }
++
++ void QWinRTBluetoothDeviceDiscoveryWorker::decrementPendingDevicesCountAndCheckFinished(
++ std::shared_ptr<QWinRTBluetoothDeviceDiscoveryWorker> worker)
++ {
++ --m_pendingDevices;
++ if (isFinished())
++ finishDiscovery();
++ // Worker is passed here simply to make sure that the object is still alive
++ // when we call this method via QObject::invoke().
++ Q_UNUSED(worker)
++ }
++
++ // this function executes in main worker thread
++ void QWinRTBluetoothDeviceDiscoveryWorker::getLowEnergyDeviceFromId(const winrt::hstring &id)
++ {
++ ++m_pendingDevices;
++ auto asyncOp = BluetoothLEDevice::FromIdAsync(id);
++ auto thisPtr = shared_from_this();
++ asyncOp.Completed([thisPtr](auto &&op, AsyncStatus status) {
++ if (thisPtr) {
++ if (status == AsyncStatus::Completed) {
++ BluetoothLEDevice device = op.GetResults();
++ if (device) {
++ thisPtr->handleLowEnergyDevice(device);
++ return;
++ }
++ }
++ // status != Completed or failed to extract result
++ qCDebug(QT_BT_WINDOWS) << "Failed to get LE device from id";
++ invokeDecrementPendingDevicesCountAndCheckFinished(thisPtr);
++ }
++ });
++ }
++
++ // this is a callback - executes in a new thread
++ void QWinRTBluetoothDeviceDiscoveryWorker::handleLowEnergyDevice(const BluetoothLEDevice &device)
++ {
++ uint64_t address = 0;
++ if (!TRY(address = device.BluetoothAddress())) {
++ qCDebug(QT_BT_WINDOWS) << "Failed to read BluetoothAddress() for LE device";
++ invokeDecrementPendingDevicesCountAndCheckFinished(shared_from_this());
++ return;
++ }
++ const std::wstring name { SAFE(device.Name()) }; // via operator std::wstring_view()
++ const QString btName = QString::fromStdWString(name);
++
++ const auto deviceInfo = SAFE(device.DeviceInformation());
++ if (!deviceInfo) {
++ qCDebug(QT_BT_WINDOWS) << "Failed to obtain DeviceInformation for LE device";
++ invokeDecrementPendingDevicesCountAndCheckFinished(shared_from_this());
++ return;
++ }
++
++ const bool isPaired = SAFE(deviceInfo.Pairing().IsPaired());
++
++ m_leDevicesMutex.lock();
++ const LEAdvertisingInfo adInfo = m_foundLEDevicesMap.value(address);
++ m_leDevicesMutex.unlock();
++ const ManufacturerData manufacturerData = adInfo.manufacturerData;
++ const ServiceData serviceData = adInfo.serviceData;
++ const qint16 rssi = adInfo.rssi;
++
++ QBluetoothDeviceInfo info(QBluetoothAddress(address), btName, 0);
++ info.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration);
++ info.setRssi(rssi);
++ for (quint16 key : manufacturerData.keys())
++ info.setManufacturerData(key, manufacturerData.value(key));
++ for (QBluetoothUuid key : serviceData.keys())
++ info.setServiceData(key, serviceData.value(key));
++
++ // Now we need to figure out if the device is cached or not.
++ // Theoretically, we could use the "System.Devices.Aep.IsPresent"
++ // device property. However, in practice it's not reported, so
++ // let's try to do our best and estimate the value.
++ // If the device is paired, then Windows will always report it,
++ // no matter if it's actually present or not.
++ // So, treat paired devices as cached at first. If the device is
++ // actually available, we'll get an update (at least with a new
++ // rssi), and update the cache state at that point.
++ info.setCached(isPaired);
++
++ // Use the services obtained from the advertisement data if the device is not paired
++ if (!isPaired) {
++ info.setServiceUuids(adInfo.services);
++ invokeDecrementPendingDevicesCountAndCheckFinished(shared_from_this());
++ invokeDeviceFoundWithDebug(info);
++ } else {
++ auto asyncOp = SAFE(device.GetGattServicesAsync());
++ if (!asyncOp) {
++ invokeDecrementPendingDevicesCountAndCheckFinished(shared_from_this());
++ return;
++ }
++ auto thisPtr = shared_from_this();
++ const bool ok = TRY(asyncOp.Completed([thisPtr, info](auto &&op, AsyncStatus status) mutable {
++ if (status == AsyncStatus::Completed) {
++ auto servicesResult = SAFE(op.GetResults());
++ if (servicesResult) {
++ thisPtr->handleGattServices(servicesResult, info);
++ return;
++ }
++ }
++ // Failed to get services
++ qCDebug(QT_BT_WINDOWS) << "Failed to get GATT services for device" << info.name();
++ invokeDecrementPendingDevicesCountAndCheckFinished(thisPtr);
++ }));
++ if (!ok) {
++ invokeDecrementPendingDevicesCountAndCheckFinished(thisPtr);
++ return;
++ }
++ }
++ }
++
++ // this is a callback - executes in a new thread
++ void QWinRTBluetoothDeviceDiscoveryWorker::handleGattServices(
++ const GattDeviceServicesResult &servicesResult, QBluetoothDeviceInfo &info)
++ {
++ // need to perform the check even if some of the operations fails
++ auto shared = shared_from_this();
++ auto guard = qScopeGuard([shared]() {
++ invokeDecrementPendingDevicesCountAndCheckFinished(shared);
++ });
++ Q_UNUSED(guard); // to suppress warning
++
++ try {
++ const auto status = servicesResult.Status();
++ if (status == GattCommunicationStatus::Success) {
++ const auto services = servicesResult.Services();
++ QList<QBluetoothUuid> uuids;
++ for (const auto &service : services) {
++ const GUID uuid = fromWinRtGuid(service.Uuid());
++ uuids.append(QBluetoothUuid(uuid));
++ }
++ info.setServiceUuids(uuids);
++ } else {
++ qCWarning(QT_BT_WINDOWS) << "Obtaining LE services finished with status"
++ << static_cast<int>(status);
++ }
++ } catch (winrt::hresult_error const &e) {
++ LOG_HRESULT(e.code()) << "/* handleGattServices */";
++ }
++ invokeDeviceFoundWithDebug(info);
++ }
++
++ std::shared_ptr<AdvertisementWatcherWrapper>
++ QWinRTBluetoothDeviceDiscoveryWorker::createAdvertisementWatcher()
++ {
++ auto watcher = std::make_shared<AdvertisementWatcherWrapper>();
++ if (watcher) {
++ connect(watcher.get(), &AdvertisementWatcherWrapper::advertisementDataReceived,
++ this, &QWinRTBluetoothDeviceDiscoveryWorker::onAdvertisementDataReceived,
++ Qt::QueuedConnection);
++ }
++ return watcher;
++ }
++
++ QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(
++ const QBluetoothAddress &deviceAdapter, QBluetoothDeviceDiscoveryAgent *parent)
++ : q_ptr(parent), adapterAddress(deviceAdapter)
++ {
++ threadCoInit(this);
++ }
++
++ QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate()
++ {
++ disconnectAndClearWorker();
++ threadCoUninit(this);
++ }
++
++ bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const
++ {
++ return worker != nullptr;
++ }
++
++ QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods()
++ {
++ return (ClassicMethod | LowEnergyMethod);
++ }
++
++ void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)
++ {
++ QBluetoothLocalDevice adapter(adapterAddress);
++ if (!adapter.isValid()) {
++ qCWarning(QT_BT_WINDOWS) << "Cannot find Bluetooth adapter for device search";
++ lastError = QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError;
++ errorString = QBluetoothDeviceDiscoveryAgent::tr("Cannot find valid Bluetooth adapter.");
++ emit q_ptr->errorOccurred(lastError);
++ return;
++ } else if (adapter.hostMode() == QBluetoothLocalDevice::HostPoweredOff) {
++ qCWarning(QT_BT_WINDOWS) << "Bluetooth adapter powered off";
++ lastError = QBluetoothDeviceDiscoveryAgent::PoweredOffError;
++ errorString = QBluetoothDeviceDiscoveryAgent::tr("Bluetooth adapter powered off.");
++ emit q_ptr->errorOccurred(lastError);
++ return;
++ }
++
++ if (worker)
++ return;
++
++ worker = std::make_shared<QWinRTBluetoothDeviceDiscoveryWorker>(methods,
++ lowEnergySearchTimeout);
++ lastError = QBluetoothDeviceDiscoveryAgent::NoError;
++ errorString.clear();
++ discoveredDevices.clear();
++ connect(worker.get(), &QWinRTBluetoothDeviceDiscoveryWorker::deviceFound,
++ this, &QBluetoothDeviceDiscoveryAgentPrivate::registerDevice);
++ connect(worker.get(), &QWinRTBluetoothDeviceDiscoveryWorker::deviceDataChanged,
++ this, &QBluetoothDeviceDiscoveryAgentPrivate::updateDeviceData);
++ connect(worker.get(), &QWinRTBluetoothDeviceDiscoveryWorker::errorOccured,
++ this, &QBluetoothDeviceDiscoveryAgentPrivate::onErrorOccured);
++ connect(worker.get(), &QWinRTBluetoothDeviceDiscoveryWorker::scanFinished,
++ this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished);
++ worker->start();
++ }
++
++ void QBluetoothDeviceDiscoveryAgentPrivate::stop()
++ {
++ Q_Q(QBluetoothDeviceDiscoveryAgent);
++ if (worker) {
++ worker->stop();
++ disconnectAndClearWorker();
++ emit q->canceled();
++ }
++ }
++
++ void QBluetoothDeviceDiscoveryAgentPrivate::registerDevice(const QBluetoothDeviceInfo &info)
++ {
++ Q_Q(QBluetoothDeviceDiscoveryAgent);
++
++ for (QList<QBluetoothDeviceInfo>::iterator iter = discoveredDevices.begin();
++ iter != discoveredDevices.end(); ++iter) {
++ if (iter->address() == info.address()) {
++ qCDebug(QT_BT_WINDOWS) << "Updating device" << iter->name() << iter->address();
++ // merge service uuids
++ QList<QBluetoothUuid> uuids = iter->serviceUuids();
++ uuids.append(info.serviceUuids());
++ const QSet<QBluetoothUuid> uuidSet(uuids.begin(), uuids.end());
++ if (iter->serviceUuids().size() != uuidSet.size())
++ iter->setServiceUuids(uuidSet.values().toVector());
++ if (iter->coreConfigurations() != info.coreConfigurations())
++ iter->setCoreConfigurations(QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration);
++ return;
++ }
++ }
++
++ discoveredDevices << info;
++ emit q->deviceDiscovered(info);
++ }
++
++ void QBluetoothDeviceDiscoveryAgentPrivate::updateDeviceData(const QBluetoothAddress &address,
++ QBluetoothDeviceInfo::Fields fields,
++ qint16 rssi,
++ ManufacturerData manufacturerData,
++ ServiceData serviceData)
++ {
++ if (fields.testFlag(QBluetoothDeviceInfo::Field::None))
++ return;
++
++ Q_Q(QBluetoothDeviceDiscoveryAgent);
++ for (QList<QBluetoothDeviceInfo>::iterator iter = discoveredDevices.begin();
++ iter != discoveredDevices.end(); ++iter) {
++ if (iter->address() == address) {
++ qCDebug(QT_BT_WINDOWS) << "Updating data for device" << iter->name() << iter->address();
++ if (fields.testFlag(QBluetoothDeviceInfo::Field::RSSI))
++ iter->setRssi(rssi);
++ if (fields.testFlag(QBluetoothDeviceInfo::Field::ManufacturerData))
++ for (quint16 key : manufacturerData.keys())
++ iter->setManufacturerData(key, manufacturerData.value(key));
++ if (fields.testFlag(QBluetoothDeviceInfo::Field::ServiceData))
++ for (QBluetoothUuid key : serviceData.keys())
++ iter->setServiceData(key, serviceData.value(key));
++ // We got some data, so the device is definitely available now.
++ // Update the cached state.
++ iter->setCached(false);
++ emit q->deviceUpdated(*iter, fields);
++ return;
++ }
++ }
++ }
++
++ void QBluetoothDeviceDiscoveryAgentPrivate::onErrorOccured(QBluetoothDeviceDiscoveryAgent::Error e)
++ {
++ Q_Q(QBluetoothDeviceDiscoveryAgent);
++ lastError = e;
++ emit q->errorOccurred(e);
++ }
++
++ void QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished()
++ {
++ Q_Q(QBluetoothDeviceDiscoveryAgent);
++ disconnectAndClearWorker();
++ emit q->finished();
++ }
++
++ void QBluetoothDeviceDiscoveryAgentPrivate::disconnectAndClearWorker()
++ {
++ if (!worker)
++ return;
++
++ disconnect(worker.get(), &QWinRTBluetoothDeviceDiscoveryWorker::scanFinished,
++ this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished);
++ disconnect(worker.get(), &QWinRTBluetoothDeviceDiscoveryWorker::deviceFound,
++ this, &QBluetoothDeviceDiscoveryAgentPrivate::registerDevice);
++ disconnect(worker.get(), &QWinRTBluetoothDeviceDiscoveryWorker::deviceDataChanged,
++ this, &QBluetoothDeviceDiscoveryAgentPrivate::updateDeviceData);
++ disconnect(worker.get(), &QWinRTBluetoothDeviceDiscoveryWorker::errorOccured,
++ this, &QBluetoothDeviceDiscoveryAgentPrivate::onErrorOccured);
++
++ worker = nullptr;
++ }
++
++ QT_END_NAMESPACE
++
++ #include <qbluetoothdevicediscoveryagent_winrt.moc>