diff options
Diffstat (limited to 'src/bluetooth/qbluetoothlocaldevice_macos.mm')
-rw-r--r-- | src/bluetooth/qbluetoothlocaldevice_macos.mm | 489 |
1 files changed, 489 insertions, 0 deletions
diff --git a/src/bluetooth/qbluetoothlocaldevice_macos.mm b/src/bluetooth/qbluetoothlocaldevice_macos.mm new file mode 100644 index 00000000..c39ae100 --- /dev/null +++ b/src/bluetooth/qbluetoothlocaldevice_macos.mm @@ -0,0 +1,489 @@ +/**************************************************************************** +** +** 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 "osx/osxbtconnectionmonitor_p.h" +#include "qbluetoothlocaldevice_p.h" +#include "qbluetoothlocaldevice.h" +#include "osx/osxbtdevicepair_p.h" +#include "osx/osxbtutility_p.h" +#include "osx/osxbluetooth_p.h" +#include "osx/btdelegates_p.h" + +#include <QtCore/qloggingcategory.h> +#include <QtCore/qstring.h> +#include <QtCore/qglobal.h> +#include <QtCore/qdebug.h> +#include <QtCore/qmap.h> + + +#include <Foundation/Foundation.h> + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +class QBluetoothLocalDevicePrivate : public DarwinBluetooth::PairingDelegate, + public DarwinBluetooth::ConnectionMonitor +{ + friend class QBluetoothLocalDevice; +public: + typedef QBluetoothLocalDevice::Pairing Pairing; + + QBluetoothLocalDevicePrivate(QBluetoothLocalDevice *, const QBluetoothAddress & = + QBluetoothAddress()); + + bool isValid() const; + void requestPairing(const QBluetoothAddress &address, Pairing pairing); + Pairing pairingStatus(const QBluetoothAddress &address) const; + +private: + + // PairingDelegate: + void connecting(void *pair) override; + void requestPIN(void *pair) override; + void requestUserConfirmation(void *pair, + BluetoothNumericValue) override; + void passkeyNotification(void *pair, + BluetoothPasskey passkey) override; + void error(void *pair, IOReturn errorCode) override; + void pairingFinished(void *pair) override; + + // ConnectionMonitor + void deviceConnected(const QBluetoothAddress &deviceAddress) override; + void deviceDisconnected(const QBluetoothAddress &deviceAddress) override; + + void emitPairingFinished(const QBluetoothAddress &deviceAddress, Pairing pairing, bool queued); + void emitError(QBluetoothLocalDevice::Error error, bool queued); + + void unpair(const QBluetoothAddress &deviceAddress); + + QBluetoothLocalDevice *q_ptr; + + using HostController = DarwinBluetooth::ObjCScopedPointer<IOBluetoothHostController>; + HostController hostController; + + using ObjCPairingRequest = QT_MANGLE_NAMESPACE(OSXBTPairing); + using PairingRequest = DarwinBluetooth::ObjCStrongReference<ObjCPairingRequest>; + using RequestMap = QMap<QBluetoothAddress, PairingRequest>; + + RequestMap pairingRequests; + using ObjCConnectionMonitor = QT_MANGLE_NAMESPACE(OSXBTConnectionMonitor); + DarwinBluetooth::ObjCScopedPointer<ObjCConnectionMonitor> connectionMonitor; + QList<QBluetoothAddress> discoveredDevices; +}; + +QBluetoothLocalDevicePrivate::QBluetoothLocalDevicePrivate(QBluetoothLocalDevice *q, + const QBluetoothAddress &address) : + q_ptr(q) +{ + registerQBluetoothLocalDeviceMetaType(); + + Q_ASSERT_X(q, Q_FUNC_INFO, "invalid q_ptr (null)"); + + QT_BT_MAC_AUTORELEASEPOOL; + + HostController defaultController([[IOBluetoothHostController defaultController] retain]); + if (!defaultController) { + 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) << "failed to obtain an address"; + return; + } + + BluetoothDeviceAddress iobtAddress = {}; + if (IOBluetoothNSStringToDeviceAddress(hciAddress, &iobtAddress) != kIOReturnSuccess) { + qCCritical(QT_BT_OSX) << "invalid local device's address"; + return; + } + + if (address != DarwinBluetooth::qt_address(&iobtAddress)) { + qCCritical(QT_BT_OSX) << "invalid local device's address"; + return; + } + } + + hostController.reset(defaultController.take()); + + // This one is optional, if it fails to initialize, we do not care at all. + connectionMonitor.reset([[ObjCConnectionMonitor alloc] initWithMonitor:this]); +} + +bool QBluetoothLocalDevicePrivate::isValid() const +{ + return hostController.data(); +} + +void QBluetoothLocalDevicePrivate::requestPairing(const QBluetoothAddress &address, Pairing pairing) +{ + Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid local device"); + Q_ASSERT_X(!address.isNull(), Q_FUNC_INFO, "invalid device address"); + + using DarwinBluetooth::device_with_address; + using DarwinBluetooth::ObjCStrongReference; + + // That's a really special case on OS X. + if (pairing == QBluetoothLocalDevice::Unpaired) + return unpair(address); + + QT_BT_MAC_AUTORELEASEPOOL; + + if (pairing == QBluetoothLocalDevice::AuthorizedPaired) + pairing = QBluetoothLocalDevice::Paired; + + RequestMap::iterator pos = pairingRequests.find(address); + if (pos != pairingRequests.end()) { + if ([pos.value() isActive]) // Still trying to pair, continue. + return; + + // 'device' is autoreleased: + IOBluetoothDevice *const device = [pos.value() targetDevice]; + if ([device isPaired]) { + emitPairingFinished(address, pairing, true); + } else if ([pos.value() start] != kIOReturnSuccess) { + qCCritical(QT_BT_OSX) << "failed to start a new pairing request"; + emitError(QBluetoothLocalDevice::PairingError, true); + } + return; + } + + // That's a totally new request ('Paired', since we are here). + // Even if this device is paired (not by our local device), I still create a pairing request, + // 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) << "failed to allocate a new pairing request"; + emitError(QBluetoothLocalDevice::PairingError, true); + return; + } + + pos = pairingRequests.insert(address, newRequest); + const IOReturn result = [newRequest start]; + if (result != kIOReturnSuccess) { + pairingRequests.erase(pos); + qCCritical(QT_BT_OSX) << "failed to start a new pairing request"; + emitError(QBluetoothLocalDevice::PairingError, true); + } +} + +QBluetoothLocalDevice::Pairing QBluetoothLocalDevicePrivate::pairingStatus(const QBluetoothAddress &address)const +{ + Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid local device"); + Q_ASSERT_X(!address.isNull(), Q_FUNC_INFO, "invalid address"); + + using DarwinBluetooth::device_with_address; + using DarwinBluetooth::ObjCStrongReference; + + QT_BT_MAC_AUTORELEASEPOOL; + + RequestMap::const_iterator it = pairingRequests.find(address); + if (it != pairingRequests.end()) { + // All Obj-C objects are autoreleased. + IOBluetoothDevice *const device = [it.value() targetDevice]; + if (device && [device isPaired]) + return QBluetoothLocalDevice::Paired; + } else { + // Try even if device was not paired by this local device ... + const ObjCStrongReference<IOBluetoothDevice> device(device_with_address(address)); + if (device && [device isPaired]) + return QBluetoothLocalDevice::Paired; + } + + return QBluetoothLocalDevice::Unpaired; +} + +void QBluetoothLocalDevicePrivate::connecting(void *pair) +{ + // TODO: why unused and if cannot be used - remove? + Q_UNUSED(pair) +} + +void QBluetoothLocalDevicePrivate::requestPIN(void *pair) +{ + // TODO: why unused and if cannot be used - remove? + Q_UNUSED(pair) +} + +void QBluetoothLocalDevicePrivate::requestUserConfirmation(void *pair, BluetoothNumericValue intPin) +{ + // TODO: why unused and if cannot be used - remove? + Q_UNUSED(pair) + Q_UNUSED(intPin) +} + +void QBluetoothLocalDevicePrivate::passkeyNotification(void *pair, BluetoothPasskey passkey) +{ + // TODO: why unused and if cannot be used - remove? + Q_UNUSED(pair) + Q_UNUSED(passkey) +} + +void QBluetoothLocalDevicePrivate::error(void *pair, IOReturn errorCode) +{ + Q_UNUSED(pair) + Q_UNUSED(errorCode) + + emitError(QBluetoothLocalDevice::PairingError, false); +} + +void QBluetoothLocalDevicePrivate::pairingFinished(void *generic) +{ + auto pair = static_cast<ObjCPairingRequest *>(generic); + Q_ASSERT_X(pair, Q_FUNC_INFO, "invalid pairing request (nil)"); + + const QBluetoothAddress &deviceAddress = [pair targetAddress]; + Q_ASSERT_X(!deviceAddress.isNull(), Q_FUNC_INFO, + "invalid target address"); + + emitPairingFinished(deviceAddress, QBluetoothLocalDevice::Paired, false); +} + +void QBluetoothLocalDevicePrivate::deviceConnected(const QBluetoothAddress &deviceAddress) +{ + if (!discoveredDevices.contains(deviceAddress)) + discoveredDevices.append(deviceAddress); + + QMetaObject::invokeMethod(q_ptr, "deviceConnected", Qt::QueuedConnection, + Q_ARG(QBluetoothAddress, deviceAddress)); +} + +void QBluetoothLocalDevicePrivate::deviceDisconnected(const QBluetoothAddress &deviceAddress) +{ + QList<QBluetoothAddress>::iterator devicePos =std::find(discoveredDevices.begin(), + discoveredDevices.end(), + deviceAddress); + + if (devicePos != discoveredDevices.end()) + discoveredDevices.erase(devicePos); + + QMetaObject::invokeMethod(q_ptr, "deviceDisconnected", Qt::QueuedConnection, + Q_ARG(QBluetoothAddress, deviceAddress)); +} + +void QBluetoothLocalDevicePrivate::emitError(QBluetoothLocalDevice::Error error, bool queued) +{ + if (queued) { + QMetaObject::invokeMethod(q_ptr, "error", Qt::QueuedConnection, + Q_ARG(QBluetoothLocalDevice::Error, error)); + } else { + emit q_ptr->error(QBluetoothLocalDevice::PairingError); + } +} + +void QBluetoothLocalDevicePrivate::emitPairingFinished(const QBluetoothAddress &deviceAddress, + Pairing pairing, bool queued) +{ + Q_ASSERT_X(!deviceAddress.isNull(), Q_FUNC_INFO, "invalid target device address"); + Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)"); + + if (queued) { + QMetaObject::invokeMethod(q_ptr, "pairingFinished", Qt::QueuedConnection, + Q_ARG(QBluetoothAddress, deviceAddress), + Q_ARG(QBluetoothLocalDevice::Pairing, pairing)); + } else { + emit q_ptr->pairingFinished(deviceAddress, pairing); + } +} + +void QBluetoothLocalDevicePrivate::unpair(const QBluetoothAddress &deviceAddress) +{ + Q_ASSERT_X(!deviceAddress.isNull(), Q_FUNC_INFO, + "invalid target address"); + + emitPairingFinished(deviceAddress, QBluetoothLocalDevice::Unpaired, true); +} + +QBluetoothLocalDevice::QBluetoothLocalDevice(QObject *parent) : + QObject(parent), + d_ptr(new QBluetoothLocalDevicePrivate(this)) +{ +} + +QBluetoothLocalDevice::QBluetoothLocalDevice(const QBluetoothAddress &address, QObject *parent) : + QObject(parent), + d_ptr(new QBluetoothLocalDevicePrivate(this, address)) +{ +} + +QBluetoothLocalDevice::~QBluetoothLocalDevice() +{ + delete d_ptr; +} + +bool QBluetoothLocalDevice::isValid() const +{ + return d_ptr->isValid(); +} + + +QString QBluetoothLocalDevice::name() const +{ + QT_BT_MAC_AUTORELEASEPOOL; + + if (isValid()) { + if (NSString *const nsn = [d_ptr->hostController nameAsString]) + return QString::fromNSString(nsn); + qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to obtain a name"; + } + + return QString(); +} + +QBluetoothAddress QBluetoothLocalDevice::address() const +{ + QT_BT_MAC_AUTORELEASEPOOL; + + if (isValid()) { + if (NSString *const nsa = [d_ptr->hostController addressAsString]) + return QBluetoothAddress(DarwinBluetooth::qt_address(nsa)); + + qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to obtain an address"; + } else { + qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid local device"; + } + + return QBluetoothAddress(); +} + +void QBluetoothLocalDevice::powerOn() +{ + if (!isValid()) + qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid local device"; +} + +void QBluetoothLocalDevice::setHostMode(QBluetoothLocalDevice::HostMode mode) +{ + Q_UNUSED(mode) + + if (!isValid()) + qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid local device"; +} + +QBluetoothLocalDevice::HostMode QBluetoothLocalDevice::hostMode() const +{ + if (!isValid() || ![d_ptr->hostController powerState]) + return HostPoweredOff; + + return HostConnectable; +} + +QList<QBluetoothAddress> QBluetoothLocalDevice::connectedDevices() const +{ + QT_BT_MAC_AUTORELEASEPOOL; + + QList<QBluetoothAddress> connectedDevices; + + // Take the devices known to IOBluetooth to be paired and connected first: + NSArray *const pairedDevices = [IOBluetoothDevice pairedDevices]; + for (IOBluetoothDevice *device in pairedDevices) { + if ([device isConnected]) { + const QBluetoothAddress address(DarwinBluetooth::qt_address([device getAddress])); + if (!address.isNull()) + connectedDevices.append(address); + } + } + + // Add devices, discovered by the connection monitor: + connectedDevices += d_ptr->discoveredDevices; + // Find something more elegant? :) + // But after all, addresses are integers. + std::sort(connectedDevices.begin(), connectedDevices.end()); + connectedDevices.erase(std::unique(connectedDevices.begin(), + connectedDevices.end()), + connectedDevices.end()); + + return connectedDevices; +} + +QList<QBluetoothHostInfo> QBluetoothLocalDevice::allDevices() +{ + QList<QBluetoothHostInfo> localDevices; + + QBluetoothLocalDevice defaultAdapter; + if (!defaultAdapter.isValid() || defaultAdapter.address().isNull()) { + qCCritical(QT_BT_OSX) << Q_FUNC_INFO <<"no valid device found"; + return localDevices; + } + + QBluetoothHostInfo deviceInfo; + deviceInfo.setName(defaultAdapter.name()); + deviceInfo.setAddress(defaultAdapter.address()); + + localDevices.append(deviceInfo); + + return localDevices; +} + +void QBluetoothLocalDevice::pairingConfirmation(bool confirmation) +{ + Q_UNUSED(confirmation) +} + + +void QBluetoothLocalDevice::requestPairing(const QBluetoothAddress &address, Pairing pairing) +{ + if (!isValid()) + qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid local device"; + + if (!isValid() || address.isNull()) { + d_ptr->emitError(PairingError, true); + return; + } + + DarwinBluetooth::qt_test_iobluetooth_runloop(); + + return d_ptr->requestPairing(address, pairing); +} + +QBluetoothLocalDevice::Pairing QBluetoothLocalDevice::pairingStatus(const QBluetoothAddress &address) const +{ + if (!isValid()) + qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid local device"; + + if (!isValid() || address.isNull()) + return Unpaired; + + return d_ptr->pairingStatus(address); +} + +QT_END_NAMESPACE |