7#include "darwin/btledeviceinquiry_p.h"
10#include "darwin/btdeviceinquiry_p.h"
11#include "darwin/btsdpinquiry_p.h"
13#include <IOBluetooth/IOBluetooth.h>
17#include "darwin/btnotifier_p.h"
18#include "darwin/btutility_p.h"
19#include "darwin/uistrings_p.h"
21#include "darwin/uistrings_p.h"
23#include "darwin/btraii_p.h"
26#include <QtCore/qloggingcategory.h>
27#include <QtCore/qcoreapplication.h>
28#include <QtCore/qpermissions.h>
29#include <QtCore/qvector.h>
30#include <QtCore/qglobal.h>
31#include <QtCore/qstring.h>
32#include <QtCore/qdebug.h>
34#include <Foundation/Foundation.h>
36#include <CoreBluetooth/CoreBluetooth.h>
43void registerQDeviceDiscoveryMetaType()
45 static bool initDone =
false;
47 qRegisterMetaType<QBluetoothDeviceInfo>();
48 qRegisterMetaType<QBluetoothDeviceDiscoveryAgent::Error>();
55QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(
const QBluetoothAddress &adapter,
56 QBluetoothDeviceDiscoveryAgent *q) :
57 adapterAddress(adapter),
58 agentState(NonActive),
59 lowEnergySearchTimeout(DarwinBluetooth::defaultLEScanTimeoutMS),
61 requestedMethods(QBluetoothDeviceDiscoveryAgent::ClassicMethod | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod),
63 requestedMethods(QBluetoothDeviceDiscoveryAgent::ClassicMethod),
67 registerQDeviceDiscoveryMetaType();
69 Q_ASSERT_X(q !=
nullptr, Q_FUNC_INFO,
"invalid q_ptr (null)");
72QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate()
74 if (inquiryLE && agentState != NonActive) {
76 if (dispatch_queue_t leQueue = DarwinBluetooth::qt_LE_queue()) {
78 DarwinBTLEDeviceInquiry *inq = inquiryLE.getAs<DarwinBTLEDeviceInquiry>();
79 dispatch_sync(leQueue, ^{
86bool QBluetoothDeviceDiscoveryAgentPrivate::isActive()
const
94 return agentState != NonActive;
97void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)
99 using namespace DarwinBluetooth;
101 Q_ASSERT(!isActive());
102 Q_ASSERT(methods & (QBluetoothDeviceDiscoveryAgent::ClassicMethod
103 | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod));
107 IOBluetoothHostController *hostController = [IOBluetoothHostController defaultController];
108 if (!hostController) {
109 qCWarning(QT_BT_DARWIN) <<
"No default Bluetooth controller found";
110 setError(QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError);
111 emit q_ptr->errorOccurred(lastError);
113 }
else if ([hostController powerState] != kBluetoothHCIPowerStateON) {
114 qCWarning(QT_BT_DARWIN) <<
"Default Bluetooth controller is OFF";
115 setError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
116 emit q_ptr->errorOccurred(lastError);
118 }
else if (!adapterAddress.isNull()) {
120 NSString *
const hciAddress = [hostController addressAsString];
121 if (adapterAddress != QBluetoothAddress(QString::fromNSString(hciAddress))) {
122 qCWarning(QT_BT_DARWIN) <<
"Provided address" << adapterAddress
123 <<
"does not match with adapter:" << hciAddress;
124 setError(QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError);
125 emit q_ptr->errorOccurred(lastError);
129 controller.reset(hostController, DarwinBluetooth::RetainPolicy::doInitialRetain);
139 if (methods.testFlag(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)) {
140 const auto permissionStatus = qApp->checkPermission(QBluetoothPermission{});
141 if (permissionStatus != Qt::PermissionStatus::Granted) {
142 qCWarning(QT_BT_DARWIN,
143 "Use of Bluetooth LE requires explicitly requested permissions.");
144 setError(QBluetoothDeviceDiscoveryAgent::MissingPermissionsError);
145 emit q_ptr->errorOccurred(lastError);
152 requestedMethods = methods;
162 agentState = NonActive;
163 discoveredDevices.clear();
164 setError(QBluetoothDeviceDiscoveryAgent::NoError);
166 if (requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod)
167 return startClassic();
175void QBluetoothDeviceDiscoveryAgentPrivate::startClassic()
177 Q_ASSERT(!isActive());
178 Q_ASSERT(lastError == QBluetoothDeviceDiscoveryAgent::NoError);
179 Q_ASSERT(requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod);
180 Q_ASSERT(agentState == NonActive);
182 DarwinBluetooth::qt_test_iobluetooth_runloop();
186 inquiry.reset([[DarwinBTClassicDeviceInquiry alloc] initWithDelegate:
this],
187 DarwinBluetooth::RetainPolicy::noInitialRetain);
190 qCCritical(QT_BT_DARWIN) <<
"failed to initialize an Classic device inquiry";
191 setError(QBluetoothDeviceDiscoveryAgent::UnknownError,
192 QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STARTED));
193 emit q_ptr->errorOccurred(lastError);
198 agentState = ClassicScan;
200 const IOReturn res = [inquiry.getAs<DarwinBTClassicDeviceInquiry>() start];
201 if (res != kIOReturnSuccess) {
202 setError(res, QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STARTED));
203 agentState = NonActive;
204 emit q_ptr->errorOccurred(lastError);
210void QBluetoothDeviceDiscoveryAgentPrivate::startLE()
212 Q_ASSERT(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError);
213 Q_ASSERT(requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
215 using namespace DarwinBluetooth;
217 std::unique_ptr<LECBManagerNotifier> notifier = std::make_unique<LECBManagerNotifier>();
219 using ErrMemFunPtr =
void (LECBManagerNotifier::*)(QBluetoothDeviceDiscoveryAgent::Error);
220 notifier->connect(notifier.get(), ErrMemFunPtr(&LECBManagerNotifier::CBManagerError),
221 this, &QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError);
222 notifier->connect(notifier.get(), &LECBManagerNotifier::LEnotSupported,
223 this, &QBluetoothDeviceDiscoveryAgentPrivate::LEnotSupported);
224 notifier->connect(notifier.get(), &LECBManagerNotifier::discoveryFinished,
225 this, &QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished);
226 using DeviceMemFunPtr =
void (QBluetoothDeviceDiscoveryAgentPrivate::*)(
const QBluetoothDeviceInfo &);
227 notifier->connect(notifier.get(), &LECBManagerNotifier::deviceDiscovered,
228 this, DeviceMemFunPtr(&QBluetoothDeviceDiscoveryAgentPrivate::deviceFound));
231 inquiryLE.reset([[DarwinBTLEDeviceInquiry alloc] initWithNotifier:notifier.get()],
232 DarwinBluetooth::RetainPolicy::noInitialRetain);
236 dispatch_queue_t leQueue(qt_LE_queue());
237 if (!leQueue || !inquiryLE) {
238 setError(QBluetoothDeviceDiscoveryAgent::UnknownError,
239 QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STARTED_LE));
240 agentState = NonActive;
241 emit q_ptr->errorOccurred(lastError);
248 DarwinBTLEDeviceInquiry *inq = inquiryLE.getAs<DarwinBTLEDeviceInquiry>();
249 dispatch_async(leQueue, ^{
250 [inq startWithTimeout:lowEnergySearchTimeout];
254void QBluetoothDeviceDiscoveryAgentPrivate::stop()
256 Q_ASSERT_X(isActive(), Q_FUNC_INFO,
"called whithout active inquiry");
257 Q_ASSERT_X(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError,
258 Q_FUNC_INFO,
"called with invalid bluetooth adapter");
260 using namespace DarwinBluetooth;
262 const bool prevStart = startPending;
263 startPending =
false;
266 setError(QBluetoothDeviceDiscoveryAgent::NoError);
269 if (agentState == ClassicScan) {
270 const IOReturn res = [inquiry.getAs<DarwinBTClassicDeviceInquiry>() stop];
271 if (res != kIOReturnSuccess) {
272 qCWarning(QT_BT_DARWIN) <<
"failed to stop";
273 startPending = prevStart;
275 setError(res, QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STOPPED));
276 emit q_ptr->errorOccurred(lastError);
283 dispatch_queue_t leQueue(qt_LE_queue());
286 DarwinBTLEDeviceInquiry *inq = inquiryLE.getAs<DarwinBTLEDeviceInquiry>();
287 dispatch_sync(leQueue, ^{
298void QBluetoothDeviceDiscoveryAgentPrivate::inquiryFinished()
302 agentState = NonActive;
304 if (stopPending && !startPending) {
306 emit q_ptr->canceled();
307 }
else if (startPending) {
308 startPending =
false;
310 start(requestedMethods);
317 if (requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)
320 emit q_ptr->finished();
324void QBluetoothDeviceDiscoveryAgentPrivate::error(IOReturn error)
326 startPending =
false;
331 emit q_ptr->errorOccurred(lastError);
334void QBluetoothDeviceDiscoveryAgentPrivate::classicDeviceFound(
void *obj)
336 auto device =
static_cast<IOBluetoothDevice *>(obj);
337 Q_ASSERT_X(device, Q_FUNC_INFO,
"invalid IOBluetoothDevice (nil)");
339 Q_ASSERT_X(agentState == ClassicScan, Q_FUNC_INFO,
340 "invalid agent state (expected classic scan)");
342 QT_BT_MAC_AUTORELEASEPOOL;
345 const QBluetoothAddress deviceAddress(DarwinBluetooth::qt_address([device getAddress]));
346 if (deviceAddress.isNull()) {
347 qCWarning(QT_BT_DARWIN) <<
"invalid Bluetooth address";
353 deviceName = QString::fromNSString(device.name);
355 const auto classOfDevice = qint32(device.classOfDevice);
357 QBluetoothDeviceInfo deviceInfo(deviceAddress, deviceName, classOfDevice);
358 deviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::BaseRateCoreConfiguration);
359 deviceInfo.setRssi(device.RSSI);
361 const QList<QBluetoothUuid> uuids(DarwinBluetooth::extract_services_uuids(device));
362 deviceInfo.setServiceUuids(uuids);
364 deviceFound(deviceInfo);
367void QBluetoothDeviceDiscoveryAgentPrivate::setError(IOReturn error,
const QString &text)
369 if (error == kIOReturnSuccess)
370 setError(QBluetoothDeviceDiscoveryAgent::NoError, text);
371 else if (error == kIOReturnNoPower)
372 setError(QBluetoothDeviceDiscoveryAgent::PoweredOffError, text);
374 setError(QBluetoothDeviceDiscoveryAgent::UnknownError, text);
379void QBluetoothDeviceDiscoveryAgentPrivate::setError(QBluetoothDeviceDiscoveryAgent::Error error,
const QString &text)
383 if (!text.isEmpty()) {
387 case QBluetoothDeviceDiscoveryAgent::NoError:
388 errorString = QString();
390 case QBluetoothDeviceDiscoveryAgent::PoweredOffError:
391 errorString = QCoreApplication::translate(DEV_DISCOVERY, DD_POWERED_OFF);
393 case QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError:
394 errorString = QCoreApplication::translate(DEV_DISCOVERY, DD_INVALID_ADAPTER);
396 case QBluetoothDeviceDiscoveryAgent::InputOutputError:
397 errorString = QCoreApplication::translate(DEV_DISCOVERY, DD_IO);
399 case QBluetoothDeviceDiscoveryAgent::UnsupportedPlatformError:
400 errorString = QCoreApplication::translate(DEV_DISCOVERY, DD_NOTSUPPORTED);
402 case QBluetoothDeviceDiscoveryAgent::MissingPermissionsError:
403 errorString = QCoreApplication::translate(DEV_DISCOVERY, DD_MISSING_PERMISSION);
405 case QBluetoothDeviceDiscoveryAgent::UnknownError:
407 errorString = QCoreApplication::translate(DEV_DISCOVERY, DD_UNKNOWN_ERROR);
412void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError(QBluetoothDeviceDiscoveryAgent::Error error)
414 Q_ASSERT(error == QBluetoothDeviceDiscoveryAgent::PoweredOffError
415 || error == QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod
416 || error == QBluetoothDeviceDiscoveryAgent::MissingPermissionsError);
420 startPending =
false;
422 agentState = NonActive;
424 emit q_ptr->errorOccurred(lastError);
427void QBluetoothDeviceDiscoveryAgentPrivate::LEnotSupported()
429 qCDebug(QT_BT_DARWIN) <<
"no Bluetooth LE support";
432 if (requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod) {
440 LEinquiryError(QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod);
444 startPending =
false;
446 setError(QBluetoothDeviceDiscoveryAgent::UnsupportedPlatformError);
447 emit q_ptr->errorOccurred(lastError);
451void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished()
454 agentState = NonActive;
457 if (stopPending && !startPending) {
459 emit q_ptr->canceled();
460 }
else if (startPending) {
461 startPending =
false;
463 start(requestedMethods);
465 emit q_ptr->finished();
469void QBluetoothDeviceDiscoveryAgentPrivate::deviceFound(
const QBluetoothDeviceInfo &newDeviceInfo)
476 newDeviceInfo.coreConfigurations() == QBluetoothDeviceInfo::LowEnergyCoreConfiguration;
480 for (qsizetype i = 0, e = discoveredDevices.size(); i < e; ++i) {
482 if (discoveredDevices[i].deviceUuid() == newDeviceInfo.deviceUuid()) {
483 QBluetoothDeviceInfo::Fields updatedFields = QBluetoothDeviceInfo::Field::None;
484 if (discoveredDevices[i].rssi() != newDeviceInfo.rssi()) {
485 qCDebug(QT_BT_DARWIN) <<
"Updating RSSI for" << newDeviceInfo.address()
486 << newDeviceInfo.rssi();
487 discoveredDevices[i].setRssi(newDeviceInfo.rssi());
488 updatedFields.setFlag(QBluetoothDeviceInfo::Field::RSSI);
491 if (discoveredDevices[i].manufacturerData() != newDeviceInfo.manufacturerData()) {
492 qCDebug(QT_BT_DARWIN) <<
"Updating manufacturer data for" << newDeviceInfo.address();
493 const QList<quint16> keys = newDeviceInfo.manufacturerIds();
495 discoveredDevices[i].setManufacturerData(key, newDeviceInfo.manufacturerData(key));
496 updatedFields.setFlag(QBluetoothDeviceInfo::Field::ManufacturerData);
499 if (discoveredDevices[i].serviceData() != newDeviceInfo.serviceData()) {
500 qCDebug(QT_BT_DARWIN) <<
"Updating service data for" << newDeviceInfo.address();
501 const QList<QBluetoothUuid> keys = newDeviceInfo.serviceIds();
502 for (
auto key : keys)
503 discoveredDevices[i].setServiceData(key, newDeviceInfo.serviceData(key));
504 updatedFields.setFlag(QBluetoothDeviceInfo::Field::ServiceData);
507 if (lowEnergySearchTimeout > 0) {
508 if (discoveredDevices[i] != newDeviceInfo) {
509 discoveredDevices.replace(i, newDeviceInfo);
510 emit q_ptr->deviceDiscovered(newDeviceInfo);
512 if (!updatedFields.testFlag(QBluetoothDeviceInfo::Field::None))
513 emit q_ptr->deviceUpdated(discoveredDevices[i], updatedFields);
519 discoveredDevices.replace(i, newDeviceInfo);
520 emit q_ptr->deviceDiscovered(newDeviceInfo);
522 if (!updatedFields.testFlag(QBluetoothDeviceInfo::Field::None))
523 emit q_ptr->deviceUpdated(discoveredDevices[i], updatedFields);
529 if (discoveredDevices[i].address() == newDeviceInfo.address()) {
530 if (discoveredDevices[i] == newDeviceInfo)
533 discoveredDevices.replace(i, newDeviceInfo);
534 emit q_ptr->deviceDiscovered(newDeviceInfo);
543 discoveredDevices.append(newDeviceInfo);
544 emit q_ptr->deviceDiscovered(newDeviceInfo);
547QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods()
550 return ClassicMethod | LowEnergyMethod;
552 return LowEnergyMethod;