Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qbluetoothlocaldevice_macos.mm
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "darwin/btconnectionmonitor_p.h"
7#include "darwin/btdevicepair_p.h"
8#include "darwin/btdelegates_p.h"
9#include "darwin/btutility_p.h"
10
11#include <QtCore/qloggingcategory.h>
12#include <QtCore/qstring.h>
13#include <QtCore/qglobal.h>
14#include <QtCore/qdebug.h>
15#include <QtCore/qmap.h>
16
17
18#include <Foundation/Foundation.h>
19
20#include <IOBluetooth/IOBluetooth.h>
21#include <CoreBluetooth/CoreBluetooth.h>
22
23#include <algorithm>
24
25QT_BEGIN_NAMESPACE
26class QBluetoothLocalDevicePrivate;
27QT_END_NAMESPACE
28
29@interface QT_MANGLE_NAMESPACE(QDarwinBluetoothStateMonitor) : NSObject <CBCentralManagerDelegate>
30
31@property (strong, nonatomic) CBCentralManager *manager;
32@property (assign, nonatomic) QT_PREPEND_NAMESPACE(QBluetoothLocalDevicePrivate) *localDevicePrivate;
33
34- (instancetype)initWith:(QT_PREPEND_NAMESPACE(QBluetoothLocalDevicePrivate) *)localDevicePrivate;
35- (CBManagerState)currentState;
36- (void)startMonitoring;
37- (void)stopMonitoring;
38
39@end
40
41QT_BEGIN_NAMESPACE
42
43class QBluetoothLocalDevicePrivate : public DarwinBluetooth::PairingDelegate,
44 public DarwinBluetooth::ConnectionMonitor
45{
46 friend class QBluetoothLocalDevice;
47 Q_DECLARE_PUBLIC(QBluetoothLocalDevice)
48public:
49 typedef QBluetoothLocalDevice::Pairing Pairing;
50
51 QBluetoothLocalDevicePrivate(QBluetoothLocalDevice *, const QBluetoothAddress & =
52 QBluetoothAddress());
53 ~QBluetoothLocalDevicePrivate();
54
55 bool isValid() const;
56 void requestPairing(const QBluetoothAddress &address, Pairing pairing);
57 Pairing pairingStatus(const QBluetoothAddress &address) const;
58
59 void bluetoothStateChanged(CBManagerState newState);
60
61private:
62
63 // PairingDelegate:
64 void connecting(void *pair) override;
65 void requestPIN(void *pair) override;
66 void requestUserConfirmation(void *pair,
67 BluetoothNumericValue) override;
68 void passkeyNotification(void *pair,
69 BluetoothPasskey passkey) override;
70 void error(void *pair, IOReturn errorCode) override;
71 void pairingFinished(void *pair) override;
72
73 // ConnectionMonitor
74 void deviceConnected(const QBluetoothAddress &deviceAddress) override;
75 void deviceDisconnected(const QBluetoothAddress &deviceAddress) override;
76
77 void emitPairingFinished(const QBluetoothAddress &deviceAddress, Pairing pairing, bool queued);
78 void emitError(QBluetoothLocalDevice::Error error, bool queued);
79
80 void unpair(const QBluetoothAddress &deviceAddress);
81
82 DarwinBluetooth::ObjCScopedPointer<QT_MANGLE_NAMESPACE(QDarwinBluetoothStateMonitor)> bluetoothStateMonitor;
83 QBluetoothLocalDevice::HostMode hostMode = QBluetoothLocalDevice::HostMode::HostPoweredOff;
84
85 QBluetoothLocalDevice *q_ptr;
86
87 using HostController = DarwinBluetooth::ObjCScopedPointer<IOBluetoothHostController>;
88 HostController hostController;
89
90 using PairingRequest = DarwinBluetooth::ObjCStrongReference<DarwinBTClassicPairing>;
91 using RequestMap = QMap<QBluetoothAddress, PairingRequest>;
92
93 RequestMap pairingRequests;
94 DarwinBluetooth::ObjCScopedPointer<DarwinBTConnectionMonitor> connectionMonitor;
95 QList<QBluetoothAddress> discoveredDevices;
96};
97
98QBluetoothLocalDevicePrivate::QBluetoothLocalDevicePrivate(QBluetoothLocalDevice *q,
99 const QBluetoothAddress &address) :
100 q_ptr(q)
101{
102 registerQBluetoothLocalDeviceMetaType();
103 Q_ASSERT_X(q, Q_FUNC_INFO, "invalid q_ptr (null)");
104
106
107 using namespace DarwinBluetooth;
108
109 ObjCScopedPointer<IOBluetoothHostController> defaultController([IOBluetoothHostController defaultController],
110 RetainPolicy::doInitialRetain);
111 if (!defaultController) {
112 qCCritical(QT_BT_DARWIN) << "failed to init a host controller object";
113 return;
114 }
115
116 if (!address.isNull()) {
117 NSString *const hciAddress = [defaultController addressAsString];
118 if (!hciAddress) {
119 qCCritical(QT_BT_DARWIN) << "failed to obtain an address";
120 return;
121 }
122
123 BluetoothDeviceAddress iobtAddress = {};
124 if (IOBluetoothNSStringToDeviceAddress(hciAddress, &iobtAddress) != kIOReturnSuccess) {
125 qCCritical(QT_BT_DARWIN) << "invalid local device's address";
126 return;
127 }
128
129 if (address != DarwinBluetooth::qt_address(&iobtAddress)) {
130 qCCritical(QT_BT_DARWIN) << "invalid local device's address";
131 return;
132 }
133 }
134
135 defaultController.swap(hostController);
136 // This one is optional, if it fails to initialize, we do not care at all.
137 connectionMonitor.reset([[DarwinBTConnectionMonitor alloc] initWithMonitor:this],
138 DarwinBluetooth::RetainPolicy::noInitialRetain);
139 // Set the initial host mode
140 if ([hostController powerState])
141 hostMode = QBluetoothLocalDevice::HostConnectable;
142 else
143 hostMode = QBluetoothLocalDevice::HostPoweredOff;
144
145 // Start monitoring for bluetooth state changes
146 bluetoothStateMonitor.reset([[QT_MANGLE_NAMESPACE(QDarwinBluetoothStateMonitor) alloc] initWith:this],
147 DarwinBluetooth::RetainPolicy::doInitialRetain);
148 [bluetoothStateMonitor startMonitoring];
149}
150
151QBluetoothLocalDevicePrivate::~QBluetoothLocalDevicePrivate()
152{
153 [bluetoothStateMonitor stopMonitoring];
154 [connectionMonitor stopMonitoring];
155}
156
157void QBluetoothLocalDevicePrivate::bluetoothStateChanged(CBManagerState state)
158{
159 Q_Q(QBluetoothLocalDevice);
160 qCDebug(QT_BT_DARWIN) << "Bluetooth state changed to" << state;
161 // States other than 'powered ON' and 'powered OFF' are ambiguous to map
162 // unto Qt HostModes. For example lack of permissions might temporarily
163 // generate an 'unauthorized' state irrespective of bluetooth power state.
164 QBluetoothLocalDevice::HostMode mode;
165 if (state == CBManagerState::CBManagerStatePoweredOff)
166 mode = QBluetoothLocalDevice::HostPoweredOff;
167 else if (state == CBManagerState::CBManagerStatePoweredOn)
168 mode = QBluetoothLocalDevice::HostConnectable;
169 else
170 return;
171
172 if (hostMode != mode) {
173 hostMode = mode;
174 emit q->hostModeStateChanged(hostMode);
175 }
176}
177
179{
180 return hostController;
181}
182
183void QBluetoothLocalDevicePrivate::requestPairing(const QBluetoothAddress &address, Pairing pairing)
184{
185 Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid local device");
186 Q_ASSERT_X(!address.isNull(), Q_FUNC_INFO, "invalid device address");
187
188 using DarwinBluetooth::device_with_address;
189 using DarwinBluetooth::ObjCStrongReference;
190 using DarwinBluetooth::RetainPolicy;
191
192 // That's a really special case on OS X.
193 if (pairing == QBluetoothLocalDevice::Unpaired)
194 return unpair(address);
195
197
198 if (pairing == QBluetoothLocalDevice::AuthorizedPaired)
199 pairing = QBluetoothLocalDevice::Paired;
200
201 RequestMap::iterator pos = pairingRequests.find(address);
202 if (pos != pairingRequests.end()) {
203 if ([pos.value() isActive]) // Still trying to pair, continue.
204 return;
205
206 // 'device' is autoreleased:
207 IOBluetoothDevice *const device = [pos.value() targetDevice];
208 if ([device isPaired]) {
209 emitPairingFinished(address, pairing, true);
210 } else if ([pos.value() start] != kIOReturnSuccess) {
211 qCCritical(QT_BT_DARWIN) << "failed to start a new pairing request";
212 emitError(QBluetoothLocalDevice::PairingError, true);
213 }
214 return;
215 }
216
217 // That's a totally new request ('Paired', since we are here).
218 // Even if this device is paired (not by our local device), I still create a pairing request,
219 // it'll just finish with success (skipping any intermediate steps).
220 PairingRequest newRequest([[DarwinBTClassicPairing alloc] initWithTarget:address delegate:this],
221 RetainPolicy::noInitialRetain);
222 if (!newRequest) {
223 qCCritical(QT_BT_DARWIN) << "failed to allocate a new pairing request";
224 emitError(QBluetoothLocalDevice::PairingError, true);
225 return;
226 }
227
228 pos = pairingRequests.insert(address, newRequest);
229 const IOReturn result = [newRequest start];
230 if (result != kIOReturnSuccess) {
231 pairingRequests.erase(pos);
232 qCCritical(QT_BT_DARWIN) << "failed to start a new pairing request";
233 emitError(QBluetoothLocalDevice::PairingError, true);
234 }
235}
236
237QBluetoothLocalDevice::Pairing QBluetoothLocalDevicePrivate::pairingStatus(const QBluetoothAddress &address)const
238{
239 Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid local device");
240 Q_ASSERT_X(!address.isNull(), Q_FUNC_INFO, "invalid address");
241
242 using DarwinBluetooth::device_with_address;
243 using DarwinBluetooth::ObjCStrongReference;
244
246
247 RequestMap::const_iterator it = pairingRequests.find(address);
248 if (it != pairingRequests.end()) {
249 // All Obj-C objects are autoreleased.
250 IOBluetoothDevice *const device = [it.value() targetDevice];
251 if (device && [device isPaired])
252 return QBluetoothLocalDevice::Paired;
253 } else {
254 // Try even if device was not paired by this local device ...
255 const ObjCStrongReference<IOBluetoothDevice> device(device_with_address(address));
256 if (device && [device isPaired])
257 return QBluetoothLocalDevice::Paired;
258 }
259
260 return QBluetoothLocalDevice::Unpaired;
261}
262
263void QBluetoothLocalDevicePrivate::connecting(void *pair)
264{
265 // TODO: why unused and if cannot be used - remove?
266 Q_UNUSED(pair);
267}
268
269void QBluetoothLocalDevicePrivate::requestPIN(void *pair)
270{
271 // TODO: why unused and if cannot be used - remove?
272 Q_UNUSED(pair);
273}
274
275void QBluetoothLocalDevicePrivate::requestUserConfirmation(void *pair, BluetoothNumericValue intPin)
276{
277 // TODO: why unused and if cannot be used - remove?
278 Q_UNUSED(pair);
279 Q_UNUSED(intPin);
280}
281
282void QBluetoothLocalDevicePrivate::passkeyNotification(void *pair, BluetoothPasskey passkey)
283{
284 // TODO: why unused and if cannot be used - remove?
285 Q_UNUSED(pair);
286 Q_UNUSED(passkey);
287}
288
289void QBluetoothLocalDevicePrivate::error(void *pair, IOReturn errorCode)
290{
291 Q_UNUSED(pair);
292 Q_UNUSED(errorCode);
293
294 emitError(QBluetoothLocalDevice::PairingError, false);
295}
296
297void QBluetoothLocalDevicePrivate::pairingFinished(void *generic)
298{
299 auto pair = static_cast<DarwinBTClassicPairing *>(generic);
300 Q_ASSERT_X(pair, Q_FUNC_INFO, "invalid pairing request (nil)");
301
302 const QBluetoothAddress &deviceAddress = [pair targetAddress];
303 Q_ASSERT_X(!deviceAddress.isNull(), Q_FUNC_INFO,
304 "invalid target address");
305
306 emitPairingFinished(deviceAddress, QBluetoothLocalDevice::Paired, false);
307}
308
309void QBluetoothLocalDevicePrivate::deviceConnected(const QBluetoothAddress &deviceAddress)
310{
311 if (!discoveredDevices.contains(deviceAddress))
312 discoveredDevices.append(deviceAddress);
313
314 QMetaObject::invokeMethod(q_ptr, "deviceConnected", Qt::QueuedConnection,
315 Q_ARG(QBluetoothAddress, deviceAddress));
316}
317
318void QBluetoothLocalDevicePrivate::deviceDisconnected(const QBluetoothAddress &deviceAddress)
319{
320 QList<QBluetoothAddress>::iterator devicePos =std::find(discoveredDevices.begin(),
321 discoveredDevices.end(),
322 deviceAddress);
323
324 if (devicePos != discoveredDevices.end())
325 discoveredDevices.erase(devicePos);
326
327 QMetaObject::invokeMethod(q_ptr, "deviceDisconnected", Qt::QueuedConnection,
328 Q_ARG(QBluetoothAddress, deviceAddress));
329}
330
331void QBluetoothLocalDevicePrivate::emitError(QBluetoothLocalDevice::Error error, bool queued)
332{
333 if (queued) {
334 QMetaObject::invokeMethod(q_ptr, "errorOccurred", Qt::QueuedConnection,
335 Q_ARG(QBluetoothLocalDevice::Error, error));
336 } else {
337 emit q_ptr->errorOccurred(QBluetoothLocalDevice::PairingError);
338 }
339}
340
341void QBluetoothLocalDevicePrivate::emitPairingFinished(const QBluetoothAddress &deviceAddress,
342 Pairing pairing, bool queued)
343{
344 Q_ASSERT_X(!deviceAddress.isNull(), Q_FUNC_INFO, "invalid target device address");
345 Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)");
346
347 if (queued) {
348 QMetaObject::invokeMethod(q_ptr, "pairingFinished", Qt::QueuedConnection,
349 Q_ARG(QBluetoothAddress, deviceAddress),
350 Q_ARG(QBluetoothLocalDevice::Pairing, pairing));
351 } else {
352 emit q_ptr->pairingFinished(deviceAddress, pairing);
353 }
354}
355
356void QBluetoothLocalDevicePrivate::unpair(const QBluetoothAddress &deviceAddress)
357{
358 Q_ASSERT_X(!deviceAddress.isNull(), Q_FUNC_INFO,
359 "invalid target address");
360
361 emitPairingFinished(deviceAddress, QBluetoothLocalDevice::Unpaired, true);
362}
363
364QBluetoothLocalDevice::QBluetoothLocalDevice(QObject *parent) :
365 QObject(parent),
366 d_ptr(new QBluetoothLocalDevicePrivate(this))
367{
368}
369
370QBluetoothLocalDevice::QBluetoothLocalDevice(const QBluetoothAddress &address, QObject *parent) :
371 QObject(parent),
372 d_ptr(new QBluetoothLocalDevicePrivate(this, address))
373{
374}
375
376QBluetoothLocalDevice::~QBluetoothLocalDevice()
377{
378 delete d_ptr;
379}
380
381bool QBluetoothLocalDevice::isValid() const
382{
383 return d_ptr->isValid();
384}
385
386
387QString QBluetoothLocalDevice::name() const
388{
390
391 if (isValid()) {
392 if (NSString *const nsn = [d_ptr->hostController nameAsString])
393 return QString::fromNSString(nsn);
394 qCCritical(QT_BT_DARWIN) << Q_FUNC_INFO << "failed to obtain a name";
395 }
396
397 return QString();
398}
399
400QBluetoothAddress QBluetoothLocalDevice::address() const
401{
403
404 if (isValid()) {
405 if (NSString *const nsa = [d_ptr->hostController addressAsString])
406 return QBluetoothAddress(DarwinBluetooth::qt_address(nsa));
407
408 qCCritical(QT_BT_DARWIN) << Q_FUNC_INFO << "failed to obtain an address";
409 } else {
410 qCWarning(QT_BT_DARWIN) << Q_FUNC_INFO << "invalid local device";
411 }
412
413 return QBluetoothAddress();
414}
415
416void QBluetoothLocalDevice::powerOn()
417{
418 if (!isValid())
419 qCWarning(QT_BT_DARWIN) << Q_FUNC_INFO << "invalid local device";
420}
421
422void QBluetoothLocalDevice::setHostMode(QBluetoothLocalDevice::HostMode mode)
423{
424 Q_UNUSED(mode);
425
426 if (!isValid())
427 qCWarning(QT_BT_DARWIN) << Q_FUNC_INFO << "invalid local device";
428}
429
430QBluetoothLocalDevice::HostMode QBluetoothLocalDevice::hostMode() const
431{
432 Q_D(const QBluetoothLocalDevice);
433 if (!isValid())
434 return HostPoweredOff;
435
436 auto state = [d->bluetoothStateMonitor currentState];
437 // If the monitored state is unknown or ambiguous, use the HCI state directly.
438 // Otherwise use the monitored state. We can't use HCI state always, because there can
439 // be a significant delay from "monitored state change" to "HCI state change", causing
440 // handlers of hostModeStateChanged() signal to perceive conflicting results (signal
441 // parameter value vs. what this getter returns).
442 if (state == CBManagerState::CBManagerStatePoweredOff)
443 return HostPoweredOff;
444 else if (state == CBManagerState::CBManagerStatePoweredOn)
445 return HostConnectable;
446
447 return [d->hostController powerState] ? HostConnectable : HostPoweredOff;
448}
449
450QList<QBluetoothAddress> QBluetoothLocalDevice::connectedDevices() const
451{
453
454 QList<QBluetoothAddress> connectedDevices;
455
456 // Take the devices known to IOBluetooth to be paired and connected first:
457 NSArray *const pairedDevices = [IOBluetoothDevice pairedDevices];
458 for (IOBluetoothDevice *device in pairedDevices) {
459 if ([device isConnected]) {
460 const QBluetoothAddress address(DarwinBluetooth::qt_address([device getAddress]));
461 if (!address.isNull())
462 connectedDevices.append(address);
463 }
464 }
465
466 // Add devices, discovered by the connection monitor:
467 connectedDevices += d_ptr->discoveredDevices;
468 // Find something more elegant? :)
469 // But after all, addresses are integers.
470 std::sort(connectedDevices.begin(), connectedDevices.end());
471 connectedDevices.erase(std::unique(connectedDevices.begin(),
472 connectedDevices.end()),
473 connectedDevices.end());
474
475 return connectedDevices;
476}
477
478QList<QBluetoothHostInfo> QBluetoothLocalDevice::allDevices()
479{
480 QList<QBluetoothHostInfo> localDevices;
481
482 QBluetoothLocalDevice defaultAdapter;
483 if (!defaultAdapter.isValid() || defaultAdapter.address().isNull()) {
484 qCWarning(QT_BT_DARWIN) << Q_FUNC_INFO <<"no valid device found";
485 return localDevices;
486 }
487
488 QBluetoothHostInfo deviceInfo;
489 deviceInfo.setName(defaultAdapter.name());
490 deviceInfo.setAddress(defaultAdapter.address());
491
492 localDevices.append(deviceInfo);
493
494 return localDevices;
495}
496
497void QBluetoothLocalDevice::requestPairing(const QBluetoothAddress &address, Pairing pairing)
498{
499 if (!isValid())
500 qCWarning(QT_BT_DARWIN) << Q_FUNC_INFO << "invalid local device";
501
502 if (!isValid() || address.isNull()) {
503 d_ptr->emitError(PairingError, true);
504 return;
505 }
506
507 DarwinBluetooth::qt_test_iobluetooth_runloop();
508
509 return d_ptr->requestPairing(address, pairing);
510}
511
512QBluetoothLocalDevice::Pairing QBluetoothLocalDevice::pairingStatus(const QBluetoothAddress &address) const
513{
514 if (!isValid())
515 qCWarning(QT_BT_DARWIN) << Q_FUNC_INFO << "invalid local device";
516
517 if (!isValid() || address.isNull())
518 return Unpaired;
519
520 return d_ptr->pairingStatus(address);
521}
522
523QT_END_NAMESPACE
524
525@implementation QT_MANGLE_NAMESPACE(QDarwinBluetoothStateMonitor)
526
527- (instancetype)initWith:(QT_PREPEND_NAMESPACE(QBluetoothLocalDevicePrivate) *)localDevicePrivate
528{
529 if ((self = [super init])) {
530 self.manager = nil;
531 self.localDevicePrivate = localDevicePrivate;
532 }
533 return self;
534}
535
536- (void)startMonitoring
537{
538 if (self.manager != nil)
539 return;
540 self.manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
541}
542
543- (void)stopMonitoring
544{
545 if (self.manager == nil)
546 return;
547 self.manager.delegate = nil;
548 self.manager = nil;
549}
550
551- (CBManagerState)currentState
552{
553 Q_ASSERT(self.manager);
554 return self.manager.state;
555}
556
557- (void)centralManagerDidUpdateState:(CBCentralManager *)aManager
558{
559 Q_ASSERT(self.manager);
560 Q_ASSERT(self.localDevicePrivate);
561 Q_ASSERT(self.manager == aManager);
562 self.localDevicePrivate->bluetoothStateChanged(aManager.state);
563}
564
565@end
#define QT_BT_MAC_AUTORELEASEPOOL
Definition btutility_p.h:78
ObjCStrongReference< IOBluetoothDevice > device_with_address(const QBluetoothAddress &address)