summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.qmake.conf2
-rw-r--r--src/bluetooth/bluetooth.pro28
-rw-r--r--src/bluetooth/osx/osxbt.pri13
-rw-r--r--src/bluetooth/osx/osxbtcentralmanager.mm218
-rw-r--r--src/bluetooth/osx/osxbtcentralmanager_p.h6
-rw-r--r--src/bluetooth/osx/osxbtdeviceinquiry.mm10
-rw-r--r--src/bluetooth/osx/osxbtdevicepair.mm18
-rw-r--r--src/bluetooth/osx/osxbtl2capchannel.mm8
-rw-r--r--src/bluetooth/osx/osxbtledeviceinquiry.mm221
-rw-r--r--src/bluetooth/osx/osxbtledeviceinquiry_p.h31
-rw-r--r--src/bluetooth/osx/osxbtnotifier_p.h14
-rw-r--r--src/bluetooth/osx/osxbtobexsession.mm38
-rw-r--r--src/bluetooth/osx/osxbtperipheralmanager.mm754
-rw-r--r--src/bluetooth/osx/osxbtperipheralmanager_p.h174
-rw-r--r--src/bluetooth/osx/osxbtrfcommchannel.mm8
-rw-r--r--src/bluetooth/osx/osxbtsdpinquiry.mm8
-rw-r--r--src/bluetooth/osx/osxbtutility.mm8
-rw-r--r--src/bluetooth/osx/osxbtutility_p.h20
-rw-r--r--src/bluetooth/qbluetooth.cpp1
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent.cpp115
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent.h17
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp93
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp28
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm192
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm315
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_p.cpp8
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_p.h22
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp545
-rw-r--r--src/bluetooth/qbluetoothdevicediscoverytimer_osx_p.h85
-rw-r--r--src/bluetooth/qbluetoothlocaldevice_osx.mm21
-rw-r--r--src/bluetooth/qbluetoothserver_osx.mm35
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_osx.mm2
-rw-r--r--src/bluetooth/qbluetoothserviceinfo_osx.mm100
-rw-r--r--src/bluetooth/qbluetoothsocket_osx.mm12
-rw-r--r--src/bluetooth/qbluetoothtransferreply_osx.mm13
-rw-r--r--src/bluetooth/qlowenergycontroller.cpp14
-rw-r--r--src/bluetooth/qlowenergycontroller_osx.mm457
-rw-r--r--src/bluetooth/qlowenergycontroller_osx_p.h28
-rw-r--r--src/bluetooth/qlowenergycontroller_p.h28
-rw-r--r--src/bluetooth/qlowenergycontroller_winrt.cpp1019
-rw-r--r--src/bluetooth/qlowenergyservice_osx.mm6
-rw-r--r--src/imports/bluetooth/plugin.cpp2
-rw-r--r--src/imports/nfc/plugin.cpp2
-rw-r--r--tests/auto/qbluetoothdevicediscoveryagent/qbluetoothdevicediscoveryagent.pro4
-rw-r--r--tests/auto/qbluetoothdevicediscoveryagent/tst_qbluetoothdevicediscoveryagent.cpp217
-rw-r--r--tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp18
46 files changed, 3939 insertions, 1039 deletions
diff --git a/.qmake.conf b/.qmake.conf
index a2a0d418..aefa1e70 100644
--- a/.qmake.conf
+++ b/.qmake.conf
@@ -1,3 +1,3 @@
load(qt_build_config)
-MODULE_VERSION = 5.7.1
+MODULE_VERSION = 5.8.0
diff --git a/src/bluetooth/bluetooth.pro b/src/bluetooth/bluetooth.pro
index 1857e983..63e9ec11 100644
--- a/src/bluetooth/bluetooth.pro
+++ b/src/bluetooth/bluetooth.pro
@@ -153,14 +153,10 @@ config_bluez:qtHaveModule(dbus) {
qlowenergycontroller_osx.mm \
qlowenergyservice_osx.mm
- SOURCES += \
- qlowenergycontroller_p.cpp
-
PRIVATE_HEADERS += qbluetoothsocket_osx_p.h \
qbluetoothserver_osx_p.h \
qbluetoothtransferreply_osx_p.h \
qbluetoothtransferreply_osx_p.h \
- qbluetoothdevicediscoverytimer_osx_p.h \
qlowenergycontroller_osx_p.h
SOURCES -= qbluetoothdevicediscoveryagent.cpp
@@ -182,8 +178,7 @@ config_bluez:qtHaveModule(dbus) {
qlowenergyservice_osx.mm
PRIVATE_HEADERS += \
- qlowenergycontroller_osx_p.h \
- qbluetoothdevicediscoverytimer_osx_p.h
+ qlowenergycontroller_osx_p.h
include(osx/osxbt.pri)
SOURCES += \
@@ -198,6 +193,21 @@ config_bluez:qtHaveModule(dbus) {
SOURCES -= qlowenergycontroller_p.cpp
SOURCES -= qlowenergyservice.cpp
SOURCES -= qlowenergycontroller.cpp
+} else:if(winphone|winrt-*-msvc2015) {
+ DEFINES += QT_WINRT_BLUETOOTH
+ QT += core-private
+
+ # remove dummy warning once platform port is complete
+ include(dummy/dummy.pri)
+
+ SOURCES += \
+ qbluetoothdevicediscoveryagent_winrt.cpp \
+ qbluetoothlocaldevice_p.cpp \
+ qbluetoothserver_p.cpp \
+ qbluetoothservicediscoveryagent_p.cpp \
+ qbluetoothserviceinfo_p.cpp \
+ qbluetoothsocket_p.cpp \
+ qlowenergycontroller_winrt.cpp
} else {
message("Unsupported Bluetooth platform, will not build a working QtBluetooth library.")
message("Either no Qt D-Bus found or no BlueZ headers available.")
@@ -212,6 +222,12 @@ config_bluez:qtHaveModule(dbus) {
qlowenergycontroller_p.cpp
}
+winrt-*-msvc2015 {
+ MODULE_WINRT_CAPABILITIES_DEVICE += \
+ bluetooth.genericAttributeProfile \
+ bluetooth.rfcomm
+}
+
OTHER_FILES +=
HEADERS += $$PUBLIC_HEADERS $$PRIVATE_HEADERS
diff --git a/src/bluetooth/osx/osxbt.pri b/src/bluetooth/osx/osxbt.pri
index 5ca833cc..13187e4f 100644
--- a/src/bluetooth/osx/osxbt.pri
+++ b/src/bluetooth/osx/osxbt.pri
@@ -1,5 +1,6 @@
SOURCES += osx/uistrings.cpp osx/osxbtnotifier.cpp
PRIVATE_HEADERS += osx/uistrings_p.h
+//QMAKE_CXXFLAGS_WARN_ON += -Wno-nullability-completeness
CONFIG(osx) {
PRIVATE_HEADERS += osx/osxbtutility_p.h \
@@ -16,7 +17,8 @@ CONFIG(osx) {
osx/osxbtledeviceinquiry_p.h \
osx/osxbluetooth_p.h \
osx/osxbtcentralmanager_p.h \
- osx/osxbtnotifier_p.h
+ osx/osxbtnotifier_p.h \
+ osx/osxbtperipheralmanager_p.h
OBJECTIVE_SOURCES += osx/osxbtutility.mm \
osx/osxbtdevicepair.mm \
@@ -30,15 +32,22 @@ CONFIG(osx) {
osx/osxbtsocketlistener.mm \
osx/osxbtobexsession.mm \
osx/osxbtledeviceinquiry.mm \
- osx/osxbtcentralmanager.mm
+ osx/osxbtcentralmanager.mm \
+ osx/osxbtperipheralmanager.mm
} else {
PRIVATE_HEADERS += osx/osxbtutility_p.h \
osx/osxbtledeviceinquiry_p.h \
osx/osxbluetooth_p.h \
osx/osxbtcentralmanager_p.h \
osx/osxbtnotifier_p.h
+ ios {
+ PRIVATE_HEADERS += osx/osxbtperipheralmanager_p.h
+ }
OBJECTIVE_SOURCES += osx/osxbtutility.mm \
osx/osxbtledeviceinquiry.mm \
osx/osxbtcentralmanager.mm
+ ios {
+ OBJECTIVE_SOURCES += osx/osxbtperipheralmanager.mm
+ }
}
diff --git a/src/bluetooth/osx/osxbtcentralmanager.mm b/src/bluetooth/osx/osxbtcentralmanager.mm
index 06425753..f7218ca6 100644
--- a/src/bluetooth/osx/osxbtcentralmanager.mm
+++ b/src/bluetooth/osx/osxbtcentralmanager.mm
@@ -43,7 +43,6 @@
#include "osxbtnotifier_p.h"
#include <QtCore/qloggingcategory.h>
-#include <QtCore/qsysinfo.h>
#include <QtCore/qdebug.h>
#include <algorithm>
@@ -116,7 +115,7 @@ QT_END_NAMESPACE
@implementation QT_MANGLE_NAMESPACE(OSXBTCentralManager)
-- (id)initWith:(OSXBluetooth::LECentralNotifier *)aNotifier
+- (id)initWith:(OSXBluetooth::LECBManagerNotifier *)aNotifier
{
if (self = [super init]) {
manager = nil;
@@ -171,9 +170,9 @@ QT_END_NAMESPACE
if (!manager) {
managerState = OSXBluetooth::CentralManagerIdle;
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to allocate a central manager";
+ qCWarning(QT_BT_OSX) << "failed to allocate a central manager";
if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::ConnectionError);
+ emit notifier->CBManagerError(QLowEnergyController::ConnectionError);
}
} else if (managerState != OSXBluetooth::CentralManagerUpdating) {
[self retrievePeripheralAndConnect];
@@ -187,7 +186,7 @@ QT_END_NAMESPACE
Q_FUNC_INFO, "invalid state");
if ([self isConnected]) {
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "already connected";
+ qCDebug(QT_BT_OSX) << "already connected";
if (notifier)
emit notifier->connected();
return;
@@ -203,62 +202,37 @@ QT_END_NAMESPACE
// Retrieve a peripheral first ...
ObjCScopedPointer<NSMutableArray> uuids([[NSMutableArray alloc] init]);
if (!uuids) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to allocate identifiers";
+ qCWarning(QT_BT_OSX) << "failed to allocate identifiers";
if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::ConnectionError);
+ emit notifier->CBManagerError(QLowEnergyController::ConnectionError);
return;
}
-#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_7_0)
- if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_9, QSysInfo::MV_IOS_7_0)) {
- const quint128 qtUuidData(deviceUuid.toUInt128());
- // STATIC_ASSERT on sizes would be handy!
- uuid_t uuidData = {};
- std::copy(qtUuidData.data, qtUuidData.data + 16, uuidData);
- const ObjCScopedPointer<NSUUID> nsUuid([[NSUUID alloc] initWithUUIDBytes:uuidData]);
- if (!nsUuid) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to allocate NSUUID identifier";
- if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::ConnectionError);
- return;
- }
-
- [uuids addObject:nsUuid];
- // With the latest CoreBluetooth, we can synchronously retrive peripherals:
- QT_BT_MAC_AUTORELEASEPOOL;
- NSArray *const peripherals = [manager retrievePeripheralsWithIdentifiers:uuids];
- if (!peripherals || peripherals.count != 1) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to retrive a peripheral";
- if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::UnknownRemoteDeviceError);
- return;
- }
- peripheral = [static_cast<CBPeripheral *>([peripherals objectAtIndex:0]) retain];
- [self connectToPeripheral];
- return;
- }
-#endif
- // Either SDK or the target is below 10.9/7.0
- if (![manager respondsToSelector:@selector(retrievePeripherals:)]) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to retrive a peripheral";
+ const quint128 qtUuidData(deviceUuid.toUInt128());
+ uuid_t uuidData = {};
+ std::copy(qtUuidData.data, qtUuidData.data + 16, uuidData);
+ const ObjCScopedPointer<NSUUID> nsUuid([[NSUUID alloc] initWithUUIDBytes:uuidData]);
+ if (!nsUuid) {
+ qCWarning(QT_BT_OSX) << "failed to allocate NSUUID identifier";
if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::UnknownRemoteDeviceError);
+ emit notifier->CBManagerError(QLowEnergyController::ConnectionError);
return;
}
- OSXBluetooth::CFStrongReference<CFUUIDRef> cfUuid(OSXBluetooth::cf_uuid(deviceUuid));
- if (!cfUuid) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to create CFUUID object";
+ [uuids addObject:nsUuid];
+ // With the latest CoreBluetooth, we can synchronously retrive peripherals:
+ QT_BT_MAC_AUTORELEASEPOOL;
+ NSArray *const peripherals = [manager retrievePeripheralsWithIdentifiers:uuids];
+ if (!peripherals || peripherals.count != 1) {
+ qCWarning(QT_BT_OSX) << "failed to retrive a peripheral";
if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::ConnectionError);
+ emit notifier->CBManagerError(QLowEnergyController::UnknownRemoteDeviceError);
return;
}
- // With ARC this cast will be illegal:
- [uuids addObject:(id)cfUuid.data()];
- // Unfortunately, with old Core Bluetooth this call is asynchronous ...
- managerState = OSXBluetooth::CentralManagerConnecting;
- [manager performSelector:@selector(retrievePeripherals:) withObject:uuids.data()];
+
+ peripheral = [static_cast<CBPeripheral *>([peripherals objectAtIndex:0]) retain];
+ [self connectToPeripheral];
}
- (void)connectToPeripheral
@@ -270,11 +244,11 @@ QT_END_NAMESPACE
// The state is still the same - connecting.
if ([self isConnected]) {
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "already connected";
+ qCDebug(QT_BT_OSX) << "already connected";
if (notifier)
emit notifier->connected();
} else {
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "trying to connect";
+ qCDebug(QT_BT_OSX) << "trying to connect";
managerState = OSXBluetooth::CentralManagerConnecting;
[manager connectPeripheral:peripheral options:nil];
}
@@ -285,18 +259,7 @@ QT_END_NAMESPACE
if (!peripheral)
return false;
-#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_7_0)
- using OSXBluetooth::qt_OS_limit;
-
- if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_9, QSysInfo::MV_IOS_7_0))
- return peripheral.state == CBPeripheralStateConnected;
-#endif
- // Either SDK or the target is below 10.9/7.0 ...
- if (![peripheral respondsToSelector:@selector(isConnected)])
- return false;
-
- // Ugly cast to deal with id being a pointer ...
- return reinterpret_cast<quintptr>([peripheral performSelector:@selector(isConnected)]);
+ return peripheral.state == CBPeripheralStateConnected;
}
- (void)disconnectFromDevice
@@ -389,7 +352,7 @@ QT_END_NAMESPACE
Q_ASSERT_X(peripheral, Q_FUNC_INFO, "invalid peripheral (nil)");
if (servicesToDiscoverDetails.contains(serviceUuid)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO <<"already discovering for "
+ qCWarning(QT_BT_OSX) << "already discovering for"
<< serviceUuid;
return;
}
@@ -402,11 +365,11 @@ QT_END_NAMESPACE
return;
}
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown service uuid "
+ qCWarning(QT_BT_OSX) << "unknown service uuid"
<< serviceUuid;
if (notifier) {
- emit notifier->CBCentralManagerError(serviceUuid,
+ emit notifier->CBManagerError(serviceUuid,
QLowEnergyService::UnknownError);
}
}
@@ -499,9 +462,9 @@ QT_END_NAMESPACE
const QLowEnergyHandle maxHandle = std::numeric_limits<QLowEnergyHandle>::max();
if (nHandles >= maxHandle || lastValidHandle > maxHandle - nHandles) {
// Well, that's unlikely :) But we must be sure.
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "can not allocate more handles";
+ qCWarning(QT_BT_OSX) << "can not allocate more handles";
if (notifier)
- notifier->CBCentralManagerError(serviceUuid, QLowEnergyService::OperationError);
+ notifier->CBManagerError(serviceUuid, QLowEnergyService::OperationError);
return;
}
@@ -603,7 +566,7 @@ QT_END_NAMESPACE
const LERequest request(requests.dequeue());
if (request.type == LERequest::CharRead) {
if (!charMap.contains(request.handle)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "characteristic with handle"
+ qCWarning(QT_BT_OSX) << "characteristic with handle"
<< request.handle << "not found";
return [self performNextRequest];
}
@@ -613,7 +576,7 @@ QT_END_NAMESPACE
[peripheral readValueForCharacteristic:charMap[request.handle]];
} else {
if (!descMap.contains(request.handle)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "descriptor with handle"
+ qCWarning(QT_BT_OSX) << "descriptor with handle"
<< request.handle << "not found";
return [self performNextRequest];
}
@@ -640,8 +603,8 @@ QT_END_NAMESPACE
if (request.type == LERequest::DescWrite) {
if (!descMap.contains(request.handle)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "handle: "
- << request.handle << " not found";
+ qCWarning(QT_BT_OSX) << "handle:" << request.handle
+ << "not found";
return [self performNextRequest];
}
@@ -649,8 +612,7 @@ QT_END_NAMESPACE
ObjCStrongReference<NSData> data(data_from_bytearray(request.value));
if (!data) {
// Even if qtData.size() == 0, we still need NSData object.
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed "
- "to allocate an NSData object";
+ qCWarning(QT_BT_OSX) << "failed to allocate an NSData object";
return [self performNextRequest];
}
@@ -661,8 +623,8 @@ QT_END_NAMESPACE
return [peripheral writeValue:data.data() forDescriptor:descriptor];
} else {
if (!charMap.contains(request.handle)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "characteristic with "
- "handle: " << request.handle << " not found";
+ qCWarning(QT_BT_OSX) << "characteristic with handle:"
+ << request.handle << "not found";
return [self performNextRequest];
}
@@ -687,7 +649,7 @@ QT_END_NAMESPACE
ObjCStrongReference<NSData> data(data_from_bytearray(request.value));
if (!data) {
// Even if qtData.size() == 0, we still need NSData object.
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to allocate NSData object";
+ qCWarning(QT_BT_OSX) << "failed to allocate NSData object";
return [self performNextRequest];
}
@@ -717,11 +679,10 @@ QT_END_NAMESPACE
Q_ASSERT_X(charHandle, Q_FUNC_INFO, "invalid characteristic handle (0)");
if (!charMap.contains(charHandle)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO
- << "unknown characteristic handle "
+ qCWarning(QT_BT_OSX) << "unknown characteristic handle"
<< charHandle;
if (notifier) {
- emit notifier->CBCentralManagerError(serviceUuid,
+ emit notifier->CBManagerError(serviceUuid,
QLowEnergyService::DescriptorWriteError);
}
return;
@@ -732,10 +693,9 @@ QT_END_NAMESPACE
// it back, so check _now_ that we really have this descriptor.
const QBluetoothUuid qtUuid(QBluetoothUuid::ClientCharacteristicConfiguration);
if (![self descriptor:qtUuid forCharacteristic:charMap[charHandle]]) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO
- << "no client characteristic configuration found";
+ qCWarning(QT_BT_OSX) << "no client characteristic configuration found";
if (notifier) {
- emit notifier->CBCentralManagerError(serviceUuid,
+ emit notifier->CBManagerError(serviceUuid,
QLowEnergyService::DescriptorWriteError);
}
return;
@@ -760,9 +720,9 @@ QT_END_NAMESPACE
QT_BT_MAC_AUTORELEASEPOOL;
if (!charMap.contains(charHandle)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "characteristic: " << charHandle << " not found";
+ qCWarning(QT_BT_OSX) << "characteristic:" << charHandle << "not found";
if (notifier) {
- emit notifier->CBCentralManagerError(serviceUuid,
+ emit notifier->CBManagerError(serviceUuid,
QLowEnergyService::CharacteristicReadError);
}
@@ -789,10 +749,9 @@ QT_END_NAMESPACE
QT_BT_MAC_AUTORELEASEPOOL;
if (!charMap.contains(charHandle)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "characteristic: "
- << charHandle << " not found";
+ qCWarning(QT_BT_OSX) << "characteristic:" << charHandle << "not found";
if (notifier) {
- emit notifier->CBCentralManagerError(serviceUuid,
+ emit notifier->CBManagerError(serviceUuid,
QLowEnergyService::CharacteristicWriteError);
}
return;
@@ -816,10 +775,9 @@ QT_END_NAMESPACE
Q_ASSERT_X(descHandle, Q_FUNC_INFO, "invalid descriptor handle (0)");
if (!descMap.contains(descHandle)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "handle:"
- << descHandle << "not found";
+ qCWarning(QT_BT_OSX) << "handle:" << descHandle << "not found";
if (notifier) {
- emit notifier->CBCentralManagerError(serviceUuid,
+ emit notifier->CBManagerError(serviceUuid,
QLowEnergyService::DescriptorReadError);
}
return;
@@ -842,10 +800,9 @@ QT_END_NAMESPACE
Q_ASSERT_X(descHandle, Q_FUNC_INFO, "invalid descriptor handle (0)");
if (!descMap.contains(descHandle)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "handle: "
- << descHandle << " not found";
+ qCWarning(QT_BT_OSX) << "handle:" << descHandle << "not found";
if (notifier) {
- emit notifier->CBCentralManagerError(serviceUuid,
+ emit notifier->CBManagerError(serviceUuid,
QLowEnergyService::DescriptorWriteError);
}
return;
@@ -1029,27 +986,25 @@ QT_END_NAMESPACE
if ([obj isKindOfClass:[CBCharacteristic class]]) {
CBCharacteristic *const ch = static_cast<CBCharacteristic *>(obj);
if (!charMap.key(ch)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unexpected "
- "characteristic, no handle found";
+ qCWarning(QT_BT_OSX) << "unexpected characteristic, no handle found";
return false;
}
} else if ([obj isKindOfClass:[CBDescriptor class]]) {
CBDescriptor *const d = static_cast<CBDescriptor *>(obj);
if (!descMap.key(d)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unexpected "
- "descriptor, no handle found";
+ qCWarning(QT_BT_OSX) << "unexpected descriptor, no handle found";
return false;
}
} else {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid object, "
- "characteristic or descriptor required";
+ qCWarning(QT_BT_OSX) << "invalid object, characteristic "
+ "or descriptor required";
return false;
}
if (valuesToWrite.contains(obj)) {
// It can be a result of some previous errors - for example,
// we never got a callback from a previous write.
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "already has a cached value for this "
+ qCWarning(QT_BT_OSX) << "already has a cached value for this "
"object, the value will be replaced";
}
@@ -1114,7 +1069,7 @@ QT_END_NAMESPACE
// and reset managerState from CentralManagerUpdating.
managerState = CentralManagerIdle;
if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::InvalidBluetoothAdapterError);
+ emit notifier->CBManagerError(QLowEnergyController::InvalidBluetoothAdapterError);
}
return;
}
@@ -1133,7 +1088,7 @@ QT_END_NAMESPACE
// TODO: we need a better error +
// what will happen if later the state changes to PoweredOn???
if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::InvalidBluetoothAdapterError);
+ emit notifier->CBManagerError(QLowEnergyController::InvalidBluetoothAdapterError);
}
return;
}
@@ -1153,30 +1108,6 @@ QT_END_NAMESPACE
}
}
-- (void)centralManager:(CBCentralManager *)central didRetrievePeripherals:(NSArray *)peripherals
-{
- Q_UNUSED(central)
-
- // This method is required for iOS before 7.0 and OS X below 10.9.
- Q_ASSERT_X(manager, Q_FUNC_INFO, "invalid central manager (nil)");
-
- if (managerState != OSXBluetooth::CentralManagerConnecting) {
- // Canceled by calling -disconnectFromDevice method.
- return;
- }
-
- managerState = OSXBluetooth::CentralManagerIdle;
-
- if (!peripherals || peripherals.count != 1) {
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO <<"unexpected number of peripherals (!= 1)";
- if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::UnknownRemoteDeviceError);
- } else {
- peripheral = [static_cast<CBPeripheral *>([peripherals objectAtIndex:0]) retain];
- [self connectToPeripheral];
- }
-}
-
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)aPeripheral
{
Q_UNUSED(central)
@@ -1207,7 +1138,7 @@ QT_END_NAMESPACE
managerState = OSXBluetooth::CentralManagerIdle;
// TODO: better error mapping is required.
if (notifier)
- notifier->CBCentralManagerError(QLowEnergyController::UnknownRemoteDeviceError);
+ notifier->CBManagerError(QLowEnergyController::UnknownRemoteDeviceError);
}
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)aPeripheral
@@ -1221,9 +1152,9 @@ QT_END_NAMESPACE
if (error && managerState == OSXBluetooth::CentralManagerDisconnecting) {
managerState = OSXBluetooth::CentralManagerIdle;
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to disconnect";
+ qCWarning(QT_BT_OSX) << "failed to disconnect";
if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::UnknownRemoteDeviceError);
+ emit notifier->CBManagerError(QLowEnergyController::UnknownRemoteDeviceError);
} else {
managerState = OSXBluetooth::CentralManagerIdle;
if (notifier)
@@ -1248,7 +1179,7 @@ QT_END_NAMESPACE
NSLog(@"%s failed with error %@", Q_FUNC_INFO, error);
// TODO: better error mapping required.
if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::UnknownError);
+ emit notifier->CBManagerError(QLowEnergyController::UnknownError);
} else {
[self discoverIncludedServices];
}
@@ -1344,7 +1275,7 @@ QT_END_NAMESPACE
NSLog(@"%s failed with error: %@", Q_FUNC_INFO, error);
// We did not discover any characteristics and can not discover descriptors,
// inform our delegate (it will set a service state also).
- emit notifier->CBCentralManagerError(qt_uuid(service.UUID), QLowEnergyController::UnknownError);
+ emit notifier->CBManagerError(qt_uuid(service.UUID), QLowEnergyController::UnknownError);
} else {
[self readCharacteristics:service];
}
@@ -1379,7 +1310,7 @@ QT_END_NAMESPACE
if (chHandle && chHandle == currentReadHandle) {
currentReadHandle = 0;
requestPending = false;
- emit notifier->CBCentralManagerError(qtUuid, QLowEnergyService::CharacteristicReadError);
+ emit notifier->CBManagerError(qtUuid, QLowEnergyService::CharacteristicReadError);
[self performNextRequest];
}
return;
@@ -1402,7 +1333,7 @@ QT_END_NAMESPACE
// updated values ...
// TODO: this must be properly tested.
if (!chHandle) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "unexpected update notification, "
+ qCCritical(QT_BT_OSX) << "unexpected update notification, "
"no characteristic handle found";
return;
}
@@ -1481,7 +1412,7 @@ QT_END_NAMESPACE
if (dHandle && dHandle == currentReadHandle) {
currentReadHandle = 0;
requestPending = false;
- emit notifier->CBCentralManagerError(qtUuid, QLowEnergyService::DescriptorReadError);
+ emit notifier->CBManagerError(qtUuid, QLowEnergyService::DescriptorReadError);
[self performNextRequest];
}
return;
@@ -1513,7 +1444,7 @@ QT_END_NAMESPACE
}
} else {
if (!dHandle) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "unexpected value update notification, "
+ qCCritical(QT_BT_OSX) << "unexpected value update notification, "
"no descriptor handle found";
return;
}
@@ -1556,13 +1487,13 @@ QT_END_NAMESPACE
// Error or not, but the cached value has to be deleted ...
const QByteArray valueToReport(valuesToWrite.value(characteristic, QByteArray()));
if (!valuesToWrite.remove(characteristic)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no updated value found"
- " for characteristic";
+ qCWarning(QT_BT_OSX) << "no updated value found "
+ "for characteristic";
}
if (error) {
NSLog(@"%s failed with error %@", Q_FUNC_INFO, error);
- emit notifier->CBCentralManagerError(qt_uuid(characteristic.service.UUID),
+ emit notifier->CBManagerError(qt_uuid(characteristic.service.UUID),
QLowEnergyService::CharacteristicWriteError);
} else {
const QLowEnergyHandle cHandle = charMap.key(characteristic);
@@ -1592,16 +1523,15 @@ QT_END_NAMESPACE
// Error or not, a value (if any) must be removed.
const QByteArray valueToReport(valuesToWrite.value(descriptor, QByteArray()));
if (!valuesToWrite.remove(descriptor))
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no updated value found";
+ qCWarning(QT_BT_OSX) << "no updated value found";
if (error) {
NSLog(@"%s failed with error %@", Q_FUNC_INFO, error);
- emit notifier->CBCentralManagerError(qt_uuid(descriptor.characteristic.service.UUID),
+ emit notifier->CBManagerError(qt_uuid(descriptor.characteristic.service.UUID),
QLowEnergyService::DescriptorWriteError);
} else {
const QLowEnergyHandle dHandle = descMap.key(descriptor);
- Q_ASSERT_X(dHandle, Q_FUNC_INFO,
- "descriptor not found in the descriptors map");
+ Q_ASSERT_X(dHandle, Q_FUNC_INFO, "descriptor not found in the descriptors map");
emit notifier->descriptorWritten(dHandle, valueToReport);
}
@@ -1631,7 +1561,7 @@ QT_END_NAMESPACE
if (error) {
NSLog(@"%s failed with error %@", Q_FUNC_INFO, error);
// In Qt's API it's a descriptor write actually.
- emit notifier->CBCentralManagerError(qt_uuid(characteristic.service.UUID),
+ emit notifier->CBManagerError(qt_uuid(characteristic.service.UUID),
QLowEnergyService::DescriptorWriteError);
} else if (nRemoved) {
const QLowEnergyHandle dHandle = descMap.key(descriptor);
diff --git a/src/bluetooth/osx/osxbtcentralmanager_p.h b/src/bluetooth/osx/osxbtcentralmanager_p.h
index 3eee2190..697d922c 100644
--- a/src/bluetooth/osx/osxbtcentralmanager_p.h
+++ b/src/bluetooth/osx/osxbtcentralmanager_p.h
@@ -72,7 +72,7 @@ class QLowEnergyServicePrivate;
namespace OSXBluetooth {
-class LECentralNotifier;
+class LECBManagerNotifier;
enum CentralManagerState
{
@@ -141,7 +141,7 @@ QT_END_NAMESPACE
QT_PREPEND_NAMESPACE(QBluetoothUuid) deviceUuid;
- QT_PREPEND_NAMESPACE(OSXBluetooth)::LECentralNotifier *notifier;
+ QT_PREPEND_NAMESPACE(OSXBluetooth)::LECBManagerNotifier *notifier;
// Quite a verbose service discovery machinery
// (a "graph traversal").
@@ -170,7 +170,7 @@ QT_END_NAMESPACE
CBPeripheral *peripheral;
}
-- (id)initWith:(QT_PREPEND_NAMESPACE(OSXBluetooth)::LECentralNotifier *)notifier;
+- (id)initWith:(QT_PREPEND_NAMESPACE(OSXBluetooth)::LECBManagerNotifier *)notifier;
- (void)dealloc;
// IMPORTANT: _all_ these methods are to be executed on qt_LE_queue,
diff --git a/src/bluetooth/osx/osxbtdeviceinquiry.mm b/src/bluetooth/osx/osxbtdeviceinquiry.mm
index 0420a67e..79a8a92c 100644
--- a/src/bluetooth/osx/osxbtdeviceinquiry.mm
+++ b/src/bluetooth/osx/osxbtdeviceinquiry.mm
@@ -73,8 +73,7 @@ QT_USE_NAMESPACE
[m_inquiry setUpdateNewDeviceNames:NO];//Useless, disable!
m_delegate = delegate;
} else {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to create "
- "a device inquiry";
+ qCCritical(QT_BT_OSX) << "failed to create a device inquiry";
}
m_active = false;
@@ -113,8 +112,7 @@ QT_USE_NAMESPACE
if (result != kIOReturnSuccess) {
// QtBluetooth will probably convert an error into UnknownError,
// loosing the actual information.
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO <<"failed with "
- "IOKit error code: " << result;
+ qCWarning(QT_BT_OSX) << "failed with IOKit error code:" << result;
m_active = false;
}
@@ -130,6 +128,8 @@ QT_USE_NAMESPACE
const IOReturn res = [m_inquiry stop];
if (res != kIOReturnSuccess)
m_active = true;
+ else
+ qCDebug(QT_BT_OSX) << "-stop, success (waiting for 'inquiryComplete')";
return res;
}
@@ -152,7 +152,7 @@ QT_USE_NAMESPACE
if (error != kIOReturnSuccess) {
// QtBluetooth has not too many error codes, 'UnknownError' is not really
// useful, report the actual error code here:
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "IOKit error code: " << error;
+ qCWarning(QT_BT_OSX) << "IOKit error code: " << error;
m_delegate->error(sender, error);
} else {
m_delegate->inquiryFinished(sender);
diff --git a/src/bluetooth/osx/osxbtdevicepair.mm b/src/bluetooth/osx/osxbtdevicepair.mm
index dbb2fa4b..dcaa3536 100644
--- a/src/bluetooth/osx/osxbtdevicepair.mm
+++ b/src/bluetooth/osx/osxbtdevicepair.mm
@@ -41,7 +41,6 @@
#include "osxbtutility_p.h"
#include <QtCore/qloggingcategory.h>
-#include <QtCore/qsysinfo.h>
#include <QtCore/qdebug.h>
QT_BEGIN_NAMESPACE
@@ -88,18 +87,8 @@ QT_USE_NAMESPACE
- (void)dealloc
{
-#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9)
- // Stop also sets a delegate to nil (Apple's docs).
- // 10.9 only.
- if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_9)
- [m_pairing stop];
- else
- [m_pairing setDelegate:nil];
-#else
- [m_pairing setDelegate:nil];
-#endif
+ [m_pairing stop];
[m_pairing release];
-
[super dealloc];
}
@@ -116,14 +105,13 @@ QT_USE_NAMESPACE
// Device is autoreleased.
IOBluetoothDevice *const device = [IOBluetoothDevice deviceWithAddress:&iobtAddress];
if (!device) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to create a device "
- "to pair with";
+ qCCritical(QT_BT_OSX) << "failed to create a device to pair with";
return kIOReturnError;
}
m_pairing = [[IOBluetoothDevicePair pairWithDevice:device] retain];
if (!m_pairing) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to create pair";
+ qCCritical(QT_BT_OSX) << "failed to create pair";
return kIOReturnError;
}
diff --git a/src/bluetooth/osx/osxbtl2capchannel.mm b/src/bluetooth/osx/osxbtl2capchannel.mm
index e18e9e25..02ec4f90 100644
--- a/src/bluetooth/osx/osxbtl2capchannel.mm
+++ b/src/bluetooth/osx/osxbtl2capchannel.mm
@@ -104,13 +104,13 @@ QT_USE_NAMESPACE
withPSM:(BluetoothL2CAPChannelID)psm
{
if (address.isNull()) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "invalid peer address";
+ qCCritical(QT_BT_OSX) << "invalid peer address";
return kIOReturnNoDevice;
}
// Can never be called twice.
if (connected || device || channel) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "connection is already active";
+ qCCritical(QT_BT_OSX) << "connection is already active";
return kIOReturnStillOpen;
}
@@ -119,13 +119,13 @@ QT_USE_NAMESPACE
const BluetoothDeviceAddress iobtAddress = OSXBluetooth::iobluetooth_address(address);
device = [IOBluetoothDevice deviceWithAddress:&iobtAddress];
if (!device) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to create a device";
+ qCCritical(QT_BT_OSX) << "failed to create a device";
return kIOReturnNoDevice;
}
const IOReturn status = [device openL2CAPChannelAsync:&channel withPSM:psm delegate:self];
if (status != kIOReturnSuccess) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to open L2CAP channel";
+ qCCritical(QT_BT_OSX) << "failed to open L2CAP channel";
// device is still autoreleased.
device = nil;
return status;
diff --git a/src/bluetooth/osx/osxbtledeviceinquiry.mm b/src/bluetooth/osx/osxbtledeviceinquiry.mm
index 7b9e7431..8b924d82 100644
--- a/src/bluetooth/osx/osxbtledeviceinquiry.mm
+++ b/src/bluetooth/osx/osxbtledeviceinquiry.mm
@@ -39,19 +39,19 @@
#include "osxbtledeviceinquiry_p.h"
#include "qbluetoothdeviceinfo.h"
+#include "osxbtnotifier_p.h"
#include "qbluetoothuuid.h"
#include "osxbtutility_p.h"
#include <QtCore/qloggingcategory.h>
-#include <QtCore/qsysinfo.h>
#include <QtCore/qdebug.h>
+#include <algorithm>
+
QT_BEGIN_NAMESPACE
namespace OSXBluetooth {
-#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_6_0)
-
QBluetoothUuid qt_uuid(NSUUID *nsUuid)
{
if (!nsUuid)
@@ -64,58 +64,35 @@ QBluetoothUuid qt_uuid(NSUUID *nsUuid)
return QBluetoothUuid(qtUuidData);
}
-#endif
-
-QBluetoothUuid qt_uuid(CFUUIDRef uuid)
-{
- if (!uuid)
- return QBluetoothUuid();
+const int timeStepMS = 100;
- const CFUUIDBytes data = CFUUIDGetUUIDBytes(uuid);
- quint128 qtUuidData = {{data.byte0, data.byte1, data.byte2, data.byte3,
- data.byte4, data.byte5, data.byte6, data.byte7,
- data.byte8, data.byte9, data.byte10, data.byte11,
- data.byte12, data.byte13, data.byte14, data.byte15}};
-
- return QBluetoothUuid(qtUuidData);
-}
-
-typedef ObjCStrongReference<NSString> StringStrongReference;
-
-StringStrongReference uuid_as_nsstring(CFUUIDRef uuid)
-{
- // We use the UUDI's string representation as a key in a dictionary.
- if (!uuid)
- return StringStrongReference();
-
- CFStringRef cfStr = CFUUIDCreateString(kCFAllocatorDefault, uuid);
- if (!cfStr)
- return StringStrongReference();
-
- // Imporant: with ARC this will require a different cast/ownership!
- return StringStrongReference((NSString *)cfStr, false);
-}
+const int powerOffTimeoutMS = 30000;
+const qreal powerOffTimeStepS = 30. / 100.;
}
-
QT_END_NAMESPACE
QT_USE_NAMESPACE
@interface QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) (PrivateAPI) <CBCentralManagerDelegate>
+// These two methods are scheduled with a small time step
+// within a given timeout, they either re-schedule
+// themselves or emit a signal/stop some operation.
- (void)stopScan;
- (void)handlePoweredOff;
@end
@implementation QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry)
-- (id)init
+-(id)initWithNotifier:(LECBManagerNotifier *)aNotifier
{
if (self = [super init]) {
+ Q_ASSERT(aNotifier);
+ notifier = aNotifier;
uuids.reset([[NSMutableSet alloc] init]);
internalState = InquiryStarting;
- state.store(int(internalState));
+ inquiryTimeoutMS = OSXBluetooth::defaultLEScanTimeoutMS;
}
return self;
@@ -129,27 +106,36 @@ QT_USE_NAMESPACE
[manager stopScan];
}
+ if (notifier) {
+ notifier->disconnect();
+ notifier->deleteLater();
+ }
+
[super dealloc];
}
- (void)stopScan
{
- // Scan's "timeout" - we consider LE device
- // discovery finished.
using namespace OSXBluetooth;
+ // We never schedule stopScan if there is no timeout:
+ Q_ASSERT(inquiryTimeoutMS > 0);
+
if (internalState == InquiryActive) {
- if (scanTimer.elapsed() >= qt_LE_deviceInquiryLength() * 1000) {
- // We indeed stop now:
+ const int elapsed = scanTimer.elapsed();
+ if (elapsed >= inquiryTimeoutMS) {
[manager stopScan];
[manager setDelegate:nil];
internalState = InquiryFinished;
- state.store(int(internalState));
+ Q_ASSERT(notifier);
+ emit notifier->discoveryFinished();
} else {
+ // Re-schedule 'stopScan':
dispatch_queue_t leQueue(qt_LE_queue());
Q_ASSERT(leQueue);
+ const int timeChunkMS = std::min(inquiryTimeoutMS - elapsed, timeStepMS);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
- int64_t(qt_LE_deviceInquiryLength() / 100. * NSEC_PER_SEC)),
+ int64_t(timeChunkMS / 1000. * NSEC_PER_SEC)),
leQueue,
^{
[self stopScan];
@@ -164,30 +150,32 @@ QT_USE_NAMESPACE
// the system shows an alert asking to enable
// Bluetooth in the 'Settings' app. If not done yet (after 30
// seconds) - we consider it an error.
+ using namespace OSXBluetooth;
+
if (internalState == InquiryStarting) {
- if (errorTimer.elapsed() >= 30000) {
+ if (errorTimer.elapsed() >= powerOffTimeoutMS) {
[manager setDelegate:nil];
internalState = ErrorPoweredOff;
- state.store(int(internalState));
+ Q_ASSERT(notifier);
+ emit notifier->CBManagerError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
} else {
- dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
+ dispatch_queue_t leQueue(qt_LE_queue());
Q_ASSERT(leQueue);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
- (int64_t)(30 / 100. * NSEC_PER_SEC)),
+ (int64_t)(powerOffTimeStepS * NSEC_PER_SEC)),
leQueue,
^{
[self handlePoweredOff];
});
-
}
}
}
-- (void)start
+- (void)startWithTimeout:(int)timeout
{
dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
-
Q_ASSERT(leQueue);
+ inquiryTimeoutMS = timeout;
manager.reset([[CBCentralManager alloc] initWithDelegate:self queue:leQueue]);
}
@@ -199,30 +187,38 @@ QT_USE_NAMESPACE
if (internalState != InquiryActive && internalState != InquiryStarting)
return;
+ Q_ASSERT(notifier);
+
using namespace OSXBluetooth;
dispatch_queue_t leQueue(qt_LE_queue());
Q_ASSERT(leQueue);
#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_10_0)
- const CBManagerState cbState(central.state);
- if (cbState == CBManagerStatePoweredOn) {
+ const CBManagerState state(central.state);
+ if (state == CBManagerStatePoweredOn) {
#else
- const CBCentralManagerState cbState(central.state);
- if (cbState == CBCentralManagerStatePoweredOn) {
+ const CBCentralManagerState state(central.state);
+ if (state == CBCentralManagerStatePoweredOn) {
#endif
if (internalState == InquiryStarting) {
internalState = InquiryActive;
- // Scan time is actually 10 seconds. Having a block with such delay can prevent
- // 'self' from being deleted in time, which is not good. So we split this
- // 10 s. timeout into smaller 'chunks'.
- scanTimer.start();
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
- int64_t(qt_LE_deviceInquiryLength() / 100. * NSEC_PER_SEC)),
- leQueue,
- ^{
- [self stopScan];
- });
+
+ if (inquiryTimeoutMS > 0) {
+ // We have a finite-length discovery, schedule stopScan,
+ // with a smaller time step, otherwise it can prevent
+ // 'self' from being deleted in time, which is not good
+ // (the block will retain 'self', waiting for timeout).
+ scanTimer.start();
+ const int timeChunkMS = std::min(timeStepMS, inquiryTimeoutMS);
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
+ int64_t(timeChunkMS / 1000. * NSEC_PER_SEC)),
+ leQueue,
+ ^{
+ [self stopScan];
+ });
+ }
+
[manager scanForPeripheralsWithServices:nil options:nil];
} // Else we ignore.
#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_10_0)
@@ -232,28 +228,32 @@ QT_USE_NAMESPACE
#endif
if (internalState == InquiryActive) {
[manager stopScan];
- // Not sure how this is possible at all, probably, can never happen.
+ // Not sure how this is possible at all,
+ // probably, can never happen.
internalState = ErrorPoweredOff;
+ emit notifier->CBManagerError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
} else {
internalState = ErrorLENotSupported;
+ emit notifier->LEnotSupported();
}
[manager setDelegate:nil];
#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_10_0)
- } else if (cbState == CBManagerStatePoweredOff) {
+ } else if (state == CBManagerStatePoweredOff) {
#else
- } else if (cbState == CBCentralManagerStatePoweredOff) {
+ } else if (state == CBCentralManagerStatePoweredOff) {
#endif
if (internalState == InquiryStarting) {
#ifndef Q_OS_OSX
- // On iOS a user can see at this point an alert asking to enable
- // Bluetooth in the "Settings" app. If a user does,
+ // On iOS a user can see at this point an alert asking to
+ // enable Bluetooth in the "Settings" app. If a user does,
// we'll receive 'PoweredOn' state update later.
- // No change in state. Wait for 30 seconds (we split it into 'chunks' not
- // to retain 'self' for too long ) ...
+ // No change in internalState. Wait for 30 seconds
+ // (we split it into smaller steps not to retain 'self' for
+ // too long ) ...
errorTimer.start();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
- (int64_t)(30 / 100. * NSEC_PER_SEC)),
+ (int64_t)(powerOffTimeStepS * NSEC_PER_SEC)),
leQueue,
^{
[self handlePoweredOff];
@@ -261,9 +261,10 @@ QT_USE_NAMESPACE
return;
#endif
internalState = ErrorPoweredOff;
+ emit notifier->CBManagerError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
} else {
- internalState = ErrorPoweredOff;
[manager stopScan];
+ emit notifier->CBManagerError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
}
[manager setDelegate:nil];
@@ -279,8 +280,6 @@ QT_USE_NAMESPACE
// lost; an update is imminent. "
// Wait for this imminent update.
}
-
- state.store(int(internalState));
}
- (void)stop
@@ -290,11 +289,16 @@ QT_USE_NAMESPACE
[manager setDelegate:nil];
internalState = InquiryCancelled;
- state.store(int(internalState));
+
+ notifier->disconnect();
+ notifier->deleteLater();
+ notifier = nullptr;
}
-- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral
- advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
+- (void)centralManager:(CBCentralManager *)central
+ didDiscoverPeripheral:(CBPeripheral *)peripheral
+ advertisementData:(NSDictionary *)advertisementData
+ RSSI:(NSNumber *)RSSI
{
Q_UNUSED(advertisementData);
@@ -306,49 +310,28 @@ QT_USE_NAMESPACE
if (internalState != InquiryActive)
return;
- QBluetoothUuid deviceUuid;
-
-#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_7_0)
- if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_9, QSysInfo::MV_IOS_7_0)) {
- if (!peripheral.identifier) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "peripheral without NSUUID";
- return;
- }
+ if (!notifier)
+ return;
- if ([uuids containsObject:peripheral.identifier]) {
- // We already know this peripheral ...
- return;
- }
+ QBluetoothUuid deviceUuid;
- [uuids addObject:peripheral.identifier];
- deviceUuid = OSXBluetooth::qt_uuid(peripheral.identifier);
+ if (!peripheral.identifier) {
+ qCWarning(QT_BT_OSX) << "peripheral without NSUUID";
+ return;
}
-#endif
- // Either SDK or the target is below 10.9/7.0:
- // The property UUID was finally removed in iOS 9, we have
- // to avoid compilation errors ...
- if (deviceUuid.isNull()) {
- CFUUIDRef cfUUID = Q_NULLPTR;
-
- if ([peripheral respondsToSelector:@selector(UUID)]) {
- // This will require a bridged cast if we switch to ARC ...
- cfUUID = reinterpret_cast<CFUUIDRef>([peripheral performSelector:@selector(UUID)]);
- }
- if (!cfUUID) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "peripheral without CFUUID";
- return;
- }
-
- StringStrongReference key(uuid_as_nsstring(cfUUID));
- if ([uuids containsObject:key.data()])
- return; // We've seen this peripheral before ...
- [uuids addObject:key.data()];
- deviceUuid = OSXBluetooth::qt_uuid(cfUUID);
+ if ([uuids containsObject:peripheral.identifier]) {
+ // TODO: my understanding of the same peripheral reported many times seems
+ // to be outdated or even wrong - nowadays it's reported twice and the
+ // second time (AFAIK) more info can be extracted ...
+ return;
}
+ [uuids addObject:peripheral.identifier];
+ deviceUuid = OSXBluetooth::qt_uuid(peripheral.identifier);
+
if (deviceUuid.isNull()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no way to address peripheral, QBluetoothUuid is null";
+ qCWarning(QT_BT_OSX) << "no way to address peripheral, QBluetoothUuid is null";
return;
}
@@ -362,17 +345,7 @@ QT_USE_NAMESPACE
newDeviceInfo.setRssi([RSSI shortValue]);
// CoreBluetooth scans only for LE devices.
newDeviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration);
- devices.append(newDeviceInfo);
-}
-
-- (LEInquiryState) inquiryState
-{
- return LEInquiryState(state.load());
-}
-
-- (const QList<QBluetoothDeviceInfo> &)discoveredDevices
-{
- return devices;
+ emit notifier->deviceDiscovered(newDeviceInfo);
}
@end
diff --git a/src/bluetooth/osx/osxbtledeviceinquiry_p.h b/src/bluetooth/osx/osxbtledeviceinquiry_p.h
index 71e8ef53..b58904e6 100644
--- a/src/bluetooth/osx/osxbtledeviceinquiry_p.h
+++ b/src/bluetooth/osx/osxbtledeviceinquiry_p.h
@@ -58,7 +58,6 @@
#include <QtCore/qelapsedtimer.h>
#include <QtCore/qglobal.h>
-#include <QtCore/qatomic.h>
#include <QtCore/qlist.h>
#include <Foundation/Foundation.h>
@@ -67,8 +66,20 @@ QT_BEGIN_NAMESPACE
class QBluetoothUuid;
+namespace OSXBluetooth
+{
+
+class LECBManagerNotifier;
+
+}
+
QT_END_NAMESPACE
+// Ugly but all these QT_PREPEND_NAMESPACE etc. are even worse ...
+using OSXBluetooth::LECBManagerNotifier;
+using OSXBluetooth::ObjCScopedPointer;
+using QT_PREPEND_NAMESPACE(QElapsedTimer);
+
enum LEInquiryState
{
InquiryStarting,
@@ -81,29 +92,27 @@ enum LEInquiryState
@interface QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) : NSObject
{
- QT_PREPEND_NAMESPACE(OSXBluetooth)::ObjCScopedPointer<NSMutableSet> uuids;
- QT_PREPEND_NAMESPACE(OSXBluetooth)::ObjCScopedPointer<CBCentralManager> manager;
+ LECBManagerNotifier *notifier;
+ ObjCScopedPointer<NSMutableSet> uuids;
+ ObjCScopedPointer<CBCentralManager> manager;
QList<QBluetoothDeviceInfo> devices;
-
LEInquiryState internalState;
- QT_PREPEND_NAMESPACE(QAtomicInt) state;
+ int inquiryTimeoutMS;
// Timers to check if we can execute delayed callbacks:
QT_PREPEND_NAMESPACE(QElapsedTimer) errorTimer;
QT_PREPEND_NAMESPACE(QElapsedTimer) scanTimer;
}
-- (id)init;
+- (id)initWithNotifier:(LECBManagerNotifier *)aNotifier;
- (void)dealloc;
-// IMPORTANT: both 'start' and 'stop' are to be executed on the "Qt's LE queue".
-- (void)start;
+// IMPORTANT: both 'startWithTimeout' and 'stop'
+// can be executed only on the "Qt's LE queue".
+- (void)startWithTimeout:(int)timeout;
- (void)stop;
-- (LEInquiryState)inquiryState;
-- (const QList<QBluetoothDeviceInfo> &)discoveredDevices;
-
@end
#endif
diff --git a/src/bluetooth/osx/osxbtnotifier_p.h b/src/bluetooth/osx/osxbtnotifier_p.h
index 6cb2b019..0ffd7f51 100644
--- a/src/bluetooth/osx/osxbtnotifier_p.h
+++ b/src/bluetooth/osx/osxbtnotifier_p.h
@@ -52,7 +52,9 @@
//
+#include "qbluetoothdevicediscoveryagent.h"
#include "qlowenergycontroller.h"
+#include "qbluetoothdeviceinfo.h"
#include "qbluetoothuuid.h"
#include "qbluetooth.h"
@@ -68,11 +70,14 @@ class QLowEnergyServicePrivate;
namespace OSXBluetooth
{
-class LECentralNotifier : public QObject
+class LECBManagerNotifier : public QObject
{
Q_OBJECT
Q_SIGNALS:
+ void deviceDiscovered(QBluetoothDeviceInfo deviceInfo);
+ void discoveryFinished();
+
void connected();
void disconnected();
@@ -85,9 +90,10 @@ Q_SIGNALS:
void descriptorWritten(QLowEnergyHandle descHandle, const QByteArray &value);
void LEnotSupported();
- void CBCentralManagerError(QLowEnergyController::Error error);
- void CBCentralManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyController::Error error);
- void CBCentralManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyService::ServiceError error);
+ void CBManagerError(QBluetoothDeviceDiscoveryAgent::Error error);
+ void CBManagerError(QLowEnergyController::Error error);
+ void CBManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyController::Error error);
+ void CBManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyService::ServiceError error);
};
diff --git a/src/bluetooth/osx/osxbtobexsession.mm b/src/bluetooth/osx/osxbtobexsession.mm
index 9e324405..b8e604c8 100644
--- a/src/bluetooth/osx/osxbtobexsession.mm
+++ b/src/bluetooth/osx/osxbtobexsession.mm
@@ -203,7 +203,7 @@ QList<OBEXHeader> qt_bluetooth_headers(const uint8_t *data, std::size_t length)
break;
}
default:
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid header format";
+ qCWarning(QT_BT_OSX) << "invalid header format";
return empty;
}
@@ -358,7 +358,7 @@ bool check_connect_event(const OBEXSessionEvent *e, OBEXError &error, OBEXOpCode
response = e->u.connectCommandResponseData.serverResponseOpCode;
return response == kOBEXResponseCodeSuccessWithFinalBit;
} else {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unexpected event type";
+ qCWarning(QT_BT_OSX) << "unexpected event type";
error = kOBEXGeneralError;
return false;
}
@@ -378,7 +378,7 @@ bool check_put_event(const OBEXSessionEvent *e, OBEXError &error, OBEXOpCode &re
return response == kOBEXResponseCodeContinueWithFinalBit ||
response == kOBEXResponseCodeSuccessWithFinalBit;
} else {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unexpected event type";
+ qCWarning(QT_BT_OSX) << "unexpected event type";
error = kOBEXGeneralError;
return false;
}
@@ -395,7 +395,7 @@ bool check_abort_event(const OBEXSessionEvent *e, OBEXError &error, OBEXOpCode &
response = e->u.abortCommandResponseData.serverResponseOpCode;
return response == kOBEXResponseCodeSuccessWithFinalBit;
} else {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unexpected event type";
+ qCWarning(QT_BT_OSX) << "unexpected event type";
return false;
}
}
@@ -443,13 +443,13 @@ QT_USE_NAMESPACE
const BluetoothDeviceAddress addr(OSXBluetooth::iobluetooth_address(deviceAddress));
device = [[IOBluetoothDevice deviceWithAddress:&addr] retain];
if (!device) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to create an IOBluetoothDevice";
+ qCWarning(QT_BT_OSX) << "failed to create an IOBluetoothDevice";
return self;
}
session = [[IOBluetoothOBEXSession alloc] initWithDevice:device channelID:port];
if (!session) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to create an OBEX session";
+ qCWarning(QT_BT_OSX) << "failed to create an OBEX session";
return self;
}
@@ -474,7 +474,7 @@ QT_USE_NAMESPACE
- (OBEXError)OBEXConnect
{
if (!session) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid session (nil)";
+ qCWarning(QT_BT_OSX) << "invalid session (nil)";
return kOBEXGeneralError;
}
@@ -517,7 +517,7 @@ QT_USE_NAMESPACE
}
if (currentRequest != OBEXConnect) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "called while there is no "
+ qCWarning(QT_BT_OSX) << "called while there is no "
"active connect request";
return;
}
@@ -596,7 +596,7 @@ QT_USE_NAMESPACE
Q_ASSERT_X(session, Q_FUNC_INFO, "invalid OBEX session (nil)");
if (currentRequest != OBEXAbort) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "called while there "
+ qCWarning(QT_BT_OSX) << "called while there "
"is no ABORT request";
return;
}
@@ -628,13 +628,13 @@ QT_USE_NAMESPACE
// a payload.
const qint64 fileSize = input->size();
if (fileSize <= 0 || fileSize >= std::numeric_limits<uint32_t>::max()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid input file size";
+ qCWarning(QT_BT_OSX) << "invalid input file size";
return kOBEXBadArgumentError;
}
ObjCStrongReference<NSMutableData> headers([[NSMutableData alloc] init], false);
if (!headers) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to allocate headers";
+ qCWarning(QT_BT_OSX) << "failed to allocate headers";
return kOBEXNoResourcesError;
}
@@ -644,16 +644,14 @@ QT_USE_NAMESPACE
if (connectionIDFound) {
if (!append_four_byte_header(headers, kOBEXHeaderIDConnectionID, connectionID)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to "
- "append connection ID header";
+ qCWarning(QT_BT_OSX) << "failed to append connection ID header";
return kOBEXNoResourcesError;
}
}
if (name.length()) {
if (!append_unicode_header(headers, kOBEXHeaderIDName, name)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to append "
- "a unicode string";
+ qCWarning(QT_BT_OSX) << "failed to append a unicode string";
return kOBEXNoResourcesError;
}
}
@@ -666,7 +664,7 @@ QT_USE_NAMESPACE
if (!chunk || ![chunk length]) {
// We do not support PUT-DELETE (?)
// At least the first chunk is expected to be non-empty.
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid input stream";
+ qCWarning(QT_BT_OSX) << "invalid input stream";
return kOBEXBadArgumentError;
}
@@ -713,7 +711,7 @@ QT_USE_NAMESPACE
}
if (currentRequest != OBEXPut) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "called while the current "
+ qCWarning(QT_BT_OSX) << "called while the current "
"request is not a put request";
return;
}
@@ -735,8 +733,7 @@ QT_USE_NAMESPACE
// 0 for the headers length, no more headers.
ObjCStrongReference<NSMutableData> chunk(next_data_chunk(*inputStream, session, 0, lastChunk));
if (!chunk && !lastChunk) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to "
- "allocate the next memory chunk";
+ qCWarning(QT_BT_OSX) << "failed to allocate the next memory chunk";
return;
}
@@ -752,8 +749,7 @@ QT_USE_NAMESPACE
refCon:Q_NULLPTR];
if (status != kOBEXSuccess) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to "
- "send the next memory chunk";
+ qCWarning(QT_BT_OSX) << "failed to send the next memory chunk";
currentRequest = OBEXNoop;
if (delegate) // Response code is not important here.
delegate->OBEXPutError(kOBEXNoResourcesError, 0);
diff --git a/src/bluetooth/osx/osxbtperipheralmanager.mm b/src/bluetooth/osx/osxbtperipheralmanager.mm
new file mode 100644
index 00000000..4731fdd2
--- /dev/null
+++ b/src/bluetooth/osx/osxbtperipheralmanager.mm
@@ -0,0 +1,754 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+
+#include "qlowenergycharacteristicdata.h"
+#include "qlowenergydescriptordata.h"
+#include "osxbtperipheralmanager_p.h"
+#include "qlowenergyservicedata.h"
+#include "osxbtnotifier_p.h"
+#include "qbluetooth.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qlist.h>
+
+#include <algorithm>
+#include <limits>
+#include <set>
+
+namespace
+{
+
+CBCharacteristicProperties cb_properties(const QLowEnergyCharacteristicData &data)
+{
+ // Direct 'mapping' is ok.
+ return CBCharacteristicProperties(int(data.properties()));
+}
+
+CBAttributePermissions cb_permissions(const QLowEnergyCharacteristicData &data)
+{
+ using QLEC = QLowEnergyCharacteristic;
+
+ const auto props = data.properties();
+ CBAttributePermissions cbFlags = {};
+
+ if ((props & QLEC::Write) || (props & QLEC::WriteNoResponse)
+ || (props & QLEC::WriteSigned)) {
+ cbFlags = CBAttributePermissions(cbFlags | CBAttributePermissionsWriteable);
+ }
+
+ if (props & QLEC::Read)
+ cbFlags = CBAttributePermissions(cbFlags | CBAttributePermissionsReadable);
+
+ if (data.writeConstraints() & QBluetooth::AttEncryptionRequired)
+ cbFlags = CBAttributePermissions(cbFlags | CBAttributePermissionsWriteEncryptionRequired);
+
+ if (data.readConstraints() & QBluetooth::AttEncryptionRequired)
+ cbFlags = CBAttributePermissions(cbFlags | CBAttributePermissionsReadEncryptionRequired);
+
+ return cbFlags;
+}
+
+ObjCStrongReference<CBMutableCharacteristic> create_characteristic(const QLowEnergyCharacteristicData &data)
+{
+ const ObjCStrongReference<CBMutableCharacteristic> ch([[CBMutableCharacteristic alloc] initWithType:cb_uuid(data.uuid())
+ properties:cb_properties(data)
+ value:nil
+ permissions:cb_permissions(data)],
+ false /*do not retain*/);
+ return ch;
+}
+
+ObjCStrongReference<CBMutableDescriptor> create_descriptor(const QLowEnergyDescriptorData &data)
+{
+ // CoreBluetooth supports only:
+ /*
+ "That said, only two of these are currently supported when creating local,
+ mutable descriptors: the characteristic user description descriptor and
+ the characteristic format descriptor, represented by the CBUUID constants
+ CBUUIDCharacteristicUserDescriptionString and CBUUIDCharacteristicFormatString"
+ */
+
+ if (data.uuid() != QBluetoothUuid::CharacteristicUserDescription &&
+ data.uuid() != QBluetoothUuid::CharacteristicPresentationFormat) {
+ qCWarning(QT_BT_OSX) << "unsupported descriptor" << data.uuid();
+ return {};
+ }
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ // Descriptors are immutable with CoreBluetooth, that's why we
+ // have to provide a value here and not able to change it later.
+ ObjCStrongReference<NSObject> value;
+ if (data.uuid() == QBluetoothUuid::CharacteristicUserDescription) {
+ const QString asQString(QString::fromUtf8(data.value()));
+ value.reset(asQString.toNSString());
+ } else {
+ const auto nsData = data_from_bytearray(data.value());
+ value.reset(nsData.data());
+ }
+
+ const ObjCStrongReference<CBMutableDescriptor> d([[CBMutableDescriptor alloc]
+ initWithType:cb_uuid(data.uuid())
+ value:value], false /*do not retain*/);
+ return d;
+}
+
+quint32 qt_countGATTEntries(const QLowEnergyServiceData &data)
+{
+ const auto maxu32 = std::numeric_limits<quint32>::max();
+ // + 1 for a service itself.
+ quint32 nEntries = 1 + quint32(data.includedServices().count());
+ for (const auto &ch : data.characteristics()) {
+ if (maxu32 - 2 < nEntries)
+ return {};
+ nEntries += 2;
+ if (maxu32 - ch.descriptors().count() < nEntries)
+ return {};
+ nEntries += ch.descriptors().count();
+ }
+
+ return nEntries;
+}
+
+}
+
+@interface QT_MANGLE_NAMESPACE(OSXBTPeripheralManager) (PrivateAPI)
+
+- (void)addConnectedCentral:(CBCentral *)central;
+- (void)removeConnectedCentral:(CBCentral *)central;
+- (CBService *)findIncludedService:(const QBluetoothUuid &)qtUUID;
+
+- (void)addIncludedServices:(const QLowEnergyServiceData &)data
+ to:(CBMutableService *)cbService
+ qtService:(QLowEnergyServicePrivate *)qtService;
+
+- (void)addCharacteristicsAndDescriptors:(const QLowEnergyServiceData &)data
+ to:(CBMutableService *)cbService
+ qtService:(QLowEnergyServicePrivate *)qtService;
+
+- (CBATTError)validateWriteRequest:(CBATTRequest *)request;
+
+@end
+
+@implementation QT_MANGLE_NAMESPACE(OSXBTPeripheralManager)
+
+- (id)initWith:(LECBManagerNotifier *)aNotifier
+{
+ if (self = [super init]) {
+ Q_ASSERT(aNotifier);
+ notifier = aNotifier;
+ state = PeripheralState::idle;
+ nextServiceToAdd = {};
+ connectedCentrals.reset([[NSMutableSet alloc] init]);
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [self detach];
+ [super dealloc];
+}
+
+- (QSharedPointer<QLowEnergyServicePrivate>)addService:(const QLowEnergyServiceData &)data
+{
+ using QLES = QLowEnergyService;
+
+ const auto nEntries = qt_countGATTEntries(data);
+ if (!nEntries || nEntries > std::numeric_limits<QLowEnergyHandle>::max() - lastHandle) {
+ qCCritical(QT_BT_OSX) << "addService: not enough handles";
+ return {};
+ }
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ const BOOL primary = data.type() == QLowEnergyServiceData::ServiceTypePrimary;
+ const auto cbUUID = cb_uuid(data.uuid());
+
+ const ObjCStrongReference<CBMutableService>
+ newCBService([[CBMutableService alloc] initWithType:cbUUID primary:primary],
+ false /*do not retain*/);
+
+ if (!newCBService) {
+ qCCritical(QT_BT_OSX) << "addService: failed to create CBMutableService";
+ return {};
+ }
+
+ auto newQtService = QSharedPointer<QLowEnergyServicePrivate>::create();
+ newQtService->state = QLowEnergyService::LocalService;
+ newQtService->uuid = data.uuid();
+ newQtService->type = primary ? QLES::PrimaryService : QLES::IncludedService;
+ newQtService->startHandle = ++lastHandle;
+ // Controller will be set by ... controller :)
+
+ [self addIncludedServices:data to:newCBService qtService:newQtService.data()];
+ [self addCharacteristicsAndDescriptors:data to:newCBService qtService:newQtService.data()];
+
+ services.push_back(newCBService);
+ serviceIndex[data.uuid()] = newCBService;
+
+ return newQtService;
+}
+
+- (void) setParameters:(const QLowEnergyAdvertisingParameters &)parameters
+ data:(const QLowEnergyAdvertisingData &)data
+ scanResponse:(const QLowEnergyAdvertisingData &)scanResponse
+{
+ Q_UNUSED(parameters)
+
+ // This is the last method we call on the controller's thread
+ // before starting advertising on the Qt's LE queue.
+ // From Apple's docs:
+ /*
+ - (void)startAdvertising:(NSDictionary *)advertisementData
+
+ Advertises peripheral manager data.
+
+ * advertisementData
+
+ - An optional dictionary containing the data you want to advertise.
+ The possible keys of an advertisementData dictionary are detailed in CBCentralManagerDelegate
+ Protocol Reference. That said, only two of the keys are supported for peripheral manager objects:
+ CBAdvertisementDataLocalNameKey and CBAdvertisementDataServiceUUIDsKey.
+ */
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ advertisementData.reset([[NSMutableDictionary alloc] init]);
+ if (!advertisementData) {
+ qCWarning(QT_BT_OSX) << "setParameters: failed to allocate "
+ "NSMutableDictonary (advertisementData)";
+ return;
+ }
+
+ auto localName = scanResponse.localName();
+ if (!localName.size())
+ localName = data.localName();
+
+ if (localName.size()) {
+ [advertisementData setObject:localName.toNSString()
+ forKey:CBAdvertisementDataLocalNameKey];
+ }
+
+ if (!data.services().count() && !scanResponse.services().count())
+ return;
+
+ const ObjCScopedPointer<NSMutableArray> uuids([[NSMutableArray alloc] init]);
+ if (!uuids) {
+ qCWarning(QT_BT_OSX) << "setParameters: failed to allocate "
+ "NSMutableArray (services uuids)";
+ return;
+ }
+
+
+ for (const auto &qtUUID : data.services()) {
+ const auto cbUUID = cb_uuid(qtUUID);
+ if (cbUUID)
+ [uuids addObject:cbUUID];
+ }
+
+ for (const auto &qtUUID : scanResponse.services()) {
+ const auto cbUUID = cb_uuid(qtUUID);
+ if (cbUUID)
+ [uuids addObject:cbUUID];
+ }
+
+ if ([uuids count]) {
+ [advertisementData setObject:uuids
+ forKey:CBAdvertisementDataServiceUUIDsKey];
+ }
+}
+
+- (void)startAdvertising
+{
+ state = PeripheralState::waitingForPowerOn;
+ if (manager)
+ [manager setDelegate:nil];
+ manager.reset([[CBPeripheralManager alloc] initWithDelegate:self
+ queue:OSXBluetooth::qt_LE_queue()]);
+}
+
+- (void)stopAdvertising
+{
+ [manager stopAdvertising];
+ state = PeripheralState::idle;
+}
+
+- (void)detach
+{
+ if (notifier) {
+ notifier->disconnect();
+ notifier->deleteLater();
+ notifier = nullptr;
+ }
+
+ if (state == PeripheralState::advertising) {
+ [manager stopAdvertising];
+ [manager setDelegate:nil];
+ state = PeripheralState::idle;
+ }
+}
+
+- (void)write:(const QByteArray &)value
+ charHandle:(QLowEnergyHandle)charHandle
+{
+ if (!notifier)
+ return;
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ if (!charMap.contains(charHandle)) {
+ emit notifier->CBManagerError(QLowEnergyController::UnknownError);
+ return;
+ }
+
+ const auto nsData = data_from_bytearray(value);
+ charValues[charHandle] = nsData;
+ updateQueue.push_back(UpdateRequest{charHandle, nsData});
+ [self sendUpdateRequests];
+}
+
+- (void) addServicesToPeripheral
+{
+ Q_ASSERT(manager);
+
+ if (nextServiceToAdd < services.size())
+ [manager addService:services[nextServiceToAdd++]];
+}
+
+// CBPeripheralManagerDelegate:
+
+- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral
+{
+ if (peripheral != manager || !notifier)
+ return;
+
+ if (peripheral.state == CBPeripheralManagerStatePoweredOn) {
+ // "Bluetooth is currently powered on and is available to use."
+ if (state == PeripheralState::waitingForPowerOn) {
+ [manager removeAllServices];
+ nextServiceToAdd = {};
+ state = PeripheralState::advertising;
+ [self addServicesToPeripheral];
+ }
+ return;
+ }
+
+ /*
+ "A state with a value lower than CBPeripheralManagerStatePoweredOn implies that
+ advertising has stopped and that any connected centrals have been disconnected."
+ */
+
+ [connectedCentrals removeAllObjects];
+
+ if (state == PeripheralState::advertising) {
+ state = PeripheralState::waitingForPowerOn;
+ } else if (state == PeripheralState::connected) {
+ state = PeripheralState::idle;
+ emit notifier->disconnected();
+ }
+
+ // The next four states are _below_ "powered off"; according to the docs:
+ /*
+ "In addition, the local database is cleared and all services must be
+ explicitly added again."
+ */
+
+ if (peripheral.state == CBPeripheralManagerStateUnauthorized ||
+ peripheral.state == CBPeripheralManagerStateUnsupported) {
+ emit notifier->LEnotSupported();
+ state = PeripheralState::idle;
+ }
+}
+
+- (void)peripheralManager:(CBPeripheralManager *)peripheral
+ willRestoreState:(NSDictionary *)dict
+{
+ Q_UNUSED(peripheral)
+ Q_UNUSED(dict)
+ // NOOP atm.
+}
+
+- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
+ error:(NSError *)error
+{
+ if (peripheral != manager || !notifier)
+ return;
+
+ if (error) {
+ NSLog(@"failed to start advertising, error: %@", error);
+ state = PeripheralState::idle;
+ emit notifier->CBManagerError(QLowEnergyController::AdvertisingError);
+ }
+}
+
+- (void)peripheralManager:(CBPeripheralManager *)peripheral
+ didAddService:(CBService *)service error:(NSError *)error
+{
+ Q_UNUSED(service)
+
+ if (peripheral != manager || !notifier)
+ return;
+
+ if (error) {
+ NSLog(@"failed to add a service, error: %@", error);
+ emit notifier->CBManagerError(QLowEnergyController::AdvertisingError);
+ state = PeripheralState::idle;
+ return;
+ }
+
+ if (nextServiceToAdd == services.size())
+ [manager startAdvertising:[advertisementData count] ? advertisementData.data() : nil];
+ else
+ [self addServicesToPeripheral];
+}
+
+- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central
+ didSubscribeToCharacteristic:(CBCharacteristic *)characteristic
+{
+ Q_UNUSED(characteristic)
+
+ if (peripheral != manager || !notifier)
+ return;
+
+ [self addConnectedCentral:central];
+}
+
+- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central
+ didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic
+{
+ Q_UNUSED(characteristic)
+
+ if (peripheral != manager || !notifier)
+ return;
+
+ [self removeConnectedCentral:central];
+}
+
+- (void)peripheralManager:(CBPeripheralManager *)peripheral
+ didReceiveReadRequest:(CBATTRequest *)request
+{
+ if (peripheral != manager || !notifier)
+ return;
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ const auto handle = charMap.key(request.characteristic);
+ if (!handle || !charValues.contains(handle)) {
+ qCWarning(QT_BT_OSX) << "invalid read request, unknown characteristic";
+ [manager respondToRequest:request withResult:CBATTErrorInvalidHandle];
+ return;
+ }
+
+ const auto &value = charValues[handle];
+ if (request.offset > [value length]) {
+ qCWarning(QT_BT_OSX) << "invalid offset in a read request";
+ [manager respondToRequest:request withResult:CBATTErrorInvalidOffset];
+ return;
+ }
+
+ [self addConnectedCentral:request.central];
+
+ NSData *dataToSend = nil;
+ if (!request.offset) {
+ dataToSend = value;
+ } else {
+ dataToSend = [value subdataWithRange:
+ NSMakeRange(request.offset, [value length] - request.offset)];
+ }
+
+ request.value = dataToSend;
+ [manager respondToRequest:request withResult:CBATTErrorSuccess];
+}
+
+- (void)peripheralManager:(CBPeripheralManager *)peripheral
+ didReceiveWriteRequests:(NSArray *)requests
+{
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ if (peripheral != manager || !notifier) {
+ // Detached already.
+ return;
+ }
+
+ // We first test if all requests are valid
+ // since CoreBluetooth requires "all or none"
+ // and respond only _once_ to the first one.
+ for (CBATTRequest *request in requests) {
+ const auto status = [self validateWriteRequest:request];
+ if (status != CBATTErrorSuccess) {
+ [manager respondToRequest:[requests objectAtIndex:0]
+ withResult:status];
+ return;
+ }
+ }
+
+ std::set<QLowEnergyHandle> updated;
+
+ for (CBATTRequest *request in requests) {
+ // Transition to 'connected' if needed.
+ [self addConnectedCentral:request.central];
+
+ const auto charHandle = charMap.key(request.characteristic);
+ updated.insert(charHandle);
+ NSMutableData *const data = static_cast<NSMutableData *>(charValues[charHandle]);
+ [data replaceBytesInRange:NSMakeRange(request.offset, request.value.length)
+ withBytes:data.bytes];
+ }
+
+ for (const auto handle : updated)
+ emit notifier->characteristicUpdated(handle, qt_bytearray(charValues[handle]));
+
+ if (requests.count) {
+ [manager respondToRequest:[requests objectAtIndex:0]
+ withResult:CBATTErrorSuccess];
+ }
+}
+
+- (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral
+{
+ if (peripheral != manager || !notifier) {
+ // Detached.
+ return;
+ }
+
+ [self sendUpdateRequests];
+}
+
+- (void)sendUpdateRequests
+{
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ while (updateQueue.size()) {
+ const auto &request = updateQueue.front();
+ Q_ASSERT(charMap.contains(request.charHandle));
+ const BOOL res = [manager updateValue:request.value
+ forCharacteristic:static_cast<CBMutableCharacteristic *>(charMap[request.charHandle])
+ onSubscribedCentrals:nil];
+ if (!res) {
+ // Have to wait for the 'ManagerIsReadyToUpdate'.
+ break;
+ }
+
+ updateQueue.pop_front();
+ }
+}
+
+// Private API:
+
+- (void)addConnectedCentral:(CBCentral *)central
+{
+ if (!central)
+ return;
+
+ if (!notifier) {
+ // We were detached.
+ return;
+ }
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ if (state == PeripheralState::advertising) {
+ state = PeripheralState::connected;
+ [manager stopAdvertising];
+ emit notifier->connected();
+ }
+
+ if (![connectedCentrals containsObject:central.identifier])
+ [connectedCentrals addObject:central.identifier];
+}
+
+- (void)removeConnectedCentral:(CBCentral *)central
+{
+ if (!notifier) {
+ // Detached.
+ return;
+ }
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ if ([connectedCentrals containsObject:central.identifier])
+ [connectedCentrals removeObject:central.identifier];
+
+ if (state == PeripheralState::connected && ![connectedCentrals count]) {
+ state = PeripheralState::idle;
+ emit notifier->disconnected();
+ }
+}
+
+- (CBService *)findIncludedService:(const QBluetoothUuid &)qtUUID
+{
+ const auto it = serviceIndex.find(qtUUID);
+ if (it == serviceIndex.end())
+ return nil;
+
+ return it->second;
+}
+
+- (void)addIncludedServices:(const QLowEnergyServiceData &)data
+ to:(CBMutableService *)cbService
+ qtService:(QLowEnergyServicePrivate *)qtService
+{
+ Q_ASSERT(cbService);
+ Q_ASSERT(qtService);
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ ObjCScopedPointer<NSMutableArray> included([[NSMutableArray alloc] init]);
+ if (!included) {
+ qCWarning(QT_BT_OSX) << "addIncludedSerivces: failed "
+ "to allocate NSMutableArray";
+ return;
+ }
+
+ for (auto includedService : data.includedServices()) {
+ if (CBService *cbs = [self findIncludedService:includedService->serviceUuid()]) {
+ [included addObject:cbs];
+ qtService->includedServices << includedService->serviceUuid();
+ ++lastHandle;
+ } else {
+ qCWarning(QT_BT_OSX) << "can not use" << includedService->serviceUuid()
+ << "as included, it has to be added first";
+ }
+ }
+
+ if ([included count])
+ cbService.includedServices = included;
+}
+
+- (void)addCharacteristicsAndDescriptors:(const QLowEnergyServiceData &)data
+ to:(CBMutableService *)cbService
+ qtService:(QLowEnergyServicePrivate *)qtService
+{
+ Q_ASSERT(cbService);
+ Q_ASSERT(qtService);
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ ObjCScopedPointer<NSMutableArray> newCBChars([[NSMutableArray alloc] init]);
+ if (!newCBChars) {
+ qCWarning(QT_BT_OSX) << "addCharacteristicsAndDescritptors: "
+ "failed to allocate NSMutableArray "
+ "(characteristics)";
+ return;
+ }
+
+ for (const auto &ch : data.characteristics()) {
+ const auto cbChar(create_characteristic(ch));
+ if (!cbChar) {
+ qCWarning(QT_BT_OSX) << "addCharacteristicsAndDescritptors: "
+ "failed to allocate a characteristic";
+ continue;
+ }
+
+ const auto nsData(data_from_bytearray(ch.value()));
+ if (!nsData) {
+ qCWarning(QT_BT_OSX) << "addCharacteristicsAndDescritptors: "
+ "addService: failed to allocate NSData (char value)";
+ continue;
+ }
+
+ [newCBChars addObject:cbChar];
+
+ const auto declHandle = ++lastHandle;
+ // CB part:
+ charMap[declHandle] = cbChar;
+ charValues[declHandle] = data_from_bytearray(ch.value());
+ // QT part:
+ QLowEnergyServicePrivate::CharData charData;
+ charData.valueHandle = ++lastHandle;
+ charData.uuid = ch.uuid();
+ charData.properties = ch.properties();
+ charData.value = ch.value();
+
+ const ObjCScopedPointer<NSMutableArray> newCBDescs([[NSMutableArray alloc] init]);
+ if (!newCBDescs) {
+ qCWarning(QT_BT_OSX) << "addCharacteristicsAndDescritptors: "
+ "failed to allocate NSMutableArray "
+ "(descriptors)";
+ continue;
+ }
+
+ for (const auto &desc : ch.descriptors()) {
+ // CB part:
+ const auto cbDesc(create_descriptor(desc));
+ const auto descHandle = ++lastHandle;
+ if (cbDesc) {
+ // See comments in create_descriptor on
+ // why cbDesc can be nil.
+ [newCBDescs addObject:cbDesc];
+ }
+ // QT part:
+ QLowEnergyServicePrivate::DescData descData;
+ descData.uuid = desc.uuid();
+ descData.value = desc.value();
+ charData.descriptorList.insert(descHandle, descData);
+ }
+
+ if ([newCBDescs count])
+ cbChar.data().descriptors = newCBDescs.data(); // retains
+
+ qtService->characteristicList.insert(declHandle, charData);
+ }
+
+ if ([newCBChars count])
+ cbService.characteristics = newCBChars.data();
+}
+
+- (CBATTError)validateWriteRequest:(CBATTRequest *)request
+{
+ Q_ASSERT(request);
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ const auto handle = charMap.key(request.characteristic);
+ if (!handle || !charValues.contains(handle))
+ return CBATTErrorInvalidHandle;
+
+ NSMutableData *data = static_cast<NSMutableData *>(charValues[handle]);
+ if (request.offset > data.length || request.value.length > data.length - request.offset)
+ return CBATTErrorInvalidOffset;
+
+ return CBATTErrorSuccess;
+}
+
+@end
diff --git a/src/bluetooth/osx/osxbtperipheralmanager_p.h b/src/bluetooth/osx/osxbtperipheralmanager_p.h
new file mode 100644
index 00000000..30ddd073
--- /dev/null
+++ b/src/bluetooth/osx/osxbtperipheralmanager_p.h
@@ -0,0 +1,174 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef OSXBTPERIPHERALMANAGER_P_H
+#define OSXBTPERIPHERALMANAGER_P_H
+
+
+#include "osxbtutility_p.h"
+
+#include "qlowenergyadvertisingparameters.h"
+#include "qlowenergyserviceprivate_p.h"
+#include "qbluetoothuuid.h"
+#include "qbluetooth.h"
+
+#include <QtCore/qsharedpointer.h>
+#include <QtCore/qbytearray.h>
+#include <QtCore/qsysinfo.h>
+#include <QtCore/qglobal.h>
+#include <QtCore/qmap.h>
+
+#include <vector>
+#include <deque>
+#include <map>
+
+#include <Foundation/Foundation.h>
+
+#include "osxbluetooth_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QLowEnergyServiceData;
+
+namespace OSXBluetooth
+{
+
+class LECBManagerNotifier;
+
+}
+
+QT_END_NAMESPACE
+
+
+// Exposing names in a header is ugly, but constant QT_PREPEND_NAMESPACE is even worse ...
+// After all, this header is to be included only in its own and controller's *.mm files.
+
+QT_USE_NAMESPACE
+
+using namespace OSXBluetooth;
+
+
+template<class Type>
+using GenericLEMap = QMap<QLowEnergyHandle, Type>;
+
+enum class PeripheralState
+{
+ idle,
+ waitingForPowerOn,
+ advertising,
+ connected
+};
+
+struct UpdateRequest
+{
+ UpdateRequest() = default;
+ UpdateRequest(QLowEnergyHandle handle, const ObjCStrongReference<NSData> &val)
+ : charHandle(handle),
+ value(val)
+ {
+ }
+
+ QLowEnergyHandle charHandle = {};
+ ObjCStrongReference<NSData> value;
+};
+
+@interface QT_MANGLE_NAMESPACE(OSXBTPeripheralManager) : NSObject<CBPeripheralManagerDelegate>
+{
+ ObjCScopedPointer<CBPeripheralManager> manager;
+ LECBManagerNotifier *notifier;
+
+ QLowEnergyHandle lastHandle;
+ // Services in this vector are placed in such order:
+ // the one that has included services, must
+ // follow its included services to avoid exceptions from CBPeripheralManager.
+ std::vector<ObjCStrongReference<CBMutableService>> services;
+ decltype(services.size()) nextServiceToAdd;
+
+ // Lookup map for included services:
+ std::map<QBluetoothUuid, CBService *> serviceIndex;
+ ObjCScopedPointer<NSMutableDictionary> advertisementData;
+
+ GenericLEMap<CBCharacteristic *> charMap;
+ GenericLEMap<ObjCStrongReference<NSData>> charValues;
+
+ std::deque<UpdateRequest> updateQueue;
+
+ ObjCScopedPointer<NSMutableSet> connectedCentrals;
+
+ PeripheralState state;
+}
+
+- (id)initWith:(LECBManagerNotifier *)notifier;
+- (void)dealloc;
+
+- (QSharedPointer<QLowEnergyServicePrivate>)addService:(const QLowEnergyServiceData &)data;
+- (void) setParameters:(const QLowEnergyAdvertisingParameters &)parameters
+ data:(const QLowEnergyAdvertisingData &)data
+ scanResponse:(const QLowEnergyAdvertisingData &)scanResponse;
+
+// To be executed on the Qt's special BTLE dispatch queue.
+- (void)startAdvertising;
+- (void)stopAdvertising;
+- (void)detach;
+
+- (void)write:(const QByteArray &)value
+ charHandle:(QLowEnergyHandle)charHandle;
+
+
+// CBPeripheralManagerDelegate's callbacks (BTLE queue).
+- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral;
+- (void)peripheralManager:(CBPeripheralManager *)peripheral
+ willRestoreState:(NSDictionary *)dict;
+- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
+ error:(NSError *)error;
+- (void)peripheralManager:(CBPeripheralManager *)peripheral
+ didAddService:(CBService *)service error:(NSError *)error;
+- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central
+ didSubscribeToCharacteristic:(CBCharacteristic *)characteristic;
+- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central
+ didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic;
+- (void)peripheralManager:(CBPeripheralManager *)peripheral
+ didReceiveReadRequest:(CBATTRequest *)request;
+- (void)peripheralManager:(CBPeripheralManager *)peripheral
+ didReceiveWriteRequests:(NSArray *)requests;
+- (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral;
+
+@end
+
+#endif
diff --git a/src/bluetooth/osx/osxbtrfcommchannel.mm b/src/bluetooth/osx/osxbtrfcommchannel.mm
index e929f335..ea679ec8 100644
--- a/src/bluetooth/osx/osxbtrfcommchannel.mm
+++ b/src/bluetooth/osx/osxbtrfcommchannel.mm
@@ -97,13 +97,13 @@ QT_USE_NAMESPACE
withChannelID:(BluetoothRFCOMMChannelID)channelID
{
if (address.isNull()) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "invalid peer address";
+ qCCritical(QT_BT_OSX) << "invalid peer address";
return kIOReturnNoDevice;
}
// Can never be called twice.
if (connected || device || channel) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "connection is already active";
+ qCCritical(QT_BT_OSX) << "connection is already active";
return kIOReturnStillOpen;
}
@@ -112,14 +112,14 @@ QT_USE_NAMESPACE
const BluetoothDeviceAddress iobtAddress = OSXBluetooth::iobluetooth_address(address);
device = [IOBluetoothDevice deviceWithAddress:&iobtAddress];
if (!device) { // TODO: do I always check this BTW??? Apple's docs say nothing about nil.
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to create a device";
+ qCCritical(QT_BT_OSX) << "failed to create a device";
return kIOReturnNoDevice;
}
const IOReturn status = [device openRFCOMMChannelAsync:&channel
withChannelID:channelID delegate:self];
if (status != kIOReturnSuccess) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to open L2CAP channel";
+ qCCritical(QT_BT_OSX) << "failed to open L2CAP channel";
// device is still autoreleased.
device = nil;
return status;
diff --git a/src/bluetooth/osx/osxbtsdpinquiry.mm b/src/bluetooth/osx/osxbtsdpinquiry.mm
index a1d6af1c..a0bfdeef 100644
--- a/src/bluetooth/osx/osxbtsdpinquiry.mm
+++ b/src/bluetooth/osx/osxbtsdpinquiry.mm
@@ -184,7 +184,7 @@ using namespace OSXBluetooth;
if (qtFilters.size()) {
array.reset([[NSMutableArray alloc] init]);
if (!array) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to allocate an uuid filter";
+ qCCritical(QT_BT_OSX) << "failed to allocate an uuid filter";
return kIOReturnError;
}
@@ -195,7 +195,7 @@ using namespace OSXBluetooth;
}
if (int([array count]) != qtFilters.size()) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to create an uuid filter";
+ qCCritical(QT_BT_OSX) << "failed to create an uuid filter";
return kIOReturnError;
}
}
@@ -203,7 +203,7 @@ using namespace OSXBluetooth;
const BluetoothDeviceAddress iobtAddress(iobluetooth_address(address));
ObjCScopedPointer<IOBluetoothDevice> newDevice([[IOBluetoothDevice deviceWithAddress:&iobtAddress] retain]);
if (!newDevice) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to create an IOBluetoothDevice object";
+ qCCritical(QT_BT_OSX) << "failed to create an IOBluetoothDevice object";
return kIOReturnError;
}
@@ -217,7 +217,7 @@ using namespace OSXBluetooth;
result = [device performSDPQuery:self];
if (result != kIOReturnSuccess) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to start an SDP query";
+ qCCritical(QT_BT_OSX) << "failed to start an SDP query";
device = oldDevice.take();
} else {
isActive = true;
diff --git a/src/bluetooth/osx/osxbtutility.mm b/src/bluetooth/osx/osxbtutility.mm
index 942fed1c..adbc327b 100644
--- a/src/bluetooth/osx/osxbtutility.mm
+++ b/src/bluetooth/osx/osxbtutility.mm
@@ -37,6 +37,7 @@
**
****************************************************************************/
+#include "qlowenergycharacteristicdata.h"
#include "qbluetoothaddress.h"
#include "osxbtutility_p.h"
#include "qbluetoothuuid.h"
@@ -70,6 +71,8 @@ Q_LOGGING_CATEGORY(QT_BT_OSX, "qt.bluetooth.ios")
namespace OSXBluetooth {
+const int defaultLEScanTimeoutMS = 25000;
+
QString qt_address(NSString *address)
{
if (address && address.length) {
@@ -350,11 +353,6 @@ dispatch_queue_t qt_LE_queue()
return leQueue.data();
}
-unsigned qt_LE_deviceInquiryLength()
-{
- return 10;
-}
-
}
QT_END_NAMESPACE
diff --git a/src/bluetooth/osx/osxbtutility_p.h b/src/bluetooth/osx/osxbtutility_p.h
index de3d3ea2..2d11692d 100644
--- a/src/bluetooth/osx/osxbtutility_p.h
+++ b/src/bluetooth/osx/osxbtutility_p.h
@@ -55,13 +55,14 @@
#include <QtCore/qloggingcategory.h>
#include <QtCore/qscopedpointer.h>
-#include <QtCore/qsysinfo.h>
#include <QtCore/qglobal.h>
#include <Foundation/Foundation.h>
QT_BEGIN_NAMESPACE
+class QLowEnergyCharacteristicData;
+class QBluetoothAddress;
class QBluetoothUuid;
namespace OSXBluetooth {
@@ -280,7 +281,7 @@ QString qt_address(NSString *address);
#ifndef QT_IOS_BLUETOOTH
-class QBluetoothAddress qt_address(const BluetoothDeviceAddress *address);
+QBluetoothAddress qt_address(const BluetoothDeviceAddress *address);
BluetoothDeviceAddress iobluetooth_address(const QBluetoothAddress &address);
ObjCStrongReference<IOBluetoothSDPUUID> iobluetooth_uuid(const QBluetoothUuid &uuid);
@@ -298,20 +299,9 @@ QByteArray qt_bytearray(NSData *data);
QByteArray qt_bytearray(NSObject *data);
ObjCStrongReference<NSData> data_from_bytearray(const QByteArray & qtData);
-inline QSysInfo::MacVersion qt_OS_limit(QSysInfo::MacVersion osxVersion, QSysInfo::MacVersion iosVersion)
-{
-#ifdef Q_OS_OSX
- Q_UNUSED(iosVersion)
- return osxVersion;
-#else
- Q_UNUSED(osxVersion)
- return iosVersion;
-#endif
-}
-
dispatch_queue_t qt_LE_queue();
-// LE scan, in seconds.
-unsigned qt_LE_deviceInquiryLength();
+
+extern const int defaultLEScanTimeoutMS;
} // namespace OSXBluetooth
diff --git a/src/bluetooth/qbluetooth.cpp b/src/bluetooth/qbluetooth.cpp
index 483fe65a..37a4774d 100644
--- a/src/bluetooth/qbluetooth.cpp
+++ b/src/bluetooth/qbluetooth.cpp
@@ -101,5 +101,6 @@ namespace QBluetooth {
Q_LOGGING_CATEGORY(QT_BT, "qt.bluetooth")
Q_LOGGING_CATEGORY(QT_BT_ANDROID, "qt.bluetooth.android")
Q_LOGGING_CATEGORY(QT_BT_BLUEZ, "qt.bluetooth.bluez")
+Q_LOGGING_CATEGORY(QT_BT_WINRT, "qt.bluetooth.winphone")
QT_END_NAMESPACE
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent.cpp
index b6faee75..b033ae3c 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent.cpp
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent.cpp
@@ -40,9 +40,12 @@
#include "qbluetoothdevicediscoveryagent.h"
#include "qbluetoothdevicediscoveryagent_p.h"
+#include <QtCore/qloggingcategory.h>
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(QT_BT)
+
/*!
\class QBluetoothDeviceDiscoveryAgent
\inmodule QtBluetooth
@@ -88,6 +91,8 @@ QT_BEGIN_NAMESPACE
platform. The error is set in response to a call to \l start().
An example for such cases are iOS versions below 5.0 which do not support
Bluetooth device search at all. This value was introduced by Qt 5.5.
+ \value UnsupportedDiscoveryMethod One of the requested discovery methods is not supported by
+ the current platform. This value was introduced by Qt 5.8.
\value UnknownError An unknown error has occurred.
*/
@@ -110,6 +115,22 @@ QT_BEGIN_NAMESPACE
*/
/*!
+ \enum QBluetoothDeviceDiscoveryAgent::DiscoveryMethod
+
+ This enum descibes the type of discovery method employed by the QBluetoothDeviceDiscoveryAgent.
+
+ \value NoMethod The discovery is not possible. None of the available
+ methods are supported.
+ \value ClassicMethod The discovery process searches for Bluetooth Classic
+ (BaseRate) devices.
+ \value LowEnergyMethod The discovery process searches for Bluetooth Low Energy
+ devices.
+
+ \sa supportedDiscoveryMethods()
+ \since 5.8
+*/
+
+/*!
\fn void QBluetoothDeviceDiscoveryAgent::deviceDiscovered(const QBluetoothDeviceInfo &info)
This signal is emitted when the Bluetooth device described by \a info is discovered.
@@ -232,16 +253,106 @@ QList<QBluetoothDeviceInfo> QBluetoothDeviceDiscoveryAgent::discoveredDevices()
}
/*!
+ Sets the maximum search time for Bluetooth Low Energy device search to
+ \a timeout in milliseconds. If \a timeout is \c 0 the discovery runs
+ until \l stop() is called.
+
+ This reflects the fact that the discovery process for Bluetooth Low Energy devices
+ is mostly open ended. The platform continues to look for more devices until the search is
+ manually stopped. The timeout ensures that the search is aborted after \a timeout milliseconds.
+ Of course, it is still possible to manually abort the discovery by calling \l stop().
+
+ The new timeout value does not take effect until the device search is restarted.
+ In addition the timeout does not affect the classic Bluetooth device search. Depending on
+ the platform the classic search may add more time to the total discovery process
+ beyond \a timeout.
+
+ \sa lowEnergyDiscoveryTimeout()
+ \since 5.8
+ */
+void QBluetoothDeviceDiscoveryAgent::setLowEnergyDiscoveryTimeout(int timeout)
+{
+ Q_D(QBluetoothDeviceDiscoveryAgent);
+
+ // cannot deliberately turn it off
+ if (d->lowEnergySearchTimeout < 0 || timeout < 0) {
+ qCDebug(QT_BT) << "The Bluetooth Low Energy device discovery timeout cannot be negative "
+ "or set on a backend which does not support this feature.";
+ return;
+ }
+
+ d->lowEnergySearchTimeout = timeout;
+}
+
+/*!
+ Returns a timeout in milliseconds that is applied to the Bluetooth Low Energy device search.
+ A value of \c -1 implies that the platform does not support this property and the timeout for
+ the device search cannot be adjusted. A return value of \c 0
+ implies a never-ending search which must be manually stopped via \l stop().
+
+ \sa setLowEnergyDiscoveryTimeout()
+ \since 5.8
+ */
+int QBluetoothDeviceDiscoveryAgent::lowEnergyDiscoveryTimeout() const
+{
+ Q_D(const QBluetoothDeviceDiscoveryAgent);
+ return d->lowEnergySearchTimeout;
+}
+
+/*!
+ \fn QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods()
+
+ This function returns the discovery methods supported by the current platform.
+ It can be used to limit the scope of the device discovery.
+
+ \since 5.8
+*/
+
+/*!
Starts Bluetooth device discovery, if it is not already started.
The deviceDiscovered() signal is emitted as each device is discovered. The finished() signal
- is emitted once device discovery is complete.
+ is emitted once device discovery is complete. The discovery utilizes the maximum set of
+ supported discovery methods on the platform.
+
+ \sa supportedDiscoveryMethods()
*/
void QBluetoothDeviceDiscoveryAgent::start()
{
Q_D(QBluetoothDeviceDiscoveryAgent);
if (!isActive() && d->lastError != InvalidBluetoothAdapterError)
- d->start();
+ d->start(supportedDiscoveryMethods());
+}
+
+/*!
+ Start Bluetooth device discovery, if it is not already started and the provided
+ \a methods are supported.
+ The discovery \a methods limit the scope of the device search.
+ For example, if the target service or device is a Bluetooth Low Energy device,
+ this function could be used to limit the search to Bluetooth Low Energy devices and
+ thereby reduces the discovery time significantly.
+
+ \since 5.8
+*/
+void QBluetoothDeviceDiscoveryAgent::start(DiscoveryMethods methods)
+{
+ if (methods == NoMethod)
+ return;
+
+ DiscoveryMethods supported =
+ QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods();
+
+ Q_D(QBluetoothDeviceDiscoveryAgent);
+ if (!((supported & methods) == methods)) {
+ d->lastError = UnsupportedDiscoveryMethod;
+ d->errorString = QBluetoothDeviceDiscoveryAgent::tr("One or more device discovery methods "
+ "are not supported on this platform");
+ emit error(d->lastError);
+ return;
+ }
+
+ if (!isActive() && d->lastError != InvalidBluetoothAdapterError)
+ d->start(methods);
}
/*!
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent.h b/src/bluetooth/qbluetoothdevicediscoveryagent.h
index 954ae704..84087605 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent.h
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent.h
@@ -65,6 +65,7 @@ public:
PoweredOffError,
InvalidBluetoothAdapterError,
UnsupportedPlatformError,
+ UnsupportedDiscoveryMethod,
UnknownError = 100 // New errors must be added before Unknown error
};
Q_ENUM(Error)
@@ -75,6 +76,15 @@ public:
};
Q_ENUM(InquiryType)
+ enum DiscoveryMethod
+ {
+ NoMethod = 0x0,
+ ClassicMethod = 0x01,
+ LowEnergyMethod = 0x02,
+ };
+ Q_DECLARE_FLAGS(DiscoveryMethods, DiscoveryMethod)
+ Q_FLAG(DiscoveryMethods)
+
explicit QBluetoothDeviceDiscoveryAgent(QObject *parent = Q_NULLPTR);
explicit QBluetoothDeviceDiscoveryAgent(const QBluetoothAddress &deviceAdapter,
QObject *parent = Q_NULLPTR);
@@ -91,8 +101,13 @@ public:
QList<QBluetoothDeviceInfo> discoveredDevices() const;
+ void setLowEnergyDiscoveryTimeout(int msTimeout);
+ int lowEnergyDiscoveryTimeout() const;
+
+ static DiscoveryMethods supportedDiscoveryMethods();
public Q_SLOTS:
void start();
+ void start(DiscoveryMethods method);
void stop();
Q_SIGNALS:
@@ -116,6 +131,8 @@ private:
#endif
};
+Q_DECLARE_OPERATORS_FOR_FLAGS(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods)
+
QT_END_NAMESPACE
#endif
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp
index be9a4e31..411e7a2b 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp
@@ -66,6 +66,7 @@ QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(
leScanTimeout(0),
pendingCancel(false),
pendingStart(false),
+ lowEnergySearchTimeout(25000),
q_ptr(parent)
{
adapter = QAndroidJniObject::callStaticObjectMethod("android/bluetooth/BluetoothAdapter",
@@ -93,8 +94,16 @@ bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const
return m_active != NoScanActive;
}
-void QBluetoothDeviceDiscoveryAgentPrivate::start()
+QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods()
{
+ return (LowEnergyMethod | ClassicMethod);
+}
+
+void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)
+{
+ //TODO Implement discovery method handling (see input parameter)
+ requestedMethods = methods;
+
if (pendingCancel) {
pendingStart = true;
return;
@@ -128,6 +137,32 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start()
return;
}
+ // check Android v23+ permissions
+ // -> BTLE search requires android.permission.ACCESS_COARSE_LOCATION
+ if (requestedMethods && QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) {
+ QString permission(QLatin1String("android.permission.ACCESS_COARSE_LOCATION"));
+
+ // do we have required permission already, if so nothing to do
+ if (QtAndroidPrivate::checkPermission(permission) == QtAndroidPrivate::PermissionsResult::Denied) {
+ qCWarning(QT_BT_ANDROID) << "Requesting ACCESS_COARSE_LOCATION permission";
+
+ QAndroidJniEnvironment env;
+ const QHash<QString, QtAndroidPrivate::PermissionsResult> results =
+ QtAndroidPrivate::requestPermissionsSync(env, QStringList() << permission);
+ if (!results.contains(permission)
+ || results[permission] == QtAndroidPrivate::PermissionsResult::Denied)
+ {
+ qCWarning(QT_BT_ANDROID) << "Search not possible due to missing permission (ACCESS_COARSE_LOCATION)";
+ lastError = QBluetoothDeviceDiscoveryAgent::UnknownError;
+ errorString = QBluetoothDeviceDiscoveryAgent::tr("Missing Location permission. Search is not possible");
+ emit q->error(lastError);
+ return;
+ }
+ }
+
+ qCWarning(QT_BT_ANDROID) << "ACCESS_COARSE_LOCATION permission available";
+ }
+
// install Java BroadcastReceiver
if (!receiver) {
// SDP based device discovery
@@ -140,18 +175,35 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start()
discoveredDevices.clear();
- const bool success = adapter.callMethod<jboolean>("startDiscovery");
- if (!success) {
- lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError;
- errorString = QBluetoothDeviceDiscoveryAgent::tr("Discovery cannot be started");
- emit q->error(lastError);
- return;
- }
+ // by arbitrary definition we run classic search first
+ if (requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod) {
+ const bool success = adapter.callMethod<jboolean>("startDiscovery");
+ if (!success) {
+ lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError;
+ errorString = QBluetoothDeviceDiscoveryAgent::tr("Classic Discovery cannot be started");
+ emit q->error(lastError);
+ return;
+ }
- m_active = SDPScanActive;
+ m_active = SDPScanActive;
+ qCDebug(QT_BT_ANDROID)
+ << "QBluetoothDeviceDiscoveryAgentPrivate::start() - Classic search successfully started.";
+ } else {
+ // LE search only requested
+ Q_ASSERT(requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
- qCDebug(QT_BT_ANDROID)
- << "QBluetoothDeviceDiscoveryAgentPrivate::start() - successfully executed.";
+ if (QtAndroidPrivate::androidSdkVersion() < 18) {
+ qCDebug(QT_BT_ANDROID) << "Skipping Bluetooth Low Energy device scan due to"
+ "insufficient Android version.";
+ m_active = NoScanActive;
+ lastError = QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod;
+ errorString = QBluetoothDeviceDiscoveryAgent::tr("Low Energy Discovery not supported");
+ emit q->error(lastError);
+ return;
+ }
+
+ startLowEnergyScan();
+ }
}
void QBluetoothDeviceDiscoveryAgentPrivate::stop()
@@ -192,7 +244,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::processSdpDiscoveryFinished()
emit q->canceled();
} else if (pendingStart) {
pendingStart = pendingCancel = false;
- start();
+ start(requestedMethods);
} else {
// check that it didn't finish due to turned off Bluetooth Device
const int state = adapter.callMethod<jint>("getState");
@@ -204,6 +256,13 @@ void QBluetoothDeviceDiscoveryAgentPrivate::processSdpDiscoveryFinished()
return;
}
+ // no BTLE scan requested
+ if (!(requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)) {
+ m_active = NoScanActive;
+ emit q->finished();
+ return;
+ }
+
// start LE scan if supported
if (QtAndroidPrivate::androidSdkVersion() < 18) {
qCDebug(QT_BT_ANDROID) << "Skipping Bluetooth Low Energy device scan";
@@ -285,15 +344,21 @@ void QBluetoothDeviceDiscoveryAgentPrivate::startLowEnergyScan()
return;
}
+ // wait interval and sum up what was found
if (!leScanTimeout) {
leScanTimeout = new QTimer(this);
leScanTimeout->setSingleShot(true);
- leScanTimeout->setInterval(25000);
connect(leScanTimeout, &QTimer::timeout,
this, &QBluetoothDeviceDiscoveryAgentPrivate::stopLowEnergyScan);
}
- leScanTimeout->start();
+ if (lowEnergySearchTimeout > 0) { // otherwise no timeout and stop() required
+ leScanTimeout->setInterval(lowEnergySearchTimeout);
+ leScanTimeout->start();
+ }
+
+ qCDebug(QT_BT_ANDROID)
+ << "QBluetoothDeviceDiscoveryAgentPrivate::start() - Low Energy search successfully started.";
}
void QBluetoothDeviceDiscoveryAgentPrivate::stopLowEnergyScan()
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp
index 0243d31f..5288eaf8 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp
@@ -68,10 +68,12 @@ QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(
adapterBluez5(0),
discoveryTimer(0),
useExtendedDiscovery(false),
+ lowEnergySearchTimeout(-1), // remains -1 on BlueZ 4 -> timeout not supported
q_ptr(parent)
{
Q_Q(QBluetoothDeviceDiscoveryAgent);
if (isBluez5()) {
+ lowEnergySearchTimeout = 20000;
managerBluez5 = new OrgFreedesktopDBusObjectManagerInterface(
QStringLiteral("org.bluez"),
QStringLiteral("/"),
@@ -115,8 +117,17 @@ bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const
return (adapter || adapterBluez5);
}
-void QBluetoothDeviceDiscoveryAgentPrivate::start()
+QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods()
{
+ return (ClassicMethod | LowEnergyMethod);
+}
+
+void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods /*methods*/)
+{
+ // Currently both BlueZ backends do not distinguish discovery methods.
+ // The DBus API's always return both device types. Therefore we ignore
+ // the passed in methods.
+
if (pendingCancel == true) {
pendingStart = true;
return;
@@ -273,16 +284,18 @@ void QBluetoothDeviceDiscoveryAgentPrivate::startBluez5()
}
}
- // wait 20s and sum up what was found
+ // wait interval and sum up what was found
if (!discoveryTimer) {
discoveryTimer = new QTimer(q);
discoveryTimer->setSingleShot(true);
- discoveryTimer->setInterval(20000); // 20s
QObject::connect(discoveryTimer, SIGNAL(timeout()),
q, SLOT(_q_discoveryFinished()));
}
- discoveryTimer->start();
+ if (lowEnergySearchTimeout > 0) { // otherwise no timeout and stop() required
+ discoveryTimer->setInterval(lowEnergySearchTimeout);
+ discoveryTimer->start();
+ }
}
void QBluetoothDeviceDiscoveryAgentPrivate::stop()
@@ -436,7 +449,9 @@ void QBluetoothDeviceDiscoveryAgentPrivate::_q_propertyChanged(const QString &na
pendingStart = false;
pendingCancel = false;
- start();
+ // start parameter ignored since Bluez 4 doesn't distinguish them
+ start(QBluetoothDeviceDiscoveryAgent::ClassicMethod
+ | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
} else {
// happens when agent is created while other agent called StopDiscovery()
if (!adapter)
@@ -514,7 +529,8 @@ void QBluetoothDeviceDiscoveryAgentPrivate::_q_discoveryFinished()
} else if (pendingStart) {
pendingStart = false;
pendingCancel = false;
- start();
+ start(QBluetoothDeviceDiscoveryAgent::ClassicMethod
+ | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
} else {
emit q->finished();
}
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm b/src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm
index 0e4b460f..3f4c6755 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm
@@ -37,16 +37,17 @@
**
****************************************************************************/
-#include "qbluetoothdevicediscoverytimer_osx_p.h"
#include "qbluetoothdevicediscoveryagent.h"
#include "osx/osxbtledeviceinquiry_p.h"
#include "qbluetoothlocaldevice.h"
#include "qbluetoothdeviceinfo.h"
+#include "osx/osxbtnotifier_p.h"
#include "osx/osxbtutility_p.h"
#include "osx/uistrings_p.h"
#include "qbluetoothuuid.h"
#include <QtCore/qloggingcategory.h>
+#include <QtCore/qobject.h>
#include <QtCore/qglobal.h>
#include <QtCore/qstring.h>
#include <QtCore/qdebug.h>
@@ -56,12 +57,24 @@
QT_BEGIN_NAMESPACE
-using OSXBluetooth::ObjCScopedPointer;
+namespace
+{
+
+void registerQDeviceDiscoveryMetaType()
+{
+ static bool initDone = false;
+ if (!initDone) {
+ qRegisterMetaType<QBluetoothDeviceInfo>();
+ qRegisterMetaType<QBluetoothDeviceDiscoveryAgent::Error>();
+ initDone = true;
+ }
+}
+
+}//namespace
-class QBluetoothDeviceDiscoveryAgentPrivate
+class QBluetoothDeviceDiscoveryAgentPrivate : public QObject
{
friend class QBluetoothDeviceDiscoveryAgent;
- friend class OSXBluetooth::DDATimerHandler;
public:
QBluetoothDeviceDiscoveryAgentPrivate(const QBluetoothAddress &address,
@@ -70,17 +83,16 @@ public:
bool isActive() const;
- void start();
+ void start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods m);
void stop();
private:
- typedef QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) LEDeviceInquiryObjC;
+ using LEDeviceInquiryObjC = QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry);
void LEinquiryError(QBluetoothDeviceDiscoveryAgent::Error error);
void LEnotSupported();
void LEdeviceFound(const QBluetoothDeviceInfo &info);
void LEinquiryFinished();
- void checkLETimeout();
void setError(QBluetoothDeviceDiscoveryAgent::Error, const QString &text = QString());
@@ -91,63 +103,30 @@ private:
QBluetoothDeviceDiscoveryAgent::InquiryType inquiryType;
- typedef ObjCScopedPointer<LEDeviceInquiryObjC> LEDeviceInquiry;
+ using LEDeviceInquiry = OSXBluetooth::ObjCScopedPointer<LEDeviceInquiryObjC>;
LEDeviceInquiry inquiryLE;
- typedef QList<QBluetoothDeviceInfo> DevicesList;
+ using DevicesList = QList<QBluetoothDeviceInfo>;
DevicesList discoveredDevices;
bool startPending;
bool stopPending;
- QScopedPointer<OSXBluetooth::DDATimerHandler> timer;
+ int lowEnergySearchTimeout;
};
-namespace OSXBluetooth {
-
-DDATimerHandler::DDATimerHandler(QBluetoothDeviceDiscoveryAgentPrivate *d)
- : owner(d)
-{
- Q_ASSERT_X(owner, Q_FUNC_INFO, "invalid pointer");
-
- timer.setSingleShot(false);
- connect(&timer, &QTimer::timeout, this, &DDATimerHandler::onTimer);
-}
-
-void DDATimerHandler::start(int msec)
-{
- Q_ASSERT_X(msec > 0, Q_FUNC_INFO, "invalid time interval");
- if (timer.isActive()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "timer is active";
- return;
- }
-
- timer.start(msec);
-}
-
-void DDATimerHandler::stop()
-{
- timer.stop();
-}
-
-void DDATimerHandler::onTimer()
-{
- Q_ASSERT(owner);
- owner->checkLETimeout();
-}
-
-}
-
QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(const QBluetoothAddress &adapter,
QBluetoothDeviceDiscoveryAgent *q) :
q_ptr(q),
lastError(QBluetoothDeviceDiscoveryAgent::NoError),
inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry),
startPending(false),
- stopPending(false)
+ stopPending(false),
+ lowEnergySearchTimeout(OSXBluetooth::defaultLEScanTimeoutMS)
{
Q_UNUSED(adapter);
+ registerQDeviceDiscoveryMetaType();
Q_ASSERT_X(q != Q_NULLPTR, Q_FUNC_INFO, "invalid q_ptr (null)");
}
@@ -158,7 +137,7 @@ QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate()
if (dispatch_queue_t leQueue = OSXBluetooth::qt_LE_queue()) {
// Local variable to be retained ...
LEDeviceInquiryObjC *inq = inquiryLE.data();
- dispatch_async(leQueue, ^{
+ dispatch_sync(leQueue, ^{
[inq stop];
});
}
@@ -175,7 +154,7 @@ bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const
return inquiryLE;
}
-void QBluetoothDeviceDiscoveryAgentPrivate::start()
+void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods /*methods*/)
{
Q_ASSERT_X(!isActive(), Q_FUNC_INFO, "called on active device discovery agent");
@@ -186,26 +165,37 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start()
using namespace OSXBluetooth;
- inquiryLE.reset([[LEDeviceInquiryObjC alloc] init]);
+ QScopedPointer<LECBManagerNotifier> notifier(new LECBManagerNotifier);
+ // Connections:
+ using ErrMemFunPtr = void (LECBManagerNotifier::*)(QBluetoothDeviceDiscoveryAgent::Error);
+ notifier->connect(notifier.data(), ErrMemFunPtr(&LECBManagerNotifier::CBManagerError),
+ this, &QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError);
+ notifier->connect(notifier.data(), &LECBManagerNotifier::LEnotSupported,
+ this, &QBluetoothDeviceDiscoveryAgentPrivate::LEnotSupported);
+ notifier->connect(notifier.data(), &LECBManagerNotifier::discoveryFinished,
+ this, &QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished);
+ notifier->connect(notifier.data(), &LECBManagerNotifier::deviceDiscovered,
+ this, &QBluetoothDeviceDiscoveryAgentPrivate::LEdeviceFound);
+
+ inquiryLE.reset([[LEDeviceInquiryObjC alloc] initWithNotifier:notifier.data()]);
+ if (inquiryLE)
+ notifier.take(); // Whatever happens next, inquiryLE is already the owner ...
+
dispatch_queue_t leQueue(qt_LE_queue());
if (!leQueue || !inquiryLE) {
setError(QBluetoothDeviceDiscoveryAgent::UnknownError,
QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STARTED));
emit q_ptr->error(lastError);
+ return;
}
discoveredDevices.clear();
setError(QBluetoothDeviceDiscoveryAgent::NoError);
- // CoreBluetooth does not have a timeout. We start a timer here
- // and check if scan really started and if yes if we have a timeout.
- timer.reset(new OSXBluetooth::DDATimerHandler(this));
- timer->start(2000);
-
// Create a local variable - to have a strong referece in a block.
LEDeviceInquiryObjC *inq = inquiryLE.data();
dispatch_async(leQueue, ^{
- [inq start];
+ [inq startWithTimeout:lowEnergySearchTimeout];
});
}
@@ -223,7 +213,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::stop()
// Create a local variable - to have a strong referece in a block.
LEDeviceInquiryObjC *inq = inquiryLE.data();
- dispatch_async(leQueue, ^{
+ dispatch_sync(leQueue, ^{
[inq stop];
});
// We consider LE scan to be stopped immediately and
@@ -241,7 +231,6 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError(QBluetoothDeviceDisco
Q_FUNC_INFO, "unexpected error");
inquiryLE.reset();
- timer->stop();
startPending = false;
stopPending = false;
@@ -252,7 +241,6 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError(QBluetoothDeviceDisco
void QBluetoothDeviceDiscoveryAgentPrivate::LEnotSupported()
{
inquiryLE.reset();
- timer->stop();
startPending = false;
stopPending = false;
@@ -281,7 +269,6 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEdeviceFound(const QBluetoothDevice
void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished()
{
inquiryLE.reset();
- timer->stop();
if (stopPending && !startPending) {
stopPending = false;
@@ -289,47 +276,14 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished()
} else if (startPending) {
startPending = false;
stopPending = false;
- start();
+ // always the same method for start() on iOS
+ // classic search not supported
+ start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
} else {
emit q_ptr->finished();
}
}
-void QBluetoothDeviceDiscoveryAgentPrivate::checkLETimeout()
-{
- Q_ASSERT_X(inquiryLE, Q_FUNC_INFO, "LE device inquiry is nil");
-
- using namespace OSXBluetooth;
-
- const LEInquiryState state([inquiryLE inquiryState]);
- if (state == InquiryStarting || state == InquiryActive)
- return; // Wait ...
-
- if (state == ErrorPoweredOff)
- return LEinquiryError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
-
- if (state == ErrorLENotSupported)
- return LEnotSupported();
-
- if (state == InquiryFinished) {
- // Process found devices if any ...
- const QList<QBluetoothDeviceInfo> leDevices([inquiryLE discoveredDevices]);
- foreach (const QBluetoothDeviceInfo &info, leDevices) {
- // We were cancelled on a previous device discovered signal ...
- if (!inquiryLE)
- break;
- LEdeviceFound(info);
- }
-
- if (inquiryLE)
- LEinquiryFinished();
- return;
- }
-
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unexpected inquiry state in LE timeout";
- // Actually, this deserves an assert :)
-}
-
void QBluetoothDeviceDiscoveryAgentPrivate::setError(QBluetoothDeviceDiscoveryAgent::Error error,
const QString &text)
{
@@ -373,7 +327,7 @@ QBluetoothDeviceDiscoveryAgent::QBluetoothDeviceDiscoveryAgent(
d_ptr(new QBluetoothDeviceDiscoveryAgentPrivate(deviceAdapter, this))
{
if (!deviceAdapter.isNull()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "local device address is "
+ qCWarning(QT_BT_OSX) << "local device address is "
"not available, provided address is ignored";
d_ptr->setError(InvalidBluetoothAdapterError);
}
@@ -399,14 +353,39 @@ QList<QBluetoothDeviceInfo> QBluetoothDeviceDiscoveryAgent::discoveredDevices()
return d_ptr->discoveredDevices;
}
+QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods()
+{
+ return LowEnergyMethod;
+}
+
void QBluetoothDeviceDiscoveryAgent::start()
{
if (d_ptr->lastError != InvalidBluetoothAdapterError) {
if (!isActive())
- d_ptr->start();
+ d_ptr->start(supportedDiscoveryMethods());
else
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "already started";
+ qCDebug(QT_BT_OSX) << "already started";
+ }
+}
+
+void QBluetoothDeviceDiscoveryAgent::start(DiscoveryMethods methods)
+{
+ if (methods == NoMethod)
+ return;
+
+ DiscoveryMethods supported =
+ QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods();
+
+ if (!((supported & methods) == methods)) {
+ d_ptr->lastError = UnsupportedDiscoveryMethod;
+ d_ptr->errorString = QBluetoothDeviceDiscoveryAgent::tr("One or more device discovery methods "
+ "are not supported on this platform");
+ emit error(d_ptr->lastError);
+ return;
}
+
+ if (!isActive() && d_ptr->lastError != InvalidBluetoothAdapterError)
+ d_ptr->start(methods);
}
void QBluetoothDeviceDiscoveryAgent::stop()
@@ -430,4 +409,21 @@ QString QBluetoothDeviceDiscoveryAgent::errorString() const
return d_ptr->errorString;
}
+int QBluetoothDeviceDiscoveryAgent::lowEnergyDiscoveryTimeout() const
+{
+ return d_ptr->lowEnergySearchTimeout;
+}
+
+void QBluetoothDeviceDiscoveryAgent::setLowEnergyDiscoveryTimeout(int timeout)
+{
+ // cannot deliberately turn it off
+ if (timeout < 0) {
+ qCDebug(QT_BT_OSX) << "The Bluetooth Low Energy device discovery timeout cannot be negative.";
+ return;
+ }
+
+ d_ptr->lowEnergySearchTimeout = timeout;
+ return;
+}
+
QT_END_NAMESPACE
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm b/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm
index 63eab2b9..b308f7cc 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm
@@ -37,22 +37,23 @@
**
****************************************************************************/
-#include "qbluetoothdevicediscoverytimer_osx_p.h"
#include "qbluetoothdevicediscoveryagent.h"
#include "osx/osxbtledeviceinquiry_p.h"
#include "osx/osxbtdeviceinquiry_p.h"
#include "qbluetoothlocaldevice.h"
#include "osx/osxbtsdpinquiry_p.h"
#include "qbluetoothdeviceinfo.h"
+#include "osx/osxbtnotifier_p.h"
#include "osx/osxbtutility_p.h"
#include "osx/osxbluetooth_p.h"
#include "osx/uistrings_p.h"
#include "qbluetoothhostinfo.h"
+#include "qbluetoothaddress.h"
+#include "osx/uistrings_p.h"
#include "qbluetoothuuid.h"
#include <QtCore/qloggingcategory.h>
#include <QtCore/qscopedpointer.h>
-#include <QtCore/qdatetime.h>
#include <QtCore/qglobal.h>
#include <QtCore/qstring.h>
#include <QtCore/qdebug.h>
@@ -62,23 +63,40 @@
QT_BEGIN_NAMESPACE
-using OSXBluetooth::ObjCScopedPointer;
+namespace
+{
+
+void registerQDeviceDiscoveryMetaType()
+{
+ static bool initDone = false;
+ if (!initDone) {
+ qRegisterMetaType<QBluetoothDeviceInfo>();
+ qRegisterMetaType<QBluetoothDeviceDiscoveryAgent::Error>();
+ initDone = true;
+ }
+}
+
+}//namespace
-class QBluetoothDeviceDiscoveryAgentPrivate : public OSXBluetooth::DeviceInquiryDelegate
+class QBluetoothDeviceDiscoveryAgentPrivate : public QObject,
+ public OSXBluetooth::DeviceInquiryDelegate
{
friend class QBluetoothDeviceDiscoveryAgent;
- friend class OSXBluetooth::DDATimerHandler;
public:
- typedef QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) LEDeviceInquiryObjC;
+ template<class T>
+ using ObjCScopedPointer = OSXBluetooth::ObjCScopedPointer<T>;
+ using LEDeviceInquiryObjC = QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry);
QBluetoothDeviceDiscoveryAgentPrivate(const QBluetoothAddress & address,
QBluetoothDeviceDiscoveryAgent *q);
- virtual ~QBluetoothDeviceDiscoveryAgentPrivate();
+
+ ~QBluetoothDeviceDiscoveryAgentPrivate() Q_DECL_OVERRIDE;
bool isValid() const;
bool isActive() const;
- void start();
+ void start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods);
+ void startClassic();
void startLE();
void stop();
@@ -94,7 +112,6 @@ private:
void error(IOBluetoothDeviceInquiry *inq, IOReturn error) Q_DECL_OVERRIDE;
void deviceFound(IOBluetoothDeviceInquiry *inq, IOBluetoothDevice *device) Q_DECL_OVERRIDE;
- //
void LEinquiryFinished();
void LEinquiryError(QBluetoothDeviceDiscoveryAgent::Error error);
void LEnotSupported();
@@ -104,9 +121,8 @@ private:
void deviceFound(const QBluetoothDeviceInfo &newDeviceInfo);
void setError(IOReturn error, const QString &text = QString());
- void setError(QBluetoothDeviceDiscoveryAgent::Error, const QString &text = QString());
-
- void checkLETimeout();
+ void setError(QBluetoothDeviceDiscoveryAgent::Error,
+ const QString &text = QString());
QBluetoothDeviceDiscoveryAgent *q_ptr;
AgentState agentState;
@@ -121,56 +137,22 @@ private:
QBluetoothDeviceDiscoveryAgent::InquiryType inquiryType;
- typedef ObjCScopedPointer<DeviceInquiryObjC> DeviceInquiry;
+ using DeviceInquiry = ObjCScopedPointer<DeviceInquiryObjC>;
DeviceInquiry inquiry;
- typedef ObjCScopedPointer<LEDeviceInquiryObjC> LEDeviceInquiry;
+ using LEDeviceInquiry = ObjCScopedPointer<LEDeviceInquiryObjC>;
LEDeviceInquiry inquiryLE;
- typedef ObjCScopedPointer<IOBluetoothHostController> HostController;
+ using HostController = ObjCScopedPointer<IOBluetoothHostController>;
HostController hostController;
- typedef QList<QBluetoothDeviceInfo> DevicesList;
+ using DevicesList = QList<QBluetoothDeviceInfo>;
DevicesList discoveredDevices;
- QScopedPointer<OSXBluetooth::DDATimerHandler> timer;
+ int lowEnergySearchTimeout;
+ QBluetoothDeviceDiscoveryAgent::DiscoveryMethods requestedMethods;
};
-namespace OSXBluetooth {
-
-DDATimerHandler::DDATimerHandler(QBluetoothDeviceDiscoveryAgentPrivate *d)
- : owner(d)
-{
- Q_ASSERT_X(owner, Q_FUNC_INFO, "invalid pointer");
-
- timer.setSingleShot(false);
- connect(&timer, &QTimer::timeout, this, &DDATimerHandler::onTimer);
-}
-
-void DDATimerHandler::start(int msec)
-{
- Q_ASSERT_X(msec > 0, Q_FUNC_INFO, "invalid time interval");
- if (timer.isActive()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "timer is active";
- return;
- }
-
- timer.start(msec);
-}
-
-void DDATimerHandler::stop()
-{
- timer.stop();
-}
-
-void DDATimerHandler::onTimer()
-{
- Q_ASSERT(owner);
- owner->checkLETimeout();
-}
-
-}
-
QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(const QBluetoothAddress &adapter,
QBluetoothDeviceDiscoveryAgent *q) :
q_ptr(q),
@@ -179,26 +161,22 @@ QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(con
startPending(false),
stopPending(false),
lastError(QBluetoothDeviceDiscoveryAgent::NoError),
- inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry)
+ inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry),
+ lowEnergySearchTimeout(OSXBluetooth::defaultLEScanTimeoutMS),
+ requestedMethods(QBluetoothDeviceDiscoveryAgent::ClassicMethod
+ | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)
{
+ registerQDeviceDiscoveryMetaType();
+
Q_ASSERT_X(q != Q_NULLPTR, Q_FUNC_INFO, "invalid q_ptr (null)");
HostController controller([[IOBluetoothHostController defaultController] retain]);
if (!controller || [controller powerState] != kBluetoothHCIPowerStateON) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "no default host "
- "controller or adapter is off";
- return;
- }
-
- DeviceInquiry newInquiry([[DeviceInquiryObjC alloc]initWithDelegate:this]);
- if (!newInquiry) { // Obj-C's way of "reporting errors":
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to "
- "initialize an inquiry";
+ qCCritical(QT_BT_OSX) << "no default host controller or adapter is off";
return;
}
hostController.reset(controller.take());
- inquiry.reset(newInquiry.take());
}
QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate()
@@ -208,7 +186,7 @@ QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate()
if (dispatch_queue_t leQueue = OSXBluetooth::qt_LE_queue()) {
// Local variable to be retained ...
LEDeviceInquiryObjC *inq = inquiryLE.data();
- dispatch_async(leQueue, ^{
+ dispatch_sync(leQueue, ^{
[inq stop];
});
}
@@ -217,12 +195,7 @@ QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate()
bool QBluetoothDeviceDiscoveryAgentPrivate::isValid() const
{
- // isValid() - Qt does not use exceptions, but the ctor
- // can fail to initialize some important data-members
- // (and the error is probably not even related to Bluetooth at all)
- // - say, allocation error - this is what meant here by valid/invalid.
- return hostController && [hostController powerState] == kBluetoothHCIPowerStateON
- && inquiry;
+ return hostController && [hostController powerState] == kBluetoothHCIPowerStateON;
}
bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const
@@ -236,23 +209,56 @@ bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const
return agentState != NonActive;
}
-void QBluetoothDeviceDiscoveryAgentPrivate::start()
+void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)
{
- Q_ASSERT_X(isValid(), Q_FUNC_INFO, "called on invalid device discovery agent");
- Q_ASSERT_X(!isActive(), Q_FUNC_INFO, "called on active device discovery agent");
- Q_ASSERT_X(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError,
- Q_FUNC_INFO, "called with an invalid Bluetooth adapter");
+ Q_ASSERT(isValid());
+ Q_ASSERT(!isActive());
+ Q_ASSERT(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError);
+ Q_ASSERT(methods & (QBluetoothDeviceDiscoveryAgent::ClassicMethod
+ | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod));
+
+ requestedMethods = methods;
if (stopPending) {
startPending = true;
return;
}
- agentState = ClassicScan;
+ // This function (re)starts the scan(s) from the scratch;
+ // starting from Classic if it's in 'methods' (or LE scan if not).
+ agentState = NonActive;
discoveredDevices.clear();
setError(QBluetoothDeviceDiscoveryAgent::NoError);
+ if (requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod)
+ return startClassic();
+
+ startLE();
+}
+
+void QBluetoothDeviceDiscoveryAgentPrivate::startClassic()
+{
+ Q_ASSERT(isValid());
+ Q_ASSERT(!isActive());
+ Q_ASSERT(lastError == QBluetoothDeviceDiscoveryAgent::NoError);
+ Q_ASSERT(requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod);
+ Q_ASSERT(agentState == NonActive);
+
+ if (!inquiry) {
+ // The first Classic scan for this DDA.
+ inquiry.reset([[DeviceInquiryObjC alloc]initWithDelegate:this]);
+ if (!inquiry) {
+ qCCritical(QT_BT_OSX) << "failed to initialize an Classic device inquiry";
+ setError(QBluetoothDeviceDiscoveryAgent::UnknownError,
+ QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STARTED));
+ emit q_ptr->error(lastError);
+ return;
+ }
+ }
+
+ agentState = ClassicScan;
+
const IOReturn res = [inquiry start];
if (res != kIOReturnSuccess) {
setError(res, QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STARTED));
@@ -263,13 +269,29 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start()
void QBluetoothDeviceDiscoveryAgentPrivate::startLE()
{
- Q_ASSERT_X(isValid(), Q_FUNC_INFO, "called on invalid device discovery agent");
- Q_ASSERT_X(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError,
- Q_FUNC_INFO, "called with an invalid Bluetooth adapter");
+ Q_ASSERT(isValid());
+ Q_ASSERT(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError);
+ Q_ASSERT(requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
using namespace OSXBluetooth;
- inquiryLE.reset([[LEDeviceInquiryObjC alloc] init]);
+ QScopedPointer<LECBManagerNotifier> notifier(new LECBManagerNotifier);
+ // Connections:
+ using ErrMemFunPtr = void (LECBManagerNotifier::*)(QBluetoothDeviceDiscoveryAgent::Error);
+ notifier->connect(notifier.data(), ErrMemFunPtr(&LECBManagerNotifier::CBManagerError),
+ this, &QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError);
+ notifier->connect(notifier.data(), &LECBManagerNotifier::LEnotSupported,
+ this, &QBluetoothDeviceDiscoveryAgentPrivate::LEnotSupported);
+ notifier->connect(notifier.data(), &LECBManagerNotifier::discoveryFinished,
+ this, &QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished);
+ using DeviceMemFunPtr = void (QBluetoothDeviceDiscoveryAgentPrivate::*)(const QBluetoothDeviceInfo &);
+ notifier->connect(notifier.data(), &LECBManagerNotifier::deviceDiscovered,
+ this, DeviceMemFunPtr(&QBluetoothDeviceDiscoveryAgentPrivate::deviceFound));
+
+ // Check queue and create scanner:
+ inquiryLE.reset([[LEDeviceInquiryObjC alloc] initWithNotifier:notifier.data()]);
+ if (inquiryLE)
+ notifier.take(); // Whatever happens next, inquiryLE is already the owner ...
dispatch_queue_t leQueue(qt_LE_queue());
if (!leQueue || !inquiryLE) {
@@ -277,18 +299,15 @@ void QBluetoothDeviceDiscoveryAgentPrivate::startLE()
QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STARTED_LE));
agentState = NonActive;
emit q_ptr->error(lastError);
+ return;
}
+ // Now start in on LE queue:
agentState = LEScan;
- // CoreBluetooth does not have a timeout. We start a timer here
- // and check if scan is active/finished/finished with error(s).
- timer.reset(new OSXBluetooth::DDATimerHandler(this));
- timer->start(2000);
-
// We need the local variable so that it's retained ...
LEDeviceInquiryObjC *inq = inquiryLE.data();
dispatch_async(leQueue, ^{
- [inq start];
+ [inq startWithTimeout:lowEnergySearchTimeout];
});
}
@@ -310,7 +329,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::stop()
if (agentState == ClassicScan) {
const IOReturn res = [inquiry stop];
if (res != kIOReturnSuccess) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to stop";
+ qCWarning(QT_BT_OSX) << "failed to stop";
startPending = prevStart;
stopPending = false;
setError(res, QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STOPPED));
@@ -321,7 +340,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::stop()
Q_ASSERT(leQueue);
// We need the local variable so that it's retained ...
LEDeviceInquiryObjC *inq = inquiryLE.data();
- dispatch_async(leQueue, ^{
+ dispatch_sync(leQueue, ^{
[inq stop];
});
// We consider LE scan to be stopped immediately and
@@ -346,13 +365,17 @@ void QBluetoothDeviceDiscoveryAgentPrivate::inquiryFinished(IOBluetoothDeviceInq
} else if (startPending) {
startPending = false;
stopPending = false;
- start();
+ start(requestedMethods);
} else {
// We can be here _only_ if a classic scan
- // finished in a normal way (not cancelled).
+ // finished in a normal way (not cancelled)
+ // and requestedMethods includes LowEnergyMethod.
// startLE() will take care of old devices
// not supporting Bluetooth 4.0.
- startLE();
+ if (requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)
+ startLE();
+ else
+ emit q_ptr->finished();
}
}
@@ -384,7 +407,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::deviceFound(IOBluetoothDeviceInquiry
// Let's collect some info about this device:
const QBluetoothAddress deviceAddress(OSXBluetooth::qt_address([device getAddress]));
if (deviceAddress.isNull()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid Bluetooth address";
+ qCWarning(QT_BT_OSX) << "invalid Bluetooth address";
return;
}
@@ -414,8 +437,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::setError(IOReturn error, const QStri
setError(QBluetoothDeviceDiscoveryAgent::UnknownError, text);
}
-void QBluetoothDeviceDiscoveryAgentPrivate::setError(QBluetoothDeviceDiscoveryAgent::Error error,
- const QString &text)
+void QBluetoothDeviceDiscoveryAgentPrivate::setError(QBluetoothDeviceDiscoveryAgent::Error error, const QString &text)
{
lastError = error;
@@ -442,53 +464,18 @@ void QBluetoothDeviceDiscoveryAgentPrivate::setError(QBluetoothDeviceDiscoveryAg
}
if (lastError != QBluetoothDeviceDiscoveryAgent::NoError)
- qCDebug(QT_BT_OSX) << "error set: "<<errorString;
-}
-
-void QBluetoothDeviceDiscoveryAgentPrivate::checkLETimeout()
-{
- Q_ASSERT_X(agentState == LEScan, Q_FUNC_INFO, "invalid agent state");
- Q_ASSERT_X(inquiryLE, Q_FUNC_INFO, "LE device inquiry is nil");
-
- using namespace OSXBluetooth;
-
- const LEInquiryState state([inquiryLE inquiryState]);
- if (state == InquiryStarting || state == InquiryActive)
- return; // Wait ...
-
- if (state == ErrorPoweredOff)
- return LEinquiryError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
-
- if (state == ErrorLENotSupported)
- return LEnotSupported();
-
- if (state == InquiryFinished) {
- // Process found devices if any ...
- const QList<QBluetoothDeviceInfo> leDevices([inquiryLE discoveredDevices]);
- foreach (const QBluetoothDeviceInfo &info, leDevices) {
- // We were cancelled on a previous device discovered signal ...
- if (agentState != LEScan)
- break;
- deviceFound(info);
- }
-
- if (agentState == LEScan)
- LEinquiryFinished();
- return;
- }
-
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unexpected inquiry state in LE timeout";
- // Actually, this deserves an assert :)
+ qCDebug(QT_BT_OSX) << "error set:"<<errorString;
}
void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError(QBluetoothDeviceDiscoveryAgent::Error error)
{
- // At the moment the only error reported can be 'powered off' error, it happens
- // after the LE scan started (so we have LE support and this is a real PoweredOffError).
- Q_ASSERT(error == QBluetoothDeviceDiscoveryAgent::PoweredOffError);
+ Q_ASSERT(error == QBluetoothDeviceDiscoveryAgent::PoweredOffError
+ || error == QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod);
- timer->stop();
inquiryLE.reset();
+
+ startPending = false;
+ stopPending = false;
agentState = NonActive;
setError(error);
emit q_ptr->error(lastError);
@@ -496,9 +483,18 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError(QBluetoothDeviceDisco
void QBluetoothDeviceDiscoveryAgentPrivate::LEnotSupported()
{
- // Not supported is not an error (we still have 'Classic').
qCDebug(QT_BT_OSX) << "no Bluetooth LE support";
- LEinquiryFinished();
+
+ if (requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod) {
+ // Having both Classic | LE means this is not an error.
+ LEinquiryFinished();
+ } else {
+ // In the past this was never an error, that's why we have
+ // LEnotSupported as a special method. But now, since
+ // we can have separate Classic/LE scans, we have to report it
+ // as UnsupportedDiscoveryMethod.
+ LEinquiryError(QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod);
+ }
}
void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished()
@@ -506,7 +502,6 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished()
// The same logic as in inquiryFinished, but does not start LE scan.
agentState = NonActive;
inquiryLE.reset();
- timer->stop();
if (stopPending && !startPending) {
stopPending = false;
@@ -514,7 +509,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished()
} else if (startPending) {
startPending = false;
stopPending = false;
- start(); //Start from a classic scan again.
+ start(requestedMethods); //Start again.
} else {
emit q_ptr->finished();
}
@@ -574,7 +569,7 @@ QBluetoothDeviceDiscoveryAgent::InquiryType QBluetoothDeviceDiscoveryAgent::inqu
return d_ptr->inquiryType;
}
-void QBluetoothDeviceDiscoveryAgent::setInquiryType(QBluetoothDeviceDiscoveryAgent::InquiryType type)
+void QBluetoothDeviceDiscoveryAgent::setInquiryType(InquiryType type)
{
d_ptr->inquiryType = type;
}
@@ -584,12 +579,33 @@ QList<QBluetoothDeviceInfo> QBluetoothDeviceDiscoveryAgent::discoveredDevices()
return d_ptr->discoveredDevices;
}
+QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods()
+{
+ return ClassicMethod | LowEnergyMethod;
+}
+
void QBluetoothDeviceDiscoveryAgent::start()
{
+ start(supportedDiscoveryMethods());
+}
+
+void QBluetoothDeviceDiscoveryAgent::start(DiscoveryMethods methods)
+{
+ if (methods == NoMethod)
+ return;
+
+ if ((supportedDiscoveryMethods() & methods) != methods) {
+ d_ptr->lastError = UnsupportedDiscoveryMethod;
+ d_ptr->errorString = tr("One or more device discovery methods "
+ "are not supported on this platform");
+ emit error(d_ptr->lastError);
+ return;
+ }
+
if (d_ptr->lastError != InvalidBluetoothAdapterError) {
if (d_ptr->isValid()) {
if (!isActive())
- d_ptr->start();
+ d_ptr->start(methods);
} else {
// We previously failed to initialize d_ptr correctly:
// either some memory allocation problem or
@@ -626,4 +642,21 @@ QString QBluetoothDeviceDiscoveryAgent::errorString() const
return d_ptr->errorString;
}
+void QBluetoothDeviceDiscoveryAgent::setLowEnergyDiscoveryTimeout(int timeout)
+{
+ // cannot deliberately turn it off
+ if (timeout < 0) {
+ qCDebug(QT_BT_OSX) << "The Bluetooth Low Energy device discovery timeout cannot be negative.";
+ return;
+ }
+
+ d_ptr->lowEnergySearchTimeout = timeout;
+ return;
+}
+
+int QBluetoothDeviceDiscoveryAgent::lowEnergyDiscoveryTimeout() const
+{
+ return d_ptr->lowEnergySearchTimeout;
+}
+
QT_END_NAMESPACE
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_p.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_p.cpp
index 09076d6d..e3646db9 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent_p.cpp
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_p.cpp
@@ -53,6 +53,7 @@ QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(
QBluetoothDeviceDiscoveryAgent *parent)
: inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry),
lastError(QBluetoothDeviceDiscoveryAgent::NoError),
+ lowEnergySearchTimeout(-1),
q_ptr(parent)
{
Q_UNUSED(deviceAdapter);
@@ -68,7 +69,12 @@ bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const
return false;
}
-void QBluetoothDeviceDiscoveryAgentPrivate::start()
+QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods()
+{
+ return QBluetoothDeviceDiscoveryAgent::NoMethod;
+}
+
+void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods)
{
Q_Q(QBluetoothDeviceDiscoveryAgent);
lastError = QBluetoothDeviceDiscoveryAgent::UnsupportedPlatformError;
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_p.h b/src/bluetooth/qbluetoothdevicediscoveryagent_p.h
index f49ff8b7..de8006be 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent_p.h
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_p.h
@@ -78,10 +78,14 @@ class QDBusVariant;
QT_END_NAMESPACE
#endif
+#ifdef QT_WINRT_BLUETOOTH
+class QWinRTBluetoothDeviceDiscoveryWorker;
+#endif
+
QT_BEGIN_NAMESPACE
class QBluetoothDeviceDiscoveryAgentPrivate
-#if defined(QT_ANDROID_BLUETOOTH)
+#if defined(QT_ANDROID_BLUETOOTH) || defined(QT_WINRT_BLUETOOTH)
: public QObject
{
Q_OBJECT
@@ -95,7 +99,7 @@ public:
QBluetoothDeviceDiscoveryAgent *parent);
~QBluetoothDeviceDiscoveryAgentPrivate();
- void start();
+ void start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods);
void stop();
bool isActive() const;
@@ -155,6 +159,20 @@ private:
QTimer extendedDiscoveryTimer;
#endif
+#ifdef QT_WINRT_BLUETOOTH
+private slots:
+ void registerDevice(const QBluetoothDeviceInfo &info);
+ void onScanFinished();
+ void onScanCanceled();
+
+private:
+ void disconnectAndClearWorker();
+ QPointer<QWinRTBluetoothDeviceDiscoveryWorker> worker;
+ QTimer *leScanTimer;
+#endif
+
+ int lowEnergySearchTimeout;
+ QBluetoothDeviceDiscoveryAgent::DiscoveryMethods requestedMethods;
QBluetoothDeviceDiscoveryAgent *q_ptr;
};
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp
new file mode 100644
index 00000000..2b44c7c8
--- /dev/null
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp
@@ -0,0 +1,545 @@
+/****************************************************************************
+**
+** 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 <QtCore/QLoggingCategory>
+#include <QtCore/private/qeventdispatcher_winrt_p.h>
+
+#include <wrl.h>
+#include <windows.devices.enumeration.h>
+#include <windows.devices.bluetooth.h>
+#include <windows.foundation.collections.h>
+
+using namespace Microsoft::WRL;
+using namespace Microsoft::WRL::Wrappers;
+using namespace ABI::Windows::Foundation;
+using namespace ABI::Windows::Foundation::Collections;
+using namespace ABI::Windows::Devices;
+using namespace ABI::Windows::Devices::Bluetooth;
+using namespace ABI::Windows::Devices::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<DeviceInformationCollection *> *op,
+ QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode);
+ void gatherDeviceInformation(IDeviceInformation *deviceInfo,
+ QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode);
+ void gatherMultipleDeviceInformation(IVectorView<DeviceInformation *> *devices,
+ QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode);
+ void setupLEDeviceWatcher();
+ void bluetoothInfoFromDeviceIdAsync(HSTRING deviceId);
+ void bluetoothInfoFromLeDeviceIdAsync(HSTRING deviceId);
+ HRESULT onClassicBluetoothDeviceFoundAsync(IAsyncOperation<BluetoothDevice *> *, AsyncStatus);
+ HRESULT onBluetoothLEDeviceFoundAsync(IAsyncOperation<BluetoothLEDevice *> *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<IDeviceWatcher> m_leDeviceWatcher;
+ EventRegistrationToken m_leDeviceAddedToken;
+ int m_pendingDevices;
+};
+
+QWinRTBluetoothDeviceDiscoveryWorker::QWinRTBluetoothDeviceDiscoveryWorker(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)
+ : requestedModes(methods)
+ , m_pendingDevices(0)
+{
+ qRegisterMetaType<QBluetoothDeviceInfo>();
+}
+
+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<IDeviceInformationStatics> deviceInformationStatics;
+ HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Enumeration_DeviceInformation).Get(), &deviceInformationStatics);
+ WARN_AND_RETURN_IF_FAILED("Could not obtain device information statics", return);
+ if (mode == QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) {
+ ComPtr<IBluetoothLEDeviceStatics> 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<IBluetoothDeviceStatics> 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<IAsyncOperation<DeviceInformationCollection *>> 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<IAsyncOperationCompletedHandler<DeviceInformationCollection *>>([this, mode](IAsyncOperation<DeviceInformationCollection *> *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<DeviceInformationCollection *> *op, QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode)
+{
+ qCDebug(QT_BT_WINRT) << (mode == QBluetoothDeviceDiscoveryAgent::ClassicMethod ? "BT" : "BTLE")
+ << " scan completed";
+ ComPtr<IVectorView<DeviceInformation *>> devices;
+ HRESULT hr;
+ hr = op->GetResults(&devices);
+ Q_ASSERT_SUCCEEDED(hr);
+ 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<DeviceInformation *> *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<IDeviceInformation> device;
+ hr = devices->GetAt(i, &device);
+ Q_ASSERT_SUCCEEDED(hr);
+ gatherDeviceInformation(device.Get(), mode);
+ }
+}
+
+void QWinRTBluetoothDeviceDiscoveryWorker::setupLEDeviceWatcher()
+{
+ HString deviceSelector;
+ ComPtr<IDeviceInformationStatics> deviceInformationStatics;
+ HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Enumeration_DeviceInformation).Get(), &deviceInformationStatics);
+ WARN_AND_RETURN_IF_FAILED("Could not obtain device information statics", return);
+ ComPtr<IBluetoothLEDeviceStatics> 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<ITypedEventHandler<DeviceWatcher *, DeviceInformation *>>([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<IBluetoothDeviceStatics> deviceStatics;
+ HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothDevice).Get(), &deviceStatics);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IAsyncOperation<BluetoothDevice *>> 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<IAsyncOperationCompletedHandler<BluetoothDevice *>>
+ (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<IBluetoothLEDeviceStatics> deviceStatics;
+ HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothLEDevice).Get(), &deviceStatics);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IAsyncOperation<BluetoothLEDevice *>> 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<IAsyncOperationCompletedHandler<BluetoothLEDevice *>>
+ (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<BluetoothDevice *> *op, AsyncStatus status)
+{
+ if (status != AsyncStatus::Completed) {
+ decreaseAndCheckPendingDevices();
+ return S_OK;
+ }
+
+ ComPtr<IBluetoothDevice> device;
+ HRESULT hr = op->GetResults(&device);
+ Q_ASSERT_SUCCEEDED(hr);
+
+ if (!device) {
+ decreaseAndCheckPendingDevices();
+ return S_OK;
+ }
+ UINT64 address;
+ HString name;
+ ComPtr<IBluetoothClassOfDevice> classOfDevice;
+ UINT32 classOfDeviceInt;
+ hr = device->get_BluetoothAddress(&address);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = device->get_Name(name.GetAddressOf());
+ Q_ASSERT_SUCCEEDED(hr);
+ const QString btName = QString::fromWCharArray(WindowsGetStringRawBuffer(name.Get(), nullptr));
+ hr = device->get_ClassOfDevice(&classOfDevice);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = classOfDevice->get_RawValue(&classOfDeviceInt);
+ Q_ASSERT_SUCCEEDED(hr);
+ IVectorView <Rfcomm::RfcommDeviceService *> *deviceServices;
+ hr = device->get_RfcommServices(&deviceServices);
+ Q_ASSERT_SUCCEEDED(hr);
+ uint serviceCount;
+ hr = deviceServices->get_Size(&serviceCount);
+ Q_ASSERT_SUCCEEDED(hr);
+ QList<QBluetoothUuid> uuids;
+ for (uint i = 0; i < serviceCount; ++i) {
+ ComPtr<Rfcomm::IRfcommDeviceService> service;
+ hr = deviceServices->GetAt(i, &service);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<Rfcomm::IRfcommServiceId> id;
+ hr = service->get_ServiceId(&id);
+ Q_ASSERT_SUCCEEDED(hr);
+ GUID uuid;
+ hr = id->get_Uuid(&uuid);
+ Q_ASSERT_SUCCEEDED(hr);
+ uuids.append(QBluetoothUuid(uuid));
+ }
+
+ qCDebug(QT_BT_WINRT) << "Discovered BT device: " << QString::number(address) << btName
+ << "Num UUIDs" << uuids.count();
+
+ QBluetoothDeviceInfo info(QBluetoothAddress(address), btName, classOfDeviceInt);
+ info.setCoreConfigurations(QBluetoothDeviceInfo::BaseRateCoreConfiguration);
+ info.setServiceUuids(uuids, QBluetoothDeviceInfo::DataIncomplete);
+ info.setCached(true);
+
+ QMetaObject::invokeMethod(this, "deviceFound", Qt::AutoConnection,
+ Q_ARG(QBluetoothDeviceInfo, info));
+ decreaseAndCheckPendingDevices();
+ return S_OK;
+}
+
+HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothLEDeviceFoundAsync(IAsyncOperation<BluetoothLEDevice *> *op, AsyncStatus status)
+{
+ if (status != AsyncStatus::Completed) {
+ decreaseAndCheckPendingDevices();
+ return S_OK;
+ }
+
+ ComPtr<IBluetoothLEDevice> 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 <GenericAttributeProfile::GattDeviceService *> *deviceServices;
+ hr = device->get_GattServices(&deviceServices);
+ Q_ASSERT_SUCCEEDED(hr);
+ uint serviceCount;
+ hr = deviceServices->get_Size(&serviceCount);
+ Q_ASSERT_SUCCEEDED(hr);
+ QList<QBluetoothUuid> uuids;
+ for (uint i = 0; i < serviceCount; ++i) {
+ ComPtr<GenericAttributeProfile::IGattDeviceService> service;
+ hr = deviceServices->GetAt(i, &service);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<Rfcomm::IRfcommServiceId> id;
+ GUID uuid;
+ hr = service->get_Uuid(&uuid);
+ Q_ASSERT_SUCCEEDED(hr);
+ uuids.append(QBluetoothUuid(uuid));
+ }
+
+ 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<QBluetoothDeviceInfo>::iterator iter = discoveredDevices.begin();
+ iter != discoveredDevices.end(); ++iter) {
+ if (iter->address() == info.address()) {
+ qCDebug(QT_BT_WINRT) << "Updating device" << iter->name() << iter->address();
+ // merge service uuids
+ QList<QBluetoothUuid> uuids = iter->serviceUuids();
+ uuids.append(info.serviceUuids());
+ const QSet<QBluetoothUuid> uuidSet = uuids.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 <qbluetoothdevicediscoveryagent_winrt.moc>
diff --git a/src/bluetooth/qbluetoothdevicediscoverytimer_osx_p.h b/src/bluetooth/qbluetoothdevicediscoverytimer_osx_p.h
deleted file mode 100644
index 88906ffd..00000000
--- a/src/bluetooth/qbluetoothdevicediscoverytimer_osx_p.h
+++ /dev/null
@@ -1,85 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtBluetooth module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#ifndef QBLUETOOTHDEVICEDISCOVERYTIMER_OSX_P_H
-#define QBLUETOOTHDEVICEDISCOVERYTIMER_OSX_P_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists purely as an
-// implementation detail. This header file may change from version to
-// version without notice, or even be removed.
-//
-// We mean it.
-//
-
-#include <QtCore/qglobal.h>
-#include <QtCore/qtimer.h>
-
-QT_BEGIN_NAMESPACE
-
-class QBluetoothDeviceDiscoveryAgentPrivate;
-
-namespace OSXBluetooth {
-
-class DDATimerHandler : public QObject
-{
- Q_OBJECT
-
-public:
- DDATimerHandler(QBluetoothDeviceDiscoveryAgentPrivate *d);
-
- void start(int msec);
- void stop();
-
-private slots:
- void onTimer();
-
-private:
- QTimer timer;
- QBluetoothDeviceDiscoveryAgentPrivate *owner;
-};
-
-}
-
-QT_END_NAMESPACE
-
-#endif // QBLUETOOTHDEVICEDISCOVERYTIMER_OSX_P_H
diff --git a/src/bluetooth/qbluetoothlocaldevice_osx.mm b/src/bluetooth/qbluetoothlocaldevice_osx.mm
index c005e2ea..45fa310a 100644
--- a/src/bluetooth/qbluetoothlocaldevice_osx.mm
+++ b/src/bluetooth/qbluetoothlocaldevice_osx.mm
@@ -118,29 +118,25 @@ QBluetoothLocalDevicePrivate::QBluetoothLocalDevicePrivate(QBluetoothLocalDevice
HostController defaultController([[IOBluetoothHostController defaultController] retain]);
if (!defaultController) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to "
- "init a host controller object";
+ qCCritical(QT_BT_OSX) << "failed to init a host controller object";
return;
}
if (!address.isNull()) {
NSString *const hciAddress = [defaultController addressAsString];
if (!hciAddress) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to "
- "obtain an address";
+ qCCritical(QT_BT_OSX) << "failed to obtain an address";
return;
}
BluetoothDeviceAddress iobtAddress = {};
if (IOBluetoothNSStringToDeviceAddress(hciAddress, &iobtAddress) != kIOReturnSuccess) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "invalid "
- "local device's address";
+ qCCritical(QT_BT_OSX) << "invalid local device's address";
return;
}
if (address != OSXBluetooth::qt_address(&iobtAddress)) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "invalid "
- "local device's address";
+ qCCritical(QT_BT_OSX) << "invalid local device's address";
return;
}
}
@@ -183,8 +179,7 @@ void QBluetoothLocalDevicePrivate::requestPairing(const QBluetoothAddress &addre
if ([device isPaired]) {
emitPairingFinished(address, pairing, true);
} else if ([pos.value() start] != kIOReturnSuccess) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to "
- "start a new pairing request";
+ qCCritical(QT_BT_OSX) << "failed to start a new pairing request";
emitError(QBluetoothLocalDevice::PairingError, true);
}
return;
@@ -195,8 +190,7 @@ void QBluetoothLocalDevicePrivate::requestPairing(const QBluetoothAddress &addre
// it'll just finish with success (skipping any intermediate steps).
PairingRequest newRequest([[ObjCPairingRequest alloc] initWithTarget:address delegate:this], false);
if (!newRequest) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to "
- "allocate a new pairing request";
+ qCCritical(QT_BT_OSX) << "failed to allocate a new pairing request";
emitError(QBluetoothLocalDevice::PairingError, true);
return;
}
@@ -205,8 +199,7 @@ void QBluetoothLocalDevicePrivate::requestPairing(const QBluetoothAddress &addre
const IOReturn result = [newRequest start];
if (result != kIOReturnSuccess) {
pairingRequests.erase(pos);
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to "
- "start a new pairing request";
+ qCCritical(QT_BT_OSX) << "failed to start a new pairing request";
emitError(QBluetoothLocalDevice::PairingError, true);
}
}
diff --git a/src/bluetooth/qbluetoothserver_osx.mm b/src/bluetooth/qbluetoothserver_osx.mm
index 549a07cc..8896651d 100644
--- a/src/bluetooth/qbluetoothserver_osx.mm
+++ b/src/bluetooth/qbluetoothserver_osx.mm
@@ -95,7 +95,7 @@ QBluetoothServerPrivate::QBluetoothServerPrivate(QSInfo::Protocol type, QBluetoo
{
Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)");
if (serverType == QSInfo::UnknownProtocol)
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown protocol";
+ qCWarning(QT_BT_OSX) << "unknown protocol";
}
QBluetoothServerPrivate::~QBluetoothServerPrivate()
@@ -116,7 +116,7 @@ bool QBluetoothServerPrivate::startListener(quint16 realPort)
Q_ASSERT_X(realPort, Q_FUNC_INFO, "invalid port");
if (serverType == QSInfo::UnknownProtocol) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid protocol";
+ qCWarning(QT_BT_OSX) << "invalid protocol";
return false;
}
@@ -217,7 +217,7 @@ void QBluetoothServerPrivate::registerServer(QBluetoothServerPrivate *server, qu
Q_ASSERT_X(!psmIsBusy(port), Q_FUNC_INFO, "port is busy");
busyPSMs()[port] = server;
} else {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "can not register a server "
+ qCWarning(QT_BT_OSX) << "can not register a server "
"with unknown protocol type";
}
}
@@ -234,7 +234,7 @@ QBluetoothServerPrivate *QBluetoothServerPrivate::registeredServer(quint16 port,
if (it != busyPSMs().end())
return it.value();
} else {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid protocol";
+ qCWarning(QT_BT_OSX) << "invalid protocol";
}
return Q_NULLPTR;
@@ -251,17 +251,17 @@ void QBluetoothServerPrivate::unregisterServer(QBluetoothServerPrivate *server)
if (it != busyChannels().end()) {
busyChannels().erase(it);
} else {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "server is not registered";
+ qCWarning(QT_BT_OSX) << "server is not registered";
}
} else if (type == QSInfo::L2capProtocol) {
ServerMapIterator it = busyPSMs().find(port);
if (it != busyPSMs().end()) {
busyPSMs().erase(it);
} else {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "server is not registered";
+ qCWarning(QT_BT_OSX) << "server is not registered";
}
} else {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid protocol";
+ qCWarning(QT_BT_OSX) << "invalid protocol";
}
}
@@ -292,16 +292,15 @@ bool QBluetoothServer::listen(const QBluetoothAddress &address, quint16 port)
typedef QBluetoothServerPrivate::ObjCListener ObjCListener;
if (d_ptr->listener) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "already in listen mode, "
- "close server first";
+ qCWarning(QT_BT_OSX) << "already in listen mode, close server first";
return false;
}
const QBluetoothLocalDevice device(address);
if (!device.isValid()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "device does not support Bluetooth or "
+ qCWarning(QT_BT_OSX) << "device does not support Bluetooth or"
<< address.toString()
- << " is not a valid local adapter";
+ << "is not a valid local adapter";
d_ptr->lastError = UnknownError;
emit error(UnknownError);
return false;
@@ -309,7 +308,7 @@ bool QBluetoothServer::listen(const QBluetoothAddress &address, quint16 port)
const QBluetoothLocalDevice::HostMode hostMode = device.hostMode();
if (hostMode == QBluetoothLocalDevice::HostPoweredOff) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "Bluetooth device is powered off";
+ qCWarning(QT_BT_OSX) << "Bluetooth device is powered off";
d_ptr->lastError = PoweredOffError;
emit error(PoweredOffError);
return false;
@@ -318,7 +317,7 @@ bool QBluetoothServer::listen(const QBluetoothAddress &address, quint16 port)
const QSInfo::Protocol type = d_ptr->serverType;
if (type == QSInfo::UnknownProtocol) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid protocol";
+ qCWarning(QT_BT_OSX) << "invalid protocol";
d_ptr->lastError = UnsupportedProtocolError;
emit error(d_ptr->lastError);
return false;
@@ -332,14 +331,14 @@ bool QBluetoothServer::listen(const QBluetoothAddress &address, quint16 port)
if (port) {
if (type == QSInfo::RfcommProtocol) {
if (d_ptr->channelIsBusy(port)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO <<"server port: "
- << port << "already registered";
+ qCWarning(QT_BT_OSX) << "server port:" << port
+ << "already registered";
d_ptr->lastError = ServiceAlreadyRegisteredError;
}
} else {
if (d_ptr->psmIsBusy(port)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "server port: "
- << port << "already registered";
+ qCWarning(QT_BT_OSX) << "server port:" << port
+ << "already registered";
d_ptr->lastError = ServiceAlreadyRegisteredError;
}
}
@@ -354,7 +353,7 @@ bool QBluetoothServer::listen(const QBluetoothAddress &address, quint16 port)
}
if (!port) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "all ports are busy";
+ qCWarning(QT_BT_OSX) << "all ports are busy";
d_ptr->lastError = ServiceAlreadyRegisteredError;
emit error(d_ptr->lastError);
return false;
diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm b/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm
index 5c4efca2..4a52b379 100644
--- a/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm
+++ b/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm
@@ -335,7 +335,7 @@ void QBluetoothServiceDiscoveryAgentPrivate::SDPInquiryError(IOBluetoothDevice *
{
Q_UNUSED(device)
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "inquiry failed with IOKit code: " << int(errorCode);
+ qCWarning(QT_BT_OSX) << "inquiry failed with IOKit code:" << int(errorCode);
discoveredDevices.clear();
// TODO: find a better mapping from IOReturn to QBluetoothServiceDiscoveryAgent::Error.
diff --git a/src/bluetooth/qbluetoothserviceinfo_osx.mm b/src/bluetooth/qbluetoothserviceinfo_osx.mm
index d6aa7592..a25653ab 100644
--- a/src/bluetooth/qbluetoothserviceinfo_osx.mm
+++ b/src/bluetooth/qbluetoothserviceinfo_osx.mm
@@ -46,7 +46,6 @@
#include <QtCore/qloggingcategory.h>
#include <QtCore/qvariant.h>
-#include <QtCore/qsysinfo.h>
#include <QtCore/qglobal.h>
#include <QtCore/qmutex.h>
#include <QtCore/qmap.h>
@@ -56,44 +55,6 @@
QT_BEGIN_NAMESPACE
-namespace {
-
-// This is not in osxbtutility_p, since it's not required
-// in general and just fixes the problem with SDK < 10.9,
-// where we have to care about about IOBluetoothSDPServiceRecordRef.
-class ServiceRecordRefGuard
-{
-public:
- ServiceRecordRefGuard()
- : recordRef(Q_NULLPTR)
- {
- }
- explicit ServiceRecordRefGuard(IOBluetoothSDPServiceRecordRef r)
- : recordRef(r)
- {
- }
- ~ServiceRecordRefGuard()
- {
- if (recordRef) // Requires non-NULL pointers.
- CFRelease(recordRef);
- }
-
- void reset(IOBluetoothSDPServiceRecordRef r)
- {
- if (recordRef)
- CFRelease(recordRef);
- // Take the ownership:
- recordRef = r;
- }
-
-private:
- IOBluetoothSDPServiceRecordRef recordRef;
-
- Q_DISABLE_COPY(ServiceRecordRefGuard)
-};
-
-}
-
class QBluetoothServiceInfoPrivate
{
public:
@@ -144,48 +105,23 @@ bool QBluetoothServiceInfoPrivate::registerService(const QBluetoothAddress &loca
serviceDict(iobluetooth_service_dictionary(*q_ptr));
if (!serviceDict) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to create a service dictionary";
+ qCWarning(QT_BT_OSX) << "failed to create a service dictionary";
return false;
}
- ServiceRecordRefGuard refGuard;
SDPRecord newRecord;
-#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9)
- if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_9) {
- newRecord.reset([[IOBluetoothSDPServiceRecord
- publishedServiceRecordWithDictionary:serviceDict] retain]);
- } else {
-#else
- {
-#endif
- IOBluetoothSDPServiceRecordRef recordRef = Q_NULLPTR;
- // With ARC this will require a different cast?
- const IOReturn status = IOBluetoothAddServiceDict((CFDictionaryRef)serviceDict.data(), &recordRef);
- if (status != kIOReturnSuccess) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to register a service record";
- return false;
- }
-
- refGuard.reset(recordRef);
- newRecord.reset([[IOBluetoothSDPServiceRecord withSDPServiceRecordRef:recordRef] retain]);
- // It's weird, but ... it's not possible to release a record ref yet.
- }
+ newRecord.reset([[IOBluetoothSDPServiceRecord
+ publishedServiceRecordWithDictionary:serviceDict] retain]);
if (!newRecord) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to register a service record";
- // In case of SDK < 10.9 it's not possible to remove a service record ...
- // no way to obtain record handle yet.
+ qCWarning(QT_BT_OSX) << "failed to register a service record";
return false;
}
BluetoothSDPServiceRecordHandle newRecordHandle = 0;
if ([newRecord getServiceRecordHandle:&newRecordHandle] != kIOReturnSuccess) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to register a service record";
-#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9)
- if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_9)
- [newRecord removeServiceRecord];
-#endif
- // With SDK < 10.9 there is no way to unregister at this point ...
+ qCWarning(QT_BT_OSX) << "failed to register a service record";
+ [newRecord removeServiceRecord];
return false;
}
@@ -211,17 +147,8 @@ bool QBluetoothServiceInfoPrivate::registerService(const QBluetoothAddress &loca
}
if (!configured) {
-#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9)
- if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_9) {
- [newRecord removeServiceRecord];
- } else {
-#else
- {// Just to balance braces ...
-#endif
- IOBluetoothRemoveServiceWithRecordHandle(newRecordHandle);
- }
-
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to register a service record";
+ [newRecord removeServiceRecord];
+ qCWarning(QT_BT_OSX) << "failed to register a service record";
return false;
}
@@ -247,16 +174,7 @@ bool QBluetoothServiceInfoPrivate::unregisterService()
Q_ASSERT_X(serviceRecord, Q_FUNC_INFO, "service registered, but serviceRecord is nil");
-#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9)
- if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_9) {
- [serviceRecord removeServiceRecord];
- } else {
-#else
- {
-#endif
- // Assert on newRecordHandle? Is 0 a valid/invalid handle?
- IOBluetoothRemoveServiceWithRecordHandle(serviceRecordHandle);
- }
+ [serviceRecord removeServiceRecord];
serviceRecord.reset(nil);
diff --git a/src/bluetooth/qbluetoothsocket_osx.mm b/src/bluetooth/qbluetoothsocket_osx.mm
index 69663e58..75712868 100644
--- a/src/bluetooth/qbluetoothsocket_osx.mm
+++ b/src/bluetooth/qbluetoothsocket_osx.mm
@@ -452,7 +452,7 @@ void QBluetoothSocket::connectToService(const QBluetoothServiceInfo &service, Op
}
if (state() != UnconnectedState && state() != ServiceLookupState) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "called on a busy socket";
+ qCWarning(QT_BT_OSX) << "called on a busy socket";
d_ptr->errorString = QCoreApplication::translate(SOCKET, SOC_CONNECT_IN_PROGRESS);
setSocketError(OperationError);
return;
@@ -468,8 +468,8 @@ void QBluetoothSocket::connectToService(const QBluetoothServiceInfo &service, Op
} else {
// Try service discovery.
if (service.serviceUuid().isNull()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "No port, "
- "no PSM, and no UUID provided, unable to connect";
+ qCWarning(QT_BT_OSX) << "No port, no PSM, and no "
+ "UUID provided, unable to connect";
return;
}
@@ -489,7 +489,7 @@ void QBluetoothSocket::connectToService(const QBluetoothAddress &address, const
}
if (state() != QBluetoothSocket::UnconnectedState) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "called on a busy socket";
+ qCWarning(QT_BT_OSX) << "called on a busy socket";
d_ptr->errorString = QCoreApplication::translate(SOCKET, SOC_CONNECT_IN_PROGRESS);
setSocketError(QBluetoothSocket::OperationError);
return;
@@ -513,7 +513,7 @@ void QBluetoothSocket::connectToService(const QBluetoothAddress &address, quint1
}
if (state() != QBluetoothSocket::UnconnectedState) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "called on a busy socket";
+ qCWarning(QT_BT_OSX) << "called on a busy socket";
d_ptr->errorString = QCoreApplication::translate(SOCKET, SOC_CONNECT_IN_PROGRESS);
setSocketError(OperationError);
return;
@@ -554,7 +554,7 @@ void QBluetoothSocket::setSocketState(QBluetoothSocket::SocketState state)
// We can register for L2CAP/RFCOMM open notifications,
// that's different from 'listen' and is implemented
// in QBluetoothServer.
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "listening sockets are not supported";
+ qCWarning(QT_BT_OSX) << "listening sockets are not supported";
}
}
diff --git a/src/bluetooth/qbluetoothtransferreply_osx.mm b/src/bluetooth/qbluetoothtransferreply_osx.mm
index 99c6cab1..02133860 100644
--- a/src/bluetooth/qbluetoothtransferreply_osx.mm
+++ b/src/bluetooth/qbluetoothtransferreply_osx.mm
@@ -177,7 +177,7 @@ void QBluetoothTransferReplyOSXPrivate::sendConnect(const QBluetoothAddress &dev
errorString.clear();
if (device.isNull() || !channelID) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid device address or port";
+ qCWarning(QT_BT_OSX) << "invalid device address or port";
setReplyError(QBluetoothTransferReply::HostNotFoundError,
QCoreApplication::translate(TRANSFER_REPLY, TR_INVAL_TARGET));
return;
@@ -186,7 +186,7 @@ void QBluetoothTransferReplyOSXPrivate::sendConnect(const QBluetoothAddress &dev
OBEXSession newSession([[ObjCOBEXSession alloc] initWithDelegate:this
remoteDevice:device channelID:channelID]);
if (!newSession) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to allocate OSXBTOBEXSession object";
+ qCWarning(QT_BT_OSX) << "failed to allocate OSXBTOBEXSession object";
setReplyError(QBluetoothTransferReply::UnknownError,
QCoreApplication::translate(TRANSFER_REPLY, TR_SESSION_NO_START));
@@ -201,7 +201,7 @@ void QBluetoothTransferReplyOSXPrivate::sendConnect(const QBluetoothAddress &dev
if ([session isConnected])
sendPut();// Connected, send a PUT request.
} else {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "OBEXConnect failed";
+ qCWarning(QT_BT_OSX) << "OBEXConnect failed";
if (error == QBluetoothTransferReply::NoError) {
// The error is not set yet.
@@ -355,7 +355,7 @@ QBluetoothTransferReplyOSX::QBluetoothTransferReplyOSX(QIODevice *input,
if (input) {
QMetaObject::invokeMethod(this, "start", Qt::QueuedConnection);
} else {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid input stream (null)";
+ qCWarning(QT_BT_OSX) << "invalid input stream (null)";
osx_d_ptr->requestComplete = true;
osx_d_ptr->errorString = QCoreApplication::translate(TRANSFER_REPLY, TR_INVALID_DEVICE);
osx_d_ptr->error = FileNotFoundError;
@@ -416,7 +416,7 @@ bool QBluetoothTransferReplyOSX::start()
if (!osx_d_ptr->isActive()) {
// Step 0: find a channelID.
if (request().address().isNull()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid device address";
+ qCWarning(QT_BT_OSX) << "invalid device address";
osx_d_ptr->setReplyError(HostNotFoundError,
QCoreApplication::translate(TRANSFER_REPLY, TR_INVAL_TARGET));
return false;
@@ -455,7 +455,8 @@ void QBluetoothTransferReplyOSX::serviceDiscoveryFinished()
void QBluetoothTransferReplyOSX::serviceDiscoveryError(QBluetoothServiceDiscoveryAgent::Error errorCode)
{
- Q_ASSERT_X(osx_d_ptr->agent.data(), Q_FUNC_INFO, "invalid service discovery agent (null)");
+ Q_ASSERT_X(osx_d_ptr->agent.data(), Q_FUNC_INFO,
+ "invalid service discovery agent (null)");
if (errorCode == QBluetoothServiceDiscoveryAgent::PoweredOffError) {
// There's nothing else we can do.
diff --git a/src/bluetooth/qlowenergycontroller.cpp b/src/bluetooth/qlowenergycontroller.cpp
index c152ded2..2b28a873 100644
--- a/src/bluetooth/qlowenergycontroller.cpp
+++ b/src/bluetooth/qlowenergycontroller.cpp
@@ -198,13 +198,17 @@ Q_DECLARE_LOGGING_CATEGORY(QT_BT)
This signal is emitted when the controller successfully connects to the remote
Low Energy device (if the controller is in the \l CentralRole) or if a remote Low Energy
device connected to the controller (if the controller is in the \l PeripheralRole).
+ On iOS and OS X this signal is not reliable if the controller is in the \l PeripheralRole
+ - the controller only guesses that some central connected to our peripheral as
+ soon as this central tries to write/read a characteristic/descriptor.
*/
/*!
\fn void QLowEnergyController::disconnected()
This signal is emitted when the controller disconnects from the remote
- Low Energy device or vice versa.
+ Low Energy device or vice versa. On iOS and OS X this signal is unreliable
+ if the controller is in the \l PeripheralRole.
*/
/*!
@@ -308,6 +312,9 @@ void QLowEnergyControllerPrivate::setError(
bool QLowEnergyControllerPrivate::isValidLocalAdapter()
{
+#ifdef QT_WINRT_BLUETOOTH
+ return true;
+#endif
if (localAdapter.isNull())
return false;
@@ -687,6 +694,11 @@ void QLowEnergyController::connectToDevice()
{
Q_D(QLowEnergyController);
+ if (role() != CentralRole) {
+ qCWarning(QT_BT) << "Connection can only be established while in central role";
+ return;
+ }
+
if (!d->isValidLocalAdapter()) {
d->setError(QLowEnergyController::InvalidBluetoothAdapterError);
return;
diff --git a/src/bluetooth/qlowenergycontroller_osx.mm b/src/bluetooth/qlowenergycontroller_osx.mm
index 47f65965..6e85e630 100644
--- a/src/bluetooth/qlowenergycontroller_osx.mm
+++ b/src/bluetooth/qlowenergycontroller_osx.mm
@@ -38,11 +38,14 @@
**
****************************************************************************/
+#include "osx/osxbtnotifier_p.h"
#include "osx/osxbtutility_p.h"
#include "osx/uistrings_p.h"
+
#include "qlowenergyserviceprivate_p.h"
#include "qlowenergycontroller_osx_p.h"
+#include "qlowenergyservicedata.h"
#include "qbluetoothlocaldevice.h"
#include "qbluetoothdeviceinfo.h"
#include "qlowenergycontroller.h"
@@ -51,7 +54,6 @@
#include <QtCore/qloggingcategory.h>
#include <QtCore/qsharedpointer.h>
#include <QtCore/qbytearray.h>
-#include <QtCore/qsysinfo.h>
#include <QtCore/qglobal.h>
#include <QtCore/qstring.h>
#include <QtCore/qlist.h>
@@ -84,8 +86,7 @@ ServicePrivate qt_createLEService(QLowEnergyControllerPrivateOSX *controller, CB
CBUUID *const cbUuid = cbService.UUID;
if (!cbUuid) {
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "invalid service, "
- "UUID is nil";
+ qCDebug(QT_BT_OSX) << "invalid service, UUID is nil";
return ServicePrivate();
}
@@ -102,18 +103,12 @@ ServicePrivate qt_createLEService(QLowEnergyControllerPrivateOSX *controller, CB
// TODO: isPrimary is ... always 'NO' - to be investigated.
/*
- #if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_6_0)
- using OSXBluetooth::qt_OS_limit;
- if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_9, QSysInfo::MV_IOS_6_0)) {
- if (!cbService.isPrimary) {
- // Our guess included/not was probably wrong.
- newService->type &= ~QLowEnergyService::PrimaryService;
- newService->type |= QLowEnergyService::IncludedService;
- }
+ if (!cbService.isPrimary) {
+ // Our guess included/not was probably wrong.
+ newService->type &= ~QLowEnergyService::PrimaryService;
+ newService->type |= QLowEnergyService::IncludedService;
}
- #endif
*/
- // No such property before 10_9/6_0.
return newService;
}
@@ -136,36 +131,8 @@ UUIDList qt_servicesUuids(NSArray *services)
}
-QLowEnergyControllerPrivateOSX::QLowEnergyControllerPrivateOSX(QLowEnergyController *q)
- : q_ptr(q),
- lastError(QLowEnergyController::NoError),
- controllerState(QLowEnergyController::UnconnectedState),
- addressType(QLowEnergyController::PublicAddress)
-{
- registerQLowEnergyControllerMetaType();
-
- // This is the "wrong" constructor - no valid device UUID to connect later.
- Q_ASSERT_X(q, Q_FUNC_INFO, "invalid q_ptr (null)");
-
- using OSXBluetooth::LECentralNotifier;
-
- // We still create a manager, to simplify error handling later.
- QScopedPointer<LECentralNotifier> notifier(new LECentralNotifier);
- centralManager.reset([[ObjCCentralManager alloc] initWith:notifier.data()]);
- if (!centralManager) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO
- << "failed to initialize central manager";
- return;
- } else if (!connectSlots(notifier.data())) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO
- << "failed to connect to notifier's signals";
- }
-
- // Ownership was taken by central manager.
- notifier.take();
-}
-
-QLowEnergyControllerPrivateOSX::QLowEnergyControllerPrivateOSX(QLowEnergyController *q,
+QLowEnergyControllerPrivateOSX::QLowEnergyControllerPrivateOSX(QLowEnergyController::Role r,
+ QLowEnergyController *q,
const QBluetoothDeviceInfo &deviceInfo)
: q_ptr(q),
deviceUuid(deviceInfo.deviceUuid()),
@@ -178,44 +145,67 @@ QLowEnergyControllerPrivateOSX::QLowEnergyControllerPrivateOSX(QLowEnergyControl
Q_ASSERT_X(q, Q_FUNC_INFO, "invalid q_ptr (null)");
- using OSXBluetooth::LECentralNotifier;
+ using OSXBluetooth::LECBManagerNotifier;
+
+ role = r;
- QScopedPointer<LECentralNotifier> notifier(new LECentralNotifier);
- centralManager.reset([[ObjCCentralManager alloc] initWith:notifier.data()]);
- if (!centralManager) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO
- << "failed to initialize central manager";
+ QScopedPointer<LECBManagerNotifier> notifier(new LECBManagerNotifier);
+ if (role == QLowEnergyController::PeripheralRole) {
+#ifndef Q_OS_TVOS
+ peripheralManager.reset([[ObjCPeripheralManager alloc] initWith:notifier.data()]);
+ if (!peripheralManager) {
+ qCWarning(QT_BT_OSX) << "failed to initialize peripheral manager";
+ return;
+ }
+#else
+ qCWarning(QT_BT_OSX) << "peripheral role is not supported on your platform";
return;
- } else if (!connectSlots(notifier.data())) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO
- << "failed to connect to notifier's signals";
+#endif
+ } else {
+ centralManager.reset([[ObjCCentralManager alloc] initWith:notifier.data()]);
+ if (!centralManager) {
+ qCWarning(QT_BT_OSX) << "failed to initialize central manager";
+ return;
+ }
}
+ if (!connectSlots(notifier.data())) {
+ qCWarning(QT_BT_OSX) << "failed to connect to notifier's signal(s)";
+ }
// Ownership was taken by central manager.
notifier.take();
}
QLowEnergyControllerPrivateOSX::~QLowEnergyControllerPrivateOSX()
{
- // TODO: dispatch_sync 'setDelegate:Q_NULLPRT' to our CBCentralManager's delegate.
- if (dispatch_queue_t leQueue = OSXBluetooth::qt_LE_queue()) {
- ObjCCentralManager *manager = centralManager.data();
- dispatch_sync(leQueue, ^{
- [manager detach];
- });
+ if (const auto leQueue = OSXBluetooth::qt_LE_queue()) {
+ if (role == QLowEnergyController::CentralRole) {
+ const auto manager = centralManager.data();
+ dispatch_sync(leQueue, ^{
+ [manager detach];
+ });
+ } else {
+#ifndef Q_OS_TVOS
+ const auto manager = peripheralManager.data();
+ dispatch_sync(leQueue, ^{
+ [manager detach];
+ });
+#endif
+ }
}
}
bool QLowEnergyControllerPrivateOSX::isValid() const
{
+#ifdef Q_OS_TVOS
return centralManager;
+#else
+ return centralManager || peripheralManager;
+#endif
}
void QLowEnergyControllerPrivateOSX::_q_connected()
{
- Q_ASSERT_X(controllerState == QLowEnergyController::ConnectingState,
- Q_FUNC_INFO, "invalid state");
-
controllerState = QLowEnergyController::ConnectedState;
emit q_ptr->stateChanged(QLowEnergyController::ConnectedState);
@@ -226,10 +216,11 @@ void QLowEnergyControllerPrivateOSX::_q_disconnected()
{
controllerState = QLowEnergyController::UnconnectedState;
- invalidateServices();
+ if (role == QLowEnergyController::CentralRole)
+ invalidateServices();
+
emit q_ptr->stateChanged(QLowEnergyController::UnconnectedState);
emit q_ptr->disconnected();
-
}
void QLowEnergyControllerPrivateOSX::_q_serviceDiscoveryFinished()
@@ -258,7 +249,7 @@ void QLowEnergyControllerPrivateOSX::_q_serviceDiscoveryFinished()
continue;
if (discoveredServices.contains(newService->uuid)) {
// It's a bit stupid we first created it ...
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "discovered service with a duplicated UUID "
+ qCDebug(QT_BT_OSX) << "discovered service with a duplicated UUID"
<< newService->uuid;
continue;
}
@@ -310,7 +301,7 @@ void QLowEnergyControllerPrivateOSX::_q_serviceDiscoveryFinished()
toVisitNext.resetWithoutRetain([[NSMutableArray alloc] init]);
}
} else {
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "no services found";
+ qCDebug(QT_BT_OSX) << "no services found";
}
for (ServiceMap::const_iterator it = discoveredServices.constBegin(); it != discoveredServices.constEnd(); ++it) {
@@ -332,7 +323,7 @@ void QLowEnergyControllerPrivateOSX::_q_serviceDetailsDiscoveryFinished(QSharedP
Q_ASSERT(service);
if (!discoveredServices.contains(service->uuid)) {
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "unknown service uuid: "
+ qCDebug(QT_BT_OSX) << "unknown service uuid:"
<< service->uuid;
return;
}
@@ -357,7 +348,7 @@ void QLowEnergyControllerPrivateOSX::_q_characteristicRead(QLowEnergyHandle char
QLowEnergyCharacteristic characteristic(characteristicForHandle(charHandle));
if (!characteristic.isValid()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown characteristic";
+ qCWarning(QT_BT_OSX) << "unknown characteristic";
return;
}
@@ -374,14 +365,14 @@ void QLowEnergyControllerPrivateOSX::_q_characteristicWritten(QLowEnergyHandle c
ServicePrivate service(serviceForHandle(charHandle));
if (service.isNull()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "can not find service for characteristic handle "
+ qCWarning(QT_BT_OSX) << "can not find service for characteristic handle"
<< charHandle;
return;
}
QLowEnergyCharacteristic characteristic(characteristicForHandle(charHandle));
if (!characteristic.isValid()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown characteristic";
+ qCWarning(QT_BT_OSX) << "unknown characteristic";
return;
}
@@ -410,7 +401,7 @@ void QLowEnergyControllerPrivateOSX::_q_characteristicUpdated(QLowEnergyHandle c
QLowEnergyCharacteristic characteristic(characteristicForHandle(charHandle));
if (!characteristic.isValid()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown characteristic";
+ qCWarning(QT_BT_OSX) << "unknown characteristic";
return;
}
@@ -427,7 +418,7 @@ void QLowEnergyControllerPrivateOSX::_q_descriptorRead(QLowEnergyHandle dHandle,
const QLowEnergyDescriptor qtDescriptor(descriptorForHandle(dHandle));
if (!qtDescriptor.isValid()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown descriptor " << dHandle;
+ qCWarning(QT_BT_OSX) << "unknown descriptor" << dHandle;
return;
}
@@ -443,7 +434,7 @@ void QLowEnergyControllerPrivateOSX::_q_descriptorWritten(QLowEnergyHandle dHand
const QLowEnergyDescriptor qtDescriptor(descriptorForHandle(dHandle));
if (!qtDescriptor.isValid()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown descriptor " << dHandle;
+ qCWarning(QT_BT_OSX) << "unknown descriptor" << dHandle;
return;
}
@@ -461,7 +452,7 @@ void QLowEnergyControllerPrivateOSX::_q_LEnotSupported()
// be supported.
}
-void QLowEnergyControllerPrivateOSX::_q_CBCentralManagerError(QLowEnergyController::Error errorCode)
+void QLowEnergyControllerPrivateOSX::_q_CBManagerError(QLowEnergyController::Error errorCode)
{
// Errors reported during connect and general errors.
@@ -478,8 +469,8 @@ void QLowEnergyControllerPrivateOSX::_q_CBCentralManagerError(QLowEnergyControll
// a service/characteristic - related error.
}
-void QLowEnergyControllerPrivateOSX::_q_CBCentralManagerError(const QBluetoothUuid &serviceUuid,
- QLowEnergyController::Error errorCode)
+void QLowEnergyControllerPrivateOSX::_q_CBManagerError(const QBluetoothUuid &serviceUuid,
+ QLowEnergyController::Error errorCode)
{
// Errors reported while discovering service details etc.
Q_UNUSED(errorCode) // TODO: setError?
@@ -489,16 +480,16 @@ void QLowEnergyControllerPrivateOSX::_q_CBCentralManagerError(const QBluetoothUu
ServicePrivate qtService(discoveredServices.value(serviceUuid));
qtService->setState(QLowEnergyService::InvalidService);
} else {
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "error reported for unknown service "
+ qCDebug(QT_BT_OSX) << "error reported for unknown service"
<< serviceUuid;
}
}
-void QLowEnergyControllerPrivateOSX::_q_CBCentralManagerError(const QBluetoothUuid &serviceUuid,
- QLowEnergyService::ServiceError errorCode)
+void QLowEnergyControllerPrivateOSX::_q_CBManagerError(const QBluetoothUuid &serviceUuid,
+ QLowEnergyService::ServiceError errorCode)
{
if (!discoveredServices.contains(serviceUuid)) {
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "unknown service uuid: "
+ qCDebug(QT_BT_OSX) << "unknown service uuid:"
<< serviceUuid;
return;
}
@@ -514,10 +505,12 @@ void QLowEnergyControllerPrivateOSX::connectToDevice()
Q_FUNC_INFO, "invalid state");
Q_ASSERT_X(!deviceUuid.isNull(), Q_FUNC_INFO,
"invalid private controller (no device uuid)");
+ Q_ASSERT_X(role != QLowEnergyController::PeripheralRole,
+ Q_FUNC_INFO, "invalid role (peripheral)");
dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
if (!leQueue) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found";
+ qCWarning(QT_BT_OSX) << "no LE queue found";
setErrorDescription(QLowEnergyController::UnknownError);
return;
}
@@ -537,10 +530,12 @@ void QLowEnergyControllerPrivateOSX::discoverServices()
Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid private controller");
Q_ASSERT_X(controllerState != QLowEnergyController::UnconnectedState,
Q_FUNC_INFO, "not connected to peripheral");
+ Q_ASSERT_X(role != QLowEnergyController::PeripheralRole,
+ Q_FUNC_INFO, "invalid role (peripheral)");
dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
if (!leQueue) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found";
+ qCWarning(QT_BT_OSX) << "no LE queue found";
setErrorDescription(QLowEnergyController::UnknownError);
return;
}
@@ -559,20 +554,21 @@ void QLowEnergyControllerPrivateOSX::discoverServiceDetails(const QBluetoothUuid
Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid private controller");
if (controllerState != QLowEnergyController::DiscoveredState) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO
- << "can not discover service details in the current state, "
- << "QLowEnergyController::DiscoveredState is expected";
+ // This will also exclude peripheral role, since controller
+ // can never be in discovered state ...
+ qCWarning(QT_BT_OSX) << "can not discover service details in the current state, "
+ "QLowEnergyController::DiscoveredState is expected";
return;
}
if (!discoveredServices.contains(serviceUuid)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown service: " << serviceUuid;
+ qCWarning(QT_BT_OSX) << "unknown service: " << serviceUuid;
return;
}
dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
if (!leQueue) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found";
+ qCWarning(QT_BT_OSX) << "no LE queue found";
return;
}
@@ -593,32 +589,37 @@ void QLowEnergyControllerPrivateOSX::setNotifyValue(QSharedPointer<QLowEnergySer
Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller");
+ if (role == QLowEnergyController::PeripheralRole) {
+ qCWarning(QT_BT_OSX) << "invalid role (peripheral)";
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ return;
+ }
+
if (newValue.size() > 2) {
// Qt's API requires an error on such write.
// With Core Bluetooth we do not write any descriptor,
// but instead call a special method. So it's better to
// intercept wrong data size here:
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "client characteristic configuration descriptor "
+ qCWarning(QT_BT_OSX) << "client characteristic configuration descriptor"
"is 2 bytes, but value size is: " << newValue.size();
service->setError(QLowEnergyService::DescriptorWriteError);
return;
}
if (!discoveredServices.contains(service->uuid)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no service with uuid: "
- << service->uuid << " found";
+ qCWarning(QT_BT_OSX) << "no service with uuid:" << service->uuid << "found";
return;
}
if (!service->characteristicList.contains(charHandle)) {
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "no characteristic with handle: "
- << charHandle << " found";
+ qCDebug(QT_BT_OSX) << "no characteristic with handle:"
+ << charHandle << "found";
return;
}
dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
if (!leQueue) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found";
+ qCWarning(QT_BT_OSX) << "no LE queue found";
return;
}
ObjCCentralManager *manager = centralManager.data();
@@ -637,21 +638,26 @@ void QLowEnergyControllerPrivateOSX::readCharacteristic(QSharedPointer<QLowEnerg
Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller");
+ if (role == QLowEnergyController::PeripheralRole) {
+ qCWarning(QT_BT_OSX) << "invalid role (peripheral)";
+ return;
+ }
+
if (!discoveredServices.contains(service->uuid)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no service with uuid:"
+ qCWarning(QT_BT_OSX) << "no service with uuid:"
<< service->uuid << "found";
return;
}
if (!service->characteristicList.contains(charHandle)) {
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "no characteristic with handle:"
+ qCDebug(QT_BT_OSX) << "no characteristic with handle:"
<< charHandle << "found";
return;
}
dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
if (!leQueue) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found";
+ qCWarning(QT_BT_OSX) << "no LE queue found";
return;
}
// Attention! We have to copy UUID.
@@ -669,36 +675,47 @@ void QLowEnergyControllerPrivateOSX::writeCharacteristic(QSharedPointer<QLowEner
Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller");
- // We can work only with services, found on a given peripheral
- // (== created by the given LE controller),
- // otherwise we can not write anything at all.
+ // We can work only with services found on a given peripheral
+ // (== created by the given LE controller).
+
if (!discoveredServices.contains(service->uuid)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no service with uuid: "
+ qCWarning(QT_BT_OSX) << "no service with uuid:"
<< service->uuid << " found";
return;
}
if (!service->characteristicList.contains(charHandle)) {
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "no characteristic with handle: "
+ qCDebug(QT_BT_OSX) << "no characteristic with handle:"
<< charHandle << " found";
return;
}
dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
if (!leQueue) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found";
+ qCWarning(QT_BT_OSX) << "no LE queue found";
return;
}
- // Attention! Copy objects!
- const QBluetoothUuid serviceUuid(service->uuid);
+ // Attention! We have to copy objects!
const QByteArray newValueCopy(newValue);
- ObjCCentralManager *const manager = centralManager.data();
- dispatch_async(leQueue, ^{
- [manager write:newValueCopy
- charHandle:charHandle
+ if (role == QLowEnergyController::CentralRole) {
+ const QBluetoothUuid serviceUuid(service->uuid);
+ const auto manager = centralManager.data();
+ dispatch_async(leQueue, ^{
+ [manager write:newValueCopy
+ charHandle:charHandle
onService:serviceUuid
withResponse:mode == QLowEnergyService::WriteWithResponse];
- });
+ });
+ } else {
+#ifndef Q_OS_TVOS
+ const auto manager = peripheralManager.data();
+ dispatch_async(leQueue, ^{
+ [manager write:newValueCopy charHandle:charHandle];
+ });
+#else
+ qCWarning(QT_BT_OSX) << "peripheral role is not supported on your platform";
+#endif
+ }
}
quint16 QLowEnergyControllerPrivateOSX::updateValueOfCharacteristic(QLowEnergyHandle charHandle,
@@ -728,15 +745,20 @@ void QLowEnergyControllerPrivateOSX::readDescriptor(QSharedPointer<QLowEnergySer
Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller");
+ if (role == QLowEnergyController::PeripheralRole) {
+ qCWarning(QT_BT_OSX) << "invalid role (peripheral)";
+ return;
+ }
+
if (!discoveredServices.contains(service->uuid)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no service with uuid:"
+ qCWarning(QT_BT_OSX) << "no service with uuid:"
<< service->uuid << "found";
return;
}
dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
if (!leQueue) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found";
+ qCWarning(QT_BT_OSX) << "no LE queue found";
return;
}
// Attention! Copy objects!
@@ -755,18 +777,23 @@ void QLowEnergyControllerPrivateOSX::writeDescriptor(QSharedPointer<QLowEnergySe
Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller");
+ if (role == QLowEnergyController::PeripheralRole) {
+ qCWarning(QT_BT_OSX) << "invalid role (peripheral)";
+ return;
+ }
+
// We can work only with services found on a given peripheral
// (== created by the given LE controller),
// otherwise we can not write anything at all.
if (!discoveredServices.contains(service->uuid)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no service with uuid: "
+ qCWarning(QT_BT_OSX) << "no service with uuid:"
<< service->uuid << " found";
return;
}
dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
if (!leQueue) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found";
+ qCWarning(QT_BT_OSX) << "no LE queue found";
return;
}
// Attention! Copy objects!
@@ -868,17 +895,23 @@ void QLowEnergyControllerPrivateOSX::setErrorDescription(QLowEnergyController::E
errorString.clear();
break;
case QLowEnergyController::UnknownRemoteDeviceError:
- errorString = QCoreApplication::translate(LE_CONTROLLER, LEC_RDEV_NO_FOUND);
+ errorString = QLowEnergyController::tr("Remote device cannot be found");
break;
case QLowEnergyController::InvalidBluetoothAdapterError:
- errorString = QCoreApplication::translate(LE_CONTROLLER, LEC_NO_LOCAL_DEV);
+ errorString = QLowEnergyController::tr("Cannot find local adapter");
break;
case QLowEnergyController::NetworkError:
- errorString = QCoreApplication::translate(LE_CONTROLLER, LEC_IO_ERROR);
+ errorString = QLowEnergyController::tr("Error occurred during connection I/O");
+ break;
+ case QLowEnergyController::ConnectionError:
+ errorString = QLowEnergyController::tr("Error occurred trying to connect to remote device.");
+ break;
+ case QLowEnergyController::AdvertisingError:
+ errorString = QLowEnergyController::tr("Error occurred trying to start advertising");
break;
case QLowEnergyController::UnknownError:
default:
- errorString = QCoreApplication::translate(LE_CONTROLLER, LEC_UNKNOWN_ERROR);
+ errorString = QLowEnergyController::tr("Unknown Error");
break;
}
}
@@ -893,38 +926,38 @@ void QLowEnergyControllerPrivateOSX::invalidateServices()
discoveredServices.clear();
}
-bool QLowEnergyControllerPrivateOSX::connectSlots(OSXBluetooth::LECentralNotifier *notifier)
+bool QLowEnergyControllerPrivateOSX::connectSlots(OSXBluetooth::LECBManagerNotifier *notifier)
{
- using OSXBluetooth::LECentralNotifier;
+ using OSXBluetooth::LECBManagerNotifier;
Q_ASSERT_X(notifier, Q_FUNC_INFO, "invalid notifier object (null)");
- bool ok = connect(notifier, &LECentralNotifier::connected,
+ bool ok = connect(notifier, &LECBManagerNotifier::connected,
this, &QLowEnergyControllerPrivateOSX::_q_connected);
- ok = ok && connect(notifier, &LECentralNotifier::disconnected,
+ ok = ok && connect(notifier, &LECBManagerNotifier::disconnected,
this, &QLowEnergyControllerPrivateOSX::_q_disconnected);
- ok = ok && connect(notifier, &LECentralNotifier::serviceDiscoveryFinished,
+ ok = ok && connect(notifier, &LECBManagerNotifier::serviceDiscoveryFinished,
this, &QLowEnergyControllerPrivateOSX::_q_serviceDiscoveryFinished);
- ok = ok && connect(notifier, &LECentralNotifier::serviceDetailsDiscoveryFinished,
+ ok = ok && connect(notifier, &LECBManagerNotifier::serviceDetailsDiscoveryFinished,
this, &QLowEnergyControllerPrivateOSX::_q_serviceDetailsDiscoveryFinished);
- ok = ok && connect(notifier, &LECentralNotifier::characteristicRead,
+ ok = ok && connect(notifier, &LECBManagerNotifier::characteristicRead,
this, &QLowEnergyControllerPrivateOSX::_q_characteristicRead);
- ok = ok && connect(notifier, &LECentralNotifier::characteristicWritten,
+ ok = ok && connect(notifier, &LECBManagerNotifier::characteristicWritten,
this, &QLowEnergyControllerPrivateOSX::_q_characteristicWritten);
- ok = ok && connect(notifier, &LECentralNotifier::characteristicUpdated,
+ ok = ok && connect(notifier, &LECBManagerNotifier::characteristicUpdated,
this, &QLowEnergyControllerPrivateOSX::_q_characteristicUpdated);
- ok = ok && connect(notifier, &LECentralNotifier::descriptorRead,
+ ok = ok && connect(notifier, &LECBManagerNotifier::descriptorRead,
this, &QLowEnergyControllerPrivateOSX::_q_descriptorRead);
- ok = ok && connect(notifier, &LECentralNotifier::descriptorWritten,
+ ok = ok && connect(notifier, &LECBManagerNotifier::descriptorWritten,
this, &QLowEnergyControllerPrivateOSX::_q_descriptorWritten);
- ok = ok && connect(notifier, &LECentralNotifier::LEnotSupported,
+ ok = ok && connect(notifier, &LECBManagerNotifier::LEnotSupported,
this, &QLowEnergyControllerPrivateOSX::_q_LEnotSupported);
- ok = ok && connect(notifier, SIGNAL(CBCentralManagerError(QLowEnergyController::Error)),
- this, SLOT(_q_CBCentralManagerError(QLowEnergyController::Error)));
- ok = ok && connect(notifier, SIGNAL(CBCentralManagerError(const QBluetoothUuid &, QLowEnergyController::Error)),
- this, SLOT(_q_CBCentralManagerError(const QBluetoothUuid &, QLowEnergyController::Error)));
- ok = ok && connect(notifier, SIGNAL(CBCentralManagerError(const QBluetoothUuid &, QLowEnergyService::ServiceError)),
- this, SLOT(_q_CBCentralManagerError(const QBluetoothUuid &, QLowEnergyService::ServiceError)));
+ ok = ok && connect(notifier, SIGNAL(CBManagerError(QLowEnergyController::Error)),
+ this, SLOT(_q_CBManagerError(QLowEnergyController::Error)));
+ ok = ok && connect(notifier, SIGNAL(CBManagerError(const QBluetoothUuid &, QLowEnergyController::Error)),
+ this, SLOT(_q_CBManagerError(const QBluetoothUuid &, QLowEnergyController::Error)));
+ ok = ok && connect(notifier, SIGNAL(CBManagerError(const QBluetoothUuid &, QLowEnergyService::ServiceError)),
+ this, SLOT(_q_CBManagerError(const QBluetoothUuid &, QLowEnergyService::ServiceError)));
if (!ok)
notifier->disconnect();
@@ -935,26 +968,24 @@ bool QLowEnergyControllerPrivateOSX::connectSlots(OSXBluetooth::LECentralNotifie
QLowEnergyController::QLowEnergyController(const QBluetoothAddress &remoteAddress,
QObject *parent)
: QObject(parent),
- d_ptr(new QLowEnergyControllerPrivateOSX(this))
+ d_ptr(new QLowEnergyControllerPrivateOSX(CentralRole, this))
{
OSX_D_PTR;
- osx_d_ptr->role = CentralRole;
osx_d_ptr->remoteAddress = remoteAddress;
osx_d_ptr->localAddress = QBluetoothLocalDevice().address();
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "construction with remote address "
+ qCWarning(QT_BT_OSX) << "construction with remote address "
"is not supported!";
}
QLowEnergyController::QLowEnergyController(const QBluetoothDeviceInfo &remoteDevice,
QObject *parent)
: QObject(parent),
- d_ptr(new QLowEnergyControllerPrivateOSX(this, remoteDevice))
+ d_ptr(new QLowEnergyControllerPrivateOSX(CentralRole, this, remoteDevice))
{
OSX_D_PTR;
- osx_d_ptr->role = CentralRole;
osx_d_ptr->localAddress = QBluetoothLocalDevice().address();
// That's the only "real" ctor - with Core Bluetooth we need a _valid_ deviceUuid
// from 'remoteDevice'.
@@ -964,24 +995,23 @@ QLowEnergyController::QLowEnergyController(const QBluetoothAddress &remoteAddres
const QBluetoothAddress &localAddress,
QObject *parent)
: QObject(parent),
- d_ptr(new QLowEnergyControllerPrivateOSX(this))
+ d_ptr(new QLowEnergyControllerPrivateOSX(CentralRole, this))
{
OSX_D_PTR;
- osx_d_ptr->role = CentralRole;
osx_d_ptr->remoteAddress = remoteAddress;
osx_d_ptr->localAddress = localAddress;
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "construction with remote/local "
+ qCWarning(QT_BT_OSX) << "construction with remote/local "
"addresses is not supported!";
}
QLowEnergyController::QLowEnergyController(QObject *parent)
- : QObject(parent), d_ptr(new QLowEnergyControllerPrivateOSX(this))
+ : QObject(parent),
+ d_ptr(new QLowEnergyControllerPrivateOSX(PeripheralRole, this))
{
OSX_D_PTR;
- osx_d_ptr->role = PeripheralRole;
osx_d_ptr->localAddress = QBluetoothLocalDevice().address();
}
@@ -1059,11 +1089,16 @@ void QLowEnergyController::connectToDevice()
// A memory allocation problem.
if (!osx_d_ptr->isValid())
- return osx_d_ptr->_q_CBCentralManagerError(UnknownError);
+ return osx_d_ptr->_q_CBManagerError(UnknownError);
+
+ if (role() == PeripheralRole) {
+ qCWarning(QT_BT_OSX) << "can not connect in peripheral role";
+ return osx_d_ptr->_q_CBManagerError(ConnectionError);
+ }
// No QBluetoothDeviceInfo provided during construction.
if (osx_d_ptr->deviceUuid.isNull())
- return osx_d_ptr->_q_CBCentralManagerError(UnknownRemoteDeviceError);
+ return osx_d_ptr->_q_CBManagerError(UnknownRemoteDeviceError);
if (osx_d_ptr->controllerState != UnconnectedState)
return;
@@ -1078,6 +1113,11 @@ void QLowEnergyController::disconnectFromDevice()
OSX_D_PTR;
+ if (role() != CentralRole) {
+ qCWarning(QT_BT_OSX) << "can not disconnect while in central role";
+ return osx_d_ptr->_q_CBManagerError(ConnectionError);
+ }
+
if (osx_d_ptr->isValid()) {
const ControllerState oldState = osx_d_ptr->controllerState;
@@ -1101,14 +1141,19 @@ void QLowEnergyController::disconnectFromDevice()
emit stateChanged(UnconnectedState);
}
} else {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "qt LE queue is nil,"
- << "can not dispatch 'disconnect'";
+ qCCritical(QT_BT_OSX) << "qt LE queue is nil, "
+ "can not dispatch 'disconnect'";
}
}
}
void QLowEnergyController::discoverServices()
{
+ if (role() == PeripheralRole) {
+ qCWarning(QT_BT_OSX) << "invalid role (peripheral)";
+ return;
+ }
+
if (state() != ConnectedState)
return;
@@ -1159,30 +1204,126 @@ void QLowEnergyController::startAdvertising(const QLowEnergyAdvertisingParameter
const QLowEnergyAdvertisingData &advertisingData,
const QLowEnergyAdvertisingData &scanResponseData)
{
- Q_UNUSED(params);
- Q_UNUSED(advertisingData);
- Q_UNUSED(scanResponseData);
- qCWarning(QT_BT_OSX) << "LE advertising not implemented for OS X";
+#ifdef Q_OS_TVOS
+ Q_UNUSED(params)
+ Q_UNUSED(advertisingData)
+ Q_UNUSED(scanResponseData)
+ qCWarning(QT_BT_OSX) << "advertising is not supported on your platform";
+#else
+ OSX_D_PTR;
+
+ if (!osx_d_ptr->isValid())
+ return osx_d_ptr->_q_CBManagerError(UnknownError);
+
+ if (role() != PeripheralRole) {
+ qCWarning(QT_BT_OSX) << "invalid role";
+ return;
+ }
+
+ if (state() != UnconnectedState) {
+ qCWarning(QT_BT_OSX) << "invalid state" << state();
+ return;
+ }
+
+ auto leQueue(OSXBluetooth::qt_LE_queue());
+ if (!leQueue) {
+ qCWarning(QT_BT_OSX) << "no LE queue found";
+ osx_d_ptr->setErrorDescription(QLowEnergyController::UnknownError);
+ return;
+ }
+
+ [osx_d_ptr->peripheralManager setParameters:params
+ data:advertisingData
+ scanResponse:scanResponseData];
+
+ osx_d_ptr->controllerState = AdvertisingState;
+ emit stateChanged(AdvertisingState);
+
+ const auto manager = osx_d_ptr->peripheralManager.data();
+ dispatch_async(leQueue, ^{
+ [manager startAdvertising];
+ });
+#endif
}
void QLowEnergyController::stopAdvertising()
{
- qCWarning(QT_BT_OSX) << "LE advertising not implemented for OS X";
+#ifdef Q_OS_TVOS
+ qCWarning(QT_BT_OSX) << "advertising is not supported on your platform";
+#else
+ OSX_D_PTR;
+
+ if (!osx_d_ptr->isValid())
+ return osx_d_ptr->_q_CBManagerError(UnknownError);
+
+ if (state() != AdvertisingState) {
+ qCDebug(QT_BT_OSX) << "called in state" << state();
+ return;
+ }
+
+ if (const auto leQueue = OSXBluetooth::qt_LE_queue()) {
+ const auto manager = osx_d_ptr->peripheralManager.data();
+ dispatch_sync(leQueue, ^{
+ [manager stopAdvertising];
+ });
+
+ osx_d_ptr->controllerState = UnconnectedState;
+ emit stateChanged(UnconnectedState);
+ } else {
+ qCWarning(QT_BT_OSX) << "no LE queue found";
+ osx_d_ptr->setErrorDescription(QLowEnergyController::UnknownError);
+ return;
+ }
+#endif
}
-QLowEnergyService *QLowEnergyController::addService(const QLowEnergyServiceData &service,
+QLowEnergyService *QLowEnergyController::addService(const QLowEnergyServiceData &data,
QObject *parent)
{
- Q_UNUSED(service);
- Q_UNUSED(parent);
- qCWarning(QT_BT_OSX) << "GATT server functionality not implemented for OS X";
+#ifdef Q_OS_TVOS
+ Q_UNUSED(data)
+ Q_UNUSED(parent)
+ qCWarning(QT_BT_OSX) << "peripheral role is not supported on your platform";
+#else
+ OSX_D_PTR;
+
+ if (!osx_d_ptr->isValid()) {
+ osx_d_ptr->_q_CBManagerError(UnknownError);
+ return nullptr;
+ }
+
+ if (role() != PeripheralRole) {
+ qCWarning(QT_BT_OSX) << "not in peripheral role";
+ return nullptr;
+ }
+
+ if (state() != UnconnectedState) {
+ qCWarning(QT_BT_OSX) << "invalid state";
+ return nullptr;
+ }
+
+ if (!data.isValid()) {
+ qCWarning(QT_BT_OSX) << "invalid service";
+ return nullptr;
+ }
+
+ for (auto includedService : data.includedServices())
+ includedService->d_ptr->type |= QLowEnergyService::IncludedService;
+
+ if (const auto servicePrivate = [osx_d_ptr->peripheralManager addService:data]) {
+ servicePrivate->setController(osx_d_ptr);
+ osx_d_ptr->discoveredServices.insert(servicePrivate->uuid, servicePrivate);
+ return new QLowEnergyService(servicePrivate, parent);
+ }
+#endif
+
return nullptr;
}
void QLowEnergyController::requestConnectionUpdate(const QLowEnergyConnectionParameters &params)
{
Q_UNUSED(params);
- qCWarning(QT_BT_OSX) << "Connection update not implemented for OS X";
+ qCWarning(QT_BT_OSX) << "Connection update not implemented on your platform";
}
QT_END_NAMESPACE
diff --git a/src/bluetooth/qlowenergycontroller_osx_p.h b/src/bluetooth/qlowenergycontroller_osx_p.h
index 853e1f9b..b7399ed7 100644
--- a/src/bluetooth/qlowenergycontroller_osx_p.h
+++ b/src/bluetooth/qlowenergycontroller_osx_p.h
@@ -51,6 +51,7 @@
// We mean it.
//
+#include "osx/osxbtperipheralmanager_p.h"
#include "qlowenergyserviceprivate_p.h"
#include "osx/osxbtcentralmanager_p.h"
#include "qlowenergycontroller_p.h"
@@ -61,6 +62,7 @@
#include "qbluetoothuuid.h"
#include <QtCore/qsharedpointer.h>
+#include <QtCore/qsysinfo.h>
#include <QtCore/qglobal.h>
#include <QtCore/qstring.h>
#include <QtCore/qmap.h>
@@ -70,13 +72,13 @@ QT_BEGIN_NAMESPACE
namespace OSXBluetooth
{
-class LECentralNotifier;
+class LECBManagerNotifier;
}
class QByteArray;
-// While suffix 'OSX', it's also for iOS.
+// Suffix 'OSX' is a legacy, it's also iOS.
class QLowEnergyControllerPrivateOSX : public QLowEnergyControllerPrivate
{
friend class QLowEnergyController;
@@ -84,9 +86,8 @@ class QLowEnergyControllerPrivateOSX : public QLowEnergyControllerPrivate
Q_OBJECT
public:
- QLowEnergyControllerPrivateOSX(QLowEnergyController *q);
- QLowEnergyControllerPrivateOSX(QLowEnergyController *q,
- const QBluetoothDeviceInfo &uuid);
+ QLowEnergyControllerPrivateOSX(QLowEnergyController::Role role, QLowEnergyController *q,
+ const QBluetoothDeviceInfo &info = QBluetoothDeviceInfo());
~QLowEnergyControllerPrivateOSX();
bool isValid() const;
@@ -105,18 +106,15 @@ private Q_SLOTS:
void _q_descriptorWritten(QLowEnergyHandle charHandle, const QByteArray &value);
void _q_LEnotSupported();
- void _q_CBCentralManagerError(QLowEnergyController::Error error);
- void _q_CBCentralManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyController::Error error);
- void _q_CBCentralManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyService::ServiceError error);
+ void _q_CBManagerError(QLowEnergyController::Error error);
+ void _q_CBManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyController::Error error);
+ void _q_CBManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyService::ServiceError error);
private:
void connectToDevice();
void discoverServices();
void discoverServiceDetails(const QBluetoothUuid &serviceUuid);
- // TODO: all these read/write /setNotify can be simplified -
- // by just passing either characteristic or descriptor (that
- // has all needed information - service, handle, etc.).
void setNotifyValue(QSharedPointer<QLowEnergyServicePrivate> service,
QLowEnergyHandle charHandle, const QByteArray &newValue);
@@ -149,7 +147,7 @@ private:
void setErrorDescription(QLowEnergyController::Error errorCode);
void invalidateServices();
- bool connectSlots(OSXBluetooth::LECentralNotifier *notifier);
+ bool connectSlots(OSXBluetooth::LECBManagerNotifier *notifier);
QLowEnergyController *q_ptr;
QBluetoothUuid deviceUuid;
@@ -170,6 +168,12 @@ private:
typedef OSXBluetooth::ObjCScopedPointer<ObjCCentralManager> CentralManager;
CentralManager centralManager;
+#ifndef Q_OS_TVOS
+ typedef QT_MANGLE_NAMESPACE(OSXBTPeripheralManager) ObjCPeripheralManager;
+ typedef OSXBluetooth::ObjCScopedPointer<ObjCPeripheralManager> PeripheralManager;
+ PeripheralManager peripheralManager;
+#endif
+
typedef QMap<QBluetoothUuid, QSharedPointer<QLowEnergyServicePrivate> > ServiceMap;
typedef ServiceMap::const_iterator ConstServiceIterator;
typedef ServiceMap::iterator ServiceIterator;
diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h
index 2256025c..38e38752 100644
--- a/src/bluetooth/qlowenergycontroller_p.h
+++ b/src/bluetooth/qlowenergycontroller_p.h
@@ -82,6 +82,24 @@ QT_END_NAMESPACE
#elif defined(QT_ANDROID_BLUETOOTH)
#include <QtAndroidExtras/QAndroidJniObject>
#include "android/lowenergynotificationhub_p.h"
+#elif defined(QT_WINRT_BLUETOOTH)
+#include <wrl.h>
+
+namespace ABI {
+ namespace Windows {
+ namespace Devices {
+ namespace Bluetooth {
+ struct IBluetoothLEDevice;
+ namespace GenericAttributeProfile {
+ struct IGattDeviceService;
+ struct IGattCharacteristic;
+ }
+ }
+ }
+ }
+}
+
+class QWinRTLowEnergyServiceHandler;
#endif
#include <functional>
@@ -414,6 +432,16 @@ private slots:
QLowEnergyService::ServiceError errorCode);
void characteristicChanged(int charHandle, const QByteArray &data);
void serviceError(int attributeHandle, QLowEnergyService::ServiceError errorCode);
+#elif defined(QT_WINRT_BLUETOOTH)
+private:
+ Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice> mDevice;
+ EventRegistrationToken mStatusChangedToken;
+
+ Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattDeviceService> getNativeService(const QBluetoothUuid &serviceUuid);
+ Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattCharacteristic> getNativeCharacteristic(const QBluetoothUuid &serviceUuid, const QBluetoothUuid &charUuid);
+
+ void obtainIncludedServices(QSharedPointer<QLowEnergyServicePrivate> servicePointer,
+ Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattDeviceService> nativeService);
#endif
private:
QLowEnergyController *q_ptr;
diff --git a/src/bluetooth/qlowenergycontroller_winrt.cpp b/src/bluetooth/qlowenergycontroller_winrt.cpp
new file mode 100644
index 00000000..25371e74
--- /dev/null
+++ b/src/bluetooth/qlowenergycontroller_winrt.cpp
@@ -0,0 +1,1019 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qlowenergycontroller_p.h"
+
+#include <QtCore/qfunctions_winrt.h>
+#include <QtCore/QLoggingCategory>
+#include <private/qeventdispatcher_winrt_p.h>
+
+#include <functional>
+#include <robuffer.h>
+#include <windows.devices.enumeration.h>
+#include <windows.devices.bluetooth.h>
+#include <windows.foundation.collections.h>
+#include <windows.storage.streams.h>
+
+using namespace Microsoft::WRL;
+using namespace Microsoft::WRL::Wrappers;
+using namespace ABI::Windows::Foundation;
+using namespace ABI::Windows::Foundation::Collections;
+using namespace ABI::Windows::Devices;
+using namespace ABI::Windows::Devices::Bluetooth;
+using namespace ABI::Windows::Devices::Bluetooth::GenericAttributeProfile;
+using namespace ABI::Windows::Devices::Enumeration;
+using namespace ABI::Windows::Storage::Streams;
+
+QT_BEGIN_NAMESPACE
+
+typedef ITypedEventHandler<BluetoothLEDevice *, IInspectable *> StatusHandler;
+typedef GattReadClientCharacteristicConfigurationDescriptorResult ClientCharConfigDescriptorResult;
+typedef IGattReadClientCharacteristicConfigurationDescriptorResult IClientCharConfigDescriptorResult;
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT)
+
+static QVector<QBluetoothUuid> getIncludedServiceIds(const ComPtr<IGattDeviceService> &service)
+{
+ QVector<QBluetoothUuid> result;
+ ComPtr<IGattDeviceService2> service2;
+ HRESULT hr = service.As(&service2);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IVectorView<GattDeviceService *>> includedServices;
+ hr = service2->GetAllIncludedServices(&includedServices);
+ Q_ASSERT_SUCCEEDED(hr);
+
+ uint count;
+ hr = includedServices->get_Size(&count);
+ Q_ASSERT_SUCCEEDED(hr);
+ for (uint i = 0; i < count; ++i) {
+ ComPtr<IGattDeviceService> includedService;
+ hr = includedServices->GetAt(i, &includedService);
+ Q_ASSERT_SUCCEEDED(hr);
+ GUID guuid;
+ hr = includedService->get_Uuid(&guuid);
+ Q_ASSERT_SUCCEEDED(hr);
+ const QBluetoothUuid service(guuid);
+ result << service;
+
+ result << getIncludedServiceIds(includedService);
+ }
+ return result;
+}
+
+static QByteArray byteArrayFromBuffer(const ComPtr<IBuffer> &buffer, bool isWCharString = false)
+{
+ ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteAccess;
+ HRESULT hr = buffer.As(&byteAccess);
+ Q_ASSERT_SUCCEEDED(hr);
+ char *data;
+ hr = byteAccess->Buffer(reinterpret_cast<byte **>(&data));
+ Q_ASSERT_SUCCEEDED(hr);
+ UINT32 size;
+ hr = buffer->get_Length(&size);
+ Q_ASSERT_SUCCEEDED(hr);
+ if (isWCharString) {
+ QString valueString = QString::fromUtf16(reinterpret_cast<ushort *>(data)).left(size / 2);
+ return valueString.toUtf8();
+ }
+ return QByteArray(data, size);
+}
+
+static QByteArray byteArrayFromGattResult(const ComPtr<IGattReadResult> &gattResult, bool isWCharString = false)
+{
+ ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer;
+ HRESULT hr;
+ hr = gattResult->get_Value(&buffer);
+ Q_ASSERT_SUCCEEDED(hr);
+ return byteArrayFromBuffer(buffer, isWCharString);
+}
+
+class QWinRTLowEnergyServiceHandler : public QObject
+{
+ Q_OBJECT
+public:
+ QWinRTLowEnergyServiceHandler(const QBluetoothUuid &service, const ComPtr<IGattDeviceService2> &deviceService)
+ : mService(service)
+ , mDeviceService(deviceService)
+ {
+ qCDebug(QT_BT_WINRT) << __FUNCTION__;
+ }
+
+ ~QWinRTLowEnergyServiceHandler()
+ {
+ }
+
+public slots:
+ void obtainCharList()
+ {
+ quint16 startHandle = 0;
+ quint16 endHandle = 0;
+ qCDebug(QT_BT_WINRT) << __FUNCTION__;
+ ComPtr<IVectorView<GattCharacteristic *>> characteristics;
+ HRESULT hr = mDeviceService->GetAllCharacteristics(&characteristics);
+ Q_ASSERT_SUCCEEDED(hr);
+ if (!characteristics) {
+ emit charListObtained(mService, mCharacteristicList, startHandle, endHandle);
+ QThread::currentThread()->quit();
+ return;
+ }
+
+ uint characteristicsCount;
+ hr = characteristics->get_Size(&characteristicsCount);
+ Q_ASSERT_SUCCEEDED(hr);
+ for (uint i = 0; i < characteristicsCount; ++i) {
+ ComPtr<IGattCharacteristic> characteristic;
+ hr = characteristics->GetAt(i, &characteristic);
+ Q_ASSERT_SUCCEEDED(hr);
+ quint16 handle;
+ hr = characteristic->get_AttributeHandle(&handle);
+ Q_ASSERT_SUCCEEDED(hr);
+ QLowEnergyServicePrivate::CharData charData;
+ charData.valueHandle = handle + 1;
+ if (startHandle == 0 || startHandle > handle)
+ startHandle = handle;
+ if (endHandle == 0 || endHandle < handle)
+ endHandle = handle;
+ GUID guuid;
+ hr = characteristic->get_Uuid(&guuid);
+ Q_ASSERT_SUCCEEDED(hr);
+ charData.uuid = QBluetoothUuid(guuid);
+ GattCharacteristicProperties properties;
+ hr = characteristic->get_CharacteristicProperties(&properties);
+ Q_ASSERT_SUCCEEDED(hr);
+ charData.properties = QLowEnergyCharacteristic::PropertyTypes(properties);
+ if (charData.properties & QLowEnergyCharacteristic::Read) {
+ ComPtr<IAsyncOperation<GattReadResult *>> readOp;
+ hr = characteristic->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, &readOp);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IGattReadResult> readResult;
+ hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf());
+ Q_ASSERT_SUCCEEDED(hr);
+ if (readResult)
+ charData.value = byteArrayFromGattResult(readResult);
+ }
+ ComPtr<IGattCharacteristic2> characteristic2;
+ hr = characteristic.As(&characteristic2);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IVectorView<GattDescriptor *>> descriptors;
+ hr = characteristic2->GetAllDescriptors(&descriptors);
+ Q_ASSERT_SUCCEEDED(hr);
+ uint descriptorCount;
+ hr = descriptors->get_Size(&descriptorCount);
+ Q_ASSERT_SUCCEEDED(hr);
+ for (uint j = 0; j < descriptorCount; ++j) {
+ QLowEnergyServicePrivate::DescData descData;
+ ComPtr<IGattDescriptor> descriptor;
+ hr = descriptors->GetAt(j, &descriptor);
+ Q_ASSERT_SUCCEEDED(hr);
+ quint16 descHandle;
+ hr = descriptor->get_AttributeHandle(&descHandle);
+ Q_ASSERT_SUCCEEDED(hr);
+ GUID descriptorUuid;
+ hr = descriptor->get_Uuid(&descriptorUuid);
+ Q_ASSERT_SUCCEEDED(hr);
+ descData.uuid = QBluetoothUuid(descriptorUuid);
+ if (descData.uuid == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) {
+ ComPtr<IAsyncOperation<ClientCharConfigDescriptorResult *>> readOp;
+ hr = characteristic->ReadClientCharacteristicConfigurationDescriptorAsync(&readOp);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IClientCharConfigDescriptorResult> readResult;
+ hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf());
+ Q_ASSERT_SUCCEEDED(hr);
+ GattClientCharacteristicConfigurationDescriptorValue value;
+ hr = readResult->get_ClientCharacteristicConfigurationDescriptor(&value);
+ Q_ASSERT_SUCCEEDED(hr);
+ quint16 result = 0;
+ bool correct = false;
+ if (value & GattClientCharacteristicConfigurationDescriptorValue_Indicate) {
+ result |= GattClientCharacteristicConfigurationDescriptorValue_Indicate;
+ correct = true;
+ }
+ if (value & GattClientCharacteristicConfigurationDescriptorValue_Notify) {
+ result |= GattClientCharacteristicConfigurationDescriptorValue_Notify;
+ correct = true;
+ }
+ if (value == GattClientCharacteristicConfigurationDescriptorValue_None) {
+ correct = true;
+ }
+ if (!correct)
+ continue;
+
+ descData.value = QByteArray(2, Qt::Uninitialized);
+ qToLittleEndian(result, descData.value.data());
+ } else {
+ ComPtr<IAsyncOperation<GattReadResult *>> readOp;
+ hr = descriptor->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, &readOp);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IGattReadResult> readResult;
+ hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf());
+ Q_ASSERT_SUCCEEDED(hr);
+ if (descData.uuid == QBluetoothUuid::CharacteristicUserDescription)
+ descData.value = byteArrayFromGattResult(readResult, true);
+ else
+ descData.value = byteArrayFromGattResult(readResult);
+ }
+ charData.descriptorList.insert(descHandle, descData);
+ }
+ mCharacteristicList.insert(handle, charData);
+ }
+ emit charListObtained(mService, mCharacteristicList, startHandle, endHandle);
+ QThread::currentThread()->quit();
+ }
+
+public:
+ QBluetoothUuid mService;
+ ComPtr<IGattDeviceService2> mDeviceService;
+ QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData> mCharacteristicList;
+
+signals:
+ void charListObtained(const QBluetoothUuid &service, QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData> charList,
+ QLowEnergyHandle startHandle, QLowEnergyHandle endHandle);
+};
+
+QLowEnergyControllerPrivate::QLowEnergyControllerPrivate()
+ : QObject(),
+ state(QLowEnergyController::UnconnectedState),
+ error(QLowEnergyController::NoError)
+{
+ qCDebug(QT_BT_WINRT) << __FUNCTION__;
+}
+
+QLowEnergyControllerPrivate::~QLowEnergyControllerPrivate()
+{
+ if (mDevice && mStatusChangedToken.value)
+ mDevice->remove_ConnectionStatusChanged(mStatusChangedToken);
+}
+
+void QLowEnergyControllerPrivate::init()
+{
+}
+
+void QLowEnergyControllerPrivate::connectToDevice()
+{
+ qCDebug(QT_BT_WINRT) << __FUNCTION__;
+ Q_Q(QLowEnergyController);
+ if (remoteDevice.isNull()) {
+ qWarning() << "Invalid/null remote device address";
+ setError(QLowEnergyController::UnknownRemoteDeviceError);
+ return;
+ }
+
+ setState(QLowEnergyController::ConnectingState);
+
+ ComPtr<IBluetoothLEDeviceStatics> deviceStatics;
+ HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothLEDevice).Get(), &deviceStatics);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IAsyncOperation<BluetoothLEDevice *>> deviceFromIdOperation;
+ hr = deviceStatics->FromBluetoothAddressAsync(remoteDevice.toUInt64(), &deviceFromIdOperation);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = QWinRTFunctions::await(deviceFromIdOperation, mDevice.GetAddressOf());
+ Q_ASSERT_SUCCEEDED(hr);
+
+ if (!mDevice) {
+ qCDebug(QT_BT_WINRT) << "Could not find LE device";
+ setError(QLowEnergyController::InvalidBluetoothAdapterError);
+ setState(QLowEnergyController::UnconnectedState);
+ }
+ BluetoothConnectionStatus status;
+ hr = mDevice->get_ConnectionStatus(&status);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = QEventDispatcherWinRT::runOnXamlThread([this, q]() {
+ HRESULT hr;
+ hr = mDevice->add_ConnectionStatusChanged(Callback<StatusHandler>([this, q](IBluetoothLEDevice *dev, IInspectable *) {
+ BluetoothConnectionStatus status;
+ HRESULT hr;
+ hr = dev->get_ConnectionStatus(&status);
+ Q_ASSERT_SUCCEEDED(hr);
+ if (status == BluetoothConnectionStatus::BluetoothConnectionStatus_Connected) {
+ setState(QLowEnergyController::ConnectedState);
+ emit q->connected();
+ } else if (status == BluetoothConnectionStatus::BluetoothConnectionStatus_Disconnected) {
+ setState(QLowEnergyController::UnconnectedState);
+ emit q->disconnected();
+ }
+ return S_OK;
+ }).Get(), &mStatusChangedToken);
+ Q_ASSERT_SUCCEEDED(hr);
+ return S_OK;
+ });
+ Q_ASSERT_SUCCEEDED(hr);
+
+ if (status == BluetoothConnectionStatus::BluetoothConnectionStatus_Connected) {
+ setState(QLowEnergyController::ConnectedState);
+ emit q->connected();
+ return;
+ }
+
+ ComPtr<IVectorView <GattDeviceService *>> deviceServices;
+ hr = mDevice->get_GattServices(&deviceServices);
+ Q_ASSERT_SUCCEEDED(hr);
+ uint serviceCount;
+ hr = deviceServices->get_Size(&serviceCount);
+ Q_ASSERT_SUCCEEDED(hr);
+ // Windows Phone automatically connects to the device as soon as a service value is read/written.
+ // Thus we read one value in order to establish the connection.
+ for (uint i = 0; i < serviceCount; ++i) {
+ ComPtr<IGattDeviceService> service;
+ hr = deviceServices->GetAt(i, &service);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IGattDeviceService2> service2;
+ hr = service.As(&service2);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IVectorView<GattCharacteristic *>> characteristics;
+ hr = service2->GetAllCharacteristics(&characteristics);
+ if (hr == E_ACCESSDENIED) {
+ // Everything will work as expected up until this point if the manifest capabilties
+ // for bluetooth LE are not set.
+ qCWarning(QT_BT_WINRT) << "Could not obtain characteristic list. Please check your "
+ "manifest capabilities";
+ setState(QLowEnergyController::UnconnectedState);
+ setError(QLowEnergyController::ConnectionError);
+ return;
+ } else {
+ Q_ASSERT_SUCCEEDED(hr);
+ }
+ uint characteristicsCount;
+ hr = characteristics->get_Size(&characteristicsCount);
+ Q_ASSERT_SUCCEEDED(hr);
+ for (uint j = 0; j < characteristicsCount; ++j) {
+ ComPtr<IGattCharacteristic> characteristic;
+ hr = characteristics->GetAt(j, &characteristic);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IAsyncOperation<GattReadResult *>> op;
+ GattCharacteristicProperties props;
+ hr = characteristic->get_CharacteristicProperties(&props);
+ Q_ASSERT_SUCCEEDED(hr);
+ if (!(props & GattCharacteristicProperties_Read))
+ continue;
+ hr = characteristic->ReadValueWithCacheModeAsync(BluetoothCacheMode::BluetoothCacheMode_Uncached, &op);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IGattReadResult> result;
+ hr = QWinRTFunctions::await(op, result.GetAddressOf());
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer;
+ hr = result->get_Value(&buffer);
+ Q_ASSERT_SUCCEEDED(hr);
+ if (!buffer) {
+ qCDebug(QT_BT_WINRT) << "Problem reading value";
+ setError(QLowEnergyController::ConnectionError);
+ setState(QLowEnergyController::UnconnectedState);
+ }
+ return;
+ }
+ }
+}
+
+void QLowEnergyControllerPrivate::disconnectFromDevice()
+{
+ qCDebug(QT_BT_WINRT) << __FUNCTION__;
+ Q_Q(QLowEnergyController);
+ setState(QLowEnergyController::UnconnectedState);
+ emit q->disconnected();
+}
+
+ComPtr<IGattDeviceService> QLowEnergyControllerPrivate::getNativeService(const QBluetoothUuid &serviceUuid)
+{
+ ComPtr<IGattDeviceService> deviceService;
+ HRESULT hr;
+ hr = mDevice->GetGattService(serviceUuid, &deviceService);
+ if (FAILED(hr))
+ qCDebug(QT_BT_WINRT) << "Could not obtain native service for Uuid" << serviceUuid;
+ return deviceService;
+}
+
+ComPtr<IGattCharacteristic> QLowEnergyControllerPrivate::getNativeCharacteristic(const QBluetoothUuid &serviceUuid, const QBluetoothUuid &charUuid)
+{
+ ComPtr<IGattDeviceService> service = getNativeService(serviceUuid);
+ if (!service)
+ return nullptr;
+
+ ComPtr<IVectorView<GattCharacteristic *>> characteristics;
+ HRESULT hr = service->GetCharacteristics(charUuid, &characteristics);
+ RETURN_IF_FAILED("Could not obtain native characteristics for service", return nullptr);
+ ComPtr<IGattCharacteristic> characteristic;
+ hr = characteristics->GetAt(0, &characteristic);
+ RETURN_IF_FAILED("Could not obtain first characteristic for service", return nullptr);
+ return characteristic;
+}
+
+void QLowEnergyControllerPrivate::obtainIncludedServices(QSharedPointer<QLowEnergyServicePrivate> servicePointer,
+ ComPtr<IGattDeviceService> service)
+{
+ Q_Q(QLowEnergyController);
+ ComPtr<IGattDeviceService2> service2;
+ HRESULT hr = service.As(&service2);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IVectorView<GattDeviceService *>> includedServices;
+ hr = service2->GetAllIncludedServices(&includedServices);
+ Q_ASSERT_SUCCEEDED(hr);
+
+ uint count;
+ hr = includedServices->get_Size(&count);
+ Q_ASSERT_SUCCEEDED(hr);
+ for (uint i = 0; i < count; ++i) {
+ ComPtr<IGattDeviceService> includedService;
+ hr = includedServices->GetAt(i, &includedService);
+ Q_ASSERT_SUCCEEDED(hr);
+ GUID guuid;
+ hr = includedService->get_Uuid(&guuid);
+ Q_ASSERT_SUCCEEDED(hr);
+ const QBluetoothUuid includedUuid(guuid);
+ QSharedPointer<QLowEnergyServicePrivate> includedPointer;
+ if (serviceList.contains(includedUuid)) {
+ includedPointer = serviceList.value(includedUuid);
+ } else {
+ QLowEnergyServicePrivate *priv = new QLowEnergyServicePrivate();
+ priv->uuid = includedUuid;
+ priv->setController(this);
+
+ includedPointer = QSharedPointer<QLowEnergyServicePrivate>(priv);
+ serviceList.insert(includedUuid, includedPointer);
+ }
+ includedPointer->type |= QLowEnergyService::IncludedService;
+ servicePointer->includedServices.append(includedUuid);
+
+ obtainIncludedServices(includedPointer, includedService);
+
+ emit q->serviceDiscovered(includedUuid);
+ }
+}
+
+void QLowEnergyControllerPrivate::discoverServices()
+{
+ Q_Q(QLowEnergyController);
+
+ qCDebug(QT_BT_WINRT) << "Service discovery initiated";
+ ComPtr<IVectorView<GattDeviceService *>> deviceServices;
+ HRESULT hr = mDevice->get_GattServices(&deviceServices);
+ Q_ASSERT_SUCCEEDED(hr);
+ uint serviceCount;
+ hr = deviceServices->get_Size(&serviceCount);
+ Q_ASSERT_SUCCEEDED(hr);
+ for (uint i = 0; i < serviceCount; ++i) {
+ ComPtr<IGattDeviceService> deviceService;
+ hr = deviceServices->GetAt(i, &deviceService);
+ Q_ASSERT_SUCCEEDED(hr);
+ GUID guuid;
+ hr = deviceService->get_Uuid(&guuid);
+ Q_ASSERT_SUCCEEDED(hr);
+ const QBluetoothUuid service(guuid);
+
+ QSharedPointer<QLowEnergyServicePrivate> pointer;
+ if (serviceList.contains(service)) {
+ pointer = serviceList.value(service);
+ } else {
+ QLowEnergyServicePrivate *priv = new QLowEnergyServicePrivate();
+ priv->uuid = service;
+ priv->setController(this);
+
+ pointer = QSharedPointer<QLowEnergyServicePrivate>(priv);
+ serviceList.insert(service, pointer);
+ }
+ pointer->type |= QLowEnergyService::PrimaryService;
+
+ obtainIncludedServices(pointer, deviceService);
+
+ emit q->serviceDiscovered(service);
+ }
+
+ setState(QLowEnergyController::DiscoveredState);
+ emit q->discoveryFinished();
+}
+
+void QLowEnergyControllerPrivate::discoverServiceDetails(const QBluetoothUuid &service)
+{
+ qCDebug(QT_BT_WINRT) << __FUNCTION__ << service;
+ if (!serviceList.contains(service)) {
+ qCWarning(QT_BT_WINRT) << "Discovery done of unknown service:"
+ << service.toString();
+ return;
+ }
+
+ ComPtr<IGattDeviceService> deviceService = getNativeService(service);
+ if (!deviceService) {
+ qCDebug(QT_BT_WINRT) << "Could not obtain native service for uuid " << service;
+ return;
+ }
+
+ //update service data
+ QSharedPointer<QLowEnergyServicePrivate> pointer = serviceList.value(service);
+
+ pointer->setState(QLowEnergyService::DiscoveringServices);
+ ComPtr<IGattDeviceService2> deviceService2;
+ HRESULT hr = deviceService.As(&deviceService2);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IVectorView<GattDeviceService *>> deviceServices;
+ hr = deviceService2->GetAllIncludedServices(&deviceServices);
+ Q_ASSERT_SUCCEEDED(hr);
+ uint serviceCount;
+ hr = deviceServices->get_Size(&serviceCount);
+ Q_ASSERT_SUCCEEDED(hr);
+ for (uint i = 0; i < serviceCount; ++i) {
+ ComPtr<IGattDeviceService> includedService;
+ hr = deviceServices->GetAt(i, &includedService);
+ Q_ASSERT_SUCCEEDED(hr);
+ GUID guuid;
+ hr = includedService->get_Uuid(&guuid);
+ Q_ASSERT_SUCCEEDED(hr);
+
+ const QBluetoothUuid service(guuid);
+ if (service.isNull()) {
+ qCDebug(QT_BT_WINRT) << "Could not find service";
+ return;
+ }
+
+ pointer->includedServices.append(service);
+
+ // update the type of the included service
+ QSharedPointer<QLowEnergyServicePrivate> otherService = serviceList.value(service);
+ if (!otherService.isNull())
+ otherService->type |= QLowEnergyService::IncludedService;
+ }
+
+ QWinRTLowEnergyServiceHandler *worker = new QWinRTLowEnergyServiceHandler(service, deviceService2);
+ QThread *thread = new QThread;
+ worker->moveToThread(thread);
+ connect(thread, &QThread::started, worker, &QWinRTLowEnergyServiceHandler::obtainCharList);
+ connect(thread, &QThread::finished, thread, &QObject::deleteLater);
+ connect(thread, &QThread::finished, worker, &QObject::deleteLater);
+ connect(worker, &QWinRTLowEnergyServiceHandler::charListObtained,
+ [this, thread](const QBluetoothUuid &service, QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData> charList
+ , QLowEnergyHandle startHandle, QLowEnergyHandle endHandle) {
+ if (!serviceList.contains(service)) {
+ qCWarning(QT_BT_WINRT) << "Discovery done of unknown service:"
+ << service.toString();
+ return;
+ }
+
+ QSharedPointer<QLowEnergyServicePrivate> pointer = serviceList.value(service);
+ pointer->startHandle = startHandle;
+ pointer->endHandle = endHandle;
+ pointer->characteristicList = charList;
+ pointer->setState(QLowEnergyService::ServiceDiscovered);
+ thread->exit(0);
+ });
+ thread->start();
+}
+
+void QLowEnergyControllerPrivate::startAdvertising(const QLowEnergyAdvertisingParameters &, const QLowEnergyAdvertisingData &, const QLowEnergyAdvertisingData &)
+{
+ Q_UNIMPLEMENTED();
+}
+
+void QLowEnergyControllerPrivate::stopAdvertising()
+{
+ Q_UNIMPLEMENTED();
+}
+
+void QLowEnergyControllerPrivate::requestConnectionUpdate(const QLowEnergyConnectionParameters &)
+{
+ Q_UNIMPLEMENTED();
+}
+
+void QLowEnergyControllerPrivate::readCharacteristic(const QSharedPointer<QLowEnergyServicePrivate> service,
+ const QLowEnergyHandle charHandle)
+{
+ qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle;
+ Q_ASSERT(!service.isNull());
+ if (!service->characteristicList.contains(charHandle)) {
+ qCDebug(QT_BT_WINRT) << charHandle << "could not be found in service" << service->uuid;
+ service->setError(QLowEnergyService::CharacteristicReadError);
+ return;
+ }
+
+ HRESULT hr;
+ hr = QEventDispatcherWinRT::runOnXamlThread([charHandle, service, this]() {
+ const QLowEnergyServicePrivate::CharData charData = service->characteristicList.value(charHandle);
+ if (!(charData.properties & QLowEnergyCharacteristic::Read))
+ qCDebug(QT_BT_WINRT) << "Read flag is not set for characteristic" << charData.uuid;
+
+ ComPtr<IGattCharacteristic> characteristic = getNativeCharacteristic(service->uuid, charData.uuid);
+ if (!characteristic) {
+ qCDebug(QT_BT_WINRT) << "Could not obtain native characteristic" << charData.uuid
+ << "from service" << service->uuid;
+ service->setError(QLowEnergyService::CharacteristicReadError);
+ return S_OK;
+ }
+ ComPtr<IAsyncOperation<GattReadResult*>> readOp;
+ HRESULT hr = characteristic->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, &readOp);
+ Q_ASSERT_SUCCEEDED(hr);
+ auto readCompletedLambda = [charData, charHandle, service]
+ (IAsyncOperation<GattReadResult*> *op, AsyncStatus status)
+ {
+ if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) {
+ qCDebug(QT_BT_WINRT) << "Characteristic" << charHandle << "read operation failed.";
+ service->setError(QLowEnergyService::CharacteristicReadError);
+ return S_OK;
+ }
+ ComPtr<IGattReadResult> characteristicValue;
+ HRESULT hr;
+ hr = op->GetResults(&characteristicValue);
+ if (FAILED(hr)) {
+ qCDebug(QT_BT_WINRT) << "Could not obtain result for characteristic" << charHandle;
+ service->setError(QLowEnergyService::CharacteristicReadError);
+ return S_OK;
+ }
+
+ const QByteArray value = byteArrayFromGattResult(characteristicValue);
+ QLowEnergyServicePrivate::CharData charData = service->characteristicList.value(charHandle);
+ charData.value = value;
+ service->characteristicList.insert(charHandle, charData);
+ emit service->characteristicRead(QLowEnergyCharacteristic(service, charHandle), value);
+ return S_OK;
+ };
+ hr = readOp->put_Completed(Callback<IAsyncOperationCompletedHandler<GattReadResult *>>(readCompletedLambda).Get());
+ Q_ASSERT_SUCCEEDED(hr);
+ return S_OK;
+ });
+ Q_ASSERT_SUCCEEDED(hr);
+}
+
+void QLowEnergyControllerPrivate::readDescriptor(const QSharedPointer<QLowEnergyServicePrivate> service,
+ const QLowEnergyHandle charHandle,
+ const QLowEnergyHandle descHandle)
+{
+ qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle << descHandle;
+ Q_ASSERT(!service.isNull());
+ if (!service->characteristicList.contains(charHandle)) {
+ qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "in characteristic" << charHandle
+ << "cannot be found in service" << service->uuid;
+ service->setError(QLowEnergyService::DescriptorReadError);
+ return;
+ }
+
+ HRESULT hr;
+ hr = QEventDispatcherWinRT::runOnXamlThread([charHandle, descHandle, service, this]() {
+ QLowEnergyServicePrivate::CharData charData = service->characteristicList.value(charHandle);
+ ComPtr<IGattCharacteristic> characteristic = getNativeCharacteristic(service->uuid, charData.uuid);
+ if (!characteristic) {
+ qCDebug(QT_BT_WINRT) << "Could not obtain native characteristic" << charData.uuid
+ << "from service" << service->uuid;
+ service->setError(QLowEnergyService::DescriptorReadError);
+ return S_OK;
+ }
+
+ // Get native descriptor
+ if (!charData.descriptorList.contains(descHandle))
+ qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "cannot be found in characteristic" << charHandle;
+ QLowEnergyServicePrivate::DescData descData = charData.descriptorList.value(descHandle);
+ if (descData.uuid == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) {
+ ComPtr<IAsyncOperation<ClientCharConfigDescriptorResult *>> readOp;
+ HRESULT hr = characteristic->ReadClientCharacteristicConfigurationDescriptorAsync(&readOp);
+ Q_ASSERT_SUCCEEDED(hr);
+ auto readCompletedLambda = [&charData, charHandle, &descData, descHandle, service]
+ (IAsyncOperation<ClientCharConfigDescriptorResult *> *op, AsyncStatus status)
+ {
+ if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) {
+ qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "read operation failed";
+ service->setError(QLowEnergyService::DescriptorReadError);
+ return S_OK;
+ }
+ ComPtr<IClientCharConfigDescriptorResult> iValue;
+ HRESULT hr;
+ hr = op->GetResults(&iValue);
+ if (FAILED(hr)) {
+ qCDebug(QT_BT_WINRT) << "Could not obtain result for descriptor" << descHandle;
+ service->setError(QLowEnergyService::DescriptorReadError);
+ return S_OK;
+ }
+ GattClientCharacteristicConfigurationDescriptorValue value;
+ hr = iValue->get_ClientCharacteristicConfigurationDescriptor(&value);
+ if (FAILED(hr)) {
+ qCDebug(QT_BT_WINRT) << "Could not obtain value for descriptor" << descHandle;
+ service->setError(QLowEnergyService::DescriptorReadError);
+ return S_OK;
+ }
+ quint16 result = 0;
+ bool correct = false;
+ if (value & GattClientCharacteristicConfigurationDescriptorValue_Indicate) {
+ result |= QLowEnergyCharacteristic::Indicate;
+ correct = true;
+ }
+ if (value & GattClientCharacteristicConfigurationDescriptorValue_Notify) {
+ result |= QLowEnergyCharacteristic::Notify;
+ correct = true;
+ }
+ if (value == GattClientCharacteristicConfigurationDescriptorValue_None)
+ correct = true;
+ if (!correct) {
+ qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle
+ << "read operation failed. Obtained unexpected value.";
+ service->setError(QLowEnergyService::DescriptorReadError);
+ return S_OK;
+ }
+ descData.value = QByteArray(2, Qt::Uninitialized);
+ qToLittleEndian(result, descData.value.data());
+ charData.descriptorList.insert(descHandle, descData);
+ emit service->descriptorRead(QLowEnergyDescriptor(service, charHandle, descHandle),
+ descData.value);
+ return S_OK;
+ };
+ hr = readOp->put_Completed(Callback<IAsyncOperationCompletedHandler<ClientCharConfigDescriptorResult *>>(readCompletedLambda).Get());
+ Q_ASSERT_SUCCEEDED(hr);
+ return S_OK;
+ } else {
+ ComPtr<IVectorView<GattDescriptor *>> descriptors;
+ HRESULT hr = characteristic->GetDescriptors(descData.uuid, &descriptors);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IGattDescriptor> descriptor;
+ hr = descriptors->GetAt(0, &descriptor);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IAsyncOperation<GattReadResult*>> readOp;
+ hr = descriptor->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, &readOp);
+ Q_ASSERT_SUCCEEDED(hr);
+ auto readCompletedLambda = [&charData, charHandle, &descData, descHandle, service]
+ (IAsyncOperation<GattReadResult*> *op, AsyncStatus status)
+ {
+ if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) {
+ qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "read operation failed";
+ service->setError(QLowEnergyService::DescriptorReadError);
+ return S_OK;
+ }
+ ComPtr<IGattReadResult> descriptorValue;
+ HRESULT hr;
+ hr = op->GetResults(&descriptorValue);
+ if (FAILED(hr)) {
+ qCDebug(QT_BT_WINRT) << "Could not obtain result for descriptor" << descHandle;
+ service->setError(QLowEnergyService::DescriptorReadError);
+ return S_OK;
+ }
+ if (descData.uuid == QBluetoothUuid::CharacteristicUserDescription)
+ descData.value = byteArrayFromGattResult(descriptorValue, true);
+ else
+ descData.value = byteArrayFromGattResult(descriptorValue);
+ charData.descriptorList.insert(descHandle, descData);
+ emit service->descriptorRead(QLowEnergyDescriptor(service, charHandle, descHandle),
+ descData.value);
+ return S_OK;
+ };
+ hr = readOp->put_Completed(Callback<IAsyncOperationCompletedHandler<GattReadResult *>>(readCompletedLambda).Get());
+ return S_OK;
+ }
+ });
+ Q_ASSERT_SUCCEEDED(hr);
+}
+
+void QLowEnergyControllerPrivate::writeCharacteristic(const QSharedPointer<QLowEnergyServicePrivate> service,
+ const QLowEnergyHandle charHandle,
+ const QByteArray &newValue,
+ QLowEnergyService::WriteMode mode)
+{
+ qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle << newValue << mode;
+ Q_ASSERT(!service.isNull());
+ if (!service->characteristicList.contains(charHandle)) {
+ qCDebug(QT_BT_WINRT) << "Characteristic" << charHandle << "cannot be found in service" << service->uuid;
+ service->setError(QLowEnergyService::CharacteristicWriteError);
+ return;
+ }
+
+ QLowEnergyServicePrivate::CharData charData = service->characteristicList.value(charHandle);
+ const bool writeWithResponse = mode == QLowEnergyService::WriteWithResponse;
+ if (!(charData.properties & (writeWithResponse ? QLowEnergyCharacteristic::Write : QLowEnergyCharacteristic::WriteNoResponse)))
+ qCDebug(QT_BT_WINRT) << "Write flag is not set for characteristic" << charHandle;
+
+ HRESULT hr;
+ hr = QEventDispatcherWinRT::runOnXamlThread([charData, charHandle, this, service, newValue, writeWithResponse]() {
+ ComPtr<IGattCharacteristic> characteristic = getNativeCharacteristic(service->uuid, charData.uuid);
+ if (!characteristic) {
+ qCDebug(QT_BT_WINRT) << "Could not obtain native characteristic" << charData.uuid
+ << "from service" << service->uuid;
+ service->setError(QLowEnergyService::CharacteristicWriteError);
+ return S_OK;
+ }
+ ComPtr<ABI::Windows::Storage::Streams::IBufferFactory> bufferFactory;
+ HRESULT hr = GetActivationFactory(HStringReference(RuntimeClass_Windows_Storage_Streams_Buffer).Get(), &bufferFactory);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer;
+ const int length = newValue.length();
+ hr = bufferFactory->Create(length, &buffer);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = buffer->put_Length(length);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteAccess;
+ hr = buffer.As(&byteAccess);
+ Q_ASSERT_SUCCEEDED(hr);
+ byte *bytes;
+ hr = byteAccess->Buffer(&bytes);
+ Q_ASSERT_SUCCEEDED(hr);
+ memcpy(bytes, newValue, length);
+ ComPtr<IAsyncOperation<GattCommunicationStatus>> writeOp;
+ GattWriteOption option = writeWithResponse ? GattWriteOption_WriteWithResponse : GattWriteOption_WriteWithoutResponse;
+ hr = characteristic->WriteValueWithOptionAsync(buffer.Get(), option, &writeOp);
+ Q_ASSERT_SUCCEEDED(hr);
+ auto writeCompletedLambda =[charData, charHandle, newValue, service, this]
+ (IAsyncOperation<GattCommunicationStatus> *op, AsyncStatus status)
+ {
+ if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) {
+ qCDebug(QT_BT_WINRT) << "Characteristic" << charHandle << "write operation failed";
+ service->setError(QLowEnergyService::CharacteristicWriteError);
+ return S_OK;
+ }
+ GattCommunicationStatus result;
+ HRESULT hr;
+ hr = op->GetResults(&result);
+ if (hr == E_BLUETOOTH_ATT_INVALID_ATTRIBUTE_VALUE_LENGTH) {
+ qCDebug(QT_BT_WINRT) << "Characteristic" << charHandle << "write operation was tried with invalid value length";
+ service->setError(QLowEnergyService::CharacteristicWriteError);
+ return S_OK;
+ }
+ Q_ASSERT_SUCCEEDED(hr);
+ if (result != GattCommunicationStatus_Success) {
+ qCDebug(QT_BT_WINRT) << "Characteristic" << charHandle << "write operation failed";
+ service->setError(QLowEnergyService::CharacteristicWriteError);
+ return S_OK;
+ }
+ // only update cache when property is readable. Otherwise it remains
+ // empty.
+ if (charData.properties & QLowEnergyCharacteristic::Read)
+ updateValueOfCharacteristic(charHandle, newValue, false);
+ emit service->characteristicWritten(QLowEnergyCharacteristic(service, charHandle), newValue);
+ return S_OK;
+ };
+ hr = writeOp->put_Completed(Callback<IAsyncOperationCompletedHandler<GattCommunicationStatus>>(writeCompletedLambda).Get());
+ Q_ASSERT_SUCCEEDED(hr);
+ return S_OK;
+ });
+ Q_ASSERT_SUCCEEDED(hr);
+}
+
+void QLowEnergyControllerPrivate::writeDescriptor(
+ const QSharedPointer<QLowEnergyServicePrivate> service,
+ const QLowEnergyHandle charHandle,
+ const QLowEnergyHandle descHandle,
+ const QByteArray &newValue)
+{
+ qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle << descHandle << newValue;
+ Q_ASSERT(!service.isNull());
+ if (!service->characteristicList.contains(charHandle)) {
+ qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "in characteristic" << charHandle
+ << "could not be found in service" << service->uuid;
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ return;
+ }
+
+ HRESULT hr;
+ hr = QEventDispatcherWinRT::runOnXamlThread([charHandle, descHandle, this, service, newValue]() {
+ const QLowEnergyServicePrivate::CharData charData = service->characteristicList.value(charHandle);
+ ComPtr<IGattCharacteristic> characteristic = getNativeCharacteristic(service->uuid, charData.uuid);
+ if (!characteristic) {
+ qCDebug(QT_BT_WINRT) << "Could not obtain native characteristic" << charData.uuid
+ << "from service" << service->uuid;
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ return S_OK;
+ }
+
+ // Get native descriptor
+ if (!charData.descriptorList.contains(descHandle))
+ qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "could not be found in Characteristic" << charHandle;
+
+ QLowEnergyServicePrivate::DescData descData = charData.descriptorList.value(descHandle);
+ if (descData.uuid == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) {
+ GattClientCharacteristicConfigurationDescriptorValue value;
+ quint16 intValue = qFromLittleEndian<quint16>(newValue);
+ if (intValue & GattClientCharacteristicConfigurationDescriptorValue_Indicate && intValue & GattClientCharacteristicConfigurationDescriptorValue_Notify) {
+ qCWarning(QT_BT_WINRT) << "Setting both Indicate and Notify is not supported on WinRT";
+ value = (GattClientCharacteristicConfigurationDescriptorValue)(GattClientCharacteristicConfigurationDescriptorValue_Indicate | GattClientCharacteristicConfigurationDescriptorValue_Notify);
+ } else if (intValue & GattClientCharacteristicConfigurationDescriptorValue_Indicate) {
+ value = GattClientCharacteristicConfigurationDescriptorValue_Indicate;
+ } else if (intValue & GattClientCharacteristicConfigurationDescriptorValue_Notify) {
+ value = GattClientCharacteristicConfigurationDescriptorValue_Notify;
+ } else if (intValue == 0) {
+ value = GattClientCharacteristicConfigurationDescriptorValue_None;
+ } else {
+ Q_ASSERT(false);
+ }
+ ComPtr<IAsyncOperation<enum GattCommunicationStatus>> writeOp;
+ HRESULT hr = characteristic->WriteClientCharacteristicConfigurationDescriptorAsync(value, &writeOp);
+ Q_ASSERT_SUCCEEDED(hr);
+ auto writeCompletedLambda = [charHandle, descHandle, newValue, service, this]
+ (IAsyncOperation<GattCommunicationStatus> *op, AsyncStatus status)
+ {
+ if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) {
+ qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "write operation failed";
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ return S_OK;
+ }
+ GattCommunicationStatus result;
+ HRESULT hr;
+ hr = op->GetResults(&result);
+ if (FAILED(hr)) {
+ qCDebug(QT_BT_WINRT) << "Could not obtain result for descriptor" << descHandle;
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ return S_OK;
+ }
+ if (result != GattCommunicationStatus_Success) {
+ qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "write operation failed";
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ return S_OK;
+ }
+ updateValueOfDescriptor(charHandle, descHandle, newValue, false);
+ emit service->descriptorWritten(QLowEnergyDescriptor(service, charHandle, descHandle), newValue);
+ return S_OK;
+ };
+ hr = writeOp->put_Completed(Callback<IAsyncOperationCompletedHandler<GattCommunicationStatus >>(writeCompletedLambda).Get());
+ Q_ASSERT_SUCCEEDED(hr);
+ } else {
+ ComPtr<IVectorView<GattDescriptor *>> descriptors;
+ HRESULT hr = characteristic->GetDescriptors(descData.uuid, &descriptors);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IGattDescriptor> descriptor;
+ hr = descriptors->GetAt(0, &descriptor);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<ABI::Windows::Storage::Streams::IBufferFactory> bufferFactory;
+ hr = GetActivationFactory(HStringReference(RuntimeClass_Windows_Storage_Streams_Buffer).Get(), &bufferFactory);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer;
+ const int length = newValue.length();
+ hr = bufferFactory->Create(length, &buffer);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = buffer->put_Length(length);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteAccess;
+ hr = buffer.As(&byteAccess);
+ Q_ASSERT_SUCCEEDED(hr);
+ byte *bytes;
+ hr = byteAccess->Buffer(&bytes);
+ Q_ASSERT_SUCCEEDED(hr);
+ memcpy(bytes, newValue, length);
+ ComPtr<IAsyncOperation<GattCommunicationStatus>> writeOp;
+ hr = descriptor->WriteValueAsync(buffer.Get(), &writeOp);
+ Q_ASSERT_SUCCEEDED(hr);
+ auto writeCompletedLambda = [charHandle, descHandle, newValue, service, this]
+ (IAsyncOperation<GattCommunicationStatus> *op, AsyncStatus status)
+ {
+ if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) {
+ qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "write operation failed";
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ return S_OK;
+ }
+ GattCommunicationStatus result;
+ HRESULT hr;
+ hr = op->GetResults(&result);
+ if (FAILED(hr)) {
+ qCDebug(QT_BT_WINRT) << "Could not obtain result for descriptor" << descHandle;
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ return S_OK;
+ }
+ if (result != GattCommunicationStatus_Success) {
+ qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "write operation failed";
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ return S_OK;
+ }
+ updateValueOfDescriptor(charHandle, descHandle, newValue, false);
+ emit service->descriptorWritten(QLowEnergyDescriptor(service, charHandle, descHandle), newValue);
+ return S_OK;
+ };
+ hr = writeOp->put_Completed(Callback<IAsyncOperationCompletedHandler<GattCommunicationStatus>>(writeCompletedLambda).Get());
+ Q_ASSERT_SUCCEEDED(hr);
+ return S_OK;
+ }
+ return S_OK;
+ });
+ Q_ASSERT_SUCCEEDED(hr);
+}
+
+
+void QLowEnergyControllerPrivate::addToGenericAttributeList(const QLowEnergyServiceData &, QLowEnergyHandle)
+{
+ Q_UNIMPLEMENTED();
+}
+
+QT_END_NAMESPACE
+
+#include "qlowenergycontroller_winrt.moc"
diff --git a/src/bluetooth/qlowenergyservice_osx.mm b/src/bluetooth/qlowenergyservice_osx.mm
index 97d64040..52c2ac87 100644
--- a/src/bluetooth/qlowenergyservice_osx.mm
+++ b/src/bluetooth/qlowenergyservice_osx.mm
@@ -208,7 +208,9 @@ void QLowEnergyService::writeCharacteristic(const QLowEnergyCharacteristic &ch,
WriteMode mode)
{
QLowEnergyControllerPrivateOSX *const controller = qt_mac_le_controller(d_ptr);
- if (controller == Q_NULLPTR || state() != ServiceDiscovered || !contains(ch)) {
+ if (controller == Q_NULLPTR ||
+ (controller->role == QLowEnergyController::CentralRole && state() != ServiceDiscovered) ||
+ !contains(ch)) {
d_ptr->setError(QLowEnergyService::OperationError);
return;
}
@@ -250,6 +252,8 @@ void QLowEnergyService::writeDescriptor(const QLowEnergyDescriptor &descriptor,
{
QLowEnergyControllerPrivateOSX *const controller = qt_mac_le_controller(d_ptr);
if (controller == Q_NULLPTR || state() != ServiceDiscovered || !contains(descriptor)) {
+ // This operation error also includes LE controller in the peripheral role:
+ // on iOS/OS X - descriptors are immutable.
d_ptr->setError(OperationError);
return;
}
diff --git a/src/imports/bluetooth/plugin.cpp b/src/imports/bluetooth/plugin.cpp
index eca5736e..d6c47e35 100644
--- a/src/imports/bluetooth/plugin.cpp
+++ b/src/imports/bluetooth/plugin.cpp
@@ -57,7 +57,7 @@ QT_USE_NAMESPACE
class QBluetoothQmlPlugin : public QQmlExtensionPlugin
{
Q_OBJECT
- Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
+ Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
public:
QBluetoothQmlPlugin(QObject *parent = 0) : QQmlExtensionPlugin(parent) { initResources(); }
void registerTypes(const char *uri)
diff --git a/src/imports/nfc/plugin.cpp b/src/imports/nfc/plugin.cpp
index 5f2ccd26..b71f1f24 100644
--- a/src/imports/nfc/plugin.cpp
+++ b/src/imports/nfc/plugin.cpp
@@ -59,7 +59,7 @@ QT_USE_NAMESPACE
class QNfcQmlPlugin : public QQmlExtensionPlugin
{
Q_OBJECT
- Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
+ Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
public:
QNfcQmlPlugin(QObject *parent = 0) : QQmlExtensionPlugin(parent) { initResources(); }
diff --git a/tests/auto/qbluetoothdevicediscoveryagent/qbluetoothdevicediscoveryagent.pro b/tests/auto/qbluetoothdevicediscoveryagent/qbluetoothdevicediscoveryagent.pro
index 36c88cdc..e012ae52 100644
--- a/tests/auto/qbluetoothdevicediscoveryagent/qbluetoothdevicediscoveryagent.pro
+++ b/tests/auto/qbluetoothdevicediscoveryagent/qbluetoothdevicediscoveryagent.pro
@@ -4,3 +4,7 @@ CONFIG += testcase
QT = core concurrent bluetooth testlib
osx:QT += widgets
+
+config_bluez:qtHaveModule(dbus) {
+ DEFINES += QT_BLUEZ_BLUETOOTH
+}
diff --git a/tests/auto/qbluetoothdevicediscoveryagent/tst_qbluetoothdevicediscoveryagent.cpp b/tests/auto/qbluetoothdevicediscoveryagent/tst_qbluetoothdevicediscoveryagent.cpp
index 192173c8..efc4d8a6 100644
--- a/tests/auto/qbluetoothdevicediscoveryagent/tst_qbluetoothdevicediscoveryagent.cpp
+++ b/tests/auto/qbluetoothdevicediscoveryagent/tst_qbluetoothdevicediscoveryagent.cpp
@@ -77,8 +77,13 @@ private slots:
void tst_deviceDiscovery_data();
void tst_deviceDiscovery();
+
+ void tst_discoveryTimeout();
+
+ void tst_discoveryMethods();
private:
int noOfLocalDevices;
+ bool isBluez5Runtime = false;
};
tst_QBluetoothDeviceDiscoveryAgent::tst_QBluetoothDeviceDiscoveryAgent()
@@ -91,12 +96,59 @@ tst_QBluetoothDeviceDiscoveryAgent::~tst_QBluetoothDeviceDiscoveryAgent()
{
}
+#ifdef QT_BLUEZ_BLUETOOTH
+// This section was adopted from tst_qloggingcategory.cpp
+QString logMessage;
+
+QByteArray qMyMessageFormatString(QtMsgType type, const QMessageLogContext &context,
+ const QString &str)
+{
+ QByteArray message;
+ message.append(context.category);
+ switch (type) {
+ case QtDebugMsg: message.append(".debug"); break;
+ case QtInfoMsg: message.append(".info"); break;
+ case QtWarningMsg: message.append(".warning"); break;
+ case QtCriticalMsg:message.append(".critical"); break;
+ case QtFatalMsg: message.append(".fatal"); break;
+ }
+ message.append(": ");
+ message.append(qPrintable(str));
+
+ return message.simplified();
+}
+
+static void myCustomMessageHandler(QtMsgType type,
+ const QMessageLogContext &context,
+ const QString &msg)
+{
+ logMessage = qMyMessageFormatString(type, context, msg);
+}
+#endif
+
+
+
void tst_QBluetoothDeviceDiscoveryAgent::initTestCase()
{
qRegisterMetaType<QBluetoothDeviceInfo>();
qRegisterMetaType<QBluetoothDeviceDiscoveryAgent::InquiryType>();
+#ifdef QT_BLUEZ_BLUETOOTH
+ // To distinguish Bluez 4 and 5 we peek into the debug output
+ // of first Bluetooth ctor. It executes a runtime test and prints the result
+ // as logging output. This avoids more complex runtime detection logic within this unit test.
+ QtMessageHandler oldMessageHandler;
+ oldMessageHandler = qInstallMessageHandler(myCustomMessageHandler);
+
+ noOfLocalDevices = QBluetoothLocalDevice::allDevices().count();
+ qInstallMessageHandler(oldMessageHandler);
+ isBluez5Runtime = logMessage.contains(QStringLiteral("Bluez 5"));
+ if (isBluez5Runtime)
+ qDebug() << "BlueZ 5 runtime detected.";
+#else
noOfLocalDevices = QBluetoothLocalDevice::allDevices().count();
+#endif
+
if (!noOfLocalDevices)
return;
@@ -419,10 +471,10 @@ void tst_QBluetoothDeviceDiscoveryAgent::tst_deviceDiscovery()
}
}
}
-#ifdef Q_OS_IOS
- //On iOS, we do not have access to the local device/adapter, numberOfAdapters is 0,
+#if defined(Q_OS_IOS) || defined(Q_OS_WINRT)
+ //On iOS/WinRT, we do not have access to the local device/adapter, numberOfAdapters is 0,
//so we skip this test at all.
- QSKIP("iOS: no local Bluetooth device available. Skipping remaining part of test.");
+ QSKIP("iOS/WinRT: no local Bluetooth device available. Skipping remaining part of test.");
#endif
//For multiple Bluetooth adapter do the check only for GeneralUnlimitedInquiry.
@@ -431,6 +483,165 @@ void tst_QBluetoothDeviceDiscoveryAgent::tst_deviceDiscovery()
}
}
+
+void tst_QBluetoothDeviceDiscoveryAgent::tst_discoveryTimeout()
+{
+ QBluetoothDeviceDiscoveryAgent agent;
+
+ // check default values
+#if defined(Q_OS_OSX) || defined(Q_OS_IOS) || defined(Q_OS_ANDROID) || defined(Q_OS_WINRT)
+ QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 25000);
+ agent.setLowEnergyDiscoveryTimeout(-1); // negative ignored
+ QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 25000);
+ agent.setLowEnergyDiscoveryTimeout(20000);
+ QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 20000);
+#elif defined(QT_BLUEZ_BLUETOOTH)
+ if (isBluez5Runtime) {
+ QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 20000);
+ agent.setLowEnergyDiscoveryTimeout(-1); // negative ignored
+ QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 20000);
+ agent.setLowEnergyDiscoveryTimeout(25000);
+ QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 25000);
+ } else {
+ QCOMPARE(agent.lowEnergyDiscoveryTimeout(), -1);
+ agent.setLowEnergyDiscoveryTimeout(20000); // feature not supported -> ignored
+ QCOMPARE(agent.lowEnergyDiscoveryTimeout(), -1);
+ }
+#else
+ QCOMPARE(agent.lowEnergyDiscoveryTimeout(), -1);
+ agent.setLowEnergyDiscoveryTimeout(20000); // feature not supported -> ignored
+ QCOMPARE(agent.lowEnergyDiscoveryTimeout(), -1);
+#endif
+}
+
+void tst_QBluetoothDeviceDiscoveryAgent::tst_discoveryMethods()
+{
+ const QBluetoothLocalDevice localDevice;
+ if (localDevice.allDevices().size() != 1) {
+ // On iOS it returns 0 but we still have working BT.
+#ifndef Q_OS_IOS
+ QSKIP("This test expects exactly one local device working");
+#endif
+ }
+
+ const QBluetoothDeviceDiscoveryAgent::DiscoveryMethods
+ supportedMethods = QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods();
+
+ QVERIFY(supportedMethods != QBluetoothDeviceDiscoveryAgent::NoMethod);
+
+ QBluetoothDeviceDiscoveryAgent::DiscoveryMethod
+ unsupportedMethods = QBluetoothDeviceDiscoveryAgent::NoMethod;
+ QBluetoothDeviceInfo::CoreConfiguration
+ expectedConfiguration = QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration;
+
+ if (supportedMethods == QBluetoothDeviceDiscoveryAgent::ClassicMethod) {
+ unsupportedMethods = QBluetoothDeviceDiscoveryAgent::LowEnergyMethod;
+ expectedConfiguration = QBluetoothDeviceInfo::BaseRateCoreConfiguration;
+ } else if (supportedMethods == QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) {
+ unsupportedMethods = QBluetoothDeviceDiscoveryAgent::ClassicMethod;
+ expectedConfiguration = QBluetoothDeviceInfo::LowEnergyCoreConfiguration;
+ }
+
+ QBluetoothDeviceDiscoveryAgent agent;
+ QSignalSpy finishedSpy(&agent, SIGNAL(finished()));
+ QSignalSpy errorSpy(&agent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error)));
+ QSignalSpy discoveredSpy(&agent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)));
+
+ // NoMethod - should just immediately return:
+ agent.start(QBluetoothDeviceDiscoveryAgent::NoMethod);
+ QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::NoError);
+ QVERIFY(!agent.isActive());
+ QCOMPARE(finishedSpy.size(), 0);
+ QCOMPARE(errorSpy.size(), 0);
+ QCOMPARE(discoveredSpy.size(), 0);
+
+ if (unsupportedMethods != QBluetoothDeviceDiscoveryAgent::NoMethod) {
+ agent.start(unsupportedMethods);
+ QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod);
+ QVERIFY(!agent.isActive());
+ QVERIFY(finishedSpy.isEmpty());
+ QCOMPARE(errorSpy.size(), 1);
+ errorSpy.clear();
+ QVERIFY(discoveredSpy.isEmpty());
+ }
+
+ // Start discovery, probably both Classic and LE methods:
+ agent.start(supportedMethods);
+ QVERIFY(agent.isActive());
+ QVERIFY(errorSpy.isEmpty());
+
+
+#define RUN_DISCOVERY(maxTimeout, step, condition) \
+ for (int scanTime = maxTimeout; (condition) && scanTime > 0; scanTime -= step) \
+ QTest::qWait(step);
+
+ // Wait for up to MaxScanTime for the scan to finish
+ const int timeStep = 15000;
+ RUN_DISCOVERY(MaxScanTime, timeStep, finishedSpy.isEmpty())
+
+ QVERIFY(!agent.isActive());
+ QVERIFY(errorSpy.size() <= 1);
+
+ if (errorSpy.size()) {
+ // For example, old iOS device could report it supports LE method,
+ // but it actually does not.
+ QVERIFY(supportedMethods == QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
+ QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod);
+ } else {
+ QVERIFY(finishedSpy.count() == 1);
+ QVERIFY(agent.error() == QBluetoothDeviceDiscoveryAgent::NoError);
+ QVERIFY(agent.errorString().isEmpty());
+
+ while (!discoveredSpy.isEmpty()) {
+ const QBluetoothDeviceInfo info =
+ qvariant_cast<QBluetoothDeviceInfo>(discoveredSpy.takeFirst().at(0));
+ QVERIFY(info.isValid());
+ QVERIFY(info.coreConfigurations() & expectedConfiguration);
+ }
+ }
+
+ if (unsupportedMethods != QBluetoothDeviceDiscoveryAgent::NoMethod)
+ return;
+
+ // Both methods were reported as supported. We already tested them
+ // above, now let's test first Classic then LE.
+ finishedSpy.clear();
+ errorSpy.clear();
+ discoveredSpy.clear();
+
+ agent.start(QBluetoothDeviceDiscoveryAgent::ClassicMethod);
+ QVERIFY(agent.isActive());
+ QVERIFY(errorSpy.isEmpty());
+ QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::NoError);
+
+ RUN_DISCOVERY(MaxScanTime, timeStep, finishedSpy.isEmpty())
+
+ QVERIFY(!agent.isActive());
+ QVERIFY(errorSpy.isEmpty());
+ QCOMPARE(finishedSpy.size(), 1);
+
+ finishedSpy.clear();
+ discoveredSpy.clear();
+
+ agent.start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
+ QVERIFY(agent.isActive());
+ QVERIFY(errorSpy.isEmpty());
+
+ RUN_DISCOVERY(MaxScanTime, timeStep, finishedSpy.isEmpty() && errorSpy.isEmpty())
+
+ QVERIFY(!agent.isActive());
+ QVERIFY(errorSpy.size() <= 1);
+
+ if (errorSpy.size()) {
+ QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod);
+ qDebug() << "QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods is inaccurate"
+ " on your platform/with your device, LowEnergyMethod is not supported";
+ } else {
+ QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::NoError);
+ QCOMPARE(finishedSpy.size(), 1);
+ }
+}
+
QTEST_MAIN(tst_QBluetoothDeviceDiscoveryAgent)
#include "tst_qbluetoothdevicediscoveryagent.moc"
diff --git a/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp b/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp
index 773c673b..4acfe4d1 100644
--- a/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp
+++ b/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp
@@ -120,7 +120,7 @@ tst_QLowEnergyController::~tst_QLowEnergyController()
void tst_QLowEnergyController::initTestCase()
{
-#ifndef Q_OS_MAC
+#if !defined(Q_OS_MAC) && !defined(Q_OS_WINRT)
if (remoteDevice.isNull()
|| QBluetoothLocalDevice::allDevices().isEmpty()) {
qWarning("No remote device or local adapter found.");
@@ -244,7 +244,7 @@ void tst_QLowEnergyController::tst_connect()
{
QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices();
-#if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
+#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) || defined(Q_OS_WINRT)
if (!remoteDeviceInfo.isValid())
#else
if (localAdapters.isEmpty() || !remoteDeviceInfo.isValid())
@@ -260,7 +260,7 @@ void tst_QLowEnergyController::tst_connect()
else
QCOMPARE(control.remoteName(), remoteDeviceInfo.name());
-#if !defined(Q_OS_IOS) && !defined(Q_OS_TVOS)
+#if !defined(Q_OS_IOS) && !defined(Q_OS_TVOS) && !defined(Q_OS_WINRT)
const QBluetoothAddress localAdapter = localAdapters.at(0).address();
QCOMPARE(control.localAddress(), localAdapter);
QVERIFY(!control.localAddress().isNull());
@@ -403,7 +403,7 @@ void tst_QLowEnergyController::tst_connect()
void tst_QLowEnergyController::tst_concurrentDiscovery()
{
-#ifndef Q_OS_MAC
+#if !defined(Q_OS_MAC) && !defined(Q_OS_WINRT)
QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices();
if (localAdapters.isEmpty())
QSKIP("No local Bluetooth device found. Skipping test.");
@@ -440,7 +440,7 @@ void tst_QLowEnergyController::tst_concurrentDiscovery()
30000);
}
-#ifdef Q_OS_ANDROID
+#if defined(Q_OS_ANDROID) || defined(Q_OS_WINRT)
QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
QCOMPARE(control2.state(), QLowEnergyController::ConnectedState);
control2.disconnectFromDevice();
@@ -1642,7 +1642,7 @@ void tst_QLowEnergyController::tst_defaultBehavior()
void tst_QLowEnergyController::tst_writeCharacteristic()
{
-#ifndef Q_OS_MAC
+#if !defined(Q_OS_MAC) && !defined(Q_OS_WINRT)
QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices();
if (localAdapters.isEmpty())
QSKIP("No local Bluetooth device found. Skipping test.");
@@ -1816,7 +1816,7 @@ void tst_QLowEnergyController::tst_writeCharacteristic()
void tst_QLowEnergyController::tst_readWriteDescriptor()
{
-#ifndef Q_OS_MAC
+#if !defined(Q_OS_MAC) && !defined(Q_OS_WINRT)
QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices();
if (localAdapters.isEmpty())
QSKIP("No local Bluetooth device found. Skipping test.");
@@ -2239,7 +2239,7 @@ void tst_QLowEnergyController::tst_customProgrammableDevice()
*/
void tst_QLowEnergyController::tst_errorCases()
{
-#ifndef Q_OS_MAC
+#if !defined(Q_OS_MAC) && !defined(Q_OS_WINRT)
QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices();
if (localAdapters.isEmpty())
QSKIP("No local Bluetooth device found. Skipping test.");
@@ -2461,7 +2461,7 @@ void tst_QLowEnergyController::tst_errorCases()
*/
void tst_QLowEnergyController::tst_writeCharacteristicNoResponse()
{
-#ifndef Q_OS_MAC
+#if !defined(Q_OS_MAC) && !defined(Q_OS_WINRT)
QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices();
if (localAdapters.isEmpty())
QSKIP("No local Bluetooth device found. Skipping test.");