diff options
author | Oliver Wolff <oliver.wolff@qt.io> | 2019-08-05 11:28:30 +0200 |
---|---|---|
committer | Oliver Wolff <oliver.wolff@qt.io> | 2019-08-12 08:09:19 +0200 |
commit | cfc5eaa6904354fc3084659691a6f28a51bf2269 (patch) | |
tree | da49c1c9717a03cdc2e69970ad4848e989e6bb8c /src | |
parent | 8b7b52d66f2616040ca4aaae3f2732be96e19ab8 (diff) | |
parent | 99db6526341e6f0f2a4798088c1f954cff013b7b (diff) |
Merge "Merge remote-tracking branch 'origin/dev' into wip/win"
Diffstat (limited to 'src')
94 files changed, 3576 insertions, 4282 deletions
diff --git a/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java b/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java index dc48514a..c6ffbbf4 100644 --- a/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java +++ b/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java @@ -1,6 +1,6 @@ /**************************************************************************** ** - ** Copyright (C) 2016 The Qt Company Ltd. + ** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtBluetooth module of the Qt Toolkit. @@ -47,6 +47,11 @@ import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothProfile; +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanSettings; import android.content.Context; import android.os.Build; import android.os.Handler; @@ -90,6 +95,9 @@ public class QtBluetoothLE { private final int RUNNABLE_TIMEOUT = 3000; // 3 seconds private final Handler timeoutHandler = new Handler(Looper.getMainLooper()); + /* New BTLE scanner setup since Android SDK v21 */ + private BluetoothLeScanner mBluetoothLeScanner = null; + private class TimeoutRunnable implements Runnable { public TimeoutRunnable(int handle) { pendingJobHandle = handle; } @Override @@ -123,6 +131,7 @@ public class QtBluetoothLE { @SuppressWarnings("WeakerAccess") public QtBluetoothLE() { mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner(); } public QtBluetoothLE(final String remoteAddress, Context context) { @@ -131,7 +140,6 @@ public class QtBluetoothLE { mRemoteGattAddress = remoteAddress; } - /*************************************************************/ /* Device scan */ /*************************************************************/ @@ -144,27 +152,45 @@ public class QtBluetoothLE { return true; if (isEnabled) { - mLeScanRunning = mBluetoothAdapter.startLeScan(leScanCallback); + Log.d(TAG, "New BTLE scanning API"); + ScanSettings.Builder settingsBuilder = new ScanSettings.Builder(); + settingsBuilder = settingsBuilder.setScanMode(ScanSettings.SCAN_MODE_BALANCED); + ScanSettings settings = settingsBuilder.build(); + + List<ScanFilter> filterList = new ArrayList<ScanFilter>(2); + + mBluetoothLeScanner.startScan(filterList, settings, leScanCallback21); + mLeScanRunning = true; } else { - mBluetoothAdapter.stopLeScan(leScanCallback); + mBluetoothLeScanner.stopScan(leScanCallback21); mLeScanRunning = false; } return (mLeScanRunning == isEnabled); } - // Device scan callback - private final BluetoothAdapter.LeScanCallback leScanCallback = - new BluetoothAdapter.LeScanCallback() { + // Device scan callback (SDK v21+) + private final ScanCallback leScanCallback21 = new ScanCallback() { + @Override + public void onScanResult(int callbackType, ScanResult result) { + super.onScanResult(callbackType, result); + leScanResult(qtObject, result.getDevice(), result.getRssi(), result.getScanRecord().getBytes()); + } - @Override - public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { - if (qtObject == 0) - return; + @Override + public void onBatchScanResults(List<ScanResult> results) { + super.onBatchScanResults(results); + for (ScanResult result : results) + leScanResult(qtObject, result.getDevice(), result.getRssi(), result.getScanRecord().getBytes()); - leScanResult(qtObject, device, rssi, scanRecord); - } - }; + } + + @Override + public void onScanFailed(int errorCode) { + super.onScanFailed(errorCode); + Log.d(TAG, "BTLE device scan failed with " + errorCode); + } + }; public native void leScanResult(long qtObject, BluetoothDevice device, int rssi, byte[] scanRecord); @@ -203,7 +229,6 @@ public class QtBluetoothLE { case BluetoothGatt.GATT_FAILURE: // Android's equivalent of "do not know what error it is" errorCode = 1; break; //QLowEnergyController::UnknownError case 8: // BLE_HCI_CONNECTION_TIMEOUT - case 22: // BLE_HCI_LOCAL_HOST_TERMINATED_CONNECTION Log.w(TAG, "Connection Error: Try to delay connect() call after previous activity"); errorCode = 5; break; //QLowEnergyController::ConnectionError case 19: // BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION @@ -212,6 +237,9 @@ public class QtBluetoothLE { Log.w(TAG, "The remote host closed the connection"); errorCode = 7; //QLowEnergyController::RemoteHostClosedError break; + case 22: // BLE_HCI_LOCAL_HOST_TERMINATED_CONNECTION + // Internally, Android maps PIN_OR_KEY_MISSING to GATT_CONN_TERMINATE_LOCAL_HOST + errorCode = 8; break; //QLowEnergyController::AuthorizationError default: Log.w(TAG, "Unhandled error code on connectionStateChanged: " + status + " " + newState); errorCode = status; break; //TODO deal with all errors @@ -839,6 +867,9 @@ public class QtBluetoothLE { */ public String includedServices(String serviceUuid) { + if (mBluetoothGatt == null) + return null; + UUID uuid; try { uuid = UUID.fromString(serviceUuid); diff --git a/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp b/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp index 99245af3..f1f50516 100644 --- a/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp +++ b/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp @@ -516,7 +516,7 @@ QBluetoothDeviceInfo DeviceDiscoveryBroadcastReceiver::retrieveDeviceInfo(JNIEnv const char *scanRecordBuffer = reinterpret_cast<const char *>(elems); const int scanRecordLength = env->GetArrayLength(scanRecord); - QList<QBluetoothUuid> serviceUuids; + QVector<QBluetoothUuid> serviceUuids; int i = 0; // Spec 4.2, Vol 3, Part C, Chapter 11 @@ -567,7 +567,7 @@ QBluetoothDeviceInfo DeviceDiscoveryBroadcastReceiver::retrieveDeviceInfo(JNIEnv serviceUuids.append(foundService); } - info.setServiceUuids(serviceUuids, QBluetoothDeviceInfo::DataIncomplete); + info.setServiceUuids(serviceUuids); env->ReleaseByteArrayElements(scanRecord, elems, JNI_ABORT); } diff --git a/src/bluetooth/android/servicediscoverybroadcastreceiver.cpp b/src/bluetooth/android/servicediscoverybroadcastreceiver.cpp index be1953d5..283db623 100644 --- a/src/bluetooth/android/servicediscoverybroadcastreceiver.cpp +++ b/src/bluetooth/android/servicediscoverybroadcastreceiver.cpp @@ -99,7 +99,6 @@ QList<QBluetoothUuid> ServiceDiscoveryBroadcastReceiver::convertParcelableArray( { QList<QBluetoothUuid> result; QAndroidJniEnvironment env; - QAndroidJniObject p; jobjectArray parcels = parcelUuidArray.object<jobjectArray>(); if (!parcels) @@ -107,7 +106,7 @@ QList<QBluetoothUuid> ServiceDiscoveryBroadcastReceiver::convertParcelableArray( jint size = env->GetArrayLength(parcels); for (int i = 0; i < size; i++) { - p = env->GetObjectArrayElement(parcels, i); + auto p = QAndroidJniObject::fromLocalRef(env->GetObjectArrayElement(parcels, i)); QBluetoothUuid uuid(p.callObjectMethod<jstring>("toString").toString()); //qCDebug(QT_BT_ANDROID) << uuid.toString(); diff --git a/src/bluetooth/bluetooth.pro b/src/bluetooth/bluetooth.pro index a2fd617d..e1e4d7a2 100644 --- a/src/bluetooth/bluetooth.pro +++ b/src/bluetooth/bluetooth.pro @@ -162,42 +162,27 @@ qtConfig(bluez) { include(osx/osxbt.pri) OBJECTIVE_SOURCES += \ qbluetoothlocaldevice_osx.mm \ - qbluetoothdevicediscoveryagent_osx.mm \ + qbluetoothdevicediscoveryagent_darwin.mm \ qbluetoothserviceinfo_osx.mm \ qbluetoothservicediscoveryagent_osx.mm \ qbluetoothsocket_osx.mm \ qbluetoothserver_osx.mm \ qbluetoothtransferreply_osx.mm \ - qlowenergycontroller_osx.mm \ - qlowenergyservice_osx.mm + qlowenergycontroller_darwin.mm PRIVATE_HEADERS += qbluetoothsocket_osx_p.h \ - qbluetoothserver_osx_p.h \ qbluetoothtransferreply_osx_p.h \ - qbluetoothtransferreply_osx_p.h \ - qlowenergycontroller_osx_p.h - - SOURCES -= qbluetoothdevicediscoveryagent.cpp - SOURCES -= qbluetoothserviceinfo.cpp - SOURCES -= qbluetoothservicediscoveryagent.cpp - SOURCES -= qbluetoothsocket.cpp - SOURCES -= qbluetoothsocketbase.cpp - SOURCES -= qbluetoothserver.cpp - SOURCES -= qlowenergyservice_p.cpp - SOURCES -= qlowenergyservice.cpp - SOURCES -= qlowenergycontroller.cpp - SOURCES -= qlowenergycontrollerbase.cpp + qlowenergycontroller_darwin_p.h } else:ios|tvos { DEFINES += QT_IOS_BLUETOOTH LIBS_PRIVATE += -framework Foundation -framework CoreBluetooth OBJECTIVE_SOURCES += \ - qbluetoothdevicediscoveryagent_ios.mm \ - qlowenergycontroller_osx.mm \ - qlowenergyservice_osx.mm + qbluetoothdevicediscoveryagent_darwin.mm \ + qlowenergycontroller_darwin.mm PRIVATE_HEADERS += \ - qlowenergycontroller_osx_p.h \ + qlowenergycontroller_darwin_p.h \ qbluetoothsocket_dummy_p.h include(osx/osxbt.pri) @@ -207,11 +192,6 @@ qtConfig(bluez) { qbluetoothservicediscoveryagent_p.cpp \ qbluetoothsocket_dummy.cpp \ qbluetoothserver_p.cpp - - SOURCES -= qbluetoothdevicediscoveryagent.cpp - SOURCES -= qlowenergyservice.cpp - SOURCES -= qlowenergycontroller.cpp - SOURCES -= qlowenergycontrollerbase.cpp } else: qtConfig(winrt_bt) { DEFINES += QT_WINRT_BLUETOOTH !winrt { @@ -224,7 +204,7 @@ qtConfig(bluez) { SOURCES += \ qbluetoothdevicediscoveryagent_winrt.cpp \ - qbluetoothlocaldevice_p.cpp \ + qbluetoothlocaldevice_winrt.cpp \ qbluetoothserver_winrt.cpp \ qbluetoothservicediscoveryagent_winrt.cpp \ qbluetoothserviceinfo_winrt.cpp \ diff --git a/src/bluetooth/doc/qtbluetooth.qdocconf b/src/bluetooth/doc/qtbluetooth.qdocconf index a994652b..9ee5a567 100644 --- a/src/bluetooth/doc/qtbluetooth.qdocconf +++ b/src/bluetooth/doc/qtbluetooth.qdocconf @@ -1,4 +1,5 @@ include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf) +include($QT_INSTALL_DOCS/config/exampleurl-qtconnectivity.qdocconf) project = QtBluetooth description = Qt Bluetooth Reference Documentation diff --git a/src/bluetooth/doc/src/bluetooth-index.qdoc b/src/bluetooth/doc/src/bluetooth-index.qdoc index 2a4f72bc..a0e2a048 100644 --- a/src/bluetooth/doc/src/bluetooth-index.qdoc +++ b/src/bluetooth/doc/src/bluetooth-index.qdoc @@ -41,7 +41,7 @@ Currently, the API is supported on the following platforms: \li \l {Qt for Android}{Android} \li \l {Qt for iOS}{iOS} \li \l {Qt for Linux/X11}{Linux (BlueZ 4.x/5.x)} - \li \l {Qt for OS X}{macOS} + \li \l \macos \li \l {Qt for WinRT}{WinRT} \li \l {Qt for Windows}{Win32} \row diff --git a/src/bluetooth/osx/osxbtchanneldelegate.mm b/src/bluetooth/osx/btdelegates.cpp index 822e9d4e..531ca1df 100644 --- a/src/bluetooth/osx/osxbtchanneldelegate.mm +++ b/src/bluetooth/osx/btdelegates.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtBluetooth module of the Qt Toolkit. @@ -37,16 +37,40 @@ ** ****************************************************************************/ -#include "osxbtchanneldelegate_p.h" +#include "btdelegates_p.h" + +#if defined(Q_OS_MACOS) QT_BEGIN_NAMESPACE -namespace OSXBluetooth { +namespace DarwinBluetooth { + +DeviceInquiryDelegate::~DeviceInquiryDelegate() +{ +} + +PairingDelegate::~PairingDelegate() +{ +} + +SDPInquiryDelegate::~SDPInquiryDelegate() +{ +} ChannelDelegate::~ChannelDelegate() { } +ConnectionMonitor::~ConnectionMonitor() +{ +} + +SocketListener::~SocketListener() +{ } +} // namespace DarwinBluetooth + QT_END_NAMESPACE + +#endif diff --git a/src/bluetooth/osx/btdelegates_p.h b/src/bluetooth/osx/btdelegates_p.h new file mode 100644 index 00000000..11fbcc28 --- /dev/null +++ b/src/bluetooth/osx/btdelegates_p.h @@ -0,0 +1,149 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +#ifndef BTDELEGATES_P_H +#define BTDELEGATES_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qbluetoothdevicediscoveryagent.h" +#include "qlowenergycontroller.h" +#include "qbluetooth.h" + +#include <QtCore/qsharedpointer.h> +#include <QtCore/qglobal.h> + +#if defined(Q_OS_MACOS) + +#include <IOKit/IOReturn.h> + +#include <cstdint> + +QT_BEGIN_NAMESPACE + +class QLowEnergyServicePrivate; +class QBluetoothAddress; +class QByteArray; + +namespace DarwinBluetooth { + +class DeviceInquiryDelegate +{ +public: + virtual ~DeviceInquiryDelegate(); + + virtual void inquiryFinished() = 0; + virtual void error(IOReturn error) = 0; + virtual void classicDeviceFound(void *ioBluetoothDevice) = 0; +}; + +class PairingDelegate +{ +public: + using BluetoothNumericValue = uint32_t; + using BluetoothPasskey = BluetoothNumericValue; + + virtual ~PairingDelegate(); + + virtual void connecting(void *pair) = 0; + virtual void requestPIN(void *pair) = 0; + virtual void requestUserConfirmation(void *pair, + BluetoothNumericValue) = 0; + virtual void passkeyNotification(void *pair, + BluetoothPasskey passkey) = 0; + virtual void error(void *pair, IOReturn errorCode) = 0; + virtual void pairingFinished(void *pair) = 0; +}; + +class SDPInquiryDelegate { +public: + virtual ~SDPInquiryDelegate(); + + virtual void SDPInquiryFinished(void *ioBluetoothDevice) = 0; + virtual void SDPInquiryError(void *ioBluetoothDevice, IOReturn errorCode) = 0; +}; + +// L2CAP and RFCOMM. +class ChannelDelegate +{ +public: + virtual ~ChannelDelegate(); + + virtual void setChannelError(IOReturn errorCode) = 0; + virtual void channelOpenComplete() = 0; + virtual void channelClosed() = 0; + + virtual void readChannelData(void *data, std::size_t size) = 0; + virtual void writeComplete() = 0; +}; + +class ConnectionMonitor { +public: + virtual ~ConnectionMonitor(); + + virtual void deviceConnected(const QBluetoothAddress &address) = 0; + virtual void deviceDisconnected(const QBluetoothAddress &address) = 0; +}; + +class SocketListener +{ +public: + virtual ~SocketListener(); + + virtual void openNotifyRFCOMM(void *rfcommChannel) = 0; + virtual void openNotifyL2CAP(void *l2capChannel) = 0; +}; + + +} // namespace DarwinBluetooth + +QT_END_NAMESPACE + +#endif // Q_OS_MACOS + +#endif // DARWINBTDELEGATES_P_H diff --git a/src/bluetooth/osx/osxbtchanneldelegate_p.h b/src/bluetooth/osx/btraii.mm index 1102e935..a1bf2a8d 100644 --- a/src/bluetooth/osx/osxbtchanneldelegate_p.h +++ b/src/bluetooth/osx/btraii.mm @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtBluetooth module of the Qt Toolkit. @@ -37,43 +37,74 @@ ** ****************************************************************************/ -#ifndef OSXBTCHANNELDELEGATE_P_H -#define OSXBTCHANNELDELEGATE_P_H +#include "btraii_p.h" -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// +#include <qdebug.h> -#include <QtCore/qglobal.h> +#include <Foundation/Foundation.h> -#include <IOKit/IOReturn.h> +#include <utility> QT_BEGIN_NAMESPACE -namespace OSXBluetooth { +namespace DarwinBluetooth { -class ChannelDelegate +StrongReference::StrongReference(void *object, RetainPolicy policy) + : objCInstance(object) { -public: - virtual ~ChannelDelegate(); + if (policy == RetainPolicy::doInitialRetain) + objCInstance = [getAs<NSObject>() retain]; +} - virtual void setChannelError(IOReturn errorCode) = 0; - virtual void channelOpenComplete() = 0; - virtual void channelClosed() = 0; +StrongReference::StrongReference(const StrongReference &other) +{ + objCInstance = [other.getAs<NSObject>() retain]; +} - virtual void readChannelData(void *data, std::size_t size) = 0; - virtual void writeComplete() = 0; -}; +StrongReference::StrongReference(StrongReference &&other) +{ + std::swap(objCInstance, other.objCInstance); +} +StrongReference::~StrongReference() +{ + [getAs<NSObject>() release]; } -QT_END_NAMESPACE +StrongReference &StrongReference::operator = (const StrongReference &other) noexcept +{ + if (this != &other) { + [getAs<NSObject>() release]; + objCInstance = [other.getAs<NSObject>() retain]; + } + + return *this; +} + +StrongReference &StrongReference::operator = (StrongReference &&other) noexcept +{ + swap(other); + return *this; +} + +void StrongReference::reset() +{ + [getAs<NSObject>() release]; + objCInstance = nullptr; +} -#endif +void StrongReference::reset(void *obj, RetainPolicy policy) +{ + [getAs<NSObject>() release]; + objCInstance = obj; + + if (policy == RetainPolicy::doInitialRetain) { + auto newInstance = static_cast<NSObject *>(obj); + Q_ASSERT(newInstance); + objCInstance = [newInstance retain]; + } +} + +} // namespace DarwinBluetooth + +QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothserver_osx_p.h b/src/bluetooth/osx/btraii_p.h index 3116ca02..6053d63b 100644 --- a/src/bluetooth/qbluetoothserver_osx_p.h +++ b/src/bluetooth/osx/btraii_p.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtBluetooth module of the Qt Toolkit. @@ -37,8 +37,8 @@ ** ****************************************************************************/ -#ifndef QBLUETOOTHSERVER_OSX_P_H -#define QBLUETOOTHSERVER_OSX_P_H +#ifndef BTRAII_P_H +#define BTRAII_P_H // // W A R N I N G @@ -51,77 +51,86 @@ // We mean it. // -#ifdef QT_OSX_BLUETOOTH - -#include "osx/osxbtsocketlistener_p.h" -#include "qbluetoothserviceinfo.h" -#include "osx/osxbtutility_p.h" -#include "qbluetoothserver.h" - #include <QtCore/qglobal.h> -#include <QtCore/qlist.h> + +#include <utility> QT_BEGIN_NAMESPACE -class QMutex; +namespace DarwinBluetooth { -class QBluetoothServerPrivate : public OSXBluetooth::SocketListener +enum class RetainPolicy { - friend class QBluetoothServer; - friend class QBluetoothServiceInfoPrivate; + noInitialRetain, + doInitialRetain +}; +// The class StrongReference and its descendant ScopedGuard +// are RAII classes dealing with raw pointers to NSObject class +// and its descendants (and thus hiding Objective-C's retain/ +// release semantics). The header itself is meant to be included +// into *.cpp files so it's a pure C++ code without any Objective-C +// syntax. Thus it's a bit clunky - the type information is 'erased' +// and has to be enforced by the code using these smart pointers. +// That's because these types are Objective-C classes - thus require +// Objective-C compiler to work. Member-function template 'getAs' is +// a convenience shortcut giving the desired pointer type in +// Objective-C++ files (*.mm). + +// TODO: on top of these classes I can build ObjCStrongReference (it's +// now inside osxbtutils_p.h, a template class that does have type +// information needed but works only in Objective-C++ environment. +class StrongReference +{ public: - QBluetoothServerPrivate(QBluetoothServiceInfo::Protocol type, QBluetoothServer *q); - ~QBluetoothServerPrivate(); - - void _q_newConnection(); -private: - bool startListener(quint16 realPort); - void stopListener(); - - // SocketListener (delegate): - void openNotify(IOBluetoothRFCOMMChannel *channel) override; - void openNotify(IOBluetoothL2CAPChannel *channel) override; + StrongReference() = default; + StrongReference(void *object, RetainPolicy policy); + StrongReference(const StrongReference &other); + StrongReference(StrongReference &&other); - QBluetoothServiceInfo::Protocol serverType; - QBluetoothServer *q_ptr; - QBluetoothServer::Error lastError; + ~StrongReference(); - // Either a "temporary" channelID/PSM assigned by QBluetoothServer::listen, - // or a real channelID/PSM returned by IOBluetooth after we've registered - // a service. - quint16 port; + StrongReference &operator = (const StrongReference &other) noexcept; + StrongReference &operator = (StrongReference &&other) noexcept; - typedef OSXBluetooth::ObjCScopedPointer<ObjCListener> Listener; - Listener listener; + void swap(StrongReference &other) noexcept + { + std::swap(objCInstance, other.objCInstance); + } - int maxPendingConnections; + void reset(); + void reset(void *newInstance, RetainPolicy policy); - // These static functions below - // deal with differences between bluetooth sockets - // (bluez and QtBluetooth's API) and IOBluetooth, where it's not possible - // to have a real PSM/channelID _before_ a service is registered, - // the solution - "fake" ports. - // These functions require external locking - using channelMapMutex. - static QMutex &channelMapMutex(); + template<class ObjCType> + ObjCType *getAs() const + { + return static_cast<ObjCType *>(objCInstance); + } - static bool channelIsBusy(quint16 channelID); - static quint16 findFreeChannel(); + operator bool() const + { + return !!objCInstance; + } - static bool psmIsBusy(quint16 psm); - static quint16 findFreePSM(); - - static void registerServer(QBluetoothServerPrivate *server, quint16 port); - static QBluetoothServerPrivate *registeredServer(quint16 port, QBluetoothServiceInfo::Protocol protocol); - static void unregisterServer(QBluetoothServerPrivate *server); +private: + void *objCInstance = nullptr; +}; - typedef OSXBluetooth::ObjCStrongReference<NSObject> PendingConnection; - QList<PendingConnection> pendingConnections; +class ScopedPointer final : public StrongReference +{ +public: + ScopedPointer() = default; + ScopedPointer(void *instance, RetainPolicy policy) + : StrongReference(instance, policy) + { + } +private: + Q_DISABLE_COPY_MOVE(ScopedPointer) }; -QT_END_NAMESPACE +} // namespace DarwinBluetooth -#endif //QT_OSX_BLUETOOTH +QT_END_NAMESPACE -#endif +#endif // BTRAII_P_H diff --git a/src/bluetooth/osx/osxbt.pri b/src/bluetooth/osx/osxbt.pri index b7ac0535..8f6ea0d1 100644 --- a/src/bluetooth/osx/osxbt.pri +++ b/src/bluetooth/osx/osxbt.pri @@ -1,8 +1,15 @@ -SOURCES += osx/uistrings.cpp osx/osxbtnotifier.cpp +SOURCES += osx/uistrings.cpp \ + osx/osxbtnotifier.cpp \ + osx/btdelegates.cpp + PRIVATE_HEADERS += osx/uistrings_p.h \ - osx/osxbtgcdtimer_p.h + osx/osxbtgcdtimer_p.h \ + osx/btraii_p.h \ + osx/btdelegates_p.h + -OBJECTIVE_SOURCES += osx/osxbtgcdtimer.mm +OBJECTIVE_SOURCES += osx/osxbtgcdtimer.mm \ + osx/btraii.mm #QMAKE_CXXFLAGS_WARN_ON += -Wno-nullability-completeness CONFIG(osx) { @@ -13,7 +20,6 @@ CONFIG(osx) { osx/osxbtsdpinquiry_p.h \ osx/osxbtrfcommchannel_p.h \ osx/osxbtl2capchannel_p.h \ - osx/osxbtchanneldelegate_p.h \ osx/osxbtservicerecord_p.h \ osx/osxbtsocketlistener_p.h \ osx/osxbtobexsession_p.h \ @@ -30,7 +36,6 @@ CONFIG(osx) { osx/osxbtsdpinquiry.mm \ osx/osxbtrfcommchannel.mm \ osx/osxbtl2capchannel.mm \ - osx/osxbtchanneldelegate.mm \ osx/osxbtservicerecord.mm \ osx/osxbtsocketlistener.mm \ osx/osxbtobexsession.mm \ diff --git a/src/bluetooth/osx/osxbtcentralmanager.mm b/src/bluetooth/osx/osxbtcentralmanager.mm index cadabbaf..b9a1ae0f 100644 --- a/src/bluetooth/osx/osxbtcentralmanager.mm +++ b/src/bluetooth/osx/osxbtcentralmanager.mm @@ -39,6 +39,7 @@ #include "qlowenergyserviceprivate_p.h" #include "qlowenergycharacteristic.h" +#include "qlowenergycontroller.h" #include "osxbtcentralmanager_p.h" #include "osxbtnotifier_p.h" @@ -132,6 +133,7 @@ QT_USE_NAMESPACE - (CBDescriptor *)descriptor:(const QBluetoothUuid &)dUuid forCharacteristic:(CBCharacteristic *)ch; - (bool)cacheWriteValue:(const QByteArray &)value for:(NSObject *)obj; +- (void)handleReadWriteError:(NSError *)error; - (void)reset; @end @@ -1202,6 +1204,21 @@ QT_USE_NAMESPACE // TODO: also serviceToVisit/VisitNext and visitedServices ? } +- (void)handleReadWriteError:(NSError *)error +{ + Q_ASSERT(notifier); + + switch (error.code) { + case 0x05: // GATT_INSUFFICIENT_AUTHORIZATION + case 0x0F: // GATT_INSUFFICIENT_ENCRYPTION + emit notifier->CBManagerError(QLowEnergyController::AuthorizationError); + [self detach]; + break; + default: + break; + } +} + // CBCentralManagerDelegate (the real one). - (void)centralManagerDidUpdateState:(CBCentralManager *)central @@ -1245,6 +1262,7 @@ QT_USE_NAMESPACE if (notifier) emit notifier->CBManagerError(QLowEnergyController::InvalidBluetoothAdapterError); } + [self stopWatchers]; return; } @@ -1266,6 +1284,7 @@ QT_USE_NAMESPACE if (notifier) emit notifier->CBManagerError(QLowEnergyController::InvalidBluetoothAdapterError); } + [self stopWatchers]; return; } @@ -1280,7 +1299,7 @@ QT_USE_NAMESPACE } } else { // We actually handled all known states, but .. Core Bluetooth can change? - Q_ASSERT_X(0, Q_FUNC_INFO, "invalid centra's state"); + Q_ASSERT_X(0, Q_FUNC_INFO, "invalid central's state"); } #pragma clang diagnostic pop @@ -1371,6 +1390,30 @@ QT_USE_NAMESPACE [self discoverIncludedServices]; } +- (void)peripheral:(CBPeripheral *)aPeripheral + didModifyServices:(NSArray<CBService *> *)invalidatedServices +{ + Q_UNUSED(aPeripheral) + Q_UNUSED(invalidatedServices) + + qCWarning(QT_BT_OSX) << "The peripheral has modified its services."; + // "This method is invoked whenever one or more services of a peripheral have changed. + // A peripheral’s services have changed if: + // * A service is removed from the peripheral’s database + // * A new service is added to the peripheral’s database + // * A service that was previously removed from the peripheral’s + // database is readded to the database at a different location" + + // In case new services were added - we have to discover them. + // In case some were removed - we can end up with dangling pointers + // (see our 'watchdogs', for example). To handle the situation + // we stop all current operations here, report to QLowEnergyController + // so that it can trigger re-discovery. + [self reset]; + managerState = OSXBluetooth::CentralManagerIdle; + if (notifier) + emit notifier->servicesWereModified(); +} - (void)peripheral:(CBPeripheral *)aPeripheral didDiscoverIncludedServicesForService:(CBService *)service error:(NSError *)error @@ -1514,6 +1557,7 @@ QT_USE_NAMESPACE currentReadHandle = 0; requestPending = false; emit notifier->CBManagerError(qtUuid, QLowEnergyService::CharacteristicReadError); + [self handleReadWriteError:error]; [self performNextRequest]; } return; @@ -1632,6 +1676,7 @@ QT_USE_NAMESPACE currentReadHandle = 0; requestPending = false; emit notifier->CBManagerError(qtUuid, QLowEnergyService::DescriptorReadError); + [self handleReadWriteError:error]; [self performNextRequest]; } return; @@ -1721,6 +1766,7 @@ QT_USE_NAMESPACE NSLog(@"%s failed with error %@", Q_FUNC_INFO, error); emit notifier->CBManagerError(qt_uuid(characteristic.service.UUID), QLowEnergyService::CharacteristicWriteError); + [self handleReadWriteError:error]; } else { const QLowEnergyHandle cHandle = charMap.key(characteristic); emit notifier->characteristicWritten(cHandle, valueToReport); @@ -1755,6 +1801,7 @@ QT_USE_NAMESPACE NSLog(@"%s failed with error %@", Q_FUNC_INFO, error); emit notifier->CBManagerError(qt_uuid(descriptor.characteristic.service.UUID), QLowEnergyService::DescriptorWriteError); + [self handleReadWriteError:error]; } else { const QLowEnergyHandle dHandle = descMap.key(descriptor); Q_ASSERT_X(dHandle, Q_FUNC_INFO, "descriptor not found in the descriptors map"); diff --git a/src/bluetooth/osx/osxbtdeviceinquiry.mm b/src/bluetooth/osx/osxbtdeviceinquiry.mm index 57cd73e1..3a77c1f7 100644 --- a/src/bluetooth/osx/osxbtdeviceinquiry.mm +++ b/src/bluetooth/osx/osxbtdeviceinquiry.mm @@ -43,30 +43,16 @@ #include <QtCore/qloggingcategory.h> #include <QtCore/qdebug.h> -QT_BEGIN_NAMESPACE - -namespace OSXBluetooth { - -DeviceInquiryDelegate::~DeviceInquiryDelegate() -{ -} - -} - - -QT_END_NAMESPACE - QT_USE_NAMESPACE - @implementation QT_MANGLE_NAMESPACE(OSXBTDeviceInquiry) { IOBluetoothDeviceInquiry *m_inquiry; bool m_active; - QT_PREPEND_NAMESPACE(OSXBluetooth::DeviceInquiryDelegate) *m_delegate;//C++ "delegate" + DarwinBluetooth::DeviceInquiryDelegate *m_delegate;//C++ "delegate" } -- (id)initWithDelegate:(OSXBluetooth::DeviceInquiryDelegate *)delegate +- (id)initWithDelegate:(DarwinBluetooth::DeviceInquiryDelegate *)delegate { if (self = [super init]) { Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid device inquiry delegate (null)"); @@ -158,9 +144,9 @@ QT_USE_NAMESPACE // QtBluetooth has not too many error codes, 'UnknownError' is not really // useful, report the actual error code here: qCWarning(QT_BT_OSX) << "IOKit error code: " << error; - m_delegate->error(sender, error); + m_delegate->error(error); } else { - m_delegate->inquiryFinished(sender); + m_delegate->inquiryFinished(); } } @@ -171,7 +157,7 @@ QT_USE_NAMESPACE return; Q_ASSERT_X(m_delegate, Q_FUNC_INFO, "invalid device inquiry delegate (null)"); - m_delegate->deviceFound(sender, device); + m_delegate->classicDeviceFound(device); } - (void)deviceInquiryStarted:(IOBluetoothDeviceInquiry *)sender diff --git a/src/bluetooth/osx/osxbtdeviceinquiry_p.h b/src/bluetooth/osx/osxbtdeviceinquiry_p.h index 0fec2db2..86ed3fdf 100644 --- a/src/bluetooth/osx/osxbtdeviceinquiry_p.h +++ b/src/bluetooth/osx/osxbtdeviceinquiry_p.h @@ -52,36 +52,16 @@ // #include "osxbluetooth_p.h" +#include "btdelegates_p.h" #include <QtCore/qglobal.h> #include <Foundation/Foundation.h> #include <IOKit/IOReturn.h> -@class QT_MANGLE_NAMESPACE(OSXBTDeviceInquiry); - -QT_BEGIN_NAMESPACE - -namespace OSXBluetooth { - -class DeviceInquiryDelegate { -public: - typedef QT_MANGLE_NAMESPACE(OSXBTDeviceInquiry) DeviceInquiryObjC; - - virtual ~DeviceInquiryDelegate(); - - virtual void inquiryFinished(IOBluetoothDeviceInquiry *inq) = 0; - virtual void error(IOBluetoothDeviceInquiry *inq, IOReturn error) = 0; - virtual void deviceFound(IOBluetoothDeviceInquiry *inq, IOBluetoothDevice *device) = 0; -}; - -} - -QT_END_NAMESPACE - @interface QT_MANGLE_NAMESPACE(OSXBTDeviceInquiry) : NSObject<IOBluetoothDeviceInquiryDelegate> -- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(OSXBluetooth::DeviceInquiryDelegate) *)delegate; +- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(DarwinBluetooth::DeviceInquiryDelegate) *)delegate; - (void)dealloc; - (bool)isActive; diff --git a/src/bluetooth/osx/osxbtl2capchannel.mm b/src/bluetooth/osx/osxbtl2capchannel.mm index dc8468a0..03e3a982 100644 --- a/src/bluetooth/osx/osxbtl2capchannel.mm +++ b/src/bluetooth/osx/osxbtl2capchannel.mm @@ -37,10 +37,10 @@ ** ****************************************************************************/ -#include "osxbtchanneldelegate_p.h" #include "osxbtl2capchannel_p.h" #include "qbluetoothaddress.h" #include "osxbtutility_p.h" +#include "btdelegates_p.h" #include <QtCore/qloggingcategory.h> #include <QtCore/qdebug.h> @@ -49,13 +49,13 @@ QT_USE_NAMESPACE @implementation QT_MANGLE_NAMESPACE(OSXBTL2CAPChannel) { - QT_PREPEND_NAMESPACE(OSXBluetooth)::ChannelDelegate *delegate; + QT_PREPEND_NAMESPACE(DarwinBluetooth)::ChannelDelegate *delegate; IOBluetoothDevice *device; IOBluetoothL2CAPChannel *channel; bool connected; } -- (id)initWithDelegate:(OSXBluetooth::ChannelDelegate *)aDelegate +- (id)initWithDelegate:(DarwinBluetooth::ChannelDelegate *)aDelegate { Q_ASSERT_X(aDelegate, Q_FUNC_INFO, "invalid delegate (null)"); @@ -69,7 +69,7 @@ QT_USE_NAMESPACE return self; } -- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(OSXBluetooth::ChannelDelegate) *)aDelegate +- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(DarwinBluetooth::ChannelDelegate) *)aDelegate channel:(IOBluetoothL2CAPChannel *)aChannel { // This type of channel does not require connect, it's created with diff --git a/src/bluetooth/osx/osxbtl2capchannel_p.h b/src/bluetooth/osx/osxbtl2capchannel_p.h index 512087b4..42eec8e7 100644 --- a/src/bluetooth/osx/osxbtl2capchannel_p.h +++ b/src/bluetooth/osx/osxbtl2capchannel_p.h @@ -63,7 +63,7 @@ QT_BEGIN_NAMESPACE class QBluetoothAddress; -namespace OSXBluetooth { +namespace DarwinBluetooth { class ChannelDelegate; @@ -73,8 +73,8 @@ QT_END_NAMESPACE @interface QT_MANGLE_NAMESPACE(OSXBTL2CAPChannel) : NSObject<IOBluetoothL2CAPChannelDelegate> -- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(OSXBluetooth)::ChannelDelegate *)aDelegate; -- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(OSXBluetooth)::ChannelDelegate *)aDelegate +- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(DarwinBluetooth)::ChannelDelegate *)aDelegate; +- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(DarwinBluetooth)::ChannelDelegate *)aDelegate channel:(IOBluetoothL2CAPChannel *)aChannel; - (void)dealloc; diff --git a/src/bluetooth/osx/osxbtledeviceinquiry.mm b/src/bluetooth/osx/osxbtledeviceinquiry.mm index c56b6da3..70b96ab7 100644 --- a/src/bluetooth/osx/osxbtledeviceinquiry.mm +++ b/src/bluetooth/osx/osxbtledeviceinquiry.mm @@ -121,6 +121,11 @@ QT_END_NAMESPACE QT_USE_NAMESPACE +@interface QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry)(PrivateAPI) +- (void)stopScanSafe; +- (void)stopNotifier; +@end + @implementation QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) { LECBManagerNotifier *notifier; @@ -147,17 +152,10 @@ QT_USE_NAMESPACE - (void)dealloc { - if (manager) { - [manager setDelegate:nil]; - if (internalState == InquiryActive) - [manager stopScan]; - } - - if (notifier) { - notifier->disconnect(); - notifier->deleteLater(); - } - + [self stopScanSafe]; + [manager setDelegate:nil]; + [elapsedTimer cancelTimer]; + [self stopNotifier]; [super dealloc]; } @@ -166,7 +164,7 @@ QT_USE_NAMESPACE Q_UNUSED(sender) if (internalState == InquiryActive) { - [manager stopScan]; + [self stopScanSafe]; [manager setDelegate:nil]; internalState = InquiryFinished; Q_ASSERT(notifier); @@ -228,7 +226,7 @@ QT_USE_NAMESPACE } else if (state == CBCentralManagerStateUnsupported || state == CBCentralManagerStateUnauthorized) { #endif if (internalState == InquiryActive) { - [manager stopScan]; + [self stopScanSafe]; // Not sure how this is possible at all, // probably, can never happen. internalState = ErrorPoweredOff; @@ -244,8 +242,9 @@ QT_USE_NAMESPACE #else } else if (state == CBCentralManagerStatePoweredOff) { #endif + +#ifndef Q_OS_MACOS if (internalState == InquiryStarting) { -#ifndef Q_OS_OSX // On iOS a user can see at this point an alert asking to // enable Bluetooth in the "Settings" app. If a user does so, // we'll receive 'PoweredOn' state update later. @@ -254,17 +253,19 @@ QT_USE_NAMESPACE elapsedTimer.resetWithoutRetain([[GCDTimerObjC alloc] initWithDelegate:self]); [elapsedTimer startWithTimeout:powerOffTimeoutMS step:300]; return; + } #else Q_UNUSED(powerOffTimeoutMS) -#endif - internalState = ErrorPoweredOff; - emit notifier->CBManagerError(QBluetoothDeviceDiscoveryAgent::PoweredOffError); - } else { - [manager stopScan]; - emit notifier->CBManagerError(QBluetoothDeviceDiscoveryAgent::PoweredOffError); - } - +#endif // Q_OS_MACOS + [elapsedTimer cancelTimer]; + [self stopScanSafe]; [manager setDelegate:nil]; + internalState = ErrorPoweredOff; + // On macOS we report PoweredOffError and our C++ owner will delete us + // (here we're kwnon as 'self'). Connection is Qt::QueuedConnection so we + // are apparently safe to call -stopNotifier after the signal. + emit notifier->CBManagerError(QBluetoothDeviceDiscoveryAgent::PoweredOffError); + [self stopNotifier]; } else { // The following two states we ignore (from Apple's docs): //" @@ -281,19 +282,45 @@ QT_USE_NAMESPACE #pragma clang diagnostic pop } -- (void)stop +- (void)stopScanSafe { - if (internalState == InquiryActive) - [manager stopScan]; + // CoreBluetooth warns about API misused if we call stopScan in a state + // other than powered on. Hence this 'Safe' ... + if (!manager) + return; - [elapsedTimer cancelTimer]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability-new" + + if (internalState == InquiryActive) { + const auto state = manager.data().state; + #if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_10_0) || QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_13) + if (state == CBManagerStatePoweredOn) + #else + if (state == CBCentralManagerStatePoweredOn) + #endif + [manager stopScan]; + } + +#pragma clang diagnostic pop +} + +- (void)stopNotifier +{ + if (notifier) { + notifier->disconnect(); + notifier->deleteLater(); + notifier = nullptr; + } +} +- (void)stop +{ + [self stopScanSafe]; [manager setDelegate:nil]; + [elapsedTimer cancelTimer]; + [self stopNotifier]; internalState = InquiryCancelled; - - notifier->disconnect(); - notifier->deleteLater(); - notifier = nullptr; } - (void)centralManager:(CBCentralManager *)central diff --git a/src/bluetooth/osx/osxbtnotifier_p.h b/src/bluetooth/osx/osxbtnotifier_p.h index 47ee6ba1..397214d0 100644 --- a/src/bluetooth/osx/osxbtnotifier_p.h +++ b/src/bluetooth/osx/osxbtnotifier_p.h @@ -89,13 +89,13 @@ Q_SIGNALS: void descriptorRead(QLowEnergyHandle descHandle, const QByteArray &value); void descriptorWritten(QLowEnergyHandle descHandle, const QByteArray &value); void notificationEnabled(QLowEnergyHandle charHandle, bool enabled); + void servicesWereModified(); void LEnotSupported(); void CBManagerError(QBluetoothDeviceDiscoveryAgent::Error error); void CBManagerError(QLowEnergyController::Error error); void CBManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyController::Error error); void CBManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyService::ServiceError error); - }; } diff --git a/src/bluetooth/osx/osxbtperipheralmanager.mm b/src/bluetooth/osx/osxbtperipheralmanager.mm index 1998340a..39f9808c 100644 --- a/src/bluetooth/osx/osxbtperipheralmanager.mm +++ b/src/bluetooth/osx/osxbtperipheralmanager.mm @@ -340,7 +340,7 @@ bool qt_validate_value_range(const QLowEnergyCharacteristicData &data) - (void)startAdvertising { state = PeripheralState::waitingForPowerOn; - if (manager) + if (manager.data()) [manager setDelegate:nil]; manager.reset([[CBPeripheralManager alloc] initWithDelegate:self queue:OSXBluetooth::qt_LE_queue()]); @@ -405,7 +405,7 @@ bool qt_validate_value_range(const QLowEnergyCharacteristicData &data) - (void) addServicesToPeripheral { - Q_ASSERT(manager); + Q_ASSERT(manager.data()); if (nextServiceToAdd < services.size()) [manager addService:services[nextServiceToAdd++]]; diff --git a/src/bluetooth/osx/osxbtrfcommchannel.mm b/src/bluetooth/osx/osxbtrfcommchannel.mm index 00b67ee0..d2d3e2f8 100644 --- a/src/bluetooth/osx/osxbtrfcommchannel.mm +++ b/src/bluetooth/osx/osxbtrfcommchannel.mm @@ -37,22 +37,22 @@ ** ****************************************************************************/ -#include "osxbtchanneldelegate_p.h" #include "osxbtrfcommchannel_p.h" #include "qbluetoothaddress.h" #include "osxbtutility_p.h" +#include "btdelegates_p.h" QT_USE_NAMESPACE @implementation QT_MANGLE_NAMESPACE(OSXBTRFCOMMChannel) { - QT_PREPEND_NAMESPACE(OSXBluetooth)::ChannelDelegate *delegate; + QT_PREPEND_NAMESPACE(DarwinBluetooth)::ChannelDelegate *delegate; IOBluetoothDevice *device; IOBluetoothRFCOMMChannel *channel; bool connected; } -- (id)initWithDelegate:(OSXBluetooth::ChannelDelegate *)aDelegate +- (id)initWithDelegate:(DarwinBluetooth::ChannelDelegate *)aDelegate { Q_ASSERT_X(aDelegate, Q_FUNC_INFO, "invalid delegate (null)"); @@ -66,7 +66,7 @@ QT_USE_NAMESPACE return self; } -- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(OSXBluetooth::ChannelDelegate) *)aDelegate +- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(DarwinBluetooth::ChannelDelegate) *)aDelegate channel:(IOBluetoothRFCOMMChannel *)aChannel { // This type of channel does not require connect, it's created with diff --git a/src/bluetooth/osx/osxbtrfcommchannel_p.h b/src/bluetooth/osx/osxbtrfcommchannel_p.h index 775999ed..44416cce 100644 --- a/src/bluetooth/osx/osxbtrfcommchannel_p.h +++ b/src/bluetooth/osx/osxbtrfcommchannel_p.h @@ -63,7 +63,7 @@ QT_BEGIN_NAMESPACE class QBluetoothAddress; -namespace OSXBluetooth { +namespace DarwinBluetooth { class ChannelDelegate; @@ -73,8 +73,8 @@ QT_END_NAMESPACE @interface QT_MANGLE_NAMESPACE(OSXBTRFCOMMChannel) : NSObject<IOBluetoothRFCOMMChannelDelegate> -- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(OSXBluetooth)::ChannelDelegate *)aDelegate; -- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(OSXBluetooth)::ChannelDelegate *)aDelegate +- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(DarwinBluetooth)::ChannelDelegate *)aDelegate; +- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(DarwinBluetooth)::ChannelDelegate *)aDelegate channel:(IOBluetoothRFCOMMChannel *)aChannel; - (void)dealloc; diff --git a/src/bluetooth/osx/osxbtsdpinquiry.mm b/src/bluetooth/osx/osxbtsdpinquiry.mm index a7bdc2c4..a2b02b1a 100644 --- a/src/bluetooth/osx/osxbtsdpinquiry.mm +++ b/src/bluetooth/osx/osxbtsdpinquiry.mm @@ -41,6 +41,7 @@ #include "osxbtsdpinquiry_p.h" #include "qbluetoothuuid.h" #include "osxbtutility_p.h" +#include "btdelegates_p.h" #include <QtCore/qvariant.h> #include <QtCore/qstring.h> @@ -49,10 +50,6 @@ QT_BEGIN_NAMESPACE namespace OSXBluetooth { -SDPInquiryDelegate::~SDPInquiryDelegate() -{ -} - namespace { QBluetoothUuid sdp_element_to_uuid(IOBluetoothSDPDataElement *element) @@ -213,12 +210,12 @@ using namespace OSXBluetooth; @implementation QT_MANGLE_NAMESPACE(OSXBTSDPInquiry) { - QT_PREPEND_NAMESPACE(OSXBluetooth::SDPInquiryDelegate) *delegate; + QT_PREPEND_NAMESPACE(DarwinBluetooth::SDPInquiryDelegate) *delegate; IOBluetoothDevice *device; bool isActive; } -- (id)initWithDelegate:(SDPInquiryDelegate *)aDelegate +- (id)initWithDelegate:(DarwinBluetooth::SDPInquiryDelegate *)aDelegate { Q_ASSERT_X(aDelegate, Q_FUNC_INFO, "invalid delegate (null)"); diff --git a/src/bluetooth/osx/osxbtsdpinquiry_p.h b/src/bluetooth/osx/osxbtsdpinquiry_p.h index dd38a28b..e2658670 100644 --- a/src/bluetooth/osx/osxbtsdpinquiry_p.h +++ b/src/bluetooth/osx/osxbtsdpinquiry_p.h @@ -68,17 +68,13 @@ QT_BEGIN_NAMESPACE class QBluetoothServiceInfo; class QVariant; -namespace OSXBluetooth { +namespace DarwinBluetooth { -class SDPInquiryDelegate { -public: - typedef QT_MANGLE_NAMESPACE(OSXBTSDPInquiry) ObjCServiceInquiry; +class SDPInquiryDelegate; - virtual ~SDPInquiryDelegate(); +} - virtual void SDPInquiryFinished(IOBluetoothDevice *device) = 0; - virtual void SDPInquiryError(IOBluetoothDevice *device, IOReturn errorCode) = 0; -}; +namespace OSXBluetooth { void extract_service_record(IOBluetoothSDPServiceRecord *record, QBluetoothServiceInfo &serviceInfo); QVariant extract_attribute_value(IOBluetoothSDPDataElement *dataElement); @@ -90,7 +86,7 @@ QT_END_NAMESPACE @interface QT_MANGLE_NAMESPACE(OSXBTSDPInquiry) : NSObject -- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(OSXBluetooth::SDPInquiryDelegate) *)aDelegate; +- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(DarwinBluetooth::SDPInquiryDelegate) *)aDelegate; - (void)dealloc; - (IOReturn)performSDPQueryWithDevice:(const QBluetoothAddress &)address; diff --git a/src/bluetooth/osx/osxbtsocketlistener.mm b/src/bluetooth/osx/osxbtsocketlistener.mm index 517b7f2d..10526b0f 100644 --- a/src/bluetooth/osx/osxbtsocketlistener.mm +++ b/src/bluetooth/osx/osxbtsocketlistener.mm @@ -39,31 +39,20 @@ #include "osxbtsocketlistener_p.h" #include "osxbtutility_p.h" +#include "btdelegates_p.h" #include <QtCore/qdebug.h> -QT_BEGIN_NAMESPACE - -namespace OSXBluetooth { - -SocketListener::~SocketListener() -{ -} - -} - -QT_END_NAMESPACE - QT_USE_NAMESPACE @implementation QT_MANGLE_NAMESPACE(OSXBTSocketListener) { IOBluetoothUserNotification *connectionNotification; - QT_PREPEND_NAMESPACE(OSXBluetooth::SocketListener) *delegate; + QT_PREPEND_NAMESPACE(DarwinBluetooth::SocketListener) *delegate; quint16 port; } -- (id)initWithListener:(OSXBluetooth::SocketListener *)aDelegate +- (id)initWithListener:(DarwinBluetooth::SocketListener *)aDelegate { Q_ASSERT_X(aDelegate, Q_FUNC_INFO, "invalid delegate (null)"); if (self = [super init]) { @@ -119,7 +108,7 @@ QT_USE_NAMESPACE Q_UNUSED(notification) Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)"); - delegate->openNotify(newChannel); + delegate->openNotifyRFCOMM(newChannel); } - (void)l2capOpenNotification:(IOBluetoothUserNotification *)notification @@ -128,7 +117,7 @@ QT_USE_NAMESPACE Q_UNUSED(notification) Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)"); - delegate->openNotify(newChannel); + delegate->openNotifyL2CAP(newChannel); } - (quint16)port diff --git a/src/bluetooth/osx/osxbtsocketlistener_p.h b/src/bluetooth/osx/osxbtsocketlistener_p.h index cac0b7c4..3bbce24e 100644 --- a/src/bluetooth/osx/osxbtsocketlistener_p.h +++ b/src/bluetooth/osx/osxbtsocketlistener_p.h @@ -57,22 +57,15 @@ #include <Foundation/Foundation.h> +// TODO: use the special macros we have to create an +// alias for a mangled name. @class QT_MANGLE_NAMESPACE(OSXBTSocketListener); QT_BEGIN_NAMESPACE -namespace OSXBluetooth { +namespace DarwinBluetooth { -class SocketListener -{ -public: - typedef QT_MANGLE_NAMESPACE(OSXBTSocketListener) ObjCListener; - - virtual ~SocketListener(); - - virtual void openNotify(IOBluetoothRFCOMMChannel *channel) = 0; - virtual void openNotify(IOBluetoothL2CAPChannel *channel) = 0; -}; +class SocketListener; } @@ -83,7 +76,7 @@ QT_END_NAMESPACE @interface QT_MANGLE_NAMESPACE(OSXBTSocketListener) : NSObject -- (id)initWithListener:(QT_PREPEND_NAMESPACE(OSXBluetooth::SocketListener) *)aDelegate; +- (id)initWithListener:(QT_PREPEND_NAMESPACE(DarwinBluetooth::SocketListener) *)aDelegate; - (void)dealloc; - (bool)listenRFCOMMConnectionsWithChannelID:(BluetoothRFCOMMChannelID)channelID; diff --git a/src/bluetooth/qbluetooth.cpp b/src/bluetooth/qbluetooth.cpp index 7b5fd266..1e8ce0b8 100644 --- a/src/bluetooth/qbluetooth.cpp +++ b/src/bluetooth/qbluetooth.cpp @@ -104,5 +104,6 @@ Q_LOGGING_CATEGORY(QT_BT_ANDROID, "qt.bluetooth.android") Q_LOGGING_CATEGORY(QT_BT_BLUEZ, "qt.bluetooth.bluez") Q_LOGGING_CATEGORY(QT_BT_WINDOWS, "qt.bluetooth.windows") Q_LOGGING_CATEGORY(QT_BT_WINRT, "qt.bluetooth.winrt") +Q_LOGGING_CATEGORY(QT_BT_WINRT_SERVICE_THREAD, "qt.bluetooth.winrt.service.thread") QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent.cpp index b132a3a6..fb14850e 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent.cpp +++ b/src/bluetooth/qbluetoothdevicediscoveryagent.cpp @@ -176,8 +176,6 @@ Q_DECLARE_LOGGING_CATEGORY(QT_BT) \sa QBluetoothDeviceInfo::rssi(), lowEnergyDiscoveryTimeout() */ -// TODO deviceUpdated() signal not implemented on WinRT - /*! \fn void QBluetoothDeviceDiscoveryAgent::finished() diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp index 443be14d..2f0524d4 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp @@ -363,7 +363,8 @@ void QBluetoothDeviceDiscoveryAgentPrivate::processDiscoveredDevices( discoveredDevices.append(info); qCDebug(QT_BT_ANDROID) << "Device found: " << info.name() << info.address().toString() - << "isLeScanResult:" << isLeResult; + << "isLeScanResult:" << isLeResult + << "Manufacturer data size:" << info.manufacturerData().size(); emit q->deviceDiscovered(info); } diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm b/src/bluetooth/qbluetoothdevicediscoveryagent_darwin.mm index f62ca0dd..d9883d28 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_darwin.mm @@ -37,11 +37,14 @@ ** ****************************************************************************/ +#include "qbluetoothdevicediscoveryagent_p.h" #include "qbluetoothdevicediscoveryagent.h" + #include "osx/osxbtledeviceinquiry_p.h" +#ifdef Q_OS_MACOS #include "osx/osxbtdeviceinquiry_p.h" -#include "qbluetoothlocaldevice.h" #include "osx/osxbtsdpinquiry_p.h" +#endif // Q_OS_MACOS #include "qbluetoothdeviceinfo.h" #include "osx/osxbtnotifier_p.h" #include "osx/osxbtutility_p.h" @@ -51,13 +54,14 @@ #include "qbluetoothaddress.h" #include "osx/uistrings_p.h" #include "qbluetoothuuid.h" +#include "osx/btraii_p.h" #include <QtCore/qloggingcategory.h> #include <QtCore/qscopedpointer.h> +#include <QtCore/qvector.h> #include <QtCore/qglobal.h> #include <QtCore/qstring.h> #include <QtCore/qdebug.h> -#include <QtCore/qlist.h> #include <Foundation/Foundation.h> @@ -75,108 +79,42 @@ void registerQDeviceDiscoveryMetaType() initDone = true; } } +#ifdef Q_OS_MACOS +using InquiryObjC = QT_MANGLE_NAMESPACE(OSXBTDeviceInquiry); +#endif // Q_OS_MACOS -}//namespace - -class QBluetoothDeviceDiscoveryAgentPrivate : public QObject, - public OSXBluetooth::DeviceInquiryDelegate -{ - friend class QBluetoothDeviceDiscoveryAgent; -public: - template<class T> - using ObjCScopedPointer = OSXBluetooth::ObjCScopedPointer<T>; - using LEDeviceInquiryObjC = QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry); - - QBluetoothDeviceDiscoveryAgentPrivate(const QBluetoothAddress & address, - QBluetoothDeviceDiscoveryAgent *q); - - ~QBluetoothDeviceDiscoveryAgentPrivate() override; - - bool isValid() const; - bool isActive() const; - - void start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods); - void startClassic(); - void startLE(); - void stop(); - -private: - enum AgentState { - NonActive, - ClassicScan, - LEScan - }; - - // DeviceInquiryDelegate: - void inquiryFinished(IOBluetoothDeviceInquiry *inq) override; - void error(IOBluetoothDeviceInquiry *inq, IOReturn error) override; - void deviceFound(IOBluetoothDeviceInquiry *inq, IOBluetoothDevice *device) override; - - void LEinquiryFinished(); - void LEinquiryError(QBluetoothDeviceDiscoveryAgent::Error error); - void LEnotSupported(); - - // Check if it's a really new device/updated info and emit - // q_ptr->deviceDiscovered. - void deviceFound(const QBluetoothDeviceInfo &newDeviceInfo); - - void setError(IOReturn error, const QString &text = QString()); - void setError(QBluetoothDeviceDiscoveryAgent::Error, - const QString &text = QString()); - - QBluetoothDeviceDiscoveryAgent *q_ptr; - AgentState agentState; - - QBluetoothAddress adapterAddress; +using LEInquiryObjC = QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry); - bool startPending; - bool stopPending; - - QBluetoothDeviceDiscoveryAgent::Error lastError; - QString errorString; - - QBluetoothDeviceDiscoveryAgent::InquiryType inquiryType; - - using DeviceInquiry = ObjCScopedPointer<DeviceInquiryObjC>; - DeviceInquiry inquiry; - - using LEDeviceInquiry = ObjCScopedPointer<LEDeviceInquiryObjC>; - LEDeviceInquiry inquiryLE; - - using HostController = ObjCScopedPointer<IOBluetoothHostController>; - HostController hostController; - - using DevicesList = QList<QBluetoothDeviceInfo>; - DevicesList discoveredDevices; - - int lowEnergySearchTimeout; - QBluetoothDeviceDiscoveryAgent::DiscoveryMethods requestedMethods; -}; +} //namespace QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(const QBluetoothAddress &adapter, QBluetoothDeviceDiscoveryAgent *q) : - q_ptr(q), + inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry), + lastError(QBluetoothDeviceDiscoveryAgent::NoError), agentState(NonActive), adapterAddress(adapter), startPending(false), stopPending(false), - lastError(QBluetoothDeviceDiscoveryAgent::NoError), - inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry), lowEnergySearchTimeout(OSXBluetooth::defaultLEScanTimeoutMS), - requestedMethods(QBluetoothDeviceDiscoveryAgent::ClassicMethod - | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) +#ifdef Q_OS_MACOS + requestedMethods(QBluetoothDeviceDiscoveryAgent::ClassicMethod | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod), +#else + requestedMethods(QBluetoothDeviceDiscoveryAgent::ClassicMethod), +#endif // Q_OS_MACOS + q_ptr(q) { registerQDeviceDiscoveryMetaType(); Q_ASSERT_X(q != nullptr, Q_FUNC_INFO, "invalid q_ptr (null)"); - HostController controller([[IOBluetoothHostController defaultController] retain]); - if (!controller || [controller powerState] != kBluetoothHCIPowerStateON) { +#ifdef Q_OS_MACOS + IOBluetoothHostController *hostController = [IOBluetoothHostController defaultController]; + if (!hostController || [hostController powerState] != kBluetoothHCIPowerStateON) { qCCritical(QT_BT_OSX) << "no default host controller or adapter is off"; return; } - - hostController.reset(controller.take()); + controller.reset(hostController, DarwinBluetooth::RetainPolicy::doInitialRetain); +#endif } QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate() @@ -185,7 +123,7 @@ QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate() // We want the LE scan to stop as soon as possible. if (dispatch_queue_t leQueue = OSXBluetooth::qt_LE_queue()) { // Local variable to be retained ... - LEDeviceInquiryObjC *inq = inquiryLE.data(); + LEInquiryObjC *inq = inquiryLE.getAs<LEInquiryObjC>(); dispatch_sync(leQueue, ^{ [inq stop]; }); @@ -193,11 +131,6 @@ QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate() } } -bool QBluetoothDeviceDiscoveryAgentPrivate::isValid() const -{ - return hostController && [hostController powerState] == kBluetoothHCIPowerStateON; -} - bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const { if (startPending) @@ -211,12 +144,19 @@ bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods) { - Q_ASSERT(isValid()); Q_ASSERT(!isActive()); Q_ASSERT(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError); Q_ASSERT(methods & (QBluetoothDeviceDiscoveryAgent::ClassicMethod | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)); +#ifdef Q_OS_MACOS + if (!controller) { + setError(QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError); + emit q_ptr->error(lastError); + return; + } +#endif // Q_OS_MACOS + requestedMethods = methods; if (stopPending) { @@ -230,16 +170,18 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent agentState = NonActive; discoveredDevices.clear(); setError(QBluetoothDeviceDiscoveryAgent::NoError); - +#ifdef Q_OS_MACOS if (requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod) return startClassic(); +#endif // Q_OS_MACOS startLE(); } +#ifdef Q_OS_MACOS + void QBluetoothDeviceDiscoveryAgentPrivate::startClassic() { - Q_ASSERT(isValid()); Q_ASSERT(!isActive()); Q_ASSERT(lastError == QBluetoothDeviceDiscoveryAgent::NoError); Q_ASSERT(requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod); @@ -249,7 +191,9 @@ void QBluetoothDeviceDiscoveryAgentPrivate::startClassic() if (!inquiry) { // The first Classic scan for this DDA. - inquiry.reset([[DeviceInquiryObjC alloc]initWithDelegate:this]); + inquiry.reset([[InquiryObjC alloc] initWithDelegate:this], + DarwinBluetooth::RetainPolicy::noInitialRetain); + if (!inquiry) { qCCritical(QT_BT_OSX) << "failed to initialize an Classic device inquiry"; setError(QBluetoothDeviceDiscoveryAgent::UnknownError, @@ -261,7 +205,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::startClassic() agentState = ClassicScan; - const IOReturn res = [inquiry start]; + const IOReturn res = [inquiry.getAs<InquiryObjC>() start]; if (res != kIOReturnSuccess) { setError(res, QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STARTED)); agentState = NonActive; @@ -269,9 +213,10 @@ void QBluetoothDeviceDiscoveryAgentPrivate::startClassic() } } +#endif // Q_OS_MACOS + void QBluetoothDeviceDiscoveryAgentPrivate::startLE() { - Q_ASSERT(isValid()); Q_ASSERT(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError); Q_ASSERT(requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); @@ -291,7 +236,8 @@ void QBluetoothDeviceDiscoveryAgentPrivate::startLE() this, DeviceMemFunPtr(&QBluetoothDeviceDiscoveryAgentPrivate::deviceFound)); // Check queue and create scanner: - inquiryLE.reset([[LEDeviceInquiryObjC alloc] initWithNotifier:notifier.data()]); + inquiryLE.reset([[LEInquiryObjC alloc] initWithNotifier:notifier.data()], + DarwinBluetooth::RetainPolicy::noInitialRetain); if (inquiryLE) notifier.take(); // Whatever happens next, inquiryLE is already the owner ... @@ -307,7 +253,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::startLE() // Now start in on LE queue: agentState = LEScan; // We need the local variable so that it's retained ... - LEDeviceInquiryObjC *inq = inquiryLE.data(); + LEInquiryObjC *inq = inquiryLE.getAs<LEInquiryObjC>(); dispatch_async(leQueue, ^{ [inq startWithTimeout:lowEnergySearchTimeout]; }); @@ -315,7 +261,6 @@ void QBluetoothDeviceDiscoveryAgentPrivate::startLE() void QBluetoothDeviceDiscoveryAgentPrivate::stop() { - Q_ASSERT_X(isValid(), Q_FUNC_INFO, "called on invalid device discovery agent"); Q_ASSERT_X(isActive(), Q_FUNC_INFO, "called whithout active inquiry"); Q_ASSERT_X(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError, Q_FUNC_INFO, "called with invalid bluetooth adapter"); @@ -328,8 +273,9 @@ void QBluetoothDeviceDiscoveryAgentPrivate::stop() setError(QBluetoothDeviceDiscoveryAgent::NoError); +#ifdef Q_OS_MACOS if (agentState == ClassicScan) { - const IOReturn res = [inquiry stop]; + const IOReturn res = [inquiry.getAs<InquiryObjC>() stop]; if (res != kIOReturnSuccess) { qCWarning(QT_BT_OSX) << "failed to stop"; startPending = prevStart; @@ -338,10 +284,14 @@ void QBluetoothDeviceDiscoveryAgentPrivate::stop() emit q_ptr->error(lastError); } } else { +#else + { + Q_UNUSED(prevStart) +#endif // Q_OS_MACOS dispatch_queue_t leQueue(qt_LE_queue()); Q_ASSERT(leQueue); // We need the local variable so that it's retained ... - LEDeviceInquiryObjC *inq = inquiryLE.data(); + LEInquiryObjC *inq = inquiryLE.getAs<LEInquiryObjC>(); dispatch_sync(leQueue, ^{ [inq stop]; }); @@ -351,12 +301,10 @@ void QBluetoothDeviceDiscoveryAgentPrivate::stop() } } -void QBluetoothDeviceDiscoveryAgentPrivate::inquiryFinished(IOBluetoothDeviceInquiry *inq) -{ - Q_UNUSED(inq) - - Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid device discovery agent"); //We can never be here. +#ifdef Q_OS_MACOS +void QBluetoothDeviceDiscoveryAgentPrivate::inquiryFinished() +{ // The subsequent start(LE) function (if any) // will (re)set the correct state. agentState = NonActive; @@ -381,12 +329,8 @@ void QBluetoothDeviceDiscoveryAgentPrivate::inquiryFinished(IOBluetoothDeviceInq } } -void QBluetoothDeviceDiscoveryAgentPrivate::error(IOBluetoothDeviceInquiry *inq, IOReturn error) +void QBluetoothDeviceDiscoveryAgentPrivate::error(IOReturn error) { - Q_UNUSED(inq) - - Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid device discovery agent"); - startPending = false; stopPending = false; @@ -395,12 +339,11 @@ void QBluetoothDeviceDiscoveryAgentPrivate::error(IOBluetoothDeviceInquiry *inq, emit q_ptr->error(lastError); } -void QBluetoothDeviceDiscoveryAgentPrivate::deviceFound(IOBluetoothDeviceInquiry *inq, IOBluetoothDevice *device) +void QBluetoothDeviceDiscoveryAgentPrivate::classicDeviceFound(void *obj) { - Q_UNUSED(inq) - - Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid device discovery agent"); + auto device = static_cast<IOBluetoothDevice *>(obj); Q_ASSERT_X(device, Q_FUNC_INFO, "invalid IOBluetoothDevice (nil)"); + Q_ASSERT_X(agentState == ClassicScan, Q_FUNC_INFO, "invalid agent state (expected classic scan)"); @@ -417,7 +360,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::deviceFound(IOBluetoothDeviceInquiry if (device.name) deviceName = QString::fromNSString(device.name); - const qint32 classOfDevice(device.classOfDevice); + const auto classOfDevice = qint32(device.classOfDevice); QBluetoothDeviceInfo deviceInfo(deviceAddress, deviceName, classOfDevice); deviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::BaseRateCoreConfiguration); @@ -439,6 +382,8 @@ void QBluetoothDeviceDiscoveryAgentPrivate::setError(IOReturn error, const QStri setError(QBluetoothDeviceDiscoveryAgent::UnknownError, text); } +#endif // Q_OS_MACOS + void QBluetoothDeviceDiscoveryAgentPrivate::setError(QBluetoothDeviceDiscoveryAgent::Error error, const QString &text) { lastError = error; @@ -459,14 +404,14 @@ void QBluetoothDeviceDiscoveryAgentPrivate::setError(QBluetoothDeviceDiscoveryAg case QBluetoothDeviceDiscoveryAgent::InputOutputError: errorString = QCoreApplication::translate(DEV_DISCOVERY, DD_IO); break; + case QBluetoothDeviceDiscoveryAgent::UnsupportedPlatformError: + errorString = QCoreApplication::translate(DEV_DISCOVERY, DD_NOTSUPPORTED); + break; case QBluetoothDeviceDiscoveryAgent::UnknownError: default: errorString = QCoreApplication::translate(DEV_DISCOVERY, DD_UNKNOWN_ERROR); } } - - if (lastError != QBluetoothDeviceDiscoveryAgent::NoError) - qCDebug(QT_BT_OSX) << "error set:"<<errorString; } void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError(QBluetoothDeviceDiscoveryAgent::Error error) @@ -487,6 +432,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEnotSupported() { qCDebug(QT_BT_OSX) << "no Bluetooth LE support"; +#ifdef Q_OS_MACOS if (requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod) { // Having both Classic | LE means this is not an error. LEinquiryFinished(); @@ -497,6 +443,13 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEnotSupported() // as UnsupportedDiscoveryMethod. LEinquiryError(QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod); } +#else + inquiryLE.reset(); + startPending = false; + stopPending = false; + setError(QBluetoothDeviceDiscoveryAgent::UnsupportedPlatformError); + emit q_ptr->error(lastError); +#endif } void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished() @@ -522,8 +475,12 @@ void QBluetoothDeviceDiscoveryAgentPrivate::deviceFound(const QBluetoothDeviceIn // Core Bluetooth does not allow us to access addresses, we have to use uuid instead. // This uuid has nothing to do with uuids in Bluetooth in general (it's generated by // Apple's framework using some algorithm), but it's a 128-bit uuid after all. - const bool isLE = newDeviceInfo.coreConfigurations() == QBluetoothDeviceInfo::LowEnergyCoreConfiguration; - + const bool isLE = +#ifdef Q_OS_MACOS + newDeviceInfo.coreConfigurations() == QBluetoothDeviceInfo::LowEnergyCoreConfiguration; +#else + true; +#endif // Q_OS_MACOS for (int i = 0, e = discoveredDevices.size(); i < e; ++i) { if (isLE) { if (discoveredDevices[i].deviceUuid() == newDeviceInfo.deviceUuid()) { @@ -564,6 +521,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::deviceFound(const QBluetoothDeviceIn return; } } else { +#ifdef Q_OS_MACOS if (discoveredDevices[i].address() == newDeviceInfo.address()) { if (discoveredDevices[i] == newDeviceInfo) return; @@ -572,6 +530,9 @@ void QBluetoothDeviceDiscoveryAgentPrivate::deviceFound(const QBluetoothDeviceIn emit q_ptr->deviceDiscovered(newDeviceInfo); return; } +#else + Q_UNREACHABLE(); +#endif // Q_OS_MACOS } } @@ -579,125 +540,13 @@ void QBluetoothDeviceDiscoveryAgentPrivate::deviceFound(const QBluetoothDeviceIn emit q_ptr->deviceDiscovered(newDeviceInfo); } -QBluetoothDeviceDiscoveryAgent::QBluetoothDeviceDiscoveryAgent(QObject *parent) : - QObject(parent), - d_ptr(new QBluetoothDeviceDiscoveryAgentPrivate(QBluetoothAddress(), this)) -{ -} - -QBluetoothDeviceDiscoveryAgent::QBluetoothDeviceDiscoveryAgent( - const QBluetoothAddress &deviceAdapter, QObject *parent) : - QObject(parent), - d_ptr(new QBluetoothDeviceDiscoveryAgentPrivate(deviceAdapter, this)) -{ - if (!deviceAdapter.isNull()) { - const QList<QBluetoothHostInfo> localDevices = QBluetoothLocalDevice::allDevices(); - for (const QBluetoothHostInfo &hostInfo : localDevices) { - if (hostInfo.address() == deviceAdapter) - return; - } - d_ptr->setError(InvalidBluetoothAdapterError); - } -} - -QBluetoothDeviceDiscoveryAgent::~QBluetoothDeviceDiscoveryAgent() -{ - delete d_ptr; -} - -QBluetoothDeviceDiscoveryAgent::InquiryType QBluetoothDeviceDiscoveryAgent::inquiryType() const -{ - return d_ptr->inquiryType; -} - -void QBluetoothDeviceDiscoveryAgent::setInquiryType(InquiryType type) -{ - d_ptr->inquiryType = type; -} - -QList<QBluetoothDeviceInfo> QBluetoothDeviceDiscoveryAgent::discoveredDevices() const -{ - return d_ptr->discoveredDevices; -} - QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods() { +#ifdef Q_OS_MACOS return ClassicMethod | LowEnergyMethod; -} - -void QBluetoothDeviceDiscoveryAgent::start() -{ - start(supportedDiscoveryMethods()); -} - -void QBluetoothDeviceDiscoveryAgent::start(DiscoveryMethods methods) -{ - if (methods == NoMethod) - return; - - if ((supportedDiscoveryMethods() & methods) != methods) { - d_ptr->lastError = UnsupportedDiscoveryMethod; - d_ptr->errorString = tr("One or more device discovery methods " - "are not supported on this platform"); - emit error(d_ptr->lastError); - return; - } - - if (d_ptr->lastError != InvalidBluetoothAdapterError) { - if (d_ptr->isValid()) { - if (!isActive()) - d_ptr->start(methods); - } else { - // We previously failed to initialize d_ptr correctly: - // either some memory allocation problem or - // no BT adapter found. - d_ptr->setError(InvalidBluetoothAdapterError); - emit error(InvalidBluetoothAdapterError); - } - } -} - -void QBluetoothDeviceDiscoveryAgent::stop() -{ - if (d_ptr->isValid()) { - if (isActive() && d_ptr->lastError != InvalidBluetoothAdapterError) - d_ptr->stop(); - } -} - -bool QBluetoothDeviceDiscoveryAgent::isActive() const -{ - if (d_ptr->isValid()) - return d_ptr->isActive(); - - return false; -} - -QBluetoothDeviceDiscoveryAgent::Error QBluetoothDeviceDiscoveryAgent::error() const -{ - return d_ptr->lastError; -} - -QString QBluetoothDeviceDiscoveryAgent::errorString() const -{ - return d_ptr->errorString; -} - -void QBluetoothDeviceDiscoveryAgent::setLowEnergyDiscoveryTimeout(int timeout) -{ - // cannot deliberately turn it off - if (timeout < 0) { - qCDebug(QT_BT_OSX) << "The Bluetooth Low Energy device discovery timeout cannot be negative."; - return; - } - - d_ptr->lowEnergySearchTimeout = timeout; - return; -} - -int QBluetoothDeviceDiscoveryAgent::lowEnergyDiscoveryTimeout() const -{ - return d_ptr->lowEnergySearchTimeout; +#else + return LowEnergyMethod; +#endif // Q_OS_MACOS } QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm b/src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm deleted file mode 100644 index 059f244d..00000000 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm +++ /dev/null @@ -1,458 +0,0 @@ -/**************************************************************************** -** -** 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 "qbluetoothdevicediscoveryagent.h" -#include "osx/osxbtledeviceinquiry_p.h" -#include "qbluetoothlocaldevice.h" -#include "qbluetoothdeviceinfo.h" -#include "osx/osxbtnotifier_p.h" -#include "osx/osxbtutility_p.h" -#include "osx/uistrings_p.h" -#include "qbluetoothuuid.h" - -#include <QtCore/qloggingcategory.h> -#include <QtCore/qobject.h> -#include <QtCore/qglobal.h> -#include <QtCore/qstring.h> -#include <QtCore/qdebug.h> -#include <QtCore/qlist.h> - -#include <CoreBluetooth/CoreBluetooth.h> - -QT_BEGIN_NAMESPACE - -namespace -{ - -void registerQDeviceDiscoveryMetaType() -{ - static bool initDone = false; - if (!initDone) { - qRegisterMetaType<QBluetoothDeviceInfo>(); - qRegisterMetaType<QBluetoothDeviceDiscoveryAgent::Error>(); - initDone = true; - } -} - -}//namespace - -class QBluetoothDeviceDiscoveryAgentPrivate : public QObject -{ - friend class QBluetoothDeviceDiscoveryAgent; - -public: - QBluetoothDeviceDiscoveryAgentPrivate(const QBluetoothAddress &address, - QBluetoothDeviceDiscoveryAgent *q); - virtual ~QBluetoothDeviceDiscoveryAgentPrivate(); - - bool isActive() const; - - void start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods m); - void stop(); - -private: - using LEDeviceInquiryObjC = QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry); - - void LEinquiryError(QBluetoothDeviceDiscoveryAgent::Error error); - void LEnotSupported(); - void LEdeviceFound(const QBluetoothDeviceInfo &info); - void LEinquiryFinished(); - - void setError(QBluetoothDeviceDiscoveryAgent::Error, const QString &text = QString()); - - QBluetoothDeviceDiscoveryAgent *q_ptr; - - QBluetoothDeviceDiscoveryAgent::Error lastError; - QString errorString; - - QBluetoothDeviceDiscoveryAgent::InquiryType inquiryType; - - using LEDeviceInquiry = OSXBluetooth::ObjCScopedPointer<LEDeviceInquiryObjC>; - LEDeviceInquiry inquiryLE; - - using DevicesList = QList<QBluetoothDeviceInfo>; - DevicesList discoveredDevices; - - bool startPending; - bool stopPending; - - int lowEnergySearchTimeout; -}; - -QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(const QBluetoothAddress &adapter, - QBluetoothDeviceDiscoveryAgent *q) : - q_ptr(q), - lastError(QBluetoothDeviceDiscoveryAgent::NoError), - inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry), - startPending(false), - stopPending(false), - lowEnergySearchTimeout(OSXBluetooth::defaultLEScanTimeoutMS) -{ - Q_UNUSED(adapter); - - registerQDeviceDiscoveryMetaType(); - Q_ASSERT_X(q != nullptr, Q_FUNC_INFO, "invalid q_ptr (null)"); -} - -QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate() -{ - if (inquiryLE) { - // We want the LE scan to stop as soon as possible. - if (dispatch_queue_t leQueue = OSXBluetooth::qt_LE_queue()) { - // Local variable to be retained ... - LEDeviceInquiryObjC *inq = inquiryLE.data(); - dispatch_sync(leQueue, ^{ - [inq stop]; - }); - } - } -} - -bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const -{ - if (startPending) - return true; - if (stopPending) - return false; - - return inquiryLE; -} - -void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods /*methods*/) -{ - Q_ASSERT_X(!isActive(), Q_FUNC_INFO, "called on active device discovery agent"); - - if (stopPending) { - startPending = true; - return; - } - - using namespace OSXBluetooth; - - QScopedPointer<LECBManagerNotifier> notifier(new LECBManagerNotifier); - // Connections: - using ErrMemFunPtr = void (LECBManagerNotifier::*)(QBluetoothDeviceDiscoveryAgent::Error); - notifier->connect(notifier.data(), ErrMemFunPtr(&LECBManagerNotifier::CBManagerError), - this, &QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError); - notifier->connect(notifier.data(), &LECBManagerNotifier::LEnotSupported, - this, &QBluetoothDeviceDiscoveryAgentPrivate::LEnotSupported); - notifier->connect(notifier.data(), &LECBManagerNotifier::discoveryFinished, - this, &QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished); - notifier->connect(notifier.data(), &LECBManagerNotifier::deviceDiscovered, - this, &QBluetoothDeviceDiscoveryAgentPrivate::LEdeviceFound); - - inquiryLE.reset([[LEDeviceInquiryObjC alloc] initWithNotifier:notifier.data()]); - if (inquiryLE) - notifier.take(); // Whatever happens next, inquiryLE is already the owner ... - - dispatch_queue_t leQueue(qt_LE_queue()); - if (!leQueue || !inquiryLE) { - setError(QBluetoothDeviceDiscoveryAgent::UnknownError, - QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STARTED)); - emit q_ptr->error(lastError); - return; - } - - discoveredDevices.clear(); - setError(QBluetoothDeviceDiscoveryAgent::NoError); - - // Create a local variable - to have a strong referece in a block. - LEDeviceInquiryObjC *inq = inquiryLE.data(); - dispatch_async(leQueue, ^{ - [inq startWithTimeout:lowEnergySearchTimeout]; - }); -} - -void QBluetoothDeviceDiscoveryAgentPrivate::stop() -{ - Q_ASSERT_X(isActive(), Q_FUNC_INFO, "called whithout active inquiry"); - - startPending = false; - stopPending = true; - - dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue()); - Q_ASSERT(leQueue); - - setError(QBluetoothDeviceDiscoveryAgent::NoError); - - // Create a local variable - to have a strong referece in a block. - LEDeviceInquiryObjC *inq = inquiryLE.data(); - dispatch_sync(leQueue, ^{ - [inq stop]; - }); - // We consider LE scan to be stopped immediately and - // do not care about this LEDeviceInquiry object anymore. - LEinquiryFinished(); -} - -void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError(QBluetoothDeviceDiscoveryAgent::Error error) -{ - // At the moment the only error reported by osxbtledeviceinquiry - // can be 'powered off' error, it happens - // after the LE scan started (so we have LE support and this is - // a real PoweredOffError). - Q_ASSERT_X(error == QBluetoothDeviceDiscoveryAgent::PoweredOffError, - Q_FUNC_INFO, "unexpected error"); - - inquiryLE.reset(); - - startPending = false; - stopPending = false; - setError(error); - emit q_ptr->error(lastError); -} - -void QBluetoothDeviceDiscoveryAgentPrivate::LEnotSupported() -{ - inquiryLE.reset(); - - startPending = false; - stopPending = false; - setError(QBluetoothDeviceDiscoveryAgent::UnsupportedPlatformError); - emit q_ptr->error(lastError); -} - -void QBluetoothDeviceDiscoveryAgentPrivate::LEdeviceFound(const QBluetoothDeviceInfo &newDeviceInfo) -{ - // Update, append or discard. - for (int i = 0, e = discoveredDevices.size(); i < e; ++i) { - if (discoveredDevices[i].deviceUuid() == newDeviceInfo.deviceUuid()) { - QBluetoothDeviceInfo::Fields updatedFields = QBluetoothDeviceInfo::Field::None; - if (discoveredDevices[i].rssi() != newDeviceInfo.rssi()) { - qCDebug(QT_BT_OSX) << "Updating RSSI for" << newDeviceInfo.address() - << newDeviceInfo.rssi(); - discoveredDevices[i].setRssi(newDeviceInfo.rssi()); - updatedFields.setFlag(QBluetoothDeviceInfo::Field::RSSI); - } - - if (discoveredDevices[i].manufacturerData() != newDeviceInfo.manufacturerData()) { - qCDebug(QT_BT_OSX) << "Updating manufacturer data for" << newDeviceInfo.address(); - const QVector<quint16> keys = newDeviceInfo.manufacturerIds(); - for (auto key: keys) - discoveredDevices[i].setManufacturerData(key, newDeviceInfo.manufacturerData(key)); - updatedFields.setFlag(QBluetoothDeviceInfo::Field::ManufacturerData); - } - - if (lowEnergySearchTimeout > 0) { - if (discoveredDevices[i] != newDeviceInfo) { - discoveredDevices.replace(i, newDeviceInfo); - emit q_ptr->deviceDiscovered(newDeviceInfo); - } else { - if (!updatedFields.testFlag(QBluetoothDeviceInfo::Field::None)) - emit q_ptr->deviceUpdated(discoveredDevices[i], updatedFields); - } - - return; - } - - discoveredDevices.replace(i, newDeviceInfo); - emit q_ptr->deviceDiscovered(newDeviceInfo); - - if (!updatedFields.testFlag(QBluetoothDeviceInfo::Field::None)) - emit q_ptr->deviceUpdated(discoveredDevices[i], updatedFields); - - return; - } - } - - discoveredDevices.append(newDeviceInfo); - emit q_ptr->deviceDiscovered(newDeviceInfo); -} - -void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished() -{ - inquiryLE.reset(); - - if (stopPending && !startPending) { - stopPending = false; - emit q_ptr->canceled(); - } else if (startPending) { - startPending = false; - stopPending = false; - // always the same method for start() on iOS - // classic search not supported - start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); - } else { - emit q_ptr->finished(); - } -} - -void QBluetoothDeviceDiscoveryAgentPrivate::setError(QBluetoothDeviceDiscoveryAgent::Error error, - const QString &text) -{ - lastError = error; - - if (text.length() > 0) { - errorString = text; - } else { - switch (lastError) { - case QBluetoothDeviceDiscoveryAgent::NoError: - errorString = QString(); - break; - case QBluetoothDeviceDiscoveryAgent::PoweredOffError: - errorString = QCoreApplication::translate(DEV_DISCOVERY, DD_POWERED_OFF); - break; - case QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError: - errorString = QCoreApplication::translate(DEV_DISCOVERY, DD_INVALID_ADAPTER); - break; - case QBluetoothDeviceDiscoveryAgent::InputOutputError: - errorString = QCoreApplication::translate(DEV_DISCOVERY, DD_IO); - break; - case QBluetoothDeviceDiscoveryAgent::UnsupportedPlatformError: - errorString = QCoreApplication::translate(DEV_DISCOVERY, DD_NOTSUPPORTED); - break; - case QBluetoothDeviceDiscoveryAgent::UnknownError: - default: - errorString = QCoreApplication::translate(DEV_DISCOVERY, DD_UNKNOWN_ERROR); - } - } -} - -QBluetoothDeviceDiscoveryAgent::QBluetoothDeviceDiscoveryAgent(QObject *parent) : - QObject(parent), - d_ptr(new QBluetoothDeviceDiscoveryAgentPrivate(QBluetoothAddress(), this)) -{ -} - -QBluetoothDeviceDiscoveryAgent::QBluetoothDeviceDiscoveryAgent( - const QBluetoothAddress &deviceAdapter, QObject *parent) : - QObject(parent), - d_ptr(new QBluetoothDeviceDiscoveryAgentPrivate(deviceAdapter, this)) -{ - if (!deviceAdapter.isNull()) { - qCWarning(QT_BT_OSX) << "local device address is " - "not available, provided address is ignored"; - d_ptr->setError(InvalidBluetoothAdapterError); - } -} - -QBluetoothDeviceDiscoveryAgent::~QBluetoothDeviceDiscoveryAgent() -{ - delete d_ptr; -} - -QBluetoothDeviceDiscoveryAgent::InquiryType QBluetoothDeviceDiscoveryAgent::inquiryType() const -{ - return d_ptr->inquiryType; -} - -void QBluetoothDeviceDiscoveryAgent::setInquiryType(QBluetoothDeviceDiscoveryAgent::InquiryType type) -{ - d_ptr->inquiryType = type; -} - -QList<QBluetoothDeviceInfo> QBluetoothDeviceDiscoveryAgent::discoveredDevices() const -{ - return d_ptr->discoveredDevices; -} - -QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods() -{ - return LowEnergyMethod; -} - -void QBluetoothDeviceDiscoveryAgent::start() -{ - if (d_ptr->lastError != InvalidBluetoothAdapterError) { - if (!isActive()) - d_ptr->start(supportedDiscoveryMethods()); - else - qCDebug(QT_BT_OSX) << "already started"; - } -} - -void QBluetoothDeviceDiscoveryAgent::start(DiscoveryMethods methods) -{ - if (methods == NoMethod) - return; - - DiscoveryMethods supported = - QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods(); - - if (!((supported & methods) == methods)) { - d_ptr->lastError = UnsupportedDiscoveryMethod; - d_ptr->errorString = QBluetoothDeviceDiscoveryAgent::tr("One or more device discovery methods " - "are not supported on this platform"); - emit error(d_ptr->lastError); - return; - } - - if (!isActive() && d_ptr->lastError != InvalidBluetoothAdapterError) - d_ptr->start(methods); -} - -void QBluetoothDeviceDiscoveryAgent::stop() -{ - if (isActive() && d_ptr->lastError != InvalidBluetoothAdapterError) - d_ptr->stop(); -} - -bool QBluetoothDeviceDiscoveryAgent::isActive() const -{ - return d_ptr->isActive(); -} - -QBluetoothDeviceDiscoveryAgent::Error QBluetoothDeviceDiscoveryAgent::error() const -{ - return d_ptr->lastError; -} - -QString QBluetoothDeviceDiscoveryAgent::errorString() const -{ - return d_ptr->errorString; -} - -int QBluetoothDeviceDiscoveryAgent::lowEnergyDiscoveryTimeout() const -{ - return d_ptr->lowEnergySearchTimeout; -} - -void QBluetoothDeviceDiscoveryAgent::setLowEnergyDiscoveryTimeout(int timeout) -{ - // cannot deliberately turn it off - if (timeout < 0) { - qCDebug(QT_BT_OSX) << "The Bluetooth Low Energy device discovery timeout cannot be negative."; - return; - } - - d_ptr->lowEnergySearchTimeout = timeout; - return; -} - -QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_p.h b/src/bluetooth/qbluetoothdevicediscoveryagent_p.h index 97beced3..be3a8863 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_p.h +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_p.h @@ -59,6 +59,11 @@ #include <QtCore/QTimer> #endif +#ifdef Q_OS_DARWIN +#include "osx/btdelegates_p.h" +#include "osx/btraii_p.h" +#endif // Q_OS_DARWIN + #include <QtCore/QVariantMap> #include <QtBluetooth/QBluetoothAddress> @@ -95,6 +100,9 @@ QT_END_NAMESPACE #elif defined(QT_WINRT_BLUETOOTH) #include <QtCore/QPointer> #include <QtCore/QTimer> + +using ManufacturerData = QHash<quint16, QByteArray>; +Q_DECLARE_METATYPE(ManufacturerData) #endif QT_BEGIN_NAMESPACE @@ -104,11 +112,15 @@ class QWinRTBluetoothDeviceDiscoveryWorker; #endif class QBluetoothDeviceDiscoveryAgentPrivate -#if defined(QT_ANDROID_BLUETOOTH) || defined(QT_WINRT_BLUETOOTH) || defined(QT_WIN_BLUETOOTH) +#if defined(QT_ANDROID_BLUETOOTH) || defined(QT_WINRT_BLUETOOTH) || defined(QT_WIN_BLUETOOTH) \ + || defined(Q_OS_DARWIN) : public QObject +#if defined(Q_OS_MACOS) + , public DarwinBluetooth::DeviceInquiryDelegate +#endif // Q_OS_MACOS { Q_OBJECT -#else +#else // BlueZ { #endif Q_DECLARE_PUBLIC(QBluetoothDeviceDiscoveryAgent) @@ -208,6 +220,8 @@ private: #ifdef QT_WINRT_BLUETOOTH private slots: void registerDevice(const QBluetoothDeviceInfo &info); + void updateDeviceData(const QBluetoothAddress &address, QBluetoothDeviceInfo::Fields fields, + qint16 rssi, ManufacturerData manufacturerData); void onScanFinished(); private: @@ -216,6 +230,57 @@ private: QTimer *leScanTimer; #endif +#ifdef Q_OS_DARWIN + + void startLE(); + +#ifdef Q_OS_MACOS + + void startClassic(); + + // Classic (IOBluetooth) inquiry delegate's methods: + void inquiryFinished() override; + void error(IOReturn error) override; + void classicDeviceFound(void *device) override; + // Classic (IOBluetooth) errors: + void setError(IOReturn error, const QString &text = QString()); + +#endif // Q_OS_MACOS + + // LE scan delegates (CoreBluetooth, all Darwin OSes): + void LEinquiryFinished(); + void LEinquiryError(QBluetoothDeviceDiscoveryAgent::Error error); + void LEnotSupported(); + + // LE errors: + void setError(QBluetoothDeviceDiscoveryAgent::Error, + const QString &text = QString()); + + // Both LE and Classic devices go there: + void deviceFound(const QBluetoothDeviceInfo &newDeviceInfo); + + enum AgentState { + NonActive, + ClassicScan, // macOS (IOBluetooth) only + LEScan + } agentState; + + QBluetoothAddress adapterAddress; + + bool startPending; + bool stopPending; + +#ifdef Q_OS_MACOS + + DarwinBluetooth::ScopedPointer controller; + DarwinBluetooth::ScopedPointer inquiry; + +#endif // Q_OS_MACOS + + DarwinBluetooth::ScopedPointer inquiryLE; + +#endif // Q_OS_DARWIN + int lowEnergySearchTimeout; QBluetoothDeviceDiscoveryAgent::DiscoveryMethods requestedMethods; QBluetoothDeviceDiscoveryAgent *q_ptr; diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp index a353f5e3..9d306053 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp @@ -52,6 +52,7 @@ #include <QtCore/QLoggingCategory> #include <QtCore/private/qeventdispatcher_winrt_p.h> +#include <robuffer.h> #include <wrl.h> #include <windows.devices.enumeration.h> #include <windows.devices.bluetooth.h> @@ -68,6 +69,7 @@ using namespace ABI::Windows::Devices; using namespace ABI::Windows::Devices::Bluetooth; using namespace ABI::Windows::Devices::Bluetooth::Advertisement; using namespace ABI::Windows::Devices::Enumeration; +using namespace ABI::Windows::Storage::Streams; QT_BEGIN_NAMESPACE @@ -79,6 +81,39 @@ Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT) ret; \ } +#define WARN_AND_CONTINUE_IF_FAILED(msg) \ + if (FAILED(hr)) { \ + qCWarning(QT_BT_WINRT) << msg; \ + continue; \ + } + +static ManufacturerData extractManufacturerData(ComPtr<IBluetoothLEAdvertisement> ad) +{ + ManufacturerData ret; + ComPtr<IVector<BluetoothLEManufacturerData*>> data; + HRESULT hr = ad->get_ManufacturerData(&data); + WARN_AND_RETURN_IF_FAILED("Could not obtain list of manufacturer data.", return ret); + quint32 size; + hr = data->get_Size(&size); + WARN_AND_RETURN_IF_FAILED("Could not obtain manufacturer data's list size.", return ret); + for (quint32 i = 0; i < size; ++i) { + ComPtr<IBluetoothLEManufacturerData> d; + hr = data->GetAt(i, &d); + WARN_AND_CONTINUE_IF_FAILED("Could not obtain manufacturer data."); + quint16 id; + hr = d->get_CompanyId(&id); + WARN_AND_CONTINUE_IF_FAILED("Could not obtain manufacturer data company id."); + ComPtr<IBuffer> buffer; + hr = d->get_Data(&buffer); + WARN_AND_CONTINUE_IF_FAILED("Could not obtain manufacturer data set."); + const QByteArray bufferData = byteArrayFromBuffer(buffer); + if (ret.contains(id)) + qCWarning(QT_BT_WINRT) << "Company ID already present in manufacturer data."; + ret.insert(id, bufferData); + } + return ret; +} + class QWinRTBluetoothDeviceDiscoveryWorker : public QObject { Q_OBJECT @@ -86,7 +121,7 @@ public: explicit QWinRTBluetoothDeviceDiscoveryWorker(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods); ~QWinRTBluetoothDeviceDiscoveryWorker(); void start(); - void stop(); + void stopLEWatcher(); private: void startDeviceDiscovery(QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode); @@ -117,6 +152,8 @@ public slots: Q_SIGNALS: void deviceFound(const QBluetoothDeviceInfo &info); + void deviceDataChanged(const QBluetoothAddress &address, QBluetoothDeviceInfo::Fields, + qint16 rssi, ManufacturerData manufacturerData); void scanFinished(); public: @@ -127,9 +164,15 @@ private: EventRegistrationToken m_leDeviceAddedToken; #if QT_CONFIG(winrt_btle_no_pairing) QMutex m_foundDevicesMutex; - QMap<quint64, QVector<QBluetoothUuid>> m_foundLEDevicesMap; + struct LEAdvertisingInfo { + QVector<QBluetoothUuid> services; + qint16 rssi = 0; + }; + + QMap<quint64, LEAdvertisingInfo> m_foundLEDevicesMap; #endif - QVector<quint64> m_foundLEDevices; + QMap<quint64, qint16> m_foundLEDevices; + QMap<quint64, ManufacturerData> m_foundLEManufacturerData; int m_pendingPairedDevices; ComPtr<IBluetoothDeviceStatics> m_deviceStatics; @@ -141,6 +184,8 @@ QWinRTBluetoothDeviceDiscoveryWorker::QWinRTBluetoothDeviceDiscoveryWorker(QBlue , m_pendingPairedDevices(0) { qRegisterMetaType<QBluetoothDeviceInfo>(); + qRegisterMetaType<QBluetoothDeviceInfo::Fields>(); + qRegisterMetaType<ManufacturerData>(); #ifdef CLASSIC_APP_BUILD CoInitialize(NULL); @@ -153,7 +198,7 @@ QWinRTBluetoothDeviceDiscoveryWorker::QWinRTBluetoothDeviceDiscoveryWorker(QBlue QWinRTBluetoothDeviceDiscoveryWorker::~QWinRTBluetoothDeviceDiscoveryWorker() { - stop(); + stopLEWatcher(); #ifdef CLASSIC_APP_BUILD CoUninitialize(); #endif @@ -175,7 +220,7 @@ void QWinRTBluetoothDeviceDiscoveryWorker::start() qCDebug(QT_BT_WINRT) << "Worker started"; } -void QWinRTBluetoothDeviceDiscoveryWorker::stop() +void QWinRTBluetoothDeviceDiscoveryWorker::stopLEWatcher() { if (m_leWatcher) { HRESULT hr = m_leWatcher->Stop(); @@ -250,7 +295,8 @@ void QWinRTBluetoothDeviceDiscoveryWorker::gatherMultipleDeviceInformation(quint { for (quint32 i = 0; i < deviceCount; ++i) { ComPtr<IDeviceInformation> device; - HRESULT hr = devices->GetAt(i, &device); + HRESULT hr; + hr = devices->GetAt(i, &device); Q_ASSERT_SUCCEEDED(hr); gatherDeviceInformation(device.Get(), mode); } @@ -271,11 +317,23 @@ void QWinRTBluetoothDeviceDiscoveryWorker::setupLEDeviceWatcher() HRESULT hr; hr = args->get_BluetoothAddress(&address); Q_ASSERT_SUCCEEDED(hr); + qint16 rssi; + hr = args->get_RawSignalStrengthInDBm(&rssi); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IBluetoothLEAdvertisement> ad; + hr = args->get_Advertisement(&ad); + Q_ASSERT_SUCCEEDED(hr); + const ManufacturerData manufacturerData = extractManufacturerData(ad); + QBluetoothDeviceInfo::Fields changedFields = QBluetoothDeviceInfo::Field::None; + if (!m_foundLEManufacturerData.contains(address)) { + m_foundLEManufacturerData.insert(address, manufacturerData); + changedFields.setFlag(QBluetoothDeviceInfo::Field::ManufacturerData); + } else if (m_foundLEManufacturerData.value(address) != manufacturerData) { + m_foundLEManufacturerData[address] = manufacturerData; + changedFields.setFlag(QBluetoothDeviceInfo::Field::ManufacturerData); + } #if QT_CONFIG(winrt_btle_no_pairing) if (supportsNewLEApi()) { - ComPtr<IBluetoothLEAdvertisement> ad; - hr = args->get_Advertisement(&ad); - Q_ASSERT_SUCCEEDED(hr); ComPtr<IVector<GUID>> guids; hr = ad->get_ServiceUuids(&guids); Q_ASSERT_SUCCEEDED(hr); @@ -295,7 +353,12 @@ void QWinRTBluetoothDeviceDiscoveryWorker::setupLEDeviceWatcher() if (m_foundLEDevicesMap.contains(address)) { if (size == 0) return S_OK; - QVector<QBluetoothUuid> foundServices = m_foundLEDevicesMap.value(address); + const LEAdvertisingInfo adInfo = m_foundLEDevicesMap.value(address); + QVector<QBluetoothUuid> foundServices = adInfo.services; + if (adInfo.rssi != rssi) { + m_foundLEDevicesMap[address].rssi = rssi; + changedFields.setFlag(QBluetoothDeviceInfo::Field::RSSI); + } bool newServiceAdded = false; for (const QBluetoothUuid &uuid : qAsConst(serviceUuids)) { if (!foundServices.contains(uuid)) { @@ -303,20 +366,43 @@ void QWinRTBluetoothDeviceDiscoveryWorker::setupLEDeviceWatcher() newServiceAdded = true; } } - if (!newServiceAdded) + if (!newServiceAdded) { + if (!changedFields.testFlag(QBluetoothDeviceInfo::Field::None)) { + QMetaObject::invokeMethod(this, "deviceDataChanged", Qt::AutoConnection, + Q_ARG(QBluetoothAddress, QBluetoothAddress(address)), + Q_ARG(QBluetoothDeviceInfo::Fields, changedFields), + Q_ARG(qint16, rssi), + Q_ARG(ManufacturerData, manufacturerData)); + } return S_OK; - m_foundLEDevicesMap[address] = foundServices; + } + m_foundLEDevicesMap[address].services = foundServices; } else { - m_foundLEDevicesMap.insert(address, serviceUuids); + LEAdvertisingInfo info; + info.services = std::move(serviceUuids); + info.rssi = rssi; + m_foundLEDevicesMap.insert(address, info); } locker.unlock(); } else #endif { - if (m_foundLEDevices.contains(address)) + if (m_foundLEDevices.contains(address)) { + if (m_foundLEDevices.value(address) != rssi) { + m_foundLEDevices[address] = rssi; + changedFields.setFlag(QBluetoothDeviceInfo::Field::RSSI); + } + if (!changedFields.testFlag(QBluetoothDeviceInfo::Field::None)) { + QMetaObject::invokeMethod(this, "deviceDataChanged", Qt::AutoConnection, + Q_ARG(QBluetoothAddress, QBluetoothAddress(address)), + Q_ARG(QBluetoothDeviceInfo::Fields, changedFields), + Q_ARG(qint16, rssi), + Q_ARG(ManufacturerData, manufacturerData)); + } return S_OK; - m_foundLEDevices.append(address); + } + m_foundLEDevices.insert(address, rssi); } leBluetoothInfoFromAddressAsync(address); return S_OK; @@ -329,6 +415,7 @@ void QWinRTBluetoothDeviceDiscoveryWorker::setupLEDeviceWatcher() void QWinRTBluetoothDeviceDiscoveryWorker::finishDiscovery() { emit scanFinished(); + stopLEWatcher(); deleteLater(); } @@ -616,13 +703,19 @@ HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothLEDeviceFound(ComPtr<IB Q_ASSERT_SUCCEEDED(hr); uuids.append(QBluetoothUuid(uuid)); } + const qint16 rssi = m_foundLEDevices.value(address); + const ManufacturerData manufacturerData = m_foundLEManufacturerData.value(address); qCDebug(QT_BT_WINRT) << "Discovered BTLE device: " << QString::number(address) << btName - << "Num UUIDs" << uuids.count(); + << "Num UUIDs" << uuids.count() << "RSSI:" << rssi + << "Num manufacturer data" << manufacturerData.count(); QBluetoothDeviceInfo info(QBluetoothAddress(address), btName, 0); info.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration); info.setServiceUuids(uuids); + info.setRssi(rssi); + for (const quint16 key : manufacturerData.keys()) + info.setManufacturerData(key, manufacturerData.value(key)); info.setCached(true); QMetaObject::invokeMethod(this, "deviceFound", Qt::AutoConnection, @@ -665,11 +758,13 @@ HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothLEDeviceFound(ComPtr<IB boolean isPaired; hr = pairing->get_IsPaired(&isPaired); Q_ASSERT_SUCCEEDED(hr); - QList<QBluetoothUuid> uuids; + QVector<QBluetoothUuid> uuids; + const LEAdvertisingInfo adInfo = m_foundLEDevicesMap.value(address); + const qint16 rssi = adInfo.rssi; // Use the services obtained from the advertisement data if the device is not paired if (!isPaired) { - uuids = m_foundLEDevicesMap.value(address).toList(); + uuids = adInfo.services; } else { IVectorView <GenericAttributeProfile::GattDeviceService *> *deviceServices; hr = device->get_GattServices(&deviceServices); @@ -687,13 +782,18 @@ HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothLEDeviceFound(ComPtr<IB uuids.append(QBluetoothUuid(uuid)); } } + const ManufacturerData manufacturerData = m_foundLEManufacturerData.value(address); qCDebug(QT_BT_WINRT) << "Discovered BTLE device: " << QString::number(address) << btName - << "Num UUIDs" << uuids.count(); + << "Num UUIDs" << uuids.count() << "RSSI:" << rssi + << "Num manufacturer data" << manufacturerData.count(); QBluetoothDeviceInfo info(QBluetoothAddress(address), btName, 0); info.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration); - info.setServiceUuids(uuids, QBluetoothDeviceInfo::DataIncomplete); + info.setServiceUuids(uuids); + info.setRssi(rssi); + for (quint16 key : manufacturerData.keys()) + info.setManufacturerData(key, manufacturerData.value(key)); info.setCached(true); QMetaObject::invokeMethod(this, "deviceFound", Qt::AutoConnection, @@ -739,6 +839,8 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent discoveredDevices.clear(); connect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::deviceFound, this, &QBluetoothDeviceDiscoveryAgentPrivate::registerDevice); + connect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::deviceDataChanged, + this, &QBluetoothDeviceDiscoveryAgentPrivate::updateDeviceData); connect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::scanFinished, this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished); worker->start(); @@ -759,7 +861,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::stop() { Q_Q(QBluetoothDeviceDiscoveryAgent); if (worker) { - worker->stop(); + worker->stopLEWatcher(); disconnectAndClearWorker(); emit q->canceled(); } @@ -793,6 +895,30 @@ void QBluetoothDeviceDiscoveryAgentPrivate::registerDevice(const QBluetoothDevic emit q->deviceDiscovered(info); } +void QBluetoothDeviceDiscoveryAgentPrivate::updateDeviceData(const QBluetoothAddress &address, + QBluetoothDeviceInfo::Fields fields, + qint16 rssi, + ManufacturerData manufacturerData) +{ + if (fields.testFlag(QBluetoothDeviceInfo::Field::None)) + return; + + Q_Q(QBluetoothDeviceDiscoveryAgent); + for (QList<QBluetoothDeviceInfo>::iterator iter = discoveredDevices.begin(); + iter != discoveredDevices.end(); ++iter) { + if (iter->address() == address) { + qCDebug(QT_BT_WINRT) << "Updating data for device" << iter->name() << iter->address(); + if (fields.testFlag(QBluetoothDeviceInfo::Field::RSSI)) + iter->setRssi(rssi); + if (fields.testFlag(QBluetoothDeviceInfo::Field::ManufacturerData)) + for (quint16 key : manufacturerData.keys()) + iter->setManufacturerData(key, manufacturerData.value(key)); + emit q->deviceUpdated(*iter, fields); + return; + } + } +} + void QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished() { Q_Q(QBluetoothDeviceDiscoveryAgent); @@ -802,17 +928,18 @@ void QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished() void QBluetoothDeviceDiscoveryAgentPrivate::disconnectAndClearWorker() { - Q_Q(QBluetoothDeviceDiscoveryAgent); if (!worker) return; disconnect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::scanFinished, - this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished); + this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished); disconnect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::deviceFound, - q, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered); + this, &QBluetoothDeviceDiscoveryAgentPrivate::registerDevice); + disconnect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::deviceDataChanged, + this, &QBluetoothDeviceDiscoveryAgentPrivate::updateDeviceData); if (leScanTimer) { disconnect(leScanTimer, &QTimer::timeout, - worker, &QWinRTBluetoothDeviceDiscoveryWorker::finishDiscovery); + worker, &QWinRTBluetoothDeviceDiscoveryWorker::finishDiscovery); } worker.clear(); } diff --git a/src/bluetooth/qbluetoothdeviceinfo.cpp b/src/bluetooth/qbluetoothdeviceinfo.cpp index cc0d98a4..46df5c7b 100644 --- a/src/bluetooth/qbluetoothdeviceinfo.cpp +++ b/src/bluetooth/qbluetoothdeviceinfo.cpp @@ -647,7 +647,6 @@ QVector<quint16> QBluetoothDeviceInfo::manufacturerIds() const */ QByteArray QBluetoothDeviceInfo::manufacturerData(quint16 manufacturerId) const { - // TODO Currently not implemented on WinRT Q_D(const QBluetoothDeviceInfo); return d->manufacturerData.value(manufacturerId); } diff --git a/src/bluetooth/qbluetoothdeviceinfo.h b/src/bluetooth/qbluetoothdeviceinfo.h index db0de7cd..11cb2bea 100644 --- a/src/bluetooth/qbluetoothdeviceinfo.h +++ b/src/bluetooth/qbluetoothdeviceinfo.h @@ -284,5 +284,8 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(QBluetoothDeviceInfo::ServiceClasses) QT_END_NAMESPACE Q_DECLARE_METATYPE(QBluetoothDeviceInfo) +#ifdef QT_WINRT_BLUETOOTH +Q_DECLARE_METATYPE(QBluetoothDeviceInfo::Fields) +#endif #endif diff --git a/src/bluetooth/qbluetoothlocaldevice_android.cpp b/src/bluetooth/qbluetoothlocaldevice_android.cpp index b46923eb..40e4c2d4 100644 --- a/src/bluetooth/qbluetoothlocaldevice_android.cpp +++ b/src/bluetooth/qbluetoothlocaldevice_android.cpp @@ -90,23 +90,16 @@ static QAndroidJniObject getDefaultAdapter() QAndroidJniObject adapter = QAndroidJniObject::callStaticObjectMethod( "android/bluetooth/BluetoothAdapter", "getDefaultAdapter", "()Landroid/bluetooth/BluetoothAdapter;"); + QAndroidJniExceptionCleaner exCleaner{QAndroidJniExceptionCleaner::OutputMode::Verbose}; if (!adapter.isValid()) { - QAndroidJniEnvironment env; - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->ExceptionClear(); - } + exCleaner.clean(); // workaround stupid bt implementations where first call of BluetoothAdapter.getDefaultAdapter() always fails adapter = QAndroidJniObject::callStaticObjectMethod( "android/bluetooth/BluetoothAdapter", "getDefaultAdapter", "()Landroid/bluetooth/BluetoothAdapter;"); - if (!adapter.isValid()) { - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->ExceptionClear(); - } - } + if (!adapter.isValid()) + exCleaner.clean(); } return adapter; } diff --git a/src/bluetooth/qbluetoothlocaldevice_p.cpp b/src/bluetooth/qbluetoothlocaldevice_p.cpp index 793a8311..fa4a509e 100644 --- a/src/bluetooth/qbluetoothlocaldevice_p.cpp +++ b/src/bluetooth/qbluetoothlocaldevice_p.cpp @@ -51,7 +51,7 @@ QBluetoothLocalDevice::QBluetoothLocalDevice(QObject *parent) : QObject(parent), d_ptr(new QBluetoothLocalDevicePrivate(this, QBluetoothAddress())) { -#if !defined(QT_IOS_BLUETOOTH) && !defined(QT_WINRT_BLUETOOTH) +#if !defined(QT_IOS_BLUETOOTH) printDummyWarning(); #endif registerQBluetoothLocalDeviceMetaType(); @@ -85,11 +85,7 @@ void QBluetoothLocalDevice::setHostMode(QBluetoothLocalDevice::HostMode mode) QBluetoothLocalDevice::HostMode QBluetoothLocalDevice::hostMode() const { -#ifdef QT_WINRT_BLUETOOTH - return HostConnectable; -#else return HostPoweredOff; -#endif } QList<QBluetoothAddress> QBluetoothLocalDevice::connectedDevices() const @@ -116,11 +112,7 @@ QBluetoothLocalDevice::Pairing QBluetoothLocalDevice::pairingStatus( const QBluetoothAddress &address) const { Q_UNUSED(address); -#ifdef QT_WINRT_BLUETOOTH - return Paired; -#else return Unpaired; -#endif } void QBluetoothLocalDevice::pairingConfirmation(bool confirmation) diff --git a/src/bluetooth/qbluetoothlocaldevice_p.h b/src/bluetooth/qbluetoothlocaldevice_p.h index e18169f9..28e7ed53 100644 --- a/src/bluetooth/qbluetoothlocaldevice_p.h +++ b/src/bluetooth/qbluetoothlocaldevice_p.h @@ -85,6 +85,21 @@ QT_END_NAMESPACE #include <QtCore/QPair> #endif +#ifdef QT_WINRT_BLUETOOTH +#include <wrl.h> + +namespace ABI { + namespace Windows { + namespace Devices { + namespace Bluetooth { + struct IBluetoothDeviceStatics; + struct IBluetoothLEDeviceStatics; + } + } + } +} +#endif + QT_BEGIN_NAMESPACE extern void registerQBluetoothLocalDeviceMetaType(); @@ -232,7 +247,22 @@ public: private: QBluetoothLocalDevice *q_ptr; }; -#elif !defined(QT_OSX_BLUETOOTH) // winrt and dummy backend +#elif defined(QT_WINRT_BLUETOOTH) +class QBluetoothLocalDevicePrivate : public QObject +{ + Q_DECLARE_PUBLIC(QBluetoothLocalDevice) +public: + QBluetoothLocalDevicePrivate(QBluetoothLocalDevice *q, + QBluetoothAddress = QBluetoothAddress()); + + bool isValid() const; + +private: + QBluetoothLocalDevice *q_ptr; + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::IBluetoothDeviceStatics> mStatics; + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::IBluetoothLEDeviceStatics> mLEStatics; +}; +#elif !defined(QT_OSX_BLUETOOTH) // dummy backend class QBluetoothLocalDevicePrivate : public QObject { public: @@ -243,11 +273,7 @@ public: bool isValid() const { -#ifndef QT_WINRT_BLUETOOTH return false; -#else - return true; -#endif } }; #endif diff --git a/src/bluetooth/qbluetoothlocaldevice_winrt.cpp b/src/bluetooth/qbluetoothlocaldevice_winrt.cpp new file mode 100644 index 00000000..ae794db0 --- /dev/null +++ b/src/bluetooth/qbluetoothlocaldevice_winrt.cpp @@ -0,0 +1,202 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "qbluetoothlocaldevice.h" +#include "qbluetoothaddress.h" + +#include "qbluetoothlocaldevice_p.h" + +#ifdef CLASSIC_APP_BUILD +#define Q_OS_WINRT +#endif +#include <QtCore/qfunctions_winrt.h> + +#include <robuffer.h> +#include <windows.devices.bluetooth.h> +#include <wrl.h> + +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Devices::Bluetooth; +using namespace ABI::Windows::Devices::Enumeration; +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; + +QT_BEGIN_NAMESPACE + +template <class DeviceStatics, class OpResult, class Device, class Device2> +ComPtr<IDeviceInformationPairing> getPairingInfo(ComPtr<DeviceStatics> deviceStatics, + const QBluetoothAddress &address) +{ + ComPtr<IAsyncOperation<OpResult *>> op; + if (!deviceStatics) + return nullptr; + HRESULT hr = deviceStatics->FromBluetoothAddressAsync(address.toUInt64(), &op); + RETURN_IF_FAILED("Could not obtain device from address", return nullptr); + ComPtr<Device> device; + hr = QWinRTFunctions::await(op, device.GetAddressOf(), + QWinRTFunctions::ProcessMainThreadEvents, 5000); + if (FAILED(hr) || !device) { + qErrnoWarning("Could not obtain device from address"); + return nullptr; + } + ComPtr<Device2> device2; + hr = device.As(&device2); + RETURN_IF_FAILED("Could not cast device", return nullptr); + ComPtr<IDeviceInformation> deviceInfo; + hr = device2->get_DeviceInformation(&deviceInfo); + if (FAILED(hr) || !deviceInfo) { + qErrnoWarning("Could not obtain device information"); + return nullptr; + } + ComPtr<IDeviceInformation2> deviceInfo2; + hr = deviceInfo.As(&deviceInfo2); + RETURN_IF_FAILED("Could not cast device information", return nullptr); + ComPtr<IDeviceInformationPairing> pairingInfo; + hr = deviceInfo2->get_Pairing(&pairingInfo); + RETURN_IF_FAILED("Could not obtain pairing information", return nullptr); + return pairingInfo; +} + +QBluetoothLocalDevice::QBluetoothLocalDevice(QObject *parent) : + QObject(parent), + d_ptr(new QBluetoothLocalDevicePrivate(this, QBluetoothAddress())) +{ + registerQBluetoothLocalDeviceMetaType(); +} + +QBluetoothLocalDevice::QBluetoothLocalDevice(const QBluetoothAddress &address, QObject *parent) : + QObject(parent), + d_ptr(new QBluetoothLocalDevicePrivate(this, address)) +{ + registerQBluetoothLocalDeviceMetaType(); +} + +QBluetoothLocalDevicePrivate::QBluetoothLocalDevicePrivate(QBluetoothLocalDevice *q, QBluetoothAddress) + : q_ptr(q) +{ + GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothLEDevice).Get(), &mLEStatics); + GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothDevice).Get(), &mStatics); +} + +bool QBluetoothLocalDevicePrivate::isValid() const +{ + return (mStatics != nullptr && mLEStatics != nullptr); +} + +void QBluetoothLocalDevice::requestPairing(const QBluetoothAddress &address, Pairing pairing) +{ + Q_UNUSED(address); + Q_UNUSED(pairing); + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, + Q_ARG(QBluetoothLocalDevice::Error, + QBluetoothLocalDevice::PairingError)); +} + +QBluetoothLocalDevice::Pairing QBluetoothLocalDevice::pairingStatus( + const QBluetoothAddress &address) const +{ + if (!isValid() || address.isNull()) + return QBluetoothLocalDevice::Unpaired; + + ComPtr<IDeviceInformationPairing> pairingInfo = getPairingInfo<IBluetoothLEDeviceStatics, + BluetoothLEDevice, IBluetoothLEDevice, IBluetoothLEDevice2>(d_ptr->mLEStatics, address); + if (!pairingInfo) + pairingInfo = getPairingInfo<IBluetoothDeviceStatics, BluetoothDevice, + IBluetoothDevice, IBluetoothDevice2>(d_ptr->mStatics, address); + if (!pairingInfo) + return QBluetoothLocalDevice::Unpaired; + boolean isPaired; + HRESULT hr = pairingInfo->get_IsPaired(&isPaired); + RETURN_IF_FAILED("Could not obtain device pairing", return QBluetoothLocalDevice::Unpaired); + if (!isPaired) + return QBluetoothLocalDevice::Unpaired; + + ComPtr<IDeviceInformationPairing2> pairingInfo2; + hr = pairingInfo.As(&pairingInfo2); + RETURN_IF_FAILED("Could not cast pairing info", return QBluetoothLocalDevice::Paired); + DevicePairingProtectionLevel protection = DevicePairingProtectionLevel_None; + hr = pairingInfo2->get_ProtectionLevel(&protection); + RETURN_IF_FAILED("Could not obtain pairing protection level", return QBluetoothLocalDevice::Paired); + if (protection == DevicePairingProtectionLevel_Encryption + || protection == DevicePairingProtectionLevel_EncryptionAndAuthentication) + return QBluetoothLocalDevice::AuthorizedPaired; + return QBluetoothLocalDevice::Paired; +} + +void QBluetoothLocalDevice::pairingConfirmation(bool confirmation) +{ + Q_UNUSED(confirmation); +} + +void QBluetoothLocalDevice::setHostMode(QBluetoothLocalDevice::HostMode mode) +{ + Q_UNUSED(mode); +} + +QBluetoothLocalDevice::HostMode QBluetoothLocalDevice::hostMode() const +{ + return HostConnectable; +} + +QList<QBluetoothAddress> QBluetoothLocalDevice::connectedDevices() const +{ + return QList<QBluetoothAddress>(); +} + +void QBluetoothLocalDevice::powerOn() +{ +} + +QString QBluetoothLocalDevice::name() const +{ + return QString(); +} + +QBluetoothAddress QBluetoothLocalDevice::address() const +{ + return QBluetoothAddress(); +} + +QList<QBluetoothHostInfo> QBluetoothLocalDevice::allDevices() +{ + QList<QBluetoothHostInfo> localDevices; + return localDevices; +} + +QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothserver.cpp b/src/bluetooth/qbluetoothserver.cpp index 75ac9979..daed5dc2 100644 --- a/src/bluetooth/qbluetoothserver.cpp +++ b/src/bluetooth/qbluetoothserver.cpp @@ -265,7 +265,7 @@ bool QBluetoothServer::isListening() const { Q_D(const QBluetoothServer); -#if defined(QT_ANDROID_BLUETOOTH) || defined(QT_WINRT_BLUETOOTH) +#if defined(QT_ANDROID_BLUETOOTH) || defined(QT_WINRT_BLUETOOTH) || defined(QT_OSX_BLUETOOTH) return d->isListening(); #endif diff --git a/src/bluetooth/qbluetoothserver_osx.mm b/src/bluetooth/qbluetoothserver_osx.mm index eefaf4da..83d7e060 100644 --- a/src/bluetooth/qbluetoothserver_osx.mm +++ b/src/bluetooth/qbluetoothserver_osx.mm @@ -38,7 +38,7 @@ ****************************************************************************/ #include "osx/osxbtsocketlistener_p.h" -#include "qbluetoothserver_osx_p.h" +#include "qbluetoothserver_p.h" // The order is important: a workround for // a private header included by private header @@ -58,7 +58,6 @@ #include <QtCore/qglobal.h> #include <QtCore/qmutex.h> -// Import, since Obj-C headers do not have inclusion guards. #include <Foundation/Foundation.h> #include <limits> @@ -67,7 +66,9 @@ QT_BEGIN_NAMESPACE namespace { -typedef QBluetoothServiceInfo QSInfo; +using DarwinBluetooth::RetainPolicy; +using ServiceInfo = QBluetoothServiceInfo; +using ObjCListener = QT_MANGLE_NAMESPACE(OSXBTSocketListener); QMap<quint16, QBluetoothServerPrivate *> &busyPSMs() { @@ -86,79 +87,89 @@ typedef QMap<quint16, QBluetoothServerPrivate *>::iterator ServerMapIterator; } -QBluetoothServerPrivate::QBluetoothServerPrivate(QSInfo::Protocol type, QBluetoothServer *q) - : serverType(type), - q_ptr(q), - lastError(QBluetoothServer::NoError), - port(0), - maxPendingConnections(1) +QBluetoothServerPrivate::QBluetoothServerPrivate(ServiceInfo::Protocol type, + QBluetoothServer *parent) + : socket(nullptr), + maxPendingConnections(1), + securityFlags(QBluetooth::NoSecurity), + serverType(type), + q_ptr(parent), + m_lastError(QBluetoothServer::NoError), + port(0) { - Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)"); - if (serverType == QSInfo::UnknownProtocol) + if (serverType == ServiceInfo::UnknownProtocol) qCWarning(QT_BT_OSX) << "unknown protocol"; } QBluetoothServerPrivate::~QBluetoothServerPrivate() { - // Actually, not good, but lock must be acquired. - // TODO: test this. const QMutexLocker lock(&channelMapMutex()); unregisterServer(this); } -void QBluetoothServerPrivate::_q_newConnection() -{ - // Noop, we have openNotify for this. -} - bool QBluetoothServerPrivate::startListener(quint16 realPort) { Q_ASSERT_X(realPort, Q_FUNC_INFO, "invalid port"); - if (serverType == QSInfo::UnknownProtocol) { + if (serverType == ServiceInfo::UnknownProtocol) { qCWarning(QT_BT_OSX) << "invalid protocol"; return false; } - if (!listener) - listener.reset([[ObjCListener alloc] initWithListener:this]); + if (!listener) { + listener.reset([[ObjCListener alloc] initWithListener:this], + RetainPolicy::noInitialRetain); + } bool result = false; - if (serverType == QSInfo::RfcommProtocol) - result = [listener listenRFCOMMConnectionsWithChannelID:realPort]; + if (serverType == ServiceInfo::RfcommProtocol) + result = [listener.getAs<ObjCListener>() listenRFCOMMConnectionsWithChannelID:realPort]; else - result = [listener listenL2CAPConnectionsWithPSM:realPort]; + result = [listener.getAs<ObjCListener>() listenL2CAPConnectionsWithPSM:realPort]; if (!result) - listener.reset(nil); + listener.reset(); return result; } +bool QBluetoothServerPrivate::isListening() const +{ + if (serverType == ServiceInfo::UnknownProtocol) + return false; + + const QMutexLocker lock(&QBluetoothServerPrivate::channelMapMutex()); + return QBluetoothServerPrivate::registeredServer(q_ptr->serverPort(), serverType); +} + void QBluetoothServerPrivate::stopListener() { - listener.reset(nil); + listener.reset(); } -void QBluetoothServerPrivate::openNotify(IOBluetoothRFCOMMChannel *channel) +void QBluetoothServerPrivate::openNotifyRFCOMM(void *generic) { + auto channel = static_cast<IOBluetoothRFCOMMChannel *>(generic); + Q_ASSERT_X(listener, Q_FUNC_INFO, "invalid listener (nil)"); Q_ASSERT_X(channel, Q_FUNC_INFO, "invalid channel (nil)"); Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)"); - PendingConnection newConnection(channel, true); + PendingConnection newConnection(channel, RetainPolicy::doInitialRetain); pendingConnections.append(newConnection); emit q_ptr->newConnection(); } -void QBluetoothServerPrivate::openNotify(IOBluetoothL2CAPChannel *channel) +void QBluetoothServerPrivate::openNotifyL2CAP(void *generic) { + auto channel = static_cast<IOBluetoothL2CAPChannel *>(generic); + Q_ASSERT_X(listener, Q_FUNC_INFO, "invalid listener (nil)"); Q_ASSERT_X(channel, Q_FUNC_INFO, "invalid channel (nil)"); Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)"); - PendingConnection newConnection(channel, true); + PendingConnection newConnection(channel, RetainPolicy::doInitialRetain); pendingConnections.append(newConnection); emit q_ptr->newConnection(); @@ -209,11 +220,11 @@ void QBluetoothServerPrivate::registerServer(QBluetoothServerPrivate *server, qu // External lock is required + port must be free. Q_ASSERT_X(server, Q_FUNC_INFO, "invalid server (null)"); - const QSInfo::Protocol type = server->serverType; - if (type == QSInfo::RfcommProtocol) { + const ServiceInfo::Protocol type = server->serverType; + if (type == ServiceInfo::RfcommProtocol) { Q_ASSERT_X(!channelIsBusy(port), Q_FUNC_INFO, "port is busy"); busyChannels()[port] = server; - } else if (type == QSInfo::L2capProtocol) { + } else if (type == ServiceInfo::L2capProtocol) { Q_ASSERT_X(!psmIsBusy(port), Q_FUNC_INFO, "port is busy"); busyPSMs()[port] = server; } else { @@ -225,11 +236,11 @@ void QBluetoothServerPrivate::registerServer(QBluetoothServerPrivate *server, qu QBluetoothServerPrivate *QBluetoothServerPrivate::registeredServer(quint16 port, QBluetoothServiceInfo::Protocol protocol) { // Eternal lock is required. - if (protocol == QSInfo::RfcommProtocol) { + if (protocol == ServiceInfo::RfcommProtocol) { ServerMapIterator it = busyChannels().find(port); if (it != busyChannels().end()) return it.value(); - } else if (protocol == QSInfo::L2capProtocol) { + } else if (protocol == ServiceInfo::L2capProtocol) { ServerMapIterator it = busyPSMs().find(port); if (it != busyPSMs().end()) return it.value(); @@ -243,17 +254,17 @@ QBluetoothServerPrivate *QBluetoothServerPrivate::registeredServer(quint16 port, void QBluetoothServerPrivate::unregisterServer(QBluetoothServerPrivate *server) { // External lock is required. - const QSInfo::Protocol type = server->serverType; + const ServiceInfo::Protocol type = server->serverType; const quint16 port = server->port; - if (type == QSInfo::RfcommProtocol) { + if (type == ServiceInfo::RfcommProtocol) { ServerMapIterator it = busyChannels().find(port); if (it != busyChannels().end()) { busyChannels().erase(it); } else { qCWarning(QT_BT_OSX) << "server is not registered"; } - } else if (type == QSInfo::L2capProtocol) { + } else if (type == ServiceInfo::L2capProtocol) { ServerMapIterator it = busyPSMs().find(port); if (it != busyPSMs().end()) { busyPSMs().erase(it); @@ -265,21 +276,9 @@ void QBluetoothServerPrivate::unregisterServer(QBluetoothServerPrivate *server) } } - -QBluetoothServer::QBluetoothServer(QSInfo::Protocol serverType, QObject *parent) - : QObject(parent), - d_ptr(new QBluetoothServerPrivate(serverType, this)) -{ -} - -QBluetoothServer::~QBluetoothServer() -{ - delete d_ptr; -} - void QBluetoothServer::close() { - d_ptr->listener.reset(nil); + d_ptr->listener.reset(); // Needs a lock :( const QMutexLocker lock(&d_ptr->channelMapMutex()); @@ -289,8 +288,6 @@ void QBluetoothServer::close() bool QBluetoothServer::listen(const QBluetoothAddress &address, quint16 port) { - typedef QBluetoothServerPrivate::ObjCListener ObjCListener; - OSXBluetooth::qt_test_iobluetooth_runloop(); if (d_ptr->listener) { @@ -303,7 +300,7 @@ bool QBluetoothServer::listen(const QBluetoothAddress &address, quint16 port) qCWarning(QT_BT_OSX) << "device does not support Bluetooth or" << address.toString() << "is not a valid local adapter"; - d_ptr->lastError = UnknownError; + d_ptr->m_lastError = UnknownError; emit error(UnknownError); return false; } @@ -311,53 +308,53 @@ bool QBluetoothServer::listen(const QBluetoothAddress &address, quint16 port) const QBluetoothLocalDevice::HostMode hostMode = device.hostMode(); if (hostMode == QBluetoothLocalDevice::HostPoweredOff) { qCWarning(QT_BT_OSX) << "Bluetooth device is powered off"; - d_ptr->lastError = PoweredOffError; + d_ptr->m_lastError = PoweredOffError; emit error(PoweredOffError); return false; } - const QSInfo::Protocol type = d_ptr->serverType; + const ServiceInfo::Protocol type = d_ptr->serverType; - if (type == QSInfo::UnknownProtocol) { + if (type == ServiceInfo::UnknownProtocol) { qCWarning(QT_BT_OSX) << "invalid protocol"; - d_ptr->lastError = UnsupportedProtocolError; - emit error(d_ptr->lastError); + d_ptr->m_lastError = UnsupportedProtocolError; + emit error(d_ptr->m_lastError); return false; } - d_ptr->lastError = QBluetoothServer::NoError; + d_ptr->m_lastError = QBluetoothServer::NoError; // Now we have to register a (fake) port, doing a proper (?) lock. const QMutexLocker lock(&d_ptr->channelMapMutex()); if (port) { - if (type == QSInfo::RfcommProtocol) { + if (type == ServiceInfo::RfcommProtocol) { if (d_ptr->channelIsBusy(port)) { qCWarning(QT_BT_OSX) << "server port:" << port << "already registered"; - d_ptr->lastError = ServiceAlreadyRegisteredError; + d_ptr->m_lastError = ServiceAlreadyRegisteredError; } } else { if (d_ptr->psmIsBusy(port)) { qCWarning(QT_BT_OSX) << "server port:" << port << "already registered"; - d_ptr->lastError = ServiceAlreadyRegisteredError; + d_ptr->m_lastError = ServiceAlreadyRegisteredError; } } } else { - type == QSInfo::RfcommProtocol ? port = d_ptr->findFreeChannel() + type == ServiceInfo::RfcommProtocol ? port = d_ptr->findFreeChannel() : port = d_ptr->findFreePSM(); } - if (d_ptr->lastError != QBluetoothServer::NoError) { - emit error(d_ptr->lastError); + if (d_ptr->m_lastError != QBluetoothServer::NoError) { + emit error(d_ptr->m_lastError); return false; } if (!port) { qCWarning(QT_BT_OSX) << "all ports are busy"; - d_ptr->lastError = ServiceAlreadyRegisteredError; - emit error(d_ptr->lastError); + d_ptr->m_lastError = ServiceAlreadyRegisteredError; + emit error(d_ptr->m_lastError); return false; } @@ -365,82 +362,17 @@ bool QBluetoothServer::listen(const QBluetoothAddress &address, quint16 port) // (provided after a service was registered). d_ptr->port = port; d_ptr->registerServer(d_ptr, port); - d_ptr->listener.reset([[ObjCListener alloc] initWithListener:d_ptr]); + d_ptr->listener.reset([[ObjCListener alloc] initWithListener:d_ptr], + RetainPolicy::noInitialRetain); return true; } -QBluetoothServiceInfo QBluetoothServer::listen(const QBluetoothUuid &uuid, const QString &serviceName) -{ - if (!listen()) - return QBluetoothServiceInfo(); - - QBluetoothServiceInfo serviceInfo; - serviceInfo.setAttribute(QSInfo::ServiceName, serviceName); - QBluetoothServiceInfo::Sequence publicBrowse; - publicBrowse << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::PublicBrowseGroup)); - serviceInfo.setAttribute(QSInfo::BrowseGroupList, publicBrowse); - - QSInfo::Sequence profileSequence; - QSInfo::Sequence classId; - classId << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::SerialPort)); - classId << QVariant::fromValue(quint16(0x100)); - profileSequence.append(QVariant::fromValue(classId)); - serviceInfo.setAttribute(QSInfo::BluetoothProfileDescriptorList, profileSequence); - - classId.clear(); - classId << QVariant::fromValue(uuid); - classId << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::SerialPort)); - serviceInfo.setAttribute(QSInfo::ServiceClassIds, classId); - serviceInfo.setServiceUuid(uuid); - - QSInfo::Sequence protocolDescriptorList; - QSInfo::Sequence protocol; - protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap)); - if (d_ptr->serverType == QSInfo::L2capProtocol) - protocol << QVariant::fromValue(serverPort()); - protocolDescriptorList.append(QVariant::fromValue(protocol)); - protocol.clear(); - - if (d_ptr->serverType == QBluetoothServiceInfo::RfcommProtocol) { - protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::Rfcomm)) - << QVariant::fromValue(quint8(serverPort())); - protocolDescriptorList.append(QVariant::fromValue(protocol)); - } - - serviceInfo.setAttribute(QSInfo::ProtocolDescriptorList, - protocolDescriptorList); - - - // It's now up to a service info to acquire a real PSM/channel ID - // (provided by IOBluetooth) and start a listener. - if (!serviceInfo.registerService()) - return QBluetoothServiceInfo(); - - return serviceInfo; -} - -bool QBluetoothServer::isListening() const -{ - if (d_ptr->serverType == QSInfo::UnknownProtocol) - return false; - - const QMutexLocker lock(&QBluetoothServerPrivate::channelMapMutex()); - return QBluetoothServerPrivate::registeredServer(serverPort(), d_ptr->serverType); -} - void QBluetoothServer::setMaxPendingConnections(int numConnections) { - // That's a 'fake' limit, it affects nothing. d_ptr->maxPendingConnections = numConnections; } -int QBluetoothServer::maxPendingConnections() const -{ - // That's a 'fake' limit, it affects nothing. - return d_ptr->maxPendingConnections; -} - bool QBluetoothServer::hasPendingConnections() const { return d_ptr->pendingConnections.size(); @@ -457,11 +389,11 @@ QBluetoothSocket *QBluetoothServer::nextPendingConnection() // Remove it even if we have some errors below. d_ptr->pendingConnections.pop_front(); - if (d_ptr->serverType == QSInfo::RfcommProtocol) { - if (!newSocket->d_ptr->setChannel(static_cast<IOBluetoothRFCOMMChannel *>(channel))) + if (d_ptr->serverType == ServiceInfo::RfcommProtocol) { + if (!static_cast<QBluetoothSocketPrivate *>(newSocket->d_ptr)->setRFCOMChannel(channel.getAs<IOBluetoothRFCOMMChannel>())) return nullptr; } else { - if (!newSocket->d_ptr->setChannel(static_cast<IOBluetoothL2CAPChannel *>(channel))) + if (!static_cast<QBluetoothSocketPrivate *>(newSocket->d_ptr)->setL2CAPChannel(channel.getAs<IOBluetoothL2CAPChannel>())) return nullptr; } @@ -481,25 +413,13 @@ quint16 QBluetoothServer::serverPort() const void QBluetoothServer::setSecurityFlags(QBluetooth::SecurityFlags security) { Q_UNUSED(security) - // Not implemented (yet?) + Q_UNIMPLEMENTED(); } QBluetooth::SecurityFlags QBluetoothServer::securityFlags() const { - // Not implemented (yet?) + Q_UNIMPLEMENTED(); return QBluetooth::NoSecurity; } -QSInfo::Protocol QBluetoothServer::serverType() const -{ - return d_ptr->serverType; -} - -QBluetoothServer::Error QBluetoothServer::error() const -{ - return d_ptr->lastError; -} - -#include "moc_qbluetoothserver.cpp" - QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothserver_p.h b/src/bluetooth/qbluetoothserver_p.h index 5ace7f75..d14dc7b4 100644 --- a/src/bluetooth/qbluetoothserver_p.h +++ b/src/bluetooth/qbluetoothserver_p.h @@ -77,15 +77,25 @@ class ServerAcceptanceThread; #include <windows.networking.sockets.h> #endif +#ifdef QT_OSX_BLUETOOTH + +#include "osx/btdelegates_p.h" +#include "osx/btraii_p.h" + +#include <QtCore/qvector.h> + +#endif // QT_OSX_BLUETOOTH + QT_BEGIN_NAMESPACE class QBluetoothAddress; class QBluetoothSocket; class QBluetoothServer; -#ifndef QT_OSX_BLUETOOTH - class QBluetoothServerPrivate +#ifdef QT_OSX_BLUETOOTH + : public DarwinBluetooth::SocketListener +#endif { Q_DECLARE_PUBLIC(QBluetoothServer) @@ -142,9 +152,53 @@ public: bool initiateActiveListening(const QString &serviceName); bool deactivateActiveListening(); #endif -}; -#endif //QT_OSX_BLUETOOTH +#ifdef QT_OSX_BLUETOOTH + +public: + + friend class QBluetoothServer; + friend class QBluetoothServiceInfoPrivate; + +private: + bool startListener(quint16 realPort); + void stopListener(); + bool isListening() const; + + // SocketListener (delegate): + void openNotifyRFCOMM(void *channel) override; + void openNotifyL2CAP(void *channel) override; + + // Either a "temporary" channelID/PSM assigned by QBluetoothServer::listen, + // or a real channelID/PSM returned by IOBluetooth after we've registered + // a service. + quint16 port; + + DarwinBluetooth::StrongReference listener; + + // These static functions below + // deal with differences between bluetooth sockets + // (bluez and QtBluetooth's API) and IOBluetooth, where it's not possible + // to have a real PSM/channelID _before_ a service is registered, + // the solution - "fake" ports. + // These functions require external locking - using channelMapMutex. + static QMutex &channelMapMutex(); + + static bool channelIsBusy(quint16 channelID); + static quint16 findFreeChannel(); + + static bool psmIsBusy(quint16 psm); + static quint16 findFreePSM(); + + static void registerServer(QBluetoothServerPrivate *server, quint16 port); + static QBluetoothServerPrivate *registeredServer(quint16 port, QBluetoothServiceInfo::Protocol protocol); + static void unregisterServer(QBluetoothServerPrivate *server); + + using PendingConnection = DarwinBluetooth::StrongReference; + QVector<PendingConnection> pendingConnections; + +#endif // QT_OSX_BLUETOOTH +}; QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothservicediscoveryagent.cpp b/src/bluetooth/qbluetoothservicediscoveryagent.cpp index a5fc7654..e76c2311 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent.cpp +++ b/src/bluetooth/qbluetoothservicediscoveryagent.cpp @@ -169,6 +169,11 @@ QBluetoothServiceDiscoveryAgent::QBluetoothServiceDiscoveryAgent(QObject *parent \note On WinRT the passed adapter address will be ignored. + \note On Android passing any \a deviceAdapter address is meaningless as Android 6.0 or later does not publish + the local Bluetooth address anymore. Subsequently, the passed adapter address can never be matched + against the local adapter address. Therefore the subsequent call to \l start() will always trigger + \l InvalidBluetoothAdapterError. + \sa error() */ QBluetoothServiceDiscoveryAgent::QBluetoothServiceDiscoveryAgent(const QBluetoothAddress &deviceAdapter, QObject *parent) @@ -303,6 +308,13 @@ QBluetoothAddress QBluetoothServiceDiscoveryAgent::remoteAddress() const return QBluetoothAddress(); } +namespace OSXBluetooth { + +void qt_test_iobluetooth_runloop(); + +} + + /*! Starts service discovery. \a mode specifies the type of service discovery to perform. @@ -313,6 +325,10 @@ QBluetoothAddress QBluetoothServiceDiscoveryAgent::remoteAddress() const void QBluetoothServiceDiscoveryAgent::start(DiscoveryMode mode) { Q_D(QBluetoothServiceDiscoveryAgent); +#ifdef QT_OSX_BLUETOOTH + // Make sure we are on the right thread/have a run loop: + OSXBluetooth::qt_test_iobluetooth_runloop(); +#endif if (d->discoveryState() == QBluetoothServiceDiscoveryAgentPrivate::Inactive && d->error != InvalidBluetoothAdapterError) { @@ -568,7 +584,8 @@ bool QBluetoothServiceDiscoveryAgentPrivate::isDuplicatedService( const QBluetoothServiceInfo &info = discoveredServices.at(j); if (info.device() == serviceInfo.device() && info.serviceClassUuids() == serviceInfo.serviceClassUuids() - && info.serviceUuid() == serviceInfo.serviceUuid()) { + && info.serviceUuid() == serviceInfo.serviceUuid() + && info.serverChannel() == serviceInfo.serverChannel()) { return true; } } diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp b/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp index ce2911d3..3ab0d580 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp +++ b/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp @@ -47,6 +47,7 @@ #include <QtBluetooth/QBluetoothServiceDiscoveryAgent> #include "qbluetoothservicediscoveryagent_p.h" +#include "qbluetoothsocket_android_p.h" #include "android/servicediscoverybroadcastreceiver_p.h" #include "android/localdevicebroadcastreceiver_p.h" @@ -55,21 +56,33 @@ QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID) QBluetoothServiceDiscoveryAgentPrivate::QBluetoothServiceDiscoveryAgentPrivate( - QBluetoothServiceDiscoveryAgent *qp, const QBluetoothAddress &/*deviceAdapter*/) + QBluetoothServiceDiscoveryAgent *qp, const QBluetoothAddress &deviceAdapter) : error(QBluetoothServiceDiscoveryAgent::NoError), + m_deviceAdapterAddress(deviceAdapter), state(Inactive), mode(QBluetoothServiceDiscoveryAgent::MinimalDiscovery), singleDevice(false), q_ptr(qp) { - QList<QBluetoothHostInfo> devices = QBluetoothLocalDevice::allDevices(); - Q_ASSERT(devices.count() <= 1); //Android only supports one device at the moment - - if (devices.isEmpty()) { - error = QBluetoothServiceDiscoveryAgent::InvalidBluetoothAdapterError; - errorString = QBluetoothServiceDiscoveryAgent::tr("Invalid Bluetooth adapter address"); - return; + // If a specific adapter address is requested we need to check it matches + // the current local adapter. If it does not match we emit + // InvalidBluetoothAdapterError when calling start() + + bool createAdapter = true; + if (!deviceAdapter.isNull()) { + const QList<QBluetoothHostInfo> devices = QBluetoothLocalDevice::allDevices(); + if (devices.isEmpty()) { + createAdapter = false; + } else { + auto match = [deviceAdapter](const QBluetoothHostInfo& info) { + return info.address() == deviceAdapter; + }; + + auto result = std::find_if(devices.begin(), devices.end(), match); + if (result == devices.end()) + createAdapter = false; + } } if (QtAndroidPrivate::androidSdkVersion() < 15) @@ -84,7 +97,8 @@ QBluetoothServiceDiscoveryAgentPrivate::QBluetoothServiceDiscoveryAgentPrivate( The logic below must change once there is more than one adapter. */ - btAdapter = QAndroidJniObject::callStaticObjectMethod("android/bluetooth/BluetoothAdapter", + if (createAdapter) + btAdapter = QAndroidJniObject::callStaticObjectMethod("android/bluetooth/BluetoothAdapter", "getDefaultAdapter", "()Landroid/bluetooth/BluetoothAdapter;"); if (!btAdapter.isValid()) @@ -110,8 +124,15 @@ void QBluetoothServiceDiscoveryAgentPrivate::start(const QBluetoothAddress &addr Q_Q(QBluetoothServiceDiscoveryAgent); if (!btAdapter.isValid()) { - error = QBluetoothServiceDiscoveryAgent::UnknownError; - errorString = QBluetoothServiceDiscoveryAgent::tr("Platform does not support Bluetooth"); + if (m_deviceAdapterAddress.isNull()) { + error = QBluetoothServiceDiscoveryAgent::UnknownError; + errorString = QBluetoothServiceDiscoveryAgent::tr("Platform does not support Bluetooth"); + } else { + // specific adapter was requested which does not match the locally + // existing adapter + error = QBluetoothServiceDiscoveryAgent::InvalidBluetoothAdapterError; + errorString = QBluetoothServiceDiscoveryAgent::tr("Invalid Bluetooth adapter address"); + } //abort any outstanding discoveries discoveredDevices.clear(); @@ -315,42 +336,62 @@ void QBluetoothServiceDiscoveryAgentPrivate::_q_processFetchedUuids( void QBluetoothServiceDiscoveryAgentPrivate::populateDiscoveredServices(const QBluetoothDeviceInfo &remoteDevice, const QList<QBluetoothUuid> &uuids) { - /* Android doesn't provide decent SDP data. A list of uuids is close to meaning-less + /* Android doesn't provide decent SDP data. A flat list of uuids is all we get. * * The following approach is chosen: * - If we see an SPP service class and we see - * one or more custom uuids we match them up. Such services will always be SPP services. + * one or more custom uuids we match them up. Such services will always + * be SPP services. There is the chance that a custom uuid is eronously + * mapped as being an SPP service. In addition, the SPP uuid will be mapped as + * standalone SPP service. * - If we see a custom uuid but no SPP uuid then we return - * BluetoothServiceInfo instance with just a servuceUuid (no service class set) + * BluetoothServiceInfo instance with just a serviceUuid (no service class set) + * - If we don't find any custom uuid but the SPP uuid, we return a + * BluetoothServiceInfo instance where classId and serviceUuid() are set to SPP. * - Any other service uuid will stand on its own. * */ Q_Q(QBluetoothServiceDiscoveryAgent); //find SPP and custom uuid - QBluetoothUuid uuid; - int sppIndex = -1; + bool haveSppClass = false; QVector<int> customUuids; for (int i = 0; i < uuids.count(); i++) { - uuid = uuids.at(i); + const QBluetoothUuid uuid = uuids.at(i); if (uuid.isNull()) continue; //check for SPP protocol bool ok = false; - quint16 uuid16 = uuid.toUInt16(&ok); - if (ok && uuid16 == QBluetoothUuid::SerialPort) - sppIndex = i; + auto uuid16 = uuid.toUInt16(&ok); + haveSppClass |= ok && uuid16 == QBluetoothUuid::SerialPort; //check for custom uuid if (uuid.minimumSize() == 16) customUuids.append(i); } + auto rfcommProtocolDescriptorList = []() -> QBluetoothServiceInfo::Sequence { + QBluetoothServiceInfo::Sequence protocol; + protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::Rfcomm)) + << QVariant::fromValue(0); + return protocol; + }; + + auto sppProfileDescriptorList = []() -> QBluetoothServiceInfo::Sequence { + QBluetoothServiceInfo::Sequence profileSequence; + QBluetoothServiceInfo::Sequence classId; + classId << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::SerialPort)); + classId << QVariant::fromValue(quint16(0x100)); + profileSequence.append(QVariant::fromValue(classId)); + return profileSequence; + }; + for (int i = 0; i < uuids.count(); i++) { - if (i == sppIndex && !customUuids.isEmpty()) + const QBluetoothUuid &uuid = uuids.at(i); + if (uuid.isNull()) continue; QBluetoothServiceInfo serviceInfo; @@ -363,52 +404,38 @@ void QBluetoothServiceDiscoveryAgentPrivate::populateDiscoveredServices(const QB protocolDescriptorList.append(QVariant::fromValue(protocol)); } - if (customUuids.contains(i) && sppIndex > -1) { + if (customUuids.contains(i) && haveSppClass) { //we have a custom uuid of service class type SPP //set rfcomm protocol - QBluetoothServiceInfo::Sequence protocol; - protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::Rfcomm)) - << QVariant::fromValue(0); - protocolDescriptorList.append(QVariant::fromValue(protocol)); + protocolDescriptorList.append(QVariant::fromValue(rfcommProtocolDescriptorList())); - QBluetoothServiceInfo::Sequence profileSequence; - QBluetoothServiceInfo::Sequence classId; - classId << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::SerialPort)); - classId << QVariant::fromValue(quint16(0x100)); - profileSequence.append(QVariant::fromValue(classId)); + //set SPP profile descriptor list serviceInfo.setAttribute(QBluetoothServiceInfo::BluetoothProfileDescriptorList, - profileSequence); + sppProfileDescriptorList()); - classId.clear(); + QBluetoothServiceInfo::Sequence classId; //set SPP service class uuid - classId << QVariant::fromValue(uuids.at(i)); + classId << QVariant::fromValue(uuid); classId << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::SerialPort)); serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, classId); serviceInfo.setServiceName(QBluetoothServiceDiscoveryAgent::tr("Serial Port Profile")); - serviceInfo.setServiceUuid(uuids.at(i)); - } else if (sppIndex == i && customUuids.isEmpty()) { + serviceInfo.setServiceUuid(uuid); + } else if (uuid == QBluetoothUuid{QBluetoothUuid::SerialPort}) { //set rfcomm protocol - QBluetoothServiceInfo::Sequence protocol; - protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::Rfcomm)) - << QVariant::fromValue(0); - protocolDescriptorList.append(QVariant::fromValue(protocol)); + protocolDescriptorList.append(QVariant::fromValue(rfcommProtocolDescriptorList())); - QBluetoothServiceInfo::Sequence profileSequence; - QBluetoothServiceInfo::Sequence classId; - classId << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::SerialPort)); - classId << QVariant::fromValue(quint16(0x100)); - profileSequence.append(QVariant::fromValue(classId)); + //set SPP profile descriptor list serviceInfo.setAttribute(QBluetoothServiceInfo::BluetoothProfileDescriptorList, - profileSequence); + sppProfileDescriptorList()); //also we need to set the custom uuid to the SPP uuid //otherwise QBluetoothSocket::connectToService() would fail due to a missing service uuid - serviceInfo.setServiceUuid(uuids.at(i)); + serviceInfo.setServiceUuid(uuid); } else if (customUuids.contains(i)) { //custom uuid but no serial port - serviceInfo.setServiceUuid(uuids.at(i)); + serviceInfo.setServiceUuid(uuid); } serviceInfo.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList, protocolDescriptorList); @@ -419,18 +446,20 @@ void QBluetoothServiceDiscoveryAgentPrivate::populateDiscoveredServices(const QB if (!customUuids.contains(i)) { //if we don't have custom uuid use it as class id as well QBluetoothServiceInfo::Sequence classId; - classId << QVariant::fromValue(uuids.at(i)); + classId << QVariant::fromValue(uuid); serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, classId); - QBluetoothUuid::ServiceClassUuid clsId - = static_cast<QBluetoothUuid::ServiceClassUuid>(uuids.at(i).toUInt16()); + auto clsId = QBluetoothUuid::ServiceClassUuid(uuid.toUInt16()); serviceInfo.setServiceName(QBluetoothUuid::serviceClassToString(clsId)); } //Check if the service is in the uuidFilter if (!uuidFilter.isEmpty()) { bool match = uuidFilter.contains(serviceInfo.serviceUuid()); - for (const auto &uuid : qAsConst(uuidFilter)) + match |= uuidFilter.contains(QBluetoothSocketPrivateAndroid::reverseUuid(serviceInfo.serviceUuid())); + for (const auto &uuid : qAsConst(uuidFilter)) { match |= serviceInfo.serviceClassUuids().contains(uuid); + match |= serviceInfo.serviceClassUuids().contains(QBluetoothSocketPrivateAndroid::reverseUuid(uuid)); + } if (!match) continue; diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm b/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm index bd9cc7f3..d8decae1 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm +++ b/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm @@ -37,6 +37,7 @@ ** ****************************************************************************/ +#include "qbluetoothservicediscoveryagent_p.h" #include "qbluetoothservicediscoveryagent.h" #include "qbluetoothdevicediscoveryagent.h" #include "qbluetoothlocaldevice.h" @@ -57,132 +58,41 @@ QT_BEGIN_NAMESPACE -class QBluetoothServiceDiscoveryAgentPrivate : public QObject, public OSXBluetooth::SDPInquiryDelegate -{ - friend class QBluetoothServiceDiscoveryAgent; -public: - enum DiscoveryState { - Inactive, - DeviceDiscovery, - ServiceDiscovery, - }; - - QBluetoothServiceDiscoveryAgentPrivate(QBluetoothServiceDiscoveryAgent *qp, - const QBluetoothAddress &localAddress); - - void startDeviceDiscovery(); - void stopDeviceDiscovery(); - - void startServiceDiscovery(); - void stopServiceDiscovery(); - - DiscoveryState discoveryState(); - void setDiscoveryMode(QBluetoothServiceDiscoveryAgent::DiscoveryMode m); - QBluetoothServiceDiscoveryAgent::DiscoveryMode DiscoveryMode(); - - void _q_deviceDiscovered(const QBluetoothDeviceInfo &info); - void _q_deviceDiscoveryError(QBluetoothDeviceDiscoveryAgent::Error); - void _q_deviceDiscoveryFinished(); - -private: - // SDPInquiryDelegate: - void SDPInquiryFinished(IOBluetoothDevice *device) override; - void SDPInquiryError(IOBluetoothDevice *device, IOReturn errorCode) override; - - void performMinimalServiceDiscovery(const QBluetoothAddress &deviceAddress); - void setupDeviceDiscoveryAgent(); - bool isDuplicatedService(const QBluetoothServiceInfo &serviceInfo) const; - void serviceDiscoveryFinished(); - - bool serviceHasMathingUuid(const QBluetoothServiceInfo &serviceInfo) const; - - QBluetoothServiceDiscoveryAgent *q_ptr; +namespace { - QBluetoothServiceDiscoveryAgent::Error error; - QString errorString; +using DarwinBluetooth::RetainPolicy; +using ObjCServiceInquiry = QT_MANGLE_NAMESPACE(OSXBTSDPInquiry); - QList<QBluetoothDeviceInfo> discoveredDevices; - QList<QBluetoothServiceInfo> discoveredServices; - QList<QBluetoothUuid> uuidFilter; - - bool singleDevice; - QBluetoothAddress deviceAddress; - QBluetoothAddress localAdapterAddress; - - DiscoveryState state; - QBluetoothServiceDiscoveryAgent::DiscoveryMode discoveryMode; - - QScopedPointer<QBluetoothDeviceDiscoveryAgent> deviceDiscoveryAgent; - OSXBluetooth::ObjCScopedPointer<ObjCServiceInquiry> serviceInquiry; -}; +} QBluetoothServiceDiscoveryAgentPrivate::QBluetoothServiceDiscoveryAgentPrivate( QBluetoothServiceDiscoveryAgent *qp, const QBluetoothAddress &localAddress) : - q_ptr(qp), + error(QBluetoothServiceDiscoveryAgent::NoError), - singleDevice(false), - localAdapterAddress(localAddress), + m_deviceAdapterAddress(localAddress), state(Inactive), - discoveryMode(QBluetoothServiceDiscoveryAgent::MinimalDiscovery) -{ - serviceInquiry.reset([[ObjCServiceInquiry alloc] initWithDelegate:this]); -} + mode(QBluetoothServiceDiscoveryAgent::MinimalDiscovery), + singleDevice(false), + q_ptr(qp) -void QBluetoothServiceDiscoveryAgentPrivate::startDeviceDiscovery() { - Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)"); - Q_ASSERT_X(state == Inactive, Q_FUNC_INFO, "invalid state"); - Q_ASSERT_X(error != QBluetoothServiceDiscoveryAgent::InvalidBluetoothAdapterError, - Q_FUNC_INFO, "invalid bluetooth adapter"); - - Q_ASSERT_X(deviceDiscoveryAgent.isNull(), "startDeviceDiscovery()", - "discovery agent already exists"); - - state = DeviceDiscovery; - - setupDeviceDiscoveryAgent(); - deviceDiscoveryAgent->start(QBluetoothDeviceDiscoveryAgent::ClassicMethod); + Q_ASSERT(q_ptr); + serviceInquiry.reset([[ObjCServiceInquiry alloc] initWithDelegate:this], RetainPolicy::noInitialRetain); } -void QBluetoothServiceDiscoveryAgentPrivate::stopDeviceDiscovery() +QBluetoothServiceDiscoveryAgentPrivate::~QBluetoothServiceDiscoveryAgentPrivate() { - Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)"); - Q_ASSERT_X(!deviceDiscoveryAgent.isNull(), Q_FUNC_INFO, - "invalid device discovery agent (null)"); - Q_ASSERT_X(state == DeviceDiscovery, Q_FUNC_INFO, "invalid state"); - - deviceDiscoveryAgent->stop(); - deviceDiscoveryAgent.reset(nullptr); - state = Inactive; - - emit q_ptr->canceled(); } -void QBluetoothServiceDiscoveryAgentPrivate::startServiceDiscovery() +void QBluetoothServiceDiscoveryAgentPrivate::start(const QBluetoothAddress &deviceAddress) { - // Any of 'Inactive'/'DeviceDiscovery'/'ServiceDiscovery' states - // are possible. - - Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)"); - Q_ASSERT_X(error != QBluetoothServiceDiscoveryAgent::InvalidBluetoothAdapterError, - Q_FUNC_INFO, "invalid bluetooth adapter"); - - if (discoveredDevices.isEmpty()) { - state = Inactive; - emit q_ptr->finished(); - return; - } - QT_BT_MAC_AUTORELEASEPOOL; - state = ServiceDiscovery; - const QBluetoothAddress &address(discoveredDevices.at(0).address()); - - if (address.isNull()) { + if (deviceAddress.isNull()) { // This can happen: LE scan works with CoreBluetooth, but CBPeripherals // do not expose hardware addresses. // Pop the current QBluetoothDeviceInfo and decide what to do next. - return serviceDiscoveryFinished(); + return _q_serviceDiscoveryFinished(); } // Autoreleased object. @@ -195,17 +105,18 @@ void QBluetoothServiceDiscoveryAgentPrivate::startServiceDiscovery() emit q_ptr->error(error); } - return serviceDiscoveryFinished(); + return _q_serviceDiscoveryFinished(); } if (DiscoveryMode() == QBluetoothServiceDiscoveryAgent::MinimalDiscovery) { - performMinimalServiceDiscovery(address); + performMinimalServiceDiscovery(deviceAddress); } else { IOReturn result = kIOReturnSuccess; + auto nativeInquiry = serviceInquiry.getAs<ObjCServiceInquiry>(); if (uuidFilter.size()) - result = [serviceInquiry performSDPQueryWithDevice:address filters:uuidFilter]; + result = [nativeInquiry performSDPQueryWithDevice:deviceAddress filters:uuidFilter]; else - result = [serviceInquiry performSDPQueryWithDevice:address]; + result = [nativeInquiry performSDPQueryWithDevice:deviceAddress]; if (result != kIOReturnSuccess) { // Failed immediately to perform an SDP inquiry on IOBluetoothDevice: @@ -214,87 +125,21 @@ void QBluetoothServiceDiscoveryAgentPrivate::startServiceDiscovery() } } -void QBluetoothServiceDiscoveryAgentPrivate::stopServiceDiscovery() +void QBluetoothServiceDiscoveryAgentPrivate::stop() { - Q_ASSERT_X(state != Inactive, Q_FUNC_INFO, "invalid state"); Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)"); discoveredDevices.clear(); - state = Inactive; // "Stops" immediately. - [serviceInquiry stopSDPQuery]; + [serviceInquiry.getAs<ObjCServiceInquiry>() stopSDPQuery]; emit q_ptr->canceled(); } -QBluetoothServiceDiscoveryAgentPrivate::DiscoveryState - QBluetoothServiceDiscoveryAgentPrivate::discoveryState() -{ - return state; -} - -void QBluetoothServiceDiscoveryAgentPrivate::setDiscoveryMode( - QBluetoothServiceDiscoveryAgent::DiscoveryMode m) -{ - discoveryMode = m; - -} - -QBluetoothServiceDiscoveryAgent::DiscoveryMode - QBluetoothServiceDiscoveryAgentPrivate::DiscoveryMode() -{ - return discoveryMode; -} - -void QBluetoothServiceDiscoveryAgentPrivate::_q_deviceDiscovered(const QBluetoothDeviceInfo &info) -{ - // Look for duplicates, and cached entries - for (int i = 0; i < discoveredDevices.count(); i++) { - if (discoveredDevices.at(i).address() == info.address()) { - discoveredDevices.removeAt(i); - break; - } - } - - discoveredDevices.prepend(info); -} - -void QBluetoothServiceDiscoveryAgentPrivate::_q_deviceDiscoveryError(QBluetoothDeviceDiscoveryAgent::Error) -{ - Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)"); - - error = QBluetoothServiceDiscoveryAgent::UnknownError; - errorString = QCoreApplication::translate(DEV_DISCOVERY, DD_UNKNOWN_ERROR); - - deviceDiscoveryAgent->stop(); - deviceDiscoveryAgent.reset(nullptr); - - state = QBluetoothServiceDiscoveryAgentPrivate::Inactive; - emit q_ptr->error(error); - emit q_ptr->finished(); -} - -void QBluetoothServiceDiscoveryAgentPrivate::_q_deviceDiscoveryFinished() -{ - Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)"); - - if (deviceDiscoveryAgent->error() != QBluetoothDeviceDiscoveryAgent::NoError) { - //Forward the device discovery error - error = static_cast<QBluetoothServiceDiscoveryAgent::Error>(deviceDiscoveryAgent->error()); - errorString = deviceDiscoveryAgent->errorString(); - deviceDiscoveryAgent.reset(nullptr); - state = Inactive; - emit q_ptr->error(error); - emit q_ptr->finished(); - } else { - deviceDiscoveryAgent.reset(nullptr); - startServiceDiscovery(); - } -} - -void QBluetoothServiceDiscoveryAgentPrivate::SDPInquiryFinished(IOBluetoothDevice *device) +void QBluetoothServiceDiscoveryAgentPrivate::SDPInquiryFinished(void *generic) { + auto device = static_cast<IOBluetoothDevice *>(generic); Q_ASSERT_X(device, Q_FUNC_INFO, "invalid IOBluetoothDevice (nil)"); if (state == Inactive) @@ -323,10 +168,10 @@ void QBluetoothServiceDiscoveryAgentPrivate::SDPInquiryFinished(IOBluetoothDevic } } - serviceDiscoveryFinished(); + _q_serviceDiscoveryFinished(); } -void QBluetoothServiceDiscoveryAgentPrivate::SDPInquiryError(IOBluetoothDevice *device, IOReturn errorCode) +void QBluetoothServiceDiscoveryAgentPrivate::SDPInquiryError(void *device, IOReturn errorCode) { Q_UNUSED(device) @@ -340,7 +185,7 @@ void QBluetoothServiceDiscoveryAgentPrivate::SDPInquiryError(IOBluetoothDevice * emit q_ptr->error(error); } - serviceDiscoveryFinished(); + _q_serviceDiscoveryFinished(); } void QBluetoothServiceDiscoveryAgentPrivate::performMinimalServiceDiscovery(const QBluetoothAddress &deviceAddress) @@ -371,7 +216,7 @@ void QBluetoothServiceDiscoveryAgentPrivate::performMinimalServiceDiscovery(cons if (!serviceInfo.isValid()) continue; - if (!uuidFilter.isEmpty() && !serviceHasMathingUuid(serviceInfo)) + if (!uuidFilter.isEmpty() && !serviceHasMatchingUuid(serviceInfo)) continue; if (!isDuplicatedService(serviceInfo)) { @@ -381,52 +226,10 @@ void QBluetoothServiceDiscoveryAgentPrivate::performMinimalServiceDiscovery(cons } } - serviceDiscoveryFinished(); -} - -void QBluetoothServiceDiscoveryAgentPrivate::setupDeviceDiscoveryAgent() -{ - Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)"); - Q_ASSERT_X(deviceDiscoveryAgent.isNull() || !deviceDiscoveryAgent->isActive(), - Q_FUNC_INFO, "device discovery agent is active"); - - deviceDiscoveryAgent.reset(new QBluetoothDeviceDiscoveryAgent(localAdapterAddress, q_ptr)); - - QObject::connect(deviceDiscoveryAgent.data(), &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, - this, &QBluetoothServiceDiscoveryAgentPrivate::_q_deviceDiscovered); - QObject::connect(deviceDiscoveryAgent.data(), &QBluetoothDeviceDiscoveryAgent::finished, - this, &QBluetoothServiceDiscoveryAgentPrivate::_q_deviceDiscoveryFinished); - QObject::connect(deviceDiscoveryAgent.data(), - QOverload<QBluetoothDeviceDiscoveryAgent::Error>::of(&QBluetoothDeviceDiscoveryAgent::error), - this, - &QBluetoothServiceDiscoveryAgentPrivate::_q_deviceDiscoveryError); -} - -bool QBluetoothServiceDiscoveryAgentPrivate::isDuplicatedService(const QBluetoothServiceInfo &serviceInfo) const -{ - //check the service is not already part of our known list - for (int j = 0; j < discoveredServices.count(); j++) { - const QBluetoothServiceInfo &info = discoveredServices.at(j); - if (info.device() == serviceInfo.device() - && info.serviceClassUuids() == serviceInfo.serviceClassUuids() - && info.serviceUuid() == serviceInfo.serviceUuid()) { - return true; - } - } - - return false; -} - -void QBluetoothServiceDiscoveryAgentPrivate::serviceDiscoveryFinished() -{ - if (!discoveredDevices.isEmpty()) - discoveredDevices.removeFirst(); - - if (state == ServiceDiscovery) - startServiceDiscovery(); + _q_serviceDiscoveryFinished(); } -bool QBluetoothServiceDiscoveryAgentPrivate::serviceHasMathingUuid(const QBluetoothServiceInfo &serviceInfo) const +bool QBluetoothServiceDiscoveryAgentPrivate::serviceHasMatchingUuid(const QBluetoothServiceInfo &serviceInfo) const { for (const auto &requestedUuid : uuidFilter) { if (serviceInfo.serviceUuid() == requestedUuid) @@ -437,161 +240,4 @@ bool QBluetoothServiceDiscoveryAgentPrivate::serviceHasMathingUuid(const QBlueto return false; } -QBluetoothServiceDiscoveryAgent::QBluetoothServiceDiscoveryAgent(QObject *parent) -: QObject(parent), - d_ptr(new QBluetoothServiceDiscoveryAgentPrivate(this, QBluetoothAddress())) -{ -} - -QBluetoothServiceDiscoveryAgent::QBluetoothServiceDiscoveryAgent(const QBluetoothAddress &deviceAdapter, QObject *parent) -: QObject(parent), - d_ptr(new QBluetoothServiceDiscoveryAgentPrivate(this, deviceAdapter)) -{ - if (!deviceAdapter.isNull()) { - const QList<QBluetoothHostInfo> localDevices = QBluetoothLocalDevice::allDevices(); - for (const QBluetoothHostInfo &hostInfo : localDevices) { - if (hostInfo.address() == deviceAdapter) - return; - } - d_ptr->error = InvalidBluetoothAdapterError; - d_ptr->errorString = QCoreApplication::translate(SERVICE_DISCOVERY, SD_INVALID_ADDRESS); - } -} - -QBluetoothServiceDiscoveryAgent::~QBluetoothServiceDiscoveryAgent() -{ - delete d_ptr; -} - -QList<QBluetoothServiceInfo> QBluetoothServiceDiscoveryAgent::discoveredServices() const -{ - return d_ptr->discoveredServices; -} - -/* - Sets the UUID filter to \a uuids. Only services matching the UUIDs in \a uuids will be - returned. - - An empty UUID list is equivalent to a list containing only QBluetoothUuid::PublicBrowseGroup. - - \sa uuidFilter() -*/ -void QBluetoothServiceDiscoveryAgent::setUuidFilter(const QList<QBluetoothUuid> &uuids) -{ - d_ptr->uuidFilter = uuids; -} - -/* - This is an overloaded member function, provided for convenience. - - Sets the UUID filter to a list containing the single element \a uuid. - - \sa uuidFilter() -*/ -void QBluetoothServiceDiscoveryAgent::setUuidFilter(const QBluetoothUuid &uuid) -{ - d_ptr->uuidFilter.clear(); - d_ptr->uuidFilter.append(uuid); -} - -/* - Returns the UUID filter. - - \sa setUuidFilter() -*/ -QList<QBluetoothUuid> QBluetoothServiceDiscoveryAgent::uuidFilter() const -{ - return d_ptr->uuidFilter; -} - -/* - Sets the remote device address to \a address. If \a address is default constructed, - services will be discovered on all contactable Bluetooth devices. A new remote - address can only be set while there is no service discovery in progress; otherwise - this function returns false. - - \sa remoteAddress() -*/ -bool QBluetoothServiceDiscoveryAgent::setRemoteAddress(const QBluetoothAddress &address) -{ - if (isActive()) - return false; - - if (!address.isNull()) - d_ptr->singleDevice = true; - - d_ptr->deviceAddress = address; - return true; -} - -QBluetoothAddress QBluetoothServiceDiscoveryAgent::remoteAddress() const -{ - if (d_ptr->singleDevice) - return d_ptr->deviceAddress; - - return QBluetoothAddress(); -} - -void QBluetoothServiceDiscoveryAgent::start(DiscoveryMode mode) -{ - OSXBluetooth::qt_test_iobluetooth_runloop(); - - if (d_ptr->discoveryState() == QBluetoothServiceDiscoveryAgentPrivate::Inactive - && d_ptr->error != InvalidBluetoothAdapterError) - { - d_ptr->setDiscoveryMode(mode); - if (d_ptr->deviceAddress.isNull()) { - d_ptr->startDeviceDiscovery(); - } else { - d_ptr->discoveredDevices.append(QBluetoothDeviceInfo(d_ptr->deviceAddress, QString(), 0)); - d_ptr->startServiceDiscovery(); - } - } -} - -void QBluetoothServiceDiscoveryAgent::stop() -{ - if (d_ptr->error == InvalidBluetoothAdapterError || !isActive()) - return; - - switch (d_ptr->discoveryState()) { - case QBluetoothServiceDiscoveryAgentPrivate::DeviceDiscovery: - d_ptr->stopDeviceDiscovery(); - break; - case QBluetoothServiceDiscoveryAgentPrivate::ServiceDiscovery: - d_ptr->stopServiceDiscovery(); - default:; - } - - d_ptr->discoveredDevices.clear(); -} - -void QBluetoothServiceDiscoveryAgent::clear() -{ - // Don't clear the list while the search is ongoing - if (isActive()) - return; - - d_ptr->discoveredDevices.clear(); - d_ptr->discoveredServices.clear(); - d_ptr->uuidFilter.clear(); -} - -bool QBluetoothServiceDiscoveryAgent::isActive() const -{ - return d_ptr->state != QBluetoothServiceDiscoveryAgentPrivate::Inactive; -} - -QBluetoothServiceDiscoveryAgent::Error QBluetoothServiceDiscoveryAgent::error() const -{ - return d_ptr->error; -} - -QString QBluetoothServiceDiscoveryAgent::errorString() const -{ - return d_ptr->errorString; -} - -#include "moc_qbluetoothservicediscoveryagent.cpp" - QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_p.h b/src/bluetooth/qbluetoothservicediscoveryagent_p.h index cb588f70..41410b70 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent_p.h +++ b/src/bluetooth/qbluetoothservicediscoveryagent_p.h @@ -90,6 +90,11 @@ QT_END_NAMESPACE #include <QtCore/QPointer> #endif +#ifdef QT_OSX_BLUETOOTH +#include "osx/btdelegates_p.h" +#include "osx/btraii_p.h" +#endif + QT_BEGIN_NAMESPACE class QBluetoothDeviceDiscoveryAgent; @@ -109,6 +114,9 @@ class QBluetoothServiceDiscoveryAgentPrivate : public QObject { Q_OBJECT +#elif defined(QT_OSX_BLUETOOTH) + : public QObject, public DarwinBluetooth::SDPInquiryDelegate +{ #else { #endif @@ -238,6 +246,19 @@ private: QPointer<QWinRTBluetoothServiceDiscoveryWorker> worker; #endif +#ifdef QT_OSX_BLUETOOTH + // SDPInquiryDelegate: + void SDPInquiryFinished(void *device) override; + void SDPInquiryError(void *device, IOReturn errorCode) override; + + void performMinimalServiceDiscovery(const QBluetoothAddress &deviceAddress); + //void serviceDiscoveryFinished(); + + bool serviceHasMatchingUuid(const QBluetoothServiceInfo &serviceInfo) const; + + DarwinBluetooth::ScopedPointer serviceInquiry; +#endif // QT_OSX_BLUETOOTH + protected: QBluetoothServiceDiscoveryAgent *q_ptr; }; diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_winrt.cpp b/src/bluetooth/qbluetoothservicediscoveryagent_winrt.cpp index c6b00346..f1476758 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent_winrt.cpp +++ b/src/bluetooth/qbluetoothservicediscoveryagent_winrt.cpp @@ -52,6 +52,7 @@ #include <windows.devices.enumeration.h> #include <windows.devices.bluetooth.h> #include <windows.foundation.collections.h> +#include <windows.networking.h> #include <windows.storage.streams.h> #include <wrl.h> @@ -81,24 +82,6 @@ Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT) #define TYPE_STRING 37 #define TYPE_SEQUENCE 53 -static QByteArray byteArrayFromBuffer(const ComPtr<IBuffer> &buffer, bool isWCharString = false) -{ - ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteAccess; - HRESULT hr = buffer.As(&byteAccess); - Q_ASSERT_SUCCEEDED(hr); - char *data; - hr = byteAccess->Buffer(reinterpret_cast<byte **>(&data)); - Q_ASSERT_SUCCEEDED(hr); - UINT32 size; - hr = buffer->get_Length(&size); - Q_ASSERT_SUCCEEDED(hr); - if (isWCharString) { - QString valueString = QString::fromUtf16(reinterpret_cast<ushort *>(data)).left(size / 2); - return valueString.toUtf8(); - } - return QByteArray(data, size); -} - class QWinRTBluetoothServiceDiscoveryWorker : public QObject { Q_OBJECT @@ -226,6 +209,14 @@ void QWinRTBluetoothServiceDiscoveryWorker::processServiceSearchResult(quint64 a hr = service->get_ConnectionServiceName(name.GetAddressOf()); Q_ASSERT_SUCCEEDED(hr); const QString serviceName = QString::fromWCharArray(WindowsGetStringRawBuffer(name.Get(), nullptr)); + ComPtr<ABI::Windows::Networking::IHostName> host; + hr = service->get_ConnectionHostName(host.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + HString hostName; + hr = host->get_RawName(hostName.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + const QString qHostName = QString::fromWCharArray(WindowsGetStringRawBuffer(hostName.Get(), + nullptr)); ComPtr<IRfcommServiceId> id; hr = service->get_ServiceId(&id); Q_ASSERT_SUCCEEDED(hr); @@ -235,6 +226,8 @@ void QWinRTBluetoothServiceDiscoveryWorker::processServiceSearchResult(quint64 a Q_ASSERT_SUCCEEDED(hr); QBluetoothServiceInfo info; + info.setAttribute(0xBEEF, QVariant(qHostName)); + info.setAttribute(0xBEF0, QVariant(serviceName)); info.setServiceName(serviceName); info.setServiceUuid(uuid); ComPtr<IAsyncOperation<IMapView<UINT32, IBuffer *> *>> op; @@ -343,6 +336,17 @@ void QWinRTBluetoothServiceDiscoveryWorker::processServiceSearchResult(quint64 a } hr = iterator->MoveNext(¤t); } + // Windows is only able to discover Rfcomm services but the according protocolDescriptor is + // not always set in the raw attribute map. If we encounter a service like that we should + // fill the protocol descriptor ourselves. + if (info.protocolDescriptor(QBluetoothUuid::Rfcomm).isEmpty()) { + QBluetoothServiceInfo::Sequence protocolDescriptorList; + QBluetoothServiceInfo::Sequence protocol; + protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::Rfcomm)) + << QVariant::fromValue(0); + protocolDescriptorList.append(QVariant::fromValue(protocol)); + info.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList, protocolDescriptorList); + } emit serviceFound(address, info); } emit scanFinished(address); diff --git a/src/bluetooth/qbluetoothserviceinfo.cpp b/src/bluetooth/qbluetoothserviceinfo.cpp index 74b17ac4..23a78c81 100644 --- a/src/bluetooth/qbluetoothserviceinfo.cpp +++ b/src/bluetooth/qbluetoothserviceinfo.cpp @@ -180,7 +180,12 @@ bool QBluetoothServiceInfo::isRegistered() const bool QBluetoothServiceInfo::registerService(const QBluetoothAddress &localAdapter) { +#ifdef QT_OSX_BLUETOOTH + Q_UNUSED(localAdapter) + return d_ptr->registerService(*this); +#else return d_ptr->registerService(localAdapter); +#endif } /*! @@ -413,6 +418,9 @@ void QBluetoothServiceInfo::setDevice(const QBluetoothDeviceInfo &device) If the service information is already registered with the platform's SDP database, the database entry will not be updated until \l registerService() was called again. + \note If an attribute expectes a byte-encoded value (e.g. Bluetooth HID services), + it should be set as QByteArray. + \sa isRegistered(), registerService() */ void QBluetoothServiceInfo::setAttribute(quint16 attributeId, const QVariant &value) @@ -578,6 +586,10 @@ static void dumpAttributeVariant(QDebug dbg, const QVariant &var, const QString& dbg << QString::asprintf("%sstring %s\n", indent.toUtf8().constData(), var.toString().toUtf8().constData()); break; + case QMetaType::QByteArray: + dbg << QString::asprintf("%sbytearray %s\n", indent.toUtf8().constData(), + var.toByteArray().toHex().constData()); + break; case QMetaType::Bool: dbg << QString::asprintf("%sbool %d\n", indent.toUtf8().constData(), var.toBool()); break; @@ -631,7 +643,7 @@ QDebug operator<<(QDebug dbg, const QBluetoothServiceInfo &info) { QDebugStateSaver saver(dbg); dbg.noquote() << "\n"; - QList<quint16> attributes = info.attributes(); + const QList<quint16> attributes = info.attributes(); for (quint16 id : attributes) { dumpAttributeVariant(dbg, info.attribute(id), QStringLiteral("(%1)\t").arg(id)); } diff --git a/src/bluetooth/qbluetoothserviceinfo_bluez.cpp b/src/bluetooth/qbluetoothserviceinfo_bluez.cpp index 09829b13..d91367c4 100644 --- a/src/bluetooth/qbluetoothserviceinfo_bluez.cpp +++ b/src/bluetooth/qbluetoothserviceinfo_bluez.cpp @@ -69,66 +69,57 @@ static void writeAttribute(QXmlStreamWriter *stream, const QVariant &attribute) stream->writeAttribute(QStringLiteral("value"), unsignedFormat.arg(attribute.value<quint8>(), 2, 16, QLatin1Char('0'))); - //stream->writeAttribute(QStringLiteral("name"), foo); break; case QMetaType::UShort: stream->writeEmptyElement(QStringLiteral("uint16")); stream->writeAttribute(QStringLiteral("value"), unsignedFormat.arg(attribute.value<quint16>(), 4, 16, QLatin1Char('0'))); - //stream->writeAttribute(QStringLiteral("name"), foo); break; case QMetaType::UInt: stream->writeEmptyElement(QStringLiteral("uint32")); stream->writeAttribute(QStringLiteral("value"), unsignedFormat.arg(attribute.value<quint32>(), 8, 16, QLatin1Char('0'))); - //stream->writeAttribute(QStringLiteral("name"), foo); break; case QMetaType::Char: stream->writeEmptyElement(QStringLiteral("int8")); stream->writeAttribute(QStringLiteral("value"), - QString::number(attribute.value<uchar>(), 16)); - //stream->writeAttribute(QStringLiteral("name"), foo); + QString::number(attribute.value<qint8>())); break; case QMetaType::Short: stream->writeEmptyElement(QStringLiteral("int16")); stream->writeAttribute(QStringLiteral("value"), - QString::number(attribute.value<qint16>(), 16)); - //stream->writeAttribute(QStringLiteral("name"), foo); + QString::number(attribute.value<qint16>())); break; case QMetaType::Int: stream->writeEmptyElement(QStringLiteral("int32")); stream->writeAttribute(QStringLiteral("value"), - QString::number(attribute.value<qint32>(), 16)); - //stream->writeAttribute(QStringLiteral("name"), foo); + QString::number(attribute.value<qint32>())); + break; + case QMetaType::QByteArray: + stream->writeEmptyElement(QStringLiteral("text")); + stream->writeAttribute(QStringLiteral("value"), + QString::fromLatin1(attribute.value<QByteArray>().toHex().constData())); + stream->writeAttribute(QStringLiteral("encoding"), QStringLiteral("hex")); break; case QMetaType::QString: stream->writeEmptyElement(QStringLiteral("text")); - if (/* require hex encoding */ false) { - stream->writeAttribute(QStringLiteral("value"), QString::fromLatin1( - attribute.value<QString>().toUtf8().toHex().constData())); - stream->writeAttribute(QStringLiteral("encoding"), QStringLiteral("hex")); - } else { - stream->writeAttribute(QStringLiteral("value"), attribute.value<QString>()); - stream->writeAttribute(QStringLiteral("encoding"), QStringLiteral("normal")); - } - //stream->writeAttribute(QStringLiteral("name"), foo); + stream->writeAttribute(QStringLiteral("value"), attribute.value<QString>()); + stream->writeAttribute(QStringLiteral("encoding"), QStringLiteral("normal")); break; - case QMetaType::Bool: + case QMetaType::Bool: stream->writeEmptyElement(QStringLiteral("boolean")); if (attribute.value<bool>()) stream->writeAttribute(QStringLiteral("value"), QStringLiteral("true")); else stream->writeAttribute(QStringLiteral("value"), QStringLiteral("false")); - //stream->writeAttribute(QStringLiteral("name"), foo); break; - case QMetaType::QUrl: + case QMetaType::QUrl: stream->writeEmptyElement(QStringLiteral("url")); stream->writeAttribute(QStringLiteral("value"), attribute.value<QUrl>().toString()); - //stream->writeAttribute(QStringLiteral("name"), foo); break; - case QVariant::UserType: + case QVariant::UserType: if (attribute.userType() == qMetaTypeId<QBluetoothUuid>()) { stream->writeEmptyElement(QStringLiteral("uuid")); diff --git a/src/bluetooth/qbluetoothserviceinfo_osx.mm b/src/bluetooth/qbluetoothserviceinfo_osx.mm index 27da70fc..41e4e8b7 100644 --- a/src/bluetooth/qbluetoothserviceinfo_osx.mm +++ b/src/bluetooth/qbluetoothserviceinfo_osx.mm @@ -38,9 +38,10 @@ ****************************************************************************/ #include "osx/osxbtservicerecord_p.h" -#include "qbluetoothserver_osx_p.h" +#include "qbluetoothserviceinfo_p.h" #include "qbluetoothserviceinfo.h" #include "qbluetoothdeviceinfo.h" +#include "qbluetoothserver_p.h" #include "osx/osxbtutility_p.h" #include "osx/osxbluetooth_p.h" @@ -55,85 +56,116 @@ QT_BEGIN_NAMESPACE -class QBluetoothServiceInfoPrivate +namespace { + +using DarwinBluetooth::RetainPolicy; +using ServiceInfo = QBluetoothServiceInfo; + +// Alas, since there is no d_ptr<->q_ptr link (which is not that bad in itself), +// I need these getters duplicated here: +ServiceInfo::Protocol socket_protocol(const QBluetoothServiceInfoPrivate &privateInfo) { -public: + ServiceInfo::Sequence parameters = privateInfo.protocolDescriptor(QBluetoothUuid::Rfcomm); + if (!parameters.isEmpty()) + return ServiceInfo::RfcommProtocol; - typedef QBluetoothServiceInfo QSInfo; + parameters = privateInfo.protocolDescriptor(QBluetoothUuid::L2cap); + if (!parameters.isEmpty()) + return ServiceInfo::L2capProtocol; - bool registerService(const OSXBluetooth::ObjCStrongReference<NSMutableDictionary> &serviceDict); - bool isRegistered() const; - bool unregisterService(); + return ServiceInfo::UnknownProtocol; +} - QBluetoothDeviceInfo deviceInfo; - QMap<quint16, QVariant> attributes; +int channel_or_psm(const QBluetoothServiceInfoPrivate &privateInfo, QBluetoothUuid::ProtocolUuid uuid) +{ + const auto parameters = privateInfo.protocolDescriptor(uuid); + if (parameters.isEmpty()) + return -1; + else if (parameters.count() == 1) + return 0; - QBluetoothServiceInfo::Sequence protocolDescriptor(QBluetoothUuid::ProtocolUuid protocol) const; - QBluetoothServiceInfo::Protocol socketProtocol() const; - int protocolServiceMultiplexer() const; - int serverChannel() const; + return parameters.at(1).toInt(); +} -private: +} // unnamed namespace - bool registered = false; +QBluetoothServiceInfoPrivate::QBluetoothServiceInfoPrivate() +{ +} - typedef OSXBluetooth::ObjCScopedPointer<IOBluetoothSDPServiceRecord> SDPRecord; - SDPRecord serviceRecord; - BluetoothSDPServiceRecordHandle serviceRecordHandle = 0; -}; +QBluetoothServiceInfoPrivate::~QBluetoothServiceInfoPrivate() +{ +} -bool QBluetoothServiceInfoPrivate::registerService(const OSXBluetooth::ObjCStrongReference<NSMutableDictionary> &serviceDict) +bool QBluetoothServiceInfoPrivate::registerService(const QBluetoothAddress &localAddress) +{ + Q_UNUSED(localAddress); + return false; +} + +bool QBluetoothServiceInfoPrivate::registerService(const QBluetoothServiceInfo &info) { using namespace OSXBluetooth; - Q_ASSERT(serviceDict); + if (isRegistered()) + return false; + + using namespace OSXBluetooth; + + ObjCStrongReference<NSMutableDictionary> serviceDict(iobluetooth_service_dictionary(info)); + if (!serviceDict) { + qCWarning(QT_BT_OSX) << "failed to create a service dictionary"; + return false; + } + Q_ASSERT(!registered); Q_ASSERT_X(!serviceRecord, Q_FUNC_INFO, "not registered, but serviceRecord is not nil"); SDPRecord newRecord; - newRecord.reset([[IOBluetoothSDPServiceRecord - publishedServiceRecordWithDictionary:serviceDict] retain]); + newRecord.reset([IOBluetoothSDPServiceRecord + publishedServiceRecordWithDictionary:serviceDict], RetainPolicy::doInitialRetain); if (!newRecord) { qCWarning(QT_BT_OSX) << "failed to register a service record"; return false; } BluetoothSDPServiceRecordHandle newRecordHandle = 0; - if ([newRecord getServiceRecordHandle:&newRecordHandle] != kIOReturnSuccess) { + auto *ioSDPRecord = newRecord.getAs<IOBluetoothSDPServiceRecord>(); + if ([ioSDPRecord getServiceRecordHandle:&newRecordHandle] != kIOReturnSuccess) { qCWarning(QT_BT_OSX) << "failed to register a service record"; - [newRecord removeServiceRecord]; + [ioSDPRecord removeServiceRecord]; return false; } - const QSInfo::Protocol type = socketProtocol(); + const ServiceInfo::Protocol type = info.socketProtocol(); quint16 realPort = 0; QBluetoothServerPrivate *server = nullptr; bool configured = false; if (type == QBluetoothServiceInfo::L2capProtocol) { BluetoothL2CAPPSM psm = 0; - server = QBluetoothServerPrivate::registeredServer(protocolServiceMultiplexer(), type); - if ([newRecord getL2CAPPSM:&psm] == kIOReturnSuccess) { + server = QBluetoothServerPrivate::registeredServer(info.protocolServiceMultiplexer(), type); + if ([ioSDPRecord getL2CAPPSM:&psm] == kIOReturnSuccess) { configured = true; realPort = psm; } } else if (type == QBluetoothServiceInfo::RfcommProtocol) { BluetoothRFCOMMChannelID channelID = 0; - server = QBluetoothServerPrivate::registeredServer(serverChannel(), type); - if ([newRecord getRFCOMMChannelID:&channelID] == kIOReturnSuccess) { + server = QBluetoothServerPrivate::registeredServer(info.serverChannel(), type); + if ([ioSDPRecord getRFCOMMChannelID:&channelID] == kIOReturnSuccess) { configured = true; realPort = channelID; } } if (!configured) { - [newRecord removeServiceRecord]; + [ioSDPRecord removeServiceRecord]; qCWarning(QT_BT_OSX) << "failed to register a service record"; return false; } registered = true; - serviceRecord.reset(newRecord.take()); + serviceRecord.swap(newRecord); serviceRecordHandle = newRecordHandle; if (server) @@ -154,17 +186,18 @@ bool QBluetoothServiceInfoPrivate::unregisterService() Q_ASSERT_X(serviceRecord, Q_FUNC_INFO, "service registered, but serviceRecord is nil"); - [serviceRecord removeServiceRecord]; - serviceRecord.reset(nil); + auto *nativeRecord = serviceRecord.getAs<IOBluetoothSDPServiceRecord>(); + [nativeRecord removeServiceRecord]; + serviceRecord.reset(); - const QSInfo::Protocol type = socketProtocol(); + const ServiceInfo::Protocol type = socket_protocol(*this); QBluetoothServerPrivate *server = nullptr; const QMutexLocker lock(&QBluetoothServerPrivate::channelMapMutex()); - if (type == QSInfo::RfcommProtocol) - server = QBluetoothServerPrivate::registeredServer(serverChannel(), type); - else if (type == QSInfo::L2capProtocol) - server = QBluetoothServerPrivate::registeredServer(protocolServiceMultiplexer(), type); + if (type == ServiceInfo::RfcommProtocol) + server = QBluetoothServerPrivate::registeredServer(channel_or_psm(*this, QBluetoothUuid::Rfcomm), type); + else if (type == ServiceInfo::L2capProtocol) + server = QBluetoothServerPrivate::registeredServer(channel_or_psm(*this, QBluetoothUuid::L2cap), type); if (server) server->stopListener(); @@ -175,268 +208,4 @@ bool QBluetoothServiceInfoPrivate::unregisterService() return true; } -bool QBluetoothServiceInfo::isRegistered() const -{ - return d_ptr->isRegistered(); -} - -bool QBluetoothServiceInfo::registerService(const QBluetoothAddress &localAdapter) -{ - Q_UNUSED(localAdapter); - if (isRegistered()) - return false; - - using namespace OSXBluetooth; - - ObjCStrongReference<NSMutableDictionary> serviceDict(iobluetooth_service_dictionary(*this)); - if (!serviceDict) { - qCWarning(QT_BT_OSX) << "failed to create a service dictionary"; - return false; - } - - return d_ptr->registerService(serviceDict); -} - -bool QBluetoothServiceInfo::unregisterService() -{ - return d_ptr->unregisterService(); -} - -QBluetoothServiceInfo::QBluetoothServiceInfo() - : d_ptr(new QBluetoothServiceInfoPrivate) -{ -} - -QBluetoothServiceInfo::QBluetoothServiceInfo(const QBluetoothServiceInfo &other) - : d_ptr(other.d_ptr) -{ -} - -QBluetoothServiceInfo::~QBluetoothServiceInfo() -{ -} - -bool QBluetoothServiceInfo::isValid() const -{ - return !d_ptr->attributes.isEmpty(); -} - -bool QBluetoothServiceInfo::isComplete() const -{ - return d_ptr->attributes.contains(ProtocolDescriptorList); -} - -QBluetoothDeviceInfo QBluetoothServiceInfo::device() const -{ - return d_ptr->deviceInfo; -} - -void QBluetoothServiceInfo::setDevice(const QBluetoothDeviceInfo &device) -{ - d_ptr->deviceInfo = device; -} - -void QBluetoothServiceInfo::setAttribute(quint16 attributeId, const QVariant &value) -{ - d_ptr->attributes[attributeId] = value; -} - -QVariant QBluetoothServiceInfo::attribute(quint16 attributeId) const -{ - return d_ptr->attributes.value(attributeId); -} - -QList<quint16> QBluetoothServiceInfo::attributes() const -{ - return d_ptr->attributes.keys(); -} - -bool QBluetoothServiceInfo::contains(quint16 attributeId) const -{ - return d_ptr->attributes.contains(attributeId); -} - -void QBluetoothServiceInfo::removeAttribute(quint16 attributeId) -{ - d_ptr->attributes.remove(attributeId); -} - -QBluetoothServiceInfo::Protocol QBluetoothServiceInfo::socketProtocol() const -{ - return d_ptr->socketProtocol(); -} - -int QBluetoothServiceInfo::protocolServiceMultiplexer() const -{ - return d_ptr->protocolServiceMultiplexer(); -} - -int QBluetoothServiceInfo::serverChannel() const -{ - return d_ptr->serverChannel(); -} - -QBluetoothServiceInfo::Sequence QBluetoothServiceInfo::protocolDescriptor(QBluetoothUuid::ProtocolUuid protocol) const -{ - return d_ptr->protocolDescriptor(protocol); -} - -QList<QBluetoothUuid> QBluetoothServiceInfo::serviceClassUuids() const -{ - QList<QBluetoothUuid> results; - - const QVariant var = attribute(QBluetoothServiceInfo::ServiceClassIds); - if (!var.isValid()) - return results; - - const QBluetoothServiceInfo::Sequence seq = var.value<QBluetoothServiceInfo::Sequence>(); - for (int i = 0; i < seq.count(); i++) - results.append(seq.at(i).value<QBluetoothUuid>()); - - return results; -} - -QBluetoothServiceInfo &QBluetoothServiceInfo::operator=(const QBluetoothServiceInfo &other) -{ - if (this != &other) - d_ptr = other.d_ptr; - - return *this; -} - -static void dumpAttributeVariant(const QVariant &var, const QString indent) -{ - switch (int(var.type())) { - case QMetaType::Void: - qDebug("%sEmpty", indent.toLocal8Bit().constData()); - break; - case QMetaType::UChar: - qDebug("%suchar %u", indent.toLocal8Bit().constData(), var.toUInt()); - break; - case QMetaType::UShort: - qDebug("%sushort %u", indent.toLocal8Bit().constData(), var.toUInt()); - case QMetaType::UInt: - qDebug("%suint %u", indent.toLocal8Bit().constData(), var.toUInt()); - break; - case QMetaType::Char: - qDebug("%schar %d", indent.toLocal8Bit().constData(), var.toInt()); - break; - case QMetaType::Short: - qDebug("%sshort %d", indent.toLocal8Bit().constData(), var.toInt()); - break; - case QMetaType::Int: - qDebug("%sint %d", indent.toLocal8Bit().constData(), var.toInt()); - break; - case QMetaType::QString: - qDebug("%sstring %s", indent.toLocal8Bit().constData(), var.toString().toLocal8Bit().constData()); - break; - case QMetaType::Bool: - qDebug("%sbool %d", indent.toLocal8Bit().constData(), var.toBool()); - break; - case QMetaType::QUrl: - qDebug("%surl %s", indent.toLocal8Bit().constData(), var.toUrl().toString().toLocal8Bit().constData()); - break; - case QVariant::UserType: - if (var.userType() == qMetaTypeId<QBluetoothUuid>()) { - QBluetoothUuid uuid = var.value<QBluetoothUuid>(); - switch (uuid.minimumSize()) { - case 0: - qDebug("%suuid NULL", indent.toLocal8Bit().constData()); - break; - case 2: - qDebug("%suuid %04x", indent.toLocal8Bit().constData(), uuid.toUInt16()); - break; - case 4: - qDebug("%suuid %08x", indent.toLocal8Bit().constData(), uuid.toUInt32()); - break; - case 16: - qDebug("%suuid %s", indent.toLocal8Bit().constData(), QByteArray(reinterpret_cast<const char *>(uuid.toUInt128().data), 16).toHex().constData()); - break; - default: - qDebug("%suuid ???", indent.toLocal8Bit().constData()); - ; - } - } else if (var.userType() == qMetaTypeId<QBluetoothServiceInfo::Sequence>()) { - qDebug("%sSequence", indent.toLocal8Bit().constData()); - const QBluetoothServiceInfo::Sequence *sequence = static_cast<const QBluetoothServiceInfo::Sequence *>(var.data()); - for (const QVariant &v : *sequence) - dumpAttributeVariant(v, indent + QLatin1Char('\t')); - } else if (var.userType() == qMetaTypeId<QBluetoothServiceInfo::Alternative>()) { - qDebug("%sAlternative", indent.toLocal8Bit().constData()); - const QBluetoothServiceInfo::Alternative *alternative = static_cast<const QBluetoothServiceInfo::Alternative *>(var.data()); - for (const QVariant &v : *alternative) - dumpAttributeVariant(v, indent + QLatin1Char('\t')); - } - break; - default: - qDebug("%sunknown variant type %d", indent.toLocal8Bit().constData(), var.userType()); - } -} - -QDebug operator << (QDebug dbg, const QBluetoothServiceInfo &info) -{ - const QList<quint16> attributes = info.attributes(); - for (quint16 id : attributes) { - dumpAttributeVariant(info.attribute(id), QString::fromLatin1("(%1)\t").arg(id)); - } - return dbg; -} - -QBluetoothServiceInfo::Sequence QBluetoothServiceInfoPrivate::protocolDescriptor(QBluetoothUuid::ProtocolUuid protocol) const -{ - if (!attributes.contains(QBluetoothServiceInfo::ProtocolDescriptorList)) - return QBluetoothServiceInfo::Sequence(); - - const QBluetoothServiceInfo::Sequence sequence - = attributes.value(QBluetoothServiceInfo::ProtocolDescriptorList).value<QBluetoothServiceInfo::Sequence>(); - for (const QVariant &v : sequence) { - QBluetoothServiceInfo::Sequence parameters = v.value<QBluetoothServiceInfo::Sequence>(); - if (parameters.empty()) - continue; - if (parameters.at(0).userType() == qMetaTypeId<QBluetoothUuid>()) { - if (parameters.at(0).value<QBluetoothUuid>() == protocol) - return parameters; - } - } - - return QBluetoothServiceInfo::Sequence(); -} - -QBluetoothServiceInfo::Protocol QBluetoothServiceInfoPrivate::socketProtocol() const -{ - QBluetoothServiceInfo::Sequence parameters = protocolDescriptor(QBluetoothUuid::Rfcomm); - if (!parameters.isEmpty()) - return QBluetoothServiceInfo::RfcommProtocol; - - parameters = protocolDescriptor(QBluetoothUuid::L2cap); - if (!parameters.isEmpty()) - return QBluetoothServiceInfo::L2capProtocol; - - return QBluetoothServiceInfo::UnknownProtocol; -} - - -int QBluetoothServiceInfoPrivate::protocolServiceMultiplexer() const -{ - const QBluetoothServiceInfo::Sequence parameters = protocolDescriptor(QBluetoothUuid::L2cap); - if (parameters.isEmpty()) - return -1; - else if (parameters.count() == 1) - return 0; - - return parameters.at(1).toUInt(); -} - - -int QBluetoothServiceInfoPrivate::serverChannel() const -{ - const QBluetoothServiceInfo::Sequence parameters = protocolDescriptor(QBluetoothUuid::Rfcomm); - if (parameters.isEmpty()) - return -1; - else if (parameters.count() == 1) - return 0; - - return parameters.at(1).toUInt(); -} - QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothserviceinfo_p.h b/src/bluetooth/qbluetoothserviceinfo_p.h index 0638867d..3ed005e1 100644 --- a/src/bluetooth/qbluetoothserviceinfo_p.h +++ b/src/bluetooth/qbluetoothserviceinfo_p.h @@ -59,6 +59,10 @@ #include <QMap> #include <QVariant> +#ifdef Q_OS_MACOS +#include "osx/btraii_p.h" +#endif + class OrgBluezServiceInterface; class OrgBluezProfileManager1Interface; @@ -87,7 +91,6 @@ QT_BEGIN_NAMESPACE class QBluetoothServiceInfo; -#ifndef QT_OSX_BLUETOOTH class QBluetoothServiceInfoPrivate : public QObject @@ -133,11 +136,20 @@ private: QVector<WCHAR> serviceDescription; #endif - mutable bool registered; -}; +#if QT_OSX_BLUETOOTH +public: + bool registerService(const QBluetoothServiceInfo &info); -#endif +private: + + using SDPRecord = DarwinBluetooth::ScopedPointer; + SDPRecord serviceRecord; + quint32 serviceRecordHandle = 0; +#endif // QT_OSX_BLUETOOTH + + mutable bool registered = false; +}; QT_END_NAMESPACE -#endif +#endif // QBLUETOOTHSERVICEINFO_P_H diff --git a/src/bluetooth/qbluetoothserviceinfo_winrt.cpp b/src/bluetooth/qbluetoothserviceinfo_winrt.cpp index 45262735..e806096f 100644 --- a/src/bluetooth/qbluetoothserviceinfo_winrt.cpp +++ b/src/bluetooth/qbluetoothserviceinfo_winrt.cpp @@ -297,6 +297,14 @@ static ComPtr<IBuffer> bufferFromAttribute(const QVariant &attribute) hr = writer->WriteInt64(attribute.value<qint64>()); Q_ASSERT_SUCCEEDED(hr); break; + case QMetaType::QByteArray: { + qCDebug(QT_BT_WINRT) << Q_FUNC_INFO << "Registering attribute of type QMetaType::QByteArray:" << attribute.value<QString>(); + const QString stringValue = QString::fromLatin1(attribute.value<QByteArray>().toHex()); + const bool writeSuccess = writeStringHelper(stringValue, writer); + if (!writeSuccess) + return nullptr; + break; + } case QMetaType::QString: { qCDebug(QT_BT_WINRT) << Q_FUNC_INFO << "Registering attribute of type QMetaType::QString:" << attribute.value<QString>(); const QString stringValue = attribute.value<QString>(); diff --git a/src/bluetooth/qbluetoothsocket.cpp b/src/bluetooth/qbluetoothsocket.cpp index 54eb6024..e4d85447 100644 --- a/src/bluetooth/qbluetoothsocket.cpp +++ b/src/bluetooth/qbluetoothsocket.cpp @@ -49,6 +49,8 @@ #include "qbluetoothsocket_winrt_p.h" #elif defined(QT_WIN_BLUETOOTH) #include "qbluetoothsocket_win_p.h" +#elif defined(QT_OSX_BLUETOOTH) +#include "qbluetoothsocket_osx_p.h" #else #include "qbluetoothsocket_dummy_p.h" #endif @@ -271,6 +273,8 @@ static QBluetoothSocketBasePrivate *createSocketPrivate() return new QBluetoothSocketPrivateWinRT(); #elif defined(QT_WIN_BLUETOOTH) return new QBluetoothSocketPrivateWin(); +#elif defined(QT_OSX_BLUETOOTH) + return new QBluetoothSocketPrivate(); #else return new QBluetoothSocketPrivateDummy(); #endif @@ -364,8 +368,8 @@ qint64 QBluetoothSocket::bytesToWrite() const /*! Attempts to connect to the service described by \a service. - The socket is opened in the given \a openMode. The \l socketType() may change - depending on the protocol required by \a service. + The socket is opened in the given \a openMode. The \l socketType() is ignored + if \a service specifies a differing \l QBluetoothServiceInfo::socketProtocol(). The socket first enters ConnectingState and attempts to connect to the device providing \a service. If a connection is established, QBluetoothSocket enters ConnectedState and @@ -376,6 +380,9 @@ qint64 QBluetoothSocket::bytesToWrite() const Note that most platforms require a pairing prior to connecting to the remote device. Otherwise the connection process may fail. + On Android, only RFCOMM connections are possible. This function ignores any socket protocol indicator + and assumes RFCOMM. + \sa state(), disconnectFromService() */ void QBluetoothSocket::connectToService(const QBluetoothServiceInfo &service, OpenMode openMode) @@ -514,6 +521,9 @@ QString QBluetoothSocket::errorString() const */ void QBluetoothSocket::setPreferredSecurityFlags(QBluetooth::SecurityFlags flags) { +#ifdef QT_OSX_BLUETOOTH + return; // not supported on macOS. +#endif Q_D(QBluetoothSocketBase); if (d->secFlags != flags) d->secFlags = flags; @@ -535,8 +545,13 @@ void QBluetoothSocket::setPreferredSecurityFlags(QBluetooth::SecurityFlags flags */ QBluetooth::SecurityFlags QBluetoothSocket::preferredSecurityFlags() const { +#if QT_OSX_BLUETOOTH + // not supported on macOS - platform always uses encryption + return QBluetooth::Secure; +#else Q_D(const QBluetoothSocketBase); return d->secFlags; +#endif // QT_OSX_BLUETOOTH } /*! @@ -560,6 +575,9 @@ void QBluetoothSocket::setSocketState(QBluetoothSocket::SocketState state) emit disconnected(); } if(state == ListeningState){ +#ifdef QT_OSX_BLUETOOTH + qCWarning(QT_BT) << "listening socket is not supported by IOBluetooth"; +#endif // TODO: look at this, is this really correct? // if we're a listening socket we can't handle connects? if (d->readNotifier) { @@ -641,6 +659,13 @@ void QBluetoothSocket::serviceDiscovered(const QBluetoothServiceInfo &service) connectToService(service, d->openMode); d->discoveryAgent->deleteLater(); d->discoveryAgent = nullptr; +#ifdef QT_WINRT_BLUETOOTH + } else if (!service.attribute(0xBEEF).isNull() + && !service.attribute(0xBEF0).isNull()) { + connectToService(service, d->openMode); + d->discoveryAgent->deleteLater(); + d->discoveryAgent = nullptr; +#endif } else { qCDebug(QT_BT) << "Could not find port/psm for potential remote service"; } diff --git a/src/bluetooth/qbluetoothsocket.h b/src/bluetooth/qbluetoothsocket.h index eefcd2ad..8d35f77e 100644 --- a/src/bluetooth/qbluetoothsocket.h +++ b/src/bluetooth/qbluetoothsocket.h @@ -52,21 +52,14 @@ QT_BEGIN_NAMESPACE -#ifndef QT_OSX_BLUETOOTH + class QBluetoothSocketBasePrivate; -#else -class QBluetoothSocketPrivate; -#endif class Q_BLUETOOTH_EXPORT QBluetoothSocket : public QIODevice { Q_OBJECT -#ifndef QT_OSX_BLUETOOTH - Q_DECLARE_PRIVATE(QBluetoothSocketBase) -#else - Q_DECLARE_PRIVATE(QBluetoothSocket) -#endif + Q_DECLARE_PRIVATE(QBluetoothSocketBase) friend class QBluetoothServer; friend class QBluetoothServerPrivate; @@ -188,11 +181,8 @@ protected: QBluetoothServiceInfo::Protocol socketType, QObject *parent = nullptr); #endif -#ifndef QT_OSX_BLUETOOTH + QBluetoothSocketBasePrivate *d_ptr; -#else - QBluetoothSocketPrivate *d_ptr; -#endif private: friend class QLowEnergyControllerPrivateBluez; diff --git a/src/bluetooth/qbluetoothsocket_android.cpp b/src/bluetooth/qbluetoothsocket_android.cpp index d7f17d17..85da325b 100644 --- a/src/bluetooth/qbluetoothsocket_android.cpp +++ b/src/bluetooth/qbluetoothsocket_android.cpp @@ -182,30 +182,6 @@ private: QPointer<SocketConnectWorker> workerPointer; }; -/* - * This function is part of a workaround for QTBUG-61392 - * - * Returns null uuid if the given \a serviceUuid is not a uuid - * derived from the Bluetooth base uuid. - */ -static QBluetoothUuid reverseUuid(const QBluetoothUuid &serviceUuid) -{ - if (serviceUuid.isNull()) - return QBluetoothUuid(); - - bool isBaseUuid = false; - serviceUuid.toUInt32(&isBaseUuid); - if (isBaseUuid) - return serviceUuid; - - const quint128 original = serviceUuid.toUInt128(); - quint128 reversed; - for (int i = 0; i < 16; i++) - reversed.data[15-i] = original.data[i]; - - return QBluetoothUuid(reversed); -} - QBluetoothSocketPrivateAndroid::QBluetoothSocketPrivateAndroid() : inputThread(0) @@ -518,7 +494,33 @@ void QBluetoothSocketPrivateAndroid::connectToService( return; } - if (!ensureNativeSocket(service.socketProtocol())) { + // Workaround for QTBUG-75035 + /* Not all Android devices publish or discover the SPP uuid for serial services. + * Also, Android does not permit the detection of the protocol used by a serial + * Bluetooth connection. + * + * Therefore, QBluetoothServiceDiscoveryAgentPrivate::populateDiscoveredServices() + * may have to guess what protocol a potential custom uuid uses. The guessing works + * reasonably well as long as the SDP discovery finds the SPP uuid. Otherwise + * the SPP and rfcomm protocol info is missing in \a service. + * + * Android only supports RFCOMM (no L2CP). We assume (in favor of user experience) + * that a non-RFCOMM protocol implies a missing SPP uuid during discovery but the user + * still wanting to connect with the given \a service instance. + */ + + auto protocol = service.socketProtocol(); + switch (protocol) { + case QBluetoothServiceInfo::L2capProtocol: + case QBluetoothServiceInfo::UnknownProtocol: + qCWarning(QT_BT_ANDROID) << "Changing socket protocol to RFCOMM"; + protocol = QBluetoothServiceInfo::RfcommProtocol; + break; + case QBluetoothServiceInfo::RfcommProtocol: + break; + } + + if (!ensureNativeSocket(protocol)) { errorString = QBluetoothSocket::tr("Socket type not supported"); q->setSocketError(QBluetoothSocket::UnsupportedProtocolError); return; @@ -942,6 +944,32 @@ qint64 QBluetoothSocketPrivateAndroid::bytesToWrite() const return 0; // nothing because always unbuffered } +/* + * This function is part of a workaround for QTBUG-61392 + * + * Returns null uuid if the given \a serviceUuid is not a uuid + * derived from the Bluetooth base uuid. + */ +QBluetoothUuid QBluetoothSocketPrivateAndroid::reverseUuid(const QBluetoothUuid &serviceUuid) +{ + if (QtAndroid::androidSdkVersion() < 23) + return serviceUuid; + + if (serviceUuid.isNull()) + return QBluetoothUuid(); + + bool isBaseUuid = false; + serviceUuid.toUInt32(&isBaseUuid); + if (isBaseUuid) + return serviceUuid; + + const quint128 original = serviceUuid.toUInt128(); + quint128 reversed; + for (int i = 0; i < 16; i++) + reversed.data[15-i] = original.data[i]; + return QBluetoothUuid{reversed}; +} + bool QBluetoothSocketPrivateAndroid::canReadLine() const { // We cannot access buffer directly as it is part of different thread diff --git a/src/bluetooth/qbluetoothsocket_android_p.h b/src/bluetooth/qbluetoothsocket_android_p.h index 7bf42e32..042e1bcd 100644 --- a/src/bluetooth/qbluetoothsocket_android_p.h +++ b/src/bluetooth/qbluetoothsocket_android_p.h @@ -112,6 +112,8 @@ public: bool canReadLine() const override; qint64 bytesToWrite() const override; + static QBluetoothUuid reverseUuid(const QBluetoothUuid &serviceUuid); + QAndroidJniObject adapter; QAndroidJniObject socketObject; QAndroidJniObject remoteDevice; diff --git a/src/bluetooth/qbluetoothsocket_bluez.cpp b/src/bluetooth/qbluetoothsocket_bluez.cpp index bbc32a90..25f07bb3 100644 --- a/src/bluetooth/qbluetoothsocket_bluez.cpp +++ b/src/bluetooth/qbluetoothsocket_bluez.cpp @@ -674,6 +674,9 @@ bool QBluetoothSocketPrivateBluez::setSocketDescriptor(int socketDescriptor, QBl connectWriteNotifier = nullptr; socketType = socketType_; + if (socket != -1) + QT_CLOSE(socket); + socket = socketDescriptor; // ensure that O_NONBLOCK is set on new connections. diff --git a/src/bluetooth/qbluetoothsocket_bluezdbus.cpp b/src/bluetooth/qbluetoothsocket_bluezdbus.cpp index c98d0c26..d3fc13e4 100644 --- a/src/bluetooth/qbluetoothsocket_bluezdbus.cpp +++ b/src/bluetooth/qbluetoothsocket_bluezdbus.cpp @@ -538,6 +538,8 @@ void QBluetoothSocketPrivateBluezDBus::remoteConnected(const QDBusUnixFileDescri q, &QBluetoothSocket::readyRead); connect(localSocket, &QLocalSocket::stateChanged, this, &QBluetoothSocketPrivateBluezDBus::socketStateChanged); + connect(localSocket, &QLocalSocket::bytesWritten, + q, &QBluetoothSocket::bytesWritten); socket = descriptor; q->setSocketState(QBluetoothSocket::ConnectedState); diff --git a/src/bluetooth/qbluetoothsocket_osx.mm b/src/bluetooth/qbluetoothsocket_osx.mm index 7f630146..f74c14f8 100644 --- a/src/bluetooth/qbluetoothsocket_osx.mm +++ b/src/bluetooth/qbluetoothsocket_osx.mm @@ -43,6 +43,9 @@ // dependencies problem. #include "qbluetoothsocketbase_p.h" #include "qbluetoothsocket_osx_p.h" + +#include "osx/osxbtrfcommchannel_p.h" +#include "osx/osxbtl2capchannel_p.h" #include "qbluetoothlocaldevice.h" #include "qbluetoothdeviceinfo.h" #include "osx/osxbtutility_p.h" @@ -57,30 +60,294 @@ QT_BEGIN_NAMESPACE +namespace { + +using DarwinBluetooth::RetainPolicy; +using ObjCL2CAPChannel = QT_MANGLE_NAMESPACE(OSXBTL2CAPChannel); +using ObjCRFCOMMChannel = QT_MANGLE_NAMESPACE(OSXBTRFCOMMChannel); + +} // unnamed namespace + QBluetoothSocketPrivate::QBluetoothSocketPrivate() - : writeChunk(std::numeric_limits<UInt16>::max()), - openMode(QIODevice::NotOpen), // That's what is set in public class' ctors. - state(QBluetoothSocket::UnconnectedState), - socketType(QBluetoothServiceInfo::UnknownProtocol), - socketError(QBluetoothSocket::NoSocketError), - isConnecting(false) + : writeChunk(std::numeric_limits<UInt16>::max()) { q_ptr = nullptr; } QBluetoothSocketPrivate::~QBluetoothSocketPrivate() { - // "Empty" dtor to make a shared pointer happy (parametrized with - // incomplete type in the header file). +} + +bool QBluetoothSocketPrivate::ensureNativeSocket(QBluetoothServiceInfo::Protocol type) +{ + // For now - very simplistic, we don't call it in this file, public class + // only calls it in a ctor, setting the protocol RFCOMM (in case of Android) + // or, indeed, doing, socket-related initialization in BlueZ backend. + Q_ASSERT(socketType == QBluetoothServiceInfo::UnknownProtocol); + socketType = type; + return type != QBluetoothServiceInfo::UnknownProtocol; +} + +QString QBluetoothSocketPrivate::localName() const +{ + const QBluetoothLocalDevice device; + return device.name(); +} + +QBluetoothAddress QBluetoothSocketPrivate::localAddress() const +{ + const QBluetoothLocalDevice device; + return device.address(); +} + +quint16 QBluetoothSocketPrivate::localPort() const +{ + return 0; +} + +QString QBluetoothSocketPrivate::peerName() const +{ + QT_BT_MAC_AUTORELEASEPOOL; + + NSString *nsName = nil; + if (socketType == QBluetoothServiceInfo::RfcommProtocol) { + if (rfcommChannel) + nsName = [rfcommChannel.getAs<ObjCRFCOMMChannel>() peerName]; + } else if (socketType == QBluetoothServiceInfo::L2capProtocol) { + if (l2capChannel) + nsName = [l2capChannel.getAs<ObjCL2CAPChannel>() peerName]; + } + + if (nsName) + return QString::fromNSString(nsName); + + return QString(); +} + +QBluetoothAddress QBluetoothSocketPrivate::peerAddress() const +{ + BluetoothDeviceAddress addr = {}; + if (socketType == QBluetoothServiceInfo::RfcommProtocol) { + if (rfcommChannel) + addr = [rfcommChannel.getAs<ObjCRFCOMMChannel>() peerAddress]; + } else if (socketType == QBluetoothServiceInfo::L2capProtocol) { + if (l2capChannel) + addr = [l2capChannel.getAs<ObjCL2CAPChannel>() peerAddress]; + } + + return OSXBluetooth::qt_address(&addr); +} + +quint16 QBluetoothSocketPrivate::peerPort() const +{ + if (socketType == QBluetoothServiceInfo::RfcommProtocol) { + if (rfcommChannel) + return [rfcommChannel.getAs<ObjCRFCOMMChannel>() getChannelID]; + } else if (socketType == QBluetoothServiceInfo::L2capProtocol) { + if (l2capChannel) + return [l2capChannel.getAs<ObjCL2CAPChannel>() getPSM]; + } + + return 0; +} + +void QBluetoothSocketPrivate::abort() +{ + // Can never be called while we're in connectToService: + Q_ASSERT_X(!isConnecting, Q_FUNC_INFO, "internal inconsistency - " + "still in connectToService()"); + + if (socketType == QBluetoothServiceInfo::RfcommProtocol) + rfcommChannel.reset(); + else if (socketType == QBluetoothServiceInfo::L2capProtocol) + l2capChannel.reset(); + + Q_ASSERT(q_ptr); + + q_ptr->setSocketState(QBluetoothSocket::UnconnectedState); + emit q_ptr->readChannelFinished(); + emit q_ptr->disconnected(); + +} + +void QBluetoothSocketPrivate::close() +{ + // Can never be called while we're in connectToService: + Q_ASSERT_X(!isConnecting, Q_FUNC_INFO, "internal inconsistency - " + "still in connectToService()"); + + if (!txBuffer.size()) + abort(); +} + + +qint64 QBluetoothSocketPrivate::writeData(const char *data, qint64 maxSize) +{ + Q_ASSERT_X(data, Q_FUNC_INFO, "invalid data (null)"); + Q_ASSERT_X(maxSize > 0, Q_FUNC_INFO, "invalid data size"); + + if (state != QBluetoothSocket::ConnectedState) { + errorString = QCoreApplication::translate(SOCKET, SOC_NOWRITE); + q_ptr->setSocketError(QBluetoothSocket::OperationError); + return -1; + } + + // We do not have a real socket API under the hood, + // IOBluetoothL2CAPChannel is buffered (writeAsync). + + if (!txBuffer.size()) + QMetaObject::invokeMethod(this, "_q_writeNotify", Qt::QueuedConnection); + + char *dst = txBuffer.reserve(int(maxSize)); + std::copy(data, data + maxSize, dst); + + return maxSize; +} + +qint64 QBluetoothSocketPrivate::readData(char *data, qint64 maxSize) +{ + if (!data) + return 0; + + if (state != QBluetoothSocket::ConnectedState) { + errorString = QCoreApplication::translate(SOCKET, SOC_NOREAD); + q_ptr->setSocketError(QBluetoothSocket::OperationError); + return -1; + } + + if (!buffer.isEmpty()) + return buffer.read(data, int(maxSize)); + + return 0; +} + +qint64 QBluetoothSocketPrivate::bytesAvailable() const +{ + return buffer.size(); +} + +bool QBluetoothSocketPrivate::canReadLine() const +{ + return buffer.canReadLine(); +} + +qint64 QBluetoothSocketPrivate::bytesToWrite() const +{ + return txBuffer.size(); +} + +bool QBluetoothSocketPrivate::setSocketDescriptor(int socketDescriptor, QBluetoothServiceInfo::Protocol socketType, + QBluetoothSocket::SocketState socketState, QIODevice::OpenMode openMode) +{ + Q_UNUSED(socketDescriptor) + Q_UNUSED(socketType) + Q_UNUSED(socketState) + Q_UNUSED(openMode) + + qCWarning(QT_BT_OSX) << "setting a socket descriptor is not supported by IOBluetooth"; + // Noop on macOS. + return true; +} + +void QBluetoothSocketPrivate::connectToServiceHelper(const QBluetoothAddress &address, quint16 port, + QIODevice::OpenMode openMode) +{ + Q_UNUSED(address) + Q_UNUSED(port) + Q_UNUSED(openMode) +} + +void QBluetoothSocketPrivate::connectToService(const QBluetoothServiceInfo &service, QIODevice::OpenMode openMode) +{ + Q_ASSERT(q_ptr); + + OSXBluetooth::qt_test_iobluetooth_runloop(); + + if (state!= QBluetoothSocket::UnconnectedState && state != QBluetoothSocket::ServiceLookupState) { + qCWarning(QT_BT_OSX) << "called on a busy socket"; + errorString = QCoreApplication::translate(SOCKET, SOC_CONNECT_IN_PROGRESS); + q_ptr->setSocketError(QBluetoothSocket::OperationError); + return; + } + + // Report this problem early, potentially avoid device discovery: + if (service.socketProtocol() == QBluetoothServiceInfo::UnknownProtocol) { + qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "cannot connect with 'UnknownProtocol' type"; + errorString = QCoreApplication::translate(SOCKET, SOC_NETWORK_ERROR); + q_ptr->setSocketError(QBluetoothSocket::UnsupportedProtocolError); + return; + } + + socketType = service.socketProtocol(); + + if (service.protocolServiceMultiplexer() > 0) { + connectToService(service.device().address(), + quint16(service.protocolServiceMultiplexer()), + openMode); + } else if (service.serverChannel() > 0) { + connectToService(service.device().address(), + quint16(service.serverChannel()), + openMode); + } else { + // Try service discovery. + if (service.serviceUuid().isNull()) { + qCWarning(QT_BT_OSX) << "No port, no PSM, and no " + "UUID provided, unable to connect"; + return; + } + + q_ptr->doDeviceDiscovery(service, openMode); + } +} + +void QBluetoothSocketPrivate::connectToService(const QBluetoothAddress &address, const QBluetoothUuid &uuid, + QIODevice::OpenMode openMode) +{ + Q_ASSERT(q_ptr); + + OSXBluetooth::qt_test_iobluetooth_runloop(); + + // Report this problem early, avoid device discovery: + if (socketType == QBluetoothServiceInfo::UnknownProtocol) { + qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "cannot connect with 'UnknownProtocol' type"; + errorString = QCoreApplication::translate(SOCKET, SOC_NETWORK_ERROR); + q_ptr->setSocketError(QBluetoothSocket::UnsupportedProtocolError); + return; + } + + if (state != QBluetoothSocket::UnconnectedState) { + qCWarning(QT_BT_OSX) << "called on a busy socket"; + errorString = QCoreApplication::translate(SOCKET, SOC_CONNECT_IN_PROGRESS); + q_ptr->setSocketError(QBluetoothSocket::OperationError); + return; + } + + QBluetoothDeviceInfo device(address, QString(), QBluetoothDeviceInfo::MiscellaneousDevice); + QBluetoothServiceInfo service; + service.setDevice(device); + service.setServiceUuid(uuid); + q_ptr->doDeviceDiscovery(service, openMode); } void QBluetoothSocketPrivate::connectToService(const QBluetoothAddress &address, quint16 port, QIODevice::OpenMode mode) { - Q_ASSERT_X(state == QBluetoothSocket::ServiceLookupState - || state == QBluetoothSocket::UnconnectedState, + Q_ASSERT(q_ptr); + + OSXBluetooth::qt_test_iobluetooth_runloop(); + + if (socketType == QBluetoothServiceInfo::UnknownProtocol) { + qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "cannot connect with 'UnknownProtocol' type"; + errorString = QCoreApplication::translate(SOCKET, SOC_NETWORK_ERROR); + q_ptr->setSocketError(QBluetoothSocket::UnsupportedProtocolError); + return; + } + + Q_ASSERT_X(state == QBluetoothSocket::ServiceLookupState || state == QBluetoothSocket::UnconnectedState, Q_FUNC_INFO, "invalid state"); + q_ptr->setOpenMode(mode); + socketError = QBluetoothSocket::NoSocketError; errorString.clear(); buffer.clear(); @@ -100,15 +367,15 @@ void QBluetoothSocketPrivate::connectToService(const QBluetoothAddress &address, openMode = mode; if (socketType == QBluetoothServiceInfo::RfcommProtocol) { - rfcommChannel.reset([[ObjCRFCOMMChannel alloc] initWithDelegate:this]); + rfcommChannel.reset([[ObjCRFCOMMChannel alloc] initWithDelegate:this], RetainPolicy::noInitialRetain); if (rfcommChannel) - status = [rfcommChannel connectAsyncToDevice:address withChannelID:port]; + status = [rfcommChannel.getAs<ObjCRFCOMMChannel>() connectAsyncToDevice:address withChannelID:port]; else status = kIOReturnNoMemory; } else if (socketType == QBluetoothServiceInfo::L2capProtocol) { - l2capChannel.reset([[ObjCL2CAPChannel alloc] initWithDelegate:this]); + l2capChannel.reset([[ObjCL2CAPChannel alloc] initWithDelegate:this], RetainPolicy::noInitialRetain); if (l2capChannel) - status = [l2capChannel connectAsyncToDevice:address withPSM:port]; + status = [l2capChannel.getAs<ObjCL2CAPChannel>() connectAsyncToDevice:address withPSM:port]; else status = kIOReturnNoMemory; } @@ -148,84 +415,6 @@ void QBluetoothSocketPrivate::connectToService(const QBluetoothAddress &address, } } -void QBluetoothSocketPrivate::close() -{ - // Can never be called while we're in connectToService: - Q_ASSERT_X(!isConnecting, Q_FUNC_INFO, "internal inconsistency - " - "still in connectToService()"); - - if (!txBuffer.size()) - abort(); -} - -void QBluetoothSocketPrivate::abort() -{ - // Can never be called while we're in connectToService: - Q_ASSERT_X(!isConnecting, Q_FUNC_INFO, "internal inconsistency - " - "still in connectToService()"); - - if (socketType == QBluetoothServiceInfo::RfcommProtocol) - rfcommChannel.reset(nil); - else if (socketType == QBluetoothServiceInfo::L2capProtocol) - l2capChannel.reset(nil); -} - -quint64 QBluetoothSocketPrivate::bytesAvailable() const -{ - return buffer.size(); -} - -QString QBluetoothSocketPrivate::peerName() const -{ - QT_BT_MAC_AUTORELEASEPOOL; - - NSString *nsName = nil; - if (socketType == QBluetoothServiceInfo::RfcommProtocol) { - if (rfcommChannel) - nsName = [rfcommChannel peerName]; - } else if (socketType == QBluetoothServiceInfo::L2capProtocol) { - if (l2capChannel) - nsName = [l2capChannel peerName]; - } - - if (nsName) - return QString::fromNSString(nsName); - - return QString(); -} - -QBluetoothAddress QBluetoothSocketPrivate::peerAddress() const -{ - BluetoothDeviceAddress addr = {}; - if (socketType == QBluetoothServiceInfo::RfcommProtocol) { - if (rfcommChannel) - addr = [rfcommChannel peerAddress]; - } else if (socketType == QBluetoothServiceInfo::L2capProtocol) { - if (l2capChannel) - addr = [l2capChannel peerAddress]; - } - - return OSXBluetooth::qt_address(&addr); -} - -quint16 QBluetoothSocketPrivate::peerPort() const -{ - if (socketType == QBluetoothServiceInfo::RfcommProtocol) { - if (rfcommChannel) - return [rfcommChannel getChannelID]; - } else if (socketType == QBluetoothServiceInfo::L2capProtocol) { - if (l2capChannel) - return [l2capChannel getPSM]; - } - - return 0; -} - -void QBluetoothSocketPrivate::_q_readNotify() -{ - // Noop. -} - void QBluetoothSocketPrivate::_q_writeNotify() { Q_ASSERT_X(socketType == QBluetoothServiceInfo::L2capProtocol @@ -238,14 +427,14 @@ void QBluetoothSocketPrivate::_q_writeNotify() if (txBuffer.size()) { const bool isL2CAP = socketType == QBluetoothServiceInfo::L2capProtocol; writeChunk.resize(isL2CAP ? std::numeric_limits<UInt16>::max() : - [rfcommChannel getMTU]); + [rfcommChannel.getAs<ObjCRFCOMMChannel>() getMTU]); const int size = txBuffer.read(writeChunk.data(), writeChunk.size()); IOReturn status = kIOReturnError; if (!isL2CAP) - status = [rfcommChannel writeAsync:writeChunk.data() length:UInt16(size)]; + status = [rfcommChannel.getAs<ObjCRFCOMMChannel>() writeAsync:writeChunk.data() length:UInt16(size)]; else - status = [l2capChannel writeAsync:writeChunk.data() length:UInt16(size)]; + status = [l2capChannel.getAs<ObjCL2CAPChannel>() writeAsync:writeChunk.data() length:UInt16(size)]; if (status != kIOReturnSuccess) { errorString = QCoreApplication::translate(SOCKET, SOC_NETWORK_ERROR); @@ -260,21 +449,22 @@ void QBluetoothSocketPrivate::_q_writeNotify() close(); } -bool QBluetoothSocketPrivate::setChannel(IOBluetoothRFCOMMChannel *channel) +bool QBluetoothSocketPrivate::setRFCOMChannel(void *generic) { // A special case "constructor": on OS X we do not have a real listening socket, // instead a bluetooth server "listens" for channel open notifications and // creates (if asked by a user later) a "socket" object // for this connection. This function initializes // a "socket" from such an external channel (reported by a notification). - + auto channel = static_cast<IOBluetoothRFCOMMChannel *>(generic); // It must be a newborn socket! Q_ASSERT_X(socketError == QBluetoothSocket::NoSocketError && state == QBluetoothSocket::UnconnectedState && !rfcommChannel && !l2capChannel, Q_FUNC_INFO, "unexpected socket state"); openMode = QIODevice::ReadWrite; - rfcommChannel.reset([[ObjCRFCOMMChannel alloc] initWithDelegate:this channel:channel]); + rfcommChannel.reset([[ObjCRFCOMMChannel alloc] initWithDelegate:this channel:channel], + RetainPolicy::noInitialRetain); if (rfcommChannel) {// We do not handle errors, up to an external user. q_ptr->setOpenMode(QIODevice::ReadWrite); state = QBluetoothSocket::ConnectedState; @@ -284,13 +474,14 @@ bool QBluetoothSocketPrivate::setChannel(IOBluetoothRFCOMMChannel *channel) return rfcommChannel; } -bool QBluetoothSocketPrivate::setChannel(IOBluetoothL2CAPChannel *channel) +bool QBluetoothSocketPrivate::setL2CAPChannel(void *generic) { // A special case "constructor": on OS X we do not have a real listening socket, // instead a bluetooth server "listens" for channel open notifications and // creates (if asked by a user later) a "socket" object // for this connection. This function initializes // a "socket" from such an external channel (reported by a notification). + auto channel = static_cast<IOBluetoothL2CAPChannel *>(generic); // It must be a newborn socket! Q_ASSERT_X(socketError == QBluetoothSocket::NoSocketError @@ -298,7 +489,7 @@ bool QBluetoothSocketPrivate::setChannel(IOBluetoothL2CAPChannel *channel) Q_FUNC_INFO, "unexpected socket state"); openMode = QIODevice::ReadWrite; - l2capChannel.reset([[ObjCL2CAPChannel alloc] initWithDelegate:this channel:channel]); + l2capChannel.reset([[ObjCL2CAPChannel alloc] initWithDelegate:this channel:channel], RetainPolicy::noInitialRetain); if (l2capChannel) {// We do not handle errors, up to an external user. q_ptr->setOpenMode(QIODevice::ReadWrite); state = QBluetoothSocket::ConnectedState; @@ -308,7 +499,6 @@ bool QBluetoothSocketPrivate::setChannel(IOBluetoothL2CAPChannel *channel) return l2capChannel; } - void QBluetoothSocketPrivate::setChannelError(IOReturn errorCode) { Q_UNUSED(errorCode) @@ -365,7 +555,7 @@ void QBluetoothSocketPrivate::readChannelData(void *data, std::size_t size) Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)"); const char *src = static_cast<char *>(data); - char *dst = buffer.reserve(size); + char *dst = buffer.reserve(int(size)); std::copy(src, src + size, dst); if (!isConnecting) { @@ -379,449 +569,4 @@ void QBluetoothSocketPrivate::writeComplete() _q_writeNotify(); } -qint64 QBluetoothSocketPrivate::writeData(const char *data, qint64 maxSize) -{ - Q_ASSERT_X(data, Q_FUNC_INFO, "invalid data (null)"); - Q_ASSERT_X(maxSize > 0, Q_FUNC_INFO, "invalid data size"); - - if (state != QBluetoothSocket::ConnectedState) { - errorString = QCoreApplication::translate(SOCKET, SOC_NOWRITE); - q_ptr->setSocketError(QBluetoothSocket::OperationError); - return -1; - } - - // We do not have a real socket API under the hood, - // IOBluetoothL2CAPChannel buffered (writeAsync). - - if (!txBuffer.size()) - QMetaObject::invokeMethod(this, "_q_writeNotify", Qt::QueuedConnection); - - char *dst = txBuffer.reserve(maxSize); - std::copy(data, data + maxSize, dst); - - return maxSize; -} - -QBluetoothSocket::QBluetoothSocket(QBluetoothServiceInfo::Protocol socketType, QObject *parent) - : QIODevice(parent), - d_ptr(new QBluetoothSocketPrivate) -{ - d_ptr->q_ptr = this; - d_ptr->socketType = socketType; - - setOpenMode(NotOpen); -} - -QBluetoothSocket::QBluetoothSocket(QObject *parent) - : QIODevice(parent), - d_ptr(new QBluetoothSocketPrivate) -{ - d_ptr->q_ptr = this; - setOpenMode(NotOpen); -} - -QBluetoothSocket::~QBluetoothSocket() -{ - delete d_ptr; -} - -bool QBluetoothSocket::isSequential() const -{ - return true; -} - -qint64 QBluetoothSocket::bytesAvailable() const -{ - return QIODevice::bytesAvailable() + d_ptr->bytesAvailable(); -} - -qint64 QBluetoothSocket::bytesToWrite() const -{ - return d_ptr->txBuffer.size(); -} - -void QBluetoothSocket::connectToService(const QBluetoothServiceInfo &service, OpenMode openMode) -{ - OSXBluetooth::qt_test_iobluetooth_runloop(); - - if (state() != UnconnectedState && state() != ServiceLookupState) { - qCWarning(QT_BT_OSX) << "called on a busy socket"; - d_ptr->errorString = QCoreApplication::translate(SOCKET, SOC_CONNECT_IN_PROGRESS); - setSocketError(OperationError); - return; - } - - // Report this problem early, potentially avoid device discovery: - if (service.socketProtocol() == QBluetoothServiceInfo::UnknownProtocol) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "cannot connect with 'UnknownProtocol' type"; - d_ptr->errorString = QCoreApplication::translate(SOCKET, SOC_NETWORK_ERROR); - setSocketError(QBluetoothSocket::UnsupportedProtocolError); - return; - } - - d_ptr->socketType = service.socketProtocol(); - - if (service.protocolServiceMultiplexer() > 0) { - d_ptr->connectToService(service.device().address(), - service.protocolServiceMultiplexer(), - openMode); - } else if (service.serverChannel() > 0) { - d_ptr->connectToService(service.device().address(), - service.serverChannel(), openMode); - } else { - // Try service discovery. - if (service.serviceUuid().isNull()) { - qCWarning(QT_BT_OSX) << "No port, no PSM, and no " - "UUID provided, unable to connect"; - return; - } - - doDeviceDiscovery(service, openMode); - } -} - -void QBluetoothSocket::connectToService(const QBluetoothAddress &address, const QBluetoothUuid &uuid, - OpenMode openMode) -{ - OSXBluetooth::qt_test_iobluetooth_runloop(); - - // Report this problem early, avoid device discovery: - if (socketType() == QBluetoothServiceInfo::UnknownProtocol) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "cannot connect with 'UnknownProtocol' type"; - d_ptr->errorString = QCoreApplication::translate(SOCKET, SOC_NETWORK_ERROR); - setSocketError(QBluetoothSocket::UnsupportedProtocolError); - return; - } - - if (state() != QBluetoothSocket::UnconnectedState) { - qCWarning(QT_BT_OSX) << "called on a busy socket"; - d_ptr->errorString = QCoreApplication::translate(SOCKET, SOC_CONNECT_IN_PROGRESS); - setSocketError(QBluetoothSocket::OperationError); - return; - } - - QBluetoothDeviceInfo device(address, QString(), QBluetoothDeviceInfo::MiscellaneousDevice); - QBluetoothServiceInfo service; - service.setDevice(device); - service.setServiceUuid(uuid); - doDeviceDiscovery(service, openMode); -} - -void QBluetoothSocket::connectToService(const QBluetoothAddress &address, quint16 port, - OpenMode openMode) -{ - OSXBluetooth::qt_test_iobluetooth_runloop(); - - if (socketType() == QBluetoothServiceInfo::UnknownProtocol) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "cannot connect with 'UnknownProtocol' type"; - d_ptr->errorString = QCoreApplication::translate(SOCKET, SOC_NETWORK_ERROR); - setSocketError(QBluetoothSocket::UnsupportedProtocolError); - return; - } - - if (state() != QBluetoothSocket::UnconnectedState) { - qCWarning(QT_BT_OSX) << "called on a busy socket"; - d_ptr->errorString = QCoreApplication::translate(SOCKET, SOC_CONNECT_IN_PROGRESS); - setSocketError(OperationError); - return; - } - - setOpenMode(openMode); - d_ptr->connectToService(address, port, openMode); -} - -QBluetoothServiceInfo::Protocol QBluetoothSocket::socketType() const -{ - return d_ptr->socketType; -} - -QBluetoothSocket::SocketState QBluetoothSocket::state() const -{ - return d_ptr->state; -} - -QBluetoothSocket::SocketError QBluetoothSocket::error() const -{ - return d_ptr->socketError; -} - -QString QBluetoothSocket::errorString() const -{ - return d_ptr->errorString; -} - -void QBluetoothSocket::setSocketState(QBluetoothSocket::SocketState state) -{ - const SocketState oldState = d_ptr->state; - d_ptr->state = state; - if (oldState != d_ptr->state) - emit stateChanged(state); - - if (state == ListeningState) { - // We can register for L2CAP/RFCOMM open notifications, - // that's different from 'listen' and is implemented - // in QBluetoothServer. - qCWarning(QT_BT_OSX) << "listening sockets are not supported"; - } -} - -bool QBluetoothSocket::canReadLine() const -{ - return d_ptr->buffer.canReadLine() || QIODevice::canReadLine(); -} - -void QBluetoothSocket::setSocketError(QBluetoothSocket::SocketError socketError) -{ - d_ptr->socketError = socketError; - emit error(socketError); -} - -void QBluetoothSocket::doDeviceDiscovery(const QBluetoothServiceInfo &service, OpenMode openMode) -{ - OSXBluetooth::qt_test_iobluetooth_runloop(); - - setSocketState(ServiceLookupState); - - if (d_ptr->discoveryAgent) - d_ptr->discoveryAgent->stop(); - - d_ptr->discoveryAgent.reset(new QBluetoothServiceDiscoveryAgent(this)); - d_ptr->discoveryAgent->setRemoteAddress(service.device().address()); - - connect(d_ptr->discoveryAgent.data(), SIGNAL(serviceDiscovered(QBluetoothServiceInfo)), - this, SLOT(serviceDiscovered(QBluetoothServiceInfo))); - connect(d_ptr->discoveryAgent.data(), SIGNAL(finished()), - this, SLOT(discoveryFinished())); - - d_ptr->openMode = openMode; - - if (!service.serviceUuid().isNull()) - d_ptr->discoveryAgent->setUuidFilter(service.serviceUuid()); - - if (!service.serviceClassUuids().isEmpty()) - d_ptr->discoveryAgent->setUuidFilter(service.serviceClassUuids()); - - Q_ASSERT_X(!d_ptr->discoveryAgent->uuidFilter().isEmpty(), Q_FUNC_INFO, - "invalid service info"); - - d_ptr->discoveryAgent->start(QBluetoothServiceDiscoveryAgent::FullDiscovery); -} - -void QBluetoothSocket::serviceDiscovered(const QBluetoothServiceInfo &service) -{ - if (service.protocolServiceMultiplexer() != 0 || service.serverChannel() != 0) { - d_ptr->discoveryAgent->stop(); - connectToService(service, d_ptr->openMode); - } -} - -void QBluetoothSocket::discoveryFinished() -{ - d_ptr->errorString = QCoreApplication::translate(SOCKET, SOC_SERVICE_NOT_FOUND); - setSocketState(UnconnectedState); - setSocketError(ServiceNotFoundError); -} - -void QBluetoothSocket::abort() -{ - if (state() == UnconnectedState) - return; - - setOpenMode(NotOpen); - - if (state() == ServiceLookupState && d_ptr->discoveryAgent) { - d_ptr->discoveryAgent->disconnect(); - d_ptr->discoveryAgent->stop(); - d_ptr->discoveryAgent.reset(); - } - - setSocketState(QBluetoothSocket::ClosingState); - d_ptr->abort(); - - setSocketState(QBluetoothSocket::UnconnectedState); - emit readChannelFinished(); - emit disconnected(); -} - -void QBluetoothSocket::disconnectFromService() -{ - close(); -} - -QString QBluetoothSocket::localName() const -{ - const QBluetoothLocalDevice device; - return device.name(); -} - -QBluetoothAddress QBluetoothSocket::localAddress() const -{ - const QBluetoothLocalDevice device; - return device.address(); -} - -quint16 QBluetoothSocket::localPort() const -{ - return 0; -} - -QString QBluetoothSocket::peerName() const -{ - return d_ptr->peerName(); -} - -QBluetoothAddress QBluetoothSocket::peerAddress() const -{ - return d_ptr->peerAddress(); -} - -quint16 QBluetoothSocket::peerPort() const -{ - return d_ptr->peerPort(); -} - -qint64 QBluetoothSocket::writeData(const char *data, qint64 maxSize) -{ - if (!data || maxSize <= 0) { - d_ptr->errorString = QCoreApplication::translate(SOCKET, SOC_INVAL_DATASIZE); - setSocketError(QBluetoothSocket::OperationError); - return -1; - } - - return d_ptr->writeData(data, maxSize); -} - -qint64 QBluetoothSocketPrivate::readData(char *data, qint64 maxSize) -{ - if (state != QBluetoothSocket::ConnectedState) { - errorString = QCoreApplication::translate(SOCKET, SOC_NOREAD); - q_ptr->setSocketError(QBluetoothSocket::OperationError); - return -1; - } - - if (!buffer.isEmpty()) - return buffer.read(data, maxSize); - - return 0; -} - -qint64 QBluetoothSocket::readData(char *data, qint64 maxSize) -{ - return d_ptr->readData(data, maxSize); -} - -void QBluetoothSocket::close() -{ - if (state() == UnconnectedState) - return; - - setOpenMode(NotOpen); - - if (state() == ServiceLookupState && d_ptr->discoveryAgent) { - d_ptr->discoveryAgent->disconnect(); - d_ptr->discoveryAgent->stop(); - d_ptr->discoveryAgent.reset(); - } - - setSocketState(ClosingState); - - d_ptr->close(); - - setSocketState(UnconnectedState); - emit readChannelFinished(); - emit disconnected(); -} - -bool QBluetoothSocket::setSocketDescriptor(int socketDescriptor, QBluetoothServiceInfo::Protocol socketType, - SocketState socketState, OpenMode openMode) -{ - Q_UNUSED(socketDescriptor) - Q_UNUSED(socketType) - Q_UNUSED(socketState) - Q_UNUSED(openMode) - - // Noop on OS X. - return true; -} - -int QBluetoothSocket::socketDescriptor() const -{ - return -1; -} - -/* not supported on OS X */ -void QBluetoothSocket::setPreferredSecurityFlags(QBluetooth::SecurityFlags flags) -{ - Q_UNUSED(flags) -} - -/* not supported on OS X - platform always uses encryption */ -QBluetooth::SecurityFlags QBluetoothSocket::preferredSecurityFlags() const -{ - return QBluetooth::Secure; -} - -#ifndef QT_NO_DEBUG_STREAM - -QDebug operator<<(QDebug debug, QBluetoothSocket::SocketError error) -{ - switch (error) { - case QBluetoothSocket::UnknownSocketError: - debug << "QBluetoothSocket::UnknownSocketError"; - break; - case QBluetoothSocket::HostNotFoundError: - debug << "QBluetoothSocket::HostNotFoundError"; - break; - case QBluetoothSocket::RemoteHostClosedError: - debug << "QBluetoothSocket::RemoteHostClosedError"; - break; - case QBluetoothSocket::ServiceNotFoundError: - debug << "QBluetoothSocket::ServiceNotFoundError"; - break; - case QBluetoothSocket::NetworkError: - debug << "QBluetoothSocket::NetworkError"; - break; - case QBluetoothSocket::UnsupportedProtocolError: - debug << "QBluetoothSocket::UnsupportedProtocolError"; - break; - default: - debug << "QBluetoothSocket::SocketError(" << (int)error << ")"; - } - return debug; -} - -QDebug operator<<(QDebug debug, QBluetoothSocket::SocketState state) -{ - switch (state) { - case QBluetoothSocket::UnconnectedState: - debug << "QBluetoothSocket::UnconnectedState"; - break; - case QBluetoothSocket::ConnectingState: - debug << "QBluetoothSocket::ConnectingState"; - break; - case QBluetoothSocket::ConnectedState: - debug << "QBluetoothSocket::ConnectedState"; - break; - case QBluetoothSocket::BoundState: - debug << "QBluetoothSocket::BoundState"; - break; - case QBluetoothSocket::ClosingState: - debug << "QBluetoothSocket::ClosingState"; - break; - case QBluetoothSocket::ListeningState: - debug << "QBluetoothSocket::ListeningState"; - break; - case QBluetoothSocket::ServiceLookupState: - debug << "QBluetoothSocket::ServiceLookupState"; - break; - default: - debug << "QBluetoothSocket::SocketState(" << (int)state << ")"; - } - return debug; -} - -#endif // QT_NO_DEBUG_STREAM - -#include "moc_qbluetoothsocket.cpp" - QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothsocket_osx_p.h b/src/bluetooth/qbluetoothsocket_osx_p.h index dcc684b8..1291878c 100644 --- a/src/bluetooth/qbluetoothsocket_osx_p.h +++ b/src/bluetooth/qbluetoothsocket_osx_p.h @@ -53,12 +53,11 @@ #ifdef QT_OSX_BLUETOOTH -#include "osx/osxbtchanneldelegate_p.h" -#include "osx/osxbtrfcommchannel_p.h" -#include "osx/osxbtl2capchannel_p.h" +#include "qbluetoothsocketbase_p.h" #include "qbluetoothserviceinfo.h" -#include "osx/osxbtutility_p.h" +#include "osx/btdelegates_p.h" #include "qbluetoothsocket.h" +#include "osx/btraii_p.h" #ifndef QPRIVATELINEARBUFFER_BUFFERSIZE #define QPRIVATELINEARBUFFER_BUFFERSIZE Q_INT64_C(16384) @@ -74,14 +73,11 @@ #include <QtCore/qstring.h> #include <QtCore/qvector.h> -@class IOBluetoothRFCOMMChannel; -@class IOBluetoothL2CAPChannel; - QT_BEGIN_NAMESPACE class QBluetoothAddress; -class QBluetoothSocketPrivate : public QBluetoothSocketBasePrivate, public OSXBluetooth::ChannelDelegate +class QBluetoothSocketPrivate : public QBluetoothSocketBasePrivate, public DarwinBluetooth::ChannelDelegate { friend class QBluetoothSocket; friend class QBluetoothServer; @@ -90,25 +86,47 @@ public: QBluetoothSocketPrivate(); ~QBluetoothSocketPrivate(); - void connectToService(const QBluetoothAddress &address, quint16 port, - QIODevice::OpenMode openMode); + // + bool ensureNativeSocket(QBluetoothServiceInfo::Protocol type) override; + + QString localName() const override; + QBluetoothAddress localAddress() const override; + quint16 localPort() const override; + + QString peerName() const override; + QBluetoothAddress peerAddress() const override; + quint16 peerPort() const override; - void close(); - void abort(); + void abort() override; + void close() override; - quint64 bytesAvailable() const; + qint64 writeData(const char *data, qint64 maxSize) override; + qint64 readData(char *data, qint64 maxSize) override; - QString peerName() const; - QBluetoothAddress peerAddress() const; - quint16 peerPort() const; + qint64 bytesAvailable() const override; + bool canReadLine() const override; + qint64 bytesToWrite() const override; - void _q_readNotify(); - void _q_writeNotify() override; + bool setSocketDescriptor(int socketDescriptor, QBluetoothServiceInfo::Protocol socketType, + QBluetoothSocket::SocketState socketState, + QBluetoothSocket::OpenMode openMode) override; + + void connectToServiceHelper(const QBluetoothAddress &address, quint16 port, + QIODevice::OpenMode openMode) override; + void connectToService(const QBluetoothServiceInfo &service, + QIODevice::OpenMode openMode) override; + void connectToService(const QBluetoothAddress &address, const QBluetoothUuid &uuid, + QIODevice::OpenMode openMode) override; + + void connectToService(const QBluetoothAddress &address, quint16 port, + QIODevice::OpenMode openMode) override; + + void _q_writeNotify(); private: // Create a socket from an external source (without connectToService). - bool setChannel(IOBluetoothRFCOMMChannel *channel); - bool setChannel(IOBluetoothL2CAPChannel *channel); + bool setRFCOMChannel(void *channel); + bool setL2CAPChannel(void *channel); // L2CAP and RFCOMM delegate void setChannelError(IOReturn errorCode) override; @@ -117,33 +135,15 @@ private: void readChannelData(void *data, std::size_t size) override; void writeComplete() override; - qint64 writeData(const char *data, qint64 maxSize); - qint64 readData(char *data, qint64 maxSize); - - QScopedPointer<QBluetoothServiceDiscoveryAgent> discoveryAgent; - - QPrivateLinearBuffer buffer; - QPrivateLinearBuffer txBuffer; QVector<char> writeChunk; - // Probably, not needed. - QIODevice::OpenMode openMode; - - QBluetoothSocket::SocketState state; - QBluetoothServiceInfo::Protocol socketType; - - QBluetoothSocket::SocketError socketError; - QString errorString; - - typedef QT_MANGLE_NAMESPACE(OSXBTL2CAPChannel) ObjCL2CAPChannel; - typedef OSXBluetooth::ObjCScopedPointer<ObjCL2CAPChannel> L2CAPChannel; + using L2CAPChannel = DarwinBluetooth::ScopedPointer; L2CAPChannel l2capChannel; - typedef QT_MANGLE_NAMESPACE(OSXBTRFCOMMChannel) ObjCRFCOMMChannel; - typedef OSXBluetooth::ObjCScopedPointer<ObjCRFCOMMChannel> RFCOMMChannel; + using RFCOMMChannel = L2CAPChannel; RFCOMMChannel rfcommChannel; // A trick to deal with channel open too fast (synchronously). - bool isConnecting; + bool isConnecting = false; }; QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothsocket_winrt.cpp b/src/bluetooth/qbluetoothsocket_winrt.cpp index 79dccdd6..48b14757 100644 --- a/src/bluetooth/qbluetoothsocket_winrt.cpp +++ b/src/bluetooth/qbluetoothsocket_winrt.cpp @@ -94,15 +94,22 @@ static inline QString qt_QStringFromHString(const HString &string) { UINT32 length; PCWSTR rawString = string.GetRawBuffer(&length); - return QString::fromWCharArray(rawString, length); + if (length > INT_MAX) + length = INT_MAX; + return QString::fromWCharArray(rawString, int(length)); } static qint64 writeIOStream(ComPtr<IOutputStream> stream, const char *data, qint64 len) { ComPtr<IBuffer> buffer; - HRESULT hr = g->bufferFactory->Create(len, &buffer); + if (len > UINT32_MAX) { + qCWarning(QT_BT_WINRT) << "writeIOStream can only write up to" << UINT32_MAX << "bytes."; + len = UINT32_MAX; + } + quint32 ulen = static_cast<quint32>(len); + HRESULT hr = g->bufferFactory->Create(ulen, &buffer); Q_ASSERT_SUCCEEDED(hr); - hr = buffer->put_Length(len); + hr = buffer->put_Length(ulen); Q_ASSERT_SUCCEEDED(hr); ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteArrayAccess; hr = buffer.As(&byteArrayAccess); @@ -110,7 +117,7 @@ static qint64 writeIOStream(ComPtr<IOutputStream> stream, const char *data, qint byte *bytes; hr = byteArrayAccess->Buffer(&bytes); Q_ASSERT_SUCCEEDED(hr); - memcpy(bytes, data, len); + memcpy(bytes, data, ulen); ComPtr<IAsyncOperationWithProgress<UINT32, UINT32>> op; hr = stream->WriteAsync(buffer.Get(), &op); RETURN_IF_FAILED("Failed to write to stream", return -1); @@ -257,7 +264,7 @@ public: return S_OK; } - QByteArray newData(reinterpret_cast<const char*>(data), qint64(bufferLength)); + QByteArray newData(reinterpret_cast<const char*>(data), int(bufferLength)); QMutexLocker readLocker(&m_mutex); if (m_pendingData.isEmpty()) QMetaObject::invokeMethod(this, "notifyAboutNewData", Qt::QueuedConnection); @@ -359,10 +366,11 @@ bool QBluetoothSocketPrivateWinRT::ensureNativeSocket(QBluetoothServiceInfo::Pro return true; } -void QBluetoothSocketPrivateWinRT::connectToServiceHelper(const QBluetoothAddress &address, quint16 port, QIODevice::OpenMode openMode) +void QBluetoothSocketPrivateWinRT::connectToService(Microsoft::WRL::ComPtr<IHostName> hostName, + const QString &serviceName, + QIODevice::OpenMode openMode) { Q_Q(QBluetoothSocket); - Q_UNUSED(openMode); if (socket == -1 && !ensureNativeSocket(socketType)) { errorString = QBluetoothSocket::tr("Unknown socket error"); @@ -370,20 +378,9 @@ void QBluetoothSocketPrivateWinRT::connectToServiceHelper(const QBluetoothAddres return; } - const QString addressString = address.toString(); - HStringReference hostNameRef(reinterpret_cast<LPCWSTR>(addressString.utf16())); - ComPtr<IHostNameFactory> hostNameFactory; - HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Networking_HostName).Get(), - &hostNameFactory); - Q_ASSERT_SUCCEEDED(hr); - ComPtr<IHostName> remoteHost; - hr = hostNameFactory->CreateHostName(hostNameRef.Get(), &remoteHost); - RETURN_VOID_IF_FAILED("QBluetoothSocketPrivateWinRT::connectToService: Could not create hostname."); - - const QString portString = QString::number(port); - HStringReference portReference(reinterpret_cast<LPCWSTR>(portString.utf16())); + HStringReference serviceNameReference(reinterpret_cast<LPCWSTR>(serviceName.utf16())); - hr = m_socketObject->ConnectAsync(remoteHost.Get(), portReference.Get(), &m_connectOp); + HRESULT hr = m_socketObject->ConnectAsync(hostName.Get(), serviceNameReference.Get(), &m_connectOp); if (hr == E_ACCESSDENIED) { qErrnoWarning(hr, "QBluetoothSocketPrivateWinRT::connectToService: Unable to connect to bluetooth socket." "Please check your manifest capabilities."); @@ -404,6 +401,29 @@ void QBluetoothSocketPrivateWinRT::connectToServiceHelper(const QBluetoothAddres Q_ASSERT_SUCCEEDED(hr); } +void QBluetoothSocketPrivateWinRT::connectToServiceHelper(const QBluetoothAddress &address, quint16 port, QIODevice::OpenMode openMode) +{ + Q_Q(QBluetoothSocket); + + if (socket == -1 && !ensureNativeSocket(socketType)) { + errorString = QBluetoothSocket::tr("Unknown socket error"); + q->setSocketError(QBluetoothSocket::UnknownSocketError); + return; + } + + const QString addressString = address.toString(); + HStringReference hostNameRef(reinterpret_cast<LPCWSTR>(addressString.utf16())); + ComPtr<IHostNameFactory> hostNameFactory; + HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Networking_HostName).Get(), + &hostNameFactory); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IHostName> remoteHost; + hr = hostNameFactory->CreateHostName(hostNameRef.Get(), &remoteHost); + RETURN_VOID_IF_FAILED("QBluetoothSocketPrivateWinRT::connectToService: Could not create hostname."); + const QString portString = QString::number(port); + connectToService(remoteHost, portString, openMode); +} + void QBluetoothSocketPrivateWinRT::connectToService( const QBluetoothServiceInfo &service, QIODevice::OpenMode openMode) { @@ -425,6 +445,8 @@ void QBluetoothSocketPrivateWinRT::connectToService( return; } + const QString connectionHostName = service.attribute(0xBEEF).toString(); + const QString connectionServiceName = service.attribute(0xBEF0).toString(); if (service.protocolServiceMultiplexer() > 0) { Q_ASSERT(service.socketProtocol() == QBluetoothServiceInfo::L2capProtocol); @@ -433,8 +455,24 @@ void QBluetoothSocketPrivateWinRT::connectToService( q->setSocketError(QBluetoothSocket::UnknownSocketError); return; } - connectToServiceHelper(service.device().address(), service.protocolServiceMultiplexer(), openMode); - } else if (service.serverChannel() > 0) { + connectToServiceHelper(service.device().address(), + quint16(service.protocolServiceMultiplexer()), openMode); + } else if (!connectionHostName.isEmpty() && !connectionServiceName.isEmpty()) { + Q_ASSERT(service.socketProtocol() == QBluetoothServiceInfo::RfcommProtocol); + if (!ensureNativeSocket(QBluetoothServiceInfo::RfcommProtocol)) { + errorString = QBluetoothSocket::tr("Unknown socket error"); + q->setSocketError(QBluetoothSocket::UnknownSocketError); + return; + } + HStringReference hostNameRef(reinterpret_cast<LPCWSTR>(connectionHostName.utf16())); + ComPtr<IHostNameFactory> hostNameFactory; + HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Networking_HostName).Get(), + &hostNameFactory); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IHostName> remoteHost; + hr = hostNameFactory->CreateHostName(hostNameRef.Get(), &remoteHost); + connectToService(remoteHost, connectionServiceName, openMode); + } else if (service.serverChannel() > 0) { Q_ASSERT(service.socketProtocol() == QBluetoothServiceInfo::RfcommProtocol); if (!ensureNativeSocket(QBluetoothServiceInfo::RfcommProtocol)) { @@ -442,8 +480,9 @@ void QBluetoothSocketPrivateWinRT::connectToService( q->setSocketError(QBluetoothSocket::UnknownSocketError); return; } - connectToServiceHelper(service.device().address(), service.serverChannel(), openMode); - } else { + connectToServiceHelper(service.device().address(), quint16(service.serverChannel()), + openMode); + } else { // try doing service discovery to see if we can find the socket if (service.serviceUuid().isNull() && !service.serviceClassUuids().contains(QBluetoothUuid::SerialPort)) { @@ -567,7 +606,13 @@ quint16 QBluetoothSocketPrivateWinRT::localPort() const HString localPortString; hr = info->get_LocalPort(localPortString.GetAddressOf()); Q_ASSERT_SUCCEEDED(hr); - return qt_QStringFromHString(localPortString).toInt(); + bool ok = true; + const uint port = qt_QStringFromHString(localPortString).toUInt(&ok); + if (!ok || port > UINT16_MAX) { + qCWarning(QT_BT_WINRT) << "Unexpected local port"; + return 0; + } + return quint16(port); } QString QBluetoothSocketPrivateWinRT::peerName() const @@ -618,7 +663,13 @@ quint16 QBluetoothSocketPrivateWinRT::peerPort() const HString remotePortString; hr = info->get_LocalPort(remotePortString.GetAddressOf()); Q_ASSERT_SUCCEEDED(hr); - return qt_QStringFromHString(remotePortString).toInt(); + bool ok = true; + const uint port = qt_QStringFromHString(remotePortString).toUInt(&ok); + if (!ok || port > UINT16_MAX) { + qCWarning(QT_BT_WINRT) << "Unexpected remote port"; + return 0; + } + return quint16(port); } qint64 QBluetoothSocketPrivateWinRT::writeData(const char *data, qint64 maxSize) @@ -657,8 +708,11 @@ qint64 QBluetoothSocketPrivateWinRT::readData(char *data, qint64 maxSize) return -1; } - if (!buffer.isEmpty()) - return buffer.read(data, maxSize); + if (!buffer.isEmpty()) { + if (maxSize > INT_MAX) + maxSize = INT_MAX; + return buffer.read(data, int(maxSize)); + } return 0; } @@ -749,7 +803,7 @@ void QBluetoothSocketPrivateWinRT::addToPendingData(const QVector<QByteArray> &d m_pendingData.append(data); for (const QByteArray &newData : data) { char *writePointer = buffer.reserve(newData.length()); - memcpy(writePointer, newData.data(), newData.length()); + memcpy(writePointer, newData.data(), size_t(newData.length())); } locker.unlock(); emit q->readyRead(); @@ -767,17 +821,22 @@ HRESULT QBluetoothSocketPrivateWinRT::handleConnectOpFinished(ABI::Windows::Foun HRESULT hr = action->GetResults(); switch (hr) { - case 0x8007274c: // A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. + + // A connection attempt failed because the connected party did not properly respond after a + // period of time, or established connection failed because connected host has failed to respond. + case HRESULT_FROM_WIN32(WSAETIMEDOUT): errorString = QBluetoothSocket::tr("Connection timed out"); q->setSocketError(QBluetoothSocket::NetworkError); q->setSocketState(QBluetoothSocket::UnconnectedState); return S_OK; - case 0x80072751: // A socket operation was attempted to an unreachable host. + // A socket operation was attempted to an unreachable host. + case HRESULT_FROM_WIN32(WSAEHOSTUNREACH): errorString = QBluetoothSocket::tr("Host not reachable"); q->setSocketError(QBluetoothSocket::HostNotFoundError); q->setSocketState(QBluetoothSocket::UnconnectedState); return S_OK; - case 0x8007274d: // No connection could be made because the target machine actively refused it. + // No connection could be made because the target machine actively refused it. + case HRESULT_FROM_WIN32(WSAECONNREFUSED): errorString = QBluetoothSocket::tr("Host refused connection"); q->setSocketError(QBluetoothSocket::HostNotFoundError); q->setSocketState(QBluetoothSocket::UnconnectedState); @@ -802,8 +861,7 @@ HRESULT QBluetoothSocketPrivateWinRT::handleConnectOpFinished(ABI::Windows::Foun hr = info->Close(); Q_ASSERT_SUCCEEDED(hr); } - hr = m_connectOp.Reset(); - Q_ASSERT_SUCCEEDED(hr); + m_connectOp.Reset(); } q->setOpenMode(requestedOpenMode); diff --git a/src/bluetooth/qbluetoothsocket_winrt_p.h b/src/bluetooth/qbluetoothsocket_winrt_p.h index 40e87f01..de8b7d67 100644 --- a/src/bluetooth/qbluetoothsocket_winrt_p.h +++ b/src/bluetooth/qbluetoothsocket_winrt_p.h @@ -57,6 +57,19 @@ QT_FORWARD_DECLARE_CLASS(SocketWorker) +namespace ABI { + namespace Windows { + namespace Networking { + struct IHostName; + } + } +} + +namespace Microsoft { + namespace WRL { + template <typename T> class ComPtr; + } +} QT_BEGIN_NAMESPACE class QBluetoothSocketPrivateWinRT final: public QBluetoothSocketBasePrivate @@ -125,6 +138,8 @@ private slots: void handleError(QBluetoothSocket::SocketError error); private: + void connectToService(Microsoft::WRL::ComPtr<ABI::Windows::Networking::IHostName> hostName, + const QString &serviceName, QIODevice::OpenMode openMode); HRESULT handleConnectOpFinished(ABI::Windows::Foundation::IAsyncAction *action, ABI::Windows::Foundation::AsyncStatus status); diff --git a/src/bluetooth/qbluetoothsocketbase_p.h b/src/bluetooth/qbluetoothsocketbase_p.h index 410dcbbd..d1894e96 100644 --- a/src/bluetooth/qbluetoothsocketbase_p.h +++ b/src/bluetooth/qbluetoothsocketbase_p.h @@ -89,7 +89,6 @@ QT_FORWARD_DECLARE_CLASS(QBluetoothServiceDiscoveryAgent) QT_BEGIN_NAMESPACE -#ifndef QT_OSX_BLUETOOTH class QBluetoothSocketBasePrivate : public QObject { Q_OBJECT @@ -198,26 +197,6 @@ static inline quint64 convertAddress(const quint8 (&from)[6], quint64 *to = null return result; } -#else // QT_OSX_BLUETOOTH - -// QBluetoothSocketPrivate on macOS can not contain -// Q_OBJECT (moc does not parse Objective-C syntax). -// But QBluetoothSocket still requires QMetaObject::invokeMethod -// to work. Here's the trick: -class QBluetoothSocketBasePrivate : public QObject -{ -// The most important part of it: - Q_OBJECT -public slots: - virtual void _q_writeNotify() = 0; - -protected: - Q_DECLARE_PUBLIC(QBluetoothSocket) - QBluetoothSocket *q_ptr; -}; - -#endif // QT_OSX_BLUETOOTH - QT_END_NAMESPACE #endif // QBLUETOOTHSOCKETBASE_P_H diff --git a/src/bluetooth/qbluetoothutils_winrt.cpp b/src/bluetooth/qbluetoothutils_winrt.cpp index 1d44221b..de4355c6 100644 --- a/src/bluetooth/qbluetoothutils_winrt.cpp +++ b/src/bluetooth/qbluetoothutils_winrt.cpp @@ -42,12 +42,15 @@ #include <QtCore/qfunctions_winrt.h> +#include <robuffer.h> #include <wrl.h> #include <windows.foundation.metadata.h> +#include <windows.storage.streams.h> using namespace Microsoft::WRL; using namespace Microsoft::WRL::Wrappers; using namespace ABI::Windows::Foundation::Metadata; +using namespace ABI::Windows::Storage::Streams; QT_BEGIN_NAMESPACE @@ -76,4 +79,26 @@ bool supportsNewLEApi() return apiPresent; } +QByteArray byteArrayFromBuffer(const ComPtr<NativeBuffer> &buffer, bool isWCharString) +{ + if (!buffer) { + qErrnoWarning("nullptr passed to byteArrayFromBuffer"); + return QByteArray(); + } + ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteAccess; + HRESULT hr = buffer.As(&byteAccess); + RETURN_IF_FAILED("Could not cast buffer", return QByteArray()) + char *data; + hr = byteAccess->Buffer(reinterpret_cast<byte **>(&data)); + RETURN_IF_FAILED("Could not obtain buffer data", return QByteArray()) + UINT32 size; + hr = buffer->get_Length(&size); + RETURN_IF_FAILED("Could not obtain buffer size", return QByteArray()) + if (isWCharString) { + QString valueString = QString::fromUtf16(reinterpret_cast<ushort *>(data)).left(size / 2); + return valueString.toUtf8(); + } + return QByteArray(data, qint32(size)); +} + QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothutils_winrt_p.h b/src/bluetooth/qbluetoothutils_winrt_p.h index c272bae1..93950fc9 100644 --- a/src/bluetooth/qbluetoothutils_winrt_p.h +++ b/src/bluetooth/qbluetoothutils_winrt_p.h @@ -53,10 +53,26 @@ #include <QtCore/QtGlobal> +#include <wrl/client.h> + +namespace ABI { + namespace Windows { + namespace Storage { + namespace Streams { + struct IBuffer; + } + } + } +} + QT_BEGIN_NAMESPACE bool supportsNewLEApi(); +using NativeBuffer = ABI::Windows::Storage::Streams::IBuffer; +QByteArray byteArrayFromBuffer(const Microsoft::WRL::ComPtr<NativeBuffer> &buffer, + bool isWCharString = false); + QT_END_NAMESPACE #endif // QBLUETOOTHSOCKET_WINRT_P_H diff --git a/src/bluetooth/qlowenergycharacteristic.h b/src/bluetooth/qlowenergycharacteristic.h index bb6487c4..fe9b73fa 100644 --- a/src/bluetooth/qlowenergycharacteristic.h +++ b/src/bluetooth/qlowenergycharacteristic.h @@ -101,8 +101,8 @@ protected: friend class QLowEnergyControllerPrivateBluez; friend class QLowEnergyControllerPrivateBluezDBus; friend class QLowEnergyControllerPrivateCommon; - friend class QLowEnergyControllerPrivateOSX; friend class QLowEnergyControllerPrivateWin32; + friend class QLowEnergyControllerPrivateDarwin; friend class QLowEnergyControllerPrivateWinRT; friend class QLowEnergyControllerPrivateWinRTNew; QLowEnergyCharacteristicPrivate *data = nullptr; diff --git a/src/bluetooth/qlowenergycontroller.cpp b/src/bluetooth/qlowenergycontroller.cpp index 444bfb38..bd263812 100644 --- a/src/bluetooth/qlowenergycontroller.cpp +++ b/src/bluetooth/qlowenergycontroller.cpp @@ -62,6 +62,8 @@ #endif #elif defined(QT_WIN_BLUETOOTH) #include "qlowenergycontroller_win_p.h" +#elif defined(Q_OS_DARWIN) +#include "qlowenergycontroller_darwin_p.h" #else #include "qlowenergycontroller_p.h" #endif @@ -159,6 +161,9 @@ Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT) This value was introduced by Qt 5.7. \value RemoteHostClosedError The remote device closed the connection. This value was introduced by Qt 5.10. + \value AuthorizationError The local Bluetooth device closed the connection due to + insufficient authorization. + This value was introduced by Qt 5.14. */ /*! @@ -326,6 +331,9 @@ static QLowEnergyControllerPrivate *privateController(QLowEnergyController::Role #elif defined(QT_WIN_BLUETOOTH) Q_UNUSED(role); return new QLowEnergyControllerPrivateWin32(); +#elif defined(Q_OS_DARWIN) + Q_UNUSED(role) + return new QLowEnergyControllerPrivateDarwin(); #else Q_UNUSED(role); return new QLowEnergyControllerPrivateCommon(); @@ -349,6 +357,9 @@ QLowEnergyController::QLowEnergyController( QObject *parent) : QObject(parent) { + // Note: a central created using this ctor is useless + // on Darwin - no way to use addresses when connecting. + d_ptr = privateController(CentralRole); Q_D(QLowEnergyController); @@ -378,11 +389,12 @@ QLowEnergyController::QLowEnergyController( QObject *parent) : QObject(parent) { - d_ptr = privateController(CentralRole); + d_ptr = privateController(CentralRole); Q_D(QLowEnergyController); d->q_ptr = this; d->role = CentralRole; + d->deviceUuid = remoteDeviceInfo.deviceUuid(); d->remoteDevice = remoteDeviceInfo.address(); d->localAdapter = QBluetoothLocalDevice().address(); d->addressType = QLowEnergyController::PublicAddress; @@ -411,6 +423,8 @@ QLowEnergyController::QLowEnergyController( QObject *parent) : QObject(parent) { + // Note: a central create using this ctor is useless on + // Darwin (CoreBluetooth does not work with addresses). d_ptr = privateController(CentralRole); Q_D(QLowEnergyController); @@ -437,6 +451,29 @@ QLowEnergyController *QLowEnergyController::createCentral(const QBluetoothDevice return new QLowEnergyController(remoteDevice, parent); } +/*! + Returns a new instance of this class with \a parent. + + The \a remoteDevice must contain the address of the remote Bluetooth Low + Energy device to which this object should attempt to connect later on. + + The connection is established via \a localDevice. If \a localDevice is invalid, + the local default device is automatically selected. If \a localDevice specifies + a local device that is not a local Bluetooth adapter, \l error() is set to + \l InvalidBluetoothAdapterError once \l connectToDevice() is called. + + Note that specifying the local device to be used for the connection is only + possible when using BlueZ. All other platforms do not support this feature. + + \since 5.13 + */ +QLowEnergyController *QLowEnergyController::createCentral(const QBluetoothAddress &remoteDevice, + const QBluetoothAddress &localDevice, + QObject *parent) +{ + return new QLowEnergyController(remoteDevice, localDevice, parent); +} + /*! Returns a new object of this class that is in the \l PeripheralRole and has the @@ -516,7 +553,7 @@ QBluetoothAddress QLowEnergyController::remoteAddress() const */ QBluetoothUuid QLowEnergyController::remoteDeviceUuid() const { - return QBluetoothUuid(); + return d_ptr->deviceUuid; } /*! diff --git a/src/bluetooth/qlowenergycontroller.h b/src/bluetooth/qlowenergycontroller.h index 9fe46fe5..37e7b82d 100644 --- a/src/bluetooth/qlowenergycontroller.h +++ b/src/bluetooth/qlowenergycontroller.h @@ -66,7 +66,8 @@ public: InvalidBluetoothAdapterError, ConnectionError, AdvertisingError, - RemoteHostClosedError + RemoteHostClosedError, + AuthorizationError }; Q_ENUM(Error) @@ -93,13 +94,16 @@ public: explicit QLowEnergyController(const QBluetoothAddress &remoteDevice, QObject *parent = nullptr); // TODO Qt 6 remove ctor explicit QLowEnergyController(const QBluetoothDeviceInfo &remoteDevice, - QObject *parent = nullptr); + QObject *parent = nullptr); // TODO Qt 6 make private explicit QLowEnergyController(const QBluetoothAddress &remoteDevice, const QBluetoothAddress &localDevice, QObject *parent = nullptr); // TODO Qt 6 remove ctor static QLowEnergyController *createCentral(const QBluetoothDeviceInfo &remoteDevice, QObject *parent = nullptr); + static QLowEnergyController *createCentral(const QBluetoothAddress &remoteDevice, + const QBluetoothAddress &localDevice, + QObject *parent = nullptr); static QLowEnergyController *createPeripheral(QObject *parent = nullptr); // TODO: Allow to set connection timeout (disconnect when no data has been exchanged for n seconds). diff --git a/src/bluetooth/qlowenergycontroller_android_p.h b/src/bluetooth/qlowenergycontroller_android_p.h index 3f97e363..f05c63ca 100644 --- a/src/bluetooth/qlowenergycontroller_android_p.h +++ b/src/bluetooth/qlowenergycontroller_android_p.h @@ -60,15 +60,8 @@ #include "qlowenergycontroller.h" #include "qlowenergycontrollerbase_p.h" -#if QT_CONFIG(bluez) && !defined(QT_BLUEZ_NO_BTLE) -#include <QtBluetooth/QBluetoothSocket> -#elif defined(QT_ANDROID_BLUETOOTH) #include <QtAndroidExtras/QAndroidJniObject> #include "android/lowenergynotificationhub_p.h" -#elif defined(QT_WINRT_BLUETOOTH) -#include <wrl.h> -#include <windows.devices.bluetooth.h> -#endif #include <functional> @@ -77,16 +70,7 @@ QT_BEGIN_NAMESPACE class QLowEnergyServiceData; class QTimer; -#if QT_CONFIG(bluez) && !defined(QT_BLUEZ_NO_BTLE) -class HciManager; -class LeCmacCalculator; -class QSocketNotifier; -class RemoteDeviceManager; -#elif defined(QT_ANDROID_BLUETOOTH) class LowEnergyNotificationHub; -#elif defined(QT_WINRT_BLUETOOTH) -class QWinRTLowEnergyServiceHandler; -#endif extern void registerQLowEnergyControllerMetaType(); diff --git a/src/bluetooth/qlowenergycontroller_bluez.cpp b/src/bluetooth/qlowenergycontroller_bluez.cpp index 65f4e0c2..dfa21004 100644 --- a/src/bluetooth/qlowenergycontroller_bluez.cpp +++ b/src/bluetooth/qlowenergycontroller_bluez.cpp @@ -3115,6 +3115,14 @@ void QLowEnergyControllerPrivateBluez::handleConnectionRequest() if (connectionHandle == 0) qCWarning(QT_BT_BLUEZ) << "Received client connection, but no connection complete event"; + if (l2cpSocket) { + disconnect(l2cpSocket); + if (l2cpSocket->isOpen()) + l2cpSocket->close(); + + l2cpSocket->deleteLater(); + l2cpSocket = nullptr; + } closeServerSocket(); QBluetoothSocketPrivateBluez *rawSocketPrivate = new QBluetoothSocketPrivateBluez(); diff --git a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp index 441eca6b..4e5f3430 100644 --- a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp +++ b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp @@ -268,9 +268,13 @@ void QLowEnergyControllerPrivateBluezDBus::connectToDeviceHelper() const QVariantMap &ifaceValues = jt.value(); if (iface == QStringLiteral("org.bluez.Device1")) { - if (remoteDevice.toString() == ifaceValues.value(QStringLiteral("Address")).toString()) { - devicePath = it.key().path(); - break; + if (remoteDevice.toString() == ifaceValues.value(QStringLiteral("Address")).toString()) + { + const QVariant adapterForCurrentDevice = ifaceValues.value(QStringLiteral("Adapter")); + if (qvariant_cast<QDBusObjectPath>(adapterForCurrentDevice).path() == hostAdapterPath) { + devicePath = it.key().path(); + break; + } } } } @@ -350,6 +354,9 @@ void QLowEnergyControllerPrivateBluezDBus::connectToDevice() void QLowEnergyControllerPrivateBluezDBus::disconnectFromDevice() { + if (!device) + return; + setState(QLowEnergyController::ClosingState); QDBusPendingReply<> reply = device->Disconnect(); diff --git a/src/bluetooth/qlowenergycontroller_osx.mm b/src/bluetooth/qlowenergycontroller_darwin.mm index 8cef621c..253956e2 100644 --- a/src/bluetooth/qlowenergycontroller_osx.mm +++ b/src/bluetooth/qlowenergycontroller_darwin.mm @@ -38,13 +38,17 @@ ** ****************************************************************************/ -#include "osx/osxbtnotifier_p.h" #include "osx/osxbtutility_p.h" #include "osx/uistrings_p.h" +#ifndef Q_OS_TVOS +#include "osx/osxbtperipheralmanager_p.h" +#endif // Q_OS_TVOS +#include "qlowenergycontroller_darwin_p.h" #include "qlowenergyserviceprivate_p.h" -#include "qlowenergycontroller_osx_p.h" +#include "osx/osxbtcentralmanager_p.h" + #include "qlowenergyservicedata.h" #include "qbluetoothlocaldevice.h" #include "qbluetoothdeviceinfo.h" @@ -58,30 +62,14 @@ #include <QtCore/qstring.h> #include <QtCore/qlist.h> -#define OSX_D_PTR QLowEnergyControllerPrivateOSX *osx_d_ptr = static_cast<QLowEnergyControllerPrivateOSX *>(d_ptr) - QT_BEGIN_NAMESPACE namespace { -static void registerQLowEnergyControllerMetaType() -{ - static bool initDone = false; - if (!initDone) { - qRegisterMetaType<QLowEnergyController::ControllerState>(); - qRegisterMetaType<QLowEnergyController::Error>(); - qRegisterMetaType<QLowEnergyHandle>("QLowEnergyHandle"); - qRegisterMetaType<QSharedPointer<QLowEnergyServicePrivate> >(); - qRegisterMetaType<QLowEnergyCharacteristic>(); - qRegisterMetaType<QLowEnergyDescriptor>(); - initDone = true; - } -} - typedef QSharedPointer<QLowEnergyServicePrivate> ServicePrivate; // Convenience function, can return a smart pointer that 'isNull'. -ServicePrivate qt_createLEService(QLowEnergyControllerPrivateOSX *controller, CBService *cbService, bool included) +ServicePrivate qt_createLEService(QLowEnergyControllerPrivateDarwin *controller, CBService *cbService, bool included) { Q_ASSERT_X(controller, Q_FUNC_INFO, "invalid controller (null)"); Q_ASSERT_X(cbService, Q_FUNC_INFO, "invalid service (nil)"); @@ -131,110 +119,276 @@ UUIDList qt_servicesUuids(NSArray *services) return uuids; } -} +} // unnamed namespace -QLowEnergyControllerPrivateOSX::QLowEnergyControllerPrivateOSX(QLowEnergyController::Role r, - QLowEnergyController *q, - const QBluetoothDeviceInfo &deviceInfo) - : q_ptr(q), - deviceUuid(deviceInfo.deviceUuid()), - deviceName(deviceInfo.name()), - lastError(QLowEnergyController::NoError), - controllerState(QLowEnergyController::UnconnectedState), - addressType(QLowEnergyController::PublicAddress) +#ifndef Q_OS_TVOS +using ObjCPeripheralManager = QT_MANGLE_NAMESPACE(OSXBTPeripheralManager); +#endif // Q_OS_TVOS + +using ObjCCentralManager = QT_MANGLE_NAMESPACE(OSXBTCentralManager); + +QLowEnergyControllerPrivateDarwin::QLowEnergyControllerPrivateDarwin() { + void registerQLowEnergyControllerMetaType(); registerQLowEnergyControllerMetaType(); + qRegisterMetaType<QLowEnergyHandle>("QLowEnergyHandle"); + qRegisterMetaType<QSharedPointer<QLowEnergyServicePrivate>>(); +} - Q_ASSERT_X(q, Q_FUNC_INFO, "invalid q_ptr (null)"); +QLowEnergyControllerPrivateDarwin::~QLowEnergyControllerPrivateDarwin() +{ + if (const auto leQueue = OSXBluetooth::qt_LE_queue()) { + if (role == QLowEnergyController::CentralRole) { + const auto manager = centralManager.getAs<ObjCCentralManager>(); + dispatch_sync(leQueue, ^{ + [manager detach]; + }); + } else { +#ifndef Q_OS_TVOS + const auto manager = peripheralManager.getAs<ObjCPeripheralManager>(); + dispatch_sync(leQueue, ^{ + [manager detach]; + }); +#endif + } + } +} - using OSXBluetooth::LECBManagerNotifier; +bool QLowEnergyControllerPrivateDarwin::isValid() const +{ +#ifdef Q_OS_TVOS + return centralManager; +#else + return centralManager || peripheralManager; +#endif +} - role = r; +void QLowEnergyControllerPrivateDarwin::init() +{ + using OSXBluetooth::LECBManagerNotifier; QScopedPointer<LECBManagerNotifier> notifier(new LECBManagerNotifier); if (role == QLowEnergyController::PeripheralRole) { #ifndef Q_OS_TVOS - peripheralManager.reset([[ObjCPeripheralManager alloc] initWith:notifier.data()]); + peripheralManager.reset([[ObjCPeripheralManager alloc] initWith:notifier.data()], + DarwinBluetooth::RetainPolicy::noInitialRetain); if (!peripheralManager) { - qCWarning(QT_BT_OSX) << "failed to initialize peripheral manager"; + qCWarning(QT_BT_OSX) << "failed to create a peripheral manager"; return; } #else - qCWarning(QT_BT_OSX) << "peripheral role is not supported on your platform"; + qCWarning(QT_BT_OSX) << "the peripheral role is not supported on your platform"; return; -#endif +#endif // Q_OS_TVOS } else { - centralManager.reset([[ObjCCentralManager alloc] initWith:notifier.data()]); + centralManager.reset([[ObjCCentralManager alloc] initWith:notifier.data()], + DarwinBluetooth::RetainPolicy::noInitialRetain); if (!centralManager) { - qCWarning(QT_BT_OSX) << "failed to initialize central manager"; + qCWarning(QT_BT_OSX) << "failed to initialize a central manager"; return; } } - if (!connectSlots(notifier.data())) { + if (!connectSlots(notifier.data())) qCWarning(QT_BT_OSX) << "failed to connect to notifier's signal(s)"; - } + // Ownership was taken by central manager. notifier.take(); } -QLowEnergyControllerPrivateOSX::~QLowEnergyControllerPrivateOSX() +void QLowEnergyControllerPrivateDarwin::connectToDevice() { - if (const auto leQueue = OSXBluetooth::qt_LE_queue()) { - if (role == QLowEnergyController::CentralRole) { - const auto manager = centralManager.data(); - dispatch_sync(leQueue, ^{ - [manager detach]; + Q_ASSERT_X(state == QLowEnergyController::UnconnectedState, + Q_FUNC_INFO, "invalid state"); + + if (!isValid()) { + // init() had failed for was never called. + return _q_CBManagerError(QLowEnergyController::UnknownError); + } + + if (deviceUuid.isNull()) { + // Wrong constructor was used or invalid UUID was provided. + return _q_CBManagerError(QLowEnergyController::UnknownRemoteDeviceError); + } + + // The logic enforcing the role is in the public class. + Q_ASSERT_X(role != QLowEnergyController::PeripheralRole, + Q_FUNC_INFO, "invalid role (peripheral)"); + + dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue()); + if (!leQueue) { + qCWarning(QT_BT_OSX) << "no LE queue found"; + setErrorDescription(QLowEnergyController::UnknownError); + return; + } + + setErrorDescription(QLowEnergyController::NoError); + setState(QLowEnergyController::ConnectingState); + + const QBluetoothUuid deviceUuidCopy(deviceUuid); + ObjCCentralManager *manager = centralManager.getAs<ObjCCentralManager>(); + dispatch_async(leQueue, ^{ + [manager connectToDevice:deviceUuidCopy]; + }); +} + +void QLowEnergyControllerPrivateDarwin::disconnectFromDevice() +{ + if (role == QLowEnergyController::PeripheralRole) { + // CoreBluetooth API intentionally does not provide any way of closing + // a connection. All we can do here is to stop the advertisement. + stopAdvertising(); + return; + } + + if (isValid()) { + const auto oldState = state; + + if (dispatch_queue_t leQueue = OSXBluetooth::qt_LE_queue()) { + setState(QLowEnergyController::ClosingState); + invalidateServices(); + + auto manager = centralManager.getAs<ObjCCentralManager>(); + dispatch_async(leQueue, ^{ + [manager disconnectFromDevice]; }); + + if (oldState == QLowEnergyController::ConnectingState) { + // With a pending connect attempt there is no + // guarantee we'll ever have didDisconnect callback, + // set the state here and now to make sure we still + // can connect. + setState(QLowEnergyController::UnconnectedState); + } } else { -#ifndef Q_OS_TVOS - const auto manager = peripheralManager.data(); - dispatch_sync(leQueue, ^{ - [manager detach]; - }); -#endif + qCCritical(QT_BT_OSX) << "qt LE queue is nil, " + "can not dispatch 'disconnect'"; } } } -bool QLowEnergyControllerPrivateOSX::isValid() const +void QLowEnergyControllerPrivateDarwin::discoverServices() +{ + Q_ASSERT_X(state != QLowEnergyController::UnconnectedState, + Q_FUNC_INFO, "not connected to peripheral"); + Q_ASSERT_X(role != QLowEnergyController::PeripheralRole, + Q_FUNC_INFO, "invalid role (peripheral)"); + + dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue()); + Q_ASSERT_X(leQueue, Q_FUNC_INFO, "LE queue not found"); + + setState(QLowEnergyController::DiscoveringState); + + ObjCCentralManager *manager = centralManager.getAs<ObjCCentralManager>(); + dispatch_async(leQueue, ^{ + [manager discoverServices]; + }); +} + +void QLowEnergyControllerPrivateDarwin::discoverServiceDetails(const QBluetoothUuid &serviceUuid) { + if (state != QLowEnergyController::DiscoveredState) { + qCWarning(QT_BT_OSX) << "can not discover service details in the current state, " + "QLowEnergyController::DiscoveredState is expected"; + return; + } + + if (!serviceList.contains(serviceUuid)) { + qCWarning(QT_BT_OSX) << "unknown service: " << serviceUuid; + return; + } + + dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue()); + Q_ASSERT(leQueue); + + ServicePrivate qtService(serviceList.value(serviceUuid)); + qtService->setState(QLowEnergyService::DiscoveringServices); + // Copy objects ... + ObjCCentralManager *manager = centralManager.getAs<ObjCCentralManager>(); + const QBluetoothUuid serviceUuidCopy(serviceUuid); + dispatch_async(leQueue, ^{ + [manager discoverServiceDetails:serviceUuidCopy]; + }); +} + +void QLowEnergyControllerPrivateDarwin::requestConnectionUpdate(const QLowEnergyConnectionParameters ¶ms) +{ + Q_UNUSED(params); + // TODO: implement this, if possible. + qCWarning(QT_BT_OSX) << "Connection update not implemented on your platform"; +} + +void QLowEnergyControllerPrivateDarwin::addToGenericAttributeList(const QLowEnergyServiceData &service, + QLowEnergyHandle startHandle) +{ + Q_UNUSED(service); + Q_UNUSED(startHandle); + // TODO: check why I don't need this (apparently it is used in addServiceHelper + // of the base class). +} + +QLowEnergyService * QLowEnergyControllerPrivateDarwin::addServiceHelper(const QLowEnergyServiceData &service) +{ + // Three checks below should be removed, they are done in the q_ptr's class. #ifdef Q_OS_TVOS - return centralManager; + Q_UNUSED(service); + qCDebug(QT_BT_OSX, "peripheral role is not supported on tvOS"); #else - return centralManager || peripheralManager; -#endif + if (role != QLowEnergyController::PeripheralRole) { + qCWarning(QT_BT_OSX) << "not in peripheral role"; + return nullptr; + } + + if (state != QLowEnergyController::UnconnectedState) { + qCWarning(QT_BT_OSX) << "invalid state"; + return nullptr; + } + + if (!service.isValid()) { + qCWarning(QT_BT_OSX) << "invalid service"; + return nullptr; + } + + for (auto includedService : service.includedServices()) + includedService->d_ptr->type |= QLowEnergyService::IncludedService; + + const auto manager = peripheralManager.getAs<ObjCPeripheralManager>(); + Q_ASSERT(manager); + if (const auto servicePrivate = [manager addService:service]) { + servicePrivate->setController(this); + servicePrivate->state = QLowEnergyService::LocalService; + localServices.insert(servicePrivate->uuid, servicePrivate); + return new QLowEnergyService(servicePrivate); + } +#endif // Q_OS_TVOS + return nullptr; } -void QLowEnergyControllerPrivateOSX::_q_connected() +void QLowEnergyControllerPrivateDarwin::_q_connected() { - controllerState = QLowEnergyController::ConnectedState; - - emit q_ptr->stateChanged(QLowEnergyController::ConnectedState); + setState(QLowEnergyController::ConnectedState); emit q_ptr->connected(); } -void QLowEnergyControllerPrivateOSX::_q_disconnected() +void QLowEnergyControllerPrivateDarwin::_q_disconnected() { - controllerState = QLowEnergyController::UnconnectedState; - if (role == QLowEnergyController::CentralRole) invalidateServices(); - emit q_ptr->stateChanged(QLowEnergyController::UnconnectedState); + setState(QLowEnergyController::UnconnectedState); emit q_ptr->disconnected(); } -void QLowEnergyControllerPrivateOSX::_q_serviceDiscoveryFinished() +void QLowEnergyControllerPrivateDarwin::_q_serviceDiscoveryFinished() { - Q_ASSERT_X(controllerState == QLowEnergyController::DiscoveringState, + Q_ASSERT_X(state == QLowEnergyController::DiscoveringState, Q_FUNC_INFO, "invalid state"); using namespace OSXBluetooth; QT_BT_MAC_AUTORELEASEPOOL; - NSArray *const services = [centralManager.data() peripheral].services; + NSArray *const services = [centralManager.getAs<ObjCCentralManager>() peripheral].services; // Now we have to traverse the discovered services tree. // Essentially it's an iterative version of more complicated code from the // OSXBTCentralManager's code. @@ -249,13 +403,13 @@ void QLowEnergyControllerPrivateOSX::_q_serviceDiscoveryFinished() const ServicePrivate newService(qt_createLEService(this, cbService, false)); if (!newService.data()) continue; - if (discoveredServices.contains(newService->uuid)) { + if (serviceList.contains(newService->uuid)) { // It's a bit stupid we first created it ... qCDebug(QT_BT_OSX) << "discovered service with a duplicated UUID" << newService->uuid; continue; } - discoveredServices.insert(newService->uuid, newService); + serviceList.insert(newService->uuid, newService); discoveredCBServices.insert(newService->uuid, cbService); } @@ -273,8 +427,8 @@ void QLowEnergyControllerPrivateOSX::_q_serviceDiscoveryFinished() } const QBluetoothUuid uuid(qt_uuid(s.UUID)); - if (discoveredServices.contains(uuid) && discoveredCBServices.value(uuid) == s) { - ServicePrivate qtService(discoveredServices.value(uuid)); + if (serviceList.contains(uuid) && discoveredCBServices.value(uuid) == s) { + ServicePrivate qtService(serviceList.value(uuid)); // Add included UUIDs: qtService->includedServices.append(qt_servicesUuids(s.includedServices)); }// Else - we ignored this CBService object. @@ -286,15 +440,15 @@ void QLowEnergyControllerPrivateOSX::_q_serviceDiscoveryFinished() for (NSUInteger i = 0, e = [toVisitNext count]; i < e; ++i) { CBService *const s = [toVisitNext objectAtIndex:i]; const QBluetoothUuid uuid(qt_uuid(s.UUID)); - if (discoveredServices.contains(uuid)) { + if (serviceList.contains(uuid)) { if (discoveredCBServices.value(uuid) == s) { - ServicePrivate qtService(discoveredServices.value(uuid)); + ServicePrivate qtService(serviceList.value(uuid)); qtService->type |= QLowEnergyService::IncludedService; } // Else this is the duplicate we ignored already. } else { // Oh, we do not even have it yet??? ServicePrivate newService(qt_createLEService(this, s, true)); - discoveredServices.insert(newService->uuid, newService); + serviceList.insert(newService->uuid, newService); discoveredCBServices.insert(newService->uuid, s); } } @@ -306,31 +460,26 @@ void QLowEnergyControllerPrivateOSX::_q_serviceDiscoveryFinished() qCDebug(QT_BT_OSX) << "no services found"; } - for (ServiceMap::const_iterator it = discoveredServices.constBegin(); it != discoveredServices.constEnd(); ++it) { - const QBluetoothUuid &uuid = it.key(); - QMetaObject::invokeMethod(q_ptr, "serviceDiscovered", Qt::QueuedConnection, - Q_ARG(QBluetoothUuid, uuid)); - } + for (ServiceMap::const_iterator it = serviceList.constBegin(); it != serviceList.constEnd(); ++it) + emit q_ptr->serviceDiscovered(it.key()); - controllerState = QLowEnergyController::DiscoveredState; - QMetaObject::invokeMethod(q_ptr, "stateChanged", Qt::QueuedConnection, - Q_ARG(QLowEnergyController::ControllerState, controllerState)); - QMetaObject::invokeMethod(q_ptr, "discoveryFinished", Qt::QueuedConnection); + setState(QLowEnergyController::DiscoveredState); + emit q_ptr->discoveryFinished(); } -void QLowEnergyControllerPrivateOSX::_q_serviceDetailsDiscoveryFinished(QSharedPointer<QLowEnergyServicePrivate> service) +void QLowEnergyControllerPrivateDarwin::_q_serviceDetailsDiscoveryFinished(QSharedPointer<QLowEnergyServicePrivate> service) { QT_BT_MAC_AUTORELEASEPOOL; Q_ASSERT(service); - if (!discoveredServices.contains(service->uuid)) { + if (!serviceList.contains(service->uuid)) { qCDebug(QT_BT_OSX) << "unknown service uuid:" << service->uuid; return; } - ServicePrivate qtService(discoveredServices.value(service->uuid)); + ServicePrivate qtService(serviceList.value(service->uuid)); // Assert on handles? qtService->startHandle = service->startHandle; qtService->endHandle = service->endHandle; @@ -339,8 +488,23 @@ void QLowEnergyControllerPrivateOSX::_q_serviceDetailsDiscoveryFinished(QSharedP qtService->setState(QLowEnergyService::ServiceDiscovered); } -void QLowEnergyControllerPrivateOSX::_q_characteristicRead(QLowEnergyHandle charHandle, - const QByteArray &value) +void QLowEnergyControllerPrivateDarwin::_q_servicesWereModified() +{ + if (!(state == QLowEnergyController::DiscoveringState + || state == QLowEnergyController::DiscoveredState)) { + qCWarning(QT_BT_OSX) << "services were modified while controller is not in Discovered/Discovering state"; + return; + } + + if (state == QLowEnergyController::DiscoveredState) + invalidateServices(); + + setState(QLowEnergyController::ConnectedState); + q_ptr->discoverServices(); +} + +void QLowEnergyControllerPrivateDarwin::_q_characteristicRead(QLowEnergyHandle charHandle, + const QByteArray &value) { Q_ASSERT_X(charHandle, Q_FUNC_INFO, "invalid characteristic handle(0)"); @@ -360,8 +524,8 @@ void QLowEnergyControllerPrivateOSX::_q_characteristicRead(QLowEnergyHandle char emit service->characteristicRead(characteristic, value); } -void QLowEnergyControllerPrivateOSX::_q_characteristicWritten(QLowEnergyHandle charHandle, - const QByteArray &value) +void QLowEnergyControllerPrivateDarwin::_q_characteristicWritten(QLowEnergyHandle charHandle, + const QByteArray &value) { Q_ASSERT_X(charHandle, Q_FUNC_INFO, "invalid characteristic handle(0)"); @@ -384,8 +548,8 @@ void QLowEnergyControllerPrivateOSX::_q_characteristicWritten(QLowEnergyHandle c emit service->characteristicWritten(characteristic, value); } -void QLowEnergyControllerPrivateOSX::_q_characteristicUpdated(QLowEnergyHandle charHandle, - const QByteArray &value) +void QLowEnergyControllerPrivateDarwin::_q_characteristicUpdated(QLowEnergyHandle charHandle, + const QByteArray &value) { // TODO: write/update notifications are quite similar (except asserts/warnings messages // and different signals emitted). Merge them into one function? @@ -413,8 +577,8 @@ void QLowEnergyControllerPrivateOSX::_q_characteristicUpdated(QLowEnergyHandle c emit service->characteristicChanged(characteristic, value); } -void QLowEnergyControllerPrivateOSX::_q_descriptorRead(QLowEnergyHandle dHandle, - const QByteArray &value) +void QLowEnergyControllerPrivateDarwin::_q_descriptorRead(QLowEnergyHandle dHandle, + const QByteArray &value) { Q_ASSERT_X(dHandle, Q_FUNC_INFO, "invalid descriptor handle (0)"); @@ -429,8 +593,8 @@ void QLowEnergyControllerPrivateOSX::_q_descriptorRead(QLowEnergyHandle dHandle, emit service->descriptorRead(qtDescriptor, value); } -void QLowEnergyControllerPrivateOSX::_q_descriptorWritten(QLowEnergyHandle dHandle, - const QByteArray &value) +void QLowEnergyControllerPrivateDarwin::_q_descriptorWritten(QLowEnergyHandle dHandle, + const QByteArray &value) { Q_ASSERT_X(dHandle, Q_FUNC_INFO, "invalid descriptor handle (0)"); @@ -446,8 +610,8 @@ void QLowEnergyControllerPrivateOSX::_q_descriptorWritten(QLowEnergyHandle dHand emit service->descriptorWritten(qtDescriptor, value); } -void QLowEnergyControllerPrivateOSX::_q_notificationEnabled(QLowEnergyHandle charHandle, - bool enabled) +void QLowEnergyControllerPrivateDarwin::_q_notificationEnabled(QLowEnergyHandle charHandle, + bool enabled) { // CoreBluetooth in peripheral role does not allow mutable descriptors, // in central we can only call setNotification:enabled/disabled. @@ -489,7 +653,7 @@ void QLowEnergyControllerPrivateOSX::_q_notificationEnabled(QLowEnergyHandle cha } } -void QLowEnergyControllerPrivateOSX::_q_LEnotSupported() +void QLowEnergyControllerPrivateDarwin::_q_LEnotSupported() { // Report as an error. But this should not be possible // actually: before connecting to any device, we have @@ -497,32 +661,30 @@ void QLowEnergyControllerPrivateOSX::_q_LEnotSupported() // be supported. } -void QLowEnergyControllerPrivateOSX::_q_CBManagerError(QLowEnergyController::Error errorCode) +void QLowEnergyControllerPrivateDarwin::_q_CBManagerError(QLowEnergyController::Error errorCode) { - // Errors reported during connect and general errors. - - setErrorDescription(errorCode); - emit q_ptr->error(lastError); - - if (controllerState == QLowEnergyController::ConnectingState) { - controllerState = QLowEnergyController::UnconnectedState; - emit q_ptr->stateChanged(controllerState); - } else if (controllerState == QLowEnergyController::DiscoveringState) { - controllerState = QLowEnergyController::ConnectedState; - emit q_ptr->stateChanged(controllerState); - } // In any other case we stay in Discovered, it's - // a service/characteristic - related error. + // This function handles errors reported while connecting to a remote device + // and also other errors in general. + setError(errorCode); + + if (state == QLowEnergyController::ConnectingState) + setState(QLowEnergyController::UnconnectedState); + else if (state == QLowEnergyController::DiscoveringState) + setState(QLowEnergyController::ConnectedState); + + // In any other case we stay in Discovered, it's + // a service/characteristic - related error. } -void QLowEnergyControllerPrivateOSX::_q_CBManagerError(const QBluetoothUuid &serviceUuid, - QLowEnergyController::Error errorCode) +void QLowEnergyControllerPrivateDarwin::_q_CBManagerError(const QBluetoothUuid &serviceUuid, + QLowEnergyController::Error errorCode) { // Errors reported while discovering service details etc. Q_UNUSED(errorCode) // TODO: setError? // We failed to discover any characteristics/descriptors. - if (discoveredServices.contains(serviceUuid)) { - ServicePrivate qtService(discoveredServices.value(serviceUuid)); + if (serviceList.contains(serviceUuid)) { + ServicePrivate qtService(serviceList.value(serviceUuid)); qtService->setState(QLowEnergyService::InvalidService); } else { qCDebug(QT_BT_OSX) << "error reported for unknown service" @@ -530,109 +692,24 @@ void QLowEnergyControllerPrivateOSX::_q_CBManagerError(const QBluetoothUuid &ser } } -void QLowEnergyControllerPrivateOSX::_q_CBManagerError(const QBluetoothUuid &serviceUuid, - QLowEnergyService::ServiceError errorCode) +void QLowEnergyControllerPrivateDarwin::_q_CBManagerError(const QBluetoothUuid &serviceUuid, + QLowEnergyService::ServiceError errorCode) { - if (!discoveredServices.contains(serviceUuid)) { + if (!serviceList.contains(serviceUuid)) { qCDebug(QT_BT_OSX) << "unknown service uuid:" << serviceUuid; return; } - ServicePrivate service(discoveredServices.value(serviceUuid)); + ServicePrivate service(serviceList.value(serviceUuid)); service->setError(errorCode); } -void QLowEnergyControllerPrivateOSX::connectToDevice() -{ - Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid private controller"); - Q_ASSERT_X(controllerState == QLowEnergyController::UnconnectedState, - Q_FUNC_INFO, "invalid state"); - Q_ASSERT_X(!deviceUuid.isNull(), Q_FUNC_INFO, - "invalid private controller (no device uuid)"); - Q_ASSERT_X(role != QLowEnergyController::PeripheralRole, - Q_FUNC_INFO, "invalid role (peripheral)"); - - dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue()); - if (!leQueue) { - qCWarning(QT_BT_OSX) << "no LE queue found"; - setErrorDescription(QLowEnergyController::UnknownError); - return; - } - - setErrorDescription(QLowEnergyController::NoError); - controllerState = QLowEnergyController::ConnectingState; - - const QBluetoothUuid deviceUuidCopy(deviceUuid); - ObjCCentralManager *manager = centralManager.data(); - dispatch_async(leQueue, ^{ - [manager connectToDevice:deviceUuidCopy]; - }); -} - -void QLowEnergyControllerPrivateOSX::discoverServices() -{ - Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid private controller"); - Q_ASSERT_X(controllerState != QLowEnergyController::UnconnectedState, - Q_FUNC_INFO, "not connected to peripheral"); - Q_ASSERT_X(role != QLowEnergyController::PeripheralRole, - Q_FUNC_INFO, "invalid role (peripheral)"); - - dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue()); - if (!leQueue) { - qCWarning(QT_BT_OSX) << "no LE queue found"; - setErrorDescription(QLowEnergyController::UnknownError); - return; - } - - controllerState = QLowEnergyController::DiscoveringState; - emit q_ptr->stateChanged(QLowEnergyController::DiscoveringState); - - ObjCCentralManager *manager = centralManager.data(); - dispatch_async(leQueue, ^{ - [manager discoverServices]; - }); -} - -void QLowEnergyControllerPrivateOSX::discoverServiceDetails(const QBluetoothUuid &serviceUuid) -{ - Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid private controller"); - - if (controllerState != QLowEnergyController::DiscoveredState) { - // This will also exclude peripheral role, since controller - // can never be in discovered state ... - qCWarning(QT_BT_OSX) << "can not discover service details in the current state, " - "QLowEnergyController::DiscoveredState is expected"; - return; - } - - if (!discoveredServices.contains(serviceUuid)) { - qCWarning(QT_BT_OSX) << "unknown service: " << serviceUuid; - return; - } - - dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue()); - if (!leQueue) { - qCWarning(QT_BT_OSX) << "no LE queue found"; - return; - } - - ServicePrivate qtService(discoveredServices.value(serviceUuid)); - qtService->setState(QLowEnergyService::DiscoveringServices); - // Copy objects ... - ObjCCentralManager *manager = centralManager.data(); - const QBluetoothUuid serviceUuidCopy(serviceUuid); - dispatch_async(leQueue, ^{ - [manager discoverServiceDetails:serviceUuidCopy]; - }); -} - -void QLowEnergyControllerPrivateOSX::setNotifyValue(QSharedPointer<QLowEnergyServicePrivate> service, - QLowEnergyHandle charHandle, - const QByteArray &newValue) +void QLowEnergyControllerPrivateDarwin::setNotifyValue(QSharedPointer<QLowEnergyServicePrivate> service, + QLowEnergyHandle charHandle, + const QByteArray &newValue) { Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)"); - Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller"); if (role == QLowEnergyController::PeripheralRole) { qCWarning(QT_BT_OSX) << "invalid role (peripheral)"; @@ -651,7 +728,7 @@ void QLowEnergyControllerPrivateOSX::setNotifyValue(QSharedPointer<QLowEnergySer return; } - if (!discoveredServices.contains(service->uuid)) { + if (!serviceList.contains(service->uuid)) { qCWarning(QT_BT_OSX) << "no service with uuid:" << service->uuid << "found"; return; } @@ -663,11 +740,9 @@ void QLowEnergyControllerPrivateOSX::setNotifyValue(QSharedPointer<QLowEnergySer } dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue()); - if (!leQueue) { - qCWarning(QT_BT_OSX) << "no LE queue found"; - return; - } - ObjCCentralManager *manager = centralManager.data(); + Q_ASSERT_X(leQueue, Q_FUNC_INFO, "no LE queue found"); + + ObjCCentralManager *manager = centralManager.getAs<ObjCCentralManager>(); const QBluetoothUuid serviceUuid(service->uuid); const QByteArray newValueCopy(newValue); dispatch_async(leQueue, ^{ @@ -677,18 +752,17 @@ void QLowEnergyControllerPrivateOSX::setNotifyValue(QSharedPointer<QLowEnergySer }); } -void QLowEnergyControllerPrivateOSX::readCharacteristic(QSharedPointer<QLowEnergyServicePrivate> service, - QLowEnergyHandle charHandle) +void QLowEnergyControllerPrivateDarwin::readCharacteristic(const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle) { Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)"); - Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller"); if (role == QLowEnergyController::PeripheralRole) { qCWarning(QT_BT_OSX) << "invalid role (peripheral)"; return; } - if (!discoveredServices.contains(service->uuid)) { + if (!serviceList.contains(service->uuid)) { qCWarning(QT_BT_OSX) << "no service with uuid:" << service->uuid << "found"; return; @@ -701,29 +775,26 @@ void QLowEnergyControllerPrivateOSX::readCharacteristic(QSharedPointer<QLowEnerg } dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue()); - if (!leQueue) { - qCWarning(QT_BT_OSX) << "no LE queue found"; - return; - } + Q_ASSERT_X(leQueue, Q_FUNC_INFO, "no LE queue found"); + // Attention! We have to copy UUID. - ObjCCentralManager *manager = centralManager.data(); + ObjCCentralManager *manager = centralManager.getAs<ObjCCentralManager>(); const QBluetoothUuid serviceUuid(service->uuid); dispatch_async(leQueue, ^{ [manager readCharacteristic:charHandle onService:serviceUuid]; }); } -void QLowEnergyControllerPrivateOSX::writeCharacteristic(QSharedPointer<QLowEnergyServicePrivate> service, - QLowEnergyHandle charHandle, const QByteArray &newValue, - QLowEnergyService::WriteMode mode) +void QLowEnergyControllerPrivateDarwin::writeCharacteristic(const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle, const QByteArray &newValue, + QLowEnergyService::WriteMode mode) { Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)"); - Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller"); // We can work only with services found on a given peripheral // (== created by the given LE controller). - if (!discoveredServices.contains(service->uuid)) { + if (!serviceList.contains(service->uuid) && !localServices.contains(service->uuid)) { qCWarning(QT_BT_OSX) << "no service with uuid:" << service->uuid << " found"; return; @@ -736,15 +807,12 @@ void QLowEnergyControllerPrivateOSX::writeCharacteristic(QSharedPointer<QLowEner } dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue()); - if (!leQueue) { - qCWarning(QT_BT_OSX) << "no LE queue found"; - return; - } + Q_ASSERT_X(leQueue, Q_FUNC_INFO, "no LE queue found"); // Attention! We have to copy objects! const QByteArray newValueCopy(newValue); if (role == QLowEnergyController::CentralRole) { const QBluetoothUuid serviceUuid(service->uuid); - const auto manager = centralManager.data(); + const auto manager = centralManager.getAs<ObjCCentralManager>(); dispatch_async(leQueue, ^{ [manager write:newValueCopy charHandle:charHandle @@ -753,7 +821,7 @@ void QLowEnergyControllerPrivateOSX::writeCharacteristic(QSharedPointer<QLowEner }); } else { #ifndef Q_OS_TVOS - const auto manager = peripheralManager.data(); + const auto manager = peripheralManager.getAs<ObjCPeripheralManager>(); dispatch_async(leQueue, ^{ [manager write:newValueCopy charHandle:charHandle]; }); @@ -763,9 +831,9 @@ void QLowEnergyControllerPrivateOSX::writeCharacteristic(QSharedPointer<QLowEner } } -quint16 QLowEnergyControllerPrivateOSX::updateValueOfCharacteristic(QLowEnergyHandle charHandle, - const QByteArray &value, - bool appendValue) +quint16 QLowEnergyControllerPrivateDarwin::updateValueOfCharacteristic(QLowEnergyHandle charHandle, + const QByteArray &value, + bool appendValue) { QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle); if (!service.isNull()) { @@ -784,18 +852,20 @@ quint16 QLowEnergyControllerPrivateOSX::updateValueOfCharacteristic(QLowEnergyHa return 0; } -void QLowEnergyControllerPrivateOSX::readDescriptor(QSharedPointer<QLowEnergyServicePrivate> service, - QLowEnergyHandle descriptorHandle) +void QLowEnergyControllerPrivateDarwin::readDescriptor(const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle, + const QLowEnergyHandle descriptorHandle) { + Q_UNUSED(charHandle) // Hehe, yes! + Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)"); - Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller"); if (role == QLowEnergyController::PeripheralRole) { qCWarning(QT_BT_OSX) << "invalid role (peripheral)"; return; } - if (!discoveredServices.contains(service->uuid)) { + if (!serviceList.contains(service->uuid)) { qCWarning(QT_BT_OSX) << "no service with uuid:" << service->uuid << "found"; return; @@ -808,19 +878,21 @@ void QLowEnergyControllerPrivateOSX::readDescriptor(QSharedPointer<QLowEnergySer } // Attention! Copy objects! const QBluetoothUuid serviceUuid(service->uuid); - ObjCCentralManager * const manager = centralManager.data(); + ObjCCentralManager * const manager = centralManager.getAs<ObjCCentralManager>(); dispatch_async(leQueue, ^{ [manager readDescriptor:descriptorHandle onService:serviceUuid]; }); } -void QLowEnergyControllerPrivateOSX::writeDescriptor(QSharedPointer<QLowEnergyServicePrivate> service, - QLowEnergyHandle descriptorHandle, - const QByteArray &newValue) +void QLowEnergyControllerPrivateDarwin::writeDescriptor(const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle, + const QLowEnergyHandle descriptorHandle, + const QByteArray &newValue) { + Q_UNUSED(charHandle) + Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)"); - Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller"); if (role == QLowEnergyController::PeripheralRole) { qCWarning(QT_BT_OSX) << "invalid role (peripheral)"; @@ -830,20 +902,17 @@ void QLowEnergyControllerPrivateOSX::writeDescriptor(QSharedPointer<QLowEnergySe // We can work only with services found on a given peripheral // (== created by the given LE controller), // otherwise we can not write anything at all. - if (!discoveredServices.contains(service->uuid)) { + if (!serviceList.contains(service->uuid)) { qCWarning(QT_BT_OSX) << "no service with uuid:" << service->uuid << " found"; return; } dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue()); - if (!leQueue) { - qCWarning(QT_BT_OSX) << "no LE queue found"; - return; - } + Q_ASSERT_X(leQueue, Q_FUNC_INFO, "no LE queue found"); // Attention! Copy objects! const QBluetoothUuid serviceUuid(service->uuid); - ObjCCentralManager * const manager = centralManager.data(); + ObjCCentralManager * const manager = centralManager.getAs<ObjCCentralManager>(); const QByteArray newValueCopy(newValue); dispatch_async(leQueue, ^{ [manager write:newValueCopy @@ -852,8 +921,8 @@ void QLowEnergyControllerPrivateOSX::writeDescriptor(QSharedPointer<QLowEnergySe }); } -quint16 QLowEnergyControllerPrivateOSX::updateValueOfDescriptor(QLowEnergyHandle charHandle, QLowEnergyHandle descHandle, - const QByteArray &value, bool appendValue) +quint16 QLowEnergyControllerPrivateDarwin::updateValueOfDescriptor(QLowEnergyHandle charHandle, QLowEnergyHandle descHandle, + const QByteArray &value, bool appendValue) { QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle); if (!service.isNull()) { @@ -878,66 +947,15 @@ quint16 QLowEnergyControllerPrivateOSX::updateValueOfDescriptor(QLowEnergyHandle return 0; } -QSharedPointer<QLowEnergyServicePrivate> QLowEnergyControllerPrivateOSX::serviceForHandle(QLowEnergyHandle handle) -{ - const QList<QSharedPointer<QLowEnergyServicePrivate>> services - = discoveredServices.values(); - for (QSharedPointer<QLowEnergyServicePrivate> service : services) { - if (service->startHandle <= handle && handle <= service->endHandle) - return service; - } - - return QSharedPointer<QLowEnergyServicePrivate>(); -} - -QLowEnergyCharacteristic QLowEnergyControllerPrivateOSX::characteristicForHandle(QLowEnergyHandle charHandle) -{ - QSharedPointer<QLowEnergyServicePrivate> service(serviceForHandle(charHandle)); - if (service.isNull()) - return QLowEnergyCharacteristic(); - - if (service->characteristicList.isEmpty()) - return QLowEnergyCharacteristic(); - - // Check whether it is the handle of a characteristic header - if (service->characteristicList.contains(charHandle)) - return QLowEnergyCharacteristic(service, charHandle); - - // Check whether it is the handle of the characteristic value or its descriptors - QList<QLowEnergyHandle> charHandles(service->characteristicList.keys()); - std::sort(charHandles.begin(), charHandles.end()); - - for (int i = charHandles.size() - 1; i >= 0; --i) { - if (charHandles.at(i) > charHandle) - continue; - - return QLowEnergyCharacteristic(service, charHandles.at(i)); - } - - return QLowEnergyCharacteristic(); -} - -QLowEnergyDescriptor QLowEnergyControllerPrivateOSX::descriptorForHandle(QLowEnergyHandle descriptorHandle) -{ - const QLowEnergyCharacteristic ch(characteristicForHandle(descriptorHandle)); - if (!ch.isValid()) - return QLowEnergyDescriptor(); - - const QLowEnergyServicePrivate::CharData charData = ch.d_ptr->characteristicList[ch.attributeHandle()]; - - if (charData.descriptorList.contains(descriptorHandle)) - return QLowEnergyDescriptor(ch.d_ptr, ch.attributeHandle(), descriptorHandle); - - return QLowEnergyDescriptor(); -} - -void QLowEnergyControllerPrivateOSX::setErrorDescription(QLowEnergyController::Error errorCode) +void QLowEnergyControllerPrivateDarwin::setErrorDescription(QLowEnergyController::Error errorCode) { // This function does not emit! + // TODO: well, it is not a reason to duplicate a significant part of + // setError though! - lastError = errorCode; + error = errorCode; - switch (lastError) { + switch (error) { case QLowEnergyController::NoError: errorString.clear(); break; @@ -963,46 +981,36 @@ void QLowEnergyControllerPrivateOSX::setErrorDescription(QLowEnergyController::E } } -void QLowEnergyControllerPrivateOSX::invalidateServices() -{ - const QList<QSharedPointer<QLowEnergyServicePrivate>> services - = discoveredServices.values(); - for (const QSharedPointer<QLowEnergyServicePrivate> service : services) { - service->setController(nullptr); - service->setState(QLowEnergyService::InvalidService); - } - - discoveredServices.clear(); -} - -bool QLowEnergyControllerPrivateOSX::connectSlots(OSXBluetooth::LECBManagerNotifier *notifier) +bool QLowEnergyControllerPrivateDarwin::connectSlots(OSXBluetooth::LECBManagerNotifier *notifier) { using OSXBluetooth::LECBManagerNotifier; Q_ASSERT_X(notifier, Q_FUNC_INFO, "invalid notifier object (null)"); bool ok = connect(notifier, &LECBManagerNotifier::connected, - this, &QLowEnergyControllerPrivateOSX::_q_connected); + this, &QLowEnergyControllerPrivateDarwin::_q_connected); ok = ok && connect(notifier, &LECBManagerNotifier::disconnected, - this, &QLowEnergyControllerPrivateOSX::_q_disconnected); + this, &QLowEnergyControllerPrivateDarwin::_q_disconnected); ok = ok && connect(notifier, &LECBManagerNotifier::serviceDiscoveryFinished, - this, &QLowEnergyControllerPrivateOSX::_q_serviceDiscoveryFinished); + this, &QLowEnergyControllerPrivateDarwin::_q_serviceDiscoveryFinished); + ok = ok && connect(notifier, &LECBManagerNotifier::servicesWereModified, + this, &QLowEnergyControllerPrivateDarwin::_q_servicesWereModified); ok = ok && connect(notifier, &LECBManagerNotifier::serviceDetailsDiscoveryFinished, - this, &QLowEnergyControllerPrivateOSX::_q_serviceDetailsDiscoveryFinished); + this, &QLowEnergyControllerPrivateDarwin::_q_serviceDetailsDiscoveryFinished); ok = ok && connect(notifier, &LECBManagerNotifier::characteristicRead, - this, &QLowEnergyControllerPrivateOSX::_q_characteristicRead); + this, &QLowEnergyControllerPrivateDarwin::_q_characteristicRead); ok = ok && connect(notifier, &LECBManagerNotifier::characteristicWritten, - this, &QLowEnergyControllerPrivateOSX::_q_characteristicWritten); + this, &QLowEnergyControllerPrivateDarwin::_q_characteristicWritten); ok = ok && connect(notifier, &LECBManagerNotifier::characteristicUpdated, - this, &QLowEnergyControllerPrivateOSX::_q_characteristicUpdated); + this, &QLowEnergyControllerPrivateDarwin::_q_characteristicUpdated); ok = ok && connect(notifier, &LECBManagerNotifier::descriptorRead, - this, &QLowEnergyControllerPrivateOSX::_q_descriptorRead); + this, &QLowEnergyControllerPrivateDarwin::_q_descriptorRead); ok = ok && connect(notifier, &LECBManagerNotifier::descriptorWritten, - this, &QLowEnergyControllerPrivateOSX::_q_descriptorWritten); + this, &QLowEnergyControllerPrivateDarwin::_q_descriptorWritten); ok = ok && connect(notifier, &LECBManagerNotifier::notificationEnabled, - this, &QLowEnergyControllerPrivateOSX::_q_notificationEnabled); + this, &QLowEnergyControllerPrivateDarwin::_q_notificationEnabled); ok = ok && connect(notifier, &LECBManagerNotifier::LEnotSupported, - this, &QLowEnergyControllerPrivateOSX::_q_LEnotSupported); + this, &QLowEnergyControllerPrivateDarwin::_q_LEnotSupported); ok = ok && connect(notifier, SIGNAL(CBManagerError(QLowEnergyController::Error)), this, SLOT(_q_CBManagerError(QLowEnergyController::Error))); ok = ok && connect(notifier, SIGNAL(CBManagerError(const QBluetoothUuid &, QLowEnergyController::Error)), @@ -1016,253 +1024,9 @@ bool QLowEnergyControllerPrivateOSX::connectSlots(OSXBluetooth::LECBManagerNotif return ok; } -QLowEnergyController::QLowEnergyController(const QBluetoothAddress &remoteAddress, - QObject *parent) - : QObject(parent), - d_ptr(new QLowEnergyControllerPrivateOSX(CentralRole, this)) -{ - OSX_D_PTR; - - osx_d_ptr->remoteAddress = remoteAddress; - osx_d_ptr->localAddress = QBluetoothLocalDevice().address(); - - qCWarning(QT_BT_OSX) << "construction with remote address " - "is not supported!"; -} - -QLowEnergyController::QLowEnergyController(const QBluetoothDeviceInfo &remoteDevice, - QObject *parent) - : QObject(parent), - d_ptr(new QLowEnergyControllerPrivateOSX(CentralRole, this, remoteDevice)) -{ - OSX_D_PTR; - - osx_d_ptr->localAddress = QBluetoothLocalDevice().address(); - // That's the only "real" ctor - with Core Bluetooth we need a _valid_ deviceUuid - // from 'remoteDevice'. -} - -QLowEnergyController::QLowEnergyController(const QBluetoothAddress &remoteAddress, - const QBluetoothAddress &localAddress, - QObject *parent) - : QObject(parent), - d_ptr(new QLowEnergyControllerPrivateOSX(CentralRole, this)) -{ - OSX_D_PTR; - - osx_d_ptr->remoteAddress = remoteAddress; - osx_d_ptr->localAddress = localAddress; - - qCWarning(QT_BT_OSX) << "construction with remote/local " - "addresses is not supported!"; -} - -QLowEnergyController::QLowEnergyController(QObject *parent) - : QObject(parent), - d_ptr(new QLowEnergyControllerPrivateOSX(PeripheralRole, this)) -{ - OSX_D_PTR; - - osx_d_ptr->localAddress = QBluetoothLocalDevice().address(); -} - -QLowEnergyController *QLowEnergyController::createCentral(const QBluetoothDeviceInfo &remoteDevice, - QObject *parent) -{ - return new QLowEnergyController(remoteDevice, parent); -} - -QLowEnergyController *QLowEnergyController::createPeripheral(QObject *parent) -{ - return new QLowEnergyController(parent); -} - -QLowEnergyController::~QLowEnergyController() -{ - // Deleting a peripheral will also disconnect. - delete d_ptr; -} - -QLowEnergyController::Role QLowEnergyController::role() const -{ - OSX_D_PTR; - - return osx_d_ptr->role; -} - -QBluetoothAddress QLowEnergyController::localAddress() const -{ - OSX_D_PTR; - - return osx_d_ptr->localAddress; -} - -QBluetoothAddress QLowEnergyController::remoteAddress() const -{ - OSX_D_PTR; - - return osx_d_ptr->remoteAddress; -} - -QBluetoothUuid QLowEnergyController::remoteDeviceUuid() const -{ - OSX_D_PTR; - - return osx_d_ptr->deviceUuid; -} - -QString QLowEnergyController::remoteName() const -{ - OSX_D_PTR; - - return osx_d_ptr->deviceName; -} - -QLowEnergyController::ControllerState QLowEnergyController::state() const -{ - OSX_D_PTR; - - return osx_d_ptr->controllerState; -} - -QLowEnergyController::RemoteAddressType QLowEnergyController::remoteAddressType() const -{ - OSX_D_PTR; - - return osx_d_ptr->addressType; -} - -void QLowEnergyController::setRemoteAddressType(RemoteAddressType type) -{ - Q_UNUSED(type) - - OSX_D_PTR; - - osx_d_ptr->addressType = type; -} - -void QLowEnergyController::connectToDevice() -{ - OSX_D_PTR; - - // A memory allocation problem. - if (!osx_d_ptr->isValid()) - return osx_d_ptr->_q_CBManagerError(UnknownError); - - if (role() == PeripheralRole) { - qCWarning(QT_BT_OSX) << "can not connect in peripheral role"; - return osx_d_ptr->_q_CBManagerError(ConnectionError); - } - - // No QBluetoothDeviceInfo provided during construction. - if (osx_d_ptr->deviceUuid.isNull()) - return osx_d_ptr->_q_CBManagerError(UnknownRemoteDeviceError); - - if (osx_d_ptr->controllerState != UnconnectedState) - return; - - osx_d_ptr->connectToDevice(); -} - -void QLowEnergyController::disconnectFromDevice() -{ - if (state() == UnconnectedState || state() == ClosingState) - return; - - OSX_D_PTR; - - if (role() == PeripheralRole) { - // CoreBluetooth API intentionally does not provide any way of closing - // a connection. All we can do here is to stop the advertisement. - stopAdvertising(); - return; - } - - if (osx_d_ptr->isValid()) { - const ControllerState oldState = osx_d_ptr->controllerState; - - if (dispatch_queue_t leQueue = OSXBluetooth::qt_LE_queue()) { - osx_d_ptr->controllerState = ClosingState; - emit stateChanged(ClosingState); - osx_d_ptr->invalidateServices(); - - QT_MANGLE_NAMESPACE(OSXBTCentralManager) *manager - = osx_d_ptr->centralManager.data(); - dispatch_async(leQueue, ^{ - [manager disconnectFromDevice]; - }); - - if (oldState == ConnectingState) { - // With a pending connect attempt there is no - // guarantee we'll ever have didDisconnect callback, - // set the state here and now to make sure we still - // can connect. - osx_d_ptr->controllerState = UnconnectedState; - emit stateChanged(UnconnectedState); - } - } else { - qCCritical(QT_BT_OSX) << "qt LE queue is nil, " - "can not dispatch 'disconnect'"; - } - } -} - -void QLowEnergyController::discoverServices() -{ - if (role() == PeripheralRole) { - qCWarning(QT_BT_OSX) << "invalid role (peripheral)"; - return; - } - - if (state() != ConnectedState) - return; - - OSX_D_PTR; - - osx_d_ptr->discoverServices(); -} - -QList<QBluetoothUuid> QLowEnergyController::services() const -{ - OSX_D_PTR; - - return osx_d_ptr->discoveredServices.keys(); -} - -QLowEnergyService *QLowEnergyController::createServiceObject(const QBluetoothUuid &serviceUuid, - QObject *parent) -{ - OSX_D_PTR; - - QLowEnergyService *service = nullptr; - - QLowEnergyControllerPrivateOSX::ServiceMap::const_iterator it = osx_d_ptr->discoveredServices.constFind(serviceUuid); - if (it != osx_d_ptr->discoveredServices.constEnd()) { - const QSharedPointer<QLowEnergyServicePrivate> &serviceData = it.value(); - - service = new QLowEnergyService(serviceData, parent); - } - - return service; -} - -QLowEnergyController::Error QLowEnergyController::error() const -{ - OSX_D_PTR; - - return osx_d_ptr->lastError; -} - -QString QLowEnergyController::errorString() const -{ - OSX_D_PTR; - - return osx_d_ptr->errorString; -} - -void QLowEnergyController::startAdvertising(const QLowEnergyAdvertisingParameters ¶ms, - const QLowEnergyAdvertisingData &advertisingData, - const QLowEnergyAdvertisingData &scanResponseData) +void QLowEnergyControllerPrivateDarwin::startAdvertising(const QLowEnergyAdvertisingParameters ¶ms, + const QLowEnergyAdvertisingData &advertisingData, + const QLowEnergyAdvertisingData &scanResponseData) { #ifdef Q_OS_TVOS Q_UNUSED(params) @@ -1270,123 +1034,65 @@ void QLowEnergyController::startAdvertising(const QLowEnergyAdvertisingParameter Q_UNUSED(scanResponseData) qCWarning(QT_BT_OSX) << "advertising is not supported on your platform"; #else - OSX_D_PTR; - if (!osx_d_ptr->isValid()) - return osx_d_ptr->_q_CBManagerError(UnknownError); + if (!isValid()) + return _q_CBManagerError(QLowEnergyController::UnknownError); - if (role() != PeripheralRole) { - qCWarning(QT_BT_OSX) << "invalid role"; + if (role != QLowEnergyController::PeripheralRole) { + qCWarning(QT_BT_OSX) << "controller is not a peripheral, cannot start advertising"; return; } - if (state() != UnconnectedState) { - qCWarning(QT_BT_OSX) << "invalid state" << state(); + if (state != QLowEnergyController::UnconnectedState) { + qCWarning(QT_BT_OSX) << "invalid state" << state; return; } auto leQueue(OSXBluetooth::qt_LE_queue()); if (!leQueue) { qCWarning(QT_BT_OSX) << "no LE queue found"; - osx_d_ptr->setErrorDescription(QLowEnergyController::UnknownError); + setErrorDescription(QLowEnergyController::UnknownError); return; } - [osx_d_ptr->peripheralManager setParameters:params - data:advertisingData - scanResponse:scanResponseData]; + const auto manager = peripheralManager.getAs<ObjCPeripheralManager>(); + [manager setParameters:params data:advertisingData scanResponse:scanResponseData]; - osx_d_ptr->controllerState = AdvertisingState; - emit stateChanged(AdvertisingState); + setState(QLowEnergyController::AdvertisingState); - const auto manager = osx_d_ptr->peripheralManager.data(); dispatch_async(leQueue, ^{ [manager startAdvertising]; }); #endif } -void QLowEnergyController::stopAdvertising() +void QLowEnergyControllerPrivateDarwin::stopAdvertising() { #ifdef Q_OS_TVOS qCWarning(QT_BT_OSX) << "advertising is not supported on your platform"; #else - OSX_D_PTR; + if (!isValid()) + return _q_CBManagerError(QLowEnergyController::UnknownError); - if (!osx_d_ptr->isValid()) - return osx_d_ptr->_q_CBManagerError(UnknownError); - - if (state() != AdvertisingState) { - qCDebug(QT_BT_OSX) << "cannot stop advertising, called in state" << state(); + if (state != QLowEnergyController::AdvertisingState) { + qCDebug(QT_BT_OSX) << "cannot stop advertising, called in state" << state; return; } if (const auto leQueue = OSXBluetooth::qt_LE_queue()) { - const auto manager = osx_d_ptr->peripheralManager.data(); + const auto manager = peripheralManager.getAs<ObjCPeripheralManager>(); dispatch_sync(leQueue, ^{ [manager stopAdvertising]; }); - osx_d_ptr->controllerState = UnconnectedState; - emit stateChanged(UnconnectedState); + setState(QLowEnergyController::UnconnectedState); } else { qCWarning(QT_BT_OSX) << "no LE queue found"; - osx_d_ptr->setErrorDescription(QLowEnergyController::UnknownError); + setErrorDescription(QLowEnergyController::UnknownError); return; } #endif } -QLowEnergyService *QLowEnergyController::addService(const QLowEnergyServiceData &data, - QObject *parent) -{ -#ifdef Q_OS_TVOS - Q_UNUSED(data) - Q_UNUSED(parent) - qCWarning(QT_BT_OSX) << "peripheral role is not supported on your platform"; -#else - OSX_D_PTR; - - if (!osx_d_ptr->isValid()) { - osx_d_ptr->_q_CBManagerError(UnknownError); - return nullptr; - } - - if (role() != PeripheralRole) { - qCWarning(QT_BT_OSX) << "not in peripheral role"; - return nullptr; - } - - if (state() != UnconnectedState) { - qCWarning(QT_BT_OSX) << "invalid state"; - return nullptr; - } - - if (!data.isValid()) { - qCWarning(QT_BT_OSX) << "invalid service"; - return nullptr; - } - - for (auto includedService : data.includedServices()) - includedService->d_ptr->type |= QLowEnergyService::IncludedService; - - if (const auto servicePrivate = [osx_d_ptr->peripheralManager addService:data]) { - servicePrivate->setController(osx_d_ptr); - servicePrivate->state = QLowEnergyService::LocalService; - osx_d_ptr->discoveredServices.insert(servicePrivate->uuid, servicePrivate); - return new QLowEnergyService(servicePrivate, parent); - } -#endif - - return nullptr; -} - -void QLowEnergyController::requestConnectionUpdate(const QLowEnergyConnectionParameters ¶ms) -{ - Q_UNUSED(params); - qCWarning(QT_BT_OSX) << "Connection update not implemented on your platform"; -} - QT_END_NAMESPACE -#include "moc_qlowenergycontroller_osx_p.cpp" diff --git a/src/bluetooth/qlowenergycontroller_osx_p.h b/src/bluetooth/qlowenergycontroller_darwin_p.h index 24b7c6e9..960d7fbc 100644 --- a/src/bluetooth/qlowenergycontroller_osx_p.h +++ b/src/bluetooth/qlowenergycontroller_darwin_p.h @@ -37,8 +37,8 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ -#ifndef QLOWENERGYCONTROLLER_OSX_P_H -#define QLOWENERGYCONTROLLER_OSX_P_H +#ifndef QLOWENERGYCONTROLLER_DARWIN_P_H +#define QLOWENERGYCONTROLLER_DARWIN_P_H // // W A R N I N G @@ -51,46 +51,64 @@ // We mean it. // -#include "osx/osxbtperipheralmanager_p.h" #include "qlowenergyserviceprivate_p.h" -#include "osx/osxbtcentralmanager_p.h" #include "qlowenergycontrollerbase_p.h" #include "qlowenergycontroller.h" #include "osx/osxbtnotifier_p.h" -#include "osx/osxbtutility_p.h" #include "qbluetoothaddress.h" #include "qbluetoothuuid.h" +#include "osx/btraii_p.h" #include <QtCore/qsharedpointer.h> -#include <QtCore/qsysinfo.h> #include <QtCore/qglobal.h> #include <QtCore/qstring.h> #include <QtCore/qmap.h> QT_BEGIN_NAMESPACE -namespace OSXBluetooth -{ - -class LECBManagerNotifier; - -} - class QByteArray; -// Suffix 'OSX' is a legacy, it's also iOS. -class QLowEnergyControllerPrivateOSX : public QLowEnergyControllerPrivate +class QLowEnergyControllerPrivateDarwin : public QLowEnergyControllerPrivate { friend class QLowEnergyController; friend class QLowEnergyService; Q_OBJECT public: - QLowEnergyControllerPrivateOSX(QLowEnergyController::Role role, QLowEnergyController *q, - const QBluetoothDeviceInfo &info = QBluetoothDeviceInfo()); - ~QLowEnergyControllerPrivateOSX(); - - bool isValid() const; + QLowEnergyControllerPrivateDarwin(); + ~QLowEnergyControllerPrivateDarwin(); + + void init() override; + void connectToDevice() override; + void disconnectFromDevice() override; + void discoverServices() override; + void discoverServiceDetails(const QBluetoothUuid &serviceUuid) override; + + void readCharacteristic(const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle) override; + void readDescriptor(const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle, + const QLowEnergyHandle descriptorHandle) override; + + void writeCharacteristic(const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle, const QByteArray &newValue, + QLowEnergyService::WriteMode mode) override; + void writeDescriptor(const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle, + const QLowEnergyHandle descriptorHandle, + const QByteArray &newValue) override; + + + void requestConnectionUpdate(const QLowEnergyConnectionParameters ¶ms) override; + void addToGenericAttributeList(const QLowEnergyServiceData &service, + QLowEnergyHandle startHandle) override; + + void startAdvertising(const QLowEnergyAdvertisingParameters ¶ms, + const QLowEnergyAdvertisingData &advertisingData, + const QLowEnergyAdvertisingData &scanResponseData) override; + void stopAdvertising()override; + QLowEnergyService *addServiceHelper(const QLowEnergyServiceData &service) override; + bool isValid() const; // QT6 - delete this logic. private Q_SLOTS: void _q_connected(); @@ -98,6 +116,7 @@ private Q_SLOTS: void _q_serviceDiscoveryFinished(); void _q_serviceDetailsDiscoveryFinished(QSharedPointer<QLowEnergyServicePrivate> service); + void _q_servicesWereModified(); void _q_characteristicRead(QLowEnergyHandle charHandle, const QByteArray &value); void _q_characteristicWritten(QLowEnergyHandle charHandle, const QByteArray &value); @@ -112,75 +131,30 @@ private Q_SLOTS: void _q_CBManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyService::ServiceError error); private: - void connectToDevice(); - void discoverServices(); - void discoverServiceDetails(const QBluetoothUuid &serviceUuid); - void setNotifyValue(QSharedPointer<QLowEnergyServicePrivate> service, QLowEnergyHandle charHandle, const QByteArray &newValue); - void readCharacteristic(QSharedPointer<QLowEnergyServicePrivate> service, - QLowEnergyHandle charHandle); - void writeCharacteristic(QSharedPointer<QLowEnergyServicePrivate> service, - QLowEnergyHandle charHandle, const QByteArray &newValue, - QLowEnergyService::WriteMode mode); - quint16 updateValueOfCharacteristic(QLowEnergyHandle charHandle, const QByteArray &value, bool appendValue); - void readDescriptor(QSharedPointer<QLowEnergyServicePrivate> service, - QLowEnergyHandle charHandle); - void writeDescriptor(QSharedPointer<QLowEnergyServicePrivate> service, - QLowEnergyHandle descriptorHandle, - const QByteArray &newValue); - - quint16 updateValueOfDescriptor(QLowEnergyHandle charHandle, QLowEnergyHandle descHandle, const QByteArray &value, bool appendValue); - // 'Lookup' functions: - QSharedPointer<QLowEnergyServicePrivate> serviceForHandle(QLowEnergyHandle serviceHandle); - QLowEnergyCharacteristic characteristicForHandle(QLowEnergyHandle charHandle); - QLowEnergyDescriptor descriptorForHandle(QLowEnergyHandle descriptorHandle); - void setErrorDescription(QLowEnergyController::Error errorCode); - void invalidateServices(); bool connectSlots(OSXBluetooth::LECBManagerNotifier *notifier); - QLowEnergyController *q_ptr; - QBluetoothUuid deviceUuid; - QString deviceName; - - QString errorString; - QLowEnergyController::Error lastError; - - QBluetoothAddress localAddress; - QBluetoothAddress remoteAddress; - - QLowEnergyController::Role role; - - QLowEnergyController::ControllerState controllerState; - QLowEnergyController::RemoteAddressType addressType; - - typedef QT_MANGLE_NAMESPACE(OSXBTCentralManager) ObjCCentralManager; - typedef OSXBluetooth::ObjCScopedPointer<ObjCCentralManager> CentralManager; - CentralManager centralManager; + DarwinBluetooth::ScopedPointer centralManager; #ifndef Q_OS_TVOS - typedef QT_MANGLE_NAMESPACE(OSXBTPeripheralManager) ObjCPeripheralManager; - typedef OSXBluetooth::ObjCScopedPointer<ObjCPeripheralManager> PeripheralManager; - PeripheralManager peripheralManager; + DarwinBluetooth::ScopedPointer peripheralManager; #endif - typedef QMap<QBluetoothUuid, QSharedPointer<QLowEnergyServicePrivate> > ServiceMap; - typedef ServiceMap::const_iterator ConstServiceIterator; - typedef ServiceMap::iterator ServiceIterator; - ServiceMap discoveredServices; + using ServiceMap = QMap<QBluetoothUuid, QSharedPointer<QLowEnergyServicePrivate>>; }; QT_END_NAMESPACE -#endif +#endif // QLOWENERGYCONTROLLER_DARWIN_P_H diff --git a/src/bluetooth/qlowenergycontroller_winrt.cpp b/src/bluetooth/qlowenergycontroller_winrt.cpp index ed279ffa..ab566bd9 100644 --- a/src/bluetooth/qlowenergycontroller_winrt.cpp +++ b/src/bluetooth/qlowenergycontroller_winrt.cpp @@ -38,6 +38,7 @@ ****************************************************************************/ #include "qlowenergycontroller_winrt_p.h" +#include "qbluetoothutils_winrt_p.h" #include <QtBluetooth/QLowEnergyCharacteristicData> #include <QtBluetooth/QLowEnergyDescriptorData> @@ -75,52 +76,7 @@ typedef GattReadClientCharacteristicConfigurationDescriptorResult ClientCharConf typedef IGattReadClientCharacteristicConfigurationDescriptorResult IClientCharConfigDescriptorResult; Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT) - -static QVector<QBluetoothUuid> getIncludedServiceIds(const ComPtr<IGattDeviceService> &service) -{ - QVector<QBluetoothUuid> result; - ComPtr<IGattDeviceService2> service2; - HRESULT hr = service.As(&service2); - Q_ASSERT_SUCCEEDED(hr); - ComPtr<IVectorView<GattDeviceService *>> includedServices; - hr = service2->GetAllIncludedServices(&includedServices); - Q_ASSERT_SUCCEEDED(hr); - - uint count; - hr = includedServices->get_Size(&count); - Q_ASSERT_SUCCEEDED(hr); - for (uint i = 0; i < count; ++i) { - ComPtr<IGattDeviceService> includedService; - hr = includedServices->GetAt(i, &includedService); - Q_ASSERT_SUCCEEDED(hr); - GUID guuid; - hr = includedService->get_Uuid(&guuid); - Q_ASSERT_SUCCEEDED(hr); - const QBluetoothUuid service(guuid); - result << service; - - result << getIncludedServiceIds(includedService); - } - return result; -} - -static QByteArray byteArrayFromBuffer(const ComPtr<IBuffer> &buffer, bool isWCharString = false) -{ - ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteAccess; - HRESULT hr = buffer.As(&byteAccess); - Q_ASSERT_SUCCEEDED(hr); - char *data; - hr = byteAccess->Buffer(reinterpret_cast<byte **>(&data)); - Q_ASSERT_SUCCEEDED(hr); - UINT32 size; - hr = buffer->get_Length(&size); - Q_ASSERT_SUCCEEDED(hr); - if (isWCharString) { - QString valueString = QString::fromUtf16(reinterpret_cast<ushort *>(data)).left(size / 2); - return valueString.toUtf8(); - } - return QByteArray(data, size); -} +Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT_SERVICE_THREAD) static QByteArray byteArrayFromGattResult(const ComPtr<IGattReadResult> &gattResult, bool isWCharString = false) { @@ -284,6 +240,9 @@ QLowEnergyControllerPrivateWinRT::QLowEnergyControllerPrivateWinRT() qCDebug(QT_BT_WINRT) << __FUNCTION__; registerQLowEnergyControllerMetaType(); + connect(this, &QLowEnergyControllerPrivateWinRT::characteristicChanged, + this, &QLowEnergyControllerPrivateWinRT::handleCharacteristicChanged, + Qt::QueuedConnection); } QLowEnergyControllerPrivateWinRT::~QLowEnergyControllerPrivateWinRT() @@ -291,9 +250,7 @@ QLowEnergyControllerPrivateWinRT::~QLowEnergyControllerPrivateWinRT() if (mDevice && mStatusChangedToken.value) mDevice->remove_ConnectionStatusChanged(mStatusChangedToken); - qCDebug(QT_BT_WINRT) << "Unregistering " << mValueChangedTokens.count() << " value change tokens"; - for (const ValueChangedEntry &entry : qAsConst(mValueChangedTokens)) - entry.characteristic->remove_ValueChanged(entry.token); + unregisterFromValueChanges(); } void QLowEnergyControllerPrivateWinRT::init() @@ -340,8 +297,10 @@ void QLowEnergyControllerPrivateWinRT::connectToDevice() && status == BluetoothConnectionStatus::BluetoothConnectionStatus_Connected) { setState(QLowEnergyController::ConnectedState); emit q->connected(); - } else if (state == QLowEnergyController::ConnectedState + } else if (state != QLowEnergyController::UnconnectedState && status == BluetoothConnectionStatus::BluetoothConnectionStatus_Disconnected) { + invalidateServices(); + unregisterFromValueChanges(); setError(QLowEnergyController::RemoteHostClosedError); setState(QLowEnergyController::UnconnectedState); emit q->disconnected(); @@ -436,12 +395,17 @@ void QLowEnergyControllerPrivateWinRT::disconnectFromDevice() { qCDebug(QT_BT_WINRT) << __FUNCTION__; Q_Q(QLowEnergyController); + setState(QLowEnergyController::ClosingState); + unregisterFromValueChanges(); + if (mDevice) { + if (mStatusChangedToken.value) { + mDevice->remove_ConnectionStatusChanged(mStatusChangedToken); + mStatusChangedToken.value = 0; + } + mDevice = nullptr; + } setState(QLowEnergyController::UnconnectedState); emit q->disconnected(); - if (mDevice && mStatusChangedToken.value) { - mDevice->remove_ConnectionStatusChanged(mStatusChangedToken); - mStatusChangedToken.value = 0; - } } ComPtr<IGattDeviceService> QLowEnergyControllerPrivateWinRT::getNativeService(const QBluetoothUuid &serviceUuid) @@ -493,7 +457,7 @@ void QLowEnergyControllerPrivateWinRT::registerForValueChanges(const QBluetoothU ComPtr<IBuffer> buffer; hr = args->get_CharacteristicValue(&buffer); Q_ASSERT_SUCCEEDED(hr); - characteristicChanged(handle, byteArrayFromBuffer(buffer)); + emit characteristicChanged(handle, byteArrayFromBuffer(buffer)); return S_OK; }).Get(), &token); Q_ASSERT_SUCCEEDED(hr); @@ -502,6 +466,17 @@ void QLowEnergyControllerPrivateWinRT::registerForValueChanges(const QBluetoothU << serviceUuid << "registered for value changes"; } +void QLowEnergyControllerPrivateWinRT::unregisterFromValueChanges() +{ + qCDebug(QT_BT_WINRT) << "Unregistering " << mValueChangedTokens.count() << " value change tokens"; + HRESULT hr; + for (const ValueChangedEntry &entry : qAsConst(mValueChangedTokens)) { + hr = entry.characteristic->remove_ValueChanged(entry.token); + Q_ASSERT_SUCCEEDED(hr); + } + mValueChangedTokens.clear(); +} + void QLowEnergyControllerPrivateWinRT::obtainIncludedServices(QSharedPointer<QLowEnergyServicePrivate> servicePointer, ComPtr<IGattDeviceService> service) { @@ -527,6 +502,9 @@ void QLowEnergyControllerPrivateWinRT::obtainIncludedServices(QSharedPointer<QLo Q_ASSERT_SUCCEEDED(hr); const QBluetoothUuid includedUuid(guuid); QSharedPointer<QLowEnergyServicePrivate> includedPointer; + qCDebug(QT_BT_WINRT_SERVICE_THREAD) << __FUNCTION__ + << "Changing service pointer from thread" + << QThread::currentThread(); if (serviceList.contains(includedUuid)) { includedPointer = serviceList.value(includedUuid); } else { @@ -537,6 +515,9 @@ void QLowEnergyControllerPrivateWinRT::obtainIncludedServices(QSharedPointer<QLo includedPointer = QSharedPointer<QLowEnergyServicePrivate>(priv); serviceList.insert(includedUuid, includedPointer); } + qCDebug(QT_BT_WINRT_SERVICE_THREAD) << __FUNCTION__ + << "Changing service pointer from thread" + << QThread::currentThread(); includedPointer->type |= QLowEnergyService::IncludedService; servicePointer->includedServices.append(includedUuid); @@ -566,6 +547,9 @@ void QLowEnergyControllerPrivateWinRT::discoverServices() Q_ASSERT_SUCCEEDED(hr); const QBluetoothUuid service(guuid); + qCDebug(QT_BT_WINRT_SERVICE_THREAD) << __FUNCTION__ + << "Changing service pointer from thread" + << QThread::currentThread(); QSharedPointer<QLowEnergyServicePrivate> pointer; if (serviceList.contains(service)) { pointer = serviceList.value(service); @@ -605,6 +589,8 @@ void QLowEnergyControllerPrivateWinRT::discoverServiceDetails(const QBluetoothUu //update service data QSharedPointer<QLowEnergyServicePrivate> pointer = serviceList.value(service); + qCDebug(QT_BT_WINRT_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread" + << QThread::currentThread(); pointer->setState(QLowEnergyService::DiscoveringServices); ComPtr<IGattDeviceService2> deviceService2; @@ -698,6 +684,8 @@ void QLowEnergyControllerPrivateWinRT::readCharacteristic(const QSharedPointer<Q const QLowEnergyHandle charHandle) { qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle; + qCDebug(QT_BT_WINRT_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread" + << QThread::currentThread(); Q_ASSERT(!service.isNull()); if (role == QLowEnergyController::PeripheralRole) { service->setError(QLowEnergyService::CharacteristicReadError); @@ -763,6 +751,8 @@ void QLowEnergyControllerPrivateWinRT::readDescriptor(const QSharedPointer<QLowE const QLowEnergyHandle descHandle) { qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle << descHandle; + qCDebug(QT_BT_WINRT_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread" + << QThread::currentThread(); Q_ASSERT(!service.isNull()); if (role == QLowEnergyController::PeripheralRole) { service->setError(QLowEnergyService::DescriptorReadError); @@ -779,7 +769,7 @@ void QLowEnergyControllerPrivateWinRT::readDescriptor(const QSharedPointer<QLowE HRESULT hr; hr = QEventDispatcherWinRT::runOnXamlThread([charHandle, descHandle, service, this]() { - QLowEnergyServicePrivate::CharData charData = service->characteristicList.value(charHandle); + const QLowEnergyServicePrivate::CharData charData = service->characteristicList.value(charHandle); ComPtr<IGattCharacteristic> characteristic = getNativeCharacteristic(service->uuid, charData.uuid); if (!characteristic) { qCDebug(QT_BT_WINRT) << "Could not obtain native characteristic" << charData.uuid @@ -791,12 +781,13 @@ void QLowEnergyControllerPrivateWinRT::readDescriptor(const QSharedPointer<QLowE // Get native descriptor if (!charData.descriptorList.contains(descHandle)) qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "cannot be found in characteristic" << charHandle; - QLowEnergyServicePrivate::DescData descData = charData.descriptorList.value(descHandle); - if (descData.uuid == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) { + const QLowEnergyServicePrivate::DescData descData = charData.descriptorList.value(descHandle); + const QBluetoothUuid descUuid = descData.uuid; + if (descUuid == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) { ComPtr<IAsyncOperation<ClientCharConfigDescriptorResult *>> readOp; HRESULT hr = characteristic->ReadClientCharacteristicConfigurationDescriptorAsync(&readOp); Q_ASSERT_SUCCEEDED(hr); - auto readCompletedLambda = [&charData, charHandle, &descData, descHandle, service] + auto readCompletedLambda = [charHandle, descHandle, service] (IAsyncOperation<ClientCharConfigDescriptorResult *> *op, AsyncStatus status) { if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) { @@ -837,9 +828,11 @@ void QLowEnergyControllerPrivateWinRT::readDescriptor(const QSharedPointer<QLowE service->setError(QLowEnergyService::DescriptorReadError); return S_OK; } + QLowEnergyServicePrivate::DescData descData; + descData.uuid = QBluetoothUuid::ClientCharacteristicConfiguration; descData.value = QByteArray(2, Qt::Uninitialized); qToLittleEndian(result, descData.value.data()); - charData.descriptorList.insert(descHandle, descData); + service->characteristicList[charHandle].descriptorList[descHandle] = descData; emit service->descriptorRead(QLowEnergyDescriptor(service, charHandle, descHandle), descData.value); return S_OK; @@ -857,7 +850,7 @@ void QLowEnergyControllerPrivateWinRT::readDescriptor(const QSharedPointer<QLowE ComPtr<IAsyncOperation<GattReadResult*>> readOp; hr = descriptor->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, &readOp); Q_ASSERT_SUCCEEDED(hr); - auto readCompletedLambda = [&charData, charHandle, &descData, descHandle, service] + auto readCompletedLambda = [charHandle, descHandle, descUuid, service] (IAsyncOperation<GattReadResult*> *op, AsyncStatus status) { if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) { @@ -873,11 +866,12 @@ void QLowEnergyControllerPrivateWinRT::readDescriptor(const QSharedPointer<QLowE service->setError(QLowEnergyService::DescriptorReadError); return S_OK; } - if (descData.uuid == QBluetoothUuid::CharacteristicUserDescription) + QLowEnergyServicePrivate::DescData descData; + if (descUuid == QBluetoothUuid::CharacteristicUserDescription) descData.value = byteArrayFromGattResult(descriptorValue, true); else descData.value = byteArrayFromGattResult(descriptorValue); - charData.descriptorList.insert(descHandle, descData); + service->characteristicList[charHandle].descriptorList[descHandle] = descData; emit service->descriptorRead(QLowEnergyDescriptor(service, charHandle, descHandle), descData.value); return S_OK; @@ -895,6 +889,8 @@ void QLowEnergyControllerPrivateWinRT::writeCharacteristic(const QSharedPointer< QLowEnergyService::WriteMode mode) { qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle << newValue << mode; + qCDebug(QT_BT_WINRT_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread" + << QThread::currentThread(); Q_ASSERT(!service.isNull()); if (role == QLowEnergyController::PeripheralRole) { service->setError(QLowEnergyService::CharacteristicWriteError); @@ -985,6 +981,8 @@ void QLowEnergyControllerPrivateWinRT::writeDescriptor( const QByteArray &newValue) { qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle << descHandle << newValue; + qCDebug(QT_BT_WINRT_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread" + << QThread::currentThread(); Q_ASSERT(!service.isNull()); if (role == QLowEnergyController::PeripheralRole) { service->setError(QLowEnergyService::DescriptorWriteError); @@ -1128,9 +1126,12 @@ void QLowEnergyControllerPrivateWinRT::addToGenericAttributeList(const QLowEnerg Q_UNIMPLEMENTED(); } -void QLowEnergyControllerPrivateWinRT::characteristicChanged( - int charHandle, const QByteArray &data) +void QLowEnergyControllerPrivateWinRT::handleCharacteristicChanged( + quint16 charHandle, const QByteArray &data) { + qCDebug(QT_BT_WINRT) << __FUNCTION__ << charHandle << data; + qCDebug(QT_BT_WINRT_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread" + << QThread::currentThread(); QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle); if (service.isNull()) diff --git a/src/bluetooth/qlowenergycontroller_winrt_new.cpp b/src/bluetooth/qlowenergycontroller_winrt_new.cpp index 1f70807e..a22064fd 100644 --- a/src/bluetooth/qlowenergycontroller_winrt_new.cpp +++ b/src/bluetooth/qlowenergycontroller_winrt_new.cpp @@ -39,7 +39,9 @@ #include "qlowenergycontroller_winrt_new_p.h" #include "qlowenergycontroller_winrt_p.h" +#include "qbluetoothutils_winrt_p.h" +#include <QtBluetooth/qbluetoothlocaldevice.h> #include <QtBluetooth/QLowEnergyCharacteristicData> #include <QtBluetooth/QLowEnergyDescriptorData> #include <QtBluetooth/private/qbluetoothutils_winrt_p.h> @@ -56,6 +58,7 @@ #include <robuffer.h> #include <windows.devices.enumeration.h> #include <windows.devices.bluetooth.h> +#include <windows.devices.bluetooth.genericattributeprofile.h> #include <windows.foundation.collections.h> #include <windows.foundation.metadata.h> #include <windows.storage.streams.h> @@ -78,7 +81,39 @@ typedef ITypedEventHandler<GattCharacteristic *, GattValueChangedEventArgs *> Va typedef GattReadClientCharacteristicConfigurationDescriptorResult ClientCharConfigDescriptorResult; typedef IGattReadClientCharacteristicConfigurationDescriptorResult IClientCharConfigDescriptorResult; +#define EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr, ret) \ + if (FAILED(hr)) { \ + emitErrorAndQuitThread(hr); \ + ret; \ + } + +#define WARN_AND_CONTINUE_IF_FAILED(hr, msg) \ + if (FAILED(hr)) { \ + qCWarning(QT_BT_WINRT) << msg; \ + continue; \ + } + +#define CHECK_FOR_DEVICE_CONNECTION_ERROR_IMPL(this, hr, msg, ret) \ + if (FAILED(hr)) { \ + qCWarning(QT_BT_WINRT) << msg; \ + this->unregisterFromStatusChanges(); \ + this->setError(QLowEnergyController::ConnectionError); \ + this->setState(QLowEnergyController::UnconnectedState); \ + ret; \ + } + +#define CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, msg, ret) \ + CHECK_FOR_DEVICE_CONNECTION_ERROR_IMPL(this, hr, msg, ret) + +#define CHECK_HR_AND_SET_SERVICE_ERROR(hr, msg, service, error, ret) \ + if (FAILED(hr)) { \ + qCDebug(QT_BT_WINRT) << msg; \ + service->setError(error); \ + ret; \ + } + Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT) +Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT_SERVICE_THREAD) QLowEnergyControllerPrivate *createWinRTLowEnergyController() { @@ -91,31 +126,16 @@ QLowEnergyControllerPrivate *createWinRTLowEnergyController() return new QLowEnergyControllerPrivateWinRT(); } -static QByteArray byteArrayFromBuffer(const ComPtr<IBuffer> &buffer, bool isWCharString = false) -{ - ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteAccess; - HRESULT hr = buffer.As(&byteAccess); - Q_ASSERT_SUCCEEDED(hr); - char *data; - hr = byteAccess->Buffer(reinterpret_cast<byte **>(&data)); - Q_ASSERT_SUCCEEDED(hr); - UINT32 size; - hr = buffer->get_Length(&size); - Q_ASSERT_SUCCEEDED(hr); - if (isWCharString) { - QString valueString = QString::fromUtf16(reinterpret_cast<ushort *>(data)).left(size / 2); - return valueString.toUtf8(); - } - return QByteArray(data, int(size)); -} - static QByteArray byteArrayFromGattResult(const ComPtr<IGattReadResult> &gattResult, bool isWCharString = false) { ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer; HRESULT hr; hr = gattResult->get_Value(&buffer); - Q_ASSERT_SUCCEEDED(hr); + if (FAILED(hr) || !buffer) { + qCWarning(QT_BT_WINRT) << "Could not obtain buffer from GattReadResult"; + return QByteArray(); + } return byteArrayFromBuffer(buffer, isWCharString); } @@ -124,7 +144,7 @@ class QWinRTLowEnergyServiceHandlerNew : public QObject Q_OBJECT public: QWinRTLowEnergyServiceHandlerNew(const QBluetoothUuid &service, - const ComPtr<IGattDeviceService2> &deviceService) + const ComPtr<IGattDeviceService3> &deviceService) : mService(service) , mDeviceService(deviceService) { @@ -138,58 +158,47 @@ public: public slots: void obtainCharList() { - QVector<QBluetoothUuid> indicateChars; - quint16 startHandle = 0; - quint16 endHandle = 0; + mIndicateChars.clear(); qCDebug(QT_BT_WINRT) << __FUNCTION__; - ComPtr<IVectorView<GattCharacteristic *>> characteristics; - HRESULT hr = mDeviceService->GetAllCharacteristics(&characteristics); - Q_ASSERT_SUCCEEDED(hr); - if (!characteristics) { - emit charListObtained(mService, mCharacteristicList, indicateChars, startHandle, endHandle); - QThread::currentThread()->quit(); + ComPtr<IAsyncOperation<GattCharacteristicsResult *>> characteristicsOp; + ComPtr<IGattCharacteristicsResult> characteristicsResult; + HRESULT hr = mDeviceService->GetCharacteristicsAsync(&characteristicsOp); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr, return); + hr = QWinRTFunctions::await(characteristicsOp, characteristicsResult.GetAddressOf(), + QWinRTFunctions::ProcessMainThreadEvents, 5000); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr, return); + GattCommunicationStatus status; + hr = characteristicsResult->get_Status(&status); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr, return); + if (status != GattCommunicationStatus_Success) { + emitErrorAndQuitThread(QLatin1String("Could not obtain char list")); return; } + ComPtr<IVectorView<GattCharacteristic *>> characteristics; + hr = characteristicsResult->get_Characteristics(&characteristics); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr, return); uint characteristicsCount; hr = characteristics->get_Size(&characteristicsCount); + EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr, return); - // If there are no characteristics, we assume that the device is not paired (and not - // discovered by Windows) and we use new API (GetCharacteristicsAsync) to discover them - // without pairing. - if (characteristicsCount == 0) { - ComPtr<IGattDeviceService3> deviceService3; - hr = mDeviceService.As(&deviceService3); - Q_ASSERT_SUCCEEDED(hr); - ComPtr<IAsyncOperation<GattCharacteristicsResult*>> asyncResult; - deviceService3->GetCharacteristicsAsync(&asyncResult); - hr = asyncResult->put_Completed( - Callback<IAsyncOperationCompletedHandler<GattCharacteristicsResult*>>( - [this](IAsyncOperation<GattCharacteristicsResult*> *, AsyncStatus status) { - if (status != AsyncStatus::Completed) { - qCDebug(QT_BT_WINRT) << "Could not obtain characteristics"; - return S_OK; - } - // TODO We should check if we found any characteristics. It makes no sense but - // there is a possibility that device doesn't state any characteristics under a service. - // So, for sanity, we should not continue endless loop here. - obtainCharList(); - return S_OK; - }).Get()); - Q_ASSERT_SUCCEEDED(hr); - return; - } - - Q_ASSERT_SUCCEEDED(hr); mCharacteristicsCountToBeDiscovered = characteristicsCount; for (uint i = 0; i < characteristicsCount; ++i) { ComPtr<IGattCharacteristic> characteristic; hr = characteristics->GetAt(i, &characteristic); - Q_ASSERT_SUCCEEDED(hr); + if (FAILED(hr)) { + qCWarning(QT_BT_WINRT) << "Could not obtain characteristic at" << i; + --mCharacteristicsCountToBeDiscovered; + continue; + } ComPtr<IGattCharacteristic3> characteristic3; hr = characteristic.As(&characteristic3); - Q_ASSERT_SUCCEEDED(hr); + if (FAILED(hr)) { + qCWarning(QT_BT_WINRT) << "Could not cast characteristic"; + --mCharacteristicsCountToBeDiscovered; + continue; + } // For some strange reason, Windows doesn't discover descriptors of characteristics (if not paired). // Qt API assumes that all characteristics and their descriptors are discovered in one go. @@ -197,157 +206,246 @@ public slots: // when GetDescriptorsAsync for all characteristics return. ComPtr<IAsyncOperation<GattDescriptorsResult*>> descAsyncResult; hr = characteristic3->GetDescriptorsAsync(&descAsyncResult); - Q_ASSERT_SUCCEEDED(hr); + if (FAILED(hr)) { + qCWarning(QT_BT_WINRT) << "Could not obtain list of descriptors"; + --mCharacteristicsCountToBeDiscovered; + continue; + } hr = descAsyncResult->put_Completed( - Callback<IAsyncOperationCompletedHandler<GattDescriptorsResult*>>( - [this, characteristic](IAsyncOperation<GattDescriptorsResult*> *, AsyncStatus status) { - if (status != AsyncStatus::Completed) { - qCDebug(QT_BT_WINRT) << "Could not obtain descriptors"; + Callback<IAsyncOperationCompletedHandler<GattDescriptorsResult*>>( + [this, characteristic] + (IAsyncOperation<GattDescriptorsResult *> *op, + AsyncStatus status) { + if (status != AsyncStatus::Completed) { + qCWarning(QT_BT_WINRT) << "Descriptor operation unsuccessful"; + --mCharacteristicsCountToBeDiscovered; + checkAllCharacteristicsDiscovered(); + return S_OK; + } + quint16 handle; + + HRESULT hr = characteristic->get_AttributeHandle(&handle); + if (FAILED(hr)) { + qCWarning(QT_BT_WINRT) << "Could not obtain characteristic's attribute handle"; + --mCharacteristicsCountToBeDiscovered; + checkAllCharacteristicsDiscovered(); + return S_OK; + } + QLowEnergyServicePrivate::CharData charData; + charData.valueHandle = handle + 1; + if (mStartHandle == 0 || mStartHandle > handle) + mStartHandle = handle; + if (mEndHandle == 0 || mEndHandle < handle) + mEndHandle = handle; + GUID guuid; + hr = characteristic->get_Uuid(&guuid); + if (FAILED(hr)) { + qCWarning(QT_BT_WINRT) << "Could not obtain characteristic's Uuid"; + --mCharacteristicsCountToBeDiscovered; + checkAllCharacteristicsDiscovered(); + return S_OK; + } + charData.uuid = QBluetoothUuid(guuid); + GattCharacteristicProperties properties; + hr = characteristic->get_CharacteristicProperties(&properties); + if (FAILED(hr)) { + qCWarning(QT_BT_WINRT) << "Could not obtain characteristic's properties"; + --mCharacteristicsCountToBeDiscovered; + checkAllCharacteristicsDiscovered(); + return S_OK; + } + charData.properties = QLowEnergyCharacteristic::PropertyTypes(properties & 0xff); + if (charData.properties & QLowEnergyCharacteristic::Read) { + ComPtr<IAsyncOperation<GattReadResult *>> readOp; + hr = characteristic->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, + &readOp); + if (FAILED(hr)) { + qCWarning(QT_BT_WINRT) << "Could not read characteristic"; + --mCharacteristicsCountToBeDiscovered; + checkAllCharacteristicsDiscovered(); return S_OK; } - quint16 handle; - - HRESULT hr = characteristic->get_AttributeHandle(&handle); - Q_ASSERT_SUCCEEDED(hr); - QLowEnergyServicePrivate::CharData charData; - charData.valueHandle = handle + 1; - if (mStartHandle == 0 || mStartHandle > handle) - mStartHandle = handle; - if (mEndHandle == 0 || mEndHandle < handle) - mEndHandle = handle; - GUID guuid; - hr = characteristic->get_Uuid(&guuid); - Q_ASSERT_SUCCEEDED(hr); - charData.uuid = QBluetoothUuid(guuid); - GattCharacteristicProperties properties; - hr = characteristic->get_CharacteristicProperties(&properties); - Q_ASSERT_SUCCEEDED(hr); - charData.properties = QLowEnergyCharacteristic::PropertyTypes(properties & 0xff); - if (charData.properties & QLowEnergyCharacteristic::Read) { - ComPtr<IAsyncOperation<GattReadResult *>> readOp; - hr = characteristic->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, - &readOp); - Q_ASSERT_SUCCEEDED(hr); - ComPtr<IGattReadResult> readResult; - hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf()); - Q_ASSERT_SUCCEEDED(hr); - if (readResult) - charData.value = byteArrayFromGattResult(readResult); + ComPtr<IGattReadResult> readResult; + hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf()); + if (FAILED(hr)) { + qCWarning(QT_BT_WINRT) << "Could not obtain characteristic read result"; + --mCharacteristicsCountToBeDiscovered; + checkAllCharacteristicsDiscovered(); + return S_OK; } + if (!readResult) + qCWarning(QT_BT_WINRT) << "Characteristic read result is null"; + else + charData.value = byteArrayFromGattResult(readResult); + } + mCharacteristicList.insert(handle, charData); - QVector<QBluetoothUuid> indicateChars; - ComPtr<IVectorView<GattDescriptor *>> descriptors; - - ComPtr<IGattCharacteristic2> characteristic2; - hr = characteristic.As(&characteristic2); - Q_ASSERT_SUCCEEDED(hr); - - hr = characteristic2->GetAllDescriptors(&descriptors); - Q_ASSERT_SUCCEEDED(hr); - - - uint descriptorCount; - hr = descriptors->get_Size(&descriptorCount); - Q_ASSERT_SUCCEEDED(hr); - for (uint j = 0; j < descriptorCount; ++j) { - QLowEnergyServicePrivate::DescData descData; - ComPtr<IGattDescriptor> descriptor; - hr = descriptors->GetAt(j, &descriptor); - Q_ASSERT_SUCCEEDED(hr); - quint16 descHandle; - hr = descriptor->get_AttributeHandle(&descHandle); - Q_ASSERT_SUCCEEDED(hr); - GUID descriptorUuid; - hr = descriptor->get_Uuid(&descriptorUuid); - Q_ASSERT_SUCCEEDED(hr); - descData.uuid = QBluetoothUuid(descriptorUuid); - if (descData.uuid == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) { - ComPtr<IAsyncOperation<ClientCharConfigDescriptorResult *>> readOp; - hr = characteristic->ReadClientCharacteristicConfigurationDescriptorAsync(&readOp); - Q_ASSERT_SUCCEEDED(hr); - ComPtr<IClientCharConfigDescriptorResult> readResult; - hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf()); - Q_ASSERT_SUCCEEDED(hr); - GattClientCharacteristicConfigurationDescriptorValue value; - hr = readResult->get_ClientCharacteristicConfigurationDescriptor(&value); - Q_ASSERT_SUCCEEDED(hr); - quint16 result = 0; - bool correct = false; - if (value & GattClientCharacteristicConfigurationDescriptorValue_Indicate) { - result |= GattClientCharacteristicConfigurationDescriptorValue_Indicate; - correct = true; - } - if (value & GattClientCharacteristicConfigurationDescriptorValue_Notify) { - result |= GattClientCharacteristicConfigurationDescriptorValue_Notify; - correct = true; - } - if (value == GattClientCharacteristicConfigurationDescriptorValue_None) { - correct = true; - } - if (!correct) - continue; - - descData.value = QByteArray(2, Qt::Uninitialized); - qToLittleEndian(result, descData.value.data()); - indicateChars << charData.uuid; - } else { - ComPtr<IAsyncOperation<GattReadResult *>> readOp; - hr = descriptor->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, - &readOp); - Q_ASSERT_SUCCEEDED(hr); - ComPtr<IGattReadResult> readResult; - hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf()); - Q_ASSERT_SUCCEEDED(hr); - if (descData.uuid == QBluetoothUuid::CharacteristicUserDescription) - descData.value = byteArrayFromGattResult(readResult, true); - else - descData.value = byteArrayFromGattResult(readResult); + ComPtr<IVectorView<GattDescriptor *>> descriptors; + + ComPtr<IGattDescriptorsResult> result; + hr = op->GetResults(&result); + if (FAILED(hr)) { + qCWarning(QT_BT_WINRT) << "Could not obtain descriptor read result"; + --mCharacteristicsCountToBeDiscovered; + checkAllCharacteristicsDiscovered(); + return S_OK; + } + GattCommunicationStatus commStatus; + hr = result->get_Status(&commStatus); + if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) { + qCWarning(QT_BT_WINRT) << "Descriptor operation failed"; + --mCharacteristicsCountToBeDiscovered; + checkAllCharacteristicsDiscovered(); + return S_OK; + } + + hr = result->get_Descriptors(&descriptors); + if (FAILED(hr)) { + qCWarning(QT_BT_WINRT) << "Could not obtain list of descriptors"; + --mCharacteristicsCountToBeDiscovered; + checkAllCharacteristicsDiscovered(); + return S_OK; + } + + uint descriptorCount; + hr = descriptors->get_Size(&descriptorCount); + if (FAILED(hr)) { + qCWarning(QT_BT_WINRT) << "Could not obtain list of descriptors' size"; + --mCharacteristicsCountToBeDiscovered; + checkAllCharacteristicsDiscovered(); + return S_OK; + } + for (uint j = 0; j < descriptorCount; ++j) { + QLowEnergyServicePrivate::DescData descData; + ComPtr<IGattDescriptor> descriptor; + hr = descriptors->GetAt(j, &descriptor); + WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain descriptor") + quint16 descHandle; + hr = descriptor->get_AttributeHandle(&descHandle); + WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain descriptor's attribute handle") + GUID descriptorUuid; + hr = descriptor->get_Uuid(&descriptorUuid); + WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain descriptor's Uuid") + descData.uuid = QBluetoothUuid(descriptorUuid); + charData.descriptorList.insert(descHandle, descData); + if (descData.uuid == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) { + ComPtr<IAsyncOperation<ClientCharConfigDescriptorResult *>> readOp; + hr = characteristic->ReadClientCharacteristicConfigurationDescriptorAsync(&readOp); + WARN_AND_CONTINUE_IF_FAILED(hr, "Could not read descriptor value") + ComPtr<IClientCharConfigDescriptorResult> readResult; + hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf()); + WARN_AND_CONTINUE_IF_FAILED(hr, "Could not await descriptor read result") + GattClientCharacteristicConfigurationDescriptorValue value; + hr = readResult->get_ClientCharacteristicConfigurationDescriptor(&value); + WARN_AND_CONTINUE_IF_FAILED(hr, "Could not get descriptor value from result") + quint16 result = 0; + bool correct = false; + if (value & GattClientCharacteristicConfigurationDescriptorValue_Indicate) { + result |= GattClientCharacteristicConfigurationDescriptorValue_Indicate; + correct = true; } - charData.descriptorList.insert(descHandle, descData); - } + if (value & GattClientCharacteristicConfigurationDescriptorValue_Notify) { + result |= GattClientCharacteristicConfigurationDescriptorValue_Notify; + correct = true; + } + if (value == GattClientCharacteristicConfigurationDescriptorValue_None) { + correct = true; + } + if (!correct) + continue; - mCharacteristicList.insert(handle, charData); - mCharacteristicsCountToBeDiscovered--; - if (mCharacteristicsCountToBeDiscovered == 0) { - emit charListObtained(mService, mCharacteristicList, indicateChars, - mStartHandle, mEndHandle); - QThread::currentThread()->quit(); + descData.value = QByteArray(2, Qt::Uninitialized); + qToLittleEndian(result, descData.value.data()); + mIndicateChars << charData.uuid; + } else { + ComPtr<IAsyncOperation<GattReadResult *>> readOp; + hr = descriptor->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, + &readOp); + WARN_AND_CONTINUE_IF_FAILED(hr, "Could not read descriptor value") + ComPtr<IGattReadResult> readResult; + hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf()); + WARN_AND_CONTINUE_IF_FAILED(hr, "Could await descriptor read result") + if (descData.uuid == QBluetoothUuid::CharacteristicUserDescription) + descData.value = byteArrayFromGattResult(readResult, true); + else + descData.value = byteArrayFromGattResult(readResult); } - return S_OK; - }).Get()); - Q_ASSERT_SUCCEEDED(hr); + charData.descriptorList.insert(descHandle, descData); + } + + mCharacteristicList.insert(handle, charData); + --mCharacteristicsCountToBeDiscovered; + checkAllCharacteristicsDiscovered(); + return S_OK; + }).Get()); + if (FAILED(hr)) { + qCWarning(QT_BT_WINRT) << "Could not register descriptor callback"; + --mCharacteristicsCountToBeDiscovered; + continue; + } } + checkAllCharacteristicsDiscovered(); } +private: + bool checkAllCharacteristicsDiscovered(); + void emitErrorAndQuitThread(HRESULT hr); + void emitErrorAndQuitThread(const QString &error); + public: QBluetoothUuid mService; - ComPtr<IGattDeviceService2> mDeviceService; + ComPtr<IGattDeviceService3> mDeviceService; QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData> mCharacteristicList; uint mCharacteristicsCountToBeDiscovered; quint16 mStartHandle = 0; quint16 mEndHandle = 0; + QVector<QBluetoothUuid> mIndicateChars; signals: void charListObtained(const QBluetoothUuid &service, QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData> charList, QVector<QBluetoothUuid> indicateChars, QLowEnergyHandle startHandle, QLowEnergyHandle endHandle); + void errorOccured(const QString &error); }; +bool QWinRTLowEnergyServiceHandlerNew::checkAllCharacteristicsDiscovered() +{ + if (mCharacteristicsCountToBeDiscovered == 0) { + emit charListObtained(mService, mCharacteristicList, mIndicateChars, + mStartHandle, mEndHandle); + QThread::currentThread()->quit(); + return true; + } + + return false; +} + +void QWinRTLowEnergyServiceHandlerNew::emitErrorAndQuitThread(HRESULT hr) +{ + emitErrorAndQuitThread(qt_error_string(hr)); +} + +void QWinRTLowEnergyServiceHandlerNew::emitErrorAndQuitThread(const QString &error) +{ + emit errorOccured(error); + QThread::currentThread()->quit(); +} + QLowEnergyControllerPrivateWinRTNew::QLowEnergyControllerPrivateWinRTNew() : QLowEnergyControllerPrivate() { registerQLowEnergyControllerMetaType(); + connect(this, &QLowEnergyControllerPrivateWinRTNew::characteristicChanged, + this, &QLowEnergyControllerPrivateWinRTNew::handleCharacteristicChanged, + Qt::QueuedConnection); } QLowEnergyControllerPrivateWinRTNew::~QLowEnergyControllerPrivateWinRTNew() { - if (mDevice && mStatusChangedToken.value) - mDevice->remove_ConnectionStatusChanged(mStatusChangedToken); - - qCDebug(QT_BT_WINRT) << "Unregistering " << mValueChangedTokens.count() << " value change tokens"; - for (const ValueChangedEntry &entry : qAsConst(mValueChangedTokens)) - entry.characteristic->remove_ValueChanged(entry.token); + unregisterFromStatusChanges(); + unregisterFromValueChanges(); + mAbortPending = true; } void QLowEnergyControllerPrivateWinRTNew::init() @@ -357,6 +455,7 @@ void QLowEnergyControllerPrivateWinRTNew::init() void QLowEnergyControllerPrivateWinRTNew::connectToDevice() { qCDebug(QT_BT_WINRT) << __FUNCTION__; + mAbortPending = false; Q_Q(QLowEnergyController); if (remoteDevice.isNull()) { qWarning() << "Invalid/null remote device address"; @@ -370,162 +469,46 @@ void QLowEnergyControllerPrivateWinRTNew::connectToDevice() HRESULT hr = GetActivationFactory( HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothLEDevice).Get(), &deviceStatics); - Q_ASSERT_SUCCEEDED(hr); + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain device factory", return) ComPtr<IAsyncOperation<BluetoothLEDevice *>> deviceFromIdOperation; hr = deviceStatics->FromBluetoothAddressAsync(remoteDevice.toUInt64(), &deviceFromIdOperation); - Q_ASSERT_SUCCEEDED(hr); - hr = QWinRTFunctions::await(deviceFromIdOperation, mDevice.GetAddressOf()); - Q_ASSERT_SUCCEEDED(hr); - - if (!mDevice) { - qCDebug(QT_BT_WINRT) << "Could not find LE device"; + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not find LE device from address", return) + hr = QWinRTFunctions::await(deviceFromIdOperation, mDevice.GetAddressOf(), + QWinRTFunctions::ProcessMainThreadEvents, 5000); + if (FAILED(hr) || !mDevice) { + qCWarning(QT_BT_WINRT) << "Could not find LE device"; setError(QLowEnergyController::InvalidBluetoothAdapterError); setState(QLowEnergyController::UnconnectedState); + return; } BluetoothConnectionStatus status; hr = mDevice->get_ConnectionStatus(&status); - Q_ASSERT_SUCCEEDED(hr); - hr = QEventDispatcherWinRT::runOnXamlThread([this, q]() { - HRESULT hr; - hr = mDevice->add_ConnectionStatusChanged( - Callback<StatusHandler>([this, q](IBluetoothLEDevice *dev, IInspectable *) { - BluetoothConnectionStatus status; - HRESULT hr; - hr = dev->get_ConnectionStatus(&status); - Q_ASSERT_SUCCEEDED(hr); - if (state == QLowEnergyController::ConnectingState - && status == BluetoothConnectionStatus::BluetoothConnectionStatus_Connected) { - setState(QLowEnergyController::ConnectedState); - emit q->connected(); - } else if (state == QLowEnergyController::ConnectedState - && status == BluetoothConnectionStatus::BluetoothConnectionStatus_Disconnected) { - setError(QLowEnergyController::RemoteHostClosedError); - setState(QLowEnergyController::UnconnectedState); - emit q->disconnected(); - } - return S_OK; - }).Get(), &mStatusChangedToken); - Q_ASSERT_SUCCEEDED(hr); - return S_OK; - }); - Q_ASSERT_SUCCEEDED(hr); - + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain device's connection status", return) if (status == BluetoothConnectionStatus::BluetoothConnectionStatus_Connected) { setState(QLowEnergyController::ConnectedState); emit q->connected(); return; } - ComPtr<IVectorView <GattDeviceService *>> deviceServices; - hr = mDevice->get_GattServices(&deviceServices); - Q_ASSERT_SUCCEEDED(hr); - uint serviceCount; - hr = deviceServices->get_Size(&serviceCount); - Q_ASSERT_SUCCEEDED(hr); - - // Windows doesn't provide any explicit connect/reconnect. We need to 'start using' the device - // and windows will initiate connection as a cause of that. - if (serviceCount == 0) { - // If we don't have any services discovered yet (for devices not paired), the simplest - // way to initiate connect is to start discovering services. It's not exactly how Qt API - // expects it to be but IMHO doesn't do any harm either. Services will already be discovered - // when coonnection state changes to 'connected'. - ComPtr<IBluetoothLEDevice3> device3; - hr = mDevice.As(&device3); - Q_ASSERT_SUCCEEDED(hr); - ComPtr<IAsyncOperation<GenericAttributeProfile::GattDeviceServicesResult *>> asyncResult; - hr = device3->GetGattServicesAsync(&asyncResult); - Q_ASSERT_SUCCEEDED(hr); - hr = asyncResult->put_Completed( - Callback<IAsyncOperationCompletedHandler<GenericAttributeProfile::GattDeviceServicesResult *>>( - [this, q](IAsyncOperation<GenericAttributeProfile::GattDeviceServicesResult *> *, AsyncStatus status) { - if (status != AsyncStatus::Completed) { - qCDebug(QT_BT_WINRT) << "Could not obtain services"; - return S_OK; - } - setState(QLowEnergyController::ConnectedState); - emit q->connected(); - return S_OK; - }).Get()); - Q_ASSERT_SUCCEEDED(hr); - } else { - // Windows Phone automatically connects to the device as soon as a service value is read/written. - // Thus we read one value in order to establish the connection. - for (uint i = 0; i < serviceCount; ++i) { - ComPtr<IGattDeviceService> service; - hr = deviceServices->GetAt(i, &service); - Q_ASSERT_SUCCEEDED(hr); - ComPtr<IGattDeviceService2> service2; - hr = service.As(&service2); - Q_ASSERT_SUCCEEDED(hr); - ComPtr<IVectorView<GattCharacteristic *>> characteristics; - hr = service2->GetAllCharacteristics(&characteristics); - if (hr == E_ACCESSDENIED) { - // Everything will work as expected up until this point if the manifest capabilties - // for bluetooth LE are not set. - qCWarning(QT_BT_WINRT) << "Could not obtain characteristic list. Please check your " - "manifest capabilities"; - setState(QLowEnergyController::UnconnectedState); - setError(QLowEnergyController::ConnectionError); - return; - } else if (FAILED(hr)) { - qCWarning(QT_BT_WINRT) << "Connecting to device failed: " - << qt_error_string(hr); - setError(QLowEnergyController::ConnectionError); - setState(QLowEnergyController::UnconnectedState); - return; - } - uint characteristicsCount; - hr = characteristics->get_Size(&characteristicsCount); - Q_ASSERT_SUCCEEDED(hr); - for (uint j = 0; j < characteristicsCount; ++j) { - ComPtr<IGattCharacteristic> characteristic; - hr = characteristics->GetAt(j, &characteristic); - Q_ASSERT_SUCCEEDED(hr); - ComPtr<IAsyncOperation<GattReadResult *>> op; - GattCharacteristicProperties props; - hr = characteristic->get_CharacteristicProperties(&props); - Q_ASSERT_SUCCEEDED(hr); - if (!(props & GattCharacteristicProperties_Read)) - continue; - hr = characteristic->ReadValueWithCacheModeAsync(BluetoothCacheMode::BluetoothCacheMode_Uncached, &op); - Q_ASSERT_SUCCEEDED(hr); - ComPtr<IGattReadResult> result; - hr = QWinRTFunctions::await(op, result.GetAddressOf()); - if (hr == E_INVALIDARG) { - // E_INVALIDARG happens when user tries to connect to a device that was paired - // before but is not available. - qCDebug(QT_BT_WINRT) << "Could not obtain characteristic read result that triggers" - "device connection. Is the device reachable?"; - setError(QLowEnergyController::ConnectionError); - setState(QLowEnergyController::UnconnectedState); - return; - } - Q_ASSERT_SUCCEEDED(hr); - ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer; - hr = result->get_Value(&buffer); - Q_ASSERT_SUCCEEDED(hr); - if (!buffer) { - qCDebug(QT_BT_WINRT) << "Problem reading value"; - setError(QLowEnergyController::ConnectionError); - setState(QLowEnergyController::UnconnectedState); - } - return; - } - } - } + QBluetoothLocalDevice localDevice; + QBluetoothLocalDevice::Pairing pairing = localDevice.pairingStatus(remoteDevice); + if (pairing == QBluetoothLocalDevice::Unpaired) + connectToUnpairedDevice(); + else + connectToPairedDevice(); } void QLowEnergyControllerPrivateWinRTNew::disconnectFromDevice() { qCDebug(QT_BT_WINRT) << __FUNCTION__; Q_Q(QLowEnergyController); + setState(QLowEnergyController::ClosingState); + unregisterFromValueChanges(); + unregisterFromStatusChanges(); + mAbortPending = true; + mDevice = nullptr; setState(QLowEnergyController::UnconnectedState); emit q->disconnected(); - if (mDevice && mStatusChangedToken.value) { - mDevice->remove_ConnectionStatusChanged(mStatusChangedToken); - mStatusChangedToken.value = 0; - } } ComPtr<IGattDeviceService> QLowEnergyControllerPrivateWinRTNew::getNativeService( @@ -546,9 +529,30 @@ ComPtr<IGattCharacteristic> QLowEnergyControllerPrivateWinRTNew::getNativeCharac if (!service) return nullptr; - ComPtr<IVectorView<GattCharacteristic *>> characteristics; - HRESULT hr = service->GetCharacteristics(charUuid, &characteristics); + ComPtr<IGattDeviceService3> service3; + HRESULT hr = service.As(&service3); + RETURN_IF_FAILED("Could not cast service", return nullptr); + + ComPtr<IAsyncOperation<GattCharacteristicsResult *>> op; + ComPtr<IGattCharacteristicsResult> result; + hr = service3->GetCharacteristicsForUuidAsync(charUuid, &op); RETURN_IF_FAILED("Could not obtain native characteristics for service", return nullptr); + hr = QWinRTFunctions::await(op, result.GetAddressOf(), QWinRTFunctions::ProcessMainThreadEvents, 5000); + RETURN_IF_FAILED("Could not await completion of characteristic operation", return nullptr); + GattCommunicationStatus status; + hr = result->get_Status(&status); + if (FAILED(hr) || status != GattCommunicationStatus_Success) { + qErrnoWarning(hr, "Native characteristic operation failed."); + return nullptr; + } + ComPtr<IVectorView<GattCharacteristic *>> characteristics; + hr = result->get_Characteristics(&characteristics); + RETURN_IF_FAILED("Could not obtain characteristic list.", return nullptr); + uint size; + hr = characteristics->get_Size(&size); + RETURN_IF_FAILED("Could not obtain characteristic list's size.", return nullptr); + if (size != 1) + qErrnoWarning("More than 1 characteristic found."); ComPtr<IGattCharacteristic> characteristic; hr = characteristics->GetAt(0, &characteristic); RETURN_IF_FAILED("Could not obtain first characteristic for service", return nullptr); @@ -564,59 +568,152 @@ void QLowEnergyControllerPrivateWinRTNew::registerForValueChanges(const QBluetoo GUID guuid; HRESULT hr; hr = entry.characteristic->get_Uuid(&guuid); - Q_ASSERT_SUCCEEDED(hr); + WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain characteristic's Uuid") if (QBluetoothUuid(guuid) == charUuid) return; } ComPtr<IGattCharacteristic> characteristic = getNativeCharacteristic(serviceUuid, charUuid); + if (!characteristic) { + qCDebug(QT_BT_WINRT).nospace() << "Could not obtain native characteristic " << charUuid + << " from service " << serviceUuid << ". Qt will not be able to signal" + << " changes for this characteristic."; + return; + } EventRegistrationToken token; HRESULT hr; hr = characteristic->add_ValueChanged( - Callback<ValueChangedHandler>( - [this](IGattCharacteristic *characteristic, IGattValueChangedEventArgs *args) { - HRESULT hr; - quint16 handle; - hr = characteristic->get_AttributeHandle(&handle); - Q_ASSERT_SUCCEEDED(hr); - ComPtr<IBuffer> buffer; - hr = args->get_CharacteristicValue(&buffer); - Q_ASSERT_SUCCEEDED(hr); - characteristicChanged(handle, byteArrayFromBuffer(buffer)); - return S_OK; - }).Get(), &token); - Q_ASSERT_SUCCEEDED(hr); + Callback<ValueChangedHandler>(this, &QLowEnergyControllerPrivateWinRTNew::onValueChange).Get(), + &token); + RETURN_IF_FAILED("Could not register characteristic for value changes", return) mValueChangedTokens.append(ValueChangedEntry(characteristic, token)); qCDebug(QT_BT_WINRT) << "Characteristic" << charUuid << "in service" << serviceUuid << "registered for value changes"; } +void QLowEnergyControllerPrivateWinRTNew::unregisterFromValueChanges() +{ + qCDebug(QT_BT_WINRT) << "Unregistering " << mValueChangedTokens.count() << " value change tokens"; + HRESULT hr; + for (const ValueChangedEntry &entry : qAsConst(mValueChangedTokens)) { + if (!entry.characteristic) { + qCWarning(QT_BT_WINRT) << "Unregistering from value changes for characteristic failed." + << "Characteristic has been deleted"; + continue; + } + hr = entry.characteristic->remove_ValueChanged(entry.token); + if (FAILED(hr)) + qCWarning(QT_BT_WINRT) << "Unregistering from value changes for characteristic failed."; + } + mValueChangedTokens.clear(); +} + +HRESULT QLowEnergyControllerPrivateWinRTNew::onValueChange(IGattCharacteristic *characteristic, IGattValueChangedEventArgs *args) +{ + HRESULT hr; + quint16 handle; + hr = characteristic->get_AttributeHandle(&handle); + RETURN_IF_FAILED("Could not obtain characteristic's handle", return S_OK) + ComPtr<IBuffer> buffer; + hr = args->get_CharacteristicValue(&buffer); + RETURN_IF_FAILED("Could not obtain characteristic's value", return S_OK) + emit characteristicChanged(handle, byteArrayFromBuffer(buffer)); + return S_OK; +} + +bool QLowEnergyControllerPrivateWinRTNew::registerForStatusChanges() +{ + if (!mDevice) + return false; + + qCDebug(QT_BT_WINRT) << __FUNCTION__; + + HRESULT hr; + hr = QEventDispatcherWinRT::runOnXamlThread([this]() { + HRESULT hr; + hr = mDevice->add_ConnectionStatusChanged( + Callback<StatusHandler>(this, &QLowEnergyControllerPrivateWinRTNew::onStatusChange).Get(), + &mStatusChangedToken); + RETURN_IF_FAILED("Could not register connection status callback", return hr) + return S_OK; + }); + RETURN_FALSE_IF_FAILED("Could not add status callback on Xaml thread") + return true; +} + +void QLowEnergyControllerPrivateWinRTNew::unregisterFromStatusChanges() +{ + qCDebug(QT_BT_WINRT) << __FUNCTION__; + if (mDevice && mStatusChangedToken.value) { + mDevice->remove_ConnectionStatusChanged(mStatusChangedToken); + mStatusChangedToken.value = 0; + } +} + +HRESULT QLowEnergyControllerPrivateWinRTNew::onStatusChange(IBluetoothLEDevice *dev, IInspectable *) +{ + Q_Q(QLowEnergyController); + BluetoothConnectionStatus status; + HRESULT hr; + hr = dev->get_ConnectionStatus(&status); + RETURN_IF_FAILED("Could not obtain connection status", return S_OK) + if (state == QLowEnergyController::ConnectingState + && status == BluetoothConnectionStatus::BluetoothConnectionStatus_Connected) { + setState(QLowEnergyController::ConnectedState); + emit q->connected(); + } else if (state != QLowEnergyController::UnconnectedState + && status == BluetoothConnectionStatus::BluetoothConnectionStatus_Disconnected) { + invalidateServices(); + unregisterFromValueChanges(); + unregisterFromStatusChanges(); + mDevice = nullptr; + setError(QLowEnergyController::RemoteHostClosedError); + setState(QLowEnergyController::UnconnectedState); + emit q->disconnected(); + } + return S_OK; +} + void QLowEnergyControllerPrivateWinRTNew::obtainIncludedServices( QSharedPointer<QLowEnergyServicePrivate> servicePointer, ComPtr<IGattDeviceService> service) { Q_Q(QLowEnergyController); - ComPtr<IGattDeviceService2> service2; - HRESULT hr = service.As(&service2); - Q_ASSERT_SUCCEEDED(hr); - ComPtr<IVectorView<GattDeviceService *>> includedServices; - hr = service2->GetAllIncludedServices(&includedServices); + ComPtr<IGattDeviceService3> service3; + HRESULT hr = service.As(&service3); + RETURN_IF_FAILED("Could not cast service", return); + ComPtr<IAsyncOperation<GattDeviceServicesResult *>> op; + hr = service3->GetIncludedServicesAsync(&op); // Some devices return ERROR_ACCESS_DISABLED_BY_POLICY - if (FAILED(hr)) + RETURN_IF_FAILED("Could not obtain included services", return); + ComPtr<IGattDeviceServicesResult> result; + hr = QWinRTFunctions::await(op, result.GetAddressOf(), QWinRTFunctions::ProcessMainThreadEvents, 5000); + RETURN_IF_FAILED("Could not await service operation", return); + GattCommunicationStatus status; + hr = result->get_Status(&status); + if (FAILED(hr) || status != GattCommunicationStatus_Success) { + qErrnoWarning("Could not obtain list of included services"); return; + } + ComPtr<IVectorView<GattDeviceService *>> includedServices; + hr = result->get_Services(&includedServices); + RETURN_IF_FAILED("Could not obtain service list", return); uint count; hr = includedServices->get_Size(&count); - Q_ASSERT_SUCCEEDED(hr); + RETURN_IF_FAILED("Could not obtain service list's size", return); for (uint i = 0; i < count; ++i) { ComPtr<IGattDeviceService> includedService; hr = includedServices->GetAt(i, &includedService); - Q_ASSERT_SUCCEEDED(hr); + WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain service from list"); GUID guuid; hr = includedService->get_Uuid(&guuid); - Q_ASSERT_SUCCEEDED(hr); + WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain included service's Uuid"); const QBluetoothUuid includedUuid(guuid); QSharedPointer<QLowEnergyServicePrivate> includedPointer; + qCDebug(QT_BT_WINRT_SERVICE_THREAD) << __FUNCTION__ + << "Changing service pointer from thread" + << QThread::currentThread(); if (serviceList.contains(includedUuid)) { includedPointer = serviceList.value(includedUuid); } else { @@ -636,68 +733,89 @@ void QLowEnergyControllerPrivateWinRTNew::obtainIncludedServices( } } -void QLowEnergyControllerPrivateWinRTNew::discoverServices() +HRESULT QLowEnergyControllerPrivateWinRTNew::onServiceDiscoveryFinished(ABI::Windows::Foundation::IAsyncOperation<GattDeviceServicesResult *> *op, AsyncStatus status) { Q_Q(QLowEnergyController); + if (status != AsyncStatus::Completed) { + qCDebug(QT_BT_WINRT) << "Could not obtain services"; + return S_OK; + } + ComPtr<IGattDeviceServicesResult> result; + ComPtr<IVectorView<GattDeviceService *>> deviceServices; + HRESULT hr = op->GetResults(&result); + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain service discovery result", + return S_OK); + GattCommunicationStatus commStatus; + hr = result->get_Status(&commStatus); + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain service discovery status", + return S_OK); + if (commStatus != GattCommunicationStatus_Success) + return S_OK; + + hr = result->get_Services(&deviceServices); + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain service list", + return S_OK); + + uint serviceCount; + hr = deviceServices->get_Size(&serviceCount); + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain service list size", + return S_OK); + for (uint i = 0; i < serviceCount; ++i) { + ComPtr<IGattDeviceService> deviceService; + hr = deviceServices->GetAt(i, &deviceService); + WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain service"); + GUID guuid; + hr = deviceService->get_Uuid(&guuid); + WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain service's Uuid"); + const QBluetoothUuid service(guuid); + + qCDebug(QT_BT_WINRT_SERVICE_THREAD) << __FUNCTION__ + << "Changing service pointer from thread" + << QThread::currentThread(); + QSharedPointer<QLowEnergyServicePrivate> pointer; + if (serviceList.contains(service)) { + pointer = serviceList.value(service); + } else { + QLowEnergyServicePrivate *priv = new QLowEnergyServicePrivate(); + priv->uuid = service; + priv->setController(this); + + pointer = QSharedPointer<QLowEnergyServicePrivate>(priv); + serviceList.insert(service, pointer); + } + pointer->type |= QLowEnergyService::PrimaryService; + obtainIncludedServices(pointer, deviceService); + + emit q->serviceDiscovered(service); + } + + setState(QLowEnergyController::DiscoveredState); + emit q->discoveryFinished(); + + return S_OK; +} + +void QLowEnergyControllerPrivateWinRTNew::discoverServices() +{ qCDebug(QT_BT_WINRT) << "Service discovery initiated"; ComPtr<IBluetoothLEDevice3> device3; HRESULT hr = mDevice.As(&device3); - Q_ASSERT_SUCCEEDED(hr); + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not cast device", return); ComPtr<IAsyncOperation<GenericAttributeProfile::GattDeviceServicesResult *>> asyncResult; hr = device3->GetGattServicesAsync(&asyncResult); - Q_ASSERT_SUCCEEDED(hr); - hr = QEventDispatcherWinRT::runOnXamlThread( [asyncResult, q, this] () { + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain services", return); + hr = QEventDispatcherWinRT::runOnXamlThread( [asyncResult, this] () { HRESULT hr = asyncResult->put_Completed( Callback<IAsyncOperationCompletedHandler<GenericAttributeProfile::GattDeviceServicesResult *>>( - [this, q](IAsyncOperation<GenericAttributeProfile::GattDeviceServicesResult *> *, AsyncStatus status) { - if (status != AsyncStatus::Completed) { - qCDebug(QT_BT_WINRT) << "Could not obtain services"; - return S_OK; - } - ComPtr<IVectorView<GattDeviceService *>> deviceServices; - HRESULT hr = mDevice->get_GattServices(&deviceServices); - Q_ASSERT_SUCCEEDED(hr); - uint serviceCount; - hr = deviceServices->get_Size(&serviceCount); - Q_ASSERT_SUCCEEDED(hr); - for (uint i = 0; i < serviceCount; ++i) { - ComPtr<IGattDeviceService> deviceService; - hr = deviceServices->GetAt(i, &deviceService); - Q_ASSERT_SUCCEEDED(hr); - GUID guuid; - hr = deviceService->get_Uuid(&guuid); - Q_ASSERT_SUCCEEDED(hr); - const QBluetoothUuid service(guuid); - - QSharedPointer<QLowEnergyServicePrivate> pointer; - if (serviceList.contains(service)) { - pointer = serviceList.value(service); - } else { - QLowEnergyServicePrivate *priv = new QLowEnergyServicePrivate(); - priv->uuid = service; - priv->setController(this); - - pointer = QSharedPointer<QLowEnergyServicePrivate>(priv); - serviceList.insert(service, pointer); - } - pointer->type |= QLowEnergyService::PrimaryService; - - obtainIncludedServices(pointer, deviceService); - - emit q->serviceDiscovered(service); - } - - setState(QLowEnergyController::DiscoveredState); - emit q->discoveryFinished(); - - return S_OK; - }).Get()); - Q_ASSERT_SUCCEEDED(hr); + this, &QLowEnergyControllerPrivateWinRTNew::onServiceDiscoveryFinished).Get()); + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not register service discovery callback", + return S_OK) return hr; }); - Q_ASSERT_SUCCEEDED(hr); + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not run registration in Xaml thread", + return) } void QLowEnergyControllerPrivateWinRTNew::discoverServiceDetails(const QBluetoothUuid &service) @@ -717,34 +835,48 @@ void QLowEnergyControllerPrivateWinRTNew::discoverServiceDetails(const QBluetoot //update service data QSharedPointer<QLowEnergyServicePrivate> pointer = serviceList.value(service); - + qCDebug(QT_BT_WINRT_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread" + << QThread::currentThread(); pointer->setState(QLowEnergyService::DiscoveringServices); - ComPtr<IGattDeviceService2> deviceService2; - HRESULT hr = deviceService.As(&deviceService2); - Q_ASSERT_SUCCEEDED(hr); - ComPtr<IVectorView<GattDeviceService *>> deviceServices; - hr = deviceService2->GetAllIncludedServices(&deviceServices); - if (FAILED(hr)) { // ERROR_ACCESS_DISABLED_BY_POLICY - qCDebug(QT_BT_WINRT) << "Could not obtain included services list for" << service; + ComPtr<IGattDeviceService3> deviceService3; + HRESULT hr = deviceService.As(&deviceService3); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not cast service", + pointer, QLowEnergyService::UnknownError, return) + ComPtr<IAsyncOperation<GattDeviceServicesResult *>> op; + hr = deviceService3->GetIncludedServicesAsync(&op); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain included service list", + pointer, QLowEnergyService::UnknownError, return) + ComPtr<IGattDeviceServicesResult> result; + hr = QWinRTFunctions::await(op, result.GetAddressOf()); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not await service operation", + pointer, QLowEnergyService::UnknownError, return) + GattCommunicationStatus status; + hr = result->get_Status(&status); + if (FAILED(hr) || status != GattCommunicationStatus_Success) { + qCDebug(QT_BT_WINRT) << "Obtaining list of included services failed"; pointer->setError(QLowEnergyService::UnknownError); - pointer->setState(QLowEnergyService::InvalidService); return; } + ComPtr<IVectorView<GattDeviceService *>> deviceServices; + hr = result->get_Services(&deviceServices); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain service list from result", + pointer, QLowEnergyService::UnknownError, return) uint serviceCount; hr = deviceServices->get_Size(&serviceCount); - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain included service list's size", + pointer, QLowEnergyService::UnknownError, return) for (uint i = 0; i < serviceCount; ++i) { ComPtr<IGattDeviceService> includedService; hr = deviceServices->GetAt(i, &includedService); - Q_ASSERT_SUCCEEDED(hr); + WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain service from list") GUID guuid; hr = includedService->get_Uuid(&guuid); - Q_ASSERT_SUCCEEDED(hr); + WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain service Uuid") const QBluetoothUuid service(guuid); if (service.isNull()) { qCDebug(QT_BT_WINRT) << "Could not find service"; - return; + continue; } pointer->includedServices.append(service); @@ -756,12 +888,14 @@ void QLowEnergyControllerPrivateWinRTNew::discoverServiceDetails(const QBluetoot } QWinRTLowEnergyServiceHandlerNew *worker - = new QWinRTLowEnergyServiceHandlerNew(service, deviceService2); + = new QWinRTLowEnergyServiceHandlerNew(service, deviceService3); QThread *thread = new QThread; worker->moveToThread(thread); connect(thread, &QThread::started, worker, &QWinRTLowEnergyServiceHandlerNew::obtainCharList); connect(thread, &QThread::finished, thread, &QObject::deleteLater); connect(thread, &QThread::finished, worker, &QObject::deleteLater); + connect(worker, &QWinRTLowEnergyServiceHandlerNew::errorOccured, + this, &QLowEnergyControllerPrivateWinRTNew::handleServiceHandlerError); connect(worker, &QWinRTLowEnergyServiceHandlerNew::charListObtained, [this, thread](const QBluetoothUuid &service, QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData> charList, QVector<QBluetoothUuid> indicateChars, @@ -783,7 +917,8 @@ void QLowEnergyControllerPrivateWinRTNew::discoverServiceDetails(const QBluetoot registerForValueChanges(service, indicateChar); return S_OK; }); - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not register for value changes in Xaml thread", + pointer, QLowEnergyService::UnknownError, return) pointer->setState(QLowEnergyService::ServiceDiscovered); thread->exit(0); @@ -815,6 +950,8 @@ void QLowEnergyControllerPrivateWinRTNew::readCharacteristic( const QLowEnergyHandle charHandle) { qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle; + qCDebug(QT_BT_WINRT_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread" + << QThread::currentThread(); Q_ASSERT(!service.isNull()); if (role == QLowEnergyController::PeripheralRole) { service->setError(QLowEnergyService::CharacteristicReadError); @@ -843,7 +980,8 @@ void QLowEnergyControllerPrivateWinRTNew::readCharacteristic( } ComPtr<IAsyncOperation<GattReadResult*>> readOp; HRESULT hr = characteristic->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, &readOp); - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not read characteristic", + service, QLowEnergyService::CharacteristicReadError, return S_OK) auto readCompletedLambda = [charData, charHandle, service] (IAsyncOperation<GattReadResult*> *op, AsyncStatus status) { @@ -855,11 +993,8 @@ void QLowEnergyControllerPrivateWinRTNew::readCharacteristic( ComPtr<IGattReadResult> characteristicValue; HRESULT hr; hr = op->GetResults(&characteristicValue); - if (FAILED(hr)) { - qCDebug(QT_BT_WINRT) << "Could not obtain result for characteristic" << charHandle; - service->setError(QLowEnergyService::CharacteristicReadError); - return S_OK; - } + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain result for characteristic", + service, QLowEnergyService::CharacteristicReadError, return S_OK) const QByteArray value = byteArrayFromGattResult(characteristicValue); QLowEnergyServicePrivate::CharData charData = service->characteristicList.value(charHandle); @@ -870,10 +1005,12 @@ void QLowEnergyControllerPrivateWinRTNew::readCharacteristic( }; hr = readOp->put_Completed(Callback<IAsyncOperationCompletedHandler<GattReadResult *>>( readCompletedLambda).Get()); - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not register characteristic read callback", + service, QLowEnergyService::CharacteristicReadError, return S_OK) return S_OK; }); - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not run registration on Xaml thread", + service, QLowEnergyService::CharacteristicReadError, return) } void QLowEnergyControllerPrivateWinRTNew::readDescriptor( @@ -882,6 +1019,8 @@ void QLowEnergyControllerPrivateWinRTNew::readDescriptor( const QLowEnergyHandle descHandle) { qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle << descHandle; + qCDebug(QT_BT_WINRT_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread" + << QThread::currentThread(); Q_ASSERT(!service.isNull()); if (role == QLowEnergyController::PeripheralRole) { service->setError(QLowEnergyService::DescriptorReadError); @@ -898,7 +1037,7 @@ void QLowEnergyControllerPrivateWinRTNew::readDescriptor( HRESULT hr; hr = QEventDispatcherWinRT::runOnXamlThread([charHandle, descHandle, service, this]() { - QLowEnergyServicePrivate::CharData charData = service->characteristicList.value(charHandle); + const QLowEnergyServicePrivate::CharData charData = service->characteristicList.value(charHandle); ComPtr<IGattCharacteristic> characteristic = getNativeCharacteristic(service->uuid, charData.uuid); if (!characteristic) { qCDebug(QT_BT_WINRT) << "Could not obtain native characteristic" << charData.uuid @@ -910,12 +1049,14 @@ void QLowEnergyControllerPrivateWinRTNew::readDescriptor( // Get native descriptor if (!charData.descriptorList.contains(descHandle)) qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "cannot be found in characteristic" << charHandle; - QLowEnergyServicePrivate::DescData descData = charData.descriptorList.value(descHandle); - if (descData.uuid == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) { + const QLowEnergyServicePrivate::DescData descData = charData.descriptorList.value(descHandle); + const QBluetoothUuid descUuid = descData.uuid; + if (descUuid == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) { ComPtr<IAsyncOperation<ClientCharConfigDescriptorResult *>> readOp; HRESULT hr = characteristic->ReadClientCharacteristicConfigurationDescriptorAsync(&readOp); - Q_ASSERT_SUCCEEDED(hr); - auto readCompletedLambda = [&charData, charHandle, &descData, descHandle, service] + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not read client characteristic configuration", + service, QLowEnergyService::DescriptorReadError, return S_OK) + auto readCompletedLambda = [charHandle, descHandle, service] (IAsyncOperation<ClientCharConfigDescriptorResult *> *op, AsyncStatus status) { if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) { @@ -926,18 +1067,12 @@ void QLowEnergyControllerPrivateWinRTNew::readDescriptor( ComPtr<IClientCharConfigDescriptorResult> iValue; HRESULT hr; hr = op->GetResults(&iValue); - if (FAILED(hr)) { - qCDebug(QT_BT_WINRT) << "Could not obtain result for descriptor" << descHandle; - service->setError(QLowEnergyService::DescriptorReadError); - return S_OK; - } + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain result for descriptor", + service, QLowEnergyService::DescriptorReadError, return S_OK) GattClientCharacteristicConfigurationDescriptorValue value; hr = iValue->get_ClientCharacteristicConfigurationDescriptor(&value); - if (FAILED(hr)) { - qCDebug(QT_BT_WINRT) << "Could not obtain value for descriptor" << descHandle; - service->setError(QLowEnergyService::DescriptorReadError); - return S_OK; - } + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain value for descriptor", + service, QLowEnergyService::DescriptorReadError, return S_OK) quint16 result = 0; bool correct = false; if (value & GattClientCharacteristicConfigurationDescriptorValue_Indicate) { @@ -956,9 +1091,11 @@ void QLowEnergyControllerPrivateWinRTNew::readDescriptor( service->setError(QLowEnergyService::DescriptorReadError); return S_OK; } + QLowEnergyServicePrivate::DescData descData; + descData.uuid = QBluetoothUuid::ClientCharacteristicConfiguration; descData.value = QByteArray(2, Qt::Uninitialized); qToLittleEndian(result, descData.value.data()); - charData.descriptorList.insert(descHandle, descData); + service->characteristicList[charHandle].descriptorList[descHandle] = descData; emit service->descriptorRead(QLowEnergyDescriptor(service, charHandle, descHandle), descData.value); return S_OK; @@ -966,19 +1103,56 @@ void QLowEnergyControllerPrivateWinRTNew::readDescriptor( hr = readOp->put_Completed( Callback<IAsyncOperationCompletedHandler<ClientCharConfigDescriptorResult *>>( readCompletedLambda).Get()); - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not register descriptor read callback", + service, QLowEnergyService::DescriptorReadError, return S_OK) return S_OK; } else { + ComPtr<IGattCharacteristic3> characteristic3; + HRESULT hr = characteristic.As(&characteristic3); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not cast characteristic", + service, QLowEnergyService::DescriptorReadError, return S_OK) + ComPtr<IAsyncOperation<GattDescriptorsResult *>> op; + hr = characteristic3->GetDescriptorsForUuidAsync(descData.uuid, &op); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain descriptor for uuid", + service, QLowEnergyService::DescriptorReadError, return S_OK) + ComPtr<IGattDescriptorsResult> result; + hr = QWinRTFunctions::await(op, result.GetAddressOf(), QWinRTFunctions::ProcessMainThreadEvents, 5000); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not await descritpor read result", + service, QLowEnergyService::DescriptorReadError, return S_OK) + + GattCommunicationStatus commStatus; + hr = result->get_Status(&commStatus); + if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) { + qErrnoWarning("Could not obtain list of descriptors"); + service->setError(QLowEnergyService::DescriptorReadError); + return S_OK; + } + ComPtr<IVectorView<GattDescriptor *>> descriptors; - HRESULT hr = characteristic->GetDescriptors(descData.uuid, &descriptors); - Q_ASSERT_SUCCEEDED(hr); + hr = result->get_Descriptors(&descriptors); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain descriptor list", + service, QLowEnergyService::DescriptorReadError, return S_OK) + uint size; + hr = descriptors->get_Size(&size); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not await descritpor list's size", + service, QLowEnergyService::DescriptorReadError, return S_OK) + if (size == 0) { + qCWarning(QT_BT_WINRT) << "No descriptor with uuid" << descData.uuid << "was found."; + service->setError(QLowEnergyService::DescriptorReadError); + return S_OK; + } else if (size > 1) { + qCWarning(QT_BT_WINRT) << "There is more than 1 descriptor with uuid" << descData.uuid; + } + ComPtr<IGattDescriptor> descriptor; hr = descriptors->GetAt(0, &descriptor); - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain descritpor from list", + service, QLowEnergyService::DescriptorReadError, return S_OK) ComPtr<IAsyncOperation<GattReadResult*>> readOp; hr = descriptor->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, &readOp); - Q_ASSERT_SUCCEEDED(hr); - auto readCompletedLambda = [&charData, charHandle, &descData, descHandle, service] + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not read descriptor value", + service, QLowEnergyService::DescriptorReadError, return S_OK) + auto readCompletedLambda = [charHandle, descHandle, descUuid, service] (IAsyncOperation<GattReadResult*> *op, AsyncStatus status) { if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) { @@ -994,22 +1168,26 @@ void QLowEnergyControllerPrivateWinRTNew::readDescriptor( service->setError(QLowEnergyService::DescriptorReadError); return S_OK; } + QLowEnergyServicePrivate::DescData descData; + descData.uuid = descUuid; if (descData.uuid == QBluetoothUuid::CharacteristicUserDescription) descData.value = byteArrayFromGattResult(descriptorValue, true); else descData.value = byteArrayFromGattResult(descriptorValue); - charData.descriptorList.insert(descHandle, descData); + service->characteristicList[charHandle].descriptorList[descHandle] = descData; emit service->descriptorRead(QLowEnergyDescriptor(service, charHandle, descHandle), descData.value); return S_OK; }; hr = readOp->put_Completed(Callback<IAsyncOperationCompletedHandler<GattReadResult *>>( readCompletedLambda).Get()); - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not register descriptor read callback", + service, QLowEnergyService::DescriptorReadError, return S_OK) return S_OK; } }); - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not run registration on Xaml thread", + service, QLowEnergyService::DescriptorReadError, return) } void QLowEnergyControllerPrivateWinRTNew::writeCharacteristic( @@ -1019,6 +1197,8 @@ void QLowEnergyControllerPrivateWinRTNew::writeCharacteristic( QLowEnergyService::WriteMode mode) { qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle << newValue << mode; + qCDebug(QT_BT_WINRT_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread" + << QThread::currentThread(); Q_ASSERT(!service.isNull()); if (role == QLowEnergyController::PeripheralRole) { service->setError(QLowEnergyService::CharacteristicWriteError); @@ -1053,26 +1233,33 @@ void QLowEnergyControllerPrivateWinRTNew::writeCharacteristic( HRESULT hr = GetActivationFactory( HStringReference(RuntimeClass_Windows_Storage_Streams_Buffer).Get(), &bufferFactory); - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain buffer factory", + service, QLowEnergyService::CharacteristicWriteError, return S_OK) ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer; const quint32 length = quint32(newValue.length()); hr = bufferFactory->Create(length, &buffer); - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not create buffer", + service, QLowEnergyService::CharacteristicWriteError, return S_OK) hr = buffer->put_Length(length); - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not set buffer length", + service, QLowEnergyService::CharacteristicWriteError, return S_OK) ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteAccess; hr = buffer.As(&byteAccess); - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not cast buffer", + service, QLowEnergyService::CharacteristicWriteError, return S_OK) byte *bytes; hr = byteAccess->Buffer(&bytes); - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not set buffer", + service, QLowEnergyService::CharacteristicWriteError, return S_OK) memcpy(bytes, newValue, length); ComPtr<IAsyncOperation<GattCommunicationStatus>> writeOp; GattWriteOption option = writeWithResponse ? GattWriteOption_WriteWithResponse : GattWriteOption_WriteWithoutResponse; hr = characteristic->WriteValueWithOptionAsync(buffer.Get(), option, &writeOp); - Q_ASSERT_SUCCEEDED(hr); - auto writeCompletedLambda =[charData, charHandle, newValue, service, writeWithResponse, this] + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could write characteristic", + service, QLowEnergyService::CharacteristicWriteError, return S_OK) + QPointer<QLowEnergyControllerPrivateWinRTNew> thisPtr(this); + auto writeCompletedLambda = [charData, charHandle, newValue, service, writeWithResponse, thisPtr] (IAsyncOperation<GattCommunicationStatus> *op, AsyncStatus status) { if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) { @@ -1089,7 +1276,8 @@ void QLowEnergyControllerPrivateWinRTNew::writeCharacteristic( service->setError(QLowEnergyService::CharacteristicWriteError); return S_OK; } - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain characteristic write result", + service, QLowEnergyService::CharacteristicWriteError, return S_OK) if (result != GattCommunicationStatus_Success) { qCDebug(QT_BT_WINRT) << "Characteristic" << charHandle << "write operation failed"; service->setError(QLowEnergyService::CharacteristicWriteError); @@ -1098,7 +1286,7 @@ void QLowEnergyControllerPrivateWinRTNew::writeCharacteristic( // only update cache when property is readable. Otherwise it remains // empty. if (charData.properties & QLowEnergyCharacteristic::Read) - updateValueOfCharacteristic(charHandle, newValue, false); + thisPtr->updateValueOfCharacteristic(charHandle, newValue, false); if (writeWithResponse) emit service->characteristicWritten(QLowEnergyCharacteristic(service, charHandle), newValue); @@ -1107,10 +1295,12 @@ void QLowEnergyControllerPrivateWinRTNew::writeCharacteristic( hr = writeOp->put_Completed( Callback<IAsyncOperationCompletedHandler<GattCommunicationStatus>>( writeCompletedLambda).Get()); - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not register characteristic write callback", + service, QLowEnergyService::CharacteristicWriteError, return S_OK) return S_OK; }); - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not run registration on Xaml thread", + service, QLowEnergyService::CharacteristicWriteError, return) } void QLowEnergyControllerPrivateWinRTNew::writeDescriptor( @@ -1120,6 +1310,8 @@ void QLowEnergyControllerPrivateWinRTNew::writeDescriptor( const QByteArray &newValue) { qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle << descHandle << newValue; + qCDebug(QT_BT_WINRT_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread" + << QThread::currentThread(); Q_ASSERT(!service.isNull()); if (role == QLowEnergyController::PeripheralRole) { service->setError(QLowEnergyService::DescriptorWriteError); @@ -1174,8 +1366,10 @@ void QLowEnergyControllerPrivateWinRTNew::writeDescriptor( } ComPtr<IAsyncOperation<enum GattCommunicationStatus>> writeOp; HRESULT hr = characteristic->WriteClientCharacteristicConfigurationDescriptorAsync(value, &writeOp); - Q_ASSERT_SUCCEEDED(hr); - auto writeCompletedLambda = [charHandle, descHandle, newValue, service, this] + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not write client characteristic configuration", + service, QLowEnergyService::DescriptorWriteError, return S_OK) + QPointer<QLowEnergyControllerPrivateWinRTNew> thisPtr(this); + auto writeCompletedLambda = [charHandle, descHandle, newValue, service, thisPtr] (IAsyncOperation<GattCommunicationStatus> *op, AsyncStatus status) { if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) { @@ -1186,17 +1380,14 @@ void QLowEnergyControllerPrivateWinRTNew::writeDescriptor( GattCommunicationStatus result; HRESULT hr; hr = op->GetResults(&result); - if (FAILED(hr)) { - qCDebug(QT_BT_WINRT) << "Could not obtain result for descriptor" << descHandle; - service->setError(QLowEnergyService::DescriptorWriteError); - return S_OK; - } + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain result for descriptor", + service, QLowEnergyService::DescriptorWriteError, return S_OK) if (result != GattCommunicationStatus_Success) { - qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "write operation failed"; + qCWarning(QT_BT_WINRT) << "Descriptor" << descHandle << "write operation failed"; service->setError(QLowEnergyService::DescriptorWriteError); return S_OK; } - updateValueOfDescriptor(charHandle, descHandle, newValue, false); + thisPtr->updateValueOfDescriptor(charHandle, descHandle, newValue, false); emit service->descriptorWritten(QLowEnergyDescriptor(service, charHandle, descHandle), newValue); return S_OK; @@ -1204,36 +1395,75 @@ void QLowEnergyControllerPrivateWinRTNew::writeDescriptor( hr = writeOp->put_Completed( Callback<IAsyncOperationCompletedHandler<GattCommunicationStatus >>( writeCompletedLambda).Get()); - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not register descriptor write callback", + service, QLowEnergyService::DescriptorWriteError, return S_OK) } else { + ComPtr<IGattCharacteristic3> characteristic3; + HRESULT hr = characteristic.As(&characteristic3); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not cast characteristic", + service, QLowEnergyService::DescriptorWriteError, return S_OK) + ComPtr<IAsyncOperation<GattDescriptorsResult *>> op; + hr = characteristic3->GetDescriptorsForUuidAsync(descData.uuid, &op); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain descriptor from Uuid", + service, QLowEnergyService::DescriptorWriteError, return S_OK) + ComPtr<IGattDescriptorsResult> result; + hr = QWinRTFunctions::await(op, result.GetAddressOf(), QWinRTFunctions::ProcessMainThreadEvents, 5000); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not await descriptor operation", + service, QLowEnergyService::DescriptorWriteError, return S_OK) + GattCommunicationStatus commStatus; + hr = result->get_Status(&commStatus); + if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) { + qCWarning(QT_BT_WINRT) << "Descriptor operation failed"; + service->setError(QLowEnergyService::DescriptorWriteError); + return S_OK; + } ComPtr<IVectorView<GattDescriptor *>> descriptors; - HRESULT hr = characteristic->GetDescriptors(descData.uuid, &descriptors); - Q_ASSERT_SUCCEEDED(hr); + hr = result->get_Descriptors(&descriptors); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain list of descriptors", + service, QLowEnergyService::DescriptorWriteError, return S_OK) + uint size; + hr = descriptors->get_Size(&size); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain list of descriptors' size", + service, QLowEnergyService::DescriptorWriteError, return S_OK) + if (size == 0) { + qCWarning(QT_BT_WINRT) << "No descriptor with uuid" << descData.uuid << "was found."; + return S_OK; + } else if (size > 1) { + qCWarning(QT_BT_WINRT) << "There is more than 1 descriptor with uuid" << descData.uuid; + } ComPtr<IGattDescriptor> descriptor; hr = descriptors->GetAt(0, &descriptor); - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain descriptor", + service, QLowEnergyService::DescriptorWriteError, return S_OK) ComPtr<ABI::Windows::Storage::Streams::IBufferFactory> bufferFactory; hr = GetActivationFactory( HStringReference(RuntimeClass_Windows_Storage_Streams_Buffer).Get(), &bufferFactory); - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain buffer factory", + service, QLowEnergyService::DescriptorWriteError, return S_OK) ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer; const quint32 length = quint32(newValue.length()); hr = bufferFactory->Create(length, &buffer); - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not create buffer", + service, QLowEnergyService::DescriptorWriteError, return S_OK) hr = buffer->put_Length(length); - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not set buffer length", + service, QLowEnergyService::DescriptorWriteError, return S_OK) ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteAccess; hr = buffer.As(&byteAccess); - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not cast buffer", + service, QLowEnergyService::DescriptorWriteError, return S_OK) byte *bytes; hr = byteAccess->Buffer(&bytes); - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not set buffer", + service, QLowEnergyService::DescriptorWriteError, return S_OK) memcpy(bytes, newValue, length); ComPtr<IAsyncOperation<GattCommunicationStatus>> writeOp; hr = descriptor->WriteValueAsync(buffer.Get(), &writeOp); - Q_ASSERT_SUCCEEDED(hr); - auto writeCompletedLambda = [charHandle, descHandle, newValue, service, this] + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not write descriptor value", + service, QLowEnergyService::DescriptorWriteError, return S_OK) + QPointer<QLowEnergyControllerPrivateWinRTNew> thisPtr(this); + auto writeCompletedLambda = [charHandle, descHandle, newValue, service, thisPtr] (IAsyncOperation<GattCommunicationStatus> *op, AsyncStatus status) { if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) { @@ -1244,17 +1474,14 @@ void QLowEnergyControllerPrivateWinRTNew::writeDescriptor( GattCommunicationStatus result; HRESULT hr; hr = op->GetResults(&result); - if (FAILED(hr)) { - qCDebug(QT_BT_WINRT) << "Could not obtain result for descriptor" << descHandle; - service->setError(QLowEnergyService::DescriptorWriteError); - return S_OK; - } + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain result for descriptor", + service, QLowEnergyService::DescriptorWriteError, return S_OK) if (result != GattCommunicationStatus_Success) { qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "write operation failed"; service->setError(QLowEnergyService::DescriptorWriteError); return S_OK; } - updateValueOfDescriptor(charHandle, descHandle, newValue, false); + thisPtr->updateValueOfDescriptor(charHandle, descHandle, newValue, false); emit service->descriptorWritten(QLowEnergyDescriptor(service, charHandle, descHandle), newValue); return S_OK; @@ -1262,12 +1489,14 @@ void QLowEnergyControllerPrivateWinRTNew::writeDescriptor( hr = writeOp->put_Completed( Callback<IAsyncOperationCompletedHandler<GattCommunicationStatus>>( writeCompletedLambda).Get()); - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not register descriptor write callback", + service, QLowEnergyService::DescriptorWriteError, return S_OK) return S_OK; } return S_OK; }); - Q_ASSERT_SUCCEEDED(hr); + CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not run registration on Xaml thread", + service, QLowEnergyService::DescriptorWriteError, return) } @@ -1277,9 +1506,12 @@ void QLowEnergyControllerPrivateWinRTNew::addToGenericAttributeList(const QLowEn Q_UNIMPLEMENTED(); } -void QLowEnergyControllerPrivateWinRTNew::characteristicChanged( +void QLowEnergyControllerPrivateWinRTNew::handleCharacteristicChanged( quint16 charHandle, const QByteArray &data) { + qCDebug(QT_BT_WINRT) << __FUNCTION__ << charHandle << data; + qCDebug(QT_BT_WINRT_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread" + << QThread::currentThread(); QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle); if (service.isNull()) @@ -1302,6 +1534,170 @@ void QLowEnergyControllerPrivateWinRTNew::characteristicChanged( emit service->characteristicChanged(characteristic, data); } +void QLowEnergyControllerPrivateWinRTNew::handleServiceHandlerError(const QString &error) +{ + if (state != QLowEnergyController::DiscoveringState) + return; + + qCWarning(QT_BT_WINRT) << "Error while discovering services:" << error; + setState(QLowEnergyController::UnconnectedState); + setError(QLowEnergyController::ConnectionError); +} + +void QLowEnergyControllerPrivateWinRTNew::connectToPairedDevice() +{ + Q_Q(QLowEnergyController); + ComPtr<IBluetoothLEDevice3> device3; + HRESULT hr = mDevice.As(&device3); + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not cast device", return) + ComPtr<IAsyncOperation<GattDeviceServicesResult *>> deviceServicesOp; + while (!mAbortPending) { + hr = device3->GetGattServicesAsync(&deviceServicesOp); + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain services", return) + ComPtr<IGattDeviceServicesResult> deviceServicesResult; + hr = QWinRTFunctions::await(deviceServicesOp, deviceServicesResult.GetAddressOf(), + QWinRTFunctions::ProcessThreadEvents, 5000); + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not await services operation", return) + + GattCommunicationStatus commStatus; + hr = deviceServicesResult->get_Status(&commStatus); + if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) { + qCWarning(QT_BT_WINRT()) << "Service operation failed"; + setError(QLowEnergyController::ConnectionError); + setState(QLowEnergyController::UnconnectedState); + unregisterFromStatusChanges(); + return; + } + + ComPtr<IVectorView <GattDeviceService *>> deviceServices; + hr = deviceServicesResult->get_Services(&deviceServices); + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain list of services", return) + uint serviceCount; + hr = deviceServices->get_Size(&serviceCount); + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain service count", return) + + if (serviceCount == 0) { + qCWarning(QT_BT_WINRT()) << "Found devices without services"; + setError(QLowEnergyController::ConnectionError); + setState(QLowEnergyController::UnconnectedState); + unregisterFromStatusChanges(); + return; + } + + // Windows automatically connects to the device as soon as a service value is read/written. + // Thus we read one value in order to establish the connection. + for (uint i = 0; i < serviceCount; ++i) { + ComPtr<IGattDeviceService> service; + hr = deviceServices->GetAt(i, &service); + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain service", return); + ComPtr<IGattDeviceService3> service3; + hr = service.As(&service3); + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not cast service", return); + ComPtr<IAsyncOperation<GattCharacteristicsResult *>> characteristicsOp; + hr = service3->GetCharacteristicsAsync(&characteristicsOp); + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain characteristic", return); + ComPtr<IGattCharacteristicsResult> characteristicsResult; + hr = QWinRTFunctions::await(characteristicsOp, characteristicsResult.GetAddressOf(), + QWinRTFunctions::ProcessThreadEvents, 5000); + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not await characteristic operation", return); + GattCommunicationStatus commStatus; + hr = characteristicsResult->get_Status(&commStatus); + if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) { + qCWarning(QT_BT_WINRT) << "Characteristic operation failed"; + break; + } + ComPtr<IVectorView<GattCharacteristic *>> characteristics; + hr = characteristicsResult->get_Characteristics(&characteristics); + if (hr == E_ACCESSDENIED) { + // Everything will work as expected up until this point if the manifest capabilties + // for bluetooth LE are not set. + qCWarning(QT_BT_WINRT) << "Could not obtain characteristic list. Please check your " + "manifest capabilities"; + setState(QLowEnergyController::UnconnectedState); + setError(QLowEnergyController::ConnectionError); + unregisterFromStatusChanges(); + return; + } + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain characteristic list", return); + uint characteristicsCount; + hr = characteristics->get_Size(&characteristicsCount); + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain characteristic list's size", return); + for (uint j = 0; j < characteristicsCount; ++j) { + ComPtr<IGattCharacteristic> characteristic; + hr = characteristics->GetAt(j, &characteristic); + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain characteristic", return); + ComPtr<IAsyncOperation<GattReadResult *>> op; + GattCharacteristicProperties props; + hr = characteristic->get_CharacteristicProperties(&props); + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain characteristic's properties", return); + if (!(props & GattCharacteristicProperties_Read)) + continue; + hr = characteristic->ReadValueWithCacheModeAsync(BluetoothCacheMode::BluetoothCacheMode_Uncached, &op); + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not read characteristic value", return); + ComPtr<IGattReadResult> result; + hr = QWinRTFunctions::await(op, result.GetAddressOf(), QWinRTFunctions::ProcessThreadEvents, 500); + // E_ILLEGAL_METHOD_CALL will be the result for a device, that is not reachable at + // the moment. In this case we should jump back into the outer loop and keep trying. + if (hr == E_ILLEGAL_METHOD_CALL) + break; + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not await characteristic read", return); + ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer; + hr = result->get_Value(&buffer); + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain characteristic value", return); + if (!buffer) { + qCDebug(QT_BT_WINRT) << "Problem reading value"; + break; + } + + setState(QLowEnergyController::ConnectedState); + emit q->connected(); + if (!registerForStatusChanges()) { + setError(QLowEnergyController::ConnectionError); + setState(QLowEnergyController::UnconnectedState); + return; + } + return; + } + } + } +} + +void QLowEnergyControllerPrivateWinRTNew::connectToUnpairedDevice() +{ + if (!registerForStatusChanges()) { + setError(QLowEnergyController::ConnectionError); + setState(QLowEnergyController::UnconnectedState); + return; + } + ComPtr<IBluetoothLEDevice3> device3; + HRESULT hr = mDevice.As(&device3); + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not cast device", return) + ComPtr<IGattDeviceServicesResult> deviceServicesResult; + while (!mAbortPending) { + ComPtr<IAsyncOperation<GattDeviceServicesResult *>> deviceServicesOp; + hr = device3->GetGattServicesAsync(&deviceServicesOp); + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain services", return) + hr = QWinRTFunctions::await(deviceServicesOp, deviceServicesResult.GetAddressOf(), + QWinRTFunctions::ProcessMainThreadEvents); + CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not await services operation", return) + + GattCommunicationStatus commStatus; + hr = deviceServicesResult->get_Status(&commStatus); + if (commStatus == GattCommunicationStatus_Unreachable) + continue; + + if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) { + qCWarning(QT_BT_WINRT()) << "Service operation failed"; + setError(QLowEnergyController::ConnectionError); + setState(QLowEnergyController::UnconnectedState); + unregisterFromStatusChanges(); + return; + } + + break; + } +} + QT_END_NAMESPACE #include "qlowenergycontroller_winrt_new.moc" diff --git a/src/bluetooth/qlowenergycontroller_winrt_new_p.h b/src/bluetooth/qlowenergycontroller_winrt_new_p.h index 716d2d07..c31408be 100644 --- a/src/bluetooth/qlowenergycontroller_winrt_new_p.h +++ b/src/bluetooth/qlowenergycontroller_winrt_new_p.h @@ -60,8 +60,22 @@ #include "qlowenergycontroller.h" #include "qlowenergycontrollerbase_p.h" +namespace ABI { + namespace Windows { + namespace Devices { + namespace Bluetooth { + struct IBluetoothLEDevice; + } + } + namespace Foundation { + template <typename T> struct IAsyncOperation; + enum class AsyncStatus; + } + } +} + #include <wrl.h> -#include <windows.devices.bluetooth.h> +#include <windows.devices.bluetooth.genericattributeprofile.h> #include <functional> @@ -116,10 +130,18 @@ public: void addToGenericAttributeList(const QLowEnergyServiceData &service, QLowEnergyHandle startHandle) override; -private slots: +signals: void characteristicChanged(quint16 charHandle, const QByteArray &data); +private slots: + void handleCharacteristicChanged(quint16 charHandle, const QByteArray &data); + void handleServiceHandlerError(const QString &error); + private: + void connectToPairedDevice(); + void connectToUnpairedDevice(); + + bool mAbortPending = false; Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice> mDevice; EventRegistrationToken mStatusChangedToken; struct ValueChangedEntry { @@ -140,10 +162,18 @@ private: Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattCharacteristic> getNativeCharacteristic(const QBluetoothUuid &serviceUuid, const QBluetoothUuid &charUuid); void registerForValueChanges(const QBluetoothUuid &serviceUuid, const QBluetoothUuid &charUuid); + void unregisterFromValueChanges(); + HRESULT onValueChange(ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattCharacteristic *characteristic, + ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattValueChangedEventArgs *args); + + bool registerForStatusChanges(); + void unregisterFromStatusChanges(); + HRESULT onStatusChange(ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice *dev, IInspectable *); void obtainIncludedServices(QSharedPointer<QLowEnergyServicePrivate> servicePointer, Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattDeviceService> nativeService); - + HRESULT onServiceDiscoveryFinished(ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::GattDeviceServicesResult *> *op, + ABI::Windows::Foundation::AsyncStatus status); }; QT_END_NAMESPACE diff --git a/src/bluetooth/qlowenergycontroller_winrt_p.h b/src/bluetooth/qlowenergycontroller_winrt_p.h index 783a71fa..fedc52d9 100644 --- a/src/bluetooth/qlowenergycontroller_winrt_p.h +++ b/src/bluetooth/qlowenergycontroller_winrt_p.h @@ -114,8 +114,11 @@ public: void addToGenericAttributeList(const QLowEnergyServiceData &service, QLowEnergyHandle startHandle) override; +signals: + void characteristicChanged(quint16 charHandle, const QByteArray &data); + private slots: - void characteristicChanged(int charHandle, const QByteArray &data); + void handleCharacteristicChanged(quint16 charHandle, const QByteArray &data); private: Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice> mDevice; @@ -138,6 +141,7 @@ private: Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattCharacteristic> getNativeCharacteristic(const QBluetoothUuid &serviceUuid, const QBluetoothUuid &charUuid); void registerForValueChanges(const QBluetoothUuid &serviceUuid, const QBluetoothUuid &charUuid); + void unregisterFromValueChanges(); void obtainIncludedServices(QSharedPointer<QLowEnergyServicePrivate> servicePointer, Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattDeviceService> nativeService); diff --git a/src/bluetooth/qlowenergycontrollerbase.cpp b/src/bluetooth/qlowenergycontrollerbase.cpp index 86108648..059bd41b 100644 --- a/src/bluetooth/qlowenergycontrollerbase.cpp +++ b/src/bluetooth/qlowenergycontrollerbase.cpp @@ -61,7 +61,7 @@ QLowEnergyControllerPrivate::~QLowEnergyControllerPrivate() bool QLowEnergyControllerPrivate::isValidLocalAdapter() { -#ifdef QT_WINRT_BLUETOOTH +#if defined(QT_WINRT_BLUETOOTH) || defined(Q_OS_DARWIN) return true; #endif if (localAdapter.isNull()) @@ -106,6 +106,9 @@ void QLowEnergyControllerPrivate::setError( case QLowEnergyController::RemoteHostClosedError: errorString = QLowEnergyController::tr("Remote device closed the connection"); break; + case QLowEnergyController::AuthorizationError: + errorString = QLowEnergyController::tr("Failed to authorize on the remote device"); + break; case QLowEnergyController::NoError: return; default: diff --git a/src/bluetooth/qlowenergycontrollerbase_p.h b/src/bluetooth/qlowenergycontrollerbase_p.h index a8d1c676..169ba07b 100644 --- a/src/bluetooth/qlowenergycontrollerbase_p.h +++ b/src/bluetooth/qlowenergycontrollerbase_p.h @@ -51,24 +51,6 @@ // We mean it. // -#if defined(QT_OSX_BLUETOOTH) || defined(QT_IOS_BLUETOOTH) - -#include <QtCore/qglobal.h> -#include <QtCore/qobject.h> - -QT_BEGIN_NAMESPACE - -class QLowEnergyControllerPrivate : public QObject -{ -public: - // This class is required to make shared pointer machinery and - // moc (== Obj-C syntax) happy on both OS X and iOS. -}; - -QT_END_NAMESPACE - -#else - #include <qglobal.h> #include <QtCore/qobject.h> @@ -135,7 +117,6 @@ public: virtual QLowEnergyService *addServiceHelper( const QLowEnergyServiceData &service); - // common backend methods bool isValidLocalAdapter(); void setError(QLowEnergyController::Error newError); @@ -174,6 +155,7 @@ protected: QLowEnergyHandle lastLocalHandle{}; QString remoteName; // device name of the remote + QBluetoothUuid deviceUuid; // quite useless anywhere but Darwin (CoreBluetooth). Q_DECLARE_PUBLIC(QLowEnergyController) QLowEnergyController *q_ptr; @@ -181,6 +163,4 @@ protected: QT_END_NAMESPACE -#endif //defined(QT_OSX_BLUETOOTH) || defined(QT_IOS_BLUETOOTH) - #endif // QLOWENERGYCONTROLLERPRIVATEBASE_P_H diff --git a/src/bluetooth/qlowenergydescriptor.h b/src/bluetooth/qlowenergydescriptor.h index adfe1203..18bb53c0 100644 --- a/src/bluetooth/qlowenergydescriptor.h +++ b/src/bluetooth/qlowenergydescriptor.h @@ -83,8 +83,8 @@ protected: friend class QLowEnergyControllerPrivateBluez; friend class QLowEnergyControllerPrivateBluezDBus; friend class QLowEnergyControllerPrivateCommon; - friend class QLowEnergyControllerPrivateOSX; friend class QLowEnergyControllerPrivateWin32; + friend class QLowEnergyControllerPrivateDarwin; friend class QLowEnergyControllerPrivateWinRT; friend class QLowEnergyControllerPrivateWinRTNew; QLowEnergyDescriptorPrivate *data = nullptr; diff --git a/src/bluetooth/qlowenergyservice.cpp b/src/bluetooth/qlowenergyservice.cpp index 1529d3c2..2e6d1f9b 100644 --- a/src/bluetooth/qlowenergyservice.cpp +++ b/src/bluetooth/qlowenergyservice.cpp @@ -47,6 +47,10 @@ #include "qlowenergycontrollerbase_p.h" #include "qlowenergyserviceprivate_p.h" +#ifdef Q_OS_DARWIN +#include "qlowenergycontroller_darwin_p.h" +#endif // Q_OS_DARWIN + QT_BEGIN_NAMESPACE /*! @@ -809,6 +813,21 @@ void QLowEnergyService::writeDescriptor(const QLowEnergyDescriptor &descriptor, d->setError(QLowEnergyService::OperationError); return; } +#ifdef Q_OS_DARWIN + if (descriptor.uuid() == QBluetoothUuid::ClientCharacteristicConfiguration) { + // We have to identify a special case - ClientCharacteristicConfiguration + // since with CoreBluetooth: + // + // "You cannot use this method to write the value of a client configuration descriptor + // (represented by the CBUUIDClientCharacteristicConfigurationString constant), + // which describes how notification or indications are configured for a + // characteristic’s value with respect to a client. If you want to manage + // notifications or indications for a characteristic’s value, you must + // use the setNotifyValue:forCharacteristic: method instead." + auto controller = static_cast<QLowEnergyControllerPrivateDarwin *>(d->controller.data()); + return controller->setNotifyValue(descriptor.d_ptr, descriptor.characteristicHandle(), newValue); + } +#endif // Q_OS_DARWIN d->controller->writeDescriptor(descriptor.d_ptr, descriptor.characteristicHandle(), diff --git a/src/bluetooth/qlowenergyservice.h b/src/bluetooth/qlowenergyservice.h index 9de65a84..a2715471 100644 --- a/src/bluetooth/qlowenergyservice.h +++ b/src/bluetooth/qlowenergyservice.h @@ -136,6 +136,7 @@ private: friend class QLowEnergyControllerPrivate; friend class QLowEnergyControllerPrivateBluez; friend class QLowEnergyControllerPrivateAndroid; + friend class QLowEnergyControllerPrivateDarwin; QLowEnergyService(QSharedPointer<QLowEnergyServicePrivate> p, QObject *parent = nullptr); }; diff --git a/src/bluetooth/qlowenergyservice_osx.mm b/src/bluetooth/qlowenergyservice_osx.mm deleted file mode 100644 index c294b693..00000000 --- a/src/bluetooth/qlowenergyservice_osx.mm +++ /dev/null @@ -1,277 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2016 Javier S. Pedro <maemo@javispedro.com> -** 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 "qlowenergycontroller_osx_p.h" -#include "qlowenergyserviceprivate_p.h" -#include "qlowenergycharacteristic.h" -#include "qlowenergydescriptor.h" -#include "qlowenergyservice.h" -#include "qbluetoothuuid.h" - -#include <QtCore/qcoreapplication.h> -#include <QtCore/qstring.h> -#include <QtCore/qlist.h> - -#include <algorithm> - -QT_BEGIN_NAMESPACE - -namespace { - -QLowEnergyControllerPrivateOSX *qt_mac_le_controller(QSharedPointer<QLowEnergyServicePrivate> d_ptr) -{ - if (d_ptr.isNull()) - return nullptr; - - return static_cast<QLowEnergyControllerPrivateOSX *>(d_ptr->controller.data()); -} - -} - -QLowEnergyService::QLowEnergyService(QSharedPointer<QLowEnergyServicePrivate> d, QObject *parent) - : QObject(parent), - d_ptr(d) -{ - qRegisterMetaType<QLowEnergyService::ServiceState>(); - qRegisterMetaType<QLowEnergyService::ServiceError>(); - - connect(d.data(), SIGNAL(error(QLowEnergyService::ServiceError)), - this, SIGNAL(error(QLowEnergyService::ServiceError))); - connect(d.data(), SIGNAL(stateChanged(QLowEnergyService::ServiceState)), - this, SIGNAL(stateChanged(QLowEnergyService::ServiceState))); - connect(d.data(), SIGNAL(characteristicChanged(QLowEnergyCharacteristic, QByteArray)), - this, SIGNAL(characteristicChanged(QLowEnergyCharacteristic, QByteArray))); - connect(d.data(), SIGNAL(characteristicWritten(QLowEnergyCharacteristic, QByteArray)), - this, SIGNAL(characteristicWritten(QLowEnergyCharacteristic, QByteArray))); - connect(d.data(), SIGNAL(descriptorWritten(QLowEnergyDescriptor, QByteArray)), - this, SIGNAL(descriptorWritten(QLowEnergyDescriptor, QByteArray))); - connect(d.data(), SIGNAL(characteristicRead(QLowEnergyCharacteristic,QByteArray)), - this, SIGNAL(characteristicRead(QLowEnergyCharacteristic,QByteArray))); - connect(d.data(), SIGNAL(descriptorRead(QLowEnergyDescriptor,QByteArray)), - this, SIGNAL(descriptorRead(QLowEnergyDescriptor,QByteArray))); - -} - -QLowEnergyService::~QLowEnergyService() -{ -} - -QList<QBluetoothUuid> QLowEnergyService::includedServices() const -{ - return d_ptr->includedServices; -} - -QLowEnergyService::ServiceTypes QLowEnergyService::type() const -{ - return d_ptr->type; -} - -QLowEnergyService::ServiceState QLowEnergyService::state() const -{ - return d_ptr->state; -} - -QLowEnergyCharacteristic QLowEnergyService::characteristic(const QBluetoothUuid &uuid) const -{ - CharacteristicDataMap::const_iterator charIt = d_ptr->characteristicList.constBegin(); - for ( ; charIt != d_ptr->characteristicList.constEnd(); ++charIt) { - const QLowEnergyHandle charHandle = charIt.key(); - const QLowEnergyServicePrivate::CharData &charDetails = charIt.value(); - - if (charDetails.uuid == uuid) - return QLowEnergyCharacteristic(d_ptr, charHandle); - } - - return QLowEnergyCharacteristic(); -} - -QList<QLowEnergyCharacteristic> QLowEnergyService::characteristics() const -{ - QList<QLowEnergyCharacteristic> result; - QList<QLowEnergyHandle> handles(d_ptr->characteristicList.keys()); - - std::sort(handles.begin(), handles.end()); - - for (const QLowEnergyHandle &handle : qAsConst(handles)) { - QLowEnergyCharacteristic characteristic(d_ptr, handle); - result.append(characteristic); - } - - return result; -} - -QBluetoothUuid QLowEnergyService::serviceUuid() const -{ - return d_ptr->uuid; -} - -QString QLowEnergyService::serviceName() const -{ - bool ok = false; - const quint16 clsId = d_ptr->uuid.toUInt16(&ok); - if (ok) { - QBluetoothUuid::ServiceClassUuid uuid - = static_cast<QBluetoothUuid::ServiceClassUuid>(clsId); - const QString name = QBluetoothUuid::serviceClassToString(uuid); - if (!name.isEmpty()) - return name; - } - - return qApp ? qApp->translate("QBluetoothServiceDiscoveryAgent", "Unknown Service") : - QStringLiteral("Unknown Service"); -} - -void QLowEnergyService::discoverDetails() -{ - QLowEnergyControllerPrivateOSX *const controller = qt_mac_le_controller(d_ptr); - - if (!controller || d_ptr->state == InvalidService) { - d_ptr->setError(OperationError); - return; - } - - if (d_ptr->state != DiscoveryRequired) - return; - - d_ptr->setState(QLowEnergyService::DiscoveringServices); - controller->discoverServiceDetails(d_ptr->uuid); -} - -QLowEnergyService::ServiceError QLowEnergyService::error() const -{ - return d_ptr->lastError; -} - -bool QLowEnergyService::contains(const QLowEnergyCharacteristic &characteristic) const -{ - if (characteristic.d_ptr.isNull() || !characteristic.data) - return false; - - if (d_ptr == characteristic.d_ptr - && d_ptr->characteristicList.contains(characteristic.attributeHandle())) { - return true; - } - - return false; -} - -void QLowEnergyService::readCharacteristic(const QLowEnergyCharacteristic &characteristic) -{ - QLowEnergyControllerPrivateOSX *const controller = qt_mac_le_controller(d_ptr); - if (controller == nullptr || state() != ServiceDiscovered || !contains(characteristic)) { - d_ptr->setError(OperationError); - return; - } - - controller->readCharacteristic(characteristic.d_ptr, characteristic.attributeHandle()); -} - - -void QLowEnergyService::writeCharacteristic(const QLowEnergyCharacteristic &ch, const QByteArray &newValue, - WriteMode mode) -{ - QLowEnergyControllerPrivateOSX *const controller = qt_mac_le_controller(d_ptr); - if (controller == nullptr || - (controller->role == QLowEnergyController::CentralRole && state() != ServiceDiscovered) || - !contains(ch)) { - d_ptr->setError(QLowEnergyService::OperationError); - return; - } - - controller->writeCharacteristic(ch.d_ptr, ch.attributeHandle(), newValue, mode); -} - -bool QLowEnergyService::contains(const QLowEnergyDescriptor &descriptor) const -{ - if (descriptor.d_ptr.isNull() || !descriptor.data) - return false; - - const QLowEnergyHandle charHandle = descriptor.characteristicHandle(); - if (!charHandle) - return false; - - if (d_ptr == descriptor.d_ptr && d_ptr->characteristicList.contains(charHandle) - && d_ptr->characteristicList[charHandle].descriptorList.contains(descriptor.handle())) - { - return true; - } - - return false; -} - -void QLowEnergyService::readDescriptor(const QLowEnergyDescriptor &descriptor) -{ - QLowEnergyControllerPrivateOSX *const controller = qt_mac_le_controller(d_ptr); - if (controller == nullptr || state() != ServiceDiscovered || !contains(descriptor)) { - d_ptr->setError(OperationError); - return; - } - - controller->readDescriptor(descriptor.d_ptr, descriptor.handle()); -} - -void QLowEnergyService::writeDescriptor(const QLowEnergyDescriptor &descriptor, - const QByteArray &newValue) -{ - QLowEnergyControllerPrivateOSX *const controller = qt_mac_le_controller(d_ptr); - if (controller == nullptr || state() != ServiceDiscovered || !contains(descriptor)) { - // This operation error also includes LE controller in the peripheral role: - // on iOS/OS X - descriptors are immutable. - d_ptr->setError(OperationError); - return; - } - - if (descriptor.uuid() == QBluetoothUuid::ClientCharacteristicConfiguration) { - // We have to identify a special case - ClientCharacteristicConfiguration - // since with Core Bluetooth: - // - // "You cannot use this method to write the value of a client configuration descriptor - // (represented by the CBUUIDClientCharacteristicConfigurationString constant), - // which describes how notification or indications are configured for a - // characteristic’s value with respect to a client. If you want to manage - // notifications or indications for a characteristic’s value, you must - // use the setNotifyValue:forCharacteristic: method instead." - controller->setNotifyValue(descriptor.d_ptr, descriptor.characteristicHandle(), newValue); - } else { - controller->writeDescriptor(descriptor.d_ptr, descriptor.handle(), newValue); - } -} - -QT_END_NAMESPACE diff --git a/src/imports/bluetooth/plugins.qmltypes b/src/imports/bluetooth/plugins.qmltypes index 163d8413..9318fe93 100644 --- a/src/imports/bluetooth/plugins.qmltypes +++ b/src/imports/bluetooth/plugins.qmltypes @@ -4,10 +4,10 @@ import QtQuick.tooling 1.2 // It is used for QML tooling purposes only. // // This file was auto-generated by: -// 'qmlplugindump -nonrelocatable QtBluetooth 5.12' +// 'qmlplugindump -nonrelocatable QtBluetooth 5.13' Module { - dependencies: ["QtQuick 2.12"] + dependencies: ["QtQuick 2.0"] Component { name: "QDeclarativeBluetoothDiscoveryModel" prototype: "QAbstractListModel" diff --git a/src/imports/bluetooth/qdeclarativebluetoothdiscoverymodel.cpp b/src/imports/bluetooth/qdeclarativebluetoothdiscoverymodel.cpp index 6b2f32f3..6213355e 100644 --- a/src/imports/bluetooth/qdeclarativebluetoothdiscoverymodel.cpp +++ b/src/imports/bluetooth/qdeclarativebluetoothdiscoverymodel.cpp @@ -165,14 +165,6 @@ QDeclarativeBluetoothDiscoveryModel::QDeclarativeBluetoothDiscoveryModel(QObject this, &QDeclarativeBluetoothDiscoveryModel::errorDiscovery); d->m_serviceAgent->setObjectName(QStringLiteral("ServiceDiscoveryAgent")); - - QHash<int, QByteArray> roleNames; - roleNames = QAbstractItemModel::roleNames(); - roleNames.insert(Name, "name"); - roleNames.insert(ServiceRole, "service"); - roleNames.insert(RemoteAddress, "remoteAddress"); - roleNames.insert(DeviceName, "deviceName"); - setRoleNames(roleNames); } QDeclarativeBluetoothDiscoveryModel::~QDeclarativeBluetoothDiscoveryModel() @@ -314,6 +306,14 @@ QVariant QDeclarativeBluetoothDiscoveryModel::data(const QModelIndex &index, int return QVariant(); } +QHash<int,QByteArray> QDeclarativeBluetoothDiscoveryModel::roleNames() const +{ + return {{Name, "name"}, + {ServiceRole, "service"}, + {RemoteAddress, "remoteAddress"}, + {DeviceName, "deviceName"}}; +} + /*! \qmlsignal BluetoothDiscoveryModel::serviceDiscovered(BluetoothService service) diff --git a/src/imports/bluetooth/qdeclarativebluetoothdiscoverymodel_p.h b/src/imports/bluetooth/qdeclarativebluetoothdiscoverymodel_p.h index 0aa134f5..6cbde088 100644 --- a/src/imports/bluetooth/qdeclarativebluetoothdiscoverymodel_p.h +++ b/src/imports/bluetooth/qdeclarativebluetoothdiscoverymodel_p.h @@ -106,13 +106,15 @@ public: Error error() const; - void componentComplete(); + void componentComplete() override; - void classBegin() { } + void classBegin() override { } // From QAbstractListModel - int rowCount(const QModelIndex &parent) const; - QVariant data(const QModelIndex &index, int role) const; + int rowCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + + QHash<int,QByteArray> roleNames() const override; DiscoveryMode discoveryMode() const; void setDiscoveryMode(DiscoveryMode discovery); diff --git a/src/imports/nfc/plugins.qmltypes b/src/imports/nfc/plugins.qmltypes index 81ce21d0..d99cac23 100644 --- a/src/imports/nfc/plugins.qmltypes +++ b/src/imports/nfc/plugins.qmltypes @@ -4,10 +4,10 @@ import QtQuick.tooling 1.2 // It is used for QML tooling purposes only. // // This file was auto-generated by: -// 'qmlplugindump -nonrelocatable QtNfc 5.12' +// 'qmlplugindump -nonrelocatable QtNfc 5.13' Module { - dependencies: ["QtQuick 2.12"] + dependencies: ["QtQuick 2.0"] Component { name: "QDeclarativeNdefFilter" prototype: "QObject" diff --git a/src/nfc/doc/qtnfc.qdocconf b/src/nfc/doc/qtnfc.qdocconf index 61ed15b6..e4f10f0e 100644 --- a/src/nfc/doc/qtnfc.qdocconf +++ b/src/nfc/doc/qtnfc.qdocconf @@ -1,4 +1,5 @@ include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf) +include($QT_INSTALL_DOCS/config/exampleurl-qtconnectivity.qdocconf) project = QtNfc description = Qt NFC Reference Documentation diff --git a/src/nfc/qnearfieldmanager.h b/src/nfc/qnearfieldmanager.h index 500b9631..22506e7e 100644 --- a/src/nfc/qnearfieldmanager.h +++ b/src/nfc/qnearfieldmanager.h @@ -99,7 +99,7 @@ public: bool unregisterNdefMessageHandler(int handlerId); Q_SIGNALS: - void adapterStateChanged(AdapterState state); + void adapterStateChanged(QNearFieldManager::AdapterState state); void targetDetected(QNearFieldTarget *target); void targetLost(QNearFieldTarget *target); diff --git a/src/nfc/qnearfieldmanager_emulator.cpp b/src/nfc/qnearfieldmanager_emulator.cpp index 8a61a3a9..4b5e5e0c 100644 --- a/src/nfc/qnearfieldmanager_emulator.cpp +++ b/src/nfc/qnearfieldmanager_emulator.cpp @@ -49,10 +49,11 @@ QT_BEGIN_NAMESPACE QNearFieldManagerPrivateImpl::QNearFieldManagerPrivateImpl() { - globalTagActivator->initialize(); + TagActivator *activator = TagActivator::instance(); + activator->initialize(); - connect(globalTagActivator, &TagActivator::tagActivated, this, &QNearFieldManagerPrivateImpl::tagActivated); - connect(globalTagActivator, &TagActivator::tagDeactivated, this, &QNearFieldManagerPrivateImpl::tagDeactivated); + connect(activator, &TagActivator::tagActivated, this, &QNearFieldManagerPrivateImpl::tagActivated); + connect(activator, &TagActivator::tagDeactivated, this, &QNearFieldManagerPrivateImpl::tagDeactivated); } QNearFieldManagerPrivateImpl::~QNearFieldManagerPrivateImpl() @@ -66,7 +67,7 @@ bool QNearFieldManagerPrivateImpl::isAvailable() const void QNearFieldManagerPrivateImpl::reset() { - globalTagActivator->reset(); + TagActivator::instance()->reset(); } void QNearFieldManagerPrivateImpl::tagActivated(TagBase *tag) diff --git a/src/nfc/qnearfieldtagtype2.cpp b/src/nfc/qnearfieldtagtype2.cpp index 24ff8280..492dc5e3 100644 --- a/src/nfc/qnearfieldtagtype2.cpp +++ b/src/nfc/qnearfieldtagtype2.cpp @@ -307,9 +307,7 @@ void QNearFieldTagType2::timerEvent(QTimerEvent *event) killTimer(event->timerId()); - QMutableMapIterator<QNearFieldTarget::RequestId, SectorSelectState> i(d->m_pendingSectorSelectCommands); - while (i.hasNext()) { - i.next(); + for (auto i = d->m_pendingSectorSelectCommands.begin(), end = d->m_pendingSectorSelectCommands.end(); i != end; ++i) { SectorSelectState &state = i.value(); @@ -318,8 +316,7 @@ void QNearFieldTagType2::timerEvent(QTimerEvent *event) setResponseForRequest(i.key(), true); - i.remove(); - + d->m_pendingSectorSelectCommands.erase(i); break; } } diff --git a/src/nfc/qnearfieldtarget.cpp b/src/nfc/qnearfieldtarget.cpp index e9a6fa11..7d83db78 100644 --- a/src/nfc/qnearfieldtarget.cpp +++ b/src/nfc/qnearfieldtarget.cpp @@ -47,7 +47,7 @@ #include <QtCore/QDebug> -#include <QTime> +#include <QElapsedTimer> #include <QCoreApplication> QT_BEGIN_NAMESPACE @@ -462,7 +462,7 @@ bool QNearFieldTarget::waitForRequestCompleted(const RequestId &id, int msecs) { Q_D(QNearFieldTarget); - QTime timer; + QElapsedTimer timer; timer.start(); do { @@ -497,13 +497,12 @@ void QNearFieldTarget::setResponseForRequest(const QNearFieldTarget::RequestId & { Q_D(QNearFieldTarget); - QMutableMapIterator<RequestId, QVariant> i(d->m_decodedResponses); - while (i.hasNext()) { - i.next(); - + for (auto i = d->m_decodedResponses.begin(), end = d->m_decodedResponses.end(); i != end; /* erasing */) { // no more external references if (i.key().refCount() == 1) - i.remove(); + i = d->m_decodedResponses.erase(i); + else + ++i; } d->m_decodedResponses.insert(id, response); diff --git a/src/nfc/qnearfieldtarget_emulator.cpp b/src/nfc/qnearfieldtarget_emulator.cpp index 0723b655..aecd743e 100644 --- a/src/nfc/qnearfieldtarget_emulator.cpp +++ b/src/nfc/qnearfieldtarget_emulator.cpp @@ -52,6 +52,8 @@ QT_BEGIN_NAMESPACE static QMutex tagMutex; static QMap<TagBase *, bool> tagMap; +Q_GLOBAL_STATIC(TagActivator, globalTagActivator); + TagType1::TagType1(TagBase *tag, QObject *parent) : QNearFieldTagType1(parent), m_tag(tag) { @@ -247,6 +249,11 @@ void TagActivator::reset() tagMap.clear(); } +TagActivator *TagActivator::instance() +{ + return globalTagActivator(); +} + void TagActivator::timerEvent(QTimerEvent *e) { Q_UNUSED(e); diff --git a/src/nfc/qnearfieldtarget_emulator_p.h b/src/nfc/qnearfieldtarget_emulator_p.h index 1b9f7bdb..70a67be8 100644 --- a/src/nfc/qnearfieldtarget_emulator_p.h +++ b/src/nfc/qnearfieldtarget_emulator_p.h @@ -122,8 +122,6 @@ private: int timerId; }; -Q_GLOBAL_STATIC(TagActivator, globalTagActivator); - QT_END_NAMESPACE #endif // QNEARFIELDTARGET_EMULATOR_P_H diff --git a/src/nfc/qqmlndefrecord.cpp b/src/nfc/qqmlndefrecord.cpp index bc3667fe..5a96bec8 100644 --- a/src/nfc/qqmlndefrecord.cpp +++ b/src/nfc/qqmlndefrecord.cpp @@ -215,10 +215,9 @@ QQmlNdefRecord *qNewDeclarativeNdefRecordForNdefRecord(const QNdefRecord &record { const QString urn = urnForRecordType(record.typeNameFormat(), record.type()); - QMapIterator<QString, const QMetaObject *> i(*registeredNdefRecordTypes()); - while (i.hasNext()) { - i.next(); + const auto *rt = registeredNdefRecordTypes(); + for (auto i = rt->cbegin(), end = rt->cend(); i != end; ++i) { QRegularExpression rx(QRegularExpression::anchoredPattern(i.key())); if (!rx.match(urn).hasMatch()) continue; |