1/**************************************************************************** 2** 3** Copyright (C) 2016 The Qt Company Ltd. 4** Contact: https://www.qt.io/licensing/ 5** 6** This file is part of the QtBluetooth module of the Qt Toolkit. 7** 8** $QT_BEGIN_LICENSE:LGPL$ 9** Commercial License Usage 10** Licensees holding valid commercial Qt licenses may use this file in 11** accordance with the commercial license agreement provided with the 12** Software or, alternatively, in accordance with the terms contained in 13** a written agreement between you and The Qt Company. For licensing terms 14** and conditions see https://www.qt.io/terms-conditions. For further 15** information use the contact form at https://www.qt.io/contact-us. 16** 17** GNU Lesser General Public License Usage 18** Alternatively, this file may be used under the terms of the GNU Lesser 19** General Public License version 3 as published by the Free Software 20** Foundation and appearing in the file LICENSE.LGPL3 included in the 21** packaging of this file. Please review the following information to 22** ensure the GNU Lesser General Public License version 3 requirements 23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. 24** 25** GNU General Public License Usage 26** Alternatively, this file may be used under the terms of the GNU 27** General Public License version 2.0 or (at your option) the GNU General 28** Public license version 3 or any later version approved by the KDE Free 29** Qt Foundation. The licenses are as published by the Free Software 30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 31** included in the packaging of this file. Please review the following 32** information to ensure the GNU General Public License requirements will 33** be met: https://www.gnu.org/licenses/gpl-2.0.html and 34** https://www.gnu.org/licenses/gpl-3.0.html. 35** 36** $QT_END_LICENSE$ 37** 38****************************************************************************/ 39 40#include "osx/osxbtconnectionmonitor_p.h" 41#include "qbluetoothlocaldevice_p.h" 42#include "qbluetoothlocaldevice.h" 43#include "osx/osxbtdevicepair_p.h" 44#include "osx/osxbtutility_p.h" 45#include "osx/osxbluetooth_p.h" 46 47#include <QtCore/qloggingcategory.h> 48#include <QtCore/qstring.h> 49#include <QtCore/qglobal.h> 50#include <QtCore/qdebug.h> 51#include <QtCore/qmap.h> 52 53 54#include <Foundation/Foundation.h> 55 56#include <algorithm> 57 58QT_BEGIN_NAMESPACE 59 60class QBluetoothLocalDevicePrivate : public OSXBluetooth::PairingDelegate, 61 public OSXBluetooth::ConnectionMonitor 62{ 63 friend class QBluetoothLocalDevice; 64public: 65 typedef QBluetoothLocalDevice::Pairing Pairing; 66 67 QBluetoothLocalDevicePrivate(QBluetoothLocalDevice *, const QBluetoothAddress & = 68 QBluetoothAddress()); 69 70 bool isValid() const; 71 void requestPairing(const QBluetoothAddress &address, Pairing pairing); 72 Pairing pairingStatus(const QBluetoothAddress &address) const; 73 74private: 75 76 // PairingDelegate: 77 void connecting(ObjCPairingRequest *pair) override; 78 void requestPIN(ObjCPairingRequest *pair) override; 79 void requestUserConfirmation(ObjCPairingRequest *pair, 80 BluetoothNumericValue) override; 81 void passkeyNotification(ObjCPairingRequest *pair, 82 BluetoothPasskey passkey) override; 83 void error(ObjCPairingRequest *pair, IOReturn errorCode) override; 84 void pairingFinished(ObjCPairingRequest *pair) override; 85 86 // ConnectionMonitor 87 void deviceConnected(const QBluetoothAddress &deviceAddress) override; 88 void deviceDisconnected(const QBluetoothAddress &deviceAddress) override; 89 90 void emitPairingFinished(const QBluetoothAddress &deviceAddress, Pairing pairing, bool queued); 91 void emitError(QBluetoothLocalDevice::Error error, bool queued); 92 93 void unpair(const QBluetoothAddress &deviceAddress); 94 95 QBluetoothLocalDevice *q_ptr; 96 97 typedef OSXBluetooth::ObjCScopedPointer<IOBluetoothHostController> HostController; 98 HostController hostController; 99 100 typedef OSXBluetooth::ObjCStrongReference<ObjCPairingRequest> PairingRequest; 101 typedef QMap<QBluetoothAddress, PairingRequest> RequestMap; 102 103 RequestMap pairingRequests; 104 105 OSXBluetooth::ObjCScopedPointer<ObjCConnectionMonitor> connectionMonitor; 106 QList<QBluetoothAddress> discoveredDevices; 107}; 108 109QBluetoothLocalDevicePrivate::QBluetoothLocalDevicePrivate(QBluetoothLocalDevice *q, 110 const QBluetoothAddress &address) : 111 q_ptr(q) 112{ 113 registerQBluetoothLocalDeviceMetaType(); 114 115 Q_ASSERT_X(q, Q_FUNC_INFO, "invalid q_ptr (null)"); 116 117 QT_BT_MAC_AUTORELEASEPOOL; 118 119 HostController defaultController([[IOBluetoothHostController defaultController] retain]); 120 if (!defaultController) { 121 qCCritical(QT_BT_OSX) << "failed to init a host controller object"; 122 return; 123 } 124 125 if (!address.isNull()) { 126 NSString *const hciAddress = [defaultController addressAsString]; 127 if (!hciAddress) { 128 qCCritical(QT_BT_OSX) << "failed to obtain an address"; 129 return; 130 } 131 132 BluetoothDeviceAddress iobtAddress = {}; 133 if (IOBluetoothNSStringToDeviceAddress(hciAddress, &iobtAddress) != kIOReturnSuccess) { 134 qCCritical(QT_BT_OSX) << "invalid local device's address"; 135 return; 136 } 137 138 if (address != OSXBluetooth::qt_address(&iobtAddress)) { 139 qCCritical(QT_BT_OSX) << "invalid local device's address"; 140 return; 141 } 142 } 143 144 hostController.reset(defaultController.take()); 145 146 // This one is optional, if it fails to initialize, we do not care at all. 147 connectionMonitor.reset([[ObjCConnectionMonitor alloc] initWithMonitor:this]); 148} 149 150bool QBluetoothLocalDevicePrivate::isValid() const 151{ 152 return hostController.data(); 153} 154 155void QBluetoothLocalDevicePrivate::requestPairing(const QBluetoothAddress &address, Pairing pairing) 156{ 157 Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid local device"); 158 Q_ASSERT_X(!address.isNull(), Q_FUNC_INFO, "invalid device address"); 159 160 using OSXBluetooth::device_with_address; 161 using OSXBluetooth::ObjCStrongReference; 162 163 // That's a really special case on OS X. 164 if (pairing == QBluetoothLocalDevice::Unpaired) 165 return unpair(address); 166 167 QT_BT_MAC_AUTORELEASEPOOL; 168 169 if (pairing == QBluetoothLocalDevice::AuthorizedPaired) 170 pairing = QBluetoothLocalDevice::Paired; 171 172 RequestMap::iterator pos = pairingRequests.find(address); 173 if (pos != pairingRequests.end()) { 174 if ([pos.value() isActive]) // Still trying to pair, continue. 175 return; 176 177 // 'device' is autoreleased: 178 IOBluetoothDevice *const device = [pos.value() targetDevice]; 179 if ([device isPaired]) { 180 emitPairingFinished(address, pairing, true); 181 } else if ([pos.value() start] != kIOReturnSuccess) { 182 qCCritical(QT_BT_OSX) << "failed to start a new pairing request"; 183 emitError(QBluetoothLocalDevice::PairingError, true); 184 } 185 return; 186 } 187 188 // That's a totally new request ('Paired', since we are here). 189 // Even if this device is paired (not by our local device), I still create a pairing request, 190 // it'll just finish with success (skipping any intermediate steps). 191 PairingRequest newRequest([[ObjCPairingRequest alloc] initWithTarget:address delegate:this], false); 192 if (!newRequest) { 193 qCCritical(QT_BT_OSX) << "failed to allocate a new pairing request"; 194 emitError(QBluetoothLocalDevice::PairingError, true); 195 return; 196 } 197 198 pos = pairingRequests.insert(address, newRequest); 199 const IOReturn result = [newRequest start]; 200 if (result != kIOReturnSuccess) { 201 pairingRequests.erase(pos); 202 qCCritical(QT_BT_OSX) << "failed to start a new pairing request"; 203 emitError(QBluetoothLocalDevice::PairingError, true); 204 } 205} 206 207QBluetoothLocalDevice::Pairing QBluetoothLocalDevicePrivate::pairingStatus(const QBluetoothAddress &address)const 208{ 209 Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid local device"); 210 Q_ASSERT_X(!address.isNull(), Q_FUNC_INFO, "invalid address"); 211 212 using OSXBluetooth::device_with_address; 213 using OSXBluetooth::ObjCStrongReference; 214 215 QT_BT_MAC_AUTORELEASEPOOL; 216 217 RequestMap::const_iterator it = pairingRequests.find(address); 218 if (it != pairingRequests.end()) { 219 // All Obj-C objects are autoreleased. 220 IOBluetoothDevice *const device = [it.value() targetDevice]; 221 if (device && [device isPaired]) 222 return QBluetoothLocalDevice::Paired; 223 } else { 224 // Try even if device was not paired by this local device ... 225 const ObjCStrongReference<IOBluetoothDevice> device(device_with_address(address)); 226 if (device && [device isPaired]) 227 return QBluetoothLocalDevice::Paired; 228 } 229 230 return QBluetoothLocalDevice::Unpaired; 231} 232 233void QBluetoothLocalDevicePrivate::connecting(ObjCPairingRequest *pair) 234{ 235 Q_UNUSED(pair) 236} 237 238void QBluetoothLocalDevicePrivate::requestPIN(ObjCPairingRequest *pair) 239{ 240 Q_UNUSED(pair) 241} 242 243void QBluetoothLocalDevicePrivate::requestUserConfirmation(ObjCPairingRequest *pair, BluetoothNumericValue intPin) 244{ 245 Q_UNUSED(pair) 246 Q_UNUSED(intPin) 247} 248 249void QBluetoothLocalDevicePrivate::passkeyNotification(ObjCPairingRequest *pair, 250 BluetoothPasskey passkey) 251{ 252 Q_UNUSED(pair) 253 Q_UNUSED(passkey) 254} 255 256void QBluetoothLocalDevicePrivate::error(ObjCPairingRequest *pair, IOReturn errorCode) 257{ 258 Q_UNUSED(pair) 259 Q_UNUSED(errorCode) 260 261 emitError(QBluetoothLocalDevice::PairingError, false); 262} 263 264void QBluetoothLocalDevicePrivate::pairingFinished(ObjCPairingRequest *pair) 265{ 266 Q_ASSERT_X(pair, Q_FUNC_INFO, "invalid pairing request (nil)"); 267 268 const QBluetoothAddress &deviceAddress = [pair targetAddress]; 269 Q_ASSERT_X(!deviceAddress.isNull(), Q_FUNC_INFO, 270 "invalid target address"); 271 272 emitPairingFinished(deviceAddress, QBluetoothLocalDevice::Paired, false); 273} 274 275void QBluetoothLocalDevicePrivate::deviceConnected(const QBluetoothAddress &deviceAddress) 276{ 277 if (!discoveredDevices.contains(deviceAddress)) 278 discoveredDevices.append(deviceAddress); 279 280 QMetaObject::invokeMethod(q_ptr, "deviceConnected", Qt::QueuedConnection, 281 Q_ARG(QBluetoothAddress, deviceAddress)); 282} 283 284void QBluetoothLocalDevicePrivate::deviceDisconnected(const QBluetoothAddress &deviceAddress) 285{ 286 QList<QBluetoothAddress>::iterator devicePos =std::find(discoveredDevices.begin(), 287 discoveredDevices.end(), 288 deviceAddress); 289 290 if (devicePos != discoveredDevices.end()) 291 discoveredDevices.erase(devicePos); 292 293 QMetaObject::invokeMethod(q_ptr, "deviceDisconnected", Qt::QueuedConnection, 294 Q_ARG(QBluetoothAddress, deviceAddress)); 295} 296 297void QBluetoothLocalDevicePrivate::emitError(QBluetoothLocalDevice::Error error, bool queued) 298{ 299 if (queued) { 300 QMetaObject::invokeMethod(q_ptr, "error", Qt::QueuedConnection, 301 Q_ARG(QBluetoothLocalDevice::Error, error)); 302 } else { 303 emit q_ptr->error(QBluetoothLocalDevice::PairingError); 304 } 305} 306 307void QBluetoothLocalDevicePrivate::emitPairingFinished(const QBluetoothAddress &deviceAddress, 308 Pairing pairing, bool queued) 309{ 310 Q_ASSERT_X(!deviceAddress.isNull(), Q_FUNC_INFO, "invalid target device address"); 311 Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)"); 312 313 if (queued) { 314 QMetaObject::invokeMethod(q_ptr, "pairingFinished", Qt::QueuedConnection, 315 Q_ARG(QBluetoothAddress, deviceAddress), 316 Q_ARG(QBluetoothLocalDevice::Pairing, pairing)); 317 } else { 318 emit q_ptr->pairingFinished(deviceAddress, pairing); 319 } 320} 321 322void QBluetoothLocalDevicePrivate::unpair(const QBluetoothAddress &deviceAddress) 323{ 324 Q_ASSERT_X(!deviceAddress.isNull(), Q_FUNC_INFO, 325 "invalid target address"); 326 327 emitPairingFinished(deviceAddress, QBluetoothLocalDevice::Unpaired, true); 328} 329 330QBluetoothLocalDevice::QBluetoothLocalDevice(QObject *parent) : 331 QObject(parent), 332 d_ptr(new QBluetoothLocalDevicePrivate(this)) 333{ 334} 335 336QBluetoothLocalDevice::QBluetoothLocalDevice(const QBluetoothAddress &address, QObject *parent) : 337 QObject(parent), 338 d_ptr(new QBluetoothLocalDevicePrivate(this, address)) 339{ 340} 341 342QBluetoothLocalDevice::~QBluetoothLocalDevice() 343{ 344 delete d_ptr; 345} 346 347bool QBluetoothLocalDevice::isValid() const 348{ 349 return d_ptr->isValid(); 350} 351 352 353QString QBluetoothLocalDevice::name() const 354{ 355 QT_BT_MAC_AUTORELEASEPOOL; 356 357 if (isValid()) { 358 if (NSString *const nsn = [d_ptr->hostController nameAsString]) 359 return QString::fromNSString(nsn); 360 qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to obtain a name"; 361 } 362 363 return QString(); 364} 365 366QBluetoothAddress QBluetoothLocalDevice::address() const 367{ 368 QT_BT_MAC_AUTORELEASEPOOL; 369 370 if (isValid()) { 371 if (NSString *const nsa = [d_ptr->hostController addressAsString]) 372 return QBluetoothAddress(OSXBluetooth::qt_address(nsa)); 373 374 qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to obtain an address"; 375 } else { 376 qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid local device"; 377 } 378 379 return QBluetoothAddress(); 380} 381 382void QBluetoothLocalDevice::powerOn() 383{ 384 if (!isValid()) 385 qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid local device"; 386} 387 388void QBluetoothLocalDevice::setHostMode(QBluetoothLocalDevice::HostMode mode) 389{ 390 Q_UNUSED(mode) 391 392 if (!isValid()) 393 qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid local device"; 394} 395 396QBluetoothLocalDevice::HostMode QBluetoothLocalDevice::hostMode() const 397{ 398 if (!isValid() || ![d_ptr->hostController powerState]) 399 return HostPoweredOff; 400 401 return HostConnectable; 402} 403 404QList<QBluetoothAddress> QBluetoothLocalDevice::connectedDevices() const 405{ 406 QT_BT_MAC_AUTORELEASEPOOL; 407 408 QList<QBluetoothAddress> connectedDevices; 409 410 // Take the devices known to IOBluetooth to be paired and connected first: 411 NSArray *const pairedDevices = [IOBluetoothDevice pairedDevices]; 412 for (IOBluetoothDevice *device in pairedDevices) { 413 if ([device isConnected]) { 414 const QBluetoothAddress address(OSXBluetooth::qt_address([device getAddress])); 415 if (!address.isNull()) 416 connectedDevices.append(address); 417 } 418 } 419 420 // Add devices, discovered by the connection monitor: 421 connectedDevices += d_ptr->discoveredDevices; 422 // Find something more elegant? :) 423 // But after all, addresses are integers. 424 std::sort(connectedDevices.begin(), connectedDevices.end()); 425 connectedDevices.erase(std::unique(connectedDevices.begin(), 426 connectedDevices.end()), 427 connectedDevices.end()); 428 429 return connectedDevices; 430} 431 432QList<QBluetoothHostInfo> QBluetoothLocalDevice::allDevices() 433{ 434 QList<QBluetoothHostInfo> localDevices; 435 436 QBluetoothLocalDevice defaultAdapter; 437 if (!defaultAdapter.isValid() || defaultAdapter.address().isNull()) { 438 qCCritical(QT_BT_OSX) << Q_FUNC_INFO <<"no valid device found"; 439 return localDevices; 440 } 441 442 QBluetoothHostInfo deviceInfo; 443 deviceInfo.setName(defaultAdapter.name()); 444 deviceInfo.setAddress(defaultAdapter.address()); 445 446 localDevices.append(deviceInfo); 447 448 return localDevices; 449} 450 451void QBluetoothLocalDevice::pairingConfirmation(bool confirmation) 452{ 453 Q_UNUSED(confirmation) 454} 455 456 457void QBluetoothLocalDevice::requestPairing(const QBluetoothAddress &address, Pairing pairing) 458{ 459 if (!isValid()) 460 qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid local device"; 461 462 if (!isValid() || address.isNull()) { 463 d_ptr->emitError(PairingError, true); 464 return; 465 } 466 467 OSXBluetooth::qt_test_iobluetooth_runloop(); 468 469 return d_ptr->requestPairing(address, pairing); 470} 471 472QBluetoothLocalDevice::Pairing QBluetoothLocalDevice::pairingStatus(const QBluetoothAddress &address) const 473{ 474 if (!isValid()) 475 qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid local device"; 476 477 if (!isValid() || address.isNull()) 478 return Unpaired; 479 480 return d_ptr->pairingStatus(address); 481} 482 483QT_END_NAMESPACE 484