| /**************************************************************************** |
| ** |
| ** Copyright (C) 2017 The Qt Company Ltd. |
| ** 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 "qbluetoothdevicediscoveryagent_p.h" |
| #include "qbluetoothaddress.h" |
| #include "qbluetoothuuid.h" |
| |
| #ifdef CLASSIC_APP_BUILD |
| #define Q_OS_WINRT |
| #endif |
| #include "qfunctions_winrt.h" |
| |
| #include <QtBluetooth/private/qtbluetoothglobal_p.h> |
| #include <QtBluetooth/private/qbluetoothutils_winrt_p.h> |
| #include <QtCore/QLoggingCategory> |
| #include <QtCore/qmutex.h> |
| #include <QtCore/private/qeventdispatcher_winrt_p.h> |
| #include <QtCore/qmutex.h> |
| |
| #include <robuffer.h> |
| #include <wrl.h> |
| #include <windows.devices.enumeration.h> |
| #include <windows.devices.bluetooth.h> |
| #include <windows.foundation.collections.h> |
| #include <windows.storage.streams.h> |
| |
| #include <windows.devices.bluetooth.advertisement.h> |
| |
| using namespace Microsoft::WRL; |
| using namespace Microsoft::WRL::Wrappers; |
| using namespace ABI::Windows::Foundation; |
| using namespace ABI::Windows::Foundation::Collections; |
| using namespace ABI::Windows::Devices; |
| using namespace ABI::Windows::Devices::Bluetooth; |
| using namespace ABI::Windows::Devices::Bluetooth::Advertisement; |
| using namespace ABI::Windows::Devices::Enumeration; |
| using namespace ABI::Windows::Storage::Streams; |
| |
| QT_BEGIN_NAMESPACE |
| |
| Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT) |
| |
| #define WARN_AND_RETURN_IF_FAILED(msg, ret) \ |
| if (FAILED(hr)) { \ |
| qCWarning(QT_BT_WINRT) << msg; \ |
| ret; \ |
| } |
| |
| #define WARN_AND_CONTINUE_IF_FAILED(msg) \ |
| if (FAILED(hr)) { \ |
| qCWarning(QT_BT_WINRT) << msg; \ |
| continue; \ |
| } |
| |
| static ManufacturerData extractManufacturerData(ComPtr<IBluetoothLEAdvertisement> ad) |
| { |
| ManufacturerData ret; |
| ComPtr<IVector<BluetoothLEManufacturerData*>> data; |
| HRESULT hr = ad->get_ManufacturerData(&data); |
| WARN_AND_RETURN_IF_FAILED("Could not obtain list of manufacturer data.", return ret); |
| quint32 size; |
| hr = data->get_Size(&size); |
| WARN_AND_RETURN_IF_FAILED("Could not obtain manufacturer data's list size.", return ret); |
| for (quint32 i = 0; i < size; ++i) { |
| ComPtr<IBluetoothLEManufacturerData> d; |
| hr = data->GetAt(i, &d); |
| WARN_AND_CONTINUE_IF_FAILED("Could not obtain manufacturer data."); |
| quint16 id; |
| hr = d->get_CompanyId(&id); |
| WARN_AND_CONTINUE_IF_FAILED("Could not obtain manufacturer data company id."); |
| ComPtr<IBuffer> buffer; |
| hr = d->get_Data(&buffer); |
| WARN_AND_CONTINUE_IF_FAILED("Could not obtain manufacturer data set."); |
| const QByteArray bufferData = byteArrayFromBuffer(buffer); |
| if (ret.contains(id)) |
| qCWarning(QT_BT_WINRT) << "Company ID already present in manufacturer data."; |
| ret.insert(id, bufferData); |
| } |
| return ret; |
| } |
| |
| class QWinRTBluetoothDeviceDiscoveryWorker : public QObject |
| { |
| Q_OBJECT |
| public: |
| explicit QWinRTBluetoothDeviceDiscoveryWorker(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods); |
| ~QWinRTBluetoothDeviceDiscoveryWorker(); |
| void start(); |
| void stopLEWatcher(); |
| |
| private: |
| void startDeviceDiscovery(QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode); |
| void onDeviceDiscoveryFinished(IAsyncOperation<DeviceInformationCollection *> *op, |
| QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode); |
| void gatherDeviceInformation(IDeviceInformation *deviceInfo, |
| QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode); |
| void gatherMultipleDeviceInformation(quint32 deviceCount, IVectorView<DeviceInformation *> *devices, |
| QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode); |
| void setupLEDeviceWatcher(); |
| void classicBluetoothInfoFromDeviceIdAsync(HSTRING deviceId); |
| void leBluetoothInfoFromDeviceIdAsync(HSTRING deviceId); |
| void leBluetoothInfoFromAddressAsync(quint64 address); |
| HRESULT onPairedClassicBluetoothDeviceFoundAsync(IAsyncOperation<BluetoothDevice *> *op, AsyncStatus status ); |
| HRESULT onPairedBluetoothLEDeviceFoundAsync(IAsyncOperation<BluetoothLEDevice *> *op, AsyncStatus status); |
| HRESULT onBluetoothLEDeviceFoundAsync(IAsyncOperation<BluetoothLEDevice *> *op, AsyncStatus status); |
| enum PairingCheck { |
| CheckForPairing, |
| OmitPairingCheck |
| }; |
| HRESULT onBluetoothLEDeviceFound(ComPtr<IBluetoothLEDevice> device, PairingCheck pairingCheck); |
| #if QT_CONFIG(winrt_btle_no_pairing) |
| HRESULT onBluetoothLEDeviceFound(ComPtr<IBluetoothLEDevice> device); |
| #endif |
| |
| public slots: |
| void finishDiscovery(); |
| |
| Q_SIGNALS: |
| void deviceFound(const QBluetoothDeviceInfo &info); |
| void deviceDataChanged(const QBluetoothAddress &address, QBluetoothDeviceInfo::Fields, |
| qint16 rssi, ManufacturerData manufacturerData); |
| void scanFinished(); |
| |
| public: |
| quint8 requestedModes; |
| |
| private: |
| ComPtr<IBluetoothLEAdvertisementWatcher> m_leWatcher; |
| EventRegistrationToken m_leDeviceAddedToken; |
| #if QT_CONFIG(winrt_btle_no_pairing) |
| QMutex m_foundDevicesMutex; |
| struct LEAdvertisingInfo { |
| QVector<QBluetoothUuid> services; |
| qint16 rssi = 0; |
| }; |
| |
| QMap<quint64, LEAdvertisingInfo> m_foundLEDevicesMap; |
| #endif |
| QMap<quint64, qint16> m_foundLEDevices; |
| QMap<quint64, ManufacturerData> m_foundLEManufacturerData; |
| int m_pendingPairedDevices; |
| |
| ComPtr<IBluetoothDeviceStatics> m_deviceStatics; |
| ComPtr<IBluetoothLEDeviceStatics> m_leDeviceStatics; |
| }; |
| |
| QWinRTBluetoothDeviceDiscoveryWorker::QWinRTBluetoothDeviceDiscoveryWorker(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods) |
| : requestedModes(methods) |
| , m_pendingPairedDevices(0) |
| { |
| qRegisterMetaType<QBluetoothDeviceInfo>(); |
| qRegisterMetaType<QBluetoothDeviceInfo::Fields>(); |
| qRegisterMetaType<ManufacturerData>(); |
| |
| #ifdef CLASSIC_APP_BUILD |
| CoInitialize(NULL); |
| #endif |
| HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothDevice).Get(), &m_deviceStatics); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothLEDevice).Get(), &m_leDeviceStatics); |
| Q_ASSERT_SUCCEEDED(hr); |
| } |
| |
| QWinRTBluetoothDeviceDiscoveryWorker::~QWinRTBluetoothDeviceDiscoveryWorker() |
| { |
| stopLEWatcher(); |
| #ifdef CLASSIC_APP_BUILD |
| CoUninitialize(); |
| #endif |
| } |
| |
| void QWinRTBluetoothDeviceDiscoveryWorker::start() |
| { |
| QEventDispatcherWinRT::runOnXamlThread([this]() { |
| if (requestedModes & QBluetoothDeviceDiscoveryAgent::ClassicMethod) |
| startDeviceDiscovery(QBluetoothDeviceDiscoveryAgent::ClassicMethod); |
| |
| if (requestedModes & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) { |
| startDeviceDiscovery(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); |
| setupLEDeviceWatcher(); |
| } |
| return S_OK; |
| }); |
| |
| qCDebug(QT_BT_WINRT) << "Worker started"; |
| } |
| |
| void QWinRTBluetoothDeviceDiscoveryWorker::stopLEWatcher() |
| { |
| if (m_leWatcher) { |
| HRESULT hr = m_leWatcher->Stop(); |
| Q_ASSERT_SUCCEEDED(hr); |
| if (m_leDeviceAddedToken.value) { |
| hr = m_leWatcher->remove_Received(m_leDeviceAddedToken); |
| Q_ASSERT_SUCCEEDED(hr); |
| } |
| } |
| } |
| |
| void QWinRTBluetoothDeviceDiscoveryWorker::startDeviceDiscovery(QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode) |
| { |
| HString deviceSelector; |
| ComPtr<IDeviceInformationStatics> deviceInformationStatics; |
| HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Enumeration_DeviceInformation).Get(), &deviceInformationStatics); |
| WARN_AND_RETURN_IF_FAILED("Could not obtain device information statics", return); |
| if (mode == QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) |
| m_leDeviceStatics->GetDeviceSelector(deviceSelector.GetAddressOf()); |
| else |
| m_deviceStatics->GetDeviceSelector(deviceSelector.GetAddressOf()); |
| ComPtr<IAsyncOperation<DeviceInformationCollection *>> op; |
| hr = deviceInformationStatics->FindAllAsyncAqsFilter(deviceSelector.Get(), &op); |
| WARN_AND_RETURN_IF_FAILED("Could not start bluetooth device discovery operation", return); |
| QPointer<QWinRTBluetoothDeviceDiscoveryWorker> thisPointer(this); |
| hr = op->put_Completed( |
| Callback<IAsyncOperationCompletedHandler<DeviceInformationCollection *>>([thisPointer, mode](IAsyncOperation<DeviceInformationCollection *> *op, AsyncStatus status) { |
| if (status == Completed && thisPointer) |
| thisPointer->onDeviceDiscoveryFinished(op, mode); |
| return S_OK; |
| }).Get()); |
| WARN_AND_RETURN_IF_FAILED("Could not add callback to bluetooth device discovery operation", return); |
| } |
| |
| void QWinRTBluetoothDeviceDiscoveryWorker::onDeviceDiscoveryFinished(IAsyncOperation<DeviceInformationCollection *> *op, QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode) |
| { |
| qCDebug(QT_BT_WINRT) << (mode == QBluetoothDeviceDiscoveryAgent::ClassicMethod ? "BT" : "BTLE") |
| << " scan completed"; |
| ComPtr<IVectorView<DeviceInformation *>> devices; |
| HRESULT hr; |
| hr = op->GetResults(&devices); |
| Q_ASSERT_SUCCEEDED(hr); |
| quint32 deviceCount; |
| hr = devices->get_Size(&deviceCount); |
| Q_ASSERT_SUCCEEDED(hr); |
| |
| // For classic discovery only paired devices will be found. If we only do classic disovery and |
| // no device is found, the scan is finished. |
| if (requestedModes == QBluetoothDeviceDiscoveryAgent::ClassicMethod && |
| deviceCount == 0) { |
| finishDiscovery(); |
| return; |
| } |
| |
| m_pendingPairedDevices += deviceCount; |
| gatherMultipleDeviceInformation(deviceCount, devices.Get(), mode); |
| } |
| |
| void QWinRTBluetoothDeviceDiscoveryWorker::gatherDeviceInformation(IDeviceInformation *deviceInfo, QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode) |
| { |
| HString deviceId; |
| HRESULT hr; |
| hr = deviceInfo->get_Id(deviceId.GetAddressOf()); |
| Q_ASSERT_SUCCEEDED(hr); |
| if (mode == QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) |
| leBluetoothInfoFromDeviceIdAsync(deviceId.Get()); |
| else |
| classicBluetoothInfoFromDeviceIdAsync(deviceId.Get()); |
| } |
| |
| void QWinRTBluetoothDeviceDiscoveryWorker::gatherMultipleDeviceInformation(quint32 deviceCount, IVectorView<DeviceInformation *> *devices, QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode) |
| { |
| for (quint32 i = 0; i < deviceCount; ++i) { |
| ComPtr<IDeviceInformation> device; |
| HRESULT hr; |
| hr = devices->GetAt(i, &device); |
| Q_ASSERT_SUCCEEDED(hr); |
| gatherDeviceInformation(device.Get(), mode); |
| } |
| } |
| |
| void QWinRTBluetoothDeviceDiscoveryWorker::setupLEDeviceWatcher() |
| { |
| HRESULT hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_Advertisement_BluetoothLEAdvertisementWatcher).Get(), &m_leWatcher); |
| Q_ASSERT_SUCCEEDED(hr); |
| #if QT_CONFIG(winrt_btle_no_pairing) |
| if (supportsNewLEApi()) { |
| hr = m_leWatcher->put_ScanningMode(BluetoothLEScanningMode_Active); |
| Q_ASSERT_SUCCEEDED(hr); |
| } |
| #endif // winrt_btle_no_pairing |
| hr = m_leWatcher->add_Received(Callback<ITypedEventHandler<BluetoothLEAdvertisementWatcher *, BluetoothLEAdvertisementReceivedEventArgs *>>([this](IBluetoothLEAdvertisementWatcher *, IBluetoothLEAdvertisementReceivedEventArgs *args) { |
| quint64 address; |
| HRESULT hr; |
| hr = args->get_BluetoothAddress(&address); |
| Q_ASSERT_SUCCEEDED(hr); |
| qint16 rssi; |
| hr = args->get_RawSignalStrengthInDBm(&rssi); |
| Q_ASSERT_SUCCEEDED(hr); |
| ComPtr<IBluetoothLEAdvertisement> ad; |
| hr = args->get_Advertisement(&ad); |
| Q_ASSERT_SUCCEEDED(hr); |
| const ManufacturerData manufacturerData = extractManufacturerData(ad); |
| QBluetoothDeviceInfo::Fields changedFields = QBluetoothDeviceInfo::Field::None; |
| if (!m_foundLEManufacturerData.contains(address)) { |
| m_foundLEManufacturerData.insert(address, manufacturerData); |
| changedFields.setFlag(QBluetoothDeviceInfo::Field::ManufacturerData); |
| } else if (m_foundLEManufacturerData.value(address) != manufacturerData) { |
| m_foundLEManufacturerData[address] = manufacturerData; |
| changedFields.setFlag(QBluetoothDeviceInfo::Field::ManufacturerData); |
| } |
| #if QT_CONFIG(winrt_btle_no_pairing) |
| if (supportsNewLEApi()) { |
| ComPtr<IVector<GUID>> guids; |
| hr = ad->get_ServiceUuids(&guids); |
| Q_ASSERT_SUCCEEDED(hr); |
| quint32 size; |
| hr = guids->get_Size(&size); |
| Q_ASSERT_SUCCEEDED(hr); |
| QVector<QBluetoothUuid> serviceUuids; |
| for (quint32 i = 0; i < size; ++i) { |
| GUID guid; |
| hr = guids->GetAt(i, &guid); |
| Q_ASSERT_SUCCEEDED(hr); |
| QBluetoothUuid uuid(guid); |
| serviceUuids.append(uuid); |
| } |
| QMutexLocker locker(&m_foundDevicesMutex); |
| // Merge newly found services with list of currently found ones |
| if (m_foundLEDevicesMap.contains(address)) { |
| if (size == 0) |
| return S_OK; |
| const LEAdvertisingInfo adInfo = m_foundLEDevicesMap.value(address); |
| QVector<QBluetoothUuid> foundServices = adInfo.services; |
| if (adInfo.rssi != rssi) { |
| m_foundLEDevicesMap[address].rssi = rssi; |
| changedFields.setFlag(QBluetoothDeviceInfo::Field::RSSI); |
| } |
| bool newServiceAdded = false; |
| for (const QBluetoothUuid &uuid : qAsConst(serviceUuids)) { |
| if (!foundServices.contains(uuid)) { |
| foundServices.append(uuid); |
| newServiceAdded = true; |
| } |
| } |
| if (!newServiceAdded) { |
| 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)); |
| } |
| return S_OK; |
| } |
| m_foundLEDevicesMap[address].services = foundServices; |
| } else { |
| LEAdvertisingInfo info; |
| info.services = std::move(serviceUuids); |
| info.rssi = rssi; |
| m_foundLEDevicesMap.insert(address, info); |
| } |
| |
| locker.unlock(); |
| } else |
| #endif |
| { |
| if (m_foundLEDevices.contains(address)) { |
| if (m_foundLEDevices.value(address) != rssi) { |
| m_foundLEDevices[address] = rssi; |
| changedFields.setFlag(QBluetoothDeviceInfo::Field::RSSI); |
| } |
| 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)); |
| } |
| return S_OK; |
| } |
| m_foundLEDevices.insert(address, rssi); |
| } |
| leBluetoothInfoFromAddressAsync(address); |
| return S_OK; |
| }).Get(), &m_leDeviceAddedToken); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = m_leWatcher->Start(); |
| Q_ASSERT_SUCCEEDED(hr); |
| } |
| |
| void QWinRTBluetoothDeviceDiscoveryWorker::finishDiscovery() |
| { |
| emit scanFinished(); |
| stopLEWatcher(); |
| deleteLater(); |
| } |
| |
| // "deviceFound" will be emitted at the end of the deviceFromIdOperation callback |
| void QWinRTBluetoothDeviceDiscoveryWorker::classicBluetoothInfoFromDeviceIdAsync(HSTRING deviceId) |
| { |
| HRESULT hr; |
| hr = QEventDispatcherWinRT::runOnXamlThread([deviceId, this]() { |
| ComPtr<IAsyncOperation<BluetoothDevice *>> deviceFromIdOperation; |
| // on Windows 10 FromIdAsync might ask for device permission. We cannot wait here but have to handle that asynchronously |
| HRESULT hr = m_deviceStatics->FromIdAsync(deviceId, &deviceFromIdOperation); |
| if (FAILED(hr)) { |
| --m_pendingPairedDevices; |
| if (!m_pendingPairedDevices |
| && !(requestedModes & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)) |
| finishDiscovery(); |
| qCWarning(QT_BT_WINRT) << "Could not obtain bluetooth device from id"; |
| return S_OK; |
| } |
| QPointer<QWinRTBluetoothDeviceDiscoveryWorker> thisPointer(this); |
| hr = deviceFromIdOperation->put_Completed(Callback<IAsyncOperationCompletedHandler<BluetoothDevice *>> |
| ([thisPointer](IAsyncOperation<BluetoothDevice *> *op, AsyncStatus status) |
| { |
| if (thisPointer) { |
| if (status == Completed) |
| thisPointer->onPairedClassicBluetoothDeviceFoundAsync(op, status); |
| --thisPointer->m_pendingPairedDevices; |
| } |
| return S_OK; |
| }).Get()); |
| if (FAILED(hr)) { |
| --m_pendingPairedDevices; |
| if (!m_pendingPairedDevices |
| && !(requestedModes & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)) |
| finishDiscovery(); |
| qCWarning(QT_BT_WINRT) << "Could not register device found callback"; |
| return S_OK; |
| } |
| return S_OK; |
| }); |
| Q_ASSERT_SUCCEEDED(hr); |
| } |
| |
| // "deviceFound" will be emitted at the end of the deviceFromIdOperation callback |
| void QWinRTBluetoothDeviceDiscoveryWorker::leBluetoothInfoFromDeviceIdAsync(HSTRING deviceId) |
| { |
| HRESULT hr; |
| hr = QEventDispatcherWinRT::runOnXamlThread([deviceId, this]() { |
| ComPtr<IAsyncOperation<BluetoothLEDevice *>> deviceFromIdOperation; |
| // on Windows 10 FromIdAsync might ask for device permission. We cannot wait here but have to handle that asynchronously |
| HRESULT hr = m_leDeviceStatics->FromIdAsync(deviceId, &deviceFromIdOperation); |
| if (FAILED(hr)) { |
| --m_pendingPairedDevices; |
| qCWarning(QT_BT_WINRT) << "Could not obtain bluetooth device from id"; |
| return S_OK; |
| } |
| QPointer<QWinRTBluetoothDeviceDiscoveryWorker> thisPointer(this); |
| hr = deviceFromIdOperation->put_Completed(Callback<IAsyncOperationCompletedHandler<BluetoothLEDevice *>> |
| ([thisPointer] (IAsyncOperation<BluetoothLEDevice *> *op, AsyncStatus status) |
| { |
| if (thisPointer) { |
| if (status == Completed) |
| thisPointer->onPairedBluetoothLEDeviceFoundAsync(op, status); |
| --thisPointer->m_pendingPairedDevices; |
| } |
| return S_OK; |
| }).Get()); |
| if (FAILED(hr)) { |
| --m_pendingPairedDevices; |
| qCWarning(QT_BT_WINRT) << "Could not register device found callback"; |
| return S_OK; |
| } |
| return S_OK; |
| }); |
| Q_ASSERT_SUCCEEDED(hr); |
| } |
| |
| // "deviceFound" will be emitted at the end of the deviceFromAdressOperation callback |
| void QWinRTBluetoothDeviceDiscoveryWorker::leBluetoothInfoFromAddressAsync(quint64 address) |
| { |
| HRESULT hr; |
| hr = QEventDispatcherWinRT::runOnXamlThread([address, this]() { |
| ComPtr<IAsyncOperation<BluetoothLEDevice *>> deviceFromAddressOperation; |
| // on Windows 10 FromBluetoothAddressAsync might ask for device permission. We cannot wait |
| // here but have to handle that asynchronously |
| HRESULT hr = m_leDeviceStatics->FromBluetoothAddressAsync(address, &deviceFromAddressOperation); |
| if (FAILED(hr)) { |
| qCWarning(QT_BT_WINRT) << "Could not obtain bluetooth device from address"; |
| return S_OK; |
| } |
| QPointer<QWinRTBluetoothDeviceDiscoveryWorker> thisPointer(this); |
| hr = deviceFromAddressOperation->put_Completed(Callback<IAsyncOperationCompletedHandler<BluetoothLEDevice *>> |
| ([thisPointer](IAsyncOperation<BluetoothLEDevice *> *op, AsyncStatus status) |
| { |
| if (status == Completed && thisPointer) |
| thisPointer->onBluetoothLEDeviceFoundAsync(op, status); |
| return S_OK; |
| }).Get()); |
| if (FAILED(hr)) { |
| qCWarning(QT_BT_WINRT) << "Could not register device found callback"; |
| return S_OK; |
| } |
| return S_OK; |
| }); |
| Q_ASSERT_SUCCEEDED(hr); |
| } |
| |
| HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onPairedClassicBluetoothDeviceFoundAsync(IAsyncOperation<BluetoothDevice *> *op, AsyncStatus status) |
| { |
| --m_pendingPairedDevices; |
| if (status != AsyncStatus::Completed) |
| return S_OK; |
| |
| ComPtr<IBluetoothDevice> device; |
| HRESULT hr = op->GetResults(&device); |
| Q_ASSERT_SUCCEEDED(hr); |
| |
| if (!device) |
| return S_OK; |
| |
| UINT64 address; |
| HString name; |
| ComPtr<IBluetoothClassOfDevice> classOfDevice; |
| UINT32 classOfDeviceInt; |
| hr = device->get_BluetoothAddress(&address); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = device->get_Name(name.GetAddressOf()); |
| Q_ASSERT_SUCCEEDED(hr); |
| const QString btName = QString::fromWCharArray(WindowsGetStringRawBuffer(name.Get(), nullptr)); |
| hr = device->get_ClassOfDevice(&classOfDevice); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = classOfDevice->get_RawValue(&classOfDeviceInt); |
| Q_ASSERT_SUCCEEDED(hr); |
| IVectorView <Rfcomm::RfcommDeviceService *> *deviceServices; |
| hr = device->get_RfcommServices(&deviceServices); |
| if (hr == E_ACCESSDENIED) { |
| qCWarning(QT_BT_WINRT) << "Could not obtain device services. Please check you have " |
| "permission to access the device."; |
| } else { |
| Q_ASSERT_SUCCEEDED(hr); |
| uint serviceCount; |
| hr = deviceServices->get_Size(&serviceCount); |
| Q_ASSERT_SUCCEEDED(hr); |
| QVector<QBluetoothUuid> uuids; |
| for (uint i = 0; i < serviceCount; ++i) { |
| ComPtr<Rfcomm::IRfcommDeviceService> service; |
| hr = deviceServices->GetAt(i, &service); |
| Q_ASSERT_SUCCEEDED(hr); |
| ComPtr<Rfcomm::IRfcommServiceId> id; |
| hr = service->get_ServiceId(&id); |
| Q_ASSERT_SUCCEEDED(hr); |
| GUID uuid; |
| hr = id->get_Uuid(&uuid); |
| Q_ASSERT_SUCCEEDED(hr); |
| uuids.append(QBluetoothUuid(uuid)); |
| } |
| |
| qCDebug(QT_BT_WINRT) << "Discovered BT device: " << QString::number(address) << btName |
| << "Num UUIDs" << uuids.count(); |
| |
| QBluetoothDeviceInfo info(QBluetoothAddress(address), btName, classOfDeviceInt); |
| info.setCoreConfigurations(QBluetoothDeviceInfo::BaseRateCoreConfiguration); |
| info.setServiceUuids(uuids); |
| info.setCached(true); |
| |
| QMetaObject::invokeMethod(this, "deviceFound", Qt::AutoConnection, |
| Q_ARG(QBluetoothDeviceInfo, info)); |
| } |
| if (!m_pendingPairedDevices && !(requestedModes & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)) |
| finishDiscovery(); |
| return S_OK; |
| } |
| |
| HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onPairedBluetoothLEDeviceFoundAsync(IAsyncOperation<BluetoothLEDevice *> *op, AsyncStatus status) |
| { |
| --m_pendingPairedDevices; |
| if (status != AsyncStatus::Completed) |
| return S_OK; |
| |
| ComPtr<IBluetoothLEDevice> device; |
| HRESULT hr; |
| hr = op->GetResults(&device); |
| Q_ASSERT_SUCCEEDED(hr); |
| #if QT_CONFIG(winrt_btle_no_pairing) |
| if (supportsNewLEApi()) |
| return onBluetoothLEDeviceFound(device); |
| else |
| #endif |
| return onBluetoothLEDeviceFound(device, OmitPairingCheck); |
| } |
| |
| HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothLEDeviceFoundAsync(IAsyncOperation<BluetoothLEDevice *> *op, AsyncStatus status) |
| { |
| if (status != AsyncStatus::Completed) |
| return S_OK; |
| |
| ComPtr<IBluetoothLEDevice> device; |
| HRESULT hr; |
| hr = op->GetResults(&device); |
| Q_ASSERT_SUCCEEDED(hr); |
| #if QT_CONFIG(winrt_btle_no_pairing) |
| if (supportsNewLEApi()) |
| return onBluetoothLEDeviceFound(device); |
| else |
| #endif |
| return onBluetoothLEDeviceFound(device, PairingCheck::CheckForPairing); |
| } |
| |
| HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothLEDeviceFound(ComPtr<IBluetoothLEDevice> device, PairingCheck pairingCheck) |
| { |
| if (!device) { |
| qCDebug(QT_BT_WINRT) << "onBluetoothLEDeviceFound: No device given"; |
| return S_OK; |
| } |
| |
| if (pairingCheck == CheckForPairing) { |
| ComPtr<IBluetoothLEDevice2> device2; |
| HRESULT hr = device.As(&device2); |
| Q_ASSERT_SUCCEEDED(hr); |
| ComPtr<IDeviceInformation> deviceInfo; |
| hr = device2->get_DeviceInformation(&deviceInfo); |
| Q_ASSERT_SUCCEEDED(hr); |
| if (!deviceInfo) { |
| qCDebug(QT_BT_WINRT) << "onBluetoothLEDeviceFound: Could not obtain device information"; |
| return S_OK; |
| } |
| ComPtr<IDeviceInformation2> deviceInfo2; |
| hr = deviceInfo.As(&deviceInfo2); |
| Q_ASSERT_SUCCEEDED(hr); |
| ComPtr<IDeviceInformationPairing> pairing; |
| hr = deviceInfo2->get_Pairing(&pairing); |
| Q_ASSERT_SUCCEEDED(hr); |
| boolean isPaired; |
| hr = pairing->get_IsPaired(&isPaired); |
| Q_ASSERT_SUCCEEDED(hr); |
| // We need a paired device in order to be able to obtain its information |
| if (!isPaired) { |
| ComPtr<IAsyncOperation<DevicePairingResult *>> pairingOp; |
| QPointer<QWinRTBluetoothDeviceDiscoveryWorker> tPointer(this); |
| hr = pairing.Get()->PairAsync(&pairingOp); |
| Q_ASSERT_SUCCEEDED(hr); |
| pairingOp->put_Completed( |
| Callback<IAsyncOperationCompletedHandler<DevicePairingResult *>>([device, tPointer](IAsyncOperation<DevicePairingResult *> *op, AsyncStatus status) { |
| if (!tPointer) |
| return S_OK; |
| |
| if (status != AsyncStatus::Completed) { |
| qCDebug(QT_BT_WINRT) << "Could not pair device"; |
| return S_OK; |
| } |
| |
| ComPtr<IDevicePairingResult> result; |
| op->GetResults(&result); |
| |
| DevicePairingResultStatus pairingStatus; |
| result.Get()->get_Status(&pairingStatus); |
| |
| if (pairingStatus != DevicePairingResultStatus_Paired) { |
| qCDebug(QT_BT_WINRT) << "Could not pair device"; |
| return S_OK; |
| } |
| |
| tPointer->onBluetoothLEDeviceFound(device, OmitPairingCheck); |
| return S_OK; |
| }).Get()); |
| return S_OK; |
| } |
| } |
| |
| UINT64 address; |
| HString name; |
| HRESULT hr = device->get_BluetoothAddress(&address); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = device->get_Name(name.GetAddressOf()); |
| Q_ASSERT_SUCCEEDED(hr); |
| const QString btName = QString::fromWCharArray(WindowsGetStringRawBuffer(name.Get(), nullptr)); |
| IVectorView <GenericAttributeProfile::GattDeviceService *> *deviceServices; |
| hr = device->get_GattServices(&deviceServices); |
| Q_ASSERT_SUCCEEDED(hr); |
| uint serviceCount; |
| hr = deviceServices->get_Size(&serviceCount); |
| Q_ASSERT_SUCCEEDED(hr); |
| QVector<QBluetoothUuid> uuids; |
| for (uint i = 0; i < serviceCount; ++i) { |
| ComPtr<GenericAttributeProfile::IGattDeviceService> service; |
| hr = deviceServices->GetAt(i, &service); |
| Q_ASSERT_SUCCEEDED(hr); |
| ComPtr<Rfcomm::IRfcommServiceId> id; |
| GUID uuid; |
| hr = service->get_Uuid(&uuid); |
| Q_ASSERT_SUCCEEDED(hr); |
| uuids.append(QBluetoothUuid(uuid)); |
| } |
| const qint16 rssi = m_foundLEDevices.value(address); |
| const ManufacturerData manufacturerData = m_foundLEManufacturerData.value(address); |
| |
| qCDebug(QT_BT_WINRT) << "Discovered BTLE device: " << QString::number(address) << btName |
| << "Num UUIDs" << uuids.count() << "RSSI:" << rssi |
| << "Num manufacturer data" << manufacturerData.count(); |
| |
| QBluetoothDeviceInfo info(QBluetoothAddress(address), btName, 0); |
| info.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration); |
| info.setServiceUuids(uuids); |
| info.setRssi(rssi); |
| for (const quint16 key : manufacturerData.keys()) |
| info.setManufacturerData(key, manufacturerData.value(key)); |
| info.setCached(true); |
| |
| QMetaObject::invokeMethod(this, "deviceFound", Qt::AutoConnection, |
| Q_ARG(QBluetoothDeviceInfo, info)); |
| return S_OK; |
| } |
| |
| #if QT_CONFIG(winrt_btle_no_pairing) |
| HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothLEDeviceFound(ComPtr<IBluetoothLEDevice> device) |
| { |
| if (!device) { |
| qCDebug(QT_BT_WINRT) << "onBluetoothLEDeviceFound: No device given"; |
| return S_OK; |
| } |
| |
| UINT64 address; |
| HString name; |
| HRESULT hr = device->get_BluetoothAddress(&address); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = device->get_Name(name.GetAddressOf()); |
| Q_ASSERT_SUCCEEDED(hr); |
| const QString btName = QString::fromWCharArray(WindowsGetStringRawBuffer(name.Get(), nullptr)); |
| |
| ComPtr<IBluetoothLEDevice2> device2; |
| hr = device.As(&device2); |
| Q_ASSERT_SUCCEEDED(hr); |
| ComPtr<IDeviceInformation> deviceInfo; |
| hr = device2->get_DeviceInformation(&deviceInfo); |
| Q_ASSERT_SUCCEEDED(hr); |
| if (!deviceInfo) { |
| qCDebug(QT_BT_WINRT) << "onBluetoothLEDeviceFound: Could not obtain device information"; |
| return S_OK; |
| } |
| ComPtr<IDeviceInformation2> deviceInfo2; |
| hr = deviceInfo.As(&deviceInfo2); |
| Q_ASSERT_SUCCEEDED(hr); |
| ComPtr<IDeviceInformationPairing> pairing; |
| hr = deviceInfo2->get_Pairing(&pairing); |
| Q_ASSERT_SUCCEEDED(hr); |
| boolean isPaired; |
| hr = pairing->get_IsPaired(&isPaired); |
| Q_ASSERT_SUCCEEDED(hr); |
| QVector<QBluetoothUuid> uuids; |
| |
| const LEAdvertisingInfo adInfo = m_foundLEDevicesMap.value(address); |
| const qint16 rssi = adInfo.rssi; |
| // Use the services obtained from the advertisement data if the device is not paired |
| if (!isPaired) { |
| uuids = adInfo.services; |
| } else { |
| IVectorView <GenericAttributeProfile::GattDeviceService *> *deviceServices; |
| hr = device->get_GattServices(&deviceServices); |
| Q_ASSERT_SUCCEEDED(hr); |
| uint serviceCount; |
| hr = deviceServices->get_Size(&serviceCount); |
| Q_ASSERT_SUCCEEDED(hr); |
| for (uint i = 0; i < serviceCount; ++i) { |
| ComPtr<GenericAttributeProfile::IGattDeviceService> service; |
| hr = deviceServices->GetAt(i, &service); |
| Q_ASSERT_SUCCEEDED(hr); |
| GUID uuid; |
| hr = service->get_Uuid(&uuid); |
| Q_ASSERT_SUCCEEDED(hr); |
| uuids.append(QBluetoothUuid(uuid)); |
| } |
| } |
| const ManufacturerData manufacturerData = m_foundLEManufacturerData.value(address); |
| |
| qCDebug(QT_BT_WINRT) << "Discovered BTLE device: " << QString::number(address) << btName |
| << "Num UUIDs" << uuids.count() << "RSSI:" << rssi |
| << "Num manufacturer data" << manufacturerData.count(); |
| |
| QBluetoothDeviceInfo info(QBluetoothAddress(address), btName, 0); |
| info.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration); |
| info.setServiceUuids(uuids); |
| info.setRssi(rssi); |
| for (quint16 key : manufacturerData.keys()) |
| info.setManufacturerData(key, manufacturerData.value(key)); |
| info.setCached(true); |
| |
| QMetaObject::invokeMethod(this, "deviceFound", Qt::AutoConnection, |
| Q_ARG(QBluetoothDeviceInfo, info)); |
| return S_OK; |
| } |
| #endif // QT_CONFIG(winrt_btle_no_pairing) |
| |
| QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate( |
| const QBluetoothAddress &deviceAdapter, |
| QBluetoothDeviceDiscoveryAgent *parent) |
| |
| : inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry), |
| lastError(QBluetoothDeviceDiscoveryAgent::NoError), |
| lowEnergySearchTimeout(25000), |
| q_ptr(parent), |
| leScanTimer(0) |
| { |
| Q_UNUSED(deviceAdapter); |
| } |
| |
| QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate() |
| { |
| disconnectAndClearWorker(); |
| } |
| |
| bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const |
| { |
| return worker; |
| } |
| |
| QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods() |
| { |
| return (ClassicMethod | LowEnergyMethod); |
| } |
| |
| void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods) |
| { |
| if (worker) |
| return; |
| |
| worker = new QWinRTBluetoothDeviceDiscoveryWorker(methods); |
| discoveredDevices.clear(); |
| connect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::deviceFound, |
| this, &QBluetoothDeviceDiscoveryAgentPrivate::registerDevice); |
| connect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::deviceDataChanged, |
| this, &QBluetoothDeviceDiscoveryAgentPrivate::updateDeviceData); |
| connect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::scanFinished, |
| this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished); |
| worker->start(); |
| |
| if (lowEnergySearchTimeout > 0 && methods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) { // otherwise no timeout and stop() required |
| if (!leScanTimer) { |
| leScanTimer = new QTimer(this); |
| leScanTimer->setSingleShot(true); |
| } |
| connect(leScanTimer, &QTimer::timeout, |
| worker, &QWinRTBluetoothDeviceDiscoveryWorker::finishDiscovery); |
| leScanTimer->setInterval(lowEnergySearchTimeout); |
| leScanTimer->start(); |
| } |
| } |
| |
| void QBluetoothDeviceDiscoveryAgentPrivate::stop() |
| { |
| Q_Q(QBluetoothDeviceDiscoveryAgent); |
| if (worker) { |
| worker->stopLEWatcher(); |
| disconnectAndClearWorker(); |
| emit q->canceled(); |
| } |
| if (leScanTimer) |
| leScanTimer->stop(); |
| } |
| |
| 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_WINRT) << "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().count() != uuidSet.count()) |
| 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) |
| { |
| 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_WINRT) << "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)); |
| emit q->deviceUpdated(*iter, fields); |
| return; |
| } |
| } |
| } |
| |
| void QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished() |
| { |
| Q_Q(QBluetoothDeviceDiscoveryAgent); |
| disconnectAndClearWorker(); |
| emit q->finished(); |
| } |
| |
| void QBluetoothDeviceDiscoveryAgentPrivate::disconnectAndClearWorker() |
| { |
| if (!worker) |
| return; |
| |
| disconnect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::scanFinished, |
| this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished); |
| disconnect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::deviceFound, |
| this, &QBluetoothDeviceDiscoveryAgentPrivate::registerDevice); |
| disconnect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::deviceDataChanged, |
| this, &QBluetoothDeviceDiscoveryAgentPrivate::updateDeviceData); |
| if (leScanTimer) { |
| disconnect(leScanTimer, &QTimer::timeout, |
| worker, &QWinRTBluetoothDeviceDiscoveryWorker::finishDiscovery); |
| } |
| worker.clear(); |
| } |
| |
| QT_END_NAMESPACE |
| |
| #include <qbluetoothdevicediscoveryagent_winrt.moc> |