/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the QtBluetooth module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL21$ ** 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 http://www.qt.io/terms-conditions. For further ** information use the contact form at http://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 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** As a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qbluetoothdevicediscoveryagent.h" #include "qbluetoothdevicediscoveryagent_p.h" #include "qbluetoothaddress.h" #include "qbluetoothuuid.h" #include "qfunctions_winrt.h" #include #include #include #include #include #include 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::Enumeration; 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; \ } class QWinRTBluetoothDeviceDiscoveryWorker : public QObject { Q_OBJECT public: explicit QWinRTBluetoothDeviceDiscoveryWorker(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods); ~QWinRTBluetoothDeviceDiscoveryWorker(); void start(); private: void startDeviceDiscovery(QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode); void onDeviceDiscoveryFinished(IAsyncOperation *op, QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode); void gatherDeviceInformation(IDeviceInformation *deviceInfo, QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode); void gatherMultipleDeviceInformation(IVectorView *devices, QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode); void setupLEDeviceWatcher(); void bluetoothInfoFromDeviceIdAsync(HSTRING deviceId); void bluetoothInfoFromLeDeviceIdAsync(HSTRING deviceId); HRESULT onClassicBluetoothDeviceFoundAsync(IAsyncOperation *, AsyncStatus); HRESULT onBluetoothLEDeviceFoundAsync(IAsyncOperation *op, AsyncStatus status); void decreaseAndCheckPendingDevices(); public slots: void handleLeTimeout(); Q_SIGNALS: void deviceFound(const QBluetoothDeviceInfo &info); void scanFinished(); void scanCanceled(); public: quint8 requestedModes; private: ComPtr m_leDeviceWatcher; EventRegistrationToken m_leDeviceAddedToken; int m_pendingDevices; }; QWinRTBluetoothDeviceDiscoveryWorker::QWinRTBluetoothDeviceDiscoveryWorker(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods) : requestedModes(methods) , m_pendingDevices(0) { qRegisterMetaType(); } QWinRTBluetoothDeviceDiscoveryWorker::~QWinRTBluetoothDeviceDiscoveryWorker() { if (m_leDeviceWatcher && m_leDeviceAddedToken.value) { HRESULT hr; hr = m_leDeviceWatcher->remove_Added(m_leDeviceAddedToken); Q_ASSERT_SUCCEEDED(hr); } } void QWinRTBluetoothDeviceDiscoveryWorker::start() { QEventDispatcherWinRT::runOnXamlThread([this]() { if (requestedModes & QBluetoothDeviceDiscoveryAgent::ClassicMethod) startDeviceDiscovery(QBluetoothDeviceDiscoveryAgent::ClassicMethod); if (requestedModes & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) startDeviceDiscovery(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); return S_OK; }); qCDebug(QT_BT_WINRT) << "Worker started"; } void QWinRTBluetoothDeviceDiscoveryWorker::startDeviceDiscovery(QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode) { HString deviceSelector; ComPtr 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) { ComPtr bluetoothLeDeviceStatics; hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothLEDevice).Get(), &bluetoothLeDeviceStatics); WARN_AND_RETURN_IF_FAILED("Could not obtain bluetooth LE device statics", return); bluetoothLeDeviceStatics->GetDeviceSelector(deviceSelector.GetAddressOf()); } else { ComPtr bluetoothDeviceStatics; hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothDevice).Get(), &bluetoothDeviceStatics); WARN_AND_RETURN_IF_FAILED("Could not obtain bluetooth device statics", return); bluetoothDeviceStatics->GetDeviceSelector(deviceSelector.GetAddressOf()); } ComPtr> op; hr = deviceInformationStatics->FindAllAsyncAqsFilter(deviceSelector.Get(), &op); WARN_AND_RETURN_IF_FAILED("Could not start bluetooth device discovery operation", return); hr = op->put_Completed( Callback>([this, mode](IAsyncOperation *op, AsyncStatus) { 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 *op, QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode) { qCDebug(QT_BT_WINRT) << (mode == QBluetoothDeviceDiscoveryAgent::ClassicMethod ? "BT" : "BTLE") << " scan completed"; ComPtr> devices; HRESULT hr; hr = op->GetResults(&devices); Q_ASSERT_SUCCEEDED(hr); gatherMultipleDeviceInformation(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) bluetoothInfoFromLeDeviceIdAsync(deviceId.Get()); else bluetoothInfoFromDeviceIdAsync(deviceId.Get()); } void QWinRTBluetoothDeviceDiscoveryWorker::gatherMultipleDeviceInformation(IVectorView *devices, QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode) { quint32 deviceCount; HRESULT hr = devices->get_Size(&deviceCount); Q_ASSERT_SUCCEEDED(hr); m_pendingDevices += deviceCount; for (quint32 i = 0; i < deviceCount; ++i) { ComPtr device; hr = devices->GetAt(i, &device); Q_ASSERT_SUCCEEDED(hr); gatherDeviceInformation(device.Get(), mode); } } void QWinRTBluetoothDeviceDiscoveryWorker::setupLEDeviceWatcher() { HString deviceSelector; ComPtr 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); ComPtr bluetoothLeDeviceStatics; hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothLEDevice).Get(), &bluetoothLeDeviceStatics); WARN_AND_RETURN_IF_FAILED("Could not obtain bluetooth LE device statics", return); hr = bluetoothLeDeviceStatics->GetDeviceSelector(deviceSelector.GetAddressOf()); WARN_AND_RETURN_IF_FAILED("Could not obtain device selector string", return); hr = deviceInformationStatics->CreateWatcherAqsFilter(deviceSelector.Get(), &m_leDeviceWatcher); WARN_AND_RETURN_IF_FAILED("Could not create le device watcher", return); auto deviceAddedCallback = Callback>([this](IDeviceWatcher *, IDeviceInformation *deviceInfo) { HString deviceId; HRESULT hr; hr = deviceInfo->get_Id(deviceId.GetAddressOf()); Q_ASSERT_SUCCEEDED(hr); bluetoothInfoFromLeDeviceIdAsync(deviceId.Get()); return S_OK; }); hr = m_leDeviceWatcher->add_Added(deviceAddedCallback.Get(), &m_leDeviceAddedToken); WARN_AND_RETURN_IF_FAILED("Could not add \"device added\" callback", return); hr = m_leDeviceWatcher->Start(); WARN_AND_RETURN_IF_FAILED("Could not start device watcher", return); } void QWinRTBluetoothDeviceDiscoveryWorker::handleLeTimeout() { // pendingDevices might be <0 if devices were added after the intitial scan was completed if (m_pendingDevices <= 0) emit scanFinished(); else emit scanCanceled(); deleteLater(); } // "deviceFound" will be emitted at the end of the deviceFromIdOperation callback void QWinRTBluetoothDeviceDiscoveryWorker::bluetoothInfoFromDeviceIdAsync(HSTRING deviceId) { ComPtr deviceStatics; HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothDevice).Get(), &deviceStatics); Q_ASSERT_SUCCEEDED(hr); ComPtr> deviceFromIdOperation; // on Windows 10 FromIdAsync might ask for device permission. We cannot wait here but have to handle that asynchronously hr = deviceStatics->FromIdAsync(deviceId, &deviceFromIdOperation); if (FAILED(hr)) { decreaseAndCheckPendingDevices(); qCWarning(QT_BT_WINRT) << "Could not obtain bluetooth device from id"; return; } QEventDispatcherWinRT::runOnXamlThread([deviceFromIdOperation, this]() { HRESULT hr; hr = deviceFromIdOperation->put_Completed(Callback> (this, &QWinRTBluetoothDeviceDiscoveryWorker::onClassicBluetoothDeviceFoundAsync).Get()); if (FAILED(hr)) { decreaseAndCheckPendingDevices(); 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::bluetoothInfoFromLeDeviceIdAsync(HSTRING deviceId) { ComPtr deviceStatics; HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothLEDevice).Get(), &deviceStatics); Q_ASSERT_SUCCEEDED(hr); ComPtr> deviceFromIdOperation; // on Windows 10 FromIdAsync might ask for device permission. We cannot wait here but have to handle that asynchronously hr = deviceStatics->FromIdAsync(deviceId, &deviceFromIdOperation); if (FAILED(hr)) { decreaseAndCheckPendingDevices(); qCWarning(QT_BT_WINRT) << "Could not obtain bluetooth device from id"; return; } QEventDispatcherWinRT::runOnXamlThread([deviceFromIdOperation, this]() { HRESULT hr; hr = deviceFromIdOperation->put_Completed(Callback> (this, &QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothLEDeviceFoundAsync).Get()); if (FAILED(hr)) { decreaseAndCheckPendingDevices(); qCWarning(QT_BT_WINRT) << "Could not register device found callback"; return S_OK; } return S_OK; }); } HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onClassicBluetoothDeviceFoundAsync(IAsyncOperation *op, AsyncStatus status) { if (status != AsyncStatus::Completed) { decreaseAndCheckPendingDevices(); return S_OK; } ComPtr device; HRESULT hr = op->GetResults(&device); Q_ASSERT_SUCCEEDED(hr); if (!device) { decreaseAndCheckPendingDevices(); return S_OK; } UINT64 address; HString name; ComPtr 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 *deviceServices; hr = device->get_RfcommServices(&deviceServices); Q_ASSERT_SUCCEEDED(hr); uint serviceCount; hr = deviceServices->get_Size(&serviceCount); Q_ASSERT_SUCCEEDED(hr); QList uuids; for (uint i = 0; i < serviceCount; ++i) { ComPtr service; hr = deviceServices->GetAt(i, &service); Q_ASSERT_SUCCEEDED(hr); ComPtr 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, QBluetoothDeviceInfo::DataIncomplete); info.setCached(true); QMetaObject::invokeMethod(this, "deviceFound", Qt::AutoConnection, Q_ARG(QBluetoothDeviceInfo, info)); decreaseAndCheckPendingDevices(); return S_OK; } HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothLEDeviceFoundAsync(IAsyncOperation *op, AsyncStatus status) { if (status != AsyncStatus::Completed) { decreaseAndCheckPendingDevices(); return S_OK; } ComPtr device; HRESULT hr = op->GetResults(&device); Q_ASSERT_SUCCEEDED(hr); if (!device) { decreaseAndCheckPendingDevices(); return S_OK; } UINT64 address; HString name; 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 *deviceServices; hr = device->get_GattServices(&deviceServices); Q_ASSERT_SUCCEEDED(hr); uint serviceCount; hr = deviceServices->get_Size(&serviceCount); Q_ASSERT_SUCCEEDED(hr); QList uuids; for (uint i = 0; i < serviceCount; ++i) { ComPtr service; hr = deviceServices->GetAt(i, &service); Q_ASSERT_SUCCEEDED(hr); ComPtr id; GUID uuid; hr = service->get_Uuid(&uuid); Q_ASSERT_SUCCEEDED(hr); uuids.append(QBluetoothUuid(uuid)); } qCDebug(QT_BT_WINRT) << "Discovered BTLE device: " << QString::number(address) << btName << "Num UUIDs" << uuids.count(); QBluetoothDeviceInfo info(QBluetoothAddress(address), btName, 0); info.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration); info.setServiceUuids(uuids, QBluetoothDeviceInfo::DataIncomplete); info.setCached(true); QMetaObject::invokeMethod(this, "deviceFound", Qt::AutoConnection, Q_ARG(QBluetoothDeviceInfo, info)); decreaseAndCheckPendingDevices(); return S_OK; } void QWinRTBluetoothDeviceDiscoveryWorker::decreaseAndCheckPendingDevices() { --m_pendingDevices; if (m_pendingDevices == 0) setupLEDeviceWatcher(); } 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::scanFinished, this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished); connect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::scanCanceled, this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanCanceled); 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::handleLeTimeout); leScanTimer->setInterval(lowEnergySearchTimeout); leScanTimer->start(); } } void QBluetoothDeviceDiscoveryAgentPrivate::stop() { Q_Q(QBluetoothDeviceDiscoveryAgent); if (worker) { disconnectAndClearWorker(); emit q->canceled(); } if (leScanTimer) { leScanTimer->stop(); worker->deleteLater(); } } void QBluetoothDeviceDiscoveryAgentPrivate::registerDevice(const QBluetoothDeviceInfo &info) { Q_Q(QBluetoothDeviceDiscoveryAgent); for (QList::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 uuids = iter->serviceUuids(); uuids.append(info.serviceUuids()); const QSet uuidSet = uuids.toSet(); if (iter->serviceUuids().count() != uuidSet.count()) iter->setServiceUuids(uuidSet.toList(), QBluetoothDeviceInfo::DataIncomplete); if (iter->coreConfigurations() != info.coreConfigurations()) iter->setCoreConfigurations(QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration); return; } } discoveredDevices << info; emit q->deviceDiscovered(info); } void QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished() { Q_Q(QBluetoothDeviceDiscoveryAgent); disconnectAndClearWorker(); emit q->finished(); } void QBluetoothDeviceDiscoveryAgentPrivate::onScanCanceled() { Q_Q(QBluetoothDeviceDiscoveryAgent); disconnectAndClearWorker(); emit q->canceled(); } void QBluetoothDeviceDiscoveryAgentPrivate::disconnectAndClearWorker() { Q_Q(QBluetoothDeviceDiscoveryAgent); if (!worker) return; disconnect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::scanCanceled, this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanCanceled); disconnect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::scanFinished, this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished); disconnect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::deviceFound, q, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered); if (leScanTimer) { disconnect(leScanTimer, &QTimer::timeout, worker, &QWinRTBluetoothDeviceDiscoveryWorker::handleLeTimeout); } worker.clear(); } QT_END_NAMESPACE #include