summaryrefslogtreecommitdiffstats
path: root/src/bluetooth
diff options
context:
space:
mode:
Diffstat (limited to 'src/bluetooth')
-rw-r--r--src/bluetooth/bluez/hcimanager.cpp6
-rw-r--r--src/bluetooth/osx/osxbtconnectionmonitor.mm9
-rw-r--r--src/bluetooth/osx/osxbtdeviceinquiry.mm19
-rw-r--r--src/bluetooth/osx/osxbtsdpinquiry.mm160
-rw-r--r--src/bluetooth/osx/osxbtservicerecord.mm50
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp32
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_android.cpp5
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_bluez.cpp11
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_osx.mm25
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_p.h2
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_winrt.cpp75
-rw-r--r--src/bluetooth/qbluetoothsocket.cpp7
-rw-r--r--src/bluetooth/qbluetoothsocket_bluezdbus.cpp4
-rw-r--r--src/bluetooth/qbluetoothsocket_winrt.cpp43
-rw-r--r--src/bluetooth/qlowenergycontroller_winrt_new.cpp17
-rw-r--r--src/bluetooth/qlowenergyserviceprivate_p.h1
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