summaryrefslogtreecommitdiffstats
path: root/src/bluetooth/qbluetoothlocaldevice_macos.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/bluetooth/qbluetoothlocaldevice_macos.mm')
-rw-r--r--src/bluetooth/qbluetoothlocaldevice_macos.mm489
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