diff options
Diffstat (limited to 'src/bluetooth')
-rw-r--r-- | src/bluetooth/bluez/hcimanager.cpp | 6 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtconnectionmonitor.mm | 9 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtdeviceinquiry.mm | 19 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtsdpinquiry.mm | 160 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtservicerecord.mm | 50 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp | 32 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothservicediscoveryagent_android.cpp | 5 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothservicediscoveryagent_bluez.cpp | 11 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothservicediscoveryagent_osx.mm | 25 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothservicediscoveryagent_p.h | 2 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothservicediscoveryagent_winrt.cpp | 75 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothsocket.cpp | 7 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothsocket_bluezdbus.cpp | 4 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothsocket_winrt.cpp | 43 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_winrt_new.cpp | 17 | ||||
-rw-r--r-- | src/bluetooth/qlowenergyserviceprivate_p.h | 1 |
16 files changed, 352 insertions, 114 deletions
diff --git a/src/bluetooth/bluez/hcimanager.cpp b/src/bluetooth/bluez/hcimanager.cpp index e2635fae..a8b8e3b9 100644 --- a/src/bluetooth/bluez/hcimanager.cpp +++ b/src/bluetooth/bluez/hcimanager.cpp @@ -563,9 +563,11 @@ void HciManager::handleHciAclPacket(const quint8 *data, int size) void HciManager::handleLeMetaEvent(const quint8 *data) { - // Spec v4.2, Vol 2, part E, 7.7.65ff + // Spec v5.3, Vol 4, part E, 7.7.65.* switch (*data) { - case 0x1: { + case 0x1: // HCI_LE_Connection_Complete + case 0xA: // HCI_LE_Enhanced_Connection_Complete + { const quint16 handle = bt_get_le16(data + 2); emit connectionComplete(handle); break; diff --git a/src/bluetooth/osx/osxbtconnectionmonitor.mm b/src/bluetooth/osx/osxbtconnectionmonitor.mm index f41bbed5..159bceb5 100644 --- a/src/bluetooth/osx/osxbtconnectionmonitor.mm +++ b/src/bluetooth/osx/osxbtconnectionmonitor.mm @@ -96,6 +96,15 @@ using namespace QT_NAMESPACE; if (!device) return; + if (!monitor) { + // Rather surprising: monitor == nullptr means we stopped monitoring. + // So apparently this thingie is still alive and keeps receiving + // notifications. + qCWarning(QT_BT_OSX, + "Connection notification received in a monitor that was cancelled"); + return; + } + QT_BT_MAC_AUTORELEASEPOOL; // All Obj-C objects are autoreleased. diff --git a/src/bluetooth/osx/osxbtdeviceinquiry.mm b/src/bluetooth/osx/osxbtdeviceinquiry.mm index 2fd0d2db..4757331c 100644 --- a/src/bluetooth/osx/osxbtdeviceinquiry.mm +++ b/src/bluetooth/osx/osxbtdeviceinquiry.mm @@ -112,11 +112,14 @@ const uint8_t IOBlueoothInquiryLengthS = 15; m_active = true; [m_inquiry clearFoundDevices]; + + qCDebug(QT_BT_OSX) << "Starting device inquiry with" + << IOBlueoothInquiryLengthS << "second timeout limit."; const IOReturn result = [m_inquiry start]; if (result != kIOReturnSuccess) { // QtBluetooth will probably convert an error into UnknownError, // losing the actual information. - qCWarning(QT_BT_OSX) << "failed with IOKit error code:" << result; + qCWarning(QT_BT_OSX) << "device inquiry start failed with IOKit error code:" << result; m_active = false; } else { // Docs say it's 10 s. by default, we set it to 15 s. (see -initWithDelegate:), @@ -124,11 +127,14 @@ const uint8_t IOBlueoothInquiryLengthS = 15; watchDog.reset(new QTimer); watchDog->connect(watchDog.get(), &QTimer::timeout, watchDog.get(), [self]{ qCWarning(QT_BT_OSX, "Manually interrupting IOBluetoothDeviceInquiry"); + qCDebug(QT_BT_OSX) << "Found devices:" << [m_inquiry foundDevices]; [self stop]; }); watchDog->setSingleShot(true); - watchDog->setInterval(IOBlueoothInquiryLengthS * 2 * 1000); // Let's make it twice as long. + // Let's use 17 s. so that IOBluetooth, if it respects 'inquiryLength' + // has a chance to stop before we do: + watchDog->setInterval((IOBlueoothInquiryLengthS + 2) * 1000); watchDog->start(); } @@ -148,6 +154,8 @@ const uint8_t IOBlueoothInquiryLengthS = 15; - (void)deviceInquiryComplete:(IOBluetoothDeviceInquiry *)sender error:(IOReturn)error aborted:(BOOL)aborted { + qCDebug(QT_BT_OSX) << "deviceInquiryComplete, error:" << error + << "user-stopped:" << aborted; if (!m_active) return; @@ -158,11 +166,11 @@ const uint8_t IOBlueoothInquiryLengthS = 15; if (error != kIOReturnSuccess && !aborted) { // QtBluetooth has not too many error codes, 'UnknownError' is not really - // useful, report the actual error code here: + // useful, log the actual error code here: qCWarning(QT_BT_OSX) << "IOKit error code: " << error; - m_delegate->error(error); // Let watchDog to stop it, calling -stop at timeout, otherwise, - // it looks like inquiry continues and keeps reporting new devices found. + // it looks like inquiry continues even after this error and + // keeps reporting new devices found. } else { // Either a normal completion or from a timer slot. watchDog.reset(); @@ -174,6 +182,7 @@ const uint8_t IOBlueoothInquiryLengthS = 15; - (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry *)sender device:(IOBluetoothDevice *)device { + qCDebug(QT_BT_OSX) << "deviceInquiryDeviceFound:" << [device nameOrAddress]; if (sender != m_inquiry) // Can never happen in the current version. return; diff --git a/src/bluetooth/osx/osxbtsdpinquiry.mm b/src/bluetooth/osx/osxbtsdpinquiry.mm index a2b02b1a..e60b9787 100644 --- a/src/bluetooth/osx/osxbtsdpinquiry.mm +++ b/src/bluetooth/osx/osxbtsdpinquiry.mm @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2022 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtBluetooth module of the Qt Toolkit. @@ -43,8 +43,12 @@ #include "osxbtutility_p.h" #include "btdelegates_p.h" +#include <QtCore/qoperatingsystemversion.h> #include <QtCore/qvariant.h> #include <QtCore/qstring.h> +#include <QtCore/qtimer.h> + +#include <memory> QT_BEGIN_NAMESPACE @@ -52,6 +56,8 @@ namespace OSXBluetooth { namespace { +const int basebandConnectTimeoutMS = 20000; + QBluetoothUuid sdp_element_to_uuid(IOBluetoothSDPDataElement *element) { QT_BT_MAC_AUTORELEASEPOOL; @@ -78,11 +84,20 @@ QVector<QBluetoothUuid> extract_service_class_ID_list(IOBluetoothSDPServiceRecor QT_BT_MAC_AUTORELEASEPOOL; IOBluetoothSDPDataElement *const idList = [record getAttributeDataElement:kBluetoothSDPAttributeIdentifierServiceClassIDList]; - if (!idList || [idList getTypeDescriptor] != kBluetoothSDPDataElementTypeDataElementSequence) - return {}; QVector<QBluetoothUuid> uuids; - NSArray *const arr = [idList getArrayValue]; + if (!idList) + return uuids; + + NSArray *arr = nil; + if ([idList getTypeDescriptor] == kBluetoothSDPDataElementTypeDataElementSequence) + arr = [idList getArrayValue]; + else if ([idList getTypeDescriptor] == kBluetoothSDPDataElementTypeUUID) + arr = @[idList]; + + if (!arr) + return uuids; + for (IOBluetoothSDPDataElement *dataElement in arr) { const auto qtUuid = sdp_element_to_uuid(dataElement); if (!qtUuid.isNull()) @@ -200,7 +215,7 @@ QVector<QBluetoothUuid> extract_services_uuids(IOBluetoothDevice *device) return uuids; } -} +} // namespace OSXBluetooth QT_END_NAMESPACE @@ -213,6 +228,9 @@ using namespace OSXBluetooth; QT_PREPEND_NAMESPACE(DarwinBluetooth::SDPInquiryDelegate) *delegate; IOBluetoothDevice *device; bool isActive; + + // Needed to workaround a broken SDP on Monterey: + std::unique_ptr<QTimer> connectionWatchdog; } - (id)initWithDelegate:(DarwinBluetooth::SDPInquiryDelegate *)aDelegate @@ -242,17 +260,41 @@ using namespace OSXBluetooth; return [self performSDPQueryWithDevice:address filters:emptyFilter]; } +- (void)interruptSDPQuery +{ + // To be only executed on timer. + Q_ASSERT(connectionWatchdog.get()); + // If device was reset, so the timer should be, we can never be here then. + Q_ASSERT(device); + + Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)"); + qCDebug(QT_BT_OSX) << "couldn't connect to device" << [device nameOrAddress] + << ", ending SDP inquiry."; + + // Stop the watchdog and close the connection as otherwise there could be + // later "connectionComplete" callbacks + connectionWatchdog->stop(); + [device closeConnection]; + + delegate->SDPInquiryError(device, kIOReturnTimeout); + [device release]; + device = nil; + isActive = false; +} + - (IOReturn)performSDPQueryWithDevice:(const QBluetoothAddress &)address filters:(const QList<QBluetoothUuid> &)qtFilters { Q_ASSERT_X(!isActive, Q_FUNC_INFO, "SDP query in progress"); Q_ASSERT_X(!address.isNull(), Q_FUNC_INFO, "invalid target device address"); + qCDebug(QT_BT_OSX) << "Starting and SDP inquiry for address:" << address; QT_BT_MAC_AUTORELEASEPOOL; // We first try to allocate "filters": ObjCScopedPointer<NSMutableArray> array; - if (qtFilters.size()) { + if (QOperatingSystemVersion::current() <= QOperatingSystemVersion::MacOSBigSur + && qtFilters.size()) { // See the comment about filters on Monterey below. array.reset([[NSMutableArray alloc] init]); if (!array) { qCCritical(QT_BT_OSX) << "failed to allocate an uuid filter"; @@ -281,7 +323,65 @@ using namespace OSXBluetooth; ObjCScopedPointer<IOBluetoothDevice> oldDevice(device); device = newDevice.data(); + qCDebug(QT_BT_OSX) << "Device" << [device nameOrAddress] << "connected:" + << bool([device isConnected]) << "paired:" << bool([device isPaired]); IOReturn result = kIOReturnSuccess; + + if (QOperatingSystemVersion::current() > QOperatingSystemVersion::MacOSBigSur) { + // SDP query on Monterey does not follow its own documented/expected behavior: + // - a simple performSDPQuery was previously ensuring baseband connection + // to be opened, now it does not do so, instead logs a warning and returns + // immediately. + // - a version with UUID filters simply does nothing except it immediately + // returns kIOReturnSuccess. + + // If the device was not yet connected, connect it first + if (![device isConnected]) { + qCDebug(QT_BT_OSX) << "Device" << [device nameOrAddress] + << "is not connected, connecting it first"; + result = [device openConnection:self]; + // The connection may succeed immediately. But if it didn't, start a connection timer + // which has two guardian roles: + // 1. Guard against connect attempt taking too long time + // 2. Sometimes on Monterey the callback indicating "connection completion" is + // not received even though the connection has in fact succeeded + if (![device isConnected]) { + qCDebug(QT_BT_OSX) << "Starting connection monitor for device" + << [device nameOrAddress] << "with timeout limit of" + << basebandConnectTimeoutMS/1000 << "seconds."; + connectionWatchdog.reset(new QTimer); + connectionWatchdog->setSingleShot(false); + QObject::connect(connectionWatchdog.get(), &QTimer::timeout, + connectionWatchdog.get(), + [self] () { + qCDebug(QT_BT_OSX) << "Connection monitor timeout for device:" + << [device nameOrAddress] + << ", connected:" << bool([device isConnected]); + // Device can sometimes get properly connected without IOBluetooth + // calling the connectionComplete callback, so we check the status here + if ([device isConnected]) + [self connectionComplete:device status:kIOReturnSuccess]; + else + [self interruptSDPQuery]; + }); + connectionWatchdog->start(basebandConnectTimeoutMS); + } + } + + if ([device isConnected]) + result = [device performSDPQuery:self]; + + if (result != kIOReturnSuccess) { + qCCritical(QT_BT_OSX, "failed to start an SDP query"); + device = oldDevice.take(); + } else { + newDevice.take(); + isActive = true; + } + + return result; + } // Monterey's code path. + if (qtFilters.size()) result = [device performSDPQuery:self uuids:array]; else @@ -291,25 +391,53 @@ using namespace OSXBluetooth; qCCritical(QT_BT_OSX) << "failed to start an SDP query"; device = oldDevice.take(); } else { - isActive = true; newDevice.take(); + isActive = true; } return result; } -- (void)stopSDPQuery +- (void)connectionComplete:(IOBluetoothDevice *)aDevice status:(IOReturn)status { - // There is no API to stop it, - // but there is a 'stop' member-function in Qt and - // after it's called sdpQueryComplete must be somehow ignored. + qCDebug(QT_BT_OSX) << "connectionComplete for device" << [aDevice nameOrAddress] + << "with status:" << status; + if (aDevice != device) { + // Connection was previously cancelled, probably, due to the timeout. + return; + } + + // The connectionComplete may be invoked by either the IOBluetooth callback or our + // connection watchdog. In either case stop the watchdog if it exists + if (connectionWatchdog) + connectionWatchdog->stop(); + if (status == kIOReturnSuccess) + status = [aDevice performSDPQuery:self]; + + if (status != kIOReturnSuccess) { + isActive = false; + qCWarning(QT_BT_OSX, "failed to open connection or start an SDP query"); + Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)"); + delegate->SDPInquiryError(aDevice, status); + } +} + +- (void)stopSDPQuery +{ + // There is no API to stop it SDP on device, but there is a 'stop' + // member-function in Qt and after it's called sdpQueryComplete + // must be somehow ignored (device != aDevice in a callback). [device release]; device = nil; + isActive = false; + connectionWatchdog.reset(); } - (void)sdpQueryComplete:(IOBluetoothDevice *)aDevice status:(IOReturn)status { + qCDebug(QT_BT_OSX) << "sdpQueryComplete for device:" << [aDevice nameOrAddress] + << "with status:" << status; // Can happen - there is no legal way to cancel an SDP query, // after the 'reset' device can never be // the same as the cancelled one. @@ -320,6 +448,15 @@ using namespace OSXBluetooth; isActive = false; + // If we used the manual connection establishment, close the + // connection here. Otherwise the IOBluetooth may call stray + // connectionComplete or sdpQueryCompletes + if (connectionWatchdog) { + qCDebug(QT_BT_OSX) << "Closing the connection established for SDP inquiry."; + connectionWatchdog.reset(); + [device closeConnection]; + } + if (status != kIOReturnSuccess) delegate->SDPInquiryError(aDevice, status); else @@ -327,3 +464,4 @@ using namespace OSXBluetooth; } @end + diff --git a/src/bluetooth/osx/osxbtservicerecord.mm b/src/bluetooth/osx/osxbtservicerecord.mm index 23d55d13..2d868f63 100644 --- a/src/bluetooth/osx/osxbtservicerecord.mm +++ b/src/bluetooth/osx/osxbtservicerecord.mm @@ -146,7 +146,7 @@ void add_attribute(const QVariant &var, AttributeId key, Dictionary dict) return; const Number num(variant_to_nsnumber<ValueType>(var)); - [dict setObject:num forKey:[NSString stringWithFormat:@"%d", int(key)]]; + [dict setObject:num forKey:[NSString stringWithFormat:@"%x", int(key)]]; } template<> @@ -160,7 +160,7 @@ void add_attribute<QString>(const QVariant &var, AttributeId key, Dictionary dic const QString string(var.value<QString>()); if (string.length()) { if (NSString *const nsString = string.toNSString()) - [dict setObject:nsString forKey:[NSString stringWithFormat:@"%d", int(key)]]; + [dict setObject:nsString forKey:[NSString stringWithFormat:@"%x", int(key)]]; } } @@ -173,7 +173,7 @@ void add_attribute<QBluetoothUuid>(const QVariant &var, AttributeId key, Diction return; SDPUUid ioUUID(iobluetooth_uuid(var.value<QBluetoothUuid>())); - [dict setObject:ioUUID forKey:[NSString stringWithFormat:@"%d", int(key)]]; + [dict setObject:ioUUID forKey:[NSString stringWithFormat:@"%x", int(key)]]; } template<> @@ -207,6 +207,25 @@ void add_attribute(const QVariant &var, NSMutableArray *list) } template<> +void add_attribute<unsigned short>(const QVariant &var, NSMutableArray *list) +{ + Q_ASSERT_X(list, Q_FUNC_INFO, "invalid list (nil)"); + + if (!var.canConvert<unsigned short>()) + return; + + const Number num(variant_to_nsnumber<unsigned short>(var)); + + NSDictionary* dict = @{ + @"DataElementType" : [NSNumber numberWithInt:1], + @"DataElementSize" : [NSNumber numberWithInt:2], + @"DataElementValue" : num + }; + + [list addObject: dict]; +} + +template<> void add_attribute<QString>(const QVariant &var, NSMutableArray *list) { Q_ASSERT_X(list, Q_FUNC_INFO, "invalid list (nil)"); @@ -273,7 +292,7 @@ void add_rfcomm_protocol_descriptor_list(uint16 channelID, Dictionary dict) [rfcommList addObject:rfcommDict]; [descriptorList addObject:rfcommList]; - [dict setObject:descriptorList forKey:[NSString stringWithFormat:@"%d", + [dict setObject:descriptorList forKey:[NSString stringWithFormat:@"%x", kBluetoothSDPAttributeIdentifierProtocolDescriptorList]]; } @@ -299,7 +318,7 @@ void add_l2cap_protocol_descriptor_list(uint16 psm, Dictionary dict) [l2capList addObject:l2capDict]; [descriptorList addObject:l2capList]; - [dict setObject:descriptorList forKey:[NSString stringWithFormat:@"%d", + [dict setObject:descriptorList forKey:[NSString stringWithFormat:@"%x", kBluetoothSDPAttributeIdentifierProtocolDescriptorList]]; } @@ -310,10 +329,10 @@ bool add_attribute(const QVariant &var, AttributeId key, NSMutableArray *list) if (var.canConvert<Sequence>()) return false; - if (var.canConvert<QString>()) { + if (var.type() == QVariant::String) { //ServiceName, ServiceDescription, ServiceProvider. add_attribute<QString>(var, list); - } else if (var.canConvert<QBluetoothUuid>()) { + } else if (var.userType() == qMetaTypeId<QBluetoothUuid>()) { add_attribute<QBluetoothUuid>(var, list); } else { // Here we need 'key' to understand the type. @@ -325,6 +344,9 @@ bool add_attribute(const QVariant &var, AttributeId key, NSMutableArray *list) case QSInfo::ServiceInfoTimeToLive: add_attribute<unsigned>(var, list); break; + case QSInfo::BluetoothProfileDescriptorList: + add_attribute<unsigned short>(var, list); + break; case QSInfo::ServiceAvailability: add_attribute<unsigned char>(var, list); break; @@ -348,10 +370,10 @@ bool add_attribute(const QBluetoothServiceInfo &serviceInfo, AttributeId key, Di if (var.canConvert<Sequence>()) return false; - if (var.canConvert<QString>()) { + if (var.type() == QVariant::String) { //ServiceName, ServiceDescription, ServiceProvider. add_attribute<QString>(var, key, dict); - } else if (var.canConvert<QBluetoothUuid>()) { + } else if (var.userType() == qMetaTypeId<QBluetoothUuid>()) { add_attribute<QBluetoothUuid>(serviceInfo.attribute(key), key, dict); } else { // We can have different integer types actually, so I have to check @@ -385,14 +407,15 @@ bool add_sequence_attribute(const QVariant &var, AttributeId key, NSMutableArray if (var.isNull() || !var.canConvert<Sequence>()) return false; + NSMutableArray *const nested = [NSMutableArray array]; + [list addObject:nested]; + const Sequence sequence(var.value<Sequence>()); for (const QVariant &var : sequence) { if (var.canConvert<Sequence>()) { - NSMutableArray *const nested = [NSMutableArray array]; add_sequence_attribute(var, key, nested); - [list addObject:nested]; } else { - add_attribute(var, key, list); + add_attribute(var, key, nested); } } @@ -415,8 +438,7 @@ bool add_sequence_attribute(const QBluetoothServiceInfo &serviceInfo, AttributeI if (!add_sequence_attribute(element, key, list)) add_attribute(element, key, list); } - [dict setObject:list forKey:[NSString stringWithFormat:@"%d", int(key)]]; - + [dict setObject:list forKey:[NSString stringWithFormat:@"%x", int(key)]]; return true; } diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp index 66834827..82a2ebbc 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp @@ -157,6 +157,8 @@ private: #endif HRESULT onBluetoothLEAdvertisementReceived(IBluetoothLEAdvertisementReceivedEventArgs *args); + void decrementPairedDevicesAndCheckFinished(); + public slots: void finishDiscovery(); @@ -492,10 +494,7 @@ void QWinRTBluetoothDeviceDiscoveryWorker::classicBluetoothInfoFromDeviceIdAsync // 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(); + decrementPairedDevicesAndCheckFinished(); qCWarning(QT_BT_WINRT) << "Could not obtain bluetooth device from id"; return S_OK; } @@ -506,15 +505,13 @@ void QWinRTBluetoothDeviceDiscoveryWorker::classicBluetoothInfoFromDeviceIdAsync if (thisPointer) { if (status == Completed) thisPointer->onPairedClassicBluetoothDeviceFoundAsync(op, status); - --thisPointer->m_pendingPairedDevices; + else + thisPointer->decrementPairedDevicesAndCheckFinished(); } return S_OK; }).Get()); if (FAILED(hr)) { - --m_pendingPairedDevices; - if (!m_pendingPairedDevices - && !(requestedModes & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)) - finishDiscovery(); + decrementPairedDevicesAndCheckFinished(); qCWarning(QT_BT_WINRT) << "Could not register device found callback"; return S_OK; } @@ -522,7 +519,7 @@ void QWinRTBluetoothDeviceDiscoveryWorker::classicBluetoothInfoFromDeviceIdAsync }); if (FAILED(hr)) { emit errorOccured(QBluetoothDeviceDiscoveryAgent::UnknownError); - --m_pendingPairedDevices; + decrementPairedDevicesAndCheckFinished(); qCWarning(QT_BT_WINRT) << "Could not obtain bluetooth device from id"; return; } @@ -531,6 +528,10 @@ void QWinRTBluetoothDeviceDiscoveryWorker::classicBluetoothInfoFromDeviceIdAsync // "deviceFound" will be emitted at the end of the deviceFromIdOperation callback void QWinRTBluetoothDeviceDiscoveryWorker::leBluetoothInfoFromDeviceIdAsync(HSTRING deviceId) { + // Note: in this method we do not need to call + // decrementPairedDevicesAndCheckFinished() because we *do* run LE + // scanning, so the condition in the check will always be false. + // It's enough to just decrement m_pendingPairedDevices. HRESULT hr; hr = QEventDispatcherWinRT::runOnXamlThread([deviceId, this]() { ComPtr<IAsyncOperation<BluetoothLEDevice *>> deviceFromIdOperation; @@ -548,7 +549,8 @@ void QWinRTBluetoothDeviceDiscoveryWorker::leBluetoothInfoFromDeviceIdAsync(HSTR if (thisPointer) { if (status == Completed) thisPointer->onPairedBluetoothLEDeviceFoundAsync(op, status); - --thisPointer->m_pendingPairedDevices; + else + --thisPointer->m_pendingPairedDevices; } return S_OK; }).Get()); @@ -687,6 +689,14 @@ HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onPairedClassicBluetoothDeviceFoun return S_OK; } +void QWinRTBluetoothDeviceDiscoveryWorker::decrementPairedDevicesAndCheckFinished() +{ + if ((--m_pendingPairedDevices == 0) + && !(requestedModes & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)) { + finishDiscovery(); + } +} + HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onPairedBluetoothLEDeviceFoundAsync(IAsyncOperation<BluetoothLEDevice *> *op, AsyncStatus status) { --m_pendingPairedDevices; diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp b/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp index 3ab0d580..96da5ddc 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp +++ b/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp @@ -274,9 +274,10 @@ void QBluetoothServiceDiscoveryAgentPrivate::_q_processFetchedUuids( Q_Q(QBluetoothServiceDiscoveryAgent); QTimer::singleShot(4000, q, [this]() { this->_q_fetchUuidsTimeout(); - }); + }); // will also call _q_seriveDiscoveryFinished() + } else { + _q_serviceDiscoveryFinished(); } - _q_serviceDiscoveryFinished(); return; } diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_bluez.cpp b/src/bluetooth/qbluetoothservicediscoveryagent_bluez.cpp index 6a93143b..4a18cfc7 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent_bluez.cpp +++ b/src/bluetooth/qbluetoothservicediscoveryagent_bluez.cpp @@ -320,7 +320,10 @@ void QBluetoothServiceDiscoveryAgentPrivate::_q_finishSdpScan(QBluetoothServiceD for (const QBluetoothUuid &id : serviceClassUuids) { if (id.minimumSize() == 16) { serviceInfo.setServiceUuid(id); - serviceInfo.setServiceName(QBluetoothServiceDiscoveryAgent::tr("Custom Service")); + if (serviceInfo.serviceName().isEmpty()) { + serviceInfo.setServiceName( + QBluetoothServiceDiscoveryAgent::tr("Custom Service")); + } QBluetoothServiceInfo::Sequence modSeq = serviceInfo.attribute(QBluetoothServiceInfo::ServiceClassIds).value<QBluetoothServiceInfo::Sequence>(); modSeq.removeOne(QVariant::fromValue(id)); @@ -334,8 +337,10 @@ void QBluetoothServiceDiscoveryAgentPrivate::_q_finishSdpScan(QBluetoothServiceD qCDebug(QT_BT_BLUEZ) << "Discovered services" << discoveredDevices.at(0).address().toString() << serviceInfo.serviceName() << serviceInfo.serviceUuid() << ">>>" << serviceInfo.serviceClassUuids(); - - emit q->serviceDiscovered(serviceInfo); + // Use queued connection to allow us finish the service looping; the application + // might call stop() when it has detected the service-of-interest. + QMetaObject::invokeMethod(q, "serviceDiscovered", Qt::QueuedConnection, + Q_ARG(QBluetoothServiceInfo, serviceInfo)); } } } diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm b/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm index d8decae1..96034ca7 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm +++ b/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm @@ -47,6 +47,7 @@ #include "osx/osxbluetooth_p.h" #include "osx/uistrings_p.h" +#include <QtCore/qoperatingsystemversion.h> #include <QtCore/qloggingcategory.h> #include <QtCore/qscopedpointer.h> #include <QtCore/qstring.h> @@ -148,15 +149,37 @@ void QBluetoothServiceDiscoveryAgentPrivate::SDPInquiryFinished(void *generic) QT_BT_MAC_AUTORELEASEPOOL; NSArray *const records = device.services; + qCDebug(QT_BT_OSX) << "SDP finished for device" << [device nameOrAddress] + << ", services found:" << [records count]; for (IOBluetoothSDPServiceRecord *record in records) { QBluetoothServiceInfo serviceInfo; Q_ASSERT_X(discoveredDevices.size() >= 1, Q_FUNC_INFO, "invalid number of devices"); + qCDebug(QT_BT_OSX) << "Processing service" << [record getServiceName]; serviceInfo.setDevice(discoveredDevices.at(0)); OSXBluetooth::extract_service_record(record, serviceInfo); - if (!serviceInfo.isValid()) + if (!serviceInfo.isValid()) { + qCDebug(QT_BT_OSX) << "Discarding invalid service"; continue; + } + + if (QOperatingSystemVersion::current() > QOperatingSystemVersion::MacOSBigSur + && uuidFilter.size()) { + const auto &serviceId = serviceInfo.serviceUuid(); + bool match = !serviceId.isNull() && uuidFilter.contains(serviceId); + if (!match) { + const auto &classUuids = serviceInfo.serviceClassUuids(); + for (const auto &uuid : classUuids) { + if (uuidFilter.contains(uuid)) { + match = true; + break; + } + } + if (!match) + continue; + } + } if (!isDuplicatedService(serviceInfo)) { discoveredServices.append(serviceInfo); diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_p.h b/src/bluetooth/qbluetoothservicediscoveryagent_p.h index 41410b70..0527cdde 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent_p.h +++ b/src/bluetooth/qbluetoothservicediscoveryagent_p.h @@ -239,10 +239,10 @@ private: private slots: void processFoundService(quint64 deviceAddress, const QBluetoothServiceInfo &info); void onScanFinished(quint64 deviceAddress); - void onScanCanceled(); void onError(); private: + void releaseWorker(); QPointer<QWinRTBluetoothServiceDiscoveryWorker> worker; #endif diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_winrt.cpp b/src/bluetooth/qbluetoothservicediscoveryagent_winrt.cpp index f1476758..e8d64efc 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent_winrt.cpp +++ b/src/bluetooth/qbluetoothservicediscoveryagent_winrt.cpp @@ -82,6 +82,13 @@ Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT) #define TYPE_STRING 37 #define TYPE_SEQUENCE 53 +// Helper to reverse given uchar array +static void reverseArray(uchar data[], size_t length) +{ + for (size_t i = length; i > length/2; i--) + std::swap(data[length - i], data[i - 1]); +} + class QWinRTBluetoothServiceDiscoveryWorker : public QObject { Q_OBJECT @@ -94,7 +101,6 @@ public: Q_SIGNALS: void serviceFound(quint64 deviceAddress, const QBluetoothServiceInfo &info); void scanFinished(quint64 deviceAddress); - void scanCanceled(); void errorOccured(); private: @@ -309,6 +315,8 @@ void QWinRTBluetoothServiceDiscoveryWorker::processServiceSearchResult(quint64 a GUID value; hr = dataReader->ReadGuid(&value); Q_ASSERT_SUCCEEDED(hr); + // The latter 8 bytes are in reverse order + reverseArray(value.Data4, sizeof(value.Data4)/sizeof(value.Data4[0])); const QBluetoothUuid uuid(value); info.setAttribute(key, uuid); qCDebug(QT_BT_WINRT) << "UUID" << uuid << "KEY" << hex << key << "TYPE" << dec << type << "UUID" << hex << uuid; @@ -422,7 +430,8 @@ QBluetoothServiceInfo::Sequence QWinRTBluetoothServiceDiscoveryWorker::readSeque GUID b; hr = dataReader->ReadGuid(&b); Q_ASSERT_SUCCEEDED(hr); - + // The latter 8 bytes are in reverse order + reverseArray(b.Data4, sizeof(b.Data4)/sizeof(b.Data4[0])); const QBluetoothUuid uuid(b); result.append(QVariant::fromValue(uuid)); remainingLength -= sizeof(GUID); @@ -494,7 +503,7 @@ QBluetoothServiceDiscoveryAgentPrivate::QBluetoothServiceDiscoveryAgentPrivate( QBluetoothServiceDiscoveryAgentPrivate::~QBluetoothServiceDiscoveryAgentPrivate() { - stop(); + releaseWorker(); } void QBluetoothServiceDiscoveryAgentPrivate::start(const QBluetoothAddress &address) @@ -508,8 +517,6 @@ void QBluetoothServiceDiscoveryAgentPrivate::start(const QBluetoothAddress &addr this, &QBluetoothServiceDiscoveryAgentPrivate::processFoundService, Qt::QueuedConnection); connect(worker, &QWinRTBluetoothServiceDiscoveryWorker::scanFinished, this, &QBluetoothServiceDiscoveryAgentPrivate::onScanFinished, Qt::QueuedConnection); - connect(worker, &QWinRTBluetoothServiceDiscoveryWorker::scanCanceled, - this, &QBluetoothServiceDiscoveryAgentPrivate::onScanCanceled, Qt::QueuedConnection); connect(worker, &QWinRTBluetoothServiceDiscoveryWorker::errorOccured, this, &QBluetoothServiceDiscoveryAgentPrivate::onError, Qt::QueuedConnection); worker->start(); @@ -517,20 +524,9 @@ void QBluetoothServiceDiscoveryAgentPrivate::start(const QBluetoothAddress &addr void QBluetoothServiceDiscoveryAgentPrivate::stop() { - if (!worker) - return; - - disconnect(worker, &QWinRTBluetoothServiceDiscoveryWorker::serviceFound, - this, &QBluetoothServiceDiscoveryAgentPrivate::processFoundService); - disconnect(worker, &QWinRTBluetoothServiceDiscoveryWorker::scanFinished, - this, &QBluetoothServiceDiscoveryAgentPrivate::onScanFinished); - disconnect(worker, &QWinRTBluetoothServiceDiscoveryWorker::scanCanceled, - this, &QBluetoothServiceDiscoveryAgentPrivate::onScanCanceled); - disconnect(worker, &QWinRTBluetoothServiceDiscoveryWorker::errorOccured, - this, &QBluetoothServiceDiscoveryAgentPrivate::onError); - // mWorker will delete itself as soon as it is done with its discovery - worker = nullptr; - setDiscoveryState(Inactive); + releaseWorker(); + Q_Q(QBluetoothServiceDiscoveryAgent); + emit q->canceled(); } void QBluetoothServiceDiscoveryAgentPrivate::processFoundService(quint64 deviceAddress, const QBluetoothServiceInfo &info) @@ -579,26 +575,12 @@ void QBluetoothServiceDiscoveryAgentPrivate::processFoundService(quint64 deviceA void QBluetoothServiceDiscoveryAgentPrivate::onScanFinished(quint64 deviceAddress) { - Q_Q(QBluetoothServiceDiscoveryAgent); - bool deviceFound; - for (const QBluetoothDeviceInfo &deviceInfo : qAsConst(discoveredDevices)) { - if (deviceInfo.address().toUInt64() == deviceAddress) { - deviceFound = true; - discoveredDevices.removeOne(deviceInfo); - if (discoveredDevices.isEmpty()) - setDiscoveryState(Inactive); - break; - } - } - Q_ASSERT(deviceFound); - stop(); - emit q->finished(); -} - -void QBluetoothServiceDiscoveryAgentPrivate::onScanCanceled() -{ - Q_Q(QBluetoothServiceDiscoveryAgent); - emit q->canceled(); + // The scan for a device's services has finished. Disconnect the + // worker and call the baseclass function which starts the scan for + // the next device if there are any unscanned devices left (or finishes + // the scan if none left) + releaseWorker(); + _q_serviceDiscoveryFinished(); } void QBluetoothServiceDiscoveryAgentPrivate::onError() @@ -610,6 +592,21 @@ void QBluetoothServiceDiscoveryAgentPrivate::onError() emit q->error(error); } +void QBluetoothServiceDiscoveryAgentPrivate::releaseWorker() +{ + if (!worker) + return; + + disconnect(worker, &QWinRTBluetoothServiceDiscoveryWorker::serviceFound, + this, &QBluetoothServiceDiscoveryAgentPrivate::processFoundService); + disconnect(worker, &QWinRTBluetoothServiceDiscoveryWorker::scanFinished, + this, &QBluetoothServiceDiscoveryAgentPrivate::onScanFinished); + disconnect(worker, &QWinRTBluetoothServiceDiscoveryWorker::errorOccured, + this, &QBluetoothServiceDiscoveryAgentPrivate::onError); + // mWorker will delete itself as soon as it is done with its discovery + worker = nullptr; +} + QT_END_NAMESPACE #include <qbluetoothservicediscoveryagent_winrt.moc> diff --git a/src/bluetooth/qbluetoothsocket.cpp b/src/bluetooth/qbluetoothsocket.cpp index e4d85447..60a64375 100644 --- a/src/bluetooth/qbluetoothsocket.cpp +++ b/src/bluetooth/qbluetoothsocket.cpp @@ -95,6 +95,11 @@ Q_DECLARE_LOGGING_CATEGORY(QT_BT) On iOS, this class cannot be used because the platform does not expose an API which may permit access to QBluetoothSocket related features. + + \note On macOS Monterey (12) the socket data flow is paused when a + modal dialogue is executing, or an event tracking mode is entered (for + example by long-pressing a Window close button). This may change in the + future releases of macOS. */ /*! @@ -593,7 +598,7 @@ void QBluetoothSocket::setSocketState(QBluetoothSocket::SocketState state) bool QBluetoothSocket::canReadLine() const { Q_D(const QBluetoothSocketBase); - return d->canReadLine(); + return d->canReadLine() || QIODevice::canReadLine(); } /*! diff --git a/src/bluetooth/qbluetoothsocket_bluezdbus.cpp b/src/bluetooth/qbluetoothsocket_bluezdbus.cpp index 084aa958..d6aa17a7 100644 --- a/src/bluetooth/qbluetoothsocket_bluezdbus.cpp +++ b/src/bluetooth/qbluetoothsocket_bluezdbus.cpp @@ -284,6 +284,10 @@ void QBluetoothSocketPrivateBluezDBus::connectToService( return; } + if (service.socketProtocol() != QBluetoothServiceInfo::Protocol::UnknownProtocol) + socketType = service.socketProtocol(); + qCDebug(QT_BT_BLUEZ) << "Socket protocol used:" << socketType; + connectToService(service.device().address(), targetService, openMode); } diff --git a/src/bluetooth/qbluetoothsocket_winrt.cpp b/src/bluetooth/qbluetoothsocket_winrt.cpp index d562f6f7..e7a42f01 100644 --- a/src/bluetooth/qbluetoothsocket_winrt.cpp +++ b/src/bluetooth/qbluetoothsocket_winrt.cpp @@ -50,6 +50,7 @@ #include <QtBluetooth/qbluetoothdeviceinfo.h> #include <QtBluetooth/qbluetoothserviceinfo.h> #include <QtCore/qloggingcategory.h> +#include <QtCore/QPointer> #include <robuffer.h> #include <windows.devices.bluetooth.h> @@ -141,20 +142,6 @@ public: void close() { m_shuttingDown = true; - if (Q_UNLIKELY(m_initialReadOp)) { - onReadyRead(m_initialReadOp.Get(), Canceled); - ComPtr<IAsyncInfo> info; - HRESULT hr = m_initialReadOp.As(&info); - Q_ASSERT_SUCCEEDED(hr); - if (info) { - hr = info->Cancel(); - Q_ASSERT_SUCCEEDED(hr); - hr = info->Close(); - Q_ASSERT_SUCCEEDED(hr); - } - m_initialReadOp.Reset(); - } - if (m_readOp) { onReadyRead(m_readOp.Get(), Canceled); ComPtr<IAsyncInfo> info; @@ -195,9 +182,16 @@ public: ComPtr<IInputStream> stream; hr = m_socket->get_InputStream(&stream); Q_ASSERT_SUCCEEDED(hr); - hr = stream->ReadAsync(buffer.Get(), READ_BUFFER_SIZE, InputStreamOptions_Partial, m_initialReadOp.GetAddressOf()); + hr = stream->ReadAsync(buffer.Get(), READ_BUFFER_SIZE, InputStreamOptions_Partial, m_readOp.GetAddressOf()); Q_ASSERT_SUCCEEDED(hr); - hr = m_initialReadOp->put_Completed(Callback<SocketReadCompletedHandler>(this, &SocketWorker::onReadyRead).Get()); + QPointer<SocketWorker> thisPtr(this); + hr = m_readOp->put_Completed( + Callback<SocketReadCompletedHandler>([thisPtr](IAsyncBufferOperation *asyncInfo, + AsyncStatus status) { + if (thisPtr) + return thisPtr->onReadyRead(asyncInfo, status); + return S_OK; + }).Get()); Q_ASSERT_SUCCEEDED(hr); return S_OK; }); @@ -209,13 +203,10 @@ public: if (m_shuttingDown) return S_OK; - if (asyncInfo == m_initialReadOp.Get()) { - m_initialReadOp.Reset(); - } else if (asyncInfo == m_readOp.Get()) { + if (asyncInfo == m_readOp.Get()) m_readOp.Reset(); - } else { + else Q_ASSERT(false); - } // A read in UnconnectedState will close the socket and return -1 and thus tell the caller, // that the connection was closed. The socket cannot be closed here, as the subsequent read @@ -301,7 +292,14 @@ public: emit socketErrorOccured(QBluetoothSocket::UnknownSocketError); return S_OK; } - hr = m_readOp->put_Completed(Callback<SocketReadCompletedHandler>(this, &SocketWorker::onReadyRead).Get()); + QPointer<SocketWorker> thisPtr(this); + hr = m_readOp->put_Completed( + Callback<SocketReadCompletedHandler>([thisPtr](IAsyncBufferOperation *asyncInfo, + AsyncStatus status) { + if (thisPtr) + return thisPtr->onReadyRead(asyncInfo, status); + return S_OK; + }).Get()); if (FAILED(hr)) { qErrnoWarning(hr, "onReadyRead(): Failed to set socket read callback."); emit socketErrorOccured(QBluetoothSocket::UnknownSocketError); @@ -323,7 +321,6 @@ private: // Protects pendingData/pendingDatagrams which are accessed from native callbacks QMutex m_mutex; - ComPtr<IAsyncOperationWithProgress<IBuffer *, UINT32>> m_initialReadOp; ComPtr<IAsyncOperationWithProgress<IBuffer *, UINT32>> m_readOp; }; diff --git a/src/bluetooth/qlowenergycontroller_winrt_new.cpp b/src/bluetooth/qlowenergycontroller_winrt_new.cpp index 42f4380e..6fd0ed2a 100644 --- a/src/bluetooth/qlowenergycontroller_winrt_new.cpp +++ b/src/bluetooth/qlowenergycontroller_winrt_new.cpp @@ -404,6 +404,8 @@ public: ~QWinRTLowEnergyConnectionHandler() { qCDebug(QT_BT_WINRT) << __FUNCTION__; + mDevice.Reset(); + mGattSession.Reset(); // To close the COM library gracefully, each successful call to // CoInitialize, including those that return S_FALSE, must be balanced // by a corresponding call to CoUninitialize. @@ -436,7 +438,7 @@ private: void QWinRTLowEnergyConnectionHandler::connectToDevice() { qCDebug(QT_BT_WINRT) << __FUNCTION__; - mInitialized = CoInitialize(NULL); + mInitialized = CoInitializeEx(NULL, COINIT_MULTITHREADED); qCDebug(QT_BT_WINRT) << qt_error_string(mInitialized); auto earlyExit = [this]() { return mAbortConnection; }; @@ -676,10 +678,23 @@ void QWinRTLowEnergyConnectionHandler::emitConnectedAndQuitThread() QThread::currentThread()->quit(); } +static void registerServiceHandlerMetaTypes() +{ + static bool registered = false; + if (!registered) { + qRegisterMetaType<QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData>>( + "QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData>"); + qRegisterMetaType<QVector<QBluetoothUuid>>("QVector<QBluetoothUuid>"); + qRegisterMetaType<QLowEnergyHandle>("QLowEnergyHandle"); + registered = true; + } +} + QLowEnergyControllerPrivateWinRTNew::QLowEnergyControllerPrivateWinRTNew() : QLowEnergyControllerPrivate() { registerQLowEnergyControllerMetaType(); + registerServiceHandlerMetaTypes(); connect(this, &QLowEnergyControllerPrivateWinRTNew::characteristicChanged, this, &QLowEnergyControllerPrivateWinRTNew::handleCharacteristicChanged, Qt::QueuedConnection); diff --git a/src/bluetooth/qlowenergyserviceprivate_p.h b/src/bluetooth/qlowenergyserviceprivate_p.h index 226af145..1816a23f 100644 --- a/src/bluetooth/qlowenergyserviceprivate_p.h +++ b/src/bluetooth/qlowenergyserviceprivate_p.h @@ -146,5 +146,6 @@ typedef QHash<QLowEnergyHandle, QLowEnergyServicePrivate::DescData> DescriptorDa QT_END_NAMESPACE Q_DECLARE_METATYPE(QSharedPointer<QLowEnergyServicePrivate>) +Q_DECLARE_METATYPE(QLowEnergyServicePrivate::CharData) #endif // QLOWENERGYSERVICEPRIVATE_P_H |